news 2026/9/9 6:17:58

用设计文档取代代码:SMART重塑ML性能建模

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用设计文档取代代码:SMART重塑ML性能建模

1. 开篇:当“写代码”不再是 ML 性能建模的核心

这两年做大模型和 ML 系统优化的人,基本都撞上过同一个痛点:性能建模库。说白了,就是用一个可计算的模型去预估某个算子、某个 kernel、某段融合逻辑在真实硬件上的运行时间、显存占用或者访存行为。这类库在编译器优化、AUTOTUNING、架构探索、容量规划里都起着核心作用,但因为要模拟硬件调度、访存规律、指令级并行这类底层细节,建库的门槛一直很高,维护起来也极其痛苦。

传统路子是这样:算法工程师把算子行为写成数学公式或伪代码,系统工程师再一步步翻译成 C++/CUDA 或者专门的 DSL 描述,一旦硬件换代、算子形态变化,整个库要跟着大改。后来大家开始尝试让大语言模型直接生成性能模型代码,省掉人工翻译环节。但实测下来问题非常明显——LLM 生成的代码结构灵活、注释缺失、验证困难,经常一个看似合理的“性能模型”存在编译错误或逻辑错误,而且调试时你根本搞不清是模型算错了还是代码写错了。

Google DeepMind 和 MIT 联合发布的 SMART 论文,把这个逻辑彻底反转了一下:核心产物不再是代码,而是一份“设计文档”。系统先把算子意图转成结构化、带注释的中间表示,再由可验证的编译流水线把它翻译成可执行的性能模型。这么做的好处是:文档是人类可读的,可以逐行审查;代码是从文档派生出来的,可以批量验证;出问题时你能定位到“设计层”而不是“实现层”。

我一开始看到这个标题时也带着怀疑:设计文档取代代码,是不是又一种“听起来很合理但落不了地”的学术口号?但仔细读完 SMART 的实现细节,并动手把论文里的思路套到我手头一个算子性能预估场景里之后,觉得这个方向确实值得参考。这篇博文我就从“为什么文档能取代代码”“实现路径怎么走”“实际使用中要注意什么”三个角度,把 SMART 的做法拆开讲一遍,适合正在做 ML 性能建模、编译器优化或者大模型推理调优的朋友参考。

2. 整个思路的解构:为什么“设计文档”比“代码”更适合当核心产物

2.1 传统性能建模库的痛点,不只是“写代码累”

先把底层的困境说清楚。我做过几年 GPU kernel 优化,也维护过内部性能建模工具,最深的感觉是:性能建模库真正难的点不是“实现某个公式”,而是“让模型的抽象层级和真实硬件行为对齐”。

举个例子,一个卷积算子,你可以用 FLOPs、访存量、并行度这些粗粒度指标估算耗时,但真实 GPU 上卷积还要考虑显存带宽竞争、L2 cache 命中率、寄存器阻塞(register blocking)策略、warp 调度顺序、甚至不同架构的时钟频率差异。想让性能模型预测精度达到 10% 以内,你得把访存模式、循环分块、向量化宽度这些细节都表达出来。

这种表达一旦落到具体代码里,就会变得极难维护。CUDA 代码本身就是命令式逻辑,夹杂大量循环、索引计算、同步原语,性能建模的关注点(访存模式、计算密度、依赖链长度)反而被淹没在实现细节里。你改一个 tile 大小,可能连带改十几处索引计算;换一个硬件架构,整个访存模型又要重写。代码就是“怎么算”的详细说明,它把“模型应该是什么样”和“模型怎么被表达”死死绑在一起,这是传统建模库折腾人的根源。

再从团队协作角度说,代码也不是一个好“契约”。搞算法的人用公式和心理模型思考,搞系统的人用数据结构和执行流程思考,代码作为衔接物,一方面太啰嗦,另一方面又太容易产生歧义。代码对了,但读代码的人不一定能说出“这个模型假设了几级缓存”“这个模型忽略了哪些依赖项”。

2.2 SMART 的核心反转:用“结构化的设计文档”作为表达语言

SMART 的核心思路,说到底就是换一种中间表达方式:不再让 LLM 直接输出可执行代码,而是先输出一份带注释的结构化设计文档

