☰
从零训练中文小语言模型:预训练、CPT、SFT、蒸馏与DPO全链路实战
2026/9/26 17:29:51 网站建设 项目流程

小语言模型这两年重新火了起来,原因很直接:不是每个人都有八卡A100,但几乎每个做算法的团队都希望有一个能自己掌控、能私有化部署、能在垂直场景里跑得动的小模型。Xihe 这个项目就是在这个背景下值得拿出来聊一聊的典型样本——它把预训练、CPT、SFT、PEFT、蒸馏、DPO 这一整条链路串了起来,从零开始训练一个中文小语言模型。我前后花了大概两个月时间把这条链路完整跑了一遍,中间踩的坑比想象中多得多,尤其是 CPT 和蒸馏这两块,网上的资料要么太粗要么直接是错的。这篇文章就把我实际操作的完整过程、每个阶段的设计理由、以及那些文档里不会写的经验全部摊开讲。

1. 为什么小模型训练链路值得从头走一遍

1.1 小语言模型的定位与 Xihe 的切入点

先说清楚"小语言模型"到底小到什么程度。业界目前没有严格定义,但通常指参数量在 0.1B 到 2B 之间的模型。Xihe 的定位大概在 1B 上下,这个量级的模型有几个非常实际的好处:单卡 24G 显存就能做全量微调,推理时量化后可以塞进手机或者边缘设备,训练成本可控到个人开发者也能承受。

但小模型的问题也很明显——它的知识容量有限,直接从头预训练一个 1B 模型,即使喂了几百 G 中文语料,效果也很难比得上在大模型上做蒸馏或者继续预训练。所以 Xihe 这条链路的设计思路不是"从零硬训一个能打的小模型",而是"用大模型的能力来喂养小模型",预训练打底、CPT 注入领域知识、SFT 对齐指令、PEFT 降低微调成本、蒸馏把大模型的能力迁移过来、DPO 做偏好对齐。这六个阶段不是随便堆的,每个阶段解决的是不同层次的问题。

我一开始也想过跳过预训练直接做 SFT,实测下来效果差很多。原因在于 SFT 的数据量通常只有几万到几十万条,模型在预训练阶段没有建立起来的语言基础能力,靠 SFT 那点数据根本补不回来。预训练是打地基,后面的 CPT、SFT 都是在地基上盖楼。

1.2 六个阶段的职责边界与依赖关系

很多人把这条链路理解成流水线,做完一个做下一个,但实际上它们之间有明确的依赖和重叠。我用一张表把每个阶段的核心职责、数据规模、典型耗时和依赖关系理清楚:

阶段核心职责数据规模单卡耗时参考前置依赖
预训练建立基础语言能力50-200G tokens数天到数周无
CPT注入领域知识1-10G tokens数小时到数天预训练
SFT指令跟随对齐1万-50万条数小时预训练/CPT
PEFT低成本适配千到万条分钟到小时SFT
蒸馏能力迁移取决于教师数小时到数天预训练
DPO偏好对齐5千-10万对数小时SFT

这里有个关键点:蒸馏和 CPT 是可以并行的,它们都依赖预训练结果但不互相依赖。DPO 必须在 SFT 之后做,因为 DPO 需要一个已经会跟随指令的模型作为起点,否则偏好对里的 chosen 和 rejected 模型都分不清好坏。

提示:不要迷信"必须按顺序做"。如果你的领域数据足够多,CPT 和蒸馏可以同时进行,最后合并。但如果数据量少,老老实实按顺序来,每一步都验证效果再往下走。

1.3 硬件门槛与成本估算

我用的是一张 4090 24G,这是个人开发者比较现实的配置。预训练阶段 1B 模型用 bf16 全量训练,显存占用大概在 18-20G,batch size 只能开到 4 左右,配合梯度累积到 64。如果显存更小,可以用 8-bit Adam 优化器把优化器状态压下来,能省 30% 左右的显存。

