想象你是一家跨境物流公司的仓库主管。仓库里堆满了用中文写的发货单——那是 PyTorch 程序员们写好的模型。这些包裹要发往世界各地:德国家庭(GPU)、日式公寓(TPU)、北欧高效住宅(NPU)。如果直接把中文发货单交给各国快递员,他们只会一脸茫然。于是你建了一座国际物流枢纽——MLIR。包裹进门时,先由“中文登记台”(torch 方言)原样编号,再到“标准纸箱分拣台”(linalg 方言)拆成统一规格,最后经“货柜装运台”(llvm 方言)转运到各国配送站。整个过程中,包裹里的“货物”纹丝不动,但包装一路标准化——这就是 MLIR 在 PyTorch 中扮演的角色。
理解 MLIR 如何接入 PyTorch,是踏入 AI 编译器领域的钥匙。掌握 dialect、lowering 与 Torch-MLIR 工具链,你就能读懂图像化优化、后端代码生成这些底层魔法,为工作流带来数量级的性能提升。
可视化演示
展示MLIR多层中间表示如何在PyTorch中实现从高层IR到底层代码的逐级 lowering 优化。
本节配有交互动效(进入视口后自动加载,约 3KB)。
IR逐级Lowering Torch → Linalg → LLVM 多层抽象逐步具体化 高层语义 → 底层优化
一、编译器困境
传统编译器世界像一座“巴别塔工地”:每个框架(PyTorch、TensorFlow)说自己的语言,每个硬件(GPU、TPU、NPU)也只懂自己的规矩。老牌快递员 LLVM 师傅很能干,但只认“门牌号级别”的底层指令,你递给他一张写着“给常青藤小区的张女士送一箱苹果”的顶层单据,他得花半天时间翻地图册——把一句高层语义翻译成成百上千条低层指令,才能动身。这套流程太脆弱,换一个小区(新硬件),地图册就要重写。
业界其实早就意识到这个问题。编译器内部存在一条“中间表示”(IR)链条:框架层有计算图,优化层有指令选择器,最底层是寄存器传输级 IR。问题在于,以往的编译器只选择其中一层作为“唯一真相”,导致高层优化无法触达底层细节,底层指令又看不懂高层语义。这就是业界常说的“语义鸿沟”。
MLIR(Multi-Level Intermediate Representation)正是 Google 在 2019 年开源的多级 IR 框架。它的核心主张是:不再逼所有人讲同一种话,而是让一个 IR 框架同时容纳多种抽象层级,每个层级是一个“方言”(dialect),方言之间通过“降级”(lowering)逐层衔接。
有了物流枢纽,问题从“每个快递员背一整本地图”变成“包裹按不同方言登记后统一装箱”。配送站不再需要理解中文发货单,只需认识标准箱上的条形码——这是 MLIR 对编译世界最大的解放。
- torch 方言 IR物流枢纽的“中文登记台”,保留 PyTorch 算子原貌(如卷积、注意力),方便做框架级优化。
- linalg 方言 IR标准纸箱分拣台,把高层算子拆成统一的矩阵/循环结构,让所有硬件都能读懂。
- tensor 方言 IR形状登记台,管理张量的形状、数据类型与布局,不涉及具体内存。
- llvm 方言 IR全球货柜台,把计算映射为 LLVM 能处理的底层指令,贴近机器码。
二、Dialect设计
MLIR 枢纽里,每个分拣台都说一种“方言”(dialect)。方言不是神秘的外语,它只是一组自带命名空间的算子(operation)和类型(type)的集合。torch 方言像“原样登记台”:包裹不动,只贴一张标签“conv2d,ResNet 第 2 层”;linalg 方言是“标准纸箱分拣台”:拆开包裹,把里面的矩阵乘、广播加按统一规格摆放;tensor 方言是“形状登记台”:只记录纸箱的长宽高和材质,不管里面的苹果品种。
这种分层设计的妙处在于,每一层都只关心自己能回答的问题。框架优化阶段,你在 torch 方言里看到的是“这是一次注意力计算”,可以放心做算子融合;硬件生成阶段,你在 linalg 方言里看到的是“这是一个四重循环嵌套的矩阵乘”,可以安心做 tiling 和向量化。
以 PyTorch 中最常见的全连接层为例,它对应 torch.aten.addmm 算子(矩阵乘加偏置)。在 MLIR 中,这个高层算子会逐步“拆箱”为 linalg 方言的通用结构化运算:
方言体系还有一个重要的隐藏好处——可扩展性。任何人可以自定义一个方言,比如 NPU 厂商可以发明 my_npu 方言来描述自家硬件特有的“脉动阵列指令”,然后写一个降级 pass 把 linalg 接到 my_npu 上。这相当于给物流枢纽加了“专用分拣通道”。
- 高层算子 addmmPyTorch 里全连接层计算“输入 × 权重 + 偏置”的复合操作,对应物流中心的“原箱包裹”。
- 参数 bias、A、Bbias 是偏置向量,A 是输入矩阵,B 是权重矩阵,三者就像包裹里的三样不同货物。
- 降级符号箭头从高位方言指向低位方言,表示“拆箱换标准盒”,而非等价代换——语义等价,表示形式改变。
- linalg.genericlinalg 方言的通用运算描述符,用循环加索引表达式表达任意张量计算,是标准箱的“通用规格”。
三、Torch-MLIR集成
物流中心光有分拣台不够,还得让 PyTorch 仓库的包裹源源不断送进来。Torch-MLIR 项目就是连接 PyTorch 与 MLIR 的“传送带”。它最初由 Google 与 LLVM 社区共同推动,目标是让 PyTorch 模型能进入 MLIR 生态,享受统一的优化与后端代码生成能力。
传送带的第一段是“前端转换”:PyTorch 模型先经过 torch.jit.trace 或 torch.jit.script,变成一份静态化的 TorchScript 计算图——相当于仓库文员把散装的发货需求整理成标准发货单。Torch-MLIR 的导入组件再读取这张发货单,把它翻译成 torch 方言 IR,正式进入 MLIR 枢纽。
第二段是“降级路径”:torch 方言里的算子不可能永远停留在高层,它们要经过一系列 pass(每个 pass 像一道工序)逐步下降。有的 pass 做算子分解,把复杂算子拆成基础算子;有的 pass 做形状推断,补全张量的维度信息;有的 pass 把算子一对多改写为 linalg 方言。最终,torch 方言被“消化”成 linalg + tensor 方言的组合。
在实际项目中,Torch-MLIR 还接出多条支线:可以降到 TOSA 方言对接嵌入式设备,也可以直接输出 LLVM IR 跑 CPU,甚至能把 ONNX 模型也当作输入。这条传送带的验收标准很简单——任何 PyTorch 模型,只要能被 TorchScript 捕获,就有机会进入 MLIR 枢纽完成降级。
- TorchScriptPyTorch 模型的静态化表示,类似仓库文员的标准化发货单,去掉了 Python 动态语法。
- Torch-MLIR 转换传送带的对接插头,把 TorchScript 图翻译成 MLIR 中的 torch 方言。
- 重写 pass 链一道道拆箱工序,每个 pass 只做一件小事(分解、推断、改写),合起来完成整体降级。
- linalg ⊕ tensor⊕ 表示两者组合出现,linalg 负责运算结构,tensor 负责形状数据,类似“标准箱 + 标签条”的搭档关系。
四、异构加速
传送带的末端是各国配送站。GPU 配送站说:“我们收标准箱,然后拆成 CUDA 核函数”;TPU 配送站说:“我们只认脉动阵列,请把矩阵乘按我们的规则排布”;NPU 配送站说:“我们有达芬奇架构,需要专用指令格式”。如果没有 MLIR 枢纽,PyTorch 仓库要和每个配送站一条一条地签“全栈合作协议”,工作量大得惊人。
有了 MLIR,情况完全不同。所有 PyTorch 模型都已被拆成 linalg “标准箱”,新硬件厂商只需实现一个“拆箱器”——把 linalg/tensor 方言映射到自家指令的降级 pass 和代码生成器。这就像汉堡王只需要和“纸箱供应商”签协议,而不必和全世界每一家农场谈苹果规格。
这个价值可以量化。假设有 F 个深度学习框架(PyTorch、TensorFlow、JAX……)和 H 种硬件(GPU、TPU、NPU、CPU……),传统模式下每对接一对(框架,硬件)都要从图优化做到指令调度,适配成本是乘积关系:
拆箱器成本也不该被低估:linalg 到 CUDA、linalg 到 TPU 指令,每一份都是硬核工程。但相比“全栈自研”,量级已从“每个配送站学一门新外语”变成“学会读统一条形码”。这也是为什么几乎所有 AI 芯片公司都在 MLIR 上押注——新芯片上市时间可以压缩一个数量级。
- F 框架数仓库里的发货方数量,每个框架都有自己的计算图和算子体系。
- H 硬件数配送站数量,每种硬件有自己的指令集和内存模型。
- 乘法关系传统模式下每对(框架,硬件)都要做全栈优化,份工作。
- 加法关系接入 MLIR 后,每个框架只需对接 MLIR 一次(前端),每个硬件只需对接一次(后端),共 F+H 份工作——各省一份力,枢纽统一协调。
五、MLIR与ONNX
提到模型转换,很多人还会想起另一家老牌驿站——ONNX。它像联邦快递:不做拆箱重装,只提供一张标准面单(模型结构描述),保证各家公司都认识面单上的标签。你把 PyTorch 模型的“面单”贴上 ONNX 格式,TensorRT、ONNX Runtime 这些下游工具就能接手推理。
ONNX 与 MLIR 的定位差异,本质是“交换格式”与“编译基础设施”的区别。ONNX 回答的是“模型长什么样”(算子图、形状、权重),它自己不做优化,也不生成代码;MLIR 回答的是“模型怎么被编译”(方言、pass、降级、代码生成)。打个比方:ONNX 是快递驿站的标准面单系统,MLIR 是整个重新分拣、合并装载、路线优化的物流枢纽。
两者也并非水火不容。onnx-mlir 项目专门负责把 ONNX 模型导入 MLIR,此时 ONNX 变成 MLIR 的一个“前端方言”。工业界常见做法是:跨框架互通选 ONNX,深度优化选 MLIR,两者配合形成“面单登记 → 枢纽分拣”的完整链路。
选择策略其实很简单:如果你的目标是“把 PyTorch 模型导出给另一个推理引擎”,ONNX 是最短路径;如果你的目标是“为自家芯片写编译器、做算子融合和指令调度”,MLIR 是正确起点。两者不是新旧取代关系,而是面单与枢纽的分工合作。
- ONNX一种开放的模型交换格式,类似联邦快递的标准面单,记录算子和网络结构。
- 模型交换格式ONNX 的核心职责是“互通有无”,它不配送(不生成代码),只贴标签(描述模型)。
- MLIR一个可扩展的编译框架,像物流枢纽一样包含方言、pass、降级流程等全套基础设施。
- 编译基础设施MLIR 不仅要“看懂包裹”,还要拆箱、重组、优化线路、最终送达(生成机器码)。
六、编译实践
讲了这么多,不如亲手看一次“传送带分拣”。最轻量的方式是装好 torch-mlir 后,把一个 PyTorch 模型编译成 MLIR IR,观察它如何在各方言间流转。这个过程就像给物流中心装了一排监控摄像头:每经过一个分拣台,你都能看到一张“状态快照”(IR 文本)。
第一个知识点是“图捕获”。PyTorch 的 eager 模式是“边执行边建图”,无法编译优化;必须先通过 torch.jit.trace 或 torch.fx 把模型“冻结”成静态计算图。这是传送带的起点:把散装发货需求整理成标准发货单。第二步是调用 torch_mlir.compile(),指定输出方言(如 linalg),得到 IR 文本。最后用 MLIR 自带的工具逐层查看降级结果。
如果你暂时不想装 Torch-MLIR,也可以先用 torch.jit.trace 配合 graph 属性观察 TorchScript 图——它是 torch 方言的“前身”,很多概念是相通的。理解整条链路后,再回看 torch.compile 也不迷路:它默认走的是 Dynamo 捕获 + Inductor/Triton 后端,和 MLIR 是两条独立的技术路线,但它们的“物流理念”惊人相似。
完整跑通这条链后,你会产生一种“开箱验货”的踏实感:原来大模型部署时的算子融合、内存重排、指令调度,都是一个接一个的 pass 在 IR 上做文章。MLIR 提供的不是某一个答案,而是一座让所有答案都能落地的枢纽。
- 图捕获把 Python 函数变成静态计算图 G,类似文员把口头需求写成发货单,去掉 if/else 等动态分支。
- 计算图 G模型中所有张量运算的依赖关系图,每个节点是一个算子,边是数据流动方向。
- 降级到 MLIR把计算图翻译成 MLIR 的方言 IR,从 torch 方言开始逐级下降到目标方言。
- 代码生成最后一个分拣台,把低层方言翻译成硬件指令,装车出发。
AI 应用场景
大模型推理引擎优化
阶段:推理优化 / 代码生成
解决的问题:大模型(如 Llama、Qwen)在 eager 模式下逐算子执行,CPU/GPU 利用率低。传统做法是为每个模型手写 CUDA 优化,工作量大且难以复用。AI 编译器将模型捕获为 IR 后,利用 MLIR 做算子融合、量化、内存规划,再生成高效后端代码。
具体案例:某推理引擎团队用 Torch-MLIR 把 PyTorch 导出的 Llama-3 模型转换为 linalg 方言,配合自定义后端 pass 做 KV Cache 内存重排与 GEMV 算子融合,在国产 NPU 上达到手写内核 85% 的吞吐量,而开发周期从 3 个月压缩到 2 周。
新芯片 SDK 快速落地
阶段:编译器后端开发
解决的问题:新 NPU 芯片上市最大的瓶颈是“生态适配”——跑不通 PyTorch 模型就等于没有客户。全栈重写编译器不现实,用 MLIR 只需实现 linalg → 自家 dialect 的降级 pass 和代码生成器,即可复用 PyTorch 全部模型。
具体案例:一家 AI 芯片初创公司以 Torch-MLIR 为底座构建工具链,先支持 ResNet-50 等经典模型,再逐步扩展注意力算子。借助 MLIR 已有的 tiling、vectorization 等通用 pass,团队仅 5 人便在两个月内让三种主流大模型跑通 demo,极大提升了融资与客户验证速度。
多框架统一编译平台
阶段:模型优化中间层
解决的问题:大型云厂商内部同时存在 PyTorch、JAX、TensorFlow 模型,每套框架都做一遍优化既浪费人力又难统一策略。MLIR 可作为“统一枢纽”,各框架导入为不同方言,再统一做融合、量化、内存规划,最后分发给 CPU/GPU/TPU 后端。
具体案例:某云厂商推理平台引入 MLIR 后,将 PyTorch(经 Torch-MLIR)与 JAX(经 StableHLO)导入同一优化管线,共享 30 余个优化 pass,模型服务部署时间从“每模型数天”缩减到“数小时”,硬件利用率提升约 40%。
Python 演示
import torchimport torch.nn as nn class MyNet(nn.Module): def __init__(self): super().__init__() self.fc = nn.Linear(8, 4) def forward(self, x): return torch.relu(self.fc(x)) def lower_to_linalg(node): """把 aten 高层算子映射为类 linalg 的通用运算描述。""" mapping = { 'aten::linear': 'linalg.generic (matmul + broadcast add)', 'aten::relu': 'linalg.generic (max(x, 0))', 'aten::addmm': 'linalg.generic (matmul)', } return mapping.get(node.kind(), 'linalg.generic (unknown)') def main(): model = MyNet() model.eval() example = torch.randn(2, 8) # 1) 图捕获:把动态 PyTorch 模型固化为 TorchScript 静态图 traced = torch.jit.trace(model, example) print("=== TorchScript 图(类似 torch 方言的'前身') ===") print(traced.graph) print("\n=== 模拟 MLIR 降级:高层算子 -> linalg 方言 ===") for node in traced.graph.nodes(): op_kind = node.kind() linalg_op = lower_to_linalg(node) print(f"{op_kind:30s} -> {linalg_op}") if __name__ == "__main__": main()
traced = torch.jit.trace(model, example)用示例输入捕获模型执行路径,生成静态 TorchScript 图,这是进入编译链的“发货单”。print(traced.graph)查看 TorchScript 图文本,其中以aten::开头的节点就对应 torch 方言里的高层算子。mapping = { 'aten::linear': ... }这是一张“分拣对照表”,把高层算子映射为低层通用运算,相当于方言降级规则。for node in traced.graph.nodes()遍历计算图的每个节点,每个节点都是未来 IR 中的一个算子。lower_to_linalg(node)模拟 lowering pass:把aten::linear这类高层算子“拆箱”成 linalg 风格的矩阵运算描述。
常见误区
误区听说 MLIR 是“编译器框架”,于是安装后丢给它一段 Python 或 C++ 代码,期待它吐出一个可执行文件。发现它只是一个库,顿时觉得被忽悠了。
正解MLIR 不是成品编译器,而是“编译器的工作台”——就像物流枢纽本身不送货,它提供分拣台(方言)、作业流程(pass)和调度规则(pass 管理器)。真正可用的编译器需要你(或社区)用 MLIR 拼装出来。想一步到位体验编译效果,应使用 Torch-MLIR 或 IREE 这类基于 MLIR 的完整工具链。
为何常见:MLIR 的名字里有“编译器”三个字,加上 Google 光环,容易被误当作开箱即用的产品级编译器。
误区看到 torch.compile 能做图优化,就想当然认为 PyTorch 官方已经用 MLIR 重写了底层编译逻辑,学 MLIR 就是学 torch.compile 的实现。
正解torch.compile 默认路径是 Dynamo 捕获计算图 + Inductor(基于 Triton)生成 GPU 代码,全程不依赖 MLIR。Torch-MLIR 是独立的社区项目,把 TorchScript/FX 图导入 MLIR 生态。两者是两套并行的“物流设备”:一条用 Triton 直送,一条进 MLIR 枢纽中转。
为何常见:两者都做“编译 + 优化”,名称里都带 torch/编译字样,不深入源码很难区分。
误区觉得 ONNX 是 MLIR 出现前的“老格式”,既然 MLIR 更强大,ONNX 应该被淘汰,学了也是白学。
正解ONNX 做模型交换,MLIR 做编译优化,两者层级不同、解决问题不同。ONNX 负责“面单互通”,MLIR 负责“拆箱重组”。事实上 onnx-mlir 项目还把 ONNX 当作 MLIR 的前端接口,二者可以串联使用,是合作关系而非替代关系。
为何常见:两者都出现在“模型转换”话题里,初学者容易把它们归类为同一类工具。
误区看到 torch 方言覆盖了成百上千个 aten 算子,以为任何 PyTorch 模型丢进去都能自动变快。
正解torch 方言只是“登记台”,后续降级才见真章。许多算子缺少 linalg 重写规则,形状推断不完整,自定义算子更是直接卡住;即便降级成功,性能也可能因额外转换而退化,需要针对算子融合和内存布局做大量调优。登记在册 ≠ 优化完毕。
为何常见:方言名列表长、覆盖面广,容易让人误以为编译器后端“无所不能”。
面试题
Q1. MLIR 中 "dialect"(方言)最准确的理解是?
- A.一种方言对应一类硬件
- B.同一命名空间下的算子集合← 正确答案
- C.一个可直接运行的编译器
- D.一种神经网络模型结构
答案:B。方言(dialect)像物流中心的一个分拣台,把同一抽象层级的算子和类型收拢在统一命名空间下(如 torch.、linalg.)。A 错在方言不绑定硬件,C 错在 MLIR 是框架而非成品编译器,D 错在方言与模型结构无关。
Q2. torch 方言的主要作用是?
- A.直接生成 GPU 机器码
- B.保留 PyTorch 高层算子语义← 正确答案
- C.定义 Python 语言语法树
- D.实现训练时的数据并行
答案:B。torch 方言是物流枢纽的“原样登记台”,保存 torch.aten.* 等高层算子及其语义,方便做框架级优化。它不生成机器码(那是 llvm 方言和后端的活),不定义 Python 语法,也不负责数据并行。
Q3. MLIR 相比传统单层 IR 的核心创新是?
- A.支持多种抽象层级共存← 正确答案
- B.采用纯文本化的格式
- C.允许直接编写 Python
- D.自动生成单元测试用例
答案:A。MLIR 的核心主张是“多级 IR”——一个框架内同时容纳 torch、linalg、tensor、llvm 等不同抽象层级,用降级链衔接。纯文本格式、Python 支持、测试生成都不是它的创新点,只是附带能力。
Q4. TorchScript 图进入 Torch-MLIR 后首选落入哪个方言?
- A.linalg 方言
- B.torch 方言← 正确答案
- C.llvm 方言
- D.spirv 方言
答案:B。TorchScript 经 Torch-MLIR 导入器转换后,会先进入 torch 方言,相当于包裹刚进物流枢纽先到“原样登记台”。后续的分解、推断、重写 pass 才会把它逐步降到 linalg、tensor 乃至 llvm 方言。
Q5. 关于 MLIR 中 pass 的说法,正确的有?
- A.pass 可以分析并改写 IR← 正确答案
- B.pass 链实现方言间的逐级降低← 正确答案
- C.pass 只能在 GPU 上执行
- D.pass 的作用是增加算子数量
答案:AB。pass 是编译优化/转换的基本单元,像物流中的一道道工序:有的做形状推断(分析),有的做算子分解(改写),串联成 pass 链完成整体降级。C 错在 pass 与硬件无关,D 错在 pass 目的是优化而非增量化算子。
Q6. 下列哪些属于 MLIR 生态中的常见方言?
- A.torch 方言← 正确答案
- B.linalg 方言← 正确答案
- C.tensor 方言← 正确答案
- D.numpy 方言
答案:ABC。torch、linalg、tensor 都是 MLIR 中真实存在的方言,分别对应高层语义、结构化运算、张量数据抽象。numpy 是 Python 数值库,不是 MLIR 方言,属于干扰项。
Q7. torch.compile 默认的代码生成后端是?
- A.Torch-MLIR 路径
- B.Inductor 与 Triton← 正确答案
- C.TVM 编译路径
- D.XLA 编译路径
答案:B。torch.compile 默认走 Dynamo 捕获 + Inductor(基于 Triton 语言)生成 GPU 内核,与 MLIR 无关。Torch-MLIR 是独立项目,TVM 与 XLA 是另外的编译技术栈,都不在 torch.compile 默认链路中。
Q8. linalg.generic 这类算子的核心特征是?
- A.仅支持单精度浮点类型
- B.用循环表达通用运算结构← 正确答案
- C.只能运行在 GPU 设备上
- D.强制要求内存连续布局
答案:B。linalg.generic 用“循环 + 索引表达式”描述张量计算,任何硬件后端都能读懂它——这正是“标准纸箱”的意义。它支持多种数据类型、不限定设备后端,也不强制特定内存布局,因此 A、C、D 均不准确。
Q9. 关于 ONNX 与 MLIR 的关系,说法正确的有?
- A.二者定位不同可以协同配合← 正确答案
- B.ONNX 模型能转成 MLIR 继续优化← 正确答案
- C.MLIR 将彻底取代 ONNX 成为新标准
- D.两者本质相同只是实现语言不同
答案:AB。ONNX 是模型交换格式(面单),MLIR 是编译基础设施(枢纽),定位互补;onnx-mlir 项目实现了 ONNX → MLIR 的导入。C 错在“彻底取代”过度断言,D 错在两者解决的问题完全不同。
Q10. 设框架数为 F、硬件数为 H,接入 MLIR 后总适配成本的核心变化在于?
- A.将全栈适配降为线性成本← 正确答案
- B.将线性成本升为全栈适配
- C.完全消除后端实现工作
- D.只降低框架端的接入成本
答案:A。MLIR 把每对(框架,硬件)都要做的全栈适配,变成“框架只对接 MLIR、硬件只对接 MLIR”的 F+H 线性成本。B 方向说反了;C 错在后端仍需实现降级与代码生成,只是不再需要理解每个框架;D 忽略硬件端同样受益。