卷二 · AI 工程 · 高频面试题

通用软件工程师 / FDE 高频面试题库 · 进阶卷二 题源:牛客网公开面经(notes/corpus.jsonl,90 份独立面试经历帖)+ 大模型工程通用知识 题目统计:普通问答 53 · 手撕 10 · N 备选 0 · 合计 63 个条目。

难度分布(全卷,含手撕):L1 11 · L2 43 · L3 9

频次口径:🔥×N = N = 该题在独立面经帖中的命中数(同义题合并后计),检索键写在每题正文的 <!-- src: ... --> 里,由 tools/fill_freq.py 机械回填、CI 用 --check 校验。低频实录·1源 = 只有 1 份独立面经支撑;补充 = 非面经的整理题,不伪造频次。

注意:通用软件岗的语料天然偏少 AI 主题题,因此本卷大量题目标注为 补充 / 低频实录·1源——这是语料覆盖的真实反映,不代表该题在 AI 岗不重要。AI 岗面试的真实高频(LoRA 原理、RAG 切片、Embedding 召回、幻觉治理、SFT 过拟合判断、DPO/PPO)已在对应题目的答案中完整覆盖。

使用方式:题目默认折叠,点开看答案。建议按 L1 → L2 → L3 顺序刷;同级内按频次从高到低。手机端原生支持。项目类 / 场景类问题的背景已用自然语言写进题干;「追问」列出常见后续考点。

与其他卷的关系:本卷考的是把模型做成可用系统所需的知识——Transformer 机制、训练与微调、推理部署、RAG 检索与评测。与卷三(Agent 工程)正交:卷二讲"模型本身与检索",卷三讲"模型怎么驱动工具与流程"。适合岗位:AI 应用工程师 / 大模型算法工程师 / AI 平台 / FDE(AI 交付面)。


01 模型基础与 Transformer(10 题)

L1 🔥×14 Q01 · Transformer 的 Self-Attention 计算流程是什么?复杂度是多少?

答:输入序列 $X \in \mathbb{R}^{n \times d}$ 分别乘三个权重矩阵得到 $Q, K, V$,然后计算:

$$\text{Attention}(Q, K, V) = \text{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}}\right)V$$

步骤:① 线性投影得 $Q, K, V$;② 计算相似度矩阵 $QK^\top$($n \times n$)并除以 $\sqrt{d_k}$ 缩放;③ softmax 归一化;④ 加权求和 $V$。

复杂度:时间 $O(n^2 d)$,空间 $O(n^2)$(注意力矩阵是瓶颈),序列长时计算量爆炸。多头注意力是 $h$ 个小头并行,总参数量不变。

易错:除以 $\sqrt{d_k}$ 不是"随便加的"——防止点积过大导致 softmax 梯度消失;去掉 scale 训练会明显变差。

追问:① 为什么要 QKV 三个不同投影?② KV Cache 解决的是哪部分复杂度?③ 长序列怎么降复杂度(MQA/GQA/FlashAttention/线性注意力)?

L2 低频实录·1源 Q02 · 为什么 Self-Attention 里要做 QKV 线性变换?Softmax 为什么必需?

答:为什么要 QKV 三个不同投影:同一份输入投影三次,得到三个不同子空间的表示——$Q$(我要找什么)、$K$(我能被什么找到)、$V$(真正被聚合的内容)。若三者相同,$QK^\top$ 会退化成 $XX^\top$,相似度矩阵对称且秩受限,表达力大降。多头就是把 $d$ 维拆成 $h$ 份、各自做这套投影。

Softmax 为什么必需:注意力权重需要归一化——$A_i = \text{softmax}(s_i)$ 满足 $\sum_i A_i = 1$,是加权平均(凸组合),输出仍在 $V$ 的凸包内,数值稳定、可解释为"注意力分布"。不用 softmax 时(如线性注意力的恒等映射核)权重不归一化,表达的是"加权求和"而非"分布"。

易错:以为 softmax 只是"把分数变成概率"——它还保证了输出的数值范围(凸组合),这是不加 softmax 时输出可能发散的原因。

追问:① 为什么除 $\sqrt{d_k}$?② 多头为什么要拼接后再投影?③ 温度系数(temperature)改的是什么?

L2 🔥×4 Q03 · encoder-only 与 decoder-only 有什么区别?纯理解任务用哪个更好?

答:encoder-only(BERT):双向注意力,输出每个 token 的表示,适合理解 / 分类 / 抽取(embedding、rerank)。decoder-only(GPT / LLaMA):因果注意力,自带自回归生成,适合生成 / 续写 / few-shot。

decoder-only 为何统一了 NLP:① 因果掩码让"双向看前文"和"自回归生成"用同一套结构;② 预训练目标简单(下一词预测),数据几乎无限;③ 上下文越长、涌现能力越强。

纯理解场景选哪个:小规模标注数据上 encoder-only 更省(参数少、推理快、效果稳);但有足量无标注语料 + 需要少样本泛化时,decoder-only 靠 prompt 就能超过 encoder。

易错:说"decoder-only 只能做生成"——它同样能做 embedding 与分类;说"BERT 已被淘汰"——在检索、重排、大分类上仍是强 baseline。

追问:① 什么是 prefix LM?② 为什么 decoder-only 更适合 in-context learning?③ embedding 模型为什么常基于 encoder?

L2 🔥×4 Q04 · 位置编码为什么必要?有哪些方案?

答:Attention 本身是置换等变的——不打乱顺序它不知道 token 先后,因此必须显式注入位置信息。

方案:① 绝对可学习(BERT):加位置向量表,最简单但外推差;② 正弦绝对编码(原论文):$PE_{(pos,2i)}=\sin(pos/10000^{2i/d})$,不需训练;③ 相对位置编码(T5、ALiBi):编码相对距离;④ RoPE(LLaMA 等主流):把位置编码成对 $q/k$ 的旋转,只作用于 $q,k$ 不作用于 $v$。

RoPE 的关键性质:它把"相对位置"注入 $q\cdot k$ 的内积里($R_m^\top R_n = R_{n-m}$),注意力分数天然只依赖相对距离,且通过插值(NTK / YaRN)可扩展上下文长度。

易错:以为"外推能力"只取决于编码方式——现代长上下文主要靠 RoPE 插值 + 继续训练。

追问:① RoPE 为什么只作用在 q/k?② 怎么把 4k 上下文扩到 32k?③ ALiBi 的做法是什么?

L2 🔥×2 Q05 · 什么是 KV Cache?为什么推理有、训练没有?

答:自回归生成时,每步的新 token 只与已生成的历史做注意力。若每步都重算全部历史的 $K, V$,代价是 $O(n^2)$。但历史 token 的 $K, V$ 不变(因果掩码保证),所以把它们缓存起来,每步只算新 token 的 $K, V$ 并拼接——单步注意力降到 $O(n)$。

训练时没有:训练是并行的(一次前向算整条序列),所有位置的 $K, V$ 同时产生并被使用,没有"上一步的结果"可缓存。

代价:KV Cache 显存随 层数 × 序列长度 × 2(K,V) × head_dim × 头数 × batch 线性增长,是长上下文推理的主要瓶颈。GQA / MQA 通过让多个 query 头共享 K/V 头把这份显存砍掉数倍。

易错:以为"有了 KV Cache 推理就是 O(n)"——单步是 O(n),生成 n 个 token 的总代价仍是 O(n²);训练里的自回归解码(如 RLHF rollout)也会用到。

追问:① PagedAttention 解决什么问题?② KV Cache 的显存怎么估?③ prefix caching 是什么?

L3 🔥×3 Q06 · GQA / MQA / MHA 在 KV Cache 存储上有什么区别?

答:设 query 头数 $n_q$、KV 头数 $n_{kv}$:

方案$n_{kv}$KV Cache 相对 MHA代表
MHA$= n_q$1×原始 Transformer
GQA介于中间(组内共享)$1/n_g$Llama 2/3、Mistral
MQA$= 1$(全部共享)$1/n_q$PaLM、Falcon

推理代价:KV Cache 显存正比于 $n_{kv}$。GQA 在质量与显存之间取平衡(Llama 2 70B 用 GQA 把 KV Cache 降到 1/8),MQA 省得最多但常有轻微质量损失。

质量差异来源:共享 K/V 意味着多个 query 头看到相同的"被查字典",表达力下降;GQA 用分组保留了组内差异。

易错:以为"GQA 就是 MQA 的折中所以一定没损失"——分组数是超参,激进分组(如每组 8 个头)仍会掉点,需要实测。

追问:① 为什么不多头也共享?② GQA 训练时怎么从 MHA 初始化?③ KV Cache 量化怎么和 GQA 配合?

L2 🔥×2 Q07 · 常见归一化方式有哪些?Transformer 为什么用 LayerNorm?

答:BatchNorm(BN):沿 batch 维归一化,训练时用 batch 统计量、推理时用 running 均值/方差;依赖 batch 大小,序列长度可变时不好用。LayerNorm(LN):沿特征维(同一样本内所有通道)归一化,不依赖 batch,训练/推理行为一致。RMSNorm:LN 去掉均值中心化、只做均方根缩放,更省算力(LLaMA 系采用)。GroupNorm(GN):把通道分组后组内归一化,小 batch 视觉任务上比 BN 稳。

