技能蒸馏进权重:抽象技能作为特权信号的自蒸馏方法
2026/9/7 3:11:34 网站建设 项目流程

我们把视线放回到一个很容易被忽视但又很关键的问题上:当我们需要让大模型稳定掌握一项能力时,到底是把能力写成提示词更可靠,还是把能力融进模型权重更可靠?

这篇文章要聊的研究方向,标题很长,但翻译过来很直白:

将技能蒸馏到权重中,而不是塞进提示词:抽象技能作为策略内自蒸馏的特权信号。

简单说,它解决的是“如何让模型真正学会技能,而不是靠提示词临时假装会”。核心途径是:在训练阶段使用“抽象技能”作为特权信号,配合 on-policy 自蒸馏,把一个技能从“可读的提示词描述”变成“模型内部权重里已经编码的行为”。

在直接讲方法前,先把最适合这篇文章的读者画个像:

  • 关注 LLM 微调、蒸馏、RL 训练的技术同学;
  • 被长提示词、复杂 few-shot 提示词搞到体感很差,想尝试“把技能沉淀进模型”的工程师;
  • 对 PPO 为什么是 on-policy、自蒸馏数据怎么组织、特权信号怎么用,有概念但想系统梳理一遍的人。

如果你属于其中任何一类,这篇文章可以收藏后慢慢对着实验框架看。

1. 核心概念速览

先把这套方法的关键属性放在最前面:

能力项说明
方法类型蒸馏 + 特权学习 + 策略内自蒸馏(on-policy self-distillation)
核心主张技能应该融进模型权重,而不是长期依赖提示词
训练信号抽象技能标签作为 privileged signal,训练阶段可见,推理阶段不可见
数据来源当前模型自身采样得到的 on-policy 轨迹
与 PPO 的关系数据收集策略与 PPO 保持一致,强调分布匹配
与 RLHF/DPO 的关系可以独立使用,也可以作为 RL 微调的前置蒸馏阶段
推荐验证硬件视基座模型规模而定,7B~13B 级模型需要 24G 以上单卡或量化方案
是否需要显式提示词训练时可借助提示词辅助,推理时目标是不依赖技能提示词
适合场景高频、稳定、可重复的技能沉淀,降低提示词长度和调用成本

这里的参数不属于某个已开源项目的实测值。实际上,这套思路更多来自 ACL/ICML 序列里对“distillation + privileged information”的长期研究,并没有统一的一键启动包。所以下面会按“方法拆解 -> 训练框架 -> 实验设计 -> 工程化落地”的路径来讲,而不是给安装命令。

2. 为什么“提示词承载技能”这件事是靠不住的

先看日常现象。现在很多大模型应用,为了让模型做对一件事,会把大量“技能”塞进提示词:

  • few-shot 示例动不动占上千 token;
  • 把完整工作流写成自然语言指令,塞进 system prompt;
  • 每次调用都重复传同一段风格要求、格式要求、安全约束。

这种做法的优点是灵活,改提示词就能改行为。但缺点也很明显。

第一,提示词有上下文窗口上限。技能描述越长,留给真实任务内容的 token 就越少。如果你需要一个 5000 token 的技能包,而输入任务文本有 8000 token,那基本就顶到窗口边缘了,处理质量会迅速下降。

第二,提示词是位置敏感的。同一个技能描述放在 system 位置和放在用户消息末尾,效果不一样;放在超长文本中间,还可能被模型忽略。模型对提示词的服从并不是稳定的,换个模板结果就漂移。

第三,提示词有成本和延迟问题。每次请求都携带同样的大段技能描述,意味着每次的 prefill 计算量都在增加,TTFT 升高、成本上升。对高频接口调用来说,这是很实际的压力。

第四,提示词是可泄露的。如果技能包里包含私有工作流、内部规则甚至敏感数据,每次把提示词发给模型,就会有被日志记录、被外部系统捕获的风险。把技能融进权重,至少能减少一部分“明文暴露”面积。

所以“技能放进权重而不是提示词”这个方向,解决的不是“提示词能不能用”,而是“能不能让模型在无提示场景下也能稳定执行技能”。

3. 问题定义:技能、特权信号和自蒸馏

要理解这套方法,先统一三个概念。

