Records the M2d lever (batch the GRPO training-side forwards), the right-pad-is-free insight, both exact gates, the end-to-end no-OOM smoke, and the 9× throughput. The honest decomposition correction: M2c claimed the training forwards "dominate" the step; the clean per-component bench falsifies the strong form — they were ~2.5 s of the ~8.5 s step (~30%), worth the 9×, but the rollout (~6 s) was always the larger share. After M2d the step is ~95% rollout, so the next step-level lever is full B×G rollout batching (today only the G samples of each prompt decode in lockstep; the B prompts are still sequential). Same measure-first lesson, once more. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
33 KiB
xtrain 演进总览 — 按维度记录每次变化
每个里程碑(T# 基建 phase 或 v# 训练 run)在四个维度上分别改了什么、结果如何。
这是活文档:每次新 run 收尾时追加一行/一段。细节见各 docs/runs/0N-*.md、各 phase 设计文档、docs/known-issues.md。
四个维度:算法(autograd/优化器/精度/反向数学)· 模型架构(dim/层/头/算子)· Infra(构建/显存/并行/吞吐)· 数据集(语料/token/epoch/tokenizer)。
一、基建 phase(T1–T13 + Phase 2 systems-depth)—— 主要动「算法」与「Infra」
| Phase | 维度 | 变化 | 结果 / 验证 |
|---|---|---|---|
| T1 | Infra | Rust↔CUDA FFI 构建链(build.rs+nvcc, no_cuda cfg),gitea↔dash5 流 |
vector-add 跑通 |
| T2 | Infra | Tensor 抽象(dtype/shape/Storage, H↔D 拷贝)+ elementwise kernel | roundtrip 保真 |
| T3 | 算法 | 手写 tiled GEMM fwd/bwd + finite-diff 梯度检查 harness | fwd vs cuBLAS 1e-7;bwd vs finite-diff |
| T4 | 算法 | tape autograd 引擎 + 11 算子 backward(含梯度扇出累加);attention 由 matmul+softmax 组合 | 每算子 finite-diff ≤2e-2 |
| T5 | 模型架构 | 组装 tiny decoder(RoPE+RMSNorm+SwiGLU)+ embedding/reshape/transpose 算子 | overfit 27/27 + PyTorch 对拍 B>1 |
| T6 | 算法 + 数据集 | 手写 AdamW + 训练 loop + LR sched + grad clip + checkpoint;gpt2 BPE + TinyStories | 真训出连贯英文 |
| T7 | Infra | cuBLAS matmul + GPU 端 AdamW/grad-norm + 去 per-op sync | ~3×:2.7K→8.5K tok/s,零回归 |
| T8 | Infra | NCCL DDP(单进程 thread-per-GPU)+ 梯度 all-reduce | 多卡(当时弱扩展 ~1.4×) |
| T9 | 算法/模型架构 + Infra | 加 per-head QK-norm(Qwen3 兼容);safetensors 导出 | xserv 闭环:贪心逐 token 一致 |
| T10 | 算法/Infra | batched 多序列 forward(linears flatten [B·S,dim] + fused batched SDPA + 每序列 RoPE) |
单卡 15–24×;MFU 0.4%→14%(修 KI-1) |
| T11 | Infra | device caching/pool allocator(复用 op 输出显存,消 per-step cudaMalloc) | 单卡 2.3×;8卡 461K tok/s 近线性(修 KI-5) |
| T12 | 算法/Infra | bf16 混合精度(fp32 master,cuBLAS GemmEx,norm/softmax/CE 保 fp32) | dim768 OOM 解除,−29% 显存/+13% tok/s(修 KI-2) |
| T13 | 算法/Infra | 激活重计算(per-block gradient checkpointing:前向 no-tape + 反向重算,backward_seeded) |
梯度对非重计算版逐位一致(0.00);dim768 31.1→14.6GB;dim1024 batch32 OOM→16.6GB 装下(修 KI-3,解锁 v8) |
| T14 | 算法/Infra | 融合 flash-attention kernel(手写单 kernel:online softmax、tiled over KV、不物化 N×N scores;flash 式 bwd:重算 scores + D=ΣdO·O 化简雅可比 + dQ/dK/dV);opt-in --flash,默认保 composed(Phase 2) |
fwd 对 composed 6.7e-5、bwd 对 composed dQ 1.7e-5、PyTorch B>1 7.9e-6、flash==composed loss rel 0.0;峰值显存 −16%@seq1024 / −23%@seq2048(不物化 N×N,收益随 seq 增长);tok/s ~2.3–2.8× 慢(hd=64 小头维干不过 cuBLAS tensor-core,flash 已知权衡=胜场在显存);md5 闭环逐位一致 |
| T15 | 模型架构 | 真 GQA(num_kv_heads<num_heads:wk/wv 投影到 kv_dim,新 repeat_kv broadcast 算子把 K/V 复制 group=nh/num_kv 份喂给未改动的 composed/flash 两条 SDPA;分组约定对齐 xserv repeat_kv dst=kvh·group+r);repeat_kv 反向=组内 group 行确定性求和(无 atomic)→ 多组 q 头梯度汇一个 kv 头;num_kv_heads 进 Config(默认=nh→MHA)、--kv-heads flag、导出写真 num_key_value_heads(Phase 2) |
repeat_kv grad-check 2.1e-4(group3)+group1 identity 逐位;GQA flash==composed fp32 grad 4.1e-5/bf16 在带;group1 对 MHA 逐位一致(回归保护);PyTorch GQA B>1 对拍 composed/flash 各 loss 1.7e-8/logits 2.3e-5/25 grad 进 rtol;小 GQA(8h/2kv) 训 600 步 10.9→3.15 连贯;xserv 闭环真 GQA(num_kv 2<8):2/3 prompt token-identical、1 在 BF16 漂移处晚分叉;MHA 默认 export md5 逐位一致(b04fc9f9) |
| T16 | 算法/Infra | 梯度累积(N 个 micro-step:每个 micro-loss ×1/N 再 backward,tape SUM 累加 → 一次 AdamW step+zero;--accum-steps);DDP 只在累积边界 all-reduce(中间 micro-step 不发 NCCL,/world 与 1/N 正交);显存随 micro 不随有效 batch |
等效大 batch逐位贴合(loss rel 8.5e-8、grad rel 3.8e-5);accum=1 逐位回归(0.00);DDP+accum 对单卡 loss 5.7e-7/跨 rank 一致;显存平:同有效 batch 64,big-batch 27.7GB→accum(4×16) 7.2GB(−74%)(big-batch OOM 而 accum 装下);全回归+xserv 闭环 md5 一致 |
| T18 | 算法 | dropout(手写 counter-based 设备 RNG → Bernoulli mask,训练 inverted 1/(1-p) scaling、eval 恒等);新 autodiff dropout 算子(fwd 生成+施加 mask,bwd 用同 mask),接 residual/ffn 两处;--dropout flag 默认 0 |
固定 seed grad-check 过;E[out]≈input + keep≈1-p;p=0 与无 dropout 逐位一致;recompute(T13) 组合下梯度仍逐位一致(counter-based seed 重算复现同 mask);全回归 + xserv 闭环绿(导出/推理 dropout 关) |
| T17 | Infra | process-per-GPU(torchrun 式:launch_processes 每卡 spawn 一个 worker 进程=独立 CUDA context;launcher 一次性铸 ncclUniqueId 后 hex 编码注入子进程 env——无共享 FS/TCP、无竞态;worker 读 env→bind device→DdpContext::init+build_model+train_rank 全复用 T8 零改动;新 train_ddp_mp bin/ddp_proc test,保留 thread-per-GPU 旧路径);scope=process-per-GPU only(ZeRO-1 用户 drop)(Phase 2) |
正确性全绿:proc vs 单卡 loss 5.67e-7、proc vs thread-per-GPU 1.5e-7、跨 rank 1.19e-7(<1e-6)、全回归+xserv 闭环 md5 逐位一致 b04fc9f9。⚠️关键发现(实测证伪原假设):本尺度 process-per-GPU 对吞吐中性——thread vs proc @ {1,2,4,8} = {1.00/1.61/2.98/5.27}× vs {1.00/1.60/2.94/5.31}×(差<1% 噪声内);8 卡全 95–99% util ⇒ 残留 ~5.3×@8 非线性是 NCCL all-reduce + 本机 PCIe 拓扑墙,非单 CUDA context 串行(KI-5/T11 doc 的猜想被钉死推翻,方法论同 T11 证伪「分桶 all-reduce」)。净价值=落地 torchrun 式标准链路 + 把误导性 backlog 项实测关闭;默认训练路径不变 |
| T21 | Infra | DDP-dropout wiring fix(V9-PILOT 暴露:T18 只把 dropout 接进单卡 train.rs,train_ddp bin 无 --dropout flag、train_rank 从不调 model.train() → DDP 下 dropout 被静默忽略。补:--dropout flag + train_rank 每步 model.train(),镜像单卡 train/eval 纪律——eval_loss 翻 eval 后由每步 train() restore);加 DDP-dropout 回归测试堵缺口 |
DDP-dropout 回归测试绿:p>0 下 dropout live(loss 轨迹对 p=0 有可观差异,pre-T21 会逐位相同)、p=0 对无 dropout 路径逐位一致、run 后 is_training()==true;既有 DDP loss-match/跨 rank 测试不变。元教训:op/单卡单元测试漏掉 launcher 级 integration gap,只有真实启动器端到端跑(pilot)才暴露 |
二、Scaling runs(v0–v10)—— 主要动「模型架构」与「数据集」
架构始终是 Qwen3-style(RoPE + RMSNorm + QK-norm + SwiGLU,gpt2 50257 词表),逐版放大 dim/层/头(v8 起首次拨容量轴到 dim1024,v9 进入 dim1280+真 GQA 双轴点,v10 固定架构只补数据轴);其余维度逐版变化如下:
| ver | 模型架构(dim/层/头·hd · 核心/总参) | 数据集(语料 · 实训 token · epoch) | 算法/精度 | Infra(GPU · 吞吐) | 结果(val · 备注) |
|---|---|---|---|---|---|
| v0 | dim32/4L/2h · 41K/3.26M | TinyStories 3MB 切片 · ~0.72M · — | fp32 单序列 | 1 GPU | val 3.80(toy,不可用) |
| v1 | dim256/8L/8h · 8.4M/34M | TinyStories 全量 · 5.1M · 0.01ep | fp32 单序列 | 1 GPU · 3.3K | val 2.58 |
| v2 | dim384/12L/12h · 28M/67M | TinyStories · 37M · 0.08ep | fp32 单序列 | 4 GPU DDP · 3.6K | val 1.71(暴露 KI-1/弱扩展) |
| v3 | dim512/16L/16h · 67M/119M | TinyStories · 246M · 0.53ep | fp32 batched(T10) | 1 GPU · 26K | val 1.30 |
| v4 | dim768/18L/24h · 127M/205M | TinyStories · 721M · 1.54ep | fp32 batched | 8 GPU(T11) · 145K | val 1.17(仍欠拟合) |
| v5 | dim768/18L(同 v4) | TinyStories · 2.49B · 5.33ep | bf16(T12) | 8 GPU · 217K | val 1.11:⚠️TinyStories 饱和(3.5×数据仅↓5%) |
| v6 | dim768/18L(同 v4/v5) | FineWeb-edu 真实网页 · 2.29B · 1.02ep | bf16 | 8 GPU · 218K | val 3.07:⚠️FineWeb 留出集,与 v0–v5 不可比(真实网页熵高,~3.0 是预期);判据=采样质量+transfer。第一版脱离 TinyStories,语言种类质变(小故事→真实说明文);transfer→TinyStories val 2.75(v5 native 1.11),纯通用数据对窄分布有代价;val 末步仍单调降=未饱和 |
| v7 | dim768/18L(同 v4/v5/v6) | 同 v6 的 FineWeb-edu 子集(非新数据)· 3.28B · 1.45ep | bf16 | 8 GPU · 218K | val 3.01(与 v6 可比):⚠️同子集多 epoch 近天花板——唯一变量=epoch(1.02→1.45),多喂 ~1B token val 仅 ↓0.05 且 ~step44000 后走平、采样无质变。与 v5 的 TinyStories 数据量饱和同类(重复老数据边际薄);真·更多数据要新 shards |
| v8 | dim1024/18L/32h · 226M/329M(+78% 容量,ffn 2730) | 同 v6/v7 的 FineWeb-edu 子集(非新数据)· 2.36B · 1.05ep | bf16 + 激活重计算(T13) | 8 GPU · 129K(重算税) | val 2.98(与 v6/v7 可比):⭐容量轴 A/B——容量有用:唯一变量=dim768→dim1024,同 ~1ep v6 3.07→2.98(↓0.085),且 v8(1.05ep) < v7(1.45ep 更多老数据) 3.01 ⇒ 放大容量 > 重复老数据 ⇒ v6/v7 部分 capacity-limited。⚠️但增益仅 ~3%、val 末步仍在降未饱和 ⇒ 单轴(数据/容量)单步都已 ~3%/lever = 全面边际递减,要双轴一起 scale(Chinchilla) |
| v9 | dim1280/18L/40h/10kv GQA · 357M/486M(ffn 4096) | FineWeb-edu 扩展 shards 000-009 · 6.01B · ~1.00ep | bf16 + recompute + flash + grad-accum + true GQA | 8 GPU · 78.6K(21.25h) | val 2.8854(与 v6-v8 可比):✅双轴 Chinchilla 点有效——容量从 v8 226M→357M,同时数据从 2.255B 子集→6.013B token,best val 比 v8 再降 0.0947 (~3.2%)。采样写真实说明文更稳一些,但 greedy 重复仍明显;收益仍是稳健增量而非质变 |
| v10 | 同 v9 | FineWeb-edu 扩展 shards 000-010 · 6.765B · ~1.00ep | bf16 + recompute + flash + grad-accum + true GQA | 8 GPU · 79.0K(23.86h) | moving-tail val 2.8816;固定 eval v1 上 v9 2.9278→v10 2.8814。结论:补 shard010 对新分布有效,但只补数据轴不解决 greedy 重复;后续应固定 eval set,并优先试更大模型+长 context |
实训 token = steps×batch×seq(非数据集大小)。v0–v5 的 val 是同一 1M-token TinyStories 留出集。v6 起换 FineWeb-edu, 且 v9/v10 追加新 shards 会移动默认 tail-heldout;严格横比改用 fixed eval v1(shard010 tail 1M): v6/v7/v8/v9/v10 = 3.2328 / 3.1850 / 3.1515 / 2.9278 / 2.8814。
三、各维度的累积演进(轴向看一条线怎么走的)
- 算法:手写 autograd(tape)+扇出累加 → AdamW/LR-sched/grad-clip → +QK-norm(Qwen3) → batched forward → bf16 混合精度(fp32 master) → 激活重计算(T13) → 融合 flash-attention(T14,online softmax + flash 式 bwd) → 梯度累积(T16,复用 tape SUM,等效大 batch 而显存随 micro) → dropout(T18,counter-based 设备 RNG + inverted scaling,train/eval 切换)。
- 模型架构:固定 Qwen3-style;dim 32→256→384→512→768→1024→1280(v8 首拨容量轴,v9 进入 dim1280);核心参数 41K→357M(总 3.26M→486M)。+QK-norm(T9,Qwen3 兼容) → 真 GQA(T15,
num_kv_heads<num_heads,repeat_kv broadcast + 组内梯度求和;默认=nh→MHA 逐位回归;v9 用 40 query / 10 kv)——架构补齐到现代 LLM 标配(MHA/GQA/MQA 一条num_kv_heads轴),两条 SDPA(composed/flash) 共用同一 broadcast,导出真num_key_value_heads且 xserv 闭环。 - Infra:单卡 fp32 → cuBLAS/GPU-optim(T7) → NCCL DDP(T8) → batched forward(T10) → caching allocator(T11) → bf16(T12) → 激活重计算(T13,解锁 dim1024) → flash-attention(T14,不物化 N×N,attention 显存收益随 seq 增长) → 梯度累积(T16,DDP 只在累积边界通信,显存随 micro 不随有效 batch) → process-per-GPU(T17,torchrun 式独立进程/CUDA context,复用 T8 train_rank 零改动)。吞吐 3.3K→217K tok/s(dim768 bf16),dim1024+重算 ~129K(重算税);MFU 0.4%→17%(每次提升都对应一块 perf 基建,详见 known-issues + MFU 分析)。T13/T14/T16 是三条显存杠杆(重计算压激活峰值、flash 不物化 N×N attention scores、梯度累积解耦有效 batch 与激活显存),可叠加放大有效 batch。T17 实测=负结果记账:process-per-GPU 在本尺度对吞吐中性(thread ~5.27× vs proc ~5.31×@8,差<1% 噪声),8 卡全 95–99% util ⇒ 残留非线性是 NCCL/PCIe 通信墙、非单 context 串行——把 KI-5/T11 doc 长挂的「process-per-GPU 是残留串行的解」猜想实测钉死推翻(方法论同 T11 证伪「分桶 all-reduce」)。
- 数据集:TinyStories 3MB 切片 → 全量 TinyStories(epoch 0.01→5.33,至饱和)→ v6 毕业到 FineWeb-edu 真实网页(2.255B 语料,1.02ep)→ v7 同子集多 epoch(1.45ep,近顶)→ v8 同子集换大模型(dim1024,1.05ep)→ v9 扩新 FineWeb shards 到 6.013B token 并同步放大模型 → v10 补 shard010 到 6.765B token(只拨数据轴)。tokenizer 全程 gpt2 BPE(复用 xserv-tokenizer;保闭环优先,KI-4 接受)。
- v5→v6 数据轴的质变:v0–v5 都吃合成幼儿故事(TinyStories,低熵、词汇受控),v5 证明同尺寸模型在它上面已饱和;v6 第一版换成真实教育类网页文本(FineWeb-edu),语言种类发生质变——采样从「只会写小故事」变成「能写历史/科学/说明文」。
- ⚠️ 同子集多 epoch 也有天花板(v6→v7):v6 的 FineWeb val 才训 1.02ep、末步仍单调降,曾被读作「还没喂够」;v7 把同一 2.255B 子集喂到 1.45ep(多 ~1B token),FineWeb val 仅 ↓0.05(3.07→3.01)且 ~step44000 后走平、采样无质变 ⇒ 该子集在 dim768 已近天花板。这与 v5 的 TinyStories 数据量饱和是同一类现象:「重复喂老数据」边际都薄,无论是 v5 的同语料多 epoch 还是 v7 的同子集多 epoch。真正抬天花板的是 v6「换更广的新语料」那一步——杠杆在「更多样的新 token」,不在「同数据多读几遍」。后续要继续降 val,必须补新 FineWeb shards(更多样、不重复),不是同子集加 epoch。
- ⚠️ val 可比性:v0–v5 的 val 是同一 TinyStories 1M 留出集(彼此可比);v6 起换 FineWeb-edu 留出集,分布不同、val 不能和 v0–v5(~1.1)比大小——真实网页熵高,~3.0 是预期而非回退。v9/v10 追加 shards 后默认 tail-heldout 会移动,不能再只看 moving-tail best。为后续建立 fixed eval v1(shard010 tail 1M):v6/v7/v8/v9/v10 = 3.2328 / 3.1850 / 3.1515 / 2.9278 / 2.8814。
- ⭐ 容量轴有用,但也只有 ~3%(v8):v6/v7 在 dim768 上「吃不动更多数据」,v8 用最干净的 A/B 回答了「是数据见够还是容量不够」——冻结数据子集、纯把 dim768→dim1024(core 127M→226M,+78%),同 ~1 epoch 下 FineWeb val 3.07→2.98(↓0.085),且 v8(1.05ep)还低于 v7(1.45ep 更多老数据)的 3.01。⇒ 容量有用,v6/v7 部分是 capacity-limited(不全是数据见够);放大容量比「给小模型多喂老数据」更值。但增益只有 ~3%,与数据轴单步杠杆同量级。
- ✅ 双轴一起 scale 有效(v9):v9 把 v8 的提案落地:模型 core 226M→357M,数据 2.255B 子集→6.013B token(实训 6.012B),best FineWeb val 2.9801→2.8854,再降 0.0947 (~3.2%)。这确认 Chinchilla 式双轴方向正确;但收益仍是 ~3% 级稳健增量,greedy 重复仍在,说明小尺度下“更好 val”尚未完全转化成肉眼质变。
- 📌 只补数据轴边际有限(v10):v10 保持 v9 架构,仅补 shard010 到 6.765B token。fixed eval v1 上 v9 2.9278→v10 2.8814,说明新 shard 分布被学到;但 moving-tail best 只从 2.8854→2.8816,且 greedy 复读不变。下一步更值得改模型/context,而不是继续一片片补数据。
三·五、Phase 2 系统栈深度综合(T14–T18 五条特性按四维收束)
scaling 科学线(v0–v8)收官后,项目重启回到本职「学训练全栈」,把此前显式延后的五条训练栈特性补齐。区别于 Phase 1 的「修真实瓶颈」(T10–T13 每条都治一个 KI),Phase 2 是补齐标配 + 一次诚实的负结果。五条按四维落点:
- 算法三条 = flash-attention(T14) + 梯度累积(T16) + dropout(T18)。
- 三条里 T14/T16 与 Phase 1 的 T13 一起构成可叠加的「显存三杠杆」:T13 压激活峰值、T14 不物化 N×N attention scores(收益随 seq 增长)、T16 解耦有效 batch 与激活显存(显存随 micro 不随 N×)——三者正交叠加可放大有效 batch / seq。
- T18 dropout 的设计点是 stateless counter-based RNG:mask 由
(seed, 元素下标)无状态产出,所以与 T13 激活重计算天然 bit-exact 组合——反向重算时同 seed 重生同一张 mask,梯度逐位一致。这是两条 Phase-2/Phase-1 特性的正交性被正确性闸门钉死的一个例子。 - 诚实账:flash-attention 赢在显存不赢墙钟(hd=64 小头维手写 kernel ~2.3× 慢于 cuBLAS tensor-core),opt-in 默认关、不回归。
- 模型架构一条 = 真 GQA(T15):架构补齐到现代 LLM 标配(MHA/GQA/MQA 一条
num_kv_heads轴)。实现关键 =repeat_kvbroadcast 算子的反向组内确定性求和(无 atomic),让 K/V 零改动喂进 composed + flash 两条 SDPA;group=1对 MHA 逐位一致作回归保护,导出真num_key_value_heads且 xserv 闭环真 GQA。 - Infra一条 = process-per-GPU(T17),但它是实测负结果而非性能提升:落地 torchrun 式独立进程/CUDA context 标准链路(复用 T8 train_rank 零改动),却实测本尺度吞吐中性(thread ~5.27× vs proc ~5.31×@8,差<1%,8 卡全 95–99% util),把 KI-5/T11 doc 长挂的「共享单 context 致残留 ~5×@8」猜想钉死推翻——残留是 NCCL all-reduce + PCIe 拓扑墙,非 context 串行。方法论与 Phase 1 的 T11(证伪「分桶 all-reduce」)一脉相承:profile/measure-first。
- 数据集零条:Phase 2 不动数据轴(KI-4 小词表用户拍板 drop 以保 xserv gpt2-tokenizer 闭环,转记为接受的建模权衡,见 known-issues)。
Phase 2 的统一闸门 = 诚实正确性,全程未为凑绿放宽容差:flash==composed(grad/PyTorch)、GQA group=1 == MHA 逐位、accum=1 逐位、dropout p=0 逐位 + dropout×重算 bit-exact、每条特性默认路径不变、xserv 闭环 md5 b04fc9f9 两阶段全程逐位一致。
📌 两条 integration 发现(非回归,pre-existing,记账):① DDP 三个测试并行会争 2 卡 deadlock → 文档/测试用
--test-threads=1(或标 serial)跑。② fresh-train md5 run-to-run 不定——反向 atomicAdd 归约序非确定 → 有效的确定性闸门是导出(export)重确定性(同 ckpt 重导 safetensors md5 逐位一致),不是 fresh-train 复现。
三·六、Phase 3 后训练栈(SFT → KV-cache → DPO → GRPO,详见 18-post-training-rl-sft.md)
Phase 1/2 把预训练全栈学完后,Phase 3 转向后训练 infra(对齐方向)。锁定路线 DPO→GRPO(reward model 可选)、rule-based 可验证 reward 优先、KV-cache 增量解码引擎前置自建、任务取可验证算术(确定性 exact-match,给 RL 干净可证伪信号)。里程碑 M1(SFT baseline)→ M2(KV-cache 解码引擎,token-identical 闸门)→ M3(DPO)→ M4(GRPO)→ M5(可选 RM)。按维度落点:
- 算法:后训练损失族——SFT(assistant-only masking,已有)→ DPO(
seq_logprob算子 + Bradley-Terry/σ(Δ) 偏好损失,frozen reference)→ GRPO(group-relative advantage,无 critic + clipped PG + KL leash)。每条沿用 Phase 1/2 闸门规矩:新损失/算子有限差分 grad-check + PyTorch parity + 退化检查(β→0 / G=1 / ε→∞ / ref==policy)+ 一条可证伪「真在学」信号(reward margin↑ / 合成 RL overfit)。 - Infra:**KV-cache 增量解码引擎(M2,前置)**是这一阶段的硬核——per-layer K/V cache + 单 token 增量 forward(prompt 灌一次 cache 后逐 token 解码)+ ragged 批量解码。硬闸门 = 解码逐 token 等价于全重算 greedy(同 xserv 导出闭环的逐位纪律),并先记解码吞吐 baseline(profile-first)。它是 DPO 造对 + GRPO rollout 的共享底座。
- 数据集:可验证任务自带数据生成器——两操作数整数算术(
+ − ×),rule-based checker 读\boxed{}做 exact-match,是 M1 SFT 数据 + M3 造对 + M4 GRPO reward 的单一共享 spec。 - 模型架构:复用 v12 1.05B 基座,不动架构。
M1(SFT task baseline,已落地):可验证算术任务 + 数据生成器 + 评分器一套,host-side 9/9 单测过(masking、SFT-target 自洽 2000 样、parser 边界、种子确定性)。dash5 单卡从 v12 基座 SFT(loss 4.68→~0.34,best val 0.386)。100 留出题 eval:格式 \boxed{} 习得率 base 0% → SFT 100%;算术正确率 8%。——SFT 只买格式(0%→100% 干净落地),算术正确性是 base 模型本身弱项(如 46*80 框成 3380),正是 M3/M4 的可验证 reward 要去补的残差。一条诚实账:M1 用的是朴素无 KV-cache 采样器(每 token 全量 forward),100 题已经很慢——这正是 M2 解码引擎前置的动机。
M2a(KV-cache 增量解码引擎,单序列,已落地):两个 forward-only 原语 + 裸 Tensor 逐 token block forward,各自隔离闸门。rope_at(绝对位置 RoPE,新 kernel,不动训练 rope → 训练路径零风险)逐位等于全序列 rope 的对应行;decode_attention(单 query × cached-K/V,由现成 strided-gemm + 普通 softmax 组合,零新 kernel)等于全 causal attention 末行(max|Δ| 6e-8)。引擎 generate_greedy_cached 镜像 block_forward 在 Tensor 层(无 autograd tape,推理不需梯度),靠公开 params() 稳定顺序拿权重(零 model 可见性改动)。核心闸门 = token-identical:与朴素全重算贪心逐 token 一致(小 GQA 单测 + v12 1.05B 上 cached eval 与 naive 逐字节相同:format 100/100, correct 8/100)。吞吐 baseline(v12, batch1, F32,profile-first 实测)= cache 收益随序列长度而定:max_new 32 ≈ 持平(108 vs 111,短序列 launch 开销 bound)、128 ~1.9×(69 vs 133)、256 naive OOM vs cached 129 tok/s。cached 吞吐近恒定(O(1)/token + 恒定显存),naive 衰减(O(t)/token,O(seq²) 图 → OOM)。⇒ 短 eval prompt overhead-bound、cache 几乎无收益,真正受益的是长 rollout(DPO 造对 / GRPO completion)——与 T17(process-per-GPU 吞吐中性)同一条 measure-first 教训:收益真实,但只在真正压到瓶颈的 regime 里。M2a 的 per-layer 主机往返是短序列 overhead-bound 的一部分原因,M2b(device 端 cache + 批量 ragged)针对它。
M3(DPO,离线偏好优化,已落地 + 诚实负结果):两个复用 CE kernel 的新算子(零新 CUDA)——seq_logprob(Σ log πθ over 非 mask 位,反向 = CE_backward 取负求和;grad-check + mask)、dpo_loss(−log σ(Δ),双 policy logprob 父节点;grad-check + 退化 Δ=0→log2/∓β·½、β=0→0)。造对(gen_dpo_pairs)= chosen=gold、rejected=SFT 自己 greedy(用 M2a 引擎)的格式合法错误答案(8% greedy 答对的跳过)。训练(train_dpo)把 SFT ckpt 同时作 policy 和冻结 reference,一次性预算 reference logprob 并缓存(单模型驻留),每步 policy forward chosen+rejected → seq_logprob → dpo_loss,两 forward 共享 param 累积梯度;loss 起步恰好 log2(Δ=0 内置校验)。结果(v12, 1500 对, β0.1;100 留出题 vs SFT 8/100):reward-margin 与 pref-acc 干净上升(loss 被正确优化、infra 对),但不转化为 held-out 正确率——lr5e-7×300→7%、×800→5%、lr1e-6×2000→margin+34 崩溃(0% 格式、输出垃圾),三档都在 100 题 ~2.7% 标准误内 = 统计持平。教训:chosen/rejected 只差最终数字 token,DPO 提升的是特定训练对的 token 偏好、reweight 现有分布,不 install 能力;base 模型没有算术算法,偏好优化不泛化,推狠了只是全局扭曲分布→不连贯。DPO 在 chosen 本就 plausible 时有效,不能凭空造模型没有的知识——这正是 M4 GRPO 的动机:在线优化真实可验证 reward(采样→check→强化真正对的)而非固定对的 proxy(但 GRPO 同样面对 8% 稀疏,能否抬动指标是 M4 的 open question)。与 v8/T17 同源的诚实账:跑通+闸门齐全,负结果如实记。
M4(GRPO,在线 critic-free RL,已落地 + 两道诚实系统墙 + 一致负结果):新算子 clipped_pg_loss(per-token ρ + clip + k3 KL,反向用新增 scale_rows per-row 缩放 kernel;grad-check active+A=0 路径 + 退化 ε→∞ vanilla/β=0 无KL)。环 train_grpo:采 B prompt × rollout G → checker reward 0/1 → group-relative advantage (r−mean)/(std+ε)(无 critic,全对/全错组跳过)→ 存 πθ_old/πref per-token → K 内层 clipped-PG。rollout 用 M2 引擎 + 新加的 temperature 采样(单行 logits 比 naive [seq,vocab] 轻)。先把任务改简单:v12 SFT 在硬/易题都 ~8-9%(只会格式不会算术)→ 在 easy(操作数≤20)上从 v12 base 重训 SFT → held-out 18.7%;但 250/600 步同样 18.7% = 1B web-text 模型从 ~550 例不泛化加减法、只记 train。两道系统墙(设计文档 Risks 预言):① 显存——KL-leash 要 policy+reference 两个 1B fp32-master+Adam≈21GB,加激活在 32GB 5090 上不稳定 OOM → 只能 β=0(去掉 reference)跑完;② rollout 长杆——naive 采样增长序列撑碎 allocator,cached 采样更轻但单序列慢仍主导墙钟(~16s/step)。结果(easy, β=0, G6·B6, 40步, lr5e-7;150 留出 vs SFT 18.7%):reward 噪声 ~0.58-0.81(被 train 重叠抬),format 100/100 不崩(温和 lr 下 β=0 也没崩),held-out 20.0%(+1.3pp,~3% 标准误内 = 统计持平)。M3+M4 一致教训:模型缺底层能力时,离线偏好(DPO)和在线 RL(GRPO)都不抬 held-out——各自在能触及的训练分布上优化目标(被记忆抬高),装不进可泛化算法;RL 强化模型已会的,不教算术。后训练弧诚实终态 = 一套完整、闸门齐全的 SFT → KV-cache → DPO → GRPO 栈,infra 学全,并测得对齐对"base 缺失能力"能做什么的诚实边界。
M2b(批量 KV-cache 解码,已落地,补全 M2 引擎 + 修 rollout 长杆):M4 后补的 rollout 长杆修复——一个 prompt 的 G 个样本同步解码(每步一次 forward 跑整组 → G× 更少 kernel 启动)。一个新原语 rope_pos(逐 row 绝对位置 kernel,G 行共享一个解码位置;闸门 = [0..n] 逐位等于全 rope、统一 P 逐行等于 rope_at(P),bit-identical)。引擎 generate_cached_batch:BatchKVCache 带 G 维,批量 decode_step 把 G 贯穿 embed/proj/QK-norm/rope_pos/cache;M2a 两件零改动复用——decode_attention 本就 batch-agnostic(bh=G·nh)、repeat_kv(nh,batch=G) 按组广播。闸门 = G 个贪心行逐字节等于单序列(tests/decode_batch.rs,8q/2kv 头练 repeat_kv 批量)。吞吐(v12, G6·B6, 接进 train_grpo):~8.5s/step vs 单序列 ~14-16s/step ≈ 1.7×(rollout-inclusive;未到满 G× 因 per_token_logp + PG 更新也占时间、M2a 主机往返还在);且显存更稳(一次批量 forward vs G 次分配撑碎 allocator 的 M4 OOM)。⇒ M2 引擎闭环(M2a 单序列 + M2b 批量),rollout 长杆从"OOM/无界"变成有界 ~1.7× 收益,device 端 cache 是点名的下一杠杆。
M2c(device 端 KV cache,已落地,瓶颈转移的 profile-first 发现):K/V 留 device 为 [bh,T,hd](每层 Option<Tensor>),每步用新 cat_seq kernel(沿 seq 拼接)append 一个 token——去掉 M2a/M2b 每层主机往返 + transpose_3d01,单序列和批量都重构到它(比 host Vec+rebuild 干净)。闸门全保:cat_seq==host concat、decode_kv 单序列 + decode_batch 批量仍 token-identical、GQA 训练路径不受影响。发现(measure-first 的点,不是加速故事):去掉主机往返让纯单序列解码 +10%(133→147 tok/s@128),但 GRPO step 不动(~8.5s/step)——因为 M2b 批量化后 rollout 已不是 step 瓶颈,per-sample per_token_logp 捕获(2×/样本)+ PG 更新 forward/backward(全序列 model.forward)成了主导。长杆从 rollout 转移到训练侧 forward(同 T11/T17/M2a:profile 后再动手——你修的不是剩下的瓶颈)。device cache 仍是真实、闸门齐全的改进(更干净、少 PCIe、解码 +10%),但下一杠杆是 per-sample forward 的 ragged 批量而非 cache。M2 引擎现 = M2a(单序列)+ M2b(批量)+ M2c(device cache),全 token-identical-gated;后训练栈完整、瓶颈已测绘。
M2d(批量 GRPO 训练侧 forward,已落地,M2c 点名的杠杆 + 一处 decomposition 纠正):M2c 点名的下一杠杆——把每步 N=B·G 个 ragged 样本的训练侧 forward(per_token_logp 捕获 + inner clipped-PG fwd/bwd)打包进一次 forward_batched。使能性质 = causal 下右 padding 免费:真 completion 行位置早于尾部 pad,causal 禁止前向 attend,故真行 logits 与单序列 forward 逐位相同,pad 行垃圾被 target=-100 屏蔽——这正是训练引擎 pad-and-mask 而非跑 ragged 的原因。两件新东西:per_token_logp_batched(右 pad → 一次 forward_batched(N) → 按真长切片)、ops::clipped_pg_loss_batched(per-row advantage[t] + per-row weight[t],caller 传 1/(N·n_s),op 不再自算 1/n_tokens → 折进 weight 即与 looped Σ_s (1/N)(1/n_s)… 逐位等价;--micro 分块界定 [chunk·Lmax,vocab] logits 显存,weight 用全局 N 故分块梯度累积精确)。两道精确闸门:forward_batched_ragged_matches_looped(右 pad 批量 forward == 单序列,fp32 max|Δ|=3.7e-7、bf16 0.0,composed+flash)+ clipped_pg_loss_batched_matches_looped(批量 op == looped,loss Δ=1.5e-8/grad 7.5e-9,f32),复合即证端到端等价;端到端短 SFT→train_grpo 12 步不 OOM(1B master+AdamW+批量激活 micro=16 容得下)、批量 inner 执行。吞吐(bench,v12 1.05B,N=48,micro16,权重无关):capture 622→71ms(8.7×)、inner 1907→208ms(9.2×)、训练侧 forward 合计 2526→280ms(9.0×)。Decomposition 纠正(诚实发现):M2c 说"训练侧 forward 主导 step",干净分量 bench 证伪强形式——训练侧 forward 是 ~8.5s step 里的 ~2.5s(~30%),可观、值这 9×,但 rollout(generate_cached_batch ~6s)一直是更大头;M2d 把训练侧砍到 ~0.28s 后,step ~95% 是 rollout,长杆又摆回 rollout。⇒ M2d 拔掉训练侧 forward 这块 overhang(分量级精确 9×),再次印证 measure-first:step 级下一杠杆 = 全 B×G rollout 批量(今天只有每 prompt 的 G 同步、B 个 prompt 仍串行)。后训练栈保持完整,step decomposition 现为实测而非断言。
四、perf 杠杆台账(详见 known-issues.md)
- 已修:KI-1 单序列 launch-bound(T10)· KI-5 per-op cudaMalloc 串行(T11)· KI-2 bf16/OOM(T12)· KI-3 激活重计算(T13,解锁 dim1024,v8 用上)。
- 实测关闭(负结果):process-per-GPU(T17)——曾挂在 KI-5/T11 doc 作残留非线性的拟修复方向,T17 实测吞吐中性(thread ~5.27× vs proc ~5.31×@8,8 卡全满载),残留是 NCCL/PCIe 通信墙非 context 串行 → 不再是 perf 待办,链路本身已落地留作可选路径。
- 待办:KI-4 大词表小 vocab(接受的建模权衡)· 要更高多卡线性 → all-reduce overlap / NVLink 互联(非本尺度优先)。
- 三次「先 profile/measure 再动手」证伪了错误的拟修复(KI-1「加大batch」、KI-5「分桶all-reduce」、T17「process-per-GPU 解残留串行」),避免了无效大改——profile/measure-first。