首页 文章

输入和输出张量到TensorFlow的OpKernel :: Compute()函数是否在多个函数调用中改变它们的地址?

提问于
浏览
1

我正致力于在新架构上支持TensorFlow .

请考虑以下TensorFlow代码:

import tensorflow as tf
import random as r

def random_10x10():
  return [[r.normalvariate(1.0,1.0) for i in range(10)] for j in range(10)]

a = tf.placeholder(tf.float32, shape=[10, 10])
b = tf.placeholder(tf.float32, shape=[10, 10]) 
c = tf.placeholder(tf.float32, shape=[10, 10]) 
d = tf.placeholder(tf.float32, shape=[10, 10]) 

with tf.device('/device:CPU:0'):
  mm1 = tf.matmul(a,b)
  mm2 = tf.matmul(c,d)
  output = tf.add(mm1,mm2)

sess = tf.Session() 
for i in xrange(10):
  print sess.run(output, 
      feed_dict={ a:random_10x10(), b:random_10x10(),
                  c:random_10x10(), d:random_10x10()} )

TensorFlow程序的执行图中存在两个matmul块,在执行期间,它们使用OpKernel的MatMulOp子类的两个实例表示,在tensorflow / core / kernels / matmul_op.cc中找到 . 在MatMulOp :: Compute()中完成的第一件事是获取输入张量的地址:

void Compute(OpKernelContext* ctx) override {
    const Tensor& a = ctx->input(0);
    const Tensor& b = ctx->input(1);
...

我对TensorFlow的理解是,在上面的sess.run()的每次迭代中,MatMulOp的两个实例都不会改变 . 对于每个MatMul块,我可以期望输入的地址在迭代中保持不变,或者可能是在sess.run()调用的第七次迭代中,ctx-> input(0)将具有不同的值比第六次?

Compute()方法还调用ctx-> allocate_output(),它最终包装了我们自己的架构的分配器 . 是否可以分配输出块一次,然后在同一会话中的未来运行中继续使用相同的块?

1 回答

  • 2

    谢谢你的提问!

    TL; DR:是的,输入和输出张量将在Run()调用的不同调用中更改其地址 .

    细节:

    如果图中有两个不同的MatMul操作,则会有两个MatMulOp OpKernel实例(保存在一个OpSegment中,在图中为一个Op查找/创建,并随后缓存以便每次调用Session ::跑()) .

    输入的地址不能保证在迭代期间保持不变 . 事实上,几乎可以肯定的是,情况并非如此,部分原因是数据流图的执行是动态的,而不是确定性的 .

    ctx-> input(0)是可能来自输入op的“allocate_output”函数的另一个调用分配的内存,因此输入和输出内存位置的答案是相同的 . allocate_output函数最终委托给设备的“Allocator”实现来分配适当大小(和对齐)的内存 . 在CPU上,当前实现委托给malloc(),所以就像malloc()一样,每次调用Run()时都可能获得不同的内存 . 在GPU上,我们使用自定义GPU分配器(BFCAllocator类)来动态分配内存,其属性类似于malloc(),因为可以通过分配器根据内存的分配/释放顺序返回不同的内存 .

    因此,通常,调用allocate_output()返回的内存由设备的Allocator实现处理 . CPU和GPU实现不提供跨同一图形的运行的稳定指针保证 .

    但是,如果要实现自定义设备,则可能必须为设备实现自定义分配器,并且可能以这样的方式编写分配器:它为图形中的相同输出返回相同的内存 . 但这需要弄清楚如何将op的标识符传递给Allocator,以便每次都可以返回相同的内存 .

    TensorFlow故意进行动态内存分配至少有几个原因:

    1)数据流图的执行顺序可能取决于外部输入,因此定制严格的计划可能会导致不必要的停顿 . 动态执行顺序确保操作仅在所有输入都准备就绪时执行 .

    2)形状在TensorFlow中可以是动态的(例如,您的图形可以处理变量批量大小),这意味着操作可能需要为从Run()到Run()的相同操作输出分配不同的内存量!这是我们不能也不能提供这种保证的一个重要原因 .

    我们理解,有些设备希望针对图形的一个实例化(具有固定大小)进行优化,因此设备可以预先规划整个数据流图并在多次尝试中分摊其执行 . 这些情况通常更适合XLA编译器框架(https://www.tensorflow.org/versions/master/resources/xla_prerelease),但有可能在没有使用现有设备框架的XLA的情况下使其适用于有限情况/图形 .

相关问题