网易大模型算法工程师面试题精选:10道高频考题+答案解析
基于网易2024-2026面试真题库,覆盖Transformer架构、大模型训练、分布式框架、模型微调、推理优化等核心领域,每题附口语化解析。
1. Transformer结构详解:从输入token到输出logits的完整流程
这是网易最高频的考题之一,几乎场场必问。
面试官让你讲完整流程,你就得从头到尾串起来,别卡壳。从输入开始说:输入文本先经过 Tokenizer 切分成 token,然后映射成 embedding 向量,加上位置编码(现在主流用 RoPE 旋转位置编码,而不是最早的 Sinusoidal)。为什么加位置编码?因为 self-attention 本身是排列不变的,它做的是加权求和,比如"我打你"和"你打我"如果不加位置信息,attention 算出来是一样的。
然后进入 Transformer Block。每个 Block 主要做两件事:Multi-Head Self-Attention 和前馈网络 FFN。在 Self-Attention 里,Q、K、V 矩阵从同一份输入线性变换而来,计算完 attention score 之后除以 √d_k 做缩放(防止内积过大导致 softmax 梯度消失),softmax 加权后输出。这里有个常考点:Pre-Norm 和 Post-Norm。现在主流用 Pre-Norm(比如 LLaMA、GPT),因为它在残差连接之前做 LayerNorm,训练更稳定;Post-Norm 是在残差之后做,训练初期容易炸,需要 warmup。
FFN 层的设计也值得说。经典的 FFN 是"先升维再降维"——比如 4096 维度升到 11008 再降回来。为什么这么设计?因为升维之后给模型更大的"记忆空间",相当于每个 token 在更高维空间做特征变换,表达能力更强。LLaMA 用的激活函数是 SwiGLU,不是 ReLU,SwiGLU 是 Swish 和 GLU 的组合,效果更好但参数量多了 1/3,所以做参数量计算的时候要小心这个坑。
最后,Decoder 部分如果有 Masked Self-Attention,训练时用 causal mask 保证每个 token 只能看前面的,推理时用 KV Cache 缓存已计算过的 K 和 V,避免重复计算。
2. Attention 计算:为什么除以√d_k?Softmax 为什么减最大值?
这道题网易二面基本都考过,主要是看你基础扎不扎实。
先说除以 √d_k。Attention 的核心公式是 softmax(QK^T / √d_k),这个 √d_k 就是 query/key 的维度。QK^T 的结果是多个向量内积的累加,每个内积的均值为 0,方差为 d_k(因为每个维度上的元素方差约 1)。当 d_k 很大时,比如 128,内积的方差就是 128,内部值可能相差很大,导致 softmax 结果向 one-hot 坍缩——最大的那个值接近 1,其他接近 0,梯度趋近于 0,没办法学习了。除以 √d_k 就是把方差重新归一化回 1,让 softmax 保持合理的梯度。
Softmax 时为什么要减去最大值?这个问题其实是个数值稳定性问题。Softmax 公式里要计算 e^x,如果 x 很大比如 1000,e^1000 直接溢出了。所以先找所有输入的最大值 max_val,每个数减掉它,最大变成 0,其他负数。e^0=1 不溢出,所有值都在 (0,1] 之间。这步操作不影响 softmax 的结果,因为分母分子同时乘了 e^{-max_val}。
代码实现起来也很直观:
import numpy as np
def softmax(x):
# 减去最大值防止数值溢出
x_max = np.max(x, axis=-1, keepdims=True)
x_stable = x - x_max
exp_x = np.exp(x_stable)
return exp_x / np.sum(exp_x, axis=-1, keepdims=True)
这块还经常延伸问:训练时的 mask 和推理时的 mask 有什么区别?训练时用 causal mask(下三角全 0 上三角全 -inf)实现自回归,推理时同样需要 mask 但用的是 KV Cache,只有第一次需要算完整序列,后面的 token 过来只需要算当前步和之前 KV 的 attention。
3. MHA、MQA、GQA 的区别与推理效率分析
这是网易一面大模型方向的压轴题,特别是在聊推理优化时必问。
先说 MHA(Multi-Head Attention),标准 Transformer 用的,每个头有独立的 Q、K、V 矩阵。比如 8 个头,每个头维度 d_k=64,那一个 token 产生 8 组 K 和 V。推理时每生成一个 token,这 8 组 K、V 要全部存下来,KV Cache 占用的显存就是 batch_size × seq_len × num_heads × d_k × 2(K 和 V 各一份)——这个量是很大的。
MQA(Multi-Query Attention)做了极端简化:所有 head 共用一组 K 和 V,只有 Q 是每个头独立的。好处是 KV Cache 减少到 1/num_heads,推理显存大头降下来了。但缺点是表达能力可能不够,因为所有头看到的上下文特征是一样的。
GQA(Grouped-Query Attention)是折中方案,也是 LLaMA 2/3 现在用的。比如 32 个 Q head 对应 8 组 KV(每组 KV 供 4 个 Q head 共用)。GQA 在推理加速和模型质量之间取得了很好的平衡,代码里通常这样实现:
# GQA 的关键:KV 矩阵复用
# 假设有 32 个 Q head, 8 个 KV head
# groups = 4,每 4 个 Q head 共享 1 组 KV
def grouped_query_attention(Q, K, V, num_groups=4):
batch_size, num_heads, seq_len, d_k = Q.shape
# K 和 V 只有 num_heads/num_groups 个头
# 通过 repeat_interleave 扩展到与 Q 相同数量
K = K.repeat_interleave(num_groups, dim=1)
V = V.repeat_interleave(num_groups, dim=1)
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(d_k)
attn = torch.softmax(scores, dim=-1)
return torch.matmul(attn, V)
从推理效率看:MQA 省显存最多但效果可能差点,MHA 效果最好但最耗资源,GQA 是工业界目前的最优选择。
4. 训练/微调一个 LLM 的完整流程
网易面试官特别看重你对整个训练链路的理解,不只是会用框架。
先说训练大流程。大模型训练分三步:预训练 → SFT(监督微调) → RLHF(人类反馈强化学习)。
预训练阶段用的数据量大,通常是万亿 token 级别的互联网语料,目标是让模型学会"语言"——语法、常识、推理基础。这个阶段用自回归 loss,就是 next token prediction。代码层面就是一个 CrossEntropy Loss:
# 预训练 loss:只预测被 mask 的位置
logits = model(input_ids) # [batch, seq_len, vocab_size]
shift_logits = logits[..., :-1, :].contiguous()
shift_labels = input_ids[..., 1:].contiguous()
loss = F.cross_entropy(shift_logits.view(-1, shift_logits.size(-1)),
shift_labels.view(-1))
SFT 阶段用高质量的人工标注数据,教模型按照指令回答问题。这里有个高频考点:SFT 的 loss 应该怎么算?答案是:只对回答部分计算 loss,不对 prompt(用户问题)部分计算。因为如果对 prompt 也计算 loss,模型会去"记忆"用户问话的方式,而不是学怎么回答。实际实现时传入一个 labels 张量,把 prompt 对应位置设成 -100,F.cross_entropy 的 ignore_index 参数会跳过这些位置。
RLHF 用 PPO 或 DPO 进一步对齐人类偏好。PPO 需要同时维护策略模型、参考模型和奖励模型,训练复杂。DPO 是 PPO 的简化替代:直接用偏好数据对(chosen vs rejected)做训练,不需要奖励模型。但 DPO 需要小心输出长度问题——DPO 优化后模型输出经常会变长,因为长回答在偏好数据中更受青睐,实际部署时要加长度惩罚。
5. LoRA 原理与调参经验
网易面试喜欢具体到参数层面问 LoRA,不止是讲原理。
LoRA(Low-Rank Adaptation)的核心思想是:大模型预训练权重 W 是满秩的,但微调时的更新量 ΔW 是低秩的。所以把 ΔW 分解成两个小矩阵 A 和 B:W' = W + BA,其中 A 是 (d, r),B 是 (r, k),r << min(d, k)。训练时 W 冻结,只更新 A 和 B。
为什么低秩分解能逼近全参数微调?因为大模型学到的特征空间很大,但在某个具体任务上的"变化方向"其实是受限的。好比一个人会很多技能,但学"写诗"这个新技能时只需要调整一小部分能力。r 就是控制了这块调整空间的大小。
参数调优经验:r 和 α 的关系是 scale = α / r。α 一般设成 r 或 2r。实战中我通常从 r=8 开始,α=16。如果微调数据量大(比如 10 万条以上),r 可以大一点到 16-32,因为数据够多,需要更大的表征空间。数据量小(几千条),r 设成 4-8 就行,太大容易过拟合。
推理时 LoRA 可以合并到原始权重里,不影响推理速度:
# 合并 LoRA 权重到原始模型
def merge_lora(base_weight, lora_A, lora_B, alpha, rank):
# scale = alpha / rank
delta = (alpha / rank) * (lora_B @ lora_A)
return base_weight + delta
如果需要反复切换任务,也可以不合并,在 forward 里直接加 BA·x,每次加载不同的 LoRA 权重就行。这就是 LoRA 的"插拔式"优势。
6. 分布式训练:DeepSpeed ZeRO 的三个阶段
网易二面会问"7B 模型训练需要多少显存?ZeRO 怎么省下来?"
先算一笔账:7B 模型参数用 FP16 存储,参数本身占用 7B × 2 bytes = 14GB。训练时还需要存储 optimizer 状态(Adam 需要维护 momentum 和 variance,FP32 的话每个状态 4 bytes,两个状态 8 bytes),梯度也是 FP16 的 14GB。所以光这些训练状态就是:
-
参数:14GB (FP16)
-
梯度:14GB (FP16)
-
Adam 状态:28GB (FP32 momentum + variance)
总计约 56GB 的显存——这还不算 activation。所以一个 7B 模型单卡训练就需要 70-80GB,A100(80GB)勉强可以。
DeepSpeed ZeRO 的核心是数据并行 + 切分状态。分三个阶段:
ZeRO-1:只切分 optimizer state。每张卡只存 1/N 的 optimizer 状态,参数和梯度仍然每张卡存全量。可以省 4 倍 optimizer 显存。
ZeRO-2:切分 optimizer state + 梯度。每张卡只存 1/N 的梯度和 optimizer 状态。反向传播完成后,通过 allreduce 通信把所有梯度汇总到对应卡上。比 ZeRO-1 显存进一步节省。
ZeRO-3:全部切分——参数、梯度、optimizer 统统分到各卡。每张卡只持有 1/N 的参数,前向传播时需要从其他卡上拉取参数。这是最省显存的方式,但通信开销也最大。
实际使用 Deepspeed 时几句话就能配好:
# deepspeed_config.json 示例
{
"train_batch_size": 64,
"zero_optimization": {
"stage": 2, # 一般7B用 stage2 就够
"offload_optimizer": {
"device": "cpu" # 可选项:优化器状态卸载到CPU
}
},
"fp16": {
"enabled": true
}
}
面试时还会问Megatron-LM 和 DeepSpeed 的区别。简单说:Megatron 侧重模型并行和张量并行,适合超大规模(100B+)的稠密模型;DeepSpeed 侧重 ZeRO 序列化,适合中等规模模型的高效数据并行。很多大厂两者混合用:张量并行用 Megatron,数据并行用 DeepSpeed ZeRO。
7. PagedAttention 与 vLLM 推理优化原理
vLLM 在网易面试中多次被问到,特别是问推理框架选型时。
PagedAttention 解决的问题非常实际:传统推理框架中,KV Cache 需要预先分配一块连续显存。但实际每个请求的序列长度不一样,预分配要么浪费(假设最大长度),要么不够。就像操作系统里的内存碎片问题。
PagedAttention 的思路就是"虚拟内存"——把 KV Cache 切成固定大小的 block(比如每个 block 存 16 个 token 的 KV)。通过 block table 维护逻辑块到物理块的映射,按需分配。生成新 token 时只增加一个 block,满了再分配新的。
# PagedAttention 的伪代码逻辑
class PagedAttention:
def __init__(self, block_size=16):
self.block_size = block_size
self.block_table = {} # 逻辑块 -> 物理块映射
def allocate_kv_cache(self, logical_block_id):
physical_block_id = find_free_block() # 从空闲池中分配
self.block_table[logical_block_id] = physical_block_id
return physical_block_id
def attention_with_paged_cache(self, query, block_table):
# 根据 block_table 把不连续的物理块拼装
kv_cache = gather_kv_blocks(block_table)
return scaled_dot_product_attention(query, kv_cache)
vLLM 的优势:近零内存浪费,支持共享 prefix(比如系统 prompt 相同的情况下多个请求共享同一段 KV Cache),而且可以直接兼容 OpenAI 的 API 格式,调用方几乎不用改代码。
8. RAG 系统:检索召回与生成质量优化
网易的大模型业务落地特别关注 RAG,面试官会连环追问各种场景问题。
先讲完整链路:用户 query → embedding 向量化 → 向量数据库检索 → 召回 Top-K → Rerank 重排 → 将相关文档拼入 prompt → LLM 生成。核心就是"检索 + 生成"两段。
面试官经常会问一些场景排查题。比如:"用户的问题在知识库里确实有,但系统经常没有召回正确文档,怎么排查?" 这类问题很考验实战经验,按这个思路答:
第一步,检查 embedding 质量。Query 和文档的语义是否真的相似?可以试几个 query,看召回文档的 cosine similarity 是否合理。如果明明"苹果手机怎么设置铃声"召回成了"苹果的营养价值",那就是 embedding 模型不太行,需要换更好的。
第二步,检查 chunk 策略。Chunk size 是 256 还是 512?overlap 设了多少?如果 chunk 太大,信息混在一起,匹配精度会下降;如果 chunk 太小,信息不完整。经验值:chunk_size=256-512,overlap=20-50 做安全边界。
第三步,如果召回结果语义相似但事实不相关(比如用户问"杭州亚运会开幕式时间",召回的是"杭州亚运会筹备工作"),可以考虑引入 Hybrid Search(向量检索 + BM25 关键词检索),或者加一层 Rerank 交叉编码器排序。
第四步,如果检索成功但生成错误,那是幻觉问题。可以检查 prompt 设计是否明确限制了"只能根据给定材料回答",以及尝试对生成结果做事实一致性校验。
9. MoE(混合专家模型)原理与实现
面试官讲到大模型参数量优化时,一定会问到 MoE。
MoE 的核心是"分而治之"。传统模型是所有参数一个不落地计算,MoE 则是通过门控网络(Router)把不同的 token 路由到不同的"专家"子网络。比如一个 8 专家的 MoE 层,每个 token 只激活 top-2 专家。
具体实现步骤:假设输入 x,先通过一个线性层算出 logits(维度为 num_experts),取 top-k 做 softmax 得到加权权重。然后把 x 分别送到选中的专家里计算,结果按权重求和输出。
class MoELayer(nn.Module):
def __init__(self, d_model, num_experts=8, top_k=2):
super().__init__()
self.gate = nn.Linear(d_model, num_experts) # 路由网络
self.experts = nn.ModuleList([
FeedForward(d_model) for _ in range(num_experts)
])
self.top_k = top_k
def forward(self, x):
gate_scores = self.gate(x) # [batch, seq, num_experts]
top_k_weights, top_k_indices = torch.topk(
gate_scores, self.top_k, dim=-1
)
top_k_weights = F.softmax(top_k_weights, dim=-1) # 归一化
outputs = torch.zeros_like(x)
for i, expert in enumerate(self.experts):
# mask 出被分配到 expert i 的 token
mask = (top_k_indices == i).any(dim=-1)
if mask.any():
# 获取对应的权重
weights = top_k_weights[top_k_indices == i]
outputs[mask] += weights @ expert(x[mask])
return outputs
MoE 的好处是:总参数量大(比如 8 专家 × 单专家参数),但计算量小(只算 top-2)。所以可以用 17B 的激活参数达到 65B 稠密模型的效果。但 MoE 也有坑:负载不均衡,有的专家接收到的 token 多,有的少。一般需要辅助 loss 惩罚负载不均衡。
面试追问:"训练时的 MoE 和推理时的 MoE 有什么不同?" 训练时每个 token 算 top-k 专家,推理时为了稳定通常只取 top-1 或加噪声。而且 MoE 模型用 DeepSpeed ZeRO-3 或者 Expert Parallelism(专家并行)来训练,不同的专家放在不同的 GPU 上。
10. LLM 的幻觉问题与 RLHF/DPO 对齐
幻觉问题是大模型面试中的万能题,几乎每次都会被问到。
先把原因说清楚:大模型本质是一个"语言模型",它学到的知识来自训练数据的统计分布。当你问一个它"没见过"的问题,它会用最概率的方式"编造"答案,而不是诚实地告诉你不知道。这不叫撒谎,叫"自信的胡诌"。主要原因有:训练数据本身有噪音、模型为了迎合用户偏好(长的、肯定的回答更容易被人工标注为"好")、以及解码策略(比如 temperature 太高容易发散)。
缓解幻觉有几层策略:
-
数据层面:预训练数据清洗,SFT 数据中加入"我不知道"的样本
-
推理层面:T=0.1 的低温采样、约束解码
-
检索增强 RAG:给模型外部知识来源,让它引用来源
-
模型对齐:通过 RLHF 惩罚幻觉
RLHF 的具体流程分为四步:SFT 得到初始模型 → 训练奖励模型(RM,给人类偏好打分) → PPO 微调 → 评估对齐效果。PPO 的核心是让模型生成的回答在 RM 上得分高,同时 KL 散度惩罚不让模型跑偏太远。
DPO 是更简洁的替代方案,不需要单独训练 RM:
# DPO loss 核心逻辑
def dpo_loss(chosen_logps, rejected_logps, beta=0.1):
"""
chosen_logps: 偏好回答的对数概率
rejected_logps: 非偏好回答的对数概率
beta: KL 散度系数
"""
log_ratio = chosen_logps - rejected_logps
loss = -F.logsigmoid(beta * log_ratio).mean()
return loss
DPO 的局限是如果偏好数据质量差,模型会学到不好的偏好。而且 DPO 训练后输出倾向变长,部署时需要考虑长度归一化,或者加一个长度惩罚项。
写在最后
网易大模型算法岗的面试风格可以用一句话总结:基础扎实第一,逻辑清晰第二,项目深度第三。Transformer 的每一个细节、分布式训练的显存计算、LoRA 的参数敏感度——这些都是确认你是否真正"懂"的技术点。好好把上面 10 道题吃透,面试过线没问题。
祝各位同学拿下 offer!
更多推荐



所有评论(0)