Skip to content

Speculative RAG:用小模型平行打草稿,大模型一次驗證

2026年3月15日 1 分鐘
TL;DR Speculative RAG 用小型專家模型從不同文件子集平行生成多個答案草稿,再由大型模型一次驗證選出最佳答案。論文在 PubHealth 上準確度提升 12.97 個百分點、延遲降低 50.83%——但這是最好的那一格,其他 benchmark 的幅度小很多。
目錄
  1. 標準 RAG 的三個瓶頸
    1. 1. 序列處理的延遲問題
    2. 2. 長 context 的注意力稀釋
    3. 3. 單次生成的賭注
  2. Speculative RAG 架構
    1. 整體流程
    2. 為什麼這樣更快?
  3. RAG Drafter 設計
    1. 模型選擇
    2. 每個 Drafter 看不同的文件子集
    3. 子集抽樣策略
    4. Rationale 生成
    5. Drafter 的訓練
  4. RAG Verifier 設計
    1. 模型選擇
    2. 驗證流程
    3. 評分機制
    4. 為什麼 Verifier 不需要看原始文件?
  5. 效能數據
    1. 準確度比較
    2. 延遲比較
    3. 準確度 vs 延遲的帕累托改善
  6. 與其他 RAG 模式比較
    1. vs Standard RAG
    2. vs Self-RAG
    3. vs CRAG(Corrective RAG)
    4. 各方法的適用場景總覽
  7. 實作指南
    1. 核心類型定義
    2. 文件子集抽樣(分群,非隨機)
    3. RAG Drafter 實作
    4. RAG Verifier 實作
    5. 完整 Pipeline
    6. 使用範例
    7. 實作注意事項
  8. 適用場景與限制
    1. 適合使用 Speculative RAG 的場景
    2. 不適合使用的場景
    3. 成本考量
    4. 後續研究發現的失效模式
  9. 未來展望
  10. 更新紀錄
  11. 參考資料

🌏 English version

標準 RAG 的流程大家都熟:檢索文件 → 拼成 context → 送給 LLM 生成。這個流程簡單有效,但有一個根本性的瓶頸:所有文件塞進同一次 LLM 呼叫,模型必須在一次生成中處理所有資訊,而且整個流程是序列的

文件越多,context 越長,延遲越高,而且模型在超長 context 中容易「迷路」——重要資訊被淹沒在大量文字裡。這就是 Speculative RAG 要解決的問題。

標準 RAG 的三個瓶頸

1. 序列處理的延遲問題

標準 RAG 是嚴格的序列流程:

Query → Retrieve → [所有文件拼接] → LLM 生成 → 回答

                              單次呼叫,等很久

文件檢索可能快(毫秒級),但 LLM 生成是整個 pipeline 的瓶頸。Context 越長,生成時間越長。10 篇文件拼起來可能有 8,000 tokens,大型模型處理這個長度需要顯著的時間。

2. 長 context 的注意力稀釋

把所有檢索到的文件塞進同一個 prompt,模型需要同時處理多份可能互相矛盾的資訊。研究一再表明,LLM 在超長 context 中有「lost in the middle」問題:頭尾的資訊被記住,中間的被忽略。

如果最相關的文件剛好排在中間位置,模型可能根本沒充分利用它。

3. 單次生成的賭注

標準 RAG 只生成一次答案。如果這次生成的方向偏了——選錯了文件中的資訊、推理鏈走歪了——就沒有修正的機會。除非加上 Agentic RAG 的迴圈重試機制,但那又帶來更多延遲。

Speculative RAG 的核心洞察:與其讓一個大模型苦苦處理所有文件,不如讓多個小模型各自處理一小份文件,再讓大模型從多個候選答案中選最好的

Speculative RAG 架構

這個名字借用了 Speculative Decoding 的概念:用小模型做「猜測」,用大模型做「驗證」。

整體流程

                          ┌─────────────────┐
                          │     Query        │
                          └────────┬────────┘

                          ┌────────▼────────┐
                          │    Retriever     │
                          │  (取回 N 篇文件)  │
                          └────────┬────────┘

                    ┌──────────────┼──────────────┐
                    │              │              │
              ┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
              │ Subset 1  │ │ Subset 2  │ │ Subset 3  │
              │ {D1, D3}  │ │ {D2, D5}  │ │ {D1, D4}  │
              └─────┬─────┘ └─────┬─────┘ └─────┬─────┘
                    │              │              │
              ┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
              │  Drafter   │ │  Drafter   │ │  Drafter   │
              │  (小模型)   │ │  (小模型)   │ │  (小模型)   │
              │  Draft 1   │ │  Draft 2   │ │  Draft 3   │
              │+ Rationale │ │+ Rationale │ │+ Rationale │
              └─────┬─────┘ └─────┬─────┘ └─────┬─────┘
                    │              │              │
                    │      ┌──────┴──────┐       │
                    └──────►             ◄───────┘
                           │  Verifier   │
                           │  (大模型)    │
                           │  評分 + 選擇  │
                           └──────┬──────┘

                          ┌───────▼───────┐
                          │  最佳答案輸出   │
                          └───────────────┘

