网页占九成就该训练九成吗?数据混合权重、温度采样与 Token 预算
从数据量比例压倒小语种出发,手算温度平滑,区分文档与 token 采样,并用 PyTorch 构造可复现、可审计的多来源预训练批次。
上一篇删除了精确重复与近重复,让镜像网页不再暗中获得额外权重。但去重后的可用数据仍可能是网页 900 亿 token、代码 80 亿、中文教材 20 亿。若把文件顺序打乱后直接读取,模型看到每类数据的概率只由“谁体积大”决定。
数据混合(Data Mixture)把这个隐含选择变成显式分布。本文只追踪三个问题:怎样把来源规模变成目标采样概率、温度平滑怎样在覆盖与过采样之间取舍,以及怎样按 token 而非按文件实现可复现的训练预算。
01 自然比例为什么不一定是学习目标?#
设共有 个来源,第 个来源有 个可训练 token。自然比例为:
它回答“仓库中随机一个 token 来自哪里”,并不回答“单位计算应花在哪里”。网页来源大可能只是更容易抓取;小来源可能更相关、更干净,也可能因反复过采样而很快记住。
混合权重 必须满足 。总预算为 个训练 token 时,期望分配是 。
02 从来源到一个 batch 的数据流#
flowchart LR
A[各来源去重文档] --> B[tokenize]
B --> C[按 EOS/边界拼接]
C --> D[每来源 token 流]
Q[q source probabilities] --> E[采样 source_id]
D --> E
E --> F[截取长度 L 的 block]
F --> G[叠成 input_ids N×L]
G --> H[因果 LM loss]
H --> I[按来源记录 token/loss/reuse]mermaid来源选择、文档选择、拼接、切块是不同随机过程。日志至少要能从一个 batch 追溯到 source_id、文档 id 与 token 区间。
03 温度平滑怎样从 得到 ?#
一种常见规则是幂次平滑:
- :保持自然 token 比例;
- :所有非空来源等概率;
- :压低大来源、抬高小来源。
两来源分别有 900 与 100 个 token,。取 :
小来源从 10% 升到 25%,但不是 50%。若预算 ,期望抽到 2,500 个小来源 token。
04 “抬高权重”意味着重复看多少次?#
定义来源 的期望 epoch 倍数:
在上例中,若 ,大来源 ,小来源 。这提醒我们:公平的来源概率不等于公平的样本复用。应同时设置:
- 目标权重 ;
- 总 token 预算 ;
- 每来源最大复用倍数或耗尽策略;
- 去重后而非去重前的 。
05 为什么按文档采样会悄悄改错比例?#
来源 A 有 100 篇、每篇 1,000 token;来源 B 有 100 篇、每篇 100 token。若以 50/50 选择来源并各抽一篇,则 token 期望比例约是 10:1,而不是 1:1。
目标若定义在 token 上,应把每个来源先变成 token 流,再取固定长度 的 block。输入 batch:
一个 block 含 个输入 token,通常产生约 个 next-token 目标;边界或 padding mask 会让有效数略少。配比统计应数有效 loss token,而不是文件数或 batch 数。
06 跨文档拼接要保留什么边界?#
doc A tokens ─ EOS ─ doc B tokens ─ EOS ─► block [L]text若允许跨文档注意力,模型可能学习到人为相邻关系;若做 document mask,则需要额外的块状因果掩码。无论哪种选择,EOS 插入、丢弃尾部还是跨块延续,都必须固定。
极短文档不应因“每篇至少一个 block”而被系统性过采样。先拼接为来源 token 流,再统一切块更接近按 token 配比。
07 用 PyTorch 采样来源#
当前稳定 PyTorch 的 torch.multinomial 接受一维非负权重,权重不必预先归一化。要独立抽取每个 block 的来源,应设 replacement=True,并传入专用 Generator。
import torch
def temperature_weights(token_counts: torch.Tensor, alpha: float) -> torch.Tensor:
if token_counts.ndim != 1 or (token_counts <= 0).any():
raise ValueError("token_counts must be positive [K]")
if not 0.0 <= alpha <= 1.0:
raise ValueError("alpha must be in [0, 1]")
log_w = alpha * token_counts.double().log()
return torch.softmax(log_w, dim=0) # [K], sum=1
counts = torch.tensor([900, 100])
q = temperature_weights(counts, alpha=0.5)
torch.testing.assert_close(q, torch.tensor([0.75, 0.25], dtype=torch.float64))
generator = torch.Generator(device="cpu").manual_seed(20260912)
source_ids = torch.multinomial(
q, num_samples=8, replacement=True, generator=generator
) # [num_blocks=8]python用 log-space 计算可避免超大计数先做幂运算溢出。source_ids 的形状是 [num_blocks],不是 token 张量。
08 从来源流组装 [N,L]#
class TokenStream:
def __init__(self, blocks):
self.blocks = blocks # list[LongTensor[L]]
self.cursor = 0
def next_block(self, generator):
if self.cursor == len(self.blocks):
order = torch.randperm(len(self.blocks), generator=generator).tolist()
self.blocks = [self.blocks[i] for i in order]
self.cursor = 0
block = self.blocks[self.cursor]
self.cursor += 1
return block
def sample_batch(streams, q, batch_size, generator):
chosen = torch.multinomial(
q, batch_size, replacement=True, generator=generator
) # [N]
rows = [streams[i].next_block(generator) for i in chosen.tolist()]
input_ids = torch.stack(rows) # [N,L]
return input_ids, chosenpython输出 input_ids[N,L] 和 chosen[N]。训练端再按此前文章的规则生成右移标签。真实分布式任务不可让各 rank 读取同一 block;需把全局 step、rank、worker 和 epoch 纳入种子,或使用可恢复的计数器式随机数生成方案。
09 一次训练步怎样审计实际配比?#
token_tally = torch.zeros(len(streams), dtype=torch.long)
loss_tally = torch.zeros(len(streams), dtype=torch.float64)
logits = model(input_ids) # [N,L,V]
token_loss = torch.nn.functional.cross_entropy(
logits[:, :-1].reshape(-1, logits.size(-1)),
input_ids[:, 1:].reshape(-1),
reduction="none",
).view(input_ids.size(0), -1) # [N,L-1]
for row, source_id in enumerate(chosen):
sid = int(source_id)
token_tally[sid] += token_loss.size(1)
loss_tally[sid] += token_loss[row].sum().double()
loss = token_loss.mean()python短窗口中的观测比例会随机波动;长窗口才应接近 。用二项分布标准差 为 个 block 的计数设置告警带,别要求每 8 个 batch 精确等于目标。
10 静态权重与动态权重有什么不同?#
静态混合在训练前固定 ,简单、可复现,也便于消融。动态混合则根据每来源验证 loss、梯度信号或小型代理模型调整权重,但会形成反馈回路:
source batch ─► update model ─► validation loss
▲ │
└──── optimizer of mixture ◄───┘text某来源 loss 高,可能表示“仍值得学习”,也可能表示“噪声极大”;仅按高 loss 加权会追逐坏数据。动态方法必须使用独立目标集、权重上下限和慢更新频率,并记录每次版本。
11 预算、耗尽与课程学习#
| 策略 | 行为 | 主要风险 |
|---|---|---|
| 有放回混合 | 始终按 抽取 | 小来源多轮记忆 |
| 无放回后重算 | 来源耗尽便移除 | 后期分布突变 |
| 分阶段课程 | 预先定义 | 阶段边界与归因复杂 |
课程学习(Curriculum Learning)改变权重随 step 的函数,不等同于随机采样。若后期提高代码比例,应保存完整 schedule,恢复训练时从同一 global step 继续。
12 常见错误与最短调试路径#
| 症状 | 常见原因 | 最短检查 |
|---|---|---|
| 小来源仍几乎看不到 | 用文档数计算 | 改数有效 token |
| 权重正确但 token 比例错 | 每来源 block 长度不同 | 统一 或按 token 加权 |
| 多卡吞吐翻倍但数据没变 | rank 共用相同随机流 | 记录前 20 个全局 block id |
| 恢复后 loss 跳变 | 未保存 sampler 状态 | 保存 seed、cursor、step |
| 小来源训练 loss 极低 | 复用倍数过高 | 统计 unique/repeated token |
| 动态采样追逐噪声 | 把高 loss 当高价值 | 查看独立验证与质量分层 |
最短验证是:构造三个只有唯一 token id 的来源,抽取 100,000 个固定长度 block,核对观测频率、跨 rank 重复率和断点恢复后序列完全一致。
13 相近方法不要混为一谈#
损失加权是在样本已进入 batch 后改变梯度;采样权重改变它进入 batch 的概率,两者组合会相乘。温度采样只利用规模,不学习“价值”。质量过滤决定样本是否可用;去重消除重复;课程学习决定随时间的顺序;重要性采样若要得到无偏估计,还需按目标分布与提议分布之比校正。
模型也可能在固定配比下出现能力干扰:代码增加帮助推理却损害自然语言,或低资源语言因 tokenizer 过碎获得更少语义内容。最终选择应以每来源独立验证集、目标任务与固定总 FLOPs 的实验为准。
14 今天真正需要记住什么?#
- 自然 token 比例 是数据体积分布,不自动等于训练目标 。
- 用一个可解释参数平滑来源规模,但必须同时检查复用倍数 。
- 想控制 token 配比,就用等长 block 和有效 loss token 计数,不能只数文档或 batch。
- 随机种子、来源 cursor、global step 和分布式分片共同决定能否复现训练。
15 思考题与小练习#
- 三来源 token 数为 ,分别计算 的 ,解释最小来源的复用风险。
- 预算 、某来源 ,计算期望 epoch 倍数;设计一个最大 5 次复用的处理策略。
- 修改示例 sampler,使断点保存后能精确恢复
source_ids与每个来源 cursor,并写重启一致性测试。
相关工作#
- Raffel et al., Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer ↗,记录 C4 构建与多任务混合策略。
- Xue et al., mT5: A Massively Multilingual Pre-trained Text-to-Text Transformer ↗,使用温度采样平衡多语言数据。
- Gao et al., The Pile: An 800GB Dataset of Diverse Text for Language Modeling ↗,构建并记录多来源语言模型语料。
- Xie et al., DoReMi: Optimizing Data Mixtures Speeds Up Language Model Pretraining ↗,用代理模型学习领域混合权重。
- Longpre et al., A Pretrainer’s Guide to Training Data ↗,研究来源组成对预训练迁移的影响。
16 下一篇预告#
数据来源与预算已确定,下一步仍不能把任意长度文档直接塞进固定上下文窗口。下一篇将研究文档拼接、sequence packing、attention/loss 边界与 padding 浪费,追踪每个 token 到训练 block 的位置。