去重:exact / MinHash / SemHash / 质量分桶¶
更新日期:2026-04-26
去重是数据 pipeline 决定模型质量第二大杠杆(仅次于规模)。Common Crawl 的原始 unique 文本在 2-5T tokens 量级;如果不去重,"训了 10T tokens" 实际可能只见过 1.5T 独立内容。本页拆三层:精确去重 / 近似去重(MinHash + SemHash)/ 质量分桶。
一、为什么必须去重¶
flowchart LR
raw["原始爬取<br/>(每条都进训练)"] -- 不去重 --> ovef["over-fit<br/>+ memorization"]
raw -- exact 去重 --> half["半-unique<br/>(URL 重复消除)"]
half -- MinHash 近似去重 --> qual["quality-tier<br/>(boilerplate 消除)"]
qual -- SemHash --> sem["真正语义去重<br/>(同义改写消除)"]
classDef pos fill:#fff,stroke:#cc785c;
class raw,half,qual,sem pos
classDef neg fill:#f5f3eb,stroke:#bdb9ab;
class ovef neg
实证:
- LLaMA-1 论文:去重后 token 量从 2.4T 降到 1.4T,但 perplexity 改善
- DCLM-Baseline:MinHash + Bloom 把 240T 池子压到 4T,模型质量比直接训 240T 更好
- The Pile / RedPajama 都把"去重失败"列为头号 lesson learned
去重不是可选项 —— 不去重训练就是把同一个 fact 反复塞进模型,长 context 容易 verbatim 复述(版权风险 + memorization 攻击面)。
二、三层去重¶
2.1 Exact Dedup(URL / Hash 级)¶
最简单也最先做:
# URL 级去重
seen_urls = set()
def keep(doc):
canonical = canonicalize_url(doc.url) # 去 query string trailing slash 等
if canonical in seen_urls:
return False
seen_urls.add(canonical)
return True
# 内容 hash 级(处理同 content 不同 URL 的镜像站)
def content_hash(text):
# 标准化前处理
normalized = re.sub(r'\s+', ' ', text.strip().lower())
return hashlib.sha256(normalized.encode()).hexdigest()
效果:CC 单 snapshot 内 URL 去重消 ~10-15%;跨 snapshot 累计去重消 ~70-80%(因为同 URL 月月被抓)。
工具:直接 Python set / Redis SET / Bloom filter(节省内存:1B 文档用 12-24GB Bloom filter,false positive ~1% 完全够用)。
2.2 Near-Dup(MinHash + LSH)¶
问题:站点 A 转载站点 B 的内容,URL 不同、内容 99% 一样、boilerplate 不同 → exact dedup 检测不到。
思路:把每个文档转成固定大小的"指纹",指纹相似度 ≈ 文档相似度。
MinHash 数学¶
文档 → n-gram 集合(shingles)→ 用 K 个独立 hash 函数取最小值 → K 维签名向量。
理论保证:E[相同位数 / K] = |A ∩ B| / |A ∪ B|。
# 简化实现
def minhash_signature(text, num_hashes=128):
shingles = set(get_ngrams(text.split(), 5)) # 5-gram 主流
signature = []
for i in range(num_hashes):
min_h = min(murmurhash(s, seed=i) for s in shingles)
signature.append(min_h)
return signature
LSH(Locality-Sensitive Hashing)¶
128 维签名两两比较是 O(N²),1B 文档 → 1e18 次比较,不可行。
LSH 思路:把 128 维签名分成 b 个 bands,每 band 内 r 维。任一 band 内完全相同 → 候选近似对。
调 b, r 控制阈值的"陡峭度":
| 相似度阈值 | 推荐 (b, r) |
|---|---|
| 0.7(去重宽松) | (b=8, r=16) |
| 0.8(典型 LLaMA / FineWeb 设置) | (b=14, r=9) |
| 0.9(保守,仅去高度相似) | (b=20, r=6) |
实操:
from datasketch import MinHash, MinHashLSH
# 1B 文档级别推荐 datasketch + redis 后端
lsh = MinHashLSH(threshold=0.8, num_perm=128, storage_config={
'type': 'redis',
'redis': {'host': 'localhost', 'port': 6379},
})
for doc in docs:
m = MinHash(num_perm=128)
for shingle in get_ngrams(doc.text, 5):
m.update(shingle.encode())
candidates = lsh.query(m)
if not candidates:
lsh.insert(doc.id, m)
else:
mark_dup(doc.id, candidates[0])
工具链¶
| 工具 | 规模 | 备注 |
|---|---|---|
| datasketch(Python) | <100M docs | 经典选择,Redis backend |
| text-dedup(HF) | <1B docs | 流水线化封装 |
| LSHBloom | 1B+ docs | Bloom filter 优化的 LSH,arXiv:2411.04257 |
| Slimpajama-DC dedup | 1T+ docs | Cerebras 开源的工业级 |
| Spark + datasketch UDF | 任意规模 | 工业自建主流 |
5-gram vs 13-gram¶
- 5-gram:宽松,捕捉"段落级相似"(boilerplate + 转载)
- 13-gram(GPT-3 / FineWeb 用):严格,只捕捉"几乎逐字重复"
LLaMA-1 用 5-gram,DCLM 用 13-gram。13-gram 不丢类似主题的不同表述,更适合保留多样性;5-gram 去得更狠。
2.3 SemHash / Semantic Dedup¶
MinHash 的局限:同义改写("猫坐垫上" vs "垫上有猫")n-gram 重合度低,MinHash 视为不同 → 训练里都进。
SemHash 思路:用 embedding 模型把文档投到向量空间,向量近邻 = 语义相似。
# 用 Sentence-Transformers / E5 / BGE 等
from sentence_transformers import SentenceTransformer
import faiss
model = SentenceTransformer('BAAI/bge-large-en-v1.5')
# 文档前 512 token 嵌入(全文 embed 太贵)
embeddings = model.encode([doc.text[:2048] for doc in docs])
# FAISS 构 index 找近邻
index = faiss.IndexFlatIP(embeddings.shape[1])
index.add(embeddings)
# 阈值 0.95 cosine = 几乎肯定语义重复
for i, emb in enumerate(embeddings):
D, I = index.search(emb.reshape(1, -1), k=5)
for j, sim in zip(I[0], D[0]):
if i != j and sim > 0.95:
mark_semantic_dup(i, j)
SemHash vs MinHash:
| 维度 | MinHash | SemHash |
|---|---|---|
| 计算开销 | 低(hash) | 高(embedding 模型 forward) |
| 1B docs 单机 | 几小时 | 数天(GPU) |
| 抓"同义改写" | 不抓 | 抓 |
| 抓"机翻 / 转译" | 不抓 | 部分抓 |
| 标准做法 | 主流默认 | 加在 MinHash 之后做 second pass |
Pipeline 实践:MinHash 作主去重(处理 99% 工作量),SemHash 在 MinHash 留下的"幸存者"上做精筛 —— 不是替代,是叠加。
论文参考¶
- SemDeDup(Abbas et al. 2023, arXiv:2303.09540):embedding-based dedup,证明在 LAION 上去 50% 数据但模型 zero-shot 提升
- D4: Improving LLM Pretraining via Document De-Duplication(Tirumala 2023, arXiv:2308.12284):把 SemDeDup 推到文本,进一步证明语义去重收益
- DataComp-LM 用 Bloom + LSH,未上 SemDeDup(速度优先)
三、去重的"边界 / 噪声"问题¶
3.1 阈值敏感性¶
设 0.8 还是 0.85 直接影响留多少。LLaMA-1 论文用 0.8,删 ~30% 内容;某 frontier lab 内部用 0.85 保留更多多样性 —— 没有"普世正确"阈值,要 ablate。
3.2 跨文档结构重复¶
文档自身全句不重复,但 100 万个文档结构都一样(template 生成的 stub 页)—— MinHash 部分捕捉,需要再加"结构指纹":
# 把所有非锚名字的 token 替换成 mask,看模板
template = re.sub(r'[A-Z][a-z]+', '[NAME]', doc.text)
template = re.sub(r'\d+', '[NUM]', template)
template_hash = hash(template[:500])
# 同一 template_hash 出现 >100 次 → 模板生成
3.3 SoftDedup(不删,只降权)¶
新方向:重复文档不删,但训练采样时降低概率:
# 在 cluster 里:cluster_size 越大,每个文档采样权重越小
def softdedup_weight(cluster_size, alpha=0.5):
return 1.0 / (cluster_size ** alpha)
好处:
- 不丢"罕见上下文"(比如同一 fact 在不同主题下出现,每次都有独特上下文)
- 平滑数据分布,避免阈值断崖
- 配合 RHO / cluster-aware sampling 可以做更细的训练 dynamics
参考:SoftDedup(Liang et al. 2023,arXiv:2407.06654)。FineWeb / DCLM 暂未默认采纳,但 frontier 内部正在 ablate。
四、Quality Reward Model:分桶 + 多维打分¶
去重之后,剩下的文档质量参差。不是简单"留 / 弃"二元,而是"打分 → 分桶 → 训练时按桶配比"。
4.1 单维度打分(旧)¶
历史方案:fastText 二分类(高质量 / 低质量)。LLaMA-1 / GPT-3 / OPT 都用。
问题:单一维度 = 单一审美。"教育性强"的文档不一定"逻辑清晰","信息密度高"的不一定"语言流畅"。
4.2 多维 Reward Model(FineWeb-Edu / DCLM 路线)¶
FineWeb-Edu:用 LLaMA-3-70B 给样本打 0-5 分"教育价值",训一个小 BERT 分类器,再对全集打分。单维度,但用 LLM 打的"细维度"。
RedPajama V2 路线:30+ 维度信号,不用一个分数过滤,作为 metadata 留给下游用户。
# RedPajama V2 每个文档伴随的 quality signals (节选)
signals = {
'doc_length': 1234,
'word_count': 250,
'mean_word_length': 4.5,
'lang_id_score': 0.99,
'perplexity_kenlm': 215.4, # KenLM 5-gram PPL
'wikipedia_classifier_score': 0.82, # FastText 分类器
'oh_classifier_score': 0.74, # OpenHermes 风格分类器
'nsfw_score': 0.02, # NSFW 检测
'fraction_lines_ending_with_punct': 0.91,
'fraction_chars_in_dupe_5grams': 0.12,
'fraction_lines_dupe_5grams': 0.08,
'fraction_chars_in_top_2gram': 0.05,
'fraction_chars_in_top_3gram': 0.03,
# ... 30+ 项
}
下游用户挑维度组合阈值,不同模型规模 / 目标用不同子集。
4.3 维度家族¶
| 家族 | 维度示例 | 信号源 |
|---|---|---|
| 结构 | 长度、段落数、平均句长、标点比例、列表 / 表格占比 | 规则 |
| 语言 | language id、语言连贯性(PPL)、字符分布 | fasttext / KenLM |
| 重复 | dupe-line ratio, top-n-gram 占比 | 自计算 |
| 教育性 | 是否像教材、解释性、有 example | LLM-judge |
| 逻辑 | argument 结构、引证密度 | LLM-judge |
| 领域 | code / math / news / fiction / forum 分类 | 多类 fasttext |
| 安全 | NSFW、toxicity、PII | 专用分类器 |
| 新颖 | 跨语料相似度(与 Wikipedia / 教科书的距离) | embedding |
4.4 训练时的桶用法¶
# 训练数据 sampler 按桶分别加权
def get_batch():
sources = {
'high_edu': 0.3, # FineWeb-Edu top tier
'medium_edu': 0.2,
'code': 0.15, # The Stack
'math': 0.1, # OpenWebMath
'multilingual': 0.1,
'general_web': 0.15, # FineWeb 剩余
}
return weighted_sample(sources)
桶配比的 ablation 是 frontier 训练的关键 hyperparameter,详见 mixture.md。
五、典型 dedup pipeline(FineWeb 风格)¶
flowchart LR
raw["WARC/extracted text"] --> exact["exact dedup<br/>(content hash)"]
exact --> mh["MinHash<br/>(5-gram, threshold 0.8)"]
mh --> qc["quality classifier<br/>(fasttext + Edu LLM-judge)"]
qc --> bucket["分桶<br/>(low / mid / high)"]
bucket --> sem["SemHash<br/>(only high tier)"]
sem --> mix["mixture sampling"]
classDef step fill:#fff,stroke:#cc785c;
class raw,exact,mh,qc,bucket,sem,mix step
实操数字(仅供参考,需各家自家 ablate):
- exact dedup 留 ~70% (消跨 snapshot 重复)
- MinHash 0.8 留 ~50% (消近似重复)
- quality 50%-percentile 阈值留 ~50%(中位数以上)
- 综合后从 240T → 4T(DCLM-Baseline ratio)≈ 1.7%
六、追问延伸¶
| 问题 | 方向 |
|---|---|
| 为什么不在 token 级去重? | token 序列 ID 没有词汇 / 语义信息,相似度计算无意义。所有 dedup 必须在原始文本阶段做 |
| 去重应该在 quality filter 之前还是之后? | 之前。先去重再筛质量,避免对同一文档反复打分 |
| 1T 文档 MinHash 工程上能做吗? | 能。Spark + LSHBloom 30+ 节点单 day 跑完 1T 级 |
| 怎么验证去重做对了? | sample 1000 对"被判定相似"的文档,人工 / LLM-judge 打 precision;sample 1000 对"被判定不相似"的,看是否真不相似算 recall |
| RedPajama V2 不删数据,下游怎么用? | 用户选维度阈值过滤。FineWeb-Edu = 在 V2 池上加 educational threshold |
| GPT-4 / Claude 训练用什么 dedup? | 不公开。猜测都包含 MinHash + SemHash + 质量分桶;DatologyAI 类公司提供专业服务 |
参考链接¶
- LLaMA-1 论文(dedup 章节)
- LSHBloom: Internet-Scale Deduplication
- SemDeDup 论文
- D4: Document De-Duplication for LLM
- SoftDedup
- datasketch
- text-dedup
- RedPajama V2 论文