我想比较Theano和CNTK在一个非常简单的任务上的性能:GPU上的矩阵向量积。我正在使用 Theano 0.9.0 和 CNTK 2.0。
我只想测量设备上计算所消耗的时间,不包括从主机到设备数据传输所用的时间,反之亦然。
我得到的结果是这样的: 图(时间 Theano vs CNTK( (N 是重复次数。D(矩阵的大小(设置为 10000。
问题1:
似乎用于某些准备(编译计算图?(的时间包含在CNTK案例中mat-vec产品的第一次执行中。 有没有办法在CNTK中拆分准备和执行,就像在Theano案例中一样?
问题2:
我习惯了 Theano,但在 CNTK是全新的,所以我不太确定 CNTK 代码是否等同于 Theano 代码。 我特别不确定CNTK代码的for循环中的操作是否真的包含在设备中,因为prod.eval((返回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 处理(由于这个答案有点太技术化了,为了其他读者,我包含此链接(。CNTK 在设备上执行此分配,尽管很难说。这是因为如果您尝试检查b_c.value
是否调用.value
属性 getter,该属性将为您提供 NumPy 数组。所以看起来结果是一个 NumPy 数组,但这只是使用b_c.value
的结果。任何其他变量都可以让你看到它是一个CNTK值对象。同样,所有这些都适用于当您执行eval(as_numpy=False)
时。
此外,CNTK 使用时间戳,因此上述评估仅在 GPU 上发生一次。所有后续对 eval(( 的N-1
调用只会返回相同的值对象(不过每次都会转换为 Numpy,除非您指定as_numpy=False
.
最后,我不期望从这个基准中学到很多有意义的教训:CNTK 和 Theano 都调用相同的 CuDNN 实现,CNTK 的优势更多地围绕更高层次的东西,例如 (a( 带有高级库 (b( 用户不必担心批处理和序列轴,除了一些专门的操作 (c( 高效的循环网络 (d( 高效的 I/O (e( 简单的分布式训练。
并回答您关于设置时间的问题:我的理解是,如果您只评估一次函数,就会编译它。CNTK 实际上有两种编译:如果只是第一次eval
,它将编译正向传递。如果你以后这样做function.grad
它将丢弃 eval 编译并再次编译它,以便它可以处理向前和向后传递。