Transformer 用 LN 的原因:① 序列长度可变、batch 小,BN 的统计量不稳;② 推理时 batch=1,BN 的 running 统计与单样本输入不匹配;③ 自回归生成每步输入长度在变,BN 统计量口径不一致。

易错:说"BN 推理更准因为用了全局统计"——训练/推理行为不一致本身就是 BN 在序列任务上的隐患;把 RMSNorm 说成"LayerNorm 的近似"——它是不同的公式(不减均值)。

追问:① 为什么 pre-LN 比 post-LN 更稳?② RMSNorm 省在哪?③ BN 的 running 统计为什么不可靠?

L1 🔥×3 Q08 · 梯度消失和梯度爆炸的原因是什么?有哪些解决方法?

答:消失:链式法则反传时,若激活函数(如 sigmoid)导数 < 1,多层连乘后梯度指数衰减 → 浅层几乎不更新。爆炸:权重矩阵乘积特征值 > 1,梯度指数增长 → 更新步过大、训练发散。

解决消失:① 换激活函数(ReLU / Leaky ReLU,导数正区间为 1);② 残差连接(skip connection 提供梯度高速公路);③ BatchNorm / LayerNorm 归一化激活分布;④ LSTM 门机制(序列模型)。

解决爆炸:① 梯度裁剪($\|g\| > \tau$ 则 $g \leftarrow g \cdot \tau / \|g\|$);② 权重初始化(Xavier / He);③ BatchNorm。

易错:ReLU 有 dying ReLU 问题(负值区梯度永久为 0),大批量 + 大学习率下死亡神经元显著,Leaky ReLU / ELU 可缓解。

追问:① 为什么残差连接能缓解梯度消失?② 梯度裁剪应该裁全局范数还是逐元素?③ 混合精度训练下梯度溢出怎么发现?

L2 🔥×6 Q09 · 优化器怎么选?Adam、AdamW、SGD 的区别是什么?

答:SGD:用统一学习率,收敛稳但慢;配合 momentum 可加速。Adam:一阶矩(梯度 EMA)+ 二阶矩(梯度平方 EMA),各参数有自适应学习率,前期快;大规模视觉任务泛化有时弱于 SGD+momentum。AdamW:Adam + 解耦 weight decay——直接把衰减作用在权重上,不进梯度统计,因此衰减强度不随自适应学习率缩放。LLM 微调几乎都用 AdamW。

易错:Adam + L2 正则 ≠ AdamW。Adam 里加 L2 是把 $wd \cdot p$ 并入梯度,衰减强度会被 $\sqrt{\hat v}$ 缩放(参数不同缩放比例不同);AdamW 是统一的乘性衰减。

追问:① 为什么 LLM 用 AdamW 而不是 SGD?② 学习率一般怎么设?③ Adafactor / Lion / Muon 各自改了什么?

L1 🔥×4 Q10 · 训练集 / 验证集 / 测试集怎么划分?什么是数据泄露?

答:训练集用于拟合参数;验证集用于选超参与早停;测试集只在最后评估一次(用测试集调参 = 测试集失效)。

划分原则:① 同分布:三集来自同一分布,否则指标不可信;② 时间性:时序数据必须按时间切分(用未来数据预测过去是泄露);③ 防重复:同一用户 / 同一会话 / 同一文档的副本不能跨集(否则泄露);④ 样本量:测试集要够大以支撑结论。

数据泄露的常见形态:① 特征里含目标(如"是否退款"出现在退款预测的特征中);② 重复样本跨集;③ 用全量数据做了归一化 / 词表统计 / 特征选择( scaler 必须只在训练集上 fit);④ 时序穿越(用了未来统计量做特征)。

易错:说"测试集调参只是浪费一点"——测试集被反复使用后已经成为验证集,给出的泛化指标会系统性偏乐观。

追问:① 验证集大小怎么定?② K 折交叉验证和固定划分的取舍?③ 怎么检测训练/推理特征不一致(training-serving skew)?


02 训练、微调与对齐(9 题)

L2 🔥×5 Q11 · LoRA 微调的原理是什么?微调哪些层?参数怎么设?

答:原理:冻结原权重 $W_0$,旁路加低秩增量 $\Delta W = BA$($r \ll \min(d,k)$),前向用 $W_0 x + \Delta W x$。可训练参数从 $O(dk)$ 降到 $O(r(d+k))$;$A$ 随机初始化、$B$ 初始化为 0,起始时 $\Delta W = 0$(等价原模型)。

微调哪些层:注意力投影 $W_q, W_k, W_v, W_o$ 是首选;也有做法加 MLP。通常不动 embedding / lm_head(词表大、收益低、易过拟合)。

超参:r 取 8/16/32、alpha 通常取 $2r$(缩放 $\alpha/r$)、lora_dropout 0.05~0.1、lr 1e-4~2e-4、epochs 1~3(SFT 极易过拟合)。

易错:只微调最后几层就期待大提升——收益与领域差异正相关;忘写缩放。

追问:① LoRA 能不能合并进权重?② QLoRA 的 4bit 量化做了什么?③ LoRA vs 全参微调:什么时候必须全参?

L2 补充 Q12 · SFT 过程中怎么判断是否过拟合?有哪些常见坑?

答:判断过拟合:① 验证集 loss 开始上升(训练 loss 继续降、验证 loss 拐头向上);② 训练 / 验证 loss 的 gap 持续拉大;③ 生成结果开始复述训练样本;④ 人工看 case 发现模型"背答案"(换问法就崩)。

常见坑:

  1. 数据量小 + epoch 多 → 必过拟合;SFT 通常 2~3 个 epoch 封顶。
  2. 数据质量差:指令-回答不匹配、格式混乱、含错误答案。
  3. 学习率过大:破坏预训练能力,表现为"通用能力下降、格式却学得很快"。
  4. 数据分布偏:只覆盖少数任务类型,表现为灾难性遗忘。
  5. chat template 不一致:训练与推理模板不同,输出错乱。

易错:把"训练 loss 下降"当成收敛——训练 loss 降只说明在拟合,过拟合时它降得最快。

追问:① 怎么缓解过拟合(更多数据 / 早停 / LoRA / 正则)?② 怎么评估"通用能力有没有被破坏"?③ SFT 数据该怎么构造?

L2 🔥×3 Q13 · SFT 之后为什么常用 RAG 而不是继续全量微调?

答:知识时效:全量微调把知识"烤"进权重,模型无法回答训练之后的新知识;RAG 在推理时检索外部知识,改文档即生效,无需重训。成本与可回滚:RAG 改索引(分钟级),微调要重新训练(小时~天级 + GPU 成本)。可溯源:RAG 能给出引用来源,便于审计与合规;微调后的"记忆"无法追溯。规模:领域文档通常远大于可微调的数据量。

RAG 的代价:① 检索质量决定上限(召回错了,模型只能编);② 多一跳延迟与成本;③ 上下文拼接受窗口限制,需要 chunk 与 rerank 策略。

易错:说"RAG 一定比微调好"——风格 / 格式 / 特定行为的适配(如让模型用某种口吻、按固定 schema 输出)更适合微调;很多系统是微调管行为、RAG 管知识的组合。

追问:① 什么知识适合微调、什么适合 RAG?② RAG 什么时候不如长上下文?③ 混合检索怎么设计?

L2 🔥×7 Q14 · 什么是 DPO?和 PPO(RLHF)有什么区别?

答:RLHF 三段式:SFT → 训练 reward model(人工标注偏好)→ 用 PPO 强化学习优化策略(KL 惩罚防偏离参考模型)。代价高:需额外训练 RM、PPO 超参敏感、显存大、易崩。

DPO(Direct Preference Optimization):跳过 RM 与 PPO,直接用偏好对 $(y_w, y_l)$ 优化策略——在参考模型 $\pi_{ref}$ 的约束下最大化偏好数据似然,核心是一个闭式的分类损失:把"策略相对参考模型的对数比"当作 logits,偏好样本为正例。

核心差异:PPO 是在线 RL(采样 → 打分 → 更新,需要 rollout);DPO 是离线(只用偏好对,梯度一步到位)。DPO 省掉 RM 与 rollout,稳定性与工程复杂度都低很多。

易错:说"DPO 效果一定不如 PPO"——很多场景 DPO 更好用(成本低、稳);DPO 的局限是只能用成对偏好,且对参考模型的 KL 约束是隐式的。

追问:① DPO 的损失怎么写?② 为什么还需要 SFT 作为冷启动?③ GRPO 相对 PPO 改了什么(省掉 value 网络)?

L3 🔥×4 Q15 · 模型复读(重复生成)怎么分析和解决?

答:先分清是哪种复读:

  1. 训练引起:SFT 数据本身有大量重复模式(模板化回答、列表项重复),模型学到"接着重复"——根治在数据清洗。
  2. 解码引起:temperature 太低 + top_p 太大 + 贪心解码 → 陷入局部高频循环。修复:提高温度、加大 top_p、加 repetition / frequency penalty、检测到重复 n-gram 就强制换词或中断。
  3. 上下文 / 位置编码引起:长上下文时位置编码外推失效或 attention 退化。
  4. 推理引擎引起:量化精度损失导致 logits 异常。

