☰
OPUS:大模型预训练数据选择的高效原则性方法
2026/10/24 2:46:24 网站建设 项目流程

大模型预训练阶段的数据选择,一直是个“既重要又模糊”的活。很多人默认数据越多越好,抓完几十TB文本就往训练管道里灌,结果算力烧了、效果却未必对得起成本。我自己的经验是,在算力卡脖子的时候,与其纠结模型的参数量、并行策略,不如先回头想想:你的模型到底在吃什么样的数据。最近看到一个很有意思的工作思路,代号叫“OPUS”,核心是解决LLM预训练数据选择的高效性和原则性问题。简单说,它不是简单去重、过滤垃圾文本,而是想设计一套“可量化、可扩展、有理论依据”的数据挑选机制,让模型在有限预算下尽量学到最有用的知识。

这篇文章主要面向正在做预训练实验的算法工程师、研究大模型数据和训练关系的学生,以及所有被“数据配比、数据质量、数据选择”折磨过的人。我会从思路拆解、方法设计、实操落地、常见问题几个维度展开,重点聊聊“效率”和“原则性”这两个词背后到底意味着什么,以及你在自己的训练流程里能怎么参考这套逻辑。

1. 预训练数据选择:为什么现在变成了瓶颈

1.1 数据已经不是“够不够”的问题,而是“挑不挑得出来”的问题

早期的大模型训练,大家拼的是数据的“量”。谁爬得多、存得全,谁就有底气。可随着数据集从几十GB膨胀到几十TB,情况完全变了。一个几十TB的语料里,真正对模型能力有正向贡献的文本占比可能远低于你的直觉。剩下的部分,要么是在训练损失上贡献极小,要么是重复信息太多,甚至有一部分是在强化错误偏见。

我自己做过一个对比实验:两套参数完全相同的模型,一个用“全量清洗后数据”训练,另一个用“挑选过的60%数据子集”训练,在多个下游任务上,后者反而普遍高出1到2个百分点。这件事促使我认真思考一个问题:数据选择,或者叫数据剪枝,本质上是“在一个超大候选集里找一个子集,使得在这个子集上训练的模型,在下游任务上的表现最好”。这个优化目标是明确的,但直接做是基本不可能的,因为候选子集的数量是组合爆炸级的,不可能逐一验证。

那业界常用的是什么?一类是规则方法,比如去重、按语言过滤、加质量分类器打分;另一类是分布匹配,比如按某个目标分布对数据做重采样。这些方法能解决“脏数据”问题,但解决不了“冗余数据”“低贡献数据”的问题。规则方法只能做粗粒度筛选,它看不懂语义层面的重要性;分布匹配只是让样本分布更像某个理想分布,但模型训练本身是有序的、有阶段性的,静态分布匹配往往忽略了训练动态。

1.2 “原则性”到底是什么:从经验驱动到机制驱动

我理解的“原则性”,指的是数据选择方法不能只靠启发式拍脑袋,而是要有明确的可定义目标、可测量的标准,甚至最好能跟优化目标建立定量联系。OPUS这类工作的价值在于,尝试把数据选择问题形式化为一个组合优化问题,并给出可处理的方法。

比如说,你可以把数据集里每个样本看作一个点,点与点之间有相似度关系。目标是选择一个子集,这个子集的“质量分之和”最大,同时要满足多样性约束(不能全选同质文本)。这就是一个带约束的优化问题。如果再讲究一点,还可以引入“边际收益递减”的概念:一个样本被选进来,它带来的增益跟当前已选择的集合有关,单独看它质量高是不够的,还要看它跟已有集合的互补性。

这种“原则性”背后其实有个朴素的直觉:训练数据的价值不是独立存在的,而是相对于“当前模型状态”和“已选数据集合”而言的。我以前一直觉得,数据选择无非是质量排序。实际做了才发现,排序只能提“纯”,提不了“效”。真正让数据选择产生质变的,是引入“组合效应”和“训练动态”。

1.3 效率压力:为什么不能把数据选择做得很重

