# Scaling Run v8: 容量轴 — dim1024/18L(core 226M, +78% 容量) + 同 v6/v7 的 FineWeb-edu 2.255B 子集 + bf16 + 激活重计算(T13) + 8 卡 DDP — Design Document

## Goal

v6/v7 把**数据轴**测到了头：同一 2.255B FineWeb-edu 子集，从 1.02 epoch（v6 3.0652）喂到 1.45 epoch
（v7 3.0149），FineWeb val 仅 ↓0.05 且走平 = **同子集多 epoch 在 dim768 上近天花板**。v7 末尾把下一步的
首选定为「新 FineWeb shards」（真·更多数据），并把「更大模型」列为待测的**容量轴**——但留了一个明确的
未解问题：**dim768（core 127M）在这本语料上到底是数据见够了，还是模型容量不够？**（即是否 capacity-limited）

v8 就是去**净测这条容量轴**：**数据完全冻结 = v6/v7 的同一个 2.255B 子集**，唯一变量 = **把模型从 dim768
放大到 dim1024**（core 127M → 226M，+78% 容量）。

1. **数据子集完全冻结 = v6/v7**：同一个 2,254,904,418-token FineWeb-edu 子集（`sample/10BT` 3 个 parquet
   分片），同一 1M held-out val（**与 v6/v7 同一把尺子，可直接比**）。**不补任何新数据**——这是干净的
   dim768-vs-dim1024 A/B。
2. **唯一变量 = 模型容量**：dim 768 → **1024**（32 heads × 32 head_dim），ffn 2048 → **2730**（≈ 8/3·dim
   的 SwiGLU 惯例），18 层不变 → **core 226.5M（+78%）/ 总 329.4M**。
3. **为装下 dim1024 而启用 T13 激活重计算**：dim1024 batch32 激活显存超 32GB → 用 KI-3 的 per-block
   gradient checkpointing（no-tape 前向 + 反向时重算）压到 16.6GB/卡，恰好装得下。这是 v8 能成立的**前置基建**。

> ### ⚠️ 方法论说明（同 v6/v7）
>
> v8 的 val（FineWeb-edu **2.9801**）与 v6（3.0652）、v7（3.0149）**同一把尺子、同一个 1M 留出集，三版可以
> 直接比**；但都**不能**和 v0–v5 的 TinyStories val（~1.1）比大小——真实网页文本熵高，~3.0 是预期值不是回退。

## 数据（v8 与 v6/v7 的唯一差别 = 模型容量，数据一字不差）

| 项 | v6/v7 | v8 |
|----|----|----|
| 来源 | FineWeb-edu `sample/10BT`（真实教育类网页）| **同（一字不差的同一子集，非新数据）** |
| 语料规模 | 2,254,904,418 tokens（3 parquet 分片）| **2,254,904,418 tokens（同子集）** |
| **训练消费 token** | v6 ~2.29B(1.02ep) / v7 ~3.28B(1.45ep) | **~2.359B**（36000 步 × global 256 × seq 256）|
| **epoch 占比** | v6 ~1.02 / v7 ~1.45 | **~1.05**（刻意取最接近 v6 的 ~1 epoch，A/B 同 epoch 对照）|
| tokenizer | gpt2 BPE（vocab 50257）| 同 |
| 缓存 | `data/fineweb-edu.txt.u16.bin`（4.51GB u16）| **同一缓存** |
| held-out val | FineWeb-edu 末尾 1M token | **同（与 v6/v7 可比）** |

**v8 的 epoch 取 1.05（不是 v7 的 1.45）是刻意的**：这样 v8 与 **v6（1.02ep）几乎同 epoch**，是最干净的
「同数据量、纯放大容量」A/B；同时与 v7（1.45ep，更多 epoch 的小模型）对照，能回答「容量 vs 更多老数据
谁更值」。

## 架构（v8 唯一变化点 = 容量）

v8 = 把 v4–v7 的 tiny Qwen3（RoPE + RMSNorm + per-head QK-norm + SwiGLU + 独立 lm_head，MHA）按容量轴放大：