成本上,预训练 100G tokens 在单张 4090 上大概需要 15-20 天,这个时间成本对个人来说很高。我的做法是先在小规模数据上跑通全流程,确认每个阶段都能正常工作,再放大数据量。CPT 和 SFT 阶段就快很多,几小时到一天就能出结果。蒸馏阶段取决于教师模型的大小和蒸馏数据量,如果教师模型是 7B 级别,生成蒸馏数据的时间可能比训练本身还长。

2. 预训练阶段:数据、分词器与训练配置的取舍

2.1 中文预训练语料的选择与清洗

预训练数据决定了模型的能力上限,这句话在小模型上尤其成立。我试过三种数据来源:通用网页爬取数据、开源中文语料集、以及自己领域内的专业文本。实测下来,通用网页数据噪声太大,小模型本身容量就小,被噪声一冲效果掉得很明显。

清洗流程我走了这么几步:先用规则过滤掉长度过短、重复率过高、包含大量特殊字符的文本;然后用一个轻量分类器过滤掉低质量内容;最后做去重,用 MinHash 做近似去重,阈值设在 0.8。去重这一步很多人会忽略,但实测下来重复数据会让模型过拟合到某些句式上,生成时反复出现同样的表达。

数据配比上,我建议通用中文数据占 70%,领域数据占 30%。如果领域数据特别干净,可以提到 40%。但不要超过 50%,否则模型的通用能力会明显下降,后面 SFT 阶段会很难拉回来。

2.2 分词器训练:为什么不用现成的

这是我在整个项目里做的第一个反直觉决定——自己训练分词器,而不是直接用现成的中文分词器。原因在于现成的分词器词表通常很大(5万到15万),对于 1B 参数的小模型来说,embedding 层会占用过多参数。一个 10 万词表的 embedding 层在 1B 模型里可能占 15% 以上的参数,这对小模型来说是很大的浪费。

我自己训练了一个 32000 词表的分词器,用 SentencePiece 的 BPE 模式,在清洗后的语料上训练。训练时要注意几个参数:character_coverage设成 0.9995 保证覆盖常见汉字,byte_fallback设成 true 处理生僻字,max_sentencepiece_length设成 16 避免过长的 token。

训练完之后一定要做覆盖率检查,我写了个脚本统计验证集里有多少字符不在词表内。如果未登录字符超过 0.5%,说明词表太小或者训练数据不够代表性,需要调整。我第一版词表只有 24000,未登录字符到了 1.2%,后来加到 32000 才降到 0.3% 以下。

2.3 预训练超参设置与显存优化

预训练的超参设置我踩了不少坑。学习率用 3e-4 配合 cosine 衰减,warmup 设成总步数的 1%。batch size 方面,虽然单卡只能开到 4,但通过梯度累积到 64 或者 128,效果和真实大 batch 差别不大。这里有个细节:梯度累积时要注意 loss 的归一化,如果直接用累积步数平均,会导致梯度尺度不对,正确做法是在 loss 计算时就除以累积步数。

显存优化上,我用了几个手段组合:bf16 混合精度训练、梯度检查点、8-bit Adam。梯度检查点会牺牲 20% 左右的速度,但能省 40% 的激活显存,对于长序列训练是必须的。8-bit Adam 把优化器状态从 fp32 压到 int8,省了大概 30% 显存,但要注意它和某些学习率调度器配合时会有精度问题,我实测下来用 cosine 衰减没问题。

序列长度我设的是 2048,这是权衡后的结果。设 4096 显存不够,设 1024 又会让模型学不到长距离依赖。如果你的数据里长文本多,可以考虑用 2048 训练后再用 4096 做继续训练,但要注意位置编码的扩展问题。

3. CPT 继续预训练:领域知识注入的正确姿势

3.1 CPT 与预训练的本质区别

CPT(Continual Pre-Training)经常被误解成"用领域数据再训一遍",但它和预训练有本质区别。预训练是从随机初始化开始,模型在学习语言本身;CPT 是在已经会语言的模型上,让它适应特定领域的表达方式和知识分布。这个区别决定了 CPT 的数据量不需要很大,但质量要求极高。