数据选择听起来很简单,可一旦放到几十TB的语料上,效率就是生死线。如果为了挑数据而扫描数十遍全集、跑大量复杂计算,那数据选择本身就成了一种巨大的算力开销。业界普遍能接受的数据选择开销,大概是全量训练的5%到10%。超过这个比例,数据处理成了新的瓶颈,大家就会觉得“不如直接训练算了”。

所以OPUS强调的“高效”不是锦上添花,而是核心约束。这意味着不能对每条样本跑一次昂贵的大模型推理来打分,也不能做复杂度太高的重聚类算法。必须找到一种近似方式,在信息丢失可以接受的范围内,极大降低计算复杂度。数据选择,本质上是一个“信息压缩与优先级排序”的过程,把几百TB文本的信息,压缩成一个能代表文本多样性的索引结构,再在这个结构上做轻量化决策。

2. OPUS核心设计:把数据选择变成“分层索引 + 组合筛选”

2.1 第一层:用聚类构建数据的地图

拿到一批海量文本后,第一步不是逐条打分,而是先做一次聚类。聚类的目的,是把语义上相似的大量文本集合成小的簇。每个簇可以理解成“同一种句式结构”或“同一类知识主题”的代表。这一步做完之后,原先几亿条文本就变成了几十万个簇,后续的计算都在簇的层面上进行,规模一下子降了几个数量级。

这里有个细节值得多说一句:聚类不是随便跑个KMeans就行。文本嵌入的质量直接决定了聚类的有效性。我试过用普通的句子编码器做嵌入再做聚类,结果发现簇内部混杂着风格差异很大的文本,原因是嵌入模型没有对领域语义做充分对齐。后来改成了一层轻量微调的嵌入模型,或者直接用预训练好的高质量文本嵌入模型,簇的纯度才明显提升。

OPUS里的做法我不方便替作者下结论,但从这套思路出发,建议你在落地上注意两点:一是嵌入维度要够高,至少能让语义差异映射到欧氏距离;二是聚类数不能太少,否则每个簇内仍然太杂,失去了细粒度挑选的意义。通常是先做一个粗粒度聚类,再在较大簇内做二次细分。

2.2 第二层:用代理模型为簇打分

簇构建好之后,需要给每个簇一个“价值分”。这个分不能靠人排,必须可以量化,最好能跟模型训练收益挂钩。问题是,一个簇里有成百上千条样本,到底该用哪条来代表这个簇?一个合理的思路是,在簇内抽样少量数据,用一个小型代理模型来计算这些样本的“训练价值”。

代理模型不必是最终要训练的大模型,它可以是一个参数量很小的模型,比如几千万参数的模型,用一小部分全量数据预训练出来,然后在这个小模型上跑一些“影响函数”或“梯度匹配”类的计算,估算每条样本对模型参数更新方向的贡献。实际落地时,我个人用的是简化版本:用代理模型在验证集上的损失下降幅度,近似作为样本质量的度量。

这里面有一个非常关键的权衡:代理模型到底要多好?“太弱”的代理模型看不出样本之间的细微差异;“太强”的代理模型本身就接近大模型,数据选择过程的成本就会失控。从我的实验看,在数据和训练配置接近的前提下,几百万到几千万参数的代理模型已经能给出相对稳定的分数排序。重点在于,分数是用来做“簇间相对排序”的,不是用来确定绝对收益的,所以即便是近似分数,只要排序稳定,后续组合筛选依然有效。

2.3 第三层:组合选择与配比迭代

打分只是万里长征第一步。真正产生决策的,是在分数基础上做组合选择。这里的核心难题,是如何在“簇的高分”与“簇之间的多样性”之间做平衡。如果一个数据集的某几个高分簇占据了90%的选择份额,模型在训练中会反复看到相似句子,泛化能力反而下降。反过来,如果为了多样性把低分簇全都拉进来,那跟不做选择也没区别。

