長年潜っていて初投稿です。4x RTX 3090 上で、各モデルそれぞれ20回以上のセッションにわたるライブなエージェント的作業を3つのQwenモデルで回しました — Qwen3.5-27B dense、Qwen3.5-122B-A10B MoE、Qwen3.6-35B-A3B MoE。以下の数字は、合成ベンチではなく、一定のオーガニックな負荷のもとでのvLLMログから解析したものです。
この投稿のすべての数字に効いてくる重要なワークロードの前提: ハーネスはマルチエージェントのオーケストレータで、1〜6並列のOpenCodeセッションを30〜60kトークンのプロンプトで実行し、さらに厳格なbash許可リストを強制しています。ツールごとに正確に uv run scripts/<name>.py のパターンのみ許可し、シェルのデコレータはなし(| head、| tail、timeout、2>&1 などなし)。Readに絶対パスはなし。cd && ... のようなチェーンも禁止です。これにより、これらの形が通ってしまうような緩いハーネスとは、ルール遵守の測定結果がはっきり変わります。
3つのルーティングされたMoEはいずれも、厳格なグローバルルールを保持する点でdenseの27Bより系統的に悪い — サイズ、アクティブパラメータ数、微調整のターゲットを変えても、傾向はあまり変わりません。まず速度の数字を文脈として示し、その後にルール遵守のギャップを示します。
モデルと量子化(4x24GBで262kコンテキストに収まるよう最大品質を狙って選定):
- Qwen3.5-27B dense — INT8(AWQ-BF16-INT8)重み、FP8 KV、MTPの推測デコーディング
- Qwen3.5-122B-A10B MoE — AWQ-INT4重み、FP8 KV。Q4でないと262kコンテキストを載せられないためQ4のみ
- Qwen3.6-35B-A3B MoE — FP8重み、FP16 KV(このモデルではFP8 KVが不安定でした)
小さいモデルは使える限りの精度を全部使い、大きいモデルは収まる分だけ使います。以下の表は250W(200/250/300Wのテストから得られた“ちょうど良い点”)時のものです。vLLM v0.19.0。
データの収集方法: vLLMは10秒ごとに Avg prompt throughput、Avg generation throughput、Running: N reqs を出力します。各セルは、その並列度におけるウィンドウの平均です — n=6 はその状態での壁時間にして約60秒です。アイドルのウィンドウもカウントします。これは最大値(ピーク)ではなく、維持されたスループットです。
https://preview.redd.it/1zpd01kd6dwg1.png?width=2231&format=png&auto=webp&s=3a95177aa3131e895d64bfe036e5cbf6042701de
並列数ごとの生成スループット(250W、平均 t/s)
括弧内の n はサンプル数(10秒ウィンドウの数)です。
| 同時 reqs | Qwen3.5-27B (n) | Qwen3.5-122B (n) | Qwen3.6-35B (n) |
| 1 | 85 (8) | 74 (21) | 122 (90) |
| 2 | 97 (28) | 48 (13) | 174 (34) |
| 3 | 133 (36) | 111 (9) | 215 (16) |
| 4 | 112 (19) | 123 (9) | 288 (8) |
| 5 | 68 (34) | 138 (17) | 348 (4) |
| 6 | 98 (16) | 33 (3) | 296 (5) |
3.6-35B はあらゆる並列レベルで生成が突出しています。122B は不均一です(c=2で48 t/sまで落ち、c=6ではn=3で33に低下)ものの、内部的には c=3〜5 の範囲で一貫しています。27Bはその2つの間に位置し、並列度レンジ全体で3つの中でもっともブレが小さいです。平均が122Bよりc=4〜5で下回っている場所でも、セルごとの分散は最小です。
プリフィルスループット(並列数ごと、250W、平均 t/s)
生成テーブル上と同じ n の取り決めです(両方のテーブルで各セルのnは同じ — 1ウィンドウ=プリフィルと生成の両方の値を含む1つのデータポイント)。プリフィルは、その並列度のすべてのウィンドウで平均化します。エンジンがそのウィンドウを純粋に生成に費やしたもの(prefill=0のもの)も含まれます。これは、その並列状態における“維持されたプリフィルのスループット”をより正直に表したものです。122Bの c=6 at n=3 はノイズが支配しています。
| 同時 reqs | Qwen3.5-27B (n) | Qwen3.5-122B (n) | Qwen3.6-35B (n) |
| 1 | 926 (8) | 573 (21) | 626 (90) |
| 2 | 553 (28) | 2343 (13) | 1589 (34) |
| 3 | 364 (36) | 1849 (9) | 1799 (16) |
| 4 | 726 (19) | 2499 (9) | 1856 (8) |
| 5 | 1001 (34) | 1754 (17) | 1896 (4) |
| 6 | 1427 (16) | 2480 (3) | 2983 (5) |
全体の維持平均(c=1〜6、すべてのウィンドウを250Wで):Qwen3.5-27B 約756 t/s、Qwen3.5-122B 約1651 t/s、Qwen3.6-35B 約1124 t/s。122Bはプリフィルで依然として約2倍勝っています。どのターンでも30〜60kトークンのほとんどはプレフィックスキャッシュで処理され、キャッシュされない末尾は1ターンあたり数千トークン程度にしかならないため、紙の上ほどは実運用で122Bの優位は効きません。
プリフィルを実際に行っているときのプリフィルスループット(zero-prefillウィンドウを除外)
もし「エンジンが実際にプロンプトを処理しているとき、どれくらいの速さで進むか?」を、維持平均ではなく知りたいなら、以下の数値は各セルの平均から prefill=0 のすべてのウィンドウを除外しています。括弧内の n は各セルにおけるプリフィル稼働中ウィンドウの数なので、セルごとに値が変わります。
| 同時リクエスト | Qwen3.5-27B(n) | Qwen3.5-122B(n) | Qwen3.6-35B(n) |
| 1 | 1235(6) | 669(18) | 751(75) |
| 2 | 860(18) | 2769(11) | 1743(31) |
| 3 | 505(26) | 2377(7) | 1799(16) |
| 4 | 985(14) | 3213(7) | 1856(8) |
| 5 | 1260(27) | 1987(15) | 1896(4) |
| 6 | 1757(13) | 3720(2) | 2983(5) |
アクティブのみの集計:Qwen3.5-27B 約1025 t/s、Qwen3.5-122B 約2155 t/s、Qwen3.6-35B 約1124 t/s。上の「持続」表は、エージェントのパイプラインがその同時実行状態にまたがって平均すると実際に体験するものに近いです。一方この表は、プリフィル(事前入力)が実際に行われているときにvLLMが出せるものにより近い。どちらを重視するかで選んでください——「自分のエージェントスタックは何をするのか」なのか、「このモデルが何に対応できるのか」なのか。
1分あたりの完了リクエスト数(250W)
トークンレートは一つの指標ですが、1分で実際にどれだけのタスクが完了するかは別の話です。POST /v1/chat/completions HTTP/1.1" 200 のログ行を10秒ごとのウィンドウで数え、そのウィンドウにおける同時実行数でビン分けしています。ミックスタスク(短い応答も長い応答もどちらも1として数える)なので、これはタスクごとのレイテンシではなく、ワークロード構成に対する実効スループット指標です。
| 同時リクエスト | Qwen3.5-27B | Qwen3.5-122B | Qwen3.6-35B |
| 1 | 8.2/分 | 9.1/分 | 14.9/分 |
| 2 | 6.6/分 | 9.7/分 | 23.1/分 |
| 3 | 6.7/分 | 10.0/分 | 26.6/分 |
| 4 | 7.3/分 | 10.0/分 | 36.8/分 |
| 5 | 7.8/分 | 8.8/分 | 27.0/分 |
| 6 | 13.9/分 | 12.0/分 | 45.6/分 |
3.6-35Bは、ほとんどの同時実行レベルで兄弟モデルのどちらよりも2〜4倍多く1分あたりのリクエストを完了させます(差が最小なのはc=1で、最大なのはc=4付近)。27Bはc=1〜5で約7/分のフラットな水準(遅いが安定)。122Bはc=2以降で約9〜10/分に飽和——2を超えて同時実行を増やしても完了できる仕事量は増えず、より多くのキューに積まれたリクエストへと分散されるだけです。
ルール遵守のギャップ
およそ20セッションの比較可能なワークロード間での「オレンジ同士」(同じタスクタイプ、同じクエリは二度と使わない):
| モデル | セッション数 | ツール呼び出し | エラー | エラー/ツール |
| qwen3.5-27b(密) | 21 | 161 | 9 | 5.6% |
| qwen3.5-122b-a10b(MoE) | 17 | 128 | 13 | 10.2% |
| qwen3.6-35b-a3b(MoE) | 20 | 158 | 19 | 12.0% |
密な27Bは、どちらのMoEよりもツール呼び出しエラーがだいたい半分です。Qwen3.5-35B-A3Bをコントロールとして追加しました——これは3.6-35Bと同じアーキテクチャです(35B総数/3Bアクティブ/256のエキスパートで上位8を使用は同一)。違うのは微調整(fine-tune)だけです。結果は11.3%になりました。3B〜10Bのアクティブパラメータに跨る3つのルーティングMoE、エキスパートごとの容量が8M〜20M、そして微調整のターゲットも完全に異なる——それらはいずれも狭い10〜12%のエラーバンドに収まっています。アーキテクチャがレートを上限づけており、事後学習では起きるエラーの種類は変わりますが、どれくらいの頻度かは変わりません。
どのように失敗するかは、どれくらいの頻度で失敗するかより重要です。長い複数ステージの研究タスクで、各ステージが3回呼び出しの状態ハンドシェイクで終わるケースでは、3.6-35Bは単一のステージも完了できませんでした。拒否されたbashのバリアントを繰り返し再試行し続けました(ls scripts/ | grep -E "search|web"、curl -s 'https://...'、--no-agentのように捏造したフラグ、youtube_fetcher.pyのように捏造したスクリプト)そして状態遷移を出すことなくターンの予算を使い切ってしまいました。後に27Bは、3.6-35Bが止まっていたまさにそのタスクインスタンスを引き継いで、きれいに完了しました——最初の拒否の時点で、許可された別のスクリプトへ切り替えました。
このパターンは3つのMoEすべてに当てはまります。戦略を変えるのではなく、同じブロックされた形のリトライバリアント(| head -5 → | head -10 → | tail -3)を繰り返します。密なモデルは切り替える。私の解釈はこうです——ルーティングはルールの厳密さ(rule specificity)を失わせます。各トークンが小さなスライスを有効化し、文脈で指定されたルールが「bashはこう見える」という事前学習(pretraining)の事前分布(priors)と競合するからです。シェルの定番イディオムには密な事前分布がありますが、カスタムの許可リストにはありません。事後学習が変えるのは、どのイディオムが漏れ出すか(leakするか)であって、漏れ出すかどうかではありません。
設定(Configs)
フラグを説明するハードウェアの文脈:4基のRTX 3090、NVLink接続が2つ+PCIのみが2つ。すべてアンダーボルトで、各250Wに固定しています。--disable-custom-all-reduceは、混在リンク構成におけるvLLMのトポロジ混乱を回避するためのものです。-O3は、プリフィルと生成の両方で得られるスループットのために、コールドスタートと追加のVRAMを支払う価値があります。
設定(config)の前に、Qwen3特有のフラグに関する注意を2点——別のファミリーへコピペする人がいるかもしれないので:--reasoning-parser qwen3は、Qwen3の「思考(thinking)」モデルにだけ適用されます(thinkingでないバリアントでは失敗します)。27B設定のqwen3_next_mtpという推論先読み(speculative decoding)メソッドはQwen3.5-Next専用で、他のモデルファミリーでは動きません。
Qwen3.5-27B(私のデイリードライバー)
name: vllm-thinking services: vllm: image: vllm/vllm-openai:v0.19.0 restart: unless-stopped runtime: nvidia shm_size: 8gb ipc: host environment: - NVIDIA_VISIBLE_DEVICES=0,2,3,4 - CUDA_DEVICE_ORDER=PCI_BUS_ID - RAY_memory_monitor_refresh_ms=0 - NCCL_CUMEM_ENABLE=0 - NCCL_NVLINK_DISABLE=0 - VLLM_ENABLE_CUDAGRAPH_GC=1 - VLLM_USE_FLASHINFER_SAMPLER=1 - PYTORCH_ALLOC_CONF=expandable_segments:True volumes: - "/mnt/ssd-4tb/ai_models/models/hub:/root/.cache/huggingface/hub" ports: - "8082:8000" command: > --model cyankiwi/Qwen3.5-27B-AWQ-BF16-INT8 --served-model-name cyankiwi/Qwen3.5-27B-AWQ-BF16-INT8 --quantization compressed-tensors --port 8000 --host 0.0.0.0 --tensor-parallel-size 4 -O3 --max-model-len 262144 --gpu-memory-utilization 0.9 --dtype auto --enable-auto-tool-choice --tool-call-parser qwen3_coder --reasoning-parser qwen3 --limit-mm-per-prompt '{"image":10,"video":2}' --enable-prefix-caching --disable-custom-all-reduce --kv-cache-dtype fp8 --max-num-seqs 12 --max-num-batched-tokens 8192 --compilation-config '{"cudagraph_capture_sizes":[1,2,4,8,12]}' --trust-remote-code --no-use-tqdm-on-load --generation-config auto --attention-backend FLASHINFER --speculative-config '{"method":"qwen3_next_mtp","num_speculative_tokens":2}' --override-generation-config '{"temperature":1.0,"top_p":0.95,"top_k":20,"min_p":0.0,"presence_penalty":1.5,"repetition_penalty":1.0}' healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 30s timeout: 10s retries: 3 start_period: 300s
サンプリングは「一般的な思考」プリセット(temperature 1.0, top_p 0.95, top_k 20, presence_penalty 1.5)です。「コーディング思考」プリセットでは、エージェントがループしたり同じアクションを繰り返したりしており、MoEではさらに悪化しました。--max-num-seqs 12 は cudagraph のキャプチャサイズに合わせています。MTPで推測トークン2個は安定しますが、3個以上にするとランダムなクラッシュが起き始めます。
Qwen3.5-122B-A10B(生のプリフィルが欲しいとき)
name: vllm-thinking services: vllm: image: vllm/vllm-openai:v0.19.0 restart: unless-stopped runtime: nvidia shm_size: 8gb ipc: host environment: - NVIDIA_VISIBLE_DEVICES=0,2,3,4 - CUDA_DEVICE_ORDER=PCI_BUS_ID - RAY_memory_monitor_refresh_ms=0 - NCCL_CUMEM_ENABLE=0 - NCCL_NVLINK_DISABLE=0 - VLLM_ENABLE_CUDAGRAPH_GC=1 - VLLM_USE_FLASHINFER_SAMPLER=1 - PYTORCH_ALLOC_CONF=expandable_segments:True volumes: - "/mnt/ssd-4tb/ai_models/models/hub:/root/.cache/huggingface/hub" ports: - "8082:8000" command: > --model QuantTrio/Qwen3.5-122B-A10B-AWQ --served-model-name QuantTrio/Qwen3.5-122B-A10B-AWQ --port 8000 --host 0.0.0.0 --tensor-parallel-size 4 --enable-expert-parallel -O3 --max-model-len 262144 --gpu-memory-utilization 0.94 --kv-cache-dtype fp8 --dtype auto --enable-auto-tool-choice --tool-call-parser qwen3_coder --reasoning-parser qwen3 --limit-mm-per-prompt '{"image":10,"video":2}' --enable-prefix-caching --disable-custom-all-reduce --max-num-seqs 8 --max-num-batched-tokens 8192 --compilation-config '{"cudagraph_capture_sizes":[1,2,4,8]}' --trust-remote-code --quantization awq_marlin --attention-backend FLASHINFER --no-use-tqdm-on-load --generation-config auto --override-generation-config '{"temperature":1.0,"top_p":0.95,"top_k":20,"min_p":0.0,"presence_penalty":1.5,"repetition_penalty":1.0}' healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 30s timeout: 10s retries: 3 start_period: 600s
--enable-expert-parallel は MoE固有の追加です。--max-num-seqs 8 は、AWQ-INT4の重み + FP8 KV + 262kコンテキストで、起動時にOOMせずに4×24GBで収まる最大の cudagraph バッチサイズだからです。実運用では、長いプロンプトで並行度が3〜4を超えると、リクエスト単位のスループットはとにかく崩れます。8は、小さなツール呼び出しのバーストをさばくための値です。
Qwen3.6-35B-A3B(スピードの王様、コーディング最適化)
name: vllm-thinking services: vllm: image: vllm/vllm-openai:v0.19.0 restart: unless-stopped runtime: nvidia shm_size: 8gb ipc: host environment: - NVIDIA_VISIBLE_DEVICES=0,2,3,4 - CUDA_DEVICE_ORDER=PCI_BUS_ID - RAY_memory_monitor_refresh_ms=0 - NCCL_CUMEM_ENABLE=0 - NCCL_NVLINK_DISABLE=0 - VLLM_ENABLE_CUDAGRAPH_GC=1 - VLLM_USE_FLASHINFER_SAMPLER=1 - PYTORCH_ALLOC_CONF=expandable_segments:True volumes: - "/mnt/ssd-4tb/ai_models/models/hub:/root/.cache/huggingface/hub" ports: - "8082:8000" command: > --model Qwen/Qwen3.6-35B-A3B-FP8 --served-model-name Qwen/Qwen3.6-35B-A3B-FP8 --port 8000 --host 0.0.0.0 --tensor-parallel-size 4 --enable-expert-parallel -O3 --max-model-len 262144 --gpu-memory-utilization 0.94 --dtype auto --enable-auto-tool-choice --tool-call-parser qwen3_coder --reasoning-parser qwen3 --limit-mm-per-prompt '{"image":10,"video":2}' --enable-prefix-caching --disable-custom-all-reduce --max-num-seqs 8 --max-num-batched-tokens 8192 --compilation-config '{"cudagraph_capture_sizes":[1,2,4,8]}' --trust-remote-code --no-use-tqdm-on-load --attention-backend FLASHINFER --generation-config auto --override-generation-config '{"temperature":1.0,"top_p":0.95,"top_k":20,"min_p":0.0,"presence_penalty":1.5,"repetition_penalty":1.0}' healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 30s timeout: 10s retries: 3 start_period: 300s
--kv-cache-dtype fp8 は入れていません。3.6-35BはFP8 KVでは不安定で、代わりにデフォルトのFP16 KVで動かします。
要点
- MoEは、ハーネスがそれらを禁止すると事前学習のシェル習慣が漏れます。 3つのルーティングされたQwen MoEはいずれも、密な27Bに対して5.6%だったのに対し、ツール呼び出しエラーが10〜12%の帯に収まりました。微調整のターゲットはこれを解消しません。これがこの記事の実際のニュースで、他はすべて運用上の詳細です。
- MoEは、スループット制約のある作業や、ハーネスがそれらのために使いたくなるシェル慣用句(
| head, timeout, 2>&1, &&/|| のチェーン)を許可するコーディングエージェントに向いています。あなたのハーネスがそれらを拒否するなら、一日中そのモデルと戦うことになります。 - 各リクエストの生成スループットは、3つのモデルすべてで並行度が3〜4を超えると低下します。エージェントごとのレイテンシが重要なら、並行度は低く保ってください。
- 27Bの最適ポイントは250Wです。3.6-35Bは実際に電力でスケールします(250Wに対して、300Wは生成が74%増)。122Bも単調にスケールします(200W: 59 → 250W: 84 → 300W: 98 t/s の合計)ものの、セルごとのばらつきは、どの電力でも27Bより広いままです。
- 量子化はMoEにとってより重要です。密な27BではINT8はきれいに動きますが、122BでのAWQ-INT4は、密なモデルでは起きなかったような判別不能なツール呼び出しを生成します。
追加の詳細
厳格な許可リストに対してMoEを動かしている人で、同様のルール順守パターンを見たことがある人はいますか?それとも、私のハーネスが単に異常なほど厳しいだけなのでしょうか。設定に関する質問にも喜んで答えます。
投稿者: