从模型到芯片:数据流、ISA 与 AI 编译器¶
AI 编译器的核心任务是把“对整个 Tensor 做什么”逐步变成“哪些处理单元,在什么时间,从哪里取哪一块数据,执行什么指令”。这条链路连接模型语义与芯片微架构。
端到端编译路径¶
flowchart TB
MODEL["PyTorch / JAX / ONNX<br/>模型与动态图"] --> GRAPH["Graph IR<br/>算子、Shape、控制流"]
GRAPH --> GOP["图级优化<br/>常量折叠 / 融合 / Layout / 量化"]
GOP --> TIR["Tensor / Loop IR<br/>迭代域、归约、Buffer"]
TIR --> SCH["Schedule / Mapping<br/>Tile / Fuse / Vectorize / Parallel"]
SCH --> KIR["Kernel / Target IR<br/>线程、共享内存、矩阵指令"]
KIR --> ISA["ISA / Binary<br/>PTX → SASS,或 NPU 命令流"]
ISA --> RT["Runtime<br/>内存、Stream、Launch、同步"]
RT --> HW["Microarchitecture<br/>Core / SRAM / Cache / NoC / HBM"]
HW -. 规格与代价模型 .-> SCH
HW -. Profile 反馈 .-> GOP 每次 Lowering 都在减少抽象自由度:高层只描述等价计算,低层逐渐固定 Layout、循环顺序、Tile 大小、并行层次、存储空间与目标指令。
编译器各层分别优化什么¶
| 层 | 仍然看得见的语义 | 典型变换 | 主要约束 |
|---|---|---|---|
| Graph IR | Conv、Attention、LayerNorm 等算子 | 融合、常量折叠、算子分解、量化插入 | 数值等价、动态 Shape、算子覆盖 |
| Tensor/Loop IR | 迭代域、Reduction、Buffer | Loop Interchange、Tiling、Vectorize | 依赖、复用、并行粒度 |
| Kernel IR | Thread/Warp/Block、地址空间 | Shared Memory Staging、Pipeline、同步 | 寄存器、Shared Memory、occupancy |
| Target ISA | 标量/向量/矩阵、Load/Store | 指令选择、寄存器分配、指令调度 | ISA 语义、发射与流水线资源 |
| Runtime | Kernel 与 Buffer 的依赖图 | 内存复用、异步执行、Graph Capture | 生命周期、设备拓扑、同步开销 |
融合不是越多越好
融合可以减少中间 Tensor 和 Kernel Launch,但也可能增加寄存器压力、拉长 Live Range、限制并行度或失去高性能库实现。是否融合必须回到目标形状和微架构资源验证。
几个主流抽象的位置¶
flowchart LR
FRAME[模型框架] --> MLIR[MLIR Dialects<br/>多层可扩展 IR]
FRAME --> TVM[TVM Relax<br/>高层图 IR]
TVM --> TENSORIR[TVM TensorIR<br/>张量程序与调度]
MLIR --> LLVM[LLVM IR / Backend]
TENSORIR --> LLVM
FRAME --> TRITON[Triton<br/>Blocked Kernel Program]
TRITON --> GPU[GPU Target]
LLVM --> CPU[CPU / Target] - MLIR 是构建多层 IR 与 Dialect/Pass 的基础设施,不是单一“AI 编译器产品”。它允许保留领域语义后再逐步 Lowering。
- TVM 用 Relax 表达高层图,用 TensorIR 表达和调度低层张量程序,再面向 LLVM、CUDA 等 Target 生成可执行模块。
- Triton 让开发者用 Python 写 Block/Tile 级 GPU 程序,编译器负责进一步映射到线程、内存与目标 GPU 指令。
- LLVM 擅长较低层的通用优化、指令选择和代码生成;高层 Tensor 语义通常在进入 LLVM 前处理。
这些系统并非简单替代关系。实际栈常把多个 IR 与 Backend 串联起来,关键在于每层是否保存了下一步优化所需的信息。
动态 Shape 为什么难¶
静态 Shape 下,编译器可以针对固定的 M/N/K 搜索 Tile、预分配 Buffer、选择专用 kernel。动态 Shape 会扩大决策空间:
- 一个 Tile 对大 Shape 高效,对小 Shape 可能浪费线程;
- 融合后的 Buffer 和寄存器压力随 Shape 改变;
- 向量化、Tensor Core 对齐和 Padding 条件不再恒定;
- JIT 特化能恢复性能,却带来编译延迟与缓存管理。
常见策略是 Shape Guard + 多版本特化 + 通用回退路径,而不是幻想一个 kernel 对所有形状都最优。
建立软硬件契约¶
为一颗新加速器设计编译器接口时,至少定义:
| 契约 | 需要明确的内容 |
|---|---|
| 算子/指令 | 支持的数据类型、Shape、累加精度、舍入/饱和语义 |
| 存储 | 地址空间、对齐、Bank、DMA 粒度、一致性与容量 |
| 并行 | Grid/Tile/队列层次、同步范围、原子与 Barrier |
| 运行时 | Buffer 生命周期、命令提交、事件、错误和设备发现 |
| 代价模型 | 延迟、吞吐、带宽、启动成本和资源占用的估计接口 |
| 可验证性 | IR verifier、数值对照、反汇编、profile counter |
编译结果如何验收¶
- 数值正确:按数据类型与近似策略设置容差,并覆盖边界 Shape。
- IR 可解释:保存关键 Pass 前后的 IR,确认预期融合、Layout 和量化确实发生。
- 代码可确认:检查生成 kernel/汇编是否使用目标矩阵指令和访存路径。
- 资源可解释:记录寄存器、Shared Memory、occupancy 与中间 Buffer。
- 端到端有效:把编译时间、Warmup、数据转换和 Runtime 开销计入,而不只报热点 kernel。