0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

8GBでLLMエージェントを回したら何ステップで壊れるか — ツール呼び出しの物理的限界と3つの迂回策

0
Posted at

エージェントの「長期記憶」は幻想だった

2026年のLLMはエージェントとして動くことが前提になりつつある。ツールを呼び、結果を受け取り、次のアクションを決め、また呼ぶ。Claude Code、Cursor、Devin——どれも「長時間ループ」を基本戦略としている。

しかしこの戦略は、8GB VRAMのローカル環境では物理的に成立しない。

私はRTX 4060 Laptop (8GB) + Qwen2.5-7B Q4_K_Mでllama.cppベースのツール呼び出しエージェントを試した。結果は単純だ。ツール呼び出し5回を超えたあたりから、応答品質が目に見えて劣化する。 10回を超えると、直前に自分が呼んだツールの結果を無視し始める。

この記事では、この劣化がなぜ起きるのかをKVキャッシュとContext Rotの観点から分解し、8GBで使える3つの迂回策を検討する。


ツール呼び出しはどれだけKVキャッシュを食うか

ツール呼び出し1回のトークンコストを考えてみる。

1回のツール呼び出しサイクル:
  システムプロンプト         : ~500 tok (固定)
  ユーザー指示               : ~200 tok (固定)
  過去の会話履歴             : 可変 (蓄積)
  ツール定義 (function schema): ~300 tok × ツール数
  LLMの応答 (tool_call)      : ~100 tok
  ツール実行結果             : ~500-2000 tok (可変)

5つのツールを定義し、各呼び出しで平均800トークンの結果が返るとする。ステップごとのKVキャッシュ蓄積を概算すると:

ステップ 累積トークン KVキャッシュ (fp16) VRAM残り (7B Q4_K_M)
0 (初期) ~2,200 0.12 GB 2.60 GB
3 ~4,900 0.26 GB 2.46 GB
5 ~6,700 0.36 GB 2.36 GB
10 ~11,200 0.60 GB 2.12 GB
20 ~20,200 1.08 GB 1.64 GB
30 ~29,200 1.56 GB 1.16 GB
def agent_vram_estimate(steps, tokens_per_step=900, base_tokens=2200,
                        model_gb=4.68, overhead_gb=0.6,
                        n_layers=28, n_kv_heads=4, head_dim=128, dtype_bytes=2):
    """エージェントループのVRAM消費推定"""
    total_tokens = base_tokens + steps * tokens_per_step
    kv_gb = (2 * n_layers * total_tokens * n_kv_heads * head_dim * dtype_bytes) / (1024**3)
    total_gb = model_gb + kv_gb + overhead_gb
    return {
        "steps": steps,
        "tokens": total_tokens,
        "kv_cache_gb": round(kv_gb, 2),
        "total_vram_gb": round(total_gb, 2),
        "remaining_gb": round(8.0 - total_gb, 2)
    }

for s in [0, 5, 10, 20, 30, 50]:
    r = agent_vram_estimate(s)
    print(f"Step {s:2d}: {r['tokens']:,} tok, KV={r['kv_cache_gb']}GB, 残り={r['remaining_gb']}GB")

30ステップあたりでVRAM残りが1GBを切り、50ステップで理論上OOMが見えてくる。Q4 KVキャッシュ量子化(--cache-type-k q4_0 --cache-type-v q4_0)を使えば約3.5倍に圧縮できるが、それでも100ステップ超の長期ループは現実的ではない。

ただし、OOMより先にもっと深刻な問題が来る。


Context Rot — 長いコンテキストが品質を殺す

VRAMに収まっていても、コンテキストが長くなると応答品質が崩壊する。これは「Context Rot」として知られる現象だ。

Chroma Researchの報告によれば、LLMの情報再現精度はトークン数に反比例して低下する。特にエージェントのような「中間結果の蓄積」パターンでは劣化が顕著だ。

