MLIR-Bufferization缓存

MLIR-Bufferization缓存

https://mlir.llvm.org/docs/Bufferization/

概述

MLIR 中的缓存是转换具有tensor张量语义的操作的过程到带有memref语义的操作。MLIR 提供了一个一次通过整个程序(一次性缓存)缓存的基础设施。此基础结构缓存实现可缓存操作接口BufferizableOpInterface 的所有操作都可以缓存。

MLIR 具有围绕方言转换构建的较旧的缓存基础结构。大多数方言转换缓存模式已迁移到单次缓存,但有些函数边界缓存等功能仍取决于方言转换及其类型转换器。新项目应使用一次性缓存,因为基于方言转换的缓存最终将被弃用。此外,单次缓存能够以更少的内存实现更好的缓存分配和缓存区副本。本文主要介绍一次性缓存,但也描述了如何将基于转换的缓存转换为一次性缓存从方言逐步迁移项目。

什么是一次性缓存?

一次性缓存是一种新的张量缓存pass,专为destination-passing style IR 设计, 并具有积极的就地缓存。

一次性缓存是:

  • 整体式:单个 MLIR 通道完成整个工作,而 MLIR 中的先前缓存被拆分为驻留在不同的方言。在一次性缓存中,BufferizableOpInterface实现分布在不同的方言中。
  • 一次分析全功能。In-place bufferization就地缓存决策是通过分析张量上的 SSA 使用定义链生成的。OP操作接口实现不仅提供从张量操作到 memref 的重写逻辑 ops,也是用于有关 OP 的缓存/内存语义的信息查询的一次性缓存区分析的帮助程序方法。
  • 可通过操作接口进行扩展:实现BufferizableOpInterface的所有操作都可以缓存。
  • 2-通过:缓存在内部分为两个步骤:首先,分析整个 IR 并做出缓存决策。然后,缓存 (重写)IR。该分析可以访问确切的 SSA 使用定义信息。以增量方式构建别名和等效集,并且不依赖于从预分配内存进行posteriori-alias后验别名分析。
  • Greedy贪婪:操作逐一分析,当场决定是否必须复制张量操作数。Heuristics启发式方法确定 分析顺序。
  • 模块化:当前的一次性分析可以替代不同的分析。分析结果特别AnalysisState::isInPlace通过缓存方式查询。任何派生类AnalysisState 实现少量虚函数可以用作自定义分析。甚至可以运行一次性缓存没有任何分析(AlwaysCopyAnalysisState),在这种情况下单发缓存的行为与基于转换的旧方言完全相同缓存(即,在写入每个缓存区之前复制它)。

为了降低复杂性,应在其他转换之后运行一次性缓存,通常作为降低 Memref 操作之前的最后步骤之一。许多变换在张量上更容易;例如,首先在张量上,tile/fuse/平铺/融合... ,然后缓存剩余的 IR。

从架构的角度来看,一次性缓存由BufferizableOpInterface(及其实现)和张量 SSA 值分析组成,该分析决定缓存区是可以直接使用还是必须复制。op 接口的 [bufferize] 方法检查分析结果和 将张量操作重写为 Memref 操作。

缓存的目标

每种缓存技术的高级目标是: 1. 尽可能少地使用尽可能的内存。2. 复制尽可能少的内存。

这意味着在可能的情况下重用已经分配的缓存区,通过类似的方法,将到算法复杂的缓存到寄存器分配。

根据具体用例,可能会有额外的缓存要求。如果缓存区的内容计算成本很高,则可能会在重新计算和计算一次并复制之间进行权衡。相反,甚至可能无法在某些运行时分配新的缓存区架构。

Destination-Passing Style风格

缓存是一个算法复杂的问题。给定一个带有张量的操作结果,缓存必须选择一个 Memref 缓存区,其中的结果可以是数据处理。分配一个全新的缓存区总是安全的,但是这样的缓存策略对于高性能代码生成来说是不可接受的。什么时候选择已经存在的缓存区,我们必须注意不要意外覆盖程序后面仍需要的数据。

