LLM预训练阶段深度拆解——从数据到基座模型的全流程解析

一、为什么预训练是大模型的第一性原理

任何一个 LLM 的一生可以分成三个阶段:预训练(Pre-training)→ 对齐(Alignment)→ 推理部署(Inference)。这三个阶段占据的成本比例大概是 95% : 4% : 1% —— 预训练花掉的钱和算力是所有后续工作的起点。

如果你把 LLM 看作一个函数拟合器,预训练就是在一个超大规模数据集上,找到一组能让模型”理解”语言分布的参数。这个”理解”不是哲学意义上的理解——它只是学到了 token 序列的条件概率分布 P(token_t | token_{<t})。但就是这么一个简单的目标函数,当模型参数量越过某个临界点(~6B),会涌现出令人惊讶的能力:数学推理、代码生成、上下文学习(in-context learning)、乃至思维链(chain-of-thought)。

这篇文章从工程实践的角度,把预训练的全流程拆开来讲:数据怎么准备、tokenizer 怎么训练、模型该怎么搭建、分布式训练怎么排布 GPU、怎么防止训崩——每一步都有具体的数字和决策逻辑。读完你应该能回答一个问题:如果给我一笔预算和一堆 GPU,我能从头训一个还过得去的基座模型出来吗?


二、数据工程:99%的汗水,1%的灵感

预训练的本质不是模型架构创新,而是数据蒸馏。 模型的好坏上限由数据质量决定,下限由算力决定。这部分的工程工作量占整个预训练流程的 60-70%。

2.1 数据从哪来

所有开源 LLM 的数据起点都是 Common Crawl——一个每月爬取的全网网页快照,原始数据量级在 PB 级别。但 Common Crawl 的原始数据质量极低:HTML 标签碎片、广告、垃圾 SEO 内容、成人内容、机器翻译文本、重复页面——直接喂给模型等于投毒。

实际使用的数据集是经过重重过滤后的子集。以下是最具代表性的几个:

数据集 规模 特点
C4 (Colossal Clean Crawled Corpus) ~750GB 最早的标准化过滤数据集,只用英文
The Pile ~825GB 22 个高质量子集混合(PubMed、GitHub、Books3 等)
RefinedWeb (Falcon) ~5TB 严格过滤 + 去重,证明了纯 Web 数据+足够大=能打
FineWeb (HuggingFace) ~15TB 2024 年最强开源 Web 数据集,edu 分类器 + MinHash 去重
DCLM ~7TB Apple 出品,fastText 分类器 + 模型化过滤
Dolma (AI2) ~3TB 完全开源可复现的管线

RefinedWeb 论文证明了一个重要结论:纯 Web 数据,只要过滤和去重做到位,不需要刻意混入教科书、Wikipedia 等”高质量”源也能训出 SOTA 模型。 FineWeb 在这个路线上更进一步,在 15T token 规模上实现了对 C4 和 RefinedWeb 的全面超越。

2.2 质量过滤:五道关卡

把 Common Crawl 变成一个可用的数据集,标准管线如下:

第一关:语言检测。 用 fastText 或其他语言分类器过滤掉非目标语言的页面。这一步通常能砍掉 40-50% 的数据。

第二关:启发式规则过滤。 这是最脏最累但最有效的部分。典型规则包括:

1
2
3
4
5
6
- 丢弃正文长度 < 100 字符的页面
- 丢弃 JavaScript/JSON 内容占比 > 30% 的页面
- 丢弃重复 n-gram 占比 > 70% 的页面(机器人生成内容)
- 丢弃包含大量特殊符号或乱码的页面
- 丢弃导航链接密度异常的页面(导航页、目录页)
- 去除 HTML 标签、CSS、广告脚本

HuggingFace 的 trafilaturajustext 是两个最常用的正文提取库。FineWeb 使用了 trafilatura 作为主提取器。

第三关:模型化质量分类器。 这步比较高级——训练一个轻量分类器,给每篇文档打”教育价值分”。FineWeb 的做法是用 Llama-3-70B-Instruct 对几千篇文档标 0-5 分的教育质量分数,然后用这个标注集训练一个 fastText 分类器,对全量数据进行评分,只保留 ≥2 分的文档。

DCLM(Apple)的方法更直接:训练一个 fastText 分类器来区分”好数据”(Wikipedia、OpenWebText)和”坏数据”(随机 Common Crawl),然后对每篇文档打分。

第四关:个人信息过滤。 去除邮箱、电话号码、IP 地址、身份证号等 PII。可以用正则 + NER 模型组合来做。BigCode 的 PII 检测工具是目前开源最好的选择。

第五关:有毒/有害内容过滤。 用 toxicity 分类器(如 Perspective API 或开源替代)筛掉仇恨言论、暴力内容等。这步对安全对齐很重要,但尺度要把握好——筛太多会丢失重要的社会语境数据。

