做预训练数据调整的人,大概率都有过这种体验:明明只是改了一个数据比例,重跑一遍几百甚至上千GPU小时的全量训练,再拿下游一堆任务去测,结果好几个任务涨了、好几个任务跌了,根本说不清是数据改进生效了,还是超参数抖动、随机种子带来的噪声。最近COLM 2026上围绕预训练数据贡献度的研究,让我觉得终于出现了一条绕开这个死循环的路子——把注意力从“下游任务评测”转移到“单个样本对训练过程本身的贡献”上,也就是给每条数据算出一个“功劳分数”。这篇博文就把我看完的理解、实操套路,还有踩过的坑一起聊聊。
1. 数据贡献度到底是什么,为什么它比下游评测香
1.1 传统调数据的两大死穴
过去我们调预训练数据,基本逃不掉两个问题。第一是后验性,数据改没改对,非要等模型完整训练完才看得出来。第二是叠加性,现实中很少有人只改一个变量就开跑,通常是一边去重、一边过滤低质量文本、一边调整混合比例,最后模型效果变了,你很难说是哪个环节起了作用,甚至说不清是不是随机性造成的。
举个例子,我把普通网页文本和代码数据的比例从7:3调成8:2,下游任务里某个代码生成指标涨了0.4个点,另一个阅读理解指标跌了0.6个点。这时候我该不该坚持这个比例?如果靠传统评测,我只能再跑一遍全量训练,换几个随机种子求平均,才能勉强判断趋势。一来一回折腾一周,结论可能还是“看不出来”。这种“测结果”的模式,本质上是在用极高的成本换取一个非常粗粒度的、噪声很大的反馈。
数据贡献度方法试图改变这个游戏规则。它不关心最终模型跑成什么样,而是关心“训练过程中每个样本对模型参数和预测行为产生了多大影响”。有了这个信息,你可以在不跑完整全量实验的情况下,先把数据集中明显有害、明显冗余、明显被低估的部分摸清楚。
1.2 一句话说清数据贡献度
给一个训练样本算贡献度,本质上就是回答一个问题:如果这个样本被拿掉,或者被多复制几遍,模型在预训练损失或者内部表示上会发生多大变化?变化越大,说明这个样本越有影响力。而影响力可以是正的、负的,也可以是接近零的。
正贡献样本通常意味着它带来了新的知识、有效的结构信息,或者帮助模型在困难的局部区域收敛。负贡献样本往往是噪声、错误标签、重复文本碎片,它们不仅浪费算力,还可能让模型学到错误模式。中性样本则像是背景数据,可有可无,但在某些场景下它们反而起到正则化作用,不能一刀切全删。
这里要特别注意,贡献度和“人类看起来质量高不高”不能直接画等号。一段语法通顺但极度重复的文本可能被模型判为负贡献,因为它只是强化了已经学会的分布,没有提供新信息。一段看起来乱七八糟的代码注释反而可能是正贡献,因为它让模型在某个冷门路径上学会了容错。数据贡献度的价值就在于,它绕开了人的主观判断,直接从模型学习过程里找证据。
1.3 从“测结果”到“算归因”的思路转变
传统的“调数据-跑训练-测下游”链路,特别像每次改代码都要跑到集成测试阶段才发现问题。而数据贡献度更像代码覆盖率,你在训练进行到一半的时候就能看到哪些样本在真正改变模型行为,哪些样本被模型“熟视无睹”。这种思路一转变,很多操作就从“猜”变成了“看”。
我自己的感受是,这套东西最适合用来做预训练数据的初筛和排序。先花十分之一甚至百分之一的训练成本,得到一份“数据质量地图”,然后只对地图上标红的区域做针对性清洗,最后再用少量下游任务验证方向。这样一来,你不需要为每一次数据调整都跑全量下游评测,但又不会完全失去对动态过程的观测。
2. 极低成本算贡献度的实操框架
2.1 先想清楚预算和精度
一上来就追求精确的影响函数是不现实的,因为预训练模型动辄几十亿参数,精确计算每个样本的影响需要求Hessian矩阵的逆,这个数学上可行,工程上直接爆炸。所以实操的第一步是确定你的“性价比”边界:你打算花多少GPU时间,换取多粗粒度的分数。
我的经验是,绝大多数数据调整决策根本不要求分数精确到小数点后四位。你只需要把样本粗分成“高贡献-中贡献-低贡献-负贡献”四个桶,就足够支撑去重、过滤、重采样这些动作了。因此,优先选择那些计算成本低、又能稳定排序的代理指标,比如基于检查点梯度的TracIn,或者基于训练动态的遗忘分数。
预算也要分成两块来规划:一块是训练探针模型的成本,一块是计算归因分数的存储和读取成本。探针模型不需要和最终模型一样大,但结构最好同源;归因分数如果需要保存每个检查点的梯度,存储量可能非常大,这时候就要考虑降维手段或者采样策略。
2.2 五步走:探针模型、子样本、训练轨迹、打分、聚合
第一步,建一个探针模型。如果最终模型是13B,探针可以选1B到3B左右;如果最终模型本身只有1B,那探针也许就只能用几百M。太小不行,小到学不动长尾知识,分数会被噪声主导。根据我的实践,只要探针模型在某个小型验证集上的loss没有明显偏离同架构预期,这个规模基本就够用了。
第二步,做分层抽样。别偷懒直接从原始文件开头截取十万条,那是灾难。预训练数据通常混合了网页、百科、代码、书籍、对话等等来源,你必须按来源、语言、主题甚至长度层次做采样,保证每个子群落在子样本里占的比例和全量一致。否则你算出来的“全局分数”只是某一类数据里的局部现象。
第三步,训练并保存运行轨迹。探针模型正常训练三到五个epoch,每个epoch结束保存一个检查点。如果你用的是TracIn这类方法,最好在训练时记录每个样本在每次更新前后的梯度或预测向量。如果你只是想用遗忘分数,那就简单很多,只需要在每个检查点重新跑一遍样本,记录其被预测对的概率变化。
第四步,算分。根据选定的方法给每个样本输出一个标量分数,并至少做一次归一化,方便跨批次比较。为了让分数更可靠,我通常会对同一组数据用不同随机种子训练两次,然后把分数取平均或取中位数。毕竟预训练本身随机性就足够大,单次分数里可能混着大量运气成分。
第五步,聚合和可视化。把分数按预设的分位切出几个桶,分别查看桶里样本的来源、长度、重复度、嵌入簇分布。这一步的意义是让你能够“看数据”,而不是只看一个数字。很多时候,分数顺藤摸瓜找到的“低分组”,会以某种你完全没想到的文本模式成片出现,比如“某个网站的页脚模板”“某种语言乱码片段”或者“同一份文档被截断后形成的伪样本”。
2.3 一个可直接照搬的流程示意
# 伪代码:只讲流程,不依赖具体框架 samples = load_subset("train_100k.jsonl", stratify_by=["source", "language"]) probe = create_same_architecture_probe(config="1.5B") ckpts = [] for epoch in range(5): train_epoch(probe, samples) ckpts.append(probe.save_checkpoint()) score = compute_contribution( samples=samples, checkpoints=ckpts, method="tracin", projection_dim=256, ) report = aggregate_by_source(samples, score) plot_distribution(report)这个示意里最关键的不是具体训练代码,而是“分层抽样”和“多检查点”。很多人在第一步就丢了分层,后面所有归因都会偏。只要把这两点做到,哪怕用最简单的分数计算方法,也能得到很不错的排序效果。
3. 几种主流归因方法怎么选
3.1 影响函数:理论漂亮但性价比低
影响函数的思想来自统计推断,原意是:如果我们在训练集里给某个样本增加一点点权重,模型对某个测试点损失的改变是多少。用它来做数据贡献度,最大优点是能解释“为什么删掉这条数据后,某个任务指标会变”,听起来非常优雅。
但问题也很直接:需要计算Hessian矩阵及其逆,在预训练场景下,这个矩阵的尺寸和参数量一个量级,根本没法真存。工程上一般用Hessian-vector product和共轭梯度来近似,但即使如此,每分析一个样本,都要跑若干次反向传播,成本依然很高。我一般只把影响函数用在小规模精剖场景,比如几个bad case背后到底哪条训练数据影响最大,或者某个有争议样本的归因争议。
对于动辄几千万条数据的预训练集,我劝你尽早放弃拿它来做全量排序的念头。它更适合“定点解释”,而不是“批量扫描”。
3.2 TracIn:实用主义首选
TracIn的核心想法很朴素:一条训练样本的影响力,等于它在多个检查点上的梯度与目标样本梯度之间的内积之和。放直观一点,就是看训练过程中,这条样本是在“推着模型朝向某个目标”,还是在“反向拉扯模型”。
TracIn最大的优点是,它不需要求逆Hessian,只需要保存检查点和对应的梯度向量。为了控制存储,一般会把高维梯度投影到256或512维再存。计算量在一张主流GPU上跑完十万条样本的分数,通常只需要几十分钟到几个小时,属于可接受的成本。
我实践中会先用TracIn给全量子样本排序,然后去看最低分的那一批长什么样。它找出来的“坏样本”往往不是人类一眼能看出的低质量文本,而是那种“和很多其他样本高度冲突”的数据。这种数据用传统质量过滤工具很难识别,但TracIn能抓到,因为它在训练轨迹上留下了矛盾信号。
3.3 遗忘分数:白嫖的代理指标
遗忘分数是训练动态分析里最便宜的一种:记录每个样本在每个检查点是否被模型正确预测,被正确预测过之后又在后续检查点被预测错的次数,就叫遗忘次数。遗忘次数越高,说明模型在这个样本上反复横跳,样本很可能具有噪声、歧义或冲突性。
它的计算成本几乎可以忽略,训练过程中顺手记录就行。而且它很稳定,不需要额外保存梯度。缺点是它对自监督预训练没那么直接,因为预训练里更多是单词预测而不是样本分类,但只要把“样本正确预测”改成“样本对应位置的loss是否降到阈值以下”,同样可以用。
我觉得遗忘分数最适合做第一道粗筛。先用它筛掉一批高遗忘样本,把剩下的样本数量降下来,再对剩余部分用TracIn或者更贵的方法精算,这样资源利用效率最高。
3.4 数据Shapley:贵但能讲故事
数据Shapley源自合作博弈论,它把每条样本看作一个玩家,把模型指标看作整个团队的总收益,然后计算每个玩家在所有子集组合中的平均边际贡献。这个概念在理论上非常干净,人人都能听懂,做报告时也很有说服力。
但精确计算需要训练2的N次方个子模型,哪怕N只是几万也完全不可能。所以只能靠蒙特卡洛近似,或者用KNN-Shapley这类基于嵌入距离的近似算法。KNN-Shapley可以在不需要重新训练的情况下,在嵌入空间估算每个样本对近邻预测的贡献,适合百万级数据,但对嵌入质量要求很高。
我一般只在需要做严格数据价值报告,或者要和协作方讨论“哪些数据值得花钱标注/购买”时才用Shapley。日常调预训练数据时用不太上,因为它的计算自由度太多,结果又受嵌入和采样影响,容易投入产出不成正比。
3.5 方法选型速查表
| 方法 | 计算成本 | 适用规模 | 输出信息 | 我最常用的场景 |
|---|---|---|---|---|
| 影响函数 | 很高 | 小规模精剖 | 对特定测试点的定向影响 | 解释bad case,定点找因果 |
| TracIn | 中等 | 数十万级 | 对训练损失的累计正负贡献 | 预训练数据分级、冲突检测 |
| 遗忘分数 | 极低 | 百万级甚至更高 | 训练动态中的鲁棒性 | 第一道粗筛,快速去噪 |
| 数据Shapley | 很高 | 小规模理论分析 | 公平的边际贡献 | 数据估值、报告、谈判 |
| KNN-Shapley | 中低 | 百万级 | 嵌入空间的近邻贡献 | 大规模去重后排序 |
这张表不是绝对的,但可以帮你按照预算和问题类型快速收敛到一两个候选方法。我建议先考虑“能不能接受只做粗排序”,如果能,就从遗忘分数和TracIn里选;如果还需要定位具体测试样本的因果链,再考虑影响函数或Shapley。
4. 拿到分数之后,预训练数据该往哪调
4.1 让分数告诉你去重和去噪
很多团队去重用的是MinHash和LSH,根据文本重叠程度去掉近似副本。但这种方式只看“相似度”,不看“贡献度”。两份文本可能重复度很高,但其中一份包含的某个短句对模型有独特价值,直接去掉就亏了。更合理的做法是把贡献度分数叠加到去重流程上:先做粗聚类,然后在同一个簇内保留正贡献最高的样本,对负贡献样本直接删除。
实际操作中,我会在每个簇里计算分数分布,如果发现簇内分数方差很大,就说明这个簇里混合了“高质量核心样本”和“冗余边缘样本”,这时候只保留高分部分比整个簇保留或者整个簇删除都更安全。噪声样本的分数通常极端为负,但波形很怪——它可能在某个阶段大幅提高loss,又在另一个阶段被模型忽略。这种样本单靠文本质量过滤发现不了,靠贡献度排序却很明显。
4.2 用分数做重采样和比例调整
预训练数据调整最常做的事就是重采样。传统办法是根据任务直觉或者下游表现反推比例,但贡献度分数能给你一个更直接的信号:如果代码类数据里低分样本占比明显高于其他类型,说明现有代码数据中存在大量低质量或重复片段,优先清洗它比盲目增加代码比例更有效。
我会把分数按来源域聚合,得出每个域的“低分率”和“负贡献率”。如果某域负贡献率超过一定阈值,比如百分之八,我会先做域内清洗,再考虑下调这个域的采样权重。反过来,如果某域正贡献率特别高,但它在全量数据里占比很小,那就有理由对它做二到三倍的过采样。
不过不要被分数推着无限过采样。任何一条数据被复制太多次,模型都容易在该局部模式上过拟合。我常用的上限是原始比例的3倍,超过这个数以后,边际贡献会肉眼可见地下降,甚至转为负贡献。
4.3 如何用少量下游评测做验收,而不是搜索
“告别下游任务评测”不等于彻底不用下游评测,而是改变它的位置和频率。调整完数据之后,还是要用四到五个有代表性的下游任务跑一次探针模型,看看方向是否和贡献度报告一致。如果贡献度报告说低分样本该删,删除后探针模型的预训练loss下降了,同时这几个任务不掉点,那就可以放心跑全量。
这套流程的核心是把下游评测从“搜索工具”变成“验收工具”。以前你是在用下游评测在巨大的数据参数空间里盲目搜索,现在你用贡献度先把参数空间压到几个候选方向,再用下游评测做最终检查。同样花一次全量训练的成本,成功率会高很多。
我自己常备的一套验收任务是:一个知识问答任务、一个代码生成或完形任务、一个多语言分类任务、一个长文本理解任务。规模都不大,探针模型上跑一遍只需要几小时。关键不是任务多样性,而是它们能覆盖你要调的数据域。
4.4 一个调整数据的具体示例
假设原始数据有100万条,TracIn算完分数后,负贡献样本占6%,低分样本占15%。我的操作顺序是先强制删除负贡献样本,再对低分样本做一次嵌入聚类,看看低分是不是集中在某个模板段落上。如果集中在“网站页脚”这类样本上,那就直接按模板匹配删掉,而不是按分数阈值一个个挑。接着对正贡献前10%的样本做2倍过采样,对中性样本保持原样。
# 继续用伪代码表示决策流程 mask_neg = score < 0 mask_low = score < percentile(score, 25) & ~mask_neg mask_high = score > percentile(score, 90) cleaned_samples = samples[~mask_neg] cluster_low = cluster_embedding(cleaned_samples[mask_low]) suspect_templates = extract_dominant_clusters(cluster_low, n=3) final_samples = drop_clusters(cleaned_samples, suspect_templates) final_samples = oversample(final_samples, mask_high, times=2)这个示例没有特别高深的地方,但它体现了一种习惯:用分数做决策依据,而不是拍脑袋还是7:3改成8:2。数据调整的每一步都能被回放、被复查,尤其在团队协作时,这种可解释性比“凭感觉调比例”珍贵得多。
5. 避坑指南:贡献度评估失效的4种情况
5.1 模型太小,归因比噪声还大
探针模型不是越小越好。模型小到一定程度,它对大部分长尾样本根本学习不动,于是这些样本的贡献度会被整体低估;相反,一些频繁出现的模板样本因为容易被学习,分数反而虚高。这种失真不是方法的问题,是模型容量和任务复杂度不匹配。
我的经验是,先用探针模型跑一遍holdout验证集,如果验证loss明显高于同架构更大模型的预期曲线,那就说明探针太小,需要换大一号。哪怕多花几倍GPU成本,也比拿一份全面失真的分数去改数据要划算。
5.2 子样本不分层,分数全跑偏
如果从全量数据里随机抽样,很多稀有来源或者短文本可能会被漏掉,导致分数分布整体偏向占比高的来源。你以为自己在评估全量数据,实际上只是在评估头部的几类数据。解决方案是强制分层抽样,并且最好是按多个维度交叉分层。
我在抽样前会先统计所有候选维度,包括来源、语言、文本长度桶、热度桶、嵌入聚类簇。然后用加权随机的方式从每个桶里取样本。这样最后算出来的分数报告才能代表全量,而不是代表抽样器偷懒的后果。分层做得好,分析结果的可信度能直接上一个台阶。
5.3 只看最终检查点,错失动态信息
影响函数只使用最终训练好的模型,所以它天然丢失了训练过程中的动态信息。TracIn如果只保存最后一个检查点,也等价于一个局部近似,效果大打折扣。很多有用信息是“某个样本在前中期贡献很大,后期逐步稳定”,这种动态特征只有在多检查点下才能显现。
我建议至少保存五个检查点,并且它们要均匀分布在整个训练过程中。如果存储压力太大,可以降低检查点频率,但最好不要一个检查点撑到底。动态分析的目标是看到“变化”,没有时序信息就等于回到静态视角。
5.4 把贡献度当真理,忽视分布偏移
贡献度计算的参照系是“预训练损失”,而不是“下游任务能力”。一个样本可能因为和训练集中主流分布一致从而高分,但它不一定能提升你在实际应用中的表现。相反,某些低分样本可能是稀缺的、困难的、和未来测试分布更接近的数据,删掉它们反而会让下游靠这些能力的任务受伤。
所以永远不要用贡献度分数单独做最终决策。它只负责缩小候选空间,最终的验收还得靠小规模下游评测或贴近真实场景的测试集。我见过有人设一个分数阈值,把所有低于阈值的包裹一股脑删掉,结果一个长尾知识任务直接崩掉。那不是方法的错,是少了校准步骤。
5.5 常见问题速查表
| 现象 | 可能原因 | 处理建议 |
|---|---|---|
| 同一条数据两次算分差异很大 | 模型太小或随机种子影响 | 多次独立训练后取平均再排序 |
| 低分样本看起来反而“高质量” | 分数反映的是对pre-train loss的贡献,不是质量判断 | 结合少量下游验收任务校准 |
| 删除负贡献样本后loss不降反升 | 删除过度,破坏了必要的多样性 | 放宽阈值,按分位保留一部分低频模板 |
| 高分组全是同一来源 | 分层抽样没做好或该来源本身重复度过高 | 检查分层字段,单独看该来源内部排序 |
| 分数排序和人工质量评测完全相反 | 预训练目标与人工偏好本来就不同 | 明确使用场景,用场景内任务做校准 |
6. 我的个人体会和扩展思路
这套方法用了大半年以后,我最大的体会是:数据贡献度最有价值的产出,不是一张“删除名单”,而是一张“数据体检地图”。它让我在动手清洗和改比例之前,就知道钱应该花在哪个区域,哪些看似高质量的数据其实在拖后腿,哪些被忽略的边角料反而值得多看两眼。
后续我想把它继续往两个方向扩展。一个是把贡献度分数做成在线指标,在预训练过程中实时估计每个batch样本的贡献变化,触发动态数据调度,相当于训练一段时间就根据反馈自动调整采样分布。另一个是把TracIn这类方法和聚类结合得更紧,让“低分样本”天然聚成可解释的模板或主题,这样清洗动作就不再是一个个挑样本,而是直接删掉一批结构明显的问题模板。
最后再分享一个小技巧:千万别急着对全量数据上重型方法。先用遗忘分数这种几乎零成本的工具把明显有问题的样本筛掉一轮,把总量砍一半,再对剩余样本跑TracIn或者KNN-Shapley,存储和算力都会轻松很多,而且最终分数的信噪比更高。预训练数据的调整永远是在“反馈成本”和“决策质量”之间做取舍,数据贡献度让我在这个取舍里多了一把可控的尺子。