推測で4回直して、4回とも外した

Moonshot AIのKimi K3をClaude Codeで動かしたかった。安いし、性能も高いという評判だったからだ。

軽い作業は普通に動く。バグを2つ直して実行確認までさせるタスクは45秒で完璧に終わる。ところが少し長い作業 — バグを3つ直して回帰テストを3本書いて、テストを全部通すところまで — になると、途中まで進んで止まる。

最初は「遅い・高いから」だと思って設定で削りにいった。上流プロバイダを固定し、思考の量を絞り、ツール定義を減らした。4回やって4回とも直らなかった。

そこで手を止めて、最適化を全部外した元の設定でもう一度走らせた。すると、それも完走しなかった。

つまり自分の変更が壊したのではなかった。最初に1回だけ成功したのは、単に運が良かっただけだった。推測でいじっていた4回は、存在しない原因を直そうとしていたことになる。

デバッグログに1行だけ書いてあった

Claude Codeにはログを吐かせるオプションがある。取ってみたら、決定的な2行が並んでいた。

tool_dispatch_end tool=Read outcome=ok durationMs=33
[WARN] Streaming idle warning: no chunks received for 150s

ツールの呼び出しは成功している。その次の応答が、まったく返ってこない。 Claude Codeは150秒待って諦め、エラーで落ちる。

原因はモデルの賢さでもツール定義でもなく、ストリーミング配信がツール結果の直後に固まることだった。軽い作業が通って長い作業だけ壊れる理由もこれで説明がつく。ツールを何度も呼ぶほど、固まる機会が増えるからだ。

対策はストリーミングをやめることだった。上流には一括で問い合わせ、受け取った答えをこちらでストリーミング形式に組み立て直す小さな中継を書いた。逐次表示は失われるが、完走するかどうかのほうがはるかに重要だ。

結果はこうなった。

完走時間コスト
そのまま接続3回中1回185秒$0.36
中継あり2回とも80秒 / 163秒$0.23 / $0.27

速くなって、安くなって、そして止まらなくなった。

ついでに見つかった、静かな地雷

同じ調査の途中で、気づきにくい罠が2つ出てきた。中継サービスは同じモデルを複数の事業者に振り分けていて、その事業者ごとに仕様が違う。

ひとつは、扱える文章の長さが8,000トークンしかない事業者が混ざっていること。Claude Codeは最初の1回で14,000トークン以上を送るので、そこに当たった瞬間に死ぬ。

もうひとつは、ツール呼び出しに対応していない事業者が混ざっていること。ツールを使う前提で投げると、構造化された返事を返せない。

どちらも「たまたま当たると壊れる」ので、再現性がなく、原因を追いにくい。事業者を明示的に固定してしまえば両方消える。

そしてもうひとつ、コストの支配項は意外なところにあった。ツール定義が249個も送られていた。 連携しているサービスの機能が全部ツールとして毎回積まれていたのだ。これを切ると249個が25個に減り、送信量は47,000トークンから17,700トークンに落ちた。入力コストの大半は、使ってもいない機能の説明文だった。

4モデルで同じ仕事をさせて、実額を並べた

せっかくなので、まったく同じ課題を4つのモデルに解かせて、実際に請求された額を測った。課題はiCalendar形式のパーサにあるバグ3件を直し、回帰テストを3本追加し、テストを全部通すこと。結果は毎回自分でテストを実行し直して確認した。

実額時間書いたテスト
Kimi K3$0.23〜0.2780秒 / 163秒5件
Claude Sonnet 5$0.44153秒6件
Claude Opus 5$0.62164秒15件
Claude Fable 5$0.7076秒5件

意外だったのはFableが一番速かったこと。Sonnetの半分の時間で終わって、コストは1.6倍。出力したトークン数を見ると、Sonnetが11,343なのに対しFableは4,496だった。遠回りせず短く終わらせているぶん速い、という構図だ。

Opusはテストを15件書いていた。頼んだのは3件の追加なので、13件は指示していない。網羅性は上がるが、頼んでいない仕事でもある。速さと網羅性とコストは、素直には並ばない。