MicrosoftとSalesforceの共同研究 "LLMs Get Lost In Multi-Turn Conversation" (arXiv:2505.06120) はさらに具体的な数字を出している。ベンチマークプロンプトをマルチターン会話(エージェントワークフロー類似)に変換したところ、6つの生成タスクで平均39%の性能低下を報告した。o3やDeepSeek-R1のような推論特化モデルでさえこの傾向から免れなかった。

7Bモデルではこの劣化がさらに早く始まる。私がQwen2.5-7Bで確認した挙動:

  • ステップ3-5: 正常動作。ツール結果を正確に参照し次のアクションを選択
  • ステップ5-8: 初期の指示を忘れ始める。同じツールを冗長に再呼び出し
  • ステップ8-10: 直前のツール結果を無視。ハルシネーション混入率が上昇
  • ステップ10+: 会話の方向性を失う。ツール呼び出しが目的と無関係になる

これは「Lost in the Middle」(Liu et al., TACL 2024)で指摘された中間情報の忘却と同じ構造だ。エージェントの場合、ステップ3-4のツール結果がちょうど「中間」に押し出され、先頭のシステムプロンプトと末尾の最新結果だけが参照される。


大型モデルなら解決するのか

反論として重要なデータがある。

GPT-4.1はツール重視の対話で劣化しなかった。 Parloaの検証で、大型モデルは長い会話でも安定した性能を維持している。

MemAgentは8Kコンテキストから3.5Mトークンのタスクに外挿し、性能損失10%未満を達成した(OpenReview)。RLM(Recursive Language Model)は1000文書・10M+トークンでも91.33%の精度を維持している。

ただし、これらはすべて数十〜数百GBのメモリを持つ大型モデルかクラウド推論の話だ。

8GB VRAMで動く7Bモデルの場合:

  1. コンテキストウィンドウ自体が物理的に制限される(前述の通り)
  2. Attentionヘッド数が少ないため長距離依存関係の保持力が弱い
  3. GQA (Grouped Query Attention) でKVキャッシュは節約できるが、モデルの「記憶力」自体は改善しない

「モデルサイズが十分なら問題は緩和される」のは事実だが、「8GBでは設計で迂回するしかない」もまた事実だ。


迂回策① 短ループ×コンテキストリセット

最もシンプルで効果的な方法。エージェントのループを短く区切り、各ループの開始時にコンテキストをリセットする。

def short_loop_agent(task: str, tools: list, max_steps_per_loop: int = 5):
    """短ループ×リセット戦略のエージェント"""
    memory = []  # ループ間で引き継ぐ要約のみ
    
    while not is_task_complete(memory):
        # コンテキストを最小限で再構成
        context = build_context(
            system_prompt=SYSTEM_PROMPT,
            task=task,
            memory_summary=summarize(memory[-3:]),  # 直近3件の要約のみ
            tools=tools
        )
        
        # 短いループを実行
        for step in range(max_steps_per_loop):
            response = llm.generate(context)
            if response.tool_call:
                result = execute_tool(response.tool_call)
                context.append(result)
                memory.append({"step": step, "tool": response.tool_call, "result_summary": summarize_result(result)})
            else:
                break
        
        # ループ終了時: コンテキストをリセット、要約のみ持ち越し
        # KVキャッシュは解放される

ポイントはmemory_summaryだ。ループ間で渡すのは生のツール結果ではなく、その要約。これでKVキャッシュの蓄積を防ぎつつ、必要な情報は保持する。

5ステップ×6ループ=30ステップ相当のタスクを、各ループ約6,700トークン(KVキャッシュ0.36GB)で処理できる。30ステップを一気に回す場合の1.56GBと比べて、VRAM消費は4分の1以下だ。


迂回策② Persistent Q4 KV Cache

arXiv:2603.04428 "Agent Memory Below the Prompt" (2026) は、エージェントのKVキャッシュをディスクにQ4量子化して永続化し、必要時にAttentionレイヤーに直接ロードする手法を提案している。

