第 29 章 篇六:基础设施与质量闭环

数据质量评估闭环与迭代

以评估驱动的数据迭代闭环:指标、看板、回归与持续优化。

质量闭环 评估迭代 Data-Centric 数据价值评估 DAS

没有闭环的数据工程是盲修。

评测掉分 → 找数据 → 改质加量 → 再评测,是工业级团队的肌肉记忆。

“You can’t improve what you can’t measure.” —— Peter Drucker

在数据工程中,这句话需要补充一个前提:你还得知道该测什么。


29.1 评估的认知基础:数据好不好,不能只看 Loss

29.1.1 训练 Loss 不等于数据质量

这是一个需要反复强调的认知:预训练 loss 的下降不代表数据质量好。

训练 loss 本质上衡量的是模型对当前数据分布的拟合程度。如果你的训练数据充斥着重复的模板化内容(比如电商网站千篇一律的产品描述),模型的 loss 会下降得很漂亮——因为这些数据的熵很低,容易预测。但这样训练出的模型在开放式生成任务上会表现得索然无味。

反过来,如果你的数据多样性极高、包含大量专业领域的低频知识,训练 loss 可能下降得很慢,但模型的下游表现可能非常好。

因此,评估数据质量需要跳出”盯着 loss 曲线”的思维定式,建立一套多维度的评估体系,并通过下游任务验证来闭环。

29.1.2 数据质量对 Scaling Laws 的量化影响

“数据不好会影响模型表现”是个直觉。但具体影响多大?有没有定量模型?ACL 2025 的一项分析 400 多个模型训练数据的大规模实证研究揭示了一个关键现象——次级缩放(sub-optimal scaling):当训练数据中冗余信息(高数据密度)过多时,增加数据量或模型规模带来的性能提升会显著减速,偏离经典 Scaling Law 预测的幂律曲线。

这给了我们一个有用的心智模型:

  • 经典 Scaling Law 假设数据是”足够好的”——只要加量就能提升
  • 次级缩放定律 修正了这个假设——数据冗余会让 scaling 变”钝”,你以为加了 2 倍数据应该提升 X%,实际只提升了 0.6X%
  • 实践含义:在盯着”数据量够不够”之前,先检查”数据重复度高不高”。后文的重复率统计不只是监控指标,而是直接关系到 scaling 效率的核心变量

这也解释了为什么去重在整个数据管线中的地位如此关键——它不只是”清理噪声”,而是在维护 Scaling Law 的有效性。

29.1.3 从”多指标并列”到”多信号融合”

建立”多维度评估体系”时会遇到一个自然的问题:当你有文本复杂性、信息量、格式规范性、教育价值等七八个质量维度的分数时,如何把它们合成一个决策?

最朴素的做法是设阈值——每个维度都过了就留下。但这要么太严(所有维度都高的数据很少),要么太松(只要有一个维度高就留下)。实际操作中,大量时间花在了调多个阈值的组合上。

FIRE(Flexible Integration of Data Quality Ratings,EMNLP 2025)提出了一种更系统的方案:将多个异构质量评级器对齐到统一的特征空间,通过渐进式数据选择进行迭代精炼。实验表明,FIRE 用不到 37.5% 的数据量即可达到随机选择全量数据的同等性能。

这代表了数据评估的一个范式升级——从”在 dashboard 上看多个指标然后拍脑袋”走向”让框架自动学习多信号的最优融合权重”。监控系统负责采集各维度指标,而 FIRE 这类框架负责将指标转化为数据选择决策。


29.2 评估指标体系:三层架构与评测集

29.2.1 三层评估体系

工业级评估体系分三层,按频率与成本递增:

┌──────────────────────────────────┐
│ L3 真人评测(每月)   成本极高      │
├──────────────────────────────────┤
│ L2 LLM-as-Judge(每周)成本中     │
├──────────────────────────────────┤
│ L1 自动评测(每 ckpt) 成本低      │
└──────────────────────────────────┘

三层并非互斥,而是相互校准:L1 提供高频信号,L2 填补生成质量的评估空白,L3 提供金标并校准前两层。任何一层缺失都会让闭环失真——只有 L1 会漏掉开放式生成的退化,只有 L3 则反馈周期太慢。

29.2.2 L1:自动评测(每 checkpoint)

频率:每个训练 checkpoint(约每 1000 steps) 成本:单次 < 1 小时,< 1 美元 用途:快速筛选 checkpoint,监控训练趋势

评测集:

  • MMLU(5-shot,4 选 1)
  • GSM8K(5-shot CoT)
  • HumanEval(pass@1)
  • IFEval(指令遵循准确率)

特点:

  • 全自动,无人介入
  • 分数稳定可复现
  • 适合横向对比 checkpoint

29.2.3 L2:LLM-as-Judge(每周)

频率:每周一次(或重大 checkpoint) 成本:单次 4-8 小时,10-100 美元 用途:评估生成质量、多轮对话、复杂任务

评测集:

  • MT-Bench(GPT-4 评分 1-10)
  • AlpacaEval 2.0(胜率 vs GPT-4)
  • Arena-Hard(与 GPT-4 对比胜率)
  • τ-Bench(Agent 任务成功率)

特点:

  • 需要调用 GPT-4 等强模型
  • 分数有偏差(详见 29.3 节)
  • 适合细粒度质量评估

准入口径:一致率必须做机会校正

L2 上线前的标准动作是拿一批人工标注的校准集,测 judge 与人的一致性。这里有个容易踩的坑:直接报 exact-match agreement(原始一致率)会系统性高估 judge 的判别能力,因为它没有扣除”瞎猜也能对”的部分。

迄今规模最大的系统性评测(Reliability without Validity,arXiv:2606.19544)覆盖 9 家供应商的 21 个 judge,在 MT-Bench / JudgeBench / RewardBench 上跑了 118 组实验、约 54.1 万条判定,结论是 kappa deflation 在整个 cohort 上普遍存在——在 MT-Bench 上,exact match 比 Cohen’s κ 高出 33–41 个百分点。也就是说一个 raw agreement 看起来 85% 的 judge,换成 κ 可能只有 0.45 左右。

工程含义有三条:

  1. 报 κ,不报 raw。校准集上必须给出 Cohen’s κ(或等价的机会校正指标);若沿用 raw agreement,“>85% 可接受”这类阈值会把一个实际中等水平的 judge 判为达标。
  2. 阈值要自己测,不要抄。κ 的绝对值随任务类别数与标签分布变化,跨任务不可移植。阈值应在自有校准集上重新测定,并把测定过程写进数据卡片。
  3. 一致 ≠ 无偏。该研究报告了一个”一致性—偏差悖论”:两个已投产的 judge 在 test–retest 上信度 > 0.95,却同时带有 > 0.10 的位置偏差。稳定复现的 judge 也可能稳定地错,因此偏差审计不能因为”重测很稳”就省略。

同一研究还指出 judge 在不同 benchmark 上的排名最多能挪动 14 位——“哪个 judge 最好”依赖于用哪个评测集来问,选 judge 时应当在自己实际要评的分布上测,而不是照搬公开榜单。

29.2.4 L3:真人评测(每月)

频率:每月一次(或发版前) 成本:单次数千-数万美元 用途:金标评测,校准 L1/L2

流程:

  • 50-100 名标注员
  • 每人评 100-500 条
  • A/B 对比(与基线模型)
  • 双盲交叉校验

特点:

  • 最接近真实用户体验
  • 慢、贵、不可复现
  • 用于最终发布决策

29.2.5 关键评测集速查

评测集类型规模指标频率
MMLU学术知识14K 题4 选 1 准确率L1
MMLU-Pro学术知识+12K 题10 选 1 准确率L1
GSM8K数学1.3K 题CoT 准确率L1
MATH数学5K 题准确率L1
HumanEval代码164 题pass@1L1
MBPP代码974 题pass@1L1
IFEval指令遵循500+准确率L1
MT-Bench多轮对话80GPT-4 评分L2
AlpacaEval 2.0通用805胜率L2
Arena-Hard通用500胜率L2
TruthfulQA诚实817真实率L1
XSTest安全250拒答率L1
τ-BenchAgent165任务成功率L2
BFCL工具调用2000+准确率L2
LiveCodeBench代码(抗污染)周更新pass@1L1

评测集选用原则:

  1. 抗污染优先。MMLU、GSM8K 等老评测集污染严重,必须配合 LiveCodeBench、AIME 等持续更新的评测。
  2. 多维度覆盖。单测一个维度(如只测 MMLU)会过拟合,必须测知识/推理/代码/安全/工具至少 5 个维度。
  3. 量级匹配。小模型(< 7B)用 GSM8K,大模型(> 70B)必须用 AIME 等更难的评测。

29.2.6 Proxy Benchmark 的选择

不是所有 benchmark 都适合用来评估数据质量。好的 proxy benchmark 应该满足:

  1. 对数据变化敏感:数据管线的微调能在该 benchmark 上体现出差异
  2. 评估稳定:多次运行结果波动小
  3. 覆盖多能力维度:不偏向某个特定领域
  4. 不容易被”刷”:对过拟合有一定抵抗力

FineWeb 团队的 benchmark 组合(经过实践验证):

Benchmark评估能力数据敏感度稳定性
HellaSwag常识推理高高
ARC-Challenge科学推理高中
WinoGrande语义理解中中
PIQA物理直觉中高
MMLU (5-shot)知识广度中中
OpenBookQA开放知识推理高中

对于中文场景,建议加入:

Benchmark评估能力备注
C-Eval中文知识广度类似中文 MMLU
CMMLU中文多学科覆盖更广
CLUE-WSC中文指代消解中文语言理解

29.2.7 数据统计与可视化:训练前的全貌检查

在正式训练之前,你需要对数据的全貌有清晰的了解。一个好的数据 dashboard 应该覆盖以下维度。

1. 语言分布

"""
数据语言分布统计与可视化
"""
from collections import Counter
from typing import List

def analyze_language_distribution(
    lang_labels: List[str],
    lang_scores: List[float],
    target_distribution: dict = None
) -> dict:
    """分析语言分布并与目标分布对比"""
    total = len(lang_labels)
    actual_dist = Counter(lang_labels)
    actual_pct = {lang: count / total * 100
                  for lang, count in actual_dist.most_common(20)}

    report = {
        "total_documents": total,
        "unique_languages": len(actual_dist),
        "top_20_languages": actual_pct,
    }

    # 与目标分布对比
    if target_distribution:
        gaps = {}
        for lang, target_pct in target_distribution.items():
            actual = actual_pct.get(lang, 0)
            gap = actual - target_pct
            gaps[lang] = {
                "target": target_pct,
                "actual": round(actual, 2),
                "gap": round(gap, 2),
                "status": "OK" if abs(gap) < 2 else ("WARN" if abs(gap) < 5 else "FAIL")
            }
        report["distribution_gaps"] = gaps

    # 低置信度文档比例
    low_confidence = sum(1 for s in lang_scores if s < 0.8) / total * 100
    report["low_confidence_pct"] = round(low_confidence, 2)

    return report

2. 文档长度分布

长度分布是数据质量的重要晴雨表。典型的健康分布应该呈对数正态分布,如果出现异常的双峰或长尾,往往暗示着数据问题。

import numpy as np

def analyze_length_distribution(lengths: list, bins: int = 50) -> dict:
    """分析文档长度分布"""
    lengths = np.array(lengths)

    stats = {
        "count": len(lengths),
        "mean": float(np.mean(lengths)),
        "median": float(np.median(lengths)),
        "std": float(np.std(lengths)),
        "min": int(np.min(lengths)),
        "max": int(np.max(lengths)),
        "p10": float(np.percentile(lengths, 10)),
        "p90": float(np.percentile(lengths, 90)),
        "p99": float(np.percentile(lengths, 99)),
    }

    # 异常检测
    # 1. 极短文档比例(可能是提取失败)
    very_short = (lengths < 50).sum() / len(lengths) * 100
    stats["very_short_pct"] = round(very_short, 2)

    # 2. 极长文档比例(可能是拼接错误)
    very_long = (lengths > 50000).sum() / len(lengths) * 100
    stats["very_long_pct"] = round(very_long, 2)

    # 3. 可疑的"精确长度"聚集(可能是截断或模板)
    length_counts = Counter(lengths)
    suspicious_peaks = [
        (length, count) for length, count in length_counts.most_common(20)
        if count > len(lengths) * 0.01  # 超过 1% 的文档长度完全相同
    ]
    stats["suspicious_length_peaks"] = suspicious_peaks

    return stats