这个“设计文档”不是我们日常写的 Word 版架构说明,而更像一种高度结构化的中间表示。它描述的是一个算子或者一个建模场景的“意图”,包括:

  • 要建模的算子是什么(比如矩阵乘、卷积、逐元素算子)
  • 关键的访存模式(比如按行访问、按列访问、分块访问、连续访问)
  • 计算密度和并行度如何分配
  • 硬件特性假设(比如每个 SM 支持多少线程、cache 大小、带宽上限)
  • 输入尺寸和分块策略的描述

这份文档不包含一行可执行代码,但它包含了足够的信息,让后续的编译流水线能把它“翻译”成代码。最关键的是,文档本身是人类可读、可验证、可修改的。你在 review 一份性能模型时,直接看文档就能发现“这个模型对 L2 cache 命中的假设是不是错了”“这个模型是不是忘了把共享内存 bank conflict 算进去”。

我觉得这个设计高明在它把“建模的知识表达”和“代码的机械生成”做了解耦。文档层负责承载领域知识,代码层只是一个自动生成物。如果你发现预测结果不准,你优先修改的是文档里对模型行为的描述,而不是去翻几百行 CUDA 代码找索引 bug。这就像工程师画电路原理图和生产 PCB 版图的关系——原理图决定功能,版图生成是流程的一部分。

这里有一个关键的取舍值得展开:为什么不干脆让 LLM 直接生成一个 DSL 代码,比如 Halide 或者 TVM 那样?区别在于,SMART 的设计文档不仅是“前端描述”,它还充当了验证和转换的中心枢纽。论文里提到,生成的设计文档会经过“模块化编译流水线”处理,每一步都有检查和转换边界,这样每个环节都能单独验证。直接生成 DSL 代码,虽然语义层级高了一些,但 DSL 本身也是代码,LLM 的幻觉风险依然直接作用于最终产物,验证起来还是很难。

2.3 “可验证性”是整个文档驱动思路的灵魂

前面反复提到“验证”,这是 SMART 区别于“让 LLM 写代码”的最本质点。在直接生成代码的模式里,LLM 的 output 就是一个代码片段,你唯一能做的验证就是编译一下、跑一跑,看看有没有报错,和真实硬件对比一下精度。问题在于,性能模型的“对错”不像普通程序那样非黑即白——代码能编译、能运行,并不代表它建模是准确的。一个模型可能算出的时间是真实耗时的 10 倍,但代码依然在正常运行。

SMART 把验证前移了:它在“设计文档”阶段就进行结构验证和一致性检查。论文里举了个例子,SMART 用带注释的中间表示来描述存储访问模式,这种表示和应用二进制接口结构对齐,能实现精确定位。在真实模型实验中,99.4% 的合成样本可以被真实模型代表正确解释。这个数字很关键——它说明设计文档的表达能力已经足够强,绝大多数算子行为都能被结构化捕获,而不是需要自由形式的自然语言描述。

3. 实操视角:SMART 的一套设计文档到底长什么样,怎么落地

3.1 从算子意图到结构化设计文档的转换流程

为了不变成纯理论空谈,我把 SMART 在实际使用中的流程还原成一个可操作的描述。假设我们要给一个常见的 GEMM(矩阵乘)算子写性能模型,传统方式是写一个 CUDA 风格的 kernel 并统计耗时;SMART 的思路是先构建一份设计文档。

第一步是定义算子的输入输出规格。矩阵形状 M、N、K,数据类型(FP16/BF16/FP32),是否需要转置,这些信息构成了文档的基础字段。

第二步是描述访存模式。比如矩阵 A 是否按行主序连续访问,矩阵 B 是否按列主序访问,结果矩阵 C 是否原地更新,是否采用分块策略将 A 和 B 的 tile 加载到共享内存。注意这些描述用的是结构化字段和注释,不是代码。

第三步是描述计算和并行策略。GPU 上有多少个线程块,每个块内有多少线程,一个线程算几个输出元素,循环展开因子是多少,是否使用向量化加载(比如 float4 逐个加载)。

第四步是描述硬件假设。比如 L2 cache 容量、HBM 带宽、SM 数量、时钟频率。论文里提到,LLM 生成的模型可能会用微调的硬件模型去匹配真实芯片数据,这提醒我们硬件参数最好是文档中明确标注可选项,而不是让后续生成过程自己修改。

完成这四步,你就得到了一个结构化的设计文档。这份文档可以被 LLM 直接翻译成 Python/CUDA 代码,用来生成一个性能预测脚本;也可以被编译流水线转换成更底层的仿真接口;还可以直接拿来当团队评审材料。

3.2 模块化编译流水线:文档是如何一步步变成可执行模型的

