新增Pass

    CxxPredictor加载模型后,在执行预测前会先优化模型。模型优化过程是通过Pass实现的。 具体调用关系如下: 图片

    • CreatePredictor(CxxConfig)函数调用了Predictor->Build(CxxConfig)
      • CxxPredictor的构建过程(Build)分为两步:
        • Predictor->LoadModel() 加载模型文件到program中
        • Predicotr->optimizer_.Run() 对Program中的原始图形结构进行优化
          • 对图结构的优化是通过调用 Pass->Apply(const std::unique_ptr<SSAGraph>& graph)方法实现的。

    每一类Pass定义了一种优化过程,包括:原模型中的kernel选取、OP融合、冗余OP去除、子图创建、内存优化、类型推导、类型转换等。

    代码位置lite/core/mir/pass.h 主要类成员const Kind kind_ : Pass类型。pass 有三种基本基本类型 :修改图结构的ProgramPass、修改状态量的StmtPass和Debug过程采集信息与控制可视化的DebugPassstd::string name_ :pass 的名称 std::set<TargetType> bound_targets_ : Pass运行的硬件平台,optimizer.Run()优化过程会根据硬件平台选择匹配的Pass。———根据硬件平台自动选择需要的pass std::unordered_map<std::string, std::set<lite_api::Place>> bound_kernels_ : Pass 绑定的kernel (what’s this used for) 主要接口Pass::Apply(const std::unique_ptr& graph) : Pass优化过程的具体操作,是新注册Pass需要实现的接口。输入为SSAGraph型指针,是对模型结构的拓扑表示。

    2、Pass管理 paddle::lite::mir::PassManager

    1. class PassManager {
    2. public:
    3. // 内部静态变量PassManager,用来存储使用的Pass和图优化操作
    4. static PassManager& Global() {
    5. static PassManager x;
    6. return x;
    7. }
    8. // 执行所有的 Pass
    9. void Run(const std::unique_ptr<SSAGraph>& graph) {
    10. for (auto& pass : passes_) {
    11. LOG(INFO) << "Running MIR pass " << pass->name();
    12. pass->Apply(graph);
    13. }
    14. private:
    15. std::list<std::unique_ptr> passes_; //存储所有的 Pass
    16. std::map<std::string, mir::Pass*> pass_map_; //使用map变量存储 PassName::Pass
    17. }

    代码位置lite/core/mir/pass_manager.h 主要类成员std::list:unique_ptr> passes_; : List类型,存储了所有已注册Pass。 std::map<std::string, mir::Pass*> pass_map_; : Map类型,存储了所有”Pass名称-Pass类”键对,用于根据名称查找Pass。

    主要接口static PassManager& Global() 返回PassManager全局静态变量,该变量存储了所有已注册的Pass bool AddNewPass(const std::string& name, Pass* pass) 添加新的Pass到PassManager中

    代码位置lite/core/mir/pass_registry.h 主要接口REGISTER_MIR_PASS(name__, class__) :宏定义函数,用于注册Pass。注册Pass过程实现的是 PassManager::Global().AddNewPass(name__, class__),将新注册Pass添加到全局变量PassManager中。

    1. Pass 注册流程

    lite/core/mir或其子目录下继承Pass基类,实现Pass::Apply接口,并使用宏REGISTER_MIR_PASS(name__, class__)将Pass注册到PassManager即完成了新Pass注册。

    **以新建 **new_demo_pass为例,具体流程如下: (1)在lite/core/mir路径下新建example_pass.ccnew_demo_pass.h 文件 (2)在example_pass.h 文件中继承Pass基类(ProgramPass、StmtPass或DebugPass)定义自己的Pass类。

    1. #include "lite/core/mir/pass.h"
    2. namespace paddle {
    3. namespace lite {
    4. namespace mir {
    5. class ExamplePass : public ProgramPass {
    6. void Apply(const std::unique_ptr<SSAGraph> &graph) override {}
    7. ...
    8. };
    9. } // namespace mir
    10. } // namespace lite
    11. } // namespace paddle

    (3)在example_pass.cc 文件中实现ExamplePass::Apply()接口,并注册ExamplePass

    1. #include "lite/core/mir/pass_registry.h"
    2. #include "lite/core/mir/example_pass.h"
    3. namespace paddle {
    4. namespace lite {
    5. ...
    6. }
    7. } // namespace mir
    8. } // namespace lite
    9. } // namespace paddle
    10. REGISTER_MIR_PASS(example_pass, paddle::lite::mir::ExamplePass)
    11. .BindTargets({TARGET(kARM)}); // Pass执行的目标硬件平台
    12. // .BindKernel("conv2d"); //Pass绑定的 kernel
    1. lite_cc_library(mir_passes
    2. SRCS
    3. demo_pass.cc // 新建的Pass文件
    4. ...
    5. memory_optimize_pass.cc
    6. DEPS mir_pass types context ${mir_fusers} ${subgraph_passes})

    将Pass注册到PassManager后不会自动生效。需要在optimizer->run() 函数中添加该Pass才会在模型优化过程中调用。 (1)在paddle_use_passes.h文件中调用该Pass

    (2)要想在优化模型时调用该Pass,需要在optimizer->run()函数中手动添加调用。

    修改lite/core/optimizer.h文件,添加new_demo_passOptimizer::Run()函数;

    1. class Optimizer {
    2. public:
    3. void Run(...) {
    4. ...
    5. if (passes.empty()) {
    6. RunPasses(std::vector<std::string>{
    7. {"new_demo_pass" //将新注册的Pass添加在这里
    8. ...
    9. }
    10. ...
    11. }

    (3)只有CxxPredictor才会在模型加载后根据Pass优化模型。

    1. ...
    2. #include "paddle_use_passes.h" // 引用Pass优化模型
    3. void RunModel() {
    4. // 1. 创建 CxxConfig
    5. CxxConfig config;
    6. config.set_model_dir(FLAGS_model_dir);
    7. config.set_valid_places(Place{TARGET(kARM), PRECISION(kFloat)});
    8. // 2. 创建CxxPredictor,该过程包括加载模型和用Pass优化模型
    9. std::shared_ptr> predictor =
    10. Creat<CxxConfig>(config);
    11. }

    Fusion Pass是一种常见图结构优化Pass,可将多个连续OP融合成单个等效OP,减少数据交换并简化图结构。Pass运行时调用Fuser自动查找并替换指定图结构,所以注册FuserPass时还需要实现对应的Fuser类。

    下面以fc_fuse_pass为例,详细说明FusionPass的效果和注册方法。

    fc_fuse_pass的作用

    将相邻的mul算子和 element_wise add算子 融合成一个 FC 算子

    1. mul(X) = X * W
    2. elementwise_add( mul(x) ) = X * W + Bias
    3. //----------> after fusion
    4. FC(X) = X * W +Bias

    Pass 运行效果如下: https://user-images.githubusercontent.com/45189361/69639193-12383100-1097-11ea-9063-21f030414080.png图片 mul和elementwise_add的原有参数映射到FC的参数上: 图片

    1、创建FcFuser

    (1)在lite/core/mir/fusion路径下新建fc_fuser.ccfc_fuser.h 文件 (2)在fc_fuser.h 文件中继承FuseBase定义自己的Fuser类。

    1. #include "lite/core/mir/pattern_matcher_high_api.h"
    2. namespace paddle {
    3. namespace lite {
    4. namespace mir {
    5. namespace fusion {
    6. class FcFuser : public FuseBase {
    7. public:
    8. void BuildPattern() override;
    9. void InsertNewNode(SSAGraph* graph, const key2nodes_t& matched) override;
    10. private:
    11. cpp::OpDesc GenOpDesc(const key2nodes_t& matched) override;
    12. } // namespace fusion
    13. } // namespace mir
    14. } // namespace paddle

    对于 FcFuser:BuildPattern描述的Pattern是mul+elementwise add,GenOpDesc创建的FC_op,InsertNewNode函数的效果是用新建的FC_op替换模型中的mul+elementwise add pattern。

    (3) 在fc_fuser.cc文件中实现 BuildPattern()GenOpDesc()InsertNewNode()接口

    下面以FcFuser为例介绍三种接口的实现:

    2、注册fc_fuse_pass

    (1)在lite/core/mir/fusion路径下新建fc_fuse_pass.ccfc_fuse_pass.h 文件 (2)在fc_fuse_pass.h 文件中,继承ProgramPass定义FcFusePass

    1. #include "lite/core/mir/pass.h"
    2. namespace paddle {
    3. namespace lite {
    4. namespace mir {
    5. class FcFusePass : public ProgramPass {
    6. public:
    7. void Apply(const std::unique_ptr<SSAGraph>& graph) override; namespace mir namespace lite namespace paddle

    (3)在fc_fuse_pass.cc 文件中实现FcFusePass::Apply()接口,并注册FcFusePass

    1. #include "lite/core/mir/pass_registry.h"
    2. #include "lite/core/mir/example_pass.h"
    3. namespace paddle {
    4. namespace lite {
    5. namespace mir {
    6. void FcFusePass::Apply(const std::unique_ptr<SSAGraph>& graph) {
    7. fusion::FcFuser fuser;
    8. fuser(graph.get());namespace mir
    9. } // namespace lite
    10. } // namespace paddle
    11. REGISTER_MIR_PASS(lite_fc_fuse_pass, paddle::lite::mir::FcFusePass)
    12. .BindTargets({TARGET(kAny)}) // FcFusePass 可以在任何硬件平台执行
    13. .BindKernel("fc"); // FcFusePass 绑定 fc_kernel

    (4)修改lite/core/mir/fusion/CMakeLists.txt文件,将fc_fuser.cc 编译到mir_fusers

    1. lite_cc_library(fuse_fc
    2. SRCS fc_fuser.cc
    3. DEPS pattern_matcher_high_api)
    4. set(mir_fusers
    5. fuse_fc
    6. ...
    7. CACHE INTERNAL "fusers")

    (5)修改lite/core/mir/CMakeLists.txt文件,将fc_fuse_pass.cc 编译到mir_pass

    1. lite_cc_library(mir_passes
    2. SRCS
    3. fusion/fc_fuse_pass.cc
    4. ...
    5. DEPS mir_pass types context ${mir_fusers} ${subgraph_passes})

    3、使用 fc_fuse_pass

    (1) lite/api/paddle_use_passes.h使用USE_LITE_PASS宏来引入新加入的pass

    (2) 在lite/core/optimizer.h文件的Optimizer::Run()函数中添加新注册的pass

    1. class Optimizer {
    2. public:
    3. void Run(Program&& program,
    4. const std::vector<Place>& valid_places,
    5. core::KernelPickFactor kernel_pick_factor,
    6. const std::vector<std::string>& passes = {}) {
    7. ...
    8. if (passes.empty()) {
    9. RunPasses(std::vector<std::string>{
    10. {"lite_fc_fuse_pass", // the newly registered pass
    11. ...
    12. "argument_type_display_pass"}});
    13. } else {
    14. RunPasses(passes);
    15. }
    16. exec_scope_ = program.exec_scope();