3. 质量分数分布

def analyze_quality_distribution(quality_scores: list, labels: list = None) -> dict:
    """分析质量分数分布,可按领域细分"""
    scores = np.array(quality_scores)

    overall = {
        "mean": float(np.mean(scores)),
        "median": float(np.median(scores)),
        "std": float(np.std(scores)),
        "below_0.3": float((scores < 0.3).sum() / len(scores) * 100),
        "between_0.3_0.7": float(((scores >= 0.3) & (scores < 0.7)).sum() / len(scores) * 100),
        "above_0.7": float((scores >= 0.7).sum() / len(scores) * 100),
    }

    if labels:
        by_domain = {}
        for label in set(labels):
            domain_scores = [s for s, l in zip(quality_scores, labels) if l == label]
            by_domain[label] = {
                "count": len(domain_scores),
                "mean": round(np.mean(domain_scores), 3),
                "median": round(np.median(domain_scores), 3),
            }
        overall["by_domain"] = by_domain

    return overall

4. 重复率统计

def analyze_duplication_stats(
    docs: List[str],
    n_gram_size: int = 5,
    sample_size: int = 10000
) -> dict:
    """快速评估数据集的重复程度:采样 + n-gram 统计"""
    import random

    if len(docs) > sample_size:
        sample = random.sample(docs, sample_size)
    else:
        sample = docs

    # 文档级精确重复
    doc_hashes = Counter()
    for doc in sample:
        doc_hash = hash(doc.strip())
        doc_hashes[doc_hash] += 1
    exact_dup_rate = sum(c - 1 for c in doc_hashes.values() if c > 1) / len(sample) * 100

    # N-gram 重复率
    all_ngrams = Counter()
    for doc in sample:
        tokens = doc.split()
        for i in range(len(tokens) - n_gram_size + 1):
            ngram = tuple(tokens[i:i + n_gram_size])
            all_ngrams[ngram] += 1

    total_ngrams = sum(all_ngrams.values())
    unique_ngrams = len(all_ngrams)
    ngram_dup_rate = (1 - unique_ngrams / total_ngrams) * 100 if total_ngrams > 0 else 0

    return {
        "exact_document_dup_rate": round(exact_dup_rate, 2),
        f"{n_gram_size}-gram_dup_rate": round(ngram_dup_rate, 2),
        "unique_ngram_ratio": round(unique_ngrams / total_ngrams, 4) if total_ngrams > 0 else 0,
        "total_ngrams_sampled": total_ngrams,
        "sample_size": len(sample),
    }

29.2.8 数据管线中的质量监控点

一条成熟的数据管线应该在关键节点设置质量监控。以下是推荐的监控点和对应的异常检测规则:

管线阶段监控指标正常范围异常信号
爬取/下载下载成功率>95%某个 CC 快照成功率骤降
HTML 提取空文档比例<5%提取器对某类网页失效
语言识别低置信度比例<15%混合语言页面增多
启发式过滤过滤掉的比例30-60%过低=过滤太松,过高=过滤太严
去重去重率20-40%过高=数据源重复度大
质量过滤保留比例20-50%分布发生显著漂移
最终数据集平均文档长度500-3000 tokens突然变短/变长

29.3 LLM-as-Judge 的偏差治理

L2 层依赖 LLM-as-Judge,但 judge 本身有系统性的偏差。上线前必须理解并治理四大陷阱。

29.3.1 Position Bias(位置偏差)

LLM judge 倾向于选第一个响应。

测试:

def test_position_bias(judge, prompt, response_a, response_b):
    r1 = judge(prompt, response_a, response_b)  # A 在前
    r2 = judge(prompt, response_b, response_a)  # B 在前
    # 如果 r1 偏向 A 且 r2 偏向 B,有 Position Bias
    return r1 == "A" and r2 == "B"

应对:双向评审,分歧的丢弃。

幅度能大到什么程度? 21 个 judge 的 MT-Bench 偏差审计(arXiv:2606.19544,Table 3)给出了全 cohort 的实测分布,位置偏差定义为 ∣P(A wins)−0.5∣|P(A\ \text{wins})-0.5|(AB/BA 双向评估):最低的 Gemini 2.5 Pro 为 0.002,最高的 Qwen 3 8B 为 0.192,原文的措辞是”span nearly two orders of magnitude”。0.192 意味着接近五分之一的判定由呈现顺序决定,而不是由内容质量决定——这是双向评审属于必做项而非可选优化的实测依据。

同一批数据里还有一条对选型更有用的观察:同家族内部的差距可以极大,Gemini 2.5 Pro(0.002)与 Gemini 2.5 Flash(0.125)相差 70 倍。按品牌或按系列挑 judge 是靠不住的。原文的总体趋势是推理型与前沿档位偏差更低、成本优化档位偏差更高,但这个趋势并不单调——GPT-5.4-mini(0.013)就明显低于 GPT-5.4(0.083),档位高低不能直接换算成偏差大小。

29.3.2 Length Bias(长度偏差)

LLM judge 倾向于选更长的响应。早期实测显示 GPT-4 judge 对长响应的偏好约 +15%。

⚠️ 这个数字存在口径争议,不要当常数用。 21-judge 大规模评测(arXiv:2606.19544)在统一的单一 pairwise 评分 rubric 下测得整个 cohort 的 verbosity bias 均小于 0.011,与”+15%“相差一个量级。两者不必然矛盾——长度偏差对 prompt 措辞、评分是否给出显式 rubric、以及成对比较还是单独打分高度敏感:在开放式无 rubric 的评分里长度偏差可以很大,在约束良好的 pairwise rubric 下可以接近消失。

实践上的结论是:长度偏差的幅度是你自己那套 judge prompt 的属性,不是模型的固有常数,必须在自己的配置上实测。若实测已经很小,长度归一化反而可能引入新的偏差。

该论文给出的四条实践建议里,第 (4) 条写得很直白:在标准 pairwise rubric 下”no longer foreground verbosity bias”。但作者在同一段与 Limitations 里划了两道范围——结论只成立于单一 pairwise 模板与固定的长度差操作化之下,原文是”we do not claim that verbosity bias has been eliminated under arbitrary rubric or task variation”,且 rubric 设计(是否给参考答案、是否要求推理)已知会与偏差轮廓交互。“先测再治”的口径不变,变化的是读者现在对实测会得到什么有了先验:大概率测出来很小,于是结论多半是不归一化。

应对:

  • prompt 中明确”不要因长度选响应”
  • 长度归一化评分
  • 加入”短而精”的对照
  • 先测再治:在自有 prompt 配置上测出长度偏差幅度,再决定是否需要归一化

29.3.3 Self-preference(自我偏好)

LLM judge 倾向于选同族的响应——GPT-4 偏好 GPT 系列的输出。实测 GPT-4 judge 评 GPT-4 vs Claude 的输出,GPT-4 胜率虚高 5-10%。

应对:

  • 用多个不同家族的 judge 交叉验证
  • 重要评测用 L3 真人评测校准

29.3.4 格式偏好

LLM judge 偏好特定格式(如 markdown 列表、加粗)。

测试:

# 同一内容,不同格式
plain = "1. 苹果 2. 香蕉 3. 橙子"
markdown = "- **苹果**\n- **香蕉**\n- **橙子**"
# 如果 judge 总是选 markdown,说明有格式偏好

应对:

  • 评分 prompt 中明确”忽略格式”
  • 用多个 judge 风格对冲

极端情况:关键词堆砌可以把 judge 打到不如随机

格式偏好的危害不止于”排版好看的空洞答案得高分”。一份针对合规判定场景的四轴基准(Principle-Bench,arXiv:2608.14329)给出了一个相当刺眼的数字:一个 120B 的开放权重 judge 在正常输入上准确率 0.94–0.96,但面对针对性堆砌合规关键词的输入时,准确率掉 47 个百分点至 0.27——低于随机猜测。作者称之为 “compliance theatre”(合规剧场):文本把该出现的词都摆齐了,judge 就判它合规。

更能说明问题的是同一研究的交叉验证:换一个不同模型家族的 judge 来评同一批被堆砌的样本,两个 judge 的一致性只有 Cohen’s κ = 0.16。分歧集中在被攻击的那一批样本上,说明失效来自模型侧而非语料标注——不是题出得有歧义,是 judge 真的被骗了。

两点实践含义:

  • “剥离格式后重评”应作为默认流程,而不是有空才做的优化项
  • 模型规模不构成抗攻击的保证。120B 也会栽,且栽得比小模型更隐蔽(它在正常输入上表现最好,最容易被信任)

引用边界:该基准的语料是 168 个加密资产金融推广场景、映射到英国 FCA 的两条原则,属于特定合规域的窄基准,且作者自述语料由单一模型家族生成、未做外部标注者验证。这里借用的是”对抗扰动能让 judge 崩到随机以下”这一现象与它的四轴测法,不宜把 0.74 → 0.27 当作通用 judge 的普适退化幅度。

29.3.5 把四大陷阱变成可测量项

上述四节给的是”有哪些坑 + 怎么绕”,但真正上线时要回答的是一个量化问题:手上这个 judge 到底有多抗偏差,够不够格评这个任务? 两条现成路径:

路径一:四轴可信度体检

Principle-Bench 提出的评估框架有普适价值(哪怕它的语料是窄域的)——任何 judge 上线前都该在四个轴上过一遍:

轴测什么怎么造样本
accuracy(准确性)正常输入上与金标的一致性原始校准集
paraphrase(改写鲁棒性)同义改写后判定是否翻转保义改写
adversarial(对抗鲁棒性)针对性堆砌/诱导后是否被骗关键词堆砌、格式灌水
calibration(校准)置信度是否可信(ECE)原始集 + Platt scaling 等后校准

落地判据:任一轴上的下降超过预设阈值即判定该 judge 不适合当前任务,而不是只看总体准确率达标就放行。该研究在六种方法(关键词计数、三种 embedder、开放权重 judge、校准级联)上的结论是没有任何一种能在四轴上全面占优——这意味着”选一个最强的 judge”往往不成立,需要按轴取舍或做级联。

路径二:偏差注入基准

JudgeBiasBench(arXiv:2603.08091)走的是另一条路:用受控的偏差注入流水线,把 12 类代表性偏差按 4 个维度的分类体系造成评测样本,从而定量比较不同 judge 对各类偏差的敏感度。它同时覆盖生成式与判别式两种 judge 形态——这一点对工程选型有用,因为两者的偏差谱系并不相同。该工作还给出了缓解侧的做法:把偏差相关属性显式引入训练(生成式用 RL、判别式用对比学习),在基本保住通用评估能力的前提下降低偏差。

选用建议:日常迭代用 JudgeBiasBench 这类偏差注入基准做回归(偏差类型全、可自动化);judge 换代或进入高风险场景(合规、安全、对外发布决策)时补一次四轴体检,尤其是对抗轴与校准轴。两者都不能替代 L3 真人评测——它们测的是 judge 的鲁棒性,不是模型的真实质量。

路径三:把条目质量拆成两个不同的属性

上面两条测的都是 judge 的抗偏差能力,还有一层常被混为一谈——rubric 条目(或评测题目)自己的质量。CalibratedRubric(arXiv:2607.29252)指出,自动构造 rubric 的常规流程依赖”judge 全体一致”加二元方差过滤,而这两个筛子分不开”可测量”与”有信息量”:一条所有人都能稳定判对的条目可测量性极高,信息量却可以是零,因为它区分不出任何能力差异。它的做法是把两件事拆开估——可测量性用 Beta–Bernoulli 一致性后验估计每个 rubric 的 measurability,信息量用 IRT 下的子模覆盖目标挑选条目、组装紧凑的 rubric bank。实测收益是 JudgmentBench 上与人类金标的 κ 从 0.604 提到 0.743,FinResearchBench 上只需 49 条 rubric 就达到随机选 131 条的相关性目标。

