MLIR中文文档完全拆解:从方言到Pass管线的编译器学习路线
简介MLIR官方文档中文翻译项目是一套面向编译器开发者与深度学习框架工程师的中文学习资料系统覆盖MLIR核心概念、多级中间表示基础设施、方言dialects、操作attributes、类型转换、Pass管理器及代码生成等内容帮助中文用户绕过英文文档门槛从原理到实践理解MLIR的架构与用法。资源包共316个文件约1.05MB以md89个翻译文档为主辅以h/cpp头文件与源码81个、toy/MLIR示例77个等便于对照代码理解机制。已有155人学习使用。针对“如何在MLIR中高效实现类型转换”“如何用Pass管理器组合优化”等关键问题资料结合具体代码与图解13个svg给出完整说明其中cpp源码与toy示例为抽象概念提供了可运行实例td文件展示了方言扩展方式既适合入门者建立整体认知也适合研究者快速检索设计细节是学习MLIR不可多得的完整中文参考。1. 把MLIR官方文档搬成中文多级中间表示不再是黑匣子MLIR这几年在编译器圈子里几乎是绕不开的词TensorFlow、IREE、LLVM都有它的影子但真正敢说自己把官方文档啃下来的中文开发者并不多。这套中文翻译资源就是把MLIR官方文档和教程整体汉化了一遍从多级中间表示的核心概念、方言Dialect体系、操作Operation、属性Attribute、类型Type到转换Conversion流程、Pass管理器、代码生成链路全部覆盖。对想上手MLIR但被英文文档劝退的人或者已经在读原版但某些章节反复看不懂的人这份资源的价值在于给了你一条能照抄的阅读路径而不是零散的专业名词堆砌。我自己用下来最直接的感受是MLIR的英文文档本身写得已经很系统了但它的行文是给熟悉LLVM的人看的默认你懂Pass、懂IR、懂模式匹配。中文翻译版把这些断层补上了遇到术语会给出对应英文原词遇到概念会放到实际编译流程里解释。这篇文章就按我实际拆这份资源时的顺序来写先讲怎么把文档变成一条可执行的学习路径再讲方言、操作、类型这些核心概念最后落到Pass管线和代码生成顺便把最容易踩的坑都列出来。2. 中文MLIR资源怎么读先搭环境再按官方教程顺序拆拿到这套翻译资源第一件事不是从头翻PDF而是先把它当成“官方教程的中文镜像”来用。我的习惯是先用一小时把基础环境搭起来然后跟着文档里MLIR官方的Toy语言教程走一遍边读边跑。这套资源里保留了官方教程的章节结构所以我按官方推荐的顺序来拆同时也把每章的知识点对应到你机器上要敲的命令。2.1 学习路线与文件拆解官方文档的Guides目录是MLIR学习的骨架这套中文资源里也沿用了同样的结构。我按自己的阅读顺序把主要章节排成了下面这张表你可以照着顺序来每一章都需要跑对应示例而不是只看文字。阅读顺序对应官方章节核心内容实践验证方式1Getting Started环境搭建、LLVM/MLIR构建ninja check-mlir跑通回归测试2Toy Tutorial (Ch1-Ch7)方言定义、IR生成、Shape推理、Pass、Lowering逐步用mlir-opt跑Toy示例3Dialects / Operations / Types方言机制、操作语义、类型系统自定义一个最小的方言并在mlir-opt中注册4Pass InfrastructurePass管理器、模式重写、管线调用mlir-opt --canonicalize观察IR变化5Conversion转换基础、类型转换、跨方言Lowering用mlir-translate把Toy Lower到LLVM IR6Codegen代码生成、LLVM IR导出、目标对接生成.ll文件并用lli执行第一步是先把LLVM和MLIR按官方方式构建出来。常见做法是直接用llvm-project源码构建ninja会在编译期间同步生成mlir-opt和mlir-translate这两个工具后面所有章节的验证都靠它们。git clone https://github.com/llvm/llvm-project.git cd llvm-project mkdir build cd build cmake -G Ninja ../llvm \ -DLLVM_ENABLE_PROJECTSmlir \ -DLLVM_BUILD_EXAMPLESON \ -DLLVM_TARGETS_TO_BUILDhost \ -DCMAKE_BUILD_TYPERelease ninja check-mlirLLVM_ENABLE_PROJECTSmlir是让CMake把MLIR子项目编进构建系统不设这个标志的话你只有LLVM本体。LLVM_TARGETS_TO_BUILDhost指定只编本机目标架构能省大量编译时间如果后续要做交叉编译再补具体后端。check-mlir是验证构建结果的核心门槛它跑的是MLIR官方回归测试套件如果这一步报错说明工具链有问题后面所有实验都会在错误基础上打转。这一步我强烈建议不要跳过。很多人拿到中文文档后直接翻到“方言定义”那章结果因为build不完整跑示例时总会莫名报错。构建过程慢是正常的机器好的也要一小时左右这段时间正好把资源里第一章到第二章的中文内容读完构建结束后刚好开始动手。2.2 第一个能跑的示例从Toy语言到MLIR IR环境就绪后不要急着看后面复杂的Pass和Lowering。先把Toy语言编译器示例跑通这是文档主线也是中文资源里篇幅最大、注释最详细的部分。Toy是MLIR官方为了教学专门设计的极简语言麻雀虽小但五脏俱全从词法解析到IR生成、再到Pass优化和LLVM代码生成整条编译链路都能看到。cd llvm-project/mlir/examples/toy mkdir toy_build cd toy_build cmake -G Ninja .. -DMLIR_DIR$(pwd)/../../../build/lib/cmake/mlir ninja toyc-ch5 ./toyc-ch5 ../../test/Examples/Toy/transpose_transpose.toy -emitmlir这条流程会把toyc-ch5编译出来然后对官方自带的测试文件做IR生成。“transpose_transpose.toy”这个文件名对应的场景是连续两次转置在Ch5里专门用来演示冗余消除优化的效果。-emitmlir的意思是把Toy源码转成MLIR格式的IR而不是直接生成LLVM IR或机器码。跑完之后对比一下优化前后的输出能看到Pass管理器是怎么把一个冗余计算消掉的。这个过程是整套MLIR文档中最值得亲手跑的一步因为后续所有关于方言、操作、类型的内容都是在IR基础上展开的。资源的中文版本在对应章节把每一条IR指令和Toy源码的对应关系都做了逐行注释这类细节在英文原版里不会专门给你标出来翻译版的价值就在这里体现出来了。如果构建过程中遇到内存不够可以在CMake命令里加-DLLVM_PARALLEL_LINK_JOBS1把并行链接任务降下来避免内存被撑爆导致构建失败。这算是LLVM构建阶段最典型的坑。3. 方言是MLIR的灵魂从定义到方言间转换方言这个词在MLIR体系里其实有明确的技术含义不是修辞。一个方言就是一套在MLIR框架内自洽的操作、类型、属性集合它们的语义由方言自己定义。翻译成白话就是MLIR允许你在同一套基础设施里为不同抽象层次的语言各自建立一套“方言”然后通过转换把这些方言串起来最终生成目标代码。3.1 一个最小方言里有什么以Toy方言为例官方教程Ch2里定义了一个最小的Toy方言包含操作Operation、类型Type、属性Attribute三要素。这三者在MLIR里是三个独立但强关联的概念操作是IR的基本执行单元类型描述操作数的数据属性属性则存放编译期就能确定的信息。class ToyDialect : public mlir::Dialect { public: ToyDialect(mlir::MLIRContext *context) : mlir::Dialect(getDialectNamespace(), context, mlir::TypeID::getToyDialect()) { addOperations #define GET_OP_LIST #include toy/Ops.cpp.inc (); addTypes #define GET_TYPEDEF_LIST #include toy/Types.cpp.inc (); addInterfacesShapeInferenceInterface(); } };这就是一个最简方言的注册逻辑。addOperations把TableGen生成的Ops.cpp.inc文件里的操作全部注册进去addTypes同理注册类型addInterfaces挂上Shape推断接口。方言本身在MLIR体系里只是一个容器真正干活的是里面注册的这些操作和类型。理解这段代码的关键在于MLIR的方言定义入口看起来是C代码但实际操作和类型的骨架是用TableGen声明的。TableGen可以理解成一种生成代码的模板语言它让你用简洁的声明来描述操作名、操作数、结果类型、约束条件然后MLIR的构建系统会帮你生成对应的C类。官方文档把这套机制叫ODSOperation Definition Specification中文资源里翻译成“操作定义规范”。这里建议把资源里关于ODS的章节前后读两遍第一遍了解怎么声明操作第二遍结合生成的Ops.h.inc和Ops.cpp.inc看生成逻辑能把黑匣子打开。我一般会让读者先跑一条最简单的验证命令来确认方言是活的而不是停留在读代码层面cd llvm-project/build/bin echo toy.plt test.toy ./toyc-ch5 ../mlir/examples/toy/Ch5/../../test/Examples/Toy/affine-lowering.mlir -emitmlir | head -30跑这条命令是为了观察已经生成的IR长什么样输出的每一条toy.xxx操作都来源于方言注册表。如果你在输出里看到toy.func、toy.constant、toy.matmul这些操作说明方言已经被正确加载到MLIRContext里了。3.2 方言为什么是“多级”的关键理解了单一方言的构成再来看“多级中间表示”就好理解了。MLIR的多级就是指同一段程序可以在不同抽象层级的方言之间流动。最上层是Toy语言这样的领域方言中间可能是仿射方言Affine Dialect这种带循环和仿射映射的结构化方言最下层是LLVM方言直接对应LLVM IR。这就是为什么MLIR官方文档反复强调“方言间转换”的价值。一个深度学习编译器不会直接用LLVM来表达卷积而是先用Tensor方言描述张量计算再逐步Lowering到更底层的方言最终进入LLVM方言。方言层级设计得好优化就可以在合适的抽象层做这是LLVM时代难做到的。拿到这套中文资源后我建议把Toy教程Ch5到Ch7连起来读因为这三章正好演示了一个完整方言转换链Toy → Affine → 标准方言 → LLVM方言。./toyc-ch5 ../../mlir/test/Examples/Toy/affine-lowering.mlir -emitmlir-affine ./toyc-ch5 ../../mlir/test/Examples/Toy/affine-lowering.mlir -emitmlir-llvm-emitmlir-affine和-emitmlir-llvm分别输出转换到对应层级后的IR。对比两个输出你能直观看到结构化控制流如何被转换成lower-level的分支跳转结构。这一步跑通以后你才真正明白为什么MLIR把自己叫“多级中间表示”——它不是一个IR而是一族IR方言就是这些IR的组织形式。参数说明上affine-lowering.mlir这个测试文件本身就是拿来做方言Lowering演练的你不要自己造一个复杂Toy程序去跑它先用官方测试文件确认链路是通的再逐步拿自己的输入做测试。方言转换里最常见的报错是类型不匹配比如仿射方言的索引类型和标准方言的整数类型在Lowering过程中没对齐这时中文资源里对应的章节会告诉你先查类型转换接口再查操作转换模式这个排查顺序很重要。4. Pass管理器与代码生成把IR变成可执行物读完方言和转换接触到的就是Pass管理器了。MLIR官方文档的Pass基础设施部分是整个框架中最体现工程价值的一章因为优化的可组合性和复用性全靠它。4.1 Pass管线的显式组合MLIR的Pass管理与LLVM一脉相承但更强调“管线”的显式组装。一条Pass管线就是一组优化Pass按顺序运行。Pass之间可以互相依赖依赖错了IR就会出问题。MLIR提供了mlir-opt这个工具专门用于以文本形式指定并运行Pass管线。cd llvm-project/build/bin ./mlir-opt --canonicalize --cse \ ../../mlir/test/Examples/Toy/transpose_transpose.toy \ -o /tmp/opt.mlir ./mlir-opt --canonicalize \ ../../mlir/test/Transforms/cse.mlir \ -o /tmp/cse.mlir--canonicalize是MLIR的内置规范化Pass它负责把IR里的冗余结构化简比如把重复的转置操作合并掉。--cse是公共子表达式消除会清理重复计算。-o指定输出文件如果不写会打印到终端。第一次跑的时候你可能看不出IR变化量建议用--print-ir-after-all选项打印每个Pass运行后的IR片段这样能清晰看到每一个Pass动了什么。mlir-opt是学习Pass管线最好的实验台因为你可以像拼积木一样任意组合Pass而不用写任何C代码。文档里关于Pass的部分建议重点读“Pass组合与重复运行”这一节。同一个Pass在一条管线里运行两次优化效果可能完全不同这是MLIR文档里明确提到的行为原因是前一个Pass会改变IR的形态后一次运行面对的是不同的输入。不熟悉这套逻辑的人常常以为Pass写进管线就算完事实际上Pass执行顺序本身就是一种设计。4.2 从MLIR到LLVM IR再到目标代码Pass管线把IR优化到合适形态后最后一步是代码生成。MLIR定义了LLVM方言作为最底层IRmlir-translate负责把MLIR格式整体翻译成LLVM IR。这一步跑通了你就拥有了从Toy语言到机器码的完整编译链路。./mlir-translate --mlir-to-llvmir /tmp/opt.mlir -o /tmp/out.ll lli /tmp/out.ll echo function main() { return 42; } /tmp/main.toy ./toyc-ch5 /tmp/main.toy -emitmlir-llvm | mlir-translate --mlir-to-llvmir -o /tmp/main.ll--mlir-to-llvmir是mlir-translate的核心转换入口它只接受LLVM方言级别的MLIR输入。如果输入IR里还有Toy或Affine方言的操作translate会直接报错。所以正确的顺序永远是先用Pass把高层方言完全Lowering到LLVM方言再交给mlir-translate。lli是LLVM的即时执行器可以直接运行.ll格式的IR文件省去汇编和链接环节很适合验证生成的代码逻辑是否与原程序一致。参数上有一个容易被忽略的点mlir-translate只做格式翻译不做优化。所以你在-emitmlir-llvm输出里看到多少冗余计算生成的LLVM IR里就有多少。想要优化必须在mlir-opt阶段做掉。这跟很多人的直觉相反第一次踩这个坑的人会以为代码生成阶段会自动做优化其实MLIR把优化和翻译拆得干干净净这本身就是设计选择。4.3 Pass顺序敏感性怎么排查Pass管线行为不像流水线那么简单它对顺序高度敏感。我踩过的一个典型案例是把--canonicalize放到--cse后边结果IR里某些表达式被反复计算因为canonicalize在cse之前运行才能先化简再删除公共子表达式。这种问题在mlir-opt里很容易定位只要加--debug-onlymlir或者--print-ir-before-all看每个Pass前后的IR差异就行。./mlir-opt --print-ir-before-all --print-ir-after-all \ --canonicalize --cse \ ../../mlir/test/Examples/Toy/transpose_transpose.toy \ -o /dev/null 21 | less--print-ir-before-all会在每个Pass运行前打印IR--print-ir-after-all在Pass运行后打印打印文件会很大所以我把标准输出编进less里翻页查看。通过对比你能清楚看到canonicalize把IR里的冗余转置合并成什么形态cse又在此基础上消除了哪些重复计算。这套排查方法同样适用于自己写的Pass只要注册进mlir-opt就能用。这条命令顺序很重要如果先用--print-ir-after-all再用--print-ir-before-all输出的打印点顺序是乱的难以对应到具体Pass。从工程习惯来说我总会把打印选项放在Pass列表前面这样打印点的执行顺序和Pass管线顺序同步日志读起来才顺。5. 中文翻译版MLIR文档避坑指南五个最容易翻车的细节翻译资源的价值大但坑也在于“翻译”二字因为MLIR文档更新快、术语多。以下五个问题是我在看这套中文资源以及对照原版时反复遇到的每条都给出解决路径能帮你少走弯路。5.1 文档版本漂移导致命令与新版工具不匹配现象按文档里的命令跑mlir-opt提示找不到某个Pass或命令行选项。原因MLIR开发迭代很快Pass名称经常变。比如早期版本里的-affize已经被并入更通用的转换框架--convert-linalg-to-affine-loops这类长Pass名也可能在不同release里调整。中文资源如果基于旧版本翻译而你自己构建的是新版llvm-project就会出现文本描述与工具行为不一致。解决先确认你构建的LLVM/MLIR版本与文档翻译基准一致。查看方式是在build目录里找llvm-config --version同时看资源开头是否说明基于哪个commit翻译。如果版本不一致优先以mlir-opt --help输出的Pass列表为准用这个列表来更新命令中的Pass名。5.2 术语不统一造成理解错位现象同一个英文术语在不同章节被翻译成不同中文词比如“multilevel”有时译成“多级”有时译成“多层”“lowering”有时叫“降低”有时叫“下降”。原因多人协作翻译或术语表建立不完整导致的副产物。MLIR文档本身也有术语不一致问题翻译进来之后这个问题会被放大。解决阅读时建立自己的“术语-英文原词”对照表。资源里每个关键术语后面都保留了英文括号标注遇到不确定的术语强制自己回到英文术语链条里去理解不要依赖单个中文词。这种习惯一旦养成你以后看任何MLIR源码和论文都不会晕词。5.3 官方示例代码跑不通现象照抄文档里的示例编译报错或运行结果与文档描述不一致。原因文档里的代码片段往往省略了上下文比如没有包含必要的#include或者依赖某个已经废弃的接口。MLIR的TableGen定义和C接口都在持续演进旧接口可能被替换。解决遇到跑不通的代码优先去llvm-project/mlir/test目录下找对应用例。那里的测试代码经过构建系统验证一定能编译通过。记得用grep -r按操作名或Pass名搜索比如grep -r toy.matmul llvm-project/mlir/test/Examples这样搜出来的文件可以直接替换文档里的残缺示例。对于生成代码类的错误一个常见解决路径是把#include toy/Ops.cpp.inc改成相对路径确保编译器能找到目标文件。5.4 只看中文不对照原版导致理解偏差现象中文文档读得懂但换到自己工程里就不知道怎么写。原因翻译是解码再编码的过程会丢失原文里的一些逻辑线索。MLIR文档里大量内容是交叉引用的原文的一句话可能指向另一个章节的某个代码片段翻译时如果只译字面意思这些指涉关系会断裂。解决我读这套资源时一定保持中英双版对照阅读。中文版用来快速抓概念英文版用来精读关键章节。特别是ODS定义、方言注册、Pass接口这类需要写代码的知识点中英对照才能避免理解偏差。这套资源的问题不是翻译质量差而是翻译本质上有损。5.5 Pass命名和接口调整导致的代码失效现象你自己写的Pass在某次构建后突然编译不过或者运行时行为变了。原因LLVM/MLIR社区对Pass接口有过几次大调整最显著的变化是把Pass从PassWrapper迁移到PassRegistration再到引入OpPassBase。如果你长期不更新上游代码这些改动不会触发但一旦同步上游旧代码必然报错。解决构建llvm-project时在构建目录里同步更新一次上游代码然后跑一遍ninja check-mlir以此确认当前接口版本。写Pass时尽量依赖MLIR的ODS自动生成代码而不是手写Pass基类的成员函数因为自动生成的代码会随接口更新。这五个坑如果都能避开这套中文MLIR资源的利用率会提升一大截。记住一个原则翻译文档是学习的起点不是唯一依据。遇到任何“为什么文档这么写但实际不是这样”的疑问优先信任代码再去对照文档。6. 把这套资源用到你的项目里方言微调训练与代码生成验证资源读完不等于会用。我最后分享一个把MLIR文档知识落地的具体路径在一个真实场景里做方言微调训练式的基础验证。这个思路来自我在做AI辅助代码生成智能体项目时的经验——在把Simulink模型转C代码、或者把领域规则转成目标代码时用MLIR做中间表示能复用整套优化基础设施而不是自己重新发明轮子。实践的第一步是不要直接改造现有编译器而是构建一个最小验证链。常见做法是基于Toy教程的Ch7骨架把你自己的语言映射到Toy方言然后走通-emitmlir到-emitmlir-llvm再到lli执行。如果你的目标语言语义比Toy复杂不要一开始就扩张方言操作集先用标准方言里已有的操作拼凑实在不够再加新操作。每加一个操作都要在ODS定义、方言注册、Lowering模式三个位置同步更新任何一处漏掉都会在运行时爆炸。第二步是设计一个针对你领域方言的微调验证。所谓方言微调其实就是根据你领域语言的特点调整Pass管线的组成。比如你的输入是PLC代码生成场景里的结构化控制逻辑那你在优化管线的中段会倾向于把结构化控制流保留更久因为后续的调度优化和资源分配需要在结构化信息完整时才能进行。具体做法是用--mlir-print-ir-after-all对比两组Pass管线的输出差异看哪组管线在IR大小和操作数量上收敛更快。./mlir-opt --canonicalize --cse --inline \ tests/plc_control.mlir -o /tmp/plc_optimized.mlir ./mlir-opt --cse --canonicalize --inline \ tests/plc_control.mlir -o /tmp/plc_optimized2.mlir diff /tmp/plc_optimized.mlir /tmp/plc_optimized2.mlir上面这组命令验证的是我在第4章提到过的Pass顺序敏感性问题。直接把同一组Pass调换顺序跑两遍用diff看两者IR差异如果差异为0说明这些Pass在这个输入上顺序无关如果有差异说明管线需要显式排序。这种基线验证在工程上是很有说服力的比单纯读文档理解得深刻。从那以后我每次拿到一套新的IR框架或代码生成资源都会先强制自己走一遍“最小输入 → 两次Pass变体 → diff对比”的流程再回去读文档。这套方法论比文档本身记得牢因为它逼着你理解了Pass管线的行为边界。这套中文MLIR资源的好处是它把官方文档里的背景知识都补齐了你可以在半个下午内完成从环境搭建到验证闭环的流程。希望这篇拆解能帮你在MLIR的学习路上少走一些弯路。本文还有配套的精品资源点击获取