PaddlePaddle inference 源码分析(一)

本文针对代码版本为Paddle/2.2,主要针对预测流程的梳理。

一、简要使用流程

paddle inference的使用较为简单,其基本代码如下:

// 创建predictor
std::shared_ptr<Predictor> InitPredictor() {
  Config config;
  if (FLAGS_model_dir != "") {
    config.SetModel(FLAGS_model_dir);
  }
  config.SetModel(FLAGS_model_file, FLAGS_params_file);
  if (FLAGS_use_gpu) {
    config.EnableUseGpu(100, 0);
  } else {
    config.EnableMKLDNN();
  }

  // Open the memory optim.
  config.EnableMemoryOptim();
  return CreatePredictor(config);
}

// 执行预测
void run(Predictor *predictor, const std::vector<float> &input,
         const std::vector<int> &input_shape, std::vector<float> *out_data) {
  int input_num = std::accumulate(input_shape.begin(), input_shape.end(), 1,
                                  std::multiplies<int>());

  auto input_names = predictor->GetInputNames();
  auto output_names = predictor->GetOutputNames();
  auto input_t = predictor->GetInputHandle(input_names[0]);
  input_t->Reshape(input_shape);
  input_t->CopyFromCpu(input.data());

  for (size_t i = 0; i < FLAGS_warmup; ++i)
    CHECK(predictor->Run());

  auto st = time();
  for (size_t i = 0; i < FLAGS_repeats; ++i) {
    CHECK(predictor->Run());
    auto output_t = predictor->GetOutputHandle(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->CopyToCpu(out_data->data());
  }
  LOG(INFO) << "run avg time is " << time_diff(st, time()) / FLAGS_repeats
            << " ms";
}

二、代码目录结构

代码库地址:https://github.com/PaddlePaddle/Paddle

目录结构如下:

--cmake #cmake编译脚本以及编译链接的第三方库等
--doc
--paddle #c++代码
    -fluid
        -distributed #分布式相关代码,主要为训练使用,包括模型内all_reduce进行跨卡通信、跨机通信等
        -extension #
        -framework #基础组件代码
        -imperative #分布式通信相关代码,包括nccl、all_reduce、bkcl等
        -inference #预测相关代码以及api定义
        -memory
        -operators #算子
        -platform #平台相关代码
        -pybind #pybind接口定义
        -string
    -scripts
    -testing
    -utils
--patches
--python #python部分代码
--r
--tools
--CMakeLists.txt #编译脚本,包括大部分编译参数、三方库依赖等逻辑

三、编译产出

  产出目录如下:

  

build
    -python #whl安装包
    -paddle_install_dir #产出的所有头文件及库
    -paddle_inference_install_dir #预测c++依赖库
    -paddle_inference_c_install_dir #预测c依赖库

 四、构件简介

1、predictor

实例持有所有资源,是预测端的主要端口。在第一个predictor创建出来后,可以使用clone接口创建新的实例,使用clone接口创建出来的predictor实例与父实例共有固定参数资源scope。

会预先进行内存和显存的分配。

2、scope

用于保存参数变量的结构。其内部结构为:scope->variable->LodTensor。scope中保存variable的map表。predictor会持有scope并保存模型权重。同时,每个predictor实例会保存sub_scope(使用scope创建的子scope)用于保存临时参数(计算过程中的可变参数、输入输出等)。

在第一次执行创建好内存后,后续的执行都是从scope中获取缓存的内存来使用。

在inference中scope由于只会有单线程写,多线程读的部分为持久参数,因此无锁。但是在训练框架中scope有锁。

3、place

用于表征当前操作运行环境的变量,如:CPUPlace、GPUPlace等。程序使用place选择对应opkernel,或者表征当前内存所在的位置为cpu还是gpu等

4、DeviceContext与DeviceContextPool

DeviceContext用于存储当前环境中所有计算资源,每种硬件资源都有对应的context,包括cpu、gpu、npu等。predictor从全局单例的DeviceContextPool中根据place获取对应DeviceContext

5、Operator与Kernel

算子,计算单元。模型文件中保存了所要运行的有向图,图中每个节点就是一个Op,predictor执行时会顺序执行图中的每个OP。这里OP有两种,一种是功能性OP,直接在OP中写好了操作内容,如打印错误信息、收集性能数据等;另一种是计算OP,基于OpWithKernel实现,调用该种OP时,会根据place参数以及op本身特性选择对应器件的Kernel执行计算。一般每个OP都会注册包括CPU、GPU、NPU等版本的对应Kernel。

6、IR Pass

图优化。加载模型文件,创建predictor后,如果开启ir_optim会进行图优化。这里实际是对原始的模型op图顺序执行符合要求的各个Pass,每个pass代表一种优化规则,比如有节点融合、特殊结构优化、子图切割进tensorrt等等。

posted @ 2021-12-14 15:00  鸭子船长  阅读(1144)  评论(0编辑  收藏  举报