ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

大模型预训练流程全解_数据算法基础设施

大模型预训练流程全解_数据算法基础设施

文章目录

    • 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=−T1​t=1∑T​logP(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 需要关注:

维度为什么重要
中文切分效率中文被切太碎会浪费上下文窗口和训练预算
代码符号缩进、括号、下划线、路径对代码能力重要
数字和公式数学、表格、金额、日期需要稳定表示
特殊 tokensystem/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 低不等于模型一定好用。原因有三点:

  1. validation 集可能和训练分布太相似,只反映续写能力;
  2. 对话能力、工具使用、安全边界通常来自后训练;
  3. 业务任务关心的是可用性,不只是平均 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 或提示工程。

下一篇

下一篇会讲预训练数据处理。因为在大模型训练里,数据不是准备工作,而是模型质量、风险和成本的起点。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进