ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

LLM面试8个核心问题:从Transformer到智能体容错的深度解析

LLM面试8个核心问题:从Transformer到智能体容错的深度解析

1. 为什么这8个问题成了LLM面试的分水岭

面过LLM岗位的人大概都有体会:面试官不会花一个小时跟你聊人生理想,往往前二十分钟就能判断出你是“真做过”还是“背过”。标题里说的“答不上来直接淘汰”不是危言耸听,我带过几轮校招和社招之后发现,真正拉开差距的就是那8个反复出现的问题。它们覆盖了从Transformer架构到Tokenization、从Function Calling到智能体容错的完整链路,任何一个环节答得含糊,面试官基本就能判断你只是调用过API,没碰过底层。

这篇文章不是面试八股文的复读机。我会把这8个问题拆开,讲清楚每个问题背后面试官到底想听什么、常见的错误回答长什么样、以及一个合格从业者应该怎么组织答案。适合准备算法工程师面试、智能体工程师面试、AIGC产品经理面试的朋友,也适合已经在大模型方向工作但想系统梳理知识体系的人。全文基于我自己的面试经历和带人经验整理,涉及参数计算和代码的地方我会给出具体过程,方便你直接抄作业。

先说一个前提:这8个问题不是孤立的,它们之间有清晰的依赖关系。Transformer是地基,Tokenization是输入层,注意力机制是核心引擎,Function Calling是模型与外部世界交互的接口,智能体容错是工程落地的保障。面试官问的顺序通常也是从底层往上走,所以你的回答也要有层次感,不能东一榔头西一棒子。

2. 问题一:Transformer为什么能取代RNN

2.1 面试官真正想听的不是“并行计算”四个字

几乎每个人都会说“Transformer可以并行计算,RNN不行”。这个答案对,但只值60分。面试官接着会问:并行计算具体体现在哪一步?为什么RNN不能并行?如果你答不上来,就暴露了你只是背了结论。

Transformer的并行性体现在训练阶段。RNN的隐藏状态 ( h_t ) 依赖于 ( h_{t-1} ),这是一个严格的串行依赖链,第100个时间步必须等前99个算完。而Transformer的自注意力机制中,每个位置的输出可以同时计算,因为注意力权重 ( \text{softmax}(QK^T/\sqrt{d_k})V ) 中,Q、K、V三个矩阵的运算是矩阵乘法,GPU可以一次性并行处理所有位置。

但这里有个容易被忽略的点:Transformer在推理阶段(自回归生成)其实也是串行的,每生成一个token都要等前一个token出来。所以准确的说法是“训练并行,推理自回归”。能把这个区分讲清楚,面试官会认为你真的跑过模型。

2.2 复杂度对比与位置编码的必要性

面试官常追问的第二个层次是复杂度。RNN的序列操作复杂度是 ( O(n) ),Transformer的自注意力是 ( O(n^2) ),其中n是序列长度。这意味着序列越长,Transformer的计算和显存开销增长越快。这也是为什么后来出现了各种稀疏注意力、线性注意力的变体。

位置编码是另一个必问点。因为自注意力本身是置换不变的,打乱输入顺序输出不变,所以必须注入位置信息。原始论文用的是正弦位置编码:

[ PE_{(pos, 2i)} = \sin(pos / 10000^{2i/d_{model}}) ] [ PE_{(pos, 2i+1)} = \cos(pos / 10000^{2i/d_{model}}) ]

现在主流模型多用可学习的位置编码或RoPE(旋转位置编码)。RoPE的核心思想是通过旋转矩阵将位置信息编码进Q和K的内积中,使得注意力分数天然携带相对位置信息。面试时如果能提到RoPE并解释它为什么在长文本场景下更优,基本能加分。

注意:不要一上来就背公式,先讲直觉。位置编码就像给每个词发一个“座位号”,没有座位号,模型分不清“猫追狗”和“狗追猫”。

2.3 实操心得:手写Transformer时最容易翻车的三个地方

我试过从零手写Transformer,踩过的坑分享给你。第一,注意力掩码写错。解码器需要因果掩码,确保位置i只能看到位置≤i的信息,如果掩码矩阵写反了,模型训练loss会异常低但推理时完全胡说。第二,层归一化的位置。原始论文是Post-LN(残差后归一化),后来GPT系列改成Pre-LN(残差前归一化),Pre-LN训练更稳定,不容易梯度爆炸。第三,学习率预热。Transformer对学习率非常敏感,warmup步数设少了前期loss震荡,设多了收敛慢,通常取总步数的1%到5%。

