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等等。