文章

LLM 预训练数据去重生产实战:用 MinHash-LSH、语义去重与污染门禁降低记忆和评测泄漏

大模型预训练语料中的模板页、转载文和基准变体会放大记忆、浪费算力并污染评测。本文给出从精确去重、MinHash-LSH 到语义去重、基准隔离和阈值回放的完整上线方案,覆盖四层去重架构、工程流水线、阈值校准与常见误区。

背景:重复语料不只是浪费存储

预训练语料中的重复内容通常来自网页镜像、转载新闻、模板化商品页、站群页面、代码仓库 Fork、合成数据重复生成,以及同一基准题目的翻译或改写版本。

这些重复不会只增加磁盘占用。它们会改变训练样本的实际权重,使少数文本被模型反复看到,带来四类生产问题:

  1. 训练算力被重复内容消耗,有效数据密度下降。
  2. 记忆和逐字复现风险上升,尤其是高频长文本和敏感片段。
  3. 验证集与训练集发生泄漏,离线指标被高估。
  4. 数据版本不可解释,团队无法回答某条样本为何被保留或删除。

Lee 等人在多个语言模型数据集中发现了大量近重复文档和长重复片段,并观察到去重后模型逐字复现训练文本的频率显著下降。这个结论不能直接外推到所有模型和语料,但足以说明:去重是训练数据质量控制,而不是简单的存储优化

核心原理:把”重复”拆成四个层级

生产系统不应使用一个算法和一个阈值处理所有重复。更稳妥的做法是把问题拆成四层。

第一层:规范化后的精确文档去重

先对文本执行可审计的轻量规范化,例如统一换行、Unicode 规范形式和确定性的空白处理,再计算内容摘要。

这一层适合处理:

  • 完全相同的网页快照;
  • 重复导入的文件;
  • 同一语料多次拼接;
  • 采集任务重跑产生的副本。

⚠️ 规范化规则本身必须版本化。若直接删除标点、数字、大小写或 HTML 结构,可能把原本不同的代码、公式、表格和实体信息错误合并。

第二层:精确长片段去重

两篇文档可能整体不同,却共享很长的重复段落。常见场景包括站点页眉页脚、法律声明、转载正文和论坛引用。

可以使用后缀数组、滚动哈希或分块指纹寻找长重复片段。Lee 等人的工作将精确长片段匹配与近重复文档匹配结合使用,说明两者解决的是不同问题。

生产落地时不应只记录”文档被删”,还应记录:

字段说明
命中片段范围重复内容在文档中的起止位置
片段长度token 数或字符数
来源文档重复片段来自哪个文档
保留文档去重后保留哪个文档
规则版本与批次去重规则的版本号和运行批次

第三层:MinHash-LSH 近重复文档聚类

MinHash 用一组哈希签名近似估算两个文档 Shingle 集合的 Jaccard 相似度;LSH 则把可能相似的文档放入同一候选桶,避免全量两两比较。

典型流程是:

  1. 对文档分词并构造 n-gram Shingle;
  2. 生成 MinHash 签名;
  3. 用 LSH 产生候选对;
  4. 对候选对执行更精确的 Jaccard、编辑相似度或包含度校验;
  5. 构建重复关系图并计算连通分量;
  6. 每个重复簇只保留一个确定性代表文档。

⚠️ LSH 只是候选召回器,不应直接承担最终删除决策。否则哈希碰撞和阈值边界会把误判直接变成不可逆数据损失。

第四层:语义去重与基准污染检查

字符串层面的去重无法稳定识别翻译、重述、变量替换和答案顺序调整。SemDeDup 等研究使用预训练模型 Embedding 寻找语义重复,说明语义冗余可以在文本表面差异较大时继续存在。

但语义去重风险也更高。两篇内容接近的文章可能分别来自不同语言、不同立场或不同专业上下文。生产上更适合采用分层使用

  • 对超大 MinHash 簇做二次细分;
  • 对合成语料做重复生成检查;
  • 对高价值来源执行人工抽样;
  • 对基准题、答案、翻译和改写版本建立独立污染扫描。

