Files
2026-08-20 17:20:09 +08:00

216 lines
9.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 阶段 1:MLIR 速查(对应规划阶段 1)
> 2026-08-14。面向「已有 TVM 经验、看过 IREE pipeline」的 MLIR 速查。
> 重点是 dialect 概念 + 各 dialect 职责,配合阶段 2 的 pipeline dump 一起看。
## 1. MLIR 的核心思想
MLIR 不是一个固定 IR,而是一个 **IR 框架**:它定义了一套通用的 IR 结构(Operation/Block/Region/Type),
然后让你**自己定义 dialect**。每个 dialect 定义自己的 op、类型、属性。
- **builtin**:最核心的部分(module、基础类型)
- **dialect**:一组相关的 op/type/attr 的集合(如 `arith`、`linalg`、`flow`)
- **op**:具体操作(如 `arith.addf`、`linalg.matmul`)
IREE 的每一层都是一个 dialect:Flow、Stream、HAL、VM 都是自定义的 MLIR dialect。
## 2. IR 结构核心概念
| 概念 | 是什么 | 类比 TVM |
|---|---|---|
| **Operation (op)** | 基本单位:名字 + operands + results + attributes + regions | IR 节点 / expr |
| **Value** | SSA 值,只能赋值一次;是 op 的 result 或 block 的 argument | 变量/临时值 |
| **Block** | 顺序执行的一串 op,可带 block argument,以 terminator 结尾 | 基本块 |
| **Region** | 一个或多个 block 的列表,支持嵌套(实现控制流) | 子图/作用域 |
| **Type** | 值的类型:`i32` `f32` `tensor<2x4xf32>` `memref<...>` `vector<...>` | dtype |
| **Attribute** | 编译期常量/元数据:`arith.constant 5 : i32` 里的 5,或 op 的 `{...}` | 常量/annotations |
| **Symbol** | 命名实体(函数名、全局名),通过符号引用 | 函数名 |
### Op 的通用语法(重点,读 IR 必会)
```mlir
%result = linalg.matmul ins(%lhs, %rhs : tensor<2x4xf32>, tensor<4x2xf32>)
outs(%init : tensor<2x2xf32>) -> tensor<2x2xf32>
```
- `%result` = 结果 SSA 值;`-> tensor<2x2xf32>` = 结果类型
- `ins` / `outs` = 输入 / 输出操作数(linalg 特有的命名,其他 op 直接用位置参数)
- `(%lhs, %rhs : type1, type2)` = 操作数及其类型
## 3. 什么是 Dialect?
**Dialect = 一组相关的 op、类型、属性的集合**,定义在同一个命名空间下。例如:
- `arith.addf` 是 `arith` dialect 里的 op,名字用 `arith.` 前缀
- `linalg.matmul` 是 `linalg` dialect 里的 op
- `flow.dispatch.region` 是 IREE 的 `flow` dialect 里的 op
为什么要分 dialect?因为**不同抽象层次需要不同的"语言"**:
- 高层用 `linalg`(表达"这是一个矩阵乘"的语义)
- 低层用 `llvm`(表达"这是 fmuladd + 循环 + 指针")
- 图级用 `flow`、异步用 `stream`、设备用 `hal`
编译过程就是**从高层 dialect 逐层 lowering 到低层 dialect**(这正是阶段 2 看到的 7 个阶段)。
## 4. 基础 dialect(IR 的积木)
### `func` —— 函数
```mlir
func.func @matmul(%arg0: tensor<2x4xf32>) -> tensor<2x2xf32> {
%0 = func.call @helper(%arg0) : (tensor<2x4xf32>) -> tensor<2x2xf32>
func.return %0 : tensor<2x2xf32>
}
```
- 定义函数 `func.func`、调用 `func.call`、返回 `func.return`
### `arith` —— 算术/比较/转换
```mlir
%0 = arith.constant 5 : i32
%1 = arith.addf %a, %b : f32 // 浮点加
%2 = arith.addi %x, %y : i32 // 整数加
%3 = arith.cmpf ogt, %a, %b : f32 // 浮点比较
%4 = arith.mulf %a, %b : f32 // 浮点乘
```
- 标量算术的"标准库",几乎所有计算最终都落到这里
### `scf` —— 结构化控制流(for / if / while)
```mlir
%0 = scf.for %i = %lb to %ub step %step iter_args(%acc = %init) -> (tensor<...>) {
%1 = arith.addi %i, %acc : i32
scf.yield %1 : i32
}
%2 = scf.if %cond -> (f32) {
scf.yield %a : f32
} else {
scf.yield %b : f32
}
```
- "结构化"= 带明确的循环语义,便于分析和变换(IREE tiling 产生的就是 `scf.for`)
### `cf` —— 非结构化控制流(底层分支)
```mlir
cf.br ^bb1(%0 : i32) // 无条件跳转
cf.cond_br %cond, ^bb1(%0 : i32), ^bb2 // 条件跳转
```
- 最低层的控制流,`scf.for` 最终会 lower 成 `cf.br` 循环
### `affine` —— 仿射循环与访存
```mlir
%0 = affine.for %i = 0 to 10 {
%1 = affine.load %memref[%i, %i + 1] : memref<10x10xf32>
}
```
- 循环边界和下标必须是仿射表达式(`i`、`i+1`、`2*i+3`),可做 polyhedral 分析
- IREE 历史上有用,现在更多用 scf + linalg
### `index` 类型
- `index` 是"可移植的整数",循环变量、下标、shape 都用它
- 编译到具体目标时再决定是 i32 还是 i64
## 5. 数据/计算 dialect(IREE codegen 的核心)
### `tensor` —— 不可变张量(值语义)
```mlir
%0 = tensor.empty() : tensor<2x2xf32> // 建空张量
%1 = tensor.extract_slice %t[0, 0][2, 2][1, 1] : tensor<4x4xf32> to tensor<2x2xf32>
%2 = tensor.insert_slice %s into %t[0, 0][2, 2][1, 1] : tensor<2x2xf32> into tensor<4x4xf32>
%3 = tensor.extract %t[0, 0] : tensor<2x2xf32> // 取标量
```
- **值语义**:不可变,像纯函数式。编译期的高层抽象
### `memref` —— 内存缓冲区(可变的 buffer)
```mlir
%0 = memref.alloc() : memref<2x2xf32>
%1 = memref.load %0[%i, %j] : memref<2x2xf32> // 读
memref.store %v, %0[%i, %j] : memref<2x2xf32> // 写
```
- 可变,带 layout/stride,对应"真实内存"。**bufferize 后 tensor 变 memref**
### `vector` —— 固定大小 SIMD 向量
```mlir
%0 = vector.load %memref[%i, %j] : memref<2x2xf32>, vector<4xf32>
%1 = vector.transfer_read %t[0, 0], %pad : tensor<4x4xf32>, vector<4xf32>
```
- 固定大小的向量,用于表达 SIMD 指令(IREE 向量化后产生 `vector` op)
### `linalg` —— 结构化计算(**IREE 的心跳**)
```mlir
// 命名 op:语义明确的高层计算
%0 = linalg.matmul ins(%lhs, %rhs : tensor<2x4xf32>, tensor<4x2xf32>)
outs(%init : tensor<2x2xf32>) -> tensor<2x2xf32>
%1 = linalg.fill ins(%cst : f32) outs(%empty : tensor<2x2xf32>) -> tensor<2x2xf32>
// generic op:自定义的"结构化计算",用 region 定义元素计算 + 迭代空间
%2 = linalg.generic {
indexing_maps = [...],
iterator_types = ["parallel", "parallel", "reduction"]
} ins(%a : tensor<...>) outs(%b : tensor<...>) {
^bb0(%in: f32, %out: f32):
%sum = arith.addf %in, %out : f32
linalg.yield %sum : f32
}
```
- **关键思想**:linalg 把"计算"和"数据如何存/遍历"分离。
它只声明"这是矩阵乘"或"元素级计算 + 迭代空间",具体 tiling/向量化由后续 pass 做。
这就是 IREE codegen 的起点(对应 TVM 的 TE 层,但声明式更强)。
## 6. 易混淆概念对照(重点)
| 对比 | 区别 |
|---|---|
| `tensor` vs `memref` | 值语义(不可变)vs 内存(可变)。编译期用 tensor,bufferize 后变 memref |
| `scf.for` vs `affine.for` | 结构化(可标并行/归约)vs 仿射(可 polyhedral 分析) |
| `arith` vs `math` | 基本算术(add/mul/cmp)vs 数学函数(`math.sqrt`、`math.exp`、`math.absf`) |
| `tensor.empty` vs `memref.alloc` | 抽象空张量(值)vs 真实内存分配 |
## 7. Pass 基础设施(看懂 pipeline 的关键)
**Pass** = 遍历 IR 做变换的基本单位。IREE 那 731 次 pass 就是一个个变换。
两种主要机制(深入各层源码必懂):
### (1) Pattern Rewriting(局部重写)
声明式:写"匹配 pattern → 替换成新 op"的规则,引擎(如 `applyPatternsAndFoldGreedily`)自动遍历应用。
适合局部的、等价的化简。
### (2) Dialect Conversion(跨 dialect 转换)
三件套:`ConversionTarget`(哪些 op 合法/非法)+ `TypeConverter`(类型怎么换)+
patterns(非法 op 怎么重写成合法 op)。
适合把整个 dialect 换成另一个(如 Flow→Stream、tensor→memref)。
> 你在 pipeline 里看到的 `ConvertToStreamPass`、`ConvertToLLVMPass`、`IREEComprehensiveBufferizePass`
> 都是 Dialect Conversion;`CanonicalizerPass` 是 Pattern Rewriting。
### 常用观察工具
```bash
iree-compile --mlir-print-ir-after-all in.mlir -o out.vmfb 2> dump.txt # 每个 pass 后 dump
iree-compile --mlir-print-ir-after=ConvertToStreamPass --mlir-print-ir-module-scope in.mlir -o out.vmfb 2>&1 # 只看某个 pass
```
## 8. 与 TVM 0.6 对照
| MLIR | TVM 0.6 |
|---|---|
| Operation | IR node / expr |
| Pass | pass / IR visitor |
| Dialect Conversion | 逐层 lowering(Relay→TE→TIR) |
| Pattern Rewriting | pass 里的 rewrite 规则 |
| linalg.matmul/generic | TE 的 compute + 描述 |
| scf.for + vector | TIR 里的 loop + vectorize |
| memref | 缓冲区/物理内存 |
## 9. IREE 自己的 dialect(概览,详细见各层笔记)
| dialect | 职责 | 关键 op(在 dump 里见过) |
|---|---|---|
| `util` | 通用工具(函数/全局包装) | `util.func`、`util.global`、`util.call`、`util.return` |
| `flow` | 图级优化 + dispatch 划分 | `flow.dispatch.region`、`flow.dispatch`、`flow.executable` |
| `stream` | 异步执行模型 | `stream.executable`、`stream.binding`、`stream.tensor.import`、`stream.async.execute`、`stream.timepoint.await` |
| `hal` | 设备抽象 | `hal.executable`、`hal.buffer_view`、`hal.command_buffer`、`hal.tensor.import/export` |
| `vm` | VM 字节码 | `vm.module`、`vm.func`、`vm.call` |
| `iree_tensor_ext` | dispatch 张量 + 屏障 | `iree_tensor_ext.dispatch.tensor.load/store`、`iree_tensor_ext.compute_barrier.start/end` |
| `iree_linalg_ext` | 扩展 linalg op(softmax/attention 等) | 复杂算子的结构化 op |
> 这些 dialect 的源码在 `src/iree/compiler/src/iree/compiler/Dialect/<X>/`,
> 每个 dialect 目录下有 `*.td`(op 定义,TableGen 格式)和 `*.cpp`(实现)。