3. 问题二:Tokenization到底在做什么

3.1 从字符到子词的权衡逻辑

Tokenization是很多人觉得“太基础”而忽略的环节,但面试官恰恰喜欢从这里切入,因为它是连接原始文本和模型的桥梁。核心问题是:为什么不用字符级或词级,而要用子词级?

字符级词表小(英文26个字母),但序列太长,一个句子几百个token,注意力计算吃不消。词级词表大(几十万),但遇到未登录词就抓瞎,而且中文分词本身就有歧义。子词级(Subword)是折中方案,代表算法有BPE、WordPiece、Unigram。

BPE(Byte Pair Encoding)的思路很直接:从字符开始,统计相邻字符对频率,把最高频的 pair 合并成新token,重复直到达到目标词表大小。比如“lower”出现频率高,“l o w e r”可能被合并成“low”和“er”。WordPiece和BPE类似,但合并标准是似然增益而非频率。Unigram反过来,从大词表开始逐步裁剪。

3.2 中文场景下的特殊坑

中文的Tokenization比英文更麻烦。一个中文字符可能对应多个byte,UTF-8编码下常用汉字占3个byte。如果直接按byte做BPE,一个汉字可能被拆成3个token,序列长度膨胀三倍。所以中文模型通常会在预处理阶段做特殊处理,或者直接用字符级tokenizer配合大词表。

我实测过一个中文句子,用某开源tokenizer编码后token数是原字数的1.8倍,而英文大约是0.75倍。这意味着同样长度的中英文文本,中文消耗的上下文窗口更多。做长文本应用时这个差异必须考虑进去,否则你会奇怪为什么中文文档总是超长。

提示:面试时如果被问到“如何优化中文Tokenization”,可以从词表扩充、字符级回退、以及训练领域专用tokenizer三个角度回答。

3.3 词表大小对模型的影响

词表大小是个需要权衡的参数。词表太小,序列变长,注意力开销大;词表太大,embedding矩阵参数量暴涨,而且低频token训练不充分。常见模型词表在3万到15万之间。LLaMA系列用32000,ChatGLM用130000左右(因为中文需要更大词表)。

embedding矩阵的参数量计算很简单:词表大小 × 隐藏维度。比如词表50000、隐藏维度4096,embedding层就有约2亿参数。如果词表翻倍,这部分参数也翻倍,对小模型来说负担很重。

4. 问题三:注意力机制的QKV到底怎么理解

4.1 用图书馆类比讲清楚QKV

QKV是面试必问中的必问。我见过太多人背“Query是查询,Key是键,Value是值”,然后就没有然后了。面试官想听的是你能否用直觉解释清楚。

我的类比是图书馆找书。你走进图书馆,脑子里有一个需求(Query),比如“我想找一本讲深度学习的书”。书架上的每本书都有一个标签(Key),比如“深度学习”“烹饪”“历史”。你拿Query去和每个Key比对,匹配度高的书获得高权重。最后你把这些书的内容(Value)按权重加权求和,得到你要的答案。

对应到公式:( \text{Attention}(Q,K,V) = \text{softmax}(\frac{QK^T}{\sqrt{d_k}})V )。( QK^T ) 就是Query和每个Key的点积相似度,除以 ( \sqrt{d_k} ) 是为了防止点积过大导致softmax梯度消失。softmax归一化后得到权重,再乘以V。

4.2 多头注意力为什么有效

多头注意力是把Q、K、V分别投影到多个子空间,每个头独立计算注意力,最后拼接。直觉解释是:不同的头可以关注不同的模式。有的头关注语法关系(主谓宾),有的头关注语义相似度,有的头关注位置邻近。

面试官可能追问:多头注意力的计算量比单头大多少?假设隐藏维度是d,头数是h,每个头的维度是d/h。单头注意力的计算量是 ( O(n^2 \cdot d) ),多头是 ( h \cdot O(n^2 \cdot d/h) = O(n^2 \cdot d) ),理论上计算量相同。但实际实现中多头会有额外的投影和拼接开销,所以略慢一点。

4.3 代码层面的实现细节

