tensorflow Profile分析神经网络性能

profiler分析TensorFlow程序性能

1.TensorFlow profiler 功能

从r1.3版本开始, tensorflow 提供profiler模块,参见github上的官网文档

profiler打开tf执行的黑盒,以graph node(神经网络模型简称为graph,其中的节点称为node.)为细粒度,从多个维度、多个层面去统计神经网络运行的时间
和内存消耗,为进一步优化神经网络模型的运行效率提供最直接的数据依据。功能如下:

  • 分析 TensorFlow 模型架构。
    • 参数量、tensor的shape、浮点运算数、运算设备等。
  • 分析 multiple-steps 模型性能。
    • 执行时间,内存消耗。
  • 自动 分析及建议。
    • 训练加速设备使用情况的检查
    • 较耗时op的检查
    • op配置的检查
    • 分布式runtime检查(非OSS)

2.profiler 主要步骤

profiler分为数据搜集和数据显示两个主要步骤。

数据收集

  1. graph node的每一次执行,记录单步统计数据,主要是执行时间和占用内存,格式参见step_stats.proto,作为原始的最小粒度统计数据源;
  2. 每一次session.Run(),所有执行到的graph node的统计数据,都集中汇总保存到 RunMetadata 数据结构中;
  3. 用户程序把每一次搜集到的 RunMetadata 添加到profiler实例,做数据累计和加工处理。

数据显示: 数据的过滤、视图组织和显示输出
部分规则需要用户自己指定:
- 数据的过滤: 比如graph node过滤条件、 显示的字段、排序方式等。

  • 四种视图: 对应显示节点之前的不同组织方式。
    1. scope:应该是 python 层代码中用 tf.name_scope() 包起来的视图
    2. graph:TensorFlow 计算图的视图
    3. op:把 TensorFlow 计算图再细化一层
    4. code:Python 代码视图
  • 视图输出方式:
    1. time line : 输出JSON events file, 再用chrome浏览器tracing功能进行查看,可视性很棒。
    2. stdout : 标准输出设备打印。
    3. pprof file: 输出pprof的文件格式,再用pprof工具查看。
    4. file: 输出到普通的文本文件。

视图和输出方式,可以自由组合,除了部分特例不能输出,比如op view 不支持time line输出,只有code view能够输出pprof格式的文件等,
详细规则参见 Options

2.快速教程

首先,确认下载安装了 r1.3 以上的tensorflow。网络模型使用mnist.py .

import相关的包

1
2
3
4
5
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
from tensorflow.python.profiler import model_analyzer
from tensorflow.python.profiler import option_builder
12341234

定义网络模型,创建session.、
网络模型为 hidden1 + hidden2 + softmax 三层架构, hidden1和hidden2都是(Wx+b)->Relu的路径。 默认都运行在gpu:0 上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# placeholder
batch_size = 100
inputs = tf.placeholder(tf.float32, [batch_size,784])
targets = tf.placeholder(tf.int32, [batch_size])

# model
hidden1 = tf.layers.dense(inputs, 128, activation=tf.nn.relu, name='hidden1')
hidden2 = tf.layers.dense(hidden1, 32, activation=tf.nn.relu, name='hidden2')
logits = tf.layers.dense(hidden2, 10, activation=None, name='softmax_linear')

# loss + train_op
loss = tf.losses.sparse_softmax_cross_entropy(labels=targets, logits=logits)
global_step = tf.Variable(0, name='global_step', trainable=False)
train_op = tf.train.GradientDescentOptimizer(0.01).minimize(loss, global_step=global_step)


init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)

创建tfprofiler实例,作为记录、处理和显示数据的主体

1
2
profiler = model_analyzer.Profiler(graph=sess.graph)
11

定义trace level为FULL_TRACE,这样我们才能搜集到包括GPU硬件在内的最全统计数据

1
2
run_options = tf.RunOptions(trace_level = tf.RunOptions.FULL_TRACE)
11

创建RunMetadata, 用于在每次session.Run()时汇总统计数据

1
2
run_metadata = tf.RunMetadata()
11

循环执行session.Run(),搜集统计数据并添加到tfprofiler实例中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mnist = input_data.read_data_sets(train_dir='./',fake_data=False)

feed_dict = dict()
for step in range(100):
images_feed, labels_feed = mnist.train.next_batch(batch_size, fake_data=False)
feed_dict = {inputs: images_feed, targets: labels_feed}
#每 10 步,搜集一下统计数据:
if step % 10 == 0:
_, loss_value = sess.run(fetches=[train_op, loss],feed_dict=feed_dict, options=run_options, run_metadata=run_metadata)

#将本步搜集的统计数据添加到tfprofiler实例中
profiler.add_step(step=step, run_meta=run_metadata)
else:
_, loss_value = sess.run(fetches=[train_op, loss],
feed_dict=feed_dict)

接下来我们就可以显示统计视图了
定义显示option 和 视图方式

定义显示option 和 视图方式
option用于设置过滤条件、显示字段,完整option 参见Options,常用设置项目:

  • account_type_regexes:采用Google RE2规则的正则表达式,
    过滤要显示的node的op type 和 device,比如 ‘.MatMul.‘, ‘.Conv2D’, ‘.gpu:0’等。
  • select:要显示的字段:
    [bytes|micros|accelerator_micros|cpu_micros|params|float_ops|occurrence|tensor_value|device|op_types|input_shapes]
  • order_by: 显示结果排序方式:
    [name|depth|bytes|micros|accelerator_micros|cpu_micros|params|float_ops|occurrence]
  • output: 输出方式:stdout, file 或者 timeline
  • step: 显示在某个具体的Run() step的统计值. 缺省值-1,显示所有步骤的平均值。

一般来说, option和试图总是结合起来使用,这里举几个典型应用例子:

例子1:grpah view显示每个graph node运行时间,并输出到timeline

1
2
3
4
5
6
7
8
9
10
11
12
13
#统计内容为每个graph node的运行时间和占用内存
profile_graph_opts_builder = option_builder.ProfileOptionBuilder(
option_builder.ProfileOptionBuilder.time_and_memory())

#输出方式为timeline
# 输出文件夹必须存在
profile_graph_opts_builder.with_timeline_output(timeline_file='/tmp/mnist_profiler.json')
#定义显示sess.Run() 第70步的统计数据
profile_graph_opts_builder.with_step(70)

#显示视图为graph view
profiler.profile_graph(profile_graph_opts_builder.build())
123456789101112123456789101112

我们得到第70步的详细time line结果,打开chrome浏览器,输入about:tracing, 然后load “/tmp/mnist_profiler.json” 文件,这时候可以看见time line的显示结果。

  • 横向是时间轴:各device对graph node的kernel调度时间轴、执行时间轴。
  • 整个graph中所有执行到的node在devices上的运行分布。由于本例中node缺省使用gpu:0,所以cpu:0上没有执行node的分布。
  • 一个kernel的执行包括调度和执行两个阶段,这两个阶段是异步操作,所以我们看到同一个时间点, 当 gpu:0/stream 上还在执行hidden1/Matmul, 而gpu:0已经开始调度下一个node: hidden1/add 的kernel, 这样实现了最大程度上不同node 间的并发。
  • 你可以通过tf.device()将部分node分布到其他gpu上或者cpu上,看看做model parallel的结果。

例子2:scope view显示模型中的参数数量分布
通过这种方式,查看各个layer中参数的总数,以控制模型的大小和参数分布。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#统计内容为所有trainable Variable Op
profile_scope_opt_builder = option_builder.ProfileOptionBuilder(
option_builder.ProfileOptionBuilder.trainable_variables_parameter())

#显示的嵌套深度为4
profile_scope_opt_builder.with_max_depth(4)
#显示字段是params,即参数
profile_scope_opt_builder.select(['params'])
#根据params数量进行显示结果排序
profile_scope_opt_builder.order_by('params')

#显示视图为scope view
profiler.profile_name_scope(profile_scope_opt_builder.build())
1234567891011121312345678910111213

我们得到param数量从高到低的排序显示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
==================Model Analysis Report======================
node name | # parameters
_TFProfRoot (--/104.94k params)
hidden1 (--/100.48k params)
hidden1/weights (784x128, 100.35k/100.35k params)
hidden1/biases (128, 128/128 params)
hidden2 (--/4.13k params)
hidden2/weights (128x32, 4.10k/4.10k params)
hidden2/biases (32, 32/32 params)
softmax_linear (--/330 params)
softmax_linear/weights (32x10, 320/320 params)
softmax_linear/biases (10, 10/10 params)

======================End of Report==========================
0%