numpy的调度:当numpy的成为协议的生态系统

介绍

不是有很多人使用Python科学的生态系统合作都知道的NEP 18派遣机构与NumPy的高层阵列功能)。鉴于该协议的重要性,我决定写这篇简短的开场白,新的调度肯定会带来很多的好处对于Python的科学生态系统。

如果您使用PyTorch,TensorFlow,DASK,等等,你肯定注意到了与numpy的他们的API合同的相似性。它由事故不是,numpy的的API是科学计算中最基本也是最广泛使用的API之一。numpy的是如此普遍,它不再是唯一的API,它变得越来越的协议或API规范。

阅读更多

在PyTorch随机现有功能

Ťrained MLP with 2 hidden layers and a sine prior.

我尝试用这种方法在描述“深强化学习随机函数之前” by Ian Osband et al. at NPS 2018, where they devised a very simple and practical method for uncertainty using bootstrap and randomized priors and decided to share the PyTorch code.

我真的很喜欢引导方法,在我看来,他们通常是执行,并提供非常好的后逼近深连接,贝叶斯方法,而不必应付变推理最简单的方法。亚洲金博宝它们实际上显示,在所述线性情况下,该方法提供了一种贝叶斯后的纸张。

该方法的主要思想是具有引导到一起提供一种非参数数据扰动采用随机先验,这无非只是随机初始化网络。

$$Q_{\theta_k}(x) = f_{\theta_k}(x) + p_k(x)$$

该final model \(Q_{\theta_k}(x)\) will be the k model of the ensemble that will fit the function \(f_{\theta_k}(x)\) with an untrained prior \(p_k(x)\).

我们去的代码。第一类是一个简单MLP 2隐藏层和Glorot初始化:

Class MLP(nn.Module): def __init__(self): super().__init__() self.l1 = nn.Linear(1, 20) self.l2 = nn.Linear(20, 20) self.l3 = nn.Linear(20, 1) nn.init.xavier_uniform_(self.l1.weight) nn.init.xavier_uniform_(self.l2.weight) nn.init.xavier_uniform_(self.l3.weight) def forward(self, inputs): x = self.l1(inputs) x = nn.functional.selu(x) x = self.l2(x) x = nn.functional.selu(x) x = self.l3(x) return x

再后来,我们定义一个类,将采取的模式,并产生最终的模型结果之前:

类ModelWithPrior(nn.Module):DEF __init __(个体,base_model:nn.Module,prior_model:nn.Module,prior_scale:浮子= 1.0):超级().__ INIT __()self.base_model = base_model self.prior_model = prior_model自。prior_scale = prior_scale def forward(self, inputs): with torch.no_grad(): prior_out = self.prior_model(inputs) prior_out = prior_out.detach() model_out = self.base_model(inputs) return model_out + (self.prior_scale * prior_out)

而且它基本上是!正如你所看到的,这是一个非常简单的方法,在第二亚洲金博宝部分,我们就创建了一个自定义的forward()方法,以避免计算/与模型预测积累梯度对于现有网络,并将其总结(缩放后)它。

要训​​练它,你只需要使用不同的白手起家每个集成模型,就像下面的代码:

DEF train_model(x_train,y_train,base_model,prior_model):模型= ModelWithPrior(base_model,prior_model,1.0)loss_fn = nn.MSELoss()优化= torch.optim.Adam(model.parameters(),LR = 0.05)为在历元范围(100):model.train()preds =模型(x_train)损耗= loss_fn(preds,y_train)optimizer.zero_grad()loss.backward()optimizer.step()的返回模型

以及使用与置换(自举)采样,如:

数据集= TensorDataset(...)bootstrap_sampler = RandomSampler(数据集,TRUE,LEN(数据集))train_dataloader =的DataLoader(数据集,=的batch_size LEN(数据集),采样= bootstrap_sampler)

在这种情况下,我用原来的文件中使用的相同的小数据集:

用一个简单的MLP训练它之后现有以及,对于不确定性的结果示于下面:

现有训练的模型与MLP,使用50种型号的合奏。

如果我们看一下刚刚的前科,我们将看到未经训练的网络的变化:

我们也可以想像,由于不同的初始化以及噪音引导个体模型预测出他们的差异:

绘制显示在红色每个单独的模型预测和真实数据。

ñow, what is also quite interesting, is that we can change the prior to let’s say a fixed sine:

