native_infer.md 9.2 KB
Newer Older
1
# C++ 预测 API介绍
S
superjomn 已提交
2

Z
Zhaolong Xing 已提交
3
为了更简单方便的预测部署,PaddlePaddle 提供了一套高层 API 预测接口。
S
superjomn 已提交
4 5 6

预测库包含:

Z
Zhaolong Xing 已提交
7 8 9 10 11 12 13
- 头文件主要包括: 
	- `paddle_analysis_config.h `
	- `paddle_api.h `
	- `paddle_inference_api.h`
- 库文件:
	- `libpaddle_fluid.so` 
	- `libpaddle_fluid.a`
S
superjomn 已提交
14

Z
Zhaolong Xing 已提交
15
下面是详细介绍。
S
superjomn 已提交
16 17


Z
Zhaolong Xing 已提交
18 19 20 21 22 23
## 内容
- [NativePredictor使用](#NativePredictor使用)
- [AnalysisPredictor使用](#AnalysisPredictor使用)
- [输入输出的管理](#输入输出的管理)	
- [多线程预测](#多线程预测)
- [性能建议](#性能建议)
S
superjomn 已提交
24

Z
Zhaolong Xing 已提交
25
## <a name="NativePredictor使用">NativePredictor使用</a>
S
superjomn 已提交
26

Z
Zhaolong Xing 已提交
27 28
`NativePredictor`为原生预测引擎,底层由 PaddlePaddle 原生的 forward operator
  组成,可以天然**支持所有Paddle 训练出的模型**
C
Cheerego 已提交
29
  
Z
Zhaolong Xing 已提交
30 31
#### NativePredictor 使用样例
```c++ 
S
superjomn 已提交
32 33
#include "paddle_inference_api.h"

Z
Zhaolong Xing 已提交
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
namespace paddle {
// 配置NativeConfig
void CreateConfig(NativeConfig *config, const std::string& model_dirname) {
  config->use_gpu=true;
  config->device=0;
  config->fraction_of_gpu_memory=0.1;
  
  /* for cpu
  config->use_gpu=false;
  config->SetCpuMathLibraryNumThreads(1);
  */
  
  // 设置模型的参数路径
  config->prog_file = model_dirname + "model";
  config->param_file = model_dirname + "params";
  // 当模型输入是多个的时候,这个配置是必要的。
  config->specify_input_name = true;
}
52

Z
Zhaolong Xing 已提交
53 54 55
void RunNative(int batch_size, const std::string& model_dirname) {
  // 1. 创建NativeConfig
  NativeConfig config;
56
  CreateConfig(&config, model_dirname);
Z
Zhaolong Xing 已提交
57 58 59 60 61 62 63
  
  // 2. 根据config 创建predictor
  auto predictor = CreatePaddlePredictor(config);
  
  int channels = 3;
  int height = 224;
  int width = 224;
64
  float *data = new float[batch_size * channels * height * width];
Z
Zhaolong Xing 已提交
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80

  // 3. 创建输入 tensor 
  PaddleTensor tensor;
  tensor.name = "image";
  tensor.shape = std::vector<int>({batch_size, channels, height, width});
  tensor.data = PaddleBuf(static_cast<void *>(data),
                          sizeof(float) * (batch_size * channels * height * width));
  tensor.dtype = PaddleDType::FLOAT32;
  std::vector<PaddleTensor> paddle_tensor_feeds(1, tensor);

  // 4. 创建输出 tensor
  std::vector<PaddleTensor> outputs;
  // 5. 预测
  predictor->Run(paddle_tensor_feeds, &outputs, batch_size);

  const size_t num_elements = outputs.front().data.length() / sizeof(float);
81
  auto *data_out = static_cast<float *>(outputs.front().data.data());
Z
Zhaolong Xing 已提交
82 83
}
}  // namespace paddle
S
superjomn 已提交
84

Z
Zhaolong Xing 已提交
85 86 87 88 89 90
int main() { 
  // 模型下载地址 http://paddle-inference-dist.cdn.bcebos.com/tensorrt_test/mobilenet.tar.gz
  paddle::RunNative(1, "./mobilenet");
  return 0;
}
```
S
superjomn 已提交
91

Z
Zhaolong Xing 已提交
92 93
## <a name="AnalysisPredictor使用"> AnalysisPredictor使用</a>
AnalysisConfig 创建了一个高性能预测引擎。该引擎通过对计算图的分析,完成对计算图的一系列的优化(Op 的融合, MKLDNN,TRT等底层加速库的支持 etc),大大提升预测引擎的性能。 
94

Z
Zhaolong Xing 已提交
95
#### AnalysisPredictor 使用样例
S
superjomn 已提交
96

Z
Zhaolong Xing 已提交
97
```c++
98 99 100
#include "paddle_inference_api.h"

namespace paddle {
Z
Zhaolong Xing 已提交
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
void CreateConfig(AnalysisConfig* config, const std::string& model_dirname) {
  // 模型从磁盘进行加载
  config->SetModel(model_dirname + "/model",                                                                                             
                      model_dirname + "/params");  
  // config->SetModel(model_dirname);
  // 如果模型从内存中加载,可以使用SetModelBuffer接口
  // config->SetModelBuffer(prog_buffer, prog_size, params_buffer, params_size); 
  config->EnableUseGpu(10 /*the initial size of the GPU memory pool in MB*/,  0 /*gpu_id*/);
  
  /* for cpu 
  config->DisableGpu();
  config->EnableMKLDNN();   // 可选
  config->SetCpuMathLibraryNumThreads(10);
  */
 
  // 当使用ZeroCopyTensor的时候,此处一定要设置为false。
  config->SwitchUseFeedFetchOps(false);
  // 当多输入的时候,此处一定要设置为true
  config->SwitchSpecifyInputNames(true);
  config->SwitchIrDebug(true); // 开关打开,会在每个图优化过程后生成dot文件,方便可视化。
  // config->SwitchIrOptim(false); // 默认为true。如果设置为false,关闭所有优化,执行过程同 NativePredictor
  // config->EnableMemoryOptim(); // 开启内存/显存复用
}
124

Z
Zhaolong Xing 已提交
125 126 127
void RunAnalysis(int batch_size, std::string model_dirname) {
  // 1. 创建AnalysisConfig
  AnalysisConfig config;
128
  CreateConfig(&config, model_dirname);
Z
Zhaolong Xing 已提交
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
  
  // 2. 根据config 创建predictor
  auto predictor = CreatePaddlePredictor(config);
  int channels = 3;
  int height = 224;
  int width = 224;
  float input[batch_size * channels * height * width] = {0};
  
  // 3. 创建输入
  // 同NativePredictor样例一样,此处可以使用PaddleTensor来创建输入
  // 以下的代码中使用了ZeroCopy的接口,同使用PaddleTensor不同的是:此接口可以避免预测中多余的cpu copy,提升预测性能。
  auto input_names = predictor->GetInputNames();
  auto input_t = predictor->GetInputTensor(input_names[0]);
  input_t->Reshape({batch_size, channels, height, width});
  input_t->copy_from_cpu(input);

  // 4. 运行
  CHECK(predictor->ZeroCopyRun());
147
   
Z
Zhaolong Xing 已提交
148 149 150 151 152 153 154 155 156 157 158
  // 5. 获取输出
  std::vector<float> out_data;
  auto output_names = predictor->GetOutputNames();
  auto output_t = predictor->GetOutputTensor(output_names[0]);
  std::vector<int> output_shape = output_t->shape();
  int out_num = std::accumulate(output_shape.begin(), output_shape.end(), 1, std::multiplies<int>());

  out_data.resize(out_num);
  output_t->copy_to_cpu(out_data.data());
}
}  // namespace paddle
S
superjomn 已提交
159

Z
Zhaolong Xing 已提交
160 161 162 163 164
int main() { 
  // 模型下载地址 http://paddle-inference-dist.cdn.bcebos.com/tensorrt_test/mobilenet.tar.gz
  paddle::RunAnalysis(1, "./mobilenet");
  return 0;
}
S
superjomn 已提交
165 166 167 168


```

Z
Zhaolong Xing 已提交
169 170 171 172
## <a name="输入输出的管理"> 输入输出的管理</a>
### PaddleTensor 的使用
PaddleTensor可用于NativePredictor和AnalysisPredictor,在 NativePredictor样例中展示了PaddleTensor的使用方式。
PaddleTensor 定义了预测最基本的输入输出的数据格式,常用字段如下:
S
superjomn 已提交
173

Z
Zhaolong Xing 已提交
174 175 176 177 178
- `name`,类型:string,用于指定输入数据对应的模型中variable的名字
- `shape`,类型:`vector<int>`, 表示一个Tensor的shape
- `data`,类型:`PaddleBuf`, 数据以连续内存的方式存储在`PaddleBuf`中,`PaddleBuf`可以接收外面的数据或者独立`malloc`内存,详细可以参考头文件中相关定义。
- `dtype`,类型:`PaddleType`, 有`PaddleDtype::FLOAT32`, `PaddleDtype::INT64`, `PaddleDtype::INT32`三种, 表示 Tensor 的数据类型。
- `lod`,类型:`vector<vector<size_t>>`,在处理变长输入的时候,需要对 `PaddleTensor`设置LoD信息。可以参考[LoD-Tensor使用说明](../../../user_guides/howto/basic_concept/lod_tensor.html)
179 180


Z
Zhaolong Xing 已提交
181 182
### ZeroCopyTensor的使用
ZeroCopyTensor的使用可避免预测时候准备输入以及获取输出时多余的数据copy,提高预测性能。**只可用于AnalysisPredictor**
F
flame 已提交
183

Z
Zhaolong Xing 已提交
184
**Note:**使用ZeroCopyTensor,务必在创建config时设置`config->SwitchUseFeedFetchOps(false)`
F
flame 已提交
185

Z
Zhaolong Xing 已提交
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
```
// 通过创建的AnalysisPredictor获取输入和输出的tensor
auto input_names = predictor->GetInputNames();
auto input_t = predictor->GetInputTensor(input_names[0]);
auto output_names = predictor->GetOutputNames();
auto output_t = predictor->GetOutputTensor(output_names[0]);

// 对tensor进行reshape
input_t->Reshape({batch_size, channels, height, width});

// 通过copy_from_cpu接口,将cpu数据输入;通过copy_to_cpu接口,将输出数据copy到cpu
input_t->copy_from_cpu<float>(input_data /*数据指针*/);
output_t->copy_to_cpu(out_data /*数据指针*/);

// 设置LOD 
std::vector<std::vector<size_t>> lod_data = {{0}, {0}};
input_t->SetLoD(lod_data);

// 获取tensor数据指针
float *input_d = input_t->mutable_data<float>(PaddlePlace::kGPU);  // CPU下使用PaddlePlace::kCPU
int output_size;
float *output_d = output_t->data<float>(PaddlePlace::kGPU, &output_size);
```
F
flame 已提交
209

Z
Zhaolong Xing 已提交
210
## <a name="多线程预测"> 多线程预测</a>
211 212


Z
Zhaolong Xing 已提交
213
多线程场景下,每个服务线程执行同一种模型,支持 CPU 和 GPU。
214

Z
Zhaolong Xing 已提交
215
下面演示最简单的实现,用户需要根据具体应用场景做相应的调整
216 217 218 219 220 221 222 223

```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;

224 225
// 将克隆的 predictor 放入 vector 供线程使用
for (int i = 0; i < num_threads; i++) {
226 227
    predictors.emplace_back(main_predictor->Clone());
}
Z
Zhaolong Xing 已提交
228

229 230 231 232 233 234 235 236 237
// 创建线程并执行
for (int i = 0; i < num_threads; i++) {
    threads.emplace_back([i, &]{
        auto& predictor = predictors[i];
        // 执行
        CHECK(predictor->Run(...));
    });
}

Z
Zhaolong Xing 已提交
238
// 线程join
239 240 241 242 243 244 245 246
for (auto& t : threads) {
    if (t.joinable()) t.join();
}

// 结束
```


Z
Zhaolong Xing 已提交
247
## <a name="性能建议"> 性能建议</a>
248

Z
Zhaolong Xing 已提交
249 250 251 252 253 254
1. 在CPU型号允许的情况下,尽量使用带AVX和MKL的版本
2. CPU或GPU预测,可以尝试把`NativeConfig`改成`AnalysisConfig`来进行优化
3. 尽量使用`ZeroCopyTensor`避免过多的内存copy
4. CPU下可以尝试使用Intel的`MKLDNN`加速
5. GPU 下可以尝试打开`TensorRT`子图加速引擎, 通过计算图分析,Paddle可以自动将计算图中部分子图切割,并调用NVidia的 `TensorRT` 来进行加速。
详细内容可以参考 [Paddle-TRT 子图引擎](./paddle_tensorrt_infer.html)