3.1 抽象技能

所谓抽象技能,是指不绑定具体输入输出格式的高层能力描述。比如:

  • “先定位问题根因,再给出修复方案”;
  • “按照时间线重排事件,并区分事实与观点”;
  • “生成代码前先写测试用例”。

它不等于“一步一步怎么做”的低级指令,也不等于一段具体的 few-shot 示例。抽象技能更接近人类说的“我会用这个套路”,而不是“我照着这个模板写”。

3.2 特权信号

“特权信号”来自 privileged information 这个经典概念。简单说:训练的时候,模型可以额外看到一个标签或者高级信息,但在推理的时候这个信息不出现。

最常见的例子就是“老师模型”和“学生模型”。老师模型在训练时能看到正确答案做辅助,学生模型不能直接看到正确答案,只能通过老师的输出分布来学习。

在这套思路里,模型训练时会被明确告知:“这条轨迹对应的是技能 A”。这个技能标签就是特权信号。推理时,模型并不知道技能标签,它必须靠自己学出来的权重完成同样的行为。

关键区别就在这里:提示词方案是在推理时也把技能名或技能描述输入给模型;特权信号方案只在训练时利用技能信号,推理时不输入。

3.3 自蒸馏

自蒸馏,简单说就是让模型自己教自己。如果模型当前生成的结果不够好,但通过某种筛选、纠错、加约束,得到了一批高质量轨迹,就可以用这批轨迹继续训练模型自身。

这里强调“自”,是因为数据不需要外部大模型或者人工全部重写,主要来自模型自身采样和基于自身分布的重建。成本更低,而且分布与当前策略更匹配。

3.4 组合起来

把三者组合,就是题目说的结构:

  1. 模型用当前策略生成一批轨迹;
  2. 每条轨迹被赋予一个抽象技能标签;
  3. 技能标签作为特权信号,参与训练;
  4. 目标是在推理时不输入该信号,模型也能复现对应技能行为。

这是一个循环:生成轨迹 -> 标注技能 -> 自蒸馏 -> 更新策略 -> 再生成轨迹。因为每一轮数据都来自当前策略,所以是 on-policy 的自我提升闭环。

4. 方法拆解:以抽象技能为特权信号的策略内自蒸馏

下面给出一个可以落地验证的方法框架。注意,这不是复述某篇论文的精确公式,而是根据这套思路整理出的通用实现路径。

4.1 整体流程

整个训练流程可以分成 4 步:

  1. 轨迹采样(on-policy generation);
  2. 技能标注(privileged labeling);
  3. 蒸馏目标计算(distillation loss);
  4. 参数更新与循环。

可以用下面的伪代码描述:

# 伪代码:按论文思路整理的训练循环示意 model = load_model("base_llm") skill_memory = [] for iteration in range(N): # 1. on-policy 采样:从当前策略生成轨迹 prompts = sample_tasks(batch_size=64) trajectories = model.generate(prompts) # 2. 为每条轨迹标注抽象技能(特权信号,仅供训练使用) # 实际工程中可由规则分类器、小型标注模型或人工标签完成 skill_labels = annotate_skills(trajectories) # 3. 构造“教师提示”:训练时把技能作为提示辅助生成参考分布 teacher_input = apply_skill_prompt(prompts, skill_labels) teacher_logits = model.forward(teacher_input) # 4. 学生输入:推理形态,不注入技能提示 student_input = prompts # 或者仅保留最基础的任务格式 # 5. 蒸馏:让学生分布逼近教师分布 loss = kl_divergence( student_logits=model(student_input), teacher_logits=teacher_logits ) # 也可以叠加 SFT / 偏好损失,取决于任务类型 loss.backward() optimizer.step()

这个循环的重点是:每一步数据都来自当前模型,而不是历史版本或者外部固定数据集,这保证了数据分布与策略的一致性。

4.2 教师提示怎么设计

训练阶段可以“作弊”,把技能写进提示词,让模型先得到一个有技能引导的输出分布。这个分布称为教师分布。但在损失计算时,学生输入不包含技能描述,模型必须依靠权重内编码的能力去逼近教师分布。

为什么不能直接微调“模型输出与目标文本”的交叉熵?