对本章的用处:“这条测得准”与”这条值得测”是两个问题。29.2.6 挑 proxy benchmark 的四条原则里,“评估稳定”对应可测量性,“对数据变化敏感”对应信息量,两者要分开测,一个总体准确率替代不了。

路径四:把三项同时成立的联合概率当作准入指标

前面三条路径都在分别测某一个属性(抗偏差、可测量性、信息量)。但一个 judge 要”可信”,需要三件事同时对同一批复判成立:复现性(同样的输入再判一次还一样)、顺序不变性(把 A/B 对调不影响结果)、准确性(与金标一致)。三者缺一不可,分开测会漏掉它们的交集。

trustworthy verdict rate(T)就是把这三者合成一个量的指标——一次判定同时满足复现、顺序不变、准确这三者的联合概率(arXiv:2610.12083,AACL-IJCNLP 2026 Main)。该研究的压力测试覆盖 6 个前沿模型 × 4 个基准 × 5 种 prompt 格式 × 2 种呈现顺序 × 3 个采样温度 × 每条件 10 次重复,三条实测结论值得记:

  1. 温度设为 0 也挡不住改判——同一批复判在 temperature=0 下仍会翻转,“设成 0 就确定性了”是错觉;
  2. 位置对调会翻掉 challenging 任务上的多数判决——顺序不变性是独立的一条轴,不是准确性的附属品;
  3. 最”确定性”的那个 judge 是靠稳定地重复错误判决拿到完美一致性的,与金标只有 51% 的一致率。

第 3 条正是 29.2.3 已经警告过的”一致性—偏差悖论”的量化形态:重测信度高不能作为放行依据,因为存在”稳定地错”这个失效模式,且它会把一致性指标拉满。T 的另一个用处是能推出位置偏差对准确率施加的理论上界——顺序不变性不达标时,准确率再高也有天花板,这条把 29.3.1 的位置偏差从”要治的毛病”升级成”对准确率有硬性上限的约束”。

由 T 还带出两条可直接执行的改进:

  • 把 pairwise 胜率换成 holistic rubric 打分,对可信度的提升大于任何单一 prompt 格式层面的干预——即”改判分范式”比”调 prompt”划算;
  • 粒度下探一层:可靠性是逐条样本(item-specific)的属性,不是模型级的。因此准入不应只报一个总体值,而应按 item 分桶看。这是对 29.2.3 末句”哪个 judge 最好依赖用哪个评测集”的再细分——不止要按评测集分,还要按条目分。

29.4 Reward Hacking 检测

RLVR 训练中,模型可能学会”骗过 verifier”而非真正解决问题:

正确行为:
prompt: "求 12 × 15"
response: "12 × 15 = 180"
reward: 1.0

Hacking 行为:
prompt: "求 12 × 15"
response: "180"  # 直接吐答案,没有推理
reward: 1.0(verifier 只检查最终答案)

Reward hacking 与数据污染、judge 偏差并称评估闭环的三大”信号失真源”——分数涨了,能力没涨。以下是四种互补的检测方法。

29.4.1 检测方法 1:reward 突增告警

class RewardAnomalyDetector:
    def __init__(self, window=100):
        self.history = []
        self.window = window

    def update(self, reward):
        self.history.append(reward)
        if len(self.history) < 2 * self.window:
            return False

        recent = np.mean(self.history[-self.window:])
        baseline = np.mean(self.history[-2*self.window:-self.window])

        # 突增 > 20% 告警
        if recent > baseline * 1.2:
            return True
        return False

29.4.2 检测方法 2:rollout 模式聚类

def detect_hacking(rollouts, threshold=0.5):
    """检测 rollout 是否过于同质化"""
    embeddings = encode(rollouts)
    labels = cluster(embeddings, n_clusters=20)

    for label in set(labels):
        ratio = sum(labels == label) / len(labels)
        if ratio > threshold:
            print(f"Warning: cluster {label} 占比 {ratio:.0%}, 疑似 hacking")
            return True
    return False

29.4.3 检测方法 3:Cross-Verifier

def cross_verify(primary_verifier, secondary_verifier, response):
    v1 = primary_verifier(response)
    v2 = secondary_verifier(response)

    if v1 == 1 and v2 == 0:
        return "hacking_suspected"
    elif v1 == 0 and v2 == 1:
        return "verifier_bug"
    else:
        return "consistent"

29.4.4 检测方法 4:人审 sample

def human_audit_hacking(rollouts_with_reward_1, sample_size=100):
    """对 reward=1 的 rollout 抽样人审"""
    samples = random.sample(rollouts_with_reward_1, sample_size)

    true_positive = 0
    for s in samples:
        # 人审判断是否真的解决问题
        if human_judge(s) == "correct":
            true_positive += 1

    accuracy = true_positive / sample_size
    if accuracy < 0.8:
        print(f"Warning: 人审准确率 {accuracy:.0%}, 疑似 hacking")
    return accuracy

29.5 消融实验与数据归因

评估指标告诉你”好不好”,消融与归因告诉你”为什么”。这是从评估走向数据调整的桥梁。

29.5.1 小模型快速验证法

大模型预训练动辄需要数千到数万 GPU·小时。你不可能每次调整一个过滤阈值就重新跑一遍完整训练。因此,业界广泛采用”小模型代理”方案:用一个参数量小得多的模型(通常 400M-1.5B)在你的数据上快速训练,然后评估这个小模型的表现来间接判断数据质量。

基本流程:

数据管线 A  ──→  训练 400M 模型  ──→  在 proxy benchmark 上评估
                                          ↓
数据管线 B  ──→  训练 400M 模型  ──→  在 proxy benchmark 上评估
                                          ↓
                                    比较 A vs B,选优
                                          ↓
                                  用最优管线训练大模型

FineWeb 团队正是用这种方法系统化地优化了他们的数据管线。他们训练了超过几十个 1.8B 参数的模型,每个模型训练 28B token(大约需要几天时间),然后在一组精心挑选的 benchmark 上比较不同数据管线的效果。

实操:搭建一个小模型验证流程

"""
小模型快速验证框架
用于对比不同数据管线的效果
"""
import json
from dataclasses import dataclass, field
from typing import List, Dict

@dataclass
class DataPipelineConfig:
    """数据管线配置"""
    name: str
    description: str
    # 关键参数
    text_extractor: str = "trafilatura"       # 提取器
    lang_threshold: float = 0.65              # 语言识别阈值
    min_doc_length: int = 200                 # 最短文档长度
    max_doc_length: int = 100000              # 最长文档长度
    dedup_threshold: float = 0.8              # 去重阈值
    quality_filter: str = "fasttext"          # 质量分类器
    quality_threshold: float = 0.5            # 质量阈值
    # 配比
    domain_weights: Dict[str, float] = field(default_factory=lambda: {
        "web": 0.65, "code": 0.15, "academic": 0.10,
        "books": 0.05, "math": 0.05
    })

@dataclass
class AblationExperiment:
    """消融实验配置"""
    pipeline_config: DataPipelineConfig
    model_size: str = "400M"           # 代理模型大小
    training_tokens: str = "10B"       # 训练 token 数
    eval_benchmarks: List[str] = field(default_factory=lambda: [
        "hellaswag", "arc_challenge", "winogrande",
        "piqa", "mmlu_5shot", "ceval"
    ])

def design_ablation_matrix(base_config: DataPipelineConfig) -> List[AblationExperiment]:
    """
    设计消融实验矩阵
    核心原则:每次只变一个变量
    """
    experiments = []

    # 基准实验
    experiments.append(AblationExperiment(
        pipeline_config=base_config
    ))

    # 变量 1:语言识别阈值
    for threshold in [0.5, 0.6, 0.7, 0.8]:
        config = DataPipelineConfig(
            **{**base_config.__dict__,
               "name": f"lang_threshold_{threshold}",
               "lang_threshold": threshold}
        )
        experiments.append(AblationExperiment(pipeline_config=config))

    # 变量 2:质量过滤阈值
    for threshold in [0.3, 0.5, 0.7, 0.9]:
        config = DataPipelineConfig(
            **{**base_config.__dict__,
               "name": f"quality_threshold_{threshold}",
               "quality_threshold": threshold}
        )
        experiments.append(AblationExperiment(pipeline_config=config))

    # 变量 3:去重阈值
    for threshold in [0.7, 0.8, 0.9, 1.0]:
        config = DataPipelineConfig(
            **{**base_config.__dict__,
               "name": f"dedup_threshold_{threshold}",
               "dedup_threshold": threshold}
        )
        experiments.append(AblationExperiment(pipeline_config=config))

    return experiments

def analyze_results(results: Dict[str, Dict[str, float]]) -> str:
    """
    分析消融实验结果
    results: {experiment_name: {benchmark: score}}
    """
    report = []
    report.append("=" * 60)
    report.append("消融实验结果分析")
    report.append("=" * 60)

    baseline = results.get("baseline", {})
    if not baseline:
        return "Error: no baseline found"

    for exp_name, scores in results.items():
        if exp_name == "baseline":
            continue

        deltas = {}
        for bench, score in scores.items():
            if bench in baseline:
                delta = score - baseline[bench]
                deltas[bench] = delta

        avg_delta = sum(deltas.values()) / len(deltas) if deltas else 0
        report.append(f"\n{exp_name}: avg delta = {avg_delta:+.2f}%")
        for bench, delta in sorted(deltas.items(), key=lambda x: abs(x[1]), reverse=True):
            direction = "↑" if delta > 0 else "↓"
            report.append(f"  {bench}: {delta:+.2f}% {direction}")

    return "\n".join(report)

29.5.1b 不训练也能预判:分布对齐评分 DAS

29.5.1 整节的方法有一个共同前提:必须先训练一个代理模型。FineWeb 团队为了定一个去重阈值训了几十个 1.8B 模型、每个 28B token。候选数据集少时这可行,但当面对「上百个候选数据源,先筛出值得训的十个」这类问题时,代理模型本身就成了瓶颈——你要为每一个候选都付一次训练成本。

DataPrep-Bench(北大 OpenDCAI 联合港中文、浙大、上海算法创新研究院发布)为此给出了一条零训练成本的路径:DAS(Distributional Alignment Score)。

做法只有三步:提取候选集的文本特征 → 与目标领域的代理集比对 → 用 MMD(最大均值差异) 计算分布距离。距离越小,判定训练价值越高。

实测表现(以与真实训练收益的相关系数 r 计,跨 6 个域):

  • 6 个域中 4 个取得最强跨模型相关性
  • 是唯一在 Math / Science / Medical 三个域上同时做到 r > 0.70 的指标,超过所有参评的质量 / 多样性 / 启发式评估器
  • ⚠️ 金融域是它的天花板:r 仅 0.42–0.50,且所有参评指标在该域集体失效

适用边界:DAS 是排序器,不是裁判。它能帮你在零算力成本下把候选池从 100 缩到 10,但不能替代 29.5.1 的代理模型做最终决策——尤其在金融这类 DAS 失效的域上。

📌 与 29.5.1 的关系:串联而非替代。 DAS 做第一轮粗筛(零成本),代理模型做第二轮精选(花算力)。这与篇一 ch00「廉价过滤优先于昂贵过滤」是同一条纪律在评估环节的体现——先便宜后昂贵。29.5.2 即将讨论的「小模型代理的可靠性问题」,恰好也说明了为什么不能只靠代理模型一条腿走路。

⚠️ 待核实:DataPrep-Bench 的 6 个域是否含中文,本书本轮未确认。若不含,在中文语料上引用 DAS 时须注明「未见中文域验证」。

29.5.2 小模型代理的可靠性问题

2025 年底,一篇引发广泛讨论的论文《Can Small Training Runs Reliably Guide Data Curation?》(arXiv: 2512.24503)对小模型代理方法提出了系统性质疑。

⚠️ 本节前一版把这篇论文的机制与补丁都记错了,以下是订正后的版本。 原版写的是「排名在不同随机种子下不稳定」,并据此开出「每个管线跑 3 个种子」的对策。回源 arXiv:2512.24503 全文(v1 HTML,2025-12-30)后,论文的主因不是种子方差,而是“为了公平比较,所有候选配方都用同一套固定超参”这个协议本身;种子/随机性是论文明确讨论的次要项(§2 脚注指向附录 D.2.1,正文图表的阴影区是 3 个种子的标准误),不是主因。机制记错会连带着把补丁也开错——按原版写法,预算会花在”多跑几个种子”上,而真正的修复几乎不花钱。

现象:用标准学习率(GPT2-Small 上常用的 3×10−43\times10^{-4})训练代理模型时,代理规模上的数据配方排名与”每个配方各自调过超参的大模型”上的排名严重不一致,出现大量会导致次优决策的重新排序。

机制:最优训练配置本身是数据依赖的——batch size、学习率、优化器在实际工程中都是按数据调的(论文举的例子是 GPT-3 按梯度噪声尺度定 batch size)。因此数据配方评估的正确目标应该是”该配方在它自己的最优超参下能达到的性能”,而不是”在某个预设的、可能对这份数据并不最优的超参下的性能”。固定超参协议与大模型开发流程也相背离——后者把超参寻优当作标准步骤。实测的敏感性:学习率的微小调整就能翻转配方排名。论文用 DCLM 与其更激进去重变体 DCLM-dedup-GS 做了对照,在”接近最优的两个相似学习率”之间切换,胜出的配方就会反转;在 batch size / weight decay / token-per-parameter 比的扫描中,排名对学习率最敏感。

补丁(成本几乎为零,优先级最高):用极小学习率训练代理模型——比常规小 1–2 个量级,论文给出的量级是 10−510^{-5} 或 10−610^{-6}。

  • 理论上,对随机特征模型可证明这样做保持数据集排序:小学习率下 SGD 的验证损失变化可分解为”反映真实数据质量差距的确定性漂移项”与”随机更新带来的方差项”,学习率足够小时漂移项主导方差项;
  • 实测上,用 10−510^{-5}(而非 3×10−43\times10^{-4})训练 GPT2-125M 作代理,代理到 Pythia-1B 目标(目标模型各自做过数据特化超参寻优)的配方排名 Spearman 相关性提升到 > 0.95,覆盖 (232)=253\binom{23}{2}=253 个配方对;
  • 23 个配方覆盖数据策展的四个维度(去重强度、过滤阈值、混合配比、来源选择一类);
  • 附带收益:极小学习率下排名出现得很早且稳定——3k 步(约完整训练的 27%)就能达到 0.95 以上,这直接把代理实验的算力预算砍掉一截。

应对策略(重排优先级):

  1. 【新增,成本最低、优先做】把代理模型的学习率降到 10−5∼10−610^{-5}\sim10^{-6} 再训练,排名稳定性问题在源头上就被削掉一大半
  2. 多种子训练:每个管线至少跑 3 个随机种子,报告均值和方差(处理残余的随机性项,非主因)
  3. 多 benchmark 交叉验证:不要只看一个 benchmark 的排名
  4. 关注大幅差异:只有在差距超过 2-3% 时才做决策
  5. 模块化消融:让消融实验的成本从指数级变成线性级(见 29.5.3)
  6. 【保留但降档】增大代理模型:如果预算允许,用 1.5B 比 400M 更可靠——但注意它解决的是”代理容量不足”,不是”超参协议错误”,两者是独立的失效路径

⚠️ 待回源:本节前一版给出的”3 个种子方差可达 ±1.5%“与”组间差 0.5-2%“两个数字,在论文 v1 HTML 全文(14.6 万字符)中逐字检索未命中(1.5 零命中)。论文正文确实用了 3 个种子,但只以标准误 / bootstrap CI 的形式出现在图表阴影区,未给出 ±1.5% 这一具体数值。该数字暂按未证实处理,不删除但不再作为决策依据;引用它时须自行回源。

💡 反直觉发现

小模型代理法并不像看起来那么可靠。一个在 400M 模型上”赢了”0.8% 的数据管线,放到 70B 模型上可能反而差了 0.3%。原因在于小模型和大模型对数据的”口味”不完全一致——大模型对数据多样性的需求更高,而小模型可能更偏好低熵、模式明确的数据。

代理模型的下一步:从同构缩小到影响力保持

上述策略都是在”用小模型近似大模型”的框架内打补丁。IProX(ICLR 2026,arXiv:2602.17835)提出了一个更根本的思路:问题不在于代理模型太小,而在于简单缩小的小模型没有保持与大模型一致的数据偏好方向。

IProX 的两阶段框架:

  1. 通过影响力保持的低秩近似压缩目标模型——不是训练一个新的小模型,而是从大模型本身出发,用低秩分解保留梯度影响力方向
  2. 通过模型梯度和输出对齐进一步精炼代理

核心区别在于:传统代理是”同架构缩小”(比如从 70B 缩到 400M),参数量变了,数据偏好也跟着变了。IProX 的代理是”影响力保持压缩”——参数量变小,但对数据的梯度响应方向尽量与大模型一致。

需要注意的是,IProX 目前主要在 SFT 数据选择上得到了验证,是否能直接迁移到预训练数据消融场景还有待更多实验。但它指明了一个方向:代理模型的可靠性问题,可能需要在”如何构建代理”而非”如何使用代理”的层面上解决。

29.5.3 消融实验的设计方法

控制变量法

数据消融的核心是控制变量:在其他条件不变的前提下,改变一个数据管线参数,观察其对模型表现的影响。

常见的消融维度:

消融维度典型变量关键观察指标
提取器选择trafilatura vs resiliparse vs jusText文档质量、信息保留率
语言过滤阈值0.5 → 0.6 → 0.7 → 0.8数据量 vs 语言纯度
质量分类器无过滤 / fastText / LLM 打分benchmark 分数变化
质量阈值保留 top-10% / 30% / 50% / 80%质量-多样性权衡
去重粒度文档级 / 段落级 / 仅精确去重去重率 vs 多样性损失
领域配比web 50-80% / code 10-30% / math 5-15%各能力维度分数
预训练优化器SGD / AdamW / Sharpness-Aware后训练效果、量化后性能

其中”预训练优化器”一行来自 Sharpness-Aware Pretraining(arXiv: 2605.02105)的发现:预训练阶段的优化器选择(不仅是学习率)会影响模型在 post-training 和量化后的能力保留程度。如果你的 pipeline 包含后训练阶段,建议将优化器类型纳入消融变量列表。

分层消融 vs 全因子消融

在消融实验设计中,一个常见的误区是试图做”全因子设计”——如果你有 5 个维度,每个维度 4 个选项,那就是 4^5 = 1024 个实验。这在大模型数据工程中完全不现实。

推荐的分层消融策略:

第 1 轮:粗筛(每个维度 2-3 个选项,单独消融)
   → 确定每个维度的大致最优范围

第 2 轮:交互实验(选择最可能存在交互效应的 2-3 个维度,组合消融)
   → 发现维度间的交互效应

第 3 轮:精调(在最优范围内细粒度搜索)
   → 确定最终参数

可扩展的模块化消融方法

2024 年 EMNLP 上发表的《Scalable Data Ablation Approximations》提出了一种创新方法:

核心思路:

  1. 将训练数据分成 N 个独立的”模块”(module),每个模块大约 1-5B token
  2. 每个模块独立训练一个小模型
  3. 通过权重合并(weight merging)来近似”在组合数据上训练”的效果

优势:

  • 新增一个数据模块,只需要训练一个新的小模型,然后与已有模型合并
  • 成本从 O(2^N) 降到 O(N),是线性增长而非指数增长
  • 已有模块的训练结果可以复用

限制:

  • 权重合并只是近似,对于强交互效应可能不准确
  • 在模块数量很多时,合并效果会退化
  • 目前主要在 1B 以下模型上验证过

另一个消融维度:数据子集选择策略

前面讨论的消融维度都是”改管线参数”——调阈值、换提取器、改配比。但还有一个正交的维度往往被忽视:在相同管线输出中,用不同策略选择训练子集。

一项针对 19 种数据采样方法的大规模对比研究(ICLR 2026)发现:

  • AskLLM(用指令微调 LLM 直接评估每条训练样本的质量)在质量维度表现最优
  • Density Sampling(基于数据分布建模选择多样性样本)在覆盖维度表现最优
  • 两者组合,可以用 10-30% 的数据达到全量训练的同等性能

这给消融实验设计带来了一个新视角:除了问”哪个管线参数最好”,还可以问”同一管线的输出数据中,用什么策略选子集最高效”。后者的实验成本更低——不需要重新跑数据管线,只需要在已有数据上做采样。

需要注意的是,该研究的验证规模主要在 1-7B 模型上,对于更大规模模型是否同样成立还需要更多实验。但 10-30% 的数据效率数字,至少表明在消融实验的”快速筛选”阶段,数据子集选择是一个值得尝试的低成本方向。

OptiMer:无需训练的配比优化

OptiMer(Cao et al.,京都电信研究院,arXiv:2603.28858)提出了一种完全不同的范式:它不通过消融实验来发现最优配比,而是通过”分布向量合并”来推导最优权重。具体做法:先为每种数据类型单独训练一个小模型,提取每种数据对应的”分布向量”(可理解为模型对该类数据的参数偏移),然后用贝叶斯优化将这些向量合并,得到最优组合权重。这个方法的核心优势是无需在候选配比上反复训练——整个搜索过程的计算成本仅为传统方法的 1/15~1/35。在实践中,如果你的预算只够跑 1-2 个完整训练,OptiMer 提供了在训练前就获得良好初始配比的途径。

29.5.4 工业级消融方法论:SmolLM3 的经验

Jeremy Howard 在 SmolLM3 预训练中完整公开了消融方法论,提供了罕见的工业级实操参考。

消融成本:消融+调试消耗 161,280 GPU-hours,超过主训练运行(276,480 GPU-hours)的 50%。这打破了”消融只是主训练的小开销”的假设——在严谨的预训练中,消融成本通常占总计算预算的 30-50%。

评估策略:CF vs MCF 的互补使用:

评估格式全称特点适用阶段
CFCloze Formulation(填空式)信噪比高、方差低训练早期(快速筛选)
MCFMultiple-Choice Formulation(多选式)信号更准确、但方差较高训练中后期(精细比较)

关键发现:CF 适合早期快速筛选方向(因为方差低,少量实验就能得到可靠信号),MCF 适合后期精细比较(因为信号更准确,能区分细微差异)。两者应配合使用而非只选其一。

消融设计原则:

  1. 每个决策都要有消融支撑——不是为了”严谨”,而是为了 debug。如果最终模型出问题,没有消融记录就无从回溯
  2. 固定架构变数据 vs 固定数据变架构的互补设计——前者隔离数据效应,后者隔离架构效应
  3. 100+ 消融实验是大规模预训练的常态,不是过度工程

长上下文数据的消融注意事项

长上下文持续预训练(LCCP)有其特殊的消融挑战。工业级模型(>10B 参数,>1T token 训练量)的 LCCP 动态与小规模实验显著不同:

  1. 小模型上”有效”的长度和课程策略可能在工业规模上失效或需要调整
  2. 工业级 LCCP 需要更长的训练步数才能观察到短上下文能力的退化
  3. 建议用比目标规模小 10 倍的模型做 proxy,但训练 token 数应保持与目标规模同量级(而非按比例缩减),以捕捉长上下文训练的特有动态

数据难度与后训练效果的关系

“数据是否越难越好”?张景昭等人(FAIC 2026)的初步结论表明:

  • 中训练阶段:中等难度的数据通常优于极难数据——与模型当前能力过于脱节的极难数据容易导致训练不稳定
  • 后训练(RL)阶段:数据的难度分布需要重新考虑,太简单的数据可能无法为 RL 提供足够的挑战信号

这与 FineWeb-Edu 的”教育价值分级”形成有趣对比——教育价值高(难度高)的数据在 SFT 阶段有帮助,但在中训练阶段不一定最优。

29.5.5 数据归因 TDA

消融回答”管线参数的影响”,TDA(Training Data Attribution)则回答更细粒度的问题:哪些训练数据对某个测试样本贡献最大——模型在测试样本 x_test 上的正确/错误,是由哪些训练样本导致的?

方法 1:消融实验(最简单)