我做过对比实验:用 5G 领域数据做 CPT,和用 50G 通用数据做预训练,在领域任务上 CPT 的效果明显更好,而且训练时间只有预训练的十分之一。原因在于 CPT 不需要重新学习语言结构,只需要调整知识分布,学习率可以设得比预训练低一个数量级,通常 1e-5 到 5e-5 就够了。

CPT 的另一个关键是数据配比。纯领域数据做 CPT 会导致灾难性遗忘,模型在通用任务上会明显退化。我的做法是领域数据和通用数据按 1:1 混合,这样既注入了领域知识,又保住了通用能力。如果领域数据特别珍贵,可以降到 1:2,但不要再低了。

3.2 灾难性遗忘的检测与缓解

灾难性遗忘是 CPT 最大的风险,我第一版就踩了这个坑——用纯医疗数据做了 3 个 epoch 的 CPT,结果模型在通用问答上开始胡言乱语。检测方法很简单:在 CPT 前后各跑一遍通用评测集,如果分数掉超过 5%,就说明遗忘了。

缓解手段我试过三种,效果从好到差排列:第一种是数据混合,前面说的 1:1 配比,这个最有效也最简单;第二种是学习率预热加低学习率,warmup 设长一点,让模型慢慢适应;第三种是 L2 正则或者 EWC 这类持续学习方法,但这些方法实现复杂,效果还不一定比数据混合好,我不推荐在小模型上用。

还有一个实用技巧:CPT 不要训太多 epoch。我实测下来 1 到 2 个 epoch 就够了,超过 3 个 epoch 几乎必然遗忘。判断标准是看领域验证集的 loss,如果连续两个 epoch 不下降就停。

3.3 CPT 数据格式与训练细节

CPT 的数据格式和预训练一样,就是纯文本拼接,但要注意拼接方式。我见过有人把不同文档直接拼在一起不加分隔符,这会让模型学到错误的边界。正确做法是在文档之间加一个特殊的分隔 token,比如<|endoftext|>,让模型知道文档边界。

训练细节上,CPT 的 batch size 可以比预训练小一些,因为数据量少,不需要那么大的 batch 来稳定梯度。我用的是 32 的等效 batch size,学习率 2e-5,cosine 衰减,warmup 比例 3%。训练时监控领域验证集和通用验证集两个 loss,如果通用 loss 开始上升就说明要遗忘了,赶紧停。

注意:CPT 阶段一定要保存 checkpoint,不要只保存最后的结果。因为最佳效果往往在中间某个 epoch,训过头反而变差。我一般每个 epoch 存一次,最后在验证集上挑最好的。

4. SFT 监督微调:从语言模型到指令跟随

4.1 SFT 数据的构造与质量把控

SFT 是把语言模型变成能听懂指令的助手的关键一步。数据质量比数量重要得多,我试过用 10 万条低质量数据,效果还不如 1 万条精心构造的数据。高质量 SFT 数据有几个特征:指令清晰无歧义、回答准确且完整、格式统一、覆盖多样化的任务类型。

数据来源上,我用了三种:开源 SFT 数据集、自己构造的领域数据、以及用大模型生成的合成数据。合成数据要特别小心,大模型生成的内容可能有事实错误,我一般会用一个小的验证模型或者规则过滤一遍。合成数据的比例不要超过 50%,否则模型会学到教师模型的偏见。

数据格式我用的是标准的 instruction-input-output 三段式,但实际训练时会拼成一个序列。拼接模板很重要,我用的格式是:

### Instruction: {instruction} ### Input: {input} ### Response: {output}

这个模板要在训练和推理时保持一致,否则模型会困惑。我见过有人训练用一套模板推理用另一套,结果模型完全不按预期输出。

4.2 训练策略:全量微调还是 LoRA

SFT 阶段有个选择:全量微调还是 LoRA。我的建议是,如果你的显存够(1B 模型全量微调大概需要 20G 左右),优先全量微调,效果更稳定。LoRA 虽然省显存,但在小模型上效果损失比较明显,因为小模型本身参数就少,LoRA 的低秩约束会进一步限制表达能力。

