第 18 章 篇三:后训练数据工程

SFT 指令数据构建

指令数据的模板设计、loss mask、Magpie 自生成与质量治理。

SFT 指令数据 模板

SFT 数据的灵魂是多样性,不是数量。

给模型写 SFT 数据,就像给一个天才写演讲稿——他什么都懂,但你得告诉他什么时候该幽默、什么时候该严肃。

Self-Instruct 多样性饱和在 52K,Magpie 用零种子打破了天花板。

SFT(Supervised Fine-Tuning)是把一个预训练基座变成”助手”的关键一步,而这一步的成败几乎完全取决于指令数据的质量。本章沿”数据结构 → 人工标注 → 指令合成 → 响应生成 → 质量清洗 → 数据选择 → 多样性保证 → 多轮对话 → 混合配比 → 评估迭代 → 工业 SOP”的完整链路展开,覆盖从一条样本的 JSON 结构到十万级数据生产管线的全部工程细节。

本章的方法论主线可以概括为一句话:先定义”什么是好数据”,再决定”怎么造好数据”,最后用”选择与多样性治理”把好数据浓缩成最有效的训练集。贯穿全章的两个反复出现的主题是:

  1. 多样性 > 数量:Deita 的实验证明 10K 精选数据胜过 1M 粗造数据;
  2. 好数据是相对的:PPL 衡量的是模型-数据兼容性,GRAPE 证明最佳回复取决于目标模型——不存在脱离模型的绝对数据质量。

18.1 SFT 数据结构工程

一切从数据结构开始。工业级 SFT 数据的第一道工程门槛不是”造数据”,而是把数据组织成训练框架能正确消费的标准形态——messages 结构、chat-template 转换、loss mask、Pack 边界,四者缺一不可。

18.1.1 messages 标准结构

工业级 SFT 数据统一用 messages 结构:

{
  "messages": [
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": "用 Python 实现快速排序"},
    {"role": "assistant", "content": "```python\ndef quicksort(arr):\n    ..."}
  ],
  "metadata": {
    "source": "magicoder",
    "task_type": "code",
    "difficulty": "medium",
    "quality_score": 0.85
  }
}

多轮对话扩展为多对 user/assistant:

{
  "messages": [
    {"role": "system", "content": "..."},
    {"role": "user", "content": "问题1"},
    {"role": "assistant", "content": "回答1"},
    {"role": "user", "content": "追问2"},
    {"role": "assistant", "content": "回答2"}
  ]
}

注意 metadata 字段的设计:source(来源溯源)、task_type(任务类型)、difficulty(难度)、quality_score(质量分)这四个字段是后续数据选择(见 18.6 节)与混合配比(见 18.9 节)的基础。没有 metadata 的数据集在后续管线中是”黑盒”,只能整体使用,无法按轴切片调度。

18.1.2 chat-template 与 loss mask

不同模型族有不同的 chat-template,训练前必须转换:

# Llama-3 chat-template
def format_llama3(messages):
    text = "<|begin_of_text|>"
    for m in messages:
        text += f"<|start_header_id|>{m['role']}<|end_header_id|>"
        text += f"{m['content']}<|eot_id|>"
    return text

# Qwen chat-template
def format_qwen(messages):
    text = ""
    for m in messages:
        text += f"<|im_start|>{m['role']}\n{m['content']}<|im_end|>\n"
    return text

loss mask 是关键:loss 只在 assistant 部分计算,user/system 部分的 token 不参与梯度。

# loss mask 计算伪代码
def compute_loss_mask(input_ids, template):
    masks = []
    for token_pos in range(len(input_ids)):
        if token_pos in assistant_region:
            masks.append(1)
        else:
            masks.append(0)
    return masks

踩坑:忘了做 loss mask,模型会学会生成 user prompt,在推理时陷入”自问自答”死循环。这是新手做 SFT 最常见的翻车点之一——训练 loss 一切正常,推理时模型却不停自说自话。

从更深层看,loss mask 决定了”SFT 到底在训练什么”。只对 assistant 部分计损失,意味着我们训练的是条件分布 P(assistant | user),而不是联合分布 P(user, assistant)。后文 18.7.5 节会进一步讨论:即使 loss mask 正确,标准交叉熵损失本身也可能引入多样性坍缩——loss mask 解决的是”学什么”,目标函数设计解决的是”怎么学”。

18.1.3 Pack 边界处理

为提升 GPU 利用率,工业训练常用 Pack——把多条短样本拼接到一个长序列。但必须正确处理 Pack 边界:

# 错误做法:直接拼接,attention 跨样本
packed = sample1 + sample2 + sample3

# 正确做法:用 attention_mask 隔离
packed = sample1 + [EOS_PAD] + sample2 + [EOS_PAD] + sample3
attention_mask = block_diagonal(mask1, mask2, mask3)

Llama-3 引入了 <|eot_id|> 作为样本边界,配合 FlashAttention 的 document-aware mask 实现正确 Pack。

如果 attention 跨越样本边界,模型会”看到”前一条样本的完整内容来预测后一条样本的开头,这既污染了训练信号,也可能导致跨样本的信息泄漏。document-aware mask 的本质是把注意力矩阵限制为块对角结构——每条样本只能看到自己内部的 token。


18.2 人工标注与种子数据

在进入大规模自动合成之前,必须先讲人工标注。原因很简单:所有合成方法(Self-Instruct、Evol-Instruct、Magpie)都需要一个”质量锚”——定义什么是好的回复。这个锚只能来自精心设计的人工种子数据。

18.2.1 种子数据的重要性

SFT 数据构造的第一步不是”大规模标注”,而是精心设计 100-500 条种子数据。

种子数据的作用:

  1. 定义标准:什么样的回复是”好的”;
  2. 指导风格:模型应该用什么语气和格式;
  3. 覆盖核心场景:最重要的任务类型都有范例。
"""
种子数据设计框架
"""

SEED_DATA_TEMPLATE = {
    "task_categories": [
        # 每个类别至少 5-10 条种子数据
        {
            "category": "知识问答",
            "count": 20,
            "difficulty_distribution": {"easy": 5, "medium": 10, "hard": 5},
            "requirements": [
                "回答必须结构化(使用标题和列表)",
                "涉及不确定的知识点时要说明",
                "适当给出参考来源",
            ],
        },
        {
            "category": "代码生成",
            "count": 30,
            "difficulty_distribution": {"easy": 5, "medium": 15, "hard": 10},
            "requirements": [
                "代码必须包含注释",
                "给出运行示例",
                "说明时间/空间复杂度(如适用)",
            ],
        },
        {
            "category": "创意写作",
            "count": 15,
            "difficulty_distribution": {"easy": 5, "medium": 5, "hard": 5},
            "requirements": [
                "风格多样化",
                "避免陈词滥调",
                "根据用户要求调整语气",
            ],
        },
        {
            "category": "数学推理",
            "count": 20,
            "difficulty_distribution": {"easy": 5, "medium": 10, "hard": 5},
            "requirements": [
                "必须展示完整推理步骤",
                "关键步骤要有解释",
                "最终答案明确标出",
            ],
        },
        {
            "category": "拒绝与安全",
            "count": 15,
            "requirements": [
                "有害请求要明确但礼貌地拒绝",
                "说明拒绝的原因",
                "如可能,提供替代建议",
            ],
        },
    ],
}

18.2.2 从任务列表到世界知识树

上述种子数据模板采用的是”任务类别 + 难度分布”的设计范式——通过枚举任务类型来保证覆盖面。这是一种直觉上自然的方案,但也有局限:类别是人工定义的,覆盖的粒度取决于设计者的经验,且难以保证层级间的系统性和完整性。

Condor(上海 AI Lab / 清华, 2025-01)提出了另一种种子设计范式——知识驱动的世界知识树。其核心思想是用层级化的知识标签体系替代扁平的任务列表,从知识本身的结构出发保证种子的多样性:

Self-Instruct 种子设计(任务驱动):
  种子任务列表 → 随机采样 → 生成新任务
  ✗ 覆盖粒度取决于人工定义的类别
  ✗ 缺乏层级结构,容易遗漏子领域

Condor 世界知识树(知识驱动):
  根节点(宏观领域)
    └─ 一级分支(子领域)
        └─ 二级分支(具体主题)
            └─ 叶节点(具体知识点)
  ✓ 层级化标签体系天然保证覆盖完整性
  ✓ 从根到叶的遍历确保无盲区

Condor 的世界知识树有三个关键设计要素:

  1. 层级化标签体系:从根到叶的树状结构确保每个知识分支都有代表——如果某个叶节点没有对应种子数据,就会被自动识别为覆盖缺口。这比人工枚举任务类别更系统化。
  2. 动态更新:知识树不是静态的——新增知识领域时,只需在对应分支添加叶节点,而不需要重新设计整个种子集。这对于需要保持时效性的领域(如科技、政策)尤其重要。
  3. 自我反思优化:生成种子数据后,模型会反思检查生成质量(是否存在幻觉、是否与知识标签对齐),通过迭代优化提升种子质量。

对比 Self-Instruct 的种子任务列表方式,世界知识树的优势在于自上而下的系统性覆盖而非自下而上的随机扩展。实践中,两种范式可以互补:用知识树定义覆盖框架,再用 Self-Instruct 的思路在每个叶节点附近扩展具体指令。

18.2.3 标注指南的撰写

标注指南(Annotation Guidelines)是 SFT 数据质量的基石。一份好的标注指南应该包含:

标注指南结构(模板):

第 1 部分:总体原则
  - 回答的核心价值观(有帮助、准确、无害)
  - 回答的风格要求(专业但友好)
  - 格式规范(何时用 markdown、何时用纯文本)

第 2 部分:任务类型指南
  - 知识问答:如何组织回答结构
  - 代码:代码规范、注释要求、测试用例
  - 创作:创意自由度、长度建议
  - 推理:步骤展示要求

第 3 部分:质量标准
  - 评分标准(1-5 分的定义)
  - 好坏样本对比(至少 20 组)
  - 常见错误清单

第 4 部分:边界情况处理
  - 不确定时怎么办
  - 主观问题如何回答
  - 安全敏感话题的处理
  - 多语言混合的处理

第 4 部分”边界情况处理”在实践中最容易被忽略,也最容易出问题——标注员的分歧几乎全部集中在边界情况上。指南写得多详细都不为过。

18.2.4 标注一致性的保证

"""
标注一致性评估
"""

from typing import List, Dict
import numpy as np

def compute_inter_annotator_agreement(
    annotations: Dict[str, List[int]],  # {annotator_name: [ratings]}
    method: str = "cohens_kappa"
) -> dict:
    """
    计算标注员间一致性

    annotations: 每个标注员对同一批样本的评分
    """
    annotators = list(annotations.keys())

    if method == "cohens_kappa" and len(annotators) == 2:
        ratings_a = np.array(annotations[annotators[0]])
        ratings_b = np.array(annotations[annotators[1]])

        # 计算观察到的一致率
        observed_agreement = np.mean(ratings_a == ratings_b)

        # 计算期望的偶然一致率
        categories = set(ratings_a) | set(ratings_b)
        expected_agreement = 0
        for cat in categories:
            p_a = np.mean(ratings_a == cat)
            p_b = np.mean(ratings_b == cat)
            expected_agreement += p_a * p_b

        # Cohen's Kappa
        kappa = (observed_agreement - expected_agreement) / (1 - expected_agreement)

        interpretation = (
            "几乎完全一致" if kappa > 0.80 else
            "高度一致" if kappa > 0.60 else
            "中等一致" if kappa > 0.40 else
            "一致性不足" if kappa > 0.20 else
            "几乎没有一致性"
        )

        return {
            "kappa": round(kappa, 3),
            "observed_agreement": round(observed_agreement, 3),
            "interpretation": interpretation,
            "sample_size": len(ratings_a),
        }

    return {"error": "method not implemented"}

一致性不达标时的处理流程:

Kappa < 0.4(一致性不足)
  → 暂停标注
  → 分析分歧点:哪些类型的样本分歧最大?
  → 修改标注指南,增加边界情况的说明
  → 举办校准会议(calibration session)
  → 重新标注试点样本
  → Kappa > 0.6 后才能继续正式标注

一致性是标注质量的前置门槛:Kappa 不达标的标注产出的数据,无论后续清洗管线多强,都难以挽救——因为噪声是系统性的(指南缺陷导致),不是随机性的。

18.2.5 LLM 辅助标注:从纯人工到人机协同

纯人工标注质量高但成本也高——标注一条高质量 SFT 数据的时间可能在 15-60 分钟。随着 LLM 能力的提升,一个自然的问题是:能否让 LLM 承担部分标注工作?

直接使用 LLM 标注存在质量风险。ACT(Annotation with Critical Thinking)(NeurIPS 2025)的实验表明,纯 LLM 标注数据训练会导致最高 10.15% 的准确率下降。ACT 提出的解决方案是让 LLM 同时担任标注员和审查员——先标注,再批判性审查自己的标注,识别并修正潜在错误。这一”双角色”管线将性能差距缩小到不到 2%。

一个可行的人机协同标注流程:

阶段 1:人工标注种子数据(100-500 条)
  → 建立质量标准和标注规范
  → 这批数据是"标准答案"

阶段 2:LLM + ACT 管线批量生成
  → LLM 按标注指南生成回复
  → 同一 LLM 或更强模型进行批判性审查
  → 标记低置信度样本

阶段 3:人工抽样审核(10-20%)
  → 重点检查低置信度样本
  → 校正系统性偏差
  → 更新标注指南

需要注意的是,ACT 的实验主要在多模态场景验证,文本 SFT 场景的迁移效果需要更多确认。但核心思路——“LLM 自我审查”比”LLM 直接标注”更可靠——具有普遍的指导意义。

这一三阶段流程与后文 18.3 节的指令合成、18.6 节的数据选择形成呼应:人工种子定义标准(18.2),合成方法放大规模(18.3),选择方法浓缩精华(18.6),人工抽检闭环质量(本节阶段 3)。


18.3 指令合成谱系:从 Self-Instruct 到 Magpie

有了种子数据,下一步是解决”规模”问题。指令合成是这个领域的核心战场,方法论经历了三代演进:Self-Instruct(种子驱动)→ Evol-Instruct(进化驱动)→ Magpie(零种子)。本节沿这条主线展开,并补充 2025-2026 年的前沿进展。

18.3.1 Self-Instruct:种子驱动的开山之作

Self-Instruct(Wang et al., 2022)是 SFT 数据合成的开山之作,用 175 个种子指令扩展到 52K 数据。

算法流程:

1. 准备 175 个种子指令(人工编写,覆盖 35 类任务)
2. 从种子池随机采样 8 条作为 in-context examples
3. 让 LLM 生成新指令
4. 过滤:去重 / 长度 / 启发式规则
5. 将新指令加入种子池
6. 对每个指令生成响应
7. 重复 2-6 直到达到目标规模

关键参数:

  • 种子数:175
  • 每轮 in-context 采样:8 条
  • 单次生成 temperature:0.7
  • 去重阈值:ROUGE-L < 0.7
  • 最终规模:52K

多样性饱和问题

Self-Instruct 的核心局限是”多样性饱和”——大约 30K-50K 后,新生成指令与已有指令的 ROUGE-L 重叠度超过 0.7,被过滤掉的比例从 20% 上升到 60%。

迭代次数 | 累计指令数 | 通过率
1        | 1K        | 95%
5        | 8K        | 80%
10       | 25K       | 60%
20       | 45K       | 35%
30       | 52K       | 10% (饱和)

根本原因:种子池的多样性上限决定生成数据的多样性上限。175 种子的任务覆盖度有限,再多迭代也是”在已知空间内打转”。

2026 年的 KITE 研究(arXiv:2607.17043, Xiaonan Luo 等, 2026-07-19)进一步揭示了更深层的问题:迭代合成中的退化不仅是”多样性饱和”,更是能力极化(Capability Polarization)——合成数据会强化模型已有的强能力,同时进一步削弱弱能力。这解释了为什么简单的数据量增加无法解决问题:需要在知识边界上针对性地生成和筛选数据。KITE 用失败引导生成 + Kernel Boundary Uncertainty 筛选知识边界上的数据,为解决能力极化提供了新方向。