因为如果只有一个目标文本,模型可能只是机械记忆。用教师分布做软目标,学生能学到技能标签带来的“行为倾向”,而不是死记某个答案。蒸馏的软标签信息量更大。

4.3 技能标注来源

给轨迹标注抽象技能,工程上有三种可行方式:

  • 规则匹配:如果任务类型固定,可以用规则直接映射。
  • 小型分类模型:训练一个轻量分类器,输入轨迹,输出技能类别。
  • LLM 自标注:让一个额外模型或当前模型本身判断“这条轨迹更像哪个技能”,注意这里需要防止信息泄漏,标注结果只进训练损失,不进推理输入。

如果你的场景本身就有明确技能分类,比如“代码审查”“SQL 生成”“报告摘要”,最稳妥的做法是先用规则保证标签准确,再逐步引入模型标注。

4.4 推理阶段使用方式

训练完成后,推理阶段应该做到:

输入:用户问题 输出:模型直接给出符合技能要求的结果 不输入:技能描述、few-shot 示例、步骤清单

为了验证是否真的做到了,可以设计这样一个实验:

  • 对照组 A:提示词中包含完整技能描述;
  • 对照组 B:提示词为空,只有任务本身;
  • 实验组:使用蒸馏后的模型,提示词为空,只给任务本身。

如果实验组的表现接近甚至超过对照组 A,说明技能已经成功融进了权重。

5. 为什么说 PPO 是 on-policy?这对自蒸馏到底意味着什么

热搜词里有“为什么说 PPO 是 on-policy”,这里单独展开。因为不理解 on-policy,就很难理解这套自蒸馏方法为什么强调数据来自当前策略。

5.1 PPO 的 on-policy 含义

PPO 的优化对象是当前策略。每一轮更新时,它先让当前策略与环境交互,采集一批轨迹,然后用这批轨迹计算优势函数,更新策略。更新完之后,这批轨迹最好直接扔掉,因为旧策略产生的数据已经不能准确反映新策略的行为分布。

换句话说:

  • on-policy:数据来自当前策略,用一次就作废;
  • off-policy:数据来自旧策略,可以通过经验回放反复利用。

PPO 通过重要性采样系数来控制新旧策略的偏差,同时对更新步长做裁剪,防止一次更新太猛。但归根结底,它并不追求高效复用旧数据,而是追求“每一步数据都与当前策略尽量匹配”。

5.2 on-policy 对自蒸馏的影响

自蒸馏如果使用历史版本模型生成的旧数据,会有分布偏移问题。旧策略的语言风格、错误模式、思考方式都和新策略不同。如果蒸馏目标中混入大量旧数据,模型会被拉向一个已经过时的行为分布。

而 on-policy 自蒸馏,要求模型在每一轮训练前重新生成轨迹。这样:

  • 数据分布与当前策略一致;
  • 模型不会学到一个“过期的自己”;
  • 蒸馏信号能准确反映当前策略下的能力边界。

这也是为什么这套方法会把 on-policy 作为核心词放在标题里。它借鉴的是强化学习中数据分布匹配的思想,而不是单纯把蒸馏当离线数据处理来做。

5.3 on-policy 的代价

on-policy 的代价很直接:每一轮都要重新采样,推理开销巨大。对 7B 模型来说,生成 1 万条轨迹可能需要几百 A100 小时。实际做的时候,建议先缩小任务集和轨迹数量,验证蒸馏效果后再扩容。

6. 实验设计:怎样验证“技能真的进入了权重”

这一节给出一个可执行的验证方案,不依赖外部基准,适合你用自己的数据和场景跑通。

6.1 选择技能组

建议从 3 个方向各选一个技能:

  • 结构化技能:例如“输出必须包含背景、问题、方案、风险四段”;
  • 推理技能:例如“先拆解问题,再给出结论”;
  • 风格技能:例如“用简洁中文回答,不出现专业术语”。

把技能描述写成标签,比如skill_structure_4partskill_reasoning_firstskill_plain_language

6.2 蒸馏前评估

在蒸馏前,先用基础模型做一组 baseline:

  • 无提示词生成效果;
  • 完整技能提示词生成效果;
  • 部分技能提示词生成效果。

记录三项指标:格式符合率、语义正确率、关键点覆盖率。

这样蒸馏后,可以直接用同一套评估脚本对比。