OPUS这类方法的优势,在这里体现得特别明显。它会把数据选择看成一个带约束的子集选择问题:设定一个选择预算,比如最终只保留40%的语料;再设定一个多样性约束,比如任何两个被选簇的中心点距离不能低于某个阈值,或者说被选簇的覆盖范围尽量均匀;然后在这个约束下,最大化所选数据的总分。这类问题在数学上可以有近似最优解,效率上也可控。

我在实际实现时,还做了一次额外处理:把选择出来的数据配置当作一个初始分配,训练到中段再重新聚类、重新打分、重新选择一次。这种两阶段选择带来的收益比单次选择高不少。原因是模型在训练初期和训练后期,对数据的“需求曲线”是不一样的。早期需要广泛接触语言结构和基础世界知识,后期更需要高质量、有挑战、与目标任务相关的数据。静态的一次性选择,本质上假定了训练过程是均匀的,这个假设太强了。

3. 实操过程:搭建一套基于OPUS思路的数据选择管线

3.1 步骤一:文本分块与嵌入

无论原始数据集是网页抓取、书籍、论文还是混合语料,第一步都是把文档切成适合嵌入和聚类的块(chunk)。我的建议是块大小以512到1024个token为宜,太小的话语义信息不完整,太大的话聚集统计噪声。切割时最好保留段落边界,别硬切句子。

嵌入层的工程实现,可以参考下面的伪流程:

from transformers import AutoTokenizer, AutoModel import torch import numpy as np tokenizer = AutoTokenizer.from_pretrained("your-embedding-model") model = AutoModel.from_pretrained("your-embedding-model") model.eval() def encode_texts(texts, batch_size=256): all_vecs = [] for i in range(0, len(texts), batch_size): batch = texts[i:i+batch_size] enc = tokenizer(batch, truncation=True, max_length=512, padding=True, return_tensors="pt") with torch.no_grad(): out = model(**enc).last_hidden_state[:, 0, :] # CLS token all_vecs.append(out.cpu().numpy()) return np.vstack(all_vecs)

提示:嵌入向量建议做L2归一化。归一化后的向量做余弦相似度计算,等价于向量内积,KMeans的收敛速度和聚类质量都会更好。

3.2 步骤二:聚类与簇中心提取

嵌入全部算完后,进入聚类环节。MiniBatchKMeans是处理千万级以上样本的实用选择,速度快、显存占用低。聚类数怎么定?我的经验是,先设定一个较大的K,跑完看每个簇的样本数量和簇内平均距离,如果某个簇平均距离太高,说明簇内该二次切分了。

聚类之后,保存每个簇的中心向量、簇内样本索引、簇内样本数。这些信息是后续数据选择的主要输入。

from sklearn.cluster import MiniBatchKMeans import numpy as np kmeans = MiniBatchKMeans(n_clusters=50000, batch_size=10000, n_init=3, random_state=42) labels = kmeans.fit_predict(normalized_vectors) cluster_centers = kmeans.cluster_centers_ cluster_counts = np.bincount(labels)

如果你觉得50万簇还是太多,后续在簇间做二次聚合也是可以的。整个索引的构建,在几千张卡并联的情况下,几个小时到一天内能完成,这个开销是可以接受的。

3.3 步骤三:代理模型评分与样本抽样

接下来就是给每个簇打价值分。模拟实现时,我在每个簇里随机抽出最多若干条文本,组成一个“代表子集”。在这个子集上跑影响分析。严格的影响函数需要计算二阶导的逆,太贵了。我采用了一种更轻的方法:用代理模型在验证集上求梯度,然后把样本的梯度方向跟验证集梯度方向做余弦相似度。相似度越高,说明这条样本对验证集能力有正向贡献。

可能有人会问,验证集哪里来?我会在下游任务的数据里划出一部分固定验证集,保证它跟训练数据不重叠。实际操作上,对十亿级别文本做一次这样的打分,用小代理模型在几百张卡上跑一个晚上就能完成,总开销可控。

3.4 步骤四:带多样性约束的选择器

最后一步是组合选择。这部分我建议把问题建模成0-1整数线性规划或贪心算法。贪心策略在几千万样本规模上速度很快:每一轮选择当前分数最高、且与已选中心距离大于阈值的簇。实践中,单纯用贪心已经能获得不错的效果。

