ローカルLLMへ切り替えた直後、最初の返事が出るまで数分待つことがありました。
一応コンテキスト窓には収まっています。なので最初は「モデルが小さいから遅いのかな」と思っていました。
実際には、セッション開始時に渡していたものが多すぎました。共通ルール、リポジトリのルール、使わないプラグイン、MCPのschema、過去の会話まで合わせて約200KBありました。
これを約4KBまで落とすと、最初のツール実行がかなり早くなりました。
単にトークンを節約したというより、いまの仕事と関係ない話を先に読ませるのをやめた、という方が近いです。
長い入力は、入ればよいわけではなかった
最初はClaude Code側の環境を、できるだけそのままQwen側へ再現しようとしていました。
ルールやツールを減らすと能力も落ちそうだったので、一旦全部渡しておく方が安全だと思っていたためです。
ただ、動かしてみると次のようなことが起きました。
- 回答前のprompt evalが数分続く
- 依頼より先に一般的なルールの説明を始める
- 使えないツールを選んで止まる
- 前のTODOと現在のTODOが混ざる
- 「次にファイルを確認します」と書いて、そのまま終わる
コンテキスト窓の上限を超えたわけではありません。
入ってはいるけど、いま必要な情報の割合がかなり薄くなっていました。小さいモデルほど、この影響を受けやすそうです。
図1: 最初から全部読ませず、開始に必要なものだけ渡して、続きは作業中に取りに行きます。
残したものと、後回しにしたもの
「大事そうか」で選ぶと、だいたい全部大事に見えます・・・!
なので、開始時にいるかどうかで3つに分けました。
| 分け方 | 扱い | 例 |
|---|---|---|
| 開始時に必要 | handoffへ短く入れる | いまの目的、残りTODO、作業場所、完了条件 |
| 必要になったら読む | パスや探し方だけ渡す | 詳細仕様、過去の議論、関連コード、長いログ |
| 今回はいらない | 読み込まない | 未使用プラグイン、別領域のルール、全MCP schema |
ローカル継続用のモードでは、通常フック、指示ファイルの自動読込、プラグイン、MCP、自動メモリを外しました。
使える道具もRead、Edit、Bashだけです。
その代わり、次の短いルールは残しています。
- TODOは一度に一つ進める
- 推測する前に対象ファイルを読む
- 検索と確認にはBashを使う
- 元へ戻せる変更だけ行う
- 完了条件をコマンドで確認する
- 外部へ書く必要があるなら、ドラフトで止める
全ツールの説明を置くより、今回迷いそうな分岐だけを書いた方が動きやすかったです。
200KBから4KBへ減らした順番
まず、何が大きいかを分けて測る
合計だけ見ても削る場所が分からないので、発生元ごとにサイズを出しました。
global instructions 46 KB
project instructions 61 KB
rules 18 KB
tool / MCP schemas 52 KB
handoff / history 23 KB
----------------------------
total 200 KB
この数字は環境によって変わります。
自分が見たかったのは、全体が何KBかより「昨日より増えたのはどこか」でした。セッション開始時に内訳を記録しておくと、プラグイン追加などで急に重くなったときに分かります。
ルールを常駐させない
フロントエンドを触るときだけ必要なルールや、公開直前だけ必要な手順まで、毎回読み込む必要はありません。
ローカル側には対象作業で使う分だけ短く渡し、残りは索引とファイルパスを置きました。
必要になった時点で読めればよいので、開始時の負担はかなり減ります。ただ、参照先が権限の外にあると詰まるので、ローカル側から本当に読めるかは先に確認しています。
ツールも一旦3つまで減らす
ツールが多いとできることは増えますが、schemaも選択肢も増えます。
ローカルでファイル作業を続けるだけなら、ブラウザ操作やメール送信は必要ありません。Read、Edit、Bashがあれば、調べる、直す、確認するところまでは進められます。
もちろんBashを何でも実行してよいわけではなく、別の安全ゲートを置いています。
会話をそのまま渡さない
会話には、途中で捨てた案や同じ説明がかなり入っています。
引き継ぎ時は全文をコピーせず、最後の依頼、残りTODO、Gitの状態、直前出力の末尾へ変換します。
ここはシリーズ第3回のhandoffの記事で、もう少し詳しく書いています。
足りないものは、その場で取りに行く
4KBだけで最後まで作業するわけではありません。
最初は4KBで現在地をつかみ、必要なファイルだけ後から読みます。
小さいhandoffを読む
↓
最初のTODOを一つ選ぶ
↓
対象ファイルを検索して読む
↓
変更して確認する
↓
次のTODOへ進む
結果として、一度も使わなかった情報は最後までコンテキストへ入りません。
モデル設定は1箇所だけ見てもダメだった
自分の構成では、メイン処理、軽量処理、サブエージェントでモデル設定が分かれていました。
メインだけローカルモデルへ変えても、別経路にクラウド用のモデル名が残っていると、Ollamaへ存在しないモデルを要求して止まります。
起動前に、全部の経路が許可したモデルへ向いているか確認するようにしました。
main_model = local-model
small_model = local-model
subagent_model = local-model
allowed_models = [local-model]
設定ファイルだけでなく、実際のAPIリクエストへ出たモデル名もログで見ています。ここは設定した気になりやすいところでした。
削りすぎたときの見分け方
もちろん短ければ短いほどよいわけではありません。
必要な情報へたどり着けないと、モデルは推測で埋め始めます。
自分は次の動きが増えていないか見ています。
- あるはずのファイルを何度も探す
- 決まっている仕様をもう一度質問する
- 禁止した操作を試す
- 同じテストを理由なく繰り返す
- TODOの順番が分からなくなる
増えた場合も、元の200KBへ戻すことはしません。
何が足りなかったかを確認して、handoffか索引へ一つ足します。この調整を何度か繰り返して、いまは約4KBになっています。
何を比較しているか
- 開始時の入力バイト数と推定トークン数
- 最初のツール実行までの時間
- prompt eval時間
- 使えないツールを選んだ回数
- 追加で読んだファイル数と総量
- 検証まで終わった割合
- 同じ品質へ届くまでの再試行数
入力だけ短くなって完了率が落ちたら、普通に削りすぎです。
少し入力が増えても、最初の正しい行動が早くなって手直しが減るなら、そちらを選んでいます。
いまの確認項目
- コンテキストのサイズを発生元ごとに出しているか
- 開始時に必要、後で読む、今回は不要に分けたか
- 今回使わないツールを外したか
- 会話全文ではなく、再開に必要な状態を渡したか
- 詳細情報の場所と探し方を残したか
- メイン、軽量、サブ処理のモデル設定を確認したか
- サイズだけでなく初動時間と完了率を見たか
200KBを4KBへ減らした、と書くと圧縮の話に見えます。
実際にやったのは、最初に全部教えるのを諦めたことでした。現在地だけ渡して、必要なものはファイルから取りに行ってもらう。この形の方が、少なくとも自分のMacではローカルLLMが素直に仕事を始めてくれています。
ローカルLLM設計・全8回
MacでローカルLLMを実際に動かして、うまくいかなかったところを8つに分けて書いています。