はじめに
〈実装編〉 で、RAG と CAG を同じ土俵に載せる準備までを終えました。同じ LLM、同じ 50 問、同じ採点者。違うのは「知識をどう渡すか」だけ——あとは走らせて、数字を見るだけです。
プレフィックスキャッシュを手元で測ってきた身としては、走らせる前から CAG 優勢 を予想していました。問題は「どれだけ」と「どこで」差がつくか、です。
実測でも、スコア・速度ともに前に出たのは CAG でした。意外だったのは速度です。CAG は毎回 6.4 倍のトークンを積むのに、最初の一文字が返るまで(TTFT)はむしろ短い——「全部入れたら遅いはず」という直感のほうを、プレフィックスキャッシュが裏切りました。そして差が最も開いたのは、定番ベンチマークが取りこぼしがちな「答えが本文に書かれていない質問」でした。
なぜ全文を積んだほうが速くて正確なのか。50 問ぶんの数字と、RAG が崩れた失敗例の実物で、順番に追っていきます。
TL;DR
- スコアは CAG 1.90 vs RAG 1.58(2.00 満点、50 問、LLM-as-a-Judge 採点)でした。
- TTFT は CAG 0.088 秒 vs RAG 0.099 秒。プロンプトが 6.4 倍でも、プレフィックスキャッシュが逆転させました。
- RAG が 0 点を取った 7 問の正体は、「引いたチャンクに引きずられた幻覚」と「チャンク 3 枚では見えない文脈」の 2 つに分かれます。
実験のしかた — 何をどう測ったか
数字に入る前に、土俵の作り方だけ簡潔にまとめます(コードを含む詳細は〈実装編〉に)。
題材とテストケース
- 知識源は、自作の日本語短編小説『潮霧湾の門守り』(約 2,450 トークン)です。モデルが事前学習で"知っている"可能性を消すため、Wikipedia のような既存テキストは使っていません。
- 質問は 50 問。難易度ではなく 問い方の種類 で 6 タイプに分けています。
| タイプ | 何を問うか |
|---|---|
| simple | 本文に直接書かれた単純な事実 |
| detail | 細部・列挙(「三つのものは?」など) |
| relation | 人物や物事の関係・役職 |
| timeline | 出来事の順序・時期 |
| inference | 明示されていない理由・背景の推論 |
| unknown | 本文に答えがない(正解は「記載なし」) |
計測方法 — RAG と CAG で変えたのは「知識の渡し方」だけ
- LLM は共通で
Qwen3.5-4B-FP8(vLLM・ローカル・RTX 4070 12GB)。生成は両手法ともtemperature=0.0・ストリーミングで行い、TTFT(最初のトークンが返るまでの時間)も計測しました。 - RAG:小説を 32 チャンクに分割して埋め込み、質問ごとに類似度上位 3 チャンクだけをプロンプトへ注入します。
- CAG:小説全文を system プロンプトに丸ごと入れ、プレフィックスキャッシュを有効化します(1 問目だけ未生成、2 問目以降はヒット)。
評価方法 — LLM-as-a-Judge
- 100 回答(50 問 × 2 手法)を、別の LLM(Gemini 3.1 Flash Lite)に 0 / 1 / 2 点 で採点させました。
- RAG・CAG どちらの回答かを伏せた ブラインド で、手法ごとに 独立 に採点。基準は「模範解答と照らして事実として正しいか」、unknown 型は「正しく『記載なし』と言えたか」を見ます。
総合スコア — 数字を素のまま見る
| RAG | CAG | |
|---|---|---|
| 平均スコア(/2.00) | 1.58 | 1.90 |
| 合計(/100点満点) | 79 | 95 |
| 2点(正解) | 36 問 | 46 問 |
| 1点(部分正解) | 7 問 | 3 問 |
| 0点(不正解) | 7 問 | 1 問 |
差は 0.32 点です。数字だけ見ると接戦のように見えますが、0 点の分布が非対称 です——RAG は 7 問、CAG は 1 問。失点量に直すと RAG 21 点 vs CAG 4 点で、5 倍以上の開きになります。「平均が近い」のではなく、「RAG だけ大きく外す問題がある」という構造です。
問題タイプ別の内訳
| タイプ | n | RAG avg | CAG avg | 差 |
|---|---|---|---|---|
| simple(単純事実) | 8 | 2.00 | 2.00 | ±0.00 |
| detail(細部) | 9 | 1.56 | 1.78 | +0.22 |
| relation(関係性) | 8 | 1.50 | 1.88 | +0.38 |
| timeline(時系列) | 8 | 1.63 | 2.00 | +0.38 |
| inference(推論) | 9 | 1.33 | 1.78 | +0.44 |
| unknown(本文に記載なし) | 8 | 1.50 | 2.00 | +0.50 |
simple(単純事実)は、両者とも満点で並びました。差が開きはじめるのは relation・timeline・inference あたりで、unknown で最大(0.50 点)になります。
inference と unknown で差が最大になる理由 は、こう考えられます。チャンクという断片だけから「なぜ?」「そもそも本文に書かれているか?」を判断するのは、もともと難しいタスクです。全文を持つ CAG は、文脈全体を俯瞰したうえで推論や「記載なし」の判定ができます。ここで差がつきました。
速度の逆転 — 6.4 倍のトークンでなぜ速いのか
| 指標 | RAG | CAG(キャッシュヒット) |
|---|---|---|
| 平均プロンプトトークン | 393 | 2529(6.4 倍) |
| 平均 TTFT | 0.099 s | 0.088 s |
| 平均合計応答時間 | 0.524 s | 0.939 s |
TTFT(Time To First Token、最初のトークンが返るまでの時間)が、トークン量で 6.4 倍ある CAG のほうが短くなりました。
理由はプレフィックスキャッシュです。vLLM の --enable-prefix-caching を有効にすると、同じ前置き部分(今回は全文が入った system プロンプト)を一度計算したら、その結果を保存しておけます。2 問目以降は全文部分の再計算をスキップして、追加された質問のテキスト分だけを処理します。2529 トークンの大半を使い回せるので、体感では RAG より速くなるわけです。
# cag_results.jsonl より
cache miss: 1 問(warmup 兼 q001)
cache hit: 49 問 ← TTFT 平均 0.088 s
〈実装編〉で「キャッシュヒットは 2 問目以降」と書いた動作が、そのまま実際の TTFT に現れた形です。
一方で、合計応答時間は CAG のほうが長くなりました(0.524 s 対 0.939 s)。CAG は全文を見ているぶん詳しい回答を生成しがちで、生成する文字数が増えるほど、最後まで返し終える時間も伸びます。TTFT(最初の文字が出るまで)と合計時間は、トレードオフの関係 です。「画面に何も出ない待ち時間」を短くしたい場面では、CAG の優位が出ます。
失敗例の実物 — RAG が 0 点を取った 7 問
RAG が 0 点を取った 7 問は、原因で見ると 2 タイプ に分かれます。どちらも「検索は部分的に当たっているのに、最後の答えを外す」という、RAG ならではの崩れ方です。
タイプ A:引いたチャンクに引きずられた幻覚(5 問 — q008・q022・q023・q048・q049)
検索が返したチャンクに正解そのものは入っていないのに、モデルがチャンク内の別の語・別の場面を拾って、それらしい答えを組み立ててしまうパターンです。q008 は「鉄箱の中身」のチャンクが引けず、近くにあった「赤霧」「潮位鐘」で誤答。q048・q049 にいたっては、本来「記載なし」が正解の unknown 型なのに、存在しない情報を作り出しています。
タイプ B:チャンク 3 枚では文脈が組み立たない(2 問 — q014・q043)
検索自体は「当たり」に近いのに、答えに必要な情報が複数のチャンクに散らばっていて、3 枚では全体像を再構成できないパターンです。q043(timeline)は類似度 0.921 と 7 問で最も高い"当たり"を引きながら、「台帳を書き始めた」記述が散在していて時系列をつなげませんでした。検索精度が高くても文脈が壊れる、という弱点です。
タイプ A は次節「RAG の構造的な弱点」で深掘りします。個別 7 問の入出力(模範解答・RAG/CAG の実回答・取得チャンクと類似度)は、下に畳んでおきます。
RAG が 0 点を取った 7 問 — 個別の入出力(クリックで展開)
実際の出力をそのまま載せます。改行や句読点はモデルの出力のままです。
q008 | detail「北の岬の鉄箱から見つかった三つのものは?」
| 回答 | |
|---|---|
| 模範解答 | 濡れた海図、真鍮の半円板、『灯ではなく門を見よ』と刻まれた薄板 |
| RAG ❌ | 三つのものは、赤霧、潮位鐘、鉄箱です。 |
| CAG ✅ | 濡れた海図、真鍮の半円板、『灯ではなく門を見よ』と刻まれた薄板です。 |
取得チャンク:chunk_010, chunk_013, chunk_016(類似度 0.863 / 0.854 / 0.850)
「鉄箱」「北の岬」を含む周辺のチャンクは引けていますが、肝心の「鉄箱の中身」が書かれたチャンクは入りませんでした。RAG は引いたチャンクの中だけで答えを作ろうとするため、別のシーンに出てくる「赤霧」「潮位鐘」を拾って誤答しています。
q014 | relation「ミナトと書庫の関係は?」
| 回答 | |
|---|---|
| 模範解答 | ミナトは書庫で記録を管理する記録官 |
| RAG ❌ | ミナトは書庫の奥で、表紙のない古い手帳を発見した。 |
| CAG ✅ | ミナトは記録官であり、書庫の奥で表紙のない古い手帳(『第三塔点検録 抜粋禁止』)を見つけて… |
取得チャンク:chunk_010, chunk_027, chunk_022(類似度 0.889 / 0.871 / 0.854)
relation 型は「役職・立場」を問う質問ですが、引いたチャンクには 出来事の描写しか入っていませんでした。「何をしたか」は書かれていても「何者か」が書かれていないチャンクでは、モデルは関係性ではなく行動のほうを答えてしまいます。
q022 | inference「セツが長く真相を語らなかった背景として何が読み取れるか?」
| 回答 | |
|---|---|
| 模範解答 | 五年前の事故を止められなかった負い目と、規則の理由が悪用されることへの警戒 |
| RAG ❌ | セツは、アオイが「理由を後から分かる」と誤解し…理由を先に伝えることを優先した。 |
| CAG ✅ | 五年前の事故への罪悪感と、規則の正体への理解の遅れ…(2点取得) |
取得チャンク:chunk_021, chunk_006, chunk_029(類似度 0.871 / 0.863 / 0.850)
質問は「なぜ 長く 語らなかったのか(その背景)」ですが、RAG の回答は「なぜ 語ることにしたのか(そのきっかけ)」にすり替わっています。引いたチャンクが「語り始めた場面」を含んでいたため、モデルがそこから理由を拾ってしまいました。問いの微妙なニュアンスが、チャンクの偏りで消えてしまった例です。
q023 | inference「赤霧や記憶の乱れは何の兆候だったか?」
| 回答 | |
|---|---|
| 模範解答 | 残響の余剰を逃がせず逆流が起きていた兆候 |
| RAG ❌ | 赤霧は「導管熱」の兆候であり、記憶の乱れは「誰かが規則や記録を消そうとしている」兆候でした。 |
| CAG ✅ | 赤霧と記憶の乱れは、中央塔の開放手順を誤って残響を逆流させたことによる過負荷の兆候でした。 |
取得チャンク:chunk_016, chunk_024, chunk_013(類似度 0.874 / 0.856 / 0.844)
「導管熱」「誰かが記録を消そうとしている」は、物語に出てくる 別の場面の仮説 です。チャンクの中に複数の仮説が混ざっていたため、モデルが誤った原因のほうを採用してしまいました。推論問題では「断片から全体の因果を組み立てる」必要がありますが、チャンク単位では全体像が見えません。
q043 | timeline「ミナトが新しい台帳を書き始めたのはいつか?」
| 回答 | |
|---|---|
| 模範解答 | 中央塔の開放が成功した翌朝 |
| RAG ❌ | 具体的な日付や時期が明記されていないため、文中からは判断できません。 |
| CAG ✅ | 第四章「中央塔の夜」で赤霧が消え、レギスが責任を問われた翌朝です。 |
取得チャンク:chunk_027, chunk_032, chunk_030(類似度 0.921 / 0.875 / 0.870)
類似度スコアは、この 7 問のなかで最も高い水準(0.921)でした。つまり、検索は「当たり」に近いチャンクをちゃんと引けています。それでも誤答しました。「台帳を書き始めた」という記述が複数のチャンクに散らばっていて、チャンク 3 枚では時系列のつながりを組み立てられなかった のです。「検索精度は高いのに文脈が壊れている」という、timeline 型の弱さがそのまま出た例です。
q048 | unknown「浮標塔の根元の鎖の先には何がつながれているか?」
| 回答 | |
|---|---|
| 模範解答 | 本文中に明記なし |
| RAG ❌ | 誰にも知られていない。 |
| CAG ✅ | 具体的なつながっているものは記載なしです。(補足注付き) |
取得チャンク:chunk_003, chunk_017, chunk_023(類似度 0.891 / 0.862 / 0.861)
RAG の「誰にも知られていない」は、架空の情報です——物語にそんな記述はありません。unknown 型なのに、チャンクが「鎖」を含む場面を返してきたため、モデルがそこから無理に回答をひねり出してしまいました。
q049 | unknown「静まり溜りはどこに位置するか?」
| 回答 | |
|---|---|
| 模範解答 | 本文中に具体的な場所の記載なし |
| RAG ❌ | 静まり溜りは、中央塔地下の室蓋計の部品である半円板が欠けて残響の流れを調整する機構の位置です。 |
| CAG ✅ | 物語には静まり溜りの具体的な位置についての記載はありません。 |
取得チャンク:chunk_026, chunk_018, chunk_003(類似度 0.846 / 0.834 / 0.827)
「静まり溜り」を含むチャンクを引いた結果、モデルが周辺の描写をつなぎ合わせて 存在しない場所の情報を作り出して しまいました。これが幻覚(hallucination)の典型です。CAG は全文を俯瞰して、正しく「記載なし」と判定しています。
RAG の構造的な弱点 — なぜ「知らない」と言えないのか
ここが、この実験でいちばん腑に落ちた部分です。
「本文に答えがない」という、いちばん正直さが問われる unknown 型 8 問。ここで両者はきれいに分かれました。
| RAG | CAG | |
|---|---|---|
| unknown 型 avg | 1.50 | 2.00(満点) |
| unknown 型 0 点 | 2 問 | 0 問 |
CAG は 8 問すべて 2 点。RAG は 0 点が 2 問、1 点が 2 問です。なぜ RAG は「記載なし」と言い切れないのでしょうか。 これは精度の問題というより、RAG という仕組みそのものに埋め込まれた構造だと考えています。
RAG の動きを最初から追ってみます。質問が来ると、RAG はまず「似ているチャンク」を検索して引いてきます。検索は類似度で順位をつけるだけなので、本文に答えがなくても、必ず"それっぽい"チャンクが上位に並びます。そして、それがそのままモデルに手渡されます。
問題はこの一手です。モデルから見れば、「関連資料として、わざわざ選ばれた数枚を渡された」という状況になります。すると「これだけ用意されたのだから、どこかに答えがあるはずだ」という前提が、モデルの側に生まれます。だから「記載なし」と引き下がるより、渡された断片をつなぎ合わせて、それらしい答えをひねり出そうとする。先ほどの q048 の「誰にも知られていない」も、q049 の存在しない場所も、この力学から出てきた幻覚です。
言い換えると、RAG では 「検索して渡す」という工程それ自体が、「答えはこの中にある」という暗黙の前提をモデルに刷り込んでしまう。これは特定のモデルが弱いという話ではなく、設計の必然です。
対して CAG は、最初から全文を見ています。「どこを探しても書かれていない」という判断は、一部ではなく全体を持っている側にしか、本当はできません。unknown 型で CAG が満点を取れたのは、賢かったからではなく、判断に必要な"全体"を手に持っていたからです。
公平のために — CAG も 1 問だけ落としている
CAG を持ち上げすぎないために書いておきます。CAG がただ 1 問落とした 0 点(q035「五年前にユナトが中央塔へ向かった理由は?」)は、逆方向の失敗でした。答えは物語にちゃんと書かれているのに、「記載なし」と答えてしまったのです。全文を持つがゆえに慎重になりすぎた、稀なケースで、ここは RAG が正解しています。RAG の失敗が「ないものを作る」方向なら、CAG の失敗は「あるものを見落とす」方向——向きが正反対なのが面白いところです。
なお、RAG の unknown 型の弱さは、プロンプトに「チャンクに答えが含まれていなければ、必ず『記載なし』と答えてください」と一文を足すだけで、ある程度はやわらぎます。今回はどちらの手法にも同じシステムプロンプトを与える公平条件を優先して、この最適化はあえて入れていません。裏を返せば、RAG をまともに運用するには、こうした"防御プロンプト"が前提になるということでもあります。
コストの裏側 — 6.4 倍のトークン差が本番で効く場面
今回 50 問を通したときの、トークン合計はこうなります。
| RAG | CAG | 比率 | |
|---|---|---|---|
| 平均プロンプトトークン | 393 | 2529 | 6.4 倍 |
| 50 問合計(概算) | 約 19,650 | 約 126,450 | 6.4 倍 |
クローズドな API(OpenAI / Anthropic など)を使う場合、このトークン差はそのままコストに直結します。1,000 問に拡張すると、RAG は約 39.3 万トークン、CAG は約 252.9 万トークン——やはり 6.4 倍の差です。
ただし、プレフィックスキャッシュ割引 を提供しているプロバイダーでは、実コストはぐっと縮みます。Anthropic の Prompt Caching はキャッシュヒット時に入力トークン価格を約 90% 削減し、OpenAI の Cached Input Tokens は約 50% 削減します。CAG のシステムプロンプト部分(今回でいえば全文 ≒ 2,450 トークン)は毎問ヒットするので、割引後の実費は大きく変わってきます。
ローカルの vLLM 環境では、今回のようにキャッシュが無料で効きます。そのぶん、コストよりも KV キャッシュの容量(GPU メモリ)が制約になります。--max-model-len 32768 で 32K コンテキストをキャッシュに保持し続けるには、GPU メモリにそれなりの余裕が必要です。RTX 4070 12GB では、ぎりぎりで収まっている状態です。
逆に言えば、知識ベースが小〜中規模で固定(今回の小説 1 本 ≒ 2,450 トークン程度)なら、CAG のトークン増はキャッシュ割引で許容範囲に収まりやすい、ということです。外部 DB に何十万件もドキュメントが入る規模になると、CAG は現実的ではなくなります。
使い分けの結論 — 知識の性質が設計を決める
論文「Don't Do RAG」の主張は、「固定・小規模な知識ベースなら、CAG のほうが速くて正確」というものです。今回の実測は、それを日本語・コンシューマ GPU・4B モデルという条件で追認した形になりました。
| 観点 | RAG | CAG |
|---|---|---|
| 知識ベースの規模 | 大規模・更新が頻繁 ◎ | 小〜中規模・固定 ◎ |
| 「記載なし」の正確さ | △(幻覚のリスク) | ◎(全文を俯瞰して判断) |
| TTFT(ストリーミングの体感) | △(毎回 prefill) | ◎(2 問目以降キャッシュ) |
| API コスト | ◎ | △(6.4 倍、割引しだい) |
| 実装の複雑さ | △(チャンク・埋め込み・DB) | ◎(system プロンプトに全文) |
| inference / unknown 型の精度 | △ | ◎ |
| 前提となる環境 | API でも動く | ローカル LLM(vLLM 等)が必須 |
「どちらが優れているか」ではなく、「知識の性質が設計を決める」——これが、測り直して確かめたかった点です。「Don't Do RAG」は煽り気味のタイトルですが、条件付きでは正しい、というのが実測から得た感触です。
この実験の制約
今回は、4B モデル(Qwen3.5-4B-FP8)・日本語小説 1 本(約 2,450 トークン)・50 問という小規模な設定です。採点者も LLM(Gemini 3.1 Flash Lite)なので、採点バイアスが残ります。大規模・多言語・多ドメインへ一般化するには追加の検証が必要で、今回の数字はあくまで「この条件での実測値」として読んでください。
まとめ
50 問 × 2 手法の実測から得た事実を、3 つに絞ります。
- スコアは CAG が上(1.90 対 1.58)。差は inference・unknown 型に集中し、単純な事実問題では並びました。
- TTFT はプレフィックスキャッシュが逆転させました(0.088 秒 対 0.099 秒、トークンが 6.4 倍あるにもかかわらず)。
- RAG の失敗の多くは、チャンクに引きずられた幻覚 でした。「それらしい断片を渡されると、モデルは何かを答えようとしてしまう」という構造的な弱点です。
どちらを選ぶかは、つきつめると 2 つの問いで決まります。
- ひとつは 「知識がコンテキストに入り切るか」 です。入り切らないほど大きい、あるいは頻繁に更新されるなら、必要な部分だけを引いてくる RAG が向いています。RAG の本来の強みは「全部は入らない知識から、回答に要る分だけを渡して正確性を保てること」です。
- もうひとつは 「同じ知識に何度も質問するか」 です。固定された資料に繰り返し問い合わせるなら、最初の 1 回だけプレフィルして以降はキャッシュを使い回せる CAG が、速さでも実装の手軽さでも有利です。逆に毎回ちがう資料を渡す使い方では、プレフィックスキャッシュは効きません。
そして、CAG にはもうひとつ前提があります。vLLM などでプレフィックスキャッシュが使える、ローカル環境であること です。API だけで使っている場合は、KV キャッシュを直接操作できないので、この設計は成立しません。
これらをすべて満たすとき——コンテキストに収まる固定の知識に、同じ前置きで何度も問い合わせるとき——CAG は速くて正確で、しかも実装までシンプルになります。そうでなければ、RAG が依然として堅実な選択肢です。
参考
- 実装コード・データ・計測スクリプト一式(本記事のリポジトリ): ai-systems-notes/local-llm-rag-cag-benchmark
- 本シリーズ〈実装編〉:ローカルLLM を動かすなら RAG より速くて正確な選択肢がある — CAG の実装と設計
- Brian J. Chan et al., "Don't Do RAG: When Cache-Augmented Generation is All You Need for Knowledge Tasks", arXiv:2412.15605, ACM Web Conference 2025
- vLLM — Automatic Prefix Caching
- Anthropic — Prompt Caching
- OpenAI — Prompt Caching
cl-nagoya/ruri-v3-310m(Hugging Face)RedHatAI/Qwen3.5-4B-FP8-dynamic(Hugging Face)