| 项 | v4–v7 (dim768) | **v8 (dim1024)** |
|----|----|----|
| dim | 768 | **1024** |
| heads × head_dim | 24 × 32 | **32 × 32** |
| layers | 18 | 18（不变）|
| SwiGLU ffn | 2048 | **2730** |
| **core 参数** | 127,432,704 | **226,495,488（+78%）** |
| embed + lm_head | 77.19M | 102.93M |
| **总参数** | 204.63M | **329.42M** |
| 导出 tensors | 201 | **201**（层数同，张量数同）|

head_dim 保持 32（与全系一致，RoPE/QK-norm 维度不变），靠加头数（24→32）把 dim 撑到 1024；ffn 2730 ≈
(8/3)·1024 取整到偶数，沿用 SwiGLU 的 2/3·4·dim 经验比例。

## 训练器：8 卡 DDP bf16 + 激活重计算（v8 新启 T13）

复用 v5/v6/v7 的训练栈（fp32 master + AdamW/clip/DDP 全 fp32，linears 走 `cublasGemmEx` 16BF/fp32 accum、
激活存 bf16，norm/softmax/rope/CE 仍 fp32，8 卡 thread-per-GPU all-reduce 取均值后各 rank 本地 GpuAdamW
step，跨 rank bit-identical），**新增一项**：

- **激活重计算 ON（T13 / KI-3）**：per-block gradient checkpointing——前向不建 tape、反向时按块重算激活。
  对非重计算版**梯度逐位一致**（0.00 rel err）。这是 dim1024 batch32 能装进 32GB 卡的关键（16.6GB/卡）。
- **代价**：dim1024 下重算税更重，稳态吞吐 **~129,000 tok/s**（vs dim768 bf16 的 ~218K）——多一遍块前向 +
  更大的矩阵。util 97–100%、16.3GB/卡。wall-clock **~5h** 训完 2.359B token（36000 步）。

## 超参

| 项 | 值 | 备注 |
|----|----|----|
| optimizer | 手写 AdamW（GPU 端 step）| wd=0.1，β/eps 用 xtrain-optim 默认（同全系）|
| LR schedule | 线性 warmup → cosine decay | max_lr **6e-4** → min_lr **6e-5**（同 v1–v7）|
| warmup | ~1800 步 | lr 峰值 6e-4，cosine 衰减到末步 6e-5 |
| grad clip | global-norm 1.0 | 平稳 gnorm ~0.21 |
| steps | **36000** | ~5h @ 8 卡（重算税）|
| global batch | **256**（per-rank 32 × world 8）| bf16 + 重计算后 dim1024 的甜点区 |
| seq_len | **256** | 同 v2–v7 |
| tokens/step | 256×256 = 65536 | 总训练 token ≈ **2.359B**（~1.05 epoch）|
| world size | **8**（RTX 5090，sm_120）| |
| 精度 | **bf16 混合精度**（fp32 master）| T12/KI-2；导出 xserv 同样 BF16 |
| **激活重计算** | **ON**（per-block，T13/KI-3）| **v8 新启**，解锁 dim1024 |

## 结果

- **train loss**：start **11.1018** → end **3.0586**（全程平滑下降）
- **best val loss 2.9801**（step 35999），**final val loss 2.9801**（同一步即末步，FineWeb-edu held-out 1M）
- FineWeb val 曲线（抽样）：

| step | 499 | 999 | 3999 | 7999 | 11999 | 15999 | 19999 | 25999 | 29999 | 31999 | 33999 | 34999 | **35999** |
|------|-----|-----|------|------|-------|-------|-------|-------|-------|-------|-------|-------|-------|
| val  | 5.5736 | 4.6699 | 3.6454 | 3.4184 | 3.2956 | 3.2153 | 3.1506 | 3.0593 | 3.0201 | 2.9999 | 2.9862 | 2.9828 | **2.9801** |

⚠️ **末步即 best、且仍在降**：从 33999(2.9862) → 34999(2.9828) → 35499(2.9820) → 35999(2.9801)，每 500 步
仍稳定 ↓~0.002，**到训练结束没有走平**——与 v7 末段（~step44000 后走平、末步还反弹）形成对比。**v8 还没吃饱**。

### ⭐ 核心 A/B 结论：容量有用（部分 capacity-limited），但增益 ~3% 边际

三版**同一 FineWeb val 尺子**，直接可比：

| | v6 | v7 | **v8** |
|---|---|---|---|
| 模型 | dim768 (core 127M) | dim768 (core 127M) | **dim1024 (core 226M, +78%)** |
| epoch | 1.02 | 1.45 | **1.05** |
| 训练 token | 2.29B | 3.28B | **2.36B** |
| best val（FineWeb，可比）| 3.0652 | 3.0149 | **2.9801** |