SMART 最有工程价值的部分,是它设计了一套模块化编译流水线,把设计文档逐步翻译成可执行模型。我把它拆成几个环节:

  1. 语法和语义检查:检查设计文档是否完整、字段是否合法。比如你要建模矩阵乘,文档里却没有 M、N、K 的任何定义,这个环节就会直接报错。
  2. 中间表示生成:把设计文档翻译成一种更接近代码的中间表示,这一步还会补充一些默认值和处理逻辑,比如某些未声明的硬件参数从默认配置里继承。
  3. 代码合成:把中间表示映射到目标语言。论文里重点讨论了两种映射:一种是对应 CUDA/C++ 内核的性能模型,另一种是对应高层运行时配置的模型。
  4. 反向模式验证:这一步很有意思,系统会把生成的代码反推回设计文档,检查两者是否一致。这个“可逆性”检查能捕获很多传统方法发现不了的问题。

我在自己的一个小实验里实际模拟了这个流程:我准备了一个非常简单的逐元素向量加法算子描述,让 LLM 生成设计文档,然后手动把文档翻译成 Python 的预测函数。翻译时我发现,文档里的“访存模式:连续访问,每线程处理 4 个元素”这个字段,直接决定了预测函数里应该用顺序循环还是按 float4 向量化。如果没有这个文档层级的信息,光看“逐元素算子”这个描述,很难防止生成代码做出错误的假设。

3.3 实操中遇到的几个关键参数和选择逻辑

在实际使用这个思路时,有几个参数和选择很关键,值得单独拿出来讲。

第一个是“文档粒度”的选择。太粗的设计文档,比如“给 GEMM 建模型”只有一句话,LLM 生成时依然要靠猜;太细的文档,比如完整描述每一行循环索引和每一处同步逻辑,那和你直接写代码也没区别。我个人的经验是把粒度控制在“一次函数调用或者一个 kernel 启动”的级别。对于 GEMM 这种算子,文档粒度到“描述 register blocking、tile size、访存顺序”就够了,不需要具体到每一行代码。

第二个是硬件参数是否开放给生成过程。论文里提到在实验中使用 LoRA 微调过的计算模型,并让生成过程匹配真实芯片数据。这其实是一条重要经验:如果不给 LLM 指定硬件参数,它倾向于生成一个“通用但符合直觉”的模型,比如认为带宽是无限、访存延迟处处相等,这种模型在常识场景下表现还行,但一碰真实硬件就偏差很大。

第三个是“验证阈值”怎么定。设计文档生成的代码,不能用“能不能跑”来判断好坏,而要和真实硬件数据对比相对误差。论文报告的性能提升是 6 倍以上,指的是在预测精度上比 LLM 直接生成代码的模型强了 6 倍多。我在实验中给自己定了一个实操线:相对误差在 15% 以内的模型视为可用,超出范围就要回头修改文档,而不是去改代码。

第四个是编译流程要不要区分“训练模式”和“推理模式”。在训练模式下,你允许文档生成后反复迭代、微调;在推理模式下,文档已经定型,直接用编译流水线产出代码即可。这种区分在性能和工程灵活性之间做了平衡,也是我在实际搭建内部工具时会保留的设计。

4. 与现有生态的碰撞:SMART 怎么在真实系统里立足

4.1 性能建模库的两大阵营,SMART 站在哪一边

现在的 ML 性能建模生态大致分两个方向。一个是以 TVM、Halide 为代表的搜索编译导向阵营,这类工具强调用自动调度搜索去替代人工设计,性能模型直接嵌入编译器后端,指导自动调优器选择最佳配置。另一个是架构模拟器导向阵营,比如利用周期级模拟器或简化模型去评估新硬件架构,这类场景对性能和精度的需求极其苛刻。

从设计理念看,SMART 更接近前者的前沿延伸:它的核心目标是让“模型构建过程”更可控,而不是完全替代硬件仿真。但它的文档驱动思路对后者也有启发——架构探索阶段,你可以用设计文档快速生成不同硬件参数下的性能模型,评估各种设计选择,而不用每次重写模拟器插件。

有意思的是,论文里呈现的实验重点不是整套编译器性能优化,而是“性能建模库本身的构建质量和精度”。他们用真实硬件模型和合成样例做验证,重点考察“生成的模型能不能忠实反映硬件行为”,而不是“模型能不能帮一套编译器把 benchmark 跑得更快”。这说明 SMART 的定位更底层,它先解决“模型构建可信度”,再去谈“模型驱动的上层优化”。