def ablation_attribution(train_data, test_data, subset_size=1000):
    """逐子集消融,看哪个子集对测试集影响最大"""
    baseline_score = evaluate(train_on(train_data), test_data)

    attributions = []
    for i in range(0, len(train_data), subset_size):
        # 移除第 i 个子集
        reduced = train_data[:i] + train_data[i+subset_size:]
        reduced_score = evaluate(train_on(reduced), test_data)

        # 分数下降越多,说明该子集越重要
        attribution = baseline_score - reduced_score
        attributions.append((i, attribution))

    return attributions

优点:直观、准确。缺点:成本极高(要训练 N 次)。

方法 2:Influence Function(EK-FAC)

用梯度近似计算”如果移除某条训练数据,测试 loss 会变化多少”:

def influence_function(train_sample, test_sample, model):
    # 1. 计算训练样本的梯度
    train_grad = compute_gradient(model, train_sample)

    # 2. 计算测试样本的梯度
    test_grad = compute_gradient(model, test_sample)

    # 3. 用 EK-FAC 近似 Hessian 逆
    hessian_inv = ekfac_approximation(model)

    # 4. Influence = -test_grad · Hessian_inv · train_grad
    influence = -test_grad @ hessian_inv @ train_grad

    return influence

优点:只需训练 1 次。缺点:EK-FAC 近似有误差,对大模型不友好。

方法 3:TracIn

用训练过程中的 checkpoint 序列计算影响:

def tracin(train_sample, test_sample, checkpoints):
    """用多个 checkpoint 计算影响"""
    influence = 0
    for ckpt in checkpoints:
        model = load(ckpt)
        train_grad = compute_gradient(model, train_sample)
        test_grad = compute_gradient(model, test_sample)
        influence += np.dot(train_grad, test_grad)
    return influence

优点:实现简单,可并行。缺点:需要保存多个 checkpoint。

两条新路径(研究阶段)

上述三种方法有一个共同的隐含前提:你得先指定一个归因目标——某个测试样本、某个验证集、某个下游任务。这在”MMLU 掉分了,去找是哪批数据的问题”这种场景下没问题,但预训练阶段常常反过来:你想知道某批语料在整个训练过程中起了什么作用,而”代表通用能力的验证集”本身就选不出来。2026 年出现了两条绕开这个前提的路径,都还在研究阶段。

(1)task-agnostic 归因:把”最终参数”当作归因目标

arXiv:2608.13515 换了个定义:一条样本的影响力 = 它的梯度更新让参数向该次预训练最终参数靠近了多少(平方距离的减少量)。目标从”某个任务的 loss”换成”这次训练自己的终点”,于是不需要挑验证集,也不需要重训——直接用训练时已经存下的中间 checkpoint 估计。

在 Pythia 与 PolyPythia 系列的 18 组配置上,它给出了一个跨配置稳定的时序结论:训练早期文学类数据与参数轨迹的对齐度更高,后期换成 STEM 类数据更高。这个”交叉点”对课程设计是直接的证据——数据配比该随训练阶段变,而不是全程一个混合比例。

成熟度:验证规模止于 Pythia 系列(最大 12B 量级的稠密模型),尚未见到在前沿 MoE 规模上的复现。“最终参数”作为归因目标也有个哲学上的循环——它衡量的是”对这次训练的贡献”,而不是”对模型好不好的贡献”,若这次训练本身跑偏了,高影响力数据也可能是坏数据。用它做训练动态分析可以,用它做数据取舍需谨慎。

(2)forward-only 归因:把算力从查询时挪到训练时

Influence Function 的工程痛点是要在推理时做反向传播和 Hessian 近似,每来一个查询都得付一次。arXiv:2511.19803 的做法是把这笔计算搬到训练期:训练时用短程梯度传播模拟每条样本的参数影响并存下来,之后任何查询只用前向评估即可读出归因结果。它保留了与 Influence Function 相同的一阶反事实目标,但去掉了 per-query 的反向传播。

这个取舍匹配一类真实场景:模型可能服务数十亿次查询,但训练数据源是固定有限的——比如一个 LLM 要向特定出版方(论文举的例子是《纽约时报》)核算贡献并付费,归因需要能反复、实时地跑。

⚠️ 引用边界要看清:该论文报告”在 LOO / LDS 指标上匹配或超过 TRAK,且推理成本低数个量级”,但实验是在标准 MLP 基准上做的,不是语言模型。它目前是一个有理论框架支撑的可扩展性主张,而非已在 LLM 规模上验证的替代方案。不要因为”免反向传播”就把它当成 EK-FAC 在大模型上的现成替代品。

当前选用建议:生产归因仍以消融、EK-FAC、TracIn 为主(消融做关键决策、EK-FAC 做大规模、TracIn 做轻量筛查);上面两条适合作为研究性探索或训练动态分析的补充手段,纳入生产前需要自己在目标规模上验证一遍。

TDA 的应用

  1. 找”毒数据”:测试集掉分时,用 TDA 找出贡献为负的训练样本
  2. 数据去重:TDA 分数相近的样本冗余,去除其一
  3. 数据增强:TDA 分数高的样本模式,用类似模式合成更多数据
  4. 冷启动:新能力快速找到”高价值”训练样本

29.6 污染检测与评估可靠性

评估结果可靠的前提是评估数据没有出现在训练集中。污染检测是评估闭环的守门员。

29.6.1 评估集与训练数据的隔离

Benchmark 数据泄露(data contamination)已经成为 LLM 评估中最头疼的问题之一。2024-2025 年间,大量研究揭示了泄露的普遍性和严重性:

  • GPT-4 的论文中承认部分 benchmark 数据可能存在于训练集中
  • 有研究发现,在某些模型上,被污染的 benchmark 子集上的分数比未污染的高出 5-15%
  • 即使做了”去污染”处理,也不能保证完全干净——因为 benchmark 的变体、改述版本可能通过其他渠道进入训练集

29.6.1b 污染不止一条通道:三通道模型与 fail-closed 编码

上面三条说的全是训练期暴露(benchmark 数据进了训练集)。这是最重要的一条通道,但不是唯一一条——尤其在 Agent 评测里,另外两条通道本章此前完全没有覆盖。

arXiv:2610.05830(2026-10-05)提出的审计框架按”需要什么证据才能支持”把污染主张分成三条独立通道:

通道含义本章现有方法是否覆盖
训练期暴露题目/答案在训练阶段就进了模型✅ 覆盖(n-gram / DCR / CDD / Self-Critique / 动态评测全在这一条)
评测期检索评测当时模型能检索到题目或含解法的产物❌ 章内无对应方法
管线 / scaffold 泄漏prompt 模板、工具配置、评测脚手架本身带进答案信息❌ 章内无对应方法

每条通道按 open / partial / closed / unknown 四档编码,并采用 fail-closed 规则——证据不足一律记为 unknown,而不是记为”未污染”。这一条对工程习惯是反的:日常报告里”没检出污染”常常被顺手写成”无污染”,fail-closed 要求的是把它写成”未知”。

该研究在 9 个 HAL(Holistic Agent Leaderboard)配置 × 3 条通道共 27 项评估上跑了一遍,没有一项能判为 closed,其中 4 个配置确认了事件。这个结果本身就说明:Agent 榜上的污染主张,绝大多数处在”证据不足”状态,而不是”已排除”。

复评怎么做:行为侧必须用同仓库匹配对照(outcome-blind、same-repository matched-control)+ 对称的 prompt 泄漏筛查,并且报告区间估计而非点估计。论文给的警示案例很典型:SWE-bench Verified 上一个已报告的 file-localization 差距,经对称筛查与配对完整性排除后保留 100 对,GPT-4.1 显示出 +10.0 点的成对加权 Top-3 基准关联差距,但预设的配对 bootstrap 与事后按仓库平衡的分析两者的 95% 区间都包含零,结论只能是”不成立”。只报 +10.0 这个点估计会得出完全相反的结论。

scorer 本身也要过闸:这条与 29.4 的 reward hacking 检测是同一道防线在评测侧的镜像——RL 侧防的是策略骗过 verifier,评测侧防的是 verifier 本身不可信。该研究中,用于复现的 scorer 没有通过它自己的验证闸:相对人类共识标签,两个模型的 scorer 灵敏度都不足以确立,DeepSeek-V4-Flash 在 correct-gold 比较上两次都是假阳。因此任何用于判定”成功/失败”的 scorer(包括 29.4.3 Cross-Verifier 里的 secondary verifier)都必须先用人标签校准灵敏度,通过后才准用于下结论。

论文自陈的三条边界也值得照抄:它不确立训练数据成员关系、不确立污染的普遍程度、不确立分数虚高的幅度。审计框架只回答”这条主张有没有被关闭”。

29.6.2 n-gram 去污染检测

"""
训练数据去污染检测
在数据管线的最后阶段运行,确保评测集不泄露到训练集
"""
from typing import List, Set
import hashlib

class DecontaminationChecker:
    """多层次去污染检测器"""
    def __init__(self, benchmark_data: List[str], ngram_size: int = 13):
        self.ngram_size = ngram_size
        # 构建 benchmark 的 n-gram 索引
        self.benchmark_ngrams: Set[str] = set()
        self.benchmark_hashes: Set[str] = set()

        for item in benchmark_data:
            # 精确匹配
            self.benchmark_hashes.add(
                hashlib.sha256(item.strip().encode()).hexdigest()
            )
            # N-gram 匹配
            tokens = item.lower().split()
            for i in range(len(tokens) - ngram_size + 1):
                ngram = " ".join(tokens[i:i + ngram_size])
                self.benchmark_ngrams.add(ngram)

    def check_document(self, doc: str) -> dict:
        """检查单个文档是否包含 benchmark 数据"""
        result = {
            "exact_match": False,
            "ngram_overlap_count": 0,
            "ngram_overlap_ratio": 0.0,
            "is_contaminated": False,
        }

        # 层次 1:精确匹配
        doc_hash = hashlib.sha256(doc.strip().encode()).hexdigest()
        if doc_hash in self.benchmark_hashes:
            result["exact_match"] = True
            result["is_contaminated"] = True
            return result

        # 层次 2:N-gram 重叠
        tokens = doc.lower().split()
        total_ngrams = max(1, len(tokens) - self.ngram_size + 1)
        overlap_count = 0

        for i in range(total_ngrams):
            ngram = " ".join(tokens[i:i + self.ngram_size])
            if ngram in self.benchmark_ngrams:
                overlap_count += 1

        overlap_ratio = overlap_count / total_ngrams
        result["ngram_overlap_count"] = overlap_count
        result["ngram_overlap_ratio"] = round(overlap_ratio, 4)

        # 阈值判定:超过 70% 的 13-gram 重叠视为污染
        # 这个阈值参考了 GPT-3 论文和 Llama 的做法
        if overlap_ratio > 0.7:
            result["is_contaminated"] = True

        return result

    def check_dataset(self, docs: List[str]) -> dict:
        """检查整个数据集的污染情况"""
        total = len(docs)
        contaminated_count = 0
        contaminated_indices = []

        for i, doc in enumerate(docs):
            result = self.check_document(doc)
            if result["is_contaminated"]:
                contaminated_count += 1
                contaminated_indices.append(i)

        return {
            "total_documents": total,
            "contaminated_documents": contaminated_count,
            "contamination_rate": round(contaminated_count / total * 100, 4),
            "contaminated_indices": contaminated_indices[:100],
        }

29.6.3 推理模型时代的新挑战:RL 训练会”洗白”污染信号

n-gram 检测是当前最广泛使用的去污染方案,GPT-3 和 Llama 都采用了类似思路。但 2025-2026 年的研究揭示了三个根本性的局限。

1. RL 训练会系统性掩盖污染信号(“洗白”效应)

ICLR 2026 的一项研究(Wang, Li, Ko, Zhang — UIUC ASTRAL Group,arXiv:2510.02386)系统性地揭示了大型推理模型(LRM)的污染检测脆弱性:

