27届大模型岗面试准备(五):预训练全流程拆解------从数据清洗到 Tokenizer 再到 PT 的每一个环节
面试官问"讲讲预训练流程"时,他想听的不是"收集数据然后训练"这种一句话答案,而是你能不能把数据管线、Tokenizer、训练配置三大块的关键决策点讲清楚。27届校招里,即使岗位 JD 写的是"微调/应用方向",预训练流程依然是高频考点------因为它是判断候选人对大模型理解深度的分水岭。这篇按照真实预训练项目的执行顺序,把每个环节的原理、工程实现和面试追问一次讲透。
一、全流程鸟瞰:预训练到底分几步
先给出全景,后面逐节展开:
- 数据获取:网页(Common Crawl)、书籍、代码(GitHub/The Stack)、论文(arXiv)、百科、对话等多源采集;
- 数据清洗:语种识别 → 质量过滤 → 去重 → 有害内容过滤 → 隐私信息(PII)脱敏;
- 数据配比与课程:不同来源按比例混合,可能分阶段调整(курriculum);
- Tokenizer 训练:BPE/BBPE/Unigram,决定词表大小与压缩率;
- 预训练(PT):Next Token Prediction 目标,配合学习率调度、混合精度、分布式并行;
- 评估与退火:训练中持续评估 loss/下游任务,末期用高质量数据做 learning rate annealing。
面试官常从任意一环切入往下挖,所以每一环都要有"能讲三分钟"的储备。
二、数据清洗:预训练成败的第一决定因素
业界共识是"数据质量 > 模型结构微创新"。LLaMA、Qwen、DeepSeek 的技术报告里,数据处理章节的篇幅都远超模型结构。清洗管线通常包含以下步骤:
语种识别:用 fastText 的语言分类器打分,保留目标语种置信度高的文档。中文模型通常保留中英双语,因为英文语料在代码、学术领域的质量密度更高。
质量过滤分两派:
- 规则派(启发式):Gopher 规则是代表------文档词数在 50~100k 之间、平均词长 3~10、符号占比不超标、含有足够多的常见停用词等。优点是便宜、可解释;缺点是误伤高质量的特殊文本(如诗歌、表格)。
- 模型派:训练一个小分类器(fastText 或 BERT 级别),用高质量语料(维基、书籍)当正例,随机网页当负例,给每个文档打质量分。LLaMA 系列用的就是类似思路(以维基引用页面为正例)。
去重是重中之重。重复数据会导致模型记忆而非泛化,还会放大隐私泄露风险。三个层级:
- URL 级:直接对 URL 去重,最便宜;
- 文档级:MinHash + LSH(局部敏感哈希)做近似去重,容忍轻微改写;
- 段落/行级:精确哈希去掉模板化内容(导航栏、版权声明)。
面试高频追问:MinHash 为什么能近似 Jaccard 相似度? 答案要点:MinHash 的核心性质是"两个集合的最小哈希值相等的概率等于它们的 Jaccard 相似度"。对每个文档的 n-gram 集合取 k 个独立哈希函数的最小值组成签名,两个签名逐位比较的相等比例就是 Jaccard 的无偏估计。再配合 LSH 分band,把候选对检索从 O(n²) 降下来。
三、数据配比:不是越多越好,而是配得越准越好
不同来源的数据对模型能力的贡献不同,配比是预训练的核心超参之一:
| 数据源 | 典型占比 | 主要贡献 | 风险 |
|---|---|---|---|
| 网页(CC 清洗后) | 50%~70% | 语言多样性、世界知识 | 质量参差、噪声多 |
| 代码 | 10%~20% | 逻辑推理、结构化输出 | 占比过高伤自然语言流畅度 |
| 书籍 | 5%~10% | 长文本连贯性、叙事能力 | 版权风险 |
| 学术论文 | 3%~8% | 专业知识、严谨表达 | 领域分布偏 STEM |
| 百科 | 2%~5% | 事实密度高 | 体量有限 |
| 数学 | 3%~10% | 数学推理 | 高质量数学语料稀缺 |
两个面试要点:一是代码数据被证明能提升非代码任务的推理能力 (多个消融实验支持),这是"为什么纯 NLP 模型也要混代码"的标准答案;二是多轮退火(annealing)阶段用高质量小数据------训练末期把学习率降下来的同时切换到教科书级语料,能显著提升下游 benchmark,MiniCPM、Llama 3 都公开过这个技巧。
四、Tokenizer:被低估的关键组件
Tokenizer 决定了模型"看世界的粒度"。主流选择是 BBPE(Byte-level BPE),GPT 系列、LLaMA、Qwen 都在用。
BPE 训练过程 :从单字符(或字节)词表出发,统计语料中相邻 token 对的共现频率,迭代合并最高频的对,直到词表达到目标大小。BBPE 的改进是以字节为初始单元,任何 UTF-8 字符都能表示,彻底消灭 OOV,代价是中文等非拉丁文字可能被切成多个字节。
词表大小的权衡 :词表越大,压缩率越高(同样文本的 token 数越少,等效上下文更长、推理更省),但 embedding 矩阵参数越多、低频 token 训练不充分。LLaMA-2 是 32k(对中文很不友好,一个汉字常被拆成 2~3 个 token),Qwen 用 152k 的大词表显著提升了中文压缩率,DeepSeek-V3 用 128k。中文压缩率是国产模型面试的高频考点:同一段中文,LLaMA-2 tokenizer 的 token 数可能是 Qwen 的 1.7 倍以上,直接影响推理成本和有效上下文。
五、可运行代码:迷你数据清洗 + BPE 训练管线
下面这段代码用纯 Python 标准库实现一个可运行的迷你管线:启发式质量过滤 → MinHash 近似去重 → 简化版 BPE 训练。直接 python pipeline.py 即可运行。
python
import re
import hashlib
from collections import Counter
# ---------- 1. 启发式质量过滤(Gopher 风格规则的简化版) ----------
def quality_filter(doc: str) -> bool:
words = doc.split()
if not (10 <= len(words) <= 100000): # 词数范围
return False
avg_len = sum(len(w) for w in words) / len(words)
if not (1.0 <= avg_len <= 12.0): # 平均词长
return False
symbol_ratio = len(re.findall(r'[#@$%^&*]', doc)) / max(len(doc), 1)
if symbol_ratio > 0.1: # 符号占比
return False
return True
# ---------- 2. MinHash 近似去重 ----------
def minhash_signature(doc: str, num_hashes: int = 64, ngram: int = 3):
tokens = doc.split()
shingles = {' '.join(tokens[i:i+ngram]) for i in range(len(tokens)-ngram+1)}
if not shingles:
return None
sig = []
for seed in range(num_hashes):
min_h = min(
int(hashlib.md5(f"{seed}:{s}".encode()).hexdigest(), 16)
for s in shingles
)
sig.append(min_h)
return tuple(sig)
def jaccard_est(sig1, sig2):
return sum(a == b for a, b in zip(sig1, sig2)) / len(sig1)
def dedup(docs, threshold: float = 0.8):
kept, sigs = [], []
for d in docs:
sig = minhash_signature(d)
if sig is None:
continue
if all(jaccard_est(sig, s) < threshold for s in sigs):
kept.append(d)
sigs.append(sig)
return kept
# ---------- 3. 简化版 BPE 训练 ----------
def train_bpe(corpus: str, num_merges: int = 50):
# 初始:每个词拆成字符序列,词尾加 </w>
words = Counter(corpus.split())
vocab = {tuple(w) + ('</w>',): c for w, c in words.items()}
merges = []
for _ in range(num_merges):
pairs = Counter()
for word, cnt in vocab.items():
for i in range(len(word) - 1):
pairs[(word[i], word[i+1])] += cnt
if not pairs:
break
best = max(pairs, key=pairs.get)
merges.append(best)
new_vocab = {}
for word, cnt in vocab.items():
w, i = [], 0
while i < len(word):
if i < len(word)-1 and (word[i], word[i+1]) == best:
w.append(word[i] + word[i+1]); i += 2
else:
w.append(word[i]); i += 1
new_vocab[tuple(w)] = cnt
vocab = new_vocab
return merges
if __name__ == "__main__":
raw_docs = [
"the quick brown fox jumps over the lazy dog again and again in the field",
"the quick brown fox jumps over the lazy dog again and again in the park", # 近重复
"#### @@@@ $$$$ spam spam spam", # 低质量
"language models are trained with next token prediction on large corpora",
]
cleaned = [d for d in raw_docs if quality_filter(d)]
print(f"质量过滤: {len(raw_docs)} -> {len(cleaned)}")
deduped = dedup(cleaned, threshold=0.6)
print(f"MinHash 去重: {len(cleaned)} -> {len(deduped)}")
merges = train_bpe(' '.join(deduped), num_merges=20)
print(f"BPE 前 5 个合并规则: {merges[:5]}")
运行结果会展示:4 篇文档过滤掉 1 篇低质量、去掉 1 篇近重复,最终用 2 篇干净文档训练出 BPE 合并规则。虽然是玩具规模,但过滤→去重→分词训练的骨架和工业界完全一致,面试手撕环节写出 MinHash 或 BPE 任意一个都非常加分。
六、预训练目标与训练动力学
训练目标就是 Next Token Prediction:最大化 P(x_t | x_<t),损失是交叉熵。看似简单,但有几个面试深挖点:
Scaling Law:Chinchilla 定律指出,给定算力预算,模型参数量 N 和训练 token 数 D 应大致满足 D ≈ 20N 才算"算力最优"。但推理成本的存在让业界普遍"过训练"(over-train)------LLaMA 3 8B 训了 15T token,远超 Chinchilla 最优点,因为小模型多训省下的是部署后每一次推理的钱。这个"训练最优 vs 推理最优"的权衡是高频考点。
学习率调度:主流是 warmup + cosine decay。warmup(通常几百到几千步)防止训练初期梯度爆炸;cosine 衰减到峰值的 10% 左右。近年 WSD(Warmup-Stable-Decay)调度兴起:中段恒定学习率方便随时分叉做退火,MiniCPM 与 DeepSeek 系列使用。
混合精度与稳定性:BF16 已成为标配(比 FP16 动态范围大,不易溢出);梯度裁剪(clip norm 1.0)、z-loss、QK-norm 等手段防 loss spike。遇到 loss 突刺的标准处理:回滚到最近 checkpoint + 跳过问题数据批次。
七、面试答题框架与自检清单
被问"如果让你从零预训练一个 7B 模型,你会怎么做"时,推荐按这个框架作答:
- 明确目标:通用还是领域模型?决定数据配比与词表设计;
- 算力核算:用 6ND 估算 FLOPs(N 参数量、D token 数),反推 GPU 时;
- 数据管线:采集 → fastText 语种/质量过滤 → MinHash 去重 → PII 脱敏,产出比目标训练量多 20% 的 token 备用;
- Tokenizer:BBPE,中文场景 100k+ 词表,先在样本语料上测压缩率;
- 训练配置:BF16、AdamW(β2=0.95)、warmup+cosine、序列长度分阶段拉长(先 4k 后 32k,省算力);
- 监控:loss 曲线、梯度范数、下游任务小评测集,出现 spike 有回滚预案;
- 收尾:退火阶段上高质量数据,产出 base 模型再进入 SFT。
自检清单:能手推 BPE 合并过程吗?能解释 MinHash 原理吗?能说出 Chinchilla 结论和它为什么被"违背"吗?知道为什么混代码数据吗?这四个问题全能答上,预训练环节基本稳了。
下一篇讲 SFT:数据怎么构造、损失怎么算、什么样的 SFT 数据是好数据。