提交 7bf8af2b 编写于 作者: W wizardforcel

2021-01-20 21:30:42

上级 1064c7ec
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
NLP 通常用于创建聊天机器人(虚拟助手)。 通过从以前的人类对话中学习,NLP 模型能够帮助一个人解决常见问题或疑问。 因此,他们的句子表达能力仅限于他们在训练过程中所学到的内容,这意味着他们只能回答所学的内容。 NLP 通常用于创建聊天机器人(虚拟助手)。 通过从以前的人类对话中学习,NLP 模型能够帮助一个人解决常见问题或疑问。 因此,他们的句子表达能力仅限于他们在训练过程中所学到的内容,这意味着他们只能回答所学的内容。
当您尝试通过在线聊天系统与银行联系时,您可能会遇到这种情况,在这种情况下,通常您会在查询超出常规范围的那一刻将您转到人工操作员那里。 现实生活中聊天机器人的另一个常见示例是通过 Facebook Messenger 进行查询的餐馆: 当您尝试通过在线聊天系统与银行联系时,您可能会遇到这种情况,在这种情况下,通常您会在查询超出常规范围的那一刻将您转到人工运算符那里。 现实生活中聊天机器人的另一个常见示例是通过 Facebook Messenger 进行查询的餐馆:
![Figure 6.1: Facebook's Messenger chatbot ](img/B15778_06_01.jpg) ![Figure 6.1: Facebook's Messenger chatbot ](img/B15778_06_01.jpg)
......
...@@ -409,7 +409,7 @@ tensor([[0.5594, 0.8875, 0.9234, 1.1294], ...@@ -409,7 +409,7 @@ tensor([[0.5594, 0.8875, 0.9234, 1.1294],
``` ```
可以使用`+`运算符或`add`函数将两个大小相同的张量相加,以获得相同形状的输出张量。 PyTorch 遵循对相同操作使用尾部下划线的约定,但这确实发生了。 例如,`a.add(b)`为您提供了一个新的张量,其总和超过了`a``b`。 此操作不会对现有的`a``b`张量进行任何更改。 但是`a.add_(b)`用总和值更新张量`a`并返回更新后的`a`。 这适用于 PyTorch 中的所有操作员 可以使用`+`运算符或`add`函数将两个大小相同的张量相加,以获得相同形状的输出张量。 PyTorch 遵循对相同操作使用尾部下划线的约定,但这确实发生了。 例如,`a.add(b)`为您提供了一个新的张量,其总和超过了`a``b`。 此操作不会对现有的`a``b`张量进行任何更改。 但是`a.add_(b)`用总和值更新张量`a`并返回更新后的`a`。 这适用于 PyTorch 中的所有运算符
#### 注意 #### 注意
......
...@@ -10,7 +10,7 @@ ONNX 很棒,并且每个人都喜欢它,但是 ONNX 的主要缺点之一是 ...@@ -10,7 +10,7 @@ ONNX 很棒,并且每个人都喜欢它,但是 ONNX 的主要缺点之一是
生产 PyTorch 的第二种方法是在 PyTorch 本身中构建高性能后端。 Caffe2 的核心与 PyTorch 核心合并在一起,而不是从头开始构建一个,但 Python API 保持不变。 但是,这并不能解决 Python 语言所具有的问题。 生产 PyTorch 的第二种方法是在 PyTorch 本身中构建高性能后端。 Caffe2 的核心与 PyTorch 核心合并在一起,而不是从头开始构建一个,但 Python API 保持不变。 但是,这并不能解决 Python 语言所具有的问题。
接下来是 TorchScript 的引入,它可以将本机 Python 模型转换为可以在高性能 Universe 中加载的序列化形式,例如 C++ 线程。 PyTorch 的后端 LibTorch 可以读取 TorchScript,这使 PyTorch 高效。 有了它,开发人员可以对模型进行原型设计,甚至可以使用 Python 本身对其进行训练。 训练后,可以将模型转换为到**中间表示****IR**)。 目前,仅开发了 C++ 后端,因此可以将 IR 作为 C++ 对象加载,然后可以从 PyTorch 的 C++ API 中读取。 TorchScript 甚至可以在 Python 程序中转换控制流,这在生产支持的情况下使其优于 ONNX 方法。 TorchScript 本身是 Python 语言中可能的操作的子集,因此不允许任何 Python 操作用 TorchScript 编写。 官方文档本身提供了非常详细的说明,并讨论了可能的情况和不可能的情况,以及许多示例[1]。 接下来是 TorchScript 的引入,它可以将本机 Python 模型转换为可以在高性能 Universe 中加载的序列化形式,例如 C++ 线程。 PyTorch 的后端 LibTorch 可以读取 TorchScript,这使 PyTorch 高效。 有了它,开发人员可以对模型进行原型设计,甚至可以使用 Python 本身对其进行训练。 训练后,可以将模型转换为到**中间表示****IR**)。 目前,仅开发了 C++ 后端,因此可以将 IR 作为 C++ 对象加载,然后可以从 PyTorch 的 C++ API 中读取。 TorchScript 甚至可以在 Python 程序中转换控制流,这在生产支持的情况下使其优于 ONNX 方法。 TorchScript 本身是 Python 语言中可能的操作的子集,因此不允许任何 Python 操作用 TorchScript 编写。 官方文档本身提供了非常详细的说明,并讨论了可能的情况和不可能的情况,以及许多示例[1]。
在本章中,我们将从使用 Flask(流行的 Python Web 框架)提供普通的 Python PyTorch 模型开始。 这样的设置通常就足够了,特别是如果您要设置示例 Web 应用程序或满足您个人需求或类似用例的东西。 然后,我们将探索 ONNX 并将 PyTorch 模型转换为 MXNet,然后可以使用 MXNet 模型服务器提供服务。 从那里,我们将转到 TorchScript,这是 PyTorch 街区的新孩子。 使用 TorchScript,我们将制作 C++ 可执行文件,然后可以在 LibTorch 的帮助下从 C++ 执行该可执行文件。 然后,可以从稳定,高性能的 C++ 服务器甚至使用 cgo 的 Go 服务器提供高效的 C++ 可执行文件。 对于所有份量,我们将使用在第 2 章,“简单神经网络”中构建的 fizzbuzz 网络。 在本章中,我们将从使用 Flask(流行的 Python Web 框架)提供普通的 Python PyTorch 模型开始。 这样的设置通常就足够了,特别是如果您要设置示例 Web 应用程序或满足您个人需求或类似用例的东西。 然后,我们将探索 ONNX 并将 PyTorch 模型转换为 MXNet,然后可以使用 MXNet 模型服务器提供服务。 从那里,我们将转到 TorchScript,这是 PyTorch 街区的新孩子。 使用 TorchScript,我们将制作 C++ 可执行文件,然后可以在 LibTorch 的帮助下从 C++ 执行该可执行文件。 然后,可以从稳定,高性能的 C++ 服务器甚至使用 cgo 的 Go 服务器提供高效的 C++ 可执行文件。 对于所有份量,我们将使用在第 2 章,“简单神经网络”中构建的 fizzbuzz 网络。
......
...@@ -259,7 +259,7 @@ def forward(self, ...@@ -259,7 +259,7 @@ def forward(self,
1. TorchScript 代码可以在其自己的解释器中调用,该解释器基本上是受限制的 Python 解释器。 该解释器不获取全局解释器锁定,因此可以在同一实例上同时处理许多请求。 1. TorchScript 代码可以在其自己的解释器中调用,该解释器基本上是受限制的 Python 解释器。 该解释器不获取全局解释器锁定,因此可以在同一实例上同时处理许多请求。
2. 这种格式允许我们将整个模型保存到磁盘上,然后将其加载到另一个环境中,例如在以 Python 以外的语言编写的服务器中 2. 这种格式允许我们将整个模型保存到磁盘上,然后将其加载到另一个环境中,例如在以 Python 以外的语言编写的服务器中
3. TorchScript 为我们提供了一种表示形式,其中我们可以对代码进行编译器优化以提供更有效的执行 3. TorchScript 为我们提供了一种表示形式,其中我们可以对代码进行编译器优化以提供更有效的执行
4. TorchScript 允许我们与许多后端/设备运行时进行交互,与单个操作员相比,它们要求更广泛的程序视图。 4. TorchScript 允许我们与许多后端/设备运行时进行交互,与单个运算符相比,它们要求更广泛的程序视图。
我们可以看到,调用`traced_cell`会产生与 Python 模块相同的结果: 我们可以看到,调用`traced_cell`会产生与 Python 模块相同的结果:
......
...@@ -100,7 +100,7 @@ traced_script_module.save("traced_resnet_model.pt") ...@@ -100,7 +100,7 @@ traced_script_module.save("traced_resnet_model.pt")
## 第 3 步:在 C++ 中加载脚本模块 ## 第 3 步:在 C++ 中加载脚本模块
要在 C++ 中加载序列化的 PyTorch 模型,您的应用程序必须依赖于 PyTorch C++ API –也称为 *LibTorch* 。 LibTorch 发行版包含共享库,头文件和 CMake 构建配置文件的集合。 虽然 CMake 不是依赖 LibTorch 的要求,但它是推荐的方法,将来会得到很好的支持。 对于本教程,我们将使用 CMake 和 LibTorch 构建一个最小的 C++ 应用程序,该应用程序简单地加载并执行序列化的 PyTorch 模型。 要在 C++ 中加载序列化的 PyTorch 模型,您的应用程序必须依赖于 PyTorch C++ API –也称为 *LibTorch* 。 LibTorch 发行版包含共享库,头文件和 CMake 构建配置文件的集合。 虽然 CMake 不是依赖 LibTorch 的要求,但它是推荐的方法,将来会得到很好的支持。 对于本教程,我们将使用 CMake 和 LibTorch 构建一个最小的 C++ 应用程序,该应用程序简单地加载并执行序列化的 PyTorch 模型。
### 最小的 C++ 应用程序 ### 最小的 C++ 应用程序
...@@ -252,11 +252,11 @@ std::cout << output.slice(/*dim=*/1, /*start=*/0, /*end=*/5) << '\n'; ...@@ -252,11 +252,11 @@ std::cout << output.slice(/*dim=*/1, /*start=*/0, /*end=*/5) << '\n';
``` ```
前两行设置了模型的输入。 我们创建一个`torch::jit::IValue`的向量(类型擦除的值类型`script::Module`方法接受并返回),并添加单个输入。 要创建输入张量,我们使用`torch::ones()`,等效于 C++ API 中的`torch.ones`。 然后,我们运行`script::Module``forward`方法,并将其传递给我们创建的输入向量。 作为回报,我们得到了一个新的`IValue`,我们可以通过调用`toTensor()`将其转换为张量。 前两行设置了模型的输入。 我们创建一个`torch::jit::IValue`的向量(类型擦除的值类型`script::Module`方法接受并返回),并添加单个输入。 要创建输入张量,我们使用`torch::ones()`,等效于 C++ API 中的`torch.ones`。 然后,我们运行`script::Module``forward`方法,并将其传递给我们创建的输入向量。 作为回报,我们得到了一个新的`IValue`,我们可以通过调用`toTensor()`将其转换为张量。
小费 小费
要总体上了解有关`torch::ones`和 PyTorch C++ API 之类的功能的更多信息,请参阅[这个页面](https://pytorch.org/cppdocs)上的文档。 PyTorch C++ API 提供了与 Python API 几乎相同的功能,使您可以像在 Python 中一样进一步操纵和处理张量。 要总体上了解有关`torch::ones`和 PyTorch C++ API 之类的功能的更多信息,请参阅[这个页面](https://pytorch.org/cppdocs)上的文档。 PyTorch C++ API 提供了与 Python API 几乎相同的功能,使您可以像在 Python 中一样进一步操纵和处理张量。
在最后一行,我们打印输出的前五个条目。 由于在本教程前面的部分中,我们为 Python 中的模型提供了相同的输入,因此理想情况下,我们应该看到相同的输出。 让我们通过重新编译我们的应用程序并以相同的序列化模型运行它来进行尝试: 在最后一行,我们打印输出的前五个条目。 由于在本教程前面的部分中,我们为 Python 中的模型提供了相同的输入,因此理想情况下,我们应该看到相同的输出。 让我们通过重新编译我们的应用程序并以相同的序列化模型运行它来进行尝试:
......
...@@ -46,7 +46,7 @@ print(imgs.names) ...@@ -46,7 +46,7 @@ print(imgs.names)
``` ```
[中的原始命名张量博客文章](http://nlp.seas.harvard.edu/NamedTensor)不同,命名维度是有序的:`tensor.names[i]``tensor`的第`i`个维度的名称。 [命名张量的原始博客文章](http://nlp.seas.harvard.edu/NamedTensor)不同,命名维度是有序的:`tensor.names[i]``tensor`的第`i`个维度的名称。
重命名`Tensor`尺寸的方法有两种: 重命名`Tensor`尺寸的方法有两种:
...@@ -198,7 +198,7 @@ print(img0.names) ...@@ -198,7 +198,7 @@ print(img0.names)
名称在称为**名称推断**的两步过程中在操作上传播: 名称在称为**名称推断**的两步过程中在操作上传播:
1. **检查名称**操作员可以在运行时执行自动检查,以检查某些尺寸名称是否匹配。 1. **检查名称**运算符可以在运行时执行自动检查,以检查某些尺寸名称是否匹配。
2. **传播名称**:名称推断将输出名称传播到输出张量。 2. **传播名称**:名称推断将输出名称传播到输出张量。
让我们看一个非常小的例子,添加 2 个一维张量,不进行广播。 让我们看一个非常小的例子,添加 2 个一维张量,不进行广播。
...@@ -596,7 +596,7 @@ print(output.names) ...@@ -596,7 +596,7 @@ print(output.names)
### 结论 ### 结论
感谢您的阅读! 命名张量仍在发展中。 如果您有反馈和/或改进建议,请通过创建[问题](https://github.com/pytorch/pytorch/issues)来通知我们。 感谢您的阅读! 命名张量仍在发展中。 如果您有反馈和/或改进建议,请通过创建 [ISSUE](https://github.com/pytorch/pytorch/issues) 来通知我们。
**脚本的总运行时间**:(0 分钟 0.094 秒) **脚本的总运行时间**:(0 分钟 0.094 秒)
......
...@@ -386,7 +386,7 @@ Optional ...@@ -386,7 +386,7 @@ Optional
``` ```
如果您发现不支持通道在最后的张量的运算符并且想要贡献力量,请随时使用[以下开发人员指](https://github.com/pytorch/pytorch/wiki/Writing-memory-format-aware-operators) 如果您发现不支持通道在最后的张量的运算符并且想要贡献力量,请随时使用[以下开发人员指](https://github.com/pytorch/pytorch/wiki/Writing-memory-format-aware-operators)
下面的代码是恢复火炬的属性。 下面的代码是恢复火炬的属性。
...@@ -405,7 +405,7 @@ for (m, attrs) in old_attrs.items(): ...@@ -405,7 +405,7 @@ for (m, attrs) in old_attrs.items():
* 测试分布式训练支持; * 测试分布式训练支持;
* 提高运营商覆盖率。 * 提高运营商覆盖率。
如果您有反馈和/或改进建议,请通过创建[问题](https://github.com/pytorch/pytorch/issues)来通知我们。 如果您有反馈和/或改进建议,请通过创建 [ISSUE](https://github.com/pytorch/pytorch/issues) 来通知我们。
**脚本的总运行时间**:(0 分钟 2.300 秒) **脚本的总运行时间**:(0 分钟 2.300 秒)
......
...@@ -8,7 +8,7 @@ PyTorch C++ 前端是 PyTorch 机器学习框架的纯 C++ 接口。 虽然 PyTo ...@@ -8,7 +8,7 @@ PyTorch C++ 前端是 PyTorch 机器学习框架的纯 C++ 接口。 虽然 PyTo
小费 小费
观看[来自 CppCon 2018](https://www.youtube.com/watch?v=auRPXMMHJzc) 的闪电演讲,获得有关 C++ 前端的快速(幽默)演示。 观看[来自 CppCon 2018 的简短演讲](https://www.youtube.com/watch?v=auRPXMMHJzc),获得有关 C++ 前端的快速(幽默)演示。
小费 小费
...@@ -23,7 +23,7 @@ PyTorch C++ 前端是 PyTorch 机器学习框架的纯 C++ 接口。 虽然 PyTo ...@@ -23,7 +23,7 @@ PyTorch C++ 前端是 PyTorch 机器学习框架的纯 C++ 接口。 虽然 PyTo
在我们开始 GAN 和 MNIST 数字的激动人心的旅程之前,让我们退后一步,讨论为什么要使用 C++ 前端而不是 Python。 我们(PyTorch 团队)创建了 C++ 前端,以便能够在无法使用 Python 或根本不适合该工具的环境中进行研究。 此类环境的示例包括: 在我们开始 GAN 和 MNIST 数字的激动人心的旅程之前,让我们退后一步,讨论为什么要使用 C++ 前端而不是 Python。 我们(PyTorch 团队)创建了 C++ 前端,以便能够在无法使用 Python 或根本不适合该工具的环境中进行研究。 此类环境的示例包括:
* **低延迟系统**:您可能希望在具有高每秒帧数和低延迟要求的纯 C++ 游戏引擎中进行强化学习研究。 与 Python 库相比,使用纯 C++ 库更适合这种环境。 由于 Python 解释器的缓慢性,Python 可能根本无法处理。 * **低延迟系统**:您可能希望在具有高每秒帧数和低延迟要求的纯 C++ 游戏引擎中进行强化学习研究。 与 Python 库相比,使用纯 C++ 库更适合这种环境。 由于 Python 解释器的缓慢性,Python 可能根本无法处理。
* **高度多线程环境**:由于全局解释器锁定(GIL),Python 一次不能运行多个系统线程。 多处理是一种替代方法,但可伸缩性却不如它,并且存在很多缺点。 C++ 没有这样的约束,线程易于使用和创建。 需要重型并行化的模型,例如[深度神经](https://eng.uber.com/deep-neuroevolution/)中使用的模型,可以从中受益。 * **高度多线程环境**:由于全局解释器锁定(GIL),Python 一次不能运行多个系统线程。 多处理是一种替代方法,但可伸缩性却不如它,并且存在很多缺点。 C++ 没有这样的约束,线程易于使用和创建。 需要重型并行化的模型,例如[深度神经](https://eng.uber.com/deep-neuroevolution/)中使用的模型,可以从中受益。
* **现有 C++ 代码库**:您可能是现有 C++ 应用程序的所有者,该应用程序从事从后端服务器中的网页服务到照片编辑软件中的 3D 图形渲染等所有工作,并且希望将机器学习方法集成到您的系统中。 C++ 前端使您可以继续使用 C++,并避免在 Python 和 C++ 之间来回绑定的麻烦,同时保留了传统 PyTorch(Python)体验的大部分灵活性和直观性。 * **现有 C++ 代码库**:您可能是现有 C++ 应用程序的所有者,该应用程序从事从后端服务器中的网页服务到照片编辑软件中的 3D 图形渲染等所有工作,并且希望将机器学习方法集成到您的系统中。 C++ 前端使您可以继续使用 C++,并避免在 Python 和 C++ 之间来回绑定的麻烦,同时保留了传统 PyTorch(Python)体验的大部分灵活性和直观性。
C++ 前端无意与 Python 前端竞争。 它是对它的补充。 我们知道研究人员和工程师都喜欢 PyTorch,因为它具有简单,灵活和直观的 API。 我们的目标是确保您可以在所有可能的环境(包括上述环境)中利用这些核心设计原则。 如果这些场景中的一种很好地描述了您的用例,或者您只是感兴趣或好奇,请在以下段落中继续研究 C++ 前端。 C++ 前端无意与 Python 前端竞争。 它是对它的补充。 我们知道研究人员和工程师都喜欢 PyTorch,因为它具有简单,灵活和直观的 API。 我们的目标是确保您可以在所有可能的环境(包括上述环境)中利用这些核心设计原则。 如果这些场景中的一种很好地描述了您的用例,或者您只是感兴趣或好奇,请在以下段落中继续研究 C++ 前端。
...@@ -249,7 +249,7 @@ struct Net : torch::nn::Module { ...@@ -249,7 +249,7 @@ struct Net : torch::nn::Module {
小费 小费
您可以在`torch::nn`命名空间[的文档中](https://pytorch.org/cppdocs/api/namespace_torch__nn.html)中找到可用的内置模块的完整列表,例如`torch::nn::Linear``torch::nn::Dropout``torch::nn::Conv2d` 您可以在[`torch::nn`命名空间的文档](https://pytorch.org/cppdocs/api/namespace_torch__nn.html)中找到可用的内置模块的完整列表,例如`torch::nn::Linear``torch::nn::Dropout``torch::nn::Conv2d`
关于上述代码的一个微妙之处在于,为什么在构造函数的初始值设定项列表中创建子模块,而在构造函数的主体内部创建参数。 这是有充分的理由的,我们将在下面有关“C++ 前端所有权模型”的部分中对此进行介绍。 但是,最终结果是,就像 Python 中一样,我们可以递归访问模块树的参数。 调用`parameters()`返回一个`std::vector<torch::Tensor>`,我们可以对其进行迭代: 关于上述代码的一个微妙之处在于,为什么在构造函数的初始值设定项列表中创建子模块,而在构造函数的主体内部创建参数。 这是有充分的理由的,我们将在下面有关“C++ 前端所有权模型”的部分中对此进行介绍。 但是,最终结果是,就像 Python 中一样,我们可以递归访问模块树的参数。 调用`parameters()`返回一个`std::vector<torch::Tensor>`,我们可以对其进行迭代:
...@@ -289,7 +289,7 @@ root@fa350df05ecf:/home/build# ./dcgan ...@@ -289,7 +289,7 @@ root@fa350df05ecf:/home/build# ./dcgan
``` ```
具有三个参数,就像在 Python 中一样。 为了也查看这些参数的名称,C++ API 提供了`named_parameters()`方法,该方法返回`OrderedDict`就像在 Python 中一样: 具有三个参数,就像在 Python 中一样。 为了也查看这些参数的名称,C++ API 提供了`named_parameters()`方法,该方法返回`OrderedDict`就像在 Python 中一样:
```py ```py
Net net(4, 5); Net net(4, 5);
...@@ -356,7 +356,7 @@ root@fa350df05ecf:/home/build# ./dcgan ...@@ -356,7 +356,7 @@ root@fa350df05ecf:/home/build# ./dcgan
#### 模块所有权 #### 模块所有权
至此,我们知道了如何使用 C++ 定义模块,注册参数,注册子模块,通过`parameters()`之类的方法遍历模块层次结构并最终运行模块的`forward()`方法。 尽管在 C++ API 中还有很多方法,类和主题需要使用,但我将为您提供完整菜单的[文档](https://pytorch.org/cppdocs/api/namespace_torch__nn.html)。 我们将在稍后实现 DCGAN 模型和端到端训练管道的过程中,涉及更多概念。 在我们这样做之前,让我简要介绍一下 C++ 前端为`torch::nn::Module`的子类提供的*所有权模型* 至此,我们知道了如何使用 C++ 定义模块,注册参数,注册子模块,通过`parameters()`之类的方法遍历模块层次结构并最终运行模块的`forward()`方法。 尽管在 C++ API 中还有很多方法,类和主题需要使用,但我将为您提供完整菜单的[文档](https://pytorch.org/cppdocs/api/namespace_torch__nn.html)。 我们将在稍后实现 DCGAN 模型和端到端训练管道的过程中,涉及更多概念。 在我们这样做之前,让我简要介绍一下 C++ 前端为`torch::nn::Module`的子类提供的*所有权模型*
在本次讨论中,所有权模型是指模块的存储和传递方式-确定特定模块实例的所有者或所有者。 在 Python 中,对象始终是动态分配的(在堆上),并且具有引用语义。 这是非常容易使用且易于理解的。 实际上,在 Python 中,您可以很大程度上忽略对象的位置以及如何引用它们,而将精力集中在完成事情上。 在本次讨论中,所有权模型是指模块的存储和传递方式-确定特定模块实例的所有者或所有者。 在 Python 中,对象始终是动态分配的(在堆上),并且具有引用语义。 这是非常容易使用且易于理解的。 实际上,在 Python 中,您可以很大程度上忽略对象的位置以及如何引用它们,而将精力集中在完成事情上。
...@@ -407,7 +407,7 @@ struct Net : torch::nn::Module { ...@@ -407,7 +407,7 @@ struct Net : torch::nn::Module {
为了使用`linear`子模块,我们想将其直接存储在我们的类中。 但是,我们还希望模块基类了解并有权访问此子模块。 为此,它必须存储对此子模块的引用。 至此,我们已经达到了共享所有权的需要。 `torch::nn::Module`类和具体的`Net`类都需要引用该子模块。 因此,基类将模块存储为`shared_ptr`,因此具体类也必须存储。 为了使用`linear`子模块,我们想将其直接存储在我们的类中。 但是,我们还希望模块基类了解并有权访问此子模块。 为此,它必须存储对此子模块的引用。 至此,我们已经达到了共享所有权的需要。 `torch::nn::Module`类和具体的`Net`类都需要引用该子模块。 因此,基类将模块存储为`shared_ptr`,因此具体类也必须存储。
可是等等! 在上面的代码中我没有提到`shared_ptr`! 这是为什么? 好吧,因为`std::shared_ptr<MyModule>`实在令人难受。 为了保持研究人员的生产力,我们提出了一个精心设计的方案,以隐藏`shared_ptr`的提法-通常保留给值语义的好处-同时保留引用语义。 要了解它是如何工作的,我们可以看一下核心库中`torch::nn::Linear`模块的简化定义(完整定义为[在此处](https://github.com/pytorch/pytorch/blob/master/torch/csrc/api/include/torch/nn/modules/linear.h)): 可是等等! 在上面的代码中我没有提到`shared_ptr`! 这是为什么? 好吧,因为`std::shared_ptr<MyModule>`实在令人难受。 为了保持研究人员的生产力,我们提出了一个精心设计的方案,以隐藏`shared_ptr`的提法-通常保留给值语义的好处-同时保留引用语义。 要了解它是如何工作的,我们可以看一下核心库中`torch::nn::Linear`模块的简化定义([完整定义在此处](https://github.com/pytorch/pytorch/blob/master/torch/csrc/api/include/torch/nn/modules/linear.h)):
```py ```py
struct LinearImpl : torch::nn::Module { struct LinearImpl : torch::nn::Module {
...@@ -471,7 +471,7 @@ struct Net : torch::nn::Module { ...@@ -471,7 +471,7 @@ struct Net : torch::nn::Module {
小费 小费
您可以在存储库中找到本教程[中提供的完整源代码。](https://github.com/pytorch/examples/tree/master/cpp/dcgan) [您可以在存储库中找到本教程中提供的完整源代码](https://github.com/pytorch/examples/tree/master/cpp/dcgan)
#### 什么是 GAN aGAN? #### 什么是 GAN aGAN?
...@@ -608,7 +608,7 @@ auto data_loader = torch::data::make_data_loader(std::move(dataset)); ...@@ -608,7 +608,7 @@ auto data_loader = torch::data::make_data_loader(std::move(dataset));
``` ```
数据加载器确实提供了很多选项。 您可以在中检查全套[。 例如,为了加快数据加载速度,我们可以增加工作人员的数量。 默认数字为零,这意味着将使用主线程。 如果将`workers`设置为`2`,将产生两个线程并发加载数据。 我们还应该将批量大小从其默认值`1`增加到更合理的值,例如`64`(`kBatchSize`的值)。 因此,让我们创建一个`DataLoaderOptions`对象并设置适当的属性:](https://github.com/pytorch/pytorch/blob/master/torch/csrc/api/include/torch/data/dataloader_options.h) 数据加载器确实提供了很多选项。 [您可以在这里检查全套](https://github.com/pytorch/pytorch/blob/master/torch/csrc/api/include/torch/data/dataloader_options.h)。 例如,为了加快数据加载速度,我们可以增加工作人员的数量。 默认数字为零,这意味着将使用主线程。 如果将`workers`设置为`2`,将产生两个线程并发加载数据。 我们还应该将批量大小从其默认值`1`增加到更合理的值,例如`64``kBatchSize`的值)。 因此,让我们创建一个`DataLoaderOptions`对象并设置适当的属性:
```py ```py
auto data_loader = torch::data::make_data_loader( auto data_loader = torch::data::make_data_loader(
...@@ -960,6 +960,6 @@ Saved out.png ...@@ -960,6 +960,6 @@ Saved out.png
小费 小费
您可以在存储库中找到本教程[中提供的完整源代码。](https://github.com/pytorch/examples/tree/master/cpp/dcgan) [您可以在存储库中找到本教程中提供的完整源代码](https://github.com/pytorch/examples/tree/master/cpp/dcgan)
与往常一样,如果您遇到任何问题或疑问,可以使用我们的[论坛](https://discuss.pytorch.org/)[GitHub 问题](https://github.com/pytorch/pytorch/issues)进行联系。 与往常一样,如果您遇到任何问题或疑问,可以使用我们的[论坛](https://discuss.pytorch.org/)[GitHub ISSUE](https://github.com/pytorch/pytorch/issues) 进行联系。
\ No newline at end of file \ No newline at end of file
...@@ -74,7 +74,7 @@ new_h, new_C = rnn(X, (h, C)) ...@@ -74,7 +74,7 @@ new_h, new_C = rnn(X, (h, C))
``` ```
自然,如果可能的话,您应该使用这种方法扩展 PyTorch。 由于 PyTorch 对 CPU 和 GPU 的操作进行了高度优化的实现,并由 [NVIDIA cuDNN](https://developer.nvidia.com/cudnn)[Intel MKL](https://software.intel.com/en-us/mkl)[NNPACK](https://github.com/Maratyszcza/NNPACK) 等库提供支持 ,上面的 PyTorch 代码通常会足够快。 但是,我们还可以看到为什么在某些情况下还有进一步改进性能的空间。 最明显的原因是 PyTorch 不了解您要实现的*算法*。 它仅知道您用于组成算法的单个操作。 因此,PyTorch 必须一个接一个地执行您的操作。 由于对操作的实现(或*核*)的每个单独调用(可能涉及 CUDA 内核的启动)都具有一定的开销,因此该开销在许多函数调用中可能变得很重要。 此外,运行我们的代码的 Python 解释器本身可能会使我们的程序变慢。 自然,如果可能的话,您应该使用这种方法扩展 PyTorch。 由于 PyTorch 对 CPU 和 GPU 的操作进行了高度优化的实现,并由 [NVIDIA cuDNN](https://developer.nvidia.com/cudnn)[Intel MKL](https://software.intel.com/en-us/mkl)[NNPACK](https://github.com/Maratyszcza/NNPACK) 等库提供支持 ,上面的 PyTorch 代码通常会足够快。 但是,我们还可以看到为什么在某些情况下还有进一步改进性能的空间。 最明显的原因是 PyTorch 不了解您要实现的*算法*。 它仅知道您用于组成算法的单个操作。 因此,PyTorch 必须一个接一个地执行您的操作。 由于对操作的实现(或*核*)的每个单独调用(可能涉及 CUDA 内核的启动)都具有一定的开销,因此该开销在许多函数调用中可能变得很重要。 此外,运行我们的代码的 Python 解释器本身可能会使我们的程序变慢。
因此,一种确定的加速方法是用 C++(或 CUDA)和*熔断*特定操作组来重写零件。 融合是指将许多功能的实现组合为一个功能,这可以从更少的内核启动以及我们可以提高全局数据流可见性的情况下执行的其他优化中获利。 因此,一种确定的加速方法是用 C++(或 CUDA)和*熔断*特定操作组来重写零件。 融合是指将许多功能的实现组合为一个功能,这可以从更少的内核启动以及我们可以提高全局数据流可见性的情况下执行的其他优化中获利。
...@@ -704,7 +704,7 @@ switch (tensor.type().scalarType()) { ...@@ -704,7 +704,7 @@ switch (tensor.type().scalarType()) {
请注意,我们使用普通的 ATen 执行一些操作。 这些操作仍将在 GPU 上运行,但使用 ATen 的默认实现。 这是有道理的,因为 ATen 会针对矩阵乘法(例如`addmm`)或卷积使用高度优化的例程,而这将很难实现和改善。 请注意,我们使用普通的 ATen 执行一些操作。 这些操作仍将在 GPU 上运行,但使用 ATen 的默认实现。 这是有道理的,因为 ATen 会针对矩阵乘法(例如`addmm`)或卷积使用高度优化的例程,而这将很难实现和改善。
至于内核启动本身,我们在这里指定每个 CUDA 块将具有 1024 个线程,并且将整个 GPU 网格分为所需的`1 x 1024`线程块,以便用每个组件一个线程填充矩阵。 例如,如果我们的状态大小为 2048,批量大小为 4,则我们将以每个 1024 个线程总共启动`4 x 2 = 8`块。 如果您以前从未听说过 CUDA 的“障碍”或“网格”,那么[简介 CUDA](https://devblogs.nvidia.com/even-easier-introduction-cuda) 可能会有所帮助。 至于内核启动本身,我们在这里指定每个 CUDA 块将具有 1024 个线程,并且将整个 GPU 网格分为所需的`1 x 1024`线程块,以便用每个组件一个线程填充矩阵。 例如,如果我们的状态大小为 2048,批量大小为 4,则我们将以每个 1024 个线程总共启动`4 x 2 = 8`块。 如果您以前从未听说过 CUDA 的“障碍”或“网格”,那么 [CUDA 简介](https://devblogs.nvidia.com/even-easier-introduction-cuda)可能会有所帮助。
实际的 CUDA 内核非常简单(如果您曾经编程过 GPU): 实际的 CUDA 内核非常简单(如果您曾经编程过 GPU):
...@@ -993,4 +993,4 @@ Forward: 129.431 us | Backward 304.641 us ...@@ -993,4 +993,4 @@ Forward: 129.431 us | Backward 304.641 us
## 结论 ## 结论
现在,您应该对 PyTorch 的 C++ 扩展机制有了一个很好的了解,并有使用它们的动机。 您可以在此处找到本说明[中显示的代码示例。 如有疑问,请使用](https://github.com/pytorch/extension-cpp)[论坛](https://discuss.pytorch.org)。 另外,请务必查看我们的[常见问题解答](https://pytorch.org/cppdocs/notes/faq.html),以防遇到任何问题。 现在,您应该对 PyTorch 的 C++ 扩展机制有了一个很好的了解,并有使用它们的动机。 [您可以在此处找到本说明中显示的代码示例](https://github.com/pytorch/extension-cpp)。 如有疑问,请使用[论坛](https://discuss.pytorch.org)。 另外,请务必查看我们的[常见问题解答](https://pytorch.org/cppdocs/notes/faq.html),以防遇到任何问题。
\ No newline at end of file \ No newline at end of file
...@@ -10,7 +10,7 @@ TorchScript 支持`torch`包提供的大量操作子集,使您可以纯粹表 ...@@ -10,7 +10,7 @@ TorchScript 支持`torch`包提供的大量操作子集,使您可以纯粹表
## 在 C++ 中实现自定义运算符 ## 在 C++ 中实现自定义运算符
在本教程中,我们将公开 [warpPerspective](https://docs.opencv.org/2.4/modules/imgproc/doc/geometric_transformations.html#warpperspective) 函数,该函数将透视转换应用于图像,从 OpenCV 到 TorchScript 作为自定义运算符。 第一步是用 C++ 编写自定义运算符的实现。 让我们将此实现的文件称为`op.cpp`,并使其如下所示: 在本教程中,我们将公开[`warpPerspective`](https://docs.opencv.org/2.4/modules/imgproc/doc/geometric_transformations.html#warpperspective)函数,该函数将透视转换应用于图像,从 OpenCV 到 TorchScript 作为自定义运算符。 第一步是用 C++ 编写自定义运算符的实现。 让我们将此实现的文件称为`op.cpp`,并使其如下所示:
```py ```py
torch::Tensor warp_perspective(torch::Tensor image, torch::Tensor warp) { torch::Tensor warp_perspective(torch::Tensor image, torch::Tensor warp) {
...@@ -41,11 +41,11 @@ torch::Tensor warp_perspective(torch::Tensor image, torch::Tensor warp) { ...@@ -41,11 +41,11 @@ torch::Tensor warp_perspective(torch::Tensor image, torch::Tensor warp) {
``` ```
该运算符的代码很短。 在文件顶部,我们包含 OpenCV 标头文件`opencv2/opencv.hpp``torch/script.h`标头,该标头暴露了 PyTorch C++ API 中所有需要编写自定义 TorchScript 运算符的必要特性。 我们的函数`warp_perspective`有两个参数:输入`image`和我们希望应用于图像的`warp`变换矩阵。 这些输入的类型是`torch::Tensor`,这是 C++ 中 PyTorch 的张量类型(也是 Python 中所有张量的基础类型)。 我们的`warp_perspective`函数的返回类型也将是`torch::Tensor` 该运算符的代码很短。 在文件顶部,我们包含 OpenCV 标头文件`opencv2/opencv.hpp``torch/script.h`标头,该标头暴露了 PyTorch C++ API 中所有需要编写自定义 TorchScript 运算符的必要特性。 我们的函数`warp_perspective`有两个参数:输入`image`和我们希望应用于图像的`warp`变换矩阵。 这些输入的类型是`torch::Tensor`,这是 C++ 中 PyTorch 的张量类型(也是 Python 中所有张量的基础类型)。 我们的`warp_perspective`函数的返回类型也将是`torch::Tensor`
小费 小费
有关 ATen 的更多信息,请参见[本说明](https://pytorch.org/cppdocs/notes/tensor_basics.html),ATen 是为 PyTorch 提供`Tensor`类的库。 此外,本教程的[描述了如何在 C++ 中分配和初始化新的张量对象(此运算符不需要)。](https://pytorch.org/cppdocs/notes/tensor_creation.html) 有关 ATen 的更多信息,请参见[本说明](https://pytorch.org/cppdocs/notes/tensor_basics.html),ATen 是为 PyTorch 提供`Tensor`类的库。 此外,[本教程](https://pytorch.org/cppdocs/notes/tensor_creation.html)描述了如何在 C++ 中分配和初始化新的张量对象(此运算符不需要)。
注意 注意
...@@ -61,7 +61,7 @@ TorchScript 编译器了解固定数量的类型。 只有这些类型可以用 ...@@ -61,7 +61,7 @@ TorchScript 编译器了解固定数量的类型。 只有这些类型可以用
``` ```
我们正在[称为 OpenCV `Mat`类的构造函数](https://docs.opencv.org/trunk/d3/d63/classcv_1_1Mat.html#a922de793eabcec705b3579c5f95a643e),将张量转换为`Mat`对象。 我们向其传递原始`image`张量的行数和列数,数据类型(在此示例中,我们将其固定为`float32`),最后是指向基础数据的原始指针– `float*``Mat`类的此构造函数的特殊之处在于它不会复制输入数据。 取而代之的是,它将简单地引用此存储器来执行`Mat`上的所有操作。 如果在`image_mat`上执行原地操作,这将反映在原始`image`张量中(反之亦然)。 即使我们实际上将数据存储在 PyTorch 张量中,这也使我们能够使用库的本机矩阵类型调用后续的 OpenCV 例程。 我们重复此过程将`warp` PyTorch 张量转换为`warp_mat` OpenCV 矩阵: 我们正在调用 [OpenCV `Mat`类的构造函数](https://docs.opencv.org/trunk/d3/d63/classcv_1_1Mat.html#a922de793eabcec705b3579c5f95a643e),将张量转换为`Mat`对象。 我们向其传递原始`image`张量的行数和列数,数据类型(在此示例中,我们将其固定为`float32`),最后是指向基础数据的原始指针– `float*``Mat`类的此构造函数的特殊之处在于它不会复制输入数据。 取而代之的是,它将简单地引用此存储器来执行`Mat`上的所有操作。 如果在`image_mat`上执行原地操作,这将反映在原始`image`张量中(反之亦然)。 即使我们实际上将数据存储在 PyTorch 张量中,这也使我们能够使用库的本机矩阵类型调用后续的 OpenCV 例程。 我们重复此过程将`warp` PyTorch 张量转换为`warp_mat` OpenCV 矩阵:
```py ```py
cv::Mat warp_mat(/*rows=*/warp.size(0), cv::Mat warp_mat(/*rows=*/warp.size(0),
...@@ -102,7 +102,7 @@ TORCH_LIBRARY(my_ops, m) { ...@@ -102,7 +102,7 @@ TORCH_LIBRARY(my_ops, m) {
``` ```
`op.cpp`文件顶层的某个位置。 `TORCH_LIBRARY`宏创建一个在程序启动时将被调用的函数。 库的名称(`my_ops`)作为第一个参数给出(不应用引号引起来)。 第二个参数(`m`)定义了`torch::Library`类型的变量,该变量是注册操作员的主要接口。 方法`Library::def`实际上创建了一个名为`warp_perspective`的运算符,将其同时暴露给 Python 和 TorchScript。 您可以通过多次调用`def`来定义任意数量的运算符。 `op.cpp`文件顶层的某个位置。 `TORCH_LIBRARY`宏创建一个在程序启动时将被调用的函数。 库的名称(`my_ops`)作为第一个参数给出(不应用引号引起来)。 第二个参数(`m`)定义了`torch::Library`类型的变量,该变量是注册运算符的主要接口。 方法`Library::def`实际上创建了一个名为`warp_perspective`的运算符,将其同时暴露给 Python 和 TorchScript。 您可以通过多次调用`def`来定义任意数量的运算符。
在后台,`def`函数实际上正在做大量工作:它正在使用模板元编程来检查函数的类型签名,并将其转换为可在 TorchScript 的类型系统中指定操作符类型的操作符架构。 在后台,`def`函数实际上正在做大量工作:它正在使用模板元编程来检查函数的类型签名,并将其转换为可在 TorchScript 的类型系统中指定操作符类型的操作符架构。
...@@ -151,7 +151,7 @@ target_link_libraries(warp_perspective opencv_core opencv_imgproc) ...@@ -151,7 +151,7 @@ target_link_libraries(warp_perspective opencv_core opencv_imgproc)
``` ```
现在要构建我们的操作员,我们可以从`warp_perspective`文件夹中运行以下命令: 现在要构建我们的运算符,我们可以从`warp_perspective`文件夹中运行以下命令:
```py ```py
$ mkdir build $ mkdir build
...@@ -438,7 +438,7 @@ TorchScript 图形表示仍可能更改。 不要依靠它看起来像这样。 ...@@ -438,7 +438,7 @@ TorchScript 图形表示仍可能更改。 不要依靠它看起来像这样。
## 在 C++ 中使用 TorchScript 自定义运算符 ## 在 C++ 中使用 TorchScript 自定义运算符
TorchScript 的一项有用功能是能够将模型序列化到磁盘文件中。 该文件可以通过有线方式发送,存储在文件系统中,或者更重要的是,可以动态反序列化和执行,而无需保留原始源代码。 这在 Python 中是可能的,但在 C++ 中也是可能的。 为此,PyTorch [提供了纯 C++ API](https://pytorch.org/cppdocs/) ,用于反序列化以及执行 TorchScript 模型。 如果您还没有的话,请阅读[有关在 C++ ](https://pytorch.org/tutorials/advanced/cpp_export.html) 中加载和运行序列化 TorchScript 模型的教程,接下来的几段将基于该教程构建。 TorchScript 的一项有用功能是能够将模型序列化到磁盘文件中。 该文件可以通过有线方式发送,存储在文件系统中,或者更重要的是,可以动态反序列化和执行,而无需保留原始源代码。 这在 Python 中是可能的,但在 C++ 中也是可能的。 为此,PyTorch [提供了纯 C++ API](https://pytorch.org/cppdocs/),用于反序列化以及执行 TorchScript 模型。 如果您还没有的话,请阅读[在 C++ 中加载和运行序列化 TorchScript 模型](https://pytorch.org/tutorials/advanced/cpp_export.html)的教程,接下来的几段将基于该教程构建。
简而言之,即使从文件反序列化并以 C++ 运行,也可以像常规`torch`运算符一样执行自定义运算符。 唯一的要求是将我们先前构建的自定义运算符共享库与执行模型的 C++ 应用程序链接。 在 Python 中,只需调用`torch.ops.load_library`即可。 在 C++ 中,您需要在使用的任何构建系统中将共享库与主应用程序链接。 下面的示例将使用 CMake 展示这一点。 简而言之,即使从文件反序列化并以 C++ 运行,也可以像常规`torch`运算符一样执行自定义运算符。 唯一的要求是将我们先前构建的自定义运算符共享库与执行模型的 C++ 应用程序链接。 在 Python 中,只需调用`torch.ops.load_library`即可。 在 C++ 中,您需要在使用的任何构建系统中将共享库与主应用程序链接。 下面的示例将使用 CMake 展示这一点。
...@@ -545,7 +545,7 @@ target_compile_features(example_app PRIVATE cxx_range_for) ...@@ -545,7 +545,7 @@ target_compile_features(example_app PRIVATE cxx_range_for)
注意 注意
上面的示例中嵌入了一个关键细节:`warp_perspective`链接行的`-Wl,--no-as-needed`前缀。 这是必需的,因为我们实际上不会在应用程序代码中从`warp_perspective`共享库中调用任何函数。 我们只需要运行`TORCH_LIBRARY`函数。 麻烦的是,这使链接器感到困惑,并使其认为可以完全跳过与库的链接。 在 Linux 上,`-Wl,--no-as-needed`标志会强制链接发生(注意:此标志特定于 Linux!)。 还有其他解决方法。 最简单的方法是在操作员库中定义*一些函数*,您需要从主应用程序中调用该函数。 这可能就像在某个标头中声明的函数`void init();`一样简单,然后在运算符库中将其定义为`void init() { }`。 在主应用程序中调用此`init()`函数会给链接器以印象,这是一个值得链接的库。 不幸的是,这超出了我们的控制范围,我们宁愿让您知道其原因和简单的解决方法,而不是让您将一些不透明的宏放入代码中。 上面的示例中嵌入了一个关键细节:`warp_perspective`链接行的`-Wl,--no-as-needed`前缀。 这是必需的,因为我们实际上不会在应用程序代码中从`warp_perspective`共享库中调用任何函数。 我们只需要运行`TORCH_LIBRARY`函数。 麻烦的是,这使链接器感到困惑,并使其认为可以完全跳过与库的链接。 在 Linux 上,`-Wl,--no-as-needed`标志会强制链接发生(注意:此标志特定于 Linux!)。 还有其他解决方法。 最简单的方法是在运算符库中定义*一些函数*,您需要从主应用程序中调用该函数。 这可能就像在某个标头中声明的函数`void init();`一样简单,然后在运算符库中将其定义为`void init() { }`。 在主应用程序中调用此`init()`函数会给链接器以印象,这是一个值得链接的库。 不幸的是,这超出了我们的控制范围,我们宁愿让您知道其原因和简单的解决方法,而不是让您将一些不透明的宏放入代码中。
现在,由于我们现在在顶层找到了`Torch`软件包,因此`warp_perspective`子目录中的`CMakeLists.txt`文件可以缩短一些。 它看起来应该像这样: 现在,由于我们现在在顶层找到了`Torch`软件包,因此`warp_perspective`子目录中的`CMakeLists.txt`文件可以缩短一些。 它看起来应该像这样:
...@@ -625,11 +625,11 @@ $ ./example_app example.pt ...@@ -625,11 +625,11 @@ $ ./example_app example.pt
本教程向您介绍了如何在 C++ 中实现自定义 TorchScript 运算符,如何将其构建到共享库中,如何在 Python 中使用它来定义 TorchScript 模型以及如何将其加载到 C++ 应用程序中以进行推理工作负载。 现在,您可以使用与第三方 C++ 库进行接口的 C++ 运算符扩展 TorchScript 模型,编写自定义的高性能 CUDA 内核,或实现任何其他需要 Python,TorchScript 和 C++ 之间的界线才能平稳融合的用例。 本教程向您介绍了如何在 C++ 中实现自定义 TorchScript 运算符,如何将其构建到共享库中,如何在 Python 中使用它来定义 TorchScript 模型以及如何将其加载到 C++ 应用程序中以进行推理工作负载。 现在,您可以使用与第三方 C++ 库进行接口的 C++ 运算符扩展 TorchScript 模型,编写自定义的高性能 CUDA 内核,或实现任何其他需要 Python,TorchScript 和 C++ 之间的界线才能平稳融合的用例。
与往常一样,如果您遇到任何问题或疑问,可以使用我们的[论坛](https://discuss.pytorch.org/)[GitHub 问题](https://github.com/pytorch/pytorch/issues)进行联系。 另外,我们的[常见问题解答(FAQ)页面](https://pytorch.org/cppdocs/notes/faq.html)可能包含有用的信息。 与往常一样,如果您遇到任何问题或疑问,可以使用我们的[论坛](https://discuss.pytorch.org/)[GitHub ISSUE](https://github.com/pytorch/pytorch/issues) 进行联系。 另外,我们的[常见问题解答(FAQ)页面](https://pytorch.org/cppdocs/notes/faq.html)可能包含有用的信息。
## 附录 A:建立自定义运算符的更多方法 ## 附录 A:建立自定义运算符的更多方法
“构建自定义运算符”一节介绍了如何使用 CMake 将自定义运算符构建到共享库中。 本附录概述了两种进一步的编译方法。 他们俩都使用 Python 作为编译过程的“驱动程序”或“接口”。 此外,两者都重用了[现有基础结构](https://pytorch.org/docs/stable/cpp_extension.html) PyTorch 提供了 [C++ 扩展](https://pytorch.org/tutorials/advanced/cpp_extension.html) ,它们是依赖于[`pybind11`](https://github.com/pybind/pybind11)用于将功能从 C++ “显式”绑定到 Python。 “构建自定义运算符”一节介绍了如何使用 CMake 将自定义运算符构建到共享库中。 本附录概述了两种进一步的编译方法。 他们俩都使用 Python 作为编译过程的“驱动程序”或“接口”。 此外,两者都重用了[现有基础结构](https://pytorch.org/docs/stable/cpp_extension.html)。 PyTorch 提供了 [C++ 扩展](https://pytorch.org/tutorials/advanced/cpp_extension.html),它们依赖于[`pybind11`](https://github.com/pybind/pybind11)用于将功能从 C++ “显式”绑定到 Python。
第一种方法是使用 C++ 扩展程序的[方便的即时(JIT)编译界面](https://pytorch.org/docs/stable/cpp_extension.html#torch.utils.cpp_extension.load)在您首次运行 PyTorch 脚本时在后台编译代码。 第二种方法依赖于古老的`setuptools`包,并涉及编写单独的`setup.py`文件。 这样可以进行更高级的配置,并与其他基于`setuptools`的项目集成。 我们将在下面详细探讨这两种方法。 第一种方法是使用 C++ 扩展程序的[方便的即时(JIT)编译界面](https://pytorch.org/docs/stable/cpp_extension.html#torch.utils.cpp_extension.load)在您首次运行 PyTorch 脚本时在后台编译代码。 第二种方法依赖于古老的`setuptools`包,并涉及编写单独的`setup.py`文件。 这样可以进行更高级的配置,并与其他基于`setuptools`的项目集成。 我们将在下面详细探讨这两种方法。
...@@ -641,7 +641,7 @@ PyTorch C++ 扩展工具包提供的 JIT 编译功能可将自定义运算符的 ...@@ -641,7 +641,7 @@ PyTorch C++ 扩展工具包提供的 JIT 编译功能可将自定义运算符的
这里的“ JIT 编译”与 TorchScript 编译器中用于优化程序的 JIT 编译无关。 这只是意味着您的自定义运算符 C++ 代码将在您首次导入时在系统`/tmp`目录下的文件夹中编译,就像您自己事先对其进行编译一样。 这里的“ JIT 编译”与 TorchScript 编译器中用于优化程序的 JIT 编译无关。 这只是意味着您的自定义运算符 C++ 代码将在您首次导入时在系统`/tmp`目录下的文件夹中编译,就像您自己事先对其进行编译一样。
此 JIT 编译功能有两种形式。 首先,您仍然将操作员实现保留在单独的文件(`op.cpp`)中,然后使用`torch.utils.cpp_extension.load()`编译扩展名。 通常,此函数将返回暴露您的 C++ 扩展的 Python 模块。 但是,由于我们没有将自定义运算符编译到其自己的 Python 模块中,因此我们只想编译一个普通的共享库。 幸运的是,`torch.utils.cpp_extension.load()`有一个参数`is_python_module`,可以将其设置为`False`,以表明我们仅对构建共享库感兴趣,而对 Python 模块不感兴趣。 然后`torch.utils.cpp_extension.load()`将会编译并将共享库也加载到当前进程中,就像`torch.ops.load_library`之前所做的那样: 此 JIT 编译功能有两种形式。 首先,您仍然将运算符实现保留在单独的文件(`op.cpp`)中,然后使用`torch.utils.cpp_extension.load()`编译扩展名。 通常,此函数将返回暴露您的 C++ 扩展的 Python 模块。 但是,由于我们没有将自定义运算符编译到其自己的 Python 模块中,因此我们只想编译一个普通的共享库。 幸运的是,`torch.utils.cpp_extension.load()`有一个参数`is_python_module`,可以将其设置为`False`,以表明我们仅对构建共享库感兴趣,而对 Python 模块不感兴趣。 然后`torch.utils.cpp_extension.load()`将会编译并将共享库也加载到当前进程中,就像`torch.ops.load_library`之前所做的那样:
```py ```py
import torch.utils.cpp_extension import torch.utils.cpp_extension
...@@ -771,7 +771,7 @@ Finished processing dependencies for warp-perspective==0.0.0 ...@@ -771,7 +771,7 @@ Finished processing dependencies for warp-perspective==0.0.0
``` ```
这将产生一个名为`warp_perspective.so`的共享库,我们可以像之前那样将其传递给`torch.ops.load_library`,以使我们的操作员对 TorchScript 可见: 这将产生一个名为`warp_perspective.so`的共享库,我们可以像之前那样将其传递给`torch.ops.load_library`,以使我们的运算符对 TorchScript 可见:
```py ```py
>>> import torch >>> import torch
......
...@@ -259,7 +259,7 @@ scripted_foo.save('foo.pt') ...@@ -259,7 +259,7 @@ scripted_foo.save('foo.pt')
我们文件系统中的`foo.pt`现在包含我们刚刚定义的序列化 TorchScript 程序。 我们文件系统中的`foo.pt`现在包含我们刚刚定义的序列化 TorchScript 程序。
现在,我们将定义一个新的 CMake 项目,以展示如何加载此模型及其所需的`.so`文件。 有关如何执行此操作的完整说明,请查看[在 C++ 教程](https://pytorch.org/tutorials/advanced/cpp_export.html)中加载 TorchScript 模型 现在,我们将定义一个新的 CMake 项目,以展示如何加载此模型及其所需的`.so`文件。 有关如何执行此操作的完整说明,请查看[在 C++ 中加载 TorchScript 模型](https://pytorch.org/tutorials/advanced/cpp_export.html)的教程
与之前类似,让我们创建一个包含以下内容的文件结构: 与之前类似,让我们创建一个包含以下内容的文件结构:
...@@ -503,7 +503,7 @@ c10::intrusive_ptr<MyStackClass<std::string>> manipulate_instance(const c10::int ...@@ -503,7 +503,7 @@ c10::intrusive_ptr<MyStackClass<std::string>> manipulate_instance(const c10::int
``` ```
有关注册 API 的更多详细信息,请参见[定制操作教程](https://pytorch.org/tutorials/advanced/torch_script_custom_ops.html) 有关注册 API 的更多详细信息,请参见[自定义操作教程](https://pytorch.org/tutorials/advanced/torch_script_custom_ops.html)
完成此操作后,您可以像以下示例一样使用操作: 完成此操作后,您可以像以下示例一样使用操作:
...@@ -526,4 +526,4 @@ class TryCustomOp(torch.nn.Module): ...@@ -526,4 +526,4 @@ class TryCustomOp(torch.nn.Module):
本教程向您介绍了如何向 TorchScript(以及扩展为 Python)公开 C++ 类,如何注册其方法,如何从 Python 和 TorchScript 使用该类以及如何使用该类保存和加载代码以及运行该代码。 在独立的 C++ 过程中。 现在,您可以使用与第三方 C++ 库连接的 C++ 类扩展 TorchScript 模型,或实现需要 Python,TorchScript 和 C++ 之间的界线平滑融合的任何其他用例。 本教程向您介绍了如何向 TorchScript(以及扩展为 Python)公开 C++ 类,如何注册其方法,如何从 Python 和 TorchScript 使用该类以及如何使用该类保存和加载代码以及运行该代码。 在独立的 C++ 过程中。 现在,您可以使用与第三方 C++ 库连接的 C++ 类扩展 TorchScript 模型,或实现需要 Python,TorchScript 和 C++ 之间的界线平滑融合的任何其他用例。
与往常一样,如果您遇到任何问题或疑问,可以使用我们的[论坛](https://discuss.pytorch.org/)[GitHub 问题](https://github.com/pytorch/pytorch/issues)进行联系。 另外,我们的[常见问题解答(FAQ)页面](https://pytorch.org/cppdocs/notes/faq.html)可能包含有用的信息。 与往常一样,如果您遇到任何问题或疑问,可以使用我们的[论坛](https://discuss.pytorch.org/)[GitHub ISSUE](https://github.com/pytorch/pytorch/issues) 进行联系。 另外,我们的[常见问题解答(FAQ)页面](https://pytorch.org/cppdocs/notes/faq.html)可能包含有用的信息。
\ No newline at end of file \ No newline at end of file
...@@ -220,7 +220,7 @@ true ...@@ -220,7 +220,7 @@ true
``` ```
有关 C++ 张量自动梯度 API 的更多信息,例如`grad`/`requires_grad`/`is_leaf`/`backward`/`detach`/`detach_`/`register_hook`/`retain_grad`,请参见[相应的 C++ API 文档](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html) 有关 C++ 张量自动梯度 API 的更多信息,例如`grad`/`requires_grad`/`is_leaf`/`backward`/`detach`/`detach_`/`register_hook`/`retain_grad`,请参见[相应的 C++ API 文档](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html)
## 用 C++ 计算高阶梯度 ## 用 C++ 计算高阶梯度
...@@ -261,7 +261,7 @@ std::cout << input.grad() << std::endl; ...@@ -261,7 +261,7 @@ std::cout << input.grad() << std::endl;
``` ```
有关如何使用它们的更多信息,请参见`torch::autograd::backward`[链接](https://pytorch.org/cppdocs/api/function_namespacetorch_1_1autograd_1afa9b5d4329085df4b6b3d4b4be48914b.html))和`torch::autograd::grad`[链接](https://pytorch.org/cppdocs/api/function_namespacetorch_1_1autograd_1a1e03c42b14b40c306f9eb947ef842d9c.html)的文档。 有关如何使用它们的更多信息,请参见[`torch::autograd::backward`](https://pytorch.org/cppdocs/api/function_namespacetorch_1_1autograd_1afa9b5d4329085df4b6b3d4b4be48914b.html)[`torch::autograd::grad`](https://pytorch.org/cppdocs/api/function_namespacetorch_1_1autograd_1a1e03c42b14b40c306f9eb947ef842d9c.html)的文档。
## 在 C++ 中使用自定义 Autograd 函数 ## 在 C++ 中使用自定义 Autograd 函数
...@@ -392,23 +392,23 @@ std::cout << x.grad() << std::endl; ...@@ -392,23 +392,23 @@ std::cout << x.grad() << std::endl;
| Python | C++ | | Python | C++ |
| --- | --- | | --- | --- |
| `torch.autograd.backward` | `torch::autograd::backward`[链接](https://pytorch.org/cppdocs/api/function_namespacetorch_1_1autograd_1afa9b5d4329085df4b6b3d4b4be48914b.html)) | | `torch.autograd.backward` | [`torch::autograd::backward`](https://pytorch.org/cppdocs/api/function_namespacetorch_1_1autograd_1afa9b5d4329085df4b6b3d4b4be48914b.html)) |
| `torch.autograd.grad` | `torch::autograd::grad`[链接](https://pytorch.org/cppdocs/api/function_namespacetorch_1_1autograd_1a1e03c42b14b40c306f9eb947ef842d9c.html)) | | `torch.autograd.grad` | [`torch::autograd::grad`](https://pytorch.org/cppdocs/api/function_namespacetorch_1_1autograd_1a1e03c42b14b40c306f9eb947ef842d9c.html)) |
| `torch.Tensor.detach` | `torch::Tensor::detach`[链接](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor6detachEv)) | | `torch.Tensor.detach` | [`torch::Tensor::detach`](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor6detachEv)) |
| `torch.Tensor.detach_` | `torch::Tensor::detach_`[链接](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor7detach_Ev)) | | `torch.Tensor.detach_` | [`torch::Tensor::detach_`](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor7detach_Ev)) |
| `torch.Tensor.backward` | `torch::Tensor::backward`[链接](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor8backwardERK6Tensorbb)) | | `torch.Tensor.backward` | [`torch::Tensor::backward`](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor8backwardERK6Tensorbb)) |
| `torch.Tensor.register_hook` | `torch::Tensor::register_hook`[链接](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4I0ENK2at6Tensor13register_hookE18hook_return_void_tI1TERR1T)) | | `torch.Tensor.register_hook` | [`torch::Tensor::register_hook`](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4I0ENK2at6Tensor13register_hookE18hook_return_void_tI1TERR1T)) |
| `torch.Tensor.requires_grad` | `torch::Tensor::requires_grad_`[链接](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor14requires_grad_Eb)) | | `torch.Tensor.requires_grad` | [`torch::Tensor::requires_grad_`](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor14requires_grad_Eb)) |
| `torch.Tensor.retain_grad` | `torch::Tensor::retain_grad`[链接](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor11retain_gradEv)) | | `torch.Tensor.retain_grad` | [`torch::Tensor::retain_grad`](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor11retain_gradEv)) |
| `torch.Tensor.grad` | `torch::Tensor::grad`[链接](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor4gradEv)) | | `torch.Tensor.grad` | [`torch::Tensor::grad`](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor4gradEv)) |
| `torch.Tensor.grad_fn` | `torch::Tensor::grad_fn`[链接](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor7grad_fnEv)) | | `torch.Tensor.grad_fn` | [`torch::Tensor::grad_fn`](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor7grad_fnEv)) |
| `torch.Tensor.set_data` | `torch::Tensor::set_data`[链接](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor8set_dataERK6Tensor)) | | `torch.Tensor.set_data` | [`torch::Tensor::set_data`](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor8set_dataERK6Tensor)) |
| `torch.Tensor.data` | `torch::Tensor::data`[链接](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor4dataEv)) | | `torch.Tensor.data` | [`torch::Tensor::data`](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor4dataEv)) |
| `torch.Tensor.output_nr` | `torch::Tensor::output_nr`[链接](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor9output_nrEv)) | | `torch.Tensor.output_nr` | [`torch::Tensor::output_nr`](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor9output_nrEv)) |
| `torch.Tensor.is_leaf` | `torch::Tensor::is_leaf`[链接](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor7is_leafEv)) | | `torch.Tensor.is_leaf` | [`torch::Tensor::is_leaf`](https://pytorch.org/cppdocs/api/classat_1_1_tensor.html#_CPPv4NK2at6Tensor7is_leafEv)) |
翻译后,您的大多数 Python Autograd 代码都应仅在 C++ 中工作。 如果不是这种情况,请在 [GitHub 问题](https://github.com/pytorch/pytorch/issues)中提交错误报告,我们将尽快对其进行修复。 翻译后,您的大多数 Python Autograd 代码都应仅在 C++ 中工作。 如果不是这种情况,请在 [GitHub ISSUE](https://github.com/pytorch/pytorch/issues) 中提交错误报告,我们将尽快对其进行修复。
## 结论 ## 结论
现在,您应该对 PyTorch 的 C++ autograd API 有了一个很好的了解。 您可以在此处找到本说明[中显示的代码示例。 与往常一样,如果您遇到任何问题或疑问,可以使用我们的](https://github.com/pytorch/examples/tree/master/cpp/autograd)[论坛](https://discuss.pytorch.org/)或 [GitHub 问题](https://github.com/pytorch/pytorch/issues)进行联系。 现在,您应该对 PyTorch 的 C++ autograd API 有了一个很好的了解。 [您可以在此处找到本说明中显示的代码示例](https://github.com/pytorch/examples/tree/master/cpp/autograd)。 与往常一样,如果您遇到任何问题或疑问,可以使用我们的[论坛](https://discuss.pytorch.org/)[GitHub ISSUE](https://github.com/pytorch/pytorch/issues) 进行联系。
\ No newline at end of file \ No newline at end of file
...@@ -5,14 +5,14 @@ ...@@ -5,14 +5,14 @@
调度程序是 PyTorch 的内部组件,负责确定调用`torch::add`之类的函数时应实际运行哪些代码。 这是不平凡的,因为 PyTorch 操作需要处理很多交叉关注点,这些关注点“层叠”在另一个之上。 以下是其处理的一些示例: 调度程序是 PyTorch 的内部组件,负责确定调用`torch::add`之类的函数时应实际运行哪些代码。 这是不平凡的,因为 PyTorch 操作需要处理很多交叉关注点,这些关注点“层叠”在另一个之上。 以下是其处理的一些示例:
* 根据输入张量的设备,在运算符的 CPU 和 CUDA 实现之间切换。 * 根据输入张量的设备,在运算符的 CPU 和 CUDA 实现之间切换。
*操作员的自动微分和后端实现之间切换,这取决于是否需要自动微分处理。 *运算符的自动微分和后端实现之间切换,这取决于是否需要自动微分处理。
* 必要时应用自动广播以实现自动混合精度。 * 必要时应用自动广播以实现自动混合精度。
*操作员`vmap`调用下运行时,应用批量规则。 *运算符`vmap`调用下运行时,应用批量规则。
* 如果要跟踪导出的模型,则跟踪操作的执行。 * 如果要跟踪导出的模型,则跟踪操作的执行。
如果在[自定义运算符代码](torch_script_custom_ops)中发现自己手动编写了`if`语句来处理这些情况,则调度程序 API 可以帮助组织代码。 (相反,如果您的自定义运算符非常简单并且仅用于 CPU 推断,则可能不需要使用调度程序,只需使用基本 API。) 如果在[自定义运算符代码](torch_script_custom_ops)中发现自己手动编写了`if`语句来处理这些情况,则调度程序 API 可以帮助组织代码。 (相反,如果您的自定义运算符非常简单并且仅用于 CPU 推断,则可能不需要使用调度程序,只需使用基本 API。)
在本教程中,我们将描述如何构造自定义操作员注册以使用调度程序来组织各种组件。 我们假设您熟悉如何[注册操作员](torch_script_custom_ops)以及如何编写[自定义自动微分功能](cpp_autograd) 在本教程中,我们将描述如何构造自定义运算符注册以使用调度程序来组织各种组件。 我们假设您熟悉如何[注册运算符](torch_script_custom_ops)以及如何编写[自定义自动微分函数](cpp_autograd)
## 定义模式和后端实现 ## 定义模式和后端实现
...@@ -57,7 +57,7 @@ TORCH_LIBRARY_IMPL(myops, CPU, m) { ...@@ -57,7 +57,7 @@ TORCH_LIBRARY_IMPL(myops, CPU, m) {
``` ```
通过`TORCH_LIBRARY_IMPL`,我们可以在特定的调度键(在本例中为 CPU)上为操作员注册实现。 每次对`impl`的调用都会将 CPU 内核与相应的运算符(我们先前在`TORCH_LIBRARY`块中定义)相关联。 如果我们还有 CUDA 实现`myadd_cuda`,我们可以将其注册在单独的`TORCH_LIBRARY_IMPL`块中: 通过`TORCH_LIBRARY_IMPL`,我们可以在特定的调度键(在本例中为 CPU)上为运算符注册实现。 每次对`impl`的调用都会将 CPU 内核与相应的运算符(我们先前在`TORCH_LIBRARY`块中定义)相关联。 如果我们还有 CUDA 实现`myadd_cuda`,我们可以将其注册在单独的`TORCH_LIBRARY_IMPL`块中:
```py ```py
TORCH_LIBRARY_IMPL(myops, CUDA, m) { TORCH_LIBRARY_IMPL(myops, CUDA, m) {
...@@ -69,17 +69,17 @@ TORCH_LIBRARY_IMPL(myops, CUDA, m) { ...@@ -69,17 +69,17 @@ TORCH_LIBRARY_IMPL(myops, CUDA, m) {
这些注册可以跨文件甚至跨库边界拆分; 因此,例如,您可以将这两个`TORCH_LIBRARY_IMPL`块编译为单独的`myops_cpu``myops_cuda`动态库。 一般来说,您的注册结构如下所示: 这些注册可以跨文件甚至跨库边界拆分; 因此,例如,您可以将这两个`TORCH_LIBRARY_IMPL`块编译为单独的`myops_cpu``myops_cuda`动态库。 一般来说,您的注册结构如下所示:
1. 单个`TORCH_LIBRARY`在集中位置列出名称空间中的每个自定义运算符。 1. 单个`TORCH_LIBRARY`在集中位置列出名称空间中的每个自定义运算符。
2. 每个调度键的`TORCH_LIBRARY_IMPL`,用于注册该键的实现(例如,CPU 或 CUDA)。 如果愿意,可以按每个运算符将`TORCH_LIBRARY_IMPL`块进一步细分为一个块。 如果每个运算符的实现都有一个单独的文件,但是又不想在标头中显示运算符,这将很方便。 您只需将注册内容放入定义您的操作员的 cpp 文件中。 2. 每个调度键的`TORCH_LIBRARY_IMPL`,用于注册该键的实现(例如,CPU 或 CUDA)。 如果愿意,可以按每个运算符将`TORCH_LIBRARY_IMPL`块进一步细分为一个块。 如果每个运算符的实现都有一个单独的文件,但是又不想在标头中显示运算符,这将很方便。 您只需将注册内容放入定义您的运算符的 cpp 文件中。
注意 注意
您知道吗,您还可以为 PyTorch 中的现有核心操作员编写`TORCH_LIBRARY_IMPL`块? 这就是实现 XLA 对 PyTorch 的支持的方式:`torch_xla`库包含一个`TORCH_LIBRARY_IMPL`,该库为 XLA 调度键上的所有基本运算符提供实现。 您知道吗,您还可以为 PyTorch 中的现有核心运算符编写`TORCH_LIBRARY_IMPL`块? 这就是实现 XLA 对 PyTorch 的支持的方式:`torch_xla`库包含一个`TORCH_LIBRARY_IMPL`,该库为 XLA 调度键上的所有基本运算符提供实现。
## 添加 Autograd 支持 ## 添加 Autograd 支持
至此,我们有了一个同时具有 CPU 和 CUDA 实现的操作员。 我们如何为它添加 Autograd 支持? 您可能会猜到,我们将注册一个 Autograd 内核(类似于[自定义 Autograd 函数](cpp_autograd)教程中描述的内容)! 但是,有一个变数:与 CPU 和 CUDA 内核不同,Autograd 内核需要*重新分发*:它需要回调分派器才能到达最终的 CPU 和 CUDA 实现。 至此,我们有了一个同时具有 CPU 和 CUDA 实现的运算符。 我们如何为它添加 Autograd 支持? 您可能会猜到,我们将注册一个 Autograd 内核(类似于[自定义 Autograd 函数](cpp_autograd)教程中描述的内容)! 但是,有一个变数:与 CPU 和 CUDA 内核不同,Autograd 内核需要*重新分发*:它需要回调分派器才能到达最终的 CPU 和 CUDA 实现。
因此,在编写 Autograd 内核之前,让我们编写一个*调度函数*,该函数调用调度程序以为您的操作员找到合适的内核。 该函数构成了供您的操作员使用的公共 C++ API,实际上,PyTorch C++ API 中的所有张量函数都在后台完全以相同的方式调用了调度程序。 调度功能如下所示: 因此,在编写 Autograd 内核之前,让我们编写一个*调度函数*,该函数调用调度程序以为您的运算符找到合适的内核。 该函数构成了供您的运算符使用的公共 C++ API,实际上,PyTorch C++ API 中的所有张量函数都在后台完全以相同的方式调用了调度程序。 调度功能如下所示:
```py ```py
Tensor myadd(const Tensor& self, const Tensor& other) { Tensor myadd(const Tensor& self, const Tensor& other) {
...@@ -97,7 +97,7 @@ Tensor myadd(const Tensor& self, const Tensor& other) { ...@@ -97,7 +97,7 @@ Tensor myadd(const Tensor& self, const Tensor& other) {
为了提高性能,此计算是在静态变量中完成的,因此我们只需要进行一次(慢速)查找。 如果键入了要调用的运算符的名称,则第一次调用此函数时,此查找将出错。 为了提高性能,此计算是在静态变量中完成的,因此我们只需要进行一次(慢速)查找。 如果键入了要调用的运算符的名称,则第一次调用此函数时,此查找将出错。
* 在第二行中,我们只需将所有参数传递到调度函数中,就可以简单地`call`操作员句柄。 这实际上将调用调度程序,最终控制权将转移到适合此调用的任何内核。 * 在第二行中,我们只需将所有参数传递到调度函数中,就可以简单地`call`运算符句柄。 这实际上将调用调度程序,最终控制权将转移到适合此调用的任何内核。
有了分发功能,我们现在可以编写 Autograd 内核: 有了分发功能,我们现在可以编写 Autograd 内核:
...@@ -163,9 +163,9 @@ public: ...@@ -163,9 +163,9 @@ public:
那么为什么要使用调度程序呢? 有几个原因: 那么为什么要使用调度程序呢? 有几个原因:
1. 它是分散的。 您可以组装运算符的所有部分(CPU,CUDA,Autograd),而不必编写引用所有元素的集中式`if`语句。 重要的是,第三方可以注册其他方面的额外实现,而不必修补操作员的原始定义。 1. 它是分散的。 您可以组装运算符的所有部分(CPU,CUDA,Autograd),而不必编写引用所有元素的集中式`if`语句。 重要的是,第三方可以注册其他方面的额外实现,而不必修补运算符的原始定义。
2. 它比 CPU,CUDA 和 Autograd 支持更多的调度键。 您可以在`c10/core/DispatchKey.h`中查看 PyTorch 中当前实现的调度密钥的完整列表。 这些分派密钥为操作员实现了多种可选功能,如果您决定希望自定义操作员支持该功能,则只需为相应的密钥注册内核即可。 2. 它比 CPU,CUDA 和 Autograd 支持更多的调度键。 您可以在`c10/core/DispatchKey.h`中查看 PyTorch 中当前实现的调度密钥的完整列表。 这些分派密钥为运算符实现了多种可选功能,如果您决定希望自定义运算符支持该功能,则只需为相应的密钥注册内核即可。
3. 调度程序实现对盒装后备功能的支持,后者是可以一次实现并应用于系统中所有操作员的功能。 盒装后备可用于提供调度键的默认行为。 如果您使用调度程序来实现您的运营商,那么您还可以选择所有这些操作的备用。 3. 调度程序实现对盒装后备功能的支持,后者是可以一次实现并应用于系统中所有运算符的功能。 盒装后备可用于提供调度键的默认行为。 如果您使用调度程序来实现您的运营商,那么您还可以选择所有这些操作的备用。
这是一些特定的调度键,您可能需要为其定义一个运算符。 这是一些特定的调度键,您可能需要为其定义一个运算符。
...@@ -195,9 +195,9 @@ TORCH_LIBRARY_IMPL(myops, Autocast, m) { ...@@ -195,9 +195,9 @@ TORCH_LIBRARY_IMPL(myops, Autocast, m) {
请注意,就像我们的 Autograd 内核一样,我们在重新分配之前从分配中排除`Autocast`键。 请注意,就像我们的 Autograd 内核一样,我们在重新分配之前从分配中排除`Autocast`键。
默认情况下,如果未提供自动广播包装器,我们将直接进入常规的操作员实现(不进行自动广播)。 (在此示例中,我们没有使用`myadd`,因为逐点加法不需要自动广播,因此应该会失败。) 默认情况下,如果未提供自动广播包装器,我们将直接进入常规的运算符实现(不进行自动广播)。 (在此示例中,我们没有使用`myadd`,因为逐点加法不需要自动广播,因此应该会失败。)
什么时候应该注册自动广播包装器? 不幸的是,对于操作人员的首选精度并没有严格的规定。 通过查看[演员表](https://pytorch.org/docs/master/amp.html#op-specific-behavior),您可以了解某些本机操作人员的首选精度。 一般指导: 什么时候应该注册自动广播包装器? 不幸的是,对于运算符的首选精度并没有严格的规定。 通过查看[运算符列表](https://pytorch.org/docs/master/amp.html#op-specific-behavior),您可以了解某些本机运算符的首选精度。 一般指导:
* 进行减少操作的操作可能应该在`float32`中执行, * 进行减少操作的操作可能应该在`float32`中执行,
* 在幕后进行卷积或宝石运算的任何操作都应在`float16`中执行,并且 * 在幕后进行卷积或宝石运算的任何操作都应在`float16`中执行,并且
...@@ -218,7 +218,7 @@ Tensor my_multiple_input_op_autocast(const Tensor& t0, const Tensor& t1) { ...@@ -218,7 +218,7 @@ Tensor my_multiple_input_op_autocast(const Tensor& t0, const Tensor& t1) {
``` ```
如果您的自定义操作[已启用 Autograd 的](#autograd-support),则只需编写和注册自动广播包装器,其名称与注册自动梯度包装器的名称相同。 例如,如果您想为 Autograd 部分中显示的`myadd`功能使用自动广播包装,那么您所需要做的就是 如果您的自定义操作[已启用 Autograd](#autograd-support),则只需编写和注册自动广播包装器,其名称与注册自动梯度包装器的名称相同。 例如,如果您想为 Autograd 部分中显示的`myadd`功能使用自动广播包装,那么您所需要做的就是
```py ```py
Tensor myadd_autocast(const Tensor& self, const Tensor& other) { Tensor myadd_autocast(const Tensor& self, const Tensor& other) {
...@@ -237,8 +237,8 @@ TORCH_LIBRARY_IMPL(myops, Autocast, m) { ...@@ -237,8 +237,8 @@ TORCH_LIBRARY_IMPL(myops, Autocast, m) {
### 批量 ### 批量
批量张量允许您按示例方式编写代码,然后在`vmap`调用下运行时自动对其进行批量。 当前正在开发用于编写批量规则的 API,但是一旦稳定该 API,就可以通过在 Batched 调度键处注册内核来为操作员添加对`vmap`的支持。 批量张量允许您按示例方式编写代码,然后在`vmap`调用下运行时自动对其进行批量。 当前正在开发用于编写批量规则的 API,但是一旦稳定该 API,就可以通过在 Batched 调度键处注册内核来为运算符添加对`vmap`的支持。
### 追踪器 ### 追踪器
Tracer 调度键实现了对在运行`torch.jit.trace`时将操作员调用记录到跟踪中的支持。 我们打算提供一个盒装后备,它将实现对任意操作的跟踪,请参阅[问题#41478](https://github.com/pytorch/pytorch/issues/41478) 以跟踪进度。 Tracer 调度键实现了对在运行`torch.jit.trace`时将运算符调用记录到跟踪中的支持。 我们打算提供一个盒装后备,它将实现对任意操作的跟踪,请参阅 [ISSUE#41478](https://github.com/pytorch/pytorch/issues/41478) 以跟踪进度。
\ No newline at end of file \ No newline at end of file
...@@ -77,7 +77,7 @@ with profiler.profile(with_stack=True, profile_memory=True) as prof: ...@@ -77,7 +77,7 @@ with profiler.profile(with_stack=True, profile_memory=True) as prof:
## 打印分析器结果 ## 打印分析器结果
最后,我们打印分析器结果。 `profiler.key_averages`通过操作员名称,以及可选地通过输入形状和/或堆栈跟踪事件来聚合结果。 按输入形状分组有助于识别模型使用哪些张量形状。 最后,我们打印分析器结果。 `profiler.key_averages`通过运算符名称,以及可选地通过输入形状和/或堆栈跟踪事件来聚合结果。 按输入形状分组有助于识别模型使用哪些张量形状。
在这里,我们使用`group_by_stack_n=5`通过操作及其回溯(截断为最近的 5 个事件)聚合运行时,并按事件注册的顺序显示事件。 还可以通过传递`sort_by`参数对表进行排序(有关有效的排序键,请参阅[文档](https://pytorch.org/docs/stable/autograd.html#profiler))。 在这里,我们使用`group_by_stack_n=5`通过操作及其回溯(截断为最近的 5 个事件)聚合运行时,并按事件注册的顺序显示事件。 还可以通过传递`sort_by`参数对表进行排序(有关有效的排序键,请参阅[文档](https://pytorch.org/docs/stable/autograd.html#profiler))。
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册