三個關鍵步驟:

  1. 文件分組:把檢索到的 N 篇文件依「與問題的關係」分群,再從每群各抽一篇組成子集,共 K 個子集(細節見下面〈子集抽樣策略〉——這一步不是隨機抽)
  2. 平行草稿生成:K 個小型 RAG Drafter 模型平行處理各自的文件子集,各自生成一份答案草稿和推理過程
  3. 單次驗證:一個大型 RAG Verifier 模型接收所有草稿,一次性評分並選出最佳答案

為什麼這樣更快?

關鍵在於平行化。三個小模型同時跑,每個只需要處理 2-3 篇文件(短 context),生成速度快。大模型只需要看幾份草稿(而不是所有原始文件),context 也短,驗證速度也快。

整體延遲 ≈ max(Drafter 延遲) + Verifier 延遲

相比標準 RAG:

整體延遲 ≈ 大模型處理全部文件的延遲

前者通常顯著更短,因為小模型快、context 短、平行執行。

RAG Drafter 設計

RAG Drafter 是整個架構的「勞動力」——小型、專精、可平行。

模型選擇

論文中使用 Mistral-7B(v0.1)作為 Drafter 的 base model,再針對 RAG 任務做指令微調(附錄還試過 instruction-tuned Gemma-2 2B,效果同樣看好)。選擇小模型的理由:

  • 推理速度快:7B 參數的模型在 GPU 上推理延遲遠低於 70B+ 的大模型
  • 可平行部署:同樣的 GPU 記憶體可以跑多個小模型實例
  • 任務專精:Drafter 不需要廣泛的世界知識,只需要從給定文件中提取和組織資訊

每個 Drafter 看不同的文件子集

這是 Speculative RAG 最巧妙的設計。假設檢索器取回了 6 篇文件 {D1, D2, D3, D4, D5, D6},先依觀點分群,再從每群各抽一篇組成多個子集:

Drafter 1 收到:{D1, D3, D5}
Drafter 2 收到:{D2, D4, D6}
Drafter 3 收到:{D1, D2, D4}
Drafter 4 收到:{D3, D5, D6}

每個 Drafter 只看部分文件,這帶來幾個好處:

  1. 每個 Drafter 的 context 短:3 篇文件 vs 6 篇文件,注意力更集中
  2. 不同子集帶來多樣性:不同文件組合可能引導出不同的答案角度
  3. 冗餘容錯:即使某個子集的文件品質差,其他子集仍可能包含正確答案所需的資訊

子集抽樣策略

這裡有一個很多二手介紹會講錯的細節:論文的子集不是隨機抽的

正確的做法是先把檢索到的文件依照「與問題的關係」做分群(clustering),每個群代表檢索結果中的一個觀點,然後從每個群各抽一篇組成子集。目的是最小化冗餘、最大化觀點多樣性。論文的消融實驗直接測過:從同一個群裡抽樣會顯著劣化,因為子集裡的文件觀點重複,草稿就失去了多樣性。

子集大小是超參數。太小(1 篇)可能資訊不足;太大(接近 N 篇)就失去了分散處理的優勢。論文的主要設定是:TriviaQA / PopQA / PubHealth / ARC-Challenge 檢索 top-10,每題生成 5 份草稿(m=5),每份用 2 篇文件(k=2);MuSiQue 因為推理更複雜,檢索 top-15、生成 10 份草稿、每份用 6 篇文件。

另外兩個實用結論來自論文的超參數分析:增加草稿數量能持續提升表現,而且因為草稿是平行生成的,不增加延遲;但增加每份草稿的文件數並不總是更好——在 TriviaQA 與 PubHealth 上,從 1、2、4、6 到 10 篇的曲線並非單調上升。

Rationale 生成

每個 Drafter 不只生成答案,還要生成 rationale(推理過程)。Drafter 的輸出格式:

Draft: [答案內容]
Rationale: [為什麼從這些文件得出這個答案的推理過程]

Rationale 的作用是給 Verifier 提供判斷依據。Verifier 不只看答案對不對,還看推理過程合不合理。一個答案可能碰巧正確但推理有漏洞,另一個答案可能不太完整但推理鏈條嚴密——Verifier 可以據此做更好的判斷。