为了简化这个问题,一次性缓存是为destination-passing风格的操作而设计的。对于每个张量结果,这样的运算都有一个张量操作数,缓存区可用于在没有操作的情况下存储无冲突操作的结果。我们将这样的张量操作数称为目的地

例如,请考虑以下操作:%0 = tensor.insert %cst into %t[%idx] : tensor<?xf32>

%t是此示例中的目标。为%0结果选择缓存区时,单次缓存区仅考虑两个选项:

  1. buffer(%0) = buffer(%t).
  1. buffer(%0)是一个新分配的缓存区。

同一函数中可能还有其他可能使用的缓存区buffer(%0),但一次性缓存区不考虑这些来保持缓存简单。一次性缓存可以扩展以考虑此类缓存器,将来可以实现更好的缓存质量。

非目标传递样式的张量操作始终缓存为内存分配。例如:

%0 = tensor.generate %sz {

^bb0(%i : index):

  %cst = arith.constant 0.0 : f32

  tensor.yield %cst : f32

} : tensor<?xf32>

结果没有“目的地”,所以缓存分配新的缓存区。这可以通过选择一个操作来避免,例如,可以用目标表达相同的计算 (“out”)张量:tensor.generatelinalg.generic

#map = affine_map<(i) -> (i)>

%0 = linalg.generic {indexing_maps = [#map], iterator_types = ["parallel"]}

                    outs(%t : tensor<?xf32>) {

  ^bb0(%arg0 : f32):

    %cst = arith.constant 0.0 : f32

    linalg.yield %cst : f32

} -> tensor<?xf32>

乍一看,上述op内容似乎不是很有用,因为输出张量完全覆盖。为什么要先用操作数传递张量?例如,用于覆盖张量切片:linalg.generic%t%t

%t = tensor.extract_slice %s [%idx] [%sz] [1] : tensor<?xf32> to tensor<?xf32>

%0 = linalg.generic ... outs(%t) { ... } -> tensor<?xf32>

%1 = tensor.insert_slice %0 into %s [%idx] [%sz] [1]

    : tensor<?xf32> into tensor<?xf32>

上面的示例缓存为memref.subview,后跟 “linalg.generic on memrefs”,覆盖子视图的内存。tensor.insert_slice缓存为无操作(在没有 RaW 冲突的情况下) 如后续%s阅读)。

通过对 SSA 使用定义链的分析来检测 RaW 冲突(详细信息 后来)。如果存在单个 SSA 使用定义链,则一次性缓存效果最佳, 其中张量运算的结果是下一个张量的“目的地”操作数 操作,例如:

%0 = "my_dialect.some_op"(%t) : (tensor<?xf32>) -> (tensor<?xf32>)

%1 = "my_dialect.another_op"(%0) : (tensor<?xf32>) -> (tensor<?xf32>)

%2 = "my_dialect.yet_another_op"(%1) : (tensor<?xf32>) -> (tensor<?xf32>)

如果 SSA use-def 链在某个时候分裂,则可能会插入缓存区副本, 例如:

%0 = "my_dialect.some_op"(%t) : (tensor<?xf32>) -> (tensor<?xf32>)

%1 = "my_dialect.another_op"(%0) : (tensor<?xf32>) -> (tensor<?xf32>)

%2 = "my_dialect.yet_another_op"(%0) : (tensor<?xf32>) -> (tensor<?xf32>)

一次性缓存区具有调试标志 (test-analysis-only print-conflicts), 打印分析结果并向用户解释为什么插入缓存副本。

使用一次性缓存

MLIR 提供一次性传递缓存,该缓存执行分析并使用张量语义缓存所有操作 BufferizableOpInterface实现。出于模块化原因,这些操作接口实现通常是存在于方言中的外部模型 “转换”构建单元。(外部模型是实现不同的构建单元中操作的机制接口)。用户有责任确保在运行 One-Shot 之前注册所有需要的外部模型缓存。

By default, One-Shot Bufferize fails when it encounters an op with tensor semantics (i.e., tensor result or tensor operand) that is not bufferizable (i.e., does not implement BufferizableOpInterface). This can be avoided with allow-unknown-ops. In that case, One-Shot Bufferize inserts to_memref/to_tensor ops around the bufferization boundary. These ops are named versions of unrealized_conversion_cast. Note that One-Shot Bufferize’s analysis can currently not analyze these ops, so input IR with such ops may fail bufferization. Therefore, running One-Shot Bufferize multiple times in a sequence is also not supported at the moment.

默认情况下,单次缓存在遇到带有不可缓存的语义张量的操作时失败(即不实施BufferizableOpInterface张量结果或张量操作数)。这可以避免allow-unknown-ops。在这种情况下,一次性缓存缓存区边界周围插入to_memref/to_tensor。这些操作是unrealized_conversion_cast的命名版本。请注意,一次性缓存的 分析目前无法分析这些操作,因此具有此类操作的输入 IR 可能会错失缓存。因此,One-Shot Bufferize目前也不支持序列。

一次性缓存可以配置为仅缓存一组dialect-filter方言。用于逐步从基于方言转换的缓存到一次性缓存。一次性缓存在这种情况下必须首先运行,因为基于方言转换的缓存生成单次缓存无法分析的to_tensor/to_memref

One-Shot Bufferize 也可以使用bufferization::runOneShotBufferize.以编程方式调用。 或者,bufferization::bufferizeOp跳过分析并在每次缓存区写入时插入一个副本,就像基于方言转换的缓存。

缓存区释放

一次性缓存区解除分配的所有缓存区。这是在与委派此作业的基于方言转换的缓存形成对比 到-buffer-deallocation的pass。默认情况下,单次缓存区拒绝新分配的缓存区的 IR 从块返回。此类 IR 将错失缓存。

当操作的结果不是在destination-passing目的地传递样式就返回。例如:

 

%0 = scf.if %c -> (tensor<?xf32>) {

  %1 = tensor.generate ... -> tensor<?xf32>

  scf.yield %1 : tensor<?xf32>

} else {

  scf.yield %another_tensor : tensor<?xf32>

}

在“其他”分支中scf.yield是可以的,但是在“然后”中scf.yield分支将被拒绝。scf.yieldscf.yield

可能返回缓存区分配的另一种情况是缓存区副本,由于 RaW 冲突,必须插入。例如:

%0 = scf.if %c -> (tensor<?xf32>) {

  %1 = tensor.insert %cst into %another_tensor[%idx] : tensor<?xf32>

  "my_dialect.reading_tensor_op"(%another_tensor) : (tensor<?xf32>) -> ()

  ...

  scf.yield %1 : tensor<?xf32>

} else {

  scf.yield %yet_another_tensor : tensor<?xf32>

}

在上面的例子中,buffer(%another_tensor) (with %cst inserted)的缓存区副本是从“then”分支生成的。

在这两个示例中,缓存区在块内分配,然后从块中生成。此类缓存区的释放很棘手,目前尚未实现以有效的方式。因此,必须显式使用一次性缓存allow-return-allocs配置为支持此类 IR。

When running with allow-return-allocs, One-Shot Bufferize may introduce allocations that cannot be deallocated by One-Shot Bufferize yet. For that reason, -buffer-deallocation must be run after One-Shot Bufferize. This buffer deallocation pass resolves yields of newly allocated buffers with copies. E.g., the scf.if example above would bufferize to IR similar to the following:

 

运行时,一次性缓存可能会引入 尚无法通过一次性缓存解除分配的分配。为此 原因,-buffer-deallocation必须在一次性缓存后运行。此缓存区解除分配传递可解析带有副本的新分配缓存区的产量。例如, 上面的示例scf.if将缓存到 IR,类似于以下内容:

%0 = scf.if %c -> (memref<?xf32>) {

  %1 = memref.alloc(...) : memref<?xf32>

  ...

  scf.yield %1 : memref<?xf32>

} else {

  %2 = memref.alloc(...) : memref<?xf32>

  memref.copy %another_memref, %2

  scf.yield %2 : memref<?xf32>

}

在缓存 IR 中,两个分支都返回新分配的缓存区,因此,不管采取哪个如果分支。在这两种情况下,生成的缓存区%0必须在之后的某个时间点释放(除非%0 从其块返回/产生)。

注意:从函数返回的缓存区分配不会解除分配,甚至没有-buffer-deallocation。调用方有责任解除分配缓存区。将来,这可以通过分配实现自动化。 提升(跨功能边界)或引用计数。

一次性缓存可以配置为泄漏所有内存,而不会生成任何内存create-deallocs=0缓存解除分配。用于具有自己的解除分配缓存区方法的旧代码兼容。

内存布局

一次性缓存从上到下缓存操作。这在所有操作是可缓存的。但是,当遇到不可缓存的allow-unknown-ops张量时,一次性缓存必须插入to_memref缓存边界并确定 memref 类型。默认情况下,单次缓存选择最动态的memref类型wrt。布局图。例如:

%0 = "my_dialect.unbufferizable_op(%t) : (tensor<?x?xf32>) -> (tensor<?x?xf32>)

%1 = tensor.extract %0[%idx1, %idx2] : tensor<?xf32>

缓存上述 IR 时,一次性缓存插入 to_memref动态偏移和步幅:

%0 = "my_dialect.unbufferizable_op(%t) : (tensor<?x?xf32>) -> (tensor<?x?xf32>)

%0_m = bufferization.to_memref %0 : memref<?x?xf32, strided<[?, ?], offset: ?>>

%1 = memref.load %0_m[%idx1, %idx2] : memref<?x?xf32, strided<[?, ?], offset: ?>>

所有%0用户都有完全动态的布局图。这可确保缓存 IR 与未来的unbufferizable_op缓存(可能由另一个pass缓存)组成得很好,无论 未来的缓存。如果运算结果被缓存到具有更简单的memref类型(例如,identity layout map布局图),我们希望规范化模式将清理不必要的动态布局图。(其中一些 规范化模式可能尚未实现。

单次缓存尝试在缓存一个操作时推断最精确的 memref 类型。如果整个 IR 是可缓存的,不必求助于保守地使用完全动态的布局图。在这种情况下,我们也没有以依赖规范化模式来清理缓存的 IR。

Note: There are some bufferizable ops for which a percise layout map cannot be inferred. E.g., a tensor.cast from a tensor<*xf32> to a tensor<?x?xf32> must be bufferized to a memref.cast with a memref type that has a fully dynamic layout map.

注意:有一些可缓存的操作,而 Percise 布局映射不能推断。例如,afrom tensor<*xf32>tensor.cast必须缓存为 memref.cast类型,该类型具有 动态布局图。

单次缓存有一个unknown-type-conversion选项来控制,无法推断出精确布局时生成布局图:

  • fully-dynamic-layout-map使用完全动态布局图,是默认布局图行为。当 IR 被部分缓存时,这很好。
  • identity-layout-map使用静态标识布局映射。此选项可以是对于无法使用布局映射处理 Memref 类型的旧代码很有用。 请注意,当使用不强制转换兼容的 memref 类型折叠 to_tensor/to_memref 时,此设置可能会导致额外的缓存区副本。

注意:该unknown-type-conversion选项不会影响function signatures的布局图 函数签名。有一个单独的function-signature-type-conversion选项用于控制函数参数和函数结果的布局图。

扩展单次缓存

如果BufferizableOpInterface自定义操作已实现,则可以对其进行缓存。用户必须至少实现以下接口方法。

  • bufferizesToMemoryRead:返回true给定张量的缓存区读取操作数。
  • bufferizesToMemoryWrite:返回true给定张量的缓存区 写入操作数(如果就地缓存)。
  • getAliasingOpResult:返回可能共享同一缓存区的 OpResults 作为给定的操作数。此接口方法描述为 OpOperand-to-OpResult映射 wrt. destination-passing风格。
  • bufferRelation:返回BufferRelation::Equivalent给定的运算结果 与缓存后的别名操作数memref完全相同(在 就地缓存的情况)。否则,(例如,重叠但不重叠必然是完全相同的memrefs),BufferRelation::None应该返回。将来将添加其他缓存区关系,但BufferRelation::None始终是安全的。
  • BufferRelation::EquivalentBufferRelation::NoneBufferRelation::None
  • bufferize:使用给定的重写器重写操作。用bufferization::replaceOpWithBufferizedValues更换操作。

为了更好地了解接口方法,我们邀请用户采取 查看 MLIR 中的现有实现,例如,实施 tensor.inserttensor.extract

调试缓存区副本

为了更好地理解为什么一次性缓存区引入了缓存区复制,用户可以用test-analysis-only print-conflicts运行pass。每 然后用一个属性注释张量运算,该属性具有每个布尔值张量 OpOperand。true意味着 OpOperand 就地缓存。false意味着 OpOperand 缓存异位,缓存区副本将是 插入。

插入缓存区副本的原因有两个。

  1. 由于 RaW 冲突,就地缓存是不安全的。即, 仍然需要覆盖的数据。
  2. 缓存区不可写。例如,memref.global缓存区是 永远不会修改arith.constant OP 的结果。

在第一种情况下,print-conflicts表示(“读取”、“冲突写入”、“最后写入”)元组的冲突。

了解 SSA 使用定义链分析

为了更好地理解SSA Use-Def链分析和RaW 冲突检测算法,我们邀请感兴趣的用户阅读设计文档并观看相应的ODM演讲幻灯片)。 可用于在单次传递中缓存程序,只要每个操作

从基于方言转换的缓存迁移

基于方言转换的缓存和一次性缓存都会在缓存边界(allow-unknown-ops)生成to_tensor/to_memref。它们可以按顺序组合和运行。然而 一次性缓存必须首先运行,因为它无法分析这些边界操作。 若要逐步更新现有代码,指定方言可能很有用单次缓存的过滤器,以便可以逐个切换方言。

缓存函数图

一次性缓存当前不支持函数图缓存。 即,CallOpReturnOp和函数 bbArgs 是不可缓存的。用户可以 在一次性缓存后运行现有的缓存pass。CallOpReturnOp--func-bufferize

或者,用户可以尝试ModuleBufferization模块缓存, 这是一次性缓存的扩展。此缓存仍在开发,不支持任意 IR。本质上,返回一个张量不支持 from 函数,除非它等效于函数 bbArg。 在这种情况下,可以简单地删除相应的返回值 缓存。

基于方言转换的缓存

免责声明:大多数基于方言转换的缓存已迁移到一次性缓存。新用户应使用一次性缓存(带或不带分析)。以下文档仅适用于方言的基于转换的缓存现有用户。

该系统是MLIR方言转换基础设施的简单应用。与缓存相关的大部分代码是方言作者为转换操作而编写的一组普通代码ConversionPattern对操作的操作。一套约定tensor和最好的memref遵循允许这些模式跨多个模式运行的做法 独立通道(而不是需要单个巨大的原子转换通道), 这使得编译管道具有可伸缩性、健壮且易于调试。

本文档面向希望利用 MLIR 缓存的用户功能,以及想要扩展它以涵盖自己的操作的人。

注意:在阅读本文档之前,请观看 演讲“Type Conversions the Not-So-Hard-Way: MLIR’s New Bufferization Infrastructure”(幻灯片录音)。 该演讲提供了缓存基础结构的高级概述和与使用 MLIR 方言转换相关的重要概念细节基础设施。

缓存在编译管道中的位置

缓存本身不会释放任何已分配的缓存区, 它在放置缓存区方面也没有做任何特别智能的事情 W.R.T. 控制流。因此,实际的编译管道通常包含:

  1. 缓存
  2. 缓存区优化,例如,buffer-hoisting, buffer-loop-hoisting, 及 promote-buffers-to-stack, 它们执行仅公开的优化后缓存。
  3. 最后,运行缓存区释放pass。

缓存区释放完成后,程序将相当 由于存在释放操作,因此难以转换。因此,其他 优化,如Memrefs上的Linalg Fusion,应该在该阶段之前完成。

缓存过程的一般结构

缓存包括运行多个部分缓存pass, 随后是一次最终缓存pass

每种方言通常有一个部分缓存传递(尽管其他可以细分)。例如,对于方言,通常X-bufferize成为pass,知道如何缓存该方言中的所有操作。 通过运行 X-bufferize pass程序中的每种方言,所有操作 在程序中进行增量缓存。

 

 

部分缓存passes创建只有部分操作的程序缓存。这些passes将创建具体化(有时也称为 “casts”)在 theandtype 之间转换,这允许 在已缓存的操作和尚未缓存的操作之间架起桥接 缓存。tensormemref

完成缓存将完成缓存过程,并保证 程序中没有剩余的张量。这涉及消除具体化。该通道提供了一个最小通道仅消除具体化,并在任何未缓存的操作时发出错误 存在于程序中。

但是,最终缓存可能不仅仅执行更多操作 消除物质化。通过添加模式(就像部分缓存一样),可以同时完成缓存pass缓存操作并消除具体化。这有许多缺点 在谈话中讨论,通常应避免。

作为一个具体的例子,我们将从mlir-npcomp参考后端查看缓存管道 (代码)。 代码略有简化和注释,现转载如下:

  // Partial bufferization passes.

  pm.addPass(createTensorConstantBufferizePass());

  pm.addNestedPass<func::FuncOp>(createTCPBufferizePass()); // Bufferizes the downstream `tcp` dialect.

  pm.addNestedPass<func::FuncOp>(createSCFBufferizePass());

  pm.addNestedPass<func::FuncOp>(createLinalgBufferizePass());

  pm.addNestedPass<func::FuncOp>(createTensorBufferizePass());

  pm.addPass(createFuncBufferizePass());

 

  // Finalizing bufferization pass.

  pm.addNestedPass<func::FuncOp>(createFinalizingBufferizePass());

首先查看部分缓存传递,我们看到有一个pass序列(在函数上并行运行)。这些功能passes由 arith-bufferize 与 func-bufferize 括起来,它们是模块passes(从而序列化并行编译过程)。这两个passes必须是模块passes,因为它们对顶级模块进行更改。

大部分缓存工作由函数传递完成。其中大多数 传递作为上游 MLIR 分布的一部分提供并缓存 它们各自的方言(例如缓存方言)。 pass 是一个例外 – 它是一个部分缓存传递 用于缓存下游方言,并与所有方言完美契合 其他passes提供上游。

最后一次是最终缓存传递。参考资料mlir-npcomp后端已安排所有操作都通过部分缓存进行缓存,因此 上游 finalizing-bufferize可以用作最终确定 缓存传递。这在出现问题时提供了出色的诊断使用缓存过程,例如由于未由任何操作处理的操作 模式。

如何编写部分缓存传递

部分缓存传递的协定是操作(或种类)的子集 的操作,可由转化目标自定义)得到缓存。

