C++ 预测 API介绍

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

预测库包含:

  • 头文件 paddle_inference_api.h 定义了所有的接口
  • 库文件libpaddle_fluid.solibpaddle_fluid.a下面是详细介绍

PaddleTensor

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

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

利用Config 创建不同引擎

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

Config 有两种,NativeConfig 较简单和稳定,AnalysisConfig 功能更新,性能更好

  • NativeConfig 原生 engine,由 paddle 原生的 forward operator 组成,可以天然支持所有paddle 训练出的模型

  • AnalysisConfig

  • 支持计算图的分析和优化

  • 支持最新的各类 op fuse,性能一般比 NativeConfig 要好
  • 支持 TensorRT mixed engine 用于 GPU 加速,用子图的方式支持了 [TensorRT] ,支持所有paddle 模型,并自动切割部分计算子图到 TensorRT 上加速,具体的使用方式可以参考这里

基于 NativeConfig 的预测部署过程

总体上分为以下步骤

  • 用合适的配置创建 PaddlePredictor
  • 创建输入用的 PaddleTensor,传入到 PaddlePredictor
  • 获取输出的 PaddleTensor ,将结果取出下面完整演示一个简单的模型,部分细节代码隐去
  1. #include "paddle_inference_api.h"
  2.  
  3. // 创建一个 config,并修改相关设置
  4. paddle::NativeConfig config;
  5. config.model_dir = "xxx";
  6. config.use_gpu = false;
  7. // 创建一个原生的 PaddlePredictor
  8. auto predictor =
  9. paddle::CreatePaddlePredictor<paddle::NativeConfig>(config);
  10. // 创建输入 tensor
  11. int64_t data[4] = {1, 2, 3, 4};
  12. paddle::PaddleTensor tensor;
  13. tensor.shape = std::vector<int>({4, 1});
  14. tensor.data.Reset(data, sizeof(data));
  15. tensor.dtype = paddle::PaddleDType::INT64;
  16. // 创建输出 tensor,输出 tensor 的内存可以复用
  17. std::vector<paddle::PaddleTensor> outputs;
  18. // 执行预测
  19. CHECK(predictor->Run(slots, &outputs));
  20. // 获取 outputs ...

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

高阶使用

输入输出的内存管理

PaddleTensordata 字段是一个 PaddleBuf,用于管理一段内存用于数据的拷贝。

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

  • 自动分配和管理内存
  1. int some_size = 1024;
  2. PaddleTensor tensor;
  3. tensor.data.Resize(some_size);
  • 外部内存传入
  1. int some_size = 1024;
  2. // 用户外部分配内存并保证 PaddleTensor 使用过程中,内存一直可用
  3. void* memory = new char[some_size];
  4.  
  5. tensor.data.Reset(memory, some_size);
  6. // ...
  7.  
  8. // 用户最后需要自行删除内存以避免内存泄漏
  9.  
  10. delete[] memory;

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

基于 AnalysisConfig 提升性能

AnalysisConfig 是目前我们重点优化的版本。

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

AnalysisConfig 的使用方法也和 NativeConfig 类似

  1. AnalysisConfig config(dirname); // dirname 是模型的路径
  2. // 对于不同的模型存储格式,也可以用 AnalysisConfig config(model_file, params_file)
  3. config.EnableUseGpu(100/*初始显存池大小(MB)*/, 0 /*gpu id*/); // 使用GPU, CPU下使用config.DisableGpu();
  4. config.SwitchIrOptim(); // 打开优化开关,运行时会执行一系列的计算图优化

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

  1. auto predictor = paddle::CreatePaddlePredictor(config); // 注意这里需要 AnalysisConfig
  2. // 创建输入 tensor
  3. int64_t data[4] = {1, 2, 3, 4};
  4. paddle::PaddleTensor tensor;
  5. tensor.shape = std::vector<int>({4, 1});
  6. tensor.data.Reset(data, sizeof(data));
  7. tensor.dtype = paddle::PaddleDType::INT64;

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

变长序列输入

在处理变长输入的时候,需要对 PaddleTensor 设置LoD信息

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

更详细的例子可以参考LoD-Tensor使用说明

多线程预测的建议

数据并行的服务

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

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

  1. auto main_predictor = paddle::CreatePaddlePredictor(config);
  2.  
  3. const int num_threads = 10; // 假设有 10 个服务线程
  4. std::vector<std::thread> threads;
  5. std::vector<decl_type(main_predictor)> predictors;
  6.  
  7. // 最好初始化时把所有predictor都创建好
  8. predictors.emplace_back(std::move(main_predictor));
  9. for (int i = 1; i < num_threads; i++) {
  10. predictors.emplace_back(main_predictor->Clone());
  11. }
  12. // 创建线程并执行
  13. for (int i = 0; i < num_threads; i++) {
  14. threads.emplace_back([i, &]{
  15. auto& predictor = predictors[i];
  16. // 执行
  17. CHECK(predictor->Run(...));
  18. });
  19. }
  20.  
  21. // 结尾
  22. for (auto& t : threads) {
  23. if (t.joinable()) t.join();
  24. }
  25.  
  26. // 结束

模型并行的服务

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

使用 AnalysisConfig 的对应接口来设置底层科学计算库使用线程的数目,具体参考 SetCpuMathLibraryNumThreads

  1. config.SetCpuMathLibraryNumThreads(8); // 一个模型使用 8 个线程加速预测
  2.  
  3. // 查询状态,可以使用如下接口
  4. config.cpu_math_library_num_threads(); // return an int

性能建议

  • 在 CPU型号允许的情况下,尽量使用带 AVX 和 MKL 的版本
  • 复用输入和输出的 PaddleTensor 以避免频繁分配内存拉低性能
  • CPU或GPU预测,可以尝试把 NativeConfig 改成成 AnalysisConfig 来进行优化

CPU下可以尝试使用 Intel 的 MKLDNN 加速

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

使用方法:

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

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

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

详细内容可以参考 TensorRT 子图引擎

详细代码参考

AnalysisConfig 完整接口可以参考 这里

inference demos