近几年只要跟企业做AI落地的朋友,应该都遇到过同一个场景:拿着开源通用大模型,比如Llama、Qwen、DeepSeek,高高兴兴接上业务数据,让模型回答行业问题时,得到的输出经常是“正确的废话”。问它某个设备的巡检周期,它能把变压器的常识背一遍,却答不出你们厂里的运维规程。很多人第一反应是“那我微调一下”,于是跑了一轮指令微调,结果模型学会了问话的格式,但该不会的照样不会。原因很直白:指令微调只能让模型把已经知道的知识换一种表达方式,它不能凭空学会你行业里那些压根不在预训练语料里的专业知识。要让通用模型成为真正“懂行”的行业模型,需要让它在行业语料上再学一遍,这个过程就是Continued Pre-Training,也就是继续预训练。这篇文章就是一份实战指南,讲清楚为什么需要它、什么场景适合用、具体怎么做,以及我在企业项目里踩过的大小坑。
1. 为什么通用大模型做不好行业任务
1.1 通用模型的“知识盲区”
通用大模型的预训练语料主要来自互联网公开内容、维基百科、开源代码、学术论文等。“通用”决定了它擅长的是广而浅的知识,而不是某个行业的深而专的知识。比如预训练语料里可能收录了很多“云计算”的百科条目,但不会把某家运营商内部的服务目录、故障等级定义、客户投诉处理流程写得清清楚楚。行业知识往往是封闭的、不在公开网络上的,模型从根上就没见过,自然回答不出来。
而且行业知识本身有很强的上下文依赖。以医疗场景为例,通用模型知道“高血压”的标准定义,但只有在你提供了该院的临床路径、药品目录和诊断规范之后,它才能给出真正可用的建议。但问题是这些内容无法靠prompt塞进去,上下文窗口再大也装不完一整本诊疗规范。更麻烦的是,很多行业知识是“结构化+非结构化”混合的,比如设备维修记录可能是工单系统里的几百个字段,也可能是老师傅留下的一段潦草笔记,通用模型没有见过这类数据,就学不会这类推理。
你可以把通用模型想象成一个刚毕业的通才员工,他聪明、自知、沟通能力强,但是一进到具体公司,不懂你的业务流程、术语黑话和行业规矩。Continued Pre-Training做的就是“岗前培训”,用真实行业资料把这些空缺补上。
1.2 指令微调解决不了知识缺口
我经常在企业项目里看到一种误区:一提到让模型更专业,就想到微调(Fine-tuning),然后默认微调就是SFT(Supervised Fine-Tuning,监督微调)。SFT本质上是让模型学会“以给定指令要求的格式回答”,它调整的是输出的风格、语气、结构,而不是填充模型缺失的知识。
举个我经历过的例子。某次我们给一家物流公司做客服大模型,一开始只做了SFT,拿几千条问答对去训练,效果确实变好了:模型的回答结构很规范,会用“您好”“感谢您的咨询”开头。但只要客户问到比较偏门的运输保险条款,模型依然乱编。为什么?因为那些条款在预训练和SFT数据集里都不存在,模型只会从相近知识里“猜”。后面我们补了一轮行业语料继续预训练,把承运条款、理赔流程、部门操作手册喂进去,再去SFT,模型才真正能答出有依据的细节。
这里有一个判断标准很实用:如果模型应该知道答案但希望它按特定语气来回答,用SFT。如果模型根本不知道答案,问题出在没有知识,那就要考虑继续预训练。很多SFT做了没效果,不是微调方法有问题,而是缺了知识注入这一步。
1.3 继续预训练才是在给模型“专业进修”
Continued Pre-Training(持续预训练)指的是在基座模型已经完成了通用预训练的基础上,继续用特定领域或特定任务的语料进行自监督训练。它的本质和预训练相同,都是让模型通过下一词预测任务学习数据的统计规律,只不过这一次语料从“全网内容”换成了“行业内容”。这样学到的知识不是临时存在上下文里的,而是被写进了模型的参数权重里,长期有效。
打个比方:通用预训练相当于读了一个综合性大学的通识课程;继续预训练相当于毕业后进入行业,啃了一整年的公司内部资料、行业标准、案例库。这种“进修”带来的提升是结构性的,尤其是在专业术语、行业规则、文本风格和数据分布上,都会更接近真实业务。当然,它也像人一样,补课补得太过也会把原来的常识忘掉,所以全程要控制“新知识”和“旧知识”的配比,这部分我放在后面讲。
2. 方案选型:CPT、SFT、RAG到底选哪个
2.1 三条路线的分工
很多企业做行业大模型时都会问,是继续预训练、指令微调还是搭RAG?这三条路线解决的是不同问题,不是互斥关系,我通常直接用一张表给客户讲清楚。
| 维度 | Continue Pre-Training | SFT | RAG |
|---|---|---|---|
| 核心作用 | 注入领域知识 | 对齐输出格式与交互方式 | 提供动态、可检索的事实信息 |
| 是否改变模型参数 | 会,且影响分布最深 | 会,但主要在输出端调整 | 不改变参数 |
| 训练成本 | 最高,需要大量语料和GPU | 中等,需要人工标注数据 | 低,构建向量库即可 |
| 适合场景 | 行业术语密集、知识型问答 | 客服、助手等需要特定话术 | 知识库频繁更新、需要引用来源 |
| 局限 | 语料不足时容易遗忘 | 无法解决知识缺失 | 强推理和多跳问答容易断裂 |
用一句话总结:CPT负责“让模型懂专业”,SFT负责“让模型会沟通”,RAG负责“给模型递资料”。一个完整的行业模型落地,经常是三者搭配使用。比如我做过的一个政务咨询项目,先用政府公开数据和政策文件做了CPT,让模型理解政策术语和办理流程;再构造了上千条咨询话术做SFT,统一回答口径;最后接了一个政策检索接口,确保最新文件能实时引用。只做任何一环都会露馅。
2.2 什么情况必须上CPT
判断要不要上成本不低的继续预训练,我有几个经验信号。
第一,模型在业务场景中频繁出现“答非所问式自信”。你问A它答B,看起来有道理,但细节全是编的。这说明模型缺乏相应概念,而不是表达问题。
第二,行业里有大量独特的专有词汇、缩写、命名规则。比如金融行业里的“非标”“委外”“预期信用损失模型”,法律里的“抗辩”“反诉”“既判力”。模型只靠通用语料很难建立起这些术语之间的逻辑关系,必须在行业语料里反复出现。
第三,你已经尝试了SFT和RAG,但效果依然不够。SFT做完了,模型说话已经很礼貌了,但内容还是空;RAG把资料检索出来了,模型却不会顺着资料做综合推理。这时候就该回归语料,做CPT。
第四,你要做的场景是知识密集型、判断型的,比如医疗辅助诊断、工业质检报告生成、法律文书审查,而不是简单的检索问答。这一类任务需要模型自己“沉淀”行业逻辑,只靠提示词或外部数据库是撑不起来的。
2.3 继续预训练与微调的衔接顺序
正确顺序通常是:先CPT,再SFT,最后按需接RAG或工具调用。这个顺序有讲究。如果先SFT再CPT,模型可能在继续预训练阶段把好不容易学到的指令表达方式覆盖掉,还得重新做SFT。如果先CPT,模型先学会行业知识,再用少量高质量对话数据去“格式化”输出,训练效率更高,最终的交互体验也更稳定。
实际项目中数据量有限的话,也可以把CPT和SFT分开训练,各自调参。不要试图在同一个数据集里混着训练,因为两者的目标和loss计算方式不一样。我在早期尝试过把领域语料和对话语料混在一起做“一步到位的训练”,结果模型既没做好词法预测,也在对话评测上翻了车。后来老实拆成两步,反而简单可控。
3. 数据工程:行业语料的收集与清洗
3.1 高质量行业语料从哪来
很多企业以为做继续预训练必须要有“海量”数据,其实真正的门槛不是数量,而是质量。我用过最小有效规模的CPT项目只有不到2亿token,照样把一个小型开源模型在一个垂直领域的术语理解提升了很大一截。关键在于数据是不是足够“像真实业务”。
常见的语料来源有几类。第一,企业内部知识库,包括操作手册、维修日志、产品说明书、客服工单和标准作业流程。第二,行业公开资料,比如行业标准、白皮书、监管文件、学术论文、专业图书。第三,脱敏后的真实业务文本,比如合同、报告、病例记录、司法文书。第四,专家问答沉淀,比如企业内网的Q&A、论坛问答、专家访谈记录。
这里特别提醒:使用公开数据和用户数据前,一定要做好合规审查,尤其是客户的业务数据,含有隐私或个人身份信息的内容必须先脱敏,否则后续麻烦非常大。数据合规是继续预训练项目里第一优先级,技术再好也不能越过红线。
3.2 清洗与去重的关键步骤
行业语料比通用语料脏得多,PDF表格、扫描件OCR乱码、网页广告、系统导出的半结构化文本都很常见。我一般会按下面几层来做。
第一层是格式清理。把HTML标签、Markdown标记、页眉页脚、水印、超链接、特殊符号去除。PDF如果是从排版工具导出的,优先用文档对象解析,比直接OCR效果好得多。
第二层是内容过滤。用关键词和正则把明显的广告、联系方式、无效页面过滤掉。还要过滤掉过短的段落,比如少于50个字的片段,对预训练帮助很小,还容易引入噪声。
第三层是去除重复。行业数据里同一份合同模板被保存了上千份,同一份行业标准被复制了几十次,如果不做去重,模型会反复背诵这部分,浪费训练成本还容易过拟合。我常用MinHash做文档级去重,再用n-gram做句子级去重,简单高效。
第四层是质量打分。可以训练一个轻量分类模型,判断文本是否属于目标行业、是否具有知识密度,也可以直接用规则加正则先过滤,再人工抽检。我们之前在每个项目里会抽5%的清洗后数据让行业专家快速判断是否“像回事”,如果专家觉得语义不清,就继续调清洗逻辑。
3.3 数据量级与配比的经验值
继续预训练到底要多少数据?回答这个问题要先想清楚模型规模和行业复杂度。如果是一个7B左右的模型,目标只是让它在某几个专业场景上更懂术语,5到10GB纯文本,大约25到50亿token,已经能看到明显效果。如果是几十B甚至上百B的模型,或者行业跨度很大,比如同时要覆盖生产、研发、供应链多个方向,数据量要按数十亿甚至百亿token来规划。
行业语料和通用语料的比例建议控制在20%到50%之间。纯用行业语料训练,模型会在行业任务上变强,但在通用常识、代码、逻辑推理上的退步会非常明显。我记得有次把行业语料比例放到70%,训练后行业评测涨了12%,通用基准却掉了15%,用户很容易感知到“变笨了”。后来把行业语料降到40%,再混入通用数据,行业评测只掉1个点,通用能力基本稳住,这个配比就比较健康。
4. 训练方案设计与参数配置
4.1 全量微调还是参数高效微调
继续预训练最直接的做法是加载基座模型,在整个行业语料上对所有参数继续梯度更新,也就是全量继续预训练。效果通常最好,但显存和算力要求最高。7B模型的AdamW优化状态量非常大,单卡基本跑不动,要靠多卡并行加DeepSpeed ZeRO或FSDP,还得配合梯度检查点。
参数高效微调,比如LoRA、QLoRA,在继续预训练中同样能用,而且很常用。LoRA通过在注意力权重上加低秩矩阵,只训练少量参数,显存占用小,调参快。但对继续预训练来说,LoRA有一个天生弱点:低秩矩阵的容量有限,对大规模、多样性的行业知识注入不够充分。如果语料只有几千万token,用LoRA完全可行;如果语料到了数十亿token,LoRA想“硬记”下大量知识会很吃力。
我个人的建议是:中小型模型,比如7B或13B,且预算有限,先用QLoRA做一轮“知识试读”,验证数据方向对不对,效果符合预期再上全量。如果是正式生产且语料充足,全量继续预训练仍是首选。还有一种折中方案:冻结大部分层,只解冻最后若干层的注意力参数,效果接近全量但成本低不少。
4.2 学习率、批次大小与训练步数
继续预训练最常犯的错是把学习率设得太高。预训练阶段通常会用到5e-4这种量级,但继续预训练是在预训练好的模型上做“精修”,学习率必须大幅降低。对全量继续预训练,常见范围是1e-5到5e-5;对LoRA或QLoRA,可以放宽到1e-4到5e-4。这里没有绝对最优,需要小规模实验。
批次大小也有讲究。理论上总batch size是“序列长度乘以batch数”,一般为了保证梯度稳定,7B模型一次更新尽量攒到256到1024条序列,每条序列长度可以是2048或4096。显存不够就用梯度累积,比如真实batch size设为8,累积16步,等效batch size就是128。训练步数不用太多,行业语料不是越多越好,通常0.5到2个epochs就够,跑太多会把模型推到只熟悉行业语料的局部区域,造成严重遗忘。
我建议训练过程中持续保存checkpoint,不要只保留最终版本。我在一个项目里遇到过第2个epoch的模型在行业评测上比第4个epoch还要好,最后选了中间checkpoint上线,说明继续预训练不是“跑得越久越好”,每一步都要用评估说话。
4.3 防止灾难性遗忘的混合训练技巧
灾难性遗忘是继续预训练最大的坑。模型在行业语料上训练几天后,行业知识倒是提升了,但可能连基本常识都开始胡说。根本原因是新数据分布持续覆盖旧知识,梯度更新在旧任务方向上的偏移过大。
最有效的办法还是混合旧知识。在每个训练step里,把行业语料和通用语料按比例混在一起,比如行业比通用等于6比4,或者模型刚训练完继续预训练后,再用一小部分通用预训练数据做“回放”。我在具体操作中通常把通用语料作为一个额外的数据流,用sampler按比例采样,行业数据不够时就增加通用数据的占比。这样模型在“补新课”的同时也没有放下“旧课”。
另外可以调低学习率、缩短训练步数,必要的时候对关键通用任务做评测。如果发现通用能力下降明显,可以考虑冻结底层embedding和部分浅层,只更新深层参数。底层往往保存了语言和常识的基本表示,动得越少,遗忘越轻。
5. 真实训练流程:从环境到监控
5.1 训练环境搭建
这一节我按在生产环境里实际使用的方案来讲。继续预训练首选Linux服务器,比如Ubuntu 20.04或22.04,至少4张A100 80G,或者8张4090也可以跑小规模实验。软件栈方面,Python 3.10、PyTorch 2.x、Transformers、Accelerate、DeepSpeed,如果要用LoRA就加PEFT,或者直接装Unsloth做快速验证。
显存不足可以先开梯度检查点(gradient checkpointing),再用DeepSpeed ZeRO Stage 2或FSDP。7B模型单张80G可以勉强装下,但优化器状态很容易爆,所以我还是推荐用多卡加ZeRO,或者直接用QLoRA把优化器开销降下来。先把流程跑通,再追求规模,这一点很重要。
5.2 用Transformers+DeepSpeed跑起来
下面给一个可以直接抄的简化训练脚本思路。我们用的是HuggingFace生态,代码量不大。先把数据组织成文本文件,每一行一条文档,按自己的行业语料做tokenizer并切成块。
from transformers import AutoTokenizer, AutoModelForCausalLM, Trainer, TrainingArguments from datasets import load_dataset model_name = "Qwen/Qwen2.5-7B" tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True) model = AutoModelForCausalLM.from_pretrained(model_name, trust_remote_code=True) dataset = load_dataset("text", data_files={"train": "industry_corpus.txt"}) def tokenize(examples): return tokenizer(examples["text"], truncation=True, max_length=2048) tokenized = dataset.map(tokenize, batched=True, remove_columns=["text"])然后是训练参数。我一般会关闭对话模板,直接把文本拼起来做自回归预测,tokenizer在遇到长文本时按block切分。接着定义TrainingArguments:
training_args = TrainingArguments( output_dir="./cpt_checkpoints", per_device_train_batch_size=4, gradient_accumulation_steps=16, learning_rate=3e-5, num_train_epochs=1, logging_steps=50, save_steps=500, fp16=True, gradient_checkpointing=True, warmup_steps=200, max_grad_norm=1.0, report_to="none", )训练时用Trainer即可,跑之前建议先拿一个mini batch做一次sanity check,确保数据能吃进去、前向反向不报错。大规模训练再叠加DeepSpeed的配置文件和accelerate launch启动。
5.3 训练过程中的监控指标
训练不是按一下运行就等结果,要盯几个关键信号。第一是loss。继续预训练的初始loss通常在1.5到2.5之间,具体看基座模型和数据难度。如果训练上百步后loss纹丝不动,先怀疑数据切块错误,大量内容是空的或重复。第二是grad norm,如果经常飙到几十上百,说明某个batch里混进了异常样本,可以做梯度裁剪,也要检查数据清洗是否漏了“中文乱码”“超长数字串”之类。
训练中点推荐用WandB或TensorBoard记录loss、学习率、显存占用和吞吐量。一个直观的经验是:7B模型在A100 80G上,使用全量继续预训练加ZeRO,吞吐能到每GPU每秒几千token;如果掉到几百,多半是数据预处理卡住了,或者dataloader的num_workers太低。监控起来能帮你快速定位是算力瓶颈还是数据瓶颈。
我还习惯在看loss的同时每500步跑一个小下游评测集。loss降了不代表业务指标一定涨,有些阶段loss降得很漂亮,但评测集里关键词都开始胡扯了。及时跑评测,能避免浪费几天的训练时间。
6. 评估与验收:怎么证明行业模型变强了
6.1 困惑度不是唯一标准
很多人用困惑度(Perplexity,PPL)来评估继续预训练效果,因为它计算简单,不需要人工标注。PPL确实能反映模型对领域语料拟合程度,我们训练中也会看,但千万不要把它当成金标准。我们遇到过PPL下降非常漂亮、明显低于基座模型,但业务专家一测,模型回答的行业问题还是很一般。
原因在于PPL衡量的是“模型对文本的惊讶程度”,而业务效果更看重“模型能否在正确答案上给出正确判断”。高维行业文本里,模型可以把常见句式学得很顺,却抓不住关键实体和逻辑关系。所以PPL只能作为粗筛,不能作为验收依据。
6.2 搭建行业评测集
真正能说明问题的,是围绕业务场景搭建的评测集。评测集不需要很大,但要有代表性。如果做法律场景,可以收集三类问题:一是名词解释类,检验术语定义是否准确;二是法条检索类,给定案情判断相关法条;三是案例分析类,给出事实让模型输出结论。每一类准备50到100题,作为固定评测集。
制作评测集时可以请业务专家出题并写参考答案,最好附带“采分点”。比如问“设备出现振动值超标如何处理”,专家答案里有“停机”“检测轴承”“记录振动频谱”三个关键词,模型回答能命中多少个,就按比例给分。这种有条件得分的方式比单纯让模型打分更稳定。
我还习惯把通用能力评测也纳入验收流程。如果只测行业能力,容易漏掉“变笨”风险。我常用MMLU、C-Eval等通用基准里的部分子集做回归测试,至少确保行业模型在常识、数学、逻辑上没有明显退化。
6.3 让一线专家参与盲评
自动评测能看出“数量上的进步”,但“质量上的飞跃”必须靠人。建议把基座模型、SFT模型、CPT加SFT模型和完整版模型放到同一个页面,让业务专家在不告知模型版本的情况下对结果盲评。评分维度可以从准确性、完整性、是否符合行业习惯三个角度打1到5分。
这里有个细节:专家盲评的题目不要和训练数据来自同一批文档,否则容易产生数据泄漏,分数虚高。我吃过这个亏,有一版模型在内部测试上几乎满分,一到真实验收就露馅,后来发现测试题和训练语料是同一份操作手册里出来的,属于典型的“背题”而非“学会”。所以评测集最好由另一批专家新出,或者至少在时间、来源上刻意隔开。
7. 常见问题与排查技巧实录
7.1 Loss不降反升
继续预训练最常见的“翻车现场”是loss完全不动或者往上涨。先不要急着调学习率,先做几个基础排查:数据文件是不是空的或乱码?tokenizer是否正常?序列长度设得过大导致绝大多数样本都是padding?模型权重是不是被错误加载成了随机初始化?
我建议第一步做sanity check:拿一段正常行业文本,只跑一个batch,看看输入和label是否对得上。如果单条文本的loss就能正常下降,说明数据管线没问题,问题出在整体配置。之后再调学习率和batch size。一开始学习率太高确实会出现loss先降后涨的现象,降到合理区间就好了。
7.2 模型“变笨”了
行业能力上去了,通用能力掉得很厉害,这是灾难性遗忘的表现。最直接的解决方案是混合更多通用语料,比如把行业语料占比降到30%左右,或者补充通用数据回放。另外一个有效手段是降低学习率,我试过把3e-5降到1e-5,通用评估掉点立刻减小,行业评测只微降一点点,这个平衡点值得多试。
如果混通用语料已经没法救了,可以考虑从更早的checkpoint重新训练,只跑0.5个epoch。不要觉得浪费,继续预训练很多时候最合适的停止点就是训练总量的一半左右,这个在多个项目里都验证过。
7.3 训练中途OOM和崩溃
显存OOM是最常见的技术问题。解决办法从易到难依次是:调小per_device_train_batch_size、开启gradient checkpointing、用DeepSpeed ZeRO Stage 2或3、开启CPU offload。如果模型本身就撑不下,就换用LoRA或QLoRA做参数高效训练。
有时不是显存不够,而是显存碎片化。可以调整PyTorch的显存分配策略,或者在启动训练前预留显存。训练崩溃如果在某个固定step发生,多半是数据里有特殊字符导致tokenizer崩了,可以定位到具体样本删掉。我们在处理一批来自旧系统的报告时遇到过很多次类似问题,后来在清洗阶段加了异常字符过滤,基本没再复发。
7.4 数据泄漏导致评估虚高
这个问题隐蔽而危险。行业语料和测试集同源,会导致模型评估分数远超真实水平。排查方式是检查测试集中是否存在训练语料里几乎原文复现的句子。如果是,必须把测试集重新构建。更稳妥的办法是把训练数据按时间线切分,比如用去年的数据训练,用今年上半年的数据做测试,模拟真实业务数据分布的未来迁移。
还有一类泄漏是“公开数据集泄漏”。如果你的训练语料是从公开数据集里爬来的,而这些公开数据集已经被基座模型在预训练阶段见过,那么再做继续预训练也可能导致指标虚高。这种情况很难完全避免,只能在报告结果时注明数据边界。
最后再分享一个小技巧。很多团队做继续预训练,一上来就挑最大规模的模型、拉最贵的GPU,结果成本花了,效果却没起来。我个人的经验是先拿最小的基座模型、最小的数据子集,比如0.1%的数据跑通全流程,确认数据清洗、训练配置、评估链路都可靠,再逐步扩大到完整规模。这个做法看起来慢,实际上最快。行业模型的路子没有想象中那么神秘,核心还是“数据+训练+评估”的闭环,尤其是数据,占了整个项目七成以上的工作量。希望这份实战指南能让你少踩几个坑,把通用大模型真正调教成自己行业里的“老师傅”。