2.3 去重:MinHash LSH 的原理

去重是数据预处理中对模型性能提升最显著的一步。Lee et al. (2022) 证明:去重后的数据训练出的模型,同 perplexity 下的事实性错误显著更少,且 memorization 风险降低。

去重分两层:

文档级去重(Document-level dedup): 对整个文档做模糊哈希,剔除相似度 > 80% 的文档对。常用工具是 SimHash 或 MinHash + LSH。

段落级去重(Paragraph-level dedup): 把文档切成段落(~256 tokens),对段落做 MinHash,删除重复段落。这步能去掉跨文档共享的模板化文本(如网站 footer、版权声明),对模型质量提升比文档级去重更大。

MinHash LSH 的工作原理:

  1. 选 N 个哈希函数。 对文档的所有 n-gram 分别计算哈希值
  2. 取最小值。 对每个哈希函数,取所有 n-gram 哈希值中的最小值,得到一个 N 维签名向量
  3. LSH 分桶。 将签名向量分成 b 个 band,每个 band 内做精确匹配。两个文档只要有一个 band 完全匹配,就认为候选相似
  4. 精确比对。 对候选对做 Jaccard 相似度精确计算,确认是否真的重复

这个算法的时间复杂度是 O(n) 而非 O(n^2),能在 TB 级文本上高效运行。FineWeb 在 15TB 数据上跑完整去重,用 Datatrove 库实现。

2.4 数据配比:不是越多越好

把所有数据扔进锅里乱炖是行不通的。不同来源的数据有不同的”营养密度”。一项来自 DeepMind 的研究给出了几条经验法则:

Epoch 数控制。 高质量数据(Wikipedia、书籍)可以多跑几轮(5-10 epochs),低质量数据(Common Crawl)建议 1-2 epochs 或更少。重复过多低质量数据会导致过拟合。

类别配比。 Llama 3 的配比经验(来自 Meta 公开发布的信息):

1
2
3
4
5
- Web text (Common Crawl + filtered): ~50%
- Code (GitHub, StackOverflow): ~25-30%
- Books + academic papers: ~10-15%
- Wikipedia + reference: ~5%
- Math, reasoning: ~5%

代码数据的占比近年来一直在上升。DeepSeek-V3 和 Qwen 2.5 都大幅提高了代码和数学数据的比例,这是提升推理能力最直接的手段。

去重后再配比。 如果先去重再按来源配比,能避免某个来源的高重复率稀释了其他来源的有效 token 数。

2.5 数据合成(Synthetic Data)

高质量数据的稀缺是一个硬约束。FineWeb 在过滤完 15TB 之后,高质量子集(edu score ≥ 2)只有约 2.5TB。一个日益流行的做法是用强模型生成训练数据

Phi 系列(Microsoft)是最极端的实践者:Phi-1 用了 7B token 的 GPT-3.5 生成数据,Phi-2 用了 1.4T token 的 GPT-4 生成数据,两个小模型在标准 benchmark 上分别超越了同类大小模型 20 个百分点以上。Phi-3 和 Phi-4 延续了这一路线——用更大模型做 teacher 生成教科书级别的数据,训小模型做 student。

合成数据的核心挑战是多样性崩塌:teacher 模型生成的文本分布比自然语言窄,反复用合成数据训练会导致模型坍缩到某个狭窄分布。解决方案是多样化 prompt 模板、引入随机采样温度、以及混合一定比例的真实数据。


三、Tokenizer:模型的第一公里也是最后一公里

Tokenizer 是预训练中最被低估的组件。它决定了模型”看”世界的基本单元,直接影响推理速度、多语言能力、和下游任务表现。

3.1 BPE:统治世界的算法

目前几乎所有主流 LLM 使用的都是 Byte-Pair Encoding(BPE) 或其变体。核心思想很朴素:

初始词汇表。 从 256 个 byte 值 + 少量特殊字符开始。

迭代合并。 每轮扫描整个训练语料,找到出现频率最高的相邻 token 对 (A, B),将它们合并成一个新的 token “AB”,加入词汇表。

重复 N 轮。 直到词汇表达到预设大小(通常是 32K、50K、100K、128K 等)。

BPE 的优雅之处在于:它是一种数据驱动的压缩算法。常见的词如 “the” 会成为一个独立 token,常见的词根如 “ing”、”tion” 会被合并,而生僻词或代码中的随机字符串会被拆成更小的子词甚至单个 byte。这意味着 BPE 天然能处理任何 Unicode 字符,包括 emoji、代码、数学符号——不需要手动定义词表。

3.2 BPE 的关键参数

词汇表大小(Vocab Size)。 这是一个工程权衡:

1
2
3
4
- 大词表 (128K-256K): token 更"语义化",相同文本生成的 token 数更少 → 推理快
但 embedding 矩阵更大(d_model × vocab_size),训练开销增加
- 小词表 (32K-50K): embedding 层轻量
但文本被拆成更多 token → 序列变长 → attention 计算量增加

