使用Python和LLVM生成TensorFlow计算图的JIT本机代码

更新:黑客新闻此处讨论

张力流计算图

张索洛戈

最令人惊奇的组成部分之一TensorFlow架构是计算图可以使用被序列协议缓冲区. 此计算图遵循定义良好的格式(点击这里对于proto文件)并描述您指定的计算(它可以是一个深入的学习模型,如CNN、一个简单的逻辑回归,甚至是任何您想要的计算)。例如,下面是一个非常简单的TensorFlow计算图的示例,我们将在本教程中使用它(使用TensorFlow Python API):亚洲金博宝

将tensorflow作为tf导入训练课(作为sess:input_placeholder=tf.占位符(国际贸易基金会32,1,name=“input”)子操作=特遣部队(输入占位符,tf.常数(2,数据类型=国际贸易基金会32))加法运算=tf.add公司(副总统,tf.常数(5,数据类型=国际贸易基金会32))输出=tf.add公司(添加操作,tf.常数(100,数据类型=国际贸易基金会32),name=”输出“)tf.train.write_图形(sess.graph定义, ".", "图形.pb“,正确)
计算图的表示。
计算图的表示。

如您所见,这是一个非常简单的计算图。首先,我们定义了亚洲金博宝保存输入张量的占位符,然后我们指定了应该使用该输入张量作为输入数据进行的计算。这里我们还可以看到,我们定义了这个图的两个重要节点,一个叫做输入“(上述占位符)和另一个称为”输出,它将保存最终计算的结果。此图与标量的以下公式相同:输出=((输入-2)-5)+100),其中我故意添加了冗余操作,以便稍后查看LLVM常量传播。

在代码的最后一行,我们坚持这个计算图(包括常量值)变成一个序列化的protobuf文件。决赛真的参数是输出一个文本表示而不是二进制,因此它将产生以下人类可读的输出原生buf文件(为了简洁,我省略了一部分):

整数:2}}}}--->(为简洁起见省略)<--节点{name:“output”op:“Add”input:“Add”input:“Const_2”attr{key:“T”value{type:DT_INT32}}}}版本{producer:9}

这是一个非常简单的图亚洲金博宝,而TensorFlow图是其实从来没有那么简单,因为TensorFlow模型很可能包含取决于你指定,专为深度学习模型的模型超过300个节点。

我们将使用上图展示了如何我们可以为这个简单的图形使用JIT本机代码LLVM框架

LLVM前端,IR和后端

LLVM-Logo-Derivative-1公司

该LLVM框架是构建编译器和工具链的一个非常好的,模块化和完整的生态系统。LL亚洲金博宝VM的架构如下图所示我们很重要的一个非常漂亮的描述:

LLVM编译器架构
LLVM编译器体系结构(AOSA/LLVM,Chris Lattner)

(上面的图片只是LLVM架构的一小部分,请对它进行全面的描述看到好文章摘自克里斯·拉特纳的《AOSA》一书)

Looking in the image above, we can see that LLVM provides a lot of core functionality, in the left side you see that many languages can write code for their respective language frontends, after that it doesn’t matter in which language you wrote your code, everything is transformed into a very powerful language calledLLVM IRLLVM中间表示),这是因为你可以想像,只是汇编代码本身之前的代码的中间表示。在my opinion, the IR is the key component of what makes LLVM so amazing, because it doesn’t matter in which language you wrote your code (or even if it was a JIT’ed IR), everything ends in the same representation, and then here is where the magic happens, because the IR can take advantage of the LLVM optimizations (also known as转换和分析过程)。

该IR生成后,可以将其送到任何LLVM后端,生成用于将LLVM(例如x86,ARM,PPC等)支持的架构原生代码,然后你可以用本机性能LLVM后,终于执行代码,也优化过程。

为了使用LLVM对代码进行JIT,您只需要以编程方式构建IR,创建一个执行引擎来转换(在执行时间)将IR转换为本机代码,为已JIT的函数获取一个指针,然后最终执行它。我将在这里为LLVM使用一个名为微石,这是非常蟒蛇和易于使亚洲金博宝用。