Drafter 的訓練

論文中的 Drafter 使用知識蒸餾(Knowledge Distillation)方式訓練:

  1. 從 Open-Instruct 與知識密集型資料集取樣指令對,用 dense retriever 補上最多 10 篇檢索文件,再用大型模型生成 rationale——論文主線用的是 Gemini-Ultra,總共 40k 筆訓練資料
  2. 用這些資料微調 Mistral-7B(v0.1)
  3. 微調後的小模型學會了「看文件 → 寫草稿 + 推理」的能力

論文另外用 GPT-4o 重做了一次資料生成,結果顯示 Speculative RAG 的優勢仍在,代表這個方法不綁定特定的教師模型。消融實驗也確認:訓練時如果拿掉 rationale,三個 benchmark 上表現都明顯下滑——rationale 不是裝飾,是驗證階段的關鍵輸入。

這種訓練方式讓 7B 的小模型在特定任務上接近大模型的品質,但保持小模型的速度優勢。代價是你得自己做一輪蒸餾與微調——這是 Speculative RAG 最大的落地門檻,不是架構本身。

RAG Verifier 設計

RAG Verifier 是架構中的「裁判」——大型、通用、一次定勝負。

模型選擇

這裡也是常被寫錯的地方:論文的 Verifier 不是 GPT-4。實驗中用的是未經任何微調的 Mistral-7B(v0.1)或 Mixtral-8x7B(v0.1),論文中記為 MVerifier-7B 與 MVerifier-8x7B。GPT-4o 與 Gemini-Ultra 只出現在「產生 Drafter 訓練資料」那一步,不在推論路徑上。

這其實是這篇論文更有意思的地方:Verifier 大到 8x7B 就夠了,甚至 7B 的 Verifier 配 7B 的 Drafter 也已經超越所有 baseline。「大模型」在這裡是相對的。

選擇(相對)大模型作為 Verifier 的理由:

  • 廣泛的世界知識:可以交叉驗證草稿的準確性
  • 強大的推理能力:可以評估推理鏈的邏輯一致性
  • 比較判斷能力:同時看多個候選答案,選出最好的

驗證流程

Verifier 收到的輸入:

Query: [原始問題]

Draft 1:
Answer: [草稿 1 的答案]
Rationale: [草稿 1 的推理過程]

Draft 2:
Answer: [草稿 2 的答案]
Rationale: [草稿 2 的推理過程]

Draft 3:
Answer: [草稿 3 的答案]
Rationale: [草稿 3 的推理過程]

Please evaluate each draft and select the best answer.

注意 Verifier 不看原始文件。它只看 Drafter 的草稿和推理過程。這是刻意的設計:

  1. Context 更短:3 份草稿遠比 6 篇原始文件短
  2. 資訊已預處理:Drafter 已經從原始文件中提取了相關資訊
  3. 聚焦比較:Verifier 的任務是比較和判斷,不是從頭提取資訊

評分機制

論文的評分不是叫 Verifier 寫一段評語打分數,而是直接讀模型的條件機率。整體信心分數是三項的乘積:

  1. ρDraft:Drafter 自己生成該草稿的機率
  2. ρSelf-contain:Verifier 在給定問題與 rationale 下,生成該答案的機率,即 Score(α | Q, β)
  3. ρSelf-reflect:Verifier 對一句自省敘述回答「Yes」的機率

消融實驗顯示三項都有貢獻:拿掉 ρDraft 影響最小(TriviaQA −0.19、PubHealth −1.12),拿掉 ρSelf-contain 或 ρSelf-reflect 則各約掉 2 個百分點(TriviaQA)與 0.8 個百分點(PubHealth)。而完全不驗證、隨機挑一份草稿會掉 5.69(TriviaQA)與 5.37(PubHealth)——驗證這一步的價值就在這個差距裡。

用機率而不是叫模型打分,好處是不需要對 Verifier 做任何微調,也不需要它輸出結構化的評分格式;壞處是你的推論端必須拿得到 logprobs,這在只給文字輸出的 API 上做不到。

為什麼 Verifier 不需要看原始文件?

這是一個違反直覺的設計選擇。我們可能會想:Verifier 不看原始文件,怎麼判斷草稿是否準確?

答案是:Verifier 依靠的是大模型自身的世界知識和推理能力

  • 如果一個草稿聲稱「台灣最高的山是玉山,海拔 3,952 公尺」,Verifier 的大模型本身就知道這是否正確
  • 如果一個草稿的推理有邏輯矛盾(前面說 A,後面推出 not A),Verifier 可以靠推理能力發現
  • 如果多個草稿給出不同答案,Verifier 可以透過交叉比對來判斷哪個更可信