全量微调的超参:学习率 1e-5 到 2e-5,比 CPT 再低一点;batch size 用 64 到 128;训练 2 到 3 个 epoch。这里要注意,SFT 很容易过拟合,如果训练 loss 一直降但验证 loss 开始升,就要停。我一般用验证 loss 做早停,patience 设 2。

如果显存实在不够必须用 LoRA,rank 不要设太小,我建议至少 16,alpha 设成 rank 的两倍。target module 要覆盖 q_proj、v_proj、k_proj、o_proj 以及 FFN 层,只调 attention 效果会差很多。

4.3 SFT 效果评估与常见问题

SFT 之后怎么判断效果好不好?我用三个维度:指令跟随率、回答质量、以及通用能力保持。指令跟随率是看模型是否按要求的格式输出,这个用规则就能测;回答质量需要人工评估或者用 GPT-4 打分;通用能力保持是看 SFT 之后模型在预训练阶段的能力有没有退化。

常见问题里最典型的是"复读机"现象——模型反复输出同样的内容。这通常是训练数据里有大量重复样本导致的,解决方法是做数据去重,尤其是指令部分的去重。另一个问题是"格式崩溃",模型不按模板输出,这往往是模板 token 在训练时被 mask 掉了,要确保 loss 只计算 response 部分,instruction 和 input 部分要 mask。

还有一个坑是 padding 的处理。如果 batch 内序列长度差异大,padding 会浪费很多计算,而且如果 attention mask 没设对,模型会学到 padding token。我一般用动态 padding,每个 batch 按最长序列 pad,同时确保 attention mask 正确设置。

5. PEFT 参数高效微调:低成本适配的实战

5.1 LoRA 之外的选择:Prefix Tuning、P-Tuning 与 Adapter

PEFT 不只是 LoRA,还有 Prefix Tuning、P-Tuning、Adapter 等方法。我在 Xihe 上把这几种都试了一遍,结论是:LoRA 综合最好,但不是所有场景都最优。

Prefix Tuning 是在每层前面加可学习的 prefix token,参数量极少,但训练不稳定,对学习率很敏感。P-Tuning v2 是 Prefix Tuning 的改进版,在 NLU 任务上效果不错,但生成任务上不如 LoRA。Adapter 是在 FFN 后面插入小模块,效果稳定但推理时会增加延迟,因为多了一层计算。

LoRA 的优势在于推理时可以合并回原权重,不增加延迟。但 LoRA 有个隐藏问题:如果多个 LoRA 要同时加载(比如一个模型适配多个任务),合并会有冲突。这时候可以用 LoRA 的变体,比如 LoRAHub 或者 MoRA,但实现复杂度上去了。

方法参数量占比训练稳定性推理延迟适用场景
LoRA0.1%-1%高无通用
Prefix Tuning0.01%-0.1%低无NLU
P-Tuning v20.01%-0.1%中无NLU
Adapter1%-5%高有多任务

5.2 LoRA 参数配置的实操经验

LoRA 的核心参数是 rank、alpha 和 target module。rank 决定低秩矩阵的维度,rank 越大表达能力越强但参数量也越大。我的经验是:1B 模型上 rank 设 16 到 32 比较合适,再大收益递减,再小效果不够。

alpha 是缩放因子,通常设成 rank 的 1 到 2 倍。alpha 和 rank 的比例决定了 LoRA 权重的缩放,比例太高会导致训练不稳定,太低则学习缓慢。我一般用 alpha = 2 * rank,这个比例在多数任务上表现稳定。

target module 的选择很关键。只调 q_proj 和 v_proj 是最省参数的方案,但效果一般。我建议至少覆盖 attention 的所有投影层(q、k、v、o)加上 FFN 的 gate 和 up 层。如果显存允许,把所有线性层都加上效果最好,但参数量会上去。