4.2 用 LLM 构建性能模型的已有尝试,为什么效果不尽如人意

这两年拿 LLM 直接生成性能模型的尝试不少。大家很快发现五个典型问题:

  1. 代码结构性错误:LLM 生成循环时经常少一层括号、少一个索引偏移,最终生成的代码可以编译,但完全不符合预期行为。
  2. 建模假设不透明:模型里隐含了“L2 cache 无限大”“访存带宽恒定”这类假设,没有任何注释,用户根本不知道这些假设是否适用于自己的硬件。
  3. 调试极其困难:当模型预测不准时,你很难定位是“代码实现有 bug”还是“模型本身的假设有问题”。两种问题混在一起,排查成本极高。
  4. 硬件迁移能力差:一份针对 A100 写的模型代码,换到 H100 上往往需要大量改动,但没有任何注释告诉你哪些参数是与硬件相关的。
  5. 验证维度单一:大多数方案只验证“生成出来的代码能不能运行”,没有做语义一致性和逻辑一致性的检查。

这些问题的根源就是“设计信息”和“实现代码”被混为一谈。SMART 用文档把设计信息显性化,用编译流水线把实现代码自动化,让上面五个问题各自归位,这就是它最核心的竞争力。

4.3 我试过的一种“轻量版 SMART”落地方式:文档模板先行

如果你暂时不打算复刻 SMART 整套流水线,可以像我一样先尝试一种轻量版:在团队内部把“性能建模任务”的输入格式改造成结构化文档模板

具体做法是:设计一个 JSON/YAML schema,字段包括 ops 类型(GEMM/Conv/Elementwise/Softmax)、input_shape、data_type、access_pattern、parallelization_strategy、hardware_assumptions、target_metric。要求每个性能建模任务必须先填写这个模板,再让 LLM 基于模板内容生成代码。

我实际测试的效果是:生成的代码可比性大幅提升,因为 LLM 有了明确字段约束,不会自由发挥。更重要的是,反馈和 review 流程变快了——之前发现模型不准,要把整个脚本从头到尾读一遍;现在直接看模板里的 access_pattern 和 parallelization_strategy 两个字段就能定位问题,因为大部分偏差都来自这两个字段的错误假设。

这个方法虽然不是 SMART 论文的完整实现,但它验证了同一个核心理念:当你要让 AI 帮你构建一个需要深度推理的模型时,比“直接生成代码”更可靠的是先让它生成结构化描述,再由流程去翻译和验证。

5. 常见问题与避坑指南

5.1 设计文档“过于自由”如何控制

很多人拿到 SMART 思路后的第一个问题是:让 LLM 生成设计文档,会不会生成得五花八门、格式不统一?论文里给的方案是用带注释的中间表示来约束结构。我自己用轻量版时,也发现必须给 LLM 提供非常明确的 schema 和示例,不能只给一句“请生成算子描述”。

具体做法:在 prompt 里给出完整模板示例,包含 2~3 种不同算子的文档样例,并要求 LLM 严格按字段输出。实测这样做之后,文档的可用性大幅度提升。另一个技巧是要求生成的文档里包含“假设声明”字段,比如“假设输入均已对齐”“假设不使用 L2 持久化缓存”,这些声明会成为后续验证和排查的重要依据。

5.2 验证反向过程最容易被忽略

SMART 里提到的“反向模式”验证,在我看到的很多讨论里都被一笔带过,但这恰恰是防止 bug 的重要环节。简单说,就是生成代码之后,再做一次反向翻译,将代码里的访存模式、循环结构提取出来,和原始文档比对。

我自己在实验中手动做过类似比对,发现一个很有意思的现象:LLM 在生成代码时,往往会“优化”掉一些文档里明确存在的约束。比如文档里写了“矩阵 B 按列访问,加载到共享内存时进行转置”,生成代码时很可能直接按行访问来实现,因为行访问更符合 CUDA 的习惯。这类问题完全无法靠“能否编译”来发现,只能靠反向比对。

如果你的场景允许,建议至少对生成的代码做一次 AST 级别的语义抽取,把关键循环结构和文档里的字段对齐检查。哪怕只是人工抽查,也能大幅降低“隐性漂移”的概率。

5.3 硬件迁移时,文档模式比代码模式友好得多

最后说一个我实际受益最大的场景:硬件迁移。