這種設計讓 Verifier 的 context window 保持簡短,進一步降低延遲。

效能數據

論文在五個 benchmark 上測試了 Speculative RAG,與標準 RAG 和其他方法比較。以下是論文 Table 1 的實際數字(accuracy %,來源見文末參考資料)。

準確度比較

RAG 方法TriviaQAMuSiQuePopQAPubHealthARC-C
Standard RAG — Mistral-7B54.1516.7131.3834.8542.75
Standard RAG — Mixtral-8x7B59.8519.1634.0237.0848.72
Standard RAG — Mistral-Instruct-7B67.1117.9942.1742.1547.70
Standard RAG — Mixtral-Instruct-8x7B73.9129.4253.6863.6378.41
CRAG (Mistral-7B)59.0349.4659.0474.87
Self-RAG (Mistral-7B)64.8421.7252.6872.4474.91
Self-CRAG (Mistral-7B)65.4356.1172.8575.26
Spec. RAG — Verifier-7B + Drafter-7B73.9131.0356.7575.7976.19
Spec. RAG — Verifier-8x7B + Drafter-7B74.2431.5757.5476.6080.55

幾個觀察:

  • PubHealth 提升最大:76.60 vs 最強 Standard RAG baseline 的 63.63,差 12.97 個百分點——這就是摘要裡那個「最高 12.97%」的來源。它是 PubHealth,不是 PopQA;很多二手介紹會把這個數字掛錯 benchmark。
  • TriviaQA 幾乎沒有提升:74.24 vs Mixtral-Instruct-8x7B 的 73.91,只差 0.33。也就是說當 baseline 本來就強、任務又不特別需要多視角時,這套架構的準確度紅利接近於零,剩下的價值主要在延遲。
  • MuSiQue(多跳推理)提升約 2 個百分點:31.57 vs 29.42。有效但不驚人;注意 MuSiQue 用的是不同的超參數(10 份草稿、每份 6 篇文件)。
  • 超越 Self-RAG 與 CRAG:在有回報數字的欄位上都優於這兩個方法。

要留意兩件事:(1)表中所有方法都跑在同一組 Mistral 系列 backbone 上以求公平,換成別的模型結果未必平移;(2)論文自己也指出,Drafter 單獨使用(不經驗證)就已經打敗多數 baseline,驗證階段的邊際貢獻約落在 0.3~4 個百分點之間。

延遲比較

論文的延遲分析是隨機抽 100 題、不做 batching、逐題計時,並以**最強的 baseline(Standard RAG: Mixtral-Instruct-8x7B)**為比較對象。實際降幅:

Benchmark延遲降低
TriviaQA−11.90%
MuSiQue−15.07%
PopQA−44.31%
PubHealth−50.83%
ARC-Challenge−22.77%

換句話說,「延遲降低 50%」是 PubHealth 那一格的數字,不是通例——TriviaQA 只降了不到 12%。降幅大小主要取決於該資料集的檢索文件有多長:文件越長,Standard RAG 被長 context 拖累得越嚴重,Speculative RAG 的相對優勢越大。

另外要注意這個實驗的硬體前提:Speculative RAG 在 TriviaQA 等資料集上同時啟動 5 個 Drafter endpoint(MuSiQue 是 10 個),Mixtral-8x7B 用 tensor parallelism = 4。沒有這種平行資源,下面的數字不會重現。

延遲降低的來源:

  1. Drafter 平行執行:多個小模型同時跑,總延遲取決於最慢的那個(而非加總)
  2. Context 更短:每個 Drafter 只處理 2-3 篇文件,Verifier 只處理幾份草稿
  3. 大模型呼叫次數減少:標準 RAG 讓大模型處理所有文件,Speculative RAG 只讓大模型做驗證(context 更短)

準確度 vs 延遲的帕累托改善

這是 Speculative RAG 最強的地方:它同時改善了準確度和延遲

通常我們面臨的取捨是:

  • 想要更準確?用更大的模型、處理更多文件 → 延遲更高
  • 想要更快?用更小的模型、處理更少文件 → 準確度下降

Speculative RAG 在論文的設定下同時改善了這兩個維度,靠的是架構設計而非單純堆硬體。

不過「帕累托改善」這個說法要打個折:從上面的實際數字看,準確度紅利集中在 PubHealth、PopQA、ARC-C,TriviaQA 幾乎沒有;延遲紅利則集中在檢索文件較長的資料集。而且它並不是免費的——你需要(a)一輪蒸餾微調來做出 Drafter,(b)能同時跑 5~10 個 Drafter 實例的推論資源,(c)拿得到 logprobs 的 Verifier。把這些成本算進去,它比較像「用工程複雜度換延遲與部分準確度」,而不是純粹的白吃午餐。

