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

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 | - 丢弃正文长度 < 100 字符的页面 |
HuggingFace 的 trafilatura 和 justext 是两个最常用的正文提取库。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 的工作原理:
- 选 N 个哈希函数。 对文档的所有 n-gram 分别计算哈希值
- 取最小值。 对每个哈希函数,取所有 n-gram 哈希值中的最小值,得到一个 N 维签名向量
- LSH 分桶。 将签名向量分成 b 个 band,每个 band 内做精确匹配。两个文档只要有一个 band 完全匹配,就认为候选相似
- 精确比对。 对候选对做 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 | - Web text (Common Crawl + filtered): ~50% |
代码数据的占比近年来一直在上升。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 | - 大词表 (128K-256K): token 更"语义化",相同文本生成的 token 数更少 → 推理快 |
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 | <|begin_of_text|> - 序列起始标记,Llama 3 引入 |
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 有三个核心优势:
- 相对位置自然内嵌: 旋转后的点积只依赖位置差 (m-n),不需要学习位置嵌入
- 长度外推: 可以通过插值(PI/NTK/YaRN)让预训练长度(如 4K)的模型在推理时处理更长上下文(如 128K 甚至 1M+)
- 训练效率: 不需要额外的可学习位置嵌入参数
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 | SwiGLU(x) = Swish(xW_gate) ⊙ (xW_up) |
相比 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 | 模型参数 (7B × 2 bytes): 14 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 | TP = 4(节点内 4 GPU 张量并行) |
更进一步的真实案例——Meta 训练 Llama 3 405B 时使用的拓扑:
1 | TP = 8 (单节点 8 GPU 张量并行) |
注意: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 | 单层激活值 = 64 × 4096 × 4096 × 4 / 1024^3 ≈ 4 GB |
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:
- 把 Q、K、V 切成小块(如 128×128)
- 每个 block 在 SRAM(比 HBM 快 ~10x)中计算局部 softmax
- 用一个递推公式把各 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 | lr_max ≈ 3e-4 ~ 1e-3(较小模型 1B-7B) |
大的 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:
- 回到 spike 前的最后一个 checkpoint
- 跳过导致 spike 的那个 batch(通过记录 global step 定位)
- 减小 learning rate 10-20%,从此继续训练
- 加强 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 | 1.3B 模型 BF16 前向约需 0.5 TFLOPs/step/token |
嗯,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 告诉你在哪里花钱最值。
建议的进一步阅读:
- Chinchilla Paper — Scaling law 的权威修正
- The Pile Paper — 数据策展的方法论基础
- FineWeb Blog Post — 2024 年数据过滤的最佳实践
- Llama 3 Paper — 从数据到架构的完整叙述
- MegaScale (Bytedance) — 万卡集群训练的工程实践
- Flash Attention Paper — Attention 的 I/O 优化
- DeepSpeed ZeRO — 显存优化的理论基础
- RoPE Paper — 旋转位置编码完整推导
预训练是整个 LLM 栈里硬核程度最高的一层,但也是最值得理解的一层——因为在这里花掉的每一块钱 GPU 费用,最后都会在模型能力的某个维度上留下印记。









