本节摘要:当开源集覆盖不了你的场景(中文、垂直领域、特定文风),标准做法是蒸馏:让一个强模型替你生产指令数据。本节拆解三步回环——①写种子指令(可手写几十条、可模板扩增、可用强模型自举扩写);②强模型按种子生成回答(或多轮对话);③质量过滤把住回流关口(规则 + 抽检 + 可选的模型打分),过滤不过的种子直接淘汰而不是修复。附可跑的蒸馏管线示意代码与成本结构分析。本节同时预告中文语料方案:中文 SFT 的主力产线正是这条回环(中文种子 → 强模型中文回答 → 中文质量过滤),详细展开在第 8 章。
阅读完本节,你应当能够:
开源指令集有两个天然盲区:你的领域(开源集里没有你业务的问答)与你的语言/文风(英文为主的语料养不出中文手感)。蒸馏用"强模型当作者、你当主编"解决两个盲区:
开源路线: 现成数据集 ──清洗──► messages JSONL (快、便宜、不贴身) 蒸馏路线: 种子指令 ──强模型生成──过滤──► messages JSONL (慢、花钱、贴身)
工业界的普遍认知(属共识级结论):主流开源对话模型的 SFT 数据大量来自更强模型的生成与蒸馏,纯人工标注只占小比例。nanochat 的发布帖思路亦同源——实验规模下取一批高质量问答对即可,数据从哪来(开源、自写、蒸馏)方法上等价。
⚠️ 合规提醒:用商业 API 的输出训练竞品模型通常违反其服务条款;用允许蒸馏的模型(各类开放权重模型,条款以其 license 为准)或自建模型为佳。教学实验之外的商业用途,先读条款。
第一步:种子指令。 三种产法按成本递增:手写(几十条定基调,值得亲手写);模板扩增("解释 X 概念/比较 X 与 Y/给 X 写大纲"套主题词,几百条即时可得);模型自举(把 20 条手写种子给强模型:"仿照这些例子再写 200 条,保持分布多样",数千条起)。种子决定分布——它的题材配比就是未来模型的题材配比。
第二步:强模型生成。 每条种子作为 user 消息,请求强模型产出 assistant 回答;多轮数据则让强模型同时扮演用户追问与助手回答。生成参数用偏保守的低温(temperature 0.6 左右),数据要稳不要野。
第三步:质量过滤。 过滤不过就淘汰,不要试图修复(修复一条脏数据的成本高于再生成一条)。三道闸:
rule_filter 直接复用;# distill_sft.py —— 最小蒸馏管线(写法示意,API 以所用服务文档为准) import json import random from openai import OpenAI # 任何 OpenAI 兼容接口均可(本地 vLLM、云端网关等) client = OpenAI() # 从环境变量读取 API Key SEEDS = [ # 第一步:手写种子(示例为中文科普向,第 8 章主力产线的雏形) "用三句话解释什么是熵。", "比较 TCP 和 UDP 的核心差异。", "给初学者列一个学习线性代数的三步计划。", # ... 实际使用时 50~2000 条,题材配比即模型未来的能力分布 ] GEN_PROMPT = "你是中文科普助手,回答准确、简洁,不超过 150 字。" JUDGE_PROMPT = "判断以下回答是否正确且完整。只输出 yes 或 no。\n\n问题:{q}\n回答:{a}" def generate(seed: str) -> dict: """第二步:强模型生成回答,产出 2.1 契约格式。""" r = client.chat.completions.create( model="填入允许蒸馏的模型名", messages=[ {"role": "system", "content": GEN_PROMPT}, {"role": "user", "content": seed}, ], temperature=0.6, # 数据要稳:低温生成 ) return {"messages": [ {"role": "user", "content": seed}, {"role": "assistant", "content": r.choices[0].message.content}, ]} def judge(obj: dict) -> bool: # 第三步·模型闸 q, a = obj["messages"][0]["content"], obj["messages"][1]["content"] r = client.chat.completions.create( model="填入裁判模型名", messages=[{"role": "user", "content": JUDGE_PROMPT.format(q=q, a=a)}], temperature=0.0, ) return r.choices[0].message.content.strip().lower().startswith("yes") if __name__ == "__main__": random.shuffle(SEEDS) kept = 0 with open("data_sft/distilled.jsonl", "w", encoding="utf-8") as out: for seed in SEEDS: obj = generate(seed) if not judge(obj): # 过滤不过直接淘汰,不修复 continue out.write(json.dumps(obj, ensure_ascii=False) + "\n") kept += 1 print(f"蒸馏完成:{kept}/{len(SEEDS)}")
工程上再加三件套才叫产线:断点续跑(按种子哈希记录已完成项)、并发(异步或线程池打满配额)、去重(回流进 2.2 的清洗管线,蒸馏数据同样要过精确去重)。
成本速算(示意估算,单价随服务波动):设生成 0.5 美元/百万输入 token、1.5 美元/百万输出 token,每条"一问一答 + 裁判一轮"约 600 输入 + 400 输出 token——一万条约 710 美元,十万条约 70100 美元。结论:蒸馏便宜到可以放心试错,贵的不是钱是种子质量。
配比建议(衔接第 8 章的伏笔):
| 目标 | 数据配比 |
|---|---|
| 中文对话模型 | 蒸馏中文主力(约 70%)+ 开源英文集(约 30%,保持通用能力不塌) |
| 垂直领域 | 领域蒸馏(约 50%)+ 通用开源集(约 50%,防止只会聊领域) |
| 快速原型 | 纯开源集起步,跑通第 3 章后再回头蒸馏 |
英文掺入的直觉:预训练语料(FineWeb-Edu)以英文为主,基座的"母语"是英文;中文 SFT 数据教它用中文表达,但底层能力仍大量寄存在英文参数里——完全切掉英文 SFT 数据,通用能力会明显滑坡(灾难性遗忘的温和形态,3.3 节展开)。
燃料备齐(2.2 的开源集 + 2.3 的蒸馏产线汇成
data_sft/train.jsonl),第 3 章点火:把它喂给基座。