标签:second *** efi pack plain temp ready csv 注意
方法1:
|
|
方法2:
|
|
视频: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 调用
TF代码又两个函数打印日志,LOG以及VLOG
LOG是正常的打印日志,通过TF_CPP_MIN_LOG_LEVEL
|
|
去设置,值越小,打印日志越多
VLOG通过
|
|
去设置,但是VLOG只有在LOG等级为0的时候设置才有用
比如要打印mkl_layout_pass.cc初始化rewirte op时的信息
|
|
添加 -c dbg选项
移除优化选项 –copt=-O3 以及 -c opt
|
|
debug版本编译完大概有20G左右
export OMP_NUM_THREADS=1
设置intra和inter值为1
默认编译在/root/.cache/bazel目录下面,有时候root目录空间不够
|
|
使用gcc6.3以及以上版本,低版本的编译器不认识broadwell的选项
https://github.com/tensorflow/tensorflow/issues/5538
以TF从0.18升级到0.19为例
|
|
搜索mkl_dnn
|
|
需要修改”//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
二种方法方法去debug TF:
method1:
|
|
method2:
|
|
如何添加python的信息 参考这个blog
http://jcf94.com/2018/01/13/2018-01-13-tfunpacking/
dir 目录
去指定文件的搜索根目录
使用gdbgui去调试的时候,也需要指定了目录之后才可以显示文件
所有并行计算线程设置为1,避免多线程导致断点带来的麻烦
命令后加&echo $! 输出PID,进行gdb -p的调试
|
|
在运行测试之前,添加环境变量
可以打出mkldnn的信息
每一行的信息Each line with verbose information is formatted as a comma-separated list containing:
看baseSession
里面调用了tf_session
|
|
看pywrap_tensorflow.py
这个就是对应了编译出来的so文件
在source insight里面搜索TF_NewSessionRef
看到定义在tf_session_help.cc里面
里面调用了TF_NewSession
source insight里面搜索TF_NewSession
已经进入到C++ 代码内部
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)
|
|
ops/math_ops.py:2277:@tf_export(“linalg.matmul”, “matmul”)
看math_ops.py:2277
api的使用有详细的解释
调用了gen_math_ops.batch_mat_mul 或者 gen_math_ops.mat_mul
看gen_math_ops.py
|
|
这个文件看文件名字,应该是在编译的时候生成的
这个文件里面搜:batch_mat_mul
|
|
所以C++里面的op函数应该是BatchMatMul
|
|
搜索op的kernel实现
|
|
找到所有定义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++中调用的数学平台,里面实现了很多通用的数学运算。
这个人博客很多好文章: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/
|
|
tensorflow_src/test_code/private-tensorflow/tensorflow/python/ops/nn_ops.py:1376:@tf_export(“nn.conv2d”, v1=[])
查找输出的地方
|
|
查看op注册和实现的地方
|
|
进入conv_ops.cc文件
看Compute方法
输入为浮点数float调用LaunchDeepConvOp
其它输入类型调用launcher_
进一步看调用到了
LaunchConv2DOp
再往下
tensorflow::LaunchGeneric::operator
这个函数里面通过不同的条件判断调用两个不同的计算kernel:functor::MatMulConvFunctor
MatMulConvFunctor定义在conv_2d.h文件里面
out.device(d) = in0.contract(in1, dim_pair, output_kernel);
到最后还是调用了矩阵乘法的函数
这个contract应该是eigen库提供的接口
搜索不到对应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:
tf_export decorators
搜索这个op
|
|
这个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
|
|
对应的kernel是MklQuantizedConv2DSumReluOp
继承了MklQuantizedConv2DOp这个kernel
MklQuantizedConv2DOp这个kernel继承了MklConvOp
MklQuantizedConv2DOp的compute方法首先调用了
|
|
MklConvOp里面的compute方法调用了mkldnn
conv_fwd->Execute执行mkldnn的计算
注意
class MklConvOp在这个文件里面有两个类的定义
通过template
根据文件里面的宏的定义,应该只有一个函数会被编译出来
看这个mkldnn的类的实现代码,可以先看看MKLDNN的教程和实例代码mkldnn代码库的simple_net.cpp以及解释
基本概念比较清晰,先创建memory/operator descriptor,再创建对应的Primitive descriptor ,最后创建primitive,然后把primitive放到stream里面去执行
tensorflow的这个类的实现follow这个逻辑只是加了一些封装
至于mkldnn里面进一步的实现(如何多线程等)就是mkldnn的事情了
可以看我的mkldnn的文章
|
|
|
|
经过前面两步在编译之后,可以在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 文件中添加接口
|
|
重新编译之后安装之后
测试代码
|
|
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开始分析
1.
session.run
|
|
在_run里面
|
|
do_run里面
|
|
call_tf_sessionrun里面
|
|
TF_SessionRun_wrapper 定义在pywrap_tensorflow_internal.py里面
就是python和C++的桥梁
TF_SessionRun_wrapper_helper函数
里面调用了TF_SessionRun
TF_SessionRun 函数
调用了TF_Run_Helper函数
TF_Run_Helper函数
调用了session->Run函数
这是个虚函数
用gdb跟进去看
参考这篇文章:https://zhuanlan.zhihu.com/p/26031658
local用direction_session
分布式用grpc_session
所以我们这边调用到了DirectSession::Run
看DirectSession::Run函数
这个函数的分析:http://jcf94.com/2018/01/13/2018-01-13-tfunpacking/
|
|
我理解就是在这里实现了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
|
|
里面调用了NewLocalExecutor
进一步调用ExecutorImpl->Initialize函数
这个函数里面调用了params_.create_kernel函数去创建kernel
(这个create_kernel函数就是之前在CreateExecutors函数里面定义的)
同时在这个函数里面看到了一行注释
|
|
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 大专栏 tensorflow二次开发(….) 的时候会根据 python 层的图构造出 C++ 层的图然后保存下来,之后如果下次 sess.run() 的目标节点是相同的,就不需要重新构造一遍了。详细可以去分析 sess.run() 的执行流程)
RunInternal函数
里面调用了item.executor->RunAsync(args, barrier->Get());
去执行异步计算
通过日志知道RunAsync会调用到executor的Process()函数
process函数做了什么:
http://jcf94.com/2018/01/13/2018-01-13-tfunpacking/
遍历每个节点,针对每个节点的kernel进行计算(调用device->Compute,里面调用op_kernel->Compute(context);)
在每个kernel里面都可以搜索到对应的Compute函数
断点打在
|
|
汾西代码知道这个setup函数是设置上下文变量的
查看调用栈
|
|
|
|
然后通过这个desc去创建primitive_desc(pd),跟进到mkldnn里面看,就是在创建pd的时候回去遍历mkldnn里面所有pd找到对应的满足条件的pd
总结,关键是这个MklIPOp的compute方法,先通过Get方法去获得对应的mkldnn的kernel,然后调用execute去执行
使用tensorboard或者Netron
推荐使用Netron,很好用,里面还可以看到各个节点的参数的值
|
|
打印出node的名字
比如其中一个MatMul
|
|
跑完之后,命令行运行
tensorboard –logdir log/
|
|
因为注册的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
|
|
关键代码分析:
op_kernel.cc:1302 CreateOpKernel函数
|
|
OpKernelConstruction context构造了找寻合适的tensorflow的条件
总结:tensorflow这边node的多态有两层
重点推荐这篇文章,介绍量化很详细
https://petewarden.com/2016/05/03/how-to-quantize-neural-networks-with-tensorflow/
基本思想:
TF1.10版本
tensorflow/tools/graph_transforms 目录下面有个readme去介绍怎么做的
包括transform_graph里面每个trainform操作做了什么
这一步不是必须的
对原来的FP32的图做一些预处理的操作
每个操作的内容都写在–transforms参数里面,生成一个列表
每一个操作在对应的文件里面通过
|
|
函数写到transform_registry里面
在主函数里面遍历–transforms的输入列表,从transform_registry里面找到对应操作的函数,执行操作,返回新的graph_def
这一步是必须的
这个脚本的作用:
转换之后多了几个节点:
输入计算节点之前:
这一步是必须的
使用transform_graph 工具 插入log节点
因为量化卷积(mkldnn)输出是INT32的,需要重新量化成INT8,而且量化成INT8的scale不在原始图里面存着,所以通过这一步,做一次inference,记录scala,去需要再量化一次,通过RequantizationRange去计算
如果量化节点的输出已经是INT8的格式(比如Maxpool节点),就不需要Re-quantization
这一步 freeze之后就没有RequantizationRange这个节点了 只保留了量化的scala
Max节点一般在量化之前出现,计算输入张量的最大值,用于量化
Min节点一般在量化之前出现,计算输入张量的最小值,用于量化
找到Min这个节点,在这个节点后面插入一个Print节点去打印输出数据的范围信息
选取一部分训练数据,进行inference,记录最大值(Print节点会打出来的),保存成min.log文件
通过这几步之后,quantize_graph.py 脚本生成的6个节点,只剩下了三个:
Requantize 又可以和conv合并成一个节点
这一步不是必须的,最好运行下
model.pb是训练得到的FP32模型
BS1 精度: 0.94085
BS128时的Throughput:2300 FPS
|
|
根据这个函数去创建MklConvOp对象并调用Compute方法
对应mkldnn里面的jit_avx512_common_convolution_fwd_t这个primitive
一步步量化得到的INT8模型是: min_max_frozen_int8_model.pb
BS1 精度: 0.93885
BS128时的Throughput: 2380.7939278833765 images/second
这个模型有两个卷积运算,第一个卷积运算没有INT8化,第二个卷积运算INT8化了
我们这里关注第二个卷积运算
|
|
去创建 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
如何证实:
问题
在运行测试时,dump jit-bin去查看是否调用了指令
|
|
我们这里没有看到调用VNNI的指令集
重要 加-64选项
|
|
这样就可以看到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
看到精度只掉了0.002
但是Throughput也没有显著提高
但是只看这一层的性能从1.4ms提高到了0.53ms
|
|
out->buf->data 是数据指针 const
根据代码里面数据类型,转换成unsigned long 类型
再*取指针值
在编写单元测试的时候可以参考Netron查看的模型结构
在tensorflow/python/kernel_tests/ 目录下面写在对应的单元测试的文件里面
比如之前写的concat的单元测试,测试是否成功创建concat op
tensorflow/python/kernel_tests/concat_op_test.py
写在这个文件目录下面
|
|
|
|
输出可能有很多语法不规范,选择和自己这个commit相关的不规范语法去修改
|
|
运行之后会生成一个标准语法的文件版本,和自己的版本的代码对比,修改语法不规范的地方
https://www.youtube.com/watch?v=VI5vjB6-zNE
标签:second *** efi pack plain temp ready csv 注意
原文地址:https://www.cnblogs.com/dajunjun/p/11712893.html