在 GPU 上使用简单的矩阵向量积对 Theano 和 CNTK 进行基准测试
Benchmarking Theano and CNTK with a simple matrix-vector product on the GPU
我想比较 Theano 和 CNTK 在一个非常简单的任务上的性能:GPU 上的矩阵向量乘积。我正在使用 Theano 0.9.0 和 CNTK 2.0.
我只想测量设备上计算所消耗的时间,不包括从主机到设备的数据传输所用的时间,反之亦然。
我得到的结果是这样的:
figure (timings theano vs cntk)
(N为重复次数,矩阵大小D为10000)
问题 1:
在 CNTK 案例中,mat-vec 产品的第一次执行似乎包含了一些准备工作(编译计算图?)所用的时间。
在 CNTK 中有什么方法可以像在 Theano 案例中那样拆分准备和执行吗?
问题 2:
我习惯了Theano,但在CNTK 是全新的,所以我不太确定CNTK 代码是否等同于Theano 代码。
我特别不确定CNTK代码的for循环中的操作是否真的包含在设备中,因为prod.eval() returns a numpy.ndarray。我错过了什么吗?
用于测量计时的代码:
import numpy as np
import time
# theano
def test_matVecDot_theano(D, N):
import theano
import theano.tensor as T
A_cpu = np.random.normal(size=[D,D]).astype(np.float32)
x_cpu = np.random.normal(size=[D]).astype(np.float32)
A_gpu = theano.shared(A_cpu)
x_gpu = theano.shared(x_cpu)
b_gpu = theano.shared(x_cpu)
b_gpu_new = T.dot(A_gpu,x_gpu)
fnc = theano.function(inputs=[], outputs=None, updates=[(b_gpu, b_gpu_new)], allow_input_downcast=True)
tic = time.time()
for i in range(N):
fnc()
toc = time.time()
print("time_theano:",toc-tic)
# cntk
def test_matVecDot_CNTK(D, N):
import cntk as C
A_cpu = np.random.normal(size=[D,D]).astype(np.float32)
x_cpu = np.random.normal(size=[D,1]).astype(np.float32)
A_c = C.Parameter(init=A_cpu, dtype=np.float32)
x_c = C.Parameter(init=x_cpu, dtype=np.float32)
b_c = C.Parameter(init=x_cpu, dtype=np.float32)
prod = C.times(A_c, x_c)
tic = time.time()
for i in range(N):
b_c.value = prod.eval() # is this operation enclosed in the device?
toc = time.time()
print("time_cntk:",toc-tic)
简短的回答是否定的,操作未包含在设备上。下面是发生的情况:当您调用 eval() 时,调用转到 C++,如果可能,它会在设备上执行操作。从 C++ 出来时,CNTK 检查 as_numpy
关键字参数的值是否默认为 True。当 as_numpy
为 True 时,gpu 缓冲区被急切地复制到 NumPy 数组中。
如果您调用 prod.eval(as_numpy=False),那么对 eval
的调用将不会将 gpu 缓冲区转换为 NumPy 数组。如果您将结果分配给一个普通的旧变量,您可以看到您获得了一个 CNTK Value 对象。但是在您的代码中,您分配给 b_c
的 .value
属性。此作业由 value
属性 的 setter 处理(因为这个答案有点过于技术性,为了其他读者,我将 this link 包括在内)。 CNTK 在设备上执行此分配,尽管很难说。这是因为如果您尝试检查 b_c.value
如果您正在调用 .value
属性 getter 这将为您提供一个 NumPy 数组。所以看起来结果是一个 NumPy 数组,但这只是使用 b_c.value
的结果。任何其他变量都会让您看到它是一个 CNTK 值对象。同样,所有这些都适用于您执行 eval(as_numpy=False)
.
时的情况
此外,CNTK 使用时间戳,因此上述评估仅在 GPU 上发生一次。所有后续 N-1
对 eval() 的调用只会 return 相同的值对象(尽管每次都会发生向 Numpy 的转换,除非您指定 as_numpy=False
.
最后,我不希望从这个基准中学到很多有意义的经验教训:CNTK 和 Theano 都调用相同的 CuDNN 实现,CNTK 的优势更多地围绕更高层次的东西,例如 (a) 带有一个高级库 (b) 除了一些专门的操作外,用户不必担心批处理和序列轴 (c) 高效的循环网络 (d) 高效 i/o (e) 易于分布式训练。
然后回答你关于设置时间的问题:我的理解是如果你只评估一次函数,就会编译它。 CNTK 实际上有两种编译方式:如果你只是 eval
第一次,它会编译前向传播。如果您稍后执行 function.grad
,它将丢弃 eval 编译并再次编译它,以便它可以处理前向和后向传递。
我想比较 Theano 和 CNTK 在一个非常简单的任务上的性能:GPU 上的矩阵向量乘积。我正在使用 Theano 0.9.0 和 CNTK 2.0.
我只想测量设备上计算所消耗的时间,不包括从主机到设备的数据传输所用的时间,反之亦然。
我得到的结果是这样的: figure (timings theano vs cntk) (N为重复次数,矩阵大小D为10000)
问题 1:
在 CNTK 案例中,mat-vec 产品的第一次执行似乎包含了一些准备工作(编译计算图?)所用的时间。 在 CNTK 中有什么方法可以像在 Theano 案例中那样拆分准备和执行吗?
问题 2:
我习惯了Theano,但在CNTK 是全新的,所以我不太确定CNTK 代码是否等同于Theano 代码。 我特别不确定CNTK代码的for循环中的操作是否真的包含在设备中,因为prod.eval() returns a numpy.ndarray。我错过了什么吗?
用于测量计时的代码:
import numpy as np
import time
# theano
def test_matVecDot_theano(D, N):
import theano
import theano.tensor as T
A_cpu = np.random.normal(size=[D,D]).astype(np.float32)
x_cpu = np.random.normal(size=[D]).astype(np.float32)
A_gpu = theano.shared(A_cpu)
x_gpu = theano.shared(x_cpu)
b_gpu = theano.shared(x_cpu)
b_gpu_new = T.dot(A_gpu,x_gpu)
fnc = theano.function(inputs=[], outputs=None, updates=[(b_gpu, b_gpu_new)], allow_input_downcast=True)
tic = time.time()
for i in range(N):
fnc()
toc = time.time()
print("time_theano:",toc-tic)
# cntk
def test_matVecDot_CNTK(D, N):
import cntk as C
A_cpu = np.random.normal(size=[D,D]).astype(np.float32)
x_cpu = np.random.normal(size=[D,1]).astype(np.float32)
A_c = C.Parameter(init=A_cpu, dtype=np.float32)
x_c = C.Parameter(init=x_cpu, dtype=np.float32)
b_c = C.Parameter(init=x_cpu, dtype=np.float32)
prod = C.times(A_c, x_c)
tic = time.time()
for i in range(N):
b_c.value = prod.eval() # is this operation enclosed in the device?
toc = time.time()
print("time_cntk:",toc-tic)
简短的回答是否定的,操作未包含在设备上。下面是发生的情况:当您调用 eval() 时,调用转到 C++,如果可能,它会在设备上执行操作。从 C++ 出来时,CNTK 检查 as_numpy
关键字参数的值是否默认为 True。当 as_numpy
为 True 时,gpu 缓冲区被急切地复制到 NumPy 数组中。
如果您调用 prod.eval(as_numpy=False),那么对 eval
的调用将不会将 gpu 缓冲区转换为 NumPy 数组。如果您将结果分配给一个普通的旧变量,您可以看到您获得了一个 CNTK Value 对象。但是在您的代码中,您分配给 b_c
的 .value
属性。此作业由 value
属性 的 setter 处理(因为这个答案有点过于技术性,为了其他读者,我将 this link 包括在内)。 CNTK 在设备上执行此分配,尽管很难说。这是因为如果您尝试检查 b_c.value
如果您正在调用 .value
属性 getter 这将为您提供一个 NumPy 数组。所以看起来结果是一个 NumPy 数组,但这只是使用 b_c.value
的结果。任何其他变量都会让您看到它是一个 CNTK 值对象。同样,所有这些都适用于您执行 eval(as_numpy=False)
.
此外,CNTK 使用时间戳,因此上述评估仅在 GPU 上发生一次。所有后续 N-1
对 eval() 的调用只会 return 相同的值对象(尽管每次都会发生向 Numpy 的转换,除非您指定 as_numpy=False
.
最后,我不希望从这个基准中学到很多有意义的经验教训:CNTK 和 Theano 都调用相同的 CuDNN 实现,CNTK 的优势更多地围绕更高层次的东西,例如 (a) 带有一个高级库 (b) 除了一些专门的操作外,用户不必担心批处理和序列轴 (c) 高效的循环网络 (d) 高效 i/o (e) 易于分布式训练。
然后回答你关于设置时间的问题:我的理解是如果你只评估一次函数,就会编译它。 CNTK 实际上有两种编译方式:如果你只是 eval
第一次,它会编译前向传播。如果您稍后执行 function.grad
,它将丢弃 eval 编译并再次编译它,以便它可以处理前向和后向传递。