# 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_heads1 对拍 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 📌 两条 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](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 缺失能力"能做什么的诚实边界。 ## 四、perf 杠杆台账(详见 [known-issues.md](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。