历史地位

Self-Instruct 第一次证明了”用 LLM 合成 SFT 数据”可行,奠定了后续所有合成方法的基础。Alpaca、Vicuna、WizardLM 早期版本都基于 Self-Instruct 范式。

18.3.2 CoT-Self-Instruct:先思考再出题

传统 Self-Instruct 的一个局限是”随机采样→直接生成”的单步流程——从种子集中随机抽样示例,然后直接生成新指令,缺乏对已有指令覆盖范围的分析,导致生成的指令多样性受限。CoT-Self-Instruct(Meta, 2025)在生成步骤前引入了一个推理与规划阶段:

CoT-Self-Instruct 升级流程:

  种子指令集
         ↓
  采样示例
         ↓
  【新增】CoT 推理:分析已有指令的特征、识别覆盖缺口
         ↓
  定向生成:填补缺口的新指令(而非随机生成)
         ↓
  生成输入/输出对
         ↓
  过滤 + 迭代

核心思想是”先思考再出题”——让模型在生成新指令之前,先推理分析种子任务的能力维度和覆盖盲区,然后有针对性地生成填补空白的指令。实验表明,CoT-Self-Instruct 生成的数据在推理基准上达到 57.2% 的平均准确率,相比标准 Self-Instruct 的 42.7% 提升显著,说明指令生成阶段的”思考质量”直接决定了合成数据的多样性和有效性。

这与 Condor 的世界知识树(18.2.2 节)殊途同归:一个从知识结构出发保证覆盖,一个从推理分析出发填补缺口,都是在对抗”随机生成的盲区”。

18.3.3 迭代合成的安全边界:模型崩溃防护

Self-Instruct 的核心是迭代合成——用模型生成数据,再用这些数据训练模型(或其后继版本),再用训练后的模型生成新数据。这个闭环有一个系统性风险:模型崩溃(model collapse)——当合成数据在分布的尾部缺乏多样性时,迭代训练会导致模型逐步丧失对低频但重要模式的覆盖。

Escaping Model Collapse(ICLR 2026)给出了一个重要的理论保证:通过外部验证器注入信息,合成数据迭代训练可以证明不会导致模型崩溃。关键定理的核心条件是验证器必须是”外部”的——不能由生成模型自身充当,必须引入独立的信息源(人类评审、更强的模型、或可执行验证器)。

这对 Self-Instruct 的实践含义很直接:流程中的”过滤”步骤不只是质量优化——它是防止模型崩溃的数学必要条件。具体而言:

  • 每轮迭代后应使用独立评估器验证数据质量,而非仅靠生成模型自身的去重和格式检查;
  • 可执行验证(代码通过测试、数学答案可验证)天然满足”外部信息注入”条件,因此代码和数学领域的迭代合成相对安全;
  • 开放域指令的迭代合成风险最高,建议限制迭代轮数或引入人类抽样审核。

18.3.4 Evol-Instruct:进化驱动

Evol-Instruct(WizardLM, Xu et al., 2023)针对 Self-Instruct 的”多样性饱和”问题,提出”进化驱动”——不停对已有指令做变换,生成更复杂、更具体的指令。

五种进化算子

1. 加约束(Constraint)
   原: "写一个 Python 函数"
   进化: "写一个 Python 函数,要求时间复杂度 O(n),不使用额外空间"

2. 加深(Deepening)
   原: "解释什么是递归"
   进化: "解释什么是尾递归,以及为什么 Python 不支持尾递归优化"

3. 具象化(Concretizing)
   原: "设计一个推荐系统"
   进化: "为一个 100 万用户的电商网站设计推荐系统,冷启动阶段如何处理"

4. 增加推理步(Reasoning Step)
   原: "计算圆的面积"
   进化: "给定半径 r=5,先用积分推导面积公式,再计算具体值"

5. 复杂输入(Input Augmentation)
   原: "总结这篇文章"
   进化: "总结这篇 5000 字文章,要求包含 3 个关键论点 + 2 个反例"

用代码实现时,可以把这些算子组织为策略字典:

"""
Evol-Instruct 的演化策略
"""

EVOLUTION_STRATEGIES = {
    # 深度演化(增加复杂度)
    "add_constraints": {
        "prompt": "请在以下指令中增加一个约束条件,使其更具挑战性:\n{instruction}",
        "example": "写一首诗 → 写一首藏头诗,首字连起来是'人工智能'",
    },
    "deepen_reasoning": {
        "prompt": "请增加推理步骤的要求,使回答需要更深入的分析:\n{instruction}",
        "example": "比较 A 和 B → 从性能、成本、可维护性三个维度比较 A 和 B,给出量化分析",
    },
    "concretize": {
        "prompt": "请将以下抽象指令具体化,增加具体的场景和参数:\n{instruction}",
        "example": "写排序算法 → 实现一个能处理百万级数据的外部排序算法",
    },
    "increase_steps": {
        "prompt": "请将以下指令扩展为一个多步骤任务:\n{instruction}",
        "example": "分析数据 → 先清洗数据,然后做 EDA,最后建模并评估",
    },

    # 广度演化(增加多样性)
    "topic_shift": {
        "prompt": "请创建一个类似但领域完全不同的指令:\n{instruction}",
        "example": "写 Python 排序 → 用 SQL 优化一个慢查询",
    },
    "format_change": {
        "prompt": "请改变回答的格式要求:\n{instruction}",
        "example": "解释概念(文字)→ 用表格对比来解释概念",
    },
}

def evolve_instruction(
    instruction: str,
    strategy: str,
    model_api,  # LLM API
    max_attempts: int = 3,
) -> str:
    """
    对一条指令进行演化
    """
    prompt_template = EVOLUTION_STRATEGIES[strategy]["prompt"]
    prompt = prompt_template.format(instruction=instruction)

    for attempt in range(max_attempts):
        evolved = model_api.generate(prompt, temperature=0.7)

        # 质量检查
        if len(evolved) > len(instruction) * 0.5:  # 不能太短
            if evolved != instruction:  # 不能和原始一样
                return evolved

    return None  # 演化失败

双向进化

Evol-Instruct 实际做双向进化:

  • 深度进化(Evol-Instruct):让指令更复杂
  • 广度进化(Evol-Instruct-Breadth):在同级生成不同主题的指令

最终 WizardLM 数据集包含 250K 指令,深度进化 90%,广度进化 10%。

进化终止条件

进化不能无限进行——指令会变得过于复杂甚至不可解。WizardLM 的经验:

  • 单条指令最多进化 3 次
  • 进化后长度超过 500 tokens 终止
  • 检测到”无法回答”标记时回退

效果

在 Llama-7B 上 SFT 后,WizardLM 在 AlpacaEval 上胜率比 Self-Instruct 高 15%。核心原因:进化算子引入了”难度梯度”,模型学会了解决复杂问题。

18.3.5 Tree-of-Evolution:从线性到树结构

Evol-Instruct 有两个内在局限:单向合成(一次只能选择一个演化方向)和随机驱动(缺乏明确的优化目标来判断哪个方向最好)。当种子指令通过 add_constraints 演化后,如果效果不佳,无法回退尝试 deepen_reasoning——演化是不可逆的。

Tree-of-Evolution(ACL 2025)将指令演化从线性流程升级为树结构多路径探索:

线性 Evol-Instruct:
  种子指令 → 约束 → 约束+深化 → ...(一条路走到底)

Tree-of-Evolution:
  种子指令 ─┬─ 约束 ─┬─ 约束+深化 ★
            │        └─ 约束+场景化
            ├─ 深化 ─── 深化+多步
            └─ 场景化 ─ 场景化+约束
  (★ = 优化函数选择的最优路径)

每个节点是一个指令变体,分支代表不同的演化方向,用目标函数(而非随机选择)评估并选择最优路径。这种方法在代码指令合成场景特别有效——因为代码有可执行验证的天然优势,可以用通过率作为优化目标。实验表明,仅用 75K 训练数据即可达到 SOTA 性能,说明结构化探索比大量随机演化更高效。

18.3.6 自进化合成范式:Evolutionary Data Making 与 LSE

Tree-of-Evolution 把树结构用于指令本身的演化。2026 年的两个新工作将”进化”思想进一步推广到采样策略的进化和自进化作为可学习 RL 技能。

Evolutionary Data Making(Wafer, 2026)

Evolutionary Data Making 的核心洞察是:进化的目标不是数据本身,而是”基因”——即采样数据的策略。

Evolutionary Data Making 的树结构:

树节点 = "基因"(采样策略)
  ├── 深度优先利用:深化有希望的基因组
  └── 广度探索:平行分支探索不同数据空间区域

关键设计:
  ├── 进化单元是策略(基因),不是数据
  ├── 树结构同时支持深度优先 + 广度探索
  ├── 积累的知识存储在树结构本身
  │     └── 历史哪些区域肥沃
  │     └── 哪些原则失败
  │     └── 跨域嫁接带来惊喜
  └── 树可随时恢复——更新 constitution、添加新数据、上采样有价值的子区域

关键实验结果(Qwen3-Embedding-0.6B 训练):

配置训练对数NDCG@10MRRRecall
evo-3.1(进化采样)38K0.49160.5300.661
mnr-v2.1(静态基线)82K0.35830.3640.529
相对提升-54% 数据+37%+45%+25%

这一结果再次印证了本章的主旋律——数据少而精优于多而杂:进化采样用 54% 更少的数据换来了 37% 的检索质量提升。

LSE(Learning to Self-Evolve, 2026)

LSE(arXiv:2603.18620,Xiaoyin Chen, Canwen Xu, Yite Wang, Boyi Liu, Zhewei Yao, Yuxiong He,Mila / University of Montreal / Snowflake,2026-03-19)将”测试时自进化”作为可学习的 RL 问题:

LSE 框架:

测试时通过 tree-guided evolution loop 支持探索和回溯
  ↓
LSE 用 improvement-based reward 显式训练自进化策略
  └── reward = (后编辑性能 - 前编辑性能)
  └── UCB 选择 + 行为模型 + 自进化策略

关键结果:4B 模型经 LSE 训练后超越 GPT-5、Claude Sonnet 4.5 等更大模型,且优于 GEPA、TextGrad 等优化方法——表明”自进化”是可以被训练为独立技能的。

意义:揭示静态合成范式的根本性局限——“自进化”不仅是工程技巧,而是可被显式训练的 RL 技能。

静态合成 vs 自进化合成决策表

维度静态合成自进化合成
进化对象数据本身采样策略(基因)
训练方式一次性合成迭代 + 元优化
数据量大(>50K)显著降低(-54%)
优化目标单一质量多目标(UCB 选择)
适用阶段初始数据池持续优化
工程复杂度中高(需要元学习)

18.3.7 Autodata:AI 数据科学家的统一框架

本节前面分别介绍了 Self-Instruct、CoT-Self-Instruct、Evol-Instruct、Tree-of-Evolution 等多种合成方法。2026 年 Meta FAIR 的 Autodata(Ilia Kulikov et al., 2026-06-24,arXiv:2606.25996v1)给出了一个统一泛化框架——将所有这些方法整合为”AI 数据科学家”智能体。

Autodata 统一框架:

一个作为"数据科学家"的智能体负责构建与整理数据
  ├── 初始数据生成阶段
  ├── 数据审查/分析阶段(类似"人工审查")
  ├── 评估表现、总结经验
  └── 迭代生成更优数据方案

关键特性:
  └── 对数据科学家智能体本身进行元优化可带来更显著性能提升

覆盖的任务:

  • 计算机科学研究任务
  • 法律推理任务
  • 数学对象推理任务

与现有方法的关系:

方法Autodata 中的对应
Self-Instruct数据科学家智能体采用 Self-Instruct 模式
CoT-Self-Instruct加入”先思考再出题”推理阶段
Grounded Self-Instruct加入 grounding 信号
self-challenging数据审查阶段主动挑战数据质量
Tree-of-Evolution元优化阶段采用树结构探索

实践决策树:

何时升级到 Autodata:

阶段 1:MVP 阶段
  └── 用 Self-Instruct 快速生成初始数据
  └── 验证基线 SFT 效果
  └── 时间:1-2 周

阶段 2:质量提升阶段
  └── 加入 CoT-Self-Instruct / Grounded Self-Instruct
  └── 用拒绝采样过滤
  └── 时间:2-4 周

阶段 3:规模化阶段
  └── 升级到 Autodata
  └── 启动"数据科学家"智能体的元优化循环
  └── 时间:4+ 周,规模化后才有 ROI

18.3.8 Magpie:零种子突破

核心洞察

Magpie(Xu et al., 2024)的洞察极其简单却深刻:对齐过的模型本身就是优秀的 prompt 生成器。

直接让模型从空 prompt 开始生成:

# Magpie 核心
def magpie_generate(model, n_samples):
    results = []
    for _ in range(n_samples):
        # 空 prompt,直接触发 user 角色
        pre_query = "<|im_start|>user\n"
        response = model.generate(
            pre_query,
            temperature=1.0,  # 关键:高温
            top_p=0.95,
            max_tokens=2048,
            stop=["<|im_end|>"]
        )
        # response 是 "user prompt + assistant response"
        user_msg, assistant_msg = parse(response)
        results.append((user_msg, assistant_msg))
    return results

关键参数

  • temperature:1.0(必须高,否则多样性坍塌)
  • top_p:0.95
  • 采样数量:100K-1M
  • 后过滤:去重(4-gram 重叠率 < 0.3)+ 质量过滤

多样性对比

方法4-gram 多样性InsTag 覆盖率任务类型数
Self-Instruct0.4232055
Evol-Instruct0.5558075
Magpie0.711240120+

Magpie 的多样性显著优于前两者,根本原因是它不依赖种子池,直接采样自模型的”指令分布”。

Magpie 的局限

  1. 依赖强模型:用弱模型做 Magpie 生成的 prompt 质量差
  2. 偏置明显:模型偏好生成”它擅长回答”的 prompt,导致能力分布失衡
  3. 安全数据缺失:模型不会主动生成越狱 prompt,需要补充

其中第 2 点与 KITE 的”能力极化”发现(18.3.1 节)互为印证——模型自生成会强化强能力、削弱弱能力,这正是 Magpie 偏置的理论根源。

Magpie 的工业实践

实际使用 Magpie 的推荐流程:

1. 用 Llama-3-70B / Qwen-2.5-72B 做生成器
2. 生成 1M 原始数据
3. 去重(MinHash + 0.7 阈值)→ 700K
4. 质量过滤(RM 评分 > 0.5)→ 400K
5. 多样性采样(Embedding 聚类,每簇 5 条)→ 200K
6. 与其他来源混合

注意这个流程的漏斗形状:1M → 700K → 400K → 200K,最终保留率仅 20%。大规模合成的常态就是”广撒网、层层筛”——生成阶段的丰沛多样性,正是为了让过滤管线有充足的挑选余地。

18.3.9 三大方法对比与工业组合

维度Self-InstructEvol-InstructMagpie
种子需求1751750
上限52K250K1M+
4-gram 多样性0.420.550.71
难度可控否是否
任务覆盖55 类75 类120+ 类
算力成本低中中
实现难度简单中等简单
推荐场景快速原型难度梯度大规模底盘

工业级组合:用 Magpie 生成 70% 底盘 + Evol-Instruct 补 20% 难度梯度 + 人工 10% 质量锚。

这个组合的逻辑:Magpie 解决”广度”(多样性最好的大规模底盘),Evol-Instruct 解决”深度”(难度梯度让模型学会解决复杂问题),人工种子解决”标准”(质量锚定义什么是好回复,同时为 LLM 辅助标注和审查提供参照,见 18.2.5 节)。三代方法不是替代关系,而是互补关系。


18.4 响应生成与拒绝采样

有了 prompt 之后,还要生成高质量响应。响应质量的决定因素有两个:生成策略(怎么生成)和筛选机制(怎么挑)。本节把这两件事放在一起讲——因为在实践中它们是一个整体:生成 N 个候选,然后选出最好的,这就是拒绝采样的本质。

18.4.1 四种主流响应生成策略

单次贪婪