6.3 蒸馏后评估

蒸馏后,评估分三组:

  1. 无提示词输入;
  2. 输入一段无关提示词,观察技能是否被干扰;
  3. 输入与技能相反的提示词,观察技能稳定性。

如果第 1 组接近 baseline 的完整技能提示词效果,说明技能已经内化。第 2 组和第 3 组能帮你判断技能是“真正写入权重”还是“只是恰好记住了训练数据里的固定输入格式”。

6.4 判断成功的标准

一个合理的成功标准是:

  • 无提示词效果达到完整技能提示词效果的 80% 以上;
  • 在无关提示词干扰下,技能效果不下降超过 10%;
  • 模型在未见过的同类型任务上也能保持该技能。

如果你的测试达不到这个标准,先别急着加数据,检查技能标签是否准确、教师分布是否稳定、训练步数是否足够。

7. 训练框架与工程实现建议

这个方向目前没有统一开源框架,但可以基于常见训练工具搭一套最小实现。

7.1 推荐技术栈

组件建议
基础模型7B~13B 开源模型,例如 Qwen 系列、Llama 系列
训练框架PyTorch + Transformers + TRL 或自写训练循环
数据管理JSONL 格式存储 prompts、trajectories、skill_labels
分布式训练单机多卡优先,DeepSpeed ZeRO-2/3
采样加速vLLM 或 SGLang 用于轨迹生成
监控wandb 或 tensorboard

7.2 数据格式示例

建议把轨迹、技能标签、训练状态分开记录:

{ "prompt": "请为上面的日志分析给出排查建议", "trajectory": "第一,先确认错误码...", "skill_label": "skill_reasoning_first", "teacher_prompt": "技能说明:先拆解问题,再给出结论。用户问题:请为上面的日志分析给出排查建议", "student_prompt": "请为上面的日志分析给出排查建议", "is_valid": true }

这里要注意:teacher_promptstudent_prompt都保留,训练时用 teacher 的 logits 作为蒸馏目标,只有 student 的 logits 参与梯度更新。

7.3 损失函数设计

实际的蒸馏损失可以拆成三部分:

loss = ( alpha * kl_loss(student_logits, teacher_logits) + beta * ce_loss(student_logits, reference_answer) + gamma * skill_contrastive_loss(student_logits, negative_skill_logits) )

参数说明:

  • kl_loss:软蒸馏主损失;
  • ce_loss:如果有标准答案,加上监督信号;
  • skill_contrastive_loss:可选,让模型明确区分当前技能与其他技能的差异。

最优参数需要按任务调试,不建议直接照搬。

7.4 训练稳定性控制

蒸馏训练里最常遇到的问题是“学生输出退化”。控制训练稳定性,有几个建议:

  • 教师分布使用带温度的 softmax,温度不宜太高,通常 1.0~2.0;
  • 对 KL 损失设置阈值,如果学生与教师差异过大,先暂停更新或降低学习率;
  • 保留一份“冻结版本”模型,每轮采样时用它当参考,防止策略崩坏;
  • 如果发现模型输出开始重复,需要检查训练数据多样性,并适当降低蒸馏权重。

8. 资源占用与性能观察重点

这里不给出具体显存数字,因为和基座模型、序列长度、批量大小直接相关,但可以给出观察方法和降载手段。

8.1 观察什么

训练过程中重点观察:

  • 单卡显存占用,以及是否出现 OOM;
  • 每轮采样时间,判断 on-policy 数据生产是否成为瓶颈;
  • KL 损失曲线,判断学生是否稳定接近教师;
  • 生成结果多样性,防止 collapse。

8.2 如何降显存

在 24G 显存的单卡上,7B 模型训练是比较紧张的选择。可以这样做:

  • 使用 LoRA/QLoRA 做参数高效微调,只训练 adapter;
  • 把教师 logits 提前计算保存到磁盘,避免训练时重复前向;
  • 轨迹生成用 vLLM 的离线批处理,训练时用浅层模型加载权重;
  • 减少序列长度,必要时对轨迹做截断。

如果条件允许,最舒服的配置是双卡 48G 以上:一张卡跑采样,一张卡跑训练,或者用流水线并行。

8.3 训练与推理的解耦