如果你想更精细一点,可以用下面的打分函数做取舍:

  • 基础分:簇的平均价值分。基于代理模型对簇内样本的估算。
  • 多样性补偿:该簇与已选簇的最小中心距离,如果距离太近,则降权。
  • 规模惩罚:簇内样本数量极少的,一次性得分偏高但承载的知识可能太少,给一个对数补偿。

整个选择器可以写得极其简单,关键是约束条件要设计好。下面给一个可视化的伪代码:

# 伪代码:贪心多样性选择 selected = [] selected_centers = [] for round in range(budget): best_cluster = None best_score = -1e9 for c in all_candidates: if c in selected_clusters: continue base_score = cluster_value_score[c] diversity = min(np.linalg.norm(cluster_centers[c] - center) for center in selected_centers) adjusted = base_score + alpha * min(diversity, threshold) if adjusted > best_score: best_score = adjusted best_cluster = c selected.append(best_cluster) selected_centers.append(cluster_centers[best_cluster])

3.5 步骤五:训练中段的动态重选

我第一次执行静态选择时,发现中后期训练损失曲线仍然有抖动,评估指标也没有明显优势。后来把数据选择拆成两个阶段:第一阶段先用均匀采样或全量数据训5%到10%的步数,然后在这个短训后的模型基础上,重新计算样本价值分,再做第二次数据选择。这个“小成本启动 + 动态重选”的方法,比一次性的选择稳定很多。

二次选择的成本,主要是重新嵌入和重新打分,由于候选池已经缩小到之前挑选的子集,开销也就几小时到十个小时级别,可以接受。

4. 工具选型与参数调优:手把手的配置参考

4.1 常用组件与选型理由

  • 嵌入模型:建议选用通用语义向量模型,不要用太小的编码器。CLS向量或mean pooling都行,但要保持一致。
  • 聚类工具:MiniBatchKMeans工程化程度高;如果数据量到了百亿级别,可以考虑先做随机采样,把样本量降到一个亿以内再聚类。
  • 代理模型:选择跟最终模型架构同类的模型,但参数量缩小到1/10甚至1/50。比如说最终模型是13B,代理模型用125M到1.5B都行,关键是要在相同的tokenizer和词汇表基础上训练或微调过。
  • 打分矩阵:影响函数类计算需要保存每样本梯度,内存占用大。建议不保存全梯度,只保存随机投影后的低维梯度指纹,减少显存和磁盘开销。

4.2 关键参数的建议值

参数建议范围备注
文本块大小512 ~ 1024 token太大则聚类特征模糊,太小则噪声高
聚类别数1万到50万视数据规模而定,大数据集往上限靠
保留数据比例30%到70%太少则知识覆盖不足,太多则选择意义变小
多样性阈值0.1到0.5基于嵌入空间距离,需在验证集上调
代理模型参数量终模的1/50到1/10代理模型不必很强,但必须与终模同源
重选轮次2到3次超过3次收益递减,计算开销增加

4.3 训练消耗对比

以一个100B token的训练计划为例,传统全量训练,假设总消耗为1个单位算力。使用OPUS式选择流程后,实际训练数据缩减到40%,加上数据选择过程的额外开销,整体算力消耗约为0.5到0.6个单位。也就是说,可以节省接近一半的算力,或者用同样的算力把数据轮数翻倍。当然,这是理想情况,实际收益取决于数据集的冗余程度以及任务分布与预训练分布的对齐程度。

5. 常见问题与排查技巧实录

5.1 聚类数怎么定才合适?

聚类数太少,簇内部太杂,谈不上精细选择。聚类数太多,计算量大,而且很多小簇本身信息不够,还会拖慢选择器。我个人的调整策略是用“簇内平均距离”做诊断:如果某个簇的样本与中心点平均距离大于整体均值两倍,说明这个簇过糙,需要增大K值或针对该簇二次聚类。如果训练评估显示某些差评任务是因为学习到的知识太少,多半是这些领域的文本被切到了太粗的簇里,被错误地过滤掉了。