response = model.generate(prompt, temperature=0, max_tokens=2048)

优点:成本低,可复现 缺点:质量方差大,对 prompt 敏感

采样投票

responses = [model.generate(prompt, temperature=0.7) for _ in range(5)]
best = select_by_consistency(responses)  # 选多数派

优点:质量稳定 缺点:成本 5 倍

Best-of-N + Reward Model

responses = [model.generate(prompt, temperature=0.7) for _ in range(8)]
scores = [reward_model(prompt, r) for r in responses]
best = responses[argmax(scores)]

优点:质量最高 缺点:依赖 RM,成本 N 倍

CoT 自反思

# 第一轮生成
initial = model.generate(prompt, temperature=0.7)
# 第二轮反思
critique = model.generate(f"反思这个回答的问题:{initial}")
# 第三轮修正
final = model.generate(f"基于反思改进:{critique}\n原回答:{initial}")

优点:推理类任务质量最高 缺点:成本 3 倍,速度慢

工业选择:通用任务用 Best-of-N+RM,推理任务用 CoT 自反思,快速实验用单次贪婪。

18.4.2 拒绝采样:核心思路与 GRAPE

拒绝采样是提升 SFT 数据质量的最有效方法之一:对同一个指令生成多个候选回复,然后选择最好的。

指令 → 生成 N 个回复(通常 N=4-16)
  ↓
对每个回复评分
  ↓
选择得分最高的回复
  ↓
(指令, 最佳回复)作为训练数据

不过,“最好的回复”并非一个客观概念。GRAPE(NeurIPS 2025)揭示了一个关键洞见:最佳回复不是绝对的——它取决于目标模型的预训练分布。对同一条指令的同一组候选回复,不同的基座模型有不同的最优选择。GRAPE 通过为每条指令选择与目标模型分布最匹配(而非人类评分最高)的回复,仅用 1/3 的数据和一半的训练 epoch,使 LLaMA-3.1-8B 超过 Tulu3-SFT 性能 3.5%,在平均性能上超出 4.5 倍数据量的基线 6.1%。

这意味着拒绝采样的核心逻辑需要一个补充维度:除了”选最好的”,还要考虑”选最适合目标模型的”。

18.4.3 评分函数的选择

评分方法优点缺点适用场景
规则评分快速、确定性只能评表面质量格式检查、长度控制
奖励模型综合质量评估有偏差通用质量评分
LLM-as-Judge灵活、可解释慢、贵复杂任务
自动验证精确只适用于可验证任务代码、数学
分布匹配模型定制化、数据高效需要目标模型的推理成本任何 SFT 数据选择场景

前四种方法隐含一个假设:存在一个”客观最优”的回复。分布匹配则打破这一假设——好回复是相对于目标模型而言的。这两种视角不矛盾:实践中可以先用前四种方法过滤掉明显低质量的候选,再在达标候选中用分布匹配选择最适合目标模型的回复。

"""
拒绝采样实现
"""

from typing import List, Tuple, Callable

def rejection_sampling(
    instruction: str,
    generator,             # 生成模型
    scorer: Callable,      # 评分函数
    n_samples: int = 8,    # 采样数量
    temperature: float = 0.8,
    min_score_threshold: float = 0.7,  # 最低可接受分数
) -> Tuple[str, float]:
    """
    拒绝采样生成最佳回复
    """
    candidates = []
    for _ in range(n_samples):
        response = generator.generate(instruction, temperature=temperature)
        score = scorer(instruction, response)
        candidates.append((response, score))

    # 按分数排序
    candidates.sort(key=lambda x: x[1], reverse=True)

    best_response, best_score = candidates[0]

    if best_score < min_score_threshold:
        return None, best_score  # 最佳候选也不达标

    return best_response, best_score

def batch_rejection_sampling(
    instructions: List[str],
    generator,
    scorer: Callable,
    n_samples: int = 8,
    temperature: float = 0.8,
    min_score_threshold: float = 0.7,
) -> dict:
    """
    批量拒绝采样
    """
    results = []
    stats = {"total": len(instructions), "passed": 0, "failed": 0}

    for inst in instructions:
        response, score = rejection_sampling(
            inst, generator, scorer,
            n_samples=n_samples,
            temperature=temperature,
            min_score_threshold=min_score_threshold,
        )
        if response is not None:
            results.append({"instruction": inst, "response": response, "score": score})
            stats["passed"] += 1
        else:
            stats["failed"] += 1

    stats["pass_rate"] = stats["passed"] / max(stats["total"], 1)
    stats["avg_score"] = (
        sum(r["score"] for r in results) / max(len(results), 1)
    )
    return {"data": results, "stats": stats}

18.4.4 采样温度与数量的权衡

温度多样性最佳候选质量推荐 N
0.6低稳定但可能不够好4-8
0.8中较好的平衡点8-16
1.0高有机会出现出色回复16-32
1.2很高有好有坏,需要更多采样32+

18.4.5 被拒绝的数据:丢弃还是回收

拒绝采样的 min_score_threshold 机制意味着不达标的候选会被完全丢弃。在传统范式中,这是理所当然的——质量不够,就不要。但 ENTP(2025)提出了一个不同的视角:被丢弃的低质量数据中可能包含有价值的信号(任务覆盖、领域知识、推理骨架等),问题出在噪声而非内容本身。

ENTP 的核心方法是符号净化 + 神经增强——先通过规则和符号操作去除回复中的噪声信息(冗余内容、格式混乱、逻辑跳跃),再将净化后的数据与高质量数据混合增强。实验表明,仅使用低质量数据经 ENTP 增强后,即可超越 13 种已有数据选择方法的表现。

这一发现值得关注但需要审慎对待——该工作尚未在顶会正式发表,其结论的普适性有待更多验证。不过核心思路有实践参考价值:拒绝采样的”废品”不必直接丢弃,可以进入一个修复管线作为候选数据源,尤其在数据稀缺的领域场景下。

18.4.6 用强模型生成数据训练弱模型:离策略挑战

这是当前最主流的 SFT 数据生成方式:用 GPT-4 / Claude 级别的模型生成训练数据,然后用这些数据训练更小的开源模型。

离策略学习的内在挑战

这种方法有一个容易被忽略的理论问题。从强化学习的视角看,SFT 本质上是离策略(off-policy)学习——训练数据来自”行为策略”(GPT-4 等强模型),但优化目标是不同的”学习策略”(你的目标模型)。两者之间的分布差距越大,训练的方差越大、效果越不稳定。

Mind the Gap(2025)将这一问题形式化:当行为策略和学习策略的分布差距较大时,标准 SFT 的梯度估计方差显著增加。论文提出的数据改写框架——主动将强模型生成的回复改写为更接近目标模型分布的版本——是缩小这一差距的可行方案。

这一发现与 GRAPE(见 18.4.2 节)形成呼应:为什么”适合目标模型的数据”比”客观最优的数据”更重要?因为跨模型数据生成本身就引入了分布偏移,选择与目标模型分布匹配的回复本质上是在缓解离策略学习的方差问题。

实践建议:用强模型生成数据时,不应只关注 Prompt 设计来提升”绝对质量”,还应考虑生成回复与目标模型能力范围的匹配度。如果目标模型是 7B 规模,让 GPT-4 生成过于复杂的推理链可能适得其反。

从”猜匹配度”到”改写分布”:projection sampling。上面的建议仍然依赖工程师凭经验估计”什么算匹配”。一条 2026 年的工作把这一步从启发式变成了可执行的一步:Finetuning with Sampling: SFT Learns Better Than You Think(Aayush Karan、Sitan Chen、Yilun Du,Harvard,2026-10-01)的做法是——不动目标函数、不改训练器,只在训练前对数据做一次改写。

它把目标定义为”在保留专家轨迹信息量的前提下,与 base model 分布最接近的那个分布”,然后用一条 MCMC 算法(Metropolis-Hastings,按 base model 的似然迭代重写轨迹)从这个分布里采样,因此叫projection sampling(信息投影采样)。改写后的数据再走一次完全普通的 SFT。代价是一次性的:MCMC 在训练前跑完,每条轨迹重采样约 kB/2kB/2 个 token、重复 NMCMCN_{\text{MCMC}} 次,论文另行估算了 token 开销;推理时不再有额外成本。作者在 Chemistry / Math / Medical 三类任务、Qwen 与 Olmo 两个模型族上的报告是:改写后的 SFT 在泛化与旧能力保留上至少持平、常常优于对应的 on-policy 后训练方法(含 OPSD 这类 on-policy 蒸馏),pass@k 曲线严格高于 OPSD 与 base model。

这条路线值得单独记一笔,因为它同时划出了 RL 的硬边界:RL 依赖模型自己能采样出正确轨迹,当模型连一条正确 rollout 都采不出来时,奖励方差为零、学习信号为零——而这恰恰是”引入新能力”最需要的那片样本区,也正是 SFT 的主场。这与本书 Ch21 讨论的 pass@8 = 0 零信号问题是同一现象的两个切面,可对照阅读。

它与本章已有的两个落点是同一原理的第三处:§18.4.2 的 GRAPE 在生成端选与目标模型分布匹配的回复,§18.6.1 的 IFD 在筛选端用 PPL 测模型与数据的兼容性,projection sampling 则在训练前直接改写分布。三者可以串成”生成端选匹配 → 筛选端测兼容 → 训练前改写分布”的完整闭环。

⚠️ 该工作为 2026-10 的预印本,尚未见顶会收录,上述结论均取自作者自陈的实验表,本书未逐条复核其主表数值。

Prompt 设计对质量的巨大影响

"""
高质量 SFT 数据生成的 Prompt 设计
"""

# ❌ 差的 prompt(太简单,生成质量不可控)
bad_prompt = "请为以下指令生成一个高质量的回答:{instruction}"

# ✅ 好的 prompt(详细的质量要求)
good_prompt = """你是一个专业的 AI 助手。请为以下用户指令生成一个高质量的回答。

用户指令:{instruction}

要求:
1. 回答必须准确、完整、有帮助
2. 使用清晰的结构(标题、列表、代码块等)
3. 如果涉及代码,必须:
   - 包含注释
   - 给出完整的可运行示例
   - 说明关键设计决策
4. 如果涉及分析或推理,必须:
   - 展示推理过程
   - 考虑多个角度
   - 给出明确的结论
5. 长度:根据问题复杂度调整,简单问题简短回答,复杂问题详细展开
6. 如果你不确定某个事实,明确说明
7. 禁止编造不存在的工具、库或 API

请直接给出回答(不要加"好的,我来回答"之类的前缀):"""

18.4.7 合成数据的工程化过滤管线

拒绝采样解决的是”从多个候选中选最好的”问题,但在大规模合成数据生产中,还需要一个前置的系统性过滤管线来去除各类低质量数据。实践经验表明,一条工程化的多阶段过滤管线比单一的复杂评分函数更有效——每个阶段专注解决一类问题,既降低了每步的复杂度,也便于定位和调试质量瓶颈。

一个经过实践验证的 5-stage filter pipeline 参考如下:

Stage 1: 精确去重(Exact Dedup)
  → MinHash / SimHash 删除完全相同的样本
  → 去除复制粘贴和模板重复
  → 保留率:通常 85-95%

Stage 2: 语义去重(Semantic Dedup)
  → Embedding 余弦相似度 > 阈值的样本只保留一条
  → 去除改写级重复(同义不同字)
  → 保留率:通常 70-85%

Stage 3: 长度过滤(Length Filter)
  → 去除过短(< 50 tokens)和过长(> 4096 tokens)的样本
  → 极端长度通常意味着质量异常
  → 保留率:通常 90-98%

Stage 4: 语言过滤(Language Filter)
  → 用 fasttext / langdetect 检测语言
  → 目标语言(如中文)纯度过低的样本去除
  → 保留率:视数据源而异

Stage 5: 质量深度过滤(IFD + LLM Judge)
  → IFD 筛选模型-数据兼容性(详见 18.6.1 节)
  → LLM Judge 对边界样本做二次判定
  → 保留率:通常 60-80%
"""
5-stage filter pipeline 实现
"""

def filter_pipeline(
    samples: list,
    dedup_threshold: float = 0.95,
    length_range: tuple = (50, 4096),
    target_language: str = "zh",
    language_threshold: float = 0.7,
    ifd_range: tuple = (5, 50),  # PPL 学习区
) -> dict:
    """5-stage 合成数据过滤管线"""
    stats = {"total": len(samples)}

    # Stage 1: 精确去重
    seen = set()
    after_s1 = []
    for s in samples:
        key = hash(s["response"])
        if key not in seen:
            seen.add(key)
            after_s1.append(s)
    stats["after_dedup"] = len(after_s1)

    # Stage 2: 语义去重
    # (需要 embedding 模型,此处伪代码)
    after_s2 = semantic_dedup(after_s1, threshold=dedup_threshold)
    stats["after_semantic_dedup"] = len(after_s2)

    # Stage 3: 长度过滤
    after_s3 = [
        s for s in after_s2
        if length_range[0] <= len(s["response"].split()) <= length_range[1]
    ]
    stats["after_length_filter"] = len(after_s3)

    # Stage 4: 语言过滤
    after_s4 = [
        s for s in after_s3
        if detect_language(s["response"]) == target_language
        or detect_language_confidence(s["response"], target_language) >= language_threshold
    ]
    stats["after_language_filter"] = len(after_s4)

    # Stage 5: IFD + LLM Judge
    after_s5 = [
        s for s in after_s4
        if ifd_range[0] <= compute_ppl(s) <= ifd_range[1]
    ]
    stats["after_ifd_filter"] = len(after_s5)

    stats["final_retention_rate"] = len(after_s5) / max(stats["total"], 1)
    return {"data": after_s5, "stats": stats}

真实数据底线与早期崩溃检测

在合成数据管线中有一个容易忽略的工程经验:合成数据不应完全替代真实数据。实践经验表明,训练数据中至少应保留 25% 的高质量真实数据作为”锚点”——纯合成数据训练的模型容易产生系统性偏差(如过度格式化、缺乏口语化表达),真实数据起到分布矫正的作用。

此外,大规模合成数据训练中一个实用的早期检测方法是 MMLU checkpoint 追踪:在训练过程中每 500-1000 step 评估一次 MMLU 得分。如果 MMLU 得分出现持续下降(而非正常的波动),说明数据质量可能导致了训练崩溃的前兆——模型正在丧失预训练阶段获得的世界知识。这种退化在训练早期就可被捕获,远早于最终评估。

MMLU checkpoint 追踪模式:

  Step 0:     MMLU = 62.3 (基线)
  Step 500:   MMLU = 63.1 ✅ 正常上升
  Step 1000:  MMLU = 62.8 ✅ 轻微波动,正常
  Step 1500:  MMLU = 60.5 ⚠️ 显著下降 → 立即检查数据质量
  Step 2000:  MMLU = 57.2 🔴 崩溃趋势 → 停止训练

  判断标准:连续 3 个 checkpoint 下降 > 1% → 触发数据审计

这一方法之所以有效,是因为 MMLU 覆盖广泛的知识领域,对世界知识的退化高度敏感——而合成数据质量不足最直接的影响正是侵蚀预训练知识的内部表示。

18.4.8 数据复用与重写:Kimi K2 Rephrasing

前面讨论的”过滤 → 重写”流程主要解决”数据质量参差”问题。但工业实践中,高质量 token 本身就是稀缺资源——一旦获取了高质量数据(如人工精标、长 CoT 推理链),如何榨干每个 token 的性能潜力?Kimi K2 的 Rephrasing Pipeline 给出了一种工业实践。

核心洞察:

传统 SFT 训练的困境:
  └── 高质量数据 1 epoch:模型还没完全学到,训练结束
  └── 高质量数据多 epoch:模型过拟合,质量变差

Kimi K2 的解法:
  └── 通过重写(Rephrasing)让同一个 token 在不同时刻呈现不同形态
  └── 1 个高质量 token 可以榨出多倍性能
  └── 不显著过拟合的前提下放大高质量 token 容量

Kimi K2 Rephrasing 设计:

Knowledge Data Rephrasing(针对 Knowledge 和 Math 领域):

原始 token: "E=mc²"
  ├── 改写 1: "E = mc²,质能等价方程"
  ├── 改写 2: "质能等价方程可写作 E=mc²"
  ├── 改写 3: "爱因斯坦提出的质能等价: E = mc²"
  └── 改写 4: 解释变体...

目标:让模型学到"知识本身"而非"特定句式"

Token-efficient 视角:

传统视角:数据越多越好(token 总数)
  └── Kimi K2 视角:每个 token 的性能提升比 token 总数更重要
  └── 一个被重写多次的高质量 token > 多个普通 token

作为对比案例,DeepSeek R1 的 reasoning data 构造展示了另一条路径——用 RL 信号而非重写来放大高质量数据:

DeepSeek R1 的 reasoning data 构造细节:

1. 用 RL checkpoint 拒绝采样
     ├── 生成 response prompt
     ├── 生成 reasoning trajectories
     └── 保留正确推理的样本
2. CoT 过滤
     ├── 筛除语言混杂
     ├── 筛除过长(超过阈值)
     └── 筛除含代码块的 CoT
3. 每个 prompt 生成多个 response
     └── 只保留正确的
4. 最终数据规模
     └── 600K reasoning samples
     └── 200K non-reasoning samples

两条路线的对比很有启发:Kimi K2 用”重写”解决同一知识的多形态呈现(对抗过拟合),DeepSeek R1 用”拒绝采样 + 正确性过滤”保证推理链的可靠性(对抗噪声)。二者与本章 18.6 节的数据选择形成方法学闭环——Rephrasing 解决”数据不够多”,数据选择解决”哪些数据更值得用”。

18.4.9 规则生成合成数据:多跳推理的低成本路径

形式化验证是高质量数据的来源之一,但依赖 verifier 限制了适用场景。Learning from Synthetic Data Improves Multi-hop Reasoning 提出用规则生成合成数据训练 LLM 多跳推理——成本远低于人类/前沿 LLM/verifier。

关键发现:

反直觉实验结论:

  即便合成数据只包含虚构知识
  训练后模型在真实多跳 QA 基准上显著更好

  关键洞察:合成数据教会 LLM "组合知识"
  └── 基础且可泛化的推理技能
  └── 不必是真实知识——重要的是"组合模式"

与传统方法对比:

数据来源成本多跳推理能力可控性
人类标注高高中
前沿 LLM 生成中-高中-高低(依赖 prompt)
Verifier 验证中高中(依赖 verifier)
规则生成低中-高高(规则明确)

这一发现对训练数据选择有重要影响:合成数据的核心价值是”推理模式”而非”具体知识”。这与 18.3.3 节的模型崩溃防护形成有趣对照——开放域知识的迭代合成风险最高,但纯推理模式的规则合成却意外安全,因为模型学到的是”怎么组合”而不是”组合出什么”。


18.5 质量标准与数据清洗

前面解决了”怎么造数据”,现在解决”什么是好数据”。质量标准是所有清洗与选择操作的前提——如果说不清什么是好样本,一切过滤都是盲目的。

18.5.1 什么是好的 SFT 样本

好样本的四个维度

维度 1: 指令清晰
  ✅ "用 Python 实现一个 LRU 缓存,要求线程安全,支持过期时间"
  ❌ "写个缓存"

维度 2: 回复准确
  ✅ 代码能正确运行,事实准确无误
  ❌ 代码有 bug,或引用不存在的 API

维度 3: 信息密度
  ✅ 每句话都传递有价值的信息
  ❌ 大量填充语("好的,让我来帮你...")和无关内容

维度 4: 格式规范
  ✅ 代码在代码块中,步骤用列表,关键词加粗
  ❌ 代码和文字混在一起,没有结构

案例分析:好样本 vs 差样本

指令:“解释什么是梯度消失问题,以及如何解决”

❌ 差样本:

好的!梯度消失问题是深度学习中很常见的一个问题哦~简单来说就是
当神经网络很深的时候梯度会变得很小很小然后就没法训练了。解决方
法有很多比如用ReLU激活函数或者用残差连接等等。希望这个回答对
你有帮助!

问题:口语化严重、信息密度低、没有结构、技术细节不足

✅ 好样本:

## 梯度消失问题

### 现象
在深层神经网络的反向传播中,梯度通过链式法则逐层相乘。当每层
的梯度值 < 1 时,经过 N 层后梯度指数级衰减到接近零,导致浅层
参数几乎无法更新。

### 数学本质
对于 L 层网络:∂Loss/∂W₁ = ∏ᵢ₌₁ᴸ (∂hᵢ/∂hᵢ₋₁) · ∂Loss/∂hₗ

当 |∂hᵢ/∂hᵢ₋₁| < 1 时,乘积 → 0

### 主要解决方案

| 方法 | 原理 | 典型应用 |
|------|------|---------|
| ReLU 激活函数 | 正区间梯度恒为 1 | CNN |
| 残差连接 | 梯度可以跳过层直接回传 | ResNet, Transformer |
| BatchNorm | 标准化中间表示,控制梯度范围 | 多数深度网络 |
| LSTM 门控 | 选择性保留/遗忘梯度 | RNN 序列模型 |
| 参数初始化 | Xavier/He 初始化保持方差稳定 | 所有网络 |

18.5.2 推理 SFT 数据的特殊质量要求

上述四维度是通用 SFT 数据的基线框架。随着 Long-CoT 和推理增强成为主流训练范式,推理 SFT 数据需要额外的质量维度。MCSQ(Multi-dimensional quantitative framework for evaluating distilled CoT SFT Data Quality, EMNLP 2025 Findings)提出了一个关键实证结论:结构胜于规模——结构化推理链的质量比数据量更重要。

推理 SFT 数据需要在通用四维度之上额外评估三个维度:

维度 5: 推理链完整性
  ✅ 每一步都有明确的前提和推导过程,无逻辑跳步
  ❌ 从问题直接跳到答案,或关键推导步骤缺失

维度 6: 步骤粒度合理性
  ✅ 每步推理恰好包含一个可验证的逻辑单元
  ❌ 一步内堆叠多个推理跳跃(过粗),或拆分为过于琐碎的子步骤(过细增加噪声)

维度 7: 中间结论可验证性
  ✅ 中间步骤的结论可以被独立验证(如数学表达式可以代入验证)
  ❌ 中间结论模糊不清,无法判断对错

这些维度的核心洞见在于:100 条结构完整、步骤粒度合理的推理链,可能优于 10,000 条结构残缺的推理样本。后续的实证研究也支持这一观点——在固定计算预算下,对 400 条精品推理数据训练 128 个 epoch(Nuremberg/Mistral/NVIDIA, 2026),在 AIME’24/‘25 和 GPQA 上显著优于对 51,200 条数据训练 1 个 epoch。这直接挑战了”更多数据总是更好”的直觉,也说明了推理数据质量评估的重要性。

18.5.3 推理数据质量的经济筛选

上述七维度评估依赖强推理模型或人工评估,成本较高。最新研究发现了一种低成本替代方案:仅用前 100 个 token 的 loss 信号即可可靠筛选推理数据(UCLA, 2026-06)。核心方法:

  1. 在预训练基座模型的随机扰动 checkpoint 上计算每条推理数据前 100-1k token 的 loss
  2. loss 模式相似的样本在微调中产生相似的梯度 → 可据此做多样性筛选
  3. 高 loss 样本对应高难度推理任务 → 可据此做难度筛选

优势:无需强推理模型筛选、token 效率提升 91%、性能比现有 baseline 高 1.7%。局限:目前仅在医疗推理和数学推理上验证,通用性待确认。

18.5.4 自动化质量检查管线

"""
SFT 数据质量检查管线
"""

from dataclasses import dataclass
from typing import List, Optional
import re

@dataclass
class QualityCheckResult:
    passed: bool
    score: float  # 0-1
    issues: List[str]

def check_sft_sample(
    instruction: str,
    response: str,
    language: str = "zh",
) -> QualityCheckResult:
    """
    SFT 样本的多维度质量检查
    """
    issues = []
    score = 1.0

    # === 格式检查 ===

    # 1. 代码块完整性
    code_block_opens = response.count("```")
    if code_block_opens % 2 != 0:
        issues.append("代码块未正确闭合")
        score -= 0.3

    # 2. Markdown 结构检查
    if len(response) > 500 and "##" not in response and "\n-" not in response:
        issues.append("长回复缺少结构化格式")
        score -= 0.1

    # === 内容检查 ===

    # 3. 空回复或过短回复
    if len(response.strip()) < 20:
        issues.append("回复过短")
        score -= 0.5

    # 4. 过长的前缀废话
    filler_patterns = [
        r"^(好的|当然|没问题|好呢)[!!,,。.]*\s*(我来|让我)",
        r"^(Sure|Of course|Absolutely)[!,.]",
    ]
    for pattern in filler_patterns:
        if re.match(pattern, response):
            issues.append("包含不必要的前缀填充语")
            score -= 0.05

    # 5. 重复内容检测
    sentences = [s.strip() for s in response.split("。") if len(s.strip()) > 10]
    if len(sentences) > 3:
        unique_ratio = len(set(sentences)) / len(sentences)
        if unique_ratio < 0.7:
            issues.append(f"内容重复度高 (unique ratio: {unique_ratio:.2f})")
            score -= 0.2

    # 6. 截断检测
    if response.rstrip()[-1:] not in ["。", ".", "!", "!", "?", "?", "`", ")", ")", "】", "]", "\n"]:
        if not response.rstrip().endswith("```"):
            issues.append("回复可能被截断")
            score -= 0.15

    # === 一致性检查 ===

    # 7. 指令-回复相关性(简单关键词检查)
    instruction_keywords = set(instruction.lower().split())
    response_keywords = set(response.lower().split()[:100])
    overlap = len(instruction_keywords & response_keywords)
    if overlap < 1 and len(instruction_keywords) > 3:
        issues.append("回复可能与指令不相关")
        score -= 0.2

    # 8. 语言一致性
    if language == "zh":
        zh_chars = len(re.findall(r'[\u4e00-\u9fff]', response))
        total_chars = max(len(response), 1)
        if zh_chars / total_chars < 0.1 and "```" not in response:
            issues.append("中文指令但回复可能不是中文")
            score -= 0.3

    return QualityCheckResult(
        passed=score >= 0.6,
        score=max(0, score),
        issues=issues,
    )

这条检查管线覆盖格式(代码块闭合、结构化)、内容(长度、填充语、重复、截断)、一致性(指令相关性、语言)三个层面,与 18.4.7 节的 5-stage pipeline 互补:质量检查管线作用于单样本级别,5-stage pipeline 作用于数据集级别。

18.5.5 真实数据底线与早期崩溃检测

(此主题已在 18.4.7 节随过滤管线详述:25% 真实数据底线 + MMLU checkpoint 追踪。此处补充一点方法论定位。)

值得强调的是,质量清洗的正确心智模型是”漏斗 + 监控”:漏斗指多级过滤逐层提纯(5-stage pipeline),监控指训练过程中的早期退化信号(MMLU 追踪)。只做漏斗不做监控的风险是——清洗规则本身可能有 bug(比如误伤某类正常样本),导致数据分布系统性偏移,而这种偏移只有在训练中才能暴露。


18.6 数据选择方法谱系

清洗解决”去掉坏的”,选择解决”挑出好的”。数据选择是 SFT 数据工程中研究最密集的方向,本节建立完整的方法谱系:从 IFD 到 Deita,从 Non-Target 到 Target 方法,从离线到在线,最后给出统一理论(谱分析)与批判性反思(Random Selection)。

18.6.1 IFD:困惑度与模型-数据兼容性

IFD(Instruction-Following Difficulty)是一种数据筛选方法:用模型自身来评估每条数据的”价值”。

核心思想:

  • 对每条 (instruction, response) 对,计算模型生成 response 的困惑度(perplexity)
  • PPL 过低 → 模型”已经会了”,这条数据的训练价值有限
  • PPL 过高 → 回复可能与模型的能力差距太大,训练效果也不好
  • PPL 适中 → 这条数据在模型的”学习区”(zone of proximal development),价值最高
数据价值的 PPL 分布:

  PPL 很低(< 5)     → "已经会了" → 训练价值低
  PPL 适中(5-50)    → "学习区"  → 训练价值最高
  PPL 很高(> 50)    → "太难了"  → 训练效果差

PPL 的更深层含义:模型-数据兼容性

上面的三档分类是直觉上的入门框架。大规模 SFT 实验(EMNLP 2025 Main)揭示了 PPL 更深层的含义:PPL 实际衡量的是模型-数据兼容性,而非绝对难度。

这一区分至关重要。同一条 SFT 数据对不同基座模型会产生不同的 PPL 值——因此”好数据”是相对于模型而言的,不存在脱离模型的绝对质量。实验发现:

  1. PPL 是 SFT 有效性的最佳预测指标——优于训练数据与 benchmark 的表面相似度。高相似度的数据如果与模型内部表示不兼容,SFT 效果可能很差。
  2. 中层权重变化与性能提升高度相关。PPL 能预测 SFT 效果的原因在于,它间接反映了数据能否有效修改模型的中层表示——真正的”学习”发生在中间层,而非表层。
  3. 实践含义:IFD 筛选不应该只做一次——当基座模型更新后,同一批数据的 PPL 分布会改变,需要重新评估。
IFD 的升级用法:

  传统:    PPL 三档 → 留中间、去两端
  升级:    PPL × 模型版本 → 每次换基座重新筛选
            PPL × 任务类型 → 不同任务的"学习区"阈值不同

这与 18.4.6 节的离策略学习、GRAPE 的分布匹配形成本章第二条主线:数据质量是模型相关的——从生成端(强模型数据要匹配目标模型能力)到筛选端(IFD 要按目标模型计算)再到选择端(GRAPE 按目标模型分布挑回复),同一个原理贯穿始终。

18.6.2 Deita:10K 精选 > 100K 粗造

实验设计

Deita(Liu et al., 2023)做了一个反直觉实验:

  • 用 1M 条 OpenHermes 数据训练 Llama-7B
  • 用 10K 条精选数据训练同模型
  • 对比 MT-Bench、AlpacaEval

实验结果

数据规模MT-BenchAlpacaEval 胜率
1M 粗造6.855%
100K 粗造7.060%
10K 精选7.575%
1K 精选7.268%

核心发现:10K 精选数据训练效果,显著优于 1M 粗造数据。同时注意 1K 精选比 10K 精选差——精选有下限,不是越少越好。

Deita Selector

Deita 的精选方法叫 Selector,核心是两个维度评分:

  1. Complexity(复杂度):用 LLM 评分 1-6,越高越复杂
  2. Quality(质量):用 Reward Model 评分 0-1,越高越好
def deita_select(data, target_size=10000):
    # 1. 评分
    for d in data:
        d['complexity'] = llm_score_complexity(d)  # 1-6
        d['quality'] = reward_model(d)  # 0-1

    # 2. 计算 complexity × quality
    for d in data:
        d['score'] = d['complexity'] * d['quality']

    # 3. 按分数排序取 top
    sorted_data = sorted(data, key=lambda x: -x['score'])

    # 4. Embedding 距离去冗余
    selected = []
    for d in sorted_data:
        if all(emb_distance(d, s) > 0.3 for s in selected):
            selected.append(d)
        if len(selected) >= target_size:
            break
    return selected

Deita 的启示

  1. 数据规模不是越大越好:精选 10K 胜过粗造 1M
  2. Complexity × Quality 是核心:复杂且高质量的数据最有价值
  3. 多样性要去冗余:即使分数高,与已有数据太相似也要剔除

注意第 3 点:Deita Selector 的第 4 步(Embedding 距离去冗余)与本节的 IFD、18.7 节的多样性度量在方法上同源——都是用 Embedding 空间距离控制覆盖。

18.6.3 数据选择方法谱系总览

IFD 和 Deita 是数据选择的两个代表,但远非全部。近年来的研究已经形成了一个完整的方法谱系,可以从是否需要目标模型参与这一维度进行分类:

数据选择方法谱系

