ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

校招大语言模型面试题:注意力、KV cache与部署工程

校招大语言模型面试题:注意力、KV cache与部署工程 简介面向校招求职者的大语言模型面试题整理聚焦 LLM 基础概念、强化学习、微调与思维链等高频考点。内容以问答形式展开涵盖大模型参数规模与 175B 含义、优缺点、预训练与微调流程、RLHF、指令微调、泛化能力以及 Chain-of-Thought 的 Few-shot、Zero-shot、Least-to-Most 等提示策略和逐步 Zero-shot 两阶段推理适合准备算法岗、NLP 方向面试的学生系统梳理知识盲区。压缩包共 1 个 doc 文档约 81KB篇幅紧凑便于打印或导入笔记软件反复查阅。目前已有 1010 人学习下载说明在校招场景中具备一定参考热度。读者可借此建立从概念到追问的答题框架用简洁表述回应面试官对模型原理、训练范式和推理方法的提问也能据此延伸复习强化学习与微调细节提升面试通过率与临场表达信心。1. 校招大语言模型岗位的面试题考的不是概念覆盖率面试官先问「Transformer 里注意力怎么算」候选人背得很顺接着换一句「7B 模型开 32K 上下文、batch 取 16KV cache 要占多少显存」回答就变成「大概几十个 G 吧」。校招大语言模型面试题的差距几乎都出现在这一步名词都能说数量级说不出来。面试真正筛的是三件事把公式换算成显存和延迟、把需求拆成检索增强还是微调、把一次线上故障讲成可复现的排查路径。背题只能覆盖第一问后两问要靠自己动手跑过一遍才答得出来。下面面向投算法、应用和大模型工程岗的应届生也面向工作几年想转方向的人。底层原理、本地部署大语言模型的实测参数、大语言模型 API 的工程细节都会拆开讲每节给出能直接跑的代码和参数照着过一遍答案就能落到具体数字上。2. 大语言模型面试题里最容易追问到底的底层原理面试官问到注意力机制时很少停在「缩放点积」这个名词上。他们会顺着往下追一层缩放因子为什么是根号 d_k 而不是 d_k位置编码为什么从正弦换成旋转同一个中文句子为什么在不同模型上计费差一倍这三问几乎覆盖了校招大语言模型原理题的追问链回答时给公式不如给推导给推导不如给一段可以当场解释的代码。2.1 自注意力为什么除以根号 d_k把方差算给面试官看标准写法是 Attention(Q,K,V) softmax(QKᵀ/√d_k)V。多数人能背出这一行但面试官想听的是这个分母从哪来。关键在于Q 和 K 的每个分量如果近似独立、均值 0、方差 1那么点积 q·k 是 d_k 个乘积项之和它的方差会随 d_k 线性增长。d_k 取 128 时点积的标准差大约是 √128 ≈ 11.3直接送进 softmax 会让输入分布过宽梯度被压到接近 0训练几乎不动。除以 √d_k 把方差拉回 1这是第一层答案。第二层是「为什么不是除以 d_k」方差随 d_k 线性增长开方之后才是标准差的缩放比例缩放的目的是对齐量纲而不是压到某个固定值。第三层是 GQA/MQA 场景Q 的头数和 KV 的头数不同缩放仍按单个 head_dim 计算与头数无关。这三层答完面试官基本不会再追问。import numpy as np def scaled_dot_product_attention(Q, K, V, maskNone): Q/K/V 形状均为 (batch, heads, seq, d_k) d_k Q.shape[-1] # 只转置最后两维batch 和 heads 维保持不动 scores Q K.transpose(0, 1, 3, 2) / np.sqrt(d_k) if mask is not None: # mask 中 True 表示该位置不可见用极小值压掉 scores np.where(mask, -1e9, scores) # 减去当前最大值为防止 exp 溢出不改变 softmax 结果 scores scores - scores.max(axis-1, keepdimsTrue) weights np.exp(scores) / np.exp(scores).sum(axis-1, keepdimsTrue) return weights V, weights上面这段代码里Q K.transpose(0, 1, 3, 2)做的是「查询-键」相似度矩阵/ np.sqrt(d_k)就是缩放点积的缩放因子mask分支处理因果掩码和 padding 掩码最后一步先减最大值是工程上的数值保护。想验证方差那一步可以跑下面这几行rng np.random.default_rng(0) d_k 128 q rng.normal(size(10000, d_k)) k rng.normal(size(10000, d_k)) raw np.sum(q * k, axis-1) # 未缩放的点积 scaled raw / np.sqrt(d_k) # 缩放后的点积 print(round(raw.std(), 2), round(scaled.std(), 2)) # 约 11.31 与 1.0把d_k改成 512 再跑一次raw.std()会涨到 22 左右scaled.std()仍然是 1.0。这个对照实验在面试现场口述出来比背公式有说服力得多。2.2 位置编码从正弦到 RoPE面试官想听的取舍位置编码这题的分水岭在于能不能说清「可外推性」这个约束。面试官常问训练长度 4K推理时给它 32K会发生什么答案取决于用的是哪套编码。方案长度外推额外参数关键实现要点正弦绝对编码差无固定公式新模型基本不用可学习绝对编码差少量超出训练长度直接失效RoPE较好可配合频率缩放无旋转 Q/K内积只与相对距离有关ALiBi好无在注意力分数上叠加线性偏置RoPE 之所以成为主流是因为它把位置信息编码成对 Q、K 的旋转操作两个位置做内积时只留下相对距离项天然具备一定的外推余量再配合 NTK-aware 或 YaRN 这类频率缩放32K、128K 的上下文才有可能跑起来。ALiBi 则是另一条路不动 Q/K直接在注意力分数上加一个和距离成正比的惩罚项实现更简单但表达能力受限。import numpy as np def rope(x, position, base10000.0): x: (seq, dim)对偶数/奇数维成对旋转 dim x.shape[-1] # dim/2 个频率最低频对应 base 决定的波长上限 inv_freq 1.0 / (base ** (np.arange(0, dim, 2) / dim)) theta position[:, None] * inv_freq[None, :] # (seq, dim/2) cos, sin np.cos(theta), np.sin(theta) x1, x2 x[:, 0::2], x[:, 1::2] out np.empty_like(x) out[:, 0::2] x1 * cos - x2 * sin out[:, 1::2] x1 * sin x2 * cos return out pos np.arange(8) print(rope(np.ones((8, 16)), pos)[0, :4])参数说明base控制最低频的波长调大 base 相当于把旋转角度整体放缓是长度外推时最常用的旋钮position从 0 开始单调递增不一定要与 token 下标一一对应多模态场景里可以按图像块或时间片重新编号。面试里被追问「长上下文为什么难」答「注意力是平方复杂度、位置外推会退化、KV cache 线性膨胀」三句话比只答第一条更完整。2.3 分词器与词表同一句话为什么换个 tokenizer 就贵了分词器是校招大语言模型题里最容易被忽略、又最容易露怯的一环。面试官问「中文一句话大概多少 token」其实是在看你有没有真正算过成本。BPE 的核心循环只有两步统计相邻符号对的频次合并最高频的一对重复到词表满为止。from collections import Counter def _merge(word, a, b): out, i [], 0 while i len(word): if i len(word) - 1 and word[i] a and word[i 1] b: out.append(a b); i 2 else: out.append(word[i]); i 1 return out def bpe_merge(corpus, num_merges4): # 每个词先拆成字符末尾 /w 标记词边界 vocab [list(w) [/w] for w in corpus] for i in range(num_merges): pairs Counter() for w in vocab: for a, b in zip(w, w[1:]): pairs[(a, b)] 1 if not pairs: break (a, b), freq pairs.most_common(1)[0] vocab [_merge(w, a, b) for w in vocab] print(fmerge {i1}: {a}{b} - {ab} (freq{freq})) return vocab print(bpe_merge([low, lower, lowest], num_merges4))这段代码里的/w是词边界符保证「low」和「lower」不会因为共享前缀而被错误切分num_merges对应词表规模实际训练时是几万到十几万次合并。中文的麻烦在于单字信息密度高但常用词组合多同一个词表下中英混排的 token 数可能相差两三倍直接决定了按 token 计费时的账单和上下文占用。# 粗估按字符数和一个经验系数算上下文占用用于容量规划 def rough_tokens(text, chars_per_token1.5): return int(len(text) / chars_per_token) doc 把这段话换成你自己的业务文本用来估算一次请求会消耗多少 token。 * 20 print(rough_tokens(doc))chars_per_token是经验值英文文本约 4中文常落在 1.2 到 1.8 之间自己拿真实语料和分词器跑一遍就能校准。面试里如果被问到「上下文怎么切分」回答「先按语义段落切再按 token 上限兜底重叠窗口取 10% 到 15%」比说「按固定长度切」更像做过事。3. 本地部署大语言模型相关面试题显存、量化与吞吐怎么答本地部署大语言模型是这两年新增的高频考点也是最能区分「跑过」和「只看过」的部分。面试官不会让你现场装环境但会让你估算一张 24G 卡能跑多大的模型、开多长的上下文、扛多少并发。这三问共用同一组公式答对一次就能连着答对三题。3.1 用一张显存清单把推理成本算清楚推理显存大致由四块组成模型权重、KV cache、激活与临时缓冲、框架预留和碎片。前两块能精确算后两块按经验留 10% 到 20%。权重部分很简单参数量乘以每参数字节数。模型规模FP16 权重INT8 权重INT4 权重单卡 24G 是否可行7B14 GB7 GB3.5 GB均可INT4 可留大量 KV 空间13B26 GB13 GB6.5 GBFP16 需双卡INT4 单卡可行34B68 GB34 GB17 GBINT4 单卡需压上下文70B140 GB70 GB35 GB需多卡或更激进的量化KV cache 才是真正的变量。公式是 2 × 层数 × KV 头数 × head_dim × 序列长度 × batch × 每元素字节数其中 2 代表 K 和 V 各存一份。def kv_cache_bytes(layers, kv_heads, head_dim, seq_len, batch, dtype_bytes2): return 2 * layers * kv_heads * head_dim * seq_len * batch * dtype_bytes def gb(n): return round(n / 1024 ** 3, 2) # 32 层、8 个 KV 头GQA、head_dim128、FP16 print(gb(kv_cache_bytes(32, 8, 128, 8192, 16))) # 约 16.0 GiB print(gb(kv_cache_bytes(32, 8, 128, 32768, 16))) # 上下文翻 4 倍KV 同样翻 4 倍 print(gb(kv_cache_bytes(32, 32, 128, 8192, 16))) # 换成 MHA直接变成 4 倍这三行输出放在一起就是一个完整的答题GQA 把 KV 头数从 32 降到 8KV cache 立刻省下四分之三序列长度和 batch 都是线性项上下文从 8K 提到 32KKV 就吃掉整张卡。面试时先报公式再报这三个数字最后补一句「所以长上下文服务的瓶颈通常在 KV 而不是权重」逻辑链就闭环了。3.2 量化方案对比GPTQ、AWQ、GGUF 的面试答法量化题的常见问法是「INT4 为什么几乎不掉点」和「什么场景下不该量化」。前者要答到分组量化和离群值处理后者要答到长尾任务和精度敏感场景。方案量化时机粒度硬件倾向典型场景GPTQ训练后需要校准集分组权重GPU 推理显存受限的服务端部署AWQ训练后按激活分布保护重要通道分组权重GPU 推理与 GPTQ 互有胜负常对比选优GGUF训练后多种量化档位块级CPU 少量 GPU单机、消费级显卡、离线场景FP8训练或推理前张量/通道新架构 GPU精度要求高又要省显存分组量化之所以有效是因为权重的离群值高度集中在少数通道按 128 个一组独立求缩放因子能把离群值的影响限制在组内。AWQ 的额外动作是在量化前根据激活幅度挑出「重要通道」并保留更高精度这也是它在部分任务上比 GPTQ 表现更好的原因。def weight_gb(params_billion, bits): 按参数量和位宽估算纯权重显存 return round(params_billion * 1e9 * bits / 8 / 1024 ** 3, 2) for bits in (16, 8, 4): print(f{bits} bit - {weight_gb(13, bits)} GiB)上面这段用于回答「13B 模型用 INT4 要多少显存」输出约 6.05 GiB。注意这只是权重实际还要加上 KV cache 和框架开销回答时主动补上这句面试官会认为你踩过坑。不建议量化的场景也要说清楚数学推理、代码生成、长链工具调用这类对细粒度精度敏感的任务INT4 可能带来肉眼可见的退化宁可上 INT8 或换更小的模型。3.3 本地部署大语言模型的最小验证命令与并发压测面试里如果被问「你怎么验证一个部署是好的」不要答「发一句话试试」。正确的顺序是先确认权重能加载、再确认单条请求链路通、最后压并发看排队。# 常见做法第一次启动把上下文和并发压到保守值先确认能加载 vllm serve /path/to/your-7b-instruct \ --dtype bfloat16 \ --max-model-len 8192 \ --gpu-memory-utilization 0.90 \ --max-num-seqs 16 \ --port 8000 # 单条请求确认链路 curl -s http://127.0.0.1:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: /path/to/your-7b-instruct, messages: [{role: user, content: 用一句话解释 KV cache}], max_tokens: 64, temperature: 0 }参数说明--dtype决定权重精度bfloat16 是常见默认--max-model-len直接决定 KV cache 的上限启动日志里会打印可容纳的 token 数--gpu-memory-utilization 0.90是预分配比例调太高会和同卡的其他任务冲突--max-num-seqs控制同时在跑的序列数出现 OOM 时优先降它而不是降上下文。temperature设成 0 是为了让同一问题在压测中得到可比结果。import concurrent.futures, json, time, urllib.request URL http://127.0.0.1:8000/v1/chat/completions BODY json.dumps({ model: /path/to/your-7b-instruct, messages: [{role: user, content: 写一句关于显存的结论}], max_tokens: 128, }).encode() def one(_): req urllib.request.Request(URL, dataBODY, headers{Content-Type: application/json}) t0 time.time() with urllib.request.urlopen(req, timeout60) as r: r.read() return time.time() - t0 with concurrent.futures.ThreadPoolExecutor(max_workers16) as ex: lat sorted(ex.map(one, range(64))) print(P50, round(lat[len(lat) // 2], 3), P99, round(lat[int(len(lat) * 0.99)], 3))max_workers16对应服务的--max-num-seqs先跑 16 并发再压到 64观察 P99 是否出现台阶式上涨——那就是排队开始的信号。把并发持续加到 P99 突然翻倍的点就是这张卡的可用并发上限这个数字在面试里比任何形容词都管用。4. 大语言模型 API 与工程化面试题从免费额度到线上限流应用岗的面试题和大模型工程岗完全不同不问你注意力怎么算而是问「接口超时了怎么办」「成本怎么控」「线上回答突然变差你怎么定位」。这些问题没有标准答案但有标准结构先说边界条件再说降级策略最后说怎么验证。4.1 大语言模型 API 调用的重试、超时与幂等怎么写面试官常从一个很具体的问题切入你调用的大语言模型 API 返回 429 了怎么办。只答「重试」会被追着问「重试几次」「间隔多少」「重复计费怎么办」。正确做法是把连接超时和读取超时拆开把可重试的状态码列清楚用幂等键保证业务侧不重复扣费。import json, os, random, time, urllib.error, urllib.request BASE_URL os.environ[LLM_BASE_URL] # 例如 https://your-endpoint/v1 def call_llm(payload, api_key, idem_key, max_retries5): body json.dumps(payload).encode() for attempt in range(max_retries): req urllib.request.Request( f{BASE_URL}/chat/completions, databody, headers{ Content-Type: application/json, Authorization: fBearer {api_key}, Idempotency-Key: idem_key, # 同一个业务请求复用同一个 key }, ) try: # 连接超时 3 秒读取超时 30 秒两者分开设置 with urllib.request.urlopen(req, timeout30) as r: return json.loads(r.read()) except urllib.error.HTTPError as e: # 4xx 中只有 408/409/429 值得重试其余是请求本身有问题 if e.code 500 and e.code not in (408, 409, 429): raise retry_after float(e.headers.get(Retry-After, 0) or 0) except (urllib.error.URLError, TimeoutError): retry_after 0 # 指数退避加抖动避免同一时刻的请求集中重试 sleep retry_after or min(0.2 * (2 ** attempt), 8) time.sleep(sleep random.uniform(0, sleep * 0.3)) raise RuntimeError(retries exhausted)这段代码有三个可以展开讲的点。Idempotency-Key由业务侧生成比如订单号加步骤号服务端据此判断是否为重复请求能避免「超时但实际成功」带来的重复计费。退避公式里的抖动是必须的大量客户端在同一秒集中重试会把限流打得更死。Retry-After优先级高于自己算的退避服务端既然给了建议值就照做。面试里还会顺带问成本。这时候可以提一句很多人纠结哪个大语言模型 API 还有免费使用额度但校招项目的正确做法是按 token 单价乘上「平均输入长度 × 请求数」算月度上限再倒推能承受的 QPS免费额度只够做功能验证撑不起压测。4.2 视觉大语言模型的输入组织与评测陷阱视觉大语言模型这两年被问得越来越多尤其是投多模态方向的岗位。核心问题只有一个一张图变成多少个 token。输入组织方式token 数估算优点主要问题直接切块编码(H/p) × (W/p)p 常取 14 或 16保留全部细节高分辨率图会吃掉大半上下文连接器压缩到固定长度固定例如 256 或 576成本可预测小字和密集文本容易丢动态分辨率随原图尺寸浮动兼顾细节与成本需要分块拼接实现复杂按 448×448 的图和 14 的块大小计算一张图约 1024 个视觉 token相当于一篇中等长度的中文文章。面试时如果能把「图像分辨率直接决定上下文预算」这句说清楚再补一句「所以多轮图文对话要在第二轮开始对历史图像降采样」基本就答到点上了。评测环节的陷阱更多。整体准确率这种指标在多模态任务上几乎没有信息量至少要拆成四类看文字识别、计数与空间关系、图表理解、常识推理。计数类任务对分辨率极其敏感把图缩小一半再测一次如果准确率掉得厉害说明模型是在猜而不是在数。还有一种常见误判是把「答对了」当成「看懂了」可以在图上做局部遮挡或轻微位移答对率如果不变说明模型靠的是语言先验而不是图像信息。def visual_tokens(height, width, patch14): 按切块编码粗估视觉 token 数量 return (height // patch) * (width // patch) for h, w in ((224, 224), (448, 448), (1024, 1024)): print(f{h}x{w} - {visual_tokens(h, w)} tokens)这三行输出分别是 256、1024、5184最后一个已经超出多数模型的单图预算实际部署时要么降采样要么走动态分块。把这段现场跑给面试官看比描述「分辨率越高 token 越多」有效得多。4.3 RAG 与微调的边界什么场景不该上微调这道题是应用岗的分水岭。判断标准可以归纳成一句话知识用检索行为用微调格式用提示。知识类需求政策条款、商品信息、内部文档更新频繁放进参数里意味着每次变更都要重训检索侧改一次索引就够了而且天然带引用来源。行为类需求固定话术、特定推理步骤、稳定的输出结构属于参数层的约束检索解决不了。需求类型首选方案原因知识频繁更新检索增强改索引即可无需重训输出格式固定少量微调或提示约束属于行为层约束需要给出引用来源检索增强证据链天然存在垂类术语理解差先补齐分词和少量样本微调语义对齐优先于加数据标注数据只有几百条检索加提示工程样本量不足以支撑微调还有一个容易被忽略的成本项微调不只是训练那点算力还包括每次基础模型升级都要重新走一遍流程。面试时主动说「我会先问这个知识多久变一次」比直接推荐微调更显成熟。5. 把面试准备收口成一份可跑的自测清单前面几章的公式和命令落到最后其实就两件事能不能现场算出一个数字能不能在五分钟内讲完一个项目。这两件事都可以提前练成肌肉记忆。5.1 用一段脚本打印显存对照表练到能默写把 KV cache 公式写成会打印表格的脚本面试前跑一遍把数字记下来。def kv_gb(layers, kv_heads, head_dim, seq, batch, dtype_bytes2): return 2 * layers * kv_heads * head_dim * seq * batch * dtype_bytes / 1024 ** 3 # 32 层、8 个 KV 头、head_dim128FP16 for seq in (8192, 32768, 131072): for batch in (1, 16, 64): print(fseq{seq:7} batch{batch:3} KV{kv_gb(32, 8, 128, seq, batch):6.2f} GiB)跑完会发现两个值得记住的锚点8K 上下文、16 并发约 16 GiB把 batch 换成 1同样的上下文只要 1 GiB。这两个数字一报出来「上下文和并发是线性乘数」这句话就有了重量。5.2 三分钟答题骨架结论、数字、边界、失败案例被问到一个开放问题时按四步说先给结论再给一个具体数字然后说清楚这个结论的边界条件最后补一个自己踩过的坑。以「LoRA 的 rank 怎么选」为例结论是「7B 模型上 8 到 16 是常见起点」数字是「rank 翻倍可训练参数量大约翻倍单卡显存增加通常在几百 MB 量级」边界是「任务越接近预训练分布rank 越小越好风格迁移类任务可以适当加大」失败案例是「rank 拉到 64 后训练集指标涨了但验证集掉点说明过拟合最后退回 16」。# 面试前一晚的自测把三个锚点数字念一遍确认不卡壳 # 1) 7B / FP16 / 8K / batch16 - KV cache 约 16 GiB # 2) 448x448 图像 / patch 14 - 约 1024 个视觉 token # 3) 13B / INT4 - 纯权重约 6.05 GiB补一句实操建议把每次面试被追问到答不上来的问题记在一个文件里面试结束当天就把对应的数字算出来补上第二次被问到就能直接报数。校招面试的题池并不大三轮下来基本能覆盖八成剩下的两成靠的是你有没有真的跑过一次推理服务。本文还有配套的精品资源点击获取
返回列表