8. 训练与部署:三个阶段、两个口径、三个时钟域
8.1 三个阶段,不是一个
前七章把结构拆完了。结构里有两种机制是报告选择放到后训练才引入的:FP4 的缓存和 HSI 的候选池。它们在原理上并非不能从头训练,只是稳定性、成本或效果上的理由让报告没有这么做;而引入时机本身就是设计的一部分。
- 预训练:从头就用稀疏注意力,先在 64K 序列上训,到 34T token 时扩到 1M。此时没有 HSI——Reindex 层在全历史里选 Top-K,没有候选池限制。主 KV 也还是高精度。
- DSpark 的专门阶段:预训练之后单独训草稿模型,主干冻结。理由是草稿模型要匹配"当前策略"的分布,和主干一起从头训会互相拖。
- 后训练:同时引入三样东西——HSI(候选池限制)、FP4 的主 KV(QAT)、以及继续训练 DSpark(梯度不回传主干);此外还模拟一遍 SWA bounded replay,让模型适应它。
为什么 HSI 和 FP4 都放后训练?因为它们都是改变推理时的数值/搜索域的操作。训练时若用"完整历史 + 高精度",上线时换成"候选池 + FP4",模型面对的就是一个它没见过的输入分布。放在后训练、且训练与推理施加完全相同的限制,才叫"训练感知"(training-aware)。
8.2 Indexer 怎么训:报告没说,这一节是外推
先说结论:报告没有披露 Indexer 的训练目标。它只在 2.4.4 提到 V4 起就对 Indexer 的 Q/K 用了 FP4 QAT,没有给出任何损失函数、教师信号或系数。所以这一节不是报告内容,而是按上一代 DSA 的公开做法做的外推;只想了解本报告的人可以直接跳到 8.3。
外推的出发点是:Top-K 是离散的,几乎处处零梯度,所以主任务梯度到不了 Indexer,它必须有一条自己的监督信号。最自然的写法是让 Indexer 的打分分布去拟合主注意力的权重分布:
\[ \mathcal L=\mathcal L_{\text{task}}(\theta) +\lambda_I\sum_{\ell\in\mathcal I}\frac{1}{|\mathcal Q_\ell|} \sum_{t\in\mathcal Q_\ell} \] \[ D_{\mathrm{KL}}\!\Big(\operatorname{sg}\big[\mathbf p^{(\ell)}_{t,\mathcal U}\big] \,\Big\|\,\mathbf q^{I,(\ell)}_{t,\mathcal U}\Big), \]其中 \(\operatorname{sg}[\cdot]\) 是停止梯度:教师(主注意力)被固定,只有学生(Indexer)被推着走。\(\mathcal U\) 是明确指定的监督支持集,\(\mathcal Q_\ell\) 是该层有有效监督的查询集合,\(\lambda_I\) 是辅助系数,\(\mathcal I\) 是要监督的那几层(对应 8 个索引来源层)。
这条式子解释了三个设计:
- 为什么按层分组、按查询平均:每层的 Indexer 是独立参数,不同层的候选分布不同,不能混在一起算;按查询平均是为了让长序列不主导梯度。
- 为什么不直接用 Top-K 的重合率当损失:Top-K 不可微,重合率是离散计数;KL 拟合的是连续分数,梯度能回传。
- \(\lambda_I\) 要按损失尺度调:Indexer 只是"选谁"的代理,不能替代主任务。系数该多大,取决于 KL 项与任务项怎么归一化、Indexer 与主干参数怎么耦合;如果 Indexer 的输入也停止梯度、和主干完全隔离,它并不直接"抢"主任务的梯度。报告没有公开取值,别把"必须很小"当成结论。
再强调一次:\(\lambda_I\)、监督支持集 \(\mathcal U\)、是否对 Full 与 Reindex 层分别监督,报告一个字都没写。上面只是"如果要这么做,长什么样"的模板,不能当作报告的设定引用。
8.3 FP4 的 QAT:为什么必须训练
第 4.5 节算了这个 FP4 格式的动态范围够(上界 2688,实测最大约 10),但"不溢出"不等于"白送"。把一个 512 维向量截断到 4 bit 会引入量化误差,而注意力对键值的相对大小敏感。所以需要一个量化感知训练(QAT)阶段:前向用"假装量化过"的值,反向按直通估计器(STE)把梯度原样传回去,让模型学会把信息放到量化后仍能分辨的位置上。
报告给的两个工程细节:
- 在 RoPE 之后量化。报告说 RoPE 之前量化只有边际提升,却会给解码带来额外开销,所以选了"旋转后量化";注意它没说"多一次显存读写"——那是实现层面的问题,DeepSeek-V4 的 FlashMLA 就是把 RoPE、注意力、反量化融在一个 kernel 里的例子(报告 3.2 节)。
- 滑动窗口的 KV 保持 FP8。128 条本地窗口是精确的局部信息,且总量是常数(不随上下文增长),没必要冒这个险;它也是 Reuse 层唯一"新鲜生成"的顶点(第 5.7 节),精度更关键。
8.4 部署:两个口径
报告给了两个不同的倍数,很多人读的时候会混:运行时约 1/4、持久化约 1/8(都以 V4-Flash 为分母)。报告在 3.2.1 明确给出了这个差额的来源,是两个相乘的因子:
\[ \frac{1}{8} =\underbrace{\frac{1}{2}}_{\text{持久化不再存 SWA KV}} \times \underbrace{\frac{1}{4}}_{\text{全局 KV 的压缩}} . \]- 运行时:HBM 里常驻的缓存 = 全局 KV(890 B/token)+ 每层固定大小的 SWA KV。1M 上下文里全局部分约 0.89 GB,SWA 部分与上下文无关。
- 持久化:为了前缀复用而写到主机内存/SSD 的那份。它只存全局 KV,不再存 SWA KV——在 V4 的部署里,SWA KV 几乎占了持久化缓存容量的一半,所以这一项单独就省掉约 1/2。
于是 1/4(架构 + 精度)再乘 1/2(去掉 SWA KV 的持久化),就是 1/8。SWA KV 改为放进一个从每台机器 10% 主机内存划出的分布式内存池,TTL 只有几分钟,靠高周转服务活跃会话;真正缺失时用 3.2.2 的 Encoder SWA Bounded Replay 补,只重放 128 个位置,而不是 \(L\times n_{\text{win}}\)。这就是"为什么持久化比运行时更省"的完整解释,不需要再猜。
三个和"存得下"直接相关的工程点:
- 分层:HBM → 主机内存 → SSD,按访问频率排;长期不用的前缀直接沉到 SSD,命中时再搬回来。
- 确定性寻址让预取可行:Engram 的 n-gram 行号由 token 元组唯一确定(第 6.4 节),所以可以在第一个 Transformer 块计算的同时,用后台 RDMA 把嵌入从主机内存拉过来——传输与计算可以重叠,从而把一部分延迟挡在关键路径之外(不保证完全隐藏)。
- 压缩的直接经济意义:890 B/token 意味着 1M 上下文的持久化副本不到 1 GB。在卡容量、权重占用与并行分片都固定的前提下,能同时驻留的前缀数量会随每 token 字节数成比例变化,于是从"几十个"变成"几百个"——这是容量与带宽上的收益,可能进一步转化为吞吐。
8.5 一个类比:三种模式就是三种缓存状态
这一节的框架是本篇的类比,报告里没有 AGU / TLB / 三级缓存这套语言;报告 3.2 讲的其实是持久化缓存管理与两级 replay。把它当"帮助记忆的比喻"用,不要当报告结论引。
把那层 CSA2 当成一台小计算机来读,三个部件各归其位:
执行单元 新算的 Q 与本地 KV 片上缓存 主 KV / Indexer K / Top-K 大容量后端 MoE 与 FFN
关键对应是"什么是可缓存状态":Q 与本地 KV 永远是新鲜操作数(像每条指令新取的寄存器值),不进共享缓存;主 KV、Indexer K、Top-K 索引是可缓存状态,是否重算取决于命中与否。于是三种模式的读写模式就是一张缓存访问表:
模式 主 KV Indexer K Top-K Full 写 写 算 Reindex 读 读 重算 Reuse 读 不访问 读
- Full = 写回填充(write-allocate):执行单元算出全部可缓存状态并写入,代价最高。
- Reindex = 数据命中、地址重算:缓存行常驻,只重跑一次"地址翻译"。
- Reuse = 全命中:数据与地址都在,执行单元只用新操作数做一次读出,最接近纯 load。
Indexer 在这套语言里是地址生成单元 + TLB:它不搬数据,只产出"读哪些条目"的地址集合。Full 层让地址单元全速跑一遍;Reindex 只在受限范围内重新翻译;Reuse 直接把上次的地址向量拿来用——等于对最贵的地址计算做了一次公共子表达式消除。
HSI 的候选池则是一张工作集约束:Decoder 的第一个 Full 层(第 21 层)选出 2048 块共 16384 个位置,等于为后续层立了一张"只能在页内寻址"的页表。两级 Top-K(先块后位置)就是两级页表:先用块级最大分选页,再在页内选具体条目。这也解释了为什么"池外位置根本不可寻址"——不是"分数低",是地址空间里没有它。
8.6 三个刷新次数:4 / 8 / 40
三种可刷新状态沿深度的刷新次数依次增加,可以读作三层嵌套的调度;"三个时钟域"是本篇的说法,报告没有用这个词(它讲的是 HBM / 主机内存 / SSD 这一套真实的存储层次):
\[ \underbrace{\text{Content } C}_{\text{刷新 }4\text{ 次}} \ \supset\ \underbrace{\text{Address } S}_{\text{刷新 }8\text{ 次}} \] \[ \supset\ \underbrace{\text{Query } q}_{\text{刷新 }40\text{ 次}} . \]- 内容 \(C\):4 次。只有 4 个来源层(第 3、9、15、21 层)生产全局主 KV 与 Indexer K(第 4.3 节)。最慢、最贵:prefill 时 Full 层要扫全部可见历史;decode 时只为新位置追加条目,不必重扫历史。
- 地址 \(S\):8 次。8 个索引来源层(第 3、9、15、21、25、29、33、37 层)重新选 Top-K。中等——它在候选池里打分。
- 查询 \(q\):40 次。每一层都算自己的 Q。最快、最便宜——只是当前隐状态的本层投影。
这三个数不是"时钟频率":4 → 8 是 2 倍,8 → 40 是 5 倍,并不都是"差一个数量级"。它们是沿深度的刷新次数,读作三层嵌套调度的节奏,而不是三个独立的周期。
越靠近执行单元的状态,刷新越快、越便宜;越靠近共享后端的状态,刷新越慢、越贵。CSA2 的模式调度说白了就是把每类状态放到与它重算成本匹配的刷新率上:如果每层都刷内容(40 次),成本爆炸;如果 40 层都复用同一份地址(1 次),召回就退化成"全模型一次检索",第 5.6 节的三个缺口会同时出现。
整篇报告的结构可以压成一行:4 份内容 × 8 份地址 × 40 份查询。读的时候记住这是嵌套的——一份内容被若干层共用,其中某些层各刷地址,所有层各刷查询——不是三个可以自由组合的维度。
8.7 容易算错的地方
- 用 1/8 去算显存。1/8 是持久化口径;显存里常驻的是运行时那份,约 1/4(890 B/token)。
- 以为 HSI 在预训练里就有。预训练是全历史选择;候选池是后训练才加的,训推同构是它的前提。
- 把"4 次内容刷新"读成"只有 4 层有 KV"。是所有层都读,只有 4 层写;Reuse 层读的正是这 4 份。
- 把 Indexer 的损失当成报告内容。报告完全没写 Indexer 怎么训(只提到对它的 Q/K 做了 FP4 QAT);8.2 节整节是外推模板,不是报告设定。
- 把 1/8 拆错。它 = 1/2(持久化不再存 SWA KV)× 1/4(全局 KV 压缩),不是"窗口占一半"。
- 把 FP4 当成推理期的矩阵乘格式。它只用于存储,计算前反量化(第 4.5 节)。