1000道大模型算法工程师面试题汇总(1-35部分 · 将持续更细)

这是第十三部分的详细参考答案。这部分内容聚焦于 RAG(检索增强生成)的业务实战与性能优化,这是目前大模型落地应用最广泛、也是坑最多的领域。


第十三部分:RAG 业务落地与性能优化 (251-275)

251. 当知识库文档数量达到千万级,Milvus/FAISS 的检索延迟明显变高,你做过分片(Sharding)吗?是按业务分还是按 ID Hash 分?

答案:

  • 必须分片: 千万级数据如果全在单机内存做 HNSW 索引,构建慢且检索延迟会超过 100ms。
  • 分片策略:
    • 按业务分(推荐): 比如按 tenant_id(租户)、year(年份)或 category(文档类别)。
    • 优势: 检索时可以带上过滤条件(Partition Key),利用 Partition Pruning(分区裁剪),直接只搜某一个分区。这样检索范围瞬间从一千万降到几万,速度极快。
    • 按 Hash 分: 仅在需要全局检索且无法预知业务过滤条件时使用,主要为了负载均衡,但不能减少单次检索的计算量。
252. 在 RAG 流程中,Embedding 模型的推理往往是耗时大户。你有做过 Embedding 模型的并发优化吗?比如 Batch Size 设多大?

答案:

  • 优化手段:
    1. Dynamic Batching(动态批处理): 不要来一条请求算一条。使用 TEI (Text Embeddings Inference)Triton 服务,它会自动积攒 10ms 内的请求,拼成一个 Batch 一起算。
    2. Batch Size 设置: 在 T4 或 A10 显卡上,Embedding 模型的 Batch Size 通常可以设得很大(如 32 到 128)。因为 BERT 类模型参数少,显存不是瓶颈,计算才是。
    3. ONNX/TensorRT 加速: 将 Embedding 模型转为 ONNX Runtime 运行,CPU 端延迟能降 50%。
253. 用户的 Query 经常非常短(比如“报销”),直接检索效果很差,你做了什么 Query Rewrite(改写)策略?改写这一步会增加多少延迟?

答案:

  • 策略:
    1. LLM 改写: Prompt: “用户搜索了 ‘报销’,请将其扩展为完整的疑问句”。 -> “公司的差旅报销流程和政策是怎样的?”。
    2. 关键词扩展: 如果 LLM 太慢,用同义词库或 NLP 工具做简单的 Keyword Expansion。
  • 延迟:
    • 用 7B 模型改写至少增加 200ms - 500ms,这对用户体验有伤害。
    • 优化: 并行执行。一边用原始 Query 去搜(保底),一边异步调用 LLM 改写后做二次搜索(精搜),或者训练一个极小的 T5 模型专门做改写(延迟 < 50ms)。
254. 你们的 Chunking(切片)策略对性能有影响吗?切太小会导致检索次数变多,切太大会导致 Embedding 计算慢,这个平衡点怎么找?

答案:

  • 平衡点: 经验值是 256 - 512 Tokens
  • 性能影响:
    • 切太小(如 64): 召回数量(Top-K)必须设很大才能凑齐上下文,导致 Rerank 阶段压力巨大,拖慢整体速度。
    • 切太大(如 1024): Embedding 模型(通常最大支持 512)会截断,导致后半部分语义丢失。
  • 最佳实践: Parent-Child Indexing(父子索引)
    • 索引时: 切小块(Child, 128 tokens)做 Embedding,检索更精准。
    • 召回时: 根据命中的 Child ID,返回其对应的父块(Parent, 512-1024 tokens),喂给 LLM。既保证了检索准度,又保证了上下文完整。
255. Rerank(重排序)模型通常很重(Cross-Encoder),如果对 Top-50 进行重排太慢,你会怎么优化?(比如蒸馏、或者先过滤一部分)

答案:

  • 瓶颈: Cross-Encoder 是 O ( N ) O(N) O(N) 复杂度,对 50 个文档打分可能需要 1-2 秒(CPU)。
  • 优化:
    1. 减少 N: 只重排 Top-20,剩下的直接截断。
    2. 模型蒸馏/量化: 使用 BGE-Reranker-M3 的量化版,或者 ColBERT(Late Interaction,速度介于 Bi-Encoder 和 Cross-Encoder 之间)。
    3. FlashRank: 使用基于 LightGBM 或极小 Transformer 的重排库,能在 CPU 上做到毫秒级。