Llama 1/2 使用 32K,Llama 3 提升到 128K。DeepSeek-V3 使用 128K(实际 102400 有效 + 保留位)。Qwen 2.5 使用 151936——多语言场景下大词表几乎是必须的,因为中文、韩文、日文等语言的字符集远大于拉丁语言。

训练数据量。 标准的 BPE 训练只需几 GB 文本就够了,但数据分布至关重要。如果你希望模型擅长中文,必须保证中文文本在 tokenizer 训练数据中占据足够比例(建议 ≥30%),否则中文会被拆成大量碎片化的 byte 级 token。

3.3 BPE 的变体:Byte-level BPE 和 SentencePiece

Byte-level BPE(BBPE,GPT-2/3/4, Llama 系列): BPE 的 merge 操作不基于 Unicode 字符,而是基于 UTF-8 字节。好处是完全消除了未知字符(UNK token)的概念——任何 Unicode 序列都可以表示为 0-255 的字节序列,然后应用 BPE。代价是初始序列长度是原来的 2-4 倍(一个中文字符通常 3 字节)。

SentencePiece(T5, PaLM, Gemma): Google 系的 tokenizer 库,底层支持 BPE 和 Unigram 两种算法。最大的特点是直接在原始文本上训练,不需要预先分词(空格也算 token),且把所有空格替换为 (U+2581)字符来实现可逆编码。训练完成后的 tokenizer 是自包含的——一个 model 文件 + 一个 vocab 文件,跨框架移植很方便。

3.4 特殊 Token 设计

特殊 token 虽然数量少(通常 < 20 个),但设计差异直接影响模型的对话体感:

1
2
3
4
<|begin_of_text|>  - 序列起始标记,Llama 3 引入
<|end_of_text|> - 序列结束标记,等同于 EOS
<|start_header_id|> ... <|end_header_id|> - 角色标签(user/assistant/system)
<|eot_id|> - 对话轮次结束(Llama 3 的 "end of turn")