使用Python和LLVM编译张力流图

流

现在让我们使用LLVM和Python来JIT TensorFlow计算图。这是决不是全面执行,这是非常简单的亚洲金博宝方法,即假设某些事情过于简单化:一个整数闭合类型,只是一些TensorFlow操作,也是一个单个标量的支持,而不是高秩张量。

所以,让我们开始建立我们的JIT代码;首先,让我们来导入所需的包,初始化一些LLVM子系统,也定义了TensorFlow整数类型的LLVM各自类型:

从ctypes import CFUNCTYPE,导入tensorflow作为tf from谷歌protobuf从导入文本格式tensorflow.core.framework格式从导入图形tensorflow.core.framework格式从导入类型tensorflow.python.framework导入操作导入微石.ir全部导入llvmlite.绑定作为llvmllvm.initialize初始化() llvm.initialize本地目标() llvm.initialize本地打印机()TYPE_TF_LLVM={types_pb2.DT_INT32:ll.IntType型(32),}

之后,我们定义一个类来打开TensorFlow导出的图,并声明一个方法来按名称获取图的节点:

类TFGraph(object):def初始(self,filename=“图形.pb“,二进制=False):自图定义=打开时的graph_pb2.GraphDef(“图形.pb“,”rb”)作为f:if二进制:自图定义.ParseFromString(f.read())其他:文本_格式化.合并(f.读(),自图定义)def get_node(self,name):中的节点自图定义.节点:如果node.name节点名称==名称:返回节点

让我们首先定义我们的主要函数,它将是代码的起点:

def run_main():graph=TFGraph(“图形.pb“,False)输入节点=graph.get_节点(“输入”)输出节点=graph.get_节点(“输出”)输入类型=输入类型_节点属性[“dtype”].type]output_type=type_TF_LLVM[输出_节点属性[“T”].type]模块=ll.模块(函数类型=ll.功能类型(输出类型,[输入类型])func=ll.函数(模块,func_type,name='tensorflow_graph')函数参数[0].name='输入'bb_项=func.append_basic_块(“entry”)iru生成器=ll.IRBuilder公司(bb U条目)

正如您在上面的代码中看到的,我们打开序列化的protobuf图,然后获取该图的输入和输出节点。之后,我们还将两个图节点的类型(输入/输出)映射到LLVM类型(从TensorFlow integer到LLVM integer)。我们首先定义一个LLVM模块,它是所有IR对象的顶级容器。LLVM中的一个模块可以包含许多不同的函数,这里我们只创建一个表示图形的函数,这个函数将接收同一类型输入节点的输入数据作为输入参数,然后返回同一类型输出节点的值。

之后,我们开始创建函数的入口块,并使用这个块实例化我们的IR Builder,这个对象将为我们提供TensorFlow图的JIT操作的构建块。

现在让我们定义一个函数,它将完成将TensorFlow节点转换为LLVM IR的实际工作:

def build_graph(ir_builder, graph, node): if节点。添加:left_op_node = graph.get_node(node.input[0]) right_op_node = graph.get_node(node.input[1]) left_op = build_graph(ir_builder, graph, left_op_node) right_op = build_graph(ir_builder, graph, right_op_node)返回ir_builder。添加(left_op, right_op) if节点。left t_op_node = graph.get_node(node.input[0]) right_op_node = graph.get_node(node.input[1]) left_op = build_graph(ir_builder, graph, left_op_node) right_op = build_graph(ir_builder, graph, right_op_node)返回ir_builder子(left_op, right_op)如果节点。function_args = ir_builder.function。在function_args中:如果arg.name == node.name:如果节点,返回RuntimeError("Input [{}] not found !".format(node.name))。op == "Const": llvm_const_type = TYPE_TF_LLVM[node.attr["dtype"]。llvm_const_value = llvm_const_type(const_value)返回llvm_const_value

在这个函数中,我们接收的参数IR生成器,我们之前创建的图形类和输出节点。该函数将递归通过IR生成器来构建LLVM IR。在这里你可以看到,我只能从TensorFlow图实施的加/减/占位符和const操作,只是为了能够支持我们先前定义的图表。

在此之后,我们只需要定义一个函数,该函数将接受一个LLVM模块,然后创建一个执行引擎,该引擎将在LLVM IR上执行LLVM优化,然后再执行将IR转换为本机x86代码的艰苦工作:

创建引擎(模块):功能=llvm.get_host_cpu_功能().flatten()llvm_模块=llvm.parse_程序集(str(module))目标=llvm.Target.from_default_三元组(目标计算机)target.create_target_机器(opt=3,features=features)引擎=llvm.create_mcjit_编译器(llvm_模块,目标机器)engine.finalize_对象()打印目标_机器发射组件(llvm_模块)返回引擎

在上面的代码中,您可以看到,我们首先将CPU特性(SSE等)放入一个列表中,然后解析模块中的LLVM IR,然后使用最大优化级别(opt=3,大致相当于GCC-O3参数)创建一个引擎,我们还打印程序集代码(在我的例子中,是由LLVM构建的x86程序集)。

在这里我们刚刚完成了run_main ()功能:

ir_build .ret(ret) with open("output ")cfunc = CFUNCTYPE(c_int, c_int)(func_ptr) ret = cfunc(10) print "执行输出:{}".format(ret)

正如您在上面的代码中看到的,我们只需要调用生成图形()方法,然后使用IR Builder添加“ret”LLVM IR指令(ret=return)以返回我们刚刚基于TensorFlow图创建的IR函数的输出。我们还在这里将IR输出写到一个外部文件,稍后我将使用这个LLVM IR文件为其他不同的体系结构(比如ARM体系结构)创建本机程序集。最后,只需获取本机代码函数地址,为该函数创建一个Python包装器,然后用参数“10”调用它,这将是输入数据,然后输出结果输出值。

当然,这只是过于简单化,但现在我们了解了对TensorFlow模型使用JIT的好处。

输出LLVM-IR、优化和多体系结构(ARM、PPC、x86等)的优势

例如,让我们创建以下TensorFlow图的LLVM IR(使用上面显示的代码):

将tensorflow作为tf导入训练课(作为sess:input_placeholder=tf.占位符(国际贸易基金会32,1,name=“input”)子操作=特遣部队(输入占位符,tf.常数(2,数据类型=国际贸易基金会32))加法运算=tf.add公司(副总统,tf.常数(5,数据类型=国际贸易基金会32))输出=tf.add公司(添加操作,tf.常数(100,数据类型=国际贸易基金会32),name=”输出“)tf.train.write_图形(sess.graph定义, ".", "图形.pb“,正确)

生成的LLVM IR如下所示:

;的moduleId = “” 目标三重= “未知未知未知” 目标datalayout = “” 定义I32 @ “tensorflow_graph”(I32% “输入”){条目:” 3" (%)=子I32% “输入”,2%“0.4" =加I32%” 3" ,5% “5" =加I32%” 4" ,100保留123-132%”。5" }

如您所见,LLVM IR看起来很像一个程序集代码,但这不是最终的程序集代码,这只是一个尚未优化的IR。就在生成x86程序集代码之前,LLVM在LLVM-IR上运行了很多优化过程,它将执行诸如死代码消除、常量传播等操作。下面是LLVM为TensorFlow图的上述LLVM-IR生成的最后一个本机x86程序集代码:

.text .file "" .globl tensorflow_graph .align 16, 0x90 .type tensorflow_graph,@function tensorflow_graph: .cfi_startproc leal 103(%rdi), %eax retq .Lfunc_end0: .size tensorflow_graph, .Lfunc_end0-tensorflow_graph .cfi_endproc .section ".note.GNU-stack","

如您所见,优化后的代码删除了大量冗余操作,最后只做了一个103的加法操作,这是我们在图中定义的计算的正确简化。对于大型图表,您可以看到这些优化非常强大,因为我们正在重用多年来开发的编译器优化在我们的机器学习模型计算

您还可以使用名为“llc”的LLVM工具,该工具可以获取LLVM IR文件,并为您所需的任何其他平台生成程序集,例如,下面的命令行将为ARM体系结构生成本机代码:

有限责任公司-O3出去-3月=arm-o样本

输出样品文件如下:

。text .syntax统一.eabi_attribute 67年“2.09”@ Tag_conformance .eabi_attribute 6 1 @ Tag_CPU_arch .eabi_attribute 8 1 @ Tag_ARM_ISA_use .eabi_attribute 17日1 @ Tag_ABI_PCS_GOT_use .eabi_attribute 20日1 @ Tag_ABI_FP_denormal .eabi_attribute 21日1 @ Tag_ABI_FP_exceptions .eabi_attribute 23日3 @ Tag_ABI_FP_number_model .eabi_attribute 34岁1 @ Tag_CPU_unaligned_access .eabi_attribute 24日1 @ Tag_ABI_align_needed .eabi_attribute 25日1 @ Tag_ABI_align_preserved .eabi_attribute 38岁1 Tag_ABI_FP_16bit_format .eabi_attribute 14, 0 @ Tag_ABI_PCS_R9_use .file "输出。@ # bbb #0: @ %条目添加r0, r0, #103 mov pc, lr .Lfunc_end0: .size tensorflow_graph, .Lfunc_end0-tensorflow_graph .fnend .section ".note.GNU-stack","" %progbits

正如您在上面看到的,ARM汇编代码也是一个“添加”汇编指令,后面跟着一个返回指令。

这真的很好,因为我们可以很自然地利用LLVM框架。例如,today ARM刚刚发布了带有可伸缩向量扩展(SVE)的ARMv8-A这将支持2048位矢量,他们确实是已经在工作了补丁LLVM. 将来,LLVM的一个很好的补充就是开发LLVM的分析和转换过程,这将考虑到机器学习模型的本质。

就这样,我希望你喜欢这个帖子!你能用几行Python、LLVM和TensorFlow做的事情真是太棒了。

2016年8月22日更新Josh Klontz只是指着他的酷的项目可能是在黑客新闻讨论

2016年8月22日更新:TensorFlow团队实际上正在使用JIT(我不知道他们是否在使用LLVM,但在我看来这似乎是最合理的方法)。在他们的论文,关于未来的工作,我在此引用一项非常亚洲金博宝重要的声明:

“我们也有一些具体的方向来改善TensorFlow的性能。一个这样的方向是我们在一个即时编译器上的初始工作,它可以获取一个TensorFlow执行的子图,也许带有一些关于典型的张量大小和形状的运行时配置信息,并且可以为这个子图生成一个优化的例程。此编译器将理解执行许多优化的语义,例如循环融合、局部性的块和平铺、特定形状和大小的专门化等。“-TensorFlow白皮书

完整代码

从ctypes import CFUNCTYPE,导入tensorflow作为tf from谷歌protobuf从导入文本格式tensorflow.core.framework格式从导入图形tensorflow.core.framework格式从导入类型tensorflow.python.framework导入操作导入微石.ir全部导入llvmlite.绑定作为llvmllvm.initialize初始化() llvm.initialize本地目标() llvm.initialize本地打印机()TYPE_TF_LLVM={types_pb2.DT_INT32:ll.IntType型(32),}类TFGraph(object):def\uu init(self,filename=”图形.pb“,二进制=False):自图定义=打开时的graph_pb2.GraphDef(“图形.pb“,”rb”)作为f:if二进制:自图定义.ParseFromString(f.read())其他:文本_格式化.合并(f.读(),自图定义)def get_node(self,name):中的节点自图定义.节点:如果node.name节点名称==名称:返回节点def build_graph(ir_builder,graph,node):如果node.op节点==“添加”:左操作节点=graph.get_节点(node.input节点[0])右操作节点=graph.get_节点(node.input节点[1] )left_op=build_graph(ir_builder,图形,left_op_node)right_op=build_graph(ir_builder,graph,right_op_node)返回ir_生成器.add(左或右)如果node.op节点=“Sub”:左操作节点=graph.get_节点(node.input节点[0])右操作节点=graph.get_节点(node.input节点[1] )left_op=build_graph(ir_builder,graph,left_op_node)right_op=build_graph(ir_builder,graph,右操作节点)返回ir_建造者.sub(左或右)如果node.op节点=“占位符”:函数_args=ir_builder.function.args参数对于函数args中的arg:if参数名称== node.name节点名称:return arg raise RuntimeError(“找不到输入{}!”。格式(node.name节点名称))如果node.op节点=“Const”:llvm_Const_type=type_TF_llvm[节点属性[“dtype”].type]常量值=节点属性[“值”]。张力值[0]llvm_const_value=llvm_const_type(const_value)返回llvm_const_value def create_引擎(模块):功能=llvm.get_host_cpu_功能().flatten()llvm_模块=llvm.parse_程序集(str(模块)目标=llvm.Target.from_default_三元组(目标计算机)target.create_target_机器(opt=3,features=features)引擎=llvm.create_mcjit_编译器(llvm_模块,目标机)engine.finalize_对象()打印目标_机器发射组件(llvm_模块)返回引擎def run_main():graph=TFGraph(“图形.pb", False)输入节点=graph.get_节点(“输入”)输出节点=graph.get_节点(“输出”)输入类型=输入类型_节点属性[“dtype”].type]output_type=type_TF_LLVM[输出_节点属性[“T”].type]模块=ll.模块(函数类型=ll.功能类型(输出类型,[输入类型])func=ll.函数(模块,函数类型,name='tensorflow_graph')函数参数[0].name='输入'bb_项=func.append_basic_块(“entry”)iru生成器=ll.IRBuilder公司(bb_entry)ret=生成图形(ir_builder,图形,输出节点)ir_生成器.ret(ret)打开(“输出.ir", “w”)作为f:f.write(str(module))engine=create_engine(module)func_ptr=engine.get_函数地址(“tensorflow_graph”)c func=CFUNCTYPE(c_int,c_int)(func_ptr)ret=cfunc(10)打印“执行输出:{}”。format(ret)if u name_uuuu==”uu main“:run_umain()
引用这篇文章:Christian S. Perone,“使用Python和LLVM生成TensorFlow计算图的JIT本机代码”,in亚洲金博宝未知领域,22/08/2016,//www.cpetem.com/2016/08/jit-native-code-generation-for-tensorflow-computation-graphs-using-python-and-llvm/

“使用Python和LLVM生成TensorFlow计算图的JIT本机代码”的三点思考

    1. 暂时还没有基准,很快就会做到这一点。然而,要注意的是使用LLVM的好处不仅是性能,而且灵活的操作和执行多架构的支持,专门为CPU推理时间。另外,需要注意的是TensorFlow开发商也在积极发展JIT(见我的帖子更新)是很重要的。

  1. 我一直在一个类似的系统上数月,和你做它似乎它几乎没有容易的。有些事情我已经运行到:
    1.无证协定缓冲符号:有时名称显示带有前导^或尾随:1。花了几周弄清楚:,仍然发现了^的混淆。
    2。许多行动,即使是像mnist这样简单的行动,都是无证的。(例如,“BroadcastGradientArgs”,我花了一周的时间才看完源代码。)
    3.被记载了许多OPS的不良记录。(例如,变量的定义包括“use_locking”属性,它本身是没有证件。锁定什么?谁锁了吗?谁解开了吗?如果你使用一个加锁的变量会发生什么,...
    四。更糟糕的是,谷歌不想回答这些问题。当我询问“BroadcastGradientArgs”的定义时,得到的答复是它是一个内部操作,可能随时都会更改,“不要使用它”。
    5.另一个问题是,用户在构建图时使用的Python名称不在协议缓冲区中,这使得与用户的通信变得困难。
    我还可以继续……
    (如果你不想张贴这一点,这很好... - 我只是想让你知道...)

留下回信

您的电子邮件地址将不会被公布。

这个网站使用Akismet来减少垃圾邮件。了解如何处理评论数据