Stage I:SFT 污染 → GRPO 洗白

  • 初始:SFT 阶段引入的污染可被检测——Min-K%/Max-K%/LOSS 类概率方法 AUROC ~73%,参考模型法 LiRA 达 89%
  • 仅 1 轮 GRPO 训练:所有方法的 AUROC 塌回 50%(随机猜测)
  • 156 步 GRPO 后:reference-free 方法(Loss、Min-K、Max-K)已接近完全失效
  • 实验数据:Qwen2.5-7B-Instruct 上 GRPO 训练后 AUROC 普遍下降 8.99%-19.84%;Llama-3.1-8B-Instruct 上下降 7.22%-32.66%
  • 关键:contaminated models 在 GRPO 之后仍保持 +7.14% 的平均性能虚高——RL 保留了污染收益但隐藏了污染证据

机制解释(PPO 式重要性采样 + clipping):

  • 对 member 样本已被记牢、概率高,clipping 给梯度加上限阻止继续推高
  • 对 non-member 无此约束,概率相对抬升
  • 一拉一压让 member/non-member 分布重叠,检测器赖以工作的”见过样本概率更高”信号被磨平

Stage II:CoT SFT 污染高级 LRM

  • 直接对强 LRM 做 CoT SFT 污染:pass@1 平均涨 8.82%
  • 所有 10 种主流检测方法 AUROC 仍 ~50%——几乎无法检测

这意味着对于使用了 RL 训练的推理模型,传统的去污染检测方法可能完全失效。

对实践清单的修正:

  1. 单一静态检测器已不够——需要时间维度 + 训练阶段维度的多重检测(训练前/训练中/训练后各做一次)
  2. Time-based partition(按时间切分数据/任务,看 cutoff 前后的性能悬崖)仍是 2026 年最稳健的检测方法
  3. reference-based 检测(如 LiRA)虽然准确但不能跨 SFT→RL 边界保持可靠性——仅在 SFT 后或 RL 之前才有意义
  4. 对 LRM 评估应默认假设存在检测盲区,在解读榜单分数时持保留态度

2. 从二元判定到连续风险量化

传统检测给出的是”污染/不污染”的二元结论。DCR(Data Contamination Risk,EMNLP 2025)提出了一种更细粒度的方案:在语义、信息、数据、标签四个级别检测污染风险,通过模糊推理系统合成统一的 DCR Factor。经调整后的准确率与未污染基准线误差控制在 4% 以内,已在 9 个 LLM(0.5B-72B)上验证通过。

DCR 的价值不在于替代 n-gram 检测,而在于提供了一种”风险调整后分数”的思路——不是简单地去掉疑似污染的样本,而是根据污染风险对评估分数做折扣。

3. 从数据比对到模型内视

另一个互补方向来自 NA-PDD(EMNLP 2025):通过分析模型内部神经元激活模式的差异,判断某条数据是否出现在预训练集中。这种”模型内视”方法不需要访问训练数据或重新训练模型,对于文本被改写、变体等传统 n-gram 方法难以捕捉的间接污染尤其有效。

⚠️ 上面这段需要加三层限定,否则会把一个尚未成熟的手段当成常规选项用。 2026 年的一项专门审计(arXiv:2608.12652)直接指出”the natural way to do this does not work”——朴素地在残差流上做线性探针读”熟悉度”是无效的:

  1. 基线深度剖面并不平坦。 真实 transformer 上,基线深度剖面在 temporal split 上最大可跨 29.1 个准确率点;且非平坦程度与两组题目的表面差异相关(6 次审计上 r = 0.87)。不做对照,探针读到的就是这条基线,而不是污染。
  2. 可用协议至少含四项,每一项都是被实测排除掉更简单的替代后才留下的:深度剖面的零和对比 + 以层级匹配的安慰剂基线重中心化 + 标签置换零假设检验 + 参考集规模为可疑集的两倍。其中控制集大小本身就是结果的一部分——实测假阳率随分析师自己的控制集规模在 0.03–0.99 之间变化,必须报告。
  3. 这类方法会拒答。 在 4 个匹配良好的 Pile arms 上一律返回 null,对 temporal split 拒绝给出结论而不给一个数字。论文自己也提醒:null 结果只是”缺席”的弱证据。

因此定位应当是:在严格对照设计下才可用的补充手段(与 29.5.5「两条新路径(研究阶段)」同级),而不是与 n-gram 并列的常规选项。另有一条对所有”模型内视”类方法都适用的提醒:论文发现为消除偏差而减去的那个量,其方差比它所检验的零假设本身的方差还大(重估后标准差为零假设自身标准差的 1.30–1.56 倍)——即”修正项引入的噪声超过信号”是这一类方法的通病。

29.6.4 污染检测方法的新进展

2025-2026 年间,三种新方法填补了重要的场景空白:

BeyondBench:动态生成抗污染基准(ICLR 2026)

核心思想:与其检测污染,不如从源头避免——通过算法化动态生成数学问题,组合空间超过 10^15 种,使任何固定题库都无法覆盖。44 个算法任务 / 117 个变体 / 3 个难度级别。101 个模型(0.5B-141B 参数)评测显示:

  • Hard Suite(10 个任务,68 个变体,NP-complete/约束满足):
    • Gemini-2.5-pro:56.21%(最强)
    • Qwen2.5-72B:33.37%
    • Llama-3.3-70B:29.16%
  • 工具依赖性(不用工具时的性能下降):
    • GPT-5:-16.81%
    • GPT-5-mini:-15.86%
    • GPT-5-nano:-43.95%(最受工具影响)
  • 关键观察:所有模型在 polynomial → exponential 复杂度提升时性能急剧下降

这代表了一种范式转变:从”事后检测污染”到”事前消除污染可能性”。

CDD:输出分布峰度检测法(arXiv: 2603.03203)

通过分析模型输出分布的峰度来检测小模型的数据污染——受污染样本的输出分布更为尖锐(过度自信)。但有一个重要的实践限制:在 LoRA 微调下检测失效。这意味着 CDD 适用于检测全量预训练的污染,但不适用于仅做了 LoRA 适配的模型。

Self-Critique:策略塌陷检测(PKU,arXiv:2510.09259,2025-10-10 提交;ICLR 2026)

专门针对 RL 后训练阶段的新型污染检测。核心观察是”策略塌陷”现象——当模型在污染问题上训练后,其推理路径会固化。检测方法:要求模型给出同一问题的不同解法,如果多条解法高度相似(而非预期的多样化推理路径),则表明可能存在污染。这是首个专门针对 RL 阶段污染的系统性检测方法——RL 会掩盖传统检测方法的信号,但 Self-Critique 利用的是 RL 训练自身产生的行为异常(策略塌陷)来检测污染。

配套基准 RL-MIA 是专为这一场景构造的污染评测集。实测:Self-Critique 在多个模型与多个污染任务上显著优于基线,AUC 最高提升 30%;而既有方法在 RL 阶段污染上接近随机猜测。

更新后的多层验证建议:

n-gram 检测(显式污染)
  → DCR 风险量化(连续风险等级)
  → Self-Critique(RL 阶段污染)
  → CDD 峰度检测(小模型全量训练污染)
  → BeyondBench/LiveBench(动态评测交叉验证)
  → LRM Contamination Detection Arena(Stage I/II 系统化测试)

29.6.5 动态评测:从源头解决污染

静态 benchmark 都有一个共同的软肋:题目公开之后,就有可能通过各种渠道进入训练数据——直接泄露、网页引用、数据集整合,甚至模型生成的训练数据中包含对这些题目的讨论。

LiveBench(ICLR 2025)代表了一种从根本上解决这个问题的范式:与其在静态 benchmark 上费力做去污染修补,不如用不可能被提前泄露的题目。LiveBench 的核心设计:

  • 定期发布全新题目,旧题目退役,确保测试集的”新鲜度”
  • 所有题目都有可验证的客观标准答案,不需要 LLM-as-Judge 这类可能引入偏差的评判方式
  • 覆盖 6 大类 18 项任务,即使最强模型也很少超过 65% 准确率

对于数据工程团队来说,动态评测的价值在于:当你在静态 benchmark 上的改进很难排除污染因素时,可以用 LiveBench 这类平台做交叉验证。如果改进在动态评测上也成立,那就是真实的数据质量提升。

29.6.6 2026 年污染检测实践清单

截至 2026 年下半年,仍有效且推荐使用的检测方法(按稳健性排序):

排名方法类型优势局限
1Time-based partition(按时间切分)时间维度最稳健,跨训练阶段保持依赖训练 cutoff 时间记录
2Min-K% probability概率(reference-free)无需参考模型,部署简单SFT+RL 后失效(Stage I)
3Time Travel guided-completion attack攻击式主动触发记忆,可控prompt 构造复杂
4ConStat performance gaps行为对比检测改写污染需要原题 + 改写题配对
5BIG-Bench canary GUID memorizationcanary 注入精确知道是否被记住仅适用 canary 子集

对 LRM 的特殊提示:

  • 上述方法在 SFT+RL 训练后均存在脆弱性
  • 解读 LRM 榜单时必须配合训练阶段维度的检测
  • Wang et al. UIUC 的 LRM Contamination Detection Arena 提供了 10 种检测器的标准化测试平台,应作为 LRM 评估的标准前置步骤

29.6.7 快速评估 vs 完整评估的权衡

在数据迭代的不同阶段,评估的粒度应该不同:

日常开发阶段(快速评估):

  • 只用 2-3 个对数据敏感的 benchmark(如 HellaSwag + ARC)
  • 模型训练到 10B token 就评估
  • 耗时:1-2 天
  • 精度:能检测出 >2% 的差异

里程碑阶段(完整评估):

  • 用 6-10 个 benchmark 全面评估
  • 模型训练到 28-50B token
  • 增加多种子验证
  • 耗时:5-7 天
  • 精度:能检测出 ~1% 的差异

发布前(深度评估):

  • 完整 benchmark suite + 人工评估
  • 在目标规模模型上验证
  • 包含安全性和偏见评估
  • 耗时:2-4 周

这与三层评估体系(L1/L2/L3)形成对应:快速评估对应 L1 的节奏,深度评估对应 L3 的严谨。评估粒度应该服务于迭代节奏,而不是一刀切。


29.7 迭代调整:数据闭环的执行

29.7.1 闭环的核心

数据-训练-评估闭环是工业级团队的”肌肉记忆”:

评测发现问题 → 数据归因 → 数据调整 → 训练 → 再评测

数据工程不是一锤子买卖。真正的价值在于建立一个持续迭代的闭环:

Step 1: 发现问题
  ├── 训练中发现 loss 异常
  ├── 评估中发现某维度退化
  ├── 人工抽检发现系统性问题
  └── 用户反馈(如果模型已部署)

Step 2: 定位原因
  ├── 数据抽样分析
  ├── 回溯管线日志
  ├── 按维度分桶排查(语言、领域、来源)
  └── 与历史版本对比

Step 3: 调整管线
  ├── 修改过滤规则/阈值
  ├── 增加/移除某个数据源
  ├── 调整配比
  └── 修复 bug

Step 4: 验证效果
  ├── 小模型消融实验
  ├── 人工抽检新数据
  ├── 数据统计对比
  └── 确认无回归

29.7.2 典型闭环场景

场景 1:评测掉分 → 找数据 → 加量改质

现象: MMLU 从 70 掉到 65
归因: TDA 显示新增数据中某些知识类样本质量差
调整: 移除低质量样本 + 补充高质量知识类数据
再评测: MMLU 恢复到 71

场景 2:某评测涨太多 → 检查污染

现象: GSM8K 从 75 暴涨到 95
排查: 用 13-gram 检查训练数据是否含 GSM8K 测试集
发现: 新加入的某开源数据集含 GSM8K 测试集
处理: 移除污染数据,重新训练

经验:任何评测分数”不正常暴涨”都要警惕污染。这正是 29.6 节污染检测体系在闭环中的触发时机。

场景 3:DPO 长度漂移 → 加 debias

现象: DPO 后输出平均长度从 200 涨到 350
归因: 偏好数据中 chosen 普遍比 rejected 长
调整: 加入长度去偏数据(短而精胜过长而水)
再评测: 长度恢复到 220

场景 4:RL reward 暴涨 → 检查 hacking

现象: RL 训练 200 步后 reward 从 0.5 暴涨到 0.95
排查: 用 Cross-Verifier 检查 rollout
发现: 模型学会"输出特定格式骗过 verifier"
处理: 加强 verifier + 加入 reward 平滑项
再训练: reward 平稳增长到 0.7

