1. 为什么这8个问题成了LLM面试的硬门槛
面过几十个候选人之后,我越来越确信一件事:LLM方向的面试,前十分钟就能判断出这个人到底是“用过”还是“懂过”。用过的人能跟你聊Prompt怎么写、API怎么调、哪个模型便宜;懂过的人才会在追问下讲清楚Attention的复杂度为什么是O(n²)、Tokenization的边界情况怎么处理、Function Calling的协议层到底发生了什么。
标题里说的“答不上来直接淘汰”,听起来残酷,但站在面试官角度其实很合理。LLM岗位的容错率极低——一个对Transformer结构理解模糊的人,你很难指望他在推理优化时做出正确判断;一个说不清Embedding原理的人,做RAG检索时大概率只会调参而不知道问题出在哪一层。这8个问题不是刁难,而是筛掉那些只会调API的“表面使用者”。
这篇文章我打算把这8个问题逐个拆开,不是给你背答案,而是把每个问题背后的知识体系、面试官真正想听的东西、以及常见的错误回答模式都讲透。适合正在准备LLM岗位面试的人,也适合已经入行但想补齐底层认知的从业者。我会尽量用从业者之间聊天的口吻,把每个技术点讲清楚“为什么”而不只是“是什么”。
先把这8个问题的范围框一下,它们基本覆盖了LLM从输入到输出的完整链路:Tokenization(文本怎么变成模型能处理的东西)、Transformer架构(模型内部怎么运转)、Attention机制(核心计算逻辑)、Embedding(语义怎么被表示)、位置编码(顺序信息怎么保留)、训练与微调(模型怎么学会做事)、Function Calling与Agent(模型怎么跟外部世界交互)、推理与部署(模型怎么高效跑起来)。每一个都是硬骨头,但每一个也都有清晰的回答框架。
2. Tokenization与Embedding:输入链路的两道关
2.1 Tokenization不是“分词”那么简单
很多人一被问到Tokenization就答“把句子切成词”,这个回答在面试官那里基本等于没答。Tokenization的核心矛盾在于:模型只能处理固定词表里的整数ID,但自然语言的组合是无限的。你要在“词表大小”和“序列长度”之间做权衡,这个权衡直接影响到模型的训练成本、推理速度和 multilingual 能力。
主流的Tokenization方案经历了几个阶段。最早是Word-level,词表爆炸且无法处理未登录词。然后是Character-level,词表小了但序列变得极长,一个中文字符一个Token,模型要处理的依赖距离太远。现在主流是Subword-level,代表方案有BPE(Byte Pair Encoding)、WordPiece和SentencePiece。
BPE的逻辑很直观:从字符开始,统计相邻字符对的出现频率,把最高频的 pair 合并成新的子词,重复这个过程直到达到预设的词表大小。举个例子,“lower”和“lowest”这两个词,BPE会先把“l o w”合并成“low”,这样“lower”变成“low er”,“lowest”变成“low est”,共享了“low”这个子词。这样做的好处是常见词保持完整,罕见词被拆成有意义的子单元,词表规模可控。
但BPE有个坑:它基于频率合并,对中文这种没有空格分隔的语言,合并出来的子词可能没有语义边界。SentencePiece的做法不同,它把输入当作原始字符流(包括空格),用Unigram Language Model来做子词切分,对中文和日文更友好。这也是为什么LLaMA系列用SentencePiece而GPT系列用BPE变体的原因之一。
面试官如果追问“Tokenization对模型性能有什么影响”,你可以从这几个角度答:词表大小影响Embedding矩阵的参数量(词表×维度),序列长度影响Attention的计算量(O(n²)),子词切分的粒度影响模型对形态丰富语言的处理能力。还有一个容易被忽略的点:数字的Tokenization。很多Tokenizer会把“1234”切成“12”和“34”,导致模型在数学任务上表现差,这也是为什么后来有工作专门研究数字的Tokenization策略。
注意:面试时如果被问到“为什么GPT-4的Tokenization对中文不友好”,不要只答“词表里中文少”,要说到BPE的合并策略是基于英文语料频率的,中文子词在词表中占比低,导致同样的中文内容需要更多Token来表示,直接推高了推理成本和上下文占用。
2.2 Embedding:从离散ID到连续语义空间
Tokenization的输出是一串整数ID,但模型不能直接处理ID,因为ID之间没有距离概念——ID 5和ID 6可能语义上毫无关系。Embedding层的作用就是把每个ID映射到一个d维的连续向量,这个向量在训练过程中学习得到,使得语义相近的Token在向量空间中距离更近。
这里要区分两个概念:Token Embedding和Sentence Embedding。Token Embedding是模型内部的第一层,每个Token对应一个向量,维度通常等于模型的hidden size(比如768、4096)。Sentence Embedding是把整个句子或段落编码成一个向量,用于检索、聚类、相似度计算,代表模型有Sentence-BERT、BGE、GTE等。
面试官常问的一个问题是:“Embedding向量为什么能表示语义?”这个问题的标准回答路径是:训练目标决定了Embedding空间的结构。以Word2Vec为例,Skip-gram的目标是用中心词预测上下文词,这个目标迫使语义相近的词出现在相似的上下文里,从而学到相似的向量表示。到了Transformer时代,Embedding不再是静态的,而是随着上下文动态变化——同一个词在不同句子里的Embedding不同,这就是Contextual Embedding。
实际做RAG系统时,Embedding模型的选择直接决定检索质量。我踩过的坑是:用通用Embedding模型去检索专业领域文档,效果很差,因为通用模型的训练语料和你的领域不匹配。这时候要么用领域数据微调Embedding模型,要么选一个在相关领域表现好的开源模型。Embedding模型排行这类榜单可以参考,但一定要在自己的数据上做评测,因为榜单的评测集和你的实际场景可能差异很大。
还有一个细节:Embedding的维度不是越高越好。维度高意味着存储成本高、检索速度慢,而且在高维空间里距离度量会变得不稳定(维度灾难)。实践中,768维到1024维通常够用,除非你的任务对细粒度语义区分要求极高。
3. Transformer架构与Attention机制:面试的重灾区
3.1 Transformer为什么能取代RNN
这个问题几乎每场LLM面试都会问,但很多人答不到点子上。标准答案是“Transformer能并行计算”,但这只是表面。更深层的原因是:RNN的序列依赖是串行的,第t步的计算必须等第t-1步完成,这在GPU上无法充分利用并行能力。而且RNN在处理长序列时,早期信息经过多步传递后会衰减或爆炸,虽然LSTM用门控机制缓解了这个问题,但没有根本解决。
Transformer用Self-Attention直接建模任意两个位置之间的关系,不管它们相距多远,注意力权重都能直接计算。这意味着路径长度从RNN的O(n)变成了O(1),梯度传播更稳定,长距离依赖更容易学到。代价是计算复杂度从O(n)变成了O(n²),因为每个位置都要和所有位置计算注意力。
面试官如果追问“O(n²)的复杂度在实际中怎么处理”,你可以从几个方向答:稀疏Attention(只计算局部窗口或固定模式)、线性Attention(用核函数近似Softmax)、FlashAttention(不改变数学等价性,通过IO优化提升实际速度)、滑动窗口Attention(Longformer的做法)。但要注意,这些方案各有取舍,不是所有场景都适用。
3.2 Self-Attention的计算过程要能手推
面试官让你“手写Attention”或者“推导Attention公式”的时候,他要看的不是你能不能背出公式,而是你理不理解Q、K、V各自的角色。
用检索系统做类比:Query是你要查的问题,Key是文档的索引,Value是文档的内容。Attention的过程就是拿Query去和每个Key算相似度,得到权重,然后用权重对Value加权求和。公式是:
Attention(Q, K, V) = softmax(QK^T / sqrt(d_k)) V除以sqrt(d_k)的原因是:当d_k很大时,QK^T的点积结果方差会变大,softmax会进入饱和区,梯度变得极小。除以sqrt(d_k)把方差拉回到1附近,保证梯度稳定。这个细节很多人会忽略,但面试官很在意。
Multi-Head Attention是把Q、K、V分别投影到h个子空间,每个子空间独立做Attention,最后拼接再投影。这样做的好处是模型可以在不同的表示子空间里关注不同的模式——有的头关注语法关系,有的头关注语义相似度,有的头关注位置邻近。面试时如果能说出“多头不是为了增加参数量,而是为了增加表示的子空间多样性”,会加分不少。
3.3 位置编码的演进与选择
Transformer本身没有位置概念,Self-Attention对输入顺序是不变的。所以必须显式注入位置信息。最早的是正弦位置编码,用不同频率的sin和cos函数生成位置向量,优点是能外推到训练时没见过的长度,但实际外推效果有限。
后来主流变成了可学习位置编码,每个位置一个可训练向量,简单直接,但无法处理超过训练长度的序列。BERT和GPT早期都用这个。
现在最常用的是RoPE(Rotary Position Embedding),它的思路不是加位置向量,而是把位置信息编码成旋转矩阵,作用在Q和K上。RoPE的好处是:相对位置信息在Attention计算中自然体现,而且有一定的外推能力。LLaMA、Qwen、ChatGLM等主流模型都用RoPE。
还有一个方案是ALiBi,直接在Attention分数上加一个与距离成正比的偏置,距离越远惩罚越大。ALiBi的外推能力很强,但表达力不如RoPE灵活。
面试时如果被问到“RoPE为什么能外推”,你可以答:RoPE把位置编码成旋转角度,两个位置的相对角度只取决于它们的距离差,而不是绝对位置。当序列变长时,相对角度关系仍然成立,所以模型能处理更长的序列。但实际外推效果还取决于训练时的长度分布和频率设置,不是无条件的。
4. 训练、微调与对齐:模型怎么学会做事
4.1 预训练、微调、对齐的三阶段
LLM的训练流程可以拆成三个阶段:预训练(Pre-training)、监督微调(SFT)、对齐(Alignment)。预训练是在海量文本上做自回归语言建模,目标是让模型学会语言的统计规律和世界知识。这个阶段成本最高,动辄几千张GPU跑几个月。
SFT是用人工标注的指令-回答对来教模型“怎么回答问题”。预训练后的模型只会续写文本,你问它“中国的首都是哪里”,它可能续写成“中国的首都是哪里?这是一个常见的问题……”而不是直接回答“北京”。SFT就是把这个能力教给它。
对齐阶段最常用的是RLHF(Reinforcement Learning from Human Feedback)或DPO(Direct Preference Optimization)。RLHF的流程是:训练一个奖励模型来模拟人类偏好,然后用PPO算法优化策略模型,让它的输出获得更高的奖励。DPO则跳过奖励模型,直接用偏好数据优化策略,更简单也更稳定。
面试官如果问“SFT和RLHF的区别是什么”,核心回答是:SFT是模仿学习,教模型“什么是好的回答”;RLHF是偏好优化,教模型“什么样的回答更好”。SFT的数据是标准答案,RLHF的数据是成对的偏好比较。
4.2 LoRA与参数高效微调
全量微调一个7B模型需要几十GB显存,不是所有人都玩得起。LoRA(Low-Rank Adaptation)的思路是:冻结原模型参数,在每层旁边加一个低秩矩阵,只训练这个低秩矩阵。假设原权重是d×d,LoRA把它分解成d×r和r×d两个矩阵,r远小于d(通常8到64)。这样可训练参数量降到原来的千分之一甚至更低。
LoRA的关键参数是rank和alpha。rank决定低秩矩阵的容量,rank越大表达能力越强但参数量也越大。alpha是缩放因子,实际更新量是alpha/rank乘以低秩矩阵的乘积。实践中rank=16、alpha=32是一个常见的起点。
我踩过的坑是:LoRA的学习率通常要比全量微调大一个数量级,因为可训练参数少,需要更大的步长才能有效更新。另外,LoRA适合风格迁移和领域适配,但如果要教模型全新的知识,效果可能不如全量微调。
注意:面试时如果被问到“LoRA和Adapter的区别”,要说到LoRA是并行结构(不改变原模型的计算路径),Adapter是串行结构(在层间插入新模块)。LoRA在推理时可以把低秩矩阵合并回原权重,不增加推理延迟;Adapter则必须保留额外模块,增加推理开销。
4.3 数据标注与质量控制
LLM的数据标注跟传统机器学习很不一样。传统标注是给样本打标签,LLM标注是写指令-回答对,或者对多个回答做排序。这里最大的挑战是标注一致性——不同标注者对“好回答”的标准可能完全不同。
我的经验是:先写一份详细的标注指南,把“好回答”的标准拆成可操作的维度,比如准确性、完整性、简洁性、格式规范性。然后让标注者先标一批,计算标注者之间的一致性(比如Cohen's Kappa),如果一致性低,就讨论分歧案例,迭代指南。这个过程通常要跑两三轮才能稳定。
对于RLHF的偏好数据,标注者要做的是给两个回答排序,而不是打分。排序比打分更可靠,因为打分涉及绝对尺度,不同标注者的尺度不一样,而排序只涉及相对比较。但排序也有问题:如果两个回答质量差不多,标注者会随机选,引入噪声。这时候可以加一个“差不多”选项,或者在训练时降低这类样本的权重。
5. Function Calling与Agent:模型怎么跟世界交互
5.1 Function Calling的协议层发生了什么
Function Calling不是模型真的去调用函数,而是模型输出一个结构化的调用请求,由外部系统执行后再把结果返回给模型。整个流程是:用户提问 → 模型判断需要调用工具 → 模型输出函数名和参数(通常是JSON格式)→ 外部系统执行函数 → 结果返回模型 → 模型生成最终回答。
面试官如果问“Function Calling和普通Prompt有什么区别”,核心区别在于:普通Prompt是模型直接生成文本,Function Calling是模型生成结构化的调用意图,需要外部系统配合执行。这意味着模型必须理解函数的语义(函数名、参数含义、返回值类型),并且能根据用户意图选择合适的函数。
实际实现时,函数定义通常用JSON Schema描述,包括函数名、描述、参数列表和参数类型。模型在训练时会见到大量这样的Schema和对应的调用示例,学会根据用户输入生成符合Schema的调用请求。
这里有个容易忽略的点:Function Calling的可靠性。模型可能生成不存在的函数名、参数类型错误、或者该调用时不调用。解决办法包括:在Prompt里明确列出可用函数、用Few-shot示例展示调用模式、在输出层做Schema校验、设置fallback策略(如果调用失败就回退到普通回答)。
5.2 Agent系统的容错设计
Agent是Function Calling的进阶形态:模型不仅能调用单个函数,还能规划多步操作、根据中间结果调整策略、在失败时重试或换方案。但Agent的可靠性是个大问题——每一步都可能出错,多步累积下来失败率很高。
我在构建Agent系统时总结了几条容错原则。第一,每一步都要有验证。模型输出调用请求后,先校验格式和参数合法性,不合法就要求模型重新生成。第二,设置最大步数限制。防止模型陷入循环,通常设5到10步。第三,关键操作要有人工确认。比如删除数据、发送邮件这类不可逆操作,应该让用户确认后再执行。第四,记录完整的执行轨迹。方便排查问题,也方便后续优化。
还有一个设计决策:Agent的规划是显式还是隐式。显式规划是让模型先输出一个完整的步骤列表,再逐步执行;隐式规划是让模型每一步根据当前状态决定下一步。显式规划更容易调试和干预,但灵活性差;隐式规划更灵活,但容易跑偏。实践中可以混合:先让模型生成粗略计划,执行时允许动态调整。
注意:Agent系统里最危险的不是模型能力不够,而是模型“自信地做错事”。比如用户问“帮我订明天去北京的机票”,模型可能直接调用订票函数而不确认日期和航班偏好。所以关键操作前一定要加确认环节,这不是降低效率,而是避免灾难。
6. 推理优化与部署:模型怎么跑得又快又省
6.1 KV Cache的原理与显存计算
自回归生成时,每生成一个Token都要重新计算所有之前Token的Key和Value,这是巨大的浪费。KV Cache的思路是:把之前Token的Key和Value缓存起来,生成新Token时只计算当前Token的Key和Value,然后和缓存拼接。
KV Cache的显存占用可以精确计算:每层每个Token的KV Cache大小是2 × hidden_size × num_layers × dtype_size。以LLaMA-7B为例,hidden_size=4096,num_layers=32,FP16下每个Token的KV Cache是2 × 4096 × 32 × 2 bytes = 512KB。如果序列长度是2048,batch size是1,KV Cache就是1GB。这还没算模型权重本身的显存。
面试官如果问“怎么优化KV Cache”,可以从几个方向答:MQA(Multi-Query Attention)让所有头共享同一组KV,大幅减少缓存;GQA(Grouped-Query Attention)是MQA和MHA的折中,每组头共享KV;PagedAttention(vLLM的核心)把KV Cache分页管理,减少碎片浪费;量化把KV Cache从FP16降到INT8,显存减半。
6.2 量化与推理加速
量化是把模型权重和激活值从高精度(FP16/FP32)降到低精度(INT8/INT4),减少显存占用和计算量。常见的方案有GPTQ、AWQ、GGUF等。GPTQ是训练后量化,用校准数据调整量化参数;AWQ是激活感知的量化,对重要通道保留更高精度;GGUF是llama.cpp用的格式,支持CPU和GPU混合推理。
量化的取舍是:精度越低,显存越省、速度越快,但模型能力可能下降。INT8量化通常几乎无损,INT4量化在大多数任务上可用,但在需要精细推理的任务上可能明显退化。我的经验是:如果显存够用,优先用FP16;如果必须量化,INT8是安全选择,INT4要评测后再用。
还有一个加速方向是投机解码(Speculative Decoding):用一个小模型快速生成多个候选Token,然后用大模型并行验证。如果小模型的猜测准确率高,整体速度能提升2到3倍。这个方法的代价是需要额外的小模型,而且加速比取决于任务难度——简单任务加速明显,复杂任务可能反而变慢。
6.3 本地部署与安卓运行
在安卓上跑LLM是很多人的需求,但资源限制很严格。目前可行的方案是用GGUF格式的量化模型,配合llama.cpp的安卓移植版。7B模型INT4量化后大约4GB,高端手机能跑但速度较慢,通常每秒几个Token。更大的模型基本不现实。
实际部署时要注意:安卓的内存管理很激进,后台应用容易被杀,所以推理时最好保持前台运行。另外,CPU推理耗电很快,长时间使用会导致发热降频。如果只是做Demo或轻量使用可以,但生产环境还是建议用服务端API。
7. 常见问题与排查技巧实录
7.1 面试中的高频追问与应对
| 追问方向 | 常见问题 | 回答要点 |
|---|---|---|
| Attention复杂度 | O(n²)怎么优化 | 稀疏Attention、线性Attention、FlashAttention,各有取舍 |
| 位置编码 | RoPE为什么比正弦编码好 | 相对位置自然体现,外推能力更强,但非无条件 |
| 微调策略 | LoRA和全量微调怎么选 | 领域适配用LoRA,学新知识用全量,资源决定 |
| RAG检索 | Embedding模型怎么选 | 领域匹配优先,榜单参考,必须自测 |
| Agent可靠性 | 怎么防止模型乱调工具 | Schema校验、最大步数、关键操作确认 |
| 推理优化 | KV Cache显存怎么算 | 2×hidden×layers×dtype×seq×batch |
| 量化 | INT4量化会掉点吗 | 大多数任务可用,精细推理任务需评测 |
| 部署 | 安卓能跑多大模型 | 7B INT4是上限,速度慢,适合Demo |
7.2 我踩过的坑与避坑建议
第一个坑是过度依赖榜单选Embedding模型。早期做RAG时,我直接用了某个榜单第一的模型,结果在自己的法律文档检索任务上效果很差。后来发现榜单的评测集是通用领域,和法律领域的语义分布差异很大。教训是:榜单只能缩小选择范围,最终必须在自己的数据上做A/B测试。
第二个坑是LoRA的rank设得太小。一开始用rank=4,觉得参数量少、训练快,结果模型学不到什么东西。后来调到rank=32,效果明显提升。经验是:rank不是越小越好,要根据任务复杂度来定。简单风格迁移rank=8够用,复杂领域适配可能需要64甚至128。
第三个坑是Agent的最大步数设得太大。有一次设了20步,结果模型在一个死循环里跑了18步才停,浪费了大量Token。后来改成8步,并且在Prompt里明确说“如果连续两次调用同一个函数且参数相同,就停止并报告失败”。这个规则很有效,基本杜绝了死循环。
第四个坑是忽略Tokenization对成本的影响。做多语言应用时,发现同样的内容用中文比英文多消耗50%以上的Token,因为中文在BPE词表里占比低。后来在Prompt设计上做了优化,把系统指令改成英文,用户输入保持中文,整体成本降了不少。
7.3 面试前的准备清单
如果你正在准备LLM面试,我建议按这个清单过一遍:能手推Self-Attention公式并解释每个符号的含义;能说清楚BPE和SentencePiece的区别及适用场景;能解释RoPE的原理和外推限制;能对比LoRA和全量微调的取舍;能描述Function Calling的完整流程和容错设计;能计算KV Cache的显存占用;能说出至少三种推理加速方案及其代价。这些不是背答案,而是真正理解之后能用自己的话讲出来。
最后分享一个我个人的习惯:每次面试前,我会拿一张白纸,把Transformer的架构图默画一遍,从输入Tokenization到输出Softmax,每个模块都标注关键参数和计算复杂度。这个过程能帮我快速进入状态,也能暴露自己理解模糊的地方。如果你也能做到不看资料默画出来,那这8个问题基本难不倒你。