两个干净的对照：

1. **同 ~1 epoch、纯放大容量（v6 vs v8）**：1.02ep → 1.05ep 几乎同数据量，dim768 → dim1024，
   val **3.0652 → 2.9801（↓0.085）**。→ **同样的数据，更大的模型榨出更多**：v6/v7 在 dim768 上吃不动的，
   不全是「数据见够了」，**有一部分是模型容量不够（capacity-limited）**。
2. **容量 vs「更多老数据的小模型」（v7 vs v8）**：v8（dim1024，**才 1.05ep**）2.9801 < v7（dim768，**1.45ep
   更多数据**）3.0149，低 **0.035**。→ **放大容量比「给小模型多喂 0.4 epoch 老数据」更值**——这正面回答了
   v7 留下的问题：在这本语料上，下一步的杠杆**容量 > 重复老数据**。

> ### ⚠️ 但要诚实：增益是 ~3% 的边际，不是质变
>
> v8 的 0.085（vs v6 同 epoch）≈ **2.8%** 的 val 改善。这和「数据轴单步杠杆」量级**一样薄**：
> - v5（同子集 ×3.5 数据）：TinyStories val ↓5%
> - v7（同子集 ×1.4 epoch）：FineWeb val ↓0.05（~1.6%）
> - **v8（容量 +78%，dim768→dim1024）：FineWeb val ↓~3%**
>
> **元结论：到 v8，无论拨数据轴还是容量轴，单轴单步的杠杆都收敛到 ~3%/lever = 全面进入边际递减。**
> 这正是 Chinchilla 的教训在小尺度上的复现：**容量与数据要匹配地一起 scale**，单独猛拨一根轴，
> 另一根很快成为新瓶颈（v8 容量 +78% 但只配同样的 2.36B token，所以容量没吃满 → val 末步仍在降 = 数据
> 这边又成了限制）。

## 采样对比 —— v7(dim768) vs v8(dim1024)（同 xserv、同贪心、同 prompt）

唯一差别是 v8 容量 +78%。喂同样的通用/说明文提示词（greedy，max 60 token）：

| prompt | **v7（dim768, 1.45ep）** | **v8（dim1024, 1.05ep）** |
|--------|------------------|------------------|
| `The history of` | the city of New York is a story of many different people. The first inhabitants… were the Native Americans… the Dutch… | the United States is a history of the United States as a nation… a nation of immigrants. (随后陷入 "a nation of immigrants" 重复) |
| `In science,` | the term "science" is used to describe the study of the natural world… biology, chemistry, physics, and mathematics… | the term "biological" refers to the organisms that are alive and have been alive for a long time…（随后重复该定义句）|
| `The most important` | thing to remember is that you can't just buy a new car and expect to pay for it… understand the basics of insurance… | thing to remember is that the best way to prevent a heart attack is to eat a heart-healthy diet.（随后重复该句）|
| `Water is` | a natural substance that is found in the earth's crust… a very important element in the Earth's ecosystem… | a good thing. It's a good thing to have…（随后漂进对话腔 "I'm not sure if you're going to be able to do that"）|

**质量观察（诚实读法）**：两版都写**同一语域的连贯说明文英文**，**没有可感知的质的跃迁**——这正是 val 仅
↓3% 在采样上的体现。更细看：

- v7 在这组 greedy prompt 上反而**信息更具体、更耐读**（具体地名/学科列表/事实），尽管也有事实瑕疵；
- v8 在 greedy 下**更快掉进重复循环**（"a nation of immigrants" ×6、"heart-healthy diet" ×3），且 "Water is"
  漂出说明文语域进了对话——这更像是**采样路径（greedy + 小模型 + 才 1 epoch）**的表现，而非容量带来的退步；
- 两版的小模型重复倾向、轻微事实瑕疵都在。

一句话：**val 上 v8 确实更低（容量有用），但在贪心采样的肉眼质量上看不出 v8 明显更好**——这与 ~3% 的边际
val 提升完全一致。要把容量优势兑现成可感知的文本质量，多半还需要**配套更多数据**（v8 才 1.05 epoch、val 未饱和）。

## xserv 验证

