優(yōu)與生成約束)
RAG 這個詞這兩年出現(xiàn)的頻率越來越高但凡聊到讓大模型“說自己的話、答自己的料”基本繞不開它。我最早接觸 RAG 是在一個內(nèi)部知識庫問答的需求里當時團隊想讓模型能回答產(chǎn)品手冊、運維文檔里的細節(jié)問題直接微調(diào)成本太高、更新又慢于是把目光轉(zhuǎn)向了檢索增強生成這條路。實測下來RAG 確實是目前把私有數(shù)據(jù)接進大模型最務(wù)實的一種做法不用動模型權(quán)重數(shù)據(jù)更新只需重建索引回答還能給出出處排查問題時心里有底。這篇內(nèi)容我想把 RAG 的原理講透再把本地化落地這一整套流程拆開揉碎講清楚——從文檔怎么切、向量怎么選、檢索怎么調(diào)到生成環(huán)節(jié)怎么約束模型別亂編。適合剛接觸 RAG 想搞明白底層邏輯的開發(fā)者也適合已經(jīng)跑通 demo 但效果不穩(wěn)定、想找優(yōu)化方向的朋友。全文基于我在幾個內(nèi)部項目里的實際做法整理涉及參數(shù)的地方會給計算過程和取舍理由能直接抄作業(yè)的部分我會盡量寫細。1. RAG 到底在解決什么問題1.1 大模型的三個硬傷與 RAG 的定位先把問題擺清楚不然容易把 RAG 當成萬能藥。大模型有三個繞不過去的硬傷一是知識有截止時間訓(xùn)練數(shù)據(jù)之后發(fā)生的事情它不知道二是私有數(shù)據(jù)進不去公司內(nèi)部的文檔、代碼、工單它沒見過三是容易一本正經(jīng)地胡說也就是幻覺尤其在它不確定的時候反而答得更自信。解決這三類問題常見有三條路微調(diào)、長上下文直接塞、RAG。微調(diào)是把知識“焊”進權(quán)重里成本高、更新慢而且容易災(zāi)難性遺忘適合學(xué)風(fēng)格和格式不適合頻繁變動的知識。長上下文是把資料全塞進 prompt簡單粗暴但受窗口限制、成本隨長度線性上漲資料一多就撐不住。RAG 的思路不一樣它是在生成之前先去外部知識庫里檢索相關(guān)內(nèi)容把檢索結(jié)果作為上下文喂給模型讓模型“看著資料答題”。打個生活化的比方微調(diào)像是讓學(xué)生把整本教材背下來考試RAG 像是開卷考試允許帶資料進場答題前先翻到相關(guān)那幾頁。開卷的好處是資料可以隨時換、答案有據(jù)可查壞處是翻書翻得不準就會答偏——這也正是 RAG 效果好壞的關(guān)鍵所在。所以 RAG 的定位很明確它解決的是“知識從哪來”的問題不解決“模型會不會推理”的問題。檢索到的資料質(zhì)量決定了回答的上限模型的能力決定了能不能把資料用好。理解這個邊界后面調(diào)優(yōu)時就不會把檢索的鍋甩給模型也不會指望換個更大的模型就能救回爛檢索。1.2 RAG 的完整鏈路拆解一條完整的 RAG 鏈路我習(xí)慣拆成離線和在線兩段。離線段是數(shù)據(jù)準備文檔采集、清洗、切分、向量化、入庫這一步是一次性或周期性的。在線段是每次問答實時跑的query 進來先做向量化去庫里檢索 top-k 相關(guān)片段拼進 prompt交給模型生成最后把答案和出處一起返回。這里面每一環(huán)都有坑。切分切得不好一個完整語義被攔腰截斷檢索出來就是半句話向量模型選得不對語義相似度算不準該召回的沒召回top-k 設(shè)得太大噪聲片段擠占上下文模型反而被帶偏prompt 約束不到位模型看著資料還能自由發(fā)揮。我見過不少 demo 跑得挺順、一上真實數(shù)據(jù)就拉胯的案例問題基本都出在這幾個環(huán)節(jié)的細節(jié)上而不是框架本身。下面這張表是我對鏈路各環(huán)節(jié)影響權(quán)重的粗略判斷基于幾個項目的調(diào)優(yōu)經(jīng)驗不是絕對數(shù)值但能幫你判斷優(yōu)化優(yōu)先級環(huán)節(jié)對最終效果的影響調(diào)優(yōu)難度常見問題文檔切分高中語義截斷、塊過大過小向量模型高低換模型即可領(lǐng)域不匹配、中文支持差檢索策略高中高單一向量召回漏檢重排序中中排序不準、引入延遲Prompt 約束中高低模型自由發(fā)揮、不引用生成模型中低換模型即可指令遵循差從表里能看出來切分、向量、檢索這三塊是重頭戲也是本地化實踐里最花時間的地方。生成模型反而是最容易換的效果不行換一個就行但前面三塊爛了換再強的模型也白搭。2. 本地化落地的整體架構(gòu)設(shè)計2.1 為什么選擇本地化而不是調(diào) API先說清楚為什么要本地化。調(diào)云端 API 跑 RAG 當然省事但有幾類場景繞不開本地部署數(shù)據(jù)敏感不能出內(nèi)網(wǎng)、調(diào)用量大成本扛不住、需要離線可用、對延遲有硬要求。我參與的幾個項目基本都是前兩個原因內(nèi)部文檔涉及業(yè)務(wù)細節(jié)走外網(wǎng)不合規(guī)量大了按 token 計費一個月賬單能嚇死人。本地化的核心是把向量模型、向量庫、生成模型三樣都放在自己機器上。這里有個關(guān)鍵取舍生成模型本地跑對硬件要求最高7B 級別的模型量化后大概需要 6-8G 顯存13B 要 12-16G再大就得靠多卡或者更激進的量化。如果硬件有限一個折中方案是檢索和向量化本地做生成走內(nèi)網(wǎng)自建的推理服務(wù)既保證數(shù)據(jù)不出內(nèi)網(wǎng)又不至于被單機顯存卡死。我的建議是先把鏈路跑通再談性能。一開始別追求大模型7B 量化版足夠驗證流程等檢索調(diào)好了、prompt 定型了再根據(jù)效果決定要不要上更大的模型。很多人一上來就糾結(jié)模型選型結(jié)果檢索一塌糊涂換了三個模型都沒救回來白白浪費時間。2.2 組件選型與硬件估算組件選型我按“夠用、好維護、社區(qū)活躍”三個標準來。向量模型優(yōu)先看中文語義能力本地部署常用的是 BGE 系列或者 M3E 這類中文優(yōu)化過的模型維度一般是 768 或 1024。向量庫方面數(shù)據(jù)量在百萬級以內(nèi)輕量方案完全夠用沒必要上重型分布式庫數(shù)據(jù)量再大或者要支持復(fù)雜過濾再考慮更專業(yè)的向量數(shù)據(jù)庫。硬件估算給個參考算法。向量庫內(nèi)存占用大致等于向量條數(shù) × 維度 × 4 字節(jié)float32比如 50 萬條 768 維向量就是 500000 × 768 × 4 ≈ 1.5GB加上索引開銷按 1.5 倍算2GB 出頭很輕。生成模型這邊7B 模型 FP16 需要約 14GB 顯存4bit 量化后約 4-5GB加上 KV cache 和上下文開銷8G 顯存能跑但上下文別開太大。這些數(shù)字是我實測的粗略值具體會因框架和量化方式有浮動。注意向量維度不是越高越好。1024 維比 768 維召回略好但存儲和計算都漲三成數(shù)據(jù)量不大時收益不明顯。先用 768 維跑通效果不夠再升維。3. 文檔處理與切分RAG 效果的地基3.1 文檔清洗與格式統(tǒng)一這一步最枯燥但最不能省。真實文檔五花八門PDF 有頁眉頁腳、掃描件是圖片、Word 里有大量樣式標記、網(wǎng)頁抓下來一堆導(dǎo)航欄。直接把這些丟進去切分切出來的全是噪聲。我的做法是先統(tǒng)一轉(zhuǎn)成純文本或 Markdown再做一輪清洗。PDF 提取優(yōu)先用能保留結(jié)構(gòu)的解析方式掃描件得先走 OCR。清洗規(guī)則我一般會做這幾件事去掉連續(xù)空行和多余空格、剔除頁眉頁腳這類重復(fù)出現(xiàn)的行、把表格轉(zhuǎn)成結(jié)構(gòu)化文本表格直接切分很容易散架、統(tǒng)一標點和全半角。這里有個容易忽略的點保留標題層級信息。標題是天然的語義邊界切分時如果能帶上所屬章節(jié)標題檢索出來的片段可讀性和相關(guān)性都會好很多。清洗完建議抽樣人工看幾十條確認沒有大面積亂碼或結(jié)構(gòu)丟失。我踩過一次坑某批 PDF 提取出來中文全是亂碼沒檢查就入庫了結(jié)果檢索永遠召回一堆亂碼片段排查了半天才發(fā)現(xiàn)是提取環(huán)節(jié)的問題。3.2 切分策略與參數(shù)計算切分是 RAG 里最需要動腦子的環(huán)節(jié)。切太大一個塊里混了好幾個主題檢索精度下降切太小語義不完整模型拿到半句話沒法答。業(yè)界常用的起點是塊大小 500-800 字符、重疊 50-100 字符但這個值必須按你的文檔類型調(diào)。重疊的作用是防止語義在邊界被切斷。比如一句話正好跨在兩個塊之間有重疊就能保證至少有一個塊包含完整句子。重疊比例一般取塊大小的 10%-20%太小起不到保護作用太大又造成冗余存儲和重復(fù)召回。我一般用遞歸切分先按段落切段落超長再按句子切句子還超長才硬切。這樣能最大限度保住語義邊界。代碼上大致是這樣from langchain.text_splitter import RecursiveCharacterTextSplitter splitter RecursiveCharacterTextSplitter( chunk_size600, chunk_overlap80, separators[\n\n, \n, 。, , , , , ], ) chunks splitter.split_text(cleaned_text)注意 separators 里中文標點要放在英文空格之前因為中文文檔主要靠句號問號斷句。這個順序錯了切分質(zhì)量會明顯下降。另外技術(shù)文檔和 FAQ 類文檔的切分邏輯不一樣FAQ 最好一問一答作為一個塊別把問題和答案切開技術(shù)文檔按章節(jié)切更合理。沒有萬能參數(shù)只能拿真實數(shù)據(jù)試切完抽幾條看看是不是完整語義。3.3 元數(shù)據(jù)設(shè)計被低估的檢索增強手段很多人只存文本和向量其實元數(shù)據(jù)能大幅提升檢索質(zhì)量。我通常會給每個塊打上來源文件名、章節(jié)標題、頁碼、更新時間這些標簽。好處有兩個一是檢索時可以按元數(shù)據(jù)過濾比如只搜某個產(chǎn)品線的文檔二是生成時可以把出處帶給用戶答案可信度立刻不一樣。更進一步可以在塊文本前面拼上章節(jié)標題再向量化相當于給每個塊加了“上下文前綴”。實測這個做法對召回率有肉眼可見的提升因為標題里往往包含關(guān)鍵主題詞能幫向量模型更好地理解這個塊在講什么。成本幾乎為零強烈建議加上。4. 向量化與檢索調(diào)優(yōu)實戰(zhàn)4.1 向量模型的選擇與本地部署向量模型決定了語義相似度算得準不準是檢索質(zhì)量的天花板。選型時重點看兩點中文語義能力和領(lǐng)域匹配度。通用模型在通用語料上表現(xiàn)不錯但遇到專業(yè)術(shù)語密集的領(lǐng)域比如醫(yī)療、法律、工控召回會明顯下降。這種情況要么找領(lǐng)域微調(diào)過的模型要么自己拿領(lǐng)域語料做對比學(xué)習(xí)微調(diào)后者成本高但效果最實在。本地部署向量模型很輕一個幾百 M 的模型 CPU 都能跑批量向量化時用 GPU 加速會快很多。部署時注意歸一化算余弦相似度前要把向量歸一化成單位向量這樣內(nèi)積就等于余弦相似度檢索時直接用內(nèi)積索引更快。很多框架默認幫你做了但自己寫代碼時容易漏。批量向量化有個細節(jié)batch size 別設(shè)太大顯存不夠會 OOM也別太小吞吐上不去。我一般從 32 開始試看顯存占用往上加。另外入庫前一定要校驗向量維度一致混入不同模型產(chǎn)生的向量會導(dǎo)致檢索結(jié)果完全錯亂這種問題排查起來很痛苦。4.2 檢索策略從單一向量到混合召回單一向量檢索有個天然短板它對精確匹配不敏感。用戶問一個具體的型號、編號、專有名詞向量檢索可能召回一堆語義相近但沒命中關(guān)鍵詞的片段。解決辦法是混合檢索向量召回負責語義相似關(guān)鍵詞召回比如 BM25負責精確匹配兩路結(jié)果融合排序。融合常用 RRF倒數(shù)排名融合邏輯很簡單每個文檔在每路召回里的排名取倒數(shù)相加排名越靠前得分越高兩路都靠前的文檔最終得分最高。這個算法不需要調(diào)權(quán)重魯棒性好我基本默認用它。偽代碼大概是這樣def rrf_fusion(vector_results, keyword_results, k60): scores {} for rank, doc_id in enumerate(vector_results): scores[doc_id] scores.get(doc_id, 0) 1 / (k rank 1) for rank, doc_id in enumerate(keyword_results): scores[doc_id] scores.get(doc_id, 0) 1 / (k rank 1) return sorted(scores.items(), keylambda x: x[1], reverseTrue)k 取 60 是論文里的經(jīng)驗值實際用下來 40-80 之間差別不大?;旌蠙z索的代價是多一路召回和融合計算延遲會漲一些但對召回率的提升很值尤其是文檔里專有名詞多的時候。4.3 重排序用精度換召回檢索階段為了不漏top-k 一般設(shè)得比較大比如 20-50但真正喂給模型的只能留幾條3-5 條不然上下文塞滿噪聲。這中間的篩選就靠重排序。重排序模型reranker比向量模型更重、更準它對 query 和每個候選片段做交叉編碼算出精細的相關(guān)性分數(shù)再取 top-n。這個思路叫“召回求全、排序求精”。向量檢索快但粗reranker 慢但準兩級配合能在延遲可控的前提下大幅提升精度。實測下來加了 reranker 之后喂給模型的片段相關(guān)性明顯提升模型答非所問的情況少了很多。代價是延遲。reranker 對每個候選都要跑一次模型20 個候選就是 20 次推理用 GPU 加速后一般能控制在幾百毫秒。如果對延遲極敏感可以只對 top-10 做重排或者用更小的 reranker 模型。我的經(jīng)驗是這一步的性價比很高除非延遲卡死否則別省。4.4 檢索參數(shù)調(diào)優(yōu)的實操方法參數(shù)調(diào)優(yōu)別拍腦袋要有評估集。做法是人工構(gòu)造幾十到上百條“問題-標準答案-應(yīng)召回的文檔塊”三元組然后跑檢索看命中率。核心指標看兩個Recallk前 k 條里有沒有包含正確塊和MRR正確塊排得靠不靠前。Recall 低說明召回不夠加大 k 或換模型MRR 低說明排序不準上 reranker。我一般按這個順序調(diào)先固定切分和向量模型調(diào) k 看 Recall 曲線找到 Recall 不再明顯上升的拐點再上混合檢索看提升最后加 reranker 看 MRR。每一步都記錄指標別一次改多個變量不然不知道是哪個改動起的作用。這套流程聽起來笨但比憑感覺調(diào)靠譜得多。5. Prompt 設(shè)計與生成約束5.1 上下文拼裝與引用規(guī)范檢索到的片段怎么拼進 prompt直接影響模型能不能用好。我的模板一般包含三部分系統(tǒng)指令你是基于資料回答的助手資料里沒有就說不知道、檢索到的片段帶編號和出處、用戶問題。片段之間用明確的分隔符隔開并標注來源方便模型引用。關(guān)鍵約束有兩條只依據(jù)資料回答、資料不足時明確說不知道。這兩條能大幅壓低幻覺率。但光寫指令不夠模型有時還是會自由發(fā)揮所以我會在 prompt 里要求它回答時標注引用的片段編號這樣即使它編了也能通過引用是否對得上快速發(fā)現(xiàn)。提示片段數(shù)量別貪多。3-5 條高質(zhì)量片段比 10 條混雜的效果好因為上下文越長模型越容易抓錯重點而且長上下文本身有“中間遺忘”現(xiàn)象關(guān)鍵信息放中間容易被忽略。把最相關(guān)的放最前面。5.2 生成模型本地部署與量化取舍本地跑生成模型量化是繞不開的話題。常見的有 4bit、8bit 量化量化越狠顯存占用越小但精度損失越大。我的經(jīng)驗是8bit 量化基本無損4bit 在多數(shù)問答任務(wù)上可接受但涉及數(shù)字計算、代碼生成這類任務(wù)時 4bit 容易出錯。如果硬件允許優(yōu)先 8bit實在緊張再上 4bit并且重點驗證量化后在你任務(wù)上的表現(xiàn)。推理框架方面本地部署常用的是支持量化和高效推理的那幾套選型時看它對量化格式的支持和吞吐表現(xiàn)。部署時注意上下文長度設(shè)置RAG 場景下上下文要能裝下系統(tǒng)指令加幾條片段一般設(shè) 4K-8K 夠用設(shè)太大反而吃顯存。溫度參數(shù)建議調(diào)低0.1-0.3RAG 要的是忠實于資料不需要創(chuàng)造性。5.3 答案后處理與出處回填生成完別直接返回做一層后處理能提升體驗。常見的有把模型標注的片段編號映射回真實出處文件名、頁碼拼在答案末尾檢查答案里有沒有“根據(jù)資料”這類空話有的話精簡掉對超長答案做截斷或分段。如果模型輸出了引用編號但內(nèi)容對不上可以標記為“低置信”提示用戶核實。這一層還能做兜底如果檢索階段最高分低于某個閾值說明庫里可能根本沒有相關(guān)內(nèi)容這時直接返回“未找到相關(guān)資料”比讓模型硬答要誠實得多。閾值怎么定拿評估集跑一遍看正樣本和負樣本的分數(shù)分布取一個能分開兩者的值我一般取正樣本最低分的 80% 左右作為保守閾值。6. 常見問題排查與避坑實錄6.1 效果問題的定位思路RAG 效果不好先別急著換模型按鏈路逐段排查。我總結(jié)了一個排查順序先看檢索再看拼裝最后看生成。具體做法是拿一個答錯的問題手動看檢索出來的片段里有沒有正確答案。如果沒有問題在檢索切分、向量、召回策略如果有但模型沒答對問題在拼裝或生成prompt、上下文順序、模型能力。這個二分法能快速定位問題域。我見過太多人一上來就懷疑模型不行結(jié)果一查檢索正確片段壓根沒召回換十個模型也沒用。反過來檢索沒問題但答案還是錯那才輪到調(diào) prompt 或換模型。6.2 高頻問題速查表下面這張表是我在實際項目里整理的高頻問題和對應(yīng)處理辦法按出現(xiàn)頻率排序現(xiàn)象可能原因排查與處理答非所問檢索召回不相關(guān)檢查切分是否截斷語義換向量模型加混合檢索說不知道但庫里有召回沒命中加大 top-k檢查 query 改寫確認向量維度一致答案含資料外內(nèi)容prompt 約束不足強化“僅依據(jù)資料”指令降低溫度要求引用專有名詞答錯精確匹配弱加 BM25 關(guān)鍵詞召回混合融合長文檔答不全上下文截斷檢查塊大小和 top-k確認上下文長度夠響應(yīng)慢reranker 或模型推理慢減候選數(shù)換小模型開 GPU 加速重復(fù)召回同一段重疊過大降低 chunk_overlap檢索后去重6.3 幾個踩過的坑第一個坑是向量庫更新不同步。文檔更新了但索引沒重建用戶問新內(nèi)容永遠答舊版本。解決辦法是把文檔更新和索引重建做成流水線文檔一變就觸發(fā)重建或者至少定時全量重建。增量更新要小心刪除舊向量不然新舊混在一起更亂。第二個坑是query 和文檔表述差異大。用戶口語化提問文檔是書面語向量相似度算出來偏低。一個低成本辦法是做 query 改寫讓模型先把用戶問題改寫成更接近文檔表述的形式再檢索實測召回率有提升。這個改寫可以用小模型做延遲增加不多。第三個坑是評估集偏差。自己構(gòu)造的評估問題往往和文檔表述接近測出來指標虛高上線后真實用戶問法千奇百怪效果打折扣。建議評估集里刻意混入口語化、模糊、帶錯別字的問法盡量貼近真實分布。7. 性能與成本的實際權(quán)衡本地化 RAG 跑起來之后性能和成本的賬要算清楚。延遲主要花在三塊向量化 query幾十毫秒、檢索加 reranker幾百毫秒、生成取決于輸出長度和模型大小幾秒到十幾秒。生成是大頭想壓延遲優(yōu)先從生成端下手減輸出長度、換更小的模型、開推理加速。成本主要是硬件折舊和電費。一臺帶單卡的機器能撐起中小規(guī)模的內(nèi)部問答量再大要考慮多實例和負載均衡。我的經(jīng)驗是先單機跑通、壓測找瓶頸再決定擴不擴別一上來就堆硬件很多瓶頸其實是檢索參數(shù)沒調(diào)好導(dǎo)致的無效計算。還有一個容易被忽略的成本是維護成本。文檔在變、模型在迭代、用戶問法在演化RAG 系統(tǒng)不是一勞永逸的得有定期評估和調(diào)優(yōu)的機制。我一般會留一個反饋入口讓用戶對答案點“有用/沒用”攢一批 badcase 定期分析這比拍腦袋調(diào)參有效得多。我個人在實際操作中的體會是RAG 的難點從來不在“把鏈路搭起來”demo 半天就能跑通難的是讓它在真實數(shù)據(jù)、真實問法下穩(wěn)定好用。切分、向量、檢索這三塊的地基打不牢后面怎么調(diào)都是事倍功半。所以如果你正準備上手我的建議是前期多花時間在數(shù)據(jù)清洗和切分上多構(gòu)造評估集把檢索指標盯住生成端反而是最后才需要操心的部分。這套流程走下來一個能用的本地 RAG 系統(tǒng)基本就成型了。