面经合集
完整信息
本篇目录
这份面经的含金量非常高,几乎涵盖了大模型算法工程师在工业界落地的所有核心考点:RAG检索增强、大模型底层架构、微调与强化学习对齐、以及工程化推理加速。
为了便于你系统性地学习和理解,我将这两面中出现的技术和问题重新划分为五大硬核技术模块,为你逐一详细拆解。
模块一:RAG(检索增强生成)与信息检索
1. 工业界文本语义切分技术(Chunking)
- 规则切分: 最基础的方案,基于特定的标识符(如换行符
\n、句号等)结合最大长度(Max Token Length)进行切分。通常会设置Overlap(重叠度,如前后保留50个字符)以防止句子从中间断开导致语义丢失。 - 语义切分: 利用小参数量的 Embedding 模型,计算相邻句子的余弦相似度。如果相似度低于某个阈值,说明语义发生了转折,就在该处进行切分。
- LLM 辅助切分: 对于复杂的长文档(如 PDF 财报),利用大模型提取文档的大纲树(结构化切分),或者让 LLM 总结每一段的主旨,将主旨与原文绑定在一起作为 Chunk。
2. 检索方案对比:Qwen3-Embedding vs BM25 vs BGE-M3
- 传统 BM25(稀疏检索): 基于词频-逆文档频率(TF-IDF)的变体。优点:对专有名词、特定料号、生僻词的精确匹配极好,无需 GPU,速度极快。缺点:无法理解同义词和语义,比如搜“苹果手机”,无法召回写着“iPhone”的文档。
- Qwen3-Embedding(稠密检索/语义检索): 将文本映射到高维稠密向量空间。优点:语义理解极强,能跨越字面表达的差异进行召回。缺点:对精准关键词匹配较弱。
- BGE-M3: 业界顶尖的检索模型,名字里的 M3 代表 Multi-lingual(多语言)、Multi-granularity(多粒度长度)、Multi-Function(多功能)。它同时输出稠密向量(Dense)、稀疏向量(Sparse,类似词频)以及多向量(ColBERT-like),是融合了 BM25 和纯 Embedding 优点的“六边形战士”。
3. RRF (Reciprocal Rank Fusion) 加权递归融合
- 原理: 在混合检索(如 BM25 + 向量检索)中,不同检索器的打分范围完全不同,无法直接相加。RRF 放弃了具体的得分,只看文档在各个检索器中的排名(Rank)。
- 公式:
\text{Score} = \sum \frac{1}{k + \text{Rank}_i}(其中k通常取 60)。 - 优势: 极度鲁棒。不需要调节权重超参数,就能平滑地将多路召回的结果融合在一起,排名越靠前的文档最终得分越高。
4. RAG 标准链路与 Query 前置优化
- 为什么不能直接检索: 用户的原始输入往往是口语化、省略主语或带有错别字的(例如:“这俩有什么区别?”)。直接去向量库搜,大概率召回垃圾信息。
- 前置优化(Query Rewrite):
- 意图识别与路由: 判断是查知识库、查实时天气、还是闲聊。
- Query 重写/指代消解: 利用 LLM 结合历史对话,把“这俩”补全为“Qwen3 和 Llama3”。
- HyDE(假设性文档嵌入): 让 LLM 先根据 Query 瞎编一个答案,拿生成的答案去向量库检索,往往能大幅提升召回率。
5. GraphRAG 图结构构建与差异化
- 逻辑: 传统 RAG 是把文章切碎,丢失了全局上下文。GraphRAG 利用 LLM 从文档中抽取“实体(Entity)”和“关系(Relationship)”,构建知识图谱。
- 差异化检索: 面对“请总结这本小说的全局剧情”这类问题,传统 RAG 只能召回几个零碎片段,而 GraphRAG 可以通过图结构的社区聚类(Community Detection)和路径游走,生成涵盖全局的宏观摘要。
模块二:大模型底层架构机制
1. Transformer 架构与 \sqrt{d_k} 的底层逻辑
- 架构简述: 包含自注意力机制(Self-Attention)、前馈神经网络(FFN)、层归一化(LayerNorm)和残差连接。
- 为什么除以
\sqrt{d_k}: 在计算注意力得分Q K^T时,Q和K的维度是d_k。随着维度d_k变大,点积结果的方差会线性增大。方差过大会导致点积数值极大,送入 Softmax 函数后,部分概率极度接近 1,其余接近 0。这会使得 Softmax 的梯度趋近于 0(梯度消失)。除以\sqrt{d_k}是为了将方差缩放回 1,保证反向传播时梯度健康流动。
2. Encoder vs Decoder 架构
- 区别: Encoder(如 BERT)是双向注意力,能同时看到上下文;Decoder(如 GPT)是单向(Causal)注意力,只能看到当前词之前的序列。
- Decoder-only 获胜的原因:
- 生成式任务(自回归预测下一个词)天然匹配 Decoder。
- Zero-shot 和 Few-shot 提示工程更契合从左到右的生成逻辑。
- Decoder 架构可以完美利用 KV Cache 加速推理,而双向 Encoder 在生成任务中每次都要重算全局注意力,效率极低。
3. 位置编码盘点
- 绝对位置编码(Sinusoidal/Learned): 直接加在 Token Embedding 上,模型难以直接感知相对距离。
- 相对位置编码(ALiBi等): 在计算 Attention Score 时直接加上距离惩罚,外推性好,但计算复杂度较高。
- RoPE(旋转位置编码): 结合了绝对与相对的优点。通过乘以一个旋转矩阵,使得两个 Token 向量进行内积时,结果天然包含它们的相对位置差。它是目前开源大模型(Llama, Qwen, DeepSeek)的绝对标配。
4. 多模态大模型基础架构
- 通用范式: Vision Encoder + Projector + LLM Backbone。
- 图像先输入到 Vision Encoder(如 ViT/CLIP)提取视觉特征
\rightarrow通过 Projector(如 MLP 线性层或 Q-Former)将视觉特征映射到 LLM 的文本特征空间\rightarrow与文本 Token 拼接后送入 LLM 进行自回归生成。
模块三:对齐算法与微调 (Alignment & Fine-tuning)
1. 演进路线:PPO \rightarrow DPO \rightarrow GRPO
- PPO(Proximal Policy Optimization): 经典 RLHF。需要 Actor、Critic、Reward、Reference 四个模型。通过 Reward 模型打分,Actor 根据分数更新策略。缺点是极其消耗显存,训练极不稳定。
- DPO(Direct Preference Optimization): 直接偏好优化。将强化学习问题等价转化为分类问题(交叉熵损失)。不需要训练 Reward 模型,直接用人类偏好数据(Chosen/Rejected)来微调 Actor 模型。训练稳定,算力友好。
- GRPO(Group Relative Policy Optimization): DeepSeek 提出的算法。彻底去除了 PPO 中的 Critic 网络。针对同一个 Prompt 采样多条回答,通过这组回答得分的组内均值和标准差来计算相对优势(Advantage),极大节省了显存开销。
2. DPO 损失函数(口述级推导)
- DPO 的核心在于最大化优选回复
y_w相对于劣选回复y_l的隐式奖励差。其公式为:
\mathcal{L}_{DPO} = - \mathbb{E}_{(x, y_w, y_l)} \left[ \log \sigma \left( \beta \log \frac{\pi_\theta(y_w\vert{}x)}{\pi_{ref}(y_w\vert{}x)} - \beta \log \frac{\pi_\theta(y_l\vert{}x)}{\pi_{ref}(y_l\vert{}x)} \right) \right]
- 解释:
\pi_\theta是当前训练模型,\pi_{ref}是冻结的参考模型。公式通过计算两个模型生成概率的比值,推动训练模型增加好数据的概率,同时降低坏数据的概率,\beta是控制偏离参考模型程度的超参数。
3. LoRA 微调核心超参数
r(Rank):低秩矩阵的秩,通常设为 8 到 64。越大拟合能力越强,但也越容易过拟合。lora_alpha:缩放因子。通常设置为r的 2 倍。它控制 LoRA 更新权重对原本模型权重的影响力度。target_modules:早期只加在 Attention 的 Q、V 上,现在的最佳实践是加在所有线性层上(包括 FFN 的 Gate、Up、Down)。
模块四:工程加速与异常排查 (Inference & OOM)
1. vLLM 推理加速的核心:PagedAttention
- 痛点: 传统推理中,KV Cache 会预先分配一大块连续的显存,导致严重的内存碎片(碎片率高达 60%)。
- PagedAttention: 借鉴操作系统的虚拟内存分页机制,将 KV Cache 划分为固定大小的块(Blocks)。显存不再需要连续,极大地减少了碎片,显存利用率飙升,从而能够显著增加并发的 Batch Size。
- Continuous Batching(连续批处理): 在迭代(Iteration)级别进行调度,只要有一个请求生成完毕,立刻把新的请求塞进来,不需要等待整个 Batch 的所有句子都生成完。
2. OOM (显存溢出) 排查思路与解决
训练阶段:
解决: 开启 Gradient Checkpointing(以计算时间换显存);使用 DeepSpeed ZeRO 策略(切分优化器状态、梯度和模型权重);降低 Batch Size 并利用 Gradient Accumulation(梯度累加)。
微调阶段:
解决: 引入 QLoRA(将基座模型量化为 4-bit 驻留显存);开启 FlashAttention-2 减小前向传播的显存占用峰值。
推理阶段:
排查: 多数是因为并发太高或用户的 prompt 太长导致 KV Cache 撑爆。
解决: 限制最大上下文长度(Max Seq Len);使用 vLLM 框架;引入 KV Cache 量化(如 INT8/FP8)来压缩缓存。
模块五:手撕代码核心思路
1. 多头注意力机制 (Multi-Head Attention)
- 核心逻辑:
- 输入
X通过线性层分别映射出Q, K, V。 - 对维度进行 Reshape,拆分出
num_heads。 - 计算
scores = matmul(Q, K.transpose) / sqrt(d_k)。 - 对
scores应用 Softmax。 - 计算
context = matmul(softmax_scores, V)。 - 将所有的 head 重新拼接(Concat),最后经过一个输出线性层。
2. 最大连通域面积 (岛屿最大面积 - LeetCode 695)
- 核心逻辑: 典型的图遍历问题,使用 DFS(深度优先搜索)或 BFS。
- 遍历二维网格矩阵的每一个点,遇到为
1的网格,以此为起点发起 DFS,将相连的1标记为已访问(如设为0防止重复计算),并累计面积。更新全局最大面积变量,直到遍历完整个网格。 没问题!针对腾讯面经中出现的两道经典手撕代码题,我为你整理了可以直接运行、且带有详细工程注释的完整 Python 代码。
一、 多头注意力机制 (Multi-Head Attention) 完整实现
在面试中手撕 Attention,面试官最看重的是你对张量维度变换(Reshape / Transpose)的熟练度,以及是否记得缩放因子(Scale)和掩码(Mask)的处理。以下是基于 PyTorch 的标准实现:
import torch
import torch.nn as nn
import math
class MultiHeadAttention(nn.Module):
def __init__(self, d_model, num_heads):
super(MultiHeadAttention, self).__init__()
# 必须保证模型维度能被头数整除
assert d_model % num_heads == 0, "d_model must be divisible by num_heads"
self.d_model = d_model
self.num_heads = num_heads
self.d_k = d_model // num_heads # 每个头的维度
# 定义 Q, K, V 的线性映射层
self.W_q = nn.Linear(d_model, d_model)
self.W_k = nn.Linear(d_model, d_model)
self.W_v = nn.Linear(d_model, d_model)
# 最后的输出线性映射层
self.W_o = nn.Linear(d_model, d_model)
def forward(self, q, k, v, mask=None):
batch_size = q.size(0)
# 1. 线性映射并切分多头
# 维度变化: [batch_size, seq_len, d_model]
# -> [batch_size, seq_len, num_heads, d_k]
# -> [batch_size, num_heads, seq_len, d_k]
Q = self.W_q(q).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
K = self.W_k(k).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
V = self.W_v(v).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
# 2. 计算注意力得分 (Q 乘以 K 的转置,并除以根号 d_k)
# K.transpose(-2, -1) 将最后两个维度翻转,即变为 [batch_size, num_heads, d_k, seq_len]
# scores 维度: [batch_size, num_heads, seq_len, seq_len]
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
# 3. 掩码机制 (可选,通常用于 Decoder 防止看到未来的词)
if mask is not None:
# 将 mask 中为 0 的位置替换为极小值,Softmax 后会趋近于 0
scores = scores.masked_fill(mask == 0, -1e9)
# 4. Softmax 归一化得到注意力权重
attn_weights = torch.softmax(scores, dim=-1)
# 5. 权重乘以 V 得到 Context 向量
# context 维度: [batch_size, num_heads, seq_len, d_k]
context = torch.matmul(attn_weights, V)
# 6. 拼接多头 (Concat)
# transpose 恢复维度顺序: [batch_size, seq_len, num_heads, d_k]
# contiguous() 保证张量在内存中连续,否则 view() 会报错
# view() 展平多头: [batch_size, seq_len, d_model]
context = context.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model)
# 7. 最后的线性投影
output = self.W_o(context)
return output
# --- 测试代码 ---
if __name__ == "__main__":
batch_size, seq_len, d_model = 2, 10, 512
num_heads = 8
# 模拟输入张量
x = torch.randn(batch_size, seq_len, d_model)
# 实例化并前向传播
mha = MultiHeadAttention(d_model=d_model, num_heads=num_heads)
out = mha(x, x, x) # 自注意力机制中,Q, K, V 都是输入 x
print(f"输入维度: {x.shape}")
print(f"输出维度: {out.shape}")
二、 最大连通域面积 / 岛屿的最大面积 (LeetCode 695)
这是一道高频的基础图论/搜索题。面试时推荐使用 DFS (深度优先搜索),代码最简洁,不易出错。核心思路是:遍历网格,遇到 1 就以此为起点进行 DFS,并把遍历过的 1 沉岛(设为 0),防止死循环重复计算。
class Solution:
def maxAreaOfIsland(self, grid: list[list[int]]) -> int:
# 边界条件处理
if not grid or not grid[0]:
return 0
rows, cols = len(grid), len(grid[0])
max_area = 0
# 定义深度优先搜索函数
def dfs(r: int, c: int) -> int:
# Base Case:如果越界,或者当前格子是水(0),返回面积 0
if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] == 0:
return 0
# 关键步骤:标记当前陆地为已访问(沉岛策略,避免死循环)
grid[r][c] = 0
# 当前格子算 1 个面积
area = 1
# 递归向上下左右四个方向扩展,累加连通的面积
area += dfs(r + 1, c) # 下
area += dfs(r - 1, c) # 上
area += dfs(r, c + 1) # 右
area += dfs(r, c - 1) # 左
return area
# 遍历整个二维网格
for r in range(rows):
for c in range(cols):
# 只有遇到陆地(1)才开始发起 DFS
if grid[r][c] == 1:
# 更新全局最大面积
max_area = max(max_area, dfs(r, c))
return max_area
# --- 测试代码 ---
if __name__ == "__main__":
grid = [
[0,0,1,0,0,0,0,1,0,0,0,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,1,1,0,1,0,0,0,0,0,0,0,0],
[0,1,0,0,1,1,0,0,1,0,1,0,0],
[0,1,0,0,1,1,0,0,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,0,0,0,0,0,0,1,1,0,0,0,0]
]
sol = Solution()
print(f"最大岛屿面积为: {sol.maxAreaOfIsland(grid)}") # 期望输出: 6
复杂度分析:
- 时间复杂度:
O(M \times N),其中M和N分别为网格的行数和列数。我们最多遍历网格中的每个单元格一次。 - 空间复杂度:
O(M \times N)。在最坏情况下(整个网格全是陆地),递归调用的调用栈深度会达到M \times N。
本篇目录
当前问题0118
- 01
**1. 工业界文本语义切分技术(Chunking)**
- 02
**2. 检索方案对比:Qwen3-Embedding vs BM25 vs BGE-M3**
- 03
**3. RRF (Reciprocal Rank Fusion) 加权递归融合**
- 04
**4. RAG 标准链路与 Query 前置优化**
- 05
**4. RAG 标准链路与 Query 前置优化**
- 06
**5. GraphRAG 图结构构建与差异化**
- 07
**1. Transformer 架构与 `\sqrt{d_k}` 的底层逻辑**
- 08
**1. Transformer 架构与 `\sqrt{d_k}` 的底层逻辑**
- 09
**2. Encoder vs Decoder 架构**
- 10
**3. 位置编码盘点**
- 11
**4. 多模态大模型基础架构**
- 12
**1. 演进路线:PPO `\rightarrow` DPO `\rightarrow` GRPO**
- 13
**2. DPO 损失函数(口述级推导)**
- 14
**3. LoRA 微调核心超参数**
- 15
**1. vLLM 推理加速的核心:PagedAttention**
- 16
**2. OOM (显存溢出) 排查思路与解决**
- 17
**1. 多头注意力机制 (Multi-Head Attention)**
- 18
**2. 最大连通域面积 (岛屿最大面积 - LeetCode 695)**