最近不少读者私信问我一个问题:个人开发者到底能不能跑通 LLM 的完整链路?这里说的完整链路,不只是用开源的 ChatGLM、Qwen 或 LLaMA 做做推理,而是从数据准备、词表训练、预训练、领域继续训练,再到 SFT、偏好对齐、检索增强,一整套流程自己动手跟一遍。我先后在单机多卡、多机多卡的环境下折腾过几轮,踩过的坑比走过的路还多,这篇就把我的完整实践路径、参数选择背后的逻辑、以及那些文档里查不到的细节全部摊开来聊一聊。不管你是想自己训一个小模型练手,还是打算做垂直领域的私有化助手,这套方法论应该都能帮你少走不少弯路。
先说三个容易被误解的事实:第一,预训练不是只有大厂才能做,参数规模压到 1B 以内、数据质量拉满,个人开发者在消费级硬件上是可以跑通的;第二,领域适配的核心不是“继续训练”这个动作,而是“数据配比”和“数据顺序”;第三,模型效果的上限由预训练决定,下限由对齐和知识库兜底,全流程缺一环都不行。所以这篇文章的定位不是某个单一环节的教程,而是一张全景地图加实际操作手册。
1. 全流程设计:先想清楚“要不要自己预训练”
1.1 个人开发者面临的路线选择:预训练 vs 微调 vs 增强
很多新手拿到 LLM 项目就开始找代码仓库,准备从头训一个模型,这其实是本末倒置。动手之前,必须先按目标拆解路线。我把它分成三条路径:从零预训练、领域继续训练(Continue Pretraining)、以及“基座模型 + 微调 + 检索增强”。这三条路的时间成本、算力需求和最终效果天差地别,选错了后面全盘皆输。
从零预训练适合哪些场景?一是想彻底搞清楚 transformer 内部机制的“教学型项目”,二是模型结构有重大创新,三是极端垂直领域且数据分布与通用语料差异过大。个人开发者选这条路,更多是练手和验证想法,而不是为了生产可用。领域继续训练适合手头有一批高质量的领域数据,比如医疗病历、法律文书、代码仓库、客服对话,需要让模型先理解这些领域的“语言习惯”,然后再通过指令微调教它执行任务。第三条路——基座模型加微调加 RAG,是目前个人开发者性价比最高的方案,基座模型搞定语言能力和通用知识,RAG 实时补充私有知识,微调只做行为规范。
我在实践中的判断标准很简单:如果领域数据量少于 10 亿 token,不要纠结继续预训练,直接微调加 RAG;如果有 50 亿以上的领域数据,且这些数据在通用预训练语料里几乎不存在,那么继续预训练值得做;如果目标是发论文或者彻底搞懂训练机制,才需要考虑从零预训练一个 1B 以下的模型。这个门槛是经验值,不是科学结论,但它能帮你把精力花在刀刃上。
1.2 如何估算算力成本和数据规模
算力估算是全流程中最容易被低估的环节。这里先说一个核心公式:训练一个 D 参数规模的模型,在 T 个 token 上训练,所需的浮点运算量大约是 6DT(前向加反向)。一块 RTX 4090 的 FP16 算力大约 82 TFLOPS,考虑到实际利用率和通信开销,单卡实际大概能跑到 40 到 60 TFLOPS。这意味着,训练一个 1B 参数模型在两个 token 量级的数据上,理论上需要 12 exaFLOPS 的运算量,换算成单卡 4090 大概要跑 60 到 90 小时。这个估算对个人开发者非常重要,因为它直接决定了你该不该做人肉炼丹。
数据规模方面,业界经验法则是 Chinchilla 定律:模型参数量和训练 token 数量应大致等比例增长,对 1B 模型来说,比较理想的训练量是 20B 到 40B token。但个人开发者没办法追求这个比例,因为数据收集和清洗的成本远高于算力成本。我的实践策略是“小而精”:模型从 130M 起跳,先跑通流程,再逐步扩展到 500M、1B。训练 token 控制在 5B 到 10B,这样在单机四卡 4090 环境下,大概一到两周能完成一轮实验。数据质量上去了,即使没有达到 Chinchilla 比例,效果也完全可用。
这里有一个容易被忽略的隐性成本:实验迭代次数。预训练不是跑一次就结束的,学习率、批大小、数据配比每一个变量都可能需要重跑。所以我强烈建议在第一轮实验时,固定一组基线超参数,只改一个变量,并且每次都保存 checkpoint。个人开发者最忌讳“全变量同时调”,因为一旦效果变差,你根本不知道是哪个环节出了问题。我自己的习惯是每轮训练结束后立刻跑一组固定评估集,记录 loss、perplexity 和各下游任务分数,形成一张 Excel 表,后面调参全靠这张表的对比。
2. 数据工程:预训练模型的“七寸”
2.1 数据从哪里来:开源语料与领域资料整理
数据是 LLM 全流程里最脏最累但回报最高的部分。预训练语料的主要来源有三个:开源数据集、公开网页爬取和自有领域资料。开源方面,中文场景常用的是 WuDaoCorpora、SkyPile、以及清洗过的 RedPajama 子集;英文场景用 FineWeb、SlimPajama 比较多。这些数据集可以直接下载但必须再次清洗,因为开源数据里依然残留大量低质内容和重复片段。领域资料这一块需要你自己整理,比如我的一个医疗问答项目,就是从公开处方数据集、医学教材、药品说明书中抽取文本,分类存档。
数据规模上,我的经验是:哪怕最后只保留 5B token,初筛阶段也要至少拿到 15B 的原始数据,因为清洗、去重、过滤之后通常会损失 60% 到 70%。有个典型例子:我下载了一个 400GB 的爬虫语料,跑完质量过滤和 MinHash 去重后,真正能用的只有 120GB 左右。不要心疼这些被滤掉的数据,低质量数据对预训练模型的损害是指数级的,一条重复一万次的病句就能把模型的生成风格带偏。
整理领域数据时,有一个细节值得注意:要把“文档级”和“片段级”数据分开。文档级数据适合做继续预训练,因为它保留了长程依赖关系;片段级数据适合做指令微调,因为你要的是问答对。我早期犯过的错误是混合使用,导致模型在生成答案时经常遗漏上下文。后来我改成“预训练用全文,微调用片段”,效果明显改善。
2.2 清洗、去重、过滤的实操方法
清洗流程我建议按这个顺序执行:编码检测、HTML 标签剥离、敏感信息过滤、语言识别、质量过滤、语义去重。编码检测用 charset_normalizer 这类库跑一遍,把所有非 UTF-8 的转成统一编码,乱码原文直接丢弃。HTML 标签剥离不是简单用正则把尖括号去掉,而是要处理转义字符和 JavaScript 残留;建议用 BeautifulSoup 解析之后只抽取正文区域,再用 nltk 或者自定义规则清一遍残余杂质。
质量过滤是整个清洗链路里最有技术含量的环节。我会同时启用三个维度的过滤器:启发式规则、分类器打分、困惑度过滤。启发式规则包括标点密度、数字占比、句子长度分布、重复率等;分类器打分是训练一个小型的 fastText 模型区分“高质量文本”和“低质文本”,这比纯规则更泛化;困惑度过滤是拿一个现成的语言模型给每段文本算 perplexity,高于某个阈值的直接丢弃。这个方法有点像高考阅卷——先按字数格式初筛,再按语言流畅度打分,最后按难度曲线淘汰一部分。三类过滤器的权重各占三分之一,效果比单一规则稳得多。
语义去重是个人开发者最容易偷懒但绝不能省的环节。我用 MinHash 加 LSH 做近似去重,先把文本转成 n-gram 集合,再通过哈希降维。实际操作中用 datasketch 库就够了,对百万级文档去重速度很快。去重的阈值不要统一,短文档去重阈值要调高一点,长文档可以调低一点,否则要么去重不彻底,要么把正常相似的长文档误删了。这里给出一个参考值:短文档(小于 200 词)相似度阈值设 0.8,长文档(大于 1000 词)设 0.65。
2.3 Tokenizer训练:BPE和词表的取舍
Tokenizer 是整个预训练流程里最不起眼但影响最深远的模块。词表大小、训练语料、合并规则直接决定了模型的文本表示效率和泛化能力。现在主流的 LLM 几乎都使用 BPE 或者 Unigram 算法,在 GPT-2 时代用的是一个简单的字节级 BPE,现在更常见的是 SentencePiece 和 tokenizers 库的实现。选择哪个不是重点,重点是怎么控制词表大小和训练语料。
词表大小要根据模型参数规模来定。过于激进的小词表会大幅增加 token 序列长度,导致训练和推理速度都变慢;过大的词表则会把大量参数量浪费在 embedding 矩阵上。我个人的经验法则:1B 以下模型词表控制在 32K 到 48K,1B 到 7B 模型用 64K 到 100K,更大的模型才考虑 128K 以上。中文场景要特别注意,中文单字数量多、组合灵活,过小的词表会导致“被子”“杯子”“筷子”这类常见词被拆成单字,既浪费序列长度,又丢失语义信息。
训练 Tokenizer 的语料必须是预训练语料的子集,并且最好从各类来源均匀采样,避免某一类数据主导词表。我试过只用百科类数据训练词表,结果在代码和口语场景表现很差。另一个容易踩的坑是特殊 token 的设计:<pad>``<unk>``<s>``</s>这些必须有,但是自定义的“功能 token”要谨慎添加,因为每加一个 token 都会稍微增加训练复杂度。我现在只加了系统提示符和人类、助手分隔符这类必要的控制 token,其余全部让模型通过自然语言自己学习。
Tokenizer 训练完成之后,一定要跑一遍“往返一致性”测试:编码之后解码,文本是否能完整还原。我遇到过 SentencePiece 配置不当导致空格丢失的问题,中英文混排场景尤其明显,解码出来的文本粘连在一起,直接导致下游训练数据质量大幅下降。这个问题在训练时看不出来,但推理时生成的文本会非常难读,排查起来很隐蔽。
3. 预训练实操:跑通一次百亿级参数训练
3.1 模型架构选型:decoder-only的基础设施
如果有人问现在预训练语言模型到底该用哪种架构,我会毫不犹豫地推荐标准 decoder-only 的 GPT 风格结构,也就是“因果注意力 + 多层 transformer 块”。相比之下,BERT 风格的 encoder-only 模型擅长理解任务,但不适合做生成式应用;encoder-decoder 模型适合翻译等序列到序列任务,但工程实现复杂。个人开发者没有资源重复造轮子,直接站在主流架构的肩膀上更明智。
具体到 decoder-only 内部,近几年有几个关键改进已经成了标配。第一个是 RMSNorm 替代 LayerNorm,收敛更稳定;第二个是 SwiGLU 激活函数,比 GELU 在同样参数下能带来更低的困惑度;第三个是旋转位置编码 RoPE,对长文本外推能力的提升非常明显;第四个是 Grouped-Query Attention,让 KV cache 更小,推理速度更快。如果你打算从零训练,建议直接把这几项做成“默认配置”,不要用 GPT-2 时代的旧结构。
模型深度和宽度怎么配?在总参数固定的前提下,一般倾向“深一点、窄一点”,因为深层网络能学到更抽象的表征。但层数不是越多越好,过深会导致优化困难,特别在没有残差连接预激活设计的老结构里。1B 参数级别我常用的配置是 24 到 28 层,hidden size 1536 到 2048,注意力头数 16 到 24。你可以用之前的参数量公式反推验证一下:大约L * (12 * H^2)能大致估算 transformer 部分参数量,剩下的在 embedding 和输出层。
3.2 训练超参、优化器和稳定训练技巧
预训练超参的初始值我直接给你一份可以抄作业的配置。Batch size 按 token 数算的话,建议 0.5M 到 2M token 起步,过小的 batch 会导致收敛慢且噪声大;学习率峰值 3e-4 到 1e-3,但需要和 batch size 联合调整,batch 越大学习率可以适度提高;优化器首选 AdamW,beta 值默认 0.9 和 0.95,权重衰减设为 0.1。warmup 步数占总步数的 2% 到 5%,之后按余弦曲线衰减到峰值的十分之一。
梯度累积是个人开发者单机多卡环境下的必备操作。假设你的显存只够一轮放 16 个样本,但希望等效 batch 是 128,那就在优化器更新前累积 8 个步的梯度再统一做一次参数更新。这个操作有个我吃过亏的细节:BatchNorm 之类的层在累积和真实大 batch 下统计量不同,但 transformer 里的归一化层不依赖 batch 统计,所以问题不大;真正要小心的是 loss 缩放和梯度裁剪,累积梯度前要对每个微批次的 loss 除以累积步数,梯度裁剪阈值设 1.0 左右比较稳。
训练稳定性是预训练里的噩梦。loss 突然炸到 NaN 是最常见的问题,排查顺序应当是:先检查学习率是否过大,再看数据里有没有异常长文本或极端字符,然后检查混合精度策略。我现在默认全程 BF16 混合精度,因为 BF16 的指数范围和 FP32 一致,从底层上避免了部分溢出导致的 NaN。另一个技巧是给 loss 加一个小值的 epsilon,类似loss = loss + 1e-8,能有效避免单条样本产生极小 loss 时反向传播出现异常。
3.3 评估与检查:只看loss是不够的
预训练时很多人只盯着训练 loss 曲线,loss 降了就以为模型在变好,这是一个极其危险的误区。Loss 下降只能说明模型在拟合训练数据分布,不能说明它学到了可泛化的知识。我固定了一套“四级评估体系”:第一级是即时指标,包括 training loss 和 gradient norm;第二级是通用语言指标,比如 WikiText、C4 上的 perplexity;第三级是任务指标,比如中文的 C-Eval、英文的 MMLU 和军事类的 HumanEval;第四级才是你自己的领域指标。
在训练过程中,我每隔固定步数存一次 checkpoint,并用第二级指标做一次快速验证。这里的关键经验是:perplexity 并不是越低越好,特别要关注它和下游任务的关联。比如我的某个实验里,继续预训练后 perplexity 持续下降,但在下游摘要任务上分数反而掉了,原因是继续预训练的数据过于单一,模型过度拟合了领域风格而丧失了通用能力。所以领域预训练一定要在通用语料里掺入一定比例的通用数据,我常用 8:2 的领域数据与通用数据比例。
评估集本身也要谨慎设计。不要用训练集里出现过的文本做评估,否则分数虚高得离谱。我在一个项目的评估集里混入了网上公开但没清洗过的文本,导致模型明显“背诵”了答案;在检查训练数据时才发现大量评估句子和训练语料有重合。现在我用 MinHash 把评估集和训练集也做一次去重,确保没有任何句子级别的重叠。
4. 领域适配:从通用模型到专属助手
4.1 继续预训练:给模型补专业背景
领域适配的第一步是继续预训练,这一步的目标是让模型学会领域内的术语、行文风格和知识关联。和从零预训练不同,继续预训练需要用小得多的学习率,否则会灾难性遗忘通用能力。我把学习率设为原预训练峰值的十分之一到二十分之一,比如通用阶段峰值是 3e-4,那继续训练就用 1.5e-5 到 3e-5。数据量上,50 亿到 100 亿 token 对大多数垂直领域已经足够,再多就要考虑边际收益递减了。
继续预训练最大的风险是过拟合领域数据,导致模型在通用问题上的表现大幅退化。我处理这个问题有三个手段:第一,混合通用数据,一般按 70% 领域数据加 30% 通用数据混合喂入;第二,控制训练轮数,领域数据最多训练 1 到 2 个 epoch,因为领域知识密度高,重复太多容易记住噪声;第三,训练过程中持续监控通用评估集,一旦通用指标下降超过 5%,立刻降低学习率或提前停止。你可以在通用评估集上设置一个“警戒线”,这是个人开发者最容易忽视但回报极高的习惯。
继续预训练还有一个策略问题:数据按时间顺序排列还是按领域内子类间隔排列?我的实践结论是,如果领域内有明确的知识递进关系,比如从基础理论到临床案例,那就按难度递进排列;如果没有明显递进,就随机打乱并保证每个 batch 内部数据来源多样。不要在一个 batch 里全放同一类型的数据,因为模型会学到“说话风格突变”,导致生成不稳定。
4.2 SFT与LoRA:教模型“说人话”的正确姿势
继续预训练之后,模型只是“懂了领域知识”,还不具备“听指令办事”的能力,这时轮到 SFT(监督微调)登场。SFT 需要高质量的指令-回答对,数量和质量的权衡很重要。对个人开发者,我的建议是先把 1 万到 3 万条高质量数据做到极致,比盲目堆到 10 万条但质量参差的效果要好得多。数据怎么构建?从已有的领域文档里抽取问答对,或者人工编写高频场景的指令,再对答案做严格的过滤和格式统一。
LoRA 是个人开发者微调大模型的首选方案。LoRA 通过在权重矩阵旁添加低秩矩阵来减少可训练参数量,通常只占总参数的 1% 到 5%。这里的关键参数是秩 r 和缩放系数 alpha。我常用的 r 是 16 到 64,alpha 设为 r 的两倍。注意,r 不是越大越好,过大的 r 会导致过拟合和训练不稳定。实操时如果训练损失一直降不下去,先检查数据格式和指令模板,而不是急着调大秩。
全参微调和 LoRA 怎么选?如果基座模型小于 7B 且你有足够的单卡显存,建议做全参微调,因为效果上限更高;如果模型大于 13B,或者只能单卡运行,LoRA 是明智的选择。个人开发者经常忽略一个问题:LoRA 训练完之后,最好做一次“合并回原模型”的操作,把低秩矩阵融合进权重里,这样推理阶段不增加额外延迟。合并前记得在评估集上重新验证效果,我遇到过合并后精度下降的情况,后来发现是量化加上 LoRA 合并导致的精度损失,解决办法是合并时用 FP16 而不是 int8。
4.3 对齐与偏好优化:DPO等常用方法
SFT 做完以后,模型能“听懂人话”了,但有时候会生成长篇大论却没有重点,或者对同一个问题给出前后不一致的答案。这个阶段需要做对齐,让模型的行为更符合人类的偏好。个人开发者没必要去做复杂的 RLHF,因为那需要训练奖励模型,而且多阶段训练非常大。直接在 SFT 模型上跑 DPO(Direct Preference Optimization)是性价比最高的方案,只需要偏好数据对,不需要奖励模型。
DPO 的核心思路是:给定同一个提示,一组由“偏好的回答”和“不偏好的回答”组成数据对,模型直接优化两者之间的概率差距,让偏好回答的概率上升,不偏好的下降。关键在 beta 参数,它控制对偏离参考模型(一般是 SFT 模型)的惩罚强度。我一般设置 beta 为 0.1 左右,如果模型输出变得过于简短,就把 beta 调高一点;如果训练不稳定,就调低。偏好数据的来源是我自己标注排序的领域问答,每一条包括“好答案”和“坏答案”,坏答案不是随便写几句错误内容,最好是真实场景下模型容易犯的错误,比如答非所问、编造数据。
对齐阶段还要注意一个问题:DPO 训练很容易把模型搞成“安全但平庸”。模型学会了避免犯错,但回答质量并没有显著提升。我的应对策略是对偏好数据做分层抽样,刻意保留一部分有挑战性的指令,让模型在“安全”和“能力”之间维持平衡。这个平衡点没有统一公式,需要自己在评估集上反复试。我个人对齐阶段的比例是一半“纠错型偏好”加一半“能力提升型偏好”。
5. 知识库与检索增强:让模型“查得到、答得准”
5.1 RAG 的基础架构与chunking策略
模型全流程跑到这一步,知识已经固化在权重里,但正式的私域知识、实时更新的资料它依然无能为力。RAG(检索增强生成)弥补的正是这个短板。基础架构不复杂:把文档切成块,向量化后存入向量数据库;用户提问时把问题向量化,检索最相似的若干块;把这些块放到 prompt 上下文里,让 LLM 基于检索结果生成答案。难点不在架构,而在切分和召回质量。
Chunking 策略是 RAG 效果的分水岭。早期我做 RAG 喜欢固定长度切分,比如每 500 个字符切一块,结果上下文被切得七零八落,召回的内容经常只有半句话。后来改成“结构感知切分”:先按标题层级拆成章节,再按段落拆块,块与块之间保留少量重叠以防查询词跨越边界。对中文文档,块大小建议 300 到 800 字,重叠 50 到 100 字;对代码文档,尽量按函数或类切分,不要按行数硬切。块太大,检索结果含大量无关噪声;块太小,检索结果缺少上下文,模型无法理解语义。
向量化模型的选择会影响召回上限。我试过很多 embedding 模型,结论是:通用场景选开源的 bge 系列,中英文混排效果好;强领域场景最好用领域语料微调一个 embedding 模型,或用现成的领域适配版本。不要轻信开源榜单上的分数,召回效果一定要用自己的领域测试集验证。我自己建立了一个 500 条左右的评测集,每条记录“问题、正确文档块、领域”,每次升级 embedding 模型都要在这上面跑 MRR 和 Recall@10。
5.2 向量检索的选型与重排
向量数据库的选型,个人开发者没必要一开始就上分布式方案。数据量在百万级以内、单机使用,FAISS 加简单的元数据过滤就够了;如果想要更完善的管理功能和混合检索能力,Milvus Lite、Qdrant 或者开源的 Chroma 也能快速跑起来。我用 FAISS 跑过 200 万向量的检索,单次查询毫秒级返回,完全够用。不要一上来就分布式,那是给自己找事。
向量检索最大的问题是“语义相近但实际不相关”的误召回。比如用户问“高血压患者饮食注意事项”,检索回来的可能是一篇同样提到“高血压”但主体是“药物禁忌”的文章。单纯靠向量相似度无法解决这个问题,必须加重排层。重排模型我的首选是 bge-reranker 系列,它比 embedding 模型更精细,能对候选结果做交叉编码打分。实操流程是:向量库先召回 20 到 50 条候选,交给重排模型选出最精准的 3 到 5 条,再拼接到 prompt。多数情况下,加了重排之后,问答准确率能提升 5 到 10 个点。
有读者问过我,向量数据库到底存什么?我把字段拆成三块:原始文本 chunk、文本的向量表示、元数据(来源、标题、章节、时间戳)。元数据一定要提前设计好,因为后面做过滤、权限控制、按来源筛选都要靠它。另外,更新策略也很重要:文档一旦修改,对应 chunk 的向量要重新生成并替换,不要只加不删。我碰到过一次旧版本数据一直留在库里,导致模型反复引用已经废弃的信息,排查了很久才发现是同步逻辑漏了删除操作。
5.3 GraphRAG和ontology等热词的实际用法
RAG 发展到今天已经不止“向量加 prompt”这一种形态。热词图里的 GraphRAG 和 ontology(本体)是两条让 RAG 更“懂结构”的路线。GraphRAG 的核心思想是:先用 LLM 从文档里抽取实体和关系,构建成知识图谱,再在回答问题时沿着图中的路径做多跳推理。它最适合的领域是文档之间存在大量交叉引用的场景,比如科研论文多篇互引、法律法规条文引用、药物相互作用网络。单纯向量检索对这种“关联型”问题基本无能为力,因为每个问题可能要穿过两三个文档才能找到最终答案。
我实际跑过一个 GraphRAG 项目,用 LLM 把医学指南抽取成语义网络。具体流程是:每篇指南拆成段落级片段,让 LLM 抽取出“药物、疾病、症状、检查指标、注意事项”等实体及其关系,然后存到图数据库(Neo4j 这类)里。回答问题时,先通过向量检索锁定入口实体,再沿图扩展一跳两跳,把相关子图序列化成文本,拼入 prompt。效果确实比纯 RAG 好,但成本也很现实:LLM 做实体抽取是重大量消耗,且抽取质量直接影响后面所有步骤。个人开发者如果要尝试,建议先用小规模数据集验证 ROI。
Ontology 是通过预定义的概念体系约束 LLM 的知识组织方式。比如医疗领域定义“疾病、症状、药物、治疗方法、禁忌”等类别和关系,LLM 在抽取或生成时严格按照这个本体结构输出。好处是结构化、可控性强,坏处是灵活性差,领域扩展时本体要同步更新。我现在的做法是混合架构:基础问答走向量 RAG,涉及系统关系分析时走 GraphRAG,而 Ontology 作为 GraphRAG 中实体抽取的约束规则。三者协同,比单一方案稳定得多。如果你对这个方向感兴趣,可以先从一个小的领域本体开始,比如只定义 5 到 8 类实体和 10 种关系,跑通后再逐步细化。
6. 常见问题与排查实录:个人开发者最常踩的坑
6.1 训练不收敛?先动手查这三个地方
预训练刚开始 loss 不降,新手第一反应就是加学习率或换模型结构,但大多数情况是低级错误。我的排查顺序是:第一,检查数据加载和预处理是不是正确,尤其是 tokenizer 是否正常工作。我遇到过一次 batch 里几乎所有样本都是空序列,原因是清洗阶段把文本误删了,模型一直在学习“输出结束符号”。第二,检查 label 计算是否正确,decoder-only 模型一般用 shift 后的序列作为标签,错位一个位置看起来影响不大,实际会让 loss 虚高无法收敛。第三,检查优化器状态是否在正确的位置 reset,特别在做继续训练时,如果你直接加载预训练 checkpoint 继续跑,学习率调度器要从 warmup 重新开始,不要接着旧的学习率继续走。
还有一个被忽视的元凶:数据集的 shuffle 粒度。如果 shuffle 只发生在文件级别而没有在样本级别进行,那么同一个 batch 里的样本可能全都来自同一篇文章。之前提过的“多样来源混合”原则在这里同样适用。我踩过这个坑之后,在数据加载器里强制设置全局种子并做样本级 shuffle,才真正解决了训练早期 loss 震荡的问题。
6.2 生成结果总是一本正经胡说八道?拆解幻觉根源
模型生成的内容看起来流畅但事实性错误一大堆,这是领域模型最常见的“幻觉”问题。幻觉有两个根源:预训练阶段没有学牢的知识,以及 SFT 阶段被强化了“一定得说点什么”的倾向。第一阶段的对策是增加高质量事实性语料的占比,特别是百科类、官方文档类、统计报表类数据,这些数据含有确定的实体关系,能帮模型建立更稳固的事实骨架。第二阶段的对策是 SFT 数据里加入“承认不知道”的样本,明确告诉模型“我不会回答”是合法的优秀答案,而不是强迫它胡编。
但说了这么多,幻觉永远无法靠微调彻底消除,因为模型本质上是概率生成器。实际工程里面最有效也最可控的防线是 RAG。我给所有面向事实问答的模型都套了一层 RAG 加提示词约束:要求模型严格基于检索结果作答,检索结果中没有的内容一律说“根据现有资料无法确认”,帮它留出“坦诚认怂”的空间。部署后,事实性错误率能降低一半以上。值得一提的是,判断模型是否幻觉,不要只看单个回答,要在一个固定的“对抗性评估集”上反复测,这些测试问题专门围绕容易混淆的知识点设计。
6.3 推理速度太慢、内存不够用,怎么破?
个人开发者模型推理最常见的痛苦是“能跑但太慢”。我的优化顺序是:先量化,再剪枝,再考虑蒸馏。量化是首选,因为它对效果影响最小、工程成本最低。INT8 量化现在已经是标配,4-bit 的 GPTQ 或 AWQ 也很有用。必须提醒一句:不要把每一步优化都叠加到同一个模型上,比如先做 4-bit 量化再叠 LoRA 合并,很可能直接把模型精度搞崩。每个优化之后都要在原有评估集上验证一次。
KV cache 是内存大户。长文本生成时,KV cache 大小和输入输出长度线性增长,很容易把显存撑爆。如果你用的模型支持 GQA,显存压力会小很多;如果不支持,就只能靠滑窗注意力或对 prompt 做精简压缩。RAG 场景下有一个很实用的办法:控制输入上下文长度,只保留重排后的前几个检索块,不要贪多。实践经验是,5 个高质量块通常比 15 个中等质量块效果好得多,而且推理速度能有质的飞跃。
6.4 微调之后模型能力反而变差?数据配比和退化问题
领域微调最典型的坑是“模型变笨了”。常见表现是:领域问题答得还行,通用问题开始胡言乱语,甚至英文能力断崖式下跌。原因几乎都是数据配比失衡。微调阶段不要 100% 使用领域数据,至少要混入 20% 到 40% 的通用指令数据,让模型在学会领域能力的同时保持住语言能力。这些通用数据可以从开源指令语料里抽样,也可以从原始 SFT 数据里复用。
另一个改善退化问题的办法是分阶段微调:先用领域数据做继续预训练,然后用通用加领域混合数据做 SFT,最后再用偏好数据做 DPO。这种渐进式训练比单阶段大剂量注入领域数据稳得多。我实际把这两种方案对比过:同等数据量下,渐进式方案在通用能力评估上的掉点不到单阶段方案的一半。另外,每个阶段结束之后,保存独立的 checkpoint,不要直接覆盖。这样一旦发现某个阶段出了问题,可以回滚到上一阶段重新开始,而不是从头训一遍。
6.5 常见问题速查表
| 现象 | 根因 | 解决方案 |
|---|---|---|
| 预训练 loss 不降 | 数据为空或标签错位 | 检查 tokenizer 输出和 label shift |
| 训练中途 loss 突变为 NaN | 学习率过大或混精溢出 | 调小学习率,切 BF16 |
| 微调后过拟合领域数据 | 数据配比失衡、epoch 过多 | 通用数据混入 30% 以上,限制 epoch 在 2 以内 |
| 生成内容与事实不符 | 预训练知识不牢、SFT 鼓励编造 | 加强事实语料,加 RAG 检索约束 |
| RAG 召回不精准 | 切分不灵活、缺重排 | 结构感知切分,加重排模型 |
| 推理慢显存爆 | KV cache 过大、未量化模型 | 量化、控制上下文长度、用 GQA 结构 |
| 中文分词效果差 | 词表过小或 BPE 训练语料偏差 | 扩大词表并平衡语料来源 |
| 通用能力大退化 | 领域微调比例失衡 | 分阶段训练并混合通用数据 |
6.6 全流程成本清单与硬件配置参考
最后给一份个人开发者可以参考的硬件和成本清单。最低配是一张 24GB 显存的 RTX 4090,可以做全参微调 7B 以下模型、跑 LoRA、跑 RAG 推理和向量化。如果想从零预训练 1B 模型,至少需要四张 4090,或者租用云上四卡 A100 实例一到两周,成本大概在三到五千元人民币的量级。
数据清洗和 Tokenizer 训练是纯 CPU 密集型工作,不需要昂贵 GPU,在本地用多核 CPU 加一些内存就能完成。向量检索和重排对 GPU 要求也不高,一个消费级显卡就能轻松覆盖百万级知识库。如果预算有限,我的建议是:把大部分算力预算放在预训练和 SFT 阶段,因为那是模型能力的上限所在;RAG 和推理可以先用便宜方案顶着,等验证了效果再逐步升级。
有个比较实用的小建议:所有中间产物都要保留,包括清洗后的文本、Tokenizer 模型、每个阶段微调后的 checkpoint 和评估日志。我见过太多开发者因为丢失中间文件,后面调整时只能从头再来,既费钱又费时间。最好建一个清晰的目录结构,按阶段归档,文件名带上数据版本和超参摘要。这套习惯在个人项目和团队协作中都能省下大量重复劳动。
我自己的体会是,LLM 全流程这件事,最大的障碍从来不是某一个环节有多难,而是整条链路太多环节彼此纠缠。数据质量问题会一直渗透到推理阶段,超参选择错误会直接摧毁后续所有的适配努力。但反过来,正因为它环环相扣,每一环的改进都会带来复利效应。你花在数据清洗上的每一分心思,都会在最终效果上成倍地体现出来。希望这份实践记录能帮你把整条路看得更清楚,少踩一些我已经替你踩过的坑。