联想 大模型开发 一面

1. 自我介绍

2. 训练一个 decoder-only 大模型时,为什么 embedding 层和 lm head 常常共享权重

权重共享本质上是一种参数高效和统计一致性的设计。输入端 embedding 学到的是“token 到向量空间”的映射,输出端 lm head 学到的是“隐藏状态到词表概率”的映射,如果这两个空间本身就在描述同一个词表语义,那么共享权重可以减少冗余参数,并让输入语义空间与输出判别空间保持一致。

从优化角度看,共享权重还能起到轻微正则化作用,尤其在词表非常大时更明显。代价是模型表达自由度下降了一点,但对大多数语言模型来说这个损失远小于收益。很多实现里会保留一个独立 bias,以补偿分布偏移。

import torch
import torch.nn as nn

class TinyLM(nn.Module):
    def __init__(self, vocab_size=1000, hidden=128):
        super().__init__()
        self.embed = nn.Embedding(vocab_size, hidden)
        self.lm_head = nn.Linear(hidden, vocab_size, bias=False)
        self.lm_head.weight = self.embed.weight

    def forward(self, input_ids):
        x = self.embed(input_ids)
        logits = self.lm_head(x)
        return logits

3. Attention 中为什么要做 masked softmax,而不是先 softmax 再把未来位置置零

如果先 softmax 再置零,概率质量已经被未来 token 分走了一部分,之后再把它们清掉,相当于没有重新归一化,这会导致有效位置上的注意力权重整体偏小,概率解释也不成立。正确做法是在 softmax 之前就把非法位置加上一个极大负数,让这些位置在 softmax 后概率接近 0,同时剩余合法位置自动完成归一化。

这个细节在训练稳定性上很重要。尤其是混合精度下,如果 mask 逻辑写错,不一定会直接报错,但梯度和注意力分布会被悄悄污染,最后表现成模型训练慢、loss 不对劲或者长序列表现异常。

import torch

scores = torch.tensor([[1.2, 0.3, 2.1]])
mask = torch.tensor([[1, 1, 0]])  # 0 表示非法位置
scores = scores.masked_fill(mask == 0, float('-inf'))
attn = torch.softmax(scores, dim=-1)
print(attn)

4. GQA、MQA 和标准 MHA 的差异是什么,它们对推理系统意味着什么

标准 MHA 里每个 query head 都有独立的 key 和 value head,表达能力最强,但 KV cache 也最贵。MQA 把所有 query head 共享同一组 K/V,极大减少了 KV cache 占用和带宽压力,推理吞吐提升明显,但表达能力会有所损失。GQA 则是折中方案,多个 query head 共享一组 K/V,既比 MHA 省很多 cache,又比 MQA 更保留头间差异。

这件事在推理阶段非常关键。大模型在线服务时,瓶颈经常不在算力而在显存带宽和 cache 容量。GQA/MQA 的价值并不是“理论更先进”,而是它们直接影响 batch size、首 token 时延、长上下文吞吐和成本模型。

5. FlashAttention 为什么能加速,它解决的不是单纯算子快,而是什么问题

FlashAttention 的核心收益不是把 attention 公式换了,而是把内存访问模式改了。标准 attention 会显式构造巨大的 attention matrix,读写 HBM 的代价非常高;FlashAttention 通过 tiled 方式分块计算,把更多中间结果留在 SRAM 或寄存器里,避免大规模 materialize,从而让 IO 开销显著下降。

这也是为什么它在长序列上收益特别大。很多人以为 attention 慢是因为 FLOPs 太多,其实在现代 GPU 上常常是 memory-bound。FlashAttention 真正优化的是 IO complexity,而不是只优化理论计算量。

6. 长上下文训练时,为什么 RoPE 外推会失效,常见补救思路是什么

RoPE 在原始训练长度附近表现很好,但外推到远超训练窗口时,角频率分布会让远距离位置关系变得越来越扭曲,模型看到的是“从未在训练中出现过的相位组合”,注意力模式会失真。表面现象通常是前后文都还行,但中间证据利用率下降,长链依赖突然崩掉。

补救思路通常不是简单暴力拉长训练长度,因为成本太高。更常见的是位置缩放、插值、NTK-aware 调整、YaRN 这类改法,或者干脆在训练中混入更长序列和专项 curriculum。真正有效的方案往往还要配合数据分布设计,不然模型只是“能跑更长”,未必“会用更长”。

7. 为什么训练时 packed sequence 能提升吞吐,但实现不好会破坏监督信号

packed sequence 把多个短样本拼到一个长序列里,减少 padding 浪费,所以 token utilization 更高,吞吐也更好。但如果拼接时 attention mask 和 labels 没处理好,前一个样本的 token 可能会错误地看见后一个样本,或者把跨样本边界也算进 loss,等于监督信号被污染了。

所以 packed sequence 真正难的不是拼,而是“边界隔离”。必须保证注意力和 loss 都严格局限在各自样本内部。很多训练实现看起来显存利用率很漂亮,实际上因为 mask 写错,把样本之间串起来了,后面很难从指标里第一时间发现。

8. ZeRO-1、ZeRO-2、ZeRO-3 和 FSDP 的本质区别是什么

ZeRO 的核心思想是把原来在每张卡上重复保存的训练状态切分掉。ZeRO-1 切 optimizer states,ZeRO-2 再切 gradients,ZeRO-3 连 parameters 也切,所以节省显存越来越激进。FSDP 和 ZeRO-3 很像,都是对参数做分片,只是在实现机制、调度方式和框架集成层面有所不同。

真正要答深一点,不能只说“谁更省显存”。关键在于不同阶段的 all-gather、reduce-scatter 出现在前向还是反向、是否支持更细粒度 wrapping、和 activation checkpointing 怎么配合,以及在不同网络拓扑下通信代价是否可接受。很多时候选择哪一个,不是看论文名字,而是看模型结构、集群带宽和工程栈。

9. 为什么 BF16 在大模型训练里通常比 FP16 更稳,但并不意味着所有算子都能放心用 BF16

BF16 的优势来自更大的指数范围,因此更不容易溢出和下溢,这对大模型训练非常重要。很多梯度、激活和 logits 分布跨度很大,FP16 很容易炸,BF16 则更能兜住。但这不等于所有运算都可以无脑低精度,像归约、softmax、norm、optimizer update 这类对数值误差更敏感的地方,很多框架依旧会在内部转成 FP32 做累加或更新。

所以真正的混合精度策略从来不是“统一降精度”,而是根据数值敏感性选择精度路径。一个训练系统稳不稳,很大程度上取决于你哪些地方坚持高精度,哪些地方敢降精度。

10. 推理阶段 prefill 和 decode 的瓶颈为什么完全不同

prefill 阶段要一次性处理整段 prompt,矩阵规模大,算力利用率高,瓶颈通常更偏计算。decode 阶段每次只生成一个 token,虽然单步 FLOPs 不算夸张,但要频繁读取历史 KV cache,且 batch 动态变化,瓶颈往往转向显存带宽和调度开销。两者的系统优化策略几乎不是一回事。

这也是为什么有些优化只提升首 token 时延,有些优化只提升长回答吞吐。比如算子融合、prefill

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

AI-Agent面试实战专栏 文章被收录于专栏

本专栏聚焦 AI-Agent 面试高频考点,内容来自真实面试与项目实践。系统覆盖大模型基础、Prompt工程、RAG、Agent架构、工具调用、多Agent协作、记忆机制、评测、安全与部署优化等核心模块。以“原理+场景+实战”为主线,提供高频题解析、标准答题思路与工程落地方法,帮助你高效查漏补缺.

全部评论
老师面试时间是35min吗
点赞 回复 分享
发布于 昨天 21:47 广东

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务