文章目录
- 1. 先把问题讲清楚:预训练到底解决什么
- 2. 预训练目标:next-token prediction 怎么读
- 3. 一条真实预训练流水线长什么样
- 4. token、seq_len、batch、step 的关系
- 5. tokenizer 不是小细节
- 6. 数据配比决定模型能力结构
- 7. 学习率计划:不要一开始就猛踩油门
- 8. 训练显存为什么远大于模型权重
- 9. checkpoint 不是“保存一下模型”这么简单
- 10. 评估:validation loss 重要,但不够
- 11. 小团队是否应该从零预训练
- 12. 常见误区
- 13. 小结
- 大模型视角
- 下一篇
摘要:大模型预训练不是“把海量文本丢进 GPU 跑很久”,而是一条从数据治理、token 化、采样配比、训练目标、分布式执行、稳定性监控到评估回滚的系统工程链路。本文会先用开发者能理解的方式讲清楚预训练到底在学什么,再拆解 token、batch、step、loss、学习率和基础设施之间的关系,并用最小代码帮助你估算训练步数和预算。读完后,你不一定能立刻训练一个大模型,但应该能看懂预训练方案里每个关键字段在决定什么。
- 前置知识:专栏一的交叉熵、梯度下降、AdamW、Transformer、分布式训练基础
- 阅读时间:45-60 分钟
- 代码环境:Python 3.10+,示例只依赖标准库
- 读完目标:能画出预训练流水线,能解释 next-token loss,能估算 token、batch、step 的关系,能判断一个预训练计划是否缺少关键环节
1. 先把问题讲清楚:预训练到底解决什么
当我们说“训练一个基础大模型”时,目标不是直接训练出一个会聊天的客服助手。预训练阶段更像是在训练一个强大的文本续写器:给它一段上下文,让它预测下一个 token。这个任务看起来很简单,但当训练文本覆盖网页、书籍、代码、论文、问答、表格和多语言内容时,模型会逐渐学到语言规律、世界知识、代码模式、推理格式和常见文本结构。
所以预训练的第一层理解是:模型不是被逐条告知规则,而是在海量样本中反复做“根据前文猜下一个 token”的练习。
开发者可以把它类比成日志补全或代码补全。只看一小段日志,模型可能只会猜时间戳;看过足够多系统日志后,它会学到错误码、调用链、异常堆栈和服务命名习惯。大模型也是这样,只是训练规模和文本类型大得多。
如果只理解“预训练等于 next-token prediction”,还不够。真正的预训练工程,是让这个简单目标在万亿级 token、数百到数千张 GPU、数周到数月训练中稳定执行。
2. 预训练目标:next-token prediction 怎么读
先从最核心公式开始。给定一段 token 序列:
x 1 , x 2 , . . . , x n x_1, x_2, ..., x_nx1,x2,...,xn
模型在第t tt个位置要学习的是:
P ( x t ∣ x 1 , x 2 , . . . , x t − 1 ) P(x_t \mid x_1, x_2, ..., x_{t-1})P(xt∣x1,x2,...,xt−1)
这行公式的意思是:模型看到前面的 token 后,给所有候选 token 分配概率,其中正确答案x t x_txt的概率应该尽量高。
训练 loss 通常写成交叉熵:
L = − 1 T ∑ t = 1 T log P ( x t ∣ x < t ) \mathcal{L} = -\frac{1}{T}\sum_{t=1}^{T}\log P(x_t \mid x_{<t})L=−T1t=1∑TlogP(xt∣x<t)
逐项解释:
- T TT:参与计算 loss 的 token 数;
- x < t x_{<t}x<t:第t tt个 token 前面的上下文;
- P ( x t ∣ x < t ) P(x_t \mid x_{<t})P(xt∣x<t):模型给正确 token 的概率;
- − log -\log−log:正确 token 概率越低,惩罚越大;
- 平均值:把一批 token 的错误程度压成一个标量 loss。
用大白话说:如果正确答案是“数据库”,模型却把最高概率给了“天气”,loss 就会高;如果模型逐渐学会在技术上下文里给“数据库”更高概率,loss 就会下降。
这里有一个容易误解的点:预训练不是让模型只背下一个字,而是通过预测下一个 token 被迫学习上下文结构。要预测代码里的下一个括号,模型要理解语法;要预测证明里的下一步,模型要捕捉推理模式;要预测问答里的答案,模型要压缩事实和表达习惯。
3. 一条真实预训练流水线长什么样
一个简化但完整的预训练流水线可以写成:
这条链路里,每一步都可能决定模型质量。
比如数据清洗不充分,模型会学会广告话术和乱码;去重不足,模型可能记住重复网页;配比错误,代码能力或中文能力会被稀释;tokenizer 不合适,中文会被切得太碎,训练效率下降;checkpoint 策略粗糙,训练中断后可能损失几天进度。
预训练方案评审时,不要只问“模型多大、训练多少 token”。更应该问:
1. 数据源是否合法、可追溯、可复现吗? 2. 数据清洗和去重的标准是什么? 3. 不同领域 token 配比怎么确定? 4. tokenizer 对中文、代码、数字、特殊符号是否友好? 5. global batch size、seq_len、训练步数如何计算? 6. 学习率计划和稳定性策略是什么? 7. checkpoint 如何保存、校验、恢复? 8. validation loss 和任务评估如何分层? 9. 训练失败时能定位到数据、代码、硬件还是配置吗?这些问题听起来琐碎,但它们就是预训练能否落地的分界线。
4. token、seq_len、batch、step 的关系
入门读者最容易混淆的几个词是 token、样本、batch 和 step。先把它们放到一个例子里。
假设训练上下文长度seq_len=4096,global batch size 是 1024 条序列。那么每次参数更新看到的 token 数是:
4096 × 1024 = 4,194,304 tokens如果计划训练 100B tokens,那么总训练步数大约是:
100,000,000,000 / 4,194,304 ≈ 23841 steps下面用代码做一个小工具。它不考虑丢弃尾部、动态 padding、packing 细节,只用来建立预算直觉:
# Python 3.10+defestimate_steps(total_tokens,seq_len,global_batch_size):tokens_per_step=seq_len*global_batch_size steps=total_tokens/tokens_per_stepreturntokens_per_step,steps plans=[{"tokens":100_000_000_000,"seq_len":4096,"global_batch":1024},{"tokens":1_000_000_000_000,"seq_len":4096,"global_batch":2048},{"tokens":1_000_000_000_000,"seq_len":8192,"global_batch":1024},]forpinplans:per_step,steps=estimate_steps(p["tokens"],p["seq_len"],p["global_batch"])print(p)print(f"tokens/step={per_step:,}, steps≈{steps:,.0f}\n")这段代码输出的数字告诉我们两件事:
- seq_len 越长,每条样本 token 越多,同样 global batch 下每步消耗 token 越多;
- global batch 越大,每步 token 越多,总 step 越少,但显存和并行通信压力更大。
真实训练还会有micro_batch_size_per_gpu和gradient_accumulation_steps。它们的关系通常是:
global_batch_size = micro_batch_size_per_gpu × GPU数量 × gradient_accumulation_steps比如单卡一次只能放 2 条序列,有 64 张 GPU,累积 8 次梯度再更新:
global_batch_size = 2 × 64 × 8 = 1024这就是为什么训练配置里经常看到很小的 micro batch 和很大的 gradient accumulation。不是工程师喜欢复杂,而是显存放不下大 batch,只能拆开累积。
5. tokenizer 不是小细节
预训练先处理 token,而不是直接处理中文字符或英文单词。tokenizer 会把文本切成模型能处理的整数 id。
同一句话,如果 tokenizer 不同,token 数可能差很多。对中文、代码、数学公式、日志路径、JSON、SQL 来说,tokenizer 的效率会明显影响训练和推理成本。
可以用一个极简例子建立直觉。下面不实现真实 BPE,只模拟“切得粗”和“切得细”的差别:
texts=["用户登录失败,错误码为AUTH_TIMEOUT。","def get_user_profile(user_id): return db.query(user_id)",]# 粗粒度:为了演示,把连续英文数字下划线视作一段,中文按字切# 真实 tokenizer 会复杂得多,这里只看 token 数差异的直觉importredeftoy_tokenize(text):returnre.findall(r"[A-Za-z0-9_]+|[\u4e00-\u9fff]|[^\s]",text)fortextintexts:toks=toy_tokenize(text)print(text)print(toks)print("token_count=",len(toks),"\n")如果某个 tokenizer 把常见中文词和代码片段切得过碎,同样 1B tokens 能覆盖的信息就更少。反过来,tokenizer 词表太大也会增加 embedding 和输出层参数,带来训练与部署成本。
预训练中 tokenizer 需要关注:
| 维度 | 为什么重要 |
|---|---|
| 中文切分效率 | 中文被切太碎会浪费上下文窗口和训练预算 |
| 代码符号 | 缩进、括号、下划线、路径对代码能力重要 |
| 数字和公式 | 数学、表格、金额、日期需要稳定表示 |
| 特殊 token | system/user/assistant、BOS/EOS、padding 要明确 |
| 兼容性 | 一旦训练开始,后续更换 tokenizer 成本很高 |
很多初学者会把 tokenizer 当成前处理工具,但在大模型里它是模型接口的一部分。tokenizer 错了,后面的训练再贵也很难补救。
6. 数据配比决定模型能力结构
预训练不是把所有数据合并后随机打乱就完事。假设你有 10TB 网页、1TB 代码、200GB 数学、100GB 中文高质量文本。按体量直接采样,网页会压倒其他数据;但如果目标模型要有较强代码、中文和推理能力,这样的配比可能不合理。
数据配比要回答的是:在有限 token 预算里,每类数据给模型多少学习机会。
一个初始数据分层可能长这样:
| 数据层 | 主要贡献 | 常见风险 |
|---|---|---|
| 通用网页 | 常识、语言多样性 | 噪声、广告、重复 |
| 高质量长文 | 结构化表达、知识密度 | 许可证和覆盖范围 |
| 代码 | 编程模式、API、错误处理 | 许可证、重复仓库、过时依赖 |
| 数学推理 | 多步推理、符号表达 | 模板化、答案污染 |
| 中文数据 | 中文表达和本土语境 | 质量参差、切分效率 |
| 安全数据 | 拒答边界、风险识别 | 过度拒答或规则僵硬 |
后面会专门讲数据配比,这里先记住一个原则:配比不是静态表格,而是实验假设。你需要用小规模训练、分层 validation loss、任务评估和人工抽样不断调整。
7. 学习率计划:不要一开始就猛踩油门
预训练中,优化器通常每步根据梯度更新参数。最简化的更新可以写成:
θ t + 1 = θ t − η t ∇ θ L t \theta_{t+1} = \theta_t - \eta_t \nabla_\theta L_tθt+1=θt−ηt∇θLt
逐项解释:
- θ t \theta_tθt:第t tt步的模型参数;
- ∇ θ L t \nabla_\theta L_t∇θLt:当前 batch 计算出来的梯度;
- η t \eta_tηt:当前学习率;
- 更新方向:沿着让 loss 下降的方向移动。
这行公式真正想告诉你的是:学习率控制每一步走多远。步子太大,可能直接跨过稳定区域,出现 loss spike;步子太小,训练进展很慢。
常见学习率计划包括 warmup 和 decay。下面是一个线性 warmup + cosine decay 的简化实现:
# Python 3.10+importmathdeflr_schedule(step,total_steps,warmup_steps,max_lr,min_lr=0.0):ifstep<warmup_steps:returnmax_lr*step/max(warmup_steps,1)progress=(step-warmup_steps)/max(total_steps-warmup_steps,1)cosine=0.5*(1+math.cos(math.pi*progress))returnmin_lr+(max_lr-min_lr)*cosineforstepin[0,100,1000,5000,9000,10000]:print(step,f"{lr_schedule(step,10000,1000,3e-4,3e-5):.8f}")这段代码前 1000 步逐渐升高学习率,后面逐渐降低。warmup 的意义是:训练初期参数、激活和优化器状态都还不稳定,不要一上来就大步更新。decay 的意义是:训练后期接近较优区域时,逐渐减小步长,让模型更稳定地收敛。
8. 训练显存为什么远大于模型权重
很多人第一次估算训练资源时会犯一个错误:只算模型权重。比如 7B 参数用 BF16 存,大约需要:
7B × 2 bytes ≈ 14GB于是误以为 24GB 显卡可以训练 7B。实际训练远不止权重,还包括:
- 梯度;
- AdamW 的一阶和二阶动量;
- FP32 master weights,视训练方案而定;
- 前向传播保存的激活值;
- 通信 buffer;
- 框架临时张量;
- KV 或 attention 中间结果。
用一个粗略估算看参数相关状态:
# Python 3.10+defadam_state_memory_gb(params_billion,use_master_weights=True):params=params_billion*1_000_000_000# bf16 param 2 bytes, grad 2 bytes, Adam m/v each 4 bytesbytes_per_param=2+2+4+4ifuse_master_weights:bytes_per_param+=4returnparams*bytes_per_param/1024**3fornin[1,7,13,70]:print(f"{n}B params -> param-related training states ≈{adam_state_memory_gb(n):.1f}GB")这还没有算激活值。序列越长、batch 越大、层数越多,激活值越大。第 44 篇会展开 DeepSpeed ZeRO 如何切分这些状态。这里先建立直觉:训练显存不是推理显存乘一个小系数,而是完全不同的账本。
9. checkpoint 不是“保存一下模型”这么简单
预训练任务可能跑很多天。硬件故障、网络抖动、进程崩溃、数据异常都可能发生。如果没有可靠 checkpoint,一次故障可能浪费大量算力。
一个可恢复的 checkpoint 通常不只包含模型参数,还包含:
1. 模型权重; 2. 优化器状态; 3. 学习率 scheduler 状态; 4. 当前 step 和已消费 token 数; 5. 随机数状态; 6. 数据采样器状态; 7. tokenizer 和训练配置版本; 8. 分布式分片信息; 9. 评估记录和代码版本。如果只保存权重,不保存优化器状态,恢复后学习率和动量可能不连续,loss 会异常。如果不保存数据采样状态,恢复后可能重复训练或跳过数据。如果 tokenizer 或代码版本没有记录,几周后很难复现实验。
工程上建议至少有三类 checkpoint:
| 类型 | 用途 |
|---|---|
| frequent checkpoint | 频繁保存,用于故障恢复 |
| milestone checkpoint | 关键阶段保存,用于评估和回滚 |
| final checkpoint | 训练结束,用于后续 SFT 或发布 |
checkpoint 还要定期做恢复演练。只会保存但从未恢复过,等真正故障时才发现路径、权限或分片配置有问题,就太晚了。
10. 评估:validation loss 重要,但不够
预训练中最常见的监控指标是 train loss 和 validation loss。它们能告诉你模型是否在继续学习,是否过拟合,是否出现训练异常。
但 loss 低不等于模型一定好用。原因有三点:
- validation 集可能和训练分布太相似,只反映续写能力;
- 对话能力、工具使用、安全边界通常来自后训练;
- 业务任务关心的是可用性,不只是平均 token 概率。
一个更完整的预训练评估面板可以包含:
| 评估层 | 示例 |
|---|---|
| 语言建模 | 分领域 validation loss、perplexity |
| 通用能力 | 常识、阅读理解、知识问答 |
| 代码能力 | 补全、修复、单测、解释 |
| 数学推理 | 逐步解题、符号计算、应用题 |
| 中文能力 | 中文问答、写作、成语、口语化输入 |
| 长上下文 | 长文摘要、跨段引用、位置鲁棒性 |
| 安全边界 | 有害请求、隐私、偏见、拒答 |
| 下游适配 | SFT 后是否更容易得到好助手 |
预训练评估的关键不是追求一个万能分数,而是持续监控能力结构。某次数据配比调整后,代码能力上升但中文写作下降,这不是简单的好坏,而是取舍。
11. 小团队是否应该从零预训练
对大多数团队来说,从零预训练不是默认选项。原因很现实:
- 高质量数据难收集、难授权、难清洗;
- 训练算力成本高;
- 稳定性问题复杂;
- 评估体系建设成本高;
- 开源基础模型已经覆盖了大量通用能力;
- 后续还要 SFT、对齐、部署和监控。
更常见、更务实的路线是:
选择合适基础模型 -> 先做 Prompt/RAG 验证需求 -> 有行为格式问题再做 SFT -> 显存不足用 LoRA/QLoRA -> 有大量稳定领域语料再考虑持续预训练 -> 最后做偏好优化、安全评估和上线监控什么时候才值得考虑从零预训练或大规模持续预训练?通常需要同时满足:
1. 有独特且大规模的高质量数据; 2. 现有基础模型无法满足核心能力; 3. 有稳定训练基础设施和工程团队; 4. 有清晰评估集证明训练收益; 5. 预算允许多轮失败和迭代; 6. 模型上线规模足以摊薄训练成本。如果只是想让模型知道公司制度,RAG 往往比预训练合适。如果只是想让模型按固定 JSON 输出,SFT 比预训练合适。如果只是想让模型语气更像客服,少量高质量 SFT 数据可能已经足够。
12. 常见误区
误区一:预训练数据越多越好。低质量、重复、污染和侵权数据会浪费训练预算,还可能带来安全与法律风险。数据质量、去重、配比和治理同样重要。
误区二:loss 下降就代表模型全面变强。loss 是基础信号,但代码、数学、中文、安全、长上下文等能力要分开评估。
误区三:只要训练能启动,就能稳定跑完。大规模训练会遇到 loss spike、硬件故障、checkpoint 损坏、数据读取瓶颈和通信异常。
误区四:从零预训练比微调更专业。工程上应选择投入产出比最高的方案。很多业务问题用 RAG、SFT、LoRA 就能解决。
误区五:训练配置可以复制别人的。模型规模、GPU、网络、seq_len、数据配比和目标任务不同,最优配置也不同。
13. 小结
预训练是大模型能力的地基,但它不是神秘仪式。它的核心目标是 next-token prediction,核心链路是数据、算法和基础设施三条线共同闭环。数据决定模型能看到什么,tokenizer 决定文本如何进入模型,batch 和 step 决定训练节奏,学习率和优化器决定参数如何更新,checkpoint 和监控决定训练能否稳定跑完,评估决定你是否真的得到了想要的能力。
从开发者视角看,理解预训练的价值不只是为了自己从零训练模型,更是为了看懂后续所有技术:为什么数据清洗重要,为什么 ZeRO 能省显存,为什么 Chinchilla Law 讨论参数和 token 比,为什么持续预训练有遗忘风险,为什么 SFT 和 LoRA 不是预训练的替代品。
大模型视角
从大模型技术主线看,预训练决定模型能力的上限:后训练能调整行为、格式和边界,但很难补回数据和算力没有给到的能力。理解这条链路,你就能判断一个问题应该回到预训练解决,还是交给 SFT、RAG 或提示工程。
下一篇
下一篇会讲预训练数据处理。因为在大模型训练里,数据不是准备工作,而是模型质量、风险和成本的起点。