导出 HF Qwen3 safetensors（命名映射 + 2D 权重转置 [in,out]→[out,in] + BF16，见 T9 `docs/08`，**201 tensors**，
config.json `hidden_size 1024 / heads 32 / head_dim 32 / intermediate 2730 / layers 18`）存入 registry，
用 `xserv-cli` 加载并贪心生成：

```
$ xserv-cli ~/projects/tiny-models/v8-fineweb-edu-dim1024 --max-tokens 60
Model: qwen3, layers=18, hidden=1024, heads=32/32 kv, vocab=50257
Loaded 201 tensors
Ready (KV cache, dtype=bf16).
xserv> The history of the United States is a history of the United States as a nation. The United States is a
       nation of immigrants…
xserv> In science, the term "biological" refers to the organisms that are alive and have been alive for a long time…
xserv> The most important thing to remember is that the best way to prevent a heart attack is to eat a
       heart-healthy diet…
```

**token-match**（xtrain f32 贪心 on ckpt vs xserv BF16 贪心 on 导出权重，同 prompt）：
- `Once upon a time` / `The little` → **逐 token 完全一致**（30/30 byte-identical）；
- `One day` → 前 ~6 token 一致后在一个 BF16 近似平局处分叉（xtrain f32 "home from school" / xserv BF16
  "along the road in the village of Kambala"）——这是 v4–v7 一贯报告的 **BF16-vs-f32 贪心在近平局处翻拍** 的
  预期漂移点，非 bug。v8 训练即 bf16（fp32 master），权重本就在 bf16 数值域收敛，导出 BF16 给 xserv 后两侧
  数值路径一致——同全系闭环成立。

xserv 加载 qwen3 layers=18 hidden=1024 201 tensors、KV-cache、贪心生成连贯说明文英文，闭环成立。

## 相比 v6/v7 与 v9 提案

v8 兑现了 v7 提案里的**容量轴**，并给出一个清晰结论：**容量有用（v6/v7 在 dim768 上有一部分是
capacity-limited，不全是数据见够）**——但和数据轴一样，**单步杠杆只有 ~3%**。叠加 v5/v6/v7：

- 数据**量**轴（同子集多 epoch）：饱和，~1.6–5% / 步。
- 数据**广度**轴（换语料）：v6 唯一的质变（小故事 → 真实说明文），但那是**一次性**换分布的红利。
- **容量轴（v8）：有用但 ~3% 边际**。
- → **所有单轴都已进入 ~3%/lever 的边际递减区**。

**Chinchilla 教训（小尺度复现）**：v8 容量 +78% 但只配 2.36B token（1.05ep），val 末步仍在降 = **数据这边
立刻成了新瓶颈**。要继续进步，**容量与数据必须匹配地一起 scale**，而不是单独猛拨一根轴。v9 选项按此重排：

1. **双轴一起 scale（最符合 Chinchilla，真 scale）**：更大模型（dim1024+ 或更深）+ **新 FineWeb shards**
   （更多样、不重复 token，配上去把 dim1024 喂饱）。⚠️ 投入最大（下载 + 长训 + 磁盘紧需 /dashscope-tmp 暂存）。
2. **dim1024 多喂数据（最便宜，先验证容量能不能吃满）**：v8 才 1.05 epoch、val 未饱和——直接给 dim1024 续训
   到 2–3 epoch（或加新 shards），看 val 还能降多少。这是验证「v8 的容量是否被数据卡住」的最低成本实验。
3. **自然收尾（漂亮里程碑）**：项目已 8 版 + 从零全栈（autograd/backward/AdamW/DDP/bf16/重计算/export）+
   完整的数据轴 + 容量轴 + Chinchilla 边际分析——作为一条**学习线**已经讲完了一个完整的故事。

**我的判断**：作为**工程/学习项目**，v8 是一个天然的收尾点——8 版把「数据量 / 数据广度 / 容量」三根轴都
测过，并落到「单轴 ~3%、要双轴一起 scale」这个有分量的元结论上，全栈基建闭环。若要再做一版，**首选 2
（dim1024 多喂数据）**：它最便宜、且直接回答 v8 留下的唯一悬念（容量被数据卡住的程度）——v8 val 未饱和说明
这一版很可能立刻见效；真要追求规模再上 1（双轴）。详见对比表与 evolution.md 的「容量轴」与「边际递减」两条。
