米哈游 大模型算法 面经:LoRA 一致性、Agent 与推理优化
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 自我介绍。
- 手撕代码:ACM 模式,考察输入输出处理。
- 平时习惯用什么语言?
- 用过 Agent 吗?
- LoRA 的原理是什么?
- 微调之后如何控制输出的一致性?
- 处理数量很大时,怎么保证 QPS?
- 实习期间大模型节点验证具体做了什么?
- 60 多个自动化验证 case,挑一个典型的说说它验证了哪些功能?
- 这些 case 基本都是分支覆盖类型的吗?
- 还有哪些回调型用例?
- 这些用例主要处理哪些问题?
- 多模态模型怎么处理视频输入?
- 多模态大模型产生幻觉的原因是什么?
- 怎么通过后训练、RAG、规则来缓解视觉幻觉?
- embedding 和 reranker 分别解决什么问题?
- 大规模搜索的 ANN 算法怎么选择?
- 手撕:岛屿数量,分别用 DFS 和 BFS 实现。
- 生成式模型的基础原理是什么?
- VAE 的损失函数是什么?
- 二分类的损失函数是什么?
- Transformer 的整体架构是怎样的?
- Attention 的计算复杂度是多少?
- KV Cache 的原理是什么?
- GQA、MLA 的原理是什么?
- vLLM 的原理是什么?
- Flash Attention 的原理是什么?
- 稀疏注意力的原理是什么?
- 模型推理慢的排查思路是什么?
- 手撕:用 rand7() 实现 rand10()。
- 手撕:冒泡排序,自己实现一遍,再用内置 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(49 重采样,剩下的 (rand7()-1)*7 + rand7()),丢掉 41(x-1) % 10 + 1 就是均匀的 110;期望调用约 2.45 次 rand7,也可以先把 140 映射好再重采样,写法更直观。
冒泡排序注意外层只需要 n-1 轮、内层比较相邻元素并交换,加一个 swapped 标记可以在已经有序时提前退出;「用内置 sort 对照、assert 验证」的正确姿势是随机生成一批数组,对排序前后的结果做断言,并显式覆盖边界用例(空数组、单元素、全相等、已排序、完全逆序),而不是只测一组数据。