自分のコーディングエージェントのワークフローのうち、どれだけをローカルに移して、ホスト型ツールにずっと払い続けるのを減らせるのかを見たかったんです。
もう1つの後押しがありました。Anthropic自身の4月23日のポストモーテムで、3月/4月までの間に製品レイヤーで回帰が起きていたことが確認されたからです。ローカルモデルなら、ベンチマークしたものがそのまま得られます。
制約はコンテキストでもありました。最低でも65K〜128Kの範囲で、実用に耐えるものが必要でした。
昼の大半、RTX 5080 16GBが手つかずで空いていました。Qwen3.6はコーディング面で十分な評価を得ていて、真面目に試す価値があるように思えました。Claude Codeは、ローカルのAnthropic互換/v1/messagesエンドポイントを指すことができます(Unslothがここに良いガイドを用意しています)。そこで方針はシンプルにしました。つまりClaude Codeのワークフローは維持しつつ、モデル提供だけをローカルのllama.cppに切り替える。
これはランキング(リーダーボード)のベンチマークではありません。1台のコンシューマーGPUで長いコンテキストのコーディングエージェントを実用にしようと試したときの、現場ログです。
ハードウェア
- RTX 5080 16GB(sm_120、コンシューマー向けBlackwell GB203)
- Ryzen 9700X(8c/16t)
- 96GB DDR5
- Windows 11
- iGPUがディスプレイ駆動、5080は演算専用
- PCIe Gen 5 x16
重要な注記:私が最終的に使ったフォークにはCUDA 12.9.1が必須です。CUDA 13.xはゴミ出力になり、13.1はMMQカーネルでセグフォールトします。痛い目を見て学びました。
フォーク
メインラインのllama.cppは動かしませんでした。最初はMadreag/turbo3-cudaから始めました(TheTom/llama-cpp-turboquant系譜のTurboQuant CUDAフォーク。TurboQuantはKVキャッシュにTCQ / Trellis-Coded Quantizationを追加し、1値あたり約3.125ビット)。私がパッチしたフォークはこちら:craftogrammer/llama.cpp-adaptive-turboquant。低いコンテキストでは問題なく、だいたい64K前後まではうまく動きましたが、狙っていた長いコンテキストでは速度が急激に落ちました。なぜなのか理解したくて、d=65Kの密な27Bに対してncu(Nsight Compute)でデコードをプロファイルしました。mul_mat_q<IQ3_S>が、プロファイルされたデコード時間の43%を食っていました。さらに掘り下げると、スレッドあたり254レジスタ、理論上のoccupancyは約12.5%、DRAMスループットは7%未満でした。つまりカーネルはレジスタ束縛で、メモリ束縛ではありません。cp.async、プリフェッチ、パイプライン化のようなトリックは効きません。そこで、コミットしたカーネル変更を2つ(共有メモリへのバックトレース、アラインメント修正)と、ローカル実験を1つ(MMQタイルロードでcp.asyncを試してテスト後に戻す)行い、さらにそれぞれきれいに再ベンチしました:合計で+0.16%。結論は無。小さめのインライン化やベクタ化による勝ちを積み上げると(V-dequantのインライン、バイトペアのベクタ化、minBlocksの増加、スコアラーのインライン)、d=0で+0.7%から、d=64Kで+13%までスケールしました。個々は小さいけれど、深さ方向で積み重なって効いていました。
また、測って却下した2つのアイデアも試しました。think-anchorメカニズム(推論トークンにアンカーしたfp16のシンクレンジ。測定値は−0.28%、TG vs 無効で、出荷しないことにしました)と、疎なVのしきい値を調整するランタイムノブ(測定値は−32%デコード回帰、20.4 vs 29.8 t/s。アップストリームで検証済みの定数に戻しました)。どちらも実際に時間を使ったので、負の結果も含めて正直な全体像として言及します。
その過程でsm_120のptxas問題にもぶつかりました。FA vecカーネルではoccupancyのヒントを後退させる必要がありました(minBlocksを高くするとコンパイラがクラッシュ)。また、いくつかのTCQヘルパーは__noinline__のままにしておく必要があり、特定のTUでは--ptxas-options=-O0が必要でした。見落としがちな点を1つ挙げると、prefetch.global.L2はsm_120のSASSでCCTL.E.PF2に落ちます。PRFではなく、CCTLでgrepしてください。
これらの調査結果の上に立って、フォークを適応的KVモード選択、MoEオフロード調整、RTX 5080 16GB向けのタイトなVRAM対策でパッチしました。
最初の試み:Qwen3.6-27B dense
このモデルは16GB向けの自然な選択に見えました。Hybrid Transformer-Mambaで、KVキャッシュを持つのは16/64層だけです。メモリ計算も紙の上では問題なさそうでした。
そして低いコンテキストでは、うまくいきました。NEO-CODE IQ3_M量子化で、空のコンテキスト時に40 t/s。実用可能。
ただ、その後に深さスイープを回して、コンテキストが伸びると実際にどうなるか確認しました:
| コンテキスト深さ | デコード(t/s) |
|---|---|
| 0 | 40.5 |
| 16K | 17.4 |
| 32K | 10.6 |
| 65K | 6.0 |
| 128K | 3.2 |
128Kで3.2トークン毎秒。実際には、会話が長くなるとClaude Codeはとにかく辛いほど遅く感じました。その理由を、深さベンチを回した後に説明できることがわかりました。カーブが、まさに私が体感していたものと一致していました。
私はこの調整に何日も費やしました。ubatchサイズとスレッド数の9通りの組み合わせを総当たりで試しました。9つすべての間でのばらつきは0.46 t/sしかありませんでした。デコードは完全に帯域(バンド幅)に縛られていて、調整する余地がありませんでした。
IQ3_Mは品質面での選択というより、収まるための唯一の選択肢でした。では16GBで131Kコンテキストだと、量子化の状況はどう見えるかというと:
| Quant | ファイルサイズ | 131Kで収まる? |
|---|---|---|
| NEO-CODE IQ3_M(DavidAU提供) | 12.0 GiB | はい |
| UD-Q3_K_XL | 13.5 GiB | はい(きつい) |
| IQ4_XS | 14.3 GiB | いいえ(約1.6 GiBオーバー) |
| Q4_K_S | 14.8 GiB | いいえ |
| IQ4_NL | 15.0 GiB | いいえ |
| Q4_K_M | 15.7 GiB | いいえ |
| Q5 / Q6 | 19+ GiB | 5090の領域 |
Q4クラス以上の量子化は、実用的なコンテキストで密な27B + 16GBでは手が届きません。IQ4_XSに必要なのはCPUへ約7層をオフロードすることでしたが、それではデコードが約5 t/sまで落ちてしまい、目的が台無しになります。したがって、私はIQ3_Mの品質で固定され、しかも深さカーブのせいでエージェントのループがつらい状態でした。
ついにMoEの道を試す決め手になったのは、具体的なコーディングテストです。どちらのモデルにも、レストランの会計割り勘(整数のpaisa、合計の厳密不変条件、4つのテストケース)を解かせました。denseの27Bは、personSubtotalsではなくpersonSubtitlesを書いてしまい、3回とも実行できないコードになっていました。35B-A3B MoEは、4つすべてのテストに通るクリーンなBigIntコードを書き、しかも54%多くのトークンを生成しているのに、壁時計時間はより短く済みました。この瞬間に、denseルートで頑張って改善するのをやめることにしました。
なぜMoEの道を試したのか
16GBに完全に収まらないモデルでも、一部のエキスパートをシステムRAMにオフロードすれば、長コンテキストのコーディングで役に立つのでしょうか?
私はこの条件(コンシューマーBlackwell、16GBのGPUが1枚、長いコーディングエージェントのコンテキスト、部分的なMoEオフロード)について、十分な数値データを見たことがありませんでした。だから私は、「35B合計」という数字を自動的に無理だと決めつけるのではなく、エンドツーエンドで実際に試しました。
| コンテキスト深さ | 27B dense(旧パス) | 35B-A3B MoE(最終パス) |
|---|---|---|
| 0 | 40.5 | 91.8 |
| 16K | 17.4 | 76.9 |
| 32K | 10.6 | 54.1 |
| 65K | 6.0 | 46.2 |
| 128K | 3.2 | 30.4 |
制御された「単一変数」の比較ではありません。モデル、量子化、オフロード分割、そしてKVレイアウトを変更しました。結論は実用的です。denseはエージェントのコンテキストでは使えず、MoEはチューニング後に使えるようになりました。
オフロードのバランスがすべて
UD-Q4_K_XL GGUF(20.81 GiB)で、d=16Kにおける ncmoe のスイープ:
| ncmoe | tg32(t/s) | 注記 |
|---|---|---|
| 40(全CPU) | 36.4 | ベースライン |
| 20 | 53.2 | |
| 16 | 58.9 | このファイルでのスイートスポット |
| 12 | 36.1 | VRAMの崖にヒット |
| 8 | 5.9 | 壊滅的なスピル |
その「崖」は鋭いです。スイートスポットは、GGUFファイルのサイズと、KV割り当て後に使えるVRAMの量の関係に依存します。
APEX-I-Compact(クレジット: mudler(Hugging Face上))が勝ったのは、そのファイルが小さかった(20.8 GiBではなく16.1 GiB)ため、ncmoe=16ではなくncmoe=8を使えたからです。これによりPCIeへの負荷が十分に下がって効きました:
| コンテキスト深さ | UD-Q4_K_XL(ncmoe=16) | APEX-I-Compact(ncmoe=8) |
|---|---|---|
| 0 | 51.6 | 92.3 |
| 16K | 58.9 | 75.9 |
| 32K | 49.3 | 64.2 |
| 65K | 39.4 | 48.0 |
| 128K | — | 31.3 |
APEX-I-Quality(Q6_K、21.25 GiB)もテストしました。ncmoe=20が必要で、VRAMのスラッシングを避けるためです。そのオフロードレベルでは、共有テストハーネス上で、同じ品質のUDと同程度の速度でした。どの軸でも、どちらのキーパーにも勝てませんでした。削除しました。
僕のコーディング・ベンチマークは間違っていた(あなたのもそうかもしれない)
当初、UDの方が明らかに品質が良いと思っていました:APEX-I-Compactが29/32だったのに対し、33/34でした。差は6.5パーセントポイント。
しかし実際に何が起きているかを見ると、状況が変わりました。各モデルは自分自身のテストスイートを作り、自分自身の実装も作っていたのです。壊れているテストを4つ含む19テストを書いたモデルは15/19、きれいな11テストを書いたモデルは11/11でした。このベンチマークは (実装品質 × テスト品質) を採点して、それを実装品質と呼んでいました。
見つけた具体的なバグ:
- APEX-I-Compactには本当の実装バグがありました:購読(サブスクリプション)により
b.priorityがoptions.priorityとして保存されるため未定義になっていました。ソートのコンパレータがNaNを返し、ソートが発生しませんでした。 - APEX-I-Qualityは、ノーオペのハンドラが配列を埋めるはずだったケースで、ハンドラの削除後に宣言された配列を対象にしていました。テストが壊れており、実装ではありませんでした。
- プロンプトに、スナップショットを「emit中に取る」セマンティクスについて矛盾した条項があり、各モデルが違う解釈をしたものの、いずれも一貫していました。
プロンプトを修正した後、サンプリングを決定的に固定(temp=0、seed=42)し、3つすべてを1つの共有11テスト・ハーネスで採点しました:
| モデル | デコード t/s | 共有ハーネス |
|---|---|---|
| UD-Q4_K_XL | 64.5 | 11/11 |
| APEX-I-Compact | 86.7 | 11/11 |
| APEX-I-Quality | 53.4 | 11/11 |
品質のギャップは消えました。速度のギャップは消えませんでした。
ローカルでコーディング評価をするなら:共有のテストハーネスを使い、サンプリングを固定し、プロンプトを曖昧さなくしてください。 自作のテストは品質のシグナルになりません。
「すべてを圧縮する」トラップ
セットアップから得られた発見の1つで、他でも試す価値があるかもしれない点があります。KVの圧縮を増やしても、長いコンテキストでは必ずしも速くならない、ということです。
フォーク側で異なるKVキャッシュのレイアウトをテストしました。「TCQで全ての注意(attention)レイヤーを圧縮する」から、「一部のK+Vレイヤーをq8_0に昇格させる」までの範囲です。ここでは正確なモードマップは意図的に載せません。フォーク固有で、しかもまだ変わっているためです。ただ、結果の形はこうです:
| KVレイアウト | d=0 | d=16K | d=32K | d=65K | d=128K |
|---|---|---|---|---|---|
| 全て圧縮 | 86.8 | 55.2 | 42.3 | 28.3 | 16.6 |
| ハイブリッド(いくつかのレイヤーをq8_0) | 91.8 | 76.9 | 54.1 | 46.2 | 30.4 |
d=128Kでは、ハイブリッドレイアウトが全圧縮よりほぼ 2倍速いです。
なぜそうなるのか、確たる説明はまだありません。作業仮説は、TCQのコードブック参照(lookup)のオーバーヘッドが、Kの読み取り数に対して線形に増えるというものです。より深いコンテキストでは、読み取りごとのコストをより多く払うことになります。最もアクセスされるレイヤーをq8_0に昇格させることで、最も効くところでそのコストを回避します。原因が何であれ、測定結果は明確です。もしTCQや圧縮KVの方式を動かしているなら、d=0ではなく、実際に使うコンテキスト深さでテストしてください。
レイアウトを手動で選ぶのを避けるため、オートセレクタを書きました。キャッシュ確保時に ggml_backend_dev_memory で空きVRAMを調べ、KVサイズを、アロケータが使うのと同じ ggml_row_size の式で各レイアウトごとに見積もり、空きVRAMから計算ピーク用マージン(1 GiB)を引いた下で収まる範囲の最も攻めた(aggressive)モードを選びます。検証済み:予測1510 MiB、実際の確保は1509.88 MiB。より大きいカードでは攻めたままです。VRAMがきつい場合は自動的にフォールバックします。手動で制御したい場合は TURBO_LAYER_ADAPTIVE=N で上書きしてください。
現在のところ
日常運用の設定:
- モデル: Qwen3.6-35B-A3B APEX-I-Compact(16.10 GiB)
- フォーク: craftogrammer/llama.cpp-adaptive-turboquant, CUDA 12.9.1, sm_120
- オフロード: CPU上に8つのエキスパートレイヤー(
--n-cpu-moe 8) - コンテキスト: 131072(128K)
- KV: turbo3_tcq(自動選択されたハイブリッドレイアウト)
- サンプリング: temp=0.6, top_p=0.95, top_k=20
Claude Codeは ANTHROPIC_BASE_URL=http://127.0.0.1:8080 を通じてこの設定に接続します。実リクエスト1件からのサーバ側ログ:1078トークンのプロンプトプリフィルが1582 t/s、538トークンのデコードが90.7 t/s。
VRAMは、持続的な128Kデコード中に約13.3 / 16.0 GBに収まっています。きついですが、スピルは起きません。
プロンプトキャッシュ(--cache-ram -1)は、最初のターン以降のエージェントループを大幅に高速化します。23Kトークンのプロンプトのコールドプリフィルは1787 t/sで約13秒かかる一方、同様のプレフィックスを持つ次のターンではプレフィルがその差分のみ再実行され、419〜569 t/sになります。ハイブリッドMamba+Attentionでの落とし穴として、プレフィックスの不一致(動的なタイムスタンプやリクエストIDでさえ)だとSSM状態を部分的に巻き戻せないため、必ず全量の再プリフィルが発生します。
現実世界で回帰が起きたときのフォールバック:ncmoe=16のUD-Q4_K_XLで、約62 t/s。共有ハーネス上では同等の品質でした。
天井はハードウェア
PCIe Gen 5 x16は、MoEのデコード中に約89%まで飽和します(理論上の上限が約63 GB/sのところ、56〜61 GB/sのバースト)。SM利用率は93〜97%です。この状況で、明らかなチューニングの余地は残っていないように見えます。
d=65Kで39〜48 t/s、d=128Kで約30 t/s。これがこのハードウェアが出せる性能です。長いコンテキストで50 t/s以上を安定して出すには、より多くの巧妙なカーネルではなく、より多いVRAMが必要です(CPU上のエキスパート数を減らせば、PCIeトラフィックが減るため)。そうなったらMSRPで5090を待ちます。
16GBカードで試したいなら
要点だけ言うと:APEX-I-Compactがうまくいったのであれば、それを参考に、約16 GiBのGGUFでQwen3.6-35B-A3Bを入手し、目標のコンテキスト深さに合わせてncmoeをスイープしてください(d=0ではありません)。最適点は狭く、ファイルサイズに依存します。私の5080では、16 GiBのファイルはncmoe=8、21 GiBのファイルはncmoe=16でした。
TurboQuant由来のフォークで圧縮KVを使っている場合は、実際の稼働深さでテストしてください。全圧縮は128Kではハイブリッド配置より約2倍遅くなることを見つけました。d=0のベンチマークではそれは分かりません。
もう一つ、ちょうど公開されたので先回りしておく価値がある点:私はメインラインのNVFP4(b8967)を、出荷された当日に同じ条件でベンチを取っていました。MoE+offloadでは、フォークが39〜51 t/sなのに対しメインラインは15〜16 t/s。GitHubの