與其他 RAG 模式比較

vs Standard RAG

Standard RAG:
  Query → Retrieve ALL docs → [Big Model] → Answer
  延遲:高(大模型處理長 context)
  準確度:中(注意力稀釋)

Speculative RAG:
  Query → Retrieve docs → Split into subsets
        → [Small Model 1] → Draft 1 ─┐
        → [Small Model 2] → Draft 2 ──┤→ [Big Model] → Best Answer
        → [Small Model 3] → Draft 3 ─┘
  延遲:低(平行 + 短 context)
  準確度:高(多角度 + 驗證)

核心差異:Standard RAG 是「一個模型做所有事」,Speculative RAG 是「分工合作」。

vs Self-RAG

Self-RAG 讓模型在生成過程中自我反思,決定是否需要更多檢索。它的問題:

  1. 序列反思:反思-檢索-再生成 是序列流程,每次反思都增加延遲
  2. 同一模型身兼多職:同一個模型既要生成又要反思,任務衝突
  3. 單一視角:始終從同樣的文件集合出發

Speculative RAG 的優勢:

  1. 平行而非序列:多個 Drafter 同時工作
  2. 專職分工:Drafter 專注生成,Verifier 專注驗證
  3. 多樣化視角:不同文件子集帶來不同角度
維度Self-RAGSpeculative RAG
架構單模型 + 反思 token雙模型(Drafter + Verifier)
執行方式序列(生成→反思→再生成)平行(多 Drafter)+ 一次驗證
延遲高(多輪迭代)低(平行 + 短 context)
多樣性低(同一視角反思)高(不同文件子集)
訓練成本需要特殊 token 訓練需要 Drafter 蒸餾訓練

vs CRAG(Corrective RAG)

CRAG 的核心是「檢索品質檢測 + 修正」:如果檢索結果不好,就修正查詢重新檢索。

兩者解決的問題不同:

  • CRAG 解決的是「檢索品質差」的問題:檢索到的文件不相關,需要修正查詢
  • Speculative RAG 解決的是「生成品質差」的問題:文件已經檢索到了,問題在於如何更好地利用它們

它們其實是互補的。你可以先用 CRAG 確保檢索品質,再用 Speculative RAG 確保生成品質:

Query → CRAG(確保檢索品質)→ Speculative RAG(確保生成品質)→ Answer
維度CRAGSpeculative RAG
目標改善檢索品質改善生成品質
修正對象查詢 / 檢索結果答案草稿
額外成本多次檢索多個 Drafter 推理
模型需求單模型雙模型(大 + 小)
可組合性可與 Speculative RAG 組合可與 CRAG 組合

各方法的適用場景總覽

                    檢索品質

          高 ┃  Standard RAG    Speculative RAG
             ┃  (夠用了)         (要更準更快)

          低 ┃  CRAG            CRAG + Speculative RAG
             ┃  (先修檢索)       (兩個都要修)
             ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━→
                    低              高
                         生成複雜度

實作指南

以下是一個 TypeScript 實作範例,展示 Speculative RAG 的雙模型模式。

核心類型定義

interface Document {
  id: string;
  content: string;
  score: number;        // 檢索相關性分數
  embedding: number[];  // 分群用(論文用一個輕量的 instruction-aware embedding 模型預先算好)
}

interface Draft {
  answer: string;
  rationale: string;
  sourceDocIds: string[];
  drafterId: number;
}

interface VerificationResult {
  selectedDraft: Draft;
  scores: Map<number, number>; // drafterId → score
  confidence: number;
}

interface SpeculativeRAGConfig {
  numDrafters: number;        // Drafter 數量(預設 3-5)
  subsetSize: number;         // 每個子集的文件數(預設 2-3)
  drafterModel: string;       // 小模型 ID
  verifierModel: string;      // 大模型 ID
  maxDrafterTokens: number;   // Drafter 最大輸出 token
  maxVerifierTokens: number;  // Verifier 最大輸出 token
}

文件子集抽樣(分群,非隨機)

論文的關鍵是先分群再每群抽一篇,讓每個子集橫跨多個觀點。從同一群裡抽會顯著劣化。

/**
 * 依論文做法建立文件子集:
 * 1. 對檢索到的文件做 embedding,分成 k 群(k = subsetSize),每群代表一個觀點
 * 2. 每個子集從每一群各抽一篇 → 子集內觀點多樣、冗餘最小
 * 注意:這裡不能改成「單純隨機抽 k 篇」,論文的消融實驗顯示同群抽樣會明顯變差。
 */
function sampleDocumentSubsets(
  documents: Document[],
  numSubsets: number,
  subsetSize: number,
): Document[][] {
  // kmeans() 是你自己的實作或任何分群套件;輸入 doc.embedding,輸出 subsetSize 個群
  const clusters = kmeans(documents, subsetSize);
  const subsets: Document[][] = [];

  for (let i = 0; i < numSubsets; i++) {
    // 每個子集:從每一群各抽一篇(不放回,讓不同子集覆蓋不同文件)
    const subset = clusters
      .map((cluster) => pickWithoutReplacement(cluster, i))
      .filter((doc): doc is Document => doc !== undefined);
    subsets.push(subset);
  }

  return subsets;
}

/** 以 round-robin 的方式從群中取第 i 篇,群被取完就回頭循環 */
function pickWithoutReplacement(
  cluster: Document[],
  i: number,
): Document | undefined {
  if (cluster.length === 0) return undefined;
  return cluster[i % cluster.length];
}

kmeans 這裡是 pseudocode 佔位——論文沒有公開參考實作,實際分群方式與距離度量請自行決定並實測。)

RAG Drafter 實作

async function generateDraft(
  query: string,
  documents: Document[],
  drafterId: number,
  config: SpeculativeRAGConfig,
): Promise<Draft> {
  const docContext = documents
    .map((doc, i) => `[Document ${i + 1}] (ID: ${doc.id})\n${doc.content}`)
    .join('\n\n');

  const prompt = `You are a RAG specialist. Given the following documents, answer the query.
You MUST also provide your reasoning process (rationale).

Query: ${query}

Documents:
${docContext}

Respond in this exact format:
Answer: [Your answer based on the documents]
Rationale: [Step-by-step reasoning for how you arrived at this answer from the documents]`;

  const response = await callLLM({
    model: config.drafterModel,
    prompt,
    maxTokens: config.maxDrafterTokens,
    temperature: 0.7, // 稍高的 temperature 增加多樣性
  });

  const { answer, rationale } = parseDraftResponse(response);

  return {
    answer,
    rationale,
    sourceDocIds: documents.map((d) => d.id),
    drafterId,
  };
}

function parseDraftResponse(
  response: string,
): { answer: string; rationale: string } {
  const answerMatch = response.match(/Answer:\s*([\s\S]*?)(?=Rationale:)/i);
  const rationaleMatch = response.match(/Rationale:\s*([\s\S]*)/i);

  return {
    answer: answerMatch?.[1]?.trim() ?? response,
    rationale: rationaleMatch?.[1]?.trim() ?? 'No rationale provided',
  };
}

RAG Verifier 實作

注意這是近似版:論文用的是 token 條件機率(ρDraft × ρSelf-contain × ρSelf-reflect),下面用 prompt 叫模型自己打分,是為了能在只回傳文字的 API 上跑。如果你的推論端拿得到 logprobs,照論文做會更接近原始結果。

async function verifyDrafts(
  query: string,
  drafts: Draft[],
  config: SpeculativeRAGConfig,
): Promise<VerificationResult> {
  const draftsContext = drafts
    .map(
      (draft, i) =>
        `[Draft ${i + 1}] (Drafter #${draft.drafterId})
Answer: ${draft.answer}
Rationale: ${draft.rationale}
Source Documents: ${draft.sourceDocIds.join(', ')}`,
    )
    .join('\n\n---\n\n');

  const prompt = `You are an expert answer verifier. Given a query and multiple draft answers,
evaluate each draft and select the best one.

Evaluation criteria:
1. Factual Accuracy: Is the answer factually correct?
2. Reasoning Quality: Is the rationale logical and complete?
3. Query Relevance: Does the answer directly address the query?
4. Self-Consistency: Are the answer and rationale consistent?

Query: ${query}

Drafts:
${draftsContext}

Respond in this exact format:
Selected: [draft number]
Confidence: [0.0-1.0]
Scores: [draft1_score, draft2_score, ...]
Justification: [Why you selected this draft]`;

  const response = await callLLM({
    model: config.verifierModel,
    prompt,
    maxTokens: config.maxVerifierTokens,
    temperature: 0.0, // 驗證用低 temperature,確保一致性
  });

  return parseVerificationResponse(response, drafts);
}

