天天看点

从系统和代码实现角度解析TensorFlow的内部实现原理 | 深度

雷锋网(公众号:雷锋网)按:本文作者姚健,毕业于中科院计算所网络数据实验室,曾就职于360天眼实验室,主要从事深度学习和增强学习相关研究工作。目前就职于腾讯mig事业部,从事神经机器翻译工作。

摘要

2015年11月9日,google发布深度学习框架tensorflow并宣布开源,并迅速得到广泛关注,在图形分类、音频处理、推荐系统和自然语言处理等场景下都被大面积推广。tensorflow系统更新快速,官方文档教程齐全,上手快速且简单易用,支持python和c++接口。本文依据对tensorflow(简称tf)白皮书[1]、tf github[2]和tf官方教程[3]的理解,从系统和代码实现角度讲解tf的内部实现原理。以tensorflow

r0.8.0为基础,本文由浅入深的阐述tensor和flow的概念。先介绍了tensorflow的核心概念和基本概述,然后剖析了opkernels模块、graph模块、session模块。

1. tf系统架构

1.1 tf依赖视图

tf的依赖视图如图1所示[4],描述了tf的上下游关系链。

从系统和代码实现角度解析TensorFlow的内部实现原理 | 深度

图 1 tensorflow依赖视图

tf托管在github平台,有google groups和contributors共同维护。

tf提供了丰富的深度学习相关的api,支持python和c/c++接口。

tf提供了可视化分析工具tensorboard,方便分析和调整模型。

tf支持linux平台,windows平台,mac平台,甚至手机移动设备等各种平台。

1.2 tf系统架构

图2是tf的系统架构,从底向上分为设备管理和通信层、数据操作层、图计算层、api接口层、应用层。其中设备管理和通信层、数据操作层、图计算层是tf的核心层。

从系统和代码实现角度解析TensorFlow的内部实现原理 | 深度

图2 tf系统架构

底层设备通信层负责网络通信和设备管理。设备管理可以实现tf设备异构的特性,支持cpu、gpu、mobile等不同设备。网络通信依赖grpc通信协议实现不同设备间的数据传输和更新。

第二层是tensor的opkernels实现。这些opkernels以tensor为处理对象,依赖网络通信和设备内存分配,实现了各种tensor操作或计算。opkernels不仅包含matmul等计算操作,还包含queue等非计算操作,这些将在第5章kernels模块详细介绍。

第三层是图计算层(graph),包含本地计算流图和分布式计算流图的实现。graph模块包含graph的创建、编译、优化和执行等部分,graph中每个节点都是opkernels类型表示。关于图计算将在第6章graph模块详细介绍。

第四层是api接口层。tensor c api是对tf功能模块的接口封装,便于其他语言平台调用。

第四层以上是应用层。不同编程语言在应用层通过api接口层调用tf核心功能实现相关实验和应用。

1.3 tf代码目录组织

图3是tf的代码结构视图,下面将简单介绍tf的目录组织结构。

从系统和代码实现角度解析TensorFlow的内部实现原理 | 深度

图3 tf代码目录组织结构

tensorflow/core目录包含了tf核心模块代码。

public: api接口头文件目录,用于外部接口调用的api定义,主要是session.h 和tensor_c_api.h。

client: api接口实现文件目录。

platform: os系统相关接口文件,如file system, env等。

protobuf: 均为.proto文件,用于数据传输时的结构序列化.

common_runtime: 公共运行库,包含session, executor, threadpool, rendezvous, memory管理, 设备分配算法等。

distributed_runtime: 分布式执行模块,如rpc session, rpc master, rpc worker, graph manager。

framework: 包含基础功能模块,如log, memory, tensor

graph: 计算流图相关操作,如construct, partition, optimize, execute等

kernels: 核心op,如matmul, conv2d, argmax, batch_norm等

lib: 公共基础库,如gif、gtl(google模板库)、hash、histogram等。

ops: 基本ops运算,ops梯度运算,io相关的ops,控制流和数据流操作

tensorflow/stream_executor目录是并行计算框架,由google stream executor团队开发。

 tensorflow/contrib目录是contributor开发目录。

tensroflow/python目录是python api客户端脚本。

tensorflow/tensorboard目录是可视化分析工具,不仅可以模型可视化,还可以监控模型参数变化。

