Pass Infrastructure基础架构(上)

Pass Infrastructure基础架构(上)

pass代表了转换和优化的基本基础架构。本文档概述了MLIR中的pass基础结构以及如何使用它。

有关MLIR 及其核心方面(如IR结构和算子)的更多信息,请参见 MLIR规范

有关 在MLIR中进行图形重写的快速入门,请参见 MLIR Rewrites。如果转换涉及模式匹配算子DAG,那么这是一个很好的起点。

算子pass 

在MLIR中,抽象和转换的主要单元是一个 运算 。这样,pass管理器被设计为处理不同嵌套级别的算子实例。pass通行管理器的结构 和嵌套的概念将在下面详细介绍。MLIR中的所有pass均源自OperationPass,并遵守以下限制;在多线程和其它高级方案中,任何不符合都会导致有问题的行为:

  • 修改正在算子的当前之外引用或依赖的任何状态。这包括在父块中添加或删除算子,更改属性(取决于当前算子的规定)/当前算子数/结果/后继对象。
  • 修改另一个未嵌套在当前算子的状态。
    • 其它线程可能同时在这些算子上运行。
  • 检查同级算子的状态。
    • 其它线程可能正在并行修改这些算子。
    • 允许检查祖先/父项算子的状态。
  • 步长调用pass状态runOnOperation保持可变的。传递可能在许多不同的算子上运行,而不能保证执行顺序。
    • 在进行多线程处理时,特定的传递实例甚至可能不会在IR内的所有算子上执行。因此,传递不应该依赖于所有算子上的运行。
  • 保持任何全局可变状态,例如源文件中的静态变量。所有可变状态都应pass传递实例来维护。
  • 必须是可复制的
    • 流程管理器可以创建流程的多个实例,以并行处理算子。

创建算子传递时,根据使用情况,有两种不同的类型可供选择:

OperationPass:特定于算子 

在op-specific算子上传递给定算子类型明确的算子。此算子类型必须遵守pass管理器设置的限制,以执行pass。

要定义特定于算子通道,派生类必须遵守以下规定:

  • 从CRTP类继承,OperationPass并提供算子类型作为附加模板参数。
  • 覆盖void runOnOperation()虚拟方法。

一个简单的过程可能看起来像:

namespace {

/// Here we utilize the CRTP `PassWrapper` utility class to provide some

/// necessary utility hooks. This is only necessary for passes defined directly

/// in C++. Passes defined declaratively use a cleaner mechanism for providing

/// these utilities.

struct MyFunctionPass : public PassWrapper<OperationPass<FuncOp>,

                                           MyFunctionPass> {

  void runOnOperation() override {

    // Get the current FuncOp operation being operated on.

    FuncOp f = getOperation();

 

    // Walk the operations within the function.

    f.walk([](Operation *inst) {

      ....

    });

  }

};

} // end anonymous namespace

 

/// Register this pass so that it can be built via from a textual pass pipeline.

/// (Pass registration is discussed more below)

void registerMyPass() {

  PassRegistration<MyFunctionPass>(

    "flag-name-to-invoke-pass-via-mlir-opt", "Pass description here");

}

OperationPass:不可知 

op-agnostic pass,添加到通管理器的算子类型进行运算。这意味着这种类型的pass通道可以在几种不同的算子类型上进行运算。通常使用算子接口 和 特征来存储此类pass 。此类pass传递的示例包括“ 常见子表达式消除” 和“ 内联” 。

要创建算子pass传递,派生类必须遵守以下规定:

  • 从CRTP类继承OperationPass。
  • 覆盖虚拟void runOnOperation()方法。

一个简单的过程可能看起来像:

/// Here we utilize the CRTP `PassWrapper` utility class to provide some

/// necessary utility hooks. This is only necessary for passes defined directly

/// in C++. Passes defined declaratively use a cleaner mechanism for providing

/// these utilities.

struct MyOperationPass : public PassWrapper<OperationPass<>, MyOperationPass> {

  void runOnOperation() override {

    // Get the current operation being operated on.

    Operation *op = getOperation();

    ...

  }

};

从属语言 