function parseVerificationResponse(
  response: string,
  drafts: Draft[],
): VerificationResult {
  const selectedMatch = response.match(/Selected:\s*(\d+)/i);
  const confidenceMatch = response.match(/Confidence:\s*([\d.]+)/i);
  const scoresMatch = response.match(/Scores:\s*\[([\d.,\s]+)\]/i);

  const selectedIdx = (parseInt(selectedMatch?.[1] ?? '1') - 1);
  const confidence = parseFloat(confidenceMatch?.[1] ?? '0.5');
  const scoreValues = scoresMatch?.[1]?.split(',').map((s) => parseFloat(s.trim())) ?? [];

  const scores = new Map<number, number>();
  drafts.forEach((draft, i) => {
    scores.set(draft.drafterId, scoreValues[i] ?? 0);
  });

  return {
    selectedDraft: drafts[selectedIdx] ?? drafts[0],
    scores,
    confidence,
  };
}

完整 Pipeline

async function speculativeRAG(
  query: string,
  config: SpeculativeRAGConfig,
): Promise<{
  answer: string;
  confidence: number;
  selectedDrafterId: number;
  allDrafts: Draft[];
  verification: VerificationResult;
}> {
  // Step 1: 檢索文件
  const documents = await retrieve(query);

  // Step 2: 建立文件子集
  const subsets = sampleDocumentSubsets(
    documents,
    config.numDrafters,
    config.subsetSize,
  );

  // Step 3: 平行生成草稿(關鍵!)
  const draftPromises = subsets.map((subset, i) =>
    generateDraft(query, subset, i, config),
  );
  const drafts = await Promise.all(draftPromises);

  // Step 4: 大模型驗證
  const verification = await verifyDrafts(query, drafts, config);

  return {
    answer: verification.selectedDraft.answer,
    confidence: verification.confidence,
    selectedDrafterId: verification.selectedDraft.drafterId,
    allDrafts: drafts,
    verification,
  };
}

使用範例

const config: SpeculativeRAGConfig = {
  numDrafters: 5,   // 論文主設定:m=5
  subsetSize: 2,    // 論文主設定:k=2
  // 模型 ID 一律從環境變數注入,不要寫死——寫死的型號幾個月就下架
  drafterModel: process.env.DRAFTER_MODEL!,
  verifierModel: process.env.VERIFIER_MODEL!,
  maxDrafterTokens: 512,
  maxVerifierTokens: 256,
};

const result = await speculativeRAG(
  '台灣哪個岩場最適合初學者?',
  config,
);

console.log(`Answer: ${result.answer}`);
console.log(`Confidence: ${result.confidence}`);
console.log(`Selected Drafter: #${result.selectedDrafterId}`);
console.log(`All drafts: ${result.allDrafts.length}`);

實作注意事項

Drafter 模型的選擇

這裡不列具體型號清單——開源小模型的世代更替速度讓任何清單半年內就過期。選型的判準才是穩定的:

  • 要小到能同時跑 5~10 個實例(論文用 7B 級;附錄顯示 2B 級也可行)
  • 要能穩定遵循「Answer / Rationale」這種輸出格式
  • 最重要的是要能微調:論文的效果來自蒸餾出來的專家 Drafter,不是拿一個泛用小模型直接上。省掉微調這一步,你得到的就只是一個比較差的 ensemble。

Verifier 模型的選擇

論文用的是未微調的 Mistral-7B 或 Mixtral-8x7B——「大模型」是相對於 Drafter 而言,不必是當代最大的旗艦模型。真正的硬條件是:你的推論端必須能回傳 token logprobs,否則論文的機率式評分做不了,只能退回「叫模型寫評語打分」的近似版(就是本文的範例程式碼),效果與延遲都會不同。

超參數調整

從論文的設定出發再實測調整:

  • 基準設定:檢索 top-10,numDrafters: 5subsetSize: 2
  • 複雜多跳問題:論文在 MuSiQue 上改成檢索 top-15、numDrafters: 10subsetSize: 6
  • 想提升準確度:優先加草稿數(平行執行,不增延遲),而不是加每份草稿的文件數(論文實測非單調上升)

適用場景與限制

適合使用 Speculative RAG 的場景

1. 高延遲敏感的知識問答

如果你的應用對回應時間有嚴格要求(例如客服聊天機器人、即時搜尋引擎),Speculative RAG 可以在不犧牲準確度的前提下顯著降低延遲。

2. 文件集合大且多樣

當檢索器回傳的文件數量多(10+ 篇)且涵蓋不同面向時,Speculative RAG 的子集分散策略特別有效。不同 Drafter 看不同子集,更容易捕捉到不同面向的資訊。

3. 需要高準確度的場景

醫療主張查核(PubHealth 是 true/false 的 claim verification,不是問答)、科學推理(ARC-Challenge 為選擇題)等需要高準確度的場景。多個 Drafter 的多樣性 + Verifier 的嚴格驗證,比單次生成更可靠。

4. 有 GPU 資源支持平行推理

Speculative RAG 需要同時跑多個 Drafter 實例。如果你有足夠的 GPU 資源(或使用支持 batch inference 的 API),這個架構才能發揮平行化的優勢。

