代码之家  ›  专栏  ›  技术社区  ›  J. Rock

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

  •  0
  • J. Rock  · 技术社区  · 7 年前

    我想在一个非常简单的任务上比较Theano和CNTK的性能:GPU上的矩阵向量积。我使用的是Theano 0.9.0和CNTK 2.0。

    我只想测量设备上计算所用的时间,不包括从主机到设备的数据传输所用的时间,反之亦然。

    我得到的结果是这样的: figure (timings theano vs cntk) (N是重复次数。D是矩阵的大小,设置为10000。)

    问题1:

    有没有办法像在Theano案例中那样,在CNTK中分割准备和执行?

    问题2:

    我特别不确定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 回复  |  直到 7 年前
        1
  •  0
  •   Nikos Karampatziakis    7 年前

    简而言之,答案是否定的,操作没有封闭在设备上。下面是发生的情况:当您调用eval()时,调用转到C++,如果可能的话,C++会在设备上执行操作。当从C++中出来时,CNTK检查 as_numpy 关键字参数,默认情况下为True。什么时候 作为numpy 如果是真的,gpu缓冲区会被急切地复制到NumPy阵列。

    如果调用prod.eval(as\u numpy=False),则调用 eval 不会将gpu缓冲区转换为NumPy阵列。如果将结果赋给一个普通的旧变量,则可以看到得到一个CNTK值对象。然而,在您的代码中,您分配给 .value 的属性 b_c value this link b_c.value 如果您正在呼叫 价值 属性getter,它将为您提供一个NumPy数组。看起来结果是NumPy数组,但这只是使用 b_c.值 eval(as_numpy=False)

    此外,CNTK使用时间戳,因此上述评估仅在GPU上发生一次。所有后续 N-1 as_numpy=False .

    最后,我不希望从这个基准中学到很多有意义的教训:CNTK和Theano都在调用同一个CuDNN实现,CNTK的优势更多地体现在更高层次的事情上,例如(a)带有高级库(b)用户不必担心批次和序列轴,除了一些专门的操作(c)高效的递归网络(d)高效的输入/输出(e)易于分布式训练。

    回答你关于设置时间的问题:我的理解是,如果你只评估一次函数,它就会编译出来。CNTK实际上有两种编译:如果你只是 function.grad 它将丢弃eval编译并再次编译,以便能够处理向前和向后传递。