Files
xtrain/docs/runs/05-v5-tinystories-dim768.md
Gahow Wang 579365f4a0 docs: run v5 — TinyStories saturation at dim768 (val 1.11)
设计文档 05-v5-tinystories-dim768.md(中文,xserv 风格):数据 2.49B tok/5.33ep、
架构同 v4(净测数据变量)、bf16 8 卡 global 256、train 11.07→1.06 best val 1.1102。
核心发现「数据天花板」:v4(1.54ep)1.169→v5(5.33ep)1.110 仅 ↓5% 且末段 val 走平
⇒ TinyStories 在 dim768/127M-core 近饱和,v6 该换轴(更大模型/更广语料,非更多 TinyStories)。
xserv BF16 服务 3/3 prompt 逐 token 一致。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 17:56:25 +08:00

197 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Scaling Run v5: TinyStories 多遍(5.33 ep) + dim768/18L(同 v4) + 8 卡 DDP bf16 — Design Document
## Goal
v4 把模型放大到 dim768/18Lcore 127.43M),训了 ~720.9M token~1.54 epochval 一路降到末步仍在降
= 仍**欠拟合**。当时留下一个明确问题:**TinyStories 这本语料对 127M core 模型,到底是不是数据上限?**
v5 是为回答这个问题专门设计的**对照实验**
1. **架构完全冻结 = v4**dim 768 / 24 heads × 32 head_dim / 18 layers / SwiGLU ffn 2048core 127.43M
总 204.63M)。**一个权重维度都不改**——这样 v4→v5 的 val 差异**只能归因于「更多数据」这一个变量**。
2. **数据放大到接近饱和**v5 训 **~2.49B token = ~5.33 epoch**v4 才 1.54 ep同一份全量 TinyStories
token 流多跑 3.5×,看 val 还能不能继续降。
3. **bf16T12/KI-2找回甜点区**v4 是 bf16 的触发点——dim768 fp32 在 32GB 显存里 per-rank batch 32
global 256OOM被迫降到 per-rank 16global 128。v5 上 bf16 混合精度fp32 master激活减半
**找回 per-rank 32 / global 256 的甜点区**,同时吞吐从 v4 的 ~145K 升到 ~217K tok/s。
4. 训完存 registry + 导出 xserv 验证可服务,给出**相比 v4 的提升**与**数据天花板结论**。
> **这一版的工程意义**v5 是 xtrain scaling 阶梯上第一个**有意不放大模型**的版本——它不是为了「更低的 val」
> 而是为了**测准 TinyStories 在 dim768 的数据天花板**。结论(见下)很干脆:**3.5× 数据只换来 ~5% 的 val
> 改善,且末段 val 走平**——同尺寸模型在 TinyStories 上已近饱和v6 该换轴(更大模型 / 更广语料),而**不是**
> 继续榨 TinyStories 的 epoch。同时 v5 兑现了 v4 留的 bf16 杠杆KI-2bf16 找回 global 256 甜点区,
> 8 卡稳态 ~217K tok/s。
## 数据
| 项 | v4 | v5 |
|----|----|----|
| 来源 | TinyStories **全量 train**(复用 v1 缓存)| 同 |
| token 数(语料)| 468,260,367 | 同 |
| **训练消费 token** | ~720.9M22000 步 × 32768| **~2.49B**38000 步 × 65536|
| epoch 占比 | ~1.54 | **~5.33**×3.5|
| tokenizer | gpt2 BPEvocab 50257| 同 |
| 缓存 | `data/tinystories-train.txt.u16.bin`u16| **直接复用** |
| held-out val | 全量末尾 1,000,000 token | **同一 1M token**(与 v0v4 完全相同的保留集,公平对比)|
**复用缓存**`Corpus::load_cached``<corpus>.u16.bin`,启动即载入 467.26M train token末尾 1M 留 val
held-out val 仍是全量末尾 1M token`split_tail`),与 v0v4 同一保留集——**v0v5 的 val loss 直接可比**。
**这一版数据设计的核心**v4 才 1.54 epochv5 把同一份语料喂到 **5.33 epoch**×3.5)。如果 TinyStories
对 127M core 还有数据空间val 该继续显著下降如果已近饱和val 会迅速放缓、走平。**v5 就是来读这个信号的。**
## 架构
v5 = **与 v4 字节级同构的** tiny Qwen3RoPE + RMSNorm + per-head QK-norm + SwiGLU + 独立 lm_headMHA
**刻意一个维度都不改**,让「更多数据」成为唯一被测变量。
| 维度 | v4 | v5 |
|------|----|----|
| dim= heads·head_dim| 768 | **768** |
| n_layers | 18 | **18** |
| n_heads | 24 | **24** |
| head_dim | 32 | 32|
| ffn_hiddenSwiGLU| 2048 | 2048|
| vocab | 50257 | 50257|
| **core 参数** | 127,432,704≈127.43M| **127,432,704** |
| embed + lm_head2×vocab×dim| 77,194,752≈77.19M| 77,194,752|
| **总参数** | 204,627,456≈204.63M| **204,627,456** |
**为什么不放大模型**scaling 实验里「数据」和「容量」两个变量若同时动val 的变化无法归因。v4→v5 把容量冻死、
只动数据,得到的 val 差就**纯粹是数据的边际收益**——这是判断「TinyStories 是否到数据天花板」的唯一干净办法。
config.json 与 v4 完全一致(导出的 201 tensors 形状一字不差)。
## 训练器8 卡 DDP bf16T12 混合精度fp32 master
v4 用 8 卡 DDP **fp32**,被 dim768 的显存压到 per-rank batch 16global 128。v5 切 **bf16 混合精度**
- **fp32 master 权重 + AdamW/clip/DDP 全部保持 fp32**(数值安全),只把 linears 走
`cublasGemmEx`16BF 输入 / fp32 accum、激活存 bf16norm/softmax/rope/CE 仍 fp32。新增 cast autodiff op
桥接fwd 降精度 / bwd 升精度)→ 优化器零改动。无 loss scalingT12 实测 dim768 不需要)。
- **激活减半 → 找回甜点区**bf16 把 dim768 的 per-rank batch 重新撑到 **32global 256**,正是 v4 因 fp32
OOM 失去的甜点区。同时吞吐 **~145Kv4 fp32→ ~217K tok/sv5 bf16×1.5**。
- **8 卡 thread-per-GPU**all-reduce device 梯度取均值后各 rank 本地 GpuAdamW step跨 rank 参数 bit-identical
T8/T11 已验证)。
全程稳态 **~217,000 tok/s**、wall-clock **~3.2h** 训完 2.49B token。bf16 收敛全程对住 fp32T12 已做
150 步 3.984 vs 3.988 对拍v5 的 train/val 曲线平滑无异常。
## 超参
| 项 | 值 | 备注 |
|----|----|----|
| optimizer | 手写 AdamWGPU 端 step| wd=0.1,β/eps 用 xtrain-optim 默认 |
| LR schedule | 线性 warmup → cosine decay | max_lr **6e-4** → min_lr **6e-5**(同 v1v4|
| warmup | ~1900 步lr 在 ~step 1900 达峰 6.00e-4cosine 衰减到末步 6e-5| |
| grad clip | global-norm 1.0 | gnorm 全程平稳warmup 后 ~0.4 起持续下降)|
| steps | **38000** | ~3.2h @ 8 卡 |
| global batch | **256**per-rank 32 × world 8| **bf16 找回的甜点区**v4 fp32 只能 128|
| seq_len | **256** | 同 v2v4 |
| tokens/step | 256×256 = 65536 | 总训练 token ≈ **2.49B**~5.33 epoch|
| world size | **8**RTX 5090sm_120| |
| 精度 | **bf16 混合精度**fp32 master| T12/KI-2导出 xserv 同样 BF16 |
**算力**dash5 8× RTX 5090全程 ~217,000 tok/sstep 50 起即稳态wall-clock ≈ **3.2 小时**
## 结果
- **train loss**start **11.0675** → end **1.0588**(全程平滑下降)
- **best val lossheld-out 1M tokenstep 34999****1.1102**
- **final val lossstep 37999****1.1131**
- val loss 曲线(每 ~2000 步抽样):
| step | 499 | 1999 | 3999 | 5999 | 7999 | 9999 | 13999 | 17999 | 21999 | 25999 | 29999 | 33999 | **34999** | 37999 |
|------|-----|------|------|------|------|------|-------|-------|-------|-------|-------|-------|-------|-------|
| val | 2.6838 | 1.6033 | 1.4132 | 1.3317 | 1.2980 | 1.2596 | 1.2194 | 1.1846 | 1.1575 | 1.1374 | 1.1217 | 1.1151 | **1.1102** | 1.1131 |
### ⚠️ 数据天花板:末段走平
v5 的 val 在**末段明显走平**——这是 v4 单调下降曲线上看不到的新行为:
| step | 34999 | 35499 | 35999 | 36499 | 36999 | 37499 | 37999 |
|------|-------|-------|-------|-------|-------|-------|-------|
| val | **1.1102 (best)** | 1.1126 | 1.1131 | 1.1135 | 1.1119 | 1.1143 | 1.1131 |
best1.1102)出现在 step 34999之后 3000 步 val 在 **1.11021.1143 的 ~0.004 带内来回抖动、不再单调下降**
对比 v4 的 val 一路降到末步仍在降(欠拟合)——**v5 已经把 TinyStories 这本语料学到平台期**。
## 相比 v4 的提升 —— 以及数据天花板分析
**完整 val 阶梯(各版各自 best val同一 1M token 保留集)**
| 模型 | core 参数 | 训练 token | epoch | **best val** | 相比上一版 |
|------|-----------|-----------|-------|--------------|-----------|
| v0-baseline | 41K | ~0.72M | — | 3.8050 | — |
| v1 | 8.39M | ~5.1M | — | 2.5847 | ↓1.22 |
| v2 | 28.32M | ~36.9M | — | 1.7055 | ↓0.88 |
| v3 | 67.13M | ~245.8M | ~0.53 | 1.3027 | ↓0.40 |
| v4 | 127.43M | ~720.9M | ~1.54 | 1.1690 | ↓0.13 |
| **v5** | **127.43M(同 v4** | **~2.49B×3.5** | **~5.33** | **1.1102** | **↓0.06(仅 ~5%** |
**这是本版的核心发现**。把它和前几档对比v2→v3 数据 ×6.7val ↓0.40v3→v4 数据 ×2.9 + 模型 ×1.9
val ↓0.13)。而 **v4→v5模型不变、数据 ×3.5val 只 ↓0.06~5%**。结合末段走平:
> **结论:在 dim768 / 127M-core 这个尺寸TinyStories 已接近数据饱和。**
> 同一份语料从 1.54 epoch 喂到 5.33 epoch×3.5val 仅改善 ~5% 且末段走平——**「更多 TinyStories token」
> 这条杠杆已经基本榨干**。这不是模型欠拟合v4 那种末步仍降),而是**语料的信息量对这个尺寸的模型已学尽**。
**下一档v6的杠杆按收益排序**
1. **换轴:更大模型 或 更广语料**——这两条才是 v5 之后真正能继续降 val 的方向。
2. **不该做**:继续加 TinyStories 的 epoch。v5 已经证明 6+ epoch 的边际收益薄到不值得算力。
### 并排采样greedy 40 tokxserv 服务,同 promptv4 vs v5
| prompt | v4 | v5 |
|--------|----|----|
| `Once upon a time` | …a little girl named Lily. She loved to play outside in the **sunshine. One day, she saw a big, scary dog. She was scared and ran away.** | …a little girl named Lily. She loved to play outside in the **park. One day, she saw a big, white cloud in the sky. It looked like a fluffy pillow.** |
| `One day` | One day, a little girl named Lily went to the park with her mom. She saw a big tree with a swing. Lily wanted to play on the swing, but she was **too small. She asked her** | One day, a little girl named Lily went to the park with her mom. She saw a big tree with a swing. Lily wanted to play on the swing, but she was **too small to reach it.** |
| `The little` | The little girl was so happy that she had found the perfect **place to hide. She stayed there for a long time, until it was time to go home. She said goodbye to the tree** | The little girl was so happy that she had found the perfect **thing to replace the broken one. She thanked her mom and they both went home with a smile on their faces.** |
**结论**v4 和 v5 都写出完整、连贯、有收束的 TinyStories——两者**质量在同一水平**,差异是**情节走向的细微不同**
"scary dog → ran away" vs "white cloud → fluffy pillow"**而非可感知的质量提升**。这与 val 仅 ↓0.06 完全一致:
**同尺寸模型多看 3.5× 数据,采样质量已无肉眼可见的提升**——这是数据天花板在生成侧的直接体现。
## xserv 验证
导出 HF Qwen3 safetensors命名映射 + 2D 权重转置 [in,out]→[out,in] + BF16见 T9 `docs/08`
**201 tensors** = 18 层 × 11 + embed + norm + lm_headconfig.json 与 v4 一字不差),存入 registry 后用
`xserv-cli` 加载并贪心生成:
```
$ xserv-cli ~/projects/tiny-models/v5-tinystories-dim768 --max-tokens 40
Model: qwen3, layers=18, hidden=768, heads=24/24 kv, vocab=50257
Loaded 201 tensors
xserv> Once upon a time, there was a little girl named Lily. She loved to play outside in the park.
One day, she saw a big, white cloud in the sky. It looked like a fluffy pillow.
xserv> One day, a little girl named Lily went to the park with her mom. She saw a big tree with a
swing. Lily wanted to play on the swing, but she was too small to reach it.
xserv> The little girl was so happy that she had found the perfect thing to replace the broken one.
She thanked her mom and they both went home with a smile on their faces.
```
**token-match**xservBF16对 xtrain 自身贪心,**3 个 prompt 全部逐 token 完全一致**40 tok 内零分叉)。
v5 **训练即 bf16**fp32 master权重本就在 bf16 数值域里收敛,导出 BF16 给 xserv 后两侧贪心匹配更紧v4
是 fp32 训练 → BF16 导出,已 3/3 一致v5 同样 3/3 且数值路径更一致)。闭环成立。
## v6 提案
v5 给出了明确的数据天花板结论v6 该**换轴**。两条候选:
- **A. 更大模型dim 1024+**v5 证明 TinyStories 对 127M core 已饱和,但**更大的模型也许能从同一语料里
榨出更多**(容量上限尚未触顶)。注意 dim 越大embed/lm_head 占比越摊薄dim768 时 77.19M / 204.63M ≈ 38%
dim1024 会降到 ~34%)→ **KI-4大词表占比的压力反而变小**。但若只换更大模型、仍喂 TinyStories很可能
很快又撞上「这本语料的信息上限」——TinyStories 本身的内容多样性有限。
- **B. 更广语料FineWeb-edu 等通用高质语料)+ 可能换 tokenizerKI-4**v5 的天花板是**语料**的天花板,
不是模型的。换更丰富的语料能**抬高数据本身的信息量上限**,让更大的模型有东西可学。配合换更小/更贴合的
tokenizerKI-4可进一步降 embed/lm_head 浪费。
**我的判断B 解锁的空间更大。** v5 的核心教训是「瓶颈在语料而非容量」——只放大模型A大概率撞上同一本
TinyStories 的信息天花板收益有限换更广语料B才是抬高天花板本身。理想的 v6 = **A+B 同时**(更大模型
吃更广语料),但若只能选一个,先 **B广化语料**
**KI-3激活重计算是否需要**:若 v6 走 Adim1024+ 更大模型),激活显存会显著上升,**bf16 已经省了一半**
v5 验证),但更大 batch/更长 seq 下仍可能吃紧 → **届时 KI-3激活重计算才成为下一个显存杠杆**T12 文档
已把它列为「bf16 之后的下一个显存杠杆」)。若 v6 只走 B同 dim768 换语料),现有 bf16 + 缓存分配器够用,
**KI-3 暂不需要**。即:**KI-3 的触发条件 = v6 放大到 dim1024+**,与 A 路线绑定。