powered by TechFeed
表示モード
Deep Dive

「全部コンテキストに突っ込む」は間違い — ローカルLLMに自前ドキュメントを読ませるRAGの仕組みと現実的な注意点

6月19日、コンシューマー向けテック情報サイト「Vetted Consumer」が「RAG on a Local LLM, Explained: Give Your Model Your Documents Without Drowning in Context」と題した記事を公開した。ローカルLLMにRAGを組み合わせることで、巨大なコンテキストに頼らずに自前のドキュメントをモデルに参照させる仕組みと、実践上の注意点について詳しく解説している。

6月19日、コンシューマー向けテック情報サイト「Vetted Consumer」が「RAG on a Local LLM, Explained: Give Your Model Your Documents Without Drowning in Context」と題した記事を公開した。ローカルLLMにRAGを組み合わせることで、巨大なコンテキストに頼らずに自前のドキュメントをモデルに参照させる仕組みと、実践上の注意点について詳しく解説している。


「全部コンテキストに突っ込めばいい」は正しくない

ローカルLLMに自前のノート・コードベース・PDFフォルダを読ませたいとき、選択肢は二つある。全文をプロンプトに流し込むか、RAG(Retrieval-Augmented Generation)を使って関連箇所だけ取り出すかだ。

前者は手軽に見えるが、KVキャッシュのメモリコストが重くのしかかる。100,000トークンの文書を丸ごと渡せば、それだけVRAMと処理時間を消費する。一方RAGは、クエリに関連する数チャンク(たとえば500トークン×5件=約2,500トークン)だけをプロンプトに渡す。記事はこの差を、多くのローカル環境においてシステムが正常に動作するか否かを分ける現実的な問題として取り上げている。

さらに見過ごせないのが精度の問題だ。Liu et al.の"Lost in the Middle"(2023)によれば、モデルは長いコンテキストのうち先頭と末尾の情報は使うが、中間に埋もれた情報は見落とす。しかも長コンテキスト専用に設計されたモデルでも、コンテキストが長くなるほど精度が下がる傾向がある。大量の文書を投げ込んでも、モデルが全部読むとは限らない。


RAGの仕組み:4ステップのパイプライン

RAGのパイプラインはどんな実装でも本質的に同じ4ステップだ。

  1. Chunk(分割) — ドキュメントを数百トークン程度の小さな塊に分割する
  2. Embed(ベクトル化) — 各チャンクをembeddingモデルでベクトルに変換する。意味が近いテキストは数値空間上でも近くに配置される(Sentence-BERTの手法)
  3. Store(保存) — ベクトルをFAISS・Chroma・Qdrant・LanceDBといったベクトルデータベースに格納する
  4. Retrieve & Generate(検索+生成) — 質問文をベクトル化し、最も近いチャンクをtop-k件取得、プロンプトに差し込んでモデルに回答させる

キーワード検索ではなくdenseベクトル検索を使う理由は精度にある。Karpukhin et al.のDense Passage Retrieval(2020)では、学習済みembeddingがキーワードベース手法(BM25)を9〜19%上回る検索精度を示した。クエリと文書が一語も共有しなくても、意味が近ければ正しく引っかかる。


ローカル環境での追加コストは小さい

RAGを追加するためのハードウェア負荷は、一般的に思われているより軽い。

  • embeddingモデルは小さい。 bge-small(約33Mパラメータ)、nomic-embed(約137M)、e5-large(約335M)など、主力モデルと比べて桁違いに軽く、CPU動作も可能。VRAMは1GB以下で収まる
  • ベクトルDBは安い。 主にRAM・ディスクとCPUで動き、数百万チャンクの近傍探索もミリ秒単位

小さなモデルと軽量インデックスを追加するだけで、巨大コンテキストのコストを回避できる。


現実的な話:RAGは「調整が必要」なツールだ

記事が正直に認めているのが、RAGは「スイッチを入れれば動く」ものではないという点だ。品質はチャンクの分割粒度・embeddingモデルの選択・取得件数・ドキュメントの品質に強く依存する。

r/LocalLLaMAでは、ローカルメモリシステムを試したユーザーがこう語っている:

「要約を試したが情報が失われすぎる。ベクトルDBとembeddingも試したがうまく機能しなかった……」
— u/Independent_Plum_489

この「最初のRAGはがっかりする」という経験は広く共有されている。改善の道筋はある程度確立されており、記事は以下のアプローチを挙げている。

  • ハイブリッド検索 — denseベクトル検索とBM25(キーワード検索)を組み合わせる。固有名詞・エラーコード・型番など、embeddingが曖昧にしやすい語句に有効
  • Re-ranking(再ランク付け) — まず広めに50件取得し、cross-encoderで精度の高い並び替えを行う。「Lost in the Middle」の知見から、プロンプト内の位置が精度を左右するため、本当に重要なチャンクを先頭に置くことが効いてくる
  • クエリ書き換え — 検索前にモデルが質問を言い換えることで、ドキュメントの書き方に合わせた検索が可能になる

RAGとファインチューニングの使い分け

モデルに知識を「教える」もう一つの手段はファインチューニングだが、両者の役割は明確に異なる。RAGは参照する情報を外部から動的に供給する仕組みであり、ファインチューニングはモデルの重み自体を書き換えて挙動を変える手法だ。

目的 手段
新しい事実・ドキュメントを参照させたい RAG
出力のスタイル・フォーマット・語調を変えたい ファインチューニング

RAGはインデックスを更新するだけで知識を差し替えられ、どのチャンクを参照したか出処も追える。ファインチューニングは「何を答えるか」ではなく「どう答えるか」を変えるためのものだ。「自分のドキュメントとチャットしたい」という用途には、RAGの方が安く・早く・安全に実現できる。


embeddingモデルの選び方

embeddingモデルの選択はRAGの精度を直接左右する。記事では以下の指針を示している。

  • ドメインに合わせる — 一般テキストにはbge・e5・nomicが有効。コードや技術文書は専用モデルを検討する
  • 次元数とのトレードオフ — 大きなベクトル(1024次元以上)は精度が高い一方、ストレージと検索速度に影響する。多くのローカル用途では384〜768次元で十分
  • MTEBベンチマークを参照する — 実際の検索タスクで評価されたランキングが公開されており、ローカル動作可能なモデルも多数掲載されている
  • 一度決めたら変えない — embeddingモデルを変更した場合、コーパス全体の再ベクトル化が必要になる。異なるモデルのベクトルは比較できないためだ

長コンテキストとRAG、どちらを使うべきか

状況 選択
短い文書1件を全文参照したい 長コンテキスト(シンプルに済む)
大量・増え続けるコーパス(ノート・コードベース・wiki) RAG
知識を頻繁に更新したい RAG(インデックス更新だけで完結)
VRAMが少ないマシン RAG(コンテキストを小さく保てる)
特定の一節を正確に引きたい RAG + ハイブリッド検索

詳細はRAG on a Local LLM, Explained: Give Your Model Your Documents Without Drowning in Contextを参照していただきたい。