类SinPrior(nn.Module):高清向前(个体经营,输入):返回torch.sin(3 *输入)

然后,当我们训练用正弦之前相同的MLP模型,但这个时候,我们可以看到它是如何影响最终的预测性和不确定性范围:

If we show each individual model, we can see the effect of the prior contribution to each individual model:

Plot showing each individual model of the ensemble trained with a sine prior.

I hope you liked, these are quite amazing results for a simple method that at least pass the linear “sanity check”. I’ll explore some pre-trained networks in place of the prior to see the different effects on predictions, it’s a very interesting way to add some simple priors.

引用本文为:基督教S. Perone,“在PyTorch随机现有的功能,”在亚洲金博宝未知领域,24/03/2019,//www.cpetem.com/2019/03/randomized-prior-functions-in-pytorch/

188betiosapp

更新2019年2月28日:我添加了一个new blog post with a slide deck包括我做了PyData蒙特利尔的表现。

Ťoday, at the PyTorch Developer Conference, the PyTorch team announced the plans and the release of the PyTorch 1.0 preview with many nice features such as a JIT for model graphs (with and without tracing) as well as theLibTorch中,PyTorch C ++ API中的所述一个最重要的发行公告今天在我看来做。

鉴于理解这个新的API是如何工作的,我决定写这篇文章显示的是现在PyTorch C ++ API发布后打开许多机会的一个例子的巨大兴趣。在这篇文章中,我将PyTorch推理整合到一个使用的NodeJS c + +插件,就像不同的框架/语言使用C ++ API,它现在可能的整合的一个例子本土的NodeJS。

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

正如你所看到的,整合是无缝的,我可以用一个跟踪RESNET作为计算图模型和饲料任何张量它得到的输出预测。

介绍

简单地说,libtorch是PyTorch库版本。它包含的底层基础所使用的PyTorch,如ATEN(张量库),它包含了所有的张量操作和方法。还Libtorch包含autograd,which is the component that adds the automatic differentiation to the ATen tensors.

谨慎为那些谁现在正在开始一个字是要小心使用,可以从ATEN和autograd创建两个张量,不要混合使用它们中,ATEN将返回平原张量(当你使用它们创建命名空间)而autograd函数(从火炬命名空间)将返回Variable中,加入其自动分化机制。

有关PyTorch内部工作方式的一个更广泛的教程,请把我以前的上教程一看PyTorch内部架构

Libtorch可以从下载Pytorch网站它仅作为一会儿预览。您还可以找到文档本网站,这主要是一个Doxygen的渲染文档。我发现图书馆相当稳定,这是有道理的,因为它实际上是暴露PyTorch的稳定基础,但是,也有一些问题,标题和一些小问题,关于图书馆的组织,而开始使用它,你可能会发现(将希望尽快修复)。

For NodeJS, I’ll use theñ在ive Abstractions库(楠),这是最值得推荐的库(实际上基本是仅标头库)来创建的NodeJS C ++的附加组件和Cmake-js,because libtorch already provide the cmake files that make our building process much easier. However, the focus here will be on the C++ code and not on the building process.

该flow for the development, tracing, serializing and loading the model can be seen in the figure on the left side.

它从开发过程和PyTorch(Python的域)追查正在做的,然后在C ++领域的装载和推理(在我们的例子中添加的NodeJS上)。

Wrapping the Tensor

在的NodeJS,创建一个对象的JavaScript的世界一流的公民,你需要继承自ObjectWrapClass, which will be responsible for wrapping a C++ component.

#ifndef TENSOR_H #define TENSOR_H #include  #include  namespace torchjs { class Tensor : public Nan::ObjectWrap { public: static NAN_MODULE_INIT(Init); void setTensor(at::Tensor tensor) { this->mTensor = tensor; } torch::Tensor getTensor() { return this->mTensor; } static v8::Local NewInstance(); private: explicit Tensor(); ~Tensor(); static NAN_METHOD(New); static NAN_METHOD(toString); static Nan::Persistent constructor; private: torch::Tensor mTensor; }; } // namespace torchjs #endif

As you can see, most of the code for the definition of our Tensor class is just boilerplate. The key point here is that thetorchjs ::张量将一个包裹火炬::张量我们增加了两个特殊的公共方法(setTensorgetTensor)to set and get this internal torch tensor.

