大模型算法工程师面试题精讲(一):预训练与数据工程高频15问
文章目录
- 大模型算法工程师面试题精讲(一):预训练与数据工程高频15问
-
- 前言
- 一、数据构建与清洗
-
- [1. 预训练数据是怎么构建的?从原始网页到训练数据经过哪些步骤?](#1. 预训练数据是怎么构建的?从原始网页到训练数据经过哪些步骤?)
- [2. 数据清洗中如何去除重复内容?MinHash和SimHash的区别?](#2. 数据清洗中如何去除重复内容?MinHash和SimHash的区别?)
- [3. 什么是数据配比(Data Mixing)?不同配比对模型能力的影响?](#3. 什么是数据配比(Data Mixing)?不同配比对模型能力的影响?)
- 二、Tokenization与数据配比
-
- [4. Tokenization对模型性能有多大影响?BPE vs SentencePiece vs WordPiece?](#4. Tokenization对模型性能有多大影响?BPE vs SentencePiece vs WordPiece?)
- [5. 什么是Scaling Law?Chinchilla定律给出了什么结论?](#5. 什么是Scaling Law?Chinchilla定律给出了什么结论?)
- [6. 预训练中的数据质量如何评估?有哪些量化指标?](#6. 预训练中的数据质量如何评估?有哪些量化指标?)
- [7. 什么是课程学习(Curriculum Learning)?在预训练中有效吗?](#7. 什么是课程学习(Curriculum Learning)?在预训练中有效吗?)
- 三、训练策略与优化
-
- [8. 预训练数据的时效性问题怎么处理?如何做数据更新?](#8. 预训练数据的时效性问题怎么处理?如何做数据更新?)
- [9. 多语言预训练的数据配比怎么做?tokenizer怎么选?](#9. 多语言预训练的数据配比怎么做?tokenizer怎么选?)
- [10. 什么是数据污染(Data Contamination)?怎么检测和处理?](#10. 什么是数据污染(Data Contamination)?怎么检测和处理?)
- [11. 预训练loss为什么会突然跳升?常见原因和解决方案?](#11. 预训练loss为什么会突然跳升?常见原因和解决方案?)
- [12. Muon优化器是什么?和AdamW比有什么优势?](#12. Muon优化器是什么?和AdamW比有什么优势?)
- [13. 预训练中梯度裁剪的作用和阈值怎么选?](#13. 预训练中梯度裁剪的作用和阈值怎么选?)
- [14. 训练中断后如何恢复?Restore Checkpoint需要注意什么?](#14. 训练中断后如何恢复?Restore Checkpoint需要注意什么?)
- [15. 预训练模型的Token Embedding初始化策略有哪些?](#15. 预训练模型的Token Embedding初始化策略有哪些?)
- 总结
前言
预训练与数据工程是大模型算法工程师面试中最硬核的考察方向之一。无论你面试的是基础模型团队还是应用层算法团队,面试官都会从数据构建、清洗、配比、训练策略等角度切入,考察你对大模型训练全链路的理解深度。本文精选15道高频面试题,覆盖从原始数据到训练优化的完整流程,每题都给出原理拆解和实战建议,帮助你系统梳理知识体系。
一、数据构建与清洗
1. 预训练数据是怎么构建的?从原始网页到训练数据经过哪些步骤?
参考答案:
预训练数据的构建是一个多阶段流水线,核心目标是把互联网上的海量非结构化文本转化为高质量、可训练的token序列。典型流程如下:
| 阶段 | 操作 | 工具/方法 |
|---|---|---|
| 1. 数据采集 | 爬取网页、PDF、代码仓库 | Common Crawl, GitHub API |
| 2. 格式提取 | HTML→纯文本,PDF→文本 | resiliparse, pdfplumber |
| 3. 语言识别 | 分离不同语言文本 | fastText, CLD3 |
| 4. 规则过滤 | 去除过短、乱码、模板页面 | 启发式规则(长度、重复度、特殊字符占比) |
| 5. 质量过滤 | 机器学习分类器过滤低质量内容 | fastText分类器, GPT-4评分 |
| 6. 去重 | 精确去重 + 模糊去重 | Bloom Filter, MinHash LSH |
| 7. 敏感信息处理 | 去除PII、有害内容 | 正则 + NER模型 |
| 8. 数据混合 | 按比例混合不同来源 | Data Mixing策略 |
| 9. Tokenization | 文本→token ID序列 | BPE, SentencePiece |
以LLaMA系列为例,其数据管线包含以下关键决策:
python
# 简化的数据构建管线伪代码
def build_pretraining_corpus(raw_sources):
documents = []
for source in raw_sources:
# 1. 格式提取
text = extract_text(source) # HTML -> text
# 2. 语言识别
lang = detect_language(text)
if lang not in target_languages:
continue
# 3. 规则过滤
if len(text) < min_length: continue
if symbol_ratio(text) > 0.3: continue
if repetition_ratio(text) > 0.3: continue
# 4. 质量打分
quality_score = quality_classifier(text)
if quality_score < threshold: continue
# 5. 去重
if is_near_duplicate(text, documents): continue
# 6. 敏感信息脱敏
text = remove_pii(text)
documents.append(text)
# 7. 数据混合
corpus = mix_data(documents, ratios=config.mixing_ratios)
return corpus
质量过滤是其中最关键的环节,常见做法是训练一个fastText二分类器,正样本来自高质量语料(如Wikipedia),负样本来自随机网页。GPT-3和LLaMA都采用了类似策略。更近期的做法(如Phi系列)使用GPT-4生成合成数据作为训练源,从源头保证质量。
面试加分点:提到CCNet管线(Meta开源),以及"质量过滤器的recall比precision更重要"这一实践洞察------宁可漏掉一些好数据,也不要让太多低质量数据混入。
2. 数据清洗中如何去除重复内容?MinHash和SimHash的区别?
参考答案:
数据去重分两个层次:精确去重和模糊去重。精确去重用Hash或Bloom Filter即可,复杂的是模糊去重------找到"几乎相同但不完全一样"的文档。
MinHash + LSH(Locality Sensitive Hashing):
MinHash的核心思想是:对文档的n-gram集合,用多个独立的哈希函数,每个函数取集合中最小的哈希值作为签名。两个文档的MinHash签名相同的概率等于它们的Jaccard相似度:
P ( min ( h ( A ) ) = min ( h ( B ) ) ) = ∣ A ∩ B ∣ ∣ A ∪ B ∣ = J ( A , B ) P(\min(h(A)) = \min(h(B))) = \frac{|A \cap B|}{|A \cup B|} = J(A, B) P(min(h(A))=min(h(B)))=∣A∪B∣∣A∩B∣=J(A,B)
然后用LSH把签名分成多个band,只要有一个band完全匹配就认为是候选重复对,大幅降低比较次数。
python
from datasketch import MinHash, MinHashLSH
def minhash_dedup(documents, num_perm=128, threshold=0.8):
lsh = MinHashLSH(threshold=threshold, num_perm=num_perm)
for doc_id, text in enumerate(documents):
m = MinHash(num_perm=num_perm)
for ngram in get_ngrams(text, n=5):
m.update(ngram.encode('utf-8'))
lsh.insert(str(doc_id), m)
# 查询重复文档对
duplicates = []
for doc_id, text in enumerate(documents):
m = MinHash(num_perm=num_perm)
for ngram in get_ngrams(text, n=5):
m.update(ngram.encode('utf-8'))
result = lsh.query(m)
if len(result) > 1:
duplicates.append((doc_id, result))
return duplicates
SimHash:
SimHash由Google提出,核心是对文档的每个特征(词/n-gram)计算哈希值,然后根据词频加权得到一个固定长度的指纹。两个SimHash指纹的Hamming距离越小,文档越相似。一般汉明距离≤3认为是近似重复。
| 维度 | MinHash | SimHash |
|---|---|---|
| 相似度度量 | Jaccard相似度 | Cosine相似度 |
| 签名长度 | 通常128-256个整数 | 通常64位 |
| 检索方式 | LSH分band | Hamming距离空间划分 |
| 适合场景 | 文档级去重(整篇相似) | 段落级/局部去重 |
| 计算开销 | 中等(需多个哈希函数) | 较低(单次哈希+加权) |
| 工程成熟度 | CCNet/BigScience大量使用 | Google网页去重经典方案 |
实践选择:大规模预训练数据去重通常用MinHash + LSH(如LLaMA、GPT-3的数据管线),因为文档级Jaccard相似度更直观,且datasketch等库已经做了高度优化。SimHash在搜索引擎网页去重中更常见。两者都可以分布式实现。
面试加分点:提到去重不仅是文档级,还要做"跨文档n-gram去重"(如CCNet的n-gram重复度统计),防止模型memorize重复出现的段落导致eval inflate。
3. 什么是数据配比(Data Mixing)?不同配比对模型能力的影响?
参考答案:
数据配比是指在预训练语料中,不同来源/领域数据的混合比例。它直接影响模型的能力分布------多投Wikipedia模型更擅长正式语言理解,多投代码模型更擅长推理。
典型的预训练数据配比(以LLaMA 2为例):
| 数据来源 | 占比 | 作用 |
|---|---|---|
| Web数据(CC等) | ~67% | 通用语言能力、世界知识 |
| 代码(GitHub) | ~4.5% | 逻辑推理、代码生成 |
| 学术论文 | ~2.5% | 学术理解、专业知识 |
| 书籍(Books3/Gutenberg) | ~4.5% | 长文本理解、文学表达 |
| Wikipedia | ~4.5% | 事实知识、多语言 |
| 新闻 | ~少量 | 时效性知识 |
| 对话数据 | ~少量 | 对话能力 |
数据配比的关键研究发现:
-
DOGE(Domain Reweighting with Generalization Estimation):通过评估不同领域数据对下游任务的泛化贡献,动态调整配比,发现代码和数学数据对推理能力贡献显著高于其占比例。
-
Chinchilla的启示:不仅total tokens重要,数据多样性同样重要。过拟合到单一分布会导致模型在其他分布上表现退化。
-
课程式配比:训练前期多用通用数据建立语言基础,后期增加代码/学术数据比例,类似学习课程的循序渐进。
python
# 数据配比的动态调整示例
mixing_schedule = {
"phase_1": { # 0-50% training
"web": 0.80, "code": 0.05, "academic": 0.02, "wiki": 0.08, "books": 0.05
},
"phase_2": { # 50-90% training
"web": 0.60, "code": 0.15, "academic": 0.08, "wiki": 0.10, "books": 0.07
},
"phase_3": { # 90-100% training(退火阶段)
"web": 0.40, "code": 0.20, "academic": 0.15, "wiki": 0.15, "books": 0.10
}
}
配比失衡的后果:
- 代码数据过少 → 数学推理和符号推理能力弱(早期GPT-3的问题)
- Web数据过多 → 模型输出风格口语化、事实准确性下降
- 学术数据过少 → 专业领域问答能力差
面试加分点:提到最近的研究(如DataComp-LM)发现"小而精"的数据配比可以让7B模型超越用杂乱数据训练的70B模型,数据质量比数据量更重要这一趋势。
二、Tokenization与数据配比
4. Tokenization对模型性能有多大影响?BPE vs SentencePiece vs WordPiece?
参考答案:
Tokenization决定了文本被切分为token的方式,直接影响模型的词汇覆盖率、序列长度、多语言能力和推理效率。一个好的tokenizer应该做到:高压缩比(更少token表达更多文本)、跨语言公平、可逆、无OOV。
三种主流算法对比:
| 维度 | BPE | WordPiece | SentencePiece |
|---|---|---|---|
| 合并策略 | 频率最高优先 | 似然增益最大优先 | BPE或Unigram |
| 预处理 | 需要预分词(空格/规则) | 需要预分词 | 直接处理原始文本 |
| 多语言 | 依赖预分词器 | 依赖预分词 | 原生支持 |
| 空格处理 | 特殊符号表示 | 特殊符号表示 | 用▁表示空格 |
| 代表模型 | GPT系列, LLaMA | BERT, DistilBERT | T5, ALBERT, ChatGLM |
BPE(Byte Pair Encoding):
从字符级别开始,反复合并频率最高的相邻pair:
python
# BPE合并过程简化示例
vocab = {'l','o','w','e','r','s','t','n'}
# 统计pair频率 → 合并最高频pair
# Round 1: 'l'+'o' = 'lo' (出现最频繁)
# Round 2: 'lo'+'w' = 'low'
# Round 3: 'e'+'r' = 'er'
# 最终词表逐渐增长到目标大小
GPT-2使用byte-level BPE,把所有文本编码为UTF-8字节,确保任何字符都能被编码,彻底消除OOV问题。
SentencePiece:
SentencePiece的Unigram算法从一个大词表开始,逐步删除使语料似然下降最小的子词,直到达到目标词表大小。它的优势在于不需要预分词,直接处理原始文本,对中文、日文等无空格语言更友好。
L = ∑ x ∈ D log P ( x ) = ∑ x ∈ D log ∑ s ∈ S ( x ) P ( s ) L = \sum_{x \in D} \log P(x) = \sum_{x \in D} \log \sum_{s \in S(x)} P(s) L=x∈D∑logP(x)=x∈D∑logs∈S(x)∑P(s)
其中 S ( x ) S(x) S(x) 是句子 x x x 的所有可能分词方式,使用Viterbi算法求解最优分词。
对性能的影响:
-
压缩比:同样1万字中文,LLaMA tokenizer可能需要15000 tokens,而ChatGLM tokenizer只需约8000 tokens。压缩比差直接导致训练效率差2倍。
-
公平性:多语言模型中,如果tokenizer对非英语语言压缩比差,模型在非英语任务上表现会显著落后。
-
下游能力:代码能力与tokenizer中代码token的比例强相关。如果tokenizer训练数据中没有足够代码,代码会被切得很碎,模型学代码更难。
面试加分点:提到DeepSeekV3使用的BBPE + 增量词表扩展策略,以及tokenizer对模型推理成本的影响------同样70B模型,不同tokenizer的推理吞吐可以差30%以上。
5. 什么是Scaling Law?Chinchilla定律给出了什么结论?
参考答案:
Scaling Law描述的是模型性能(loss)与模型参数量(N)、数据量(D)、计算量(C)之间的幂律关系。这是大模型训练最核心的理论基础。
Kaplan Scaling Law(2020):
OpenAI发现loss与参数量、数据量之间存在幂律关系:
L ( N ) = ( N c N ) α N , L ( D ) = ( D c D ) α D L(N) = \left(\frac{N_c}{N}\right)^{\alpha_N}, \quad L(D) = \left(\frac{D_c}{D}\right)^{\alpha_D} L(N)=(NNc)αN,L(D)=(DDc)αD
Kaplan的关键结论:模型参数量比数据量更重要,推荐在固定计算预算下优先增大模型。这导致GPT-3用了175B参数但只训练了300B tokens。
Chinchilla Law(2022):
DeepMind通过400+次实验发现,Kaplan的结论是错的。在计算最优条件下,模型参数量和数据量应该等比例增长:
N o p t ∝ C 0.5 , D o p t ∝ C 0.5 N_{opt} \propto C^{0.5}, \quad D_{opt} \propto C^{0.5} Nopt∝C0.5,Dopt∝C0.5
具体来说,最优训练token数大约是参数数的20倍:
| 模型参数量 | Chinchilla最优tokens | Kaplan实际训练tokens | 差距 |
|---|---|---|---|
| 70B | 1.4T | 1.4T(LLaMA2) | 符合 |
| 175B | 3.5T | 300B(GPT-3) | 严重欠训练 |
| 7B | 140B | 2T(LLaMA2) | 过训练(为推理优化) |
python
# Chinchilla最优计算分配
def chinchilla_optimal(compute_budget_flops):
"""给定FLOPs预算,返回最优参数量和数据量"""
N = 0.6 * (compute_budget_flops / 6) ** 0.5
D = 20 * N # tokens ≈ 20 * parameters
return int(N), int(D)
Chinchilla的实践影响:
-
LLaMA系列的战略转向:Meta接受Chinchilla结论后,选择"小模型+多数据"路线。LLaMA 7B训练1T tokens(远超140B的Chinchilla最优),用更多数据换更小模型,推理成本大幅降低。
-
"过度训练"成为主流:Chinchilla给出的是训练loss最优解,但实际部署中推理成本占大头。用2-4倍数据训练更小模型,推理更快、部署更便宜。
-
数据量成为瓶颈:高质量数据是有限的,当模型继续增大,数据不够成为核心问题。这直接推动了合成数据、数据筛选技术的发展。
面试加分点:提到最近的IsoFLOP曲线和"compute-optimal frontier"概念,以及LLaMA 3的15T tokens训练量说明行业已经从"Chinchilla最优"转向"推理成本最优"的过度训练策略。
6. 预训练中的数据质量如何评估?有哪些量化指标?
参考答案:
数据质量评估贯穿整个数据管线,从单文档质量到整体语料分布都要量化。
单文档质量指标:
| 指标 | 计算方式 | 用途 |
|---|---|---|
| 困惑度(PPL) | 用参考模型计算 PPL = exp ( − 1 N ∑ log P ( x i ) ) \text{PPL} = \exp(-\frac{1}{N}\sum \log P(x_i)) PPL=exp(−N1∑logP(xi)) | PPL过高→噪声/乱码,PPL过低→重复模板 |
| n-gram重复度 | 文档内n-gram重复比例 | 检测重复/模板内容 |
| 符号占比 | 非字母数字字符比例 | 过高→乱码 |
| 语言模型打分 | fastText分类器打分 | 区分高低质量文本 |
| 信息熵 | 字符级/词级Shannon熵 | 过低→低信息量 |
语料级质量指标:
-
下游任务性能:在小模型上训练后评估MMLU、HellaSwag等benchmark,直接看数据质量对任务的影响。
-
数据多样性:
- Self-BLEU:衡量文档间相似度,越高越不多样
- 嵌入聚类:用sentence embedding做K-means,看簇分布是否均匀
- n-gram diversity:unique n-grams / total n-grams
-
数据污染检测:检查预训练数据中是否包含评测集,防止"test leakage"导致虚高。
python
import numpy as np
from collections import Counter
def corpus_quality_metrics(text):
# 1. 字符级信息熵
char_freq = Counter(text)
total = len(text)
entropy = -sum((c/total) * np.log2(c/total) for c in char_freq.values())
# 2. n-gram重复度
ngrams = [text[i:i+5] for i in range(len(text)-4)]
repeat_ratio = 1 - len(set(ngrams)) / max(len(ngrams), 1)
# 3. 符号占比
symbol_ratio = sum(1 for c in text if not c.isalnum() and not c.isspace()) / max(len(text), 1)
return {
"char_entropy": entropy,
"ngram_repeat": repeat_ratio,
"symbol_ratio": symbol_ratio
}
参考模型打分法:
用已有的强模型(如GPT-4)对数据打分是近年来的趋势。Phi-3使用GPT-4生成教科书级别数据,本质上是用模型质量保证数据质量。但成本很高,通常只用于小量高质量数据。
CCNet的质量分级:
Meta的CCNet管线用KenLM训练的语言模型对文档打分,按PPL分位数把数据分为HQ(top 20%)、MQ(20-50%)、LQ(50-100%)三级,训练时可以按需选择。
面试加分点:提到"数据质量不是静态的"------随着模型变强,早期的高质量数据可能不够难,需要动态调整质量标准。以及最近DataComp-LM的发现:用小模型做数据筛选时,筛选模型和目标模型的差距太大会导致选偏。
7. 什么是课程学习(Curriculum Learning)?在预训练中有效吗?
参考答案:
课程学习(Curriculum Learning)源自Bengio 2009年的工作,核心思想是:模型训练应该像人类学习一样,先学简单的再学难的,逐步提升难度。
在预训练中的应用:
传统观点认为预训练应该是i.i.d.采样,让模型看到随机顺序的数据。但近年来的实践(LLaMA 2、MiniCPM、Phi系列)证明,一定程度的数据课程确实有效。
常见的课程策略:
-
难度课程:
- 前期:高质量、易理解的文本(Wikipedia、教材)
- 中期:混合难度(Web、新闻)
- 后期:高难度、专业内容(论文、代码、数学)
-
领域课程:
- 前期:通用语言建模
- 后期:增加代码/数学/专业领域比例
-
退火策略(Annealing) :
训练最后10-20%阶段,线性降低学习率的同时切换到高质量数据:
η t = η b a s e × ( 1 − t − T a n n e a l _ s t a r t T t o t a l − T a n n e a l _ s t a r t ) \eta_t = \eta_{base} \times \left(1 - \frac{t - T_{anneal\start}}{T{total} - T_{anneal\_start}}\right) ηt=ηbase×(1−Ttotal−Tanneal_startt−Tanneal_start)
python
# LLaMA风格的退火数据策略
class AnnealingDataSampler:
def __init__(self, corpus, anneal_start_ratio=0.9):
self.corpus = corpus
self.anneal_start = int(len(corpus) * anneal_start_ratio)
def get_batch(self, step, total_steps):
progress = step / total_steps
if progress < 0.9:
return sample_mixed(self.corpus)
else:
return sample_high_quality(self.corpus)
为什么有效?:
- 早期阶段:模型需要建立基本的语言模式,高质量、规范的数据有助于快速收敛
- 后期阶段:模型已经有基本能力,需要从困难数据中学习更精细的模式
- 退火阶段:用高质量数据"固化"模型能力,减少噪声对最终checkpoint的干扰
效果证据:
- MiniCPM在训练最后阶段用高质量数据退火,在相同参数量下显著超过均匀采样的baseline
- LLaMA 2的退火阶段使用更高比例的学术和代码数据
- DeepSeekV2在训练末期增加数学和代码数据比例
面试加分点:提到"退火不只是学习率调度,更是数据调度"这一观点,以及最近研究表明数据课程的收益在数据量有限时更显著------当数据足够多时,i.i.d.采样和课程的差距会缩小。
三、训练策略与优化
8. 预训练数据的时效性问题怎么处理?如何做数据更新?
参考答案:
预训练数据有时效性限制------模型训练完成后,世界知识已经更新了。GPT-4训练截止到2023年4月,但用户问到2024年的事件时模型只能"不知道"或"幻觉"。
时效性问题的几个层面:
- 知识截止:模型不知道训练后的新事件
- 概念漂移:新术语、新概念(如"大模型"、"GPT"在2020年前几乎不存在)
- 事实更新:总统换了、公司倒闭了、API变了
解决方案:
| 方案 | 做法 | 优点 | 缺点 |
|---|---|---|---|
| 持续预训练 | 定期用新数据增量训练 | 保留旧知识 | 灾难性遗忘 |
| 混合重训 | 新数据+部分旧数据重训 | 平衡新旧 | 计算成本高 |
| RAG | 运行时检索最新信息 | 实时更新 | 依赖检索质量 |
| 指令微调 | 用新知识做SFT | 简单有效 | 只是"记住"而非"理解" |
| MoE时间路由 | 不同expert学不同时期数据 | 灵活 | 架构复杂 |
持续预训练的最佳实践:
python
# 持续预训练的数据混合策略
continuous_train_mix = {
"new_data": 0.70, # 新增数据
"old_high_quality": 0.20, # 旧高质量数据(防遗忘)
"instruction_data": 0.10 # 指令数据(保持对齐)
}
# 学习率策略:从低开始,避免破坏已学知识
lr_schedule = {
"initial": 1e-5, # 比首次预训练低一个数量级
"peak": 5e-5,
"final": 1e-6,
"warmup_ratio": 0.05 # 较短warmup
}
数据更新的工程实现:
- 增量数据管线:定期爬取新数据,走完整清洗→去重→质量过滤流程
- 版本管理:用DVC或类似工具管理数据版本,确保可追溯
- 差分去重:新数据和旧数据做跨集去重,防止重复学习
- 评估回归:更新后跑完整eval suite,确保新能力提升的同时旧能力没退化
面试加分点:提到"知识注入不如RAG"这一趋势------对于时效性强的知识,RAG比持续预训练更可靠;预训练更适合注入"慢变知识"(如科学原理、语言能力),RAG适合"快变知识"(如新闻、股价)。
9. 多语言预训练的数据配比怎么做?tokenizer怎么选?
参考答案:
多语言预训练的核心挑战是:如何在保证英语能力不退化的同时,让模型掌握其他语言。
数据配比策略:
以多语言LLaMA和Qwen/DeepSeek(原生多语言)为例:
| 策略 | 描述 | 代表模型 |
|---|---|---|
| 按人口比例 | 按语言使用人口分配 | 较少使用(英语过度优势) |
| 按数据量比例 | 按可用高质量数据量分配 | 大部分模型 |
| 均衡采样 | 每种语言相同比例 | XLM-R |
| 指数平滑 | 按数据量的对数分配 | mBERT |
| 任务驱动 | 按目标下游任务语言需求 | 商业模型 |
python
# Qwen风格的多语言配比示例
multilingual_mix = {
"en": 0.50, # 英语保底
"zh": 0.25, # 中文主力
"code": 0.10, # 代码(语言无关但偏英文)
"ja": 0.04,
"ko": 0.03,
"eu": 0.04, # 欧洲语言合并
"other": 0.04
}
Tokenizer选择:
多语言tokenizer的核心挑战是"公平性"------不同语言应该有相近的压缩比(fertility)。
| 方案 | 优点 | 缺点 | 代表 |
|---|---|---|---|
| 共享BPE | 实现简单 | 高资源语言占便宜 | mBERT |
| 语言感知BPE | 每种语言单独BPE | 词表爆炸 | XLM |
| SentencePiece Unigram | 天然多语言 | 需要调参 | XLM-R, Qwen |
| BBPE(Byte-level) | 绝对公平(都是字节) | 序列变长 | LLaMA, DeepSeek |
公平性量化:
python
def tokenizer_fairness(tokenizer, texts_by_lang):
"""计算tokenizer在不同语言上的压缩比"""
fertility = {} # tokens per character
for lang, texts in texts_by_lang.items():
total_tokens = sum(len(tokenizer.encode(t)) for t in texts)
total_chars = sum(len(t) for t in texts)
fertility[lang] = total_tokens / max(total_chars, 1)
# 公平性 = 1 - 变异系数
mean_f = np.mean(list(fertility.values()))
std_f = np.std(list(fertility.values()))
fairness = 1 - std_f / mean_f
return fertility, fairness
面试加分点:提到BBPE虽然理论上最公平,但中文字符在UTF-8中占3字节,BBPE会把一个汉字切成3个token,实际压缩比不如SentencePiece。所以DeepSeekV3采用"BBPE + 扩充中文常用字token到词表"的混合策略。
10. 什么是数据污染(Data Contamination)?怎么检测和处理?
参考答案:
数据污染指预训练数据中包含了下游评测集的数据,导致模型在评测时"作弊"------不是真正泛化而是记住了答案。这会让模型看起来很强,实际能力被高估。
污染类型:
- 直接污染:评测集原文出现在预训练数据中
- 近似污染:评测集的改写/翻译版本出现
- 指令污染:评测集被改写成指令格式混入训练数据
- 交叉污染:评测集的内容出现在相关但不完全相同的文档中
检测方法:
python
def detect_contamination(train_data, eval_set, method="ngram"):
"""检测预训练数据是否包含评测集内容"""
contaminated = []
if method == "ngram":
# 基于n-gram匹配
eval_ngrams = set()
for example in eval_set:
ngrams = get_ngrams(example, n=13) # 13-gram是常用窗口
eval_ngrams.update(ngrams)
for doc_id, doc in enumerate(train_data):
doc_ngrams = set(get_ngrams(doc, n=13))
overlap = doc_ngrams & eval_ngrams
if len(overlap) > 0:
contaminated.append((doc_id, len(overlap)))
elif method == "minhash":
# 基于MinHash模糊匹配
eval_minhash = [compute_minhash(ex) for ex in eval_set]
for doc_id, doc in enumerate(train_data):
doc_hash = compute_minhash(doc)
for i, eh in enumerate(eval_minhash):
if jaccard_similarity(doc_hash, eh) > 0.5:
contaminated.append((doc_id, i))
return contaminated
主流评测集的污染检测:
| 评测集 | 检测方法 | 污染阈值 |
|---|---|---|
| MMLU | 13-gram精确匹配 | 大于等于1个匹配即污染 |
| HellaSwag | 8-gram匹配 | 匹配率大于5% |
| GSM8K | 问题文本匹配 | 精确匹配 |
| HumanEval | 函数签名匹配 | 精确匹配 |
处理策略:
- 预防:在数据清洗阶段做去重时,把评测集作为"污染黑名单"一起做匹配
- 去除:发现污染文档后直接从训练数据中删除
- 报告:在论文中透明报告污染率和清洗后的评测结果
- 新建评测集:定期更新评测集,用训练截止后新发布的数据
GPT-3论文中报告了部分benchmark的污染情况,发现污染样本的准确率确实比未污染样本高5-10个点。LLaMA 2也做了详细污染分析。这说明污染检测是负责任的模型开发的标准动作。
面试加分点:提到最近的"动态评测集"趋势------用程序化生成的评测题(如数学题随机生成数字),从结构上避免污染;以及"contamination fingerprinting"------在模型output中检测是否逐字背诵了训练数据,作为污染的间接证据。
11. 预训练loss为什么会突然跳升?常见原因和解决方案?
参考答案:
预训练过程中loss突然跳升是常见现象,轻则影响收敛速度,重则导致模型崩溃(loss spike/NaN)。理解原因和应对策略是算法工程师的必备技能。
常见原因:
| 原因 | 机制 | 典型表现 |
|---|---|---|
| 数据异常 | 损坏/异常文档进入batch | 单步跳升后恢复 |
| 梯度爆炸 | 梯度norm突然增大 | 持续升高,可能NaN |
| 学习率过大 | 当前LR不适应当前loss landscape | 周期性跳升 |
| 硬件错误 | GPU bit flip | 随机跳升,不可复现 |
| 混合精度溢出 | fp16梯度下溢/上溢 | 特定层出现inf |
| 数据分布突变 | 切换到新数据分片 | 阶段性跳升后稳定 |
| Checkpoint恢复不当 | optimizer state不匹配 | 恢复后立即跳升 |
诊断流程:
python
def diagnose_loss_spike(loss_history, grad_norm_history, data_shard_info):
"""Loss spike诊断工具"""
spikes = detect_spikes(loss_history, threshold=2.0)
for spike_step in spikes:
grad_norm = grad_norm_history[spike_step]
if grad_norm > 10 * median(grad_norm_history):
print(f"Step {spike_step}: 梯度爆炸")
if is_shard_boundary(spike_step, data_shard_info):
print(f"Step {spike_step}: 数据分片切换")
if is_lr_peak(spike_step):
print(f"Step {spike_step}: 学习率处于峰值")
if is_after_checkpoint_restore(spike_step):
print(f"Step {spike_step}: checkpoint恢复后跳升")
解决方案:
- 梯度裁剪:最直接的防御手段
python
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
- 动态学习率回退:检测到spike后临时降低LR
python
if loss > spike_threshold * moving_avg:
lr_scheduler.reduce_lr(factor=0.5)
- 跳过异常batch:检测到异常batch直接跳过
python
if batch_loss > 10 * moving_avg_loss:
log_warning(f"Skipping batch {step}")
continue # 不更新参数
- 混合精度策略调整:使用BF16替代FP16(BF16动态范围更大,不易溢出)
python
model = model.to(torch.bfloat16) # 替代 torch.float16
- Checkpoint恢复最佳实践:保存optimizer state + RNG state + data sampler state
经验法则:
- 偶尔单步跳升通常是数据问题,跳过即可
- 持续跳升需检查学习率和梯度
- 出现NaN立即回滚到上一个好的checkpoint
- BF16比FP16更稳定,现代训练基本用BF16
面试加分点:提到"loss spike在训练大模型时几乎不可避免",关键不是防止而是"快速恢复"------好的训练框架应该能自动检测spike、回滚checkpoint、降低LR继续训练,全程不需要人工干预。
12. Muon优化器是什么?和AdamW比有什么优势?
参考答案:
Muon(Momentum + Orthogonalization)是2024年提出的新优化器,在预训练任务上展现出超越AdamW的潜力,受到社区广泛关注。
AdamW回顾:
AdamW维护一阶动量(梯度均值)和二阶动量(梯度方差),用两者的比值作为更新方向:
m t = β 1 m t − 1 + ( 1 − β 1 ) g t m_t = \beta_1 m_{t-1} + (1-\beta_1) g_t mt=β1mt−1+(1−β1)gt
v t = β 2 v t − 1 + ( 1 − β 2 ) g t 2 v_t = \beta_2 v_{t-1} + (1-\beta_2) g_t^2 vt=β2vt−1+(1−β2)gt2
θ t = θ t − 1 − η m ^ t v ^ t + ϵ \theta_t = \theta_{t-1} - \eta \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon} θt=θt−1−ηv^t +ϵm^t
AdamW的优点是自适应学习率、对超参数不敏感,缺点是显存占用大(需要存m和v两个状态)。
Muon的核心创新:
Muon在动量基础上对梯度矩阵做正交化(Newton-Schulz迭代),使得更新方向更均匀地分布在参数空间中:
python
# Muon优化器核心逻辑(简化版)
class MuonOptimizer:
def __init__(self, params, lr=0.02, momentum=0.95):
self.lr = lr
self.momentum = momentum
self.momentum_buffer = {}
def step(self):
for p in self.params:
if p.grad is None:
continue
g = p.grad
# 1. 动量更新
if id(p) not in self.momentum_buffer:
self.momentum_buffer[id(p)] = torch.zeros_like(g)
buf = self.momentum_buffer[id(p)]
buf.mul_(self.momentum).add_(g)
# 2. Newton-Schulz正交化(关键创新)
update = self.newton_schulz_orthogonalize(buf)
# 3. 参数更新
p.data.add_(update, alpha=-self.lr)
def newton_schulz_orthogonalize(self, G, steps=5):
"""用Newton-Schulz迭代近似正交化"""
a, b, c = (3.4445, -4.7750, 2.0315)
X = G.bfloat16()
if G.size(0) > G.size(1):
X = X.T
X = X / (X.norm() + 1e-7)
for _ in range(steps):
A = X @ X.T
B = b * A + c * (A @ A)
X = a * X + B @ X
if G.size(0) > G.size(1):
X = X.T
return X
Muon vs AdamW对比:
| 维度 | AdamW | Muon |
|---|---|---|
| 更新方向 | m ^ / v ^ \hat{m}/\sqrt{\hat{v}} m^/v^ | 动量正交化 |
| 显存占用 | 2x参数量(m+v) | 1x参数量(仅momentum) |
| 收敛速度 | 基准 | 更快(约1.2-1.5x) |
| 超参数敏感性 | 低 | 中等 |
| 适用范围 | 所有参数 | 主要用于2D矩阵参数 |
| 最终loss | 基准 | 更低 |
实践注意:
- Muon主要适用于二维矩阵参数(Linear、Embedding),对一维参数(LayerNorm、bias)仍用AdamW
- 通常采用混合策略:Muon用于大部分参数,AdamW用于小参数
- 学习率需要重新调参,不能直接复用AdamW的LR
面试加分点:提到Muon的直觉解释------正交化相当于"把梯度投影到均匀覆盖各方向的空间",避免AdamW中某些方向更新过快、某些方向更新过慢的问题。以及Kimi K2等模型已经验证了Muon在大规模训练中的有效性。
13. 预训练中梯度裁剪的作用和阈值怎么选?
参考答案:
梯度裁剪(Gradient Clipping)是防止梯度爆炸的最后防线,在预训练中几乎是标配。
两种裁剪方式:
- 按范数裁剪(推荐):
if ∥ g ∥ 2 > τ : g ← τ ∥ g ∥ 2 ⋅ g \text{if } \|g\|_2 > \tau: \quad g \leftarrow \frac{\tau}{\|g\|_2} \cdot g if ∥g∥2>τ:g←∥g∥2τ⋅g
python
# PyTorch实现
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
- 按值裁剪:
g i ← clip ( g i , − τ , τ ) g_i \leftarrow \text{clip}(g_i, -\tau, \tau) gi←clip(gi,−τ,τ)
python
torch.nn.utils.clip_grad_value_(model.parameters(), clip_value=0.5)
按范数裁剪保留了梯度方向,只缩放大小,是预训练中的标准做法。按值裁剪会改变梯度方向,较少使用。
阈值选择:
| 模型规模 | 推荐阈值 | 理由 |
|---|---|---|
| <1B | 1.0 | 小模型梯度norm波动大 |
| 1B-7B | 1.0 | 通用阈值 |
| 7B-70B | 1.0 | 大模型仍用1.0 |
| >70B | 1.0 | 行业共识 |
实践中阈值1.0几乎是所有规模预训练的默认值。关键是配合梯度norm监控:
python
# 训练循环中的梯度监控
loss = model(batch)
loss.backward()
# 裁剪前记录梯度norm
grad_norm_before = compute_grad_norm(model.parameters())
# 梯度裁剪
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
# 裁剪后记录
grad_norm_after = compute_grad_norm(model.parameters())
# 如果频繁触发裁剪,说明训练不稳定
clip_ratio = grad_norm_after / grad_norm_before
if clip_ratio < 0.1: # 裁剪掉了90%以上的梯度
logger.warning(f"Severe gradient clipping at step {step}")
什么时候梯度裁剪会频繁触发?
- 学习率过大
- 数据中有异常batch(如超长重复序列)
- 混合精度训练中FP16溢出
- 模型初始化不当
面试加分点:提到梯度裁剪只是"症状治疗"------如果频繁触发,应该找根因(数据质量、学习率、初始化),而不是单纯调大阈值。以及最近的研究表明,过大的梯度裁剪阈值会减慢收敛,因为大梯度往往出现在学习率warmup阶段,是正常现象。
14. 训练中断后如何恢复?Restore Checkpoint需要注意什么?
参考答案:
大规模预训练训练时间通常以周计,硬件故障导致的中断几乎不可避免。好的checkpoint恢复机制是工程能力的重要体现。
完整的Checkpoint需要保存什么?
python
def save_checkpoint(model, optimizer, scheduler, dataloader, step, path):
"""保存完整训练状态"""
checkpoint = {
# 1. 模型状态
"model_state_dict": model.state_dict(),
# 2. 优化器状态(AdamW的m和v)
"optimizer_state_dict": optimizer.state_dict(),
# 3. 学习率调度器状态
"scheduler_state_dict": scheduler.state_dict(),
# 4. 数据采样状态(确保不重复读数据)
"dataloader_state": dataloader.state_dict(),
# 5. 随机数生成器状态
"torch_rng_state": torch.get_rng_state(),
"cuda_rng_state": torch.cuda.get_rng_state_all(),
"numpy_rng_state": np.random.get_state(),
# 6. 训练元信息
"step": step,
"loss_history": loss_history,
"config": config,
}
torch.save(checkpoint, path)
常见的恢复陷阱:
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 数据重复 | DataLoader的sampler状态未保存 | 保存sampler的epoch和offset |
| 梯度norm突变 | optimizer的momentum未恢复 | 完整保存optimizer state_dict |
| 学习率不对 | scheduler状态未恢复 | 保存scheduler state_dict |
| 随机性变化 | RNG state未保存 | 保存所有RNG state |
| 模型精度变化 | BF16/FP32混合精度状态不一致 | 保存master weights |
| 分布式不一致 | DP/PP/TP的rank映射变化 | 保存拓扑信息和rank映射 |
恢复后的验证清单:
python
def verify_checkpoint_restore(model, optimizer, dataloader, checkpoint):
"""恢复后的完整性检查"""
# 1. 验证模型参数
for name, param in model.named_parameters():
saved_param = checkpoint["model_state_dict"][name]
assert torch.allclose(param, saved_param), f"Param mismatch: {name}"
# 2. 验证梯度状态
for group in optimizer.param_groups:
for p in group['params']:
if p in optimizer.state:
state = optimizer.state[p]
assert 'exp_avg' in state # AdamW的一阶动量
assert 'exp_avg_sq' in state # AdamW的二阶动量
# 3. 验证数据不重复
expected_batch = dataloader.peek_next_batch()
assert not is_seen_before(expected_batch, checkpoint["step"])
# 4. 验证学习率
current_lr = optimizer.param_groups[0]['lr']
expected_lr = get_expected_lr(checkpoint["step"])
assert abs(current_lr - expected_lr) < 1e-8
print("Checkpoint verification passed.")
Checkpoint频率的权衡:
- 保存频率越高,丢失的训练进度越少
- 但保存checkpoint需要额外存储和IO时间
- 实践中每1000-5000步保存一次,保留最近3-5个checkpoint
- 大模型(>7B)的checkpoint可能几百GB,需要分布式存储
面试加分点:提到"异步checkpoint保存"------在不阻塞训练的情况下将checkpoint写入分布式存储,这在大模型训练中是刚需。以及Megatron-LM和DeepSpeed的checkpoint格式兼容性问题------不同框架的checkpoint格式不同,迁移时需要转换。
15. 预训练模型的Token Embedding初始化策略有哪些?
参考答案:
Token Embedding是模型最先接触数据的参数,初始化质量直接影响训练初期的收敛速度和最终性能。
常见初始化策略:
| 策略 | 公式 | 优点 | 缺点 |
|---|---|---|---|
| 随机正态 | E ∼ N ( 0 , σ 2 ) E \sim \mathcal{N}(0, \sigma^2) E∼N(0,σ2) | 简单 | 需要调 σ \sigma σ |
| Xavier/Glorot | σ = 2 / ( d i n + d o u t ) \sigma = \sqrt{2/(d_{in}+d_{out})} σ=2/(din+dout) | 自适应 | 对Embedding不完全适用 |
| Kaiming | σ = 2 / d i n \sigma = \sqrt{2/d_{in}} σ=2/din | 适合ReLU | 对Transformer非最优 |
| 截断正态 | 截断到 − 2 σ , 2 σ -2\\sigma, 2\\sigma −2σ,2σ | 避免极端值 | 略改变方差 |
| 缩放正态 | E = N ( 0 , 1 ) × 0.02 E = \mathcal{N}(0, 1) \times 0.02 E=N(0,1)×0.02 | GPT标准 | 值较小 |
GPT/LLaMA的标准做法:
python
# GPT-2/LLaMA风格初始化
def init_token_embeddings(num_tokens, dim, std=0.02):
"""标准初始化:均值0,标准差0.02的截断正态"""
embeddings = torch.randn(num_tokens, dim) * std
# 截断到[-2*std, 2*std]
embeddings = torch.clamp(embeddings, -2*std, 2*std)
return embeddings
# 额外处理:特殊token的初始化
special_tokens = {
"<pad>": torch.zeros(dim), # padding用全零
"<bos>": torch.randn(dim)*0.02, # 起始符随机初始化
"<eos>": torch.randn(dim)*0.02, # 结束符随机初始化
"<unk>": torch.randn(dim)*0.02, # 未知符随机初始化
}
关键细节:
-
Embedding和LM Head共享权重:GPT-2、LLaMA等模型将Embedding矩阵和输出层权重绑定(weight tying),减少参数量并提升效果。共享时初始化尤为重要。
-
缩放因子 :GPT-2在Embedding后乘以 d m o d e l \sqrt{d_{model}} dmodel ,补偿维度缩放:
h = E ⋅ d m o d e l h = E \cdot \sqrt{d_{model}} h=E⋅dmodel
- Layer-wise初始化:深层Transformer需要对不同层用不同方差初始化,防止深层梯度消失/爆炸。
python
# LLaMA风格:不同模块不同初始化
def init_transformer_weights(module):
if isinstance(module, nn.Embedding):
nn.init.normal_(module.weight, mean=0.0, std=0.02)
elif isinstance(module, nn.Linear):
nn.init.normal_(module.weight, mean=0.0, std=0.02)
if module.bias is not None:
nn.init.zeros_(module.bias)
elif isinstance(module, nn.LayerNorm):
nn.init.ones_(module.weight)
nn.init.zeros_(module.bias)
-
预训练初始化:用已有模型的Embedding初始化新模型,在词表重叠时可以加速收敛。这是持续预训练的常见做法。
-
词表扩展初始化:当在已有模型上扩展词表(如增加中文token),新token的初始化可以用已有token的均值:
python
def init_extended_embeddings(old_embeddings, new_token_count):
"""词表扩展:新token用旧token均值初始化"""
old_mean = old_embeddings.mean(dim=0)
new_embeddings = torch.randn(new_token_count, old_embeddings.size(1)) * 0.02
# 混合均值和随机
new_embeddings = 0.7 * old_mean.unsqueeze(0) + 0.3 * new_embeddings
return torch.cat([old_embeddings, new_embeddings], dim=0)
面试加分点 :提到"初始化不是越精细越好"------对于大规模预训练(万亿token),初始化的影响会在训练中被冲淡,简单的N(0, 0.02)就够用。初始化对小数据量训练(如微调)更重要。以及最近的研究发现,Embedding的初始方差过大会导致训练初期loss居高不下,过小则导致某些token学不到有意义的表示。
总结
预训练与数据工程是大模型能力的基石。从数据构建的角度看,多阶段清洗管线(规则过滤+质量分类器+MinHash去重)决定了模型上限,数据配比和课程策略决定了能力分布,Tokenization的压缩比和公平性对训练效率和推理成本有深远影响。从训练策略的角度看,Scaling Law从Kaplan到Chinchilla的演进告诉我们"数据量和参数量等比例增长"才是计算最优解,而LLaMA系列的过度训练策略代表了从训练最优到推理最优的工程转向;梯度裁剪、BF16混合精度、自动spike恢复是稳定训练的三板斧;Muon等新优化器正在拓展收敛速度的边界。从数据安全的角度看,污染检测和时效性管理是负责任模型开发的必要环节。
面试中真正拉开差距的,不是知道某个概念的定义,而是理解它背后的数学原理、工程权衡和失败模式------比如知道MinHash为什么用Jaccard而不是Cosine,知道梯度裁剪阈值1.0为什么能通用,知道checkpoint恢复需要保存哪些状态。建议在准备时结合实际项目经验,准备好"我在训练中遇到过什么问题、怎么解决的"这类实战故事,这比背诵概念更有说服力。大模型领域发展极快,保持对前沿论文的追踪、动手复现关键实验、在真实项目中积累踩坑经验,才是面试中真正让你脱颖而出的核心竞争力。