很早就想读读tensorflow的白皮书,由于拖延症直到现在才读。
Google分别在2015和2016发布了两份tensorflow白皮书, 内容各有侧重。
先读15年的, 它偏向于浅显的框架整体特性介绍。
1. Introduction
主要内容有两点:tensorflow的由来,tensorflow的特点
在Tensorflow之前, Google Brain(始于2011)年就已经开发出DistBlief框架。在与众多机构/部门合作使用DistBlief的过程中, Google Brain 开发团队积累了大量相关经验,了解了在研发、测试、生产阶段人们对机器学习系统的期望与需求。根据这些需求,团队开发了tensorflow框架。在2015年12月开源之前,已经在google内部大量使用。
Tensorflow主要有以下几个特点:
- 是一个通用的机器学习算法框架, 可以完成算法的描述(Computation Graph)与运算
- 可运行在多种平台上:移动设备,单机/集群,CPU/GPU
- 跨平台迁移需要改代码,但较少
文章之外我还想提一点, Google开源的是tensorflow的API与一个参考实现。Google内部(应该)不使用这个实现,因为他们可以使用TPU(Tensorflow Processing Unit, 一种专门为tensorflow定制的芯片),而我们只能使用GPU/CPU。
2. Programming models and basic concepts
- Computation Graph: 计算过程由计算图表示。
- Node:图的节点。每个节点都可以有多个输出或输出。
- Edge: 分为两类。一类是有数据流动的边, 一类是没有数据流动的边,用于控制执行顺序
- Tensor: 多维数组,在computation graph的数据边上流动
- Operation:操作节点, 在抽象层次上定义运算操作的逻辑。它有自己的属性(Attributes),用于实现多态,例如Add操作,可以是Integer + Integer,也可以是Float + Float。
- Kernel:Operation的实现,例如CPU/GPU/TPU版本的tensorflow,使用相同的Operations, 但使用不同的Kernels。Operation与 Kernel都是通过注册方式添加。
- Variable: 变量,用于存储模型中的可变参数
- Session:用户与Computation Graph的交互都要通过Session(会话)来完成。Session对Graph有两种操作: extend与run。(Run很清楚,但extend还没了解/直接碰到过)
3. Implementation
Client, Master, Worker, Device
无论是单/多 机/卡,tensorflow程序的运行过程都可以概括为:我们在Client上创建好Graph后,通过Session与Master交互,Master控制Worker执行Graph的运算过程。
具体的运算由Worker(在Master的指挥下)在运算设备上进行,例如CPU/GPU。每个设备都有自己的编号,例如/cpu:0
.
单机单设备
单机单设备是最简单的情景。将Graph建立好之后,用拓扑排序算法决定节点的计算顺序。
单机多设备
相比于单机单设备,单机多设备需要考虑节点的存放位置和设备间的通信。
节点存放设备选择算法
给定一个graph和可用设备,此算法通过一次模拟计算,根据人为制定的或之前的运行历史估计出各个节点在各个可用设备上需要消耗的运算和通信时间,用贪心法选出当前最优的。
设备间通信
将graph分配到多个设备上后,每个设备上都存有由各自存放的节点组成的子图。各设备的子图在graph运算过程中需要通信。Tensorflow将子图中需要与别的子图通信的节点抽象成send节点(负责传送数据到其它设备)和recv节点(负责从其它设备接收数据)。
集群
与单机多设备相似,只不过数据通信是通过远程I/O完成。
4. Extensions
这一章主要对之前的编程模型在深度上进行了拓展
自动求导
1. 基于计算图的框架与基于命令的框架相比,它的最迷人之处就是可以实现自动求导功能
2. 用户创建的graph一般只包括前向计算。训练时需要的反向计算graph由tensorflow自动构成:假如要计算
O
相对于
3. 求导路径中的前向计算中间结果在反向计算时也要用到,存储这些中间结果会消耗大量空间。不过这个问题在基于graph与命令的框架中都存在。
Partial Execution:只计算需要计算的节点
1. 每个节点都有自己的命名。一个节点可能有多个输出, 输出的命名方式为:node_name:index
, 例如W:0
2. 在运行graph时, 不一定要运行整个graph,用户可以选择运行它的任意subgraph。运行方式为:session.run(fetches, feed_dict)
。fetches
是一个list
, 代表需要计算的节点;feed_dict
是一个dict
, 代表需要注入值的节点。
3. 在运行run
方法时,会先给fetches
对应的节点之后加上fetch
节点,给feed_dict
对应的节点之前加上feed
节点, 然后从fetch
节点出发,找到它依赖的所有节点,组成一个subgraph。run
只运算这个subgraph,而不是整个graph。
其他
Tensorflow还支持:
1. 指定节点所在设备
2. 流程控制,例如FOR, WHILE
3. 提供了更高效的非feed
方式的数据节点
4. 队列
5. Optimizations(这一章还挺有用的)
Common Subexpression Elimination
为了方便写代码,在构建图的时候,我们常常有意/无意地创建一些相同的计算节点,但tensorflow在底层实现时,会将有相同输入输出的节点合并成了一个, 所以我们不用担心这些冗余的相同节点会拖慢我们的计算,大大方便了我们的编程:不用那么小心翼翼。
Controlling Data Communication and Memory Usage
主要意思是tf在显存使用和数据传输控制上做了优化。
Asynchronous Kernel, 异步计算核
例如Receive Kernel, Enqueue/Dequeue Kernel, 可以不创建线程就实现异步计算。
Optimized Libraries for Kernel Implementation,底层使用了加速框架
tf的计算核的实现是基于很多已有的加速框架如BLAS, CUDA,CUDNN。
Lossy Compression, 有损压缩
很多算法的优化过程对精度并不敏感。为了加快多设备/多机训练的数据传输,tf在传输前将32位的float压缩成16,接收方接收后再恢复到32位。恢复的过程中用损失的精度位(32-16 = 16位)用0填充,这样又在一定程度上加快了计算。(TODO有个问题,如果只使用一个device训练, 会有这个过程吗?)
6. Status and Experience, 描述了tf项目(在2015年)的状态及分享了Google工程师们在实现tf的过程中的宝贵经验。
7. Common Programming Idioms,常用编程习语
Data Parallel Training, 数据并行训练
每一个device/机器上都有一个完整的graph, 各自独立完成数据的读取与梯度计算,根据更新方式,分为同步和异步。在同步中,所有的梯度会被平均然后统一更新所有模型(在一个device/机器上更新,然后复制到所有device/机器上)。
notes: 在单机多卡时,多使用同步的数据并行训练。
Model Parallel Training, 模型并行训练
不同device/机器计算不同的子图:
notes: 单机多卡训练时, tf默认使用这种方式: 如果不指定使用的显卡及占用显存的比例,默认会占用所有可用显卡和显存,然后在所有可用设备范围内放置计算节点。它的优势是可以装下很大的模型/batch_size, 劣势是并行程度低,多卡训练的加速效果不明显。
8. Performance, 这个版本没有这一部分内容
9. Tools,TF提供的分析工具
Tensorboard,这个不必再说了
Performance tracing, 使用chrome trace功能可视化graph执行的timeline,可用于找出速度瓶颈。