# 阶段 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//`, > 每个 dialect 目录下有 `*.td`(op 定义,TableGen 格式)和 `*.cpp`(实现)。