┌─ Non-Target(不需要目标模型训练信号)
│   ├─ IFD:基于 PPL 的模型-数据兼容性筛选
│   ├─ Superfiltering:弱模型筛选强模型数据
│   ├─ MoDS:三维度联合筛选(质量 + 多样性 + 必要性)
│   └─ CaR:质量 + 多样性双轮驱动
│
└─ Target(需要目标模型的训练信号)
    ├─ Nuggets:one-shot 评分选择
    └─ LESS:梯度影响力选择

Non-Target 方法

IFD 已在 18.6.1 节详述,核心是用 PPL 衡量模型-数据兼容性。

Superfiltering(弱到强筛选)是一个反直觉但实证有力的发现:用弱模型(如 1B)的 IFD 信号来筛选训练强模型(如 70B)的数据,效果竟然优于直接用强模型自身的 IFD 信号筛选。其原理在于:弱模型”觉得难”的数据,恰恰是能力边界附近的样本——而强模型在这些样本上仍有提升空间,因为弱模型的”难”反映了数据的内在复杂度,而非模型的大小。这一洞察极大降低了数据筛选的计算成本——不需要在目标大模型上计算 PPL,一个小模型就够用。

MoDS(Multi-objective Data Selection)将数据选择分解为三个维度的联合优化:质量(回复的准确性和信息密度)、多样性(避免冗余覆盖)、必要性(目标模型在该数据上的学习空间)。MoDS 先按质量过滤,再在高质量数据中按多样性采样,最后用必要性指标(本质上是 IFD 的变体)精筛,形成”漏斗式”选择管线。

CaR(Classification and Regression)采用”质量 + 多样性”双轮驱动:先用分类模型将数据按质量分档,再在每档内用回归模型预测多样性得分,保证选出的数据在各质量层级都有分布。

Target 方法

Target 方法需要获取目标模型在训练过程中的梯度或损失信号,计算成本更高,但选择精度也更高。

Nuggets 采用 one-shot 评分策略:对每条候选数据,只用一个样本(one-shot)评估目标模型在该数据上的学习增益。这种方法简化了评估流程,但可能对 one-shot 的选择敏感。

LESS(Low-rank Gradient Similarity Search)是目前最精细的 Target 方法。它计算每条训练数据对目标模型参数的梯度影响力——具体来说,用低秩近似计算训练数据梯度与验证集梯度的相似度,选择影响力最大的数据。LESS 有两个突出特点:

  1. 跨语言深层选择:LESS 基于梯度而非表面文本相似度选择数据,因此能选出表面上看起来不相关但对模型内部表示有正面影响的数据——例如,用英文数据提升中文推理能力,因为底层推理模式的梯度表示是跨语言共享的。
  2. 深层选择能力:相比基于 embedding 相似度的浅层匹配,LESS 能捕获数据对模型参数的深层影响,选出”表面不相似但训练价值高”的数据。
Non-Target vs Target 方法对比

| 维度       | Non-Target (IFD/Superfiltering/MoDS/CaR) | Target (Nuggets/LESS) |
|-----------|------------------------------------------|----------------------|
| 计算成本    | 低(仅需前向推理)                         | 高(需梯度计算)       |
| 选择精度    | 中等                                      | 高                    |
| 适用阶段    | 数据生产早期(还没有目标模型时)            | 有明确训练目标后       |
| 跨语言能力  | 有限(基于文本/ppl匹配)                   | LESS 有跨语言能力      |
| 实践建议    | 大规模初筛                                 | 精细化精选             |

实践中,两类方法可以串联使用:先用 Non-Target 方法(如 IFD + Superfiltering)做大范围初筛,将数据量从数十万缩减到数万;再用 Target 方法(如 LESS)做精准精选,得到最终训练集。

第三根轴:预算往哪些概念上倾斜

上面两根轴——要不要目标模型信号、什么时候选——回答的都是同一个问题:这条数据值不值得留。但在有限预算下还有第二个问题被忽略了:预算该往哪些概念上倾斜。

PASS(论文标题 Overcoming Prior Barriers: Supervised Fine-Tuning under Long-Tail Distribution;PASS 为方法名,Haohui Wang 等 12 人,Virginia Tech / Amazon / Meta / MBZUAI / Dartmouth,2026-10-08)把这个缺口落成了一个可计量的量。它提出 prior barrier:预训练对”竞争概念”的支持强于对”目标概念”的支持程度。关键观察是 prior barrier 本身呈长尾分布——头部概念起点低(已经学得不错,再加数据收益递减),尾部概念起点高(需要额外指令才能翻越)。论文给出了长尾 prior barrier 下的预测风险界,把”prior barrier + 已累积的 SFT 证据”如何共同决定性能写成了显式表达式。

据此 PASS 分三步走:先构造一个 reference-derived 概念空间,再估计每条指令能提供的区分性证据(distinguishing evidence),最后按”这个概念现在还缺多少证据”把选择预算自适应地分配给覆盖不足的概念。消融显示,这种自适应分配在 Mistral-7B 上比均匀分配高 0.32 / 0.26 分(nn 分别取 5,000 与 10,000),在 Qwen2.5-7B 上高 0.29 / 0.56 分。论文报告 PASS 在四种 backbone-预算设置下优于 7 种指令选择方法(Random、RDS+、LESS、TSDS、TADS 等)。

这里有一个必须一并读出的反直觉细节:在其中一种设置下(Qwen2.5-7B、n=10,000n=10{,}000),Random 拿到 65.6 的平均分,优于除 PASS 以外的所有其他基线——PASS 在同一格上的值也是 65.6,即两者打平,并非严格胜出。也就是说,PASS 自己的实验也复现了 §18.6.6 的结论——随机选择不是可以被轻易打翻的软柿子。两条结论并不冲突:§18.6.6 说”别过度精挑”,PASS 说”如果要精挑,就按 prior barrier 分配预算,而不是按数据的表面属性”。预算轴解决的是分配,不是是否值得选。

⚠️ PASS 为 2026-10 的预印本,“优于 7 种 SOTA”为作者自述,四种 backbone-预算设置的逐项数值本书未逐条复核;论文注明源码将在发表后释出,目前不可复现。

18.6.4 第三维度:选择时机——Offline vs Online

上述 Non-Target/Target 分类隐含了一个共同前提:离线选择——先扫描全部候选数据,再选出最优子集,然后开始训练。2026 年 ICML 上的一项工作挑战了这一前提。

UDS(Utility-Diversity Sampling,Heming Zou、Yixiu Mao、Yun Qu、Qi Wang、Xiangyang Ji,清华大学自动化系,arXiv 2025-10-19,ICML 2026 accepted;代码 github.com/gfyddha/UDS)提出了在线批选择范式——在每个训练 step 动态决定哪些样本进入当前 batch,将”先选后训”改为”边训边选”。核心理念:利用模型前向传播的 logits 信号同时评估样本效用与多样性,无需验证集、参考模型或昂贵的样本级梯度计算。

具体做法是两条支路拼在一起:一条用 logits 矩阵的核范数同时刻画优化效用与句内多样性——核范数对”样本能被模型学到多少”与”句子内部表征是否丰富”给出同一个标量;另一条把 logits 做低维双线性随机投影后,与一个存放历史样本的 FIFO 内存缓冲区比对,得到句间多样性。两者加权后选 top-K 进入当前 batch。由于全程只用前向传播已经产出的 logits,不需要参考模型、不需要留出验证集、也不做额外反传,论文报告训练总时间低于全量 SFT。

选择时机维度的扩展谱系:

┌─ Offline(先选后训)——现有谱系全覆盖
│   ├─ Non-Target:IFD/Superfiltering/MoDS/CaR(本章详述)
│   └─ Target:Nuggets/LESS(本章详述)
│
└─ Online(边训边选)——2026 新方向
    └─ UDS:基于前向 logits 的效用-多样性联合在线选择
        - 在每个 batch 动态决定,无需预扫描全量数据
        - MMLU/ScienceQA/GSM8K/HumanEval 全面优于 FisherSFT
        - 算力节省近 50%

在线选择的工程意义在于:当数据池大到无法预先评估(如几十亿条合成数据流)时,离线选择变得不切实际。UDS 的范式——“每个 step 看一眼当前数据、做一个快速决定”——更适合数据持续膨胀的生产环境。

18.6.5 谱分析统一理论:Layer-wise Gradients

本节前面按 IFD/Superfiltering/MoDS/CaR/LESS 等多种方法平行列出,读者可能会困惑:这些方法是否各自独立?是否有一个统一的理论视角? Ming Li, Yanhong Li, Ziyue Li, Tianyi Zhou (University of Maryland & University of Chicago, ACL 2026 Long Paper)(arXiv:2504.10766)提出了Layer-wise Gradients 谱分析理论,给出了根本性统一视角。

核心发现:

Layer-wise Gradients 谱分析的根本性发现:

1. 更高质量的数据通常与:
   ├── 更低的核范数(nuclear norm)相关
   └── 更高的有效秩(effective rank)相关

2. 广泛研究的数据评估指标:
   ├── IFD(Instruction-Following Difficulty)
   ├── InsTag(Instruction Tag)
   ├── Difficulty
   └── Reward
   都可以被梯度 SVD 的谱性质解释和统一

3. 有效秩比核范数更鲁棒地捕捉细微质量差异

4. 推理数据 vs 指令数据:
   └── 推理数据实现显著更高的有效秩
   └── 意味着在更复杂任务上具有更丰富的梯度结构

5. 跨模型家族的发现:
   └── 同一家族内的模型共享相似的梯度模式(无论大小)
   └── 不同模型家族梯度模式显著分歧

谱性质作为统一视角:

数据选择方法谱性质视角的解释
IFD反映梯度谱的能量集中度
InsTag对应不同标签诱导的梯度子空间正交性
Difficulty与梯度范数 / 核范数相关
Reward与梯度方向在奖励函数上的投影相关
MoDS联合优化谱能量 + 谱多样性
CaR分类梯度 + 回归梯度的协调
LESS训练数据梯度与验证集梯度的谱相似度

方法学升级:

之前(方法平行列出):
  读者需要为每个场景选对方法——容易迷失
  └── 缺乏统一的"何时用什么方法"决策框架

现在(谱分析统一理论):
  决策依据:检查数据的梯度谱性质
  ├── 核范数异常高 → 数据可能不健康
  ├── 有效秩低 → 数据多样性不足
  ├── 与目标任务谱性质不匹配 → IFD 筛选
  └── 与目标任务谱性质高度对齐 → 直接训练

对工程实践的指导:

"""
基于谱分析的数据质量诊断(伪代码)
"""
import torch

def diagnose_data_quality_spectral(
    model,
    candidate_data: list,
) -> dict:
    """
    计算候选数据的梯度谱性质
    """
    nuclear_norms = []
    effective_ranks = []

    for sample in candidate_data:
        # 前向 + 反向计算梯度
        loss = compute_loss(model, sample)
        grads = torch.autograd.grad(loss, model.parameters())
        flat_grad = torch.cat([g.flatten() for g in grads])

        # SVD 分解
        U, S, V = torch.svd(flat_grad.unsqueeze(0))

        # 核范数 = S 之和
        nuclear_norm = S.sum().item()
        # 有效秩 = exp(H(p)),其中 p = S/S.sum() 的归一化熵
        p = S / S.sum()
        entropy = -(p * torch.log(p + 1e-12)).sum()
        effective_rank = torch.exp(entropy).item()

        nuclear_norms.append(nuclear_norm)
        effective_ranks.append(effective_rank)

    return {
        "nuclear_norms": nuclear_norms,
        "effective_ranks": effective_ranks,
        "high_quality_threshold": {
            "nuclear_norm_max": 1.5 * torch.tensor(nuclear_norms).median().item(),
            "effective_rank_min": 0.7 * torch.tensor(effective_ranks).max().item(),
        }
    }

18.6.6 临界反思:Random Selection is Almost All You Need

本节前面推荐了 IFD/Superfiltering/MoDS/CaR/LESS 等多种复杂数据选择方法。2024 年底的一项反思性研究 Rethinking Data Selection at Scale: Random Selection is Almost All You Need(Tingyu Xia、Bowen Yu、Kai Dang、An Yang、Yuan Wu、Yuan Tian、Yi Chang、Junyang Lin,吉林大学 + 阿里巴巴,2024-10-12;代码 github.com/xiatingyu/SFT-DataSelection-at-scale)提供了反向证据,挑战了”必须用复杂选择方法”的根叙事。

需要留意这条结论的年代。它不是 2026 年才出现的新发现,而是 2024 年底就已经确立、此后被大量后续工作直接当作基线前提使用的老结论。读到”随机选择是强基线”时如果默认是新近共识,会误判当前选择方法的进步幅度——近两年的新方法要打的是这条 2024 年的基线,而不是一条空基线。

该研究在两百万级数据池上复现了若干 self-scoring 选择方法——这是它与此前工作最大的区别:绝大多数选择方法是在几万条的小池子上验证的,而它把池子推到了真实工业规模。结论有三条:① 这些复杂方法几乎都无法显著超过随机选择;② 多样性比”挑高质量”更关键;③ 按 token 长度过滤是稳定有效的改进手段,对 Llama3 一类较弱基座、长文本尤其明显。

核心论点:

反直觉发现:

  复杂数据选择的优势在规模上减弱
  简单的随机选择在大规模 SFT 中是高度竞争的基线

关键证据:

发现实验支持含义
复杂方法优势随规模减弱在 >100K 样本规模下,IFD/LESS 的优势在统计上不显著大规模时随机选择足够
数据多样性 > 数据质量在固定样本数下,覆盖更广的多样性数据效果更好覆盖比精选更重要
令牌长度作为过滤标准对弱模型有效Llama3-8B 训练中弱模型需要”长回答”信号
LESS 依赖验证集代表性验证集越不代表目标分布,LESS 效果越差不适合多任务 SFT
IFD 偏短响应由于 PPL 计算性质短响应被高估
SelectIT 偏向高置信度可能丢失有挑战性的样本

数据选择决策树(修订版):

数据选择决策树(基于规模与多样性考量):

  Q1: 数据规模有多大?
  │
  ├── < 10K(精品小数据)
  │     └── 使用复杂方法(IFD + LESS)
  │     └── 选每条数据都很重要
  │
  └── ≥ 100K(大规模数据)
        │
        Q2: 任务是否多样化?
        │
        ├── 任务集中(单一领域)
        │     └── IFD / LESS 仍可能有效
        │     └── 但随机基线已经很接近
        │
        └── 任务多样(多任务 / 通用助手)
              │
              Q3: 验证集是否能代表目标分布?
              │
              ├── 是 → LESS 仍可用
              └── 否 → 优先保证多样性
                    └── 随机 + 多样性采样
                    └── 复杂方法可能引入偏差

  通用建议:
    └── 大规模 SFT 中,"多样性保证 + 随机"是稳健基线
    └── 复杂方法适合小规模精标或特定场景
    └── 不要把"用 LESS"当作默认选择

这个反思与 Deita 的”10K 精选 > 100K 粗造”并不矛盾,而是同一枚硬币的两面:当你的预算只允许 10K 条数据时,每一条都要精挑细选(复杂方法有价值);当你可以 afford 100K+ 时,把功夫花在多样性保证上,比逐条精选更划算。

18.6.7 目标函数层:PoFT 补齐 SFT 的第三层

本章至此覆盖了 SFT 数据改进的两层:数据合成(18.3 节:Self-Instruct / Evol-Instruct / Magpie)与数据选择(本节:Deita / IFD / LESS)。但 SFT 改进实际上有三层结构——合成 → 选择 → 目标函数,第三层常被忽略。

PoFT(Preference-Oriented SFT, arXiv:2412.12865) 在 SFT 阶段引入偏好信号:用 Bradley-Terry(BT)偏好退化的交叉熵(CE)目标,为每个样本赋予逐样本权重 τ。核心价值在于:对噪声标注鲁棒,且与 IFD / Instag / Deita 等”数据选择”方法正交——Deita 改的是”选哪些数据”,PoFT 改的是”怎么用这些数据训练”。实验显示 PoFT+DPO 优于 SFT+DPO。