必须先在MLIRContext中加载语言,然后才能从这些语言创建实体(算子,类型,属性等)。在开始执行多线程传递管道之前,还必须加载语言。为此,可能无法保证已从其加载的语言创建实体的过程,必须pass重写getDependentDialects() 方法并明确声明此语言列表来表达。

分析管理 

一个重要的概念,以及转换过程,都是分析。这些在概念上与转换过程相似,不同之处在于不修改特定算子的情况下计算信息。在MLIR中,分析不是pass而是pass独立式类,这些类是按需延迟计算并缓存的,以避免不必要的重新计算。MLIR中的分析必须遵循以下条件:

  • 提供一个采用Operation*的有效构造函数。
  • 不得修改给定的算子。

分析可能会提供其它钩子来控制各种行为:

  • bool isInvalidated(const AnalysisManager::PreservedAnalyses &)

给定一个保留的分析集,如果该分析确实无效,则该分析返回true。如果没有明确标记保留分析,但可以根据其它属性(例如分析集)保留(或使之无效),则可以进行更精细的失效。

查询分析 

基OperationPass类提供用于查询和保留当前正在处理的算子的分析的实用程序。

  • OperationPass自动提供以下实用程序来查询分析:
    • getAnalysis<>
      • 获得当前算子的分析,并在必要时进行构建。
    • getCachedAnalysis<>
      • 获取当前算子的分析(如果已经存在)。
    • getCachedParentAnalysis<>
      • 获取给定父算子的分析(如果存在)。
    • getCachedChildAnalysis<>
      • 对给定的子算子(如果存在)进行分析。
    • getChildAnalysis<>
      • 对给定的子算子进行分析,并在必要时进行构造。

使用上面定义的示例pass传递,看一些示例:

/// An interesting analysis.

struct MyOperationAnalysis {

  // Compute this analysis with the provided operation.

  MyOperationAnalysis(Operation *op);

};

 

void MyOperationPass::runOnOperation() {

  // Query MyOperationAnalysis for the current operation.

  MyOperationAnalysis &myAnalysis = getAnalysis<MyOperationAnalysis>();

 

  // Query a cached instance of MyOperationAnalysis for the current operation.

  // It will not be computed if it doesn't exist.

  auto optionalAnalysis = getCachedAnalysis<MyOperationAnalysis>();

  if (optionalAnalysis)

    ...

 

  // Query a cached instance of MyOperationAnalysis for the parent operation of

  // the current operation. It will not be computed if it doesn't exist.

  auto optionalAnalysis = getCachedParentAnalysis<MyOperationAnalysis>();

  if (optionalAnalysis)

    ...

}

保存分析 

在pass查询后构造的分析将被缓存,以避免不必要的计算(如果稍后再次请求)。为避免过时的分析,假定所有分析均pass传递而无效。为避免无效,过程必须专门标记已知保留的分析。

  • 所有Pass类都会自动提供以下用于保留分析的实用程序:
    • markAllAnalysesPreserved
    • markAnalysesPreserved<>

void MyOperationPass::runOnOperation() {

  // Mark all analyses as preserved. This is useful if a pass can guarantee

  // that no transformation was performed.

  markAllAnalysesPreserved();

 

  // Mark specific analyses as preserved. This is used if some transformation

  // was performed, but some analyses were either unaffected or explicitly

  // preserved.

  markAnalysesPreserved<MyAnalysis, MyAnalyses...>();

}

pass失败 

允许MLIR中的传递正常失败。如果pass的某些不变性被破坏,可能会使IR处于某种无效状态,则可能发生这种情况。如果发生这种情况,signalPassFailure方法直接向pass管理器发出故障信号。如果pass指示执行时失败,则管道中不会执行其它任何传递,并且顶级调用PassManager::run将返回failure。

void MyOperationPass::runOnOperation() {

  // Signal failure on a broken invariant.

  if (some_broken_invariant)

    return signalPassFailure();

}

pass管理器 

以上各节介绍了pass的不同类型及其不变性。本节介绍PassManager的概念,以及如何将其用于配置和计划pass管道。与pass管理相关的主要类别有两个,PassManager和和OpPassManager。 PassManager类作为顶层的入口点,并包含用于整个管道的各种配置。OpPassManager用于调度类会将以嵌套的一个特定的水平上运行。顶级 PassManager还用作OpPassManager。

OpPassManager  