⚠️ 基准污染不应被混入普通语料去重。它应是一条单独门禁,因为其目标不是压缩语料,而是保护评测可信度。

工程落地:建立可回放的去重流水线

1. 先生成不可变数据快照和稳定 ID

每条原始记录至少保留:

字段说明
document_id稳定唯一标识
source_id数据来源标识
原始 URI / 对象存储位置可追溯的存储路径
抓取时间数据获取时间戳
原始内容摘要未规范化的内容哈希
语言、文档类型和许可信息元数据
数据批次版本批次标识

去重层只生成保留、聚类和排除清单,不直接覆盖原始对象。这样才能在阈值变化或误删时重新回放。

2. 把规范化规则当成数据契约

下面是一个示意配置。阈值必须基于真实语料抽样校准,不能照抄为统一默认值。

dedup_pipeline:
  version: "2026-07-14-v1"
  normalization:
    unicode_form: "NFC"
    normalize_line_endings: true
    collapse_repeated_spaces: true
    remove_boilerplate: false
  exact_document:
    hash: "sha256"
  exact_substring:
    enabled: true
    minimum_match_tokens: "${CALIBRATED_MIN_MATCH_TOKENS}"
  near_duplicate:
    shingle_size: 5
    num_permutations: "${CALIBRATED_NUM_PERMUTATIONS}"
    lsh_threshold: "${CALIBRATED_LSH_THRESHOLD}"
    secondary_verifier: "jaccard_and_edit_similarity"
  semantic_duplicate:
    enabled_for:
      - synthetic_data
      - oversized_duplicate_clusters
      - benchmark_decontamination
    embedding_model_version: "${PINNED_MODEL_VERSION}"
    similarity_threshold: "${CALIBRATED_SEMANTIC_THRESHOLD}"

3. 代表文档必须确定性选择

重复簇中不能简单保留”最先到达”的记录,否则并行任务重跑可能产生不同结果。可以使用稳定排序规则:

from dataclasses import dataclass
from datetime import datetime

@dataclass(frozen=True)
class Document:
    document_id: str
    source_priority: int
    quality_score: float
    license_ok: bool
    captured_at: datetime
    token_count: int

def representative_key(doc: Document) -> tuple:
    return (
        0 if doc.license_ok else 1,
        -doc.source_priority,
        -doc.quality_score,
        -doc.token_count,
        doc.captured_at,
        doc.document_id,
    )

def choose_representative(cluster: list[Document]) -> Document:
    if not cluster:
        raise ValueError("duplicate cluster must not be empty")
    return min(cluster, key=representative_key)

排序依据应写入数据字典,并随去重结果一起保存。若规则升级,必须生成新的数据版本,而不是原地修改旧结果。

4. 把污染门禁放在数据切分之前和之后

建议执行两次检查:

  • 切分之前:使用保留的评测集指纹扫描全量原始语料,移除明显命中的训练候选。
  • 切分之后:再次检查训练、验证和测试之间的精确、近重复与语义重叠,防止聚类拆分或后续增量数据把污染重新带回。

对公开基准至少维护三类指纹:

指纹类型说明
规范化文本和 n-gram 指纹字符串层面的精确匹配
题目与答案拆分后的组合指纹防止拼接绕过检测
翻译、改写和代码变量替换的语义表示覆盖语义层面的泄漏

Yang 等人的研究表明,简单改写或翻译可能绕过基于字符串的污染检测。因此,语义扫描应作为高风险基准的补充手段,而不是完全替代精确匹配。

5. 增量语料不要每次全量重算

持续预训练和周期性爬取场景中,可以维护版本化签名索引:

  • 新文档先与历史保留集比较;
  • 新批次内部再聚类;
  • 只重算受影响的连通分量;
  • 删除和恢复动作都写入变更日志;
  • 定期执行全量审计,防止索引漂移。

需要避免”今天保留 A、明天保留 B”的代表文档震荡。历史代表文档在质量未明显下降时应优先保持稳定。

