native_infer.md 7.9 KB
Newer Older
1
# C++ 预测 API介绍
S
superjomn 已提交
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

为了更简单方便的预测部署,Fluid 提供了一套高层 API 用来隐藏底层不同的优化实现。

预测库包含:

- 头文件 `paddle_inference_api.h` 定义了所有的接口
- 库文件`libpaddle_fluid.so``libpaddle_fluid.a`

下面是详细介绍

## PaddleTensor

PaddleTensor 定义了预测最基本的输入输出的数据格式,常用字段:

- `name` 用于指定输入数据对应的 模型中variable 的名字
- `shape` 表示一个 Tensor 的 shape
- `data`  数据以连续内存的方式存储在`PaddleBuf` 中,`PaddleBuf` 可以接收外面的数据或者独立`malloc`内存,详细可以参考头文件中相关定义。
- `dtype` 表示 Tensor 的数据类型

## 利用Config 创建不同引擎

23
高层 API 底层有多种优化实现,我们称之为 engine;不同 engine 的切换通过传递不同的 Config 实现重载。
S
superjomn 已提交
24

25
`Config` 有两种,`NativeConfig` 较简单和稳定,`AnalysisConfig` 功能更新,性能更好
S
superjomn 已提交
26

27 28 29 30 31 32
- `NativeConfig` 原生 engine,由 paddle 原生的 forward operator
  组成,可以天然支持所有paddle 训练出的模型,
- `AnalysisConfig` 
  - 支持计算图的分析和优化
  - 支持最新的各类 op fuse,性能一般比  `NativeConfig` 要好
  - 支持 TensorRT mixed engine 用于 GPU
S
superjomn 已提交
33
    加速,用子图的方式支持了 [TensorRT] ,支持所有paddle
