去年我遇到一个很典型的性能问题一个经过手工调优的卷积模型在特定显卡上还是跑不满算力CUDA kernel 层面的循环展开、shared memory 复用都已经做到位收益却越来越小。后来把视野从算子挪到整个执行图直接在编译层面做算子融合和内存复用延迟反而一下砍掉了三成。从那以后我意识到搞 ML 部署优化的人不能只在 kernel 层面下功夫必须把目光投向ML Compiler 系列论文从中找到体系化的优化方法。这篇文章不是论文列表的堆砌而是我梳理这些年精读 ML Compiler 相关论文后的一套方法论读哪些、从什么角度读、怎么把论文里的思路落到实际工程里。适合正在做模型部署、推理加速、算子优化和框架开发的工程师也适合刚入门 ML Systems 方向、希望建立全局认知的研究生。我会从工程实践的角度把这些论文拆开揉碎讲清楚它们解决的问题、提出的抽象方式以及今天的大模型推理引擎里处处可见的思想源头。1. ML Compiler 论文值得精读但先得明确要从中得到什么很多人在读 ML Compiler 论文时会有挫败感论文里讲自动调度、循环变换、多面体模型概念一个接一个代码量巨大读完后却不知道对自己的工作有什么帮助。这通常是因为没有搞清楚自己读论文的目标。对我而言读这些论文的核心目标不是复现某个编译算法而是建立一套完整的“图优化—算子生成—代码调度”认知链路。只有把这条链路打通了在实际做推理优化时才能从更高的视角判断瓶颈在哪、该在哪里下手。1.1 被忽略的现实多数人栽在“算子和内核层面”我在不同团队见过不少做性能优化的工程师日常工作主要是手写 CUDA kernel、调整循环顺序、尝试不同的 tiling 尺寸。这些工作在单算子层面确实有效但当模型越来越深、分支越来越多、动态 shape 成为常态之后纯手工调优就会撞到天花板算子之间的 kernel launch 开销、显存读写冗余、融合机会无法跨算子表达这些都不是单个 kernel 能解决的。ML Compiler 论文的价值恰好在这里。它们会讨论如何在计算图上做 Pass如何把多个算子融合成一个 kernel如何在满足依赖关系的前提下进行内存规划如何让编译器自动搜索最合适的循环变换参数。想获得这些能力靠看文档或读源码是不够的源码是“how”论文讲的才是“why”。1.2 读论文前先把这条技术链路画出来我建议每个读者在开始之前先建立一条最基础的技术链路。这条链路可以简单表示为计算图表示 → 图级优化 → 算子选择/内核生成 → 循环变换与调度 → 内存规划 → 代码生成与运行时计算图表示模型如何从深度学习框架的图结构转成编译器能处理的中间表示。图级优化算子融合、死代码消除、常量折叠、公共子表达式消除等。算子选择/内核生成选择手工库如 cuDNN、oneDNN还是生成专用代码。循环变换与调度决定循环顺序、并行度、向量化和数据分块方式。内存规划显存复用、静态规划、Inference 场景下的内存分配策略。代码生成与运行时最终代码产物如何交给 runtime 执行动态 shape 如何处理。一旦你在心里有了这条链路再看任何一篇 ML Compiler 论文时第一反应就不该是“这个优化很厉害”而是“它的核心贡献落在链路上的哪个环节”。带着这个定位去读你会发现很多论文之间的关系一下子变得清晰了。2. “系列论文”背后的演进脉络从 Halide 到 MLIR一条不断抽象的主线如果只是按时间顺序罗列论文你会觉得每年都有新框架一山更比一山高很难抓住主线。我自己读下来最大的体会是ML Compiler 领域有一条非常清晰的演化逻辑从“手工规则编译”走向“可搜索的调度”再从“调度搜索”走向“多级抽象的基础设施”。这条主线可以用下面几篇必须精读的论文串起来。论文/系统核心贡献技术链路环节一句话理解Halide计算与调度分离算子生成你只描述“算什么”把“怎么算得快”交给调度语言TVM端到端深度学习编译器全链路把 Halide 思想搬到深度学习模型加入图优化和代码生成XLA显式子图编译图级优化把子图编译成融合 kernel压缩 kernel launch 开销MLIR多级 IR 基础设施中间表示一套可复用的编译器基础设施天然支持渐进式 loweringTriton面向 GPU 的 Python 式编程模型算子开发范式在 Python 里写 GPU kernel由编译器做 tile 级优化TorchInductorPyTorch 官方编译器后端图级算子生成把 PyTorch 的图编译成 Triton 代码让动态 shape 场景受益2.1 Halide 的“计算-调度分离”思想是理解所有后续工作的钥匙很多读源码的人看到 TVM 的te.compute和te.schedule会有点似懂非懂这是因为没读过 Halide。Halide 论文《Halide: A Language and Compiler for Optimizing Parallelism, Locality, and Recomputation in Image Processing Pipelines》提出一个核心抽象把算法Algorithm和调度Schedule分开描述。算法负责说明“我要计算什么”比如一个模糊滤波的数学定义调度负责说明“怎么算”比如循环拆分、重排、并行化、向量化。这个解耦的威力在于同一份算法描述可以配套多套调度策略不需要重写算法代码。这个思想今天已经全面渗透到 ML Compiler 领域。TVM 里的Schedule操作、Ansor 的自动调度搜索、Triton 里用tl.constexpr控制 tile 大小本质上都是“计算与调度分离”的变体。因此读不懂 Halide后面很多论文都容易产生“每个字都认识但不知道在干什么”的困惑。2.2 TVM 论文中的端到端架构从图优化到代码生成到了 TVM也就是《TVM: An Automated End-to-End Optimizing Compiler for Deep Learning》整个框架把 Halide 的思想从“单算子/单图像处理流水线”扩展到了“整个深度学习模型”。这篇论文最值得精读的是它的系统架构先在计算图级别做优化再把图中的算子逐一 lower 成 TETensor Expression然后经过调度和代码生成最终交给 runtime 执行。TVM 论文还提到了一个至关重要的点异构环境下的代码生成问题。同为卷积算子CPU、GPU、NPU 的循环结构和内存访问模式差异极大手工库难以全面覆盖。TVM 通过模板化的调度和代码生成让同一算子定义在不同硬件上生成有针对性的实现这个设计思路在今天的推理引擎比如 TensorRT、OpenVINO中依然能找到影子。2.3 XLA 的显式编译生产环境约束下不得不做的取舍如果说 TVM 是学术风格浓郁的编译器那么《XLA: Optimizing Compiler for Machine Learning》就是工业级场景下的产物。XLA 的思路很直接把 TensorFlow 计算图中的稳定子图截出来整体编译成高效的融合 kernel减少 kernel launch 次数和中间结果落显存的次数。XLA 论文里有几个工程细节非常值得回味。一是CSE公共子表达式消除和算子融合的优先级设计先合并冗余计算再做融合能避免融合后 kernel 体积膨胀。二是内存的 liveness 分析XLA 会对子图中的每个 buffer 做生命周期分析决定哪些内存可以原地复用。三是动态 shape 带来的挑战动态 shape 会让内存规划退化为运行时分配性能大幅下降这也是为什么很多成功案例集中在静态 shape 的训练或推理场景。2.4 MLIR一套多级 IR 基础设施的想象力MLIRMulti-Level Intermediate Representation论文《MLIR: Scaling Compiler Infrastructure for Domain Specific Computation》没有直接提出某个具体优化 Pass而是提出了一个“编译器领域的 Linux”——一套可复用的、支持多级抽象的基础设施。我读 MLIR 论文时印象最深的是dialect 机制和渐进式 lowering的配合。一个自定义算子可以先保留在一个高层 dialect 里语义清晰、便于做图级优化随着编译流程推进逐级降低到低层 dialect最终变成 LLVM IR 或硬件相关表示。这种设计让芯片厂商、框架方和编译器团队不再各搞一套彻底独立的编译器栈而是共享一套基础架构在各个抽象层级上分别做贡献。对普通工程师而言理解 MLIR 的核心价值在于你看到的 PyTorch 图、ONNX 图、Triton 语言都只是不同抽象层级的表示编译过程本质上是表示之间的翻译和优化。3. 精读一篇 ML Compiler 论文的通用拆解法把“论文标题”变成一个可操作的调试过程面对任何一个 ML Compiler 论文标题我拿到手后不会直接从头读到尾而是按一套固定的拆解路径去分析。这套拆解路径我在团队内部分享过很多次核心是五个维度问题定义、抽象边界、系统架构、实验设置、局限讨论。下面以 TVM 为例展示这套拆解法的完整操作。3.1 第一步只看两样东西先读懂问题和评估指标一篇论文最容易被忽略但最关键的是它开头“Introduction”里定义的问题。许多人在读 ML Compiler 论文时习惯性跳过问题定义直接看方法结果在实验部分一脸茫然。我的习惯是读完 Introduction 后立刻看实验部分的 benchmark 配置用了什么模型ResNet、Transformer、BERT、什么硬件V100、A100、什么框架做 baselineTensorFlow、XLA、cuDNN。这一步能让你快速判断论文的适用边界。比如 TVM 论文里的实验重点覆盖 CNN 类模型和 NLP 模型baseline 是 TensorFlow 和 XLA如果你自己手头的问题主要是推荐系统里的稀疏模型那么论文里强调的卷积优化技巧可能并不直接适用你需要关注它在 embedding 和 gather 这类算子上有没有讨论。评估指标同样重要除了延迟和吞吐之外还要关注编译时间、显存占用、kernel 数量这些次级指标它们往往决定方案在生产环境是否可行。3.2 第二步把系统架构图“翻译”成操作流程ML Compiler 论文几乎都有一张系统架构图。我的读法是拿一张白纸按照数据流方式重新画一遍一个模型从入口开始先经过什么 Pass再变成什么表示最后生成什么代码中间哪些部分是在线做的、哪些是离线做的。比如 TVM 的经典工作流是Framework → Relay/TE → Graph Optimizations → Operator Lowering → Schedule Exploration → CodeGen → Runtime。你把这串流程写下来之后再回到论文正文里找每个阶段的细节图优化阶段包含哪些 Passschedule exploration 用的是模板搜索还是自动搜索未搜索到的算子有没有 fallback 路径这样操作一遍比单纯读十遍正文效果都好。我在精读过程中还会做一件额外工作给每个架构组件标注“数据格式变化”。比如模型进入编译器后是 graph 还是 IRop 被 lower 后变成什么中间表示buffer 信息什么时候绑定到具体设备这种标注能帮你快速定位论文中各个模块的职责边界也方便和实际源码对应起来。3.3 第三步从实验部分反推设计动机ML Compiler 论文的实验部分不仅仅要证明“我们的方法更快”还隐含了设计者的动机和取舍。读实验我有一个“反推法”看到某个消融实验ablation study时先不看结论自己猜测这一组对照实验是为了排除什么干扰、证明什么设计点然后再去看作者的解释。比如 TVM 或 Ansor 这类自动调优论文通常会做一组“搜索时间 vs 性能提升”的实验。这组实验想表达的是我们的编译器虽然可能花更多离线时间去搜索但搜出来的调度策略能在长期运行中赚回成本。反推设计动机之后能帮你判断这个框架适不适合你的场景如果你的部署模式是“每天换一个新模型”编译时间短比峰值性能重要得多如果模型长期固定那么多花一点编译时间换 10% 的延迟收益是划算的。这种判断能力只能通过反复拆解论文实验获得。4. 从论文到工程落地必须跨过的那几道坎论文里的系统在论文环境里跑得很好迁移到你的真实场景却可能水土不服这不是论文造假而是工程环境变了。这些年我在参考 ML Compiler 系列论文做工程实现时踩过不少坑挑几个最典型的展开说说。4.1 论文里的自动调度算法在真实设备上未必能搜出理想结果以自动调优/自动调度方向为例很多论文假设搜索空间里的调度方式在目标硬件上都有对应的高效代码模式。但实际 GPU 上某些循环变换组合会产生 bank conflict、寄存器溢出甚至因为占用率过高而性能崩坏。论文里可能用很有代表性的设备比如 V100跑出漂亮数据但同等算法换到消费级显卡上搜索出来的调度甚至可能打不过手写 kernel。我在实际测试 Ansor 的过程中发现对同一个算子不同 CUDA 版本的代码生成结果差异很大。遇到这种情况不要盲目调搜索参数先确认设备与 CUDA 版本的兼容性然后逐步缩小搜索空间并在候选调度里加入“baseline 模板”作为保底选项。引用论文的方案做工程时一定要给搜索这类非确定过程加保护机制设置搜索时间上限、设置性能回退阈值、对关键路径算子直接使用人工调优模板。4.2 调度策略与硬件强绑定换卡不重调就是灾难这一点最容易踩也最容易忽略。ML Compiler 论文中提到的“最优调度”经常是针对某个特定 GPU 的线程组织方式和缓存大小设计的。我见过团队把在一张 A100 上调好的配置直接部署到 T4 机器上性能反而倒退 20%。原因不复杂SM 数量、寄存器文件大小、L2 缓存容量完全不一样原来设的 block 数量、tile 大小和向量化宽度都不再合适。解决这个问题没有银弹只能建立一套“硬件自适应”机制。做法是对算子做特征化记录计算量和访存量把调度参数化不再硬编码每次部署前在目标设备上跑一段 micro-benchmark用少量样本搜索最优参数。如今 Triton 这类工具把部分适配工作自动化了但理解背后“换卡必须重调”的原因能让你在架构设计时提前留出配置接口。4.3 论文里的加速比数字要警惕统计口径ML Compiler 论文经常会给出“相对 PyTorch 加速 3 倍”“相对 TensorFlow 加速 10 倍”之类的数据。这些数字本身没问题但背后很可能藏着不同的比较口径。比如某些 baseline 开启了 eager 模式kernel launch 开销没有隐藏而编译器的 benchmark 里可能预先把图固定好、关闭了动态 shape、甚至对 baseline 的算子实现没有做充分调优。读论文实验时我会特别留意以下几个细节baseline 是否经过等价优化比如 PyTorch 是否开启了torch.compile或 Channels Last输入 shape 是否固定动态 shape 下加速比通常大幅缩水是否包含端到端耗时还是只比较单个 kernel显存占用与首次编译warm-up时间是否被计入用论文的加速比数字去指导生产决策前先做一次自己的 baseline 复测。多数编译器在固定输入、静态 shape 条件下确实能跑出不错的数据但动态 shape、小算子、不规则模型仍然是不少编译器的短板。5. 持续跟踪 ML Compiler 方向可以从这几条线入手“持续更新”这个标题意味着你要保持长期关注但这个领域每年的论文量很大盲目的全面跟踪会让人疲惫。我目前主要盯住三条技术线基本能和主流工作保持同步也能较快判断哪些新论文值得精读。5.1 自动调度与大搜索空间从模板到无 template 演进从 TVM 的模板调度、到 Ansor 的自动搜索、再到后来的 MetaSchedule这个方向一直在解决“如何让编译器自己找到好调度”的问题。关注这条线时我建议重点把握两个技术点一是搜索空间怎么定义二是有没有学习型初始化或成本模型参与。比如 MetaSchedule 引入 evolutional search 和 learned cost model目的就是减少搜索时间让编译开销可控。这个方向的新论文通常都有较充分的实验数据是跟踪性能优化前沿的常用途径。5.2 LLM 推理与编译的边界融合随着大语言模型普及ML Compiler 的关注点正在从“CNN 算子的卷积优化”转向“Transformer block 的融合、显存管理和 KV cache 优化”。比如 FlashAttention 系列、Split-K、PageAttention 这类工作即使不完全是编译器论文也深刻影响编译器设计和 kernel 生成策略。关注这条线你会看到编译器在做算子融合时不再只关注计算效率而是把显存带宽和中间状态的开销放到同等重要的位置。FlashAttention 的思路还带来一个启示重计算recomputation在现代硬件上可能比缓存中间结果更划算这和传统编译优化的直觉完全不同。5.3 编译器与框架的深度耦合PyTorch 2.x 的torch.compile和 TorchInductor实际上把编译器直接嵌进了主流框架的默认使用路径。这意味着编译器的行为会直接影响大规模生产系统的默认性能。跟踪这条线重点看框架侧如何定义“图编译的边界”哪些图被编译、哪些图走 eager以及动态 shape 如何渐进式支持。PyTorch 社区的做法是“分层编译”先用 inductor 处理可编译子图遇到不支持的算子再用图 fallback 或 eager fallback这套机制本身就是一套很好的 ML Compiler 工程实践。如果你有兴趣自己动手我建议以torch.compile为入口跑一批典型模型观察它的编译报告torch._dynamo日志和 inductor 日志亲眼看一遍“图优化—算子融合—Triton 代码生成”的完整流程。你会发现之前读的论文里的抽象概念在这个工具里都能落实到代码行级别。读完论文再看工具的实际行为比单纯追求论文数量更有价值。6. 实操建议给自己设计一个论文精读的闭环流程理论讲完了最后分享一套我目前在用的论文精读闭环方法。它不是学院派的正规文献综述流程而是更适合工程师的快节奏流程筛选 → 略读 → 深度笔记 → 实验验证 → 归档。6.1 筛选和略读用十分钟决定一篇论文是否要精读我先看标题和摘要然后直接看实验部分的图表重点看两个指标编译时间和端到端加速比。如果论文的模型类型、硬件平台和任务场景与我当前的工作无关就先放入“延后阅读”列表。如果相关我会花 15 分钟精读 Introduction 和 Conclusion只抓两件事问题是什么以及作者认为核心贡献是什么。如果读完这两部分仍然没感觉就果断放弃精读避免陷入“开篇文章必须读完”的强迫症。6.2 深度笔记格式我不抄摘要只写四栏这是我目前最受用的笔记形式栏目内容问题定义这篇论文解决的是技术链路哪个环节的问题抽象/方法核心抽象是什么方法提出了哪些关键概念取舍相对 baseline 有哪些代价什么条件下方法会失效工程落地启发我能借鉴什么到自己的项目里要不要写个代码原型写这个笔记的过程其实是在逼自己和论文对话。如果你只是把摘要抄一遍基本不会留下印象但当你被迫去回答“这个方案在什么条件下失效”时你才会开始认真审视论文里的实验设计和系统假设。6.3 实验验证从论文到最小复现精读完一篇论文后我强烈建议在自己的目标硬件上跑一个最小复现。不一定需要完整复现论文环境只要验证论文的核心主张即可。比如读 Ansor 之后不需要复现它的全部搜索算法而是直接跑一个from tvm import auto_scheduler的例子观察它生成调度并对比手写 baseline 的性能。这一小步能把抽象的论文知识和真实的性能行为建立连接比多读十篇相关论文更有用。我不建议一上来就复现整个系统ML Compiler 的全栈复现成本极高很容易被环境配置消耗全部精力。正确做法是先挑一个点验证比如论文里的融合策略、调度搜索策略或内存复用算法然后把这个点用最小代码实现出来。这一步走通之后你回看论文时会很有底气。6.4 归档和更新把论文笔记变成一个活文档我的归档方式很简单用表格维护一个阅读列表标注每篇论文的状态、精读日期、基于它做的实验链接以及“工程落地启发”栏有没有被后续工作推翻。ML Compiler 领域更新很快几年前认为正确的优化策略可能被新硬件特性推翻所以定期回看笔记、对已归档论文做追加更新是一个值得养成的习惯。我在“持续更新”这个系列里本身也是这么运作的新论文进来先按筛选流程过一遍值得精读的走完整套笔记和实验流程最后把笔记补进对应主题的归档文档里。这套流程不复杂但坚持下来积累的东西会很可观。最后再分享一点我自己的体会这个领域有一个特点论文的核心思想通常只有一两个点很关键但读懂这一两个点需要大量背景。不要被复杂的公式和庞大的系统吓住保持“每篇论文只解决一个主要问题”的心态慢慢积累最终你会形成自己的判断力。我现在的日常工作里遇到一个性能问题时脑子里会快速闪过“论文里是怎么处理这个环节的”“这个方案在哪些条件下失效”这种判断力正是长期读论文带来的复利回报。如果你正为 ML Compiler 庞大的知识体系感到无从下手就从今天开始精读第一篇跑通第一个最小复现后面的路会越来越顺。