利用LLVM和Arrow对Pandas表达式进行JIT和求值

简介

这是2020年的职位,所以新年快乐敬你们大家!

我是LLVM的超级粉丝,因为11年前我开始玩它JIT数据结构比如avl,然后再到JIT限制AST树来自TensorFlow图的JIT本机代码.从那时起,LLVM发展成为最重要的编译器框架生态系统之一,现在许多重要的开源项目都在使用它。

我最近注意到的一个很酷的项目是Gandiva.Gandiva是由Dremio之后捐赠给Apache Arrow向Dremio团队致敬).Gandiva的主要思想是它提供了一个编译器来生成LLVM IR,该编译器可以操作批量的Apache箭头.Gandiva是用c++编写的,并提供了许多不同的函数来构建可以使用LLVM进行JIT化的表达式树。这种设计的一个很好的特性是,它可以使用LLVM自动优化复杂的表达式,在操作Arrow批处理时添加本机目标平台向量化(如AVX),并执行本机代码来计算表达式。

下图是刚帝梵的概述:

Gandiva如何工作的概述。图片来自:https://www.dremio.com/announcing-gandiva-initiative-for-apache-arrow

在这篇文章中,我将构建一个非常简单的表达式解析器,支亚洲金博宝持有限的操作集,我将使用它来过滤Pandas DataFrame。

用Gandiva构建简单的表达式

在本节中,我将展示如何使用Gandiva中的树生成器手动创建一个简单的表达式。

使用Gandiva Python绑定到JIT和表达式

在为表达式构建解析器和表达式构建器之前,让我们用Gandiva手动构建一个简单的表达式。首先,我们将创建一个简单的Pandas DataFrame,数字从0.0到9.0:

Import pandas as pd Import pyarrow as pa Import pyarrow。gandivaas gandiva # Create a simple Pandas DataFrame df = pd.DataFrame({"x": [1.0 * i for i in range(10)]}) table = pa.Table.from_pandas(df) schema = pa.Schema.from_pandas(df)

我们将DataFrame转换为箭头表重要的是要注意,在这种情况下,它是一个零拷贝操作,Arrow没有从Pandas复制数据,也没有复制DataFrame。之后我们得到模式,其中包含列类型和其他元数据。

之后,我们想使用Gandiva构建以下表达式来过滤数据:

(x > 2.0)和(x < 6.0)

这个表达式将使用来自Gandiva的节点来构建:

builder = gandiva.TreeExprBuilder() #引用列"x" node_x = builder.make_field(table.schema.field("x")) #创建两个字面量:2.0和6.0 two = builder.make_literal(2.0, pa.float64()) six = builder.make_literal(6.0, pa.float64()) #为"x > 2.0"创建一个函数gt_five_node = builder.make_field。make_function("greater_than", [node_x, two], pa.bool_()) #为"x < 6.0"创建一个函数lt_ten_node = builder。make_function("less_than", [node_x, six], pa.bool_()) #创建一个"and"节点,用于"(x > 2.0) and (x < 6.0)" and_node = builder。make_and([gt_five_node, lt_ten_node]) #将表达式作为条件,并创建一个过滤条件= builder.make_condition(and_node) filter_ = gandiva.make_filter(table. gt_five_node)模式、条件)

这段代码现在看起来有点复杂,但很容易理解。我们基本上是在创建树的节点,这些节点将表示我们前面展示的表达式。下面是它的图形表示:

检查生成的LLVM IR

不幸的是,还没有找到一种方法来转储使用Arrow的Python绑定生成的LLVM IR,但是,我们可以使用c++ API来构建相同的树,然后查看生成的LLVM IR:

Auto field_x = field("x", float32());自动模式=箭头::模式({field_x});auto node_x = TreeExprBuilder::MakeField(field_x);auto two = TreeExprBuilder::MakeLiteral((float_t)2.0);auto six = TreeExprBuilder::MakeLiteral((float_t)6.0);auto gt_five_node = TreeExprBuilder::MakeFunction("greater_than", {node_x, two}, arrow::boolean());auto lt_ten_node = TreeExprBuilder::MakeFunction("less_than", {node_x, six}, arrow::boolean());auto and_node = TreeExprBuilder::MakeAnd({gt_five_node, lt_ten_node});auto condition = TreeExprBuilder::MakeCondition(and_node);std::要查看> <过滤器过滤; auto status = Filter::Make(schema, condition, TestConfiguration(), &filter);

上面的代码与Python代码相同,但是使用了c++ Gandiva API。现在我们用c++构建了树,我们可以获取LLVM模块并为它转储IR代码。生成的IR充满了来自Gandiva注册表的样板代码和JIT化函数,但重要的部分如下所示:

;函数属性:alwaysinline norecurse nounwind readnone ssp uwtable define internal zeroext i1 @less_than_float32_float32(float, float) local_unnamed_addr #0 {%3 = fmpp olt float %0, %1 ret i1 %3};函数Attrs: alwaysinline norecurse nounwind readnone ssp uwtable定义内部zeroext i1 @greater_than_float32_float32(浮动,浮动)local_unnamed_addr # 0 {% 3 = fcmp油气痕迹浮动% 0,% 1 i1 ret % 3  } (...) % x =负载浮子,浮子* % 11% greater_than_float32_float32 =叫i1 @greater_than_float32_float32 (% x浮动,浮动2.000000 e + 00)(…)% x11 =负载浮子,浮子* % 15% less_than_float32_float32 =叫i1 @less_than_float32_float32 (% x11浮动,浮动6.000000 e + 00)

如您所见,在IR中,我们可以看到对函数的调用less_than_float32_float_32而且greater_than_float32_float32它们是用来做浮点数比较的(在这个例子中非常简单)Gan亚洲金博宝diva函数。通过查看函数名前缀,注意函数的专门化。

非常有趣的是,LLVM将应用这段代码中的所有优化,它将为目标平台生成有效的本机代码,而Godiva和LLVM将负责确保内存对齐将是正确的扩展,如AVX用于向量化。

我展示的IR代码并不是实际执行的,而是优化后的。在优化后的代码中,我们可以看到LLVM内联了函数,如下面的部分优化代码所示:

% x。我们=负载浮动,浮动* %10,对齐4% 11 = fmp ogt浮动%x。Us, 2.000000e+00 %12 = fmpp olt float %x。Us, 6.000000e+00 %not.or。Cond =和i1 %12, %11

您可以看到,由于LLVM应用了强大的优化并内联了许多Gandiva函数,优化后的表达式现在简单多了。

用Gandiva构建Pandas过滤器表达式JIT

现在我们希望能够实现一些类似于Pandas的DataFrame.query ()函数使用Gandiva。我们将面临的第一个问题是,我们需要解析一个字符串,例如(x > 2.0)和(x < 6.0)之后,我们将不得不使用Gandiva中的树生成器来构建Gandiva表达式树,然后在箭头数据上计算该表达式。

现在,我将不实现表达式字符串的完整解析,而是使用Python AST模块解析有效的Python代码并构建该表达式的抽象语法树(AST),稍后将使用它来发出Gandiva/LLVM节点。

解析字符串的繁重工作将委托给Python AST模块,我们的工作主要是在这棵树上行走,并根据该语法树发出Gandiva节点。访问此Python AST树的节点并发出Gandiva节点的代码如下所示:

class LLVMGandivaVisitor(ast.NodeVisitor): def __init__(self, df_table): self。表= df_table self。builder = gandiva.TreeExprBuilder() self。columns = {f.name: self.builder.make_field(f) for f in self.table.schema} self.compare_ops = { "Gt": "greater_than", "Lt": "less_than", } self.bin_ops = { "BitAnd": self.builder.make_and, "BitOr": self.builder.make_or, } def visit_Module(self, node): return self.visit(node.body[0]) def visit_BinOp(self, node): left = self.visit(node.left) right = self.visit(node.right) op_name = node.op.__class__.__name__ gandiva_bin_op = self.bin_ops[op_name] return gandiva_bin_op([left, right]) def visit_Compare(self, node): op = node.ops[0] op_name = op.__class__.__name__ gandiva_comp_op = self.compare_ops[op_name] comparators = self.visit(node.comparators[0]) left = self.visit(node.left) return self.builder.make_function(gandiva_comp_op, [left, comparators], pa.bool_()) def visit_Num(self, node): return self.builder.make_literal(node.n, pa.float64()) def visit_Expr(self, node): return self.visit(node.value) def visit_Name(self, node): return self.columns[node.id] def generic_visit(self, node): return node def evaluate_filter(self, llvm_mod): condition = self.builder.make_condition(llvm_mod) filter_ = gandiva.make_filter(self.table.schema, condition) result = filter_.evaluate(self.table.to_batches()[0], pa.default_memory_pool()) arr = result.to_array() pd_result = arr.to_numpy() return pd_result @staticmethod def gandiva_query(df, query): df_table = pa.Table.from_pandas(df) llvm_gandiva_visitor = LLVMGandivaVisitor(df_table) mod_f = ast.parse(query) llvm_mod = llvm_gandiva_visitor.visit(mod_f) results = llvm_gandiva_visitor.evaluate_filter(llvm_mod) return results