dropout 我设的是 0.05,这个值在防止过拟合和保持表达能力之间比较平衡。如果数据量特别小(几千条),可以提到 0.1。

5.3 PEFT 在多任务场景下的组合策略

实际项目中经常遇到一个模型要适配多个任务的情况。这时候有两种策略:一种是每个任务训一个 LoRA,推理时按任务切换;另一种是训一个统一的 LoRA 覆盖所有任务。

第一种策略的优点是每个任务的效果都能调到最好,缺点是管理麻烦,而且如果任务之间有冲突,切换时会有性能波动。第二种策略简单,但不同任务之间会互相干扰,尤其是任务差异大的时候。

我的做法是折中:把任务按领域分组,同一领域的任务共用一个 LoRA,不同领域用不同的 LoRA。这样既减少了 LoRA 数量,又避免了跨领域干扰。实测下来,3 到 5 个 LoRA 能覆盖大部分场景,管理成本可接受。

提示:LoRA 权重保存时只存 adapter 部分,通常只有几 M 到几十 M,非常方便版本管理和分发。但要注意记录基座模型的版本,不同版本的基座模型 LoRA 不通用。

6. 知识蒸馏:把大模型的能力迁移到小模型

6.1 蒸馏的三种范式与选择依据

知识蒸馏在小模型训练里是性价比最高的一步。大模型的能力通过蒸馏迁移到小模型,比小模型自己从头学要高效得多。蒸馏有三种主要范式:logits 蒸馏、feature 蒸馏和 response 蒸馏。

logits 蒸馏是让小模型模仿大模型的输出概率分布,需要教师和学生模型在同一词表上,而且教师模型的 logits 要能拿到。这个方法的优点是信息量大,因为 soft label 包含了类间关系;缺点是对词表一致性要求高,而且教师模型推理成本高。

feature 蒸馏是让小模型的中间层特征模仿大模型的,通常需要维度对齐,实现复杂。在小模型上效果不一定好,因为大小模型的表示空间差异大。

response 蒸馏是最简单也最常用的:用大模型生成回答,小模型在这些回答上做 SFT。这个方法的优点是实现简单,不需要访问教师模型的内部;缺点是信息量比 logits 蒸馏少,因为只有 hard label。

我实际用的是 response 蒸馏为主,logits 蒸馏为辅。先用大模型生成一批高质量回答做 SFT,再用 logits 蒸馏在小规模数据上做精细对齐。这样兼顾了效率和效果。

6.2 蒸馏数据的生成与筛选

蒸馏数据的质量直接决定蒸馏效果。我用的是 7B 级别的教师模型,生成数据时注意几个点:temperature 设成 0.7 到 0.9,太低会导致输出单一,太高会有噪声;top_p 设 0.9 做 nucleus sampling;每个 prompt 生成 1 到 3 个回答,增加多样性。

生成完之后要筛选。我用三个过滤条件:长度过滤,太短(少于 10 token)或太长(超过 1024 token)的丢掉;重复过滤,用 n-gram 重复率检测,重复率超过 0.3 的丢掉;质量过滤,用一个小的奖励模型或者规则打分,低分的丢掉。

筛选后的数据量通常是生成量的 60% 到 70%。这个比例是正常的,不要觉得浪费。我第一版没做筛选,直接用生成数据训,结果模型学到了很多教师的坏习惯,比如过度使用某些句式。

6.3 蒸馏温度与损失函数的调参

如果用 logits 蒸馏,温度和损失函数是关键。温度 T 控制 soft label 的平滑程度,T 越大分布越平滑,小模型能学到的类间关系越多,但太大会导致信息模糊。我实测下来 T=2 到 4 比较合适,具体看任务。

损失函数通常是 KL 散度加上交叉熵的加权和:

loss = alpha * KL(student_logits / T, teacher_logits / T) * T * T + (1 - alpha) * CE(student_logits, labels)