third_party目录是tf第三方依赖库。

eigen3: eigen矩阵运算库,tf基础ops调用

gpus: 封装了cuda/cudnn编程库

2. tf核心概念

tf的核心是围绕graph展开的,简而言之,就是tensor沿着graph传递闭包完成flow的过程。所以在介绍graph之前需要讲述一下符号编程、计算流图、梯度计算、控制流的概念。

2.1 tensor

在数学上,matrix表示二维线性映射,tensor表示多维线性映射,tensor是对matrix的泛化,可以表示1-dim、2-dim、n-dim的高维空间。图4对比了矩阵乘法(matrix

product)和张量积(tensor

contract),可以看出tensor的泛化能力,其中张量积运算在tf的matmul和conv2d运算中都有用到。

从系统和代码实现角度解析TensorFlow的内部实现原理 | 深度

图4 tensor contract

tensor在高维空间数学运算比matrix计算复杂,计算量也非常大,加速张量并行运算是tf优先考虑的问题,如add, contract, slice, reshape, reduce, shuffle等运算。

tf中tensor的维数描述为阶,数值是0阶,向量是1阶,矩阵是2阶,以此类推,可以表示n阶高维数据。

tf中tensor支持的数据类型有很多,如tf.float16,

tf.float32, tf.float64, tf.uint8, tf.int8, tf.int16, tf.int32,

tf.int64, tf.string, tf.bool, tf.complex64等,所有tensor运算都使用泛化的数据类型表示。

tf的tensor定义和运算主要是调用eigen矩阵计算库完成的。tf中tensor的uml定义如图4。其中tensorbuffer指针指向eigen::tensor类型。其中,eigen::tensor[5][6]不属于eigen官方维护的程序,由贡献者提供文档和维护,所以tensor定义在eigen

unsupported模块中。

从系统和代码实现角度解析TensorFlow的内部实现原理 | 深度

图5 tensor数据结构定义

图5中,tensor主要包含两个变量m_data和m_dimension,m_data保存了tensor的数据块,t是泛化的数据类型,m_dimensions保存了tensor的维度信息。

eigen:tensor的成员变量很简单,却支持非常多的基本运算,再借助eigen的加速机制实现快速计算,参考章节3.2。eigen::tensor主要包含了

一元运算(unary),如sqrt、square、exp、abs等。

二元运算(binary),如add,sub,mul,div等

选择运算(selection),即if / else条件运算

归纳运算(reduce),如reduce_sum, reduce_mean等

几何运算(geometry),如reshape,slice,shuffle,chip,reverse,pad,concatenate,extract_patches,extract_image_patches等

张量积(contract)和卷积运算(convolve)是重点运算,后续会详细讲解。

2.2 符号编程

编程模式通常分为命令式编程(imperative style programs)和符号式编程(symbolic style programs)。

命令式编程容易理解和调试,命令语句基本没有优化,按原有逻辑执行。符号式编程涉及较多的嵌入和优化,不容易理解和调试,但运行速度有同比提升。

这两种编程模式在实际中都有应用,torch是典型的命令式风格,caffe、theano、mxnet和tensorflow都使用了符号式编程。其中caffe、mxnet采用了两种编程模式混合的方法,而tensorflow是完全采用了符号式编程,theano和tensorflow的编程模式更相近。

命令式编程是常见的编程模式,编程语言如python/c++都采用命令式编程。命令式编程明确输入变量,并根据程序逻辑逐步运算,这种模式非常在调试程序时进行单步跟踪,分析中间变量。举例来说,设a=10, b=10,计算逻辑:

从系统和代码实现角度解析TensorFlow的内部实现原理 | 深度

第一步计算得出c=100,第二步计算得出d=101,输出结果d=101。

符号式编程将计算过程抽象为计算图,计算流图可以方便的描述计算过程,所有输入节点、运算节点、输出节点均符号化处理。计算图通过建立输入节点到输出节点的传递闭包,从输入节点出发,沿着传递闭包完成数值计算和数据流动,直到达到输出节点。这个过程经过计算图优化,以数据(计算)流方式完成,节省内存空间使用,计算速度快,但不适合程序调试,通常不用于编程语言中。举上面的例子,先根据计算逻辑编写符号式程序并生成计算图

从系统和代码实现角度解析TensorFlow的内部实现原理 | 深度