部分缓存传递只是使用方言转换ConversionPatterns框架应用于 atotype 转换的传递。

为了描述如何编写这样的pass,我们将通过一个例子, pass (代码测试) 缓存方言。请注意,这些pass已被替换成基于A的实现,所以我们必须查看旧版本的代码。

 

pass中的大部分代码将是一组转换模式,具有简单的例子是BufferizeCastOp)。

class BufferizeCastOp : public OpConversionPattern<tensor::CastOp> {

public:

  using OpConversionPattern::OpConversionPattern;

  LogicalResult

  matchAndRewrite(tensor::CastOp op, OpAdaptor adaptor,

                  ConversionPatternRewriter &rewriter) const override {

    auto resultType = getTypeConverter()->convertType(op.getType());

    rewriter.replaceOpWithNewOp<MemRefCastOp>(op, resultType, adaptor.source());

    return success();

  }

};

有关如何编写这些模式的更多详细信息,请参阅讨论

pass本身非常小,并且遵循任何方言转换pass的基本模式。

void mlir::populateTensorBufferizePatterns(

    BufferizeTypeConverter &typeConverter, RewritePatternSet &patterns) {

  patterns.add<BufferizeCastOp, BufferizeExtractOp>(typeConverter,

                                                    patterns.getContext());

}

 

