Happy-LLM 学习笔记 07:从预训练到 LoRA,训练流程要先看数据和成本

第五章是手写一个小 LLM,第六章的重点则变成:真正做训练时,为什么大家通常不会一直停留在手写训练循环里。答案很现实,模型结构、数据处理、分布式训练、checkpoint 恢复、日志监控和参数高效微调,每一项单独看都不难,合在一起却很容易把实验搞乱。

这一章给我的最大启发是:训练流程不是“模型 forward 一下再 backward 一下”,而是一条必须能被恢复、能被复现、能控制成本的流水线。先想明白数据要学什么、资源能承受什么,再决定 Pretrain、SFT 还是 LoRA,比先写训练脚本重要得多。

从手写实现转向 Transformers 生态

手写模型和训练流程很适合理解原理,但不适合长期维护。新模型结构更新很快,自己实现不仅要追结构,还要处理 tokenizer、权重加载、分布式训练和保存格式。更麻烦的是,手写模型通常很难直接复用社区里的预训练权重,实验一开始就和主流生态脱节了。

Transformers 的价值不只是“少写代码”。AutoConfigAutoModelForCausalLMAutoTokenizer 把模型配置、权重和分词器统一到一套接口里;Trainer 又把训练循环、日志、保存、混合精度、梯度累积和分布式启动封装起来。这样一来,研究者可以把注意力放在数据和实验设计上,而不是每次重新搭基础设施。

这并不意味着底层原理不重要。恰恰相反,只有知道模型是怎么 forward、loss 是怎么算、mask 是怎么控制的,使用框架时才不容易被默认参数带偏。框架应该帮我们减少重复劳动,而不是替我们做所有判断。

Pretrain 和 SFT 的差别,最后都体现在数据里

这一章把 Pretrain 和 SFT 放在同一个 Transformers 流程里实现,但两者的数据处理逻辑完全不同。Pretrain 面向连续文本,通常会把大量文本 tokenize 后拼接成固定长度的 block,例如 2048 个 token。因为目标是因果语言建模,labels 基本就是 input_ids 的副本,模型需要对整段文本学习“下一个 token 是什么”。

SFT 面向对话或指令数据,重点变成角色边界和 loss 掩码。教程沿用了 Qwen 风格的 <|im_start|><|im_end|> 模板,把 system、user、assistant 拼成一条序列,但只让 assistant 的回答参与 loss。user 的问题、system 的设定和 padding 位置都会被 IGNORE_TOKEN_ID 遮蔽。

这个差异非常值得记住。Pretrain 是让模型“读懂并续写世界”,SFT 是让模型“在指定上下文里给出合格回答”。如果 SFT 里把 prompt 也算进 loss,模型可能会去学习用户怎么提问;如果特殊 token、换行或角色边界拼错,模型推理时就会出现格式漂移。很多微调效果问题,其实不是模型能力不够,而是监督信号给错了位置。

DeepSpeed 解决的不是神秘感,而是训练能不能跑完

第六章使用 DeepSpeed 启动多卡训练,并采用 ZeRO-2 配置。对个人实验来说,这部分看起来工程味很重,但它解决的问题非常具体:单卡显存不够、训练时间太长、长时间任务可能中断。

pretrain.shfinetune.sh 里真正值得关注的不是每个参数都记住,而是几个组合关系。per_device_train_batch_size 决定单卡一次吃多少样本,gradient_accumulation_steps 决定累积多少步再更新,gradient_checkpointing 用额外计算换显存,bf16 降低数值精度开销,save_steps 和 checkpoint 恢复保证训练中断后不用从零再来。DeepSpeed 则负责把这些训练状态分布到多卡上。

我以前容易把“上大模型训练”理解成直接堆显卡,现在会更谨慎。显存是否够用,取决于参数、梯度、优化器状态、激活值、batch size、序列长度和并行策略。只改其中一个参数不一定有效,反而可能让吞吐更差。真正稳妥的做法是先用小样本、短序列和小 batch 验证路径,再逐步扩大规模。

LoRA 的核心不是省参数,而是明确只改一小部分

LoRA 的公式很简洁:冻结原始权重 W0,只学习低秩矩阵 AB,用 BA 近似权重更新量。放在 Transformer 里,常见做法是把 LoRA 接到注意力层的 q_projv_proj 等线性层上。这样原模型的大部分参数不需要梯度,也不需要保存对应优化器状态,训练成本会显著下降。

我更愿意把 LoRA 理解成一种工程约束:默认基座模型已经具备大部分能力,当前任务只需要在这个能力空间里做有限调整。它特别适合风格适配、格式约束、任务偏好和小规模领域数据微调,因为目标不是重新注入大量知识,而是改变模型调用已有知识的方式。

这也解释了 LoRA 的边界。它不适合承担“从零学习大量新知识”的任务,也不适合替代完整预训练。如果业务问题是知识缺失,RAG、继续预训练或更高质量的数据注入可能更合适;如果问题是回答格式、语气、任务协议不稳定,LoRA 往往才是性价比更高的选择。

训练前先做的几个判断

看完这一章,我会把训练前的检查压缩成四个问题。

第一,数据到底想让模型学什么?如果是通用语言能力,预训练数据和 block 构造是重点;如果是指令遵循,对话模板和 response-only loss 是重点。

第二,训练是否能被恢复?长时间任务必须考虑日志、checkpoint、断点续训和输出目录管理,否则一次中断就可能让实验失去可比性。

第三,成本瓶颈在哪里?显存、吞吐、数据读取和通信都可能是瓶颈,不能只盯 GPU 数量。Trainer、DeepSpeed、混合精度和梯度检查点都是围绕这些约束做取舍。

第四,是否真的需要全量训练?如果基座模型已经具备所需知识,只是输出行为不稳定,先做小规模 SFT 或 LoRA 通常更合理。训练方法越重,数据和评估成本也越高。

这篇让我意识到,大模型训练最关键的并不是某个框架命令,而是把“目标、数据、资源、恢复机制”连成一条清楚的链路。能跑通只是第一步,能解释为什么这样跑、出问题从哪里查,才算真正进入工程实践。

参考项目:https://github.com/datawhalechina/happy-llm
在线阅读:https://datawhalechina.github.io/happy-llm/

posted @ 2026-07-25 15:05  Hazy_star  阅读(0)  评论(0)    收藏  举报