Llama 3 的特殊 token 设计是一次重要的工程改进:用明确的标记界定对话轮次和角色,让模型不再依赖 prompt 模板中的字符串模式(如 “### User:”),大幅减少 prompt injection 和格式混淆问题。

3.5 Tokenizer 评估:Fertility Score

怎么评估一个 tokenizer 好不好?常用指标是 Fertility(繁殖率)—— 平均一个词被切成多少个 token。1.0 是最理想情况(一个词 = 一个 token)。

不同语言的 fertility 差异巨大:

语言 英文 tokenizer(32K)Fertility 说明
English ~1.3 基准
French/Spanish ~1.6 拉丁语系尚可
Chinese ~2.5-4.5 取决于 tokenizer 是否包含中文字符
Japanese/Korean ~4-8 BPE 未经适配时极差
Code (Python) ~2.0 特殊符号和数字拆得厉害

Fertility 每高 1,序列长度就增加一倍,训练和推理成本线性增加。这就是为什么多语言模型往往需要更大的词汇表——比起 embedding 矩阵多一倍的参数,序列长度翻倍带来的 attention O(n^2) 开销要贵得多。


四、模型架构:Transformer 的当代形态

2017 年的原始 Transformer 是一个很好的起点,但今天你在实际训练中使用的架构已经做了大量细节改动。这些改动每一个单独看都是小优化,叠在一起对训练稳定性和最终性能有显著影响。

4.1 核心组件全景图

一个完整的 decoder-only Transformer block 长这样:

1
Input → RMSNorm → Attention(Q, K, V) → Residual(+Input) → RMSNorm → SwiGLU FFN → Residual(+Output) → ...

每个组件的选择都不是随意的:

组件 原始 Transformer 当代标准 为什么改
Normalization Post-LN (LayerNorm after residual) Pre-LN + RMSNorm Pre-LN 更稳定,RMSNorm 更快
激活函数 ReLU SwiGLU SwiGLU 性能显著好于 ReLU/GeLU
位置编码 Sinusoidal RoPE RoPE 外推性更好
Attention MHA (Multi-Head) GQA (Grouped Query) GQA 大幅减少 KV cache
FFN 4× expansion 8/3×4 ≈ 10.67× (SwiGLU 等效) SwiGLU 需要调整比例

下面逐个展开。

4.2 Pre-LN + RMSNorm

原始 Transformer 把 Layer Norm 放在残差连接之后(Post-LN):

1
x = LayerNorm(Sublayer(x) + x)  # Post-LN: 归一化在残差之后

这个方案在深层网络中梯度不稳定——反向传播时,残差分支的梯度会经过 Layer Norm 的梯度缩放,在深层累积出爆炸的方差。Pre-LN 把归一化移到子层之前:

1
x = Sublayer(LayerNorm(x)) + x  # Pre-LN: 归一化在子层之前

好处是残差分支的梯度永远是 1——梯度可以无损地流回浅层。代价是 Pre-LN 的”身份映射”偏置更强,训练初期模型更快收敛到一个接近恒等函数的解,有人 argue 这限制了模型的上限表达能力。NanoGPT 和大多数开源实现选 Pre-LN 是因为训练更稳定、不太会训崩

RMSNorm 是 LayerNorm 的简化版:去掉了均值的中心化操作,只做根均方归一化,计算量减少约 30%,且效果无统计学显著差异。Llama、Mistral、Qwen 全用了 RMSNorm。

4.3 RoPE(旋转位置编码)

位置编码告诉自注意力机制”哪个 token 在哪个位置”。RoPE(Rotary Position Embedding)是当前的事实标准,被 Llama、Mistral、Qwen、DeepSeek、Gemma 等几乎所有主流模型采用。

核心思想:通过旋转矩阵把位置信息编码到 attention 的 Q 和 K 向量中,使得 Q_i 和 K_j 的点积自带位置差 (i-j) 信息。

具体做法是对 Q 和 K 向量的每两个相邻维度施加一个角度为 θ_pos = pos / 10000^(2i/d) 的二维旋转。数学上等价于在复数域中将向量乘以 e^(iθ)。

RoPE 相比 ALiBi(只加 bias)和 T5 bias 有三个核心优势:

  1. 相对位置自然内嵌: 旋转后的点积只依赖位置差 (m-n),不需要学习位置嵌入
  2. 长度外推: 可以通过插值(PI/NTK/YaRN)让预训练长度(如 4K)的模型在推理时处理更长上下文(如 128K 甚至 1M+)
  3. 训练效率: 不需要额外的可学习位置嵌入参数

4.4 GQA(Grouped Query Attention)

MHA(多头注意力)的问题是 KV cache 太大。每个 attention head 都有一对 K、V 矩阵,推理时所有历史 token 的 K/V 都要存在显存里。

MQA(Multi-Query Attention,多查询注意力): 所有 head 共享一组 K/V,只有 Q 是独立的。KV cache 缩小到 MHA 的 1/h(h 为 head 数)。但训练时容易不稳定,表达能力略有损失。

GQA(Grouped Query Attention,分组查询注意力): MHA 和 MQA 的折中。把 Q heads 分成 G 组,每组共享一组 K/V。例如 Llama 3 70B 用 G=8,表示 64 个 Q heads 分成 8 组,每组 8 个 Q heads 共享 1 个 K/V head。

KV cache 大小:MHA → MQA 是 h× 压缩,MHA → GQA 是 h/G× 压缩。在实际部署中,GQA 的推理吞吐远好于 MHA,且训练效果和 MHA 基本持平。

4.5 SwiGLU 激活函数

GLU(Gated Linear Unit)的基本形式是 output = gate ⊙ up,其中 gate 通过激活函数控制信息流,up 是线性变换的结果。

SwiGLU = Swish 激活 + GLU 门控:

1
2
SwiGLU(x) = Swish(xW_gate) ⊙ (xW_up)
Swish(x) = x · σ(x) (σ 是 sigmoid)

相比 ReLU,SwiGLU 的优势:非单调性允许”门”在某些区域关闭(Swish < 0 的情况),提供更好的非线性表达能力;平滑函数对梯度友好;实际 benchmark 提升 1-3%。

代价:GLU 需要两个线性变换(gate 和 up),参数量是普通 FFN 的 1.5 倍。常见的补偿做法是把 SwiGLU FFN 的 hidden dim 从 4×d_model 调成 8/3×d_model ≈ 2.67×,使总参数量基本持平。


五、分布式训练:让一千块 GPU 协作

当模型参数超过 10B,单张 GPU 装不下了。即使装得下(比如 H100 80GB 能装下 13B 的 FP16 模型),训练速度也慢到不可接受——一张卡跑完 1T token 可能要几十年。

分布式训练的目标:把计算和显存分摊到 N 张 GPU 上,让训练时间接近线性缩短。 实际中的通信开销使得线性加速是不可能的,但工程目标是把效率保持在 50-60% 以上。

5.1 显存到底被什么占了

先算一笔账,以 Llama 2 7B(FP16 训练)为例:

1
2
3
4
5
6
模型参数 (7B × 2 bytes):      14 GB
优化器状态 (Adam, 每个参数 3 个值): 42 GB (momentum + variance + master weights)
梯度 (7B × 2 bytes): 14 GB
激活值(取决于 batch size): ~10-30 GB
─────────────────────────────────────────
总计: 80-100 GB

一张 H100 有 80GB,A100 有 40/80GB。7B 模型在 FP16 下需要 3-4 张 A100 80GB 才能装下训练状态。这还只是 7B——100B+ 模型需要几百张卡。

显存的大头不是模型参数,而是优化器状态。 参数、梯度、优化器状态的比例约是 1:1:3。所以 FP16 训练中,实际总显存约是模型大小的 6-8 倍。

5.2 数据并行(DP)与 ZeRO

数据并行是最直观的方案:每个 GPU 存一份完整模型,吃不同的 mini-batch,反向传播后对所有 GPU 的梯度做 AllReduce 求平均。简单直接但不省显存——每张卡要有完整的模型 + 优化器状态。

ZeRO-1(DeepSpeed): 优化器状态切分。把 Adam 的 momentum 和 variance 切分到所有 GPU 上,每个 GPU 只存 1/N。通信量是 AllReduce 量级(每个 step 通信参数量级数据),基本不增加通信开销。省 3/4 的冗余(从 1:1:3 变成 1:1:3/N)。

ZeRO-2: 在 ZeRO-1 基础上再切分梯度。每个 GPU 只存自己那部分参数的梯度。通信量不变,内存再省梯度的那份。

ZeRO-3: 连模型参数也切分。每个 GPU 只存 1/N 的模型参数,前向/反向传播时需要哪部分参数就临时从其他 GPU 拉取。这也是为什么 ZeRO-3 训练时 GPU 显存看起来”只用了 30% 不到”——大部分参数在用的时候才通信加载。 代价是通信量显著增加(每个 layer 的前向和反向都有 AllGather),对节点内带宽(NVLink)有硬要求,跨节点跑 ZeRO-3 速度会掉得很厉害。

5.3 流水线并行(PP)

把模型按层切分:GPU 0 跑 layers 1-10,GPU 1 跑 layers 11-20,以此类推。问题是——GPU 1 必须等 GPU 0 算完 batch 的前半部分才能开始算。标准做法里大部分时间 GPU 是空闲的。

GPipe 方案: 把 mini-batch 切成多个 micro-batch。micro-batch 1 跑完 GPU 0 后立刻进入 GPU 1,同时 GPU 0 开始处理 micro-batch 2。这样流水线填充后,空闲时间(bubble)从一个 micro-batch 的 K-1 个 step(K=层切片数)缩减到 O(K/N_micro)。micro-batch 越多气泡越小,但显存占用按 micro-batch 线性增加。

1F1B(One Forward One Backward)调度: 前向跑满 N_micro 个 micro-batch 填充流水线后,立即开始反向传播——每个 GPU 做一次前向就做一次反向(1F1B),交替进行。这是 DeepSpeed 和 Megatron-LM 的默认策略。

5.4 张量并行(TP)

在单个 transformer block 内部切分。例如把 self-attention 的 QKV 矩阵沿列切分到 4 张 GPU,每张 GPU 只算 1/4 —— 整个矩阵乘法的中间结果在 GPU 之间做 AllReduce。

张量并行的优势是通信在 NVLink 范围内(节点内),带宽极高(H100 NVLink 4.0 约 900GB/s 双向)。劣势是通信量很大——每个前向/反向 step 都要至少两次 AllReduce。所以通常只在节点内(8 张 GPU)用 TP,跨节点改用 PP 或 ZeRO。

5.5 3D 并行:实战组合

实际训练一个 70B+ 的模型,三种并行的组合如下:

1
2
3
4
TP = 4(节点内 4 GPU 张量并行)
PP = 4(4 个 pipeline stage)
DP = 16(ZeRO-1/2 切分优化器状态)
总 GPU: 4 × 4 × 16 = 256 张

更进一步的真实案例——Meta 训练 Llama 3 405B 时使用的拓扑:

1
2
3
4
TP = 8  (单节点 8 GPU 张量并行)
PP = 16 (16 个 pipeline stages,一个 stage 占 2 层)
DP = 512 (数据并行,跨所有 512 个 pipeline replica)
总 GPU: 8 × 16 = 128 张/副本 × 512 副本 = 16384 张 H100

注意:Llama 3 405B 没有使用 ZeRO,而是纯 DP——每个数据并行副本维护完整的模型参数和优化器状态。这意味着每个数据并行副本需要约 4TB 显存(405B × 2 × 8 ≈ 6.5TB,分摊到每张卡 50GB),这对于 H100 80GB 在 TP=8 后是可行的。

5.6 通信瓶颈量化

在实际训练中,通信时间往往超过计算时间。不同并行策略的通信量级:

策略 通信量级 通信模式 对带宽要求
DP (AllReduce) O(参数量) 每 step AllReduce 中(100Gbps 起步)
ZeRO-3 O(参数量) 每 layer 每 step AllGather + ReduceScatter 高(NVLink 更好)
PP O(batch_size × d_model) 每 micro-batch P2P send/recv
TP O(batch_size × d_model × #layers) 每 step AllReduce × 2 极高(必须 NVLink)

选择策略时把带宽最高的通信(TP 的 AllReduce)放在节点内 NVLink,中等带宽的(ZeRO-3 的 AllGather)放在同机架内,低带宽的(PP 的 P2P)可以跨机架。这是一个硬件拓扑驱动的软件设计问题。


六、混合精度与内存优化

6.1 FP16/BF16/FP8

全精度 FP32 训练太贵(参数 + 梯度 + 优化器各 4 bytes)。业界演进路线:

FP16(半精度,2017-2021): 参数和梯度用 FP16(2 bytes),但 FP16 的动态范围太小(表示范围 ±65504),大梯度会溢出到 Inf/NaN。解决方案是 Loss Scaling——在前向之前把 loss 放大 N 倍(N 通常是 2^8 到 2^16),反向传播时梯度跟着放大,更新前再缩小回去。这是个 hack,但奏效。NVIDIA Apex 是最早的实现。

BF16(2021-至今): Brain Float 16 是 Google 提出、Intel/AMD/NVIDIA 跟进的标准。和 FP16 一样是 2 bytes,但指数位从 5 位扩大到 8 位(和 FP32 一样),尾数位从 10 位缩减到 7 位。结果是:BF16 的表示范围 = FP32 的表示范围,不需要 loss scaling,不会出现梯度溢出。代价是精度稍低(7 位尾数 vs FP16 的 10 位),但对于梯度这种本身就是近似值的量来说完全足够。

FP8(2024-至今,H100/H200): NVIDIA Hopper 架构引入的 FP8 Tensor Core 支持两种格式——E5M2(5 指数 2 尾数,大范围低精度)和 E4M3(4 指数 3 尾数,小范围高精度)。前向用 E4M3,反向用 E5M2。FP8 相比 BF16 把带宽和内存占用再砍一半,但需要精细的 per-tensor scaling 来防止溢出。Transformer Engine(NVIDIA)在内部自动处理 scaling factor。实际加速约 1.3-1.5x(不是 2x,因为 scaling 操作和部分层仍保持高精度)。

6.2 Gradient Checkpointing / Activation Recomputation

训练时,前向传播产生的激活值要存下来供反向传播使用。对于一个 L 层的模型,每层的激活值大概是 batch_size × seq_len × d_model × 4 bytes。seq_len=4096, d_model=4096, batch_size=64 时:

1
2
单层激活值 = 64 × 4096 × 4096 × 4 / 1024^3 ≈ 4 GB
40 层 = 160 GB —— 远超任何 GPU 显存

Gradient Checkpointing 的核心 tradeoff:反向传播需要激活值时,不从显存读(删掉了),而是重新算一遍前向。这用约 20-30% 的计算开销,换回数十倍的显存节省。实践中通常每隔 1-2 层设一个 checkpoint,或者对整个 transformer block 做 full checkpoint(Llama 3 的做法)。

PyTorch 的 torch.utils.checkpoint 和 Transformer Engine 的 te.checkpoint 提供了开箱即用的实现。

6.3 Flash Attention

标准 attention 计算需要构造一个 (seq_len × seq_len) 的注意力矩阵,对长序列来说这是灾难——4096 长序列就是 64MB 的 softmax 中间结果(HBM 内),长上下文(128K)更是 O(128K^2) = 64GB 的理论值。

Flash Attention 的洞见很优雅:attention 可以分块计算。

核心技巧是 tiling + online softmax

  1. 把 Q、K、V 切成小块(如 128×128)
  2. 每个 block 在 SRAM(比 HBM 快 ~10x)中计算局部 softmax
  3. 用一个递推公式把各 block 的结果拼起来,和全局 softmax 数学等价

Flash Attention 的 I/O 复杂度从 O(N^2) 降到了 O(N^2/d^2 × M),其中 M 是 SRAM 大小。实测加速 2-4x(长序列效果更好),且显存占用接近 O(N)。Flash Attention 2 加入了对 A100/H100 Tensor Core 的专门优化,转发速度进一步提升 2x。

所有现代 LLM 训练框架(Megatron-LM、DeepSpeed、HuggingFace、vLLM)都已集成 Flash Attention 2/3。


七、训练稳定性:怎么防止训崩

7.1 Learning Rate Schedule

标准的 LR schedule 是 Warmup + Cosine Decay:

Warmup: 前 N 步(通常 1000-5000 步)从 0 线性增长到最大值。为什么要 warmup?初始化时模型参数是随机的,梯度方向不稳定。直接上大 learning rate 会让方向还不靠谱的梯度把参数推到一个糟糕的 basin。Warmup 给时间让 Adam 的动量积累一阶/二阶统计量。

Cosine Decay: warmup 结束后 learning rate 沿余弦曲线从峰值平滑降到接近 0。相比 constant LR,cosine decay 通常带来 0.1-0.3 的困惑度优势。

峰值 learning rate 的选择 主要取决于 batch size 和模型大小:

1
2
3
lr_max ≈ 3e-4 ~ 1e-3(较小模型 1B-7B)
lr_max ≈ 1e-4 ~ 3e-4(中等模型 7B-70B)
lr_max ≈ 5e-5 ~ 1e-4(大模型 70B+)

大的 batch size 允许/需要更大的 learning rate(梯度估计更精确),这大致符合平方根缩放律。但更大的模型需要更小的 learning rate——参数量越大,每步更新的”步子”要越小。

7.2 Loss Spike:训到一半突然炸了

Loss spike 是每个预训练工程师的噩梦:训了几千 GPU 小时,loss curve 突然从 2.3 跳到 20、500,甚至直接 NaN。

常见原因和应对:

原因 1:Batch 内出现脏数据。 某几个 batch 包含未经过滤的恶意内容或格式异常数据,导致 loss 异常大。应对:监控 per-batch loss variance,当某个 batch 的 loss 超过移动平均的 N 个标准差时,丢弃该 batch 的梯度更新。

原因 2:Gradient 爆炸。 深度网络中某几层的梯度过大,在 FP16 下溢出。BF16 基本解决这个问题,但极端值仍可能发生。应对:Gradient Clipping——把全局梯度的 L2 范数裁剪到预设阈值(通常 0.1-1.0)。

原因 3:Adam 二阶矩估算失灵。 β_2 过大(默认 0.999)时,Adam 的二阶矩对近期梯度变化不敏感。训练到一半某个参数的梯度突然变大,Adam 没能及时反映——更新步长过大 → loss spike。应对:减小 β_2(如 0.95),或直接重新初始化 Adam 状态。

原因 4:硬件故障。 GPU 内存位翻转(cosmic ray / 硬件老化)导致参数中出现 NaN。H100 比 A100 更稳定但在大规模集群上概率不可忽略。应对:定期 health check,挂掉后恢复训练时跳过问题数据段。

如果已经发生了 spike:

  1. 回到 spike 前的最后一个 checkpoint
  2. 跳过导致 spike 的那个 batch(通过记录 global step 定位)
  3. 减小 learning rate 10-20%,从此继续训练
  4. 加强 gradient clipping 阈值

7.3 Batch Size 与 Training Dynamics

小 batch size(< 256):梯度噪声大,训练不稳但泛化可能更好(噪声 = 隐式正则化)。
大 batch size(> 1024):梯度估计精确,训练更稳定但可能卡在 sharp minima 导致泛化差。

当代实践:batch size 递增策略。 训练初期用小 batch(如 128),随着模型进入稳定区域后逐步增大(512 → 1024 → 4096 → …),让模型在早期多探索(高噪声),后期在好区域里精细收敛(低噪声)。Llama 3 405B 训练的 batch size 从初始的 ~4M tokens 逐步增大到 ~16M tokens(1 GPU 约 4M tokens,通过梯度累积等效)。


八、评估与 Scaling Law

8.1 常用 Benchmark 全景

预训练阶段的评估通常不跑下游任务 fine-tuning,而是在”零样本/少样本”设定下测:

Benchmark 测什么 典型指标
MMLU 57 个学科的多选题(法律→物理→医学) 0-shot/5-shot accuracy
HellaSwag 常识推理,选最佳故事结局 accuracy
ARC-Challenge 科学推理多选题 accuracy
HumanEval / MBPP 代码生成 pass@1, pass@10
GSM8K / MATH 数学推理 0-shot accuracy
WinoGrande 代词消歧 / 常识 accuracy
BBH / MMLU-Pro 更难的推理 accuracy

评估频率:大训练(70B+)通常是每 10B-50B tokens 跑一次 eval。小模型可以每 1B-5B tokens 跑一次。评估结果用于监控 loss 之外的能力增长是否健康——loss 下降不一定等于能力提升,特别是后段(overtraining 时 loss 继续降但 benchmark 不动甚至倒退)。

8.2 Scaling Law:花多少钱买多少能力

Scaling law 是目前业界对预训练最靠谱的指导理论。核心结论来自 Kaplan et al. (2020, OpenAI) 和 Hoffmann et al. (2022, DeepMind / Chinchilla):

Kaplan scaling law(第一个版本): 给定算力预算 C,最优的模型大小 N 和训练数据量 D 满足 N ∝ C^0.73, D ∝ C^0.27。翻译成人话:有钱先堆模型参数,数据多点少点问题不大。

Chinchilla scaling law(修正版本): Kaplan 的结论是错的——他们没控制 learning rate schedule 的等效性。Chinchilla 的修正结论是 N ∝ C^0.5, D ∝ C^0.5 —— 参数和数据应该等比例增长。对于给定的算力预算,一个”正好”的模型应该让每个参数吃掉约 20 个训练 token。 这就是为什么我们看到很多模型叫 “Chinchilla-optimal” ——参数量和训练 token 数保持 ~1:20 的比例。

实际中的”过度训练”(Overtraining): Chinchilla 平衡点在推理成本敏感的场景下不是最优的。小模型 + 大数据(如 Llama 3 8B 训了 15T tokens)虽然”浪费”了 Chinchilla 准则下的算力,但换来的是极低的推理成本——8B 模型用一块消费级显卡就能跑,用户部署门槛极低。这也是 Meta、Microsoft(Phi)、Google(Gemma)同时在走的方向:推小模型但给极多数据。

下表给出几个典型模型的对比:

模型 参数量 训练 token 数 tokens/param Chinchilla-optimal?
Chinchilla 70B 70B 1.4T 20 ✅ 定义标准
Llama 2 70B 70B 2T 28.6 ~接近
Llama 3 8B 8B 15T 1875 ❌ 严重过度训练
Llama 3 405B 405B 15T 37 ~接近
DeepSeek-V3 671B 671B (37B active MoE) 14.8T ~400/有效 ⚠️ MoE 不直接适用
Phi-3 Mini 3.8B 3.8B 3.3T 868 ❌ 有意过度训练
Qwen 2.5 72B 72B 18T 250 ❌ 重度过度训练

过度训练的趋势非常明确——2024-2025 年的共识是:多喂数据比追求 Chinchilla 平衡点更划算(因为推理成本 >> 训练成本),尤其是对中小尺寸模型。

8.3 为什么 Scaling Law 仍会 hold

Scaling law 背后的直觉很简单:神经网络是在做高维插值。更多的数据 = 更密集的采样点,更大的模型 = 更灵活的插值函数。只要数据质量保持恒定,二者的错误(泛化误差 + 逼近误差)都随 scale 平滑下降。因此 loss 和模型大小/数据量之间的幂律关系在未来几个数量级上仍可能持续。

但”数据质量保持恒定”这个假设正在被打破——数据不够用了。Epoch AI 估计高质量文本数据的总量约在 200T-500T tokens。按照当前每年 2-3 倍的增长速度,5 年内可能逼近上限。到那时,合成数据和多模态数据将是唯一的方向。


九、动手实践:从零训练一个 1B 模型要多少钱

作为总结,让我们算一笔具体的账。假设你要从头训一个 1.3B 参数 的类 Llama 模型,训 100B tokens(Chinchilla optimal 的 ~4 倍——有意过度训练以降低推理成本)。

硬件配置:

  • 8 × H100 80GB 单节点
  • BF16 混合精度 + Flash Attention 2
  • TP=2 张量并行(同节点 NVLink),ZeRO-2 切分优化器状态

训练时间估算:

1
2
3
4
5
6
7
1.3B 模型 BF16 前向约需 0.5 TFLOPs/step/token
前后向总计约 ~1.5 TFLOPs/step/token
H100 BF16 FP8 有效算力约 ~1000 TFLOPs/GPU(考虑 MFU ~60%)
8 GPU 约 8000 TFLOPs
需要计算的 token 总数: 100B tokens
总计算量: 100 × 10^9 × 1.5 = 1.5 × 10^11 TFLOPs
训练时间: 1.5 × 10^11 / 8000 ≈ 1.875 × 10^7 秒 ≈ 217 天

嗯,8 卡 H100 不够快。如果要在一个月内完成:

1
所需 H100 数: 217 / 30 ≈ 7.25 个节点 ≈ 58 张 H100

成本估算(2025 年 H100 云租用价格约 $2/hour/GPU):

1
58 × 24 × 30 × $2 ≈ $83,520

这只是一个 1.3B 的”小”模型一个月训练的成本。一个 Llama 3 405B(15T tokens)按同样方法估算约需 $300M-500M——当然 Meta 用的是自家数据中心,边际成本低很多,但机会成本(这些 GPU 本来可以租出去)是真实的。

关键洞察:预训练的成本门槛在 2025 年大约是这样的——

  • 1B 级模型:$50K-100K(小公司/研究组可负担)
  • 7B 级模型:$500K-2M(需要融资/公司预算)
  • 70B 级模型:$5M-20M(大型创业公司)
  • 400B+ 级模型:$100M+(只有巨头玩得起)

十、总结

预训练不是一个神秘的炼丹过程——它是一套精心设计的工程管线,每一步的选择背后都有明确的量化理由。数据决定上限,架构选择影响训练稳定性和推理效率,分布式策略在显卡数量和训练时间之间做 tradeoff,scaling law 告诉你在哪里花钱最值。

建议的进一步阅读:

预训练是整个 LLM 栈里硬核程度最高的一层,但也是最值得理解的一层——因为在这里花掉的每一块钱 GPU 费用,最后都会在模型能力的某个维度上留下印记。