なお品質そのものには、この課題では大きな差が出なかった。修正内容はどれもほぼ同じで、Kimi K3が書いたテストも、行の折り返し処理が正しく連結されるところまでちゃんと検証していた。安いモデルが雑、ということはなかった。

プロンプトキャッシュは3〜4倍効いている

上の数字は、実は全部キャッシュが効いた状態のものだ。切るとどうなるかを、実際のトークン数から計算した。

キャッシュから読んだ量ヒット率キャッシュ無しなら
Kimi K399,23776.3%2.7倍
Sonnet 5366,43793.2%3.6倍
Opus 5386,47394.9%4.1倍
Fable 5157,89290.8%3.4倍

仕組みはどのモデルも共通で、キャッシュから読むと入力単価の10分の1、書き込むと1.25倍。同じ前置きを2回使えば元が取れる。

AIエージェントは毎回おなじ長い前置きと道具の説明を送り直すので、ここが効く。Opus 5にいたっては、実際に満額請求された生の入力はわずか25トークンで、残りの386,473トークンは全部キャッシュから読んでいた。

単独で確かめた実験もある。同じ長い文章をキャッシュ指定の有無だけ変えて2回投げたら、$0.042 が $0.0047 になった。9倍の差だ。実際の作業で2.7〜4.1倍に収まるのは、初回の書き込み分と、毎回変わる部分があるからだ。

ここで大事なのは、キャッシュを効かせるには前置きが1バイトも変わらないことが要る、という点。前置きに現在時刻を混ぜたり、道具の一覧を毎回組み替えたりすると、それだけで全部無効になる。効いているかどうかは応答に含まれる数字を見れば分かるので、まず見たほうがいい。

GPUを自分で借りると、まず「起動しない」

同じ週に、Kimi K3を自前のGPUで動かせないかも試した。こちらは別の記事に詳しく書いたが、教訓が今日の話と地続きなので要点だけ。

8枚構成のGPUを6回借りて、5回失敗した。

しかも最初の5回は、失敗の理由がまったく見えなかった。使っていた配信ソフトの公式イメージにSSHが入っておらず、中で何が起きているのかログを1行も読めなかったからだ。ダウンロードが遅いのか、共有メモリが足りないのか、起動が単に遅いのか — 全部推測で、全部ハズレだった。約$29が原因不明のまま溶けた。

SSHが入ったイメージに変えてログを読んだら、5分で分かった。モデルの重みの一部の寸法が96で、配信ソフトが要求する128の倍数で割り切れない、というだけの話だった。GPUの枚数を変えても直らない種類の非互換で、対応版の重みを使えば済む。

そして今日のClaude Codeの件も、まったく同じ形をしていた。推測で4回外して、ログを取ったら1行で分かった。

道具の起動品質というのは、速いとか安いとかの前に「動かなかったとき、なぜ動かないかが見えるか」で決まる。ログが読めない環境は、それだけで高くつく。

結局なにを学んだか

推測で直すのをやめて、対照実験をする。 今回の転機は、最適化を全部外して元の設定を走らせ直したところだった。それで初めて「自分のせいではない」と分かった。直した気になっている間は、ずっと的外れなところを触っていた。

測っていない数字は信じない。 「遅い」「高い」の理由は、思っていたところには無かった。コストの支配項は使ってもいない機能の説明文で、遅さの原因はストリーミングの停止だった。どちらも最初の仮説には入っていなかった。

ログが読めるかどうかは、性能より先に効く。 GPUの5連敗も、Claude Codeの4連敗も、原因は同じ「見えていなかった」だ。見えた瞬間にどちらも数分で解決した。

安い選択肢は、思ったより悪くない。 一番安いモデルの成果物が一番雑ということはなかった。ただし、そこに至るまでに中継を1つ自分で書く必要があった。安さには手間という値札がついている。

数字は全部この日に自分で測ったもので、テストは毎回自分で実行し直して確認した。環境が違えば結果も変わると思うので、気になる人は自分の課題で測ってみてほしい。測ると、だいたい予想と違う。