正如您所看到的,代码非常简单,因为我不支持所有可能的Python表达式,而是支持它的一个小子集。亚洲金博宝我们在这个类中所做的基本上是将Python AST节点(如comparator和BinOps(二进制操作))转换为Gandiva节点。的语义也发生了变化|运算符分别表示AND和OR,例如Pandas查询()函数。

注册为Pandas扩展

方法创建一个简单的Pandas扩展gandiva_query ()方法:

@pd.api.extensions.register_dataframe_accessor("gandiva")类GandivaAcessor: def __init__(self, pandas_obj): self。pandas_obj = pandas_obj def query(self, query):返回llvmgandivavvisitor .gandiva_query(self。pandas_obj、查询)

就是这样,现在我们可以使用这个扩展来做一些事情,比如:

Df = pd。D在aFrame({"a": [1.0 * i for i in range(nsize)]}) results = df.gandiva.query("a > 10.0")

因为我们已经注册了一个熊猫扩展名为gandiva它现在是熊猫数据框架的一级公民。

让我们现在创建一个500万浮动的DataFrame,并使用新的查询()方法进行过滤:

Df = pd。D在aFrame({"a": [1.0 * i for i in range(50000000)]}) df.gandiva.query("a < 4.0") # This will output: # array([0, 1, 2, 3], dtype=uint32)

注意,返回值是满足我们实现的条件的索引,因此它与Pandas不同查询()返回已过滤的数据。

我做了一些基准测试,发现Gandiva通常总是比Pandas快,但是我将把适当的基准测试留给下一篇关于Gandiva的文章,因为这篇文章是为了展示如何将它用于JIT表达式。

就是这样!我希望你喜欢这篇文章,就像我喜欢探索刚帝梵一样。看起来我们可能会有越来越多的Gandiva加速工具出现,特别是用于SQL解析/投影/JITing。Gandiva不仅仅是我刚刚展示的内容,现在您可以开始了解它的架构以及如何构建表达式树。

- Christian S. Perone

引用本文为:Christian S. Perone,“Gandiva,使用LLVM和Arrow来JIT和计算Pandas表达式”,在亚洲金博宝未发现的地域19/01/2020,//www.cpetem.com/2020/01/gandiva-using-llvm-and-arrow-to-jit-and-evaluate-pandas-expressions/

在训练过程中倾听神经网络梯度规范

训练神经网络通常是通过测量许多不同的指标来完成的,如准确度、损失、梯度等。这大部分时间都是在TensorBoard上聚合这些指标并绘制可视化图。

然而,我们可以使用其他感官来监控神经网络的训练,例如声音.声音是目前在神经网络训练中探索得非常少的一种视角。亚洲金博宝人类的听觉可以很好地分辨出节奏和音高等特征亚洲金博宝上的微小扰动,即使这些扰动在时间上很短或很细微。

在这个实验中,我做了一个非常简单的例子,展示了一个合成声音,亚洲金博宝它是使用每一层的梯度模和MNIST上卷积神经网络训练的训练步骤,使用不同的设置,如不同的学习率、优化器、动量等。

您需要安装PyAudio而且PyTorch要运行代码(在这篇文章的结尾).

用LR 0.01的SGD训练声音

这段表示在第一个纪元的前200步中,使用10个批次大小的4层梯度的训练会话。音调越高,一层的范数越高,有一个短的沉默来表示不同的批次。注意梯度随着时间的推移而增加。

使用LR 0.1的SGD训练声音

同上,但学习率更高。

使用LR 1.0使用SGD训练声音

同上,但学习率高,使网络发散,注意规范爆发时的高音然后发散。

使用lr1.0和bs256的SGD训练声音

相同的设置,但学习率为1.0,批处理大小为256。注意渐变是如何爆发的,然后有nan引起最后的声音。

用LR 0.01训练Adam的声音

这是把亚当和SGD放在同一个场景里。

源代码

对于那些感兴趣的人,这里是我用来制作声音剪辑的整个源代码:

Import pyaudio Import numpy as np Import wave Import torch Import torch。nn作为nn import torch.nn.function作为F import torch。optim作为optim从torchvision导入数据集,转换类Net(nn.Module): def __init__(self): super(Net, self).__init__() self。Conv1 = nn。C在v2d(1, 20, 5, 1) self.conv2 = nn.Conv2d(20, 50, 5, 1) self.fc1 = nn.Linear(4*4*50, 500) self.fc2 = nn.Linear(500, 10) self.ordered_layers = [self.conv1, self.conv2, self.fc1, self.fc2] def forward(self, x): x = F.relu(self.conv1(x)) x = F.max_pool2d(x, 2, 2) x = F.relu(self.conv2(x)) x = F.max_pool2d(x, 2, 2) x = x.view(-1, 4*4*50) x = F.relu(self.fc1(x)) x = self.fc2(x) return F.log_softmax(x, dim=1) def open_stream(fs): p = pyaudio.PyAudio() stream = p.open(format=pyaudio.paFloat32, channels=1, rate=fs, output=True) return p, stream def generate_tone(fs, freq, duration): npsin = np.sin(2 * np.pi * np.arange(fs*duration) * freq / fs) samples = npsin.astype(np.float32) return 0.1 * samples def train(model, device, train_loader, optimizer, epoch): model.train() fs = 44100 duration = 0.01 f = 200.0 p, stream = open_stream(fs) frames = [] for batch_idx, (data, target) in enumerate(train_loader): data, target = data.to(device), target.to(device) optimizer.zero_grad() output = model(data) loss = F.nll_loss(output, target) loss.backward() norms = [] for layer in model.ordered_layers: norm_grad = layer.weight.grad.norm() norms.append(norm_grad) tone = f + ((norm_grad.numpy()) * 100.0) tone = tone.astype(np.float32) samples = generate_tone(fs, tone, duration) frames.append(samples) silence = np.zeros(samples.shape[0] * 2, dtype=np.float32) frames.append(silence) optimizer.step() # Just 200 steps per epoach if batch_idx == 200: break wf = wave.open("sgd_lr_1_0_bs256.wav", 'wb') wf.setnchannels(1) wf.setsampwidth(p.get_sample_size(pyaudio.paFloat32)) wf.setframerate(fs) wf.writeframes(b''.join(frames)) wf.close() stream.stop_stream() stream.close() p.terminate() def run_main(): device = torch.device("cpu") train_loader = torch.utils.data.DataLoader( datasets.MNIST('../data', train=True, download=True, transform=transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ])), batch_size=256, shuffle=True) model = Net().to(device) optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5) for epoch in range(1, 2): train(model, device, train_loader, optimizer, epoch) if __name__ == "__main__": run_main()
引用这篇文章为:Christian S. Perone,“在训练中倾听神经网络梯度规范”,在亚洲金博宝未发现的地域04/08/2019,//www.cpetem.com/2019/08/listening-to-the-neural-network-gradient-norms-during-training/

Numpy dispatcher:当Numpy成为生态系统的协议时

简介

在Python科学生态系统中工作的人并不多棉结18NumPy高级数组函数的调度机制).考虑到这个协议的重要性,我决定写这篇简短的介绍,介绍这个新的调度程序,它肯定会带来一个很多好处Python科学生态系统。

如果你使用PyTorch, TensorFlow, Dask等,你肯定会注意到它们的API契约与Numpy的相似性。Numpy的API是科学计算中最基本和最广泛使用的API之一,这并非偶然。Numpy是如此普及,它不再只是一个API,它正在成为一个协议或API规范。

阅读更多

GPT-2语言模型的本福德定律

我几个月前写过本福德定律是如何从语言模型中产生的今天我决定用同样的方法来评估一下GPT-2会用一些句子表现出来结果是它似乎也抓住了这些幂律。你可以从下面的例子中找到一些图表,这些图表显示了给定一个特定的句子,比如“人口规模为",显示$$P(\{1,2, \ldots, 9\} \vert \text{" with a population size of "})$$ for GPT-2 medium model (345M):

引用本文为:Christian S. Perone,“GPT-2语言模型的本福德定律”,在亚洲金博宝未发现的地域14/06/2019,//www.cpetem.com/2019/06/benford-law-on-gpt-2-language-model/

188betiosapp

2019年2月28日更新:我添加了一个带有幻灯片的新博客文章里面有我为PyData Montreal做的演示。

