39.md 13.7 KB
Newer Older
W
wizardforcel 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
# 在 C++ 中加载 TorchScript 模型

> 原文:<https://pytorch.org/tutorials/advanced/cpp_export.html>

顾名思义,PyTorch 的主要接口是 Python 编程语言。 尽管 Python 是许多需要动态性和易于迭代的场景的合适且首选的语言,但是在同样许多情况下,Python 的这些属性恰恰是不利的。 后者经常应用的一种环境是*生产* –低延迟和严格部署要求的土地。 对于生产场景,即使仅将 C++ 绑定到 Java,Rust 或 Go 之类的另一种语言中,它也是经常选择的语言。 以下各段将概述 PyTorch 提供的从现有 Python 模型到序列化表示形式的路径,该序列化表示形式可以完全由 C++ *加载**执行*,不依赖于 Python。

## 第 1 步:将 PyTorch 模型转换为 Torch 脚本

PyTorch 模型从 Python 到 C++ 的旅程由 [Torch 脚本](https://pytorch.org/docs/master/jit.html)启用,它是 PyTorch 模型的一种表示形式,可以由 Torch 脚本编译器理解,编译和序列化。 如果您从使用原始“渴望” API 编写的现有 PyTorch 模型开始,则必须首先将模型转换为 Torch 脚本。 在最常见的情况下(如下所述),只需很少的努力。 如果您已经有了 Torch 脚本模块,则可以跳到本教程的下一部分。

有两种将 PyTorch 模型转换为 Torch 脚本的方法。 第一种称为*跟踪*,该机制通过使用示例输入对模型的结构进行一次评估,并记录这些输入在模型中的流量来捕获模型的结构。 这适用于有限使用控制流的模型。 第二种方法是在模型中添加显式注解,以告知 TorchScript 编译器可以根据 Torch Script 语言施加的约束直接解析和编译模型代码。

小费

您可以在官方 [Torch 脚本参考](https://pytorch.org/docs/master/jit.html)中找到这两种方法的完整文档以及使用方法的进一步指导。

### 通过跟踪转换为 Torch 脚本

要将 PyTorch 模型通过跟踪转换为 Torch 脚本,必须将模型的实例以及示例输入传递给`torch.jit.trace`函数。 这将产生一个`torch.jit.ScriptModule`对象,并将模型评估的轨迹嵌入到模块的`forward`方法中:

```py
import torch
import torchvision

# An instance of your model.
model = torchvision.models.resnet18()

# An example input you would normally provide to your model's forward() method.
example = torch.rand(1, 3, 224, 224)

# Use torch.jit.trace to generate a torch.jit.ScriptModule via tracing.
traced_script_module = torch.jit.trace(model, example)

```

现在可以对跟踪的`ScriptModule`进行评估,使其与常规 PyTorch 模块相同:

```py
In[1]: output = traced_script_module(torch.ones(1, 3, 224, 224))
In[2]: output[0, :5]
Out[2]: tensor([-0.2698, -0.0381,  0.4023, -0.3010, -0.0448], grad_fn=<SliceBackward>)

```

### 通过注解转换为 Torch 脚本

在某些情况下,例如,如果模型采用特定形式的控制流,则可能需要直接在 Torch 脚本中编写模型并相应地注解模型。 例如,假设您具有以下原始 Pytorch 模型:

```py
import torch

class MyModule(torch.nn.Module):
    def __init__(self, N, M):
        super(MyModule, self).__init__()
        self.weight = torch.nn.Parameter(torch.rand(N, M))

    def forward(self, input):
        if input.sum() > 0:
          output = self.weight.mv(input)
        else:
          output = self.weight + input
        return output

```

因为此模块的`forward`方法使用取决于输入的控制流,所以它不适合跟踪。 相反,我们可以将其转换为`ScriptModule`。 为了将模块转换为`ScriptModule`,需要使用`torch.jit.script`编译模块,如下所示:

```py
class MyModule(torch.nn.Module):
    def __init__(self, N, M):
        super(MyModule, self).__init__()
        self.weight = torch.nn.Parameter(torch.rand(N, M))

    def forward(self, input):
        if input.sum() > 0:
          output = self.weight.mv(input)
        else:
          output = self.weight + input
        return output

my_module = MyModule(10,20)
sm = torch.jit.script(my_module)

```

如果您需要在`nn.Module`中排除某些方法,因为它们使用的是 TorchScript 不支持的 Python 函数,则可以使用`@torch.jit.ignore`来注解这些方法

`my_module`是已准备好进行序列化的`ScriptModule`的实例。

## 第 2 步:将脚本模块序列化为文件

跟踪或注解 PyTorch 模型后,一旦有了`ScriptModule`,就可以将其序列化为文件了。 稍后,您将能够使用 C++ 从此文件加载模块并执行它,而无需依赖 Python。 假设我们要序列化先前在跟踪示例中显示的`ResNet18`模型。 要执行此序列化,只需在模块上调用[`save`](https://pytorch.org/docs/master/jit.html#torch.jit.ScriptModule.save)并为其传递文件名:

```py
traced_script_module.save("traced_resnet_model.pt")

```

这将在您的工作目录中生成一个`traced_resnet_model.pt`文件。 如果您还想序列化`my_module`,请致电`my_module.save("my_module_model.pt")`。我们现在已经正式离开 Python 领域,并准备跨入 C++ 领域。

## 第 3 步:在 C++ 中加载脚本模块

要在 C++ 中加载序列化的 PyTorch 模型,您的应用必须依赖于 PyTorch C++ API –也称为 *LibTorch* 。 LibTorch 发行版包含共享库,头文件和 CMake 构建配置文件的集合。 虽然 CMake 不是依赖 LibTorch 的要求,但它是推荐的方法,将来会得到很好的支持。 对于本教程,我们将使用 CMake 和 LibTorch 构建一个最小的 C++ 应用,该应用简单地加载并执行序列化的 PyTorch 模型。

### 最小的 C++ 应用

让我们从讨论加载模块的代码开始。 以下将已经做:

```py
#include <torch/script.h> // One-stop header.

#include <iostream>
#include <memory>

int main(int argc, const char* argv[]) {
  if (argc != 2) {
    std::cerr << "usage: example-app <path-to-exported-script-module>\n";
    return -1;
  }

  torch::jit::script::Module module;
  try {
    // Deserialize the ScriptModule from a file using torch::jit::load().
    module = torch::jit::load(argv[1]);
  }
  catch (const c10::Error& e) {
    std::cerr << "error loading the model\n";
    return -1;
  }

  std::cout << "ok\n";
}

```

`<torch/script.h>`标头包含了运行示例所需的 LibTorch 库中的所有相关包含。 我们的应用接受序列化的 PyTorch `ScriptModule`的文件路径作为其唯一的命令行参数,然后继续使用`torch::jit::load()`函数对该模块进行反序列化,该函数将该文件路径作为输入。 作为回报,我们收到一个`torch::jit::script::Module`对象。 我们将稍后讨论如何执行它。

### 依赖 LibTorch 并构建应用

假设我们将以上代码存储到名为`example-app.cpp`的文件中。 最小的`CMakeLists.txt`构建起来看起来很简单:

```py
cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
project(custom_ops)

find_package(Torch REQUIRED)

add_executable(example-app example-app.cpp)
target_link_libraries(example-app "${TORCH_LIBRARIES}")
set_property(TARGET example-app PROPERTY CXX_STANDARD 14)

```

建立示例应用的最后一件事是 LibTorch 发行版。 您可以随时从 PyTorch 网站上的[下载页面](https://pytorch.org/)获取最新的稳定版本。 如果下载并解压缩最新的归档文件,则应该收到具有以下目录结构的文件夹:

```py
libtorch/
  bin/
  include/
  lib/
  share/

```

*   `lib/`文件夹包含您必须链接的共享库,
*   `include/`文件夹包含程序需要包含的头文件,
*   `share/`文件夹包含必要的 CMake 配置,以启用上面的简单`find_package(Torch)`命令。

小费

在 Windows 上,调试和发行版本不兼容 ABI。 如果计划以调试模式构建项目,请尝试使用 LibTorch 的调试版本。 另外,请确保在下面的`cmake --build .`行中指定正确的配置。

最后一步是构建应用。 为此,假定示例目录的布局如下:

```py
example-app/
  CMakeLists.txt
  example-app.cpp

```

现在,我们可以运行以下命令从`example-app/`文件夹中构建应用:

```py
mkdir build
cd build
cmake -DCMAKE_PREFIX_PATH=/path/to/libtorch ..
cmake --build . --config Release

```

其中`/path/to/libtorch`应该是解压缩的 LibTorch 发行版的完整路径。 如果一切顺利,它将看起来像这样:

```py
root@4b5a67132e81:/example-app# mkdir build
root@4b5a67132e81:/example-app# cd build
root@4b5a67132e81:/example-app/build# cmake -DCMAKE_PREFIX_PATH=/path/to/libtorch ..
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Looking for pthread.h
-- Looking for pthread.h - found
-- Looking for pthread_create
-- Looking for pthread_create - not found
-- Looking for pthread_create in pthreads
-- Looking for pthread_create in pthreads - not found
-- Looking for pthread_create in pthread
-- Looking for pthread_create in pthread - found
-- Found Threads: TRUE
-- Configuring done
-- Generating done
-- Build files have been written to: /example-app/build
root@4b5a67132e81:/example-app/build# make
Scanning dependencies of target example-app
[ 50%] Building CXX object CMakeFiles/example-app.dir/example-app.cpp.o
[100%] Linking CXX executable example-app
[100%] Built target example-app

```

如果我们提供到先前创建的跟踪`ResNet18`模型`traced_resnet_model.pt`到生成的`example-app`二进制文件的路径,则应该以友好的“确定”来回报。 请注意,如果尝试使用`my_module_model.pt`运行此示例,则会收到一条错误消息,提示您输入的形状不兼容。 `my_module_model.pt`期望使用 1D 而不是 4D。

```py
root@4b5a67132e81:/example-app/build# ./example-app <path_to_model>/traced_resnet_model.pt
ok

```

## 步骤 4:在 C++ 中执行脚本模块

在用 C++ 成功加载序列化的`ResNet18`之后,我们现在离执行它仅几行代码了! 让我们将这些行添加到 C++ 应用的`main()`函数中:

```py
// Create a vector of inputs.
std::vector<torch::jit::IValue> inputs;
inputs.push_back(torch::ones({1, 3, 224, 224}));

// Execute the model and turn its output into a tensor.
at::Tensor output = module.forward(inputs).toTensor();
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::ones`和 PyTorch C++ API 之类的功能的更多信息,请参阅[这个页面](https://pytorch.org/cppdocs)上的文档。 PyTorch C++ API 提供了与 Python API 几乎相同的功能,使您可以像在 Python 中一样进一步操纵和处理张量。

在最后一行,我们打印输出的前五个条目。 由于在本教程前面的部分中,我们为 Python 中的模型提供了相同的输入,因此理想情况下,我们应该看到相同的输出。 让我们通过重新编译我们的应用并以相同的序列化模型运行它来进行尝试:

```py
root@4b5a67132e81:/example-app/build# make
Scanning dependencies of target example-app
[ 50%] Building CXX object CMakeFiles/example-app.dir/example-app.cpp.o
[100%] Linking CXX executable example-app
[100%] Built target example-app
root@4b5a67132e81:/example-app/build# ./example-app traced_resnet_model.pt
-0.2698 -0.0381  0.4023 -0.3010 -0.0448
[ Variable[CPUFloatType]{1,5} ]

```

作为参考,Python 以前的输出为:

```py
tensor([-0.2698, -0.0381,  0.4023, -0.3010, -0.0448], grad_fn=<SliceBackward>)

```

看起来很不错!

小费

要将模型移至 GPU 内存,可以编写`model.to(at::kCUDA);`。 通过调用`tensor.to(at::kCUDA)`来确保模型的输入也位于 CUDA 内存中,这将在 CUDA 内存中返回新的张量。

## 第 5 步:获得帮助并探索 API

本教程有望使您对 PyTorch 模型从 Python 到 C++ 的路径有一个大致的了解。 利用本教程中介绍的概念,您应该能够从原始的“急切的” PyTorch 模型,到 Python 中的已编译`ScriptModule`,再到磁盘上的序列化文件,以及–结束循环–到可执行文件`script::Module`在 C++ 中。

当然,有许多我们没有介绍的概念。 例如,您可能会发现自己想要扩展使用 C++ 或 CUDA 实现的自定义运算符来扩展`ScriptModule`,并希望在纯 C++ 生产环境中加载的`ScriptModule`内执行该自定义运算符。 好消息是:这是可能的,并且得到了很好的支持! 现在,您可以浏览[这个](https://github.com/pytorch/pytorch/tree/master/test/custom_operator)文件夹作为示例,我们将很快提供一个教程。 目前,以下链接通常可能会有所帮助:

*   [Torch 脚本参考](https://pytorch.org/docs/master/jit.html)
*   [PyTorch C++ API 文档](https://pytorch.org/cppdocs/)
*   [PyTorch Python API 文档](https://pytorch.org/docs/)

与往常一样,如果您遇到任何问题或疑问,可以使用我们的[论坛](https://discuss.pytorch.org/)[GitHub ISSUE](https://github.com/pytorch/pytorch/issues) 进行联系。