不適合使用的場景

1. 簡單的事實查詢

「台灣的首都是什麼?」這種問題,標準 RAG 甚至直接讓 LLM 回答就行了,不需要多 Drafter 驗證。過度架構化反而浪費資源。

2. GPU 資源有限

如果你只有一個 GPU 或使用的 API 不支持 batch/concurrent 呼叫,Drafter 的平行化優勢就沒了。序列跑 4 個 Drafter + 1 個 Verifier,延遲反而比標準 RAG 更高。

3. 文件品質一致且高

如果你的知識庫品質很高、文件之間不矛盾,標準 RAG 的單次生成通常就足夠好。Speculative RAG 的多樣性優勢在這種情況下不明顯。

4. 需要即時串流的場景

Speculative RAG 需要等所有 Drafter 完成 + Verifier 驗證才能輸出。如果你的應用需要 token-by-token 串流(例如 ChatGPT 式的漸進顯示),這個架構需要額外的改造。

一種可能的串流方案:先串流 confidence 最高的 Drafter 的草稿,同時在背景跑 Verifier。如果 Verifier 選了不同的草稿,再替換顯示。但這增加了 UX 複雜度。

5. 文件數量很少

如果只檢索到 2-3 篇文件,分成多個子集意義不大(每個子集可能只有 1 篇文件)。這時候標準 RAG 直接處理就好。

成本考量

Speculative RAG 的成本結構跟標準 RAG 不同:

項目Standard RAGSpeculative RAG
大模型呼叫次數1 次(長 context)1 次(短 context)
小模型呼叫次數0K 次(平行)
大模型 input tokens多(所有文件)少(只有草稿)
小模型 input tokens0K × 子集文件 tokens
總 token 成本中偏高
GPU 需求中偏高

Token 成本方面,Speculative RAG 可能略高(因為多了 K 次 Drafter 呼叫),但大模型的 input tokens 減少了(草稿 vs 原始文件)。如果大小模型的價格差距大(例如 GPT-4 vs Mistral-7B),總成本可能持平甚至更低。

延遲成本方面,Speculative RAG 明顯更低,這在延遲敏感的場景中價值很高。

後續研究發現的失效模式

有一個論文沒測、但後續研究測出來的重要缺口:當問題本身有歧義、有多個都正確的答案時,Speculative RAG 會退化。

2025 年的〈Retrieval-Augmented Generation with Conflicting Evidence〉把 Speculative RAG 當作 baseline,在 AmbigDocs(要求把所有有效答案都列出來)上得到這組數字:

Backbone單純 prompt 拼接Speculative RAG
Llama3.3-70B-Instruct54.2044.30
Qwen2.5-72B-Instruct41.2013.40
GPT-4o-mini51.5022.50

在 Qwen 這一格,Speculative RAG 掉到只剩三分之一。原因不難推:架構的最後一步是從多份草稿中選一份,這對「只有一個正確答案」的任務是優點,對「必須同時呈現多個正確答案」的任務就是結構性缺陷——不同 Drafter 從不同文件子集拿到的不同有效答案,會在驗證階段被丟掉。

同一篇也顯示 Speculative RAG 在 FaithEval(要求壓制錯誤資訊、只有單一正解)上反而是強的:Llama3.3-70B 上 41.80 vs prompt 拼接的 27.30、Qwen2.5-72B 上 56.20 vs 38.50。這正好對上前面的推論——單一正解的任務適合,多正解/歧義查詢不適合

導入前請照這個判準檢查你的查詢分布。如果使用者常問「有哪些方案」「有哪幾種做法」這類本質上是多答案的問題,這個架構會系統性地漏掉東西。

未來展望

Speculative RAG 在 2024 年 7 月發表(arXiv:2407.08223),2025 年被 ICLR 接收。這個架構的核心思想——分工與平行化——很有可能被更廣泛地應用在其他 LLM pipeline 中。

幾個可能的發展方向:

  1. 自適應 Drafter 數量:根據問題的複雜度動態調整 Drafter 數量。簡單問題用 2 個,複雜問題用 5 個。
  2. 智慧子集分配:不是隨機分配文件子集,而是根據文件的主題、類型進行策略性分組。
  3. Drafter 特化:不同 Drafter 專精不同類型的問題(事實型、推理型、比較型),根據問題類型路由。
  4. 與其他 RAG 技術組合:CRAG + Speculative RAG、Graph RAG + Speculative RAG 等組合。

更新紀錄

  • 2026-08-19:對照官方文件逐篇查證翻新,移除易腐內容,並收進「RAG 技法大全」系列

參考資料