256. 向量数据库的索引构建太慢了,每次更新数据都要等好久,你们是实时索引还是 T+1 离线构建?

答案:

  • 策略: 实时写入 + 异步合并
  • Milvus 机制: 数据插入后先进入 Growing Segment(未索引区,暴力搜索),此时数据立即可见。后台会有 Compaction 任务定期将 Growing Segment 合并并构建 HNSW 索引(Sealed Segment)。
  • 业务妥协: 用户刚上传的文档,允许它是暴力搜索(量少不慢);存量的千万级文档走 HNSW。不需要 T+1,实现了近实时(Near Real-time)。
257. 遇到过“热门文档”并发读取的瓶颈吗?如果所有人都问同一个政策文件,RAG 系统会不会卡住?怎么做缓存?

答案:

  • 会卡住: 主要是数据库 I/O 瓶颈(如果是从 MinIO/S3 拉取全文)。
  • 缓存策略:
    1. Content Cache: 在应用层(Redis)缓存 DocID -> Text Content。热门文档直接读 Redis,不读对象存储。
    2. Semantic Cache: 对于高频 Query(“年假怎么算”),直接缓存 Query Vector -> Final LLM Answer。完全跳过检索和生成过程,QPS 提升百倍。
258. 在解析 PDF 表格时,OCR 占用的时间太长,你们有没有试过用“多线程解析”或者“分布式解析”的任务队列?

答案:

  • 必须分布式: 单机解析一个 100 页的扫描件 PDF 可能要 5 分钟,会把 Web 服务拖死。
  • 架构:
    • 前端: 上传成功后立即返回 Processing 状态。
    • 后端: 将 PDF 按页拆分(Split Pages),把每一页的 OCR 任务扔进 Celery + Redis 队列。
    • Worker: 多个 GPU Worker 并行消费队列,使用 PaddleOCR/RapidOCR 进行识别。
    • 合并: 所有页处理完后,再 Reduce 成一个完整文档。
259. 如果检索回来的 Context 超长,超过了 LLM 的窗口限制,你是直接截断,还是做了关键信息提取(Summarization)?提取这一步怎么优化速度?

答案:

  • 处理超长: 直接截断是最烂的,Summarization 是最好的但最慢。
  • 折中方案(Selected Context):
    1. 基于并行的摘要: 如果必须要摘要,使用 Map-Reduce 思想,并发调用小模型(3B/7B)对每个 Chunk 做摘要,最后汇总。
    2. 基于困惑度的压缩(LLMLingua): 使用小模型计算 Token 的重要性(Perplexity),丢弃那些对语义贡献不大的停用词和废话,能压缩 30%-50% 且不怎么损失精度。
260. 混合检索(Hybrid Search)里,关键词检索(ES)和向量检索是串行还是并行执行的?怎么合并结果最快?

答案:

  • 执行: 并行 (Asyncio Gather)。
    • 同时发起 ElasticSearch 请求和 Milvus 请求。
  • 合并: 使用 RRF (Reciprocal Rank Fusion) 算法。
    • 公式: S c o r e = 1 k + r a n k 1 + 1 k + r a n k 2 Score = \frac{1}{k + rank_1} + \frac{1}{k + rank_2} Score=k+rank11+k+rank21
    • RRF 不需要归一化分数(向量分数是 0-1,ES 分数可能是 0-100),直接基于排名融合,鲁棒性最好,计算也极快。
261. 你们的 RAG 系统里,向量数据库是部署在 GPU 上还是 CPU 上?对于 10M 级别的数据,CPU 检索够用吗?

答案:

  • 部署: CPU 足够。
  • 算账:
    • 10M 向量(768维)大约占 30GB 内存。买一台 64GB 内存的服务器,跑 HNSW 索引,单次检索延迟在 10ms-20ms 级别。
    • GPU 向量库(如 Milvus GPU 版或 RAFT)主要用于 亿级数据 或者 超高 QPS(万级) 场景。对于 10M 数据,上 GPU 性价比极低,而且显存可能还不够装。