struct TensorBufferizePass : public TensorBufferizeBase<TensorBufferizePass> {

  void runOnOperation() override {

    auto *context = &getContext();

    BufferizeTypeConverter typeConverter;

    RewritePatternSet patterns(context);

    ConversionTarget target(*context);

 

    populateTensorBufferizePatterns(typeConverter, patterns);

    target.addIllegalOp<tensor::CastOp, tensor::ExtractOp>();

    target.addLegalDialect<func::FuncDialect>();

 

    if (failed(

            applyPartialConversion(getOperation(), target, std::move(patterns))))

      signalPassFailure();

  }

};

该passes具有方言转换passes的所有标志,该passes确实键入转换:TypeConverter, a RewritePatternSet, 及 ConversionTarget,。请注意,函数是分开的,以便高级用户可以使用 模式独立,如有必要(例如组合多组 将模式转换为单个转化调用,以提高效果)。

MLIR 缓存基础设施提供的一个方便的实用程序是,它预加载了必要的转换tensor memref和之间的物质化。

在这种情况下,BufferizationOpsDialect被标记为合法,因此插入的t bufferization.to_tensor 及bufferization.to_memref ops。 自动由方言转换框架作为具体化,是合法的。有一个助手(代码) 这通常对此有所帮助。

其他部分缓存示例

  • scf-bufferize ( 代码测试)
    • 缓存方言中的scf操作。
    • 这是如何缓存实现RegionBranchOpInterface的操作的示例(也就是说,它们使用区域来表示 控制流)。
    • 大部分工作由(代码)完成, 这是很好的评论,涵盖了如何正确转换操作 lib/Dialect/SCF/Transforms/StructuralTypeConversions.cpp ( code)包含区域。
  • func-bufferize ( 代码测试)
    • funccall, and BranchOpInterface
    • 这是如何缓存具有多块的操作的示例区域。
    • 这是未按方言拆分的passes示例细分。