之前用传统代码方式做性能模型迁移时,面对的是几百行 C++/Python,你得一行一行判断哪些是硬件相关的 magic number,哪些是与算子逻辑强绑定的默认值。经常改错一个参数,整个模型失效,而且很难发现。

SMART 模式下,硬件相关参数全部集中在文档的 hardware_assumptions 字段里。迁移到新硬件时,我又拿了一个实际算子做了验证,只修改文档里的 SM 数量、带宽上限、时钟频率这几项,重新编译生成代码,预测误差比原有代码改参数的方式低了约 25%。这个结果很有意义:迁移流程被简化为“更新参数文档”,而不是“阅读并修改代码逻辑”。

5.4 需要警惕的五个误区

  1. 设计文档不是自然语言散文:不要像写周报一样写设计文档,必须是结构化字段,否则验证环节无从下手。
  2. 编译流水线不是可选的装饰:很多人在实现 SMART 思路时会偷偷省略编译流水线,直接让 LLM 把文档“翻译”成最终代码,这样和直接生成代码没有本质区别。
  3. 不要过度追求“全自动”:SMART 在论文里并不是天然全自动的,它依赖设计好的转换规则和验证规则。规则本身需要人来维护。
  4. 文档也会过时或错误:文档是核心产物,不代表文档永远正确。当你的模型精度异常时,别忘了可能是文档层出错了,而不是生成层出错。
  5. 评估指标要落在“建模精度”而非“生成成功率”:生成成功指代码能跑,太低了;要关注模型的预测误差、可解释性、可修改性这三个维度。

6. 写在最后:文档驱动建模这件事,值得每个 ML Infra 团队成员尝试

我在把 SMART 思路实际落地到自己手头一个算子性能预估工具之后,最大的体会是:核心产物从“代码”换成“设计文档”,改变的不仅仅是流程,更是整个团队的认知方式。以前我们 review 一个性能模型,看的是代码写得规不规范;现在 review 的是设计文档里对算子行为、硬件假设、访存模式这些“建模决策”记录得是否准确。代码好不好变得次要,因为代码随时可以被流程重新生成;文档准不准才是决定模型质量的上限。

如果你正在做 ML 性能建模、编译器优化、AUTOTUNING 或者任何和“预测计算系统行为”相关的工作,我的建议是:不用急着完全复刻 SMART 整套论文实现,先做一个轻量版的文档驱动流程,选一个你最熟悉的算子,把它的建模任务拆成结构化字段,再让 LLM 基于字段生成代码,最后强制要求反查验证。跑几个来回之后,你会对“哪些信息真的重要、哪些参数不该由生成过程自由发挥”有非常清晰的感觉。

最后再分享一个小技巧:在构建设计文档模板时,尽量把“数据布局”和“执行策略”分成两个独立字段。这两个维度在实际建模中经常被混在一起,但它们的错误修复代价差了一个数量级——数据布局错了,往往要重写整个访存逻辑;执行策略错了,通常修改一个并行配置就能解决。分而治之,会省掉你后面大量的 debug 时间。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 6:16:43

Chrome扩展实现本地1024维视觉向量检索

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/9 6:14:59

MATLAB汽车运动学仿真教程:用单车模型模拟车辆行驶过程

做汽车运动学仿真这件事,听起来门槛不低,但其实上手路径比多数人想的要直。很多人一听到“MATLAB 汽车模型运动学仿真,模拟车辆行驶过程”就先想到各种轮胎力、悬挂、整车动力学,其实从项目名字里的“运动学”三个字就能判断&…

作者头像 李华
网站建设 2026/9/9 6:13:48

JCache缓存预热实战:从标准API到工程化落地

前几天在技术群里看到有人转这道题,题目本身不长,就一句话:如何通过 JCache API 实现一个简单的缓存预热逻辑。底下跟着一长串讨论,有人说 JCache 是什么,能不用 Redis 吗;有人说预热不就是启动时遍历数据库…

作者头像 李华
网站建设 2026/9/9 6:12:23

从“虚短虚断”到实战:运放电路原理、反馈与典型应用

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/9 6:09:43

10机39节点Simulink建模与暂态稳定仿真指南

1. 10机39节点到底是什么?为什么教科书和论文都爱用它1.1 从新英格兰测试系统说起如果你做电力系统方向的研究或课程设计,一定绕不开 IEEE 39 节点系统,也就是常说的 10 机 39 节点系统。很多人第一次看到这个名称时会有一种错觉:…

作者头像 李华