手写多头注意力时,关键步骤是reshape和transpose。假设输入形状是(batch, seq_len, d_model),先经过线性层得到Q、K、V,形状不变。然后reshape成(batch, seq_len, num_heads, d_k),再transpose成(batch, num_heads, seq_len, d_k)。这样每个头就能独立做矩阵乘法。

import torch import torch.nn as nn class MultiHeadAttention(nn.Module): def __init__(self, d_model, num_heads): super().__init__() self.num_heads = num_heads self.d_k = d_model // num_heads 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, x, mask=None): batch, seq_len, _ = x.shape Q = self.W_q(x).view(batch, seq_len, self.num_heads, self.d_k).transpose(1, 2) K = self.W_k(x).view(batch, seq_len, self.num_heads, self.d_k).transpose(1, 2) V = self.W_v(x).view(batch, seq_len, self.num_heads, self.d_k).transpose(1, 2) scores = torch.matmul(Q, K.transpose(-2, -1)) / (self.d_k ** 0.5) if mask is not None: scores = scores.masked_fill(mask == 0, -1e9) attn = torch.softmax(scores, dim=-1) out = torch.matmul(attn, V).transpose(1, 2).contiguous().view(batch, seq_len, -1) return self.W_o(out)

这段代码面试时如果被要求手写,注意三个点:mask的填充值用负无穷或很大的负数、transpose后要contiguous才能view、以及缩放因子 ( \sqrt{d_k} ) 不能漏。

5. 问题四:Function Calling的底层原理是什么

5.1 从Prompt到结构化输出的演进

Function Calling是当前智能体方向面试的高频题。早期做法是在prompt里描述工具,让模型输出特定格式的文本,然后正则解析。这种做法脆弱,模型稍微跑偏就解析失败。后来OpenAI推出了原生Function Calling,模型直接输出结构化的JSON,包含函数名和参数。

底层原理其实不神秘。模型在训练阶段见过大量“用户请求-函数调用”的配对数据,学会了在特定语境下输出特定格式。推理时,系统会把可用函数的schema(名称、描述、参数类型)作为特殊token注入上下文,模型根据这些信息决定是否调用、调用哪个、参数填什么。

5.2 参数校验与错误处理

实际工程中,Function Calling最麻烦的不是让模型输出JSON,而是处理模型输出的错误参数。比如模型可能把日期写成“明天”而不是“2026-05-12”,或者把数字写成字符串。所以生产环境必须在模型输出后加一层校验。

我的做法是定义严格的JSON Schema,用Pydantic或类似库做校验。校验失败时,把错误信息回传给模型,让它重新生成。这个重试机制通常设置2到3次上限,避免死循环。

注意:不要完全信任模型的输出。我见过模型在参数里塞入额外字段、漏掉必填字段、甚至输出不存在的函数名。校验层是必须的,不是可选的。

5.3 多工具编排的挑战

当可用工具超过10个时,模型的选择准确率会下降。解决方案有两种:一是分层路由,先用一个轻量模型或规则做粗筛,缩小候选工具范围;二是把工具描述写得更有区分度,避免功能重叠。

另一个坑是工具调用的顺序依赖。比如先查天气再订机票,如果模型并行调用两个工具,可能拿到错误结果。这时候需要在系统提示里明确说明依赖关系,或者在编排层做串行控制。

6. 问题五:如何评估一个LLM的输出质量

6.1 自动指标与人工评估的取舍

评估是面试中容易被低估的环节。很多人只会说“用BLEU和ROUGE”,但这两个指标在生成式任务上相关性很差。BLEU基于n-gram重叠,对于开放式生成几乎没用,因为同一个意思可以有无数种表达。

现在主流的自动评估方案有三类:基于嵌入的相似度(如BERTScore)、基于LLM的评估(用GPT-4打分)、以及任务特定指标(如代码任务的pass@k)。BERTScore计算生成文本和参考文本的语义相似度,比BLEU好很多,但仍有局限。LLM评估更灵活,但成本高且有位置偏见(倾向于给靠前的选项高分)。

人工评估是金标准,但成本高、一致性差。我的经验是:自动指标做日常迭代监控,人工评估做关键节点验收。两者结合,不要偏废。

6.2 构建领域评估集的实操步骤

通用评估集(如MMLU、HellaSwag)只能反映模型的通用能力,不能反映它在你的业务场景下的表现。所以必须构建领域评估集。

