专业书籍精读 · Generative AI System Design Interview · 第 6 章
Generative AI System Design Interview · Ch 6 · Ali Aminian, Hao Sheng & Alex Xu · 2024
你问公司的 AI 助手「出差住宿能报多少」,它答得又快又顺——可这条规定上个月才改,它凭什么知道?这一章讲的就是让机器先翻资料、再开口的那套办法。
一个不知道答案的人,通常会说「我不清楚」。可这类机器不会。它没学过的事,它照样答,而且答得和真话一模一样顺。更糟的是你没法追问它「这是哪来的」——它自己也说不清,一切都糊在它读过的几万亿字里,指不出具体哪一句。
两条老路都走不通。一条是重新教它一遍:把新文件塞进教材让它重读——这像为改一个电话号码重印整本字典,贵、慢,而且下周文件又改了。另一条是全摊在桌上:把整个档案室搬到它面前。桌子就那么大,摊不下;勉强摊下了,一屋子纸里找一句话也容易看花眼,越靠中间的越容易被跳过去。
真正管用的办法是请一个图书管理员,分两步走。
第一步是平时的活:整理卡片柜。把文件提前裁成一段段卡片,再按意思归档——说同一件事的放一起,哪怕用词完全不同(「住宿标准」和「酒店费用上限」进同一格)。这活在没人提问时就做完了。
第二步是你提问的那一刻:管理员冲进卡片柜抓出最像的一百张,筛一遍留下最相关的五张,连同问题递给答题的人。答题人手上只有这五张,规矩只有一条:只准照卡片上写的答,还得标出这句出自哪一张。没抓到卡片时,正确答法是「档案里没有」。
三样好处立刻到手:文件今天改今天生效,不用重教;答案能顺着标注翻回原文核对;不许这个人看的文件,在抓卡片那步就拦掉了。代价只有一句,但很硬:上限被「抓卡片」那一步锁死——卡片抓错,后面答得越流畅越糟,因为错得像真的。
它的产出不只是一个答案,而是一个答案外加它的出处。你以为这套东西是在给机器补知识,其实是在把「凭空说」改成「照着说」——从此每句话都有地方可查。
想进到具体机制、记号和示意图? → 切到精读版
你以为检索增强生成(RAG,retrieval-augmented generation)是「给模型补知识」的省钱替代品,其实它真正交付的是一条可核对的证据链:知识当天可换、来源可点回、答错了能定位到是哪一环坏的——这三样微调和超长上下文都给不了。而整章最该记住的工程结论只有一句:RAG 的质量上限由检索那一半决定,生成模型只是在这个上限之下打折。
384–1536 维),意思相近的文字数字也相近。Part II 十道真题的第五道,也是文本簇的收官题。上承 Ch4 ChatGPT——那里的知识全靠参数记忆;这一章第一次把外部知识库接进生成链路,Ch7 起则转入图像生成。现实对应企业知识库问答、客服助手、文档助理,以及今天所有带「引用来源」的搜索型 AI 产品。
参数记忆有三宗罪,且每一宗都不是把模型做大能解决的。
一是过期。权重冻结在训练截止那天,而知识库每天都有文档在改。为一次政策更新重跑微调,从数据准备到评估上线通常以周计,而文档改动以分钟计——差了四五个数量级,不是调优能补上的。
二是指不出出处。模型说「住宿上限 800 元」时,这个数字糊在几万亿 token 的语料里,定位不到任何一句原文。而在法务、医疗、金融场景,不能核对的正确答案和错误答案价值相同——你分不出手里这条是哪种。
三是私有数据进不去。内网文档、用户自己的资料、有权限分级的材料,不该也不能塞进一份全员共享的权重——权重没有 ACL(访问控制表),一旦记住,就没法只对一部分人忘掉。
不解决会怎样?模型在它不知道的事情上依然流畅作答,且全链路没有任何一处可供核对。这正是上一章那个结论的升级版:生成式系统最危险的失败不是答不上来,是错得流畅。
整条流水线被一条硬边界切成两半,看懂它,后面所有权衡都好解释。
离线(索引期):文档 → 分块 → 逐块算嵌入 → 写进向量索引。百万级文档切成千万级片段、算完嵌入建好索引,是个批处理作业,耗时按小时算——因为没人在等。
在线(问答期):问题 → 算嵌入 → 检索 → 重排序 → 拼进 prompt → 生成。整体要压在一两秒内,而生成往往就吃掉一秒以上,留给检索加重排的常只有 100–300 毫秒——这个预算才是后面索引选型的真正约束。
检索的单位不是文档,是块。块怎么切,几乎决定了后面能不能检到。
直觉是「越小越准」:一块只讲一件事,嵌入不被稀释,容易对上。但小块会把完整论述撕成两半——模型拿到半截话,更容易自行补全出错的那一半。块太大则一块混了五六个主题,嵌入被平均成一个谁也不像的向量,检索分数低,还白占 prompt 预算。
常见落点是 200–500 token 一块、相邻块留 10%–20% 重叠(overlap,防止答案正好卡在切口上)。但比调参更重要的是三件事:按结构切(沿标题与段落边界,别按固定字数把表格拦腰砍断);带上元数据(标题、章节路径、日期、权限标签,既用于过滤也用于排序);上下文化——把所属章节标题或一句摘要拼回块首。最后一条针对一个具体毛病:一块写着「该上限自次月起调整为 1000 元」,孤立看根本不知道说的是哪项费用,检索永远对不上。
两条路的失手方式几乎正好互补。
BM25(关键词)在精确串上无敌:错误码 ERR_5021、型号 X-200、人名、法条编号——这些东西算嵌入毫无意义,字面匹配却百发百中。死穴是同义改写:用户问「怎么退钱」、文档写「退款流程」,词面不重合就是零分。
稠密检索(向量)正好相反:能跨过用词差异抓语义,却常在罕见专名上翻车——嵌入模型没见过这个型号,就当它是一串普通字符,返回一堆「看着像技术文档」的无关块。
混合检索把两路的排名合起来(常用倒数排名融合 RRF:按名次而非分数加权,省掉两套分数量纲不一致的麻烦)。这是绝大多数生产系统的默认选择,代价是两套索引都要维护、还多一个融合权重要调。
另一层是怎么在千万级向量里快速找出最近的几条。逐条算距离召回 100%,但千万量级下单次查询要几百毫秒到秒级,直接撑爆预算。ANN 用结构换速度:HNSW 建一张分层邻居图,查询从稀疏的顶层起跳、逐层逼近,能压到毫秒级,代价是可能漏掉真正的最近邻。漏多少由搜索宽度这一个旋钮控制,调大则召回和延迟同涨。它是连续可调的旋钮、不是开关——该当成 SLO 的一部分来定,而不是抄个默认值。
检索阶段是双塔:问题和片段各自独立编码,片段向量能离线算好存起来——这正是它快的原因,但两边从未「见过面」,只能比粗略的相似度。
重排序器反过来:把问题和片段拼成一条送进模型一起算,能捕捉「这段是否真的回答了这个问题」。代价是它没法预计算,每个候选都要现场跑一次——所以它比双塔慢一到两个数量级,只能用在少量候选上。标准做法因此是漏斗:ANN 粗召 100 条 → 重排精算 → 留 3–5 条进 prompt。
「装配」还有两个易漏的细节。其一,位置有影响:摆在长上下文正中间的材料最容易被忽略,所以最相关的片段该放首尾,而不是按相似度一路铺下去。其二,每块都按 token 收费、也按 token 吃延迟:5 块 × 400 token = 2000 token,每问一次付一次。k 从 5 调到 20,答对率通常不怎么涨,账单和首字延迟却实打实地涨。
拿到片段后,生成的任务不是「发挥」,而是受约束地复述。三条落地要求:提示里写死「只依据给定材料作答」;逐句挂片段编号,让引用能被程序校验而不只是给人看;允许说不知道——检索分数低于阈值或片段互相矛盾时,正确输出是「材料中未提及」。
最后一条是整套系统里最反直觉的产品决策:拒答率是一个要主动调的旋钮,不是一个越低越好的缺陷。企业问答场景宁可多拒答几个百分点,也不愿放出一条看不出错的编造。
第一个决策在动手之前:这份知识到底该怎么进模型。
表 1 · 四条给模型补知识的路
| 直接写进提示词 | RAG | 微调 | 长上下文全塞 | |
|---|---|---|---|---|
| 知识多新 | 实时 | 分钟级(重算那几块) | 以周计,且要重跑评估 | 实时 |
| 能否溯源 | 能 | 能,可逐句挂引用 | 不能 | 勉强,需模型自报位置 |
| 知识规模上限 | 几千 token | 几乎无上限 | 受训练语料限制 | 受上下文窗口限制 |
| 单次问答成本 | 最低 | 低(只带几块) | 低(不带材料) | 最高,每次都为整份资料付费 |
| 权限控制 | 调用方自己管 | 在检索层按元数据过滤 | 做不到,权重没有 ACL | 调用方自己管 |
| 主要代价 | 放不下多少东西 | 多一整套检索系统要维护和评估 | 贵、慢、会灾难性遗忘 | 延迟与账单随资料线性涨;中段材料易被忽略 |
| 适合 | 少量稳定规则 | 知识量大、常变、要可核对 | 改语气、学格式、固化领域风格 | 单份长文档的一次性问答 |
注意最后一行:微调和 RAG 从来不是竞品——前者改「怎么说」,后者补「说什么」,生产系统里常常同时存在。
第二个决策是检索路线,它直接决定 recall@k 的天花板。
表 2 · 四种检索配置
| 只用 BM25 | 只用稠密向量 | 混合 | 混合 + 重排序 | |
|---|---|---|---|---|
| 擅长 | 错误码、型号、人名、法条号 | 同义改写、口语化提问 | 两者兼顾 | 兼顾,且能判断「是否真的回答了」 |
| 典型失手 | 用户换个说法就零命中 | 罕见专名被当成乱码 | 融合权重调不好时互相拖累 | 预算吃紧时被迫砍候选数 |
| 在线开销 | 最低 | 低(毫秒级 ANN) | 两路并发,取较慢的一路 | 额外几十毫秒 + 一次模型调用 |
| 运维负担 | 一套倒排索引 | 一套向量索引 + 嵌入模型版本管理 | 两套索引都要保持同步 | 再加一个模型要监控 |
| 什么时候用 | 语料高度术语化、查询也术语化 | 原型验证、语料口语化 | 绝大多数生产系统的默认起点 | 精度敏感、且延迟预算允许 |
第三个决策是索引形态,本质是在召回、延迟、内存、更新代价四者之间挑两样。
表 3 · 三类向量索引
| 暴力精确检索 | HNSW(图) | IVF + 量化压缩 | |
|---|---|---|---|
| 召回 | 100% | 高,且可用搜索宽度连续调 | 中高,压缩越狠掉得越多 |
| 延迟 | 随数据量线性涨,千万级不可用 | 毫秒级 | 毫秒级 |
| 内存 | 存原始向量 | 最吃内存:原始向量 + 图结构 | 最省:向量被压成几十字节 |
| 增删改 | 最简单 | 可增量插入,长期大量删改后质量退化 | 需按批重建或定期重训码本 |
| 适合 | 十万条以内,或作为召回率基准线 | 单机放得下的主流规模 | 十亿级、内存是硬约束时 |
一条实用建议:无论最终上哪种索引,都先用暴力检索在一批标注好的问题上量一次 recall@k 当基准线。ANN 掉了多少召回、值不值,只有对着这条线才谈得清。
三个落点已在大规模跑:企业知识库问答(内网文档 + 权限过滤,商业价值最集中)、带引用的搜索型回答(答案下方挂可点来源,用户自己核对)、客服与技术支持(工单、手册、变更记录一起索引,答案能追到具体哪份文档的哪一段)。第三个最能看出它的真身:这不是一个更聪明的模型,而是把组织已有文字资产接进生成链路的基础设施。
① RAG 的真正产出不是答案,是可核对的证据链:知识分钟级更新、来源可点回、错了能定位到哪一环。
② 参数记忆的三宗罪——过期、指不出出处、私有数据进不去(权重没有 ACL)——做大模型都解决不了。
③ 流水线被切成两半:离线建索引按小时算,在线问答全程 1–2 秒,检索加重排只分到 100–300 毫秒。
④ 检索决定上限,生成只在上限之下打折;正确片段没召回,后面再强也救不回来。
⑤ 分块是最被低估的决策:常见 200–500 token、10%–20% 重叠,但按结构切、带元数据、把章节标题拼回块首,比调参更重要。
⑥ BM25 和向量的失手方式互补——前者死于同义改写,后者死于罕见专名;混合检索是生产默认。
⑦ ANN 用召回换速度,搜索宽度是连续旋钮;先用暴力检索量一条 recall@k 基准线,再谈掉多少值不值。
⑧ 漏斗形状固定:粗召 100 条 → 重排留 3–5 条;重排慢一到两个数量级,所以只能用在少量候选上。
⑨ 装配两条纪律:最相关的放首尾(中段易被忽略);k 调大账单和延迟实打实涨,答对率往往不涨。
⑩ 拒答率是要主动调的旋钮,不是缺陷;引用必须被程序校验,权限必须在检索层过滤。