この論文がApple M4 Proで検証した結果:

  • FP16では10.2GBのKVキャッシュ予算で3エージェント分しか保持できない
  • Q4量子化で4倍のエージェントコンテキストが同じメモリに収まる
  • キャッシュ復元によるTTFT (Time-to-First-Token) 改善: 最大136倍 (Gemmaで22〜136倍、DeepSeekで11〜76倍)

この手法の本質は「再計算の回避」だ。通常、コンテキストを復元するには全トークンのprefill計算をやり直す必要がある。Persistent KV Cacheはこの計算を丸ごとスキップして、保存済みのKVキャッシュを直接ロードする。

この論文はApple M4 Proで検証されたものだが、原理はRTX 4060でも同じだ。llama.cppには現時点でKVキャッシュの保存/復元APIが実験的に実装されている(--save-state, --load-state)。NVMe SSD上にエージェントごとのKVキャッシュスナップショットを保存し、タスク切り替え時にロードすることで、prefill再計算を回避できる。8GBでは同時に保持できるエージェントコンテキストが1つに限られるため、この「スワップ」戦略の恩恵がM4 Pro以上に大きい。


迂回策③ 動的ツール選択(Tool Loadout)

ツール定義が増えるとモデルの選択精度が落ちる。Berkeleyのfunction-calling leaderboardでも、ツール数が増えるほど説明が重複し正しいツールの選択が困難になる傾向が確認されている。経験的には、7Bモデルでは5〜10ツールが実用的な上限だ。ツール定義自体がコンテキストを消費し、KVキャッシュを圧迫するためだ。

解決策は「全ツールを常時定義しない」こと。

def dynamic_tool_selection(query: str, all_tools: list, max_tools: int = 5):
    """クエリに応じてツールを動的に選択"""
    # 軽量な分類器でクエリカテゴリを判定
    category = classify_query(query)  # "search", "code", "data", etc.
    
    # カテゴリに応じてツールサブセットを選択
    tool_groups = {
        "search": ["web_search", "file_search", "grep"],
        "code": ["run_python", "read_file", "write_file"],
        "data": ["sql_query", "csv_parse", "chart_generate"],
    }
    
    selected = tool_groups.get(category, all_tools[:max_tools])
    return selected

20ツールの定義をすべてコンテキストに入れると約6,000トークン。5ツールに絞れば約1,500トークン。差の4,500トークンは、Q4 KVキャッシュでも0.04GBの節約になる。小さく見えるが、30ステップのループではこの差が蓄積して1.2GB以上の差になる。


8GBエージェントの設計原則

3つの迂回策を組み合わせた設計原則をまとめる。

原則1: ループは5ステップ以内

7Bモデルのコンテキスト品質が維持できるのは約6,000〜8,000トークンまで。ツール呼び出し1回あたり900トークンとすると、5ステップが上限だ。

原則2: 記憶は「要約」で持ち越す

生のツール結果をコンテキストに残さない。各ループ終了時に結果を要約し、次のループではその要約のみを参照する。

原則3: ツール定義は5つまで

動的ツール選択で、各ステップに必要なツールだけをロードする。「万能エージェント」は8GBでは成立しない。

原則4: 「コンテキスト品質」を監視する

ツール呼び出しの「的中率」(呼んだツールが目的に合致していたか)を追跡し、低下したらループをリセットする。自動リセットのトリガーに使える。


8GBの制約はエージェント設計を改善する

128Kコンテキストの記事でも書いたが、8GBの制約は「ハンデ」ではなく「設計の強制力」だ。

クラウドの大型モデルは100ステップのエージェントループを力技で回せる。しかし、MicrosoftとSalesforceの研究が示すように、回せることと品質が維持されることは別問題だ。o3ですら39%劣化している。

8GBの制約は「5ステップで品質が落ちる」という事実を隠さない。だからこそ、短ループ設計・要約持ち越し・動的ツール選択という「本質的に正しい設計」に到達できる。これらの設計原則はクラウド環境でもそのまま適用できる——むしろ適用すべきだ。

エージェントの性能を決めるのは、コンテキストの長さではなく、コンテキストの品質だ。


参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?