29.7.3 显微镜案例:一次数据迭代的完整记录

背景:某团队在训练一个中英双语的 13B 模型,训练进行到 5T token 时,发现模型在数学推理任务上的表现异常低下——比同期的开源模型低了 8 个百分点。

Step 1: 发现问题

团队的监控 dashboard 在训练到 4T token 时就发出了预警:GSM8K 的 5-shot 准确率停滞在 32%,而预期应该在 45% 左右。

Step 2: 定位原因

# 数据分析过程(简化版)
# 1. 先看数学相关数据的占比
math_data_ratio = count_domain(training_data, "math") / total_tokens
# 结果:2.1%(预期 5%)

# 2. 检查数学数据的质量
math_quality = sample_and_inspect(training_data, domain="math", n=500)
# 发现:大量数学数据来自一个质量较差的来源
# 其中约 30% 的数据包含被截断的 LaTeX 公式

# 3. 回溯管线日志
# 发现:某次管线更新中,数学数据的来源权重被意外修改
# 一个高质量的 arXiv 数学子集被错误地排除了

Step 3: 调整管线

  1. 恢复 arXiv 数学子集
  2. 增加数学数据占比从 2.1% 到 5.5%
  3. 对数学数据增加 LaTeX 完整性检查
  4. 额外混入合成数学题(经过质量验证的)

Step 4: 验证效果

在 400M 代理模型上训练 10B token,对比调整前后:

Benchmark调整前调整后变化
GSM8K (5-shot)12.3%16.7%+4.4%
MATH4.1%6.2%+2.1%
HellaSwag58.2%57.9%-0.3%
ARC-Challenge35.6%36.1%+0.5%

数学能力显著提升,通用能力无回归。批准上线。最终在 13B 模型上,GSM8K 分数从 32% 提升到 48%,达到预期目标。

这个案例体现了闭环的完整链路:监控发现问题 → 分桶归因 → 管线修复 → 代理验证 → 上线确认。其中”通用能力无回归”的确认与目标能力的提升同等重要——数据调整最常见的副作用就是按下葫芦浮起瓢。

29.7.4 数据重写作为迭代手段:Transform-and-Retain 范式

传统数据迭代的逻辑是”排除式”的——发现低质量数据,然后过滤掉。但 SwallowCode/SwallowMath(Fujii et al., arXiv: 2505.02881, 2025)提出了一种截然不同的范式:与其排除低质量数据,不如通过重写提升其质量。这被称为”Transform-and-Retain”(转换保留)范式。

核心思想:公共数据中大量内容的”低质量”不是固有的——而是格式混乱、冗余多、上下文缺失。通过 LLM 重写,可以在保留核心知识的同时大幅提升数据质量。

SwallowCode(代码数据重写,~16.1B tokens):

四阶段管线:

1. 语法验证 → 移除无法解析的代码
2. pylint 风格过滤 → 移除风格极差的代码
3. LLM 第一阶段重写 → 修复错误、补充注释、改善结构
4. LLM 第二阶段重写 → 深度优化逻辑、增加 docstring

效果:HumanEval pass@1 提升 +17.0

SwallowMath(数学数据重写,~2.3B tokens):

三阶段管线:

1. 去冗余 → 移除无关推导步骤
2. 恢复上下文 → 补充题目背景和定义
3. 重格式化 → 转为简洁分步解释

效果:GSM8K 提升 +12.4,MATH 提升 +7.6

消融研究的关键洞察:每个处理步骤都带来增量改进,但重写步骤贡献最大。这意味着”转换”本身的价值超过了”过滤”。

与排除式过滤的对比:

维度排除式过滤Transform-and-Retain
数据量减少(丢弃低质量数据)保持或增加(重写后保留)
知识损失有(被过滤数据中的知识丢失)最小(核心知识被保留)
计算成本低(只做判断)高(需要 LLM 推理)
适用场景数据充足时的质量控制数据稀缺时的质量提升

实践启示:在数据稀缺的场景下(如特定语言、特定领域),重写比过滤更优——因为每一条数据都弥足珍贵,不应轻易丢弃。即使在数据充足的场景下,对”中等质量”数据做重写也是值得的——它能将大量”灰色地带”数据转化为高质量数据,扩大有效训练集。

29.7.5 数据版本管理

当你的数据管线每周都在迭代时,你需要能够:

  • 回溯到任意历史版本的数据
  • 对比两个版本的差异
  • 在不同版本上复现训练结果
  • 追踪每条数据的来龙去脉

推荐的版本管理方案:

方案 1: DVC(Data Version Control)
├── 优点:与 Git 深度集成,学习成本低
├── 缺点:对 PB 级数据的支持较弱
└── 适用:中小规模团队,数据 < 10TB

方案 2: lakeFS
├── 优点:Git-like 分支模型,支持 S3/GCS
├── 缺点:需要额外维护服务
└── 适用:中大规模团队,云原生架构

方案 3: Delta Lake / Iceberg
├── 优点:PB 级数据的高效版本管理
├── 缺点:需要 Spark 生态
└── 适用:大规模团队,已有 data lake 基础设施

最低可行的版本管理实践:

即使你没有上述工具,也应该至少做到用 manifest 文件记录每个版本的数据组成:

"""
最小版本管理方案
用 manifest 文件记录每个版本的数据组成
"""
import json
import hashlib
from datetime import datetime

def create_data_manifest(
    version: str,
    data_sources: dict,
    pipeline_config: dict,
    output_stats: dict,
    notes: str = ""
) -> dict:
    """
    创建数据版本 manifest
    每次数据管线运行后生成
    """
    manifest = {
        "version": version,
        "created_at": datetime.now().isoformat(),
        "data_sources": data_sources,
        # 例如: {"cc_2024_10": {"path": "...", "doc_count": 1000000, "sha256": "..."}, ...}
        "pipeline_config": pipeline_config,
        # 记录所有管线参数
        "output_stats": output_stats,
        # 例如: {"total_tokens": "500B", "total_docs": 50000000, "lang_distribution": {...}}
        "notes": notes,
    }

    # 生成 manifest 自身的 hash
    manifest_str = json.dumps(manifest, sort_keys=True, ensure_ascii=False)
    manifest["manifest_hash"] = hashlib.sha256(manifest_str.encode()).hexdigest()[:16]

    return manifest

# 使用示例
manifest = create_data_manifest(
    version="v2.3",
    data_sources={
        "cc_2024_10": {"doc_count": 500_000_000, "tokens": "2T"},
        "cc_2024_06": {"doc_count": 480_000_000, "tokens": "1.8T"},
        "arxiv_2024": {"doc_count": 2_000_000, "tokens": "50B"},
        "github_code": {"doc_count": 50_000_000, "tokens": "300B"},
    },
    pipeline_config={
        "text_extractor": "trafilatura",
        "lang_threshold": 0.65,
        "dedup_method": "minhash",
        "dedup_threshold": 0.8,
        "quality_filter": "fasttext_edu_v2",
        "quality_threshold": 0.5,
    },
    output_stats={
        "total_tokens": "1.5T",
        "total_docs": 200_000_000,
    },
    notes="修复了数学数据配比问题,arXiv 子集恢复"
)

manifest 是数据卡片(见 29.8.3)的机器可读形态:前者面向管线自动化,后者面向人类审阅与合规。

manifest 里建议再加一类字段:评估侧的版本快照——本次评估用的 judge 模型名与版本号、prompt 模板版本号、采样温度与重复次数。数据有 manifest、模型有 checkpoint,唯独”尺子”常常没有版本记录,而 judge 换代在生产中是高频事件(29.8.4 展开)。三者对齐之后,“某次分数变了”才有唯一解释:数据变了、模型变了,还是尺子变了。

29.7.6 闭环的周期

工业级闭环周期:

  • 快闭环:1-2 天(L1 评测 + 数据调整 + 再训练)
  • 中闭环:1-2 周(L2 评测 + 配方调整 + 完整训练)
  • 慢闭环:1-2 月(L3 评测 + 重大配方调整 + 发版)

每个团队应该同时运行三个闭环,对应不同节奏。这与 29.6.7 的快速/完整/深度评估分层一一对应:评估粒度决定闭环周期,闭环周期反过来约束评估投入。


29.8 闭环组织:工程化与制度化

29.8.1 数据质量监控系统

数据管线不是搭完就完了。互联网数据在变化,爬取策略在更新,上游数据源在演化。你需要一个持续运行的监控系统来捕捉数据质量的漂移。

监控系统的核心组件:

                    ┌──────────────┐
                    │  数据管线输入  │
                    └──────┬───────┘
                           │
                    ┌──────▼───────┐
                    │  阶段性检查点  │
                    │  (每个阶段末)  │
                    └──────┬───────┘
                           │
              ┌────────────┼────────────┐
              │            │            │
    ┌─────────▼──┐  ┌──────▼─────┐  ┌──▼──────────┐
    │ 统计指标采集 │  │ 异常检测引擎 │  │ 人工抽样队列  │
    └─────────┬──┘  └──────┬─────┘  └──┬──────────┘
              │            │            │
              └────────────┼────────────┘
                           │
                    ┌──────▼───────┐
                    │   告警系统    │
                    │  (阈值触发)   │
                    └──────┬───────┘
                           │
                    ┌──────▼───────┐
                    │  Dashboard   │
                    │  (可视化)     │
                    └──────────────┘

关键监控指标:

"""
数据管线监控指标定义
"""
MONITORING_METRICS = {
    # 数量指标
    "input_doc_count": {
        "description": "管线输入文档数",
        "alert_rule": "与上一批次偏差 > 20%",
    },
    "output_doc_count": {
        "description": "管线输出文档数",
        "alert_rule": "与上一批次偏差 > 20%",
    },
    "pass_through_rate": {
        "description": "通过率 = output / input",
        "alert_rule": "< 15% 或 > 80%",
    },

    # 质量指标
    "avg_quality_score": {
        "description": "平均质量分",
        "alert_rule": "较历史均值偏差 > 0.05",
    },
    "quality_score_distribution": {
        "description": "质量分分布",
        "alert_rule": "分布形状发生显著变化(KL 散度 > 0.1)",
    },

    # 分布指标
    "language_distribution": {
        "description": "语言分布",
        "alert_rule": "任一语言占比变化 > 5%",
    },
    "length_distribution": {
        "description": "长度分布",
        "alert_rule": "中位数变化 > 30%",
    },
    "domain_distribution": {
        "description": "领域分布",
        "alert_rule": "任一领域占比变化 > 10%",
    },

    # 管线健康指标
    "processing_time": {
        "description": "处理耗时",
        "alert_rule": "较历史均值慢 > 50%",
    },
    "error_rate": {
        "description": "处理错误率",
        "alert_rule": "> 1%",
    },
}

29.8.2 数据 A/B 测试的方法论

当你有两个候选的数据管线,并且小模型消融给出的结论不够确定时,你可能需要在真正的大模型训练中做 A/B 测试。

数据 A/B 测试的设计要点:

  1. 时间切片法:在同一次训练中,前 N% 的 step 用管线 A,后 N% 用管线 B,观察切换前后的评估指标变化

    • 优点:只需要一次训练
    • 缺点:顺序效应,不够严谨
  2. 并行训练法:同时启动两次训练,分别用管线 A 和管线 B

    • 优点:最严谨
    • 缺点:2 倍 GPU 成本
  3. 混合比例法:用管线 A 作为基线,逐渐混入管线 B 的数据(比如 10%、30%、50%),观察效果变化

    • 优点:可以发现最优混合比例
    • 缺点:需要多次实验

💡 反直觉发现

数据 A/B 测试的结论在不同训练阶段可能不同。某个数据管线在训练前期(<1T token)表现更好,但在后期(>5T token)可能被另一个管线反超。这是因为模型在不同训练阶段对数据的”需求”不同——前期需要高信息密度的数据来快速建立知识基础,后期需要高多样性的数据来避免过拟合和提升泛化能力。

29.8.3 数据卡片 Datasheet

数据卡片(Datasheet for Datasets, Gebru et al., 2021)是数据集的”出厂说明书”,也是闭环组织中对人类最友好的沉淀形式:

# 数据卡片示例
name: "aide-sft-v3"
version: "3.0"
date: "2026-07-06"

