Design Docs Are All You Need:技术学习文档
从“维护一套不断老化的性能建模代码”,转向“维护可执行语义的设计文档,并由 Agent 定期从零生成实现”。重点理解 SMART 的文档 DAG、符号化算子 IR、两级调度模型,以及这种研发模式真正成立所需的验证闭环。
传统:代码是事实来源,文档追赶代码。
SMART:设计文档是事实来源,代码由文档全量派生。
“舍入误差级一致”描述的是生成实现对人工参考模型的复现精度,不是模型预测对真实 TPU 运行时间的硬件实测误差。
为什么性能建模软件特别容易积累技术债
性能模型夹在两个高速变化层之间:上层模型架构不断出现 MoE、潜在注意力、异构推理阶段;下层加速器、内存层级和互连也持续变化。任何“当前看起来合理”的抽象,都可能很快失效。
| 失败模式 | 发生机制 | 长期后果 |
|---|---|---|
| 增量生成债务 | 新版本不是从新规格独立生成,而是在旧实现上继续补丁 | 旧约束、兼容层和局部妥协不断叠加 |
| 上下文窗口近视 | Agent 只能看到局部文件或片段,看不到全局不变量 | 局部正确、整体结构却逐渐失去一致性 |
| 双向架构漂移 | 模型语义与硬件语义同时变化 | 中间抽象频繁重构,测试和文档快速失真 |
Dt+1 = | G(St+1, Ct) - G(St+1) | S:当前规格 · G:人或 Agent 生成器 · C:实现 · D:增量修补与从零生成之间的“距离”
论文的做法是定期重新计算 C = G(S),不把旧实现 Ct 喂给下一轮,从流程上切断补丁历史。这里的“债务归零”是一个研发过程定义:它消除了由旧代码作为额外输入产生的偏差,但不会自动消除规格本身的错误、生成器随机性或验证盲区。
来源:PDF 第 1-2 页,公式(1)及 Introduction。
SMART 的全量再生成闭环
人只修改设计文档,不直接修改生成代码。
只读 Agent 分析文档,推断依赖边,形成 DAG。
编排器按依赖波次分配“每文档一个编码子 Agent”。
从干净状态构建符号化性能模型实现。
参考模型、参数保护、单元测试和精确锚点共同验收。
歧义和错误回写为规格改进,再触发下一轮生成。
来源:PDF 第 2-3 页,Figure 1 与 Cost and iteration speed。成本是作者当时实践数据,不应外推为固定价格。
设计文档 DAG:把大代码库切成 Agent 可处理单元
主分支几乎不保存实现代码,而是保存可以组合成有向无环图的 Markdown 文档。依赖顺序由内容决定,例如拓扑和数值类型必须先于集合通信成本,后者又必须先于模型目录。
上下文被限制在一个自包含模块,降低单次任务长度和模型迷失全局的概率。
编排器记录难以解释的描述和前序波次暴露的缺陷,告诉人类下一次应该改哪份规格。
同层无依赖文档可以并行生成;下游只读取已通过验收的上游接口。
核心 DSL 等高难文档交给更强模型,下游重复性模块交给成本更低的模型。
来源:PDF 第 2-3 页,Design docs as the source of truth。
可靠生成依赖怎样的设计文档
高层规则像“宪法”,能约束结构,却不一定固定细节语义。论文强调补上一种自底向上的表达:逐步工作示例。它既是人类可读的解释,也是生成 Agent 的上下文示范。
| 文档元素 | 必须回答的问题 | 对生成的作用 |
|---|---|---|
| 输入与符号 | 维度、单位、取值域、分片含义是什么? | 防止形状和量纲漂移 |
| 逐步轨迹 | 每一步的中间形状、值和资源动作是什么? | 把隐含语义变成可模仿过程 |
| 闭式表达式 | 最终成本应化简成哪个精确符号表达式? | 提供强于样例输出的代数约束 |
| 边界与反例 | 哪些参数组合非法?哪些直觉容易算错? | 生成参数保护与负向测试 |
| 对账锚点 | 一个小型 preset 的精确期望输出是什么? | 将示例直接落成回归测试 |
实用判断:如果读者只能“理解大意”,却不能拿纸笔复演出中间状态和最终值,这份文档仍不足以作为可再生代码的规格。
来源:PDF 第 3 页,An opinionated way of writing design docs。
最小符号 IR:一个递归 Op 覆盖模型与系统
稳定再生成要求被描述的系统足够小、正交且不随架构变化频繁改形。SMART 将模型压缩为一种递归操作 Op:内部节点表达循环或子图,叶子节点表达硬件可定价的原子动作。
Op ├─ inputs : List[Tensor] # 符号形状 ├─ outputs : List[Tensor] ├─ cost : OpCost # compute / memory / comm 的 SymPy 表达式 ├─ rrt : ResourceReservation # 资源 × 周期占用表 └─ params ├─ InnerLoop(n_iter, body) # 带迭代次数的循环 ├─ GraphParams(graph) # 普通子图 └─ LeafParams(...) # 系统定价叶子
用循环、子图和 Tensor 数据流组合 leaf,不把 TPU 细节散落到每个模型定义中。
为 MXU tile、VMEM tile load、ICI collective 等 leaf 定义 OpCost 与 RRT。
解耦结果:替换注意力算法主要改模型文档;替换互连或加速器代际主要改系统定价文档。递归结构不需要跟着每个模型家族重写。
来源:PDF 第 3 页,A minimal symbolic IR 与 The Op abstraction。
Builder DSL:从 Python 轨迹生成 IR
模型作者不直接手写 Op 树,而是在轻量 Python 内嵌 DSL 中写出可追踪的计算。装饰器 block 形成命名子图;两类 loop 则保留并行与归约语义。
| 构造 | 语义 | Flash Attention 中的角色 |
|---|---|---|
@smart_map_loop | 彼此独立、可并行的 map | 遍历 query block,并沿指定轴收集输出 |
@smart_loop | 携带 accumulator 的真归约 | 遍历 KV block,持续累加 attention value |
| builder leaf | 发出系统已定价的原子 Op | load/store tile、MXU einsum、tensor add |
| SymPy dimension | shape 与 trip count 保持符号化 | 同一 nest 覆盖 prefill 与 flash decoding |
@smart_map_loop(n_iter = Tq / q_block)
def each_query_block():
q_tile = load(q)
acc = load(initial_value)
@smart_loop(n_iter = Tkv / kv_block)
def reduce_over_kv(acc):
k_tile, v_tile = load(k), load(v)
scores = mxu("Q × Kᵀ", q_tile, k_tile)
partial = mxu("scores × V", scores, v_tile)
return add(acc, partial)
return store(acc)
关键不是 Python 语法,而是 trace 后保留了层级、循环类型、符号迭代次数和硬件 leaf。因为 Tq 与 Tkv 不被提前绑定,同一个图可表示 Tq=Tkv=T 的 prefill,也可表示 Tq=1, Tkv=Tctx 的 decoding。
来源:PDF 第 3-4 页,The builder DSL 与 Listing 1。示例为语义化缩写,并非原代码复制。
分片注解驱动通信推导
数据分布通过 Tensor 的 sharding annotation 表达,而不是由模型作者到处手插 collective。sharded einsum 根据输入、权重和输出的布局推断必要通信。
在计算刚需要时推导 AllGather,恢复局部 einsum 所需数据。
推导 ReduceScatter,将归约与目标输出布局结合。
显式写出 collective;不能仅从 einsum 局部关系推出。
AllToAll 将 expert parallel 轴在 token group 与 expert 维之间来回移动。
来源:PDF 第 4 页,Distribution is expressed as sharding annotations。
从 Op 成本到墙钟时间:快慢两种模式
| 维度 | Fast:分析汇总 | Slow:模调度 |
|---|---|---|
| 目标 | 快速扫描数千设计点 | 分析少量候选点的细粒度 schedule |
| 基本方法 | leaf 成本乘以所有外层 trip count | 把循环 body 软件流水化进 RRT |
| 重叠处理 | 用 roofline 风格的可组合分析变换估计 | 显式考虑依赖和各资源容量 |
| 核心输出 | 闭式时间表达式 / 上下界 | 达到的启动间隔 II,再递归向上汇总 |
| 优势 | 速度快、适合大规模参数 sweep | 细节强、能看到资源争用与流水化 |
| 代价 | 对时序和冲突做了粗化 | 计算更慢,不适合穷举全空间 |
RRT 怎样工作
Resource Reservation Table 的行是资源(例如 MXU、VMEM、ICI),列是 cycle,单元格是该周期占用数量。Modulo Scheduling 寻找可重复启动循环迭代的最小可行间隔 II,同时满足数据依赖与资源容量,然后把该结果作为父级 Op 的成本继续向上汇总。
符号传播为什么重要
batch、序列长度、带宽、mesh 轴、数据类型宽度都保持为 SymPy 自由变量。构图一次,只有在设计空间边界才做数值替换。因此:
- 同一模型结构可以服务大量参数 sweep。
- 文档可直接声明期望的闭式成本表达式。
- 测试不仅比较数值,还能比较符号化简后的结构或等价性。
来源:PDF 第 4 页,Rolling up the Op hierarchy 与 Symbolic propagation。
论文给出的证据,以及没有给出的证据
| 结论 | 证据强度 | 正确解读 |
|---|---|---|
| 全库可由设计文档再生成 | 作者工程实践 | SMART 约 50 份文档、约 9,000 行规格 prose,按版本全量再生成 |
| 再生成成本可接受 | 单项目观测 | 约 1.5-3 小时、约 $100;依赖当时模型、工具和项目规模 |
| 生成实现可保持语义一致 | 参考模型对账 | 包括 DeepSeek-V3 on TPU pod slice 的人工参考模型,复现到舍入误差 |
| 性能预测准确 | PDF 未充分展示 | 没有给出系统性的真实硬件测量表、误差分布或跨硬件验证 |
| 方法优于增量维护 | 概念论证 + 经验 | 没有消融实验或长期对照组来量化维护成本差异 |
| 可独立复现 | 信息不足 | 论文中没有提供明确公开代码仓库链接或完整生成提示词、测试资产 |
来源:PDF Abstract、Section 2、Section 4。上述“未给出”基于所附 5 页 PDF 的内容范围。
可落地的工程复现方案
不要第一天就删除生产代码。更稳妥的验证方式是选择一个边界清晰、可精确对账的模块,建立“规格 → 隔离生成 → 验证 → 发布”的旁路,再根据结果扩大范围。
选择 3-5 个依赖清晰的模块;要求有确定输入输出、可建立 oracle,且不存在隐式线上状态。
为每个模块写自包含文档、符号/单位表、逐步示例、边界条件和精确对账锚点。
让只读 Agent 输出候选依赖边,同时用静态 lint 阻止循环依赖、未声明符号和跨层导入。
每次在空目录或隔离 worktree 中生成;子 Agent 只获得当前 doc、已验收依赖接口与项目规则。
依次执行格式/类型检查、单元测试、锚点测试、差分参考模型、安全扫描和性能预算。
全部通过后才整体替换构建产物;失败时保留上一版,并把歧义记录到对应设计文档。
建议的硬门禁
- 禁止生成任务读取上一版
generated/,避免旧实现成为隐式输入。 - 依赖接口必须先验收再暴露给下游;同一波次只能依赖更早波次。
- 所有数值模块至少包含一个符号对账和一个具体数值对账。
- 关键路径采用多样化验证:测试生成者与代码生成者使用不同提示上下文或独立 oracle。
- 发布采用全有或全无;不得把局部通过模块拼进生产分支。
工程方案依据论文工作流扩展而来;目录、门禁和分阶段试点属于可执行的工程推导,不是论文原样规定。
适合 Agent 再生成的设计文档模板
# 模块名称 ## 1. 目标与非目标 - 必须实现什么 - 明确不负责什么 ## 2. 公开接口 - 输入 / 输出 / 类型 / shape / 单位 - 错误与边界行为 ## 3. 依赖与不变量 - 允许依赖的上游文档 - 跨模块必须始终成立的条件 ## 4. 语义步骤 1. 从输入开始逐步执行 2. 写出每一步中间 shape、值、资源动作 3. 给出最终符号表达式 ## 5. Worked Example - 固定一组小输入 - 完整列出中间结果 - 写出唯一正确的最终输出 ## 6. 反例与参数保护 - 非法输入 - 容易混淆的情况 - 应抛出的错误 ## 7. Reconciliation Anchor - preset - exact_expected_output - symbolic_equivalence_rule ## 8. 验收条件 - 类型 / 单元 / 属性 / 差分 / 性能 / 安全测试
哪些项目适合,哪些项目要谨慎
| 场景 | 适配度 | 原因 |
|---|---|---|
| 性能模型、编译器 IR、规则引擎 | 高 | 语义可形式化,容易建立符号或差分 oracle |
| SDK、协议适配层、代码生成器 | 中高 | 接口明确,但要处理兼容矩阵和真实端到端测试 |
| 普通业务 CRUD 模块 | 中 | 可以规格化,但全量生成的收益未必覆盖验证成本 |
| 长期有状态服务与数据迁移 | 低 | 历史状态本身就是事实,不能通过删除旧代码归零 |
| 极致手工优化 kernel | 低到中 | 隐性硬件知识和性能技巧难完整写入自然语言 |
| 高合规、安全关键系统 | 谨慎 | 需要稳定可追溯构建、供应链控制和独立验证证据 |
六类主要风险
文档是事实来源后,错误会被一致地传播到所有生成实现。需要独立 oracle,而不是只测试“是否符合文档”。
模型、温度、工具版本变化可能产生不同代码。需要锁定环境、保留生成清单并做行为哈希。
机器发现的 DAG 可能漏边或错边。应用架构层级规则、循环检测和接口访问审计约束它。
如果代码与测试由相同上下文生成,二者可能一起误解。关键锚点应由人工或独立来源维护。
全量生成扩大依赖注入、危险默认值和许可漂移风险。生成后仍需 SAST、依赖和许可证检查。
规模增加后,重建时间和 Token 成本可能超线性增长。需要缓存已验收接口、并行波次与模型路由。
建议学习路径:从读懂到做出最小原型
用自己的话解释 S → G → C、增量生成债务、为什么旧代码不能进入生成上下文。
实现最小 Tensor / Op / OpCost / RRT 数据结构,并构造一个两层递归 Op 树。
用 SymPy 写 matmul、内存搬运和 AllGather 成本,验证代入不同 batch/带宽时无需重建图。
用 decorator/context manager 记录 block、map loop、reduce loop 和 leaf。
选择 Flash Attention 的一个子模块,补齐逐步示例、闭式表达式与 reconciliation anchor。
从空目录生成实现;自动运行 lint、单测、符号等价和参考差分。
只改规格不改生成代码,观察失败能否通过文档澄清被永久修复,并记录成本、时间和失败类型。
术语速查与资料
| 术语 | 在这篇论文中的含义 |
|---|---|
| Durable artifact | 跨版本长期保存、由人直接维护的事实来源;在 SMART 中是设计文档 |
| Build product | 可由规格重新产生的实现产物;在 SMART 中是 generated library |
| Reconciliation anchor | 文档末尾的小型 preset 与精确期望结果,对应生成测试的黄金锚点 |
| RRT | 资源预留表:描述每个周期对各类硬件资源的占用 |
| Modulo scheduling | 让循环不同迭代重叠执行,并求满足依赖/容量的启动间隔 II |
| Symbolic propagation | 成本表达式携带自由变量向上汇总,直到设计点边界才做数值替换 |
| Roofline-style bound | 用计算吞吐与数据移动瓶颈估计性能上界/时间下界的分析方式 |
- 论文页面:Design Docs Are All You Need(arXiv)
- 论文 PDF(arXiv)
- 背景概念:In-context Learning、TPU v4、SymPy、Modulo Scheduling、Roofline Model;对应文献见论文 References。