这里的 alpha 控制蒸馏损失和真实标签损失的权重。alpha 设 0.7 到 0.9 比较常见,意味着主要学教师,但也保留一些真实标签的监督。T*T 这个缩放是因为 KL 散度在除以 T 后会缩小 T 倍,需要乘回来保持梯度尺度。

如果只用 response 蒸馏,那就没有温度的问题,直接当 SFT 做就行。但要注意,response 蒸馏的数据分布和真实数据分布可能有差异,最好混合一些真实数据一起训。

7. DPO 偏好对齐:让模型输出更符合人类偏好

7.1 DPO 与 RLHF 的取舍

DPO(Direct Preference Optimization)是 RLHF 的简化版,省掉了奖励模型和强化学习那套复杂流程,直接在偏好数据上优化。对于小模型和个人开发者来说,DPO 比 RLHF 现实得多,因为 RLHF 需要训奖励模型、做 PPO,工程复杂度高,而且不稳定。

DPO 的核心思想是:给定一个 prompt 和一对回答(chosen 和 rejected),直接优化模型让 chosen 的概率相对 rejected 更高。损失函数里有一个参考模型(通常是 SFT 后的模型)做约束,防止模型偏离太远。

但 DPO 也不是没有缺点。它对偏好数据的质量非常敏感,如果 chosen 和 rejected 的差异不明显,训练效果会很差。而且 DPO 容易过拟合到偏好数据的表面特征,比如长度偏好——如果 chosen 普遍比 rejected 长,模型会学会输出更长而不是更好的回答。

7.2 偏好数据的构造与陷阱

偏好数据的构造是 DPO 最关键的一步。我用三种方式构造:人工标注、大模型生成加人工筛选、以及用规则构造。人工标注质量最高但成本高,我一般只标 2000 到 5000 对作为种子数据。大模型生成加筛选是主力,用教师模型对同一个 prompt 生成多个回答,然后用奖励模型或者人工挑出好坏对。

构造偏好数据有几个陷阱要避开。第一个是长度偏差,前面说了,要确保 chosen 和 rejected 的长度分布接近,或者显式控制长度。第二个是风格偏差,如果 chosen 都是某种风格,模型会只学这种风格。第三个是难度偏差,如果 rejected 太差,模型学不到细粒度的偏好。

我一般会做数据平衡:长度上,chosen 和 rejected 的平均长度差控制在 20% 以内;质量上,rejected 不能太差,最好是"看起来还行但实际有问题"的回答,这样模型才能学到细微差别。

7.3 DPO 训练中的 beta 参数与过拟合控制

DPO 的 beta 参数控制模型偏离参考模型的程度。beta 越大,约束越强,模型越保守;beta 越小,模型越激进,容易过拟合。我实测下来 beta 设 0.1 到 0.5 比较合适,具体看数据量和质量。数据量少的时候 beta 设大一点,防止过拟合。

训练时监控两个指标:DPO loss 和 chosen/rejected 的 reward margin。如果 margin 一直增大但验证集上的效果不提升,说明过拟合了。这时候可以降低 beta、增加数据、或者早停。

还有一个实用技巧:DPO 训练前先用 SFT 数据做一轮轻量微调,让模型适应偏好数据的格式。这样 DPO 收敛更快,效果也更稳定。我试过直接从 SFT 模型做 DPO,和先做一轮轻量 SFT 再做 DPO,后者效果明显更好。

8. 全链路串起来的工程细节与踩坑记录

8.1 各阶段之间的模型版本管理

这条链路涉及至少 6 个模型版本:预训练模型、CPT 模型、SFT 模型、PEFT 模型、蒸馏模型、DPO 模型。版本管理如果做不好,后面会非常混乱。我的做法是用一个统一的命名规范:{基座}-{阶段}-{数据版本}-{日期},比如xihe-1b-sft-v2-20240115。

每个版本都要记录训练配置、数据版本、评测结果。我用的是一个简单的 YAML 文件加 Git 管理,每次训练完更新。这样回溯的时候能快速定位到某个版本用了什么数据什么配置。