CLEAR(模型置信度过滤) 则是”模型驱动的数据清洗”邻接路线:用模型自身置信度作为 SFT 数据过滤信号,与 Deita 的 IFD 复杂度评分形成”复杂度评分 vs 置信度过滤”的对照。二者机制不同,可作为 Deita 的补充而非替代。

三层结构的完整图景:

层改什么代表方法
合成层数据从哪来Self-Instruct / Evol-Instruct / Magpie
选择层哪些数据入训IFD / Deita / LESS / 随机+多样性
目标函数层怎么用这些数据训PoFT / SED-SFT / DFT(见 18.7.5 节)

18.7 多样性度量与保证

合成数据必须做多样性度量——这是判断”数据池健康度”的核心体检指标。同时,多样性的保证需要数据与训练目标双管齐下,本节展开完整方案。

18.7.1 三大度量指标

4-gram 多样性

from collections import Counter
def ngram_diversity(texts, n=4):
    all_ngrams = []
    for text in texts:
        tokens = text.split()
        all_ngrams.extend([' '.join(tokens[i:i+n]) for i in range(len(tokens)-n+1)])
    unique = len(set(all_ngrams))
    total = len(all_ngrams)
    return unique / total  # 0-1,越高越好

经验阈值:

  • ≥ 0.6:优秀
  • 0.4-0.6:合格
  • < 0.4:不合格,需要去重

Embedding 聚类

from sentence_transformers import SentenceTransformer
from sklearn.cluster import KMeans

embeddings = model.encode(prompts)
kmeans = KMeans(n_clusters=100)
labels = kmeans.fit_predict(embeddings)

# 计算每个簇的样本数
cluster_counts = Counter(labels)
max_cluster_ratio = max(cluster_counts.values()) / len(prompts)
# 最大簇占比 < 5% 才算合格

InsTag 覆盖率

InsTag(Lu et al., 2023)是一个指令标签系统,标注每条指令的”意图标签”(如”代码生成”、“数学计算”、“创意写作”等)。

# InsTag 标签覆盖
tags = ins_tag_annotate(prompts)
unique_tags = len(set(tags))
# 工业级要求 unique_tags ≥ 1000

18.7.2 多样性的五个维度

n-gram 和标签覆盖是量化指标,但多样性的完整图景有五个维度:

维度 1: 任务多样性
  - 覆盖尽可能多的任务类型
  - 避免某一类任务过度主导

维度 2: 领域多样性
  - 科技、医学、法律、教育、金融、日常...
  - 每个领域都有代表性样本

维度 3: 长度多样性
  - 短回复(一两句话)到长回复(数千字)
  - 不要让模型形成固定的长度偏好

维度 4: 难度多样性
  - 从入门到专家级的全谱系覆盖

维度 5: 风格多样性
  - 正式/非正式、技术/通俗、详细/简洁

18.7.3 Embedding 多样性分析

综合多个量化指标的完整分析工具:

"""
基于 Embedding 的 SFT 数据多样性分析
"""

import numpy as np
from typing import List

def analyze_diversity(
    instructions: List[str],
    embeddings: np.ndarray,  # (N, dim) 的 embedding 矩阵
    n_clusters: int = 20,
) -> dict:
    """
    分析 SFT 数据集的多样性
    """
    from sklearn.cluster import KMeans
    from sklearn.metrics import silhouette_score

    N = len(instructions)

    # 1. 聚类分析
    kmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init=10)
    labels = kmeans.fit_predict(embeddings)

    # 各类别大小
    cluster_sizes = np.bincount(labels)
    size_std = np.std(cluster_sizes)

    # 2. 覆盖度分析
    # 理想情况下,类别大小应该相对均匀
    coverage_score = 1 - (size_std / np.mean(cluster_sizes))

    # 3. 平均余弦距离(全局多样性)
    # 采样计算避免 O(N²)
    sample_size = min(5000, N)
    indices = np.random.choice(N, sample_size, replace=False)
    sample_emb = embeddings[indices]
    # 归一化
    norms = np.linalg.norm(sample_emb, axis=1, keepdims=True)
    normalized = sample_emb / np.maximum(norms, 1e-8)
    # 平均余弦距离
    cos_sim_matrix = normalized @ normalized.T
    avg_similarity = (cos_sim_matrix.sum() - sample_size) / (sample_size * (sample_size - 1))
    avg_diversity = 1 - avg_similarity

    # 4. 识别稀疏区域(可能缺少样本的类别)
    sparse_clusters = [i for i, size in enumerate(cluster_sizes)
                       if size < N / n_clusters * 0.3]

    return {
        "total_samples": N,
        "n_clusters": n_clusters,
        "cluster_sizes": cluster_sizes.tolist(),
        "coverage_score": round(float(coverage_score), 3),
        "avg_diversity": round(float(avg_diversity), 3),
        "sparse_cluster_count": len(sparse_clusters),
        "silhouette_score": round(float(silhouette_score(embeddings, labels)), 3),
        "recommendation": (
            "多样性良好" if coverage_score > 0.7 and avg_diversity > 0.5
            else "建议增加稀疏领域的样本"
        ),
    }

18.7.4 模式坍缩检测

合成数据最常见的问题是模式坍缩:模型倾向于生成相似模式的回复。

检测方法:

def detect_pattern_collapse(responses: List[str], n_samples: int = 1000) -> dict:
    """
    检测合成回复中的模式坍缩
    """
    from collections import Counter

    # 1. 开头句式检测
    first_sentences = [r.split("。")[0][:50] if "。" in r else r[:50]
                       for r in responses[:n_samples]]
    opening_counter = Counter(first_sentences)
    top_openings = opening_counter.most_common(10)
    repetitive_openings = sum(c for _, c in top_openings if c > n_samples * 0.05)

    # 2. 结构模式检测
    structures = []
    for r in responses[:n_samples]:
        # 简单的结构指纹:标题数量、列表项数量、代码块数量
        n_headers = r.count("##")
        n_list_items = r.count("\n-") + r.count("\n1.")
        n_code_blocks = r.count("```") // 2
        structures.append((n_headers, n_list_items, n_code_blocks))

    structure_counter = Counter(structures)
    top_structures = structure_counter.most_common(5)

    # 3. 汇总
    collapse_risk = "low"
    if repetitive_openings > n_samples * 0.3:
        collapse_risk = "high"
    elif repetitive_openings > n_samples * 0.15:
        collapse_risk = "medium"

    return {
        "collapse_risk": collapse_risk,
        "top_repeated_openings": top_openings[:5],
        "repetitive_opening_ratio": repetitive_openings / n_samples,
        "top_structural_patterns": top_structures,
        "unique_structures": len(structure_counter),
    }

18.7.5 训练目标层面的多样性:SED-SFT 与 DFT

上面的检测方法都是从数据层面入手。但模式坍缩的根源可能不仅在数据,还在训练目标本身。

SED-SFT(Selectively Encouraging Diversity in SFT, 2026)揭示了一个被忽视的机制:标准交叉熵(CE)损失天然鼓励模式坍缩。CE 优化的是 argmax 概率——它把所有概率质量集中到”最可能的下一个 token”上,系统性地挤压那些不是最优但完全合理的表达。换句话说,即使训练数据足够多样,CE 损失也在训练过程中把多样性”抹平”。

SED-SFT 的应对策略是在 token 级别自适应鼓励多样性:对探索空间大的位置(如句子开头、风格选择点)加入熵正则项,保留模型在这些位置的多路径能力;对确定性高的位置(如事实表述、代码语法)仍然正常优化。

另一个视角来自 DFT(Dynamic Fine-Tuning, ICLR 2026),它从强化学习视角分析 SFT 的有限泛化能力,发现仅在 NLL 损失基础上加入奖励信号校正——本质上是一行代码的修改——就能显著提升 SFT 的泛化性。

DFT 的核心理论贡献是揭示了 SFT 梯度的强化学习本质:SFT 的交叉熵梯度等价于逆概率加权的策略梯度。形式化地:

标准 SFT 梯度:
  ∇L_CE = -∑_t ∇log π(y_t | x, y_<t)

等价于策略梯度:
  ∇L_CE = -∑_t (1/π(y_t | x, y_<t)) · π(y_t | x, y_<t) · ∇log π(y_t | x, y_<t)
         = -∑_t w_t · ∇log π(y_t | x, y_<t)

其中 w_t = 1/π(y_t | x, y_<t) 是逆概率权重

逆概率加权意味着低概率 token 的梯度被放大——这与直觉一致,模型不太会的 token 应该获得更多学习信号。但问题在于:低概率也可能是”不需要会”(如风格偏好)而非”应该会但不会”,标准 SFT 无法区分这两种情况。

DFT 的 reward rectification 通过引入奖励信号对权重进行矫正:用 r(y_t) 替代逆概率权重 1/π(y_t),使得只有”应该会且不会”的 token 获得高权重,而”不需要会”的 token 权重被压低。在代码实现上,这本质上是将标准 loss 乘以一个 token 级别的奖励系数:

# 标准 SFT loss
loss = -log_probs  # log π(y_t | x, y_<t)

# DFT reward rectification:一行代码的修改
loss = -reward_weights * log_probs  # reward_weights 由奖励模型给出

这一修改的理论意义在于:它将 SFT 从”盲目拟合所有 token”升级为”有选择地学习有价值的 token”,在不改变模型架构和训练流程的前提下,仅通过损失函数的加权矫正即可显著提升泛化性。

这些发现的实践意义是:真正的多样性保证需要数据层面和目标函数层面双管齐下。

层面方法效果
数据层面多样化采集 + embedding 聚类 + 模式坍缩检测保证输入多样性
目标函数层面SED-SFT 熵正则 / DFT 奖励修正保证训练过程不丢失多样性

仅靠其中一个层面是不够的——多样化的数据配合 CE 损失仍会坍缩,正则化的目标函数配合单一数据也无法凭空创造多样性。

至此,SFT 改进的完整三层结构(合成 → 选择 → 目标函数)全部就位:18.3 节的合成方法提供多样化的原料,18.6 节的选择方法浓缩精华,本节的目标函数改造防止训练过程”抹平”多样性。


18.8 多轮对话数据

多轮对话数据是 SFT 数据中的”高阶形态”——它不仅要求每一轮质量合格,还要求轮次之间有真实的依赖关系。

18.8.1 多轮依赖关系的构造

多轮对话数据需要有真实的轮次间依赖——不能是”把多个单轮拼在一起”。

❌ 假多轮(没有真正的依赖):
  User: "什么是 TCP?"
  Assistant: "TCP 是传输控制协议..."
  User: "Python 怎么读文件?"  ← 和第一轮完全无关
  Assistant: "可以使用 open() 函数..."

✅ 真多轮(有上下文依赖):
  User: "什么是 TCP?"
  Assistant: "TCP 是传输控制协议..."
  User: "它和 UDP 有什么区别?"  ← 依赖第一轮的上下文
  Assistant: "TCP 和 UDP 的主要区别在于..."
  User: "什么场景下应该用 UDP?"  ← 依赖前两轮
  Assistant: "UDP 适合实时性要求高..."

假多轮训练出的模型学不到”指代消解”和”上下文延续”——用户说”它”的时候,模型不知道”它”指什么。

18.8.2 上下文一致性检查

def check_multi_turn_consistency(
    conversation: list,  # [{"role": "user"|"assistant", "content": str}, ...]
) -> dict:
    """
    多轮对话一致性检查
    """
    issues = []

    for i in range(1, len(conversation)):
        current = conversation[i]
        if current["role"] == "assistant":
            # 检查是否与自己之前的回复矛盾
            for j in range(i - 1, -1, -1):
                if conversation[j]["role"] == "assistant":
                    # 简化检查:关键实体是否前后一致
                    # 实际中应该用 NLI 模型检查
                    pass

            # 检查是否遵循了最近的用户指令
            prev_user = None
            for j in range(i - 1, -1, -1):
                if conversation[j]["role"] == "user":
                    prev_user = conversation[j]["content"]
                    break

            if prev_user and len(current["content"]) < 10:
                issues.append(f"Turn {i}: 回复过短,可能未充分回应用户")

    # 检查对话长度合理性
    assistant_turns = [c for c in conversation if c["role"] == "assistant"]
    lengths = [len(c["content"]) for c in assistant_turns]
    if len(lengths) > 2:
        if max(lengths) / max(min(lengths), 1) > 10:
            issues.append("各轮回复长度差异过大")

    return {
        "turn_count": len(conversation),
        "issues": issues,
        "is_consistent": len(issues) == 0,
    }

18.8.3 MDS:对话级数据选择框架

真多轮 vs 假多轮的区分和一致性检查解决的是多轮对话数据的基础质量问题。更进一步,多轮对话数据的选择也有专门方法。

多轮对话语料的常见质量问题:

多轮对话语料的常见质量问题:
  ├── 主题漂移:跨轮话题不一致
  ├── 重复闲聊:低信息量轮次
  ├── 跨轮格式不一致:长度/风格突变
  └── 信息进展不足:第二轮开始没有新增信息

2026 年 Bo Li et al. (ACL 2026 Findings) 提出的 MDS (Multi-turn Dialogue Selection) 框架是首篇对话级数据选择框架,而非单轮。

MDS 两阶段设计:

MDS 框架:

阶段 1:全局阶段(Global Stage)
  ├── 在 user-query trajectory 空间将对话分桶
  ├── bin-wise 选择(保留代表性、非冗余)
  └── 避免全数据集层面的覆盖偏差

阶段 2:局部阶段(Local Stage)
  ├── 评估"实体基础的主题衔接"
  │     └── 后续轮次是否引用前文实体?
  ├── 评估"信息进展"
  │     └── 每轮是否引入新信息?
  └── 评估"问答形式一致性"
        └── 跨轮回答风格是否连贯?

关键结果:

配置表现含义
三个多轮基准测试MDS 整体排名第一通用性
Banking 测试集MDS 整体排名第一领域适应性
长对话场景MDS 鲁棒性优势明显解决”长对话质量下滑”问题

GitHub 开源:https://github.com/pkuserc/MDS

MDS 的两阶段设计与 Deita 的”评分 + 去冗余”(18.6.2 节)思路同构,但作用对象从单条指令升级为完整对话轨迹——这再次说明数据选择的基本范式(全局覆盖 + 局部质量)具有跨数据形态的普适性。

18.8.4 从线性到分支:Conversation Forests

上面讨论的多轮数据是线性序列。更进一步,Conversation Forests(2025)提出了一种超越线性序列的数据组织方式——分支对话树。

传统的多轮对话数据是线性的:User → Assistant → User → Assistant → …。但在真实交互中,同一个对话上下文下可能有多种合理的后续发展方向。分支对话树允许在每个节点探索不同的回复路径:

           User: "解释 Transformer"
                 │
          Assistant: "Transformer 是..."
                 │
    ┌────────────┼────────────┐
    │            │            │
 "注意力机制    "和RNN比     "代码实现
  怎么工作?"   有什么优势?"  能看看吗?"
    │            │            │
  (分支A)      (分支B)      (分支C)

分支结构天然编码了多样性——模型从中学到的不是”给定上下文只有一种正确回应”,而是”同一上下文下有多种合理的发展方向”。在医疗对话等需要探索多种诊断路径的场景中,分支对话树已被验证能显著提升对话质量。

不过需要注意,分支对话树目前的验证主要集中在垂直领域(如医疗对话),推广到通用 SFT 的效果还需要更多实证。这一范式更适合作为高价值对话数据的构造策略,而非通用的大规模数据生产方法。


18.9 数据混合与配比

数据造好、选好之后,还剩最后一个问题:怎么混。SFT 数据的配比直接决定模型的”性格”——同样的数据,不同的混合比例,训出来的模型可能完全不同。

18.9.1 任务类型配比

一个通用助手模型的 SFT 数据配比参考:

推荐配比(100K 条数据为例):

知识问答      : 15K (15%)
代码生成/调试  : 20K (20%)  ← 代码通常需要较多样本
数学推理      : 12K (12%)
创意写作      : 8K  (8%)
翻译/摘要     : 8K  (8%)
对话/闲聊     : 10K (10%)
工具调用      : 10K (10%)
指令遵循      : 7K  (7%)   ← 格式约束、角色扮演等
拒绝/安全     : 5K  (5%)
多轮对话      : 5K  (5%)