排查顺序:先换解码参数验证是不是解码问题 → 再看训练数据重复率 → 最后查推理引擎与量化精度。

易错:一律靠调 repetition_penalty 硬压——副作用是打断正常的高频词(代码里的变量名、法律条款),更该从数据侧解决。

追问:① 怎么量化"复读率"?② 不同模型对 repetition penalty 的支持差异?③ 什么场景必须保留重复(代码、表格)?

L3 🔥×3 Q16 · 大模型幻觉是什么?有哪些缓解手段?

答:幻觉 = 模型生成了流畅但事实错误的内容。分内在幻觉(与输入矛盾)与外在幻觉(编造不存在的 API、论文、法条)。

成因:① 训练目标只优化"下一个 token 的似然",不惩罚"自信地说错";② 知识缺失时用统计规律补全;③ RAG 召回错误时强行从错误上下文里编;④ 长上下文中信息被稀释。

缓解:① RAG + 引用约束(检索不到就说"不知道");② 结构化约束(schema / function calling 限定字段,非法值直接拒绝);③ 后置校验(用规则 / 另一模型 / 数据库核对关键字段);④ 采样与拒答(低温度 + 允许"无法回答");⑤ 训练侧(加负例与"不知道"样本,RLHF / DPO 惩罚编造)。

易错:以为"换更强的模型就不幻觉"——幻觉率更低但不为零,仍需 grounding 与校验。

追问:① 怎么度量幻觉率?② RAG 场景下幻觉的主要来源是哪个环节?③ 什么场景必须"宁可拒答"?

L2 低频实录·1源 Q17 · 混合精度训练是什么?为什么大模型训练要用?

答:混合精度 = 部分算子用低精度(FP16 / BF16)、部分用 FP32。显存省一半以上、算得更快(tensor core 支持),代价是低精度动态范围有限、易下溢/上溢。

两种低精度的差别:FP16 动态范围小(同 scale 下最大 ~65504、最小正常数 ~6e-5),容易下溢,训练时必须配 loss scaling(把 loss 放大若干倍,避免梯度下溢为 0)。BF16 与 FP32 的指数位相同(8 位),动态范围一样大,不会下溢,代价是尾数位少(精度略低),因此不需要 loss scaling——大模型训练更常用 BF16。

易错:把"FP16 需要 loss scaling"说成"BF16 也需要";以为"混合精度 = 全程 FP16"——归约、softmax、优化器状态仍应保持 FP32(GradScaler、master weights)。

追问:① loss scaling 怎么工作?② 为什么优化器状态不用半精度?③ FP8 相比 BF16 的取舍?

L2 低频实录·1源 Q18 · 什么是知识蒸馏?在大模型压缩里怎么用?

答:蒸馏 = 用大模型(teacher)的软标签(输出分布,含"暗知识"——各类别的相对相似度)训练小模型(student)。

损失:$L = \alpha \cdot L_{CE}(y, \hat{y}) + (1-\alpha) \cdot T^2 \cdot L_{KL}(p_t^T \| p_s^T)$,其中 $T$ 是温度(放大分布、暴露暗知识),$T^2$ 是梯度尺度补偿。

在 LLM 里的用法:① 蒸馏小模型(7B → 1B),用大模型的输出作为软标签训练小模型;② 蒸馏上下文 / 思维链(让小模型学会长推理);③ 黑盒蒸馏(只有 API 访问权时,用大模型生成训练数据再微调)。

易错:认为"蒸馏 = 量化"——量化是降精度换显存,蒸馏是换小模型换效果;两者可叠加(先蒸馏再量化)。温度 $T$ 不补偿梯度尺度会破坏训练平衡。

追问:① 为什么软标签比硬标签有用?② 蒸馏后小模型能超过 teacher 吗(同任务)?③ 怎么构造蒸馏数据?

L2 🔥×4 Q19 · 怎么判断模型是否收敛?为什么需要单独的验证集?

答:判断收敛(多角度交叉验证):

  1. 验证集 loss 不再下降(平台期)——最直接。
  2. 训练 loss 与验证 loss 的 gap 稳定(不再扩大)。
  3. 指标不再提升:生成任务看人工 / 自动评测(pass@k、Rouge、判分模型),分类看 F1 / AUC 的平台期。
  4. 学习率已衰减到很小、梯度范数趋于 0。

为什么需要独立验证集:① 超参(学习率、层数、rank、正则)选择必须用模型没见过的数据,否则等于在训练集上选参 → 过拟合;② 早停需要一个持续监控的、与训练解耦的信号;③ 测试集只能用一次,用它反复选参就等于把它变成了验证集。

易错:拿训练 loss 判断收敛——训练 loss 一直降,降到 0 也不代表泛化好;把验证集和测试集合并以省数据——一旦用它选参,它就失效了。

追问:① 验证集 loss 抖动怎么办(EMA / 多次平均)?② 小数据量怎么做交叉验证?③ 怎么判断是欠拟合还是过拟合?


03 推理部署与工程化(8 题)

L2 🔥×5 Q20 · 大模型部署有哪些框架?各自的优缺点怎么选?

答:推理引擎:vLLM(PagedAttention 解决 KV Cache 显存碎片、支持连续批处理,吞吐高)/ SGLang(结构化生成与前缀复用)/ TensorRT-LLM(NVIDIA 生态极致性能)/ llama.cpp(CPU 与边缘友好)。服务框架(vLLM OpenAI-compatible server / TGI)提供 OpenAI 兼容接口。

其他选择:量化(W4A16 / INT8 / FP8)、张量并行 + 流水并行跨卡切分、continuous batching 让请求随到随走。

选型:① 文本生成 → vLLM / SGLang;② 边缘 / CPU → llama.cpp;③ 极致延迟 → TensorRT-LLM;④ 多模态 → 选支持视觉 encoder 的引擎。

易错:只比单请求延迟——生产更看 P99 延迟下的吞吐;忽略长上下文的 KV Cache 显存瓶颈。

追问:① continuous batching 是什么?② PagedAttention 解决什么?③ 张量并行和流水并行怎么选?

L2 🔥×3 Q21 · 量化有哪些类型?INT8 / FP8 / AWQ / GPTQ 怎么选?

答:按粒度:权重量化(W-only,如 GPTQ / AWQ)、激活量化(W+A,如 SmoothQuant)、KV Cache 量化(FP8 / INT8)。

INT8:对称 / 非对称量化,精度损失小,硬件支持广。FP8(E4M3 / E5M2):Hopper 原生支持,动态范围比 INT8 大,更少溢出。AWQ:基于激活分布做激活感知的权重量化(保护"显著通道"),不需要重训,4bit 质量通常优于 GPTQ。GPTQ:逐层近似二阶信息求解量化误差最小,需校准集。

选型:① 显存够、追求稳定 → INT8 / FP8;② 显存吃紧、要 4bit → AWQ(默认推荐);③ 要极致 W4 质量 → GPTQ + 好的校准集。

易错:以为量化只是"减精度换显存"——量化会掉点,尤其在长尾能力与指令遵循上;要按任务实测(用业务评测集,不要只看 MMLU)。

追问:① AWQ 保护的是什么?② 量化后为什么还需要校准?③ 量化对 KV Cache 有什么影响?

L2 补充 Q22 · 推理服务的延迟由哪些部分组成?怎么优化?

答:两个关键指标:TTFT(首 token 延迟)与 TPOT(每输出 token 间隔),总延迟 ≈ TTFT + n × TPOT。

TTFT 组成:排队等待 + prefill(处理输入 prompt,计算量随 prompt 长度增长)+ 首 token 解码。 TPOT 组成:decode 阶段每步的模型前向(访存带宽瓶颈)+ 采样。

优化手段:① continuous batching(新请求插队进正在跑的 batch);② PagedAttention / KV Cache 复用;③ 前缀缓存(系统提示词只算一次);④ 投机解码(小模型起草 + 大模型验证,无损);⑤ 量化减显存、提并发;⑥ 分块预填充避免长 prompt 阻塞短请求。

易错:只优化 decode 阶段——TTFT 常是用户体感的瓶颈;只报平均延迟——要看 P99。

追问:① 投机解码为什么无损?② chunked prefill 解决什么?③ 怎么压 TTFT?

L2 🔥×2 Q23 · LLM 服务的可观测性怎么做?该埋哪些指标?

答:四类指标:① 性能:TTFT、TPOT、端到端延迟(P50/P95/P99)、吞吐、并发数、排队时长;② 质量:离线评测分 + 线上代理指标(采纳率、重试率、用户改写率、拒答率);③ 成本:每千 token 成本、GPU 利用率、KV Cache 占用、显存水位;④ 可靠性:错误率(5xx / 超时 / OOM)、熔断与降级次数。

关键工程点:traceId 贯穿(网关 → 推理服务 → 工具调用 → 下游);输入输出采样留痕(脱敏 + 采样率)用于回放;版本维度(模型版本、prompt 版本、索引版本都要带在指标标签上——线上问题常常是 prompt 改了);GPU 指标与业务指标同屏。