步骤是这样的:第一,从真实业务日志中采样500到1000条输入,覆盖主要场景和边缘case。第二,为每条输入写参考答案,或者定义评分标准(如事实准确性、格式合规性、安全性)。第三,用待评估模型生成输出,人工或LLM打分。第四,计算平均分和方差,方差大说明模型不稳定。

这个过程听起来简单,但实际做起来很耗时。我建议先用100条快速验证评估流程,跑通后再扩展到全量。

6.3 常见评估陷阱

第一个陷阱是数据泄漏。评估集的问题如果出现在训练数据里,分数会虚高。所以评估集必须严格隔离,最好由不同的人构建。

第二个陷阱是评估标准模糊。比如“回答是否有帮助”这种标准,不同标注员理解不同,导致一致性低。解决办法是把标准拆细,比如“是否包含用户问的所有要点”“是否有事实错误”“格式是否符合要求”。

第三个陷阱是忽略延迟和成本。一个模型准确率高但推理慢、费用贵,在生产环境可能不可用。所以评估要包含延迟、吞吐量、单次调用成本等工程指标。

7. 问题六:智能体的容错控制怎么做

7.1 智能体失败的典型模式

智能体(Agent)和单轮问答的最大区别是它要执行多步操作,每一步都可能出错。常见失败模式有:工具调用参数错误、工具返回异常未处理、多步规划偏离目标、陷入循环、以及上下文超长导致遗忘。

面试官问容错控制,其实是想看你有没有真正部署过智能体。只做过demo的人通常没考虑过这些。

7.2 分层容错架构

我的方案是三层防护。第一层是输入校验,在工具调用前检查参数合法性,不合法直接拦截并让模型重新生成。第二层是执行监控,每一步执行后检查结果是否符合预期,比如API返回码、数据格式、数值范围。第三层是全局兜底,设置最大步数限制和超时机制,超过阈值就终止并返回当前最佳结果。

重试策略也要区分。参数错误可以重试,但网络超时重试可能加重服务负担,需要指数退避。逻辑错误(如规划偏离)重试没用,需要重新规划。

7.3 状态管理与回滚

多步智能体需要维护状态。我通常用一个状态字典记录每一步的输入、输出、时间戳和状态标记。如果某一步失败,可以回滚到上一个成功状态,而不是从头开始。

这个设计在长流程任务中特别重要。比如一个订票智能体,已经选好航班和座位,支付步骤失败了,回滚到选座状态比重新搜索航班高效得多。

提示:状态管理要考虑持久化。如果智能体运行时间较长,进程崩溃后状态丢失会很麻烦。可以用Redis或数据库做checkpoint。

8. 问题七:如何优化LLM的推理速度

8.1 量化、蒸馏与剪枝的适用场景

推理优化是工程岗必问。三大方向:量化、蒸馏、剪枝。量化是把FP16权重转成INT8或INT4,减少显存占用和计算量。蒸馏是用大模型教小模型,让小模型获得接近大模型的能力。剪枝是去掉不重要的权重或层。

量化最实用,因为改动小、收益明显。INT8量化通常能减少一半显存,速度提升30%到50%,精度损失很小。INT4量化更激进,显存减少75%,但精度损失需要评估。GPTQ和AWQ是当前主流的量化方法。

蒸馏适合需要极致延迟的场景,但训练成本高,且小模型的能力上限受限于大模型。剪枝在LLM上效果不如在CV模型上明显,因为LLM的权重分布更密集。

8.2 KV Cache的原理与显存计算

KV Cache是自回归生成的关键优化。没有KV Cache时,每生成一个token都要重新计算所有历史token的K和V,计算量随序列长度平方增长。有了KV Cache,历史K和V缓存下来,每步只计算新token的K和V,计算量降为线性。

但KV Cache占显存。计算公式是:2 × batch_size × seq_len × num_layers × num_heads × d_k × 精度字节数。以LLaMA-7B为例,32层、32头、d_k=128、FP16精度,序列长度2048、batch size 1时,KV Cache约1GB。序列长度翻倍,显存也翻倍。这就是长文本推理显存暴涨的原因。

8.3 批处理与连续批处理

批处理能提高GPU利用率,但传统批处理要等最长的序列生成完才能处理下一批,短序列浪费了等待时间。连续批处理(Continuous Batching)动态地把新请求插入到正在运行的批次中,显著提升吞吐量。vLLM和TensorRT-LLM都支持这个特性。