还有一个细节:PEFT 模型要记录基座模型的版本,因为 LoRA 权重不能跨基座版本使用。我见过有人换了基座模型但忘了换 LoRA,结果效果一塌糊涂还找不到原因。

8.2 训练中断与恢复的实操方案

训练中断是常态,尤其是预训练要跑好几天。恢复方案我用的比较简单:每个 epoch 或者每 N 步保存一次 checkpoint,包括模型权重、优化器状态、学习率调度器状态、以及随机数种子。恢复时加载这些状态,从断点继续。

这里有个坑:数据加载器的状态也要保存,否则恢复后数据顺序会变,影响训练稳定性。我用的是 HuggingFace 的 Trainer,它内置了断点恢复功能,但要注意resume_from_checkpoint参数要设对,而且 checkpoint 目录结构要完整。

另一个坑是混合精度训练的 scaler 状态。如果没保存 scaler,恢复后前几步 loss 可能会异常。Trainer 默认会保存,但如果你自己写训练循环,记得手动保存和恢复。

8.3 评测体系:怎么判断每一步都做对了

没有评测就没有优化。我建了一个三层评测体系:通用能力评测、领域能力评测、以及人工评测。

通用能力用 C-Eval、CMMLU 这类中文评测集,看模型的基础能力有没有退化。领域能力用自己构造的测试集,覆盖领域内的典型任务。人工评测是抽样看生成质量,这个最耗时但最能发现问题。

每个阶段做完都要跑一遍评测,和上一阶段对比。如果某个阶段之后通用能力掉太多,说明这个阶段有问题,要回去检查。我一般设的阈值是通用能力掉不超过 3%,领域能力提升至少 5%。

注意:评测集要和训练数据严格隔离,否则评测结果虚高。我见过有人不小心把评测数据混进了训练集,结果评测分数很高但实际效果很差。

8.4 推理部署时的量化与加速

训练完之后要部署,小模型的部署优势这时候体现出来了。1B 模型用 int8 量化后大概占 1G 显存,int4 量化后 500M 左右,可以塞进很多设备。我用的是 GPTQ 做量化,4-bit 量化后效果损失大概 1% 到 2%,可以接受。

推理加速上,vLLM 和 llama.cpp 是两个主流选择。vLLM 适合服务端部署,吞吐高;llama.cpp 适合端侧,支持 CPU 推理。我两个都试过,服务端用 vLLM,端侧用 llama.cpp 的 GGUF 格式。

量化时要注意校准数据的选取,校准数据要和实际使用场景接近,否则量化误差会偏大。我用的是领域内的一小批数据做校准,效果比用通用数据好。

9. 一些不那么显然的经验

整条链路跑下来,有几个经验是文档里不会写但实际很重要的。第一个是数据质量的重要性远超模型结构和超参,我花在数据清洗和构造上的时间占了整个项目的 60% 以上,但这是值得的。第二个是每个阶段都要做消融实验,不要想当然地认为某个阶段有用,我一开始觉得 CPT 没必要,做了对比实验才发现 CPT 对领域任务提升很明显。

第三个经验是关于学习率的。小模型对学习率比大模型更敏感,我试过同样的配置在大模型上没问题,在小模型上直接发散。建议每个阶段都做学习率扫描,至少试 3 个值。

第四个是随机种子的影响。小模型训练对随机种子比大模型敏感,同样的配置不同种子效果可能差 2% 到 3%。我一般会跑 2 到 3 个种子取平均,或者至少确认结果不是某个种子的偶然。

最后一个经验是关于耐心。这条链路完整跑一遍需要几周甚至几个月,中间会有很多次想放弃。我的建议是先在小规模数据上跑通全流程,确认每个环节都能工作,再逐步放大。这样即使某个环节出问题,也能快速定位,不会浪费太多时间。

我在实际使用中发现,最有价值的往往不是某个具体的技巧,而是对整个链路的理解——知道每个阶段在做什么、为什么这么做、以及出了问题该去哪里找原因。这种理解只能通过完整走一遍才能获得,看再多文章也替代不了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询