34
    模型,并自动切割部分计算子图到 TensorRT 上加速,具体的使用方式可以参考[这里](http://paddlepaddle.org/documentation/docs/zh/1.1/user_guides/howto/inference/paddle_tensorrt_infer.html)
S
superjomn 已提交
35

36
## 基于 NativeConfig 的预测部署过程
S
superjomn 已提交
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59

总体上分为以下步骤

1. 用合适的配置创建 `PaddlePredictor`
2. 创建输入用的 `PaddleTensor`,传入到 `PaddlePredictor`
3. 获取输出的 `PaddleTensor` ,将结果取出

下面完整演示一个简单的模型,部分细节代码隐去

```c++
#include "paddle_inference_api.h"

// 创建一个 config,并修改相关设置
paddle::NativeConfig config;
config.model_dir = "xxx";
config.use_gpu = false;
// 创建一个原生的 PaddlePredictor
auto predictor =
      paddle::CreatePaddlePredictor<paddle::NativeConfig>(config);
// 创建输入 tensor
int64_t data[4] = {1, 2, 3, 4};
paddle::PaddleTensor tensor;
tensor.shape = std::vector<int>({4, 1});
S
update  
superjomn 已提交
60
tensor.data.Reset(data, sizeof(data));
S
superjomn 已提交
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
tensor.dtype = paddle::PaddleDType::INT64;
// 创建输出 tensor,输出 tensor 的内存可以复用
std::vector<paddle::PaddleTensor> outputs;
// 执行预测
CHECK(predictor->Run(slots, &outputs));
// 获取 outputs ...
```

编译时,联编 `libpaddle_fluid.a/.so` 便可。 



## 高阶使用

### 输入输出的内存管理
76

S
superjomn 已提交
77 78 79 80 81
`PaddleTensor``data` 字段是一个 `PaddleBuf`,用于管理一段内存用于数据的拷贝。 

`PaddleBuf` 在内存管理方面有两种模式:

1. 自动分配和管理内存
82 83 84 85 86 87

   ```c++
   int some_size = 1024;
   PaddleTensor tensor;
   tensor.data.Resize(some_size);
   ```
S
superjomn 已提交
88 89

2. 外部内存传入
90 91 92 93 94 95 96 97 98 99 100 101 102

   ```c++
   int some_size = 1024;
   // 用户外部分配内存并保证 PaddleTensor 使用过程中,内存一直可用
   void* memory = new char[some_size]; 
   
   tensor.data.Reset(memory, some_size);
   // ...
   
   // 用户最后需要自行删除内存以避免内存泄漏
   
   delete[] memory;
   ```
S
superjomn 已提交
103 104 105

两种模式中,第一种比较方便;第二种则可以严格控制内存的管理,便于与 `tcmalloc` 等库的集成。

106 107 108
### 基于 AnalysisConfig  提升性能

`AnalysisConfig` 是目前我们重点优化的版本。
S
superjomn 已提交
109

110
类似 `NativeConfig``AnalysisConfig` 可以创建一个经过一系列优化的高性能预测引擎。 其中包含了计算图的分析和优化,以及对一些重要 Op 的融合改写等,比如对使用了 While, LSTM, GRU 等模型性能有大幅提升 。
S
superjomn 已提交
111

112
`AnalysisConfig` 的使用方法也和 `NativeConfig` 类似
S
superjomn 已提交
113 114

```c++
115 116 117 118
AnalysisConfig config(dirname);  // dirname 是模型的路径
// 对于不同的模型存储格式,也可以用 AnalysisConfig config(model_file, params_file)
config.EnableUseGpu(100/*初始显存池大小(MB)*/, 0 /*gpu id*/);  // 使用GPU, CPU下使用config.DisableGpu();
config.SwitchIrOptim();                  // 打开优化开关,运行时会执行一系列的计算图优化
S
superjomn 已提交
119 120 121 122 123
```

这里需要注意的是,输入的 PaddleTensor 需要指定,比如之前的例子需要修改为

```c++
124
auto predictor = paddle::CreatePaddlePredictor(config); // 注意这里需要 AnalysisConfig
S
superjomn 已提交
125 126 127 128
// 创建输入 tensor
int64_t data[4] = {1, 2, 3, 4};
paddle::PaddleTensor tensor;
tensor.shape = std::vector<int>({4, 1});
S
update  
superjomn 已提交
129
tensor.data.Reset(data, sizeof(data));
S
superjomn 已提交
130
tensor.dtype = paddle::PaddleDType::INT64;
131 132 133 134
```

后续的执行过程与 `NativeConfig` 完全一致。

F
flame 已提交
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
### 变长序列输入
在处理变长输入的时候,需要对 `PaddleTensor` 设置LoD信息

``` c++
# 假设序列长度依次为 [3, 2, 4, 1, 2, 3]
tensor.lod = {{0,
               /*0 + 3=*/3,
               /*3 + 2=*/5,
               /*5 + 4=*/9,
               /*9 + 1=*/10,
               /*10 + 2=*/12,
               /*12 + 3=*/15}};
```

更详细的例子可以参考[LoD-Tensor使用说明](../../../user_guides/howto/basic_concept/lod_tensor.html)

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
### 多线程预测的建议

#### 数据并行的服务

这种场景下,每个服务线程执行同一种模型,支持 CPU 和 GPU。

Paddle 并没有相关的接口支持,但用户可以简单组合得出,下面演示最简单的实现,用户最好参考具体应用场景做调整

```c++
auto main_predictor = paddle::CreatePaddlePredictor(config);

const int num_threads = 10;  // 假设有 10 个服务线程
std::vector<std::thread> threads;
std::vector<decl_type(main_predictor)> predictors;

// 最好初始化时把所有predictor都创建好
predictors.emplace_back(std::move(main_predictor));
for (int i = 1; i < num_threads; i++) {
    predictors.emplace_back(main_predictor->Clone());
}
// 创建线程并执行
for (int i = 0; i < num_threads; i++) {
    threads.emplace_back([i, &]{
        auto& predictor = predictors[i];
        // 执行
        CHECK(predictor->Run(...));
    });
}

// 结尾
for (auto& t : threads) {
    if (t.joinable()) t.join();
}

// 结束
```

#### 模型并行的服务

这种场景,使用多个线程/CPU核加速单个模型的预测,**目前只支持 CPU下使用 MKL/MKLDNN 的情况**

使用 `AnalysisConfig` 的对应接口来设置底层科学计算库使用线程的数目,具体参考 [SetCpuMathLibraryNumThreads](https://github.com/PaddlePaddle/Paddle/blob/release/1.3/paddle/fluid/inference/api/paddle_analysis_config.h#L159)

```c++
config.SetCpuMathLibraryNumThreads(8); // 一个模型使用 8 个线程加速预测

// 查询状态,可以使用如下接口
config.cpu_math_library_num_threads(); // return an int
S
superjomn 已提交
199 200 201
```

### 性能建议
202

S
superjomn 已提交
203 204
1. 在 CPU型号允许的情况下,尽量使用带 AVX 和 MKL 的版本
2. 复用输入和输出的 `PaddleTensor` 以避免频繁分配内存拉低性能
205
3. CPU或GPU预测,可以尝试把 `NativeConfig` 改成成 `AnalysisConfig` 来进行优化
S
superjomn 已提交
206

207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
#### CPU下可以尝试使用 Intel 的  `MKLDNN` 加速

MKLDNN 对 `CNN` 类的模型预测有不错的加速效果,可以尝试对比与 `MKLML` 的性能。

使用方法:

```c++
// AnalysisConfig config(...);
config.EnableMKLDNN();
// 查看 mkldnn 是否已经打开,可以用如下代码
config.mkldnn_enabled();  // return a bool
```

#### GPU 下可以尝试打开 `TensorRT` 子图加速引擎

通过计算图分析,Paddle 可以自动将计算图中部分子图切割,并调用 NVidia 的 `TensorRT` 来进行加速。

详细内容可以参考 [TensorRT 子图引擎](./paddle_tensorrt_infer.html)

S
superjomn 已提交
226 227
## 详细代码参考

228 229
`AnalysisConfig` 完整接口可以参考 [这里](https://github.com/PaddlePaddle/Paddle/blob/release/1.3/paddle/fluid/inference/api/paddle_analysis_config.h#L35)

S
update  
Superjomn 已提交
230
[inference demos](https://github.com/PaddlePaddle/Paddle/tree/develop/paddle/fluid/inference/api/demo_ci)
231 232