面灵AI→

米哈游 大模型算法 面经:LoRA 一致性、Agent 与推理优化

时间
2026-10
来源
牛客网

《面试题目》

  1. 自我介绍。
  2. 手撕代码:ACM 模式,考察输入输出处理。
  3. 平时习惯用什么语言?
  4. 用过 Agent 吗?
  5. LoRA 的原理是什么?
  6. 微调之后如何控制输出的一致性?
  7. 处理数量很大时,怎么保证 QPS?
  8. 实习期间大模型节点验证具体做了什么?
  9. 60 多个自动化验证 case,挑一个典型的说说它验证了哪些功能?
  10. 这些 case 基本都是分支覆盖类型的吗?
  11. 还有哪些回调型用例?
  12. 这些用例主要处理哪些问题?
  13. 多模态模型怎么处理视频输入?
  14. 多模态大模型产生幻觉的原因是什么?
  15. 怎么通过后训练、RAG、规则来缓解视觉幻觉?
  16. embedding 和 reranker 分别解决什么问题?
  17. 大规模搜索的 ANN 算法怎么选择?
  18. 手撕:岛屿数量,分别用 DFS 和 BFS 实现。
  19. 生成式模型的基础原理是什么?
  20. VAE 的损失函数是什么?
  21. 二分类的损失函数是什么?
  22. Transformer 的整体架构是怎样的?
  23. Attention 的计算复杂度是多少?
  24. KV Cache 的原理是什么?
  25. GQA、MLA 的原理是什么?
  26. vLLM 的原理是什么?
  27. Flash Attention 的原理是什么?
  28. 稀疏注意力的原理是什么?
  29. 模型推理慢的排查思路是什么?
  30. 手撕:用 rand7() 实现 rand10()。
  31. 手撕:冒泡排序,自己实现一遍,再用内置 sort 对照、用 assert 验证结果。

《参考解析》

LoRA 与一致性控制

LoRA 冻结原权重,只在旁边挂一个低秩增量:原来 D×D 的矩阵变成 W + BA,其中 A 是 r×D、B 是 D×r,可训练参数从 D² 降到 2Dr;训练时通常把 A 随机初始化、B 置零,保证起点与基座等价,推理时 BA 可以合并回 W,不增加延迟。

「一致性」有几种可能的问法,答之前先把口径说清。若指同一输入多次输出要稳定,手段是解码侧收紧——关掉或调低采样(temperature 趋零、固定种子)、固定 prompt 模板与权重版本(base 与 LoRA 版本绑定成一个发布单元)、避免批内不同请求互相影响。若指微调后整体风格与人设不发生漂移,重点在数据与训练:训练集分布要和目标场景一致且干净,低 rank、低学习率、少 epoch,混入一部分通用数据做 replay 抑制灾难性遗忘,必要时加与基座输出的 KL 约束。上线前用一组带扰动的问题集回归(同义改写、换问法、边界输入),看答案稳定率与拒答率有没有退化,比看 loss 曲线可靠得多。

大请求量下的 QPS 保障

先分清要优化的是吞吐(QPS)还是延迟(TTFT/单 token 时间),两者的手段不完全一样。吞吐侧最有效的是连续批处理:请求不等整批结束,某个序列生成完就立刻换入新请求填满显存,vLLM、TensorRT-LLM 都是这个思路。再往上叠:PagedAttention 之类的分页 KV 管理减少显存碎片、prefix caching 复用相同的系统提示与 few-shot 前缀、量化(权重 INT8/FP8 加 KV Cache 量化)、投机解码用小模型起草大模型校验、prefill 与 decode 分离部署、多卡张量并行。

工程上还有几条常被忽略的:按难度路由——简单请求走小模型或规则,只有复杂请求进大模型;结果缓存——相同或近似请求直接命中缓存;限流排队——宁可让请求排队也别让显存打满触发抢占重算,那会让吞吐断崖式下跌;多 LoRA 场景用支持多适配器的推理框架(S-LoRA 方向)共享一份基座权重,而不是每个 LoRA 单独部署一个实例。

实习期自动化验证 case 怎么讲

这类追问的答法是「先给体系,再给一个例子」。体系上要说清验证对象是节点级的输入输出与状态流转,用例怎么组织(数据驱动 + 参数化,可回归、能进流水线),断言怎么写,失败怎么定位。挑典型例子时按四步讲:这个 case 覆盖的是哪条分支或哪种异常、输入怎么构造(需不需要 mock 外部依赖、怎么造回调与超时)、断言了什么、它实际暴露过一个什么 bug。被问到「是不是都只有分支覆盖」时,主动补上异步侧:回调型、事件驱动型用例要验证回调时序、重复回调与超时重试的幂等,以及异常路径(超时、乱序、部分成功);这些恰恰是分支覆盖覆盖不到的部分。

多模态幻觉与缓解

幻觉的根因可以分几层:视觉编码器与语言模型之间存在模态对齐差距,训练目标只优化「下一个 token」而从不惩罚「无视图像也能答对」的表述;视觉侧信息不足(分辨率低、帧采样稀疏、视觉 token 被过度压缩)导致模型看不清细节,只能靠语言先验补全;预训练图文数据里的共现偏差让模型倾向于输出高频物体,出现「图里没有的东西也说有」;解码时的随机采样与长度偏置也会放大编造。

缓解是分层做的。后训练:用带幻觉标注的样本做偏好优化,正例是准确描述、负例是编造与过度推断,并把「图里不存在」这类负向问答混进训练集。RAG:把相关的相似图像、结构化资料、字幕与 ASR 文本作为证据一起送进上下文,要求回答落到证据上。规则:生成结果用检测模型或视觉模型做一次复核(grounding 校验,检查提到的物体在图中是否真的有),不确定就降级为「无法确认」,并对关键结论强制给出图像位置依据。视频场景还要注意时间维度——按镜头切分并保留时间戳,比等间隔抽帧更能保证「说的是画面里真实发生的事」。

embedding、reranker 与 ANN 选型

embedding 模型是双塔结构,query 和文档各自独立编码,可以离线建索引、在线只算一次向量,因此适合做大规模粗召回;reranker 是 cross-encoder,把 query 与候选文档拼在一起过一遍模型,精度高但必须在线逐对计算,只能对前几十到几百条候选做精排。两者的分工就是「召回保底、精排提准」,所以 reranker 的效果上限取决于召回候选里有没有正确答案。

ANN 选型看四个维度:数据规模与向量维度、内存预算、召回率与延迟目标、是否需要频繁增删和强过滤。常见结论是:千万级以内且内存充裕,选图索引 HNSW(召回高、延迟低,代价是内存吃得多);上亿规模、内存受限,用 IVF-PQ 这类带量化的倒排索引或 DiskANN 这类磁盘索引,用少量精度换容量;过滤条件重的业务(按类目、时间、权限筛)要提前考虑过滤与索引的结合方式,否则只能拿到一批结果再过滤,召回会掉得很厉害。

Transformer、Attention 与推理侧原理

Transformer 由多层堆叠,每层是「多头自注意力 + 前馈网络」,配残差连接、LayerNorm 与位置编码;编码器结构早已不是主流,生成式模型基本是 decoder-only。标准自注意力的时间与显存复杂度都是 O(n²d),瓶颈在于要显式构造并读写 n×n 的注意力矩阵;前馈层的复杂度是 O(nd²),在长序列上两者会互换主导地位。