18.9.2 难度分布设计

推荐的难度分布(倒金字塔形):

  Level 1 (简单): 30%
    → 建立基础的指令遵循能力
    → 让模型学会"按格式回答"

  Level 2 (中等): 40%
    → 主体能力训练
    → 覆盖各类实际使用场景

  Level 3 (困难): 25%
    → 挑战性任务,提升上限
    → 多步骤推理、复杂分析

  Level 4 (专家): 5%
    → 少量极难样本
    → 避免过多导致训练不稳定

反直觉发现

过多的难样本反而会降低模型表现。研究发现,当 Level 3+4 的比例超过 40% 时,模型在简单任务上的表现反而会退化。原因是模型学会了”过度思考”——即使简单问题也会给出冗长的分析。

这与 18.3.9 节的工业组合(Magpie 70% 底盘 + Evol 20% 难度梯度 + 人工 10% 质量锚)在难度轴上相互印证:难度梯度要有,但要克制——Evol-Instruct 的进化终止条件(最多 3 次进化、500 tokens 上限)同样是防”过度复杂化”。

18.9.3 长度分布设计

SFT 数据的长度分布直接影响模型的输出长度偏好:

如果训练数据的回复普遍很长:
  → 模型倾向于给出冗长回复,即使问题很简单

如果训练数据的回复普遍很短:
  → 模型回复不够详细,复杂问题回答不充分

推荐策略:
  - 回复长度应该与问题复杂度相匹配
  - 简单问题:50-200 tokens
  - 中等问题:200-500 tokens
  - 复杂问题:500-2000 tokens
  - 不应该有统一的"目标长度"

长度分布与 18.6.6 节的”IFD 偏短响应”问题相关:如果选择方法系统性偏向某一长度段,会破坏精心设计的长度分布。这也是数据选择之后要做一次配比审计的原因——选择方法的偏置会在配比上留下痕迹。

配比审计的第四根轴:按”模型还缺什么”切分。任务类型、难度、长度这三根轴有一个共同点:它们都是按数据自己的表面属性切分的,切分时不看模型当前的状态。§18.6.3 末尾那根预算轴提供了另一种切法——按”模型还缺什么”切:先估计每个概念的 prior barrier(预训练对竞争概念的支持强于目标概念的程度),找出当前覆盖不足的概念簇,再把预算往这些簇上倾斜。

实践上这意味着配比审计要分两层做:先按三根表面轴核对实际配比是否偏离设计值(这一步是查”有没有做歪”),再按概念覆盖度核对”剩下的预算该补给谁”(这一步是查”该往哪加”)。只有第一层的配比审计会漏掉一类问题——总量、难度、长度都对,但某一簇尾部概念始终没被覆盖到。


18.10 评估与迭代

数据造完、训完,还不是终点。SFT 数据工程的最后一环是评估数据本身的贡献,并把评估结果反馈到下一轮数据生产——这就是数据飞轮。

18.10.1 数据影响力分析

不要把”模型表现”和”数据质量”混为一谈。以下是专门针对 SFT 数据质量的评估方法:

1. 数据影响力分析

找出哪些训练样本对模型表现贡献最大:

方法 A: Leave-One-Out(太贵,不实用)
  → 每次移除一条数据,重新训练,观察影响

方法 B: 影响函数(Influence Functions)
  → 近似计算每条数据的影响
  → 计算成本可控

方法 C: 数据 Shapley 值
  → 用采样方法估计每条数据的边际贡献
  → 最公平但也最贵

方法 D: RL-readiness 评估
  → 不看 SFT 后的表现,而是看经 RL 后的表现
  → PEAR 的发现:SFT 得分最高 ≠ RL 后得分最高

18.10.2 RL-readiness 评估

上述前三种方法都从”SFT 后模型表现”角度评估数据质量。PEAR(Policy Evaluation-inspired Algorithm for Offline Learning Loss Re-weighting, 2026)提出了一个颠覆性的发现:SFT 性能更强的检查点,经过相同 RL 训练后反而可能表现更差。

这意味着评估 SFT 数据质量时,仅看 SFT 阶段的指标可能产生误导。好的 SFT 数据应该为下游 RL 保留足够的探索空间,而非过度拟合到一种”最优回复”。PEAR 通过重要性采样对损失进行重加权,使 SFT 检查点更适合后续 RL 优化。

RL-readiness 评估的实践方法:

快速 RL 探测(RL Probing):

  1. 在候选 SFT 数据集上训练得到检查点
  2. 用少量 RL step(如 100-200 step)测试检查点的可优化性
  3. 对比不同数据集训练出的检查点在 RL 探测后的提升幅度
  4. 选择 RL 提升幅度最大的数据集——它保留了最多的优化空间

  关键指标:improvability = RL后得分 - SFT得分
  → improvability 越高,说明 SFT 数据为 RL 留出了越好的起点

这与 18.7.5 节的多样性讨论深层呼应:SFT 过度拟合”最优回复”会压缩模型的输出分布(模式坍缩的目标函数根源),而坍缩的分布恰恰是 RL 探索的敌人——RL 需要策略空间有足够的多样性才能找到更优解。

18.10.3 类别覆盖度评估

def evaluate_data_coverage(
    sft_data: list,          # SFT 数据集
    eval_failures: list,     # 模型在评估中失败的案例
) -> dict:
    """
    分析评估失败案例与训练数据的覆盖度关系
    """
    # 对失败案例做分类
    failure_categories = categorize_failures(eval_failures)

    # 检查每个失败类别在训练数据中的覆盖度
    coverage_gaps = {}
    for category, count in failure_categories.items():
        training_count = count_category_in_data(sft_data, category)
        if training_count < 10:
            coverage_gaps[category] = {
                "eval_failures": count,
                "training_samples": training_count,
                "recommendation": "严重不足,建议增加该类型的训练数据",
            }

    return coverage_gaps

18.10.4 从一次性标注到数据飞轮

SFT 数据迭代的认知演变

  • 2022:“标注 10K 条,训练,发布”
  • 2023:“标注 → 训练 → 评估 → 补充标注 → 再训练”
  • 2024:“建立数据飞轮——模型的失败案例成为新的标注重点”
  • 2025-2026:“全自动化的数据飞轮——模型评估 → 自动识别弱点 → 自动合成补充数据 → 人工验证 → 训练”
  • 2026(新认知):“不是所有场景都需要更多数据”——推理 SFT 中精品重复优于规模扩展,数据×基座×训练策略三位一体

数据飞轮的流程:

  模型部署
      ↓
  收集真实用户交互 + 失败案例
      ↓
  分析失败模式(按类型、领域、难度分类)
      ↓
  针对性地合成/标注补充数据
      ↓
  质量过滤 + 人工抽检
      ↓
  混入新数据 + 重新训练
      ↓
  评估 → 如有改善,部署新版本
      ↓
  循环 ↑

注意飞轮第三步”分析失败模式”与 KITE 的失败引导生成(18.3.1 节)的呼应:数据飞轮的自动版就是”失败案例 → 定向合成 → 边界筛选”——用失败驱动生成正是对抗能力极化的方案。

18.10.5 精品重复 vs 规模扩展:场景决定策略

数据飞轮隐含一个假设:更多的数据总是更好。最新实证(2026, Nuremberg/Mistral/NVIDIA)挑战了这一假设:在推理 SFT 场景下,对 400 条精品数据训练 128 个 epoch,在 AIME 和 GPQA 上显著优于对 51,200 条数据训练 1 个 epoch。泛化条件研究(2026.04)进一步确认,Long-CoT SFT 在充分训练(多 epoch)+ 高质量数据 + 强基座模型三条件下可以泛化。

这意味着数据飞轮的”补充数据”步骤需要根据场景做不同决策:

场景策略核心驱动力
通用指令跟随多样性驱动的规模扩展覆盖更多任务类型和领域
推理/CoT质量驱动的精品重复推理链结构完整性 > 数据量
领域专精深度驱动的垂直扩展领域知识的准确性和深度

更深层的认知转变是:数据飞轮不仅是补充数据的循环,而是数据×基座×训练策略的联合优化。同一批数据在弱模型上可能无效,在强模型上效果显著;同一个模型用不同训练策略(单 epoch vs 多 epoch)处理同一批数据,结果也可能天壤之别。

18.10.6 推理 SFT 泛化的三个条件

“SFT 只是记忆,RL 才是泛化”——这是 2025 年以来的流行叙事。最新研究(2026.04)对此提出了有力挑战:Long-CoT SFT 在满足以下三个条件时,同样可以实现泛化。

条件 1:充分训练。多 epoch 训练显著优于单 epoch。这与预训练中”数据只看一次”的最佳实践相反——在推理 SFT 中,模型需要反复”消化”推理模式才能内化为泛化能力。

条件 2:高质量推理数据。不是更多的推理数据,而是更好的推理数据。质量标准包括:验证通过率(数学题有正确答案、代码能通过测试)、推理链步骤完整性(参见 18.5.2 节的 MCSQ 框架)、以及解题路径的多样性。

条件 3:强基座模型。弱模型即使获得最好的推理数据也难以泛化——基座能力构成了推理 SFT 的”地板”。这解释了为什么同样的推理数据在 7B 和 70B 模型上效果差距巨大。

三条件的实践意义是:推理 SFT 数据质量的”及格线”比通用 SFT 高得多,但所需数据量可能远低于预期。投入应从”标注更多推理数据”转向”确保每条推理数据都满足三条件”。

推理 SFT 数据质量决策树:

  基座模型是否足够强?
    ├─ 否 → 先提升基座能力(预训练/中训练),数据再好也难泛化
    └─ 是 → 推理数据质量是否达标?(MCSQ 七维度)
               ├─ 否 → 优先提升数据质量,不要扩展规模
               └─ 是 → 训练策略是否充分?(多 epoch + 合适学习率)
                          ├─ 否 → 调整训练配方
                          └─ 是 → 三条件满足,预期可泛化

多模态特化:多模态 SFT 数据:锚定性与多样性

本章关于指令数据的结论(Self-Instruct、Evol-Instruct、拒绝采样、Deita 的”10K 精选 > 100K 粗造”)在 VLM 上都不是直接成立,而是要先过一道闸门:这条指令的回答是否真的依赖图?

锚定性(groundedness)是多模态 SFT 数据的第一质量指标。判定方法很朴素——盲答过滤:把图抽掉,只给文本问同一个模型,若它照样答对,这条样本对视觉能力的贡献接近零,应丢弃或降权。这一步之所以是必备项,是因为多模态指令数据最常见的污染路径,恰恰是把纯文本指令模板套到一张图上:问”图里有什么”配一张风景照,模型靠世界知识就能编出像样的描述,训练信号却全是幻觉强化。不看图也能答对的样本,是在教模型编造。

数据来源代表做法优势主要风险
强模型生成对话LLaVA 范式:给强模型看图 + 文字侧信息生成问答语言质量高、指令多样教师若只看到 caption/box,易产出幻觉标注
VQA / caption 集改写公开标注集改成对话体答案有标注,锚定性强句式单一,易模式坍缩(见 18.7.4)
垂类合成OCR / 文档 / 图表 / 数学几何直接补能力短板需程序化生成与可验证答案

第一行值得展开:LLaVA 范式的关键设计是让教师模型在只拿到文字侧信息(caption、检测框)的前提下提问,这既是它便宜的原因,也是幻觉的来源。工程上的对冲是让教师同时接收 OCR 文本与版面结构(见第 5 章),或要求答案可被程序验证。

三类样本的构造差异:多图样本(对比 / 计数 / 时序)必须给图编号并显式引用,否则模型学不会指称;视频样本先做镜头切分(PySceneDetect 一类)再抽帧,帧数与 token 预算直接挂钩;多轮样本要保证后续轮次的提问依赖前一轮提到的视觉细节,否则退化成多条独立单轮。

规模上依旧成立的是本章的老结论:10K 精选 > 1M 粗造。多模态 SFT 的边际成本更高(标注、存储、视觉 token 训练开销都是),因此 18.6 的数据选择在 VLM 场景下的投入回报比纯文本更高。

(详见第 26 章多模态数据工程)


18.11 工业 SOP 与动手环节

18.11.1 SFT 数据构建的工业 SOP

综合本章内容,工业级 SFT 数据构建 SOP:

1. 需求定义:要补什么能力
2. 种子设计:人工标注 100-500 条种子(含标注指南 + Kappa 校准)
3. 来源规划:
   - 70% Magpie 合成(底盘)
   - 20% Evol-Instruct(难度梯度)
   - 10% 人工(质量锚)
4. Prompt 生成:Magpie 1M + Evol 200K
5. 响应生成:Best-of-N + RM(通用)/ CoT 自反思(推理)
6. 5-stage 过滤管线:去重 ×2 + 长度 + 语言 + IFD
7. 质量评分:Complexity + Quality
8. Deita Selector 精选:100K(数据少时)/ 随机+多样性采样(数据多时)
9. 多样性度量:4-gram ≥ 0.6, InsTag ≥ 1000, 最大簇占比 < 5%
10. 配比设计:任务 × 难度 × 长度三轴混合 + 25% 真实数据锚
11. 去污染:13-gram 匹配 20+ 评测集
12. 训练 + MMLU checkpoint 追踪 + 评估 + 数据飞轮闭环

这条 SOP 的每一环都对应本章的一节:步骤 2 对应 18.2(人工标注与种子),步骤 3-4 对应 18.3(指令合成谱系),步骤 5 对应 18.4(响应生成与拒绝采样),步骤 6 对应 18.4.7 + 18.5(清洗过滤),步骤 7-8 对应 18.6(数据选择),步骤 9 对应 18.7(多样性),步骤 10 对应 18.9(配比),步骤 12 对应 18.10(评估迭代)。

第 13 步:引文本身的巡检。上面十二步覆盖了数据的全部环节,每一步都有对应的质量指标,唯独引文没有——它是唯一一处”写着什么就算什么”、没有任何机器校验的地方。而引文恰好是最容易出静默错误的一类内容:编号抄错一位(2506 与 2606)、链接留着占位符没替换、论文年代标错,三类错误都不会让构建失败,只会让读者点进去撞上 404,或者把一个 2024 年的老结论当成 2026 年的新结论。建议给 SOP 补一条收尾检查,三步即可自动化:

# 1) 逐个取 HTTP 状态码(本章本轮靠这一步抓到 2506.26797 的 404)
grep -oE 'https?://[^)"[:space:]]+' chapter.md | sort -u \
  | xargs -I{} sh -c 'echo "$(curl -s -o /dev/null -w "%{http_code}" -L --max-time 20 "{}")  {}"' \
  | grep -v '^200'

# 2) 全文扫占位符(本章本轮抓到 arxiv.org/abs/example-random-selection)
grep -nE 'example-|TODO|FIXME|XXX|<placeholder' chapter.md

# 3) 编号回源核对标题:abs 页 citation_title 必须与章内写法一致
#    编号抄错但技术描述正确时,只有这一步能发现——
#    §18.6.4 的 UDS 就是这种情况:描述完全对,编号错了一位。
for id in $(grep -oE 'arxiv\.org/abs/[0-9]{4}\.[0-9]{4,5}' chapter.md | cut -d/ -f3 | sort -u); do
  echo "$id :: $(curl -s --max-time 20 "https://arxiv.org/abs/$id" \
    | grep -o 'name="citation_title" content="[^"]*"' | head -1)"
done

第 3 步的价值最高,也最容易被跳过:多数引文错误是”编号错、描述对”,此时全文读起来毫无破绽,只有拿 citation_title 与章内写法逐字比对才会暴露。

18.11.2 动手环节:构建迷你 SFT 数据合成流水线

目标:实现本章介绍的 Evol-Instruct + 拒绝采样的核心流程。

练习 1:Evol-Instruct 指令演化

"""
实现 Evol-Instruct 的演化策略
不需要调用 LLM API,用规则模拟演化过程
"""

import random