# 动机
purpose: "用于 SFT 训练通用对话能力"
funding: "内部研发"

# 构成
composition: |
  - 总量: 939K
  - 任务分布: 通用30% / 数学20% / 代码15% / 安全10% / 指令10% / 多语5% / 知识5% / 工具5%
  - 语言: 中文 40% / 英文 50% / 其他 10%

# 采集
collection_process: |
  - 70% Magpie 合成(Llama-3-70B 生成)
  - 20% Evol-Instruct 进化
  - 10% 人工标注
collection_time: "2026-Q2"

# 预处理
preprocessing: |
  - 格式归一化到 messages 结构
  - PII 脱敏(Presidio)
  - 13-gram 去污染(覆盖 20+ 评测集)
  - Deita Selector 精选

# 质量
quality_assessment: |
  - IFD: 0.5 平均
  - 4-gram 多样性: 0.68
  - InsTag 覆盖: 1240
  - 拒答率: 7%

# 已知局限
limitations: |
  - 偏向中英文,小语种覆盖不足
  - 数学数据难度偏低
  - 合成数据有 GPT-4 风格偏置

# 推荐/禁忌
recommended_use: "通用 SFT 训练"
prohibited_use: "不可用于医疗/法律等专业领域"

Datasheet 的工业价值:

  1. 可追溯:训练出问题时能找到数据源头
  2. 可复现:他人能用同样数据复现训练
  3. 可审计:合规审计时能证明数据合法
  4. 可对比:版本间能清楚对比数据变化

29.8.4 评估闭环工业 SOP

1. 三层评估体系搭建:
   - L1 自动评测(每 ckpt)
   - L2 LLM-as-Judge(每周)
   - L3 真人评测(每月)

2. 评测集管理:
   - 必扫 20+ 评测集
   - 抗污染评测(LiveCodeBench/AIME/LiveBench)
   - 多维度覆盖

3. LLM-as-Judge 偏差控制:
   - 一致率报 Cohen's κ,不报 raw agreement
   - 双向评审去 Position Bias
   - 先实测长度偏差幅度,再决定是否长度归一化
   - 多 judge 交叉去 Self-preference
   - 剥离格式后重评(默认执行,非可选)
   - 换代/高风险场景补四轴可信度体检
   - **judge 版本要写进评估结果元数据**(换代不是可靠性干预,见下)
   - **重复投票的收益要按实测误差相关 ρ 折算,不能按独立同分布假设估算**(见下)

4. Reward Hacking 检测:
   - reward 突增告警
   - rollout 聚类
   - Cross-Verifier
   - 人审 sample

5. 消融与数据归因:
   - 消融实验(关键决策;消融成本通常占总预算 30-50%)
   - Influence Function(大规模)
   - TracIn(轻量级)
   - task-agnostic / forward-only(研究阶段,需自行验证)
   - 模块化消融 / OptiMer(控制成本)

6. 污染检测:
   - n-gram 去污染(管线末段必做)
   - Time-based partition(最稳健)
   - RL 阶段默认假设检测盲区
   - 动态评测交叉验证

7. 闭环执行:
   - 快闭环 1-2 天
   - 中闭环 1-2 周
   - 慢闭环 1-2 月

8. 沉淀与治理:
   - 数据 manifest(每管线运行必生成)
   - 数据卡片(每个版本必填,含动机/构成/采集/质量/局限)
   - 持续监控系统 + 告警

为什么 judge 换代要单独记一笔。 Yang et al.(arXiv:2607.08535《When the Judge Changes, So Does the Measurement》)把 evaluator 换代当作测量效度问题审计:候选响应不动、只换 judge,分数就会动。他们在 4 个判定数据集上比较了两条实际可走的升级路径——Qwen3 dense 从 1.7B 扩到 32B,以及 MiniMax M2 到 M2.7 的连续发布版本。结论是换代不可互换:18 次相邻检验经 Holm 校正后,只有 Qwen3 1.7B→4B 这一步在 LLMBar 与 Arena 上仍然显著,MiniMax 的九次相邻版本发布没有一次达到未校正 p<0.05。规模变大也不等于变好:Qwen3 1.7B→32B 在 LLMBar 上从 0.463 升到 0.678,在 PandaLM 上却从 0.779 掉到 0.769,在 Judge’s Verdict 上从 0.595 掉到 0.530——四个数据集里有两个是越大越差。

工程含义只有一句:judge 版本号应当像数据 manifest 一样写进评估结果元数据。否则历史分数与新分数在时序上不可比,你无法判断某次分数变化到底是数据变好了还是尺子换了。这与 29.7.5 的数据版本管理是同一个问题的镜像——数据有 manifest、模型有 checkpoint,评估尺子本身同样需要版本记录。

重复投票的收益有硬上限。 该研究实测了 jury 投票的误差相关:Qwen3 同族 jury 在 LLMBar 上 ρ = 0.944–0.972,MiniMax jury 较低但仍有 ρ = 0.664–0.706。误差高度相关,“多跑几次取多数”就不能按独立同分布假设估算误差下降——Qwen3-1.7B 在 LLMBar 上 K=1 / 3 / 5 的实测准确率是 0.463 / 0.475 / 0.482,从 1 次加到 5 次只换来不到 2 个百分点。混合不同家族的 jury 也达不到独立假设的预测值。可用的口径是先估 ρ 再定 K:ρ 高时加大采样量是纯烧预算,不如把这笔钱换一个偏差更小的 judge。

judge 不是常量,是一组配置——版本字段要记四项。 上面”judge 版本号写进元数据”这条纪律只能防住其中一项。arXiv:2610.05094 在 10 个推理模型 × 多种 prompt / 量表设计上(两个任务:句子情感与毒性的标量打分,以及问答对的二分类准确率判定)量出了各设计选择的位移幅度:

设计选择实测位移幅度对 manifest 的要求
换模型宽松度(leniency)下降最多 56.1 个百分点模型身份(含版本号)必记——这是方差的第一来源,也是”换代不可比”的量化依据
换评分量表测得的偏差移动最多 0.93 点(1–7 量表)除 prompt 模板外,manifest 要新增 rating_scale 字段
换用详细 prompt宽松度下降 28.9 个百分点prompt 模板版本必记
降低推理强度既不影响准确率也不影响宽松度可省预算项(见下)

这三条把 29.7.5 的 manifest 字段建议从”模型名 + 版本号 + prompt 模板版本 + 温度与重复次数”扩成”模型身份 + 量表 + prompt 模板 + 推理强度”四项:量表的位移幅度(0.93 点)虽然小于换模型(56.1pp),但足以翻转一个”有没有偏差”的定性结论,而它此前完全不在记录范围内。

对照着看还有一条反直觉、可以直接省钱的观察:降低推理强度(reasoning effort)既不影响准确率也不影响宽松度,因此在批量判定场景可以在保住质量的前提下降推理档位。

⚠️ 限定:该研究的结论只在上述两个任务(情感/毒性标量打分、QA 二分类)上验证过,且所有数值为论文自报。推理强度那一条尤其要标任务范围——换到开放式长答案评分或推理链路判定上是否仍成立,尚未验证。

基线可信度参考。 同一研究给出的两个”平均水平”数字可以作为判定的参照:评分任务上 judge 与人类金标虽有显著分歧,但平均绝对偏差只有 0.11 点(1–7 量表),多数 judge 仍可认为可靠;二分类准确率任务的平均准确率为 96.5%。也就是说,设计选择造成的位移(56.1pp / 0.93 点)远大于 judge 本身的平均误差(0.11 点)——这是”judge 必须版本化”最直接的论据:误差的主要来源不是 judge 不准,而是配置在变。

29.8.5 认知演变时间线

时间认知阶段代表性实践
2020”loss 下降就好了”GPT-3 只报告了训练 loss 曲线
2021-2022”用 benchmark 评估”但很快发现 benchmark 泄露问题
2023”系统化消融实验”DCLM 建立了数据消融方法论
2024”数据管线的 CI/CD”FineWeb 展示了完整的迭代闭环;模块化消融方法出现
2025”多信号融合与动态评测”FIRE 用 37.5% 数据达到同等性能;LiveBench 动态评测范式兴起;DCR 四级污染风险量化
2026”推理模型时代的评估危机”ICLR 2026 揭示 RL 训练系统性掩盖污染信号;IProX 影响力保持代理;AskLLM+Density Sampling 数据高效选择

多模态特化:VLM 质量指标与多模态去污染

本章的指标体系(困惑度、启发式、LLM-as-Judge、n-gram 去污染)是围绕纯文本设计的,直接搬到 VLM 会漏掉最关键的一类问题:文本侧指标看不出视觉退化。

质量指标要升级为跨模态一致性。三个可用信号:一是图文对齐分(CLIP 类模型的图文相似度),衡量 caption 与图是否真的相关;二是 OCR 一致性——图内 OCR 出的文字与 caption/答案中提到的文字能否对上,这是检测”图文张冠李戴”最直接的信号;三是可验证性——答案中的实体、数量、颜色、位置能否被检测器或程序复核。

CLIP score 只能软用。它抓的是整体语义相关,对讽刺、隐喻、梗图、反常识合成图会系统性误杀,硬阈值过滤会砍掉恰恰最需要的高阶语义样本。正确做法是软重加权(低分降采样而非删除),与 29.1.3”多信号融合而非并列”一致。

去污染必须双通道。29.6 的 n-gram 去污染只覆盖文本:一条评测样本的题目文本可能被洗得很干净,但它配的那张图早就在预训练图集里躺过了。因此图像侧要再做一遍近重复检测——pHash / dHash 汉明距离 + CLIP 或 DINOv2 嵌入检索的组合(做法见第 7 章),命中的评测样本要么剔除,要么换成未参与训练的图。只看文本通道做去污染,多模态评测分数就是不可信的。

基准选型要防”不看图也能答对”。MMStar 的意义正在于此:它筛掉了纯靠语言先验就能答对的样本,保证每条都真正需要看图。实操建议是常备一组盲测基线(去掉图再跑一遍),两条曲线的差值才是视觉能力的真实量。

VLM-as-Judge 自身的偏差要校准。29.3 的四大陷阱在多模态下全部健在,还多了两个:一是 judge 自己的视觉幻觉(给不存在的细节打高分),二是视觉长度偏差(图更多、描述更长就判优)。校准方法沿用 29.3.5——把它们变成可测量项:构造已知答案的对照集,定期测 judge 的假阳率。

闭环纪律:迭代时必须图文指标一起看。只看文本指标会掩盖视觉退化,这与第 11 章的模态消融、第 15 章的灾难性遗忘监控是同一条原则。

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


本章要点回顾

  1. 训练 loss 不等于数据质量——低 loss 可能只是因为数据容易预测
  2. 数据冗余会导致次级缩放——高数据密度让 Scaling Law 变”钝”,去重不只是清理噪声,而是维护 scaling 效率
  3. 多质量信号应该融合而非并列——FIRE 等框架能用不到 37.5% 的数据达到同等性能
  4. 三层评估体系(L1/L2/L3)是工业标配——按频率与成本分层,L3 金标校准前两层
  5. 小模型代理法是当前最实用的评估方案,但有可靠性限制;影响力保持代理(IProX)可能是下一步方向
  6. 消融实验要分层设计,除了调管线参数,还可以尝试数据子集选择策略;消融成本通常占总预算 30-50%
  7. LLM-as-Judge 的偏差要在自有配置上实测——一致率报 κ 不报 raw,长度偏差不是常数,四轴体检与偏差注入基准应纳入流程
  8. Reward hacking 与污染是分数失真的两大来源——reward 突增告警与多层污染检测是必备防线
  9. Benchmark 去污染是评估的前提,但在推理模型时代面临系统性失效——需要多层次验证,动态评测从源头消除污染
  10. 数据迭代是持续闭环,不是一锤子买卖;快/中/慢三闭环并行运转
  11. 数据重写是迭代的新范式——Transform-and-Retain 比排除式过滤更能保留知识价值,尤其适用于数据稀缺场景
  12. 版本管理(manifest)、数据卡片(Datasheet)与持续监控是数据管线工程化与可审计性的基础

🛠️ 本章衍生工具