今天,在PyTorch开发者大会上,PyTorch团队宣布了计划,并发布了PyTorch 1.0预览版,该预览版具有许多不错的特性,例如用于模型图的JIT(带跟踪和不带跟踪)以及LibTorch, PyTorch c++ API,是最重要的发布公告在我看来是今天。

考虑到理解这个新API如何工作的巨大兴趣,我决定写这篇文章来展示在PyTorch c++ API发布后出现的许多机会的示例。在这篇文章中,我将使用NodeJS c++插件将PyTorch推理集成到原生NodeJS中,这只是一个现在可以使用c++ API在不同框架/语言之间集成的例子。

下面你可以看到最终的结果:

正如你所看到的,集成是无缝的,我可以使用一个跟踪的ResNet作为计算图模型,并向它输入任何张量来获得输出预测。

简介

简单地说,libtorch是PyTorch的一个库版本。它包含PyTorch使用的底层基础,例如阿托恩(张量库),它包含所有的张量操作和方法。Libtorch还包含autograd,这是将自动微分添加到ATen张量的分量。

对于那些现在开始学习的人来说,要小心使用可以从ATen和autograd中创建的张量,不要混合, ATen将返回普通张量(当您使用命名空间),而自动升级函数(从火炬命名空间)将返回变量,增加其自动分辩机制。

关于PyTorch内部如何工作的更详细的教程,请参阅我之前关于PyTorch内部架构

Libtorch可从Pytorch网站而且它只提供了一段时间的预览版。您也可以在这个网站,这主要是一个Doxygen渲染的文档。我发现这个库非常稳定,这是有意义的,因为它实际上暴露了PyTorch的稳定基础,然而,在开始使用它时,可能会发现一些关于头文件的问题和库组织的一些小问题(希望很快就能修复)。

对于NodeJS,我将使用本机抽象库(nan),这是最推荐的库(实际上基本上是一个头文件库)来创建NodeJS c++附加组件和cmake-js,因为libtorch已经提供了cmake文件,使我们的构建过程更容易。然而,这里的重点将是c++代码,而不是构建过程。

开发、跟踪、序列化和加载模型的流程可以在左侧的图中看到。

它从PyTorch (Python域)中的开发过程和跟踪开始,然后在c++域(在我们的例子中是NodeJS插件)上加载和推断。

包装张量

在NodeJS中,要创建一个对象作为JavaScript世界的一等公民,您需要继承ObjectWrap类,它将负责包装c++组件。

#ifndef TENSOR_H #define TENSOR_H #include < Nan .h> #include  namespace torchjs {class Tensor: public Nan::ObjectWrap {public: static NAN_MODULE_INIT(Init);void setTensor(at::Tensor张量){this->mTensor =张量;} torch::Tensor getTensor(){返回this->mTensor;} static v8::Local NewInstance();private: explicit Tensor();~张量();静态NAN_METHOD(新);静态NAN_METHOD (toString);static Nan::Persistent构造函数;private: torch::Tensor mTensor; }; } // namespace torchjs #endif

正如你所看到的,我们的Tensor类定义的大部分代码只是样板。这里的关键点是torchjs:张量将封装一个火炬:张量并且我们添加了两个特殊的公共方法(setTensor而且getTensor)来设置和获取这个内部火炬张量。

我不会展示所有的实现细节,因为它的大部分都是NodeJS的样板代码来构造对象,等等。我将专注于触及libtorch API的部分,就像在下面的代码中,我们正在创建一个张量的小文本表示来显示在JavaScript上(toString方法):

