面灵AI→

美团 AI 应用开发一面:召回串味、KV 缓存与手撕滑窗

轮次
一面
结果
已通过一面
时间
2026-09
来源
牛客网

《面试题目》

  1. 讲讲你的实习项目(电商客服),你遇到最坑的地方是什么?
  2. 召回串味怎么拆?(用户问「怎么退款」和「退款到哪了」老是撞到一起)
  3. Transformer 的注意力是怎么算的?KV 缓存为什么能省掉重算历史 token?
  4. 多轮对话的状态怎么存?
  5. 手撕:最长无重复子串
  6. 反问:组里线上用什么框架?

《参考解析》

召回串味:先拆意图,再拆粒度 「怎么退款」和「退款到哪了」词面重合度极高(都含「退款」),但一个是流程咨询、一个是订单状态查询,纯向量相似度检索必然串。三条可落地的改法:① 意图标签 + 路由——query 先过一层意图分类(流程类 / 状态类 / 投诉类),不同意图走不同的知识库分片和不同的 prompt 模板,从检索源头就分开;② 长答案做句子级切片——客服文档里「退款流程」和「退款进度查询」常常在同一篇长文里,段落级切片会把两件事塞进同一个 chunk,切成句子级并给 chunk 补元数据(所属子主题、适用意图)后命中精度明显上升;③ 查询改写与多路召回——用模型把口语 query 改写成检索友好的形式(补上「我的订单」这类隐含主体),再走「BM25 + 向量」双路召回后 rerank,让词面精确匹配(订单号、状态词)和语义匹配各发挥所长。上线后要建 badcase 回流机制,把撞车的 query 对做成对比样本放进评测集,否则改了一版根本不知道是真好了还是样本换了。

Transformer 注意力与 KV 缓存 自注意力是 Attention(Q, K, V) = softmax(QKᵀ / √d_k) V:Q 是当前 token 的查询,K/V 来自历史 token,除以 √d_k 是为了防止点积随维度增大把 softmax 推入饱和区(梯度消失)。自回归生成时第 t 步只依赖前 t 个 token,但朴素实现每生成一个 token 都要把整段历史重算一遍 Q/K/V,是 O(n²) 的重复计算。KV 缓存把每层已经算过的 K、V 存下来,新 token 只算自己的 Q/K/V 再拼到缓存尾部做注意力,单步复杂度从 O(n²) 降到 O(n)。

代价是显存:缓存大小约等于 2 × 层数 × 头数 × head_dim × 序列长度 × batch × 精度字节,长上下文下它会成为显存主要占用,所以才有 MQA/GQA(多个 query 头共享一组 KV 头)、PagedAttention(分页管理 KV、减少碎片)和 KV 量化这些优化。被追问「多轮状态怎么存」时,答案通常是「槽位结构化落库 + 只把最近几轮原文给模型 + 更早的做摘要」,而不是把全部历史塞进上下文。

多轮状态存储 三层结合:① 槽位结构化落库——订单号、商品、时间这类确定字段存数据库或 Redis,模型每轮只读,避免模型「记错」;② 最近 N 轮原文直接进上下文,保证代词消解和语气连贯;③ 更早的会话做滚动摘要,摘要只保留结论和未完成事项,不保留寒暄。要点是硬约束(用户明确说过「不要打电话」「只用中文回复」)不参与摘要,单独存成结构化字段每轮注入,否则多轮之后必然被忘掉。会话状态要设 TTL 和版本号,模型或 prompt 升级时要能平滑失效旧状态,不然会出现「用新 prompt 解释旧状态」的诡异回答。

手撕最长无重复子串 滑动窗口 + 哈希表记录字符最后出现的位置,右指针永不回退:遍历右指针 r,若 s[r] 已在窗口内(last[s[r]] >= l),把 l 直接跳到 last[s[r]] + 1,再更新 last[s[r]] = r,每步用 r - l + 1 更新答案。时间 O(n)、空间 O(字符集)。现场容易卡的就是「右指针回退」的错觉——只要把左边界跳过去,右指针根本不用动。写之前先确认字符集(ASCII 用 128 长度的数组更快,中文/Unicode 用哈希表),边界是空串返回 0。若继续被追问,可以主动提两个变体:「最多允许 K 个重复字符」(窗口内计数,超过 K 就收缩左边界)和「最长重复字符替换」。

这场面试的复盘 面试官用一句「你直接说遇到最坑的地方」打断了架构铺垫,说明一面更想听你自己做过的取舍而不是名词堆砌:讲清「问题现象 → 试过什么 → 为什么这么选 → 代价是什么」比背一堆框架名有用。反问环节问「线上用什么框架」「怎么压测吞吐」这类工程问题,既显得在意落地,也能拿到判断 offer 的信息——对方答「自研混 vLLM」,就可以顺着追问量化、批调度、GPU 利用率。面试当晚收到二面,也印证了「项目深挖讲透 > 八股背全」在一面的权重。