262. 在高并发下,Embedding 服务可能会遇到 Thread Pool 满的情况,你会怎么调整 Python 的线程池参数?

答案:

  • Python 限制: Python 线程受 GIL 限制,只对 I/O 密集型有效。
  • 场景区分:
    • Embedding 是远程 API: 也就是 I/O 密集型。可以开大线程池(比如 max_workers=50),或者直接用 aiohttp 做异步请求。
    • Embedding 是本地模型: 也就是 CPU/GPU 密集型。开多线程没用,反而增加切换开销。应该使用 Gunicorn 多进程 或专门的 Model Server (Triton/TEI) 来处理并发。
263. 针对长文档问答,Sliding Window(滑动窗口)会产生大量的切片,导致存储成本暴增,你们做过数据去重或压缩吗?

答案:

  • 去重:
    • 向量库层面: 仅存储 DocIDVector,不存 Payload(原始文本)。
    • 对象存储层面: 原始文本存在 MinIO。检索到 Vector 后,拿着偏移量(Offset)去 MinIO 读对应的文本。
    • 跨文档去重: 计算 Chunk 的 MD5。如果很多文档都是标准合同模板,大部分 Chunk 是一样的,只存一份。
264. 遇到过 Milvus 内存暴涨导致 Pod 重启的问题吗?通常是 query_node 还是 data_node 的问题?

答案:

  • 通常是 query_node
  • 原因:
    1. 加载过多 Collection: 用户创建了太多集合,Milvus 尝试把它们都 Load 进内存。
    2. Segment 碎片: 频繁删除未做 Compaction,内存里维护了大量的 Delete Bitmap。
  • 解决: 限制 queryNode.cacheSize 配置,设置内存淘汰策略(LRU),并定期 Release 不活跃的 Collection。
265. 你们怎么评估 RAG 的“端到端延迟”?从用户回车到第一个字出来,这一秒钟里,检索耗时占比多少?模型耗时占比多少?

答案:

  • Tracing 分析:
    • 总目标: < 1.5秒 (首字)。
    • Embedding: 50ms (TEI 加速后)。
    • Retrieval (Milvus): 30ms。
    • Rerank (关键瓶颈): 300ms - 500ms (如果用了 Cross-Encoder)。
    • LLM Prefill (关键瓶颈): 400ms (如果 Prompt 很长)。
    • 网络传输: 100ms。
  • 结论: 优化重点永远在 RerankLLM 推理 上。
266. 如果知识库需要频繁删除和更新,HNSW 索引的效率会下降,你们多久做一次 Rebuild?

答案:

  • 原理: HNSW 是图结构,物理删除节点很麻烦,通常是标记删除。标记多了,搜索时跳过的节点就多,效率下降。
  • 策略:
    • 自动 Compact: 向量数据库通常有自动合并小 Segment 的机制。
    • 强制 Rebuild: 如果软删除比例超过 20%,我们会安排在凌晨低峰期做一次 force rebuild,虽然耗时,但能把检索性能恢复到最佳状态。
267. 在做多路召回时,每一路的权重(Weight)你们是写死的,还是根据用户反馈动态调整的?

答案:

  • 写死的(经验值): 通常是 Dense(0.7) + Sparse(0.3)。因为语义检索通常更重要,关键词检索作为补充。
  • 动态调整(进阶): 很难做。需要训练一个 Query Classifier
    • 如果是查“错误码 503”,分类器识别为“精确匹配需求”,动态提高 Sparse 权重。
    • 如果是查“服务器为什么慢”,分类器识别为“语义需求”,动态提高 Dense 权重。
268. 遇到过 RAG 返回结果包含重复内容的情况吗?在 Prompt 拼接阶段你是怎么去重的?

答案:

  • 现象: 多个 Chunk 讲的是同一件事(可能是文档里的重复段落,或者是滑动窗口重叠)。
  • 去重:
    1. 文本去重: 计算 Chunk 之间的 Jaccard 相似度编辑距离。如果相似度 > 0.9,直接扔掉一个。
    2. 包含关系: 如果 Chunk A 是 Chunk B 的子串,扔掉 A。
    3. LLM 去重: 在 Prompt 里加一句:“Ignore duplicated information in the context.”(效果一般,不如工程上去重稳)。