一个常见问题是:训练轮次多,但采样和训练耦合,导致 GPU 利用率很低。工程上建议把采样和训练拆成两个进程:

  • 进程 A:定时加载最新权重,生成新轨迹;
  • 进程 B:消费轨迹,训练模型;
  • 两个进程通过文件系统或消息队列传递数据。

这样可以让采样和训练各自用满 GPU,而不是交替等待。

9. 常见困惑与排查方法

问题现象可能原因排查方式解决思路
蒸馏后模型输出没变化KL 权重太低,教师信号没有影响检查 loss 曲线中 KL 项是否下降调大 KL 系数,提高训练步数
无提示词时技能失效学生只在教师提示存在的输入分布上学到了对比 student_prompt 与 teacher_prompt 的数据结构增强 student_prompt 与真实推理输入的一致性
模型开始复读蒸馏温度过高或数据多样性不足观察生成文本的 n-gram 重复率降低温度,增加采样温度或轨迹数量
技能 A 学会了,技能 B 丢失训练数据中两个技能类别不均衡检查 skill_label 分布按技能类别加权采样,或补充技能 B 轨迹
on-policy 采样成本太高每轮重新生成大量轨迹统计单条轨迹平均生成耗时先小批量验证,再用 vLLM 并行采样
训练时显存 OOM序列过长或 teacher logits 占用查看显存监控截断轨迹、保存 logits、LoRA
推理时发现技能提示词仍被需要训练没有完成真正的“去提示化”检查训练时 student_prompt 是否包含了技能描述确保 student_prompt 不含技能提示,重复,微调

10. 设计 & Engineering 思维:把这个思路用在自己的系统里

如果你不做论文实验,只把“技能融进权重,而不是提示词”当成一种产品设计思路,同样有短期可落地的路径。

10.1 什么场景适合“提示词转权重”

  • 技能稳定且长期不变;
  • 调用频率很高,token 成本敏感;
  • 技能描述属于私有知识或安全规则,不希望每次明文传输;
  • 提示词已经长到影响效果,比如超过 2K token 的技能包。

10.2 什么场景不适合

  • 技能还在快速迭代,每周都在变,用权重更新跟不上;
  • 技能只在一小部分请求中用到,做成权重徒增存储和微调成本;
  • 技能强依赖外部实时信息,本身无法完全靠模型权重承载;
  • 团队没有微调基础设施,只有 API 调用能力。

10.3 渐进式落地建议

计划可以分三步:

  1. 先把你系统里最稳定、最高频的一段技能提示词抽出来,单独测效果;
  2. 用 on-policy 自蒸馏方式做一个技能蒸馏实验,保留对照数据;
  3. 用 A/B 测试比较“提示词版”和“权重版”的效果、成本、延迟和稳定性。

如果第一步和第二步效果稳定,再扩展到更多技能。

这里需要强调一点:如果技能涉及隐私数据、他人版权内容或者人脸/声音等敏感信息,训练数据收集和蒸馏过程必须确认已获得合法授权,并且对数据访问权限做控制。不要把未授权的用户数据直接用于模型蒸馏。

11. 总结与下一步

这个方向值得尝试的核心点在于:它把“提示词工程”问题重新拉回到“模型能力构建”层面。技能不再是一段写在请求里的文本,而是模型参数中可被调用的行为模式。

如果你的目标是降低接口调用成本、缩短提示词长度、让私有技能以更安全的方式部署,那么“技能蒸馏到权重”非常值得做一轮小规模验证。

最先应该验证的功能是:选一个你手头最高频且稳定的技能,用 on-policy 数据完成一轮蒸馏,然后对比“无提示词 + 蒸馏模型”和“完整提示词 + 基础模型”的效果。这个对比能直接告诉你,权重承载技能这条路在你场景里到底走不走得通。

最容易踩的坑是:训练时偷偷把技能标签传进了 student_prompt,导致推理时没有技能提示就失效。这个坑,手动检查 prompt 字段就能避开。

后面的扩展方向也很清楚:技能仓库统一管理、多技能蒸馏、蒸馏+RLHF 结合,以及把蒸馏后的模型封装成轻量 API 服务。每一步都能独立验证,也都能复用本文这套评估思路。

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

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

立即咨询