EVOLUTION_RULES = {
    "add_constraint": {
        "description": "增加约束条件",
        "templates": [
            "在以下指令基础上,增加约束'{constraint}':\n{instruction}",
        ],
        "constraints": [
            "用不超过 100 字回答",
            "以表格形式呈现",
            "从初学者角度解释",
            "需要给出具体数字和来源",
            "用类比的方式解释",
        ],
    },
    "deepen": {
        "description": "增加深度",
        "suffixes": [
            ",并深入分析其背后的原因",
            ",包括常见的误区和反直觉发现",
            ",给出具体的数据和案例支撑",
            ",并与其他方法进行对比分析",
        ],
    },
    "add_steps": {
        "description": "增加多步骤",
        "templates": [
            "请分步骤完成以下任务:\n1. 先{step1}\n2. 然后{step2}\n3. 最后{step3}",
        ],
    },
}

def evolve_instruction(instruction: str, strategy: str) -> str:
    """对指令进行一次演化"""
    rule = EVOLUTION_RULES.get(strategy)
    if not rule:
        return instruction

    if strategy == "add_constraint":
        constraint = random.choice(rule["constraints"])
        return f"{instruction}(要求:{constraint})"

    elif strategy == "deepen":
        suffix = random.choice(rule["suffixes"])
        return instruction.rstrip("。?!") + suffix

    elif strategy == "add_steps":
        return f"请分步骤完成:首先理解问题,然后{instruction},最后总结要点"

    return instruction

# 种子指令集
seed_instructions = [
    "解释什么是 TCP 协议",
    "用 Python 实现二分查找",
    "对比 SQL 和 NoSQL 数据库",
    "解释什么是微服务架构",
    "写一个正则表达式匹配邮箱",
]

print("=== 指令演化示例 ===\n")
for inst in seed_instructions[:3]:
    print(f"原始: {inst}")
    for strategy in ["add_constraint", "deepen", "add_steps"]:
        evolved = evolve_instruction(inst, strategy)
        print(f"  [{strategy}] → {evolved}")
    print()

# 批量演化
print("=== 批量演化(从 5 条种子生成 15 条)===\n")
evolved_set = set()
for inst in seed_instructions:
    for strategy in ["add_constraint", "deepen", "add_steps"]:
        evolved = evolve_instruction(inst, strategy)
        evolved_set.add(evolved)

for i, inst in enumerate(sorted(evolved_set)[:15], 1):
    print(f"{i:2d}. {inst}")

练习 2:模拟拒绝采样

"""
模拟拒绝采样流程
用启发式规则代替真实的评分模型
"""

import random

def mock_generate(instruction: str, temperature: float = 0.8) -> str:
    """模拟 LLM 生成回复(用模板 + 随机变化)"""
    templates = {
        "good": [
            f"## 回答\n\n关于「{instruction}」:\n\n"
            f"核心概念:这是一个重要的技术主题。\n\n"
            f"### 关键要点\n1. 第一个要点的详细解释\n"
            f"2. 第二个要点及其应用\n3. 第三个要点和注意事项",
        ],
        "medium": [
            f"好的,让我来解释一下。{instruction.replace('解释', '')}是一个"
            f"常见的概念,主要包括几个方面。首先是基本定义,"
            f"其次是应用场景,最后是注意事项。",
        ],
        "bad": [
            f"这个问题很好。嗯,让我想想。{instruction}的话,"
            f"就是那个意思啦,你可以去百度搜搜看。",
            f"好的好的!!!这个{instruction}真是太棒了!!!"
            f"让我来告诉你这个超级厉害的东西!!!",
        ],
    }

    # 温度越高,越可能出现差的回复
    rand = random.random()
    if temperature < 0.6:
        quality = "good" if rand < 0.6 else "medium"
    elif temperature < 1.0:
        quality = "good" if rand < 0.35 else ("medium" if rand < 0.7 else "bad")
    else:
        quality = "good" if rand < 0.2 else ("medium" if rand < 0.5 else "bad")

    return random.choice(templates[quality])

def score_response(instruction: str, response: str) -> float:
    """启发式评分函数"""
    score = 0.5

    # 结构加分
    if "##" in response: score += 0.15
    if "\n1." in response or "\n-" in response: score += 0.1

    # 长度适当加分
    if 100 < len(response) < 1000: score += 0.1

    # 填充语扣分
    fillers = ["好的", "让我想想", "嗯", "啦", "!!!"]
    filler_count = sum(response.count(f) for f in fillers)
    score -= filler_count * 0.05

    # 空洞回复扣分
    if "百度搜" in response or "谷歌搜" in response:
        score -= 0.3

    return max(0, min(1, score))

def rejection_sampling(instruction: str, n_samples: int = 8) -> dict:
    """拒绝采样"""
    candidates = []
    for _ in range(n_samples):
        response = mock_generate(instruction, temperature=0.8)
        score = score_response(instruction, response)
        candidates.append({"response": response, "score": score})

    candidates.sort(key=lambda x: x["score"], reverse=True)
    best = candidates[0]
    worst = candidates[-1]

    return {
        "instruction": instruction,
        "best_response": best["response"][:80] + "...",
        "best_score": round(best["score"], 2),
        "worst_score": round(worst["score"], 2),
        "margin": round(best["score"] - worst["score"], 2),
        "pass": best["score"] >= 0.6,
    }

# 批量拒绝采样
instructions = [
    "解释什么是 Docker 容器",
    "用 Python 实现快速排序",
    "对比 React 和 Vue 框架",
]

print(f"{'指令':<25} {'最佳分':>6} {'最差分':>6} {'Margin':>7} {'通过'}")
print("-" * 55)
for inst in instructions:
    result = rejection_sampling(inst, n_samples=8)
    status = "✅" if result["pass"] else "❌"
    print(f"{inst:<25} {result['best_score']:>6.2f} "
          f"{result['worst_score']:>6.2f} {result['margin']:>7.2f} {status}")

练习 3:SFT 数据质量审计

"""
SFT 数据集质量审计工具
综合本章的多个质量检查维度
"""

import re
from collections import Counter
from typing import List, Dict

def audit_sft_dataset(samples: List[Dict]) -> dict:
    """对 SFT 数据集进行全面质量审计"""

    issues = Counter()
    scores = []
    length_distribution = []

    for sample in samples:
        inst = sample.get("instruction", "")
        resp = sample.get("response", "")
        sample_issues = []

        # --- 格式检查 ---
        # 代码块闭合
        if resp.count("```") % 2 != 0:
            sample_issues.append("代码块未闭合")
        # 长回复无结构
        if len(resp) > 500 and "##" not in resp and "\n-" not in resp and "\n1." not in resp:
            sample_issues.append("长回复缺结构")

        # --- 内容检查 ---
        # 过短
        if len(resp.strip()) < 30:
            sample_issues.append("回复过短")
        # 填充语
        if re.match(r'^(好的|当然|没问题)[,!]', resp):
            sample_issues.append("不必要的前缀")
        # 截断
        if resp and resp.rstrip()[-1:] not in "。.!!??`))】]\n":
            if not resp.rstrip().endswith("```"):
                sample_issues.append("可能被截断")

        # --- 重复检测 ---
        sentences = [s.strip() for s in resp.split("。") if len(s.strip()) > 10]
        if len(sentences) > 3:
            unique = len(set(sentences))
            if unique / len(sentences) < 0.7:
                sample_issues.append("内容重复度高")

        # 记录
        for issue in sample_issues:
            issues[issue] += 1

        score = max(0, 1.0 - len(sample_issues) * 0.2)
        scores.append(score)
        length_distribution.append(len(resp))

    # 生成报告
    n = len(samples)
    avg_score = sum(scores) / max(n, 1)
    pass_rate = sum(1 for s in scores if s >= 0.6) / max(n, 1)

    return {
        "总样本数": n,
        "平均质量分": round(avg_score, 3),
        "通过率": f"{pass_rate*100:.1f}%",
        "平均回复长度": f"{sum(length_distribution)/max(n,1):.0f} 字符",
        "长度范围": f"{min(length_distribution)}-{max(length_distribution)}",
        "问题分布": dict(issues.most_common(10)),
        "建议": (
            "质量良好,可以使用" if pass_rate > 0.85
            else "需要清洗,建议修复上述问题后重新检查" if pass_rate > 0.6
            else "质量较差,建议重新构造数据"
        ),
    }

# === 模拟数据集 ===
mock_dataset = [
    {"instruction": "解释什么是梯度下降", "response":
     "## 梯度下降\n\n梯度下降是一种优化算法,用于最小化损失函数。\n\n"
     "### 基本原理\n参数沿着损失函数梯度的反方向更新:\n"
     "θ = θ - α · ∇L(θ)\n\n其中 α 是学习率。"},

    {"instruction": "写个排序算法", "response":
     "好的!让我来帮你写一个排序算法。\n```python\ndef sort(arr):\n"
     "    return sorted(arr)\n```\n就是这样。"},

    {"instruction": "对比Python和Java", "response":
     "Python和Java都是编程语言"},  # 太短

    {"instruction": "实现二叉树遍历", "response":
     "```python\ndef inorder(root):\n    if not root:\n        return []\n"
     "    return inorder(root.left) + [root.val] + inorder(root.right)\n"},  # 代码块未闭合

    {"instruction": "解释TCP三次握手", "response":
     "## TCP 三次握手\n\nTCP 建立连接需要三次握手:\n\n"
     "1. **SYN**:客户端发送 SYN 包\n"
     "2. **SYN-ACK**:服务端回复 SYN-ACK\n"
     "3. **ACK**:客户端发送 ACK 确认\n\n"
     "三次握手确保了双方都能正常收发数据。"},

    {"instruction": "什么是Docker", "response":
     "Docker 是容器化平台。Docker 是容器化平台。"
     "Docker 可以打包应用。Docker 可以打包应用。"
     "Docker 使用镜像。Docker 使用镜像。"},  # 重复
]

# 执行审计
report = audit_sft_dataset(mock_dataset)
print("=== SFT 数据质量审计报告 ===\n")
for key, value in report.items():
    if key == "问题分布":
        print(f"\n{key}:")
        for issue, count in value.items():
            print(f"  - {issue}: {count} 次 ({count/report['总样本数']*100:.0f}%)")
    else:
        print(f"{key}: {value}")

练习 4:数据飞轮设计

参考本章 18.10.4 节的数据飞轮概念,为你的 SFT 数据集设计一个迭代优化流程:

  1. 用上面的审计工具找出当前数据集的问题
  2. 针对每类问题设计修复策略
  3. 定义”一轮迭代”的具体步骤和验收标准
  4. 估算 3 轮迭代后数据质量的预期提升

本章要点回顾

  1. 数据结构是地基:messages 结构、chat-template、loss mask、Pack 边界四件套缺一不可——忘了 loss mask 会导致”自问自答”死循环
  2. 种子数据先行:精心设计 100-500 条种子数据,定义质量标准;标注指南是基石,Cohen’s Kappa 低于 0.4 时应暂停标注
  3. 人机协同标注:LLM + 批判性审查(ACT)可将成本降低一个数量级,性能差距控制在 2% 以内
  4. 指令合成三代演进:Self-Instruct(种子驱动,52K 饱和)→ Evol-Instruct(进化驱动,难度梯度)→ Magpie(零种子,多样性最高);工业组合是 70% Magpie + 20% Evol + 10% 人工
  5. 迭代合成须防模型崩溃:外部验证器是迭代安全的数学必要条件;能力极化(KITE)说明要在知识边界上定向生成
  6. 响应生成看场景:通用任务用 Best-of-N+RM,推理任务用 CoT 自反思,快速实验用单次贪婪
  7. 拒绝采样的”最佳”是相对的:GRAPE 证明与目标模型分布匹配的回复优于客观评分最高的回复
  8. 好样本有明确标准:指令清晰、回复准确、信息密度高、格式规范四维度;推理数据再加推理链完整性、步骤粒度、可验证性三维度——结构胜于规模
  9. IFD 的 PPL 衡量模型-数据兼容性,而非绝对难度——好数据是相对于模型而言的
  10. 10K 精选 > 100K 粗造(Deita),但大规模场景下”多样性保证 + 随机”是稳健基线(Random Selection 反思)
  11. 多样性需要数据和目标函数双管齐下:CE 损失天然鼓励模式坍缩,需要 SED-SFT 熵正则或 DFT 奖励修正配合
  12. 多轮对话的核心是真实的轮次间依赖;MDS 提供对话级选择框架,分支对话树是更高级的数据组织范式
  13. 评估 SFT 数据不能只看 SFT 得分:RL-readiness 评估揭示 SFT 最优 ≠ RL 后最优(PEAR)
  14. 数据飞轮需要区分场景:通用指令跟随走规模扩展,推理/CoT 走精品重复;推理 SFT 泛化三条件——充分训练 + 高质量数据 + 强基座
  15. 质量控制的视野应扩展到全流程:数据×基座×训练策略的联合优化,而非仅关注数据本身

下一章将深入偏好数据构建,重点拆解 DPO vs RLHF 路线选择与 RLAIF 实践。


🛠️ 本章衍生工具

🧬

数据工程·SFT 指令演化器

《AI 数据全书》第 18 章「SFT 指令工程」配套——① 三代方法谱系:Self-Instruct / Evol-Instruct / Magpie 三卡并排,共享参数台(种子数 / in-context 采样数 / 温度 / 去重阈值 / 每轮生成量)实时驱动饱和曲线(左轴累计通过量、右轴通过率、10% 增长阈值线),⭐ 文献值 ⇄ 本机实算严格分栏对照(52K 对 543/1.2K,明写两列不可直接比较,看的是曲线形状与饱和点的相对移动)+ §18.3.1 饱和表 + 八维对比表 + 多样性条组 + 工业级 70/20/10 配比可点击堆叠条 · ② 演化树工坊:§18.3.4 六算子(add_constraint / deepen / concretize / increase_reasoning / topic_shift / format_change)卡片带书上原生示例对,前 3 个照 §18.11.2 规则忠实实现、后 3 个规则化近似并打 ⚠ 标;线性链 ⇄ 树结构一键切换(§18.3.5 Tree-of-Evolution),深度:广度滑杆(双向进化默认 90:10)+ 分叉数控制,递归渲染节点树并高亮★最优路径、灰化被终止节点,线性链旁挂「不可逆」红字警示,实时算复杂度 / 可解性并点亮三盏终止条件灯(轮数 / 长度 / 演化失败)· ③ 模型崩溃与坍缩检测:可拖「模板化比例」生成 20-200 条语料,三指标卡真算(4-gram 多样性按中文字符切词、最大簇占比 k-means、InsTag 领域覆盖数),§18.7.4 模式坍缩检测器列出 Top5 重复开头与 Top5 结构指纹 (##, \n-+\n1., ```/2) 并给出双门槛切换(书上门槛在小样本下退化);KITE 能力极化双曲线 + 外部验证器开关下的崩溃曲线,并诚实给出一个反直觉教学点:模板化语料因主题词各异,4-gram 多样性可能不降反升 · ④ 质量筛选谱系:拒绝采样温度 × N 双曲线(best-of-N 阈值 1.0)+ §18.4.4 温度对照表 + Deita Selector 四步真算(complexity × quality 打分 → 降序 → 伪 Embedding 余弦去冗余 → top-K,可拖 K 与阈值并逐条看冗余距离)+ Deita 文献结果表 + IFD 三档(字符级二元语言模型 add-k 平滑真算 PPL,阈值可拖)+ Magpie 工业漏斗四段 + Non-Target/Target 谱系 + 复杂性×质量散点;页脚三分诚实性标注 📖 文献值 / 🧮 真算 / ⚠ 教学模型

🔄

数据工坊·SFT/DPO 数据格式转换器

《AI 数据全书》第 18/19 章配套产品原型(系列第一个实用工具,非教学沙盘)——6 种格式智能识别(Alpaca/ShareGPT/OpenAI messages/Completion/TRL DPO/Anthropic HH,JSON 与 JSONL 通吃,置信度+证据链) · 浏览器内真转换(多轮→Alpaca 逐轮拆分vs只取末轮、system 保留/丢弃、DPO↔SFT 互转有损提示) · 质量关卡校验(空轮次/角色顺序/零信号偏好对/长度偏置/完全重复,对应 ch18 数据质量关卡) · 统计条(样本/轮次/token 估算) · 数据全程不出本机