人工智能 tensorflow二次开发

huangsan · October 21, 2019 · 95 hits

编译

  • 方法 1:

    1234567
    ./configurebazel build --config=opt //tensorflow/tools/pip_package:build_pip_packagebuild出错清理:/root/.cache/bazel把下面的之前出错的缓存文件给删除掉生成whell包bazel-bin/tensorflow/tools/pip_package/build_pip_package /root/tensorflow/wheel_pkg/build_withSource

  • 方法 2:

    1234
    yes "" | python configure.pybazel build --config=mkl --copt=-mavx2 --copt=-O3 --copt=-DINTEL_MKL_QUANTIZED -s //tensorflow/tools/pip_package:build_pip_package生成whell包bazel-bin/tensorflow/tools/pip_package/build_pip_package /root/tensorflow/wheel_pkg/build_withSource

编译命令和过程分析

视频:https://www.youtube.com/watch?v=Rw-KrbfyABQ
https://www.cnblogs.com/shouhuxianjian/p/9416934.html

运行 configure.py 会把一些编译参数放入.bazelrc 和.tf_configure.bazelrc 文件里面 (https://www.jianshu.com/p/5cd111ebb8bb)
bazelrc 文件的解释
https://docs.bazel.build/versions/master/guide.html

build 后面接的都是默认的编译参数
build:mkl 后面接的编译参数只有当 bazel build –config=mkl 的时候 mkl 后面的编译参数才会起作用

-c 的选项有可能是–config 的缩写

bazel build 的其他编译选项:
https://docs.bazel.build/versions/master/user-manual.html
–copt: This option takes an argument which is to be passed to the compiler. 所以–copt 后面传进来的都是 gcc 或者是 icc 的编译参数

–strip 是否删除 debug 信息,never 表示不删除 debug 信息

增量编译

直接 bazel build
然后重新生成 wheel 包
pip unistall tensorflow
一定先卸载然后重新安装
否则还是原来的包

编译之后

生成 pywrap_tensorflow_internal.py 以及 pywrap_tensorflow_internal.cc 在~/.cache/bazel 目录下面,所有代码都在_pywrap_tensorflow_internal.so 的动态链接库里面
pywrap_tensorflow_internal.py: 负责对接上层 Python 调用
pywrap_tensorflow_internal.cc: 负责对接下层 C API 调用

  • pywrap_tensorflow_internal.py 模块首次被导入时,自动地加
    载 _pywrap_tensorflow_internal.so 的动态链接库;其中, _pywrap_tensorflow_internal.so
    包含了整个 TensorFlow 运行时的所有符号。
  • 在 pywrap_tensorflow_internal.cc 的实现中,静态注册了一个函数符号表,实现了 Python 函数名到 C 函数名的二元关系。在运行时,按照 Python 的函数名称,匹找到对应的 C 函数实现,最终实现 Python 到 c_api.c 具体实现的调用关系。

调整 tensorflow 运行的日志等级

TF 代码又两个函数打印日志,LOG 以及 VLOG
LOG 是正常的打印日志,通过 TF_CPP_MIN_LOG_LEVEL

1
export TF_CPP_MIN_LOG_LEVEL=level

去设置,值越小,打印日志越多
VLOG 通过

1
export TF_CPP_MIN_VLOG_LEVEL=level

去设置,但是 VLOG 只有在 LOG 等级为 0 的时候设置才有用
比如要打印 mkl_layout_pass.cc 初始化 rewirte op 时的信息

12
export TF_CPP_MIN_LOG_LEVEL=0export TF_CPP_MIN_VLOG_LEVEL=1

编译 debug 版本的 tensorflow

添加 -c dbg 选项
移除优化选项 –copt=-O3 以及 -c opt

1
bazel build --config=mkl --copt=-mavx2 --copt=-O3 --copt=-DINTEL_MKL_QUANTIZED -s -c dbg //tensorflow/tools/pip_package:build_pip_package

debug 版本编译完大概有 20G 左右
export OMP_NUM_THREADS=1
设置 intra 和 inter 值为 1

指定编译目录

默认编译在/root/.cache/bazel 目录下面,有时候 root 目录空间不够

123
build_dir=/home/lesliefang/bazel_buildbazel --output_user_root=$build_dir cleanbazel --output_user_root=$build_dir build --config=mkl --copt=-mavx2 --copt=-O3 --copt=-DINTEL_MKL_QUANTIZED -s -c dbg //tensorflow/tools/pip_package:build_pip_package

编译报错找不到–march=broadwell

使用 gcc6.3 以及以上版本,低版本的编译器不认识 broadwell 的选项

whell 太大无法打包

https://github.com/tensorflow/tensorflow/issues/5538

替换 mkldnn 版本

以 TF 从 0.18 升级到 0.19 为例

下载 mkldnn0.19 计算 sha256sum

12345
wget https://github.com/intel/mkl-dnn/archive/v0.19.tar.gz
sha256sum v0.19.tar.gz记录这个结果ba39da6adb263df05c4ca2a120295641fc97be75b588922e4274cb628dbe1dcd后面会用到

修改 $tensorflow_root/tensorflow/workspace.bzl

搜索 mkl_dnn

123456789101112131415
121     # Important: If you are upgrading MKL-DNN, then update the version numbers 122     # in third_party/mkl_dnn/mkldnn.BUILD. In addition, the new version of 123     # MKL-DNN might require upgrading MKL ML libraries also. If they need to be 124     # upgraded then update the version numbers on all three versions above 125     # (Linux, Mac, Windows). 126     tf_http_archive( 127         name = "mkl_dnn", 128         build_file = clean_dep("//third_party/mkl_dnn:mkldnn.BUILD"), 129         sha256 = "38a1c02104ee9f630c1ad68164119cd58ad0aaf59e04ccbe7bd5781add7bfbea", 130         strip_prefix = "mkl-dnn-0.18", 131         urls = [ 132             "http://mirror.tensorflow.org/github.com/intel/mkl-dnn/archive/v0.18.tar.gz";, 133             "https://github.com/intel/mkl-dnn/archive/v0.18.tar.gz";, 134         ], 135     )

  • 把里面所有 0.18 替换成 0.19
  • 替换上面得到的 sha256sum

看第二步的注释和代码

需要修改”//third_party/mkl_dnn:mkldnn.BUILD”
$tensorflow_root/tensorflow/workspace.bzl
vim $tensorflow_root/third_party/mkl_dnn/mkldnn.BUILD
把里面的版本号从 0.18 改到 0.19

注意:
tensorflow 里面,mkldnn 是被当做 source code 编译进去的,
所以不存在动态链接库

check:
build_dir/b3a4cb07d89ceca0353d37b5d32ffadc/external/mkl_dnn
里面是 mkldnn 下载下来的代码
里面有个 readme 文件在开头的地方可以 check 版本是 0.18 还是 0.19

gdb 调试

二种方法方法去 debug TF:
method1:

123
1. gdb python2. run file.py3. bt

method2:

123456
1. 跑测试2. top 看到python进程的pid3. gdb -p pid挂上之后,原来测试会挂住break 函数名或者其它打上断点,tensorflow找不到符号的情况下可以 文件名:line的方式去打断点continue 继续测试直到core-dump

如何添加 python 的信息 参考这个 blog
http://jcf94.com/2018/01/13/2018-01-13-tfunpacking/

warning 找不到文件

dir 目录
去指定文件的搜索根目录
使用 gdbgui 去调试的时候,也需要指定了目录之后才可以显示文件

调试前的参数设置以及技巧

所有并行计算线程设置为 1,避免多线程导致断点带来的麻烦
命令后加&echo $! 输出 PID,进行 gdb -p 的调试

mkldnn 调试

12
export MKLDNN_VERBOSE=1python ***

在运行测试之前,添加环境变量
可以打出 mkldnn 的信息
每一行的信息 Each line with verbose information is formatted as a comma-separated list containing:

  • mkldnn_verbose
  • stage, e.g. create or exec
  • primitive-kind, e.g. convolution, reorder, sum, …
  • primitive implementation name
  • propagation-kind, e.g. forward_training
  • input/output data info, e.g. data type and data format
  • auxiliary information, e.g. algorithm or number of input
  • problem description
    • for convolution the problem description is dumped in benchdnn friendly format
    • for reorder, sum, and concat problem description is simply logical dims
    • for other primitives the problem description is similar to convolution one

  • execution time in milliseconds

看 python 到 C++ 调用关系

以 Session 为例子:tf.Session 时候的调用关系

  • python api
    /root/tensorflow_src/test_code/private-tensorflow/tensorflow/python
    目录下面:
  1. grep -rni “class Session”
    client/session.py:1475:class Session(BaseSession):
    里面调用了 baseSession 的构造函数
  2. 看 baseSession
    里面调用了 tf_session

    12
    self._session = tf_session.TF_NewSessionRef(self._graph._c_graph, opts)from tensorflow.python import pywrap_tensorflow as tf_session
  3. 看 pywrap_tensorflow.py
    这个就是对应了编译出来的 so 文件

  4. 在 source insight 里面搜索 TF_NewSessionRef
    看到定义在 tf_session_help.cc 里面
    里面调用了 TF_NewSession

  5. source insight 里面搜索 TF_NewSession
    已经进入到 C++ 代码内部

以 matmul 为列

https://ggaaooppeenngg.github.io/zh-CN/2018/05/29/Tensorflow-%E7%9A%84-Tensor-%E5%92%8C-OpKernel-%E5%88%86%E6%9E%90/
调用 tf.matmul(a,b)

  1. 查看
    1
    grep -rni "tf_export.*matmul" #这个函数需要用tf_export导出

ops/math_ops.py:2277:@tf_export(“linalg.matmul”, “matmul”)

  1. 看 math_ops.py:2277
    api 的使用有详细的解释
    调用了 gen_math_ops.batch_mat_mul 或者 gen_math_ops.mat_mul

  2. 看 gen_math_ops.py

    1
    find / -name "gen_math_ops.py"

这个文件看文件名字,应该是在编译的时候生成的
这个文件里面搜:batch_mat_mul

  1. batch_mat_mul 函数
    这个函数里面调用了
    1234
    _result = _pywrap_tensorflow.TFE_Py_FastPathExecute(        _ctx._context_handle, _ctx._eager_context.device_name, "BatchMatMul",        name, _ctx._post_execution_callbacks, x, y, "adj_x", adj_x, "adj_y",        adj_y)

所以 C++ 里面的 op 函数应该是 BatchMatMul

  1. 搜索所有注册这个 op 的地方
    搜索 op 定义
    12
    [root@localhost private-tensorflow]# grep -rni "REGISTER_OP("MatMul")"tensorflow/core/ops/math_ops.cc:763:REGISTER_OP("MatMul")

搜索 op 的 kernel 实现

1
grep -rni "Name("MatMul")"

找到所有定义 operation
break 文件名:行
在每个 computer 的 d 地方打断点
看看调用到了哪个 kernel

看 class MatMulOp 的 Compute 方法里面最后调用了 LaunchMatMul 方法
LaunchMatMul 继承自 LaunchMatMulBase,在 LaunchMatMulBase 当中调用了 functor::MatMulFunctor,这个 functor 主要就会执行乘法操作

MatMulFunctor 里面调用了 MatMul 方法
MatMul 方法里面进一步调用了 out.device(d) = in0.contract(in1, dim_pair);

contract 是 Eigen 的一个方法,表示矩阵相乘,Eigen 是一套高效的 C++ 中调用的数学平台,里面实现了很多通用的数学运算。

以 conv2d 为例

这个人博客很多好文章:http://lanhin.xyz/
http://lanhin.xyz/2018/10/29/tensorflow%E4%B8%AD2d%E5%8D%B7%E7%A7%AF%E4%BB%A3%E7%A0%81%E7%AE%80%E6%9E%90/

  1. python 接口 tf.nn.conv2d
    1
    grep -rni "tf_export.*conv2d"

tensorflow_src/test_code/private-tensorflow/tensorflow/python/ops/nn_ops.py:1376:@tf_export(“nn.conv2d”, v1=[])

  1. 查找输出的地方

    1
    find / -name "gen_math_ops.py"
  2. 查看 op 注册和实现的地方

    12
    grep -rni "REGISTER_OP("Conv2D")"grep -rni "Name("Conv2D")"
  3. 进入 conv_ops.cc 文件
    看 Compute 方法

输入为浮点数 float 调用 LaunchDeepConvOp::Run

其它输入类型调用 launcher_
进一步看调用到了
LaunchConv2DOp::operator()
再往下
tensorflow::LaunchGeneric::operator
这个函数里面通过不同的条件判断调用两个不同的计算 kernel:functor::MatMulConvFunctor() 和 functor::SpatialConvolution()

MatMulConvFunctor 定义在 conv_2d.h 文件里面
out.device(d) = in0.contract(in1, dim_pair, output_kernel);
到最后还是调用了矩阵乘法的函数
这个 contract 应该是 eigen 库提供的接口

INT8 operation

  1. 读取 RN50 int8 的 pb
    用 tensorboard 查看
    看到用到了 op:QuantizedConv2DWithBiasAndReluAndRequantize

搜索不到对应 op 的时候
tensorflow 做了 op 的转换
private-tensorflowtensorflowcoregraphmkl_layout_pass.cc
参考这个文件
果然再这个文件里面可以搜索到
QuantizedConv2DWithBiasAndReluAndRequantize
mkl_layout_pass.cc 根据 PPT 里面的解释,会把标准的输入的 TF 的 graph 转换成 mkl 优化的图,里面有个 run 函数应该是转换的入口

也有可能定义 tensorflow/core/api_def/base_api/api_def_QuantizedMatMulWithBias.pbtxt
这个目录下面也可能定义了 pb 文件

python api 有两种定义方法(https://groups.google.com/a/tensorflow.org/forum/#! topic/developers/LmKn-y7LZ_E):
Python API endpoints are currently added using 2 ways:

  1. apidef.pbtxt files (python_op_gen_internal.cc would actually add tf_export decorator for each visible endpoint specified in apidef.pbtxt files)
  2. tf_export decorators

  3. 搜索这个 op

    12
    [root@localhost ~]# grep -rni "name("QuantizedConv2DWithBiasAndReluAndRequantize")"tensorflow_src/test_code/private-tensorflow/tensorflow/core/kernels/mkl_conv_ops.cc:1997:REGISTER_KERNEL_BUILDER(Name("QuantizedConv2DWithBiasAndReluAndRequantize")

这个 op 对应的 kernel 实现就是 QuantizedConv2DWithBiasAndReluAndRequantize
对应的 kernel 叫做 NoOp
看到注释:
// Register NoOp kernel for QuantizedConv2DWithBiasAndRelu to get a python
// interface.
// This kernel will be replaced by an MKL kernel during graph-optimization pass.

NoOp 是因为这个 op 在图优化阶段被 rewrite 了 (mkl_layout_pass.cc 的 RunPass 函数)

同一个文件里面看另外一个 op

1
_MklQuantizedConv2DWithBiasSumAndRelu

对应的 kernel 是 MklQuantizedConv2DSumReluOp
继承了 MklQuantizedConv2DOp 这个 kernel
MklQuantizedConv2DOp 这个 kernel 继承了 MklConvOp
MklQuantizedConv2DOp 的 compute 方法首先调用了

123
// Compute int32 output tensorMklConvOp<Device, quint8, qint8, Tbias, Toutput, Ttemp_output, int32,          biasEnabled, false>::Compute(context);

MklConvOp 里面的 compute 方法调用了 mkldnn
conv_fwd->Execute 执行 mkldnn 的计算

注意
class MklConvOp 在这个文件里面有两个类的定义
通过 template Execute

根据文件里面的宏的定义,应该只有一个函数会被编译出来

看这个 mkldnn 的类的实现代码,可以先看看 MKLDNN 的教程和实例代码 mkldnn 代码库的 simple_net.cpp 以及解释
基本概念比较清晰,先创建 memory/operator descriptor,再创建对应的 Primitive descriptor ,最后创建 primitive,然后把 primitive 放到 stream 里面去执行
tensorflow 的这个类的实现 follow 这个逻辑只是加了一些封装
至于 mkldnn 里面进一步的实现 (如何多线程等) 就是 mkldnn 的事情了
可以看我的 mkldnn 的文章

自己定义个 operation

参考文档:http://wiki.jikexueyuan.com/project/tensorflow-zh/how_tos/adding_an_op.html#AUTOGENERATED-adding-a-new-op

定义 operation

1234
#include "tensorflow/core/framework/op.h"REGISTER_OP("ZeroOut")    .Input("to_zero: int32")    .Output("zeroed: int32");

定义 kernel

12345678910111213141516171819202122232425
#include "tensorflow/core/framework/op_kernel.h"using namespace tensorflow;class ZeroOutOp : public OpKernel { public:  explicit ZeroOutOp(OpKernelConstruction* context) : OpKernel(context) {}  void Compute(OpKernelContext* context) override {    // 获取输入 tensor.    const Tensor& input_tensor = context->input(0);    auto input = input_tensor.flat<int32>();   // 创建一个输出 tensor.    Tensor* output_tensor = NULL;    OP_REQUIRES_OK(context, context->allocate_output(0, input_tensor.shape(),                                                     &output_tensor));    auto output = output_tensor->template flat<int32>();    // 设置 tensor 除第一个之外的元素均设为 0.    const int N = input.size();    for (int i = 1; i < N; i++) {      output(i) = 0;    }    // 尽可能地保留第一个元素的值.    if (N > 0) output(0) = input(0);  }};REGISTER_KERNEL_BUILDER(Name("ZeroOut").Device(DEVICE_CPU), ZeroOutOp);

添加 python wrap

经过前面两步在编译之后,可以在 bazel-genfiles/tensorflow/python/ops/gen_user_ops.py 文件,比如我的一个例子
vim /home/lesliefang/bazel_build/615e7e34d0a05b2b7ebac45eda8ba3c5/execroot/org_tensorflow/bazel-out/k8-opt/bin/tensorflow/tools/pip_package/build_pip_package.runfiles/org_tensorflow/tensorflow/python/ops/gen_user_ops.py
里面找到对应的 operation 的函数
为了使得 python 可以调用到,在 tensorflow/python/user_ops/user_ops.py 文件中添加接口

1234
@tf_export(v1=['user_ops.leslie_zero_out'])def leslie_zero_out(input):  """Example of overriding the generated code for an Op."""  return gen_user_ops.zero_out(input)

测试

重新编译之后安装之后
测试代码

12345678910111213
import tensorflow as tfimport numpy as npimport datetimeimport osimport timeif __name_ == "main":  #time.sleep(30) with tf.Session() as sess:      sess.run(tf.global_variables_initializer())     result = tf.user_ops.leslie_zero_out([5, 4, 3, 2, 1])       print("result is {}".format(result))        print("result is {}".format(sess.run(result)))

多线程

To write a multi-threaded CPU kernel, the Shard function in work_sharder.h can be used. This function shards a computation function across the threads configured to be used for intra-op threading (see intra_op_parallelism_threads in config.proto).

核心运行机制

推荐一个很好的 Blog:http://jcf94.com/2018/01/13/2018-01-13-tfunpacking/
这个 blog 对 C++ 部分 session 的机制分析的很清楚

这边从 python 调用 session.run 开始分析

在 python 里面

1.
session.run

  1. 12
    result = self._run(None, fetches, feed_dict, options_ptr,                   run_metadata_ptr)
  2. 在_run 里面

    12
    results = self._do_run(handle, final_targets, final_fetches,                      feed_dict_tensor, options, run_metadata)
    1. do_run 里面

      12
      return self._call_tf_sessionrun(    options, feed_dict, fetch_list, target_list, run_metadata)
    2. call_tf_sessionrun 里面

      123
      return tf_session.TF_SessionRun_wrapper(   self._session, options, feed_dict, fetch_list, target_list,   run_metadata)

TF_SessionRun_wrapper 定义在 pywrap_tensorflow_internal.py 里面
就是 python 和 C++ 的桥梁

下面进入 C++ 的部分

  1. TF_SessionRun_wrapper_helper 函数
    里面调用了 TF_SessionRun

  2. TF_SessionRun 函数
    调用了 TF_Run_Helper 函数

  3. TF_Run_Helper 函数
    调用了 session->Run 函数

  4. 这是个虚函数
    用 gdb 跟进去看
    参考这篇文章:https://zhuanlan.zhihu.com/p/26031658
    local 用 direction_session
    分布式用 grpc_session
    所以我们这边调用到了 DirectSession::Run

  5. 看 DirectSession::Run 函数
    这个函数的分析:http://jcf94.com/2018/01/13/2018-01-13-tfunpacking/

  • GetOrCreateExecutors 函数里面会去寻找有没有符合条件的 exectuor,不存在的话则调用 CreateExecutors 函数去创建 executors
    同时 CreateExecutors 里面调用到了 CreateGraphs
    在 CreateExecutors 调用了 CreateGraphs 之后看到:
    12
    params.create_kernel = [this, lib, opseg](const NodeDef& ndef,                                              OpKernel** kernel)

我理解就是在这里实现了 param 里面的创建 kernel 的函数指针
在 CreateExecutors 的最后调用了 NewExecutor 函数,会传入 param 变量 (里面带上了 create_kernel 方法)
NewExecutor 函数里面通过工厂模式来生成 Executor
是个虚函数,通过 gdb 看到里面调用了
tensorflow::(anonymous namespace)::DefaultExecutorRegistrar::Factory::NewExecutor (this=0x1fffd10, params=…, graph=…,
out_executor=0x72fdee8) at tensorflow/core/common_runtime/executor.cc:2857

12345678910
class Factory : public ExecutorFactory {  Status NewExecutor(const LocalExecutorParams& params,                     std::unique_ptr<const Graph> graph,                     std::unique_ptr<Executor>* out_executor) override {    Executor* ret = nullptr;    TF_RETURN_IF_ERROR(NewLocalExecutor(params, std::move(graph), &ret));    out_executor->reset(ret);    return Status::OK();  }};

里面调用了 NewLocalExecutor
进一步调用 ExecutorImpl->Initialize 函数
这个函数里面调用了 params_.create_kernel 函数去创建 kernel
(这个 create_kernel 函数就是之前在 CreateExecutors 函数里面定义的)
同时在这个函数里面看到了一行注释

12
// Preprocess every node in the graph to create an instance of op// kernel for each node.

调试 CreateExecutors 的 create_kernel 函数

gdb 断点进去 CreateKernel 函数
tensorflow/core/common_runtime/function.cc:521
调用到 526 行的 CreateKernel 函数
tensorflow/core/common_runtime/function.cc:526
executor.cc 的 CreateNonCachedKernel 函数
op_kernel.cc 的 CreateOpKernel 函数(*kernel = registration->factory->Create(&context);)
mkl_conv_ops.cc 的 TF_CALL_float(REGISTER_MKL_CPU_2D_FUSED);函数
mkl_conv_ops.cc 的 MklFusedConvOp 的构造函数

所以调用 session.run 多次,因为已经存在符合条件的 exectuors,并不会多次创建图
(别人的评论:第一次执行 sess.run(….) 的时候会根据 python 层的图构造出 C++ 层的图然后保存下来,之后如果下次 sess.run() 的目标节点是相同的,就不需要重新构造一遍了。详细可以去分析 sess.run() 的执行流程)

  • 调用到了 RunInternal 函数
  1. RunInternal 函数
    里面调用了 item.executor->RunAsync(args, barrier->Get());
    去执行异步计算

  2. 通过日志知道 RunAsync 会调用到 executor 的 Process() 函数
    process 函数做了什么:
    http://jcf94.com/2018/01/13/2018-01-13-tfunpacking/
    遍历每个节点,针对每个节点的 kernel 进行计算(调用 device->Compute,里面调用 op_kernel->Compute(context);)
    在每个 kernel 里面都可以搜索到对应的 Compute 函数

看一个 inner product 的 kernel 是怎么生成的

断点打在

1
b mkl_qmatmul_op.cc:183(一个setup函数里面)

汾西代码知道这个 setup 函数是设置上下文变量的
查看调用栈

12345678910111213
#0  tensorflow::MklIPFwdPrimitive<float, Eigen::QUInt8, Eigen::QInt8, Eigen::QInt32, Eigen::QUInt8>::Setup (this=0x3d1a300, IPFwdDims=...)    at tensorflow/core/kernels/mkl_qmatmul_op.cc:183#1  0x00007f6a77ee938c in tensorflow::MklIPFwdPrimitive<float, Eigen::QUInt8, Eigen::QInt8, Eigen::QInt32, Eigen::QUInt8>::MklIPFwdPrimitive (this=0x3d1a300, IPFwdDims=...)    at tensorflow/core/kernels/mkl_qmatmul_op.cc:77#2  0x00007f6a77ee81c3 in tensorflow::MklIPFwdPrimitiveFactory<float, Eigen::QUInt8, Eigen::QInt8, Eigen::QInt32, Eigen::QUInt8>::Get (IPFwdDims=..., do_not_cache=false)    at tensorflow/core/kernels/mkl_qmatmul_op.cc:298#3  0x00007f6a77ee0515 in tensorflow::MklIPOp<Eigen::ThreadPoolDevice, Eigen::QUInt8, Eigen::QInt8, Eigen::QInt32, Eigen::QUInt8, Eigen::QUInt8, true>::Compute (    this=0x1ea0f20, context=0x7f6a53f1d5f0) at tensorflow/core/kernels/mkl_qmatmul_op.cc:499#4  0x00007f6a77edee0e in tensorflow::MklQuantizedIPOp<Eigen::ThreadPoolDevice, Eigen::QInt32, Eigen::QUInt8, Eigen::QUInt8, true>::Compute (this=0x1ea0f20,    context=0x7f6a53f1d5f0) at tensorflow/core/kernels/mkl_qmatmul_op.cc:752#5  0x00007f6a78410eae in tensorflow::Device::Compute (this=0x40a6780, op_kernel=0x1ea0f20, context=0x7f6a53f1d5f0) at ./tensorflow/core/common_runtime/device.h:89#6  0x00007f6a6c90f868 in tensorflow::(anonymous namespace)::ExecutorState::Process (this=0x54f6480, tagged_node=..., scheduled_nsec=0)    at tensorflow/core/common_runtime/executor.cc:1817

  • #0 mkl_qmatmul_op.cc:183 在 tensorflow 里面这个 primitive 的 setup 函数
    看这个 setup 里面,看到先创建 mkldnn 的 primitive 的 desc
    12345
    // create a inner product context_.fwd_desc.reset(new inner_product_forward::desc(       prop_kind::forward_inference, *context_.src_md, *context_.weight_md,       *context_.bias_md,       *context_.dst_md));

然后通过这个 desc 去创建 primitive_desc(pd),跟进到 mkldnn 里面看,就是在创建 pd 的时候回去遍历 mkldnn 里面所有 pd 找到对应的满足条件的 pd

  • #1 mkl_qmatmul_op.cc:77 MklIPFwdPrimitive 的构造函数
  • #2 mkl_qmatmul_op.cc:298 MklIPFwdPrimitiveFactory 的 Get 函数,Get 函数根据输入的 MklIPFwdParams 去 try to find a suitable one in pool
    没有找到的话 (if (IP_fwd == nullptr)) 会去创建
  • #3 mkl_qmatmul_op.cc:499 MklIPOp 的 compute 方法,里面调用了 MklIPFwdPrimitiveFactory 的 Get 方法去拿到对应的 IP_fwd(Primitive)
    MklIPOp 的 compute 方法 应该是 tensorflow 在运行图的节点的时候会被调用到的方法
    继续看这个 MklIPOp 的 compute 方法
    后面会调用 IP_fwd->Execute(src_data, weight_data, bias_data, dstdata);
    去做计算
    这个根据前几步选中的 mkldnn 的 pd,会调用到 mkldnn 的 submit 函数 (context
    .fwdstream->submit(context.fwd_primitives);)
    可以用 GDB 去跟进 mkldnn 去看调用关系,这里已经比较好理解了
    结论
    所以 tensorflow 的 node 到 mkldnn 的 kernel 的对应关系,是在第一次运行这个图的时候确认的,同时如果 set 了 cache(默认都是设置的),后面几次运行的时候就会保留这个对应关系
  • #4 mkl_qmatmul_op.cc:752 MklQuantizedIPOp 的 Compute 函数,这个函数会去调用 MklIPOp 的 compute 方法
  • #5 device.h:89 Device 的 Compute() 是个虚函数,对应了 device 信息
  • #6 executor.cc:1817 ExecutorState::Process 函数,这里已经是 tensorflow 创建了 exectuor 之后的执行了
  • #7 executor.cc:2258 ExecutorState::ScheduleReady

总结,关键是这个 MklIPOp 的 compute 方法,先通过 Get 方法去获得对应的 mkldnn 的 kernel,然后调用 execute 去执行

通过 pb 文件去看调用的 kernel

读取 pb 文件,查看模型的结构

使用 tensorboard 或者 Netron
推荐使用 Netron,很好用,里面还可以看到各个节点的参数的值

打印 pb 文件中每个节点的名字

  • 在代码里面加载输出每个节点的名字
    123456
    graph_def = graph_pb2.GraphDef()with open(args.input_graph, "rb") as f:  graph_def.ParseFromString(f.read()) #f就是pb文件for node in graph_def.node:    k = node.name    print("node op is {}".format(node.op))

打印出 node 的名字
比如其中一个 MatMul

  • 加载 pb 用 tensorboard 大概看一下
    12345678910111213141516171819202122232425
    2 import pandas as pd3 import csv4 import struct5 from PIL import Image6 import numpy as np7 import datetime8 import os9 import argparse10 import tensorflow as tf1112 if __name__ == "__main__":13         parser = argparse.ArgumentParser()14         parser.add_argument("mode", help="display a square of a given number")15         args = parser.parse_args()16         from tensorflow.python.platform import gfile17         with gfile.FastGFile(args.mode, 'rb') as f:18                 graph_def = tf.GraphDef()19                 graph_def.ParseFromString(f.read())20                 for node in graph_def.node:21                         print("node name is: {} t node op is: {}".format(node.name,node.op))22                 #tensorboard23                 with tf.Session() as sess:24                         sess.graph.as_default()25                         tf.import_graph_def(graph_def, name='')26                         summaryWriter = tf.summary.FileWriter('log/', sess.graph)

跑完之后,命令行运行
tensorboard –logdir log/

  • 在 tensorlfow 里面搜索注册这个 op 和 kernel 的地方
    比如第二步打印看到的 node.op 是 Conv2D
    在代码里面搜索
    1
    grep -rni "Name(".*Conv2D.*")"

因为注册的 kernel 可能是 Conv2D
也有可能加了 mkl 前缀比如:REGISTER_KERNEL_BUILDER(Name(“_MklConv2D”)
在 directSession,创建新的 exector 的时候会去优化 graph,这个时候会把 Conv2D 这个 op 转换成_MklConv2D,一般就是添加_MKL 的前缀
在 mkl_layout_pass.cc 这个文件的 RunPass 函数里面,会去做图的优化,包括临近节点的合成,op 的 rewrite 以及 mkldnn 节点前添加数据格式的转换等 op
创建 kernel 时候的调用栈
断点打在 mkl_conv_ops.cc:861

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
#0  tensorflow::MklConvOp<Eigen::ThreadPoolDevice, float, float, float, float, float, int, false, false>::MklConvOp (this=this@entry=0x36b35400,    context=context@entry=0x7ffca8d435c0) at tensorflow/core/kernels/mkl_conv_ops.cc:861#1  0x00007fa3b9de7ecc in tensorflow::MklFusedConvOp<Eigen::ThreadPoolDevice, float, float, float, float, float, int, true>::MklFusedConvOp (    this=0x36b35400, context=0x7ffca8d435c0) at tensorflow/core/kernels/mkl_conv_ops.cc:1474#2  0x00007fa3b9dcd7b2 in operator() (__closure=0x0, context=0x7ffca8d435c0) at tensorflow/core/kernels/mkl_conv_ops.cc:2165#3  tensorflow::<lambda(tensorflow::OpKernelConstruction*)>::_FUN(tensorflow::OpKernelConstruction *) ()    at tensorflow/core/kernels/mkl_conv_ops.cc:2165#4  0x00007fa3b469ac77 in tensorflow::CreateOpKernel (device_type=..., device=device@entry=0x3c346e0, allocator=allocator@entry=0x1c1e380,    flib=flib@entry=0x36bae2c0, node_def=..., graph_def_version=0, kernel=0x15b5c4bc8) at tensorflow/core/framework/op_kernel.cc:1302#5  0x00007fa3b498f80f in tensorflow::CreateNonCachedKernel (device=0x3c346e0, flib=flib@entry=0x36bae2c0, ndef=...,    graph_def_version=<optimized out>, kernel=kernel@entry=0x15b5c4bc8) at tensorflow/core/common_runtime/executor.cc:2764#6  0x00007fa3b49aaaf7 in tensorflow::FunctionLibraryRuntimeImpl::CreateKernel (this=0x36bae2c0, ndef=..., lib_def=0x372c000, kernel=0x15b5c4bc8)    at tensorflow/core/common_runtime/function.cc:539#7  0x00007fa3b49aac18 in tensorflow::FunctionLibraryRuntimeImpl::CreateKernel (this=<optimized out>, ndef=..., kernel=<optimized out>)    at tensorflow/core/common_runtime/function.cc:515#8  0x00007fa3ba11e40b in operator() (kernel=0x15b5c4bc8, ndef=..., __closure=0x2ef1e660) at tensorflow/core/common_runtime/direct_session.cc:1261#9  std::_Function_handler<tensorflow::Status(const tensorflow::NodeDef&, tensorflow::OpKernel**), tensorflow::DirectSession::CreateExecutors(const tensorflow::CallableOptions&, std::unique_ptr<tensorflow::DirectSession::ExecutorsAndKeys>*, std::unique_ptr<tensorflow::DirectSession::FunctionInfo>*, tensorflow::DirectSession::RunStateArgs*)::<lambda(const tensorflow::NodeDef&, tensorflow::OpKernel**)> >::_M_invoke(const std::_Any_data &, const tensorflow::NodeDef &, <unknown type in /home/lesliefang/venv_python36_RN50_Debug/lib/python3.6/site-packages/tensorflow/python/_pywrap_tensorflow_internal.so, CU 0x23b51f7a, DIE 0x23c57fc7>) (__functor=..., __args#0=..., __args#1=<optimized out>)    at /home/lesliefang/gcc63/lib/gcc/x86_64-pc-linux-gnu/6.3.0/../../../../include/c++/6.3.0/functional:1717#10 0x00007fa3b49a164e in operator() (__args#1=<optimized out>, __args#0=..., this=0x169d87cf8)    at /home/lesliefang/gcc63/lib/gcc/x86_64-pc-linux-gnu/6.3.0/../../../../include/c++/6.3.0/functional:2127#11 tensorflow::(anonymous namespace)::ExecutorImpl::Initialize (this=this@entry=0x169d87ce0) at tensorflow/core/common_runtime/executor.cc:620#12 0x00007fa3b49a3646 in tensorflow::NewLocalExecutor (params=..., graph=..., executor=executor@entry=0x7ffca8d44218)    at tensorflow/core/common_runtime/executor.cc:2749#13 0x00007fa3b49a36d2 in tensorflow::(anonymous namespace)::DefaultExecutorRegistrar::Factory::NewExecutor (this=<optimized out>, params=...,    graph=..., out_executor=0x3ab72bb8) at tensorflow/core/common_runtime/executor.cc:2785#14 0x00007fa3b49a61b2 in tensorflow::NewExecutor (executor_type=..., params=..., graph=..., out_executor=out_executor@entry=0x3ab72bb8)    at tensorflow/core/common_runtime/executor_factory.cc:82#15 0x00007fa3ba128ee4 in tensorflow::DirectSession::CreateExecutors (this=this@entry=0x2edd8480, callable_options=...,    out_executors_and_keys=out_executors_and_keys@entry=0x7ffca8d448a0, out_func_info=out_func_info@entry=0x7ffca8d448b0,    run_state_args=run_state_args@entry=0x7ffca8d44fb0) at tensorflow/core/common_runtime/direct_session.cc:1296#16 0x00007fa3ba12a730 in tensorflow::DirectSession::GetOrCreateExecutors (this=this@entry=0x2edd8480, inputs=..., outputs=..., target_nodes=...,    executors_and_keys=0x7ffca8d44f48, run_state_args=0x7ffca8d44fb0) at tensorflow/core/common_runtime/direct_session.cc:1429    #17 0x00007fa3ba12b747 in tensorflow::DirectSession::Run (this=<optimized out>, run_options=..., inputs=..., output_names=..., target_nodes=...,    ---Type <return> to continue, or q <return> to quit---        outputs=0x7ffca8d45340, run_metadata=0x7ffca8d453a0) at tensorflow/core/common_runtime/direct_session.cc:749    #18 0x00007fa3b76729f1 in tensorflow::SessionRef::Run (this=0x38d4a5f0, run_options=..., inputs=..., output_tensor_names=...,        target_node_names=..., outputs=0x7ffca8d45340, run_metadata=0x7ffca8d453a0) at tensorflow/python/client/session_ref.cc:427    #19 0x00007fa3b78c2d9d in TF_Run_Helper (session=0x38d4a5f0, handle=handle@entry=0x0, run_options=run_options@entry=0x0, input_pairs=...,        output_tensor_names=..., c_outputs=c_outputs@entry=0x7ffca8d45708, target_oper_names=..., run_metadata=0x0, status=0x2b657788)        at tensorflow/c/c_api.cc:787    #20 0x00007fa3b78c3a3a in TF_SessionRun (session=session@entry=0x3b57ef60, run_options=run_options@entry=0x0, inputs=<optimized out>,        input_values=<optimized out>, ninputs=<optimized out>, outputs=0x36bbfc00, output_values=0x7ffca8d45708, noutputs=1, target_opers=0x0,        ntargets=0, run_metadata=0x0, status=0x2b657788) at tensorflow/c/c_api.cc:2638    #21 0x00007fa3b76710df in tensorflow::TF_SessionRun_wrapper_helper (session=0x3b57ef60, handle=handle@entry=0x0, run_options=0x0, inputs=...,        input_ndarrays=..., outputs=..., targets=..., run_metadata=0x0, out_status=0x2b657788, py_outputs=0x7ffca8d45a50)        at tensorflow/python/client/tf_session_helper.cc:410    #22 0x00007fa3b76711b2 in tensorflow::TF_SessionRun_wrapper (session=<optimized out>, run_options=<optimized out>, inputs=..., input_ndarrays=...,        outputs=..., targets=..., run_metadata=0x0, out_status=0x2b657788, py_outputs=0x7ffca8d45a50)        at tensorflow/python/client/tf_session_helper.cc:452    #23 0x00007fa3b760b8d0 in _wrap_TF_SessionRun_wrapper (args=<optimized out>)        at bazel-out/k8-dbg/bin/tensorflow/python/pywrap_tensorflow_internal.cc:20508

关键代码分析:
op_kernel.cc:1302 CreateOpKernel 函数

12345
// Everything needed for OpKernel construction.OpKernelConstruction context(    device_type, device, allocator, &node_def, op_def, flib, inputs,    input_memory_types, outputs, output_memory_types, graph_def_version, &s);*kernel = registration->factory->Create(&context);

OpKernelConstruction context 构造了找寻合适的 tensorflow 的条件

总结:tensorflow 这边 node 的多态有两层

  • 第一层是在 tensorflow 自己框架的设计上,在 session.run 的时候,第一次运行创建 exectuor 的时候进行
  • 第二层多态是 mkldnn 层面上的,在调用 op.Compute 的方法的时候,第一次调用会去根据输入的数据类型选择并创建正确的 mkldnn 的 pd

INT8 化操作

理论介绍:
https://aidc.gallery.video/detail/videos/all-videos/video/5790616836001/understanding-new-vector-neural-network-instructions-vnni

重点推荐这篇文章,介绍量化很详细
https://petewarden.com/2016/05/03/how-to-quantize-neural-networks-with-tensorflow/

基本思想:

  • 对于输入的张量
    每一个 FP32 的输入张量,额外通过一个 Min Op 得到最小值 Min,通过一个 Max op 得到最大值 Max。原始 FP32 张量,和 Min 以及 Max 一起过一个 quantize 的 op 得到 INT8 的张量,再过 INT8 的计算 op(POOL,Conv2D)。再将计算结果,和 Min 以及 Max 值一起过一个 Dequantize 的 op 反量化得到 FP32 的输出
    如果邻近两个节点都是 INT8 的量化操作,它们之间的反量化和量化操作可以省略
  • 对于原来存储的 FP32 格式的 weight 以及 bias
    直接 INT8 化存储就可以了,存 INT8 值以及 Min 以及 Max

TF1.10 版本

transform_graph 工具

tensorflow/tools/graph_transforms 目录下面有个 readme 去介绍怎么做的
包括 transform_graph 里面每个 trainform 操作做了什么
这一步不是必须的
对原来的 FP32 的图做一些预处理的操作
每个操作的内容都写在–transforms 参数里面,生成一个列表
每一个操作在对应的文件里面通过

1
REGISTER_GRAPH_TRANSFORM("fold_batch_norms", FoldBatchNorms);

函数写到 transform_registry 里面

在主函数里面遍历–transforms 的输入列表,从 transform_registry 里面找到对应操作的函数,执行操作,返回新的 graph_def

quantize_graph.py 脚本

这一步是必须的
这个脚本的作用:

  1. 是把原来图中的 op 转换成对应的 INT8 操作的 op,比如 conv2D 转换成 QuantizedConv2DWithBias 或者 QuantizedConv2DWithBiasAndRelu 或者等等等
  2. 同时插入量化和反量化计算的节点,额外得到 Min,Max 以及 quantize 和 dequantize 的 op
  3. weights 的量化操作也是在这一步做的,将 FP32 的 weights 值存成 INT8 的,有个 quantize_weight_eightbit 函数,将 base_name 对应的 fp32 节点换成 int8,min,max 3 个节点

转换之后多了几个节点:
输入计算节点之前:

  • Min:计算输入张量的最小值
  • Max:计算输入张量的最大值
  • QuantizeV2: 输入 FP32,Min,Max 计算量化的 INT8 输出,输入计算节点
    输入计算节点之后:计算节点的输出:比如量化卷积计算的输出是 INT32 的 (MKLDNN x8s8X32 的 primitive)
  • RequantizationRange:因为输出是 INT32 的,而且量化成 INT8 的 scale 不在原始图里面存着,需要再量化一次,通过 RequantizationRange 去计算 INT32 张量的最大值和最小值
  • Requantize:具体计算 INT32 输出量化成 INT8
  • Dequantize:INT8 结果反量化成 FP32 的格式

插入 log 节点,并得到每一层的参数范围

这一步是必须的
使用 transform_graph 工具 插入 log 节点

Freeze Re-quantization Range

因为量化卷积 (mkldnn) 输出是 INT32 的,需要重新量化成 INT8,而且量化成 INT8 的 scale 不在原始图里面存着,所以通过这一步,做一次 inference,记录 scala,去需要再量化一次,通过 RequantizationRange 去计算
如果量化节点的输出已经是 INT8 的格式 (比如 Maxpool 节点),就不需要 Re-quantization
这一步 freeze 之后就没有 RequantizationRange 这个节点了 只保留了量化的 scala

  • 找到 RequantizationRange 这个节点,在这个节点后面插入一个 Print 节点去打印输出数据的范围信息
    RequantizationRange 节点似乎是跟在 Conv2D 节点后面的,打印 Conv2D 的 INT32 输出的最大值和最小值
  • 选取一部分训练数据,进行 inference,记录最大和最小值(Print 节点会打出来的),保存成 min_max.log 文件
    python Inference.py 2> min_max.log
    因为 Print 节点的输出是 error 所以用 2 去重定向就可以了
  • 利用 min_max.log 和 transform_graph 工具去 freeze 这个 requantization_ranges 这个节点,把节点值变成常量,加快运算
    freeze 之后就没有 RequantizationRange 这个节点了
    freeze 之后把这个 requantization_ranges 节点通过 2 个 const(name/frozen_min 和 name/frozen_max) 替换了
    tensorflow/tools/graph_transforms 里面的 readme 有介绍 freeze_requantization_ranges 这个 transform 做了什么

Freeze max ranges

Max 节点一般在量化之前出现,计算输入张量的最大值,用于量化

  • 找到 Max 这个节点,在这个节点后面插入一个 Print 节点去打印输出数据的范围信息
  • 选取一部分训练数据,进行 inference,记录最大值(Print 节点会打出来的),保存成 max.log 文件
  • 利用 max.log 文件去 freeze Max 这个节点 ((去除 Max 节点,用大值 const 的节点去替换 name/frozen_max_only),加速 inference 的运算
    freeze 之后就没有 Max 这个节点了

Freeze min ranges

Min 节点一般在量化之前出现,计算输入张量的最小值,用于量化

  • 找到 Min 这个节点,在这个节点后面插入一个 Print 节点去打印输出数据的范围信息

  • 选取一部分训练数据,进行 inference,记录最大值(Print 节点会打出来的),保存成 min.log 文件

  • 利用 min.log 文件去 freeze Min 这个节点 (去除 Min 节点,用小值 const 节点替换 name/frozen_min_only),加速 inference 的运算
    freeze 之后就没有 Min 这个节点了

通过这几步之后,quantize_graph.py 脚本生成的 6 个节点,只剩下了三个:

  • QuantizeV2: 输入 FP32,Min,Max 计算量化的 INT8 输出,输入计算节点
    输入计算节点之后:计算节点的输出:比如量化卷积计算的输出是 INT32 的 (MKLDNN x8s8X32 的 primitive)
  • Requantize:具体计算 INT32 输出量化成 INT8
  • Dequantize:INT8 结果反量化成 FP32 的格式

Requantize 又可以和 conv 合并成一个节点

利用 transform_graph 工具

这一步不是必须的,最好运行下

  1. 因为前面几步产生了一些不需要的节点,利用 transform_graph 工具再移除一些不必要的节点 strip_unused_nodes
  2. 将 INT8 的 conv 和后面的 requantize 节点合并:fuse_quantized_conv_and_requantize

对比 FP32 以及 INT8 模型

FP32

model.pb 是训练得到的 FP32 模型
BS1 精度: 0.94085
BS128 时的 Throughput:2300 FPS

  • conv 层调用 op 是 Conv2D, 经过 mkl_layer_pass 之后对应了 TF 里面_mklconv 这个 op,对应了 TF 的 MklConvOp 这个 kernel,
    123456
    REGISTER_KERNEL_BUILDER(Name("_MklConv2D")                                                           .Device(DEVICE_CPU)                                                      .TypeConstraint<T>("T")                                                  .Label(mkl_op_registry::kMklOpLabel),                                MklConvOp<CPUDevice, float, float, float, float,                                   float, int32, false, false>);

根据这个函数去创建 MklConvOp 对象并调用 Compute 方法
对应 mkldnn 里面的 jit_avx512_common_convolution_fwd_t 这个 primitive

  • mkldnn verbose 的输出:mkldnn_verbose,exec,convolution,jit:avx512_common,forward_training,fsrc:nChw16c fwei:OIhw16i16o fbia:undef fdst:nChw16c,alg:convolution_direct,mb128_ic32oc64_ih14oh14kh5sh1dh0ph2_iw14ow14kw5sw1dw0pw2,1.41382

INT8

一步步量化得到的 INT8 模型是: min_max_frozen_int8_model.pb
BS1 精度: 0.93885
BS128 时的 Throughput: 2380.7939278833765 images/second
这个模型有两个卷积运算,第一个卷积运算没有 INT8 化,第二个卷积运算 INT8 化了
我们这里关注第二个卷积运算

  • conv 调用了 op 是 QuantizedConv2D, 经过 mkl_layer_pass 之后对应了 TF 里面_MklQuantizedConv2D 这个 op,TF 的 MklQuantizedConv2DOp 这个 kernel,MklQuantizedConv2DOp 的 Compute 方法先调用了 MklConvOp 的 Compute 的方法
    虽然也调用了 MklConvOp 的 Compute 的方法
    但是 MklQuantizedConv2DOp 这个 kernel 是通过
    12
    MklConvOp<Device, quint8, qint8, Tbias, Toutput, Ttemp_output, int32,          biasEnabled, false>::Compute(context);

去创建 MklConvOp 对象并调用 Compute 方法,和 FP32 的模板参数类型不一样对应了 Tinput, Tfilter 以及 Toutput
因为模板参数不一样,调用 MklConvOp 的 compute 方法的时候对应找到的对应的 mkldnn 的 pd 也不一样,所以对应的 mkldnn 的 primitive 也不一样
通过看 mkldnn 的 cpu_engine.cpp 的 cpu_impl_list 怀疑对应了 mkldnn 的 jit_avx512_core_x8s8s32x_convolution_fwd_t
这个 primitive
如何证实:

  1. 通过 gdb 去 debug,证实了猜想
  2. export MKLDNN_JIT_DUMP=1 去看 dump 出来的 bin,里面果然有 mkldnn_dump__jit_avx512_core_x8s8s32x_conv_fwd_ker_t.23.bin
    jit_avx512_core_x8s8s32x_convolution_fwd_t 这个 op 里面在满足输入条件时会去调用 VNNI 的指令集 VPDPBUSD

问题
在运行测试时,dump jit-bin 去查看是否调用了指令

1
xed64 -ir mkldnn_dump__jit_avx512_core_x8s8s32x_conv_fwd_ker_t.23.bin | grep vpdpbusd

我们这里没有看到调用 VNNI 的指令集
重要 加-64 选项

1
xed64 -ir mkldnn_dump__jit_avx512_core_x8s8s32x_conv_fwd_ker_t.23.bin -64 | grep vpdpbusd

这样就可以看到 VPDPBUSD 的指令被 dump 出来了
我们单步调试,看到 mkldnn 里面 jit_avx512_core_x8s8s32x_convolution_fwd_t 里面 jcp_ver 是 ver_vnni
同时 compute_ker 函数 (jit_avx512_core_x8s8s32x_conv_kernel.cpp) 里面的 cpmpute 部分也的确调用到了 vpdpbusd

  • mkldnn verbose 的输出:
    mkldnn_verbose,exec,convolution,jit_int8:avx512_core,forward_training,fsrc:nhwc fwei:OIhw4i16o4i fbia:undef fdst:nhwc,alg:convolution_direct,mb128_ic32oc64_ih14oh14kh5sh1dh0ph2_iw14ow14kw5sw1dw0pw2,0.535156

看到精度只掉了 0.002
但是 Throughput 也没有显著提高
但是只看这一层的性能从 1.4ms 提高到了 0.53ms

GDB 打印变量值

  • Tensor 变量
    12
    Tensor* outp *(unsigned long *)(out->buf_->data_)

out->buf->data 是数据指针 const
根据代码里面数据类型,转换成 unsigned long
类型
再 * 取指针值

  • nodedef 和 grahdef
    都是定义在 tensorflow/core/framework/.proto 文件里面
    用 DebugString 可以打印值
    p nodedef->DebugString()
    p grahdef->DebugString()

编写和触发单元测试

编写

在编写单元测试的时候可以参考 Netron 查看的模型结构

在 tensorflow/python/kernel_tests/ 目录下面写在对应的单元测试的文件里面
比如之前写的 concat 的单元测试,测试是否成功创建 concat op
tensorflow/python/kernel_tests/concat_op_test.py
写在这个文件目录下面

触发

12345678
# All tests (for C++ changes).$bazel test //tensorflow/...# All Python tests (for Python front-end changes).bazel --output_user_root=$build_dir test --config=mkl --copt=-O3 //tensorflow/python/...# 只想运行一个文件bazel --output_user_root=$build_dir test --config=mkl --copt=-O3 //tensorflow/python/kernel_tests:concat_op_test

语法检查

pylint 检查 python 语法规范

1234
pip install pylint## rcfile 文件 指定了pylint使用的规则export rcfile=$tensorflow_root/tensorflow/tools/ci_build/pylintrcpylint --rcfile=$rcfile $tensorflow_root/tensorflow/python/kernel_tests/concat_op_test.py

输出可能有很多语法不规范,选择和自己这个 commit 相关的不规范语法去修改

clang-format 检查 C++ 语法规范

12345678
#ubuntu:apt-get install clang-format#centos:#http://releases.llvm.org/download.html 下载预编译版本,现在似乎用不了
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-8.0.1/clang+llvm-8.0.1-powerpc64le-linux-rhel-7.4.tar.xz
clang-format -style=Google mkl_concat_op.cc 2>&1 | tee mkl_concat_op.cc.bkdiff mkl_concat_op.cc mkl_concat_op.cc.bk

运行之后会生成一个标准语法的文件版本,和自己的版本的代码对比,修改语法不规范的地方

Appendix

Intel 优化版本的介绍

https://www.youtube.com/watch?v=VI5vjB6-zNE

No Reply at the moment.
You need to Sign in before reply, if you don't have an account, please Sign up first.