:RAG 与微调怎么配合:知识的存放位置决策)
承接与场景上一篇把分诊模型送上了生产但整个系列反复悬着一个承诺第一篇说过知识进 RAG、行为进权重可真实请求从来不是按教科书分类的——一句我们部门新政策下出差住宿能报多少同时含有身份事实、政策知识和报销话术。本篇给出一套可执行的分拣规则场景换成制药公司的内部政策问答基座 一套 HR/财务/合规文档库 已具备微调能力的基础设施要回答每条知识到底放哪。两个实验先用纯 Python 实现的 BM25 把检索层的召回能力量出来顺带展示查询改写这一层值不值得微调一个小模型来做再算清稳定内容进权重 vs 进上下文的成本账。生产检索可换 BM25 索引Elastic/OpenSearch 向量召回双路机制与本篇的度量方法一致。一条查询的三种成分把用户请求拆开成分只有三种易变事实政策额度、流程版本、库存数字——版本管理、可溯源是刚需放文档库永远不进权重稳定行为用公文语气回答、按结论→依据→办理入口组织、超纲转法务——变化以季度计、且 prompt 约束不稳定第六篇见过基座缺分诊等级字段直接拉低半分进权重桥接技能把口语映射到制度术语请假→休假审批、盖印→用印——这是两边都不完全属于的能力恰好落在中间而它常常是检索质量的第一瓶颈。由此得到存放位置的三条决策规则。第一改一次知识就要重训一次的东西不配进权重——这是第一笔成本账第一篇的 TCO 模拟。第二每次推理都要付的钱量大就得进权重——稳定段塞在 prompt 里请求量 × 段长就是纯浪费的预填充第二笔账。第三检索器接不住的表达要么修检索要么修表达——用户的话和文档的话对不上时加一个翻译层而这个翻译层可以是一次微调术语改写模型、一个词典、或让业务模型顺手在输出里带改写结果。三条规则里只有第三条需要实验验证前两条算账就行——正好两篇各证一条。实验一检索召回实测改写层值一个微调政策问答的语料与考题如下十条制度文档六个口语化查询人话版目标文档手工标注。BM25 用字符 2-gram 切分、经典参数 k11.5、b0.75对照组给查询加一层同义词改写模拟微调改写模型的输出——真实项目里这就是一个几百样本 LoRA 干的活检索层实测: BM25 召回3 同义词改写(微调模型可承担的活), 纯 Python。importmathimportre DOCS[员工休假审批流程:年假3天以内主管审批,超过3天需部门总监批准。,费用核销指引:差旅发票须在30日内提交财务共享中心核销。,值班与夜班津贴发放标准按人力资源部2024年12号文执行。,公章使用申请:合同类用印需法务会签后到行政部办理。,IT设备报修流程与备件更换周期说明。,在职证明与收入证明开具指南。,会议室预订系统与临时占用冲突处理规则。,访客登记与园区门禁卡办理须知。,数据安全分级与外发审批管理办法。,体检安排与商业保险理赔联系人清单。,]QUERIES[(请假要找谁签字,[0]),(出差的车票钱什么时候交上去,[1]),(通宵班有没有钱拿,[2]),(协议盖印要去哪里办,[3]),(电脑出问题找谁修,[4]),(想要一份工资的单子,[5]),]SYN{请假:休假,签字:审批,车票:发票,通宵:夜班,盖印:用印,协议:合同,电脑:设备,工资:收入}deftokens(s):sre.sub(r[^\u4e00-\u9fffA-Za-z0-9],,s)return[s[i:i2]foriinrange(len(s)-1)]defbm25(query,k11.5,b0.75,topk3):docs[tokens(d)fordinDOCS]dl[len(t)fortindocs]avgdlsum(dl)/len(dl)df{}fortindocs:forginset(t):df[g]df.get(g,0)1scores[0.0]*len(DOCS)forqginset(tokens(query)):wmath.log(1(len(DOCS)-df.get(qg,0)0.5)/(df.get(qg,0)0.5))fori,tinenumerate(docs):ft.count(qg)iff:scores[i]w*f*(k11)/(fk1*(1-bb*dl[i]/avgdl))returnsorted(range(len(DOCS)),keylambdai:-scores[i])[:topk]defexpand(query):extra[vfork,vinSYN.items()ifkinquery]returnquery .join(extra)fortag,convin((原始查询,lambdaq:q),(同义词改写后,expand)):rec0.0print( %s %tag)forq,relinQUERIES:hitbm25(conv(q))rlen(set(hit)set(rel))/len(rel)recrprint( q%-18s top3%s 命中%s%(q[:16],hit,√ifrelse×))print( 平均 recall3 %.2f%(rec/len(QUERIES)))运行输出 原始查询 q请假要找谁签字 top3[0, 1, 2] 命中√ q出差的车票钱什么时候交上去 top3[0, 1, 2] 命中√ q通宵班有没有钱拿 top3[0, 1, 2] 命中√ q协议盖印要去哪里办 top3[0, 1, 2] 命中× q电脑出问题找谁修 top3[0, 1, 2] 命中× q想要一份工资的单子 top3[0, 1, 2] 命中× 平均 recall3 0.50 同义词改写后 q请假要找谁签字 top3[0, 8, 1] 命中√ q出差的车票钱什么时候交上去 top3[1, 0, 2] 命中√ q通宵班有没有钱拿 top3[2, 0, 1] 命中√ q协议盖印要去哪里办 top3[3, 0, 1] 命中√ q电脑出问题找谁修 top3[4, 0, 1] 命中√ q想要一份工资的单子 top3[5, 0, 1] 命中√ 平均 recall3 1.00recall3 从 0.50 到 1.00全部增益来自一层薄薄的术语改写。更有教学价值的是原始查询那一半前三个命中其实是假象——查询词与文档零交集时 BM25 全体得分 0排序退化成文档序号恰好前三题的答案在库靠前。这暴露了只看聚合指标的危险0.50 的分数里一半是运气逐条看 top3 列表才知道检索根本没工作第六篇分桶看明细的纪律在这里同样适用。业务含义随之清晰政策问答失败的第一归因往往不是知识没入库而是用户词表与制度词表没对上。修法有三档维护同义词典便宜但静态、微调一个查询改写模型本篇 SYN 表的学习版几百条样本就够正好是第三篇 LoRA 用武之地、或让生成端模型在回答前先输出标准化查询微调模型的行为规格之一。三档的共同点改写发生在权重或词典里知识库本身一个字不用动——这正是知识在库、表达在模的分界。实验二稳定段的钱该谁付分拣规则的第二条要算细账。政策问答有三个高频场景各自的系统 prompt角色 制度口径 输出格式模板长度 1400~2200 token另有每次真正变化的检索证据 600 token。两种架构prompt 工程版把稳定段全文拼进每次请求微调版把稳定段烙进权重、每次只留 300 token 最小指令。按 7B 模型每 token 预填充的 GPU 秒折算一万条请求并对比开启前缀缓存vLLM automatic prefix caching同前缀的 KV 块直接复用的情形稳定内容放权重还是放上下文: 预填充开销与前缀缓存模拟, 确定性。importrandom N,TF,MFU7e9,312e12,0.25SEC_PER_TOKEN2*N/(TF*MFU)# 每 token 预填充的 GPU 秒SCEN{A 休假审批:1800,B 报销指引:1400,C 门禁故障:2200}# 稳定段 tokenVAR600# 每次请求真正变化的检索片段rngrandom.Random(7)REQSrng.choices(list(SCEN),weights[0.6,0.25,0.15],k10000)defprefill_seconds(fine_tuned,cache):seen,totalset(),0.0forsinREQS:stable300iffine_tunedelseSCEN[s]ifcache:ifstableinseen:stable0# 前缀命中, KV 直接复用seen.add(300iffine_tunedelseSCEN[s])total(stableVAR)*SEC_PER_TOKENreturntotalprint(每 token 预填充 %.2e GPU 秒; 一万条请求%SEC_PER_TOKEN)print(%-26s %-10s %10s %12s%(架构,前缀缓存,GPU秒合计,单请求增量延迟))forft,labelin((False,prompt 工程(稳定段进上下文)),(True,微调承载稳定段600知识)):forcachein(False,True):secprefill_seconds(ft,cache)add(300ifftelsemax(SCEN.values()))*SEC_PER_TOKENprint(%-26s %-10s %8.0f %10.2f 秒%(label,开ifcacheelse关,sec,add))print(上下文占用: prompt 工程最高 %d token vs 微调方案 %d token (窗口余量全留给证据)%(max(SCEN.values())VAR,300VAR))运行输出每 token 预填充 1.79e-04 GPU 秒; 一万条请求 架构 前缀缓存 GPU秒合计 单请求增量延迟 prompt 工程(稳定段进上下文) 关 4235 0.39 秒 prompt 工程(稳定段进上下文) 开 1078 0.39 秒 微调承载稳定段600知识 关 1615 0.05 秒 微调承载稳定段600知识 开 1077 0.05 秒 上下文占用: prompt 工程最高 2800 token vs 微调方案 900 token (窗口余量全留给证据)账本给出了一个不许和稀泥的结论单看缓存开这一列prompt 工程1078 GPU 秒和微调1077 GPU 秒打成平手——前缀缓存确实能把重复付钱抹平第一遍的拷贝税只交一次。所以微调省钱在高缓存命中下是伪命题真正拉开差距的是缓存救不了的三样东西。其一是增量延迟缓存未命中冷启动、逐出、多租户挤兑、模板微调一字都会击穿缓存时prompt 工程版每请求多付 0.39 秒预填充微调版 0.05 秒其二是窗口预算2800 vs 900 的占用差决定了还能塞几份证据、能不能容得下长文档全文而检索质量的天花板恰恰是多召回几篇再让模型挑其三是行为确定性缓存只复用计算不复用模型听话——三千 token 的格式说明依然可能被忽略第六篇基座失败模式权重里的行为不会。所以决策表最终写为稳定段 800 token 或缓存命中率 90% 或格式合规是硬要求 → 微调承载知识永远进文档库改写层单独一个小组件——三层的钱各记各的账。常见陷阱拿微调模型背文档库把政策原文塞进 SFT 数据教给模型下个版本发布就是一场考古第一篇的账 第七篇的遗忘。权重只背行为文档永远在库里。recall 指标不逐条看全零得分退化成序号排序平均分能骗过聚合报表实验一现场演示。前缀缓存当成免费午餐模板一改即失效、多租户互相逐出容量规划要按缓存全失的最坏列做。检索和生成各自调优不闭环改写层改了检索评测却只测端到端——必须像本篇一样分段量recallk 归检索合规率/引用率归生成否则增益永远归因不清。让 RAG 背格式、让微调背时效两个仓库管反——合规话术在检索片段里每次漂、政策日期在权重里从不更新全线都是错的。落地清单需求分拣三列易变事实→文档库稳定行为→权重桥接改写→独立组件词典起步LoRA 升级检索指标分层常备recallk 离线跑本篇脚本即模板每周随知识库更新重跑前缀缓存纪律系统模板入哈希版本管理接第八篇 MANIFEST模板变更 主版本变更混合架构压测跑缓存全失与全命中两端报表取 P95 输出长度口径每季度复审一次存放位置决策表知识更新频率与查询量都会变账要重算九篇的方法论都齐了。终章《LLM 微调实战10端到端实战从 0 训练一个行业客服模型并跑通》把十篇的知识串成一条完整流水线立项算账、数据、训练、评测、防遗忘、上线、混合架构一次走完并收尾整个系列。参考来源Retrieval-Augmented Generation for Knowledge-Intensive NLP Taskshttps://arxiv.org/abs/2005.11401Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflectionhttps://arxiv.org/abs/2310.11519Okapi BM25 公式与性质https://en.wikipedia.org/wiki/Okapi_BM25vLLM automatic prefix caching 文档https://docs.vllm.ai/en/stable/Dense Passage Retrieval for Open-Domain Question Answeringhttps://arxiv.org/abs/2004.04906