{"notes":[{"id":"retrieval-augmented-generation/cag","category":"retrieval-augmented-generation","legacyPath":"","legacySlug":"","legacyPaths":[],"title":"CAG","subtitle":"背多分","author":"Z. Yuan","paperDate":"2024-12-20","publishedOn":"2026-07-19","paperTitle":"Don't Do RAG: When Cache-Augmented Generation is All You Need for Knowledge Tasks","paperUrl":"https://arxiv.org/abs/2412.15605","bibliography":{"title":"Don't Do RAG: When Cache-Augmented Generation is All You Need for Knowledge Tasks","authors":["Brian J Chan","Chao-Ting Chen","Jui-Hung Cheng","Hen-Hsen Huang"],"url":"https://arxiv.org/abs/2412.15605","pdfUrl":"https://arxiv.org/pdf/2412.15605v2","codeUrl":"https://github.com/hhhuang/CAG","preprint":{"id":"2412.15605","submittedOn":"2024-12-20","url":"https://arxiv.org/abs/2412.15605"},"publication":{"venue":"WWW Companion","year":2025,"kind":"conference","url":"https://doi.org/10.1145/3701716.3715490"},"citation":"@misc{retrievalaugmentedgenerationcag2024,\n  title = {{Don't Do RAG: When Cache-Augmented Generation is All You Need for Knowledge Tasks}},\n  author = {Brian J Chan and Chao-Ting Chen and Jui-Hung Cheng and Hen-Hsen Huang},\n  year = {2024},\n  eprint = {2412.15605},\n  archivePrefix = {arXiv},\n  url = {https://arxiv.org/abs/2412.15605}\n}\n","citationPath":"/content/papers/citations/retrieval-augmented-generation/cag.52cd8e1fb14c.bib"},"thumbnail":{"src":"/content/papers/assets/1ccb714a2b9e40fe07cb712c508763c4447c40b66c78a8c364a030957c65a789.png","width":694,"height":575},"locale":"zh-hant","path":"/content/papers/zh-hant/retrieval-augmented-generation/cag.a6d0b63abe8b.md","headings":[{"id":"定義問題","text":"定義問題","depth":2},{"id":"解決問題","text":"解決問題","depth":2},{"id":"模型讀完後留下什麼","text":"模型讀完後留下什麼？","depth":3},{"id":"三個步驟","text":"三個步驟","depth":3},{"id":"拿實際尺寸算一次","text":"拿實際尺寸算一次","depth":3},{"id":"為什麼要整包放","text":"為什麼要整包放？","depth":3},{"id":"討論","text":"討論","depth":2},{"id":"實驗怎麼比","text":"實驗怎麼比？","depth":3},{"id":"準確度大部分領先但不是全部","text":"準確度：大部分領先，但不是全部","depth":3},{"id":"速度省下的是重讀","text":"速度：省下的是重讀","depth":3},{"id":"這篇沒有回答的事","text":"這篇沒有回答的事","depth":3},{"id":"結論","text":"結論","depth":2}],"anchorAliases":{},"guide":null,"relatedReading":[]}],"text":"這篇論文的標題很有個性：「**不要做 RAG**」。\n\nRAG（Retrieval-Augmented Generation，檢索增強生成）是這幾年替語言模型接上外部資料的標準做法：使用者問一個問題，系統先去知識庫找出幾段相關文字，貼在問題前面，再讓模型照著回答。公司內部文件、產品 FAQ、客服紀錄，很多都是這樣接進聊天機器人的。\n\n這套流程好用，但每一關都可能出事。\n\n檢索器找錯段落，模型就只能拿錯的資料作答；找對了但只拿到一半，答案也會缺一塊。整條管線要切文件、建索引、調 top-k、選 embedding 模型，維護起來並不輕鬆。\n\n到了 2024 年，開源模型的上下文長度已經拉到十幾萬 token。於是作者問了一個很直接的問題：\n\n- **如果整個知識庫本來就塞得進去，為什麼還要先檢索？**\n\n\n> [!TIP]\n> \n> **作者是誰？**\n> \n> 三位共同第一作者 Brian J Chan、Chao-Ting Chen、Jui-Hung Cheng 來自國立政治大學資訊科學系，通訊作者黃瀚萱（Hen-Hsen Huang）來自中研院資訊科學研究所。論文發表於 WWW Companion '25，本文依 arXiv v2 閱讀，程式碼公開在 [**GitHub**](https://github.com/hhhuang/CAG)。\n\n\n## 定義問題\n\n把整個知識庫貼進提示詞，這件事本身沒有什麼新意。問題在於代價。\n\n假設我們的知識庫有 85,000 個 token。每來一個問題，就把這 85,000 個 token 加上問題一起送進模型。模型得從第一個 token 開始讀，讀完整份資料，才能開始寫答案的第一個字。\n\n下一個問題進來，資料一個字都沒變，模型還是得從頭再讀一次。\n\n這段「讀提示詞」的過程叫做 **prefill**。提示詞越長，prefill 越久，使用者等第一個字出現的時間（TTFT，time to first token）也越長。\n\n所以作者真正要處理的問題是：\n\n- **資料不變，能不能只讀一次？**\n\n要回答這個問題，得先知道模型「讀完」之後，手上到底留下了什麼。\n\n## 解決問題\n\n### 模型讀完後留下什麼？\n\n現在的語言模型大多是 decoder-only 的 Transformer。每一層的自注意力，會把每個 token 投影成 Query、Key、Value 三組向量。\n\n產生下一個 token 時，新的 Query 要和**前面所有 token 的 Key** 計算相似度，再把**前面所有 token 的 Value** 加權起來。\n\n這裡有個關鍵：前面那些 token 的 Key 和 Value，一旦算出來就不會再變。\n\n因此實作上會把它們存起來，下一步直接拿來用，這就是 **KV cache**。平常模型逐字生成時，本來就靠它避免重算。\n\n\n> [!TIP]\n> \n> **為什麼前面的 Key 和 Value 不會變？**\n> \n> 因為 decoder 使用因果遮罩（causal mask），每個 token 只能看自己和前面的 token。後面接上新的文字，不會回頭改變前面 token 的計算結果。\n> \n> 反過來說，如果前面的文字改了一個字，後面所有 token 的 Key 和 Value 都得重算。這點後面講限制時會再回來。\n\n\nCAG（Cache-Augmented Generation，快取增強生成）的想法，就是把這件事從「同一題內重用」擴大成「所有題目共用」：\n\n- **先讓模型把整份知識庫讀一遍，把產生的 KV cache 存起來；之後每一題只要接著讀問題就好。**\n\n下圖是論文對兩種流程的比較，粉紅色是每次推論時才會動到的部分：\n\n[![RAG 與 CAG 的流程比較：RAG 在推論時經過 IR Model 檢索知識；CAG 則離線預載成 Context Cache，推論時只處理問題](/content/papers/assets/1ccb714a2b9e40fe07cb712c508763c4447c40b66c78a8c364a030957c65a789.png)](/content/papers/assets/1ccb714a2b9e40fe07cb712c508763c4447c40b66c78a8c364a030957c65a789.png)\n\n*原論文 Figure 1，取自 [**v2 PDF 第 2 頁**](https://arxiv.org/pdf/2412.15605v2#page=2)。上半為 RAG，下半為 CAG；粉紅色元件在即時推論時運作。*\n\n上半部的 RAG，每一題都要經過 IR Model（檢索模型）去知識庫找資料，再把找到的 Context 和問題 $q$ 一起交給 LLM。\n\n下半部的 CAG 把知識庫透過「Offline Preloading」變成 Context Cache。推論時只有問題 $q$ 是粉紅色，檢索器整個不見了。\n\n### 三個步驟\n\n作者把流程寫成三步。\n\n**第一步：預載知識**。把文件集合 $\\mathcal{D}=\\{d_1,d_2,\\dots\\}$ 整理好，讓模型讀一次，得到 KV cache：\n\n$$\nC_{KV}=\\text{KV-Encode}(\\mathcal{D})\n$$\n\n這份快取可以存在記憶體，也可以寫到硬碟。讀文件的成本只付一次，不管後面來多少問題。\n\n**第二步：推論**。載入 $C_{KV}$，接上使用者的問題 $q$：\n\n$$\nr=\\mathcal{M}(\\mathcal{D}\\oplus q)=\\mathcal{M}(q\\mid C_{KV})\n$$\n\n等號左邊是「把文件和問題串起來給模型」，右邊是「模型帶著快取，只讀問題」。兩者的計算結果相同，差在文件那一段不用重讀。\n\n**第三步：重置快取**。回答第一題時，問題和答案的 token 也會被追加進 KV cache。第二題進來前，得把這些多出來的部分砍掉，否則第二題會看到第一題的對話。\n\nKV cache 是只往後追加的，所以只要記住知識庫讀完時的長度，把後面多出來的位置截掉就好，不用從硬碟重新載入。\n\n### 拿實際尺寸算一次\n\n光講「把 KV cache 存起來」有點抽象，我們用論文使用的 Llama 3.1 8B 實際算一下這份快取長什麼樣子。\n\n按 [**Llama 3 論文**](https://arxiv.org/abs/2407.21783) Table 3，8B 模型有 32 層，隱藏維度 4096，32 個 attention head，其中 Key/Value 只有 8 個 head（grouped-query attention），每個 head 是 $4096/32=128$ 維。\n\n所以，每一層、每個 token 要存的 Key 張量形狀是：\n\n$$\n[\\text{batch},\\ \\text{kv\\_heads},\\ \\text{seq\\_len},\\ \\text{head\\_dim}]=[1,\\ 8,\\ 1,\\ 128]\n$$\n\nValue 也一樣。一個 token 在所有層要存的數字有：\n\n$$\n2\\ (\\text{K 和 V})\\times 32\\ (\\text{層})\\times 8\\ (\\text{head})\\times 128\\ (\\text{維})=65{,}536\n$$\n\n若每個數字用 16 位元（2 bytes）存，一個 token 就是 128 KiB。\n\n論文最大的 HotPotQA 測試組約有 85k token，乘下去：\n\n$$\n85{,}000\\times 128\\ \\text{KiB}\\approx 11.1\\ \\text{GB}\n$$\n\n模型權重本身以 16 位元存放約 16 GB，這份快取已經接近它的七成。\n\n\n> [!NOTE]\n> \n> 這是依模型設定的估算，假設快取以 16 位元存放；論文沒有報告實際的快取大小與記憶體用量。官方程式碼預設以 `torch.float16` 載入模型，另有 4-bit 量化選項。\n\n\n有了形狀，官方程式碼裡的重置步驟就很好懂了：\n\n```python\ndef clean_up(kv: DynamicCache, origin_len: int):\n    \"\"\"\n    Truncate the KV Cache to the original length.\n    \"\"\"\n    for i in range(len(kv.key_cache)):\n        kv.key_cache[i] = kv.key_cache[i][:, :, :origin_len, :]\n        kv.value_cache[i] = kv.value_cache[i][:, :, :origin_len, :]\n```\n\n*取自 [**hhhuang/CAG**](https://github.com/hhhuang/CAG/blob/5c0d8ed6e28f34c72bb7208068f55cb3147d0c8c/kvcache.py#L120-L126) 的 `kvcache.py`，MIT 授權。*\n\n`kv.key_cache[i]` 是第 $i$ 層的 Key，形狀 `[1, 8, seq_len, 128]`。`[:, :, :origin_len, :]` 沿著第三個維度，也就是序列長度，只保留知識庫那一段。\n\n`origin_len` 在預載完成時記下：知識庫讀完後快取有多長，之後每一題開始前就切回這個長度。整個「重置」就是 32 層、每層兩次的切片。\n\n### 為什麼要整包放？\n\n讀到這裡，你可能會想：既然快取這麼好用，為什麼不把每份文件**各自**算好快取，檢索到哪幾份就拼哪幾份？\n\n問題出在剛才那個提示框。每個 token 的 Key 和 Value，取決於它前面所有的文字，也取決於它在序列中的位置。文件 B 單獨計算時，它不知道前面接的是文件 A；位置編號也從頭開始。直接拼起來，和真的讓模型連著讀 A、B 的結果不一樣。\n\n作者在前言提到，TurboRAG 預先計算各段落的 KV cache，就得額外重排位置 ID 才能正常運作。CAG 的做法比較粗暴：**知識庫固定，就整份一起讀**。檢索不見了，拼接的問題也一起不見了。\n\n代價是，知識庫裡任何一個字改了，那個位置之後的快取都得重算。\n\n\n> [!TIP]\n> \n> **這不就是 prompt caching 嗎？**\n> \n> GitHub 上也有人[**問過類似的問題**](https://github.com/hhhuang/CAG/issues/18)：把整份文件放進 system prompt，結果不是一樣嗎？\n> \n> 專案貢獻者 brian030128 的回覆是：輸出確實相同，差別在於提示詞轉成 KV cache 是很吃運算的步驟；CAG 把它預先算好，省掉的是 TTFT。之後也有人追問，這和 API 供應商提供的 context caching 有什麼不同。\n> \n> 就機制來說，兩者非常接近。現在 [**Claude 的 prompt caching**](https://platform.claude.com/docs/en/build-with-claude/prompt-caching) 和 [**Gemini 的 context caching**](https://ai.google.dev/gemini-api/docs/caching) 都能重用重複的長提示詞。這篇論文的重點，比較是把「整份知識庫預載加快取」和 RAG 放在同一組問答題上比較。\n\n\n## 討論\n\n### 實驗怎麼比？\n\n作者用兩個問答資料集：\n\n- **SQuAD 1.0**：答案是文章某個段落裡的一段文字，偏向單段定位。\n- **HotPotQA**：答案需要結合多份文件，屬於多跳（multi-hop）推理。\n\n為了看知識庫變大時會發生什麼事，每個資料集各切出小、中、大三組。以 HotPotQA small 為例，作者從文件集抽出 16 份文件，拼成一份長參考文字，測試題目只選和這 16 份文件相關的題目。\n\n| 資料集 | 組別 | 文件數 | token 數 | 題數 |\n| --- | --- | ---: | ---: | ---: |\n| HotPotQA | Small | 16 | 21k | 1,392 |\n| HotPotQA | Medium | 32 | 43k | 1,056 |\n| HotPotQA | Large | 64 | 85k | 1,344 |\n| SQuAD | Small | 3 | 21k | 500 |\n| SQuAD | Medium | 4 | 32k | 500 |\n| SQuAD | Large | 7 | 50k | 500 |\n\n*整理自原論文 Table 1。*\n\n所有系統都用 Llama 3.1 8B 回答，跑在 8 張 Tesla V100 32GB 上。對照組是兩種用 LlamaIndex 建的 RAG：\n\n- **Sparse RAG**：用 BM25，靠關鍵字比對與詞頻排序。\n- **Dense RAG**：用 OpenAI 的 embedding 索引，靠語意相似度排序。\n\n兩者都試了取回 top-1、3、5、10 段。\n\n評分用 **BERTScore**，比較模型答案和標準答案的語意相似程度。\n\n\n> [!TIP]\n> \n> **BERTScore 是什麼？**\n> \n> 它把兩段文字都丟進 BERT 類模型，取出每個 token 的向量，再逐一配對計算相似度。好處是「巴黎」和「法國首都巴黎」不會像逐字比對那樣被判成完全不同。\n> \n> 但它衡量的是「像不像」，不是「對不對」。兩個答案差一個數字，BERTScore 可能還是很高。讀下面的表時，小數第三位的差距要保守看待。\n\n\n### 準確度：大部分領先，但不是全部\n\n[![原論文 Table 2：Sparse RAG、Dense RAG 在 top-1、3、5、10 與 CAG 於 HotPotQA、SQuAD 小中大三組的 BERTScore](/content/papers/assets/13f5f729ad0290d70a0afcceaf6ec535895cb1017a05906e8dcfa0a9e91cae31.png)](/content/papers/assets/13f5f729ad0290d70a0afcceaf6ec535895cb1017a05906e8dcfa0a9e91cae31.png)\n\n*原論文 Table 2，取自 [**v2 PDF 第 4 頁**](https://arxiv.org/pdf/2412.15605v2#page=4)。左欄 HotPotQA、右欄 SQuAD，粗體為該組最高分。*\n\n先看每組最底下的「CAG (Ours)」那一列。\n\nSmall 和 Medium 兩組，CAG 在兩個資料集都拿下最高分。例如 HotPotQA small，CAG 是 0.7951，最好的 RAG 是 Sparse top-5 的 0.7676。\n\n再往下看 Large。SQuAD 那欄，CAG 的 0.7734 仍然最高；但 HotPotQA 那欄的粗體不在 CAG，而在 Sparse RAG top-5 的 **0.7535**。CAG 只有 0.7407，比 Sparse top-3 的 0.7463 低，和 Dense top-3 的 0.7409 幾乎一樣。\n\n作者在正文也寫到，資料變大後 CAG 和 RAG 的差距縮小，並引用長上下文模型處理很長輸入時會退化的研究。\n\n這裡可以再多看一個細節。作者在前言引用 RULER 的評測，說 Llama 3.1 8B 的**有效上下文長度是 32K**；HotPotQA large 卻有約 85k token，遠超過這個範圍。CAG 在這組落後，可能和這有關。不過論文沒有拆開分析，這是我們讀表時的推測。\n\n表上還有一個現象：六組裡有五組，分數最高的 RAG 設定是 BM25，只有 SQuAD medium 由 Dense top-10 以 0.0009 小勝。作者自己的解讀是，這兩個資料集可能不夠難，關鍵字比對就能找到大部分需要的資訊。換句話說，這裡的 RAG 對照組，並沒有遇到語意檢索才能解決的難題。\n\n### 速度：省下的是重讀\n\n再看時間。\n\n[![原論文 Table 3：HotPotQA 小中大三組中，各 RAG 設定、CAG 與 In-Context Learning 的檢索時間與生成時間](/content/papers/assets/fbdd7c78239b53372fb8d168a05e1cb9ffda9cfdc3c6301abb36dd2913389490.png)](/content/papers/assets/fbdd7c78239b53372fb8d168a05e1cb9ffda9cfdc3c6301abb36dd2913389490.png)\n\n*原論文 Table 3，取自 [**v2 PDF 第 4 頁**](https://arxiv.org/pdf/2412.15605v2#page=4)。單位為秒；Retrieval 為檢索時間，Generation 為生成時間。*\n\n表中多了一個對照：**In-Context Learning**。它把整份參考文字和問題直接送進模型，每題都即時計算 KV cache，也就是我們在「定義問題」那節說的「每題都從頭讀一遍」。\n\n這一列和 CAG 的比較，才是預載快取真正省下的東西：\n\n| HotPotQA 組別 | 每題重讀 | CAG | 倍數 |\n| --- | ---: | ---: | ---: |\n| Small（21k） | 9.32 秒 | 0.85 秒 | 約 11 倍 |\n| Medium（43k） | 26.37 秒 | 1.41 秒 | 約 19 倍 |\n| Large（85k） | 92.08 秒 | 2.26 秒 | 約 41 倍 |\n\n知識庫越大，重讀越貴，預載的效益也越明顯。\n\n但如果拿 CAG 和 Sparse RAG 比，事情就不一樣了。Large 組的 BM25 top-3 檢索只花 0.0008 秒，生成 0.67 秒，總共不到一秒；CAG 是 2.26 秒。BM25 只把三段短文字交給模型，模型要處理的上下文本來就短。\n\n而且 CAG 的時間也隨著知識庫變大而增加，從 0.85 秒、1.41 秒到 2.26 秒。快取省掉了重讀，但每生成一個字，注意力仍然要掃過整份 85k 的快取。\n\n還有一個表上沒有的數字：**建立快取要多久**。Table 3 只計算預載後的單題推論時間。官方程式碼會記錄預載花的時間，但論文沒有報告。對一個每天更新的知識庫來說，這個數字很重要。\n\n### 這篇沒有回答的事\n\n作者在 Limitations 一節寫得很直接：這個方法需要把所有相關文件放進模型的上下文，適合小公司的內部知識庫、FAQ、客服中心這類規模可控的場景；資料集大很多時就不實際。\n\n從前面的推導，還可以多列幾個工程上會遇到的情況：\n\n1. **文件常常更新**。改動位置之後的快取都得重算，論文沒有量測更新成本。\n2. **不同使用者能看的文件不同**。一份快取包含整個知識庫；若權限不同，可能得替不同的文件組合各存一份。這是本文的推論，論文沒有討論。\n3. **快取綁定模型**。KV cache 是某個模型、某個版本算出來的中間狀態，換模型就得重建。\n\n## 結論\n\n這篇論文提出的 CAG，做法可以濃縮成一句話：**知識庫固定且放得進上下文時，先讓模型讀一次，把 KV cache 存起來，之後每題只讀問題。**\n\n在論文的實驗設定裡：\n\n- **和每題重讀相比，速度差很多**。HotPotQA large 組從 92.08 秒降到 2.26 秒，約 41 倍。\n- **準確度大多與 RAG 相當或更好**。Small、Medium 兩組 CAG 都最高；Large 組的 SQuAD 仍最高，HotPotQA 則輸給 BM25 top-5。\n- **和輕量的 BM25 相比，CAG 不一定比較快**。只取三段短文字的 RAG，總時間不到一秒。\n\n這些結果來自單一 8B 模型、最多約 85k token 的知識庫，以及 BERTScore 這個語意相似度指標。\n\n標題說「不要做 RAG」，正文給的答案其實更溫和：知識庫小、穩定、會被反覆查詢時，可以考慮不檢索，直接背起來。\n\n至於知識庫大到背不下的時候，還是乖乖去查吧。\n","imageSizes":{"/content/papers/assets/1ccb714a2b9e40fe07cb712c508763c4447c40b66c78a8c364a030957c65a789.png":{"width":694,"height":575},"/content/papers/assets/13f5f729ad0290d70a0afcceaf6ec535895cb1017a05906e8dcfa0a9e91cae31.png":{"width":694,"height":1028},"/content/papers/assets/fbdd7c78239b53372fb8d168a05e1cb9ffda9cfdc3c6301abb36dd2913389490.png":{"width":689,"height":717}}}
