基于《大规模语言模型:从理论到实践(第2版)》第3–4章串讲
爆款小标题:原书第3–4章串讲:数据管线、损失函数与分布式训练如何配合
为什么这一节重要
前面几节分别讲了预训练数据从哪来、分布式怎么并、指令微调与 RLHF 怎么做。但要真正「把预训练跑起来」,还需要把数据管线(从原始语料到模型能吃的 input_ids)、损失函数(下一 token 预测与交叉熵)和训练循环(前向—反向—梯度同步—优化器更新—checkpoint)串成一条清晰的线。本节基于原书第 2 章语言模型目标、第 3 章数据与第 4 章分布式实践,做一次串讲,让你能说出「从原始 txt 到一次优化器更新」经过哪些步骤,以及预训练损失应如何正确计算与监控。
学习目标
学完本节,你将能够:
- 说清预训练目标:写出自回归语言模型的训练目标(下一 token 预测、交叉熵损失),并说明与「上下文长度」「batch size」「梯度累积」的关系。
- 描述数据管线:从「原始语料」到「可训练的 batch」列出至少 3 个必经步骤(如清洗、分片、tokenize、打包),并说明每步目的;理解多卡时数据如何分片与不重复。
- 理解训练循环与 checkpoint:说明单步训练包含前向、损失、反向、梯度同步(分布式)、优化器更新;checkpoint 通常需保存模型、优化器状态与步数等(原书第 4 章 DeepSpeed 实践)。
一、预训练目标:下一 token 预测与交叉熵(原书第 2–3 章)
语言模型目标:对序列 (x_1, x_2, \ldots, x_T),自回归语言模型建模条件概率 (P(x_t \mid x_1, \ldots, x_{t-1}))。训练时最大化序列的对数似然,等价于最小化交叉熵:
[ \mathcal{L} = -\frac{1}{T} \sum_{t=1}^{T} \log P(x_t \mid x_1, \ldots, x_{t-1}) ]
即对序列中每个位置(或每个非 padding 位置)的下一 token 预测算交叉熵,再求平均。通常按 token 平均(或按序列平均),这样长序列与短序列在 loss 中的权重可调(按 token 平均时长序列自然权重大)。原书第 2 章与第 3 章对目标与数据有对应说明。
与上下文长度、batch 的关系:上下文长度决定了每个样本的 token 数;batch size 与梯度累积步数共同决定有效 batch size(即一次优化器更新看到的 token 数 = batch_size × seq_len × grad_accum_steps)。预训练时常用较大有效 batch(如数百万 token per step),通过梯度累积与多卡数据并行实现。
注意:损失应对所有非 padding 位置计算,而不是只对「最后一个 token」;否则长程依赖得不到充分训练。实现时需用 mask 把 padding 位置从 loss 中排除。
二、数据管线:从原始文本到 batch(原书第 3–4 章)
步骤 1:清洗与去重(见 3.1 节)
原始语料经过清洗(格式、质量、安全)与去重(文档级或近似去重),得到「干净文本」流或文件。
步骤 2:分片与 tokenize
- 分片:把长文档切成固定长度(如 2048、4096)的片段,或按段落/句子边界切再拼接至约定长度;可重叠可不重叠,依实现而定。
- Tokenize:用词表与 tokenizer(如 BPE、SentencePiece)把文本转为 input_ids(整数序列)。注意要使用与模型一致的 tokenizer 与词表。
步骤 3:打包成 batch
- 将多条序列组成 batch;若长度不一,可 padding 到同一长度 或 packing(把多条短序列拼成一条长序列,用边界标记分隔)。Padding 时需在后续 loss 计算中 mask 掉 padding 位置。
- 分布式:数据并行时,每个进程应看到不同的数据分片(通过 DistributedSampler 或等价机制),保证同一 batch 内不重复、不同 step 间覆盖全量数据。
原书第 3–4 章对数据加载与多卡数据分片有说明;DeepSpeed 等框架通常与 PyTorch DataLoader 和 Sampler 配合使用。
三、训练循环与 checkpoint(原书第 4 章)
单步流程:
- 前向:输入 input_ids(及可选的 attention_mask、position_ids),模型输出 logits。
- 损失:对 logits 与 target(通常为 input_ids 右移一位)算交叉熵,并对非 padding 位置平均。
- 反向:loss.backward(),得到梯度。
- 梯度同步:若为数据并行,AllReduce 或 ReduceScatter 聚合各卡梯度。
- 优化器更新:optimizer.step(),更新参数;optimizer.zero_grad() 准备下一步。
Checkpoint:为便于恢复与评估,checkpoint 通常保存:模型权重(或分片)、优化器状态(若用 ZeRO 则可能分片)、当前步数/epoch、以及可选的 RNG 状态。原书第 4 章 DeepSpeed 实践部分给出了保存与加载的示例。
四、工程实战要点
1. 预训练时有效 batch 与显存
有效 batch = batch_size × seq_len × grad_accum_steps × n_gpus(数据并行时)。先小规模跑通(如 2K 长度、小 batch),再逐步放大;显存不足时增大梯度累积、减小单卡 batch 或启用 ZeRO。有效 batch 过小可能导致训练不稳定或收敛慢,过大则需相应调大学习率或 warmup 步数;业界常用数百万 token/step 量级,具体依模型规模与数据量而定。
2. 数据与脚本版本化
数据版本、tokenizer 版本与训练脚本应一起记录,便于复现与排查「换数据后效果变差」等问题。建议在 checkpoint 或实验记录中保存:数据路径与版本、tokenizer 配置、超参数、以及训练命令,便于后续对比与回溯。
3. loss 监控与异常检测
训练过程中应监控 loss 曲线:若 loss 不下降、震荡剧烈或突然上升,可能是学习率过大、数据异常或梯度爆炸。可配合 gradient norm 监控,异常时及早停止并排查。分布式训练时,各卡的 loss 应基本一致(因数据不同会有小幅差异),若某卡 loss 明显偏离,可能是数据分片或通信问题。
五、常见误区与避坑指南
误区一:预训练损失只盯「最后一个 token」
应对序列所有非 padding 位置算损失。避坑:实现时用 attention_mask 或 labels_mask 排除 padding,保证 loss 正确。
误区二:数据并行时每卡数据一样
每卡应看到不同分片。避坑:使用正确的 DistributedSampler 或等价机制,并确认数据不重复、不遗漏。
六、小结与衔接
本节串讲了预训练目标(下一 token 交叉熵)、数据管线(清洗—分片—tokenize—打包)与训练循环(前向—损失—反向—同步—更新—checkpoint),便于把原书第 3–4 章与前面 3.1–3.4 连成完整训练视角。下一模块将进入RAG 与检索:如何用外部检索增强生成、减少幻觉并注入领域与时效知识(原书第 9 章)。
课后思考题
- 若上下文长度为 2048、batch size 为 32,梯度累积 4 步,则一次优化器更新对应的「有效 token 数」是多少?
- 从「原始 txt 语料」到「模型能吃的 input_ids」,请列出你认为必须的 3 个步骤,并简要说明每步的目的。