NAN_METHOD(Tensor::toString) {Tensor* obj = ObjectWrap::Unwrap(info.Holder());std:: stringstream党卫军;at::IntList size = obj->mTensor.sizes();党卫军< <“张量(Type = " < < obj - > mTensor.type () << ", ";ss << "Size=" << sizes << std::endl;info.GetReturnValue()这里(南::新(ss.str ()) .ToLocalChecked ());}

我们在上面的代码中所做的,只是从包装对象中获得内部张量对象by打开它。之后,我们用张量大小(每个维度大小)及其类型(float等)构建一个字符串表示。

包装张量创建操作

的包装器代码火炬::的函数,它负责创建一个包含常数1的任意定义形状的张量。

NAN_METHOD(ones){//参数的完整性检查if (info.Length() < 2) return Nan::ThrowError(Nan::New("错误的参数数量").ToLocalChecked());if (!info[0]->IsArray() || !info[1]->IsBoolean())返回Nan::ThrowError(Nan::New("错误的参数类型").ToLocalChecked());//获取参数(require_grad和张量形状)const bool require_grad = info[1]->BooleanValue();const v8::Local Array = info[0].As();const uint32_t length = array-> length ();//将v8::Array转换为std::vector std::vector dimms;for (int i = 0;我<长度;i++) {v8::Local v;int d = array->Get(i)->NumberValue(); dims.push_back(d); } // Call the libtorch and create a new torchjs::Tensor object // wrapping the new torch::Tensor that was created by torch::ones at::Tensor v = torch::ones(dims, torch::requires_grad(require_grad)); auto newinst = Tensor::NewInstance(); Tensor* obj = Nan::ObjectWrap::Unwrap(newinst); obj->setTensor(v); info.GetReturnValue().Set(newinst); }

让我们看一下这段代码。我们首先检查函数的参数。对于这个函数,我们期望一个元组(JavaScript数组)作为张量形状,一个布尔值指示我们是否要为这个张量节点计算梯度。在此之后,我们将参数从V8 JavaScript类型转换为原生c++类型。一旦我们有了所需的参数,我们就调用火炬::的函数,这个函数将创建一个新的张量,其中我们使用atorchjs:张量我们之前创建的用于包装它的类。

就这样,我们只公开了一个torch操作,它可以用作原生JavaScript操作。

PyTorch JIT的间奏曲

引入的PyTorch JIT围绕着Torch Script的概念。Torch Script是Python语言的一个受限子集,并带有自己的编译器和转换通道(优化等)。

可以通过两种不同的方式创建该脚本:使用跟踪JIT或提供脚本本身。在跟踪模式下,您的计算图节点将被访问并记录操作,以生成最终的脚本,而脚本是一种模式,在这种模式下,您将考虑到Torch脚本的限制,提供对模型的描述。

请注意,如果您在代码上有依赖于外部因素或数据的分支决策,跟踪将不会像您期望的那样工作,因为它将记录图的特定执行,因此提供脚本是另一种选择。然而,在大多数情况下,跟踪是我们所需要的。

为了理解其中的区别,让我们来看看通过跟踪和脚本生成的脚本模块中的中间表示(Intermediate Representation, IR)。

@torch.jit.scriptdef happy_function_script(x): ret = torch.rand(0) if True == True: ret = torch.rand(1) else: ret = torch.rand(2) return ret def happy_function_trace(x): ret = torch.rand(0) if True == True: ret = torch.rand(1) else: ret = torch.rand(2) return ret traced_fn = torch.jit.trace(happy_function_trace, (torch.tensor(0),), check_trace=False)

在上面的代码中,我们提供了两个函数,一个是使用@torch.jit.script它是创建Torch Script的脚本方式,而第二个函数由跟踪函数使用torch.jit.trace.并不是我故意在函数上添加了一个“True == True”的决定(它总是为真)。

现在,如果我们检查这两种不同方法生成的IR,我们将清楚地看到跟踪和脚本方法之间的区别:

从脚本方法图# 1)图(% x:动态){% 16:int =整洁的::常数[value = 2] () % 10: int =整洁的::常数(值= 1)()% 7:int =整洁的::常数(值= 1)()% 8:int =整洁的::常数(值= 1)()% 9:int =阿托恩::情商(%,% 8)% ret:动态=整洁的::如果(% 9)block0 () {% 11: int[] =整洁的::ListConstruct 10 (%) % 12: int =整洁的::常数(值= 6)()% 13:int =整洁的::常数(值= 0)()% 14:int[] =整洁的::常数(value =[0,1]]() %受潮湿腐烂。2:动态=阿托恩::兰德(% % % % 11日12日13日14)- > (% ret.2)} block1 () {% 17: int[] =整洁的::ListConstruct (% 16) % 18: int =整洁的::常数(值= 6)()% 19:int =整洁的::常数(值= 0)()% 20:int[] =整洁的::常数(value =[0,1]]() %受潮湿腐烂。3:动态=阿托恩::兰德(% % % % 17日,18日19日20)- > (% ret.3)}返回(% ret);} # 2)来自跟踪方法图(%0:Long()) {%7: int = prim::Constant[value=1]() %8: int[] = prim::ListConstruct(%7) %9: int = prim::Constant[value=6]() %10: int = prim::Constant[value=0]() %11: int[] = prim::Constant[value=[0, -1]]() %12: Float(1) = aten::rand(%8, %9, %10, %11) return (%12);}

我们可以看到,IR和亚洲金博宝LLVM IR请注意,在跟踪方法中,记录的跟踪只包含来自代码的一条路径,即真实路径,而在脚本中,我们有两个分支替代方案。然而,即使在脚本中,也可以通过死代码消除转换传递来优化和删除始终为假的分支。

PyTorch JIT有很多转换通道,用于进行循环展开、死代码消除等。你可以找到这些经过这里.并不是说转换到其他格式(如ONNX)可以在这个中间表示(IR)之上实现,这非常方便。

跟踪ResNet

现在,在NodeJS中实现脚本模块之前,让我们先使用PyTorch(只使用Python)跟踪一个ResNet网络:

Traced_net = torch. jet .trace(torchvision.models.resnet18(), torch.jit.trace(), torch.jit.trace()Rand (1,3,224,224)) traced_net.save("resnet18_trace.pt")

从上面的代码中可以看到,我们只需要提供一个张量示例(在本例中是一批具有3个通道和大小为224×224的单个图像。之后,我们只需将跟踪的网络保存到一个名为resnet18_trace.pt

现在我们准备好在NodeJS中实现脚本模块,以加载这个被跟踪的文件。

包装脚本模块

这是脚本模块在NodeJS中的实现:

//类构造函数ScriptModule::ScriptModule(const std::string filename){//从文件this->mModule = torch::jit:: Load (filename);} // JavaScript对象创建NAN_METHOD(ScriptModule::New) {if (info. isconstructcall()){//获取文件名参数v8::String::Utf8Value param_filename(info[0]->ToString());std::string filename = std::string(*param_filename);//创建一个新的脚本模块使用文件名ScriptModule *obj = new ScriptModule(filename);obj - >包装(info.This ());info.GetReturnValue()这里(info.This ());} else {v8::Local cons = Nan::New(构造函数);info.GetReturnValue()这里(南:NewInstance(缺点).ToLocalChecked ());}}

从上面的代码可以看出,我们只是创建了一个类,它将调用火炬:jit:负载函数传递跟踪网络的文件名。还有JavaScript对象的实现,其中将参数转换为c++类型,然后创建对象的新实例torchjs: ScriptModule

向前传球的包装也很简单:

NAN_METHOD(ScriptModule::forward) {ScriptModule* script_module = ObjectWrap::Unwrap(info.Holder());Nan::MaybeLocal maybe = Nan::To(info[0]);张量*张量= Nan::ObjectWrap::Unwrap<张量>(maybe.ToLocalChecked());torch::Tensor torch_tensor = Tensor ->getTensor();torch::Tensor output = script_module->mModule->forward({torch_tensor}).toTensor();auto newinst =张量::NewInstance();张量* obj = Nan::ObjectWrap::Unwrap<张量>(newinst);obj - > setTensor(输出);info.GetReturnValue()这里(newinst);}

正如你所看到的,在这段代码中,我们只是接收了一个张量作为参数,我们得到了内量火炬:张量从它,然后从脚本模块调用forward方法,我们将输出包装在newtorchjs:张量然后返回。

就是这样,我们已经准备好在原生NodeJS中使用我们构建的模块了,如下例所示:

var torchjs = require("./build/Release/torchjs");var script_module = new torchjs.ScriptModule("resnet18_trace.pt");Var data = torchjs。在es([1, 3, 224, 224], false); var output = script_module.forward(data);

希望你喜欢!Libtorch为在许多不同的语言和框架中紧密集成PyTorch打开了大门,这是非常令人兴奋的,也是朝着生产部署代码方向迈出的一大步。

- Christian S. Perone

引用本文为:Christian S. Perone,“PyTorch 1.0跟踪JIT和LibTorch c++ API以将PyTorch集成到NodeJS中”亚洲金博宝未发现的地域02/10/2018,188betiosapp

PyTorch -内部架构之旅

2019年2月28日更新:我添加了一个带有幻灯片的新博客文章里面有我为PyData Montreal做的演示。

简介

这篇文章是对PyTorch代码库的一次游览,它是PyTorch的架构设计及其内部结构的指南。我的主要目标是为那些有兴趣了解在面向用户的API之外发生了什么的人提供一些有用的东西,并展示一些其他教程中已经介绍过的新内容。

注意:PyTorch构建系统广泛使用代码生成,所以我不会在这里重复其他人已经描述过的内容。如果你有兴趣了解这是如何工作的,请阅读以下教程:

C/ c++中Python扩展对象的简短介绍

正如你可能知道的,你可以使用C和c++扩展Python,并开发所谓的“扩展”。所有PyTorch繁重的工作都是用C/ c++实现的,而不是纯python。要在C/ c++中定义一个新的Python对象类型,可以定义一个如下示例所示的结构(这是autograd的基础变量类):

//返回torch.autograd.变量结构体THPVariable {PyObject_HEAD torch::autograd::Variable cdata;PyObject * backward_hooks;};

如您所见,在定义的开头有一个宏,名为PyObject_HEAD,此宏的目标是Python对象的标准化,并将扩展到另一个结构,其中包含指向类型对象(定义初始化方法、分配器等)的指针,以及带有引用计数器的字段。

在Python API中有两个额外的宏Py_INCREF ()而且Py_DECREF (),用于增加和减少Python对象的引用计数器。多个实体可以借用或拥有对其他对象的引用(引用计数器会增加),只有当这个引用计数器达到零时(当所有引用都被销毁时),Python才会使用它的垃圾收集器自动从该对象中删除内存。

你可以阅读更多关于Python C/++扩展的内容在这里

有趣的事实:在许多应用程序亚洲金博宝中,使用小整数作为索引、计数器等是非常常见的。为了效率,官员CPython的翻译缓存从-5到256的整数。因此,声明A = 200;B = 200;A等于b真正的,而声明A = 300;B = 300;A等于b

零拷贝PyTorch张量到Numpy,反之亦然

PyTorch有自己的张量表示,它将PyTorch内部表示与外部表示解耦。然而,由于处处都有Numpy数组是很亚洲金博宝常见的,特别是当数据从各种来源加载时,因此我们确实需要在Numpy和PyTorch张量之间进行转换。出于这个原因,PyTorch提供了两个方法from_numpy ()而且numpy (),它分别将Numpy数组转换为PyTorch数组,反之亦然。如果我们看一下将Numpy数组转换为PyTorch张量所调用的代码,我们可以得到更多关于PyTorch内部表示的见解:

at::张量tensor_from_numpy(PyObject* obj) {if (!PyArray_Check(obj)){抛出TypeError("expected np. numpy ")ndarray (get %s)", Py_TYPE(obj)->tp_name);} auto array = (PyArrayObject*)obj;int ndim = PyArray_NDIM(数组);auto sizes = to_aten_shape(ndim, pyarray_dim(数组));auto strides = to_aten_shape(ndim, PyArray_STRIDES(数组));// NumPy跨步使用字节。火炬跨步使用元素计数。auto element_size_in_bytes = PyArray_ITEMSIZE(数组);对于(auto& stride: strides) {stride /= element_size_in_bytes;} //(…)-省略了void* data_ptr = PyArray_DATA(数组); auto& type = CPU(dtype_to_aten(PyArray_TYPE(array))); Py_INCREF(obj); return type.tensorFromBlob(data_ptr, sizes, strides, [obj](void* data) { AutoGIL gil; Py_DECREF(obj); }); }

(代码从tensor_numpy.cpp

从这段代码中可以看到,PyTorch从Numpy表示中获取所有信息(数组元数据),然后创建自己的表示。但是,正如您可以从第18行注意到的那样,PyTorch正在获取一个指向内部Numpy数组原始数据的指针,而不是复制它。这意味着PyTorch将为该数据创建一个引用,与原始张量数据的Numpy数组对象共享相同的内存区域。

这里还有一个重要的点:当Numpy数组对象超出作用域并获得零引用计数时,它将被垃圾收集摧毁了,这就是为什么在第20行Numpy数组对象的引用计数有一个增量。

之后,PyTorch将创建一个新的张量对象从这个Numpy blob数据,在这个新成立的张量通过借用内存数据指针,内存大小一起进步以及张量函数将使用后的存储(我们将在下一节讨论这个)发布的数据递减Numpy数组对象的引用计数,让Python照顾这个对象的生命周期。

tensorFromBlob ()方法将创建一个新的张量,但必须在为这个张量创建一个新的“Storage”之后。存储是实际数据指针将被存储的地方(而不是在张量结构本身中)。这就把我们带到了下一节张量存储

张量的存储

张量的实际原始数据并不直接保存在张量结构中,而是保存在另一个名为Storage的结构上,该结构又是张量结构的一部分。

正如我们在前面的代码中看到的tensor_from_numpy (),有电话找你tensorFromBlob ()它将从原始数据blob中创建一个张量。最后一个函数将调用另一个名为storageFromBlob()的函数,该函数将根据该数据的类型为其创建存储。在CPU浮动类型的情况下,它将返回newCPUFloatStorage实例。

CPUFloatStorage基本上是一个包装器,它使用实用函数围绕实际的存储结构THFloatStorage如下所示:

typedef struct THStorage {real *data;ptrdiff_t大小;int refcount;char国旗;THAllocator *分配器;void * allocatorContext;struct THStorage *view;} THStorage;

(代码从THStorage.h

如你所见,THStorage保存一个指向原始数据的指针,它的大小,标志和一个有趣的名为分配器这个我们很快就会讨论。类型中的数据没有元数据,这一点也很重要THStorage,这是由于存储对于它的内容是“愚蠢的”,它是张量的责任,知道如何“查看”或解释这些数据。

从这里,你可能已经意识到我们可以有多个张量指向相同的存储,但对该数据有不同的视图,这就是为什么用不同形状查看张量(但保持相同数量的元素)是如此有效的原因。下面的Python代码表明,在改变Tensor查看数据的方式后,存储中的数据指针正在被共享:

>>> tensor_a = torch。在es((3, 3)) >>> tensor_b = tensor_a.view(9) >>> tensor_a.storage().data_ptr() == tensor_b.storage().data_ptr() True

正如我们在上面的例子中看到的,两个张量存储上的数据指针是相同的,但是张量表示存储数据的不同解释。

现在,正如我们在第7行看到的THFloatStorage结构中,有一个指向的指针THAllocator结构。这一点非常重要,因为它带亚洲金博宝来了可用于分配存储数据的分配器的灵活性。该结构由以下代码表示:

typedef struct THAllocator {void* (*malloc)(void*, ptrdiff_t);Void * (*realloc)(Void *, Void *, ptrdiff_t);Void (*free)(Void *, Void *);} THAllocator;

(代码从THAllocator.h

正如您所看到的,这个结构中有三个函数指针字段来定义分配器的含义:malloc、realloc和free。对于cpu分配的内存,这些函数当然与传统的malloc/realloc/free POSIX函数有关,然而,当我们想在gpu上分配存储时,我们最终将使用CUDA分配器,例如cudaMallocHost (),就像我们在THCudaHostAllocatorMalloc函数如下:

static void* THCudaHostAllocator_malloc(void* ctx, ptrdiff_t size) {void* ptr;if (size < 0) error("无效内存大小:%ld", size);if (size == 0)返回NULL;THCudaCheck (cudaMallocHost (ptr、大小));返回ptr;}

(代码从THCAllocator.c

您可能注意到存储库组织中的一种模式,但是在导航存储库时记住这些约定是很重要的,总结如下(摘自PyTorch lib自述程序):

  • THT兽人H
  • THCT兽人HC使用uda
  • thcT兽人HC使用uda年代解析
  • THCUNNT兽人HNeuralNetwork
  • T兽人HDistributed
  • THNNT兽人HNeuralNetwork
  • 解说T兽人H年代解析

这种约定也出现在函数/类名和其他对象中,所以一定要记住这些模式。虽然您可以在TH代码中找到CPU分配器,但您将在THC代码中找到CUDA分配器。

最后,我们可以看到主张量的组成THTensor结构:

typedef struct THTensor {int64_t *size;int64_t *步;int nDimension;THStorage *存储;ptrdiff_t storageOffset;int refcount;char国旗;} THTensor;

(代码从THTensor.h

如你所见,主要的THTensor结构保存大小/步长/尺寸/偏移量等,以及存储(THStorage)为张量数据。

我们可以在下图中总结所有这些结构:

现在,一旦我们有需求,比如多处理,我们想要在多个不同的进程之间共享张量数据,我们就需要一个共享内存的方法来解决它,否则,每当另一个进程需要张量时,甚至当你想要实现它时亚洲金博宝Hogwild训练过程中,所有不同的进程都将写入相同的内存区域(参数所在的区域),您需要在进程之间进行复制,这是非常低效的。亚洲金博宝因此,我们将在下一节讨论共享内存的一种特殊存储。

共享内存

根据平台支持的不同,可以以许多不同的方式实现共享内存。PyTorch支持其中一些,但为了简单起见,我将在这里讨论使用CPU(而不是GPU)在MacOS上发生的事情。由于PyTorch支持多种共享内存方法,因此这一部分有点难以理解,因为它在代码中涉及更多的间接层次。

PyTorch提供了Python的包装器多处理模块,并可以从torch.multiprocessing.他们在官方的Python多处理包装中实现的更改是为了确保每次一个张量被放到队列中或与其他进程共享时,PyTorch将确保只共享共享内存的一个句柄,而不是整个张量的新副本。亚洲金博宝

现在,很多人都不知道PyTorch中的Tensor方法叫做share_memory_ ()但是,这个函数触发了对特定张量的存储内存的整个重建。这个方法的作用是创建一个共享内存区域,可以在不同的进程之间使用。这个函数将在最后调用下面的函数:

static THStorage* THPStorage_(newFilenameStorage)(ptrdiff_t size) {int flags = TH_ALLOCATOR_MAPPED_SHAREDMEM | TH_ALLOCATOR_MAPPED_EXCLUSIVE;std::string handle = THPStorage_(__newHandle)();auto ctx = libshm_context_new(NULL, handle.c_str(), flags);返回THStorage_(newWithAllocator)(size, &THManagedSharedAllocator, (void*)ctx);}

(代码从StorageSharing.cpp

正如您所看到的,这个函数将使用名为THManagedSharedAllocator.这个函数首先定义一些标志,然后创建一个句柄,这是一个格式为字符串的句柄/torch_[进程id]_[随机数],之后,它将使用特殊创建一个新的存储THManagedSharedAllocator.这个分配器有指向内部PyTorch库的函数指针libshm,将实现aUnix域套接字共享共享内存区域句柄的通信。这个分配器实际上是一个特殊的情况,它是一种“智能分配器”,因为它包含通信控制逻辑以及它使用另一个称为THRefcountedMapAllocator它将负责创建实际的共享内存区域和调用mmap ()将此区域映射到进程虚拟地址空间。

请注意:当一个方法在PyTorch中以下划线结束时,例如被调用的方法share_memory_ (),这意味着该方法具有就地效果,它将改变当前对象,而不是创建一个新的修改对象。

现在,我将展示一个Python示例,其中一个处理使用了来自张量的数据,该张量通过手动交换共享内存句柄分配给另一个进程:

在进程A中执行:

>>> import torch >>> tensor_a = torch. .在es((5, 5)) >>> tensor_a 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 [torch.FloatTensor of size 5x5] >>> tensor_a.is_shared() False >>> tensor_a = tensor_a.share_memory_() >>> tensor_a.is_shared() True >>> tensor_a_storage = tensor_a.storage() >>> tensor_a_storage._share_filename_() (b'/var/tmp/tmp.0.yowqlr', b'/torch_31258_1218748506', 25)

在此代码中,在处理一个,我们创建一个新的张量5×5,里面都是1。之后,我们将其设置为共享,并打印带有Unix域套接字地址和句柄的元组。现在我们可以从另一个内存区域访问这个内存区域进程B如下图所示:

进程B中执行的代码:

>>> import torch >>> tensor_a = torch. tensor () >>> tuple_info = (b'/var/tmp/tmp.0。Yowqlr ', b'/torch_31258_1218748506', 25)>>> storage = torch.Storage._new_shared_filename(*tuple_info) >>> tensor_a = torch.Tensor(storage).view((5, 5)) 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 [torch.FloatTensor of size 5x5]

正如你所看到的,使用Unix域套接字地址和句柄的元组信息,我们能够从另一个进程访问张量存储。如果你改变这个张量进程B,你还会看到它会反射到处理一个因为这些张量共享同一个内存区域。

DLPack:深度学习框架Babel的希望

现在我想谈谈PyTorch代码库中最近的一些东西,它被称为DLPack.DLPack是内存张量结构的开放标准化,允许交换张量数据之间的框架,非常有趣的是,由于这种内存表示是标准化的,并且非常类似于许多框架已经在使用的内存表示,它将允许一个亚洲金博宝框架之间的零拷贝数据共享,这是一个相当惊人的倡议,因为我们今天有各种各样的框架,但它们之间没有相互交流。

这当然有助于克服我们今天在MXNet、PyTorch等中的张量表示之间的“孤岛模型”,并允许开发人员在框架之间混合框架操作,以及标准化可以给框架带来的所有好处。

DLPack的核心是一个非常简单的结构亚洲金博宝DLTensor,如下图所示:

/ * !简单的C张量对象,不管理内存。*/ typedef struct {/*!不透明数据指针指向已分配的数据。*这将是CUDA设备指针或OpenCL中的cl_mem句柄。*在CUDA中,这个指针总是对齐到256字节。*/ void*数据;/ * !张量*/ DLContext ctx的设备上下文;/ * ! \brief Number of dimensions */ int ndim; /*! \brief The data type of the pointer*/ DLDataType dtype; /*! \brief The shape of the tensor */ int64_t* shape; /*! * \brief strides of the tensor, * can be NULL, indicating tensor is compact. */ int64_t* strides; /*! \brief The offset in bytes to the beginning pointer to data */ uint64_t byte_offset; } DLTensor;

(代码从dlpack.h

如您所见,有一个数据指针,用于原始数据以及形状/步幅/偏移量/GPU vs CPU,以及关于数据的其他元数据信息DLTensor指向。

还有一个被称为张量的管理版本DLManagedTensor在这里,框架可以提供一个上下文和一个“deleter”函数,该函数可以由借用Tensor的框架调用,以通知另一个框架不再需要这些资源。

在PyTorch中,如果你想转换成DLTensor格式,你可以找到C/ c++的方法,甚至在Python中,你可以这样做,如下所示:

从火炬进口火炬。Utils导入dlpack t = torch。在es((5, 5)) dl = dlpack.to_dlpack(t)

此Python函数将调用toDLPack函数,如下所示:

DLManagedTensor* toDLPack(const Tensor& src) {ATenDLMTensor * atDLMTensor(new ATenDLMTensor);atDLMTensor->handle = src;atDLMTensor - >张量。manager_ctx = atDLMTensor;atDLMTensor->tensor.deleter = &deleter;atDLMTensor - > tensor.dl_tensor。Data = src.data_ptr();Int64_t device_id = 0;If (src.type().is_cuda()) {device_id = src.get_device();} atDLMTensor - > tensor.dl_tensor。ctx = getDLContext(src.type(), device_id); atDLMTensor->tensor.dl_tensor.ndim = src.dim(); atDLMTensor->tensor.dl_tensor.dtype = getDLDataType(src.type()); atDLMTensor->tensor.dl_tensor.shape = const_cast(src.sizes().data()); atDLMTensor->tensor.dl_tensor.strides = const_cast(src.strides().data()); atDLMTensor->tensor.dl_tensor.byte_offset = 0; return &(atDLMTensor->tensor); }

正如您所看到的,这是一个相当简单的转换,将元数据从PyTorch格式转换为DLPack格式,并分配一个指向内部Tensor数据表示的指针。

我真的希望更多的框架采用这个标准,这肯定会给生态系统带来好处。值得注意的是,与的潜在集成Apache箭头太棒了。

就是这样,我希望你喜欢这篇长文章!

- Christian S. Perone

引用本文为:Christian S. Perone,“PyTorch -内部架构之旅”,在亚洲金博宝未发现的地域12/03/2018,//www.cpetem.com/2018/03/pytorch-internal-architecture-tour/

使用InferSent嵌入和安全的两方计算来保护隐私的句子语义相似性

保护隐私计算

隐私保护计算或安全计算是密码学的一个子领域,其中两个(两方,或2PC)或多个(多方,或MPC)方可以一起计算一个函数,而不向彼此透露有关各方私人输入数据的信息。这个问题和它的第一个解决方案是在1982年由安德鲁·姚所做的一个惊人的突破提出的,这个突破后来被称为“姚明的百万富翁问题”。

“姚氏百万富翁问题”是关于两个百万富翁,爱丽丝和鲍勃,他们想知道谁更富有,但是没有透露给对方实际的财富。换句话说,他们想要的可以概括为:Alice和Bob想要安全地联合计算一个函数,除了输入数据的计算结果(对他们来说仍然是私有的)之外,不知道其他任何东西。

为了使问题具体化,Alice有一个金额A,比如10美元,Bob有一个金额B,比如50美元,他们想知道哪一个更大,而不是Bob向Alice透露金额B或Alice向Bob透露金额A。同样重要的是要注意,我们也不想信任第三方,否则问题将只是与受信任方进行信息交换的简单协议。

形式上,我们想要的是联合计算以下函数:

r = f(A, B)

比如私有值一个而且B对唯一的所有者来说是私有的,结果在哪里r只会被一方或双方知道。

像这样的问题能够被亚洲金博宝解决似乎是非常违反直觉的,但令许多人惊讶的是,它是有可能在一些安全需求上解决的。由于最近技术的发展,如FHE (完全同态加密),的转移的电路在美国,像这样的问题开始在现实生活中得到实际应用,现在它们被许多公司用于信息交换、安全位置、广告、卫星轨道碰撞避免等应用。

我不打算详细介绍这些技术,但如果你对OT(遗忘转移)背后的直觉感兴趣,你一定要阅读Craig Gidney所做的令人惊叹的解释在这里.当然,也有许多不同的协议用于实现2PC或MPC,其中每一个都假设一些安全需求(半诚实的,恶意的,等等),我不打算进入细节,以保持文章的目标,但你应该意识到这一点。

问题在于句子的相似性

我们想要实现的是使用隐私保护计算来计算句子之间的相似度,而不泄露句子的内容。举个具体的例子:Bob拥有一家公司,他有很多不同项目的描述,比如:这个项目是关于建立一个深度学习情感分析框架,将用于推文,而拥有另一家竞争对手公司的爱丽丝,也有类似的句子描述的不同项目。他们想要做的是共同计算项目之间的相似性,以确定他们是否应该在一个项目上进行合作,然而,这是重要的一点:Bob不想让Alice知道项目描述,Alice也不想让Bob知道他们的项目,他们想知道他们运行的不同项目之间最接近的匹配,但是没有披露项目构想(项目描述)。

句子相似度比较

现在,我们如何在不泄露项目描述信息的情况下交换关于Bob和Alice项目句子的信息呢?

一种简单的方法是只计算句子的哈希值,然后只比较这些哈希值,以检查它们是否匹配。然而,这将假设描述完全相同,除此之外,如果句子的熵很小(如小句子),具有合理计算能力的人可以尝试恢复该句子。

解决这个问题的另一种方法(这是我们将使用的方法)是比较句子嵌入空间中的句子。我们只需要使用机器学习模型创建句子嵌入(我们将使用InferSent稍后),然后比较句子的嵌入。然而,这种方法也引起了另一个问题:如果Bob或Alice训练一个Seq2Seq模型,该如何从另一方的嵌入返回到项目的近似描述?

考虑到它们的嵌入,人们可以恢复对句子的近似描述,这不是不合理的。这就是为什么我们将使用双方安全计算来计算嵌入的相似性,以Bob和Alice将计算嵌入的相似性的方式没有透露他们的嵌入,确保他们的项目创意安全。

整个流程如下图所示,其中Bob和Alice共享相同的机器学习模型,之后他们使用这个模型从句子到嵌入,然后对嵌入空间中的相似性进行安全计算。

整个过程的图表概述。

使用InferSent生成句子嵌入

Bi-LSTM max-pooling网络。来源:从自然语言推断数据的通用句子表示的监督学习。Alexis Conneau等人。

InferSent是一种由Facebook开发的通用句子表示的NLP技术,它使用监督训练来产生高可转移的表示。

他们使用了一种具有注意力的双向LSTM,它始终超过了许多无监督的训练方法,如SkipThought向量。他们还提供Pytorch实现我们会用它来生成句子嵌入。

注意:即使你没有GPU,你也可以有合理的性能来做几句话的嵌入。

生成句子嵌入的第一步是下载并加载预先训练好的InferSent模型:

导入numpy作为np导入torch #训练模型从:https://github.com/facebookresearch/金宝博游戏网址InferSent GLOVE_EMBS = '../dataset/GloVe/ GloVe . 840b .300d.txt' INFERSENT_MODEL = ' inference .allnli. txt'pickle' #加载训练InferSent模型模型=火炬。load(INFERSENT_MODEL, map_location=lambda storage, loc: storage) model.set_glove_path(GLOVE_EMBS) model.build_vocab_k_words(K=100000)

现在我们需要定义一个相似度度量来比较两个向量,为此,我将cos相似度(188betcom网页版)因为这很简单:

因为(\ pmb x \ pmb y) = \压裂{\ pmb x \ cdot \ pmb y} {| | \ pmb x | | \ cdot | | \ pmb y | |}

如你所见,如果我们有两个单位向量(范数为1的向量),方程分母中的两项将为1,我们将能够去除方程的整个分母,只剩下:

Cos (\hat{x}, \hat{y}) =\hat{x} \cdot\hat{y}

所以,如果我们将向量归一化,得到一个单位范数(这就是为什么向量在上面的方程中戴着帽子),我们可以使余弦相似度的计算变成一个简单的点积。这对我们以后计算相似距离有很大帮助当我们用一个框架来做点积的安全计算时。

因此,下一步是定义一个函数,该函数将获取一些句子文本并将其转发给模型以生成嵌入,然后将它们归一化为单位向量:

该函数将文本转发到模型中,#获取嵌入。在那之后,它将#归一化为单位向量。Def encode(model, text): embedding = model.encode([text])[0] embedding /= np. linalgg .norm(embedding)返回嵌入

正如您所看到的,这个函数非常简单,它将文本输入模型,然后将嵌入向量除以嵌入范数。

现在,出于实际原因,我将在后面使用整数计算来计算相似性,但是,InferSent生成的嵌入当然是实数。因此,在下面的代码中,您将看到我们创建了另一个函数缩放浮点值并移除基数点而且将它们转换为整数。还有另一个重要的问题,我们稍后将使用安全计算的框架不允许有符号整数,所以我们还需要在0.0和1.0之间剪辑嵌入值。这当然会导致一些近似错误,然而,我们仍然可以在有限精度的裁剪和缩放后得到非常好的近似(我使用14位缩放来避免在之后的点积计算中溢出问题):亚洲金博宝

此函数将缩放嵌入以#移除基数点。def scale(embedding): scale = 1 << 14 scale_embedding = np。clip(embedding, 0.0, 1.0) * SCALE return scale_embedding.astype(np.int32)

你可以在你的安全计算中使用浮点数,有很多框架支持它们,然而,这样做比较棘手,因此,我使用整数算术来简化教程。上面的函数只是一个简单的方法。很容易看出,我们可以在以后恢复这个嵌入,而不会损失太多的精度。

现在我们只需要创建一些我们将要用到的例句:

# Alice的句子列表alice_sentence = ['my cat loves to walk over my keyboard', 'I like to pet my cat',] # Bob的句子列表bob_sentence = [' The cat is always walk over my keyboard',]

并将它们转换为嵌入:

# Alice句子alice_句子1 = encode(model, alice_句子[0])alice_句子2 = encode(model, alice_句子[1])# Bob句子bob_句子1 = encode(model, bob_句子[0])

因为我们现在有了句子,而且每个句子都是标准化的,我们可以通过在向量之间做点亚洲金博宝积来计算余弦相似度:

> > > np。Dot (bob_sentence1, alice_sentence1) 0.8798542 >>> np。点(bob_sentence1, alice_sentence2) 0.62976325

我们可以看到,Bob的第一句与Alice的第一句最相似(~0.87),而Alice的第二句(~0.62)。

既然我们现在有了嵌入,我们只需要将它们转换为缩放的整数:

#缩放Alice句子嵌入alice_sentence1_scaled = Scale (alice_sentence1) alice_sentence2_scaled = Scale (alice_sentence2) #缩放Bob句子嵌入bob_sentence1_scaled = Scale (bob_sentence1) #这是句子>>> alice_sentence1数组的单位向量嵌入([0.01698913,-0.0014404,0.0010993,…, 0.00252409, 0.00828147, 0.00466533], dtype=float32) #这是作为整数的缩放向量>>> alice_sentence1_scaled array([278, 0,18,…, 44,135,76], dtype=int32)

现在,将这些嵌入作为缩放的整数,我们可以进入第二部分,在那里我们将在双方之间进行安全计算。

双方安全计算

为了在双方(Alice和Bob)之间执行安全计算,我们将使用偿框架.ABY实现了许多不同的安全计算方案,并允许您将计算描述为如下图所示的电路,其中描述了姚氏百万富翁的问题:

姚明的百万富翁问题。摘自ABY文档(https://github.com/encryptogro金宝博游戏网址up/ABY)。

正如你所看到的,我们有两个输入进入一个GT GATE(大于GATE),然后一个输出。该电路每个输入的比特长度为3,如果Alice输入大于Bob输入(GT GATE),则会计算。计算方然后秘密地共享他们的私人数据,然后可以使用算术共享、布尔共享或姚共享来安全地计算这些门。

ABY非常易于使用,因为您只需描述您的输入、共享、门,它将为您完成其余的工作,例如创建套接字通信通道,在需要时交换数据等。然而,实现完全是用c++编写的,我不知道它有任何Python绑定(这是一个很好的贡献机会)。

幸运的是,ABY有一个实现的例子,可以为我们做点积计算例子在这里.我不会在这里复制这个例子,但我们唯一需要改变的部分是读取我们之前创建的嵌入向量,而不是生成随机向量和增加位长度到32位。

之后,我们只需要在两台不同的机器上执行应用程序(或通过如下所示的本地模拟):

#这将执行服务器部分,-r 0指定角色(服务器)#,-n 4096定义向量的维度(InferSent生成# 4096维嵌入)。~# ./innerproduct -r 0 -n 4096 #在另一个进程(或另一台机器,但是对于另一台#机器执行,你必须明确指定IP)上也是一样的。~# ./innerproduct -r 1 -n 4096

得到如下结果:

alice_sentence1与bob_sentence1的内积= 226691917 alice_sentence2与bob_sentence1的内积= 171746521

即使在整数表示中,你也可以看到Alice的第一句话和Bob句子的内积更高,这意味着相似度也更高。但是现在让我们把这个值转换回float:

>>> SCALE = 1 << 14 #这是我们应该得到>>> np的点积。这是我们在安全计算>>> 226691917 / SCALE**2.0 0.8444931 #这是我们应该得到>>> np的点积。这是我们通过安全计算>>> 171746521 / SCALE**2.0 0.6398056得到的内积

正如您所看到的,我们得到了非常好的近似,即使存亚洲金博宝在低精度的数学和无符号整数要求。当然,在现实生活中,你不会有这两个值和向量,因为它们应该是隐藏的,但是为了适应这些变化是微不足道的,你只需要调整ABY代码来加载它正在执行的一方的向量,并使用双方的正确IP地址/端口。

希望你喜欢!

- Christian S. Perone

引用这篇文章为:Christian S. Perone,“使用InferSent嵌入和安全两方计算保护隐私的句子语义相似性”,在亚洲金博宝未发现的地域22/01/2018,//www.cpetem.com/2018/01/privacy-preserving-infersent/

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

更新:黑客新闻在这里讨论

张量流计算图

tensorlogo

最神奇的组成部分之一TensorFlow体系结构是可以序列化使用的计算图协议缓冲区.此计算图遵循定义良好的格式(点击这里为原型文件),并描述了您指定的计算(它可以是一个深度学习模型,如CNN,一个简单的逻辑回归,甚至任何你想要的计算)。例如,这是一个非常简单的TensorFlow计算图的例子,我们将在本教程中使亚洲金博宝用(使用TensorFlow Python API):

import tensorflow作为tf与tf. session()作为sess: input_placeholder = tf.placeholder(tf.int32, 1, name="input") sub_op = tf。子(input_placeholder特遣部队。c在stant(2, dtype=tf.int32)) add_op = tf.add(sub_op, tf.constant(5, dtype=tf.int32)) output = tf.add(add_op, tf.constant(100, dtype=tf.int32), name="output") tf.train.write_graph(sess.graph_def, ".", "graph.pb", True)
计算图的表示。
计算图的表示。

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

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

节点{名称:“输入”op:“占位符”attr{关键:“dtype”价值{类型:DT_INT32}} attr{关键:“形状”价值{{昏暗的{形状大小:1  } } } } } 节点{名称:“常量”op:“常量”attr{关键:“dtype”价值{类型:DT_INT32}} attr{关键:“价值”价值{张量{dtype: DT_INT32 tensor_shape {} int_val: 2  } } } } --- >( 为了简便起见,我们省略了)<——节点{名称:“输出”op:“添加”输入:“添加”输入:“Const_2 attr{关键:“T”值{类型:DT_INT32}}}{制作人:9}版本

这是一个非常简单的图亚洲金博宝,TensorFlow图是其实没那么简单,因为TensorFlow模型可以很容易地包含超过300个节点,这取决于你指定的模型,特别是对于深度学习模型。

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

LLVM前端,IR和后端

LLVM-Logo-Derivative-1

LLVM框架是构建编译器和工具链的一个非常好的、模块化的、完整的生态系统。下图亚洲金博宝展示了对LLVM架构的非常好的描述,这对我们来说很重要:

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

(上图只是LLVM架构的一小部分,请对其进行全面描述看这篇不错的文章选自克里斯·拉特纳写的AOSA书)

在上面的图片中,我们可以看到LLVM提供了很多核心功能,在左边你可以看到许多语言可以为各自的语言前端编写代码,在那之后,不管你用哪种语言写代码,所有的东西都转换成一种非常强大的语言叫做亚洲金博宝LLVM IR中间表示),你可以想象,它是程序集代码本身之前的中间表示。在我看来,IR是使LLVM如此神奇的关键组件,因为无论您用哪种语言编写代码(或者即使它是一个JIT的IR),所有内容都以相同的表示结束,然后这里就是魔术发生的地方,因为IR可以利用LLVM优化(也称为亚洲金博宝转换和分析传递).

在此IR生成之后,您可以将其馈送到任何LLVM后端,为LLVM支持的任何架构生成本机代码(如x86, ARM, PPC等),然后您最终可以在LLVM优化通过后以本机性能执行代码。

为了使用LLVM进行JIT代码,您所需要的只是以编程方式构建IR,创建一个执行引擎来转换(在执行时)将IR转换为本机代码,获取一个指针,指向你已经JIT化的函数,然后最终执行它。这里我将使用LLVM的Python绑定llvmlite,这是非常python亚洲金博宝化的,易于使用。

使用Python和LLVM JIT TensorFlow图

流

现在让我们使用LLVM和Python来JIT TensorFlow计算图。这是绝不是一个全面的实现这是一种非常简单亚洲金博宝的方法,一种过度简化的方法,它假设了一些东西:一个整数闭包类型,只是一些TensorFlow操作,还有一个单一的标量支持,而不是高秩张量。

那么,让我们开始构建JIT代码;首先,让我们导入所需的包,初始化一些LLVM子系统,并为TensorFlow整数类型定义LLVM各自的类型:

从谷歌导入tensorflow为tf。从tensorflow.core.framework导入types_pb2从tensorflow.python.framework导入ops导入llvmlite。Ir as ll import llvmlite。binding as llvm llvm.initialize() llvm.initialize_native_target() llvm.initialize_native_asmprinter() TYPE_TF_LLVM = {types_pb2。DT_INT32: ll.IntType(32),}

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

类TFGraph(对象):def __init__(self, filename="图形。pb",二进制=False):自我。graph_def = graph_pb2.GraphDef() with open("graph. "pb", "rb")作为f:如果二进制:self.graph_def. parsefromstring (f.r read())否则:text_format.Merge(f.r read(), self.graph_def) def get_node(self, name): self.graph_def中的节点。节点:if Node .name == name:返回节点

让我们从定义main函数开始,它将是代码的起点:

def run_main(): graph = TFGraph("graph. conf ")pb", False) input_node = graph.get_node("input") output_node = graph.get_node("output") input_type = TYPE_TF_LLVM[input_node.attr["dtype"]。output_type = TYPE_TF_LLVM[output_node.attr["T"]. type] output_type = TYPE_TF_LLVM。type] module = ll. module () func_type = ll. module ()函数类型(output_type, [input_type]) func = ll。函数(module, func_type, name='tensorflow_graph') func_args [0].name =' input' bb_entry = func_append_basic_block ('entry') ir_builder = ll.IRBuilder(bb_entry)

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

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

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

Def build_graph(ir_builder, graph, node): if节点。op == "Add": 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节点。op == "Sub": 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节点。op == "占位符":function_args = ir_builder.function。如果arg.name == node.name: return arg raise RuntimeError("Input [{}] not found !".format(node.name))op == "Const": llvm_const_type = TYPE_TF_LLVM[node.attr["dtype"].输出说明类型]const_value = node.attr["value"].tensor.int_val[0] llvm_const_value = llvm_const_type(const_value) return llvm_const_value

在这个函数中,我们通过参数接收IR Builder、我们之前创建的图形类和输出节点。这个函数将通过IR Builder递归地构建LLVM IR。在这里你可以看到,我只实现了TensorFlow图中的Add/Sub/Placeholder和Const操作,只是为了能够支持我们之前定义的图。

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

def create_engine(module): features = llvm.get_host_cpu_features().flatten() llvm_module = llvm.parse_assembly(str(module)) target = llvm.Target.from_default_triple() target_machine = target。Create_target_machine (opt=3, features=features) engine = llvm。create_mcjit_compiler(llvm_module, target_machine) engine.finalize_object() print target_machine.emit_assembly(llvm_module) return engine

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

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

Ret = build_graph(ir_builder, graph, output_node) ir_builder. Ret (Ret) with open("output. bat ")f: f.write(str(module)) engine = create_engine(module) func_ptr = engine.get_function_address("tensorflow_graph") cfunc = CFUNCTYPE(c_int, c_int)(func_ptr) ret = cfunc(10) print "执行输出:{}".format(ret)

正如您在上面的代码中所看到的,我们只调用build_graph ()方法,然后使用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(使用上面显示的代码):

import tensorflow作为tf与tf. session()作为sess: input_placeholder = tf.placeholder(tf.int32, 1, name="input") sub_op = tf。子(input_placeholder特遣部队。c在stant(2, dtype=tf.int32)) add_op = tf.add(sub_op, tf.constant(5, dtype=tf.int32)) output = tf.add(add_op, tf.constant(100, dtype=tf.int32), name="output") tf.train.write_graph(sess.graph_def, ".", "graph.pb", True)

生成的LLVM IR如下图所示:

;ModuleID = "" target triple = "unknown-unknown-unknown" target datalayout = "" define i32 @"tensorflow_graph"(i32% "input") {entry: %"。3" =子i32% "输入",2% "。4" =添加i32 %"。3", 5% "。5" =添加i32 %"。4", 100 ret i32% "。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 ".注. gnu -stack","",@progbits .size "

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

你也可以使用一个名为“llc”的LLVM工具,它可以获取LLVM IR文件,并为你想要的任何其他平台生成程序集,例如,下面的命令行将为ARM架构生成本机代码:

llc -O3 out。Ll -march=arm -o sample.s

输出文件如下:

。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 "out。ll" .globl tensorflow_graph .align 2 .type tensorflow_graph,%function tensorflow_graph: @ @tensorflow_graph .fnstart @ BB#0: @ %entry add r0, r0, #103 mov pc, lr .Lfunc_end0: .size tensorflow_graph, .Lfunc_end0-tensorflow_graph .fnend .section ".note.GNU-stack","",%progbits

如上所示,ARM汇编代码也只是一个“添加”汇编指令,后面跟着一个返回指令。

这真的很好,因为我们可以自然地利用LLVM框架。例如,今天的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白皮书

完整代码

从谷歌导入tensorflow为tf。从tensorflow.core.framework导入types_pb2从tensorflow.python.framework导入ops导入llvmlite。Ir as ll import llvmlite。binding as llvm llvm.initialize() llvm.initialize_native_target() llvm.initialize_native_asmprinter() TYPE_TF_LLVM = {types_pb2。DT_INT32: ll.IntType(32),}类TFGraph(对象):def __init__(self, filename="图形。pb",二进制=False):自我。graph_def = graph_pb2.GraphDef() with open("graph. "pb", "rb")作为f:如果二进制:self.graph_def. parsefromstring (f.r read())否则:text_format.Merge(f.r read(), self.graph_def) def get_node(self, name): self.graph_def中的节点。节点:if Node .name == name:返回节点def build_graph(ir_builder, graph, Node): if节点。op == "Add": 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节点。op == "Sub": 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节点。op == "占位符":function_args = ir_builder.function。如果arg.name == node.name: return arg raise RuntimeError("Input [{}] not found !".format(node.name))op == "Const": llvm_const_type = TYPE_TF_LLVM[node.attr["dtype"].输出说明type] const_value = node.attr["value"].tensor.int_val[0] llvm_const_value = llvm_const_type(const_value) return llvm_const_value def create_engine(module): features = llvm.get_host_cpu_features().flatten() llvm_module = llvm.parse_assembly(str(module)) target = llvm.Target.from_default_triple() target_machine = target。Create_target_machine (opt=3, features=features) engine = llvm。create_mcjit_compiler(llvm_module, target_machine) engine.finalize_object() print target_machine.emit_assembly(llvm_module) return engine def run_main(): graph = TFGraph("graph.pb", False) input_node = graph.get_node("input") output_node = graph.get_node("output") input_type = TYPE_TF_LLVM[input_node.attr["dtype"].type] output_type = TYPE_TF_LLVM[output_node.attr["T"].type] module = ll.Module() func_type = ll.FunctionType(output_type, [input_type]) func = ll.Function(module, func_type, name='tensorflow_graph') func.args[0].name = 'input' bb_entry = func.append_basic_block('entry') ir_builder = ll.IRBuilder(bb_entry) ret = build_graph(ir_builder, graph, output_node) ir_builder.ret(ret) with open("output.ir", "w") as f: f.write(str(module)) engine = create_engine(module) func_ptr = engine.get_function_address("tensorflow_graph") cfunc = CFUNCTYPE(c_int, c_int)(func_ptr) ret = cfunc(10) print "Execution output: {}".format(ret) if __name__ == "__main__": run_main()
引用本文为:Christian S. Perone,“使用Python和LLVM为TensorFlow计算图生成JIT本地代码”,在亚洲金博宝未发现的地域22/08/2016,//www.cpetem.com/2016/08/jit-native-code-generation-for-tensorflow-computation-graphs-using-python-and-llvm/