阈值如何校准

阈值不应从论文或开源配置直接复制。更可靠的方法是按语言、来源和文档类型建立标注样本。

每个阈值版本至少评估以下指标:

评估维度具体指标
去重质量重复对识别的 Precision 和 Recall
数据影响删除 Token 比例
来源均衡每个来源和语言的保留比例
簇分布超大重复簇数量及最大簇规模
污染控制训练集与验证集交叉命中率
基准安全基准精确、近重复和语义命中率
模型效果去重前后的训练 Loss 曲线
回归验证下游能力、长尾领域和多语言回归

阈值变更要先跑 Dry Run,输出”将删除什么”,再由数据负责人批准。对代码、数学、法律文本和少数语言应设置独立策略,因为相同模板和固定表达不一定意味着低价值重复。

适用场景

这套方案适合:

  • 通用大模型预训练语料;
  • 行业模型的持续预训练;
  • 大规模 SFT 或合成数据构建;
  • 代码模型的仓库和 Fork 治理;
  • 多语种网页语料;
  • 需要可信基准评测的模型研发流程。

对于规模较小、来源高度受控的人工数据集,精确哈希和人工审核可能已经足够,没有必要直接引入昂贵的全量语义去重。

常见误区

误区一:删除重复率越高,数据越干净

去重率是结果指标,不是优化目标。过度去重可能删除合理重复、少数语言样本和重要领域表达。

误区二:一套阈值适用于所有来源

新闻转载、论坛对话、源代码、专利和产品页的相似度分布完全不同。全局阈值通常会同时造成误删和漏删。

误区三:语义相似就应该只保留一份

语义相似不代表数据等价。观点、事实时间、许可证、语言和专业语境仍可能不同。

误区四:只在训练完成后检查污染

事后检查只能解释风险,无法收回已经消耗的训练算力,也无法彻底恢复被污染评测的可信度。

误区五:去重结果可以直接覆盖原始数据

没有原始快照、簇证据和规则版本,误删无法恢复,阈值也无法重放。

上线检查清单

  • 原始语料快照不可变,文档 ID 稳定。
  • 规范化、分词器、Shingle 和哈希规则均已版本化。
  • LSH 候选经过二次相似度校验。
  • 重复簇代表文档选择是确定性的。
  • 删除清单包含命中证据、来源和规则版本。
  • 训练、验证、测试之间已执行双向污染扫描。
  • 公开基准包含改写和翻译风险检查。
  • 阈值按语言、来源和文档类型抽样评估。
  • 监控长尾领域和少数语言保留比例。
  • 支持 Dry Run、回滚和历史版本重放。
  • 去重后执行小规模训练与能力回归。
  • 增量签名索引有完整性校验和周期性全量审计。

参考资料

  1. Deduplicating Training Data Makes Language Models BetterarXiv:2107.06499
  2. SemDeDup: Data-efficient learning at web-scale through semantic deduplicationarXiv:2303.09540
  3. Rethinking Benchmark and Contamination for Language Models with Rephrased SamplesarXiv:2311.04850
  4. Dolma: an Open Corpus of Three Trillion Tokens for Language Model Pretraining ResearcharXiv:2402.00159
  5. D4: Improving LLM Pretraining via Document De-Duplication and DiversificationarXiv:2308.12284

常见问题

只做 SHA-256 精确去重够吗?
不够。精确哈希只能识别完全相同的文档,无法覆盖模板字段变化、轻微改写、转载格式差异和只重复局部段落的情况。精确去重应作为第一层防御,而不是完整方案。
MinHash-LSH 的阈值可以跨语言、跨来源统一吗?
不建议。文档长度、语言分词方式、代码与自然语言结构差异都会改变相似度分布,应按来源、语言和文档类型抽样校准,避免全局阈值同时造成误删和漏删。
语义去重是否应该覆盖全部预训练语料?
通常不应直接全量执行。语义去重成本高且更容易误删,应优先用于高风险来源、超大近重复簇、合成语料和基准污染检查,并按语言和领域做回归验证。