第 13 章 第三部分:SFT 数据

SFT 数据质量的精细控制

好样本标准、清洗过滤、多样性保证与多轮对话

SFT质量 数据清洗 多样性 多轮对话 Layer-wiseGradients MDS RandomSelection

“SFT 数据的质量控制不是一个检查清单,而是一种持续精进的工艺。“


13.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 初始化保持方差稳定 | 所有网络 |

推理 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。这直接挑战了”更多数据总是更好”的直觉,也说明了推理数据质量评估的重要性。

推理数据质量的经济筛选(2026-06 新发现)

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

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

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


13.2 数据清洗与过滤

自动化质量检查管线

"""
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,
    )

IFD 方法:Instruction-Following Difficulty

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 × 任务类型 → 不同任务的"学习区"阈值不同

数据选择方法谱系

IFD 是数据选择方法的一个代表——用模型自身信号衡量数据价值。但数据选择远不止 IFD 一种,近年来的研究已经形成了一个完整的方法谱系,可以从是否需要目标模型参与这一维度进行分类:

数据选择方法谱系

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

Non-Target 方法

IFD 已在上面详述,核心是用 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)做精准精选,得到最终训练集。

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

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

UDS(Utility-Diversity Sampling,清华自动化系,ICML 2026)提出了在线批选择范式——在每个训练 step 动态决定哪些样本进入当前 batch,将”先选后训”改为”边训边选”。核心理念:利用模型前向传播的 logits 信号同时评估样本效用与多样性,无需验证集、参考模型或昂贵的样本级梯度计算。

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

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

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

谱分析统一理论: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(),
        }
    }

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

章节前面推荐了 IFD/Superfiltering/MoDS/CaR/LESS 等多种复杂数据选择方法。2026 年的反思性研究 Rethinking Data Selection at Scale: Random Selection is Almost All You Need 提供了反向证据,挑战了”必须用复杂选择方法”的根叙事。

核心论点:

反直觉发现:

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

关键证据:

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

章节决策树(修订版):

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

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

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

章节影响:

  • 避免对复杂数据选择的过度推荐
  • 在”何时用随机、何时用复杂方法”上提供平衡视角
  • 提醒读者:数据规模、任务多样性、验证集代表性是关键决策因素

多轮对话选择:MDS 框架

章节 13.4 节”多轮对话数据的特殊考量”主要采用启发式过滤。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 鲁棒性优势明显解决”长对话质量下滑”问题

章节意义:

  • 章节 13.4 节从启发式过滤升级为原则化方案
  • MDS 提供”两阶段评估”的工程框架
  • 与章节 12.2 多样性保证方法形成衔接(多样性 + 对话级选择)

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


13.3 数据多样性的保证

多样性的多个维度

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

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

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

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

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

使用 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 "建议增加稀疏领域的样本"
        ),
    }

避免合成数据中的多样性不足

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

检测方法:

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),
    }

多样性的训练目标维度

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

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 损失仍会坍缩,正则化的目标函数配合单一数据也无法凭空创造多样性。


13.4 多轮对话数据的特殊考量

多轮依赖关系的构造

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

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

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

上下文一致性检查

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,
    }

从线性到分支:多轮数据的结构化升级

上面讨论的真多轮 vs 假多轮区分,以及上下文一致性检查,解决的是多轮对话数据的基础质量问题。更进一步,Conversation Forests(2025)提出了一种超越线性序列的数据组织方式——分支对话树。

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

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

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

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


13.5 SFT 的评估与迭代

如何评估 SFT 数据的效果

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

1. 数据影响力分析

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

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

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

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

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

上述前三种方法都从”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 留出了越好的起点

2. 类别覆盖度评估

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

认知变化:从”一次性标完”到”持续数据飞轮”

SFT 数据迭代的认知演变

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

数据飞轮的流程:

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

精品重复 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)处理同一批数据,结果也可能天壤之别。

推理 SFT 数据的三个泛化条件

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

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

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

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

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

推理 SFT 数据质量决策树:

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

动手环节:对一批 SFT 数据进行质量审计

目标:综合运用本章介绍的质量检查方法,对一批模拟的 SFT 数据进行全面审计。

练习 1:自动化质量审计报告

"""
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}")

练习 2:多样性分析

"""
分析 SFT 数据集的多样性
用简单的词袋模型代替 embedding
"""

from collections import Counter

def analyze_diversity(instructions: List[str]) -> dict:
    """分析指令集的多样性"""

    # 词频分析
    all_words = []
    for inst in instructions:
        words = re.findall(r'[\u4e00-\u9fff]+|[a-zA-Z]+', inst)
        all_words.extend(words)

    word_freq = Counter(all_words)

    # 指令开头模式
    openings = [inst[:10] for inst in instructions]
    opening_freq = Counter(openings)
    repetitive_openings = sum(1 for _, c in opening_freq.items() if c > 1)

    # 长度多样性
    lengths = [len(inst) for inst in instructions]
    length_cv = (max(lengths) - min(lengths)) / (sum(lengths) / len(lengths))

    # 关键词覆盖度
    topic_keywords = {"代码", "数学", "写作", "翻译", "分析",
                      "解释", "设计", "优化", "对比", "总结"}
    covered = sum(1 for kw in topic_keywords
                  if any(kw in inst for inst in instructions))

    return {
        "指令数": len(instructions),
        "独特词数": len(word_freq),
        "重复开头数": repetitive_openings,
        "长度变异系数": round(length_cv, 2),
        "主题覆盖度": f"{covered}/{len(topic_keywords)}",
        "多样性评估": (
            "良好" if covered >= 7 and length_cv > 1.0
            else "需要改善" if covered >= 4
            else "严重不足"
        ),
    }

# 测试
test_instructions = [
    "解释什么是梯度下降",
    "用 Python 实现快速排序",
    "对比 REST API 和 GraphQL",
    "翻译以下段落为英文",
    "分析这份数据的趋势",
    "写一篇关于 AI 的文章",
    "设计一个分布式系统方案",
    "优化这段代码的性能",
    "总结这篇论文的核心观点",
    "解释量子计算的基本原理",
]

result = analyze_diversity(test_instructions)
print("=== 多样性分析 ===\n")
for k, v in result.items():
    print(f"  {k}: {v}")

练习 3:数据飞轮设计

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

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

本章要点回顾

  1. 好的 SFT 样本四要素:指令清晰、回复准确、信息密度高、格式规范
  2. 推理 SFT 数据有额外的质量维度:推理链完整性、步骤粒度合理性、中间结论可验证性——结构胜于规模
  3. 自动化质量检查应覆盖格式、内容、一致性三个层面
  4. IFD 方法用模型自身的困惑度筛选最有价值的训练样本——PPL 衡量的是模型-数据兼容性,而非绝对难度
  5. 多样性需要数据和目标函数双管齐下:CE 损失天然鼓励模式坍缩,需要 SED-SFT 熵正则或 DFT 奖励修正配合
  6. 多轮对话的核心是真实的轮次间依赖;分支对话树是更高级的数据组织范式
  7. 评估 SFT 数据不能只看 SFT 得分:RL-readiness 评估揭示 SFT 最优 ≠ RL 后最优
  8. 数据飞轮需要区分场景:通用指令跟随走规模扩展,推理/CoT 走精品重复
  9. 推理 SFT 泛化的三条件:充分训练(多 epoch)+ 高质量推理数据 + 强基座模型
  10. 质量控制的视野应扩展到全流程:数据×基座×训练策略的联合优化,而非仅关注数据本身