因为我不会显示所有的实现细节e most parts of it are NodeJS boilerplate code to construct the object, etc. I’ll focus on the parts that touch the libtorch API, like in the code below where we are creating a small textual representation of the tensor to show on JavaScript (的toString方法):

ñAN_METHOD(Tensor::toString) { Tensor* obj = ObjectWrap::Unwrap(info.Holder()); std::stringstream ss; at::IntList sizes = obj->mTensor.sizes(); ss << "Tensor[Type=" << obj->mTensor.type() << ", "; ss << "Size=" << sizes << std::endl; info.GetReturnValue().Set(Nan::New(ss.str()).ToLocalChecked()); }

我们在上面做代码,通过在刚刚起步的内部对象张从被包装的对象解缠它。之后,我们建立与张力的大小(各维的尺寸)和它的类型(浮法等)的字符串表示。

Wrapping Tensor-creation operations

Let’s create now a wrapper code for the火炬::者函数负责创建填充有恒定1项的任何定义的形状的张量。

ñAN_METHOD(ones) { // Sanity checking of the arguments if (info.Length() < 2) return Nan::ThrowError(Nan::New("Wrong number of arguments").ToLocalChecked()); if (!info[0]->IsArray() || !info[1]->IsBoolean()) return Nan::ThrowError(Nan::New("Wrong argument types").ToLocalChecked()); // Retrieving parameters (require_grad and tensor shape) const bool require_grad = info[1]->BooleanValue(); const v8::Local array = info[0].As(); const uint32_t length = array->Length(); // Convert from v8::Array to std::vector std::vector dims; for(int i=0; i 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 ++类型的参数。一旦我们所需要的参数,我们再调用火炬::者从libtorch功能,这个功能将创建在这里我们使用一个新的张量torchjs ::张量类,我们创建较早把它包起来。

就是这样,我们刚刚曝光,可以用来作为本地JavaScript运行一个火炬操作。

Intermezzo for the PyTorch JIT

引进的PyTorch JIT围绕着火炬脚本的概念。火炬脚本是Python语言的有限子集,并配有自己的编译器和转换通行证(优化等)。

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

请注意,如果你有你的代码依赖于外部因素或数据,跟踪你希望不会工作,因为它会记录下图的某次执行,因此替代方案,以提供脚本分支决定。然而,在大多数的情况下,跟踪是我们所需要的。

了解这些差异,让我们从两个通过跟踪和脚本生成的脚本模块来看看中间表示(IR)。

@ torch.jit.script DEF happy_function_script(X):RET = torch.rand(0)如果真== TRUE:RET = torch.rand(1)否则:RET = torch.rand(2)返回RET DEF happy_function_trace(X):t RET = torch.rand(0)如果真== TRUE:RET = torch.rand(1)否则:RET = torch.rand(2)返回RET traced_fn = torch.jit.trace(happy_function_trace,(torch.tensor(0),),check_trace = FALSE)

在上面的代码中,我们提供两个功能,一个是使用@ torch.jit.script装饰,并且它是脚本的方式来创建一个火炬脚本,而第二个功能是正在使用的跟踪功能torch.jit.trace。这并不是说我故意增加了一个“真正的==真”的功能(这将永远是正确的)决定。

ñow, if we inspect the IR generated by these two different approaches, we’ll clearly see the difference between the tracing and scripting approaches:

#1)格拉夫从编写脚本的方法图(%×:动态){%16:整数=拘谨::常数[值= 2]()%10:整数=拘谨::常数[值= 1]()%7:INT =拘谨::常数[值= 1]()%8:整数=拘谨::常数[值= 1]()%9:整数= ATEN ::当量(%7,%8)%保留:动态= ::拘谨如果(%9)BLOCK0(){%11:INT [] =拘谨:: ListConstruct(%10)%12:整数=拘谨::常数[值= 6]()%13:INT =拘谨::常数[值= 0]()%14:INT [] =拘谨::常数[值= [0,-1]()%ret.2:动态=阿坦::兰特(11%,12%,%13, %14) -> (%ret.2) } block1() { %17 : int[] = prim::ListConstruct(%16) %18 : int = prim::Constant[value=6]() %19 : int = prim::Constant[value=0]() %20 : int[] = prim::Constant[value=[0, -1]]() %ret.3 : Dynamic = aten::rand(%17, %18, %19, %20) -> (%ret.3) } return (%ret); } # 2) Graph from the tracing approach graph(%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

ñow, before implementing the Script Module in NodeJS, let’s first trace a ResNet network using PyTorch (using just Python):

traced_net = torch.jit.trace(torchvision.models.resnet18(),torch.rand(1,3,224,224))traced_net.save( “resnet18_trace.pt”)

As you can see from the code above, we just have to provide a tensor example (in this case a batch of a single image with 3 channels and size 224×224. After that we just save the traced network into a file calledresnet18_trace.pt

现在,我们已经准备好执行脚本模块中的NodeJS以加载该文件被追踪。

包裹脚本模块

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

// Class构造ScriptModule :: ScriptModule(常量的std :: string文件名){//加载从文件这 - > mModule =炬:: JIT ::负载(文件名)所追踪的网络;} // JavaScript对象创建NAN_METHOD(ScriptModule ::新){如果(info.IsConstructCall()){//获取文件名参数V8 ::字符串:: Utf8Value param_filename(信息[0]  - >的ToString());常量的std :: string文件名=的std :: string(* param_filename);//使用该文件名ScriptModule * OBJ =新ScriptModule(文件名)创建一个新的脚本模块;obj->裹(info.This());。info.GetReturnValue()设置(info.This());}否则{V8 ::本地缺点=楠::新(构造);。info.GetReturnValue()设置(楠:: NewInstance方法(缺点).ToLocalChecked());}}

正如你可以从上面的代码中看到,我们只是创建一个类,将调用火炬:: JIT ::负荷功能经过追查网络的文件名。我们也有JavaScript对象,我们在那里参数转换为C ++类,然后创建的一个新实例的执行torchjs :: ScriptModule

该wrapping of the forward pass is also quite straightforward:

NAN_METHOD(ScriptModule ::向前){ScriptModule * script_module = ObjectWrap ::展开(info.Holder());楠:: MaybeLocal 说不定=楠::要(信息[0]);张量*张量=楠:: ObjectWrap ::展开<张量>(maybe.ToLocalChecked());火炬::张量torch_tensor = tensor-> getTensor();火炬::张量输出= script_module-> mModule->向前({torch_tensor})toTensor();自动newinst中=张量:: NewInstance方法();张量* OBJ =楠:: ObjectWrap ::展开<张量>(newinst中);obj-> setTensor(输出);。info.GetReturnValue()设置(newinst中);}

正如你所看到的,在这段代码中,我们只收到张量作为参数,我们得到的内部火炬::张量from it and then call the forward method from the script module, we wrap the output on a newtorchjs ::张量然后返回。

And that’s it, we’re ready to use our built module in native NodeJS as in the example below:

VAR torchjs =需要( “./构建/释放/ torchjs”);VAR = script_module新torchjs.ScriptModule( “resnet18_trace.pt”);VAR数据= torchjs.ones([1,3,224,224],假);VAR输出= script_module.forward(数据);

I hope you enjoyed ! Libtorch opens the door for the tight integration of PyTorch in many different languages and frameworks, which is quite exciting and a huge step towards the direction of production deployment code.

– Christian S. Perone

引用本文为:基督教S. Perone, “PyTorch 1.0追踪JIT和LibTorch C ++ API来PyTorch融入的NodeJS,” 在亚洲金博宝未知领域,2018年2月10日,188betiosapp

PyTorch – Internal Architecture Tour

更新2019年2月28日:我添加了一个new blog post with a slide deck包括我做了PyData蒙特利尔的表现。

介绍

这篇文章是围绕PyTorch代码库参观,它的目的是为PyTorch和其内部的建筑设计指导。我的主要目标是提供一些对于那些谁有兴趣了解发生什么事超出了面向用户的API,并展示一些新的东西超出了已经包含在其他的教程非常有用。

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

小号hort intro to Python extension objects in C/C++

正如你可能知道,你可以使用C和C的Python扩展++和发展所谓的“延伸”。所有PyTorch繁重的工作在C / C ++,而不是纯Python实现。在C / C定义一个新的Python对象类型++,可以定义像低于该一个例子的结构(其是用于autograd基Variable类):

// Python对象即背torch.autograd.Variable结构THPVariable {PyObject_HEAD炬:: autograd ::可变CDATA;*的PyObject backward_hooks;};

正如你所看到的,有在定义的开始宏,叫PyObject_HEAD,该宏的目标是Python中的标准化的对象和将扩大到包含一个指向类型的对象(其限定初始化方法,分配器等),也与参考计数器的场另一种结构。

有Python的API中的两个额外的宏叫Py_INCREF()Py_DECREF(),这是用来递增和递减Python对象的引用计数器。多个实体可以借用或自己到其他对象的引用(引用计数器增加),只有当这个引用计数器达到零(当所有的引用被摧毁),Python将自动使用其垃圾回收对象中删除的记忆。

You can read more about Python C/++ extensionshere

滑稽的事实:这是在许多应用亚洲金博宝中非常普遍使用小整数作为索引,计数器等为了提高效率,官方CPython的解释Caches the integers from -5 up to 256. For that reason, the statementa = 200; b = 200; a is b将会真正,while the statementa = 300; b = 300; a is b将会

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

PyTorch有它自己的张量表示,该解耦对外交涉PyTorch内部表示。然而,因为它是很常见的,尤其是当数据亚洲金博宝从多种来源,装载有numpy的阵列无处不在,所以我们真的需要作出与NumPy和PyTorch张量之间的转换。出于这个原因,PyTorch提供了两个方法叫from_numpy()numpy的(),其将一个numpy的阵列,反之亦然,分别PyTorch阵列和。如果我们看就是被称为一个numpy的数组转换成PyTorch张量的代码,我们可以得到在PyTorch的内部表示更多的见解:

在::张量tensor_from_numpy(*的PyObject OBJ){如果(!PyArray_Check(OBJ)){抛出类型错误( “预期np.ndarray(得到的是%S)”,Py_TYPE(OBJ) - > tp_name);}自动阵列=(PyArrayObject *)OBJ;INT NDIM = PyArray_NDIM(数组);自动调整大小= to_aten_shape(NDIM,PyArray_DIMS(阵列));自动步幅= to_aten_shape(NDIM,PyArray_STRIDES(阵列));// NumPy的进步使用字节。火炬大步使用元素计数。自动element_size_in_bytes = PyArray_ITEMSIZE(数组);为(自动&步幅:步幅){步幅/ = element_size_in_bytes;} //(...) - 为了简洁省略无效* 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的数组对象共享相同的存储器区域。

该re is also an important point here: when Numpy array object goes out of scope and get a zero reference count, it will be garbage collected and销毁,这就是为什么有在管线20中的numpy的阵列对象的引用计数的增量。

在此之后,PyTorch将创建从该numpy的数据blob一个新的张量对象,并且在创建新张量的它与存储器的大小和进展以及稍后将被使用的功能通过借用存储数据指针,一起the Tensor Storage (we’ll discuss this in the next section) to release the data by decrementing the reference counting to the Numpy array object and let Python take care of this object life cycle.

tensorFromBlob()method will create a new Tensor, but only after creating a new “Storage” for this Tensor. The storage is where the actual data pointer will be stored (and not in the Tensor structure itself). This takes us to the next section about张量储量

张量存储

张量的实际原始数据不直接保存在张量结构,但在另一种结构称为存储,这又是张量结构的一部分。

正如我们在前面的代码看到从tensor_from_numpy(),有一个呼叫tensorFromBlob()将从原始数据blob创建一个张量。这最后一个功能将调用另外一个函数storageFromBlob(),这将反过来,对于这个数据根据其类型创建存储。在CPU浮点类型的情况下,它会返回一个新的CPUFloatStorageinstance.

该CPUFloatStorageis basically a wrapper with utility functions around the actual storage structure calledŤHFloatStoragethat we show below:

typedef结构THStorage {真实*数据;ptrdiff_t的大小;INT引用计数;焦标志;THAllocator *分配器;无效* allocatorContext;STRUCT THStorage *图;} THStorage;

(代码从THStorage。h

As you can see, theTHStorage具有指向原始数据,它的尺寸,标志和也是一个有趣的领域被称为分配器我们马上要讨论的。同样重要的是要注意,关于如何解释里面的数据没有元数据THStorage,这是由于这样的事实,存储是“哑”关于它的内容,它是张量的责任要懂得“视图”或解释这个数据。

From this, you already probably realized that we can have multiple tensors pointing to the same storage but with different views of this data, and that’s why viewing a tensor with a different shape (but keeping the same number of elements) is so efficient. This Python code below shows that the data pointer in the storage is being shared after changing the way Tensor views its data:

>>> tensor_a = torch.ones((3,3))>>> tensor_b = tensor_a.view(9)>>> tensor_a.storage()。DATA_PTR()== tensor_b.storage()。DATA_PTR()真

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

现在,我们的7号线锯ŤHFloatStorage结构中,存在一个指向THAllocatorstructure there. And this is very important because it brings flexibility regarding the allocator that can be used to allocate the storage data. This structure is represented by the following code:

typedef结构THAllocator {无效*(* malloc的)(无效*,ptrdiff_t的);无效*(* realloc的)(无效*,无效*,ptrdiff_t的);空隙(*免费)(无效*,无效*);} THAllocator;

(代码从THAllocator.h

As you can see, there are three function pointer fields in this structure to define what an allocator means: a malloc, realloc and free. For CPU-allocated memory, these functions will, of course, relate to the traditional malloc/realloc/free POSIX functions, however, when we want a storage allocated on GPUs we’ll end up using the CUDA allocators such as thecudaMallocHost(),就像我们可以在看THCudaHostAllocatormalloc function below:

静态无效* THCudaHostAllocator_malloc(无效* CTX,ptrdiff_t的大小){void *的PTR;如果(大小<0)THError( “无效的存储器大小:%LD”,大小);如果(大小== 0)返回NULL;THCudaCheck(cudaMallocHost(PTR,大小));返回PTR;}

(代码从THCAllocator.c

你可能注意到在库组织模式,但要记住这些公约是很重要的导航信息库时,这里总结(取自PyTorch LIB自述):

  • ŤH=ŤorcH
  • THC=ŤorcHCuda
  • 乡镇卫生院=ŤorcHCuda小号解析
  • THCUNN=ŤorcHCUDAñeuralñetwork
  • THD=ŤorcHdistributed
  • THNN=ŤorcHñeuralñetwork
  • THS=ŤorcH小号解析

Ťhis convention is also present in the function/class names and other objects, so it is important to always keep these patterns in mind. While you can find CPU allocators in the TH code, you’ll find CUDA allocators in the THC code.

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

typedef结构THTensor {*的int64_t大小;*的int64_t步幅;INT n标注;THStorage *存储;ptrdiff_t的storageOffset;INT引用计数;焦标志;} THTensor;

(Code fromTHTensor.h

正如你所看到的,主THTensor结构保持的尺寸/步幅/尺寸/偏移/等以及存储(THStorage),用于张量数据。

我们可以概括所有这种结构,我们在下图中看到:

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

共享内存

共享内存可以根据平台支持多种不同的方式来实现。PyTorch支持其中的一些,但是为了简单起见,我会在这里讨论关于使用CPU(而非GPU)的MacOS会发生什么。由于PyTorch支持多种共享存储器方法,这部分是有点棘手把握成,因为它涉及间接在代码多个级别。

PyTorch provides a wrapper around the Pythonmultiprocessing模块,并且可以从被导入torch.multiprocessing。aro他们实现的改变在这个包装器und the official Python multiprocessing were done to make sure that everytime a tensor is put on a queue or shared with another process, PyTorch will make sure that only a handle for the shared memory will be shared instead of a new entire copy of the Tensor.

现在,很多人不知道从PyTorch张量方法称为share_memory_(),however, this function is what triggers an entire rebuild of the storage memory for that particular Tensor. What this method does is to create a region of shared memory that can be used among different processes. This function will, in the end, call this following function below:

静态THStorage * THPStorage_(newFilenameStorage)(ptrdiff_t的大小){INT标志= TH_ALLOCATOR_MAPPED_SHAREDMEM |TH_ALLOCATOR_MAPPED_EXCLUSIVE;的std :: string手柄= THPStorage _(__ newHandle)();自动CTX = libshm_context_new(NULL,handle.c_str(),标志);返回THStorage_(newWithAllocator)(大小,&THManagedSharedAllocator,(无效*)CTX);}

(Code fromStorageSharing.cpp

正如你所看到的,这个功能将创建使用称为特殊分配器另一个存储ŤHManagedSharedAllocator。该功能首先定义一些标志,然后它创建一个手柄,其在格式的字符串/ torch_ [进程id] _ [随机数],之后,它就会使用特殊创建一个新的存储ŤHManagedSharedAllocator。Ťhis allocator has function pointers to an internal PyTorch library calledlibshm,将实施Unix Domain SocketCommunication to share the shared memory region handles. This allocator is actual an especial case and it is a kind of “smart allocator” because it contains the communication control logic as well as it uses another allocator calledTHRefcountedMapAllocatorthat will be responsible for creating the actual shared memory region and callmmap()的于该区域映射到进程的虚拟地址空间。

注意:当与一个PyTorch下划线的方法结束时,如该方法称为share_memory_(),这意味着,该方法具有就地效果,它将改变当前对象,而不是创建一个新的与改进的。

我现在将展示使用来自张量,是由手动交换共享存储器手柄上的另一过程中分配的数据的一个处理的一个Python例如:

这是在处理A执行:

>>> import torch >>> tensor_a = torch.ones((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)

在该代码中,在执行进程A,we create a new Tensor of 5×5 filled with ones. After that we make it shared and print the tuple with the Unix Domain Socket address as well as the handle. Now we can access this memory region from another进程B如下所示:

Code executed in the process B:

>>>进口炬>>> tensor_a = torch.Tensor()>>> tuple_info =(B '/ var / tmp中/ tmp.0.yowqlr',B '/ torch_31258_1218748506',25)>>>存储=炬。Storage._new_shared_filename(* tuple_info)>>> tensor_a = torch.Tensor(存储)。查看((5,5))1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 11 1 [5×5大小的torch.FloatTensor]

正如你所看到的,使用关于Unix域套接字的地址和手柄的元组信息,我们能够从另一个访问的过程中张量存储。如果您更改此张量进程B,你也会看到它的反映进程Abecause these Tensors are sharing the same memory region.

DLPack:对于深度学习一个希望构架巴贝尔

ñow I would like to talk about something recent in the PyTorch code base, that is calleddLPack。DLPack是一个内存张量结构,其将允许交换张量数据的一个开放的标准化框架之间,什么是相当有趣的是,因为这个内存的表示是标准化和非常相似的内存中表示已经通过许多框架的使用,这将允许亚洲金博宝zero-copy data sharing between frameworks,which is a quite amazing initiative given the variety of frameworks we have today without inter-communication among them.

这必将有助于克服“孤岛模式”,我们今天在MXNet,PyTorch,等张量表示之间,并且将允许开发者框架和标准化能够给框架的所有好处之间的混合架构的操作。

DLPack的核心操作系统被称为结构非常简单亚洲金博宝DLTensor, 如下所示:

/ *!* \简短的纯C张量的对象,不管理内存。* / typedef结构{/ *!* \介绍不透明数据指针指向分配的数据。*这将在CUDA的OpenCL设备指针或cl_mem手柄。*这个指针始终对齐到256个字节的CUDA。* / 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

As you can see, there is a data pointer for the raw data as well as shape/stride/offset/GPU vs CPU, and other metadata information about the data that theDLTensor指向。

还有那个叫做张量的托管版本DLManagedTensor,其中框架可以提供一个框架,并且可以通过谁借了张量来通知资源不再需要其他的框架,框架调用也是一个“删除器”的功能。

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

从进口torch.utils炬导入dlpack吨= torch.ones((5,5))(DL)= dlpack.to_dlpack(t)的

这Python函数将调用toDLPack从宏正函数,如下所示:

DLManagedTensor * toDLPack(常量张量&SRC){ATenDLMTensor * atDLMTensor(新ATenDLMTensor);atDLMTensor->手柄= SRC;atDLMTensor-> tensor.manager_ctx = atDLMTensor;atDLMTensor-> tensor.deleter =&删除器;atDLMTensor-> tensor.dl_tensor.data = src.data_ptr();的int64_t DEVICE_ID = 0;如果(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格式,元数据和分配的指针内部张量数据表示。

我真的希望有更多的框架,采用这种标准,必将给生态效益。这也是有趣的是,与潜在的整合Apache的箭将是惊人的。

就是这样,我希望你喜欢这个长的帖子!

– Christian S. Perone

引用本文为:基督教S. Perone,“PyTorch - 内部建筑之旅”,在亚洲金博宝未知领域,2018年12月3日,//www.cpetem.com/2018/03/pytorch-internal-architecture-tour/