Tensorflow 调试方法

Timeline

可以直观的显示一个Step内Op粒度执行之间, 包括使用的GPU stream等信息, 为性能优化提供参考

在Chrome中键入 chrome://tracing/ ,即可通过Load按钮加载time.json, 使之图形化显示

所谓的Op粒度, 就是说Timeline只表示DAG图计算的过程中,每个Op的起止时间和使用的Stream(Tensorflow使用StreamExecutor管理Stream),而Op粒度的分析有时是有误导性的,比如使用Horovod进行分布式训练时,由于多个AllreduceOp并无相关性,也分属不同的Stream,看起来并行度很高,其实在其内部实现中,一方面Horovod会做融合,另一方面只有一个后台线程真正的去做网络通信,最后,网络IO的能力上限取决于网卡,所以实际的IO路径根本不是Timeline上看到的那种并行,而是在Horovod后台,最多到网卡就已经串行了。

Continue reading

Tensorflow 编译方法

Tensorflow使用SWIG自动根据so文件生成相应的python接口, 这就引起一系列反常识的问题, 比如, TF的编译环境同时要求TF部分可运行, 通常来说, 编译环境只需要配置编译依赖即可, 但在TF中行不通, 举个例子: 如果环境中只有CUDA, 没有GPU以及可用的Driver, 那么在编译的最后阶段, SWIG机制会去运行Tensorflow Runtime来生成Python接口, 但缺失GPU或Driver的环境是无法运行TF的, 也就无法完成位于编译最后阶段的python接口生成. 此外, 这也导致了如果在SWIG调用路径上的Runtime代码一旦有逻辑问题而不是语法问题, 在Runtime的编译过程不会报错, 编译最后生成Python接口也会出错导致最终编译失败, 进一步导致该类逻辑错误难以调试.

Debug信息保留

将编译命令调整如下

第一处--output_user_root="/.cache/bazel/" 主要解决编译环境root一般较小, 无法承载编译临时文件
第二处 --copt=-g  --strip=never  用于保留调试信息, 要配合python3-dev使用, 如果使用python2, 由于带有debug信息生成so过大, 会导致wheel制作失败(int32溢出), 当然, 相应的python代码也要适配python3进行修改

离线安装依赖

Tensorflow编译过程会从Internet上load所需依赖, 如果网络环境不允许, 可以通过其他路径现将依赖包下载copy到编译环境, 再修改tensorflow/tensorflow/workspace.bzl中的相关url, 即可规避联网编译问题

https://github.com/tensorflow/tensorflow/issues/5428#issuecomment-258732841

Tensorflow OpKernel机制详解

OpKernel是Op的具体实现, tf中已经实现的tfop的OpKernel在源码中的tensorflow/core/framework/kernel/中, OpKernel通过注册时使用相同的名字将自己和相应的Op联系到一起.

在tf中, OpKernel进一步可以分为两类, OpKernelAsyncOpKernel:

  1. OpKernel是同步执行的, 即"Compute()"返回即认为数据已经被正确处理, 注册OpKernel, 子类需要重写其Compute()方法.
  2. AsyncOpKernel是对OpKernel的封装, 顾名思义, AsyncOpKernel执行返回并不意味着数据已经被处理完毕, 数据的真正被处理完毕时通过回调的方式通知Op执行引擎, 注册一个AsyncOpKernel, 子类需要实现"AsyncCompute()"而不是Compute().

接口形式

无论是哪种OpKernel, 均使用"REGISTER_KERNEL_BUILDER()"注册到运行核心.

注册原理

注册机制的实现代码主要集中在tensorflow/core/framework/op_kernel.h(.cc). 与 Optimization以及Op在注册时直接构造一个static OptimizationPassRegistration(OpRegistrationData)对象的机制略有不同, OpKernel的通过一些trick实现了对OpKernel的延迟构造, 即"REGISTER_OP_KERNEL_BUILDER()"并没有直接构造一个"OpKernel"实例, 而是构造一个" static ::tensorflow::kernel_factory::OpKernelRegistrar "对象, 并借由该构造过程构造并注册一个"KernelRegistration" 对象到 global_regsitry, 该构造过程接受上层传入的, 用于new一个OpKernel的"[](::tensorflow::OpKernelConstruction* context) -> ::tensorflow::OpKernel* { return new __VA_ARGS__(context);}" 函数, 在上层真正需要这个OpKernel的时候, 才会通过一系列调用最终执行该"create_fn()/lambda"来构造一个实实在在的OpKernel对象. 即整体上不再是Registry->Registration(Optimization/Op对象), 而是 Registry->Registrar->Registration->在需要时create_fn()构造OpKernel对象.

构造一个OpKernelRegistrar:

Continue reading

Tensorflow Op机制详解

注册一个tfop分为两部分: OpOpKernel. 其中, Op是tfop的声明部分, 类似于函数的声明, 主要描述Op静态属性. OpKernel是tfop的实现部分, 同样类似于函数的实现, 主要描述OpKernel的具体计算逻辑. tf已经实现的Op相关源码在tensorflow/core/common_runtime/ops/中, OpKernel的部分在tensorflow/core/common_runtime/kernels/. 二者通过注册时使用的名字联系到一起.

接口形式

下面是一个注册Op的例子, 使用接口"REGISTER_OP()"‘注册一个Op,包括输入输出, Op属性等内容, 注册的Op最终会转换为OpDef, 即用protobuf格式存储的静态数据, 所以, 这里用到的属性也按照pb的格式以KV的形式编写. REGISTER_OP的实现在”tensorflow/core/framework/op.h OpDefBuilderWrapper” 中, 众多可设值中,"Attr"是一个允许自定义的值, 比如XLA引擎就根据自身需求提供了"XlaCompile", 如果一个Op将该值设置为true, 就会强制XLA引擎将其编译. 当然, 也可以设置一些无用的值, 就像函数声明里有一个并没有实际使用的参数, 除了浪费存储空间没有其他用途.

注册原理

在内部原理上, 上述代码可以进一步分解为两部分: 构造一个Op + 注册一个Op, 这部分代码主要在 “tensorflow/core/framework/ op.h(.cc) op_def_builder.h(.cc) ” 中

Continue reading