在GPU上使用简单的矩阵矢量产品对Theano和CNTK进行基准测试

时间:2017-07-28 13:57:38

标签: gpu theano benchmarking cntk

我想比较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()返回一个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)

1 个答案:

答案 0 :(得分:0)

简短的回答是否定,操作未包含在设备上。这是发生的事情:当你调用eval()时,调用转到C ++,如果可能的话,它会对设备进行操作。当退出C ++时,CNTK会检查as_numpy关键字参数的值是否为True,默认为True。当as_numpy为True时,急切地将gpu缓冲区复制到NumPy数组。

如果你调用prod.eval(as_numpy = False),那么对eval的调用将不会将gpu缓冲区转换为NumPy数组。如果将结果分配给普通旧变量,则可以看到您获得了CNTK Value对象。但是,在您的代码中,您分配了.value的{​​{1}}属性。这个赋值由b_c属性的setter处理(因为这个答案有点太技术性,我为了其他读者而包括this link)。 CNTK在设备上执行此任务,但很难说。这是因为如果您正在调用value属性getter来检查b_c.value,那么它将为您提供一个NumPy数组。所以看起来结果是NumPy数组,但这只是使用.value的结果。任何其他变量都可以让你看到它是CNTK Value对象。同样,所有这些都适用于b_c.value

此外,CNTK使用时间戳,因此上述评估仅在GPU上发生一次。对eval()的所有后续eval(as_numpy=False)调用只会返回相同的值对象(除非您指定N-1,否则每次都会转换为Numpy。

最后,我不希望从这个基准测试中学到很多有意义的经验教训:CNTK和Theano都在调用相同的CuDNN实现,CNTK的优势更多地围绕更高层次的事情,例如(a)带有高 - 级别库(b)除少数专门操作外,用户不必担心批次和顺序轴(c)高效的循环网络(d)高效的i / o(e)易于分布式培训。

并回答你关于设置时间的问题:我的理解是你只需要对函数进行一次评估,然后编译它。 CNTK实际上有两种编译:如果你第一次编译正向传递你只是as_numpy=False。如果你以后执行eval它将丢弃eval编译并再次编译它,以便它可以处理前向和后向传递。