易错:只监控服务存活与 QPS——模型服务"活着但答得烂"是最常见的失败模式,必须同时盯质量指标。

追问:① 怎么把用户反馈和 trace 关联?② 离线评测怎么持续跑?③ 灰度期间怎么对比新旧模型?

L3 低频实录·1源 Q24 · 大模型应用怎么做灰度与 A/B?效果怎么归因?

答:灰度层次:① 流量灰度(1% → 10% → 50% → 100%),按 user_id / session_id 哈希分流(保证同一用户始终落在同一组,否则体验割裂);② 模型版本并存(新旧模型同时在线,按分流比例路由)。

必备护栏:错误率 / 延迟 / 成本 / 拒答率的自动熔断(指标越阈值自动回滚);影子模式(新模型只输出不返回,用于对比质量、零风险)。

效果归因:① 业务指标(转化、采纳、留存)+ 模型侧指标(离线评测分、线上代理指标);② 同时看绝对值与相对提升;③ 样本量不足时用序贯检验而不是固定周期看结果(避免"偷看"导致假阳性);④ 排除新奇效应与流量结构差异。

易错:只做 A/B 不做影子——线上出问题时无法回放对比;按请求随机分流——同一用户两个版本,体验不一致且污染统计。

追问:① 离线评测和线上指标不一致怎么办?② 怎么设计 prompt 的 A/B?③ 影子模式有什么坑?

L2 🔥×3 Q25 · 大模型 API 怎么接入?超时、重试、限流怎么处理?

答:接入:走 OpenAI 兼容接口(/v1/chat/completions),换模型只需改 base_url。

工程要点:① 超时分层(连接 / TTFT / 总超时分别设),避免长 prompt 请求占满连接池;② 重试:只对可重试错误(网络错误、429、5xx),4xx 参数错误重试无意义,必须配指数退避 + 抖动与重试上限;③ 限流:客户端做令牌桶(并发 + QPS 双限),服务端 429 要能正确退避;④ 幂等:带 request_id(重试必然带来重复请求);⑤ 降级:超时 / 限流时切小模型或缓存答案;⑥ 流式输出:长回答用 SSE,并把"用户取消"信号传给服务端。

易错:无脑重试——放大故障;忽略 429 的 Retry-After;长回答做成非流式。

追问:① 幂等键怎么设计?② 怎么降级到缓存?③ 流式场景怎么取消请求?

L2 🔥×4 Q26 · token 是什么?上下文窗口怎么算?token 和字符怎么换算?

答:token 是模型处理的基本单位,由分词器(BPE / SentencePiece)切分。不同模型粒度不同(中文约 1 汉字 ≈ 0.6~1 token),必须用目标模型自己的分词器统计,不能按字符数估算。

上下文窗口 = 一次前向能处理的 token 上限(prompt + 输出)。计费按 input / output token 分别计价(output 更贵);输出上限(max_tokens)也是硬约束。

工程要点:① 预留输出空间——窗口是"输入 + 输出"的总额;② token 预算管理——Agent 场景要在多轮之间裁剪历史;③ 缓存前缀——固定系统提示词放最前,利于 prefix caching 命中。

易错:按字符数估 token(中文尤其不准);把上下文窗口当"能放多少字";忘了系统提示词也占窗口。

追问:① 为什么不同模型 token 数差这么多?② 上下文变长时 KV Cache 怎么变?③ 怎么裁剪历史对话?

L3 补充 Q27 · Prompt 工程有哪些可靠的方法?怎么判断 prompt 是不是最优?

答:可靠方法:① 少样本示例(2~5 个"输入 → 理想输出"样例,格式与边界情况必须覆盖);② 结构化指令(角色 + 任务 + 约束 + 输出格式 + 失败行为);③ 分解与显式步骤;④ 自洽性(采样多次投票);⑤ 上下文工程(证据按相关性与位置组织,关键信息放首尾)。

怎么判断 prompt 是否最优:① 建固定评测集(几十到几百条真实 case + 判分标准);② 自动化判分(规则 + LLM-as-judge + 人工抽检);③ A/B 对比同一批 case 跑新旧 prompt,看通过率而非"感觉";④ 看失败 case 分布而不是总平均——平均分涨了但某类全崩是常见陷阱。

易错:没有评测集的 prompt 优化是玄学;同时改 prompt 和改模型导致无法归因。

追问:① LLM-as-judge 怎么保证可靠?② prompt 版本怎么管理?③ 什么场景该用 CoT、少样本?


04 RAG 检索与知识库(7 题)

L1 🔥×10 Q28 · RAG 的整体流程是什么?每个环节的常见优化点?

答:链路:文档 → 切分(chunking) → 向量化 → 建索引 → 用户问题 → 改写 / 扩展 → 检索 → 重排(rerank) → 拼上下文 → 生成 → 引用 / 后校验。

各环节优化:

  1. 切分:按语义边界(标题、段落)而非固定长度;父子块(小块检索、大块喂模型);重叠避免语义截断。
  2. 向量化:选领域适配的 embedding 模型。
  3. 召回:混合检索(向量 + BM25 关键词)——专有名词、编号、缩写上 BM25 更强。
  4. 重排:cross-encoder 精排取 top-k。
  5. 上下文组织:去重、排序、控制长度、标注来源。
  6. 生成约束:要求"仅基于给定材料回答,材料不足则说明"。

易错:只优化召回数量(top-k 越大越好)——上下文被无关片段污染,模型反而更差;不处理去重。

追问:① 为什么需要 rerank?② 混合检索怎么融合?③ 检索不到时怎么办?

L1 低频实录·1源 Q29 · RAG 切片怎么设计?切不好会导致什么问题?

答:切片维度:① 固定长度(简单,但会在语义中间截断);② 按分隔符(段落 / 标题 / 句子,保留语义单元);③ 递归切分(按多个层级依次切,优先保结构);④ 语义切分(用 embedding 判断相邻句是否主题相似再断)。

关键参数:chunk 大小与重叠。太大 → 噪声多、上下文挤占、召回不精准;太小 → 语义不完整("它"没有指代对象)、跨块推理失败。

推荐做法:父子块 / 小块检索 + 大块投喂——用小块(100~300 token)做检索保证精准,命中后把所属父块(1000+ token)送给模型保证上下文完整。

切不好的后果:召回的片段语义不完整 → 模型看到半句话就瞎编;或块太大 → 混进多个主题,rerank 打分不准。

易错:以为"重叠越大越好"——重叠大则索引膨胀、去重负担重、存储成本升;忽略表格 / 代码块 / 公式这类结构化内容不能按字符硬切。

追问:① 父子块怎么实现?② 切片大小怎么定(有没有数据驱动的方法)?③ 表格类文档怎么处理?

L2 低频实录·1源 Q30 · 怎么提高 Embedding 的召回效果?召回不准怎么定位?

答:先定位是"没召回"还是"召回了但排名低":拿真实 query 检索,肉眼看 top-k 是否含答案——这是最快的诊断;再看 query 与文档的语义鸿沟(口语 vs 术语);最后看 embedding 是否适配领域。

提升召回:① 查询改写(多路改写后融合);② 混合检索(向量 + BM25,RRF 融合——专有名词、编号、缩写上 BM25 更强);③ 模型选择(领域专用 > 通用);④ 多路召回 + rerank(召回放宽 top-50、精排收紧 top-5);⑤ 文档侧(补全内容、标结构化标签、去重合并);⑥ 结构化检索(图谱 / SQL 直查)。

易错:把 top-k 从 5 调到 50 当作"提升召回"——召回率涨了但精度崩了;只调 embedding 不先做 query 侧诊断。

追问:① HyDE 是什么?② RRF 怎么融合?③ 怎么离线评估召回率?

L2 🔥×2 Q31 · 什么是 rerank?为什么检索完还要再排一次?

答:双阶段检索:召回(retrieve)用双塔(bi-encoder)——query 与文档独立编码成向量,可离线建索引、检索极快,但query 与文档之间没有交互,精度有限;精排(rerank)用交叉编码器(cross-encoder)——把 query 与文档拼在一起送入模型算相关性,精度高但无法预计算索引(每对都要一次前向),所以只对召回的少量候选(top-50~100)做。

典型流程:向量 + BM25 召回 top-50 → rerank 取 top-3~5 → 送 LLM。

易错:把 rerank 当"锦上添花"——它通常是 RAG 精度的最大单一贡献点;反过来,把 cross-encoder 用于全量检索(慢到不可用)。

追问:① rerank 模型的输入输出是什么?② 多路召回的候选怎么去重?③ rerank 也要压延迟怎么办?

L2 🔥×4 Q32 · 向量数据库怎么选?索引方案(IVF / HNSW)有什么区别?

答:索引结构:IVF:先把向量空间聚类成 $nlist$ 个桶,查询只搜最近的 $nprobe$ 个桶——召回高、延迟稳定,但要训练、桶内要全扫。HNSW:多层图,查询贪心搜索从高层往下——召回高、延迟低、无需训练,但内存占用大、构建慢、删除需墓碑。IVF-PQ:用 PQ 压缩向量换内存,适合海量数据。

选型:① 千万级 + 内存够 → HNSW;② 数据量大 / 内存紧 / 频繁删改 → IVF-PQ;③ 百万以内 → 暴力检索(flat)往往最准也够快。

工程注意:过滤条件(metadata filter)与向量检索的组合是难点——先过滤再搜(召回低)还是先搜再过滤(候选不够),这决定索引类型的选择。

易错:只看 QPS 不看过滤后候选量;用 HNSW 却频繁删改(墓碑堆积致召回下降)。

追问:① PQ 原理是什么?② 怎么选 nlist / nprobe?③ 带过滤条件的向量检索怎么做?

L2 低频实录·1源 Q33 · 知识库更新了,怎么让 RAG 立刻生效?

答:三层时效性:

  1. 索引层:文档变更后要重新 embedding + 更新索引(HNSW 删除是墓碑,频繁删改需定期重建 / compaction);embedding 模型换了则全量重算。
  2. 缓存层:检索结果、生成结果、embedding 都要有版本化缓存键(含文档版本 / 索引版本),否则会读到旧内容——这是最常见的"改了没生效"原因。
  3. 应用层:prompt / 检索策略的变更走灰度。

做法:文档走事件流(MQ)→ 消费端做解析 → 切片 → 向量化 → 写索引 → 发事件刷新缓存。同时保留全量重建脚本兜底。

易错:只更新索引不刷缓存——用户看到的还是旧答案;增量更新时忘记更新父块(子块变了、父块没变)。

追问:① 怎么保证索引与源数据一致?② 批量导入怎么做(吞吐瓶颈)?③ 怎么监控索引新鲜度?

L3 补充 Q34 · 怎么评估一个 RAG 系统好不好?

答:拆开评:① 检索层:Recall@k(正确片段是否在 top-k)、MRR / NDCG、上下文精确率;② 生成层:正确性、忠实度(答案是否只依赖检索内容)、引用准确率;③ 端到端:任务成功率、用户采纳率、拒答率。

建评测集:从线上真实 query 采样,人工标注"相关文档集合 + 参考答案";100~300 条高质量 > 几千条自动生成。

评估方法:规则匹配 + LLM-as-judge(给 judge 明确评分标准与 few-shot)+ 人工抽检。judge 要先和人工标注对齐(算一致率)。

易错:只看"答案看起来通顺"——通顺但错误是 RAG 的典型失败;judge 用和被评模型同源的模型时会偏袒同族输出。

追问:① 忠实度和正确性的区别?② LLM-as-judge 怎么校准?③ 怎么持续回归测试 RAG?


05 模型、训练与推理进阶(11 题)

L2 🔥×4 Q35 · BF16 与 FP16 有什么区别?训练和推理各该怎么选?

答:两者都是 16 位浮点,区别在位分配。FP16:1 符号 + 5 指数 + 10 尾数,动态范围窄(上限约 6.6×10⁴),大梯度容易上溢。BF16:1 符号 + 8 指数 + 7 尾数,指数位与 FP32 相同,动态范围与 FP32 一致,只是尾数精度低。

训练怎么选:优先 BF16——范围大,不需要 loss scale,训练稳;FP16 训练要配动态缩放防梯度下溢(loss scaling 机制见 Q17),且对超参敏感。没有 BF16 硬件(Ampere 之前的卡)才退回 FP16 + AMP。

推理怎么选:FP16/BF16 都可,再往下可走 INT8/INT4 量化;BF16 尾数少,对生成质量影响通常可忽略。

易错:BF16 不是"FP16 的升级版"——总位数相同,是用尾数精度换动态范围;说"FP16 精度更高所以更好"忽略了训练稳定性主要由指数位决定。

追问:① 混合精度训练里哪些部分保持 FP32?② INT8 量化和 FP16 的适用差异?

L2 🔥×2 Q36 · FlashAttention 的原理是什么?它为什么快?

答:FlashAttention 是精确注意力的高效实现,核心是 tiling(分块)+ online softmax。标准实现要把 $n \times n$ 的注意力分数矩阵写到 HBM 再读回来,显存 $O(n^2)$、读写次数多。FlashAttention 把 Q/K/V 分块载入高速 SRAM,在片上完成"分块算分数 → 局部 softmax → 用 running max/sum 增量修正 → 加权求和",全程不物化完整的 $n \times n$ 矩阵。

为什么快:注意力计算是 memory-bound(受显存带宽限制),瓶颈在搬运不在算力。FlashAttention 把 HBM 读写从 $O(n^2)$ 降到 $O(n)$(只读写 Q/K/V 和输出),速度提升来自省 IO,FLOPs 反而略有增加。

易错:说成"近似注意力"——它是数学上完全等价的精确计算;说"快是因为算得少"——恰恰相反,快在减少 HBM 访问。

追问:① online softmax 怎么做到增量修正?② FlashAttention-2 改进了什么?③ 它和 KV Cache 的关系?

L2 🔥×4 Q37 · vLLM 的 PagedAttention 解决什么问题?为什么能提升吞吐?

答:解决 KV Cache 的显存管理碎片问题。传统做法为每个请求按最大序列长度连续预留 KV Cache 空间:预分配浪费 + 内外碎片并存,实测浪费 60-80% 显存,直接限制并发 batch 大小。

PagedAttention 借鉴操作系统虚拟内存分页:把 KV Cache 切成固定大小的 block(如 16 token/块),逻辑连续、物理按需分配,用块表映射。显存浪费降到 4% 以下。

吞吐提升的链条:显存省 → 同卡容纳的并发序列数大增 → batch 更大 → GPU 利用率更高 → 吞吐数倍提升。附带:prefix sharing(相同前缀共享 KV 块,引用计数管理)、copy-on-write 支持并行采样。

易错:PagedAttention 管的是 KV Cache 显存,不减少注意力计算量——和 FlashAttention(省 IO)解决的是不同层的问题,vLLM 两者都用。

追问:① continuous batching 是什么?② prefix caching 什么场景收益最大?③ 除了 vLLM 还知道哪些推理框架?

L2 低频实录·1源 Q38 · LLM 推理的 Prefill 与 Decode 两阶段各是什么瓶颈?分别怎么优化?

答:Prefill(预填充):一次并行处理整个 prompt,算出全部 KV Cache,每步都是大矩阵乘——compute-bound(算力瓶颈),决定首 token 延迟(TTFT)。

Decode(解码):逐 token 自回归生成,每步只算 1 个 token 却要读全部权重和 KV Cache——memory-bound(带宽瓶颈),GPU 算力空转,决定每 token 延迟(TPOT)与吞吐。

优化方向:Prefill——chunked prefill(切段与 Decode 混排)、prefix caching;Decode——continuous batching、投机解码(小模型起草 + 大模型并行验证)、MQA/GQA 压缩 KV、量化。

易错:两阶段瓶颈方向相反——Prefill 缺算力、Decode 缺带宽。盲目加大 batch 会顾此失彼(Decode 的大 batch 才是提吞吐关键)。

追问:① 投机解码为什么能保证输出分布不变?② TTFT 和 TPOT 分别影响什么体验?

L3 低频实录·1源 Q39 · MoE 架构相比 Dense 为什么省算力?代价是什么?

答:MoE 把 Transformer 的 FFN 层换成多个专家 FFN + 一个路由器(router),每个 token 只被路由到 top-k(通常 1-2 个)专家,其余专家不参与计算。

省在哪:模型总参数量可以很大(知识容量大),但每个 token 的激活参数量只有一小部分——算力开销与激活量挂钩,所以"同等计算成本下塞进了更多知识"。如 DeepSeek-V3 总参 671B、单 token 激活约 37B。

代价:① 显存不省——全部专家都要驻留显存;② 训练要 load balancing loss 防路由塌缩(大家都挤少数专家);③ 推理要做 expert parallel + all-to-all 通信,多卡部署复杂;④ 路由是离散决策,训练不稳定。

易错:说"MoE 省显存"——省的是激活算力不是显存;MoE 与集成学习无关(专家不是独立模型,是按 token 分工的子层)。

追问:① router 塌缩怎么发现和缓解?② 专家并行和流水线并行的区别?

L2 🔥×2 Q40 · DeepSeek-R1 这类推理模型的推理能力是从哪来的?

答:来自强化学习直接塑造推理行为,不是更多 SFT 数据的堆叠。核心证据是 R1-Zero:跳过 SFT,直接在 base 模型上用 GRPO 做 RL,奖励只用规则判分(答案对错 + 格式正确),推理行为(长思维链、自我反思、验算)自发涌现——模型学会把思考过程写出来,因为写出来更容易得分。

R1 的完整流程:少量冷启动 CoT 数据 SFT(解决 R1-Zero 可读性差、中英混杂)→ RL → 拒绝采样生成高质量 SFT 数据再训练 → 二次 RL 对齐全能。

蒸馏路线:用 R1 生成的 80 万条 CoT 数据 SFT 小模型,小模型推理能力大幅提升——说明推理模式可以被"教"。

易错:推理能力不是"思考时间长了就会"——必须有可验证的奖励信号(数学/代码答案可判对错);蒸馏小模型用的是 SFT 不是 RL。

追问:① 为什么规则奖励比奖励模型更稳?② 可验证奖励适合哪些领域?测试类任务怎么办?

L3 🔥×2 Q41 · GRPO 和 PPO 的区别是什么?为什么 LLM 训练偏好 GRPO?

答:两者都是 policy gradient 的 RL 算法,核心区别在 baseline(优势估计)怎么来。

PPO:训练一个 value model(critic) 估计状态价值,优势 = 奖励 − V(s),actor/critic 两网络交替更新。

GRPO:去掉 critic。对同一 prompt 采样 G 个回答,组内平均奖励当 baseline:优势 = (r − mean)/std;KL 惩罚加进损失约束偏移。

LLM 偏好 GRPO 的原因:critic 与 policy 同规模(几十 B),显存开销翻倍,且稀疏奖励(答案对错)下 critic 本身难训准;GRPO 用采样换 critic,省一半模型、实现简单——DeepSeek-R1 验证了这条路线。

易错:GRPO 不是"没有 baseline"——组内均值就是 baseline;一组回答奖励全相同时优势全为 0,该 prompt 这轮学不到东西(采样多样性很重要)。

追问:① 采样组数 G 怎么权衡成本与方差?② DPO 和 GRPO 分别适合什么场景?

L2 🔥×6 Q42 · PyTorch 的自动求导机制是怎么回事?标准训练流程是什么?

答:动态图:前向时作用在 requires_grad=True 张量上的运算即时记录成 DAG 节点(中间张量带 grad_fn);loss.backward() 沿图反向用链式法则把梯度累加到叶子节点的 .grad 上,默认图随之释放(retain_graph=True 可保留)。

训练循环:zero_grad() → 前向算 loss → backward() → step();忘 zero_grad 梯度累加出错。

要点:① 图每次前向重建(define by run),控制流天然支持,灵活但调度比静态图慢,torch.compile 补足;② 推理用 no_grad() 关闭记录省显存;③ detach 切断梯度流。

易错:以为"backward 会覆盖梯度"——它是累加,不清零就出错;把 zero_grad 放在 step 之后不等于"每步清一次"(顺序错会清掉刚算的)。

追问:① 动态图和静态图(TF1)的取舍?② 梯度检查点用什么代价换显存?

L2 🔥×7 Q43 · LoRA / Adapter / 全量微调怎么选?LoRA 为什么有效?

答:LoRA 为什么有效:微调时权重更新矩阵 ΔW 的秩很低(任务信息集中在低维子空间,机制细节见 Q11),把 ΔW 分解为 $BA$($r \ll d$)只训练它们,参数量降几十到上百倍,效果接近全量。

怎么选:① 数据少 / 多任务 / 资源紧 → LoRA(底座冻结可复用,切换任务只换几十 MB 的 BA 权重);② 领域偏移极大、数据充足(几百 MB 以上高质量数据)→ 全量微调上限更高;③ Adapter(在层间插入串行小模块):参数也省,但增加推理延迟,现已少用——LoRA 可在部署时把 BA 合并进原权重,零额外延迟。

易错:LoRA 初始化 B 全零、A 随机——保证起步时 ΔW=0 不破坏底座;r 不是越大越好,过大过拟合且失去低秩意义;说"LoRA 一定不如全量"在中小数据场景常不成立。

追问:① LoRA 作用在哪些层(Wq/Wv 还是全部线性层)?② QLoRA 又省在哪?③ 多个 LoRA 怎么热切换?

L2 🔥×5 Q44 · 多模态大模型的基本架构是什么?图像是怎么"进入"语言模型的?

答:主流架构三件套:视觉编码器 + 连接器 + LLM。① 视觉编码器(ViT,常用 CLIP 的视觉塔):把图像切块(patch)编码成特征;② 连接器:把视觉特征转成 LLM 能消费的"视觉 token"——线性投影(LLaVA,简单 MLP,最常用)、Q-Former(BLIP-2,用可学习 query 压缩成固定数量的 token)、cross-attention(Flamingo,不拼接而是插入注意力层);③ LLM:视觉 token 与文本 token 拼进同一序列自回归生成。

训练:先用图文对对齐(冻结两侧只训连接器),再用指令数据端到端微调(解冻 LLM)。

易错:视觉 token 数量随分辨率/图块数增长,高分辨率会撑爆上下文(所以有切图、压缩、动态分辨率设计);不是所有方案都把图像变成 token(cross-attention 不进序列)。

追问:① Q-Former 和线性投影的取舍?② 为什么要 OCR-free 端到端?③ 视频输入多了什么维度?

L2 低频实录·1源 Q45 · CLIP 的对比学习为什么能把图像和文本对齐到同一空间?

答:双塔结构 + 对比学习。图像编码器(ViT)和文本编码器(Transformer)各自把输入映射到同一维度的向量空间;训练时取一个 batch 的 N 对图文,算 $N \times N$ 相似度矩阵,对角线是正样本对(配对图文),其余是负样本——对矩阵逐行逐列做 cross-entropy(对称 InfoNCE),拉近配对、推远不配对。

为什么能对齐:① 4 亿级图文对的弱监督数据,覆盖足够广;② 大 batch(几万)提供海量负样本,对比信号丰富;③ 目标函数本身就是"图-文语义一致则相似度高",收敛后空间天然对齐。

用法学:零样本分类——把类名写成 prompt("a photo of a {class}")编码后与图算相似度取最大。

易错:CLIP 擅长粗粒度语义对齐,细粒度属性、计数、空间关系弱;负样本来自 batch 内,不存在"显式负例库"。

追问:① 双塔为什么不能做细粒度交互?② CLIP 怎么用在 RAG/检索里?


06 RAG 进阶与兜底(4 题)

L2 🔥×3 Q46 · 几百页的 PDF 怎么解析入库?表格和图片怎么处理?

答:先分类再处理:① 文本型 PDF 用 PyMuPDF/pdfplumber 直接抽文本;扫描型(文本层缺失或乱码)走 OCR(PaddleOCR);② 版面分析(layout 检测模型)区分正文/标题/表格/图片/页眉页脚,剔除噪声、恢复阅读顺序(双栏 PDF 直接抽文本会串行);③ 表格:用表格识别转成 Markdown/HTML 保留行列结构,或把每行序列化成"字段=值"的自然语句再入库(纯文本抽取会把表格打成乱码);④ 图片:用 VLM 生成描述文本(caption)入索引,原图存对象存储,chunk 里留引用;⑤ 切块按语义边界(标题/段落)而非固定长度,带页码/章节元数据支撑溯源;⑥ 跨页段落合并、去页眉页脚重复。

易错:拿 pdf2text 一把梭——表格碎成乱码、双栏串行、页眉当正文,检索质量差还查不出原因;解析质量要用抽样人工检查,不能只看"能入库"。

追问:① 怎么自动判断文本型还是扫描型?② 公式怎么处理?③ 解析效果怎么评测?

L2 低频实录·1源 Q47 · RAG 检索不到或答错时,有哪些兜底策略?

答:检索不到:① 设相似度阈值,top-k 分数都低于阈值就不硬答——走澄清反问、转人工或推荐相关问题;② 先多路重试:query 改写(同义展开/HyDE)、关键词补充召回,仍无结果才兜底;③ 兜底话术给用户下一步动作,不是一句"不知道"完事。

答错:④ 生成时强制引用检索片段,无依据的内容不允许输出(prompt + 结构化约束);⑤ 后置校验:用另一个模型检查答案是否被引用支撑(faithfulness),不过关就降级重答或拒答;⑥ 高风险场景人工兜底:错误代价大的领域(医疗/法务/资金)设置信度闸门,低于阈值转人工;⑦ 兜底命中的 case 进反馈闭环:补文档、调切片、补同义词表。

易错:靠提示词写"不知道就说不知道"不是兜底——阈值判断与转人工路由要在检索/编排层实现;拒答率升高要先查是不是检索质量恶化。

追问:① 相似度阈值怎么定?② 转人工的时机怎么设计?③ 兜底案例怎么变成语料改进?

L2 补充 Q48 · 长上下文模型和 RAG 怎么选?什么是 lost in the middle?

答:长上下文适合:单份资料能整个塞下(几十页合同、一次代码库走读)、无需预建索引、内容实时变化。RAG 适合:知识库海量(远超窗口)、多文档需更新、多租户隔离、成本敏感——长上下文的 token 费用与延迟随长度线性涨,且首 token 变慢。

lost in the middle:实测长上下文里模型对中段信息的召回率明显低于开头和结尾(U 形曲线)——关键信息即使"在窗口里"也可能被忽略。

实践共识:混合用——RAG 先粗筛相关片段,把少量高相关内容放进上下文精读;文档总量小才裸用长上下文。

易错:"窗口 128K = 12 万字都能用对"——有效可用长度受位置偏差与注意力稀释影响;长上下文的评测指标和 RAG 不同(无检索环节,看全窗口信息抽取率)。

追问:① lost in the middle 怎么缓解(重排/分段摘要)?② 超长文档"一次性总结"怎么做?

L2 补充 Q49 · 混合检索(BM25 + 向量)怎么做?两路分数怎么融合?

答:为什么混合:向量召回擅长语义近似(同义改写、跨语言),但对精确符号弱——型号、错误码、人名、API 名这类 OOV 词,稀疏检索 BM25 反而更准。两路互补,混合检索通常比单路召回率高几个点。

做法:① 双路并行:向量库召回 top-50 + BM25(ES/OpenSearch)召回 top-50;② 融合:RRF(倒数排名融合)——每篇文档得分 = Σ 1/(k + rank_i),只看排名不看原始分,无需调分数纲,最常用;或加权分数法(两路分数先各自归一化再加权);③ 重排:融合后的 top-20 用 cross-encoder rerank 精排,取 top-5 进 prompt。

易错:两路的原始分数量纲不同(BM25 无上界、余弦 0-1)直接相加没有意义——用 RRF 或先归一化;混合检索要维护两套索引,数据同步(新增/更新文档双写一致性)是工程难点。

追问:① RRF 的 k 起什么作用?② rerank 模型为什么用 cross-encoder 不用双塔?③ 权重怎么调?


07 训练资源与 ML 基础(4 题)

L3 低频实录·1源 Q50 · 只有少量 GPU 时,怎么完成大模型的训练/微调?

答:先算显存账:全量微调 7B 模型,FP16 权重 + 梯度 + Adam 状态约 84G,若含 fp32 master weights 约 112G——单卡放不下,资源受限时默认放弃全量微调。

阶梯方案:① QLoRA:底座 4bit 量化 + LoRA,单张 24G 卡可微调 7B,是资源紧张时的首选;② 省显存技术栈:梯度检查点(重算换显存,约慢 20-30%)、混合精度、FlashAttention、梯度累加等效大 batch;③ 多卡小集群:DeepSpeed ZeRO / FSDP 把优化器状态、梯度、参数分片到各卡;④ 换小基座:1.5B-7B + 高质量数据,常优于 70B + 少数据;⑤ 只做 SFT/LoRA 不碰预训练,数据质量优先于数量。

易错:梯度累加不省显存(单步激活照常占),省显存靠检查点和量化;QLoRA 训练完部署时要考虑 4bit 底座的推理框架支持。

追问:① ZeRO-1/2/3 各分片什么?② 梯度检查点为什么慢?③ 多卡 OOM 怎么定位?

L1 🔥×2 Q51 · bagging 和 boosting 的区别是什么?各举一个代表算法。

答:都是集成学习,核心区别是串并行与纠错方向。

Bagging:并行训练多个基学习器——每份用自助采样(有放回)抽出的不同子集,结果投票/平均。降方差,适合高方差的不稳定基模型(深决策树)。代表:随机森林(bagging + 特征随机子集进一步去相关)。

Boosting:串行训练——每一轮关注上一轮的错误(AdaBoost 加大错分样本权重;GBDT/XGBoost 拟合残差的负梯度),逐步纠错。降偏差,基模型用弱学习器(浅树)。代表:XGBoost。

怎么选:表格数据上 GBDT 系仍是首选;bagging 更抗过拟合、易并行,boosting 精度上限更高但对噪声敏感。

易错:"随机森林是 boosting"是最经典口误;"串行降偏差、并行降方差"是核心逻辑,不要背反。

追问:① XGBoost 相比 GBDT 优化了什么?② 为什么深度树配 bagging、浅树配 boosting?

L2 🔥×4 Q52 · LSTM 的遗忘门起什么作用?它怎么缓解梯度消失?

答:遗忘门:$f_t = \sigma(W_f[h_{t-1}, x_t] + b_f)$,输出 0-1 向量,逐元素乘上一时刻细胞状态 $C_{t-1}$,决定长期记忆保留多少:$C_t = f_t \odot C_{t-1} + i_t \odot \tilde{c}_t$。输入门决定写入多少新信息,输出门决定暴露多少给当前输出。

怎么缓解梯度消失:普通 RNN 的状态传递是乘法($h_t = \tanh(W h_{t-1} + x)$),梯度反传连乘 $W$,连乘小于 1 就指数消失。LSTM 的细胞状态走加性更新,梯度沿 $C$ 这条"高速通道"反传时只乘遗忘门的值——$f_t$ 接近 1 时梯度几乎无衰减地穿过很多时间步。工程上常把 $b_f$ 初始化为正数(如 1),让训练初期默认"多保留"。

易错:说成"遗忘门负责忘掉没用的信息"只是表象——它的本质是控制梯度和记忆沿时间传播的通路;LSTM 没消灭梯度消失(爆炸仍需裁剪),是显著缓解。

追问:① LSTM 和 GRU 的结构差异?② 为什么 Transformer 替代了 LSTM?

L2 🔥×6 Q53 · GAN 和 Diffusion 的区别是什么?各自优劣?

答:GAN:生成器与判别器对抗训练——G 造假、D 判真伪,均衡时 G 的输出骗过 D。一步采样出图,速度快;但训练是极小极大博弈,不稳定(不收敛、模式坍缩——生成器只出几种安全图),多样性差。代表:StyleGAN。

Diffusion:前向加噪 + 学习反向去噪——把图像逐步加噪到纯高斯,训练网络学"每一步的噪声"(回归式损失,目标明确),生成时从纯噪声迭代去噪(多步,几十次网络前向,慢)。训练稳定、模式覆盖好、条件控制强(CFG 放大引导),是当前文生图主流。代表:Stable Diffusion。

一句话对比:GAN 用对抗换速度(快但难训),Diffusion 用回归换稳定(好训但采样慢)。

易错:GAN 的 D 输出真/假概率,Diffusion 的网络预测的是噪声(或速度场)——两者学习目标完全不同;"扩散模型更稳"的代价就是多步采样延迟。

追问:① 模式坍缩怎么发现?② 扩散模型怎么加速采样(蒸馏/DDIM)?③ latent diffusion 省在哪?


08 AI 手撕代码(10 题)

L1 🔥×3 ✍ H01 · 手撕数值稳定 softmax

考察点:减 max 防 exp 溢出;keepdim=True 保证 broadcast 正确;float32 仍可能极端 -inf 输入致除零。

实现:

import torch

def softmax_stable(x, dim=-1):
    # 减 max 后所有元素 ≤ 0,exp 不会上溢
    x_max = x.max(dim=dim, keepdim=True).values
    exp_x = (x - x_max).exp()
    # keepdim=True 让 sum 形状与 exp_x 可广播
    return exp_x / exp_x.sum(dim=dim, keepdim=True)

易错:忘 keepdim 致广播错;全 -inf 输入(如整行 padding)exp=0、sum=0 → 除零,应在 mask 时先过滤。

L2 补充 ✍ H02 · 手撕 LogSumExp / log_softmax

考察点:log Σ exp(x) = x_max + log Σ exp(x - x_max);log_softmax 必须一步算(不要 log(softmax(x)))。

实现:

import torch

def logsumexp(x, dim=-1):
    x_max = x.max(dim=dim, keepdim=True).values
    # log-sum-exp 标准式:把 max 提出 exp 防上溢
    return x_max.squeeze(dim) + (x - x_max).exp().sum(dim=dim).log()

def log_softmax(x, dim=-1):
    # 等价 x - logsumexp(x);一步算避免 log(softmax) 在 prob≈0 处 log(0) = -inf
    x_max = x.max(dim=dim, keepdim=True).values
    shifted = x - x_max
    return shifted - shifted.exp().sum(dim=dim, keepdim=True).log()

易错:用 log(softmax(x));忘 keepdim 致广播错;float16 下 LSE 仍可能上溢,应用 float32 中间量。

L1 低频实录·1源 ✍ H03 · 手撕 cross-entropy(含数值稳定)

考察点:CE = NLL + log_softmax 的组合;padding 用 ignore_index 屏蔽;soft label 走另一公式。

实现:

import torch
import torch.nn.functional as F

def cross_entropy_hard(logits, target, ignore_index=-100):
    # logits: [B, C];target: [B],padding 处填 -100 屏蔽
    log_p = F.log_softmax(logits, dim=-1)        # 一步算,避免 log(softmax)
    nll = -log_p.gather(-1, target.clamp(min=0).unsqueeze(-1)).squeeze(-1)
    mask = (target != ignore_index).float()
    # mean 必须按有效样本数归一,否则 ignore 多时被稀释
    return (nll * mask).sum() / mask.sum().clamp(min=1.0)

def cross_entropy_soft(logits, soft_target):
    # soft_target: [B, C] 概率分布(label smoothing / 蒸馏常用)
    log_p = F.log_softmax(logits, dim=-1)
    return -(soft_target * log_p).sum(-1).mean()

易错:先 softmax 再 log 数值不稳;padding 未 mask 拉偏 loss;reduction='mean' 但分母用 batch 大小(应用有效样本数)。

L2 补充 ✍ H04 · 手撕 KL divergence(离散 / 高斯 / forward vs reverse)

考察点:KL 不对称(forward mode-covering / reverse mode-seeking);高斯闭式;F.kl_div 首参约定 log_q。

实现:

import torch
import torch.nn.functional as F

def kl_discrete(log_p, log_q):
    # 离散 KL(p‖q) = Σ p·(log p - log q);用 log_prob 入参数值稳
    # p=0 处约定 0·log(0/q)=0;mask 掉 log_p=-inf 项避免 0·inf=NaN
    p = log_p.exp()
    term = p * (log_p - log_q)
    return term.masked_fill(log_p == float('-inf'), 0.0).sum(-1)

def kl_gauss_to_std(mu, logvar):
    # VAE 特例:KL(N(μ,σ²) ‖ N(0,I)) 闭式
    # 0.5 · Σ(μ² + σ² - 1 - log σ²)
    return 0.5 * (mu.pow(2) + logvar.exp() - 1 - logvar).sum(-1)

def kl_via_F(log_q, p_prob):
    # PyTorch 约定:第一参传 log_q,第二参传 p(prob,非 log)
    # 等价 Σ p·(log p - log q)
    return F.kl_div(log_q, p_prob, reduction='batchmean')

易错:log 用 σ 还是 σ²(差一倍);F.kl_div 首参传 prob 而非 log_prob;forward/reverse 方向反;离散输入 p,q 都用 prob 致 log(0)。

L1 补充 ✍ H05 · 手撕 mini-batch 训练循环

考察点:zero_grad → forward → backward → step 顺序;梯度累加 + AMP 顺序;推理用 eval() + no_grad()。

实现:

import torch
from torch.cuda.amp import autocast, GradScaler

def train_epoch(model, loader, opt, crit, device, accum=1):
    model.train()
    scaler = GradScaler()
    opt.zero_grad(set_to_none=True)  # set_to_none 比置 0 更省显存
    for i, (x, y) in enumerate(loader):
        x, y = x.to(device), y.to(device)
        with autocast():
            loss = crit(model(x), y) / accum         # 梯度累加要先除步数
        scaler.scale(loss).backward()                # AMP loss 先 scale 再 backward
        if (i + 1) % accum == 0:
            scaler.step(opt); scaler.update()
            opt.zero_grad(set_to_none=True)          # step 之后清梯度

    # 处理 loader 长度非 accum 整数倍:末尾残余梯度也 step
    if len(loader) % accum != 0:
        scaler.step(opt); scaler.update()
        opt.zero_grad(set_to_none=True)

@torch.no_grad()
def evaluate(model, loader, crit, device):
    model.eval()  # 关 dropout / 停 BN running 更新
    return sum(crit(model(x.to(device)), y.to(device)) for x, y in loader)

易错:zero_grad 放在 step 之前(会清掉刚累积的梯度);AMP 下 backward 前不 scale;eval 时忘 model.eval()。

L1 🔥×3 ✍ H06 · 手撕 warmup + cosine 学习率 schedule

考察点:warmup 防初始 lr 过大;cosine 末期渐降;边界 t=t_w 处 lr 必须连续(=lr_max)。

实现:

import math
from torch.optim.lr_scheduler import LambdaLR

def warmup_cosine(opt, t_warm, t_total, min_ratio=0.0):
    # t_total 用 step 数而非 epoch(step 粒度更细)
    def lr_lambda(t):
        if t < t_warm:
            # 线性 warmup:t=0 → 0;t=t_warm → 1(边界连续)
            return float(t) / max(1, t_warm)
        # cosine 末期:t=t_warm → 1;t=t_total → min_ratio
        progress = (t - t_warm) / max(1, t_total - t_warm)
        cos = 0.5 * (1.0 + math.cos(math.pi * progress))
        return min_ratio + (1.0 - min_ratio) * cos
    return LambdaLR(opt, lr_lambda)

易错:t_warm=0 致除零;t_total 用 epoch 数末期未降到底;warmup 公式从 1 开始致 t=0 时 lr=lr_max;边界 t=t_warm lr 跳变。

L2 🔥×6 ✍ H07 · 手撕 Adam / AdamW 更新规则

考察点:一阶/二阶 momentum + bias correction;AdamW 解耦 weight decay(不混进 momentum)。

实现:

import torch

def adamw_step(params, grads, state, lr=1e-3, betas=(0.9, 0.999),
               eps=1e-8, wd=1e-2):
    # state[i] = {'m': ..., 'v': ..., 't': int};外层维护
    b1, b2 = betas
    for p, g, s in zip(params, grads, state):
        s['t'] += 1
        # 一阶/二阶 momentum 指数滑动
        s['m'].mul_(b1).add_(g, alpha=1 - b1)
        s['v'].mul_(b2).addcmul_(g, g, value=1 - b2)
        # bias correction:消除初期 m,v 偏向 0 的偏置
        m_hat = s['m'] / (1 - b1 ** s['t'])
        v_hat = s['v'] / (1 - b2 ** s['t'])
        # AdamW:weight decay 直接乘 p,不进 m/v
        # (若改为 Adam,把 wd*p 那项加到 g 里、再走主更新即为 L2 正则)
        p.data.mul_(1 - lr * wd).addcdiv_(m_hat, v_hat.sqrt().add_(eps), value=-lr)

易错:忘 bias correction(初期步太小);把 wd*p 加到 grad(变 L2 正则非 AdamW);v̂ 算根之前没存独立副本,多组 param 共享 buffer 时被破坏。

L1 低频实录·1源 ✍ H08 · 手撕 dropout(train vs eval + inverted scale)

考察点:train 随机置零并 scale;eval 直接通过;inverted dropout 把 1/(1-p) 放训练侧让推理零开销。

实现:

import torch
import torch.nn as nn

class Dropout(nn.Module):
    def __init__(self, p=0.5):
        super().__init__()
        assert 0.0 <= p < 1.0
        self.p = p

    def forward(self, x):
        if not self.training or self.p == 0.0:
            return x  # 推理无开销
        # mask ~ Bernoulli(1-p):保留概率 1-p;用 x.dtype 防 fp16/bf16 被提升
        mask = (torch.rand_like(x) >= self.p).to(x.dtype)
        # inverted dropout:除 (1-p) 让 E[out] = x,无需推理时 scale
        return x * mask / (1.0 - self.p)

易错:推理还 drop(F.dropout 忘传 training=self.training);忘 scale 致 E[out] 不一致;位置放错(残差里 dropout 在 add 之后破坏恒等映射)。

L2 🔥×4 ✍ H09 · 手撕多头注意力(MHA)前向

考察点:多头拆分/合并的 view-transpose-reshape 顺序、缩放因子 $\sqrt{d_k}$ 的位置、掩码在 softmax 之前。

实现:

import torch
import torch.nn as nn

class MHA(nn.Module):
    def __init__(self, d_model, n_heads):
        super().__init__()
        assert d_model % n_heads == 0
        self.h, self.dk = n_heads, d_model // n_heads
        self.qkv = nn.Linear(d_model, 3 * d_model)  # Q/K/V 合一个投影,省两次矩阵乘
        self.out = nn.Linear(d_model, d_model)

    def forward(self, x, mask=None):
        d = x.shape[-1]
        q, k, v = self.qkv(x).split(d, dim=-1)      # 各 (B, n, d)
        # 拆多头:(B, n, h, dk) -> (B, h, n, dk),把 h 提前便于批量矩阵乘
        def split(t):
            return t.view(*t.shape[:-1], self.h, self.dk).transpose(-3, -2)
        q, k, v = map(split, (q, k, v))
        att = (q @ k.transpose(-2, -1)) / self.dk ** 0.5   # 缩放必须在 softmax 前
        if mask is not None:
            att = att.masked_fill(mask == 0, float("-inf"))  # 屏蔽位置 -inf,softmax 后为 0
        att = att.softmax(dim=-1)
        y = (att @ v).transpose(-3, -2).reshape(*x.shape[:-1], d)  # 先转回头维度再合并
        return self.out(y)

易错:合并多头时不先 transpose 回来直接 reshape——头维度被打乱成 garbage;缩放放在 softmax 之后;掩码填 0 而不是 -inf(softmax 后仍有权重)。

L2 低频实录·1源 ✍ H10 · 手撕最小 RAG(切块 → 检索 → 拼 prompt)

考察点:切块要留重叠、余弦相似度别忘除模长、prompt 必须约束"仅依据资料"。

实现:

import math

def chunk(text, size=200, overlap=50):
    # 滑窗切块;overlap 防关键句被切块边界截断
    step = max(1, size - overlap)
    return [text[i:i + size] for i in range(0, len(text), step)] if text else []

def cos(a, b):
    # 余弦相似度:除以模长消除向量长度影响,纯内积会偏向长向量
    da = math.sqrt(sum(x * x for x in a)) or 1.0
    db = math.sqrt(sum(x * x for x in b)) or 1.0
    return sum(x * y for x, y in zip(a, b)) / (da * db)

def retrieve(query, index, embed, k=3):
    docs, vecs = index                      # index = (chunks, [embed(c) for c in chunks])
    q = embed(query)
    top = sorted(range(len(docs)), key=lambda i: cos(q, vecs[i]), reverse=True)
    return [docs[i] for i in top[:k]]

def answer(query, index, embed, llm, k=3):
    # "仅依据资料"约束 + 无据答不知道:没有这句就退化成直接问 LLM
    ctx = "\n---\n".join(retrieve(query, index, embed, k))
    return llm(f"仅依据以下资料回答,资料没有就答'不知道':\n{ctx}\n\n问题:{query}")

易错:切块不留 overlap 截断关键句;相似度用纯内积(模长偏置);prompt 不加"仅依据资料"约束,答案脱离检索内容硬编。


低频备选(未入主表)