5.2 打分不稳定怎么办?

代理模型训练不充分,或者验证集分布跟训练语料差太远,都会导致打分抖动。我遇到过最典型的场景是:验证集主要来自英文技术博客,训练语料却混合了大量中文百科,代理模型对这部分的梯度方向一直不稳定。解决方案是扩展验证集的覆盖范围,保证每个大簇都有对应的验证样本,否则该簇的价值分就无法度量。

5.3 多样性约束加太强会损失精度

多样性约束的核心是为了避免同质数据扎堆,但阈值设得太大,会把真正高质量但语义接近的簇排除掉。调整时,我建议先不加多样性约束跑一版,看选出来的数据分布长什么样。如果某些领域占比过高,再把多样性阈值从0慢慢调大,直到训练集在不同主题上的覆盖比较均衡为止。这个调节过程用聚类标签的分布来监控就行,非常直观。

5.4 在线数据流场景能套用吗?

能,但需要做增量版本。静态数据选择是离线假设,在线场景下数据是持续到达的,无法一次性聚类。我的做法是:把在线数据按天或按小时切片,每个切片都做一次小规模局部聚类和打分,然后把历史簇与新簇的相似度算出来,合并相近簇,淘汰低分簇。这样数据选择就成为持续进行的轻量化后台任务,训练主进程按周期同步最新的选择结果。

5.5 如何验证“选择出来的数据确实更好”?

最直接的方法是固定训练步数和模型结构,做几组对照:全量训练、随机子集训练、按OPUS方式选择的子集训练。只看下游任务分数还不够,我建议同时记录训练集损失和验证集损失的差距。如果选择有效,验证集损失应该在全量训练损失基本持平的情况下更低,或者下游任务分数明显提升。还有一个值得看的指标是“被选择数据上训练时模型权重更新的多样性”,这类梯度方向的多样性越高,说明数据越能帮助模型避开局部最优。

6. 对“in Every”的延伸思考:让数据选择贯穿训练的每一个角落

项目标题末尾的这个短语,虽然原文没有展开,但我觉得它点出了一个真实需求:数据选择不应该只在预训练开头做一次,而是应该在训练的每一个阶段、每一条数据、每一个训练任务上都有回应。我在实操中越来越感觉到这句话的分量。

预训练早期需要的是“广覆盖”,这时选择器应偏向多样性,让模型建立完整的语言先验。训练中期需要的是“深理解”,此时应偏向知识密集的高质量文本,尤其是需要逻辑推理或多步推断的长文档。到了预训练后期,模型已经有很强的生成能力,这时候最需要的反而是“纠偏数据和风格对齐数据”,比如去除重复表达、平衡偏见和语气。OPUS式的方法如果你把打分机制换成按阶段切换权重,完全可以支持这种“阶段性、动态化”的数据选择。

同样,“in Every”还可以理解成“每一个训练尺度下的数据选择”。在不同模型规模下,数据选择的目标函数可能完全不一样。小模型学习能力弱,太吵的数据只会拖慢收敛,应该更激进地修剪;大模型容量大,承受噪声的能力强,但冗余数据带来的计算浪费也更明显,选择策略应更看重知识密度和互补性。代理模型的规模和数据选择粒度,都应该随这个尺度自适应调整。

我自己在多次实操后有一个很深的体会:数据不是越多越好,也不是越“难”越好,而是要跟模型的当前状态形成一种“同步进化”的关系。静态红利很容易被吃光,动态选择才是可持续的路径。

最后再分享一个小技巧。数据选择过程中的很多实验,不需要用完整的大模型来验证。你可以先在一个小规模模型和一个中等规模数据子集上,用OPUS式流程跑通,再逐步放大。我在20B token数据上做过一次快速验证,使用了代理模型打分、4万簇聚类、两阶段重选,从数据准备到训练结束大概耗时三天,就把整个流程的坑都摸清了。把小的跑通,大版本的实现就只是扩容和参数微调的问题了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询