KV Cache 的作用是避免每生成一个 token 就把历史 K、V 重算一遍:缓存后单步只需对历史做一次注意力,生成总成本从「每步都平方」变成与序列长度线性相关的累积,代价是显存占用随 层数 × KV 头数 × 头维度 × 序列长度 × batch 增长,长文本大并发时它会超过权重成为主要占用。GQA 让多个 query 头共享一组 KV 头(MQA 是极端情况只留一组),直接把 KV Cache 与带宽按比例压下来;MLA 把 KV 压缩成低维潜向量、再单独保留一部分解耦的位置编码分量,在表达能力与缓存体积之间取更激进的折中。

Flash Attention 解决的是访存而不是算法复杂度:把序列分块、用 online softmax 逐步累加,不把 n×n 矩阵写回显存,中间结果留在片上 SRAM,结果是数值上精确、显存减少、速度更快。稀疏注意力是真的降复杂度——让每个 token 只关注局部窗口加少量全局节点(或按块稀疏、随机连接),把成本压到接近线性,代价是可能丢掉长程依赖,选它之前要先确认任务的长依赖真的不重要。

vLLM 是推理引擎层面的组合拳:PagedAttention 把 KV Cache 按块分页管理,消除内存碎片并支持块级共享(相同前缀只存一份);连续批处理让调度按迭代进行,吞吐显著高于静态批;再叠加 prefix caching、chunked prefill、量化与张量并行。

推理慢的排查思路

按链路分段量,别一上来就换模型。先看排队时间——是不是并发超过容量导致请求在队列里等,这也是最容易误判成「模型慢」的一段。再看 prefill(首 token 延迟):长 prompt 会线性拖慢它,能靠 prefix caching、chunked prefill 或压缩提示缓解。然后是 decode(每 token 延迟与吞吐):这一段是显存带宽受限的,batch 太大、序列太长都会让它变慢。接着查显存与缓存:KV Cache 是否打满触发抢占换出甚至重算、有没有落到 CPU offload、多卡张量并行的通信是不是成了瓶颈。最后查环境:GPU 是否被其他进程抢占、驱动与库版本、采样参数和输出长度分布。定位依据是框架自身的指标(队列时间、prefill/decode 耗时、吞吐)加 nvidia-smi 与 profiler,而不是凭感觉。

生成式模型基础与损失函数

生成式模型学的是数据分布,自回归模型把联合概率拆成条件概率的乘积逐 token 建模,训练目标是最大似然;VAE 走的是隐变量路线,编码器把输入映射到潜空间分布(通常假设高斯),解码器从采样出的 z 重建输入,损失是重建损失(MSE 或 BCE)加 KL 散度(让后验贴近标准正态先验),两者合起来就是负 ELBO;实践中会给 KL 项加权重或做 warm-up,否则容易出现后验坍缩——模型干脆不用潜变量,退化成普通自编码器。二分类用的是交叉熵损失 -[y·log p + (1-y)·log(1-p)],等价于 sigmoid 输出加负对数似然,类别不均衡时用加权或 Focal Loss 调整;几何间隔路线则是 hinge loss。

手撕题

岛屿数量的三种写法都可以,DFS 和 BFS 的区别只在遍历顺序:扫整个网格,遇到 1 答案加一,然后从该点开始把相连的 1 全部「淹没」(原地改成 0,省掉 visited 数组),DFS 用栈或递归、BFS 用队列;网格很大时递归会爆栈,另外并查集也可以做,只是没必要。注意边界判断和「只统计上下左右四个方向」这个前提。

rand7() 实现 rand10() 用拒绝采样:先构造均匀的 149((rand7()-1)*7 + rand7()),丢掉 4149 重采样,剩下的 (x-1) % 10 + 1 就是均匀的 110;期望调用约 2.45 次 rand7,也可以先把 140 映射好再重采样,写法更直观。

冒泡排序注意外层只需要 n-1 轮、内层比较相邻元素并交换,加一个 swapped 标记可以在已经有序时提前退出;「用内置 sort 对照、assert 验证」的正确姿势是随机生成一批数组,对排序前后的结果做断言,并显式覆盖边界用例(空数组、单元素、全相等、已排序、完全逆序),而不是只测一组数据。