Claude Codeをllama.cpp + Qwen3.8で動かす ― reasoning・Tool Calling・コンテキスト長の設定
Claude Codeをllama.cpp + Qwen3.8で動かす ― reasoning・Tool Calling・コンテキスト長の設定
Claude Codeは通常AnthropicのClaude APIをバックエンドとして利用しますが、Anthropic互換APIを用意すれば、ローカルLLMをバックエンドとして利用することもできます。
今回はApple Silicon Mac上でllama.cppのllama-server + Qwen3.8-27Bを起動し、Claude Codeから利用するための設定をまとめます。
単純にAPIを接続するだけなら比較的簡単ですが、実際にCoding Agentとして使おうとすると、reasoning、Tool Calling、chat template、コンテキスト長など、いくつか調整が必要でした。
検証環境は以下です。
- MacBook Pro / M5 Max
- ユニファイドメモリ 128GB
- llama.cpp
- Qwen3.8-27B-UD-Q4_K_XL.gguf
- Claude Code
llama-serverの起動設定
最終的には、おおむね以下の構成でllama-serverを起動しています。
~/src/llama.cpp/build/bin/llama-server \
--model ~/models/qwen3.8-27b-unsloth/Qwen3.8-27B-UD-Q4_K_XL.gguf \
--host 127.0.0.1 \
--port 8080 \
--ctx-size 131072 \
--n-predict 8192 \
--parallel 1 \
--flash-attn on \
--batch-size 4096 \
--ubatch-size 2048 \
--cache-type-k q8_0 \
--cache-type-v q8_0 \
-ngl 999 \
--jinja \
--chat-template-file ~/qwen-agent.jinja \
--reasoning-format deepseek \
--reasoning-budget 2048
各設定について順番に説明します。
reasoningを正しく分離する
Qwen3系のreasoningモデルをそのままClaude Codeから利用すると、モデルが次のようなレスポンスを生成することがあります。
<think>
ファイルを確認して……
</think>
<tool_call>
...
</tool_call>
この出力自体はモデルとしては正常ですが、これが通常のassistant messageのcontentとしてClaude Codeへ返ってしまうと問題になります。
Claude CodeはTool Callingを構造化されたAPIレスポンスとして扱うため、<think>や<tool_call>が単なるテキストとして返ると、ツールが実行されず処理が止まってしまうことがあります。
そこでllama-server側でreasoningを分離します。
--reasoning-format deepseek \
--reasoning-budget 2048
--reasoning-format deepseekを指定することで、<think>部分を通常のcontentとは分離して扱えるようになります。
また、reasoningを無制限にするとCoding Agentでは待ち時間が長くなりやすいため、今回は--reasoning-budget 2048としています。
単純な編集作業を中心にするなら1024程度まで下げてもよいでしょう。逆に設計や複雑な調査を多く行わせるなら4096程度まで増やす余地があります。
標準Chat TemplateではClaude Codeと衝突する場合がある
GGUFに含まれているQwen標準のChat Templateをそのまま使用すると、Claude Codeからのリクエストで次のようなエラーになることがありました。
Jinja Exception: System message must be at the beginning.
Qwen側のテンプレートがsystem messageの位置を厳密にチェックしている一方、Claude Codeから送られてくるメッセージ列が必ずしもその前提を満たさないためです。
そのため、今回は独自のJinja Chat Templateを用意しています。
ただし、単純に次のようなChatMLだけを実装するのは不十分です。
{% for message in messages %}
{{ '<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>\n' }}
{% endfor %}
{% if add_generation_prompt %}{{ '<|im_start|>assistant\n' }}{% endif %}
通常のチャットならこれでも動きますが、Claude CodeではTool Callingへの対応が必要です。
Claude Code向けChat Template
(ChatGPTが)作成したChat Templateはこちら
使用するテンプレートでは、少なくとも次の点を処理できる必要があります。
- system / developer message
- toolsの定義
- assistantからのtool_calls
- 複数のTool Call
- tool response
- reasoning
特にQwen系では、Tool Callingを次のような形式でモデルに渡します。
<tool_call>
<function=Read>
<parameter=file_path>
/workspace/example.php
</parameter>
</function>
</tool_call>
Toolの実行結果についても、
<tool_response>
...
</tool_response>
として会話履歴へ戻します。
また、Claude Codeとの互換性を優先するため、途中に現れたsystem/developer messageで例外を発生させるのではなく、最初のsystem blockへまとめるようにしています。
llama-server起動時には、このテンプレートを明示します。
--jinja \
--chat-template-file ~/bin/qwen-agent.jinja
ここで重要なのは、--skip-chat-parsingを指定しないことです。
Tool Callやreasoningを構造化されたAPIレスポンスとしてClaude Codeへ返したいので、それらを単純なcontentとして返す設定にはしません。
コンテキスト長はかなり大きめに必要
Claude CodeをローカルLLMで使っていて特に注意が必要だったのがコンテキスト長です。
Claude Codeで見えているチャット履歴がそれほど長くなくても、実際にLLMへ送られるpromptはかなり大きくなります。
リクエストには、例えば次のような情報が含まれます。
- System Prompt
- Claude Code内部のinstructions
- Tool definitions
- MCP Tool definitions
- CLAUDE.md
- 会話履歴
- Readしたファイル
- grepなどの検索結果
- git diff
- shellの実行結果
- Sub Agentの結果
実際の作業では、100,000 tokenを超えるrequestが発生することもありました。
そのため、当初使用していた64Kでは不足し、最終的にはllama.cpp側を128Kにしています。
--ctx-size 131072
128GBのユニファイドメモリを搭載したM5 Maxで27B Q4モデルを使用しているため、この程度のコンテキストを確保する余裕があります。
Claude Code側のコンテキスト上限
llama.cpp側だけでなく、Claude Code側にもバックエンドのコンテキスト長を認識させる必要があります。
ただし、llama.cppの物理的な上限である131072 tokenを、そのままClaude Codeにも使わせるのは避けた方が安全です。
Tool Callingやreasoning、compact処理などのための余裕を残しておきます。
例えば128Kのllama.cppに対して、Claude Code側は112K程度を上限とします。
llama.cpp context : 131072
Claude Code session : 114688
reserve : 16384
環境変数を使用する場合も同様に、llama.cppより少し小さい値を設定します。
export CLAUDE_CODE_MAX_CONTEXT_TOKENS=114688
export CLAUDE_CODE_MAX_OUTPUT_TOKENS=8192
Auto Compactは早めに実行させる
Claude Codeはコンテキストが大きくなると、それまでの会話を要約するCompact処理を行います。
ローカルLLMの場合、コンテキスト上限ギリギリまで使用してからCompactすると、Compactそのものが非常に重い処理になります。
100K token近いpromptをQwen3.8-27Bへ渡して要約させることになるためです。
そのため、利用している環境やラッパーでtoken budgetを設定できる場合は、70~75%程度でCompactを開始する設定が扱いやすいでしょう。
例えば次のような設定です。
{
"tokenBudget": {
"sessionLimit": 114688,
"autoCompactAt": 0.7
},
"autoCompactThreshold": 70
}
この場合、約80K token付近がCompact開始の目安になります。
なお、Claude Codeのバージョンや利用している互換レイヤーによって、これらの設定項目が実際に利用されるかどうかは異なるため、実際に何token付近でCompactが開始されるか確認した方がよいでしょう。
Compactが非常に遅い場合
ローカルLLMではCompactにも通常のLLM inferenceが使用されるため、クラウド版Claudeよりかなり時間がかかることがあります。
特に100K token前後まで育ったセッションでは、
巨大なpromptのprefill
↓
reasoning
↓
会話全体のsummary生成
という処理になるため、かなり重くなります。
llama.cpp側が本当に処理を続けているか確認したい場合は、slotの状態を見ることができます。
curl -s http://localhost:8080/slots | jq
token数が増え続けていればLLM側は処理中です。
逆にllama.cpp側の処理が完了しているにもかかわらずClaude Code側がCompact表示のままなら、Claude Code側で処理が停止している可能性があります。
KV CacheはQ8を使用
長いコンテキストを使用するとKV Cacheのメモリ消費も大きくなります。
今回は次の設定にしています。
--cache-type-k q8_0 \
--cache-type-v q8_0
Q4などへさらに量子化すればメモリ消費を減らせますが、128GBのメモリがあるため、Coding Agentで長いコンテキストを扱うことを優先してQ8としています。
batch / ubatch
Prompt Processingについては次の設定を使用しています。
--batch-size 4096 \
--ubatch-size 2048
当初は両方4096としていましたが、physical batchであるubatch-sizeは2048程度に抑えています。
Claude Codeでは長いpromptを何度も処理するため、通常のチャット以上にPrompt Processing性能が重要になります。
チューニングする場合は生成時のtokens/secだけではなく、llama.cppログのprompt eval timeも確認するとよいでしょう。
parallelを増やす場合
複数のClaude Codeを同時に使いたい場合は、
--parallel 2
などとすることで複数リクエストを並行処理できます。
ただし、Apple SiliconではGPUやメモリ帯域そのものが増えるわけではないため、同時にInferenceが走れば各リクエストの速度は低下します。
また、大きなコンテキストを複数slotで扱う場合はKV Cacheのメモリ消費にも注意が必要です。
Coding Agentの場合、
LLM inference
↓
Read / Grep / Bash
↓
build
↓
LLM inference
という動きをするため、複数Agentを起動していても常にLLM inferenceが重なるわけではありません。
そのため、個人で複数プロジェクトを並行して作業する程度なら、まず--parallel 2までを試すのが現実的だと思います。
一つのAgentの速度を最大化したい場合は--parallel 1のままにします。
生成上限はreasoning budgetより大きくする
今回は、
--reasoning-budget 2048
--n-predict 8192
としています。
--reasoning-budgetと--n-predictは別物です。
reasoningだけである程度tokenを消費するため、--n-predict 4096程度では、大きなコード生成などで余裕がなくなる可能性があります。
8192程度を上限にしておけば、通常はEOSやTool Callによってそれ以前に生成が終了するため、毎回8192 token生成されるわけではありません。
Apple Silicon向けGPU設定
Apple Siliconでは、
--flash-attn on \
-ngl 999
とし、可能なレイヤーをMetalへoffloadしています。
特に128Kのような長いコンテキストを利用する場合、Flash Attentionは有効にしておいた方がよいでしょう。
最終的なllama-server起動例
以上をまとめると、M5 Max 128GB環境では次のような構成から始めるのがよさそうです。
~/src/llama.cpp/build/bin/llama-server \
--model ~/models/qwen3.8-27b-unsloth/Qwen3.8-27B-UD-Q4_K_XL.gguf \
--host 127.0.0.1 \
--port 8080 \
\
--ctx-size 131072 \
--n-predict 8192 \
--parallel 1 \
\
--flash-attn on \
-ngl 999 \
\
--batch-size 4096 \
--ubatch-size 2048 \
\
--cache-type-k q8_0 \
--cache-type-v q8_0 \
\
--jinja \
--chat-template-file ~/qwen-agent.jinja \
--reasoning-format deepseek \
--reasoning-budget 2048
複数のClaude Codeを並行して利用したければ、メモリ使用量とInference速度を確認しながら、
--parallel 2
を試してみるのがよいでしょう。
まとめ
Claude Codeからllama.cpp上のQwen3.8を利用する場合、単純にAnthropic互換APIへ接続するだけでは、Coding Agentとして安定して動作しないことがあります。
特に重要だったのは次の点です。
- reasoningを通常のcontentから分離する
- Qwen形式のTool Callingに対応したChat Templateを使用する
- Claude Codeが送るsystem/developer messageを許容する
- Tool Callをplain textではなく構造化されたTool Callとして返す
- Claude Codeでは見た目以上にコンテキストを消費することを考慮する
- 128Kすべてを使わせず、Compact用の余裕を確保する
- コンテキストが巨大になる前にCompactする
ローカルLLMでCoding Agentを動かす場合、モデル単体のベンチマーク性能だけではなく、Tool Calling、Prompt Processing、KV Cache、Context Managementまで含めたシステム全体の構成が使い勝手に大きく影響します。
M5 Max 128GBクラスであれば、27B Q4モデルに128K程度のコンテキストを持たせる構成は十分現実的です。より大きなモデルへメモリを振るよりも、Claude Code用途ではある程度モデルサイズを抑え、長いコンテキストとKV Cacheへメモリを使う構成も有力な選択肢になると思います。