其中a和b是输入符号变量,c和d是运算符号变量,compile函数生成计算图f,如图6所示。

从系统和代码实现角度解析TensorFlow的内部实现原理 | 深度

图6 符号编程的正向计算图

最后得到a=10, b=10时变量d的值,这里d可以复用c的内存空间,省去了中间变量的空间存储。

从系统和代码实现角度解析TensorFlow的内部实现原理 | 深度

图 6是tf中的计算流图,c=f(relu(add(matmul(w, x), b))),其中每个节点都是符号化表示的。通过session创建graph,在调用session.run执行计算。

从系统和代码实现角度解析TensorFlow的内部实现原理 | 深度

图7 tf符号计算图

和目前的符号语言比起来,tf最大的特点是强化了数据流图,引入了mutation的概念。这一点是tf和包括theano在内的符号编程框架最大的不同。所谓mutation,就是可以在计算的过程更改一个变量的值,而这个变量在计算的过程中会被带入到下一轮迭代里面去。

mutation是机器学习优化算法几乎必须要引入的东西(虽然也可以通过immutable

replacement来代替,但是会有效率的问题)。 theano的做法是引入了update

statement来处理mutation。tf选择了纯符号计算的路线,并且直接把更新引入了数据流图中去。从目前的白皮书看还会支持条件和循环。这样就几乎让tf本身成为一门独立的语言。不过这一点会导致最后的api设计和使用需要特别小心,把mutation

引入到数据流图中会带来一些新的问题,比如如何处理写与写之间的依赖。[7]

2.3 梯度计算

梯度计算主要应用在误差反向传播和数据更新,是深度学习平台要解决的核心问题。梯度计算涉及每个计算节点,每个自定义的前向计算图都包含一个隐式的反向计算图。从数据流向上看,正向计算图是数据从输入节点到输出节点的流向过程,反向计算图是数据从输出节点到输入节点的流向过程。

图8是2.2节中图6对应的反向计算图。图中,由于c=a*b,则da=b*dc, db=a*dc。在反向计算图中,输入节点dd,输出节点da和db,计算表达式为da=b*dc=b*dd, db=a*dc=a*dd。每一个正向计算节点对应一个隐式梯度计算节点。

从系统和代码实现角度解析TensorFlow的内部实现原理 | 深度

图8 符号编程的反向计算图

反向计算限制了符号编程中内存空间复用的优势,因为在正向计算中的计算数据在反向计算中也可能要用到。从这一点上讲,粗粒度的计算节点比细粒度的计算节点更有优势,而tf大部分为细粒度操作,虽然灵活性很强,但细粒度操作涉及到更多的优化方案,在工程实现上开销较大,不及粗粒度简单直接。在神经网络模型中,tf将逐步侧重粗粒度运算。

2.4 控制流

tf的计算图如同数据流一样,数据流向表示计算过程,如图9。数据流图可以很好的表达计算过程,为了扩展tf的表达能力,tf中引入控制流。

从系统和代码实现角度解析TensorFlow的内部实现原理 | 深度

图9 graph的数据流

在编程语言中,if…else…是最常见的逻辑控制,在tf的数据流中也可以通过这种方式控制数据流向。接口函数如下,pred为判别表达式,fn1和fn2为运算表达式。当pred为true是,执行fn1操作;当pred为false时,执行fn2操作。

从系统和代码实现角度解析TensorFlow的内部实现原理 | 深度

tf还可以协调多个数据流,在存在依赖节点的场景下非常有用,例如节点b要读取模型参数θ更新后的值,而节点a负责更新参数θ,则节点b必须等节点a完成后才能执行,否则读取的参数θ为更新前的数值,这时需要一个运算控制器。接口函数如下,tf.control_dependencies函数可以控制多个数据流执行完成后才能执行接下来的操作,通常与tf.group函数结合使用。

从系统和代码实现角度解析TensorFlow的内部实现原理 | 深度

tf支持的控制算子有switch、merge、enter、leave和nextiteration等。

tf不仅支持逻辑控制,还支持循环控制。tf使用和mit token-tagged machine相似的表示系统,将循环的每次迭代标记为一个tag,迭代的执行状态标记为一个frame,但迭代所需的数据准备好的时候,就可以开始计算,从而多个迭代可以同时执行。

未完待续……

本文作者:深度学习大讲堂