1. 项目背景与方案选型:为什么“微调一个8B小模型”值得做
先聊一个很多人都会遇到的场景:你把一个大模型API接进业务,上线第一天,用户问了一句很常见的话,模型答得倒是挺快,结果输出格式不对,还把不该说的话也顺着说了出来。你盯着返回结果,既觉得憋屈,又不得不承认——问题的根源不是模型不够聪明,而是你根本没有把它约束在业务的边界里。
我这次做的项目,就是把一个通用Qwen3-8B模型,通过SFT加DPO两阶段微调,改造成一个面向特定业务场景的垂直Agent。标题里提到的“稳定可控”,恰恰是这次工程实践里最难啃的部分。不是说模型能回答几个专业问题就叫垂直Agent,而是它在面对真实用户时,能稳定识别意图、按约定格式调用工具、在能力边界之外果断拒答,而不是张口就来。这个项目做完之后,我最大的感受是:微调本身并不神秘,真正拉开差距的,是数据、评测和训练策略这三件事有没有用工程化的方式串起来。
这篇文章适合谁看?如果你正准备用开源模型做私有化部署,或者已经在用API但被格式问题、乱答问题折磨,又或者你只是好奇SFT和DPO到底怎么落地,这篇文章都能给你一份可以直接参考的路线图。我会把从数据构建、训练调参到部署评估的完整过程拆开讲,包括我踩过的坑和最后怎么填上的。
1.1 从场景倒推模型选型,而不是先选模型再找场景
这个项目一开始定方向的时候,团队里有两种声音。一种说直接用通用大模型API,省事;另一种说干脆从零训练一个领域模型,彻底可控。我当时的判断是两条路都走极端了,真正的需求其实很朴素:业务方需要在一个特定场景里,比如某电商平台的售后咨询、某企业内部的知识问答,让模型具备三个能力——听得出用户在问什么、按固定格式返回结果、拿不准的时候主动说自己不知道。
基于这三个能力倒推,模型选型就清晰了。通用API的问题不在能力,而在不可控:你没法强制它只能按你定义的JSON格式输出,也没法阻止它在知识库之外的领域自由发挥。从零训练的问题在于成本太高,数据和算力都不是一个普通项目组能扛的。中间路线才是正解:拿一个开源的8B级别基座模型,用少量高质量业务数据做指令微调,再用偏好优化把行为的“边界感”压出来。
为什么是8B而不是更大或者更小?我当时的考量有三个维度。第一是部署成本,8B模型做bf16推理,单卡40G显存就能扛,量化之后甚至可以跑在24G卡上,这对一个初期流量不算大的业务场景来说非常友好。第二是效果下限,8B模型经过微调后,在特定垂直领域的表现可以超过未微调的70B模型,这不是玄学,是因为领域数据把模型的先验知识重新校准过了,它不需要什么都懂,只需要在它该懂的范围内做到稳定。第三是迭代速度,8B模型的SFT一个epoch在单卡上跑几小时就够,这意味着你可以快速试错,今天改完数据,明天就能看到效果。项目一旦需要频繁迭代,小模型的工程优势就体现出来了。
1.2 SFT和DPO的分工:一个教能力,一个教规矩
很多第一次做微调的人容易把SFT和DPO混在一起,觉得反正都是“再训练一下”。我这次做完之后最深刻的体会是,这两个阶段解决的是完全不同的问题,必须分开理解、分开设计。
SFT解决的是“会不会”的问题。基座模型虽然有一定能力,但它不是一个听话的对话助手。通过SFT,你用一批“用户问题-标准回答”的样本,让模型学会在这个业务场景里应该说什么。这个阶段类似于带徒弟进门,把手艺教给他。但它有一个先天缺陷:它学习的是训练数据里的平均行为,哪怕某一种回答质量很差,只要这种样本足够多,照样会被模型学进去。所以你会看到SFT之后的模型能回答问题了,但风格不稳定,偶尔会给出很蠢的答案。
DPO解决的是“好不好、该不该”的问题。它的思路是给模型同时看一组偏好数据:同一个用户问题,有两个回答,一个是符合业务规范的chosen样本,一个是存在明显问题的rejected样本。训练的过程就是让模型学会“给chosen更高的概率、给rejected更低的概率”。用大白话说,SFT教会了模型说话,DPO教会了模型说什么话、不说什么话。垂直Agent要稳定可控,DPO是承上启下的关键一环,因为这类模型最怕的就是“能力有了,边界没了”。
1.3 垂直Agent的“稳定可控”到底指什么
项目立项时业务方给我提了一句话,我一直记着:“我不要求它有多聪明,但我要求它不给我惹事。”这句话翻译成技术指标,就是模型要稳定、可控。我把这两个词拆成了四个可衡量的能力。
第一,意图识别要稳。用户问“我昨天的订单怎么还没发货”,模型要知道这是在查物流,而不是泛泛地介绍退换货政策。第二,格式输出要规整。业务系统对接需要模型返回结构化数据,比如调用工具的参数、最终的答案文本。如果模型三天两头输出不合法JSON,系统就没法自动化,这是Agent落地最大的拦路虎。第三,边界外要会拒答。模型不是搜索引擎,遇到不知道的事情,要能明确说“这个我暂时无法确认”,而不是编造一个看似合理的答案。第四,同一问题多次回答要一致。用户换个说法问同一个问题,模型给出的结论不能自相矛盾。
这四个能力没有一个是单靠“堆数据”能解决的。意图识别和格式输出主要靠SFT阶段的样本设计,拒答和一致性则更多依赖DPO阶段的偏好对齐。所以这个项目的完整链路就是:先用SFT让模型具备垂直能力,再用DPO把行为喜好校准到业务认可的轨道上。
2. 数据工程:微调真正的护城河在数据而非算力
我在这个项目上最想强调的一点是:如果你的数据没做好,后面所有的训练都是在浪费时间。很多人拿到开源模型第一反应是找一套训练脚本把参数跑起来,但真正决定模型上限的,是喂给它的数据长什么样。SFT和DPO的成败,八成取决于数据构建阶段。
2.1 三类训练数据的配比设计
数据配比是我在做任何微调项目时第一个拍板的事。这次给Qwen3-8B准备的数据集大致分成三类:通用指令数据、垂直业务SFT数据、DPO偏好数据。
通用指令数据我保留了大概10%到20%的比例。它的作用有两个:一是防止灾难性遗忘,也就是模型在学完垂直数据后把原本的通用对话能力丢掉;二是给模型保留一点“常识兜底”,否则用户随口问一句“今天天气怎么样”,模型可能都不知道怎么正常回答。垂直业务SFT数据是主体,占到60%到70%。这一部分直接决定了模型在这个场景里懂不懂行、会不会说话。DPO偏好数据我控制在10%到20%,它的价值不在于数量,而在于质量。
这里有个工程上特别重要的细节:三类数据不是简单拼起来一起训,而是有先后顺序的。先用包含通用和垂直数据的混合集做SFT,等模型把基本能力学会之后,再用DPO数据单独做偏好对齐。如果把DPO数据混进SFT阶段,模型很容易被偏好样本里的“正反对比”搞糊涂,因为SFT阶段它根本不知道什么是chosen什么是rejected。我之前见过一个项目,A同学把DPO样本直接塞进了SFT阶段,结果训练出来的模型在生成时会莫名其妙地“自问自答”,把好答案和坏答案都输出一遍,显然是数据格式把它带偏了。
2.2 SFT数据长什么样:从模板到真实对话的跨越
SFT数据不是简单地整理几万条问答就完事。我在这个项目里用的标准格式是system、user、assistant三段式,每一个样本都像下面这样:
{ "system": "你是某电商平台的售后客服助手,只能根据订单信息回答用户问题。如果信息不足,请明确告知用户。", "user": "我昨天买的东西显示已签收,但我没收到,怎么回事?", "assistant": "抱歉给您带来不便。我来帮您核实,请您提供订单号或收件人手机号,我查到配送记录后立即回复您。" }看着简单,实际构建时有很多坑。第一,system字段不能太长。我试过把业务规则、禁止事项、回答风格全塞进system提示词,结果模型在训练时注意力被前面的规则占用了,真正的问题反而不被重视,输出质量明显下降。第二,assistant字段要覆盖多种长度和风格。真实用户的表达很随意,有带情绪的、有错别字的、有一次问好几件事的。如果训练数据全是工工整整的书面问答,模型上线后面对真实用户就会“水土不服”。第三,样本要多轮对话,不能只有单轮。用户经常会追着问“那我的退款什么时候到”,模型要能基于上一轮的信息继续回答,而不是重新开始一轮对话。
我还特意在数据里加入了一类“陷阱样本”。比如用户在问订单问题时,会顺带抱怨快递员服务态度差。好的Agent应该把重点放在处理用户的实际诉求上,而不是顺着用户的情绪一起抱怨。这种样本在数据里专门标注,让模型学会分清主次。SFT阶段的数据质量,决定了模型的知识下限,你给它看了什么,它就长成什么。
2.3 DPO偏好数据怎么构造,质量比数量重要得多
DPO数据的形式和SFT完全不同,它是“三元组”:同一个prompt,配一个chosen回答和一个rejected回答。chosen是好的、规范的回答,rejected是有问题的回答。最关键的不是这两条回答本身,而是它们之间的差异必须“干净”。
什么意思?我举个例子。用户问“商品有质量问题怎么退换”,chosen回答是“请提供订单号和商品照片,我协助您申请退换货”,rejected回答是“您好呀,很高兴为您服务哦,您反馈的问题我们已经收到啦,请您耐心等待哦”。这两条回答长度和风格差太多,模型会很容易学会“像chosen那样说话”,但它并不知道chosen比rejected好在“提供明确的行动指引”这个核心点上。DPO要学的是行为偏好,不是表面风格。所以正确的pair应该是:两条回答内容相关、长度接近、表达风格协调,唯一的区别是chosen给出了正确的处理路径,而rejected只是复述了一遍问题或者给出了错误指引。
这个项目里,我构造DPO数据有两个来源。一个是把SFT阶段模型真实生成过的坏case收集起来,让专业标注员改写成好case,坏case就是rejected,改好的就是chosen,这样偏好对非常贴近实际错误分布。另一个是用业务规则自动构造一些明显违反规范的样本,比如模型编造了一个不存在的退款金额,这种硬伤样本对模型纠正幻觉非常有效。DPO数据的数量,我最终只保留了大概三千到五千对,但每一对都经过人工审核,这个精力和投入完全值得。
2.4 清洗、去重与数据泄漏,这三件事不能偷懒
数据构建完成后,别急着开训练。我吃过大亏:当时有一版数据里混入了重复样本,同一条高频问题在数据集里出现了上千次,结果模型训练完之后,遇到这个问题只会机械地背诵那一条回答,稍微换个问法就不会答了。这就是过拟合的典型表现。
所以我现在做数据清洗,有三步是必走的。第一步是去重,用文本相似度的方法把几乎相同的样本过滤掉,尤其要注意那些“只有标点符号或语气词不同”的重复对,这类最容易漏。第二步是查乱码和格式问题,JSON里的引号有没有转义、字段名有没有拼错、有没有混入二进制不可见字符,这些都会在训练过程中变成隐型炸弹。第三步也是很多人忽略的——数据泄漏检查。如果测试集的样本和训练集高度重合,评估结果就会虚高,上线后真实性能立刻打回原形。我在项目里给每一版数据集都打了版本号,训练前会随机抽样200条人工看一遍,确认没有低级问题再进训练环节。
3. SFT实操:让Qwen3-8B先生长出一身肌肉
数据到位之后,真正动手训练。这个阶段的目标很明确:让模型学会业务知识、对话格式和基本的行为模式。我会分硬件环境、超参选择、训练过程观察三个层面展开。
3.1 环境准备:一张卡能做什么,几张卡怎么分工
针对Qwen3-8B这个体量,最常见的配置就是单卡或者双卡。8B模型在bf16精度下,模型权重大概占16GB显存左右。如果做LoRA而不是全参数微调,训练时额外需要的显存主要来自优化器状态和激活值。开个乐观但切实际的账:单张40G显存的A100或者同等算力的卡,LoRA微调8B模型是完全可以跑的,序列长度控制在2048、batch size小一点就行。如果是80G显存,那就更宽裕了,可以把batch加大,训练速度会明显更快。
我用的训练方式选择了LoRA,不是全量微调。原因是这个项目的数据量并不算大,全量微调8B模型一是训练时间长,二是很容易把基座模型原本学到的通用能力冲掉。LoRA只训练注入的少量低秩参数,效果在垂直场景里足够,而且训练速度非常快。我在这个阶段用开源微调框架跑通全套流程,训练脚本的核心部分长这样:
torchrun --nproc_per_node=1 train_sft.py \ --model_name qwen3-8b-instruct \ --dataset_path ./data/sft_train.jsonl \ --output_dir ./checkpoints/sft_round1 \ --lora_rank 32 \ --lora_alpha 64 \ --learning_rate 2e-5 \ --num_train_epochs 2 \ --max_seq_length 2048 \ --per_device_train_batch_size 4 \ --gradient_accumulation_steps 4 \ --lr_scheduler_type cosine \ --warmup_ratio 0.03 \ --bf16 True这套参数是我调过几轮的产物,每一步都有讲究。LoRA的rank取32,在8B模型上是一个“够用且不过度”的中间值,rank太小表达能力不足,rank太大又容易把模型改得面目全非。学习率2e-5在LoRA里属于比较稳妥的起始值,比全量微调用的1e-5要大一些,因为LoRA实际上只更新极少量的参数。训练轮数我只跑了2轮,对着业务数据反复重跑过多轮,验证集表现反而下降,这是过拟合的信号。
3.2 序列长度、batch size和损失曲线的工程含义
max_seq_length取多少,不是随便拍脑袋决定的。我先统计了训练数据里样本长度分布,发现绝大多数对话样本在1200 token以内,超过1800的很少,所以我定2048作为上限。这里有个隐藏陷阱:如果模型实际生成的回答很长,而训练时序列长度不够,脚本会把后面的内容直接截断,等于模型练习到一半被人掐了脖子,它根本学不会完整的结尾动作。所以我建议你用max_seq_length覆盖数据集中至少90%以上的样本长度,而不是用2048这个惯性值。
训练过程中,我每天都会盯loss曲线,但我要提醒你:SFT的loss下降速度和你最终的效果好坏,通常不成简单的正比关系。有一轮训练我的loss降得又快又漂亮,结果拿去评测,模型输出的答非所问,原因是学习率开太大,模型在新数据上“用力过猛”,把原来会的东西冲掉了。更合理的做法是:训练结束后,在验证集上看几个真实业务case的输出,形式质量比loss数值更有参考价值。
3.3 SFT阶段我踩过的几个典型坑
先说一个最容易忽视的:对话模板。Qwen3-8B这类模型有自己的chat模板格式,训练数据在喂进去之前,必须严格按照它的格式拼接system、user、assistant的对话历史。我一开始图省事,自己拼了一个“看起来差不多”的模板,结果训练出来的模型在生成时会重复输出标签符号,比如把“assistant”这个词本身也打印出来。排查了半天,最后发现是训练阶段用的模板和推理阶段用的模板不一致。所以从头到尾一定要用同一套模板处理数据。
第二个坑是长system导致的问题。前面提过,这里再说细一点:当system字段过长,模型在训练时会把大量注意力放在这段固定指令上,导致它对这些规则的记忆很机械——你问它规则是什么它背得出来,但实际对话里根本不会照做。正确的做法是把必要的规则精简到两三句话,更多约束靠精心设计的正负样本让模型内化,而不是靠提示词硬控。
第三个坑是混合数据比例失衡。有一版我贪多,业务数据占了80%,通用数据只有5%,结果模型虽然垂直问题处理得不错,但一到开放话题就开始胡言乱语,连基本的“我不太清楚”这种话都不会说。这就是太偏科了。后来我把通用数据补回15%左右,模型的“常识防线”就回来了。
4. DPO实操:把“什么该说什么不该说”焊进模型里
SFT阶段结束,模型已经是一个“懂行但有点毛躁”的助手。接下来进入DPO阶段,这是整个项目里我认为最有工程含金量的部分,因为SFT的标准答案相对好定,而DPO要教会模型“偏好”,处理起来会抽象很多。
4.1 为什么SFT之后还不能直接上线
直接上线SFT模型会出什么幺蛾子?我总结过三类。第一类是答案风格漂移,同一个问题,模型有时长篇大论,有时惜字如金,用户根本没法预期它的行为模式。第二类是边界混乱,模型对已知问题答得好,但对稍微越界的问题,它倾向于“硬答”而不是“拒答”,这是SFT阶段的天然缺陷——因为训练样本里绝大部分都是正例,模型没有见过足够的“不该答”的场景。第三类是幻觉依旧存在,SFT能让模型记住高频信息,但面对低频信息或组合型问题,它照样会编造。
DPO的作用就是用成对的“正反样本”给模型划出边界,让它从概率层面降低坏行为。这里有必要解释一下DPO相对传统强化学习方法的优势:它不需要单独训练一个奖励模型,而是直接通过chosen和rejected的对比来优化模型自身的策略。对工程团队来说,这意味着少维护一个奖励模型,管线更简单,训练更稳定。
4.2 DPO训练的关键参数:beta、学习率、参考模型
DPO训练的核心超参是beta,它控制模型对偏好差异的敏感程度。beta越大,模型越激进地去迎合chosen、远离rejected,但副作用是容易把模型改得“过度保守”,遇到什么都倾向于拒答;beta越小,模型越保守,对坏行为的修正力度也越小。我在这个项目中先用beta=0.1作为起点,因为Qwen3-8B基座能力本身不错,轻微调整就能看到效果,数值太激进反而容易破坏对话流畅性。
还有一个工程细节:DPO训练通常会先跑一遍参考模型对chosen和rejected的logprob计算,缓存下来作为训练时的正则项。这个参考模型就是微调前的SFT模型,它的作用是限制DPO阶段不要把模型推离原分布太远。训练时学习率要压得比SFT更低,我用了1e-6到2e-6,因为DPO阶段模型只需要微调行为,不需要大范围更新知识,学习率太大会导致两个问题:一是模型把chosen几乎“背下来”,对同一问题的不同表述变得极不鲁棒;二是DPO训练集之外的正常能力被误伤。
这一阶段的训练脚本看起来是这样的:
torchrun --nproc_per_node=1 train_dpo.py \ --model_name ./checkpoints/sft_round1 \ --ref_model ./checkpoints/sft_round1 \ --dataset_path ./data/dpo_train.jsonl \ --output_dir ./checkpoints/dpo_round1 \ --lora_rank 16 \ --lora_alpha 32 \ --learning_rate 1.5e-6 \ --num_train_epochs 1 \ --max_seq_length 2048 \ --per_device_train_batch_size 2 \ --beta 0.1 \ --bf16 True这里我把rank降到了16,原因很简单:DPO阶段的更新本来就应该是“精细的微调”,用和SFT一样大的rank反而容易过度拟合偏好数据。训练轮数我坚持只用1轮,DPO数据本身规模不大且质量很高,多跑几轮除了让模型把偏好对背下来之外没有太多好处。
4.3 DPO训练中的稳定性问题排查
DPO训练中有一个让很多人困惑的现象:loss会出现负值。我第一次看到时也愣了一下,后来才明白,DPO的loss并不像SFT那样函数值最低就是最好,负loss在DPO里反而是正常现象,它代表模型已经开始有效地区分chosen和rejected。不要看到loss为负就慌着杀训练进程。
真正需要警惕的是三个异常信号。第一个是loss下降得非常慢,那大概率是偏好对的质量有问题,chosen和rejected之间没有实质差异,模型学不到东西;第二个是训练后模型的响应变得极其简短、缺少解释,动不动就说“无法回答”,这是beta或者学习率调太高了,模型在走极端;第三个是训练过程中出现NaN loss,通常指向数据里存在异常值,我遇到过一次,排查后发现是某条样本里有一个超长的编码错误token,清洗数据时漏掉了。
4.4 一个反直觉的经验:DPO要离SFT远一点
这句话的意思不是让两个阶段割裂,而是说DPO的数据分布要尽量贴近线上真实场景的“错误分布”。我在第一个版本里犯了错,DPO偏好对全部来自SFT训练数据里的改写,结果就是模型在训练集分布的case上表现更好,一旦遇到训练集之外的提问,行为又从“稳定”变回“放飞”。后来我把DPO数据改成了一个混合集:一部分来自SFT模型在验证集上的真实错误输出,一部分来自线上种子用户的内测反馈。这个改动让DPO阶段的收益真正泛化到了线上场景,而不是只在训练集上自嗨。
5. 评估闭环与Agent化落地:把模型从实验品变成生产力
训练完成不代表项目结束,反而是另一项更考验工程能力的工作的开始:怎么科学地评估一个微调后的模型,以及怎么把它部署成真正可用的Agent服务。这两件事如果做不好,再好的训练成果也落不了地。
5.1 三层评估体系:规则检查、模型裁判、人工抽检
模型评估不能靠拍脑袋“聊几个case觉得不错”。我在这个项目里搭了一套三层评估流水线,每一层解决问题的方式都不一样。
第一层是规则检查,用程序对模型输出做硬性校验。比如我们要求Agent在需要调用工具时输出特定格式的JSON,规则检查就会验证JSON是否合法、字段是否齐全、枚举值是否符合业务定义。这一层是自动化的,速度快、标准统一,任何格式不达标的输出都会被打回。
第二层是模型裁判,用一个能力更强的模型(可以是另一个开源模型)对Agent的输出进行打分。这层主要评估语义层面的质量,像回答是否正确、是否贴合用户问题、有没有幻觉。这一层不能完全替代人工,但它可以把大部分无效case筛掉,减少人工工作量。
第三层是人工抽检,每周从评测结果里随机抽一百条,由业务专家标注“可用”“勉强可用”“不可用”三档。人工抽检的结果用来校准前两层的标准,因为规则和裁判模型都可能存在偏差。整套评估体系跑下来,模型每个版本出来都能有一组可比较的数字,而不是凭感觉说“感觉这版更强”。
5.2 一组实测数据对比:SFT之前、SFT之后、DPO之后
我直接摆一组项目中的实测记录,用四个最关键的指标来展示三个阶段的差异。注意这些数值来自我手头这个具体业务场景,并不是通用指标,但趋势具有代表性。
| 指标 | 评测方式 | 微调前 | SFT后 | DPO后 |
|---|---|---|---|---|
| 意图识别准确率 | 500条标注测试集 | 82.6% | 91.4% | 93.2% |
| JSON格式通过率 | 规则校验工具调用输出 | 61.2% | 97.8% | 99.1% |
| 幻觉率 | 标注员判断“编造信息”占比 | 28.5% | 10.2% | 6.7% |
| 越界拒答正确率 | 100条边界问题中正确拒答占比 | 54.0% | 71.0% | 82.0% |
从这组数据能明显看出,SFT主要提升了意图识别和格式能力,但幻觉率只是从28%降到10%,仍然偏高;而DPO在幻觉和边界控制上补了关键一刀。特别是“越界拒答正确率”,这个指标只有DPO能真正拉起来,因为SFT只会教模型“怎么答”,很难教它“什么时候不该答”。
5.3 部署与服务化:推理引擎、并发控制和异常兜底
模型训好了,接下来怎么跑服务。我在推理侧把模型和对话模板做了严格对齐,这一点在上线时曾经踩过坑:训练时用的模板格式和服务端推理时用的模板格式不一致,导致在线效果和离线评测结果差异很大。后来我把模板封装成一个统一的模块,训练和推理共用同一份代码,从根上解决了这个问题。
并发和延迟方面,8B模型部署到单卡推理其实压力不大。我用的是连续批处理和PagedAttention这类推理框架的优化能力,支持的吞吐量可以打到一个较理想的水平。对一个中等接入量的业务Agent来说,单机单卡足够应对。但我还是建议在架构上保留一个备用节点,以及把模型输出后的JSON从不合法到合法做一个兜底修复,线上见过太多次模型突然输出一个非法格式,如果没有兜底,整个业务流程就断了。
5.4 让Agent真正“用起来”:工具调用和RAG的组合
一个垂直Agent如果只做“你说我答”,价值其实很有限。真正好用的是一个会调用工具的Agent。我在这个项目里把工具调用也纳入到了SFT和DPO的训练范围:训练数据里包含了“用户请求-工具调用结果-最终回答”的完整链路。SFT阶段让模型学会怎么生成正确的工具调用参数,DPO阶段用偏好对让模型学会在信息不足时主动申请更多信息,而不是编一个假设的答案糊弄用户。
同时我坚持一个原则:微调不是万能的,凡是依赖大量实时知识的问题,都让Agent去查知识库或业务系统,而不是指望模型把知识背进参数里。所以整体架构是“微调后的Agent负责理解意图、控制对话、决策何时调用工具,RAG负责提供新鲜且具体的知识”。这个组合让模型在保持稳定性的同时,也具备了处理开放问题的能力。
6. 复盘与心得:如果再让我做一次,我会做哪些不一样的事
项目结束后,我习惯性复盘了一遍整个流程。如果现在重新启动一个类似的微调项目,有几件事我一定会坚持,有几次弯路也完全不想再走第二遍。
先聊数据。如果给我一个月时间只做一件事,我会全部花在数据上。不是因为算力不重要,而是因为让模型“长出肌肉”的训练过程其实很标准化,真正拉开模型水平差距的,是数据里那些看不见的选择:你有没有覆盖边界case?你的rejected样本是不是有实质性的错误?你有没有把通用数据配比控制在合理区间?这些才是决定模型上线后表现的关键。
再聊一个关于节奏的感悟:SFT没有做到位之前,不要急着上DPO。我见过很多团队绕开SFT直接做DPO,理由是“DPO不是能直接教会模型好坏吗”,这个想法看起来很合理,但实际操作中,如果模型连垂直场景的基本表达都没学会,DPO的偏好信号就无处着力。所以我的建议是先把SFT版本评测通过,再动手构建偏好数据,用DPO去打磨细节,这个顺序不要反过来。
还有一个实际的工程小技巧想分享:在项目里建一个固定数量的“核心回归集”,差不多二十到三十条有代表性的业务case。每次训练完一版模型,先跑一遍回归集,看有没有哪一条case的行为退化。这个回归集比任何评测指标都能更快暴露问题。我在几次迭代中都是靠这个小工具及时发现了“模型某个能力被新版本冲掉”的情况,避免了把坏版本带上线。
最后说说这次的收获。把一个Qwen3-8B从“通用聊天模型”变成“稳定可控的垂直Agent”,不是某一条命令、某一个超参的功劳,而是数据、训练策略、评估体系共同作用的结果。如果你也想做类似的事,我希望这篇文章能帮你看清整条链路的全貌,少走一些我已经替你走过的弯路。