AnOpPassManager本质上是要在特定类型的算子上执行的过程的集合。此算子类型必须符合以下要求:

  • 必须注册并标记 IsolatedFromAbove 。
    • 预期传递不会修改正在处理的当前算子或更高的算子。如果算子不是孤立的,则可能会无意间修改或遍历不应该执行的算子的SSA使用列表。

将pass添加到pass管理器addPass。该pass必须是采用 op-specific与相同算子类型OpPassManager的op-agnosticpass,或者是pass。

一个OpPassManager通常被cretedOpPassManagerpassnest<>方法明确嵌套内其它现有管道。此方法采用嵌套通行管理器将要算子的算子类型。在顶层,a PassManager充当OpPassManager。从这个意义上讲,嵌套对应 于 IR区域内的 结构嵌套 。

例如,以下content.mlir:

module {

  spv.module "Logical" "GLSL450" {

    func @foo() {

      ...

    }

  }

}

具有以下嵌套结构:

`module`

  `spv.module`

    `function`

下面是构造在上述结构上运行的管道的示例:

// Create a top-level `PassManager` class. If an operation type is not

// explicitly specific, the default is the builtin `module` operation.

PassManager pm(ctx);

// Note: We could also create the above `PassManager` this way.

PassManager pm(ctx, /*operationName=*/"module");

 

// Add a pass on the top-level module operation.

pm.addPass(std::make_unique<MyModulePass>());

 

// Nest a pass manager that operates on `spirv.module` operations nested

// directly under the top-level module.

OpPassManager &nestedModulePM = pm.nest<spirv::ModuleOp>();

nestedModulePM.addPass(std::make_unique<MySPIRVModulePass>());

 

// Nest a pass manager that operates on functions within the nested SPIRV

// module.

OpPassManager &nestedFunctionPM = nestedModulePM.nest<FuncOp>();

nestedFunctionPM.addPass(std::make_unique<MyFunctionPass>());

 

// Run the pass manager on the top-level module.

ModuleOp m = ...;

if (failed(pm.run(m)))

    ... // One of the passes signaled a failure.

上面的过程管理器包含以下管道结构:

OpPassManager<ModuleOp>

  MyModulePass

  OpPassManager<spirv::ModuleOp>

    MySPIRVModulePass

    OpPassManager<FuncOp>

      MyFunctionPass

然后,这些管道一次运行一次。这意味着,例如,给定FuncOp上的一系列连续传递,它将在第一个函数上全部执行,然后在第二个函数上全部执行,依此类推,直到整个程序都pass传递为止。这提供了几个好处:

  • 这改善了编译器的缓存行为,因为它一次只连接一个功能函数,而不遍历整个程序。
  • pass减少需要调度的作业数量以及提高每个作业的效率,这提高了多线程性能。整个函数管道可以在每个函数上异步运行。

动态pass管道 

在某些情况下,在另一个遍历中运行一个遍历管道可能是有用的,以允许基于正在运行的当前算子的某些不变性进行配置或过滤。例如, Inliner Pass 可能希望在进行内联时,运行过程内pass简化,以产生更好的成本模型,并提供更好的内联。为了实现这一点,pass过程可以OpPassManagerpassLogicalResult Pass::runPipeline(OpPassManager &, Operation *)方法对正在执行的当前算子或嵌套在当前算子内的任何算子进行任意运算。与顶级PassManager::run方法的结果类似,此方法返回动态管道是成功还是失败。下面是一个简单的示例:

void MyModulePass::runOnOperation() {

  ModuleOp module = getOperation();

  if (hasSomeSpecificProperty(module)) {

    OpPassManager dynamicPM("module");

    ...; // Build the dynamic pipeline.

    if (failed(runPipeline(dynamicPM, module)))

      return signalPassFailure();

  }

}

注意:尽管上面在runOnOperation方法中构造了动态管道 ,但这不是必需的,并且应尽可能缓存管道,因为OpPassManager可以安全地复制该类。

每当pass管道以嵌套方式运行时,即当无法与主传递管道的其余部分一起静态调度嵌套管道时,都应使用本节中描述的机制。更具体地说,PassManager通常不应在内构造a Pass。使用runPipeline还可以确保将所有分析, 设备 和其它与过程管理器相关的组件与正在执行的动态管道集成在一起。

实例特定的pass选项 

MLIR提供了一种内置的机制,用于指定配置其行为的选项。这些选项在遍历构造时针对遍历的每个实例独立地进行解析。使用Option<>和 ListOption<>类定义选项,并遵循 LLVM命令行 标志定义规则。参见以下示例:

struct MyPass ... {

  /// Make sure that we have a valid default constructor and copy constructor to

  /// ensure that the options are initialized properly.

  MyPass() = default;

  MyPass(const MyPass& pass) {}

 

  /// Any parameters after the description are forwarded to llvm::cl::list and

  /// llvm::cl::opt respectively.

  Option<int> exampleOption{*this, "flag-name", llvm::cl::desc("...")};

  ListOption<int> exampleListOption{*this, "list-flag-name",

                                    llvm::cl::desc("...")};

};

对于pass管道,PassPipelineRegistration模板采用其它模板参数作为可选的Option结构定义。该结构应继承mlir::PassPipelineOptions并包含所需的管道选项。使用PassPipelineRegistration时,构造函数现在使用带有签名的函数,该函数void (OpPassManager &pm, const MyPipelineOptions&) 应构造来自选项的传递,并将其传递给pm:

struct MyPipelineOptions : public PassPipelineOptions {

  // The structure of these options is the same as those for pass options.

  Option<int> exampleOption{*this, "flag-name", llvm::cl::desc("...")};

  ListOption<int> exampleListOption{*this, "list-flag-name",

                                    llvm::cl::desc("...")};

};

 

void registerMyPasses() {

  PassPipelineRegistration<MyPipelineOptions>(

    "example-pipeline", "Run an example pipeline.",

    [](OpPassManager &pm, const MyPipelineOptions &pipelineOptions) {

      // Initialize the pass manager.

    });

}

传递统计信息 

统计信息是跟踪编译器正在执行的算子以及各种转换的有效性的一种方式。通常,查看特定转换对特定输入有什么影响以及触发频率有多有用。传递统计信息特定于每个传递实例,从而可以查看在传递管道内特定位置放置特定转换的效果。例如,帮助回答诸如“如果我再次在这里运行CSE会怎样?”之类的问题。

可以使用'Pass :: Statistic'类将统计信息添加到过程中。此类采用构造函数自变量:父传递,名称和描述。此类的作用类似于算子无符号整数,并且可以相应地增加和更新。这些统计信息依赖于相同的基础结构 llvm::Statistic ,因此具有相似的使用约束。收集的统计信息可以pass管理器以 编程方式pass 转储 PassManager::enableStatistics;或pass-pass-statistics和 -pass-statistics-display在命令行上。

一个例子如下所示:

struct MyPass ... {

  /// Make sure that we have a valid default constructor and copy constructor to

  /// ensure that the options are initialized properly.

  MyPass() = default;

  MyPass(const MyPass& pass) {}

 

  /// Define the statistic to track during the execution of MyPass.

  Statistic exampleStat{this, "exampleStat", "An example statistic"};

 

  void runOnOperation() {

    ...

 

    // Update the statistic after some invariant was hit.

    ++exampleStat;

 

    ...

  }

};

收集的统计信息可以汇总为两种类型的视图:

管道视图,模拟了pass管理器的结构,这是默认视图:

$ mlir-opt -pass-pipeline='func(my-pass,my-pass)' foo.mlir -pass-statistics

 

===-------------------------------------------------------------------------===

                         ... Pass statistics report ...

===-------------------------------------------------------------------------===

'func' Pipeline

  MyPass

    (S) 15 exampleStat - An example statistic

  VerifierPass

  MyPass

    (S)  6 exampleStat - An example statistic

  VerifierPass

VerifierPass

列表视图汇总了特定遍历所有实例的统计信息:

$ mlir-opt -pass-pipeline='func(my-pass, my-pass)' foo.mlir -pass-statistics -pass-statistics-display=list

 

===-------------------------------------------------------------------------===

                         ... Pass statistics report ...

===-------------------------------------------------------------------------===

MyPass

  (S) 21 exampleStat - An example statistic

 

posted @ 2020-12-25 17:38  吴建明wujianming  阅读(773)  评论(0编辑  收藏  举报