269. 你的 RAG 系统支持流式输出吗?检索步骤还没做完的时候,前端是转圈圈还是显示“正在思考”?

答案:

  • 用户体验优化:
    • 不能干等: 检索 + Rerank 可能要 1 秒,这 1 秒如果白屏,体验很差。
    • Server-Sent Events (SSE): 我们会通过 SSE 推送中间状态事件。
    • 前端展示: Checking Knowledge Base... -> Found 5 documents... -> Reading... -> [LLM Token流].
    • 这样用户知道系统在干活,容忍度会变高。
270. 针对“意图识别”模块,如果用大模型做太慢,你们有没有训练一个小模型(比如 BERT)专门做意图分类?

答案:

  • 必须用小模型。
  • 选型: DistilBERT 甚至 FastText
  • 训练: 收集几千条历史 Query,标注意图(闲聊/知识问答/操作指令)。训练一个分类器,推理只需 5ms
  • 短路逻辑: 只有分类为“知识问答”的,才走 RAG 流程;“闲聊”直接走大模型;“操作”走 Agent。
271. 你们的数据清洗管道(ETL)是用 Python 单机跑,还是上了 Spark/Flink?处理 100G 的 PDF 需要多久?

答案:

  • Python 单机: 处理 100G PDF 肯定挂,内存溢出且慢。
  • 架构: Ray Data 或者 PySpark
    • 我们用 Ray 比较多,因为它对 Python 生态(PyTorch/OCR)支持更好,方便调度 GPU 资源做 OCR。
  • 耗时: 100G PDF 大约有 5-10万份文件。用 10 个节点的 Ray 集群(每节点 48核),大概跑 2-3 小时 能完成清洗、切片和 Embedding。
272. 向量数据库的 DiskANN 索引你用过吗?在内存放不下的情况下,它对 SSD 的 IOPS 要求高吗?

答案:

  • 场景: 当数据量达到 亿级,内存买不起了,就用 DiskANN(Vamana 图)。
  • SSD 要求: 极高。 必须是 NVMe SSD。
  • 原理: DiskANN 把图结构存在磁盘上,内存里只存压缩的导航点。检索时需要频繁随机读取磁盘(Random Read)。如果 SSD IOPS 不够(比如用了普通的云盘),QPS 会跌成渣。
273. 如果用户问的问题跟知识库完全无关,怎么让 RAG 快速短路(Short-circuit),直接拒答,而不去查库浪费时间?

答案:

  • 检索后拒答: 拿到 Rerank 后的 Top-1 分数。如果分数低于 阈值(比如 0.5),说明库里根本没有相关内容。
  • 动作: 直接返回预设话术:“抱歉,知识库中没有找到相关信息。”
  • 好处: 节省了最贵的 LLM 生成费用。
274. 怎么处理“表格+文字”混合的 PDF?你们是把表格转成 Markdown 还是 HTML 喂给模型?哪种效果和性能最好?

答案:

  • 最佳实践: Markdown。
  • 原因:
    • Token 效率: HTML 标签(<tr>, <td>)太多,浪费 Token。Markdown 表格简洁。
    • LLM 理解: 大模型对 Markdown 的结构理解能力很强。
  • 工具: 使用 UnstructuredLlamaParse 等专门的文档解析服务,它们能识别表格区域,重建成 Markdown 格式插入到文本流中。
275. 你的 RAG API 经常超时(Timeout),排查下来通常是 Embedding 卡了还是 LLM 卡了?

答案:

  • 排查结论: 90% 是 LLM 卡了 或者 数据库连接池满了
  • LLM 卡: 并发上来后,LLM 的等待队列变长,Prefill 变慢。
  • 数据库卡: 向量库虽然快,但用来存元数据的 MySQL/PostgreSQL 可能因为并发高锁死了。
  • Embedding: 通常很稳,除非你把它和 LLM 部署在同一张显卡上,显存被抢占了。

1000道大模型算法工程师面试题汇总(1-35部分 · 将持续更细)

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