如何编写最终缓存pass

最终缓存pass的合约是所有张量都消失了从程序。

编写最终缓存传递的最简单方法是根本不编写缓存传递! MLIR提供了一个通过消除了/具体化操作通过部分缓存传递插入,如果不是,则发出错误 足以从程序中删除bufferization.to_tensor / bufferization.to_memref所有张量。

当部分缓存传递已缓存所有 程序中的操作,只留下具体化。如果可能, 建议以这种方式构建传递管道,因为这具有 如果 OP 未得到缓存(由于缺少 模式,代码中的错误等),finalizing-bufferize会发出一个很好的清洁错误,并且 IR 看到的将只包含一个finalizing-bufferize无缓存操作。

但是,在当前的缓存基础结构到位之前,缓存只能作为单个最终缓存大通道完成 populate*BufferizePatterns使用多种方言的功能,同时缓存所有内容。因此,人们可能会在 下游项目以这种方式构建。不建议在 新代码。一个助手,populateEliminateBufferizeMaterializationsPatterns ( code) 可用于此类凭证以提供消除bufferization.to_tensor and bufferization.to_memref模式。

变化

  • func-bufferize已更改为部分转换passes,并且有一个finalizing-bufferize新这作为一般定稿缓存传递。
  • 大多数部分缓存传递已就此重新实现BufferizableOpInterface。新用户应改用一次性缓存 基于方言转换的缓存。

 

参考文献链接

https://mlir.llvm.org/docs/Bufferization/

 

posted @ 2022-11-06 13:30  吴建明wujianming  阅读(495)  评论(0编辑  收藏  举报