我实测过,在同等硬件下,连续批处理比朴素批处理吞吐量提升2到4倍,具体取决于请求长度分布。如果请求长度差异大,提升更明显。

9. 问题八:如何设计一个可靠的LLM应用架构

9.1 从原型到生产的鸿沟

最后一个问题通常是开放式的:让你设计一个LLM应用。很多人会画一个“用户输入-模型-输出”的简单图,但这远远不够。生产级架构要考虑缓存、限流、降级、监控、日志、安全过滤等。

我的架构通常分四层:接入层负责鉴权和限流,编排层负责prompt组装和工具调度,模型层负责推理和量化,数据层负责缓存和日志。每层之间用异步消息队列解耦,避免单点故障。

9.2 缓存策略与成本控制

LLM调用成本高,缓存是必须的。缓存分两种:精确缓存和语义缓存。精确缓存对相同输入直接返回结果,实现简单但命中率低。语义缓存用嵌入向量做相似度匹配,命中率高但有误判风险。

我的做法是两级缓存:先查精确缓存,未命中再查语义缓存,相似度阈值设0.95以上,避免返回不相关结果。缓存失效策略用LRU加TTL,TTL根据业务更新频率设定。

9.3 监控指标与告警

生产环境必须监控的指标包括:请求延迟(P50、P95、P99)、错误率、token消耗量、缓存命中率、以及模型输出的安全违规率。告警阈值根据业务SLA设定,比如P99延迟超过3秒告警。

日志要记录完整的请求和响应,但要注意脱敏。用户隐私数据不能明文存储,可以用哈希或掩码处理。

注意:不要等到出问题才加监控。我见过团队上线三个月后才开始看延迟指标,结果发现P99一直超标,用户体验早就受损了。

10. 面试中的表达技巧与避坑指南

10.1 如何组织一个高分回答

面试官问一个问题,你的回答结构应该是:先给结论,再解释原理,然后举实际例子,最后说注意事项。比如问“Transformer为什么能并行”,先说“因为自注意力是矩阵运算,不依赖前一步结果”,再解释QKV计算过程,然后举训练和推理的区别,最后提一句“推理阶段仍是自回归”。

这个结构让面试官觉得你既有理论深度又有工程经验。切忌一上来就背公式,也不要只讲概念不讲实践。

10.2 遇到不会的问题怎么办

诚实是最好的策略,但不要只说“不会”。可以说“这个点我没有深入做过,但我的理解是……,如果让我做我会从……入手”。这样展示的是你的思考能力,而不是知识盲区。

面试官通常不会因为一个点不会就淘汰你,但如果不懂装懂、胡编乱造,一旦被追问就露馅,那才是致命的。

10.3 反问环节该问什么

反问环节不是走过场。问得好能加分,问得差会减分。推荐问三类问题:团队当前的技术挑战是什么、模型部署的规模和数据量级、以及团队对新人前三个月的期望。避免问薪资福利、加班情况这些HR环节该问的问题。

我个人的经验是,反问环节能看出一个团队的技术成熟度。如果面试官能清晰描述技术栈和挑战,说明团队有积累;如果含糊其辞,可能还在摸索阶段。

11. 我踩过的坑与最后几条实用建议

面了这么多轮,也面了这么多人,有几个坑我反复见到。第一个坑是只背不练。很多人把八股文背得滚瓜烂熟,但让他手写一个注意力机制就卡壳。面试官一旦让你写代码,背的东西全露馅。所以每个核心模块都要亲手实现一遍,哪怕只是简化版。

第二个坑是忽略工程细节。比如问“怎么部署模型”,只回答“用vLLM”是不够的,面试官会追问显存怎么估算、并发怎么处理、故障怎么恢复。这些细节才是区分候选人的关键。

第三个坑是不关注最新进展。LLM领域变化快,RoPE、GQA、MoE、FlashAttention这些技术如果完全没听过,会显得你跟不上节奏。不需要每个都精通,但至少知道它解决什么问题。

最后分享一个我自己的习惯:每次面试后把被问倒的问题记下来,回去查清楚,下次就不会再栽同一个坑。面试本身就是最好的学习机会,哪怕这次没过,把暴露的短板补上,下一次就是你的主场。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进