
Rust に生まれ変わった NeMo Switchyard v0.2.0 を試してみた
はじめに
こんにちは、クラスメソッド製造ビジネステクノロジー部の森茂です。
NVIDIA の LLM ルーティング基盤 NeMo Switchyard に、v0.2.0 が来ました。リリースは 2026 年 8 月 10 日。v0.2.0 からはルーティングエンジンが Python から Rust に書き直され、crates.io と PyPI にパッケージが公開されています。
自分は v0.1.0 の頃にファーストタッチ記事を書き、その後は opencode + Fireworks AI の社内検証でこのルーターを常用してきました。v0.2.0 は単なるバージョンアップではなく、設定ファイルも分類器のアルゴリズムも別物になっています。ちょうど NVIDIA の技術ブログでルーティングの考え方を整理した記事も公開されたので、あわせて読みながら試していきます。
先に結論を書いておくと、v0.2.0 は「タスクごとに最適なモデルは違う」という考え方を、閾値 2 つの較正だけで運用に載せられるところまで来ていました。そして 1 ヶ月半使って実感している自動ルーティングの価値は、難しいタスクを見抜いて強いモデルに振ることではなく、そうでない大多数の作業を高価なモデルに流さないことのほうにあります。この整理は考察の章で書きます。
v0.1.0 時点の記事はこちらです(2026-07-03 時点の記事です)。当時の route.yaml や分類プリセットの話は今回で旧構成になったので、本記事が実質的な書き直しになります。
この記事では、Switchyard が解決しようとしている課題、v0.2.0 の使い方と較正の実践、そして 1 ヶ月半の運用で考えたことを紹介します。
Switchyard は何を解決しようとしているのか
コーディングエージェントは 1 つのタスクで何十回も LLM を呼びます。その内訳を見ると、ファイルを読んで要約する、テスト結果を確認する、定型的な修正を当てるといった作業が大半で、深い推論が要る場面は一部です。それでも運用は「一番賢いモデルを 1 つ選んで全部任せる」になりがちで、定型作業にもフロンティアモデルの単価を払うことになります。かといって小型モデル一本にすると、肝心の場面で品質が崩れます。
Switchyard の前提は、この二択が偽物だということです。公式の技術ブログでは、モデルにはそれぞれ強み・弱み・コスト特性があり、単一モデルではなく「システム・オブ・モデルズ」としてリクエスト単位で使い分ける、という整理をしています。面白いのはその根拠で、Terminal-Bench Hard を使った実験では、タスク群ごとに最適なモデルが異なるという結果が示されています。ML 系のタスク群はこのモデル、数学・科学はあのモデル、残りは別のモデル、という具合に、単一の「最強」は存在しません。ルーティングの目的は最強モデルを当てることではなく、要求品質を最も安く満たすモデルを選ぶことだ、という立場です。
実際の削減幅も事例として出ています。LangChain のマルチターンエージェント評価ではフロンティアモデル単独と比べて 74% のコスト削減でフロンティア行きの呼び出しは 7% のみ、Cognition の評価でも精度差 2.8 ポイント以内でコスト約 28% 減という数字が紹介されています。品質を全部捨てて安くするのではなく、落ちる幅を測ったうえで削る、という読み方ができます。
では何を手がかりにルーティングするのか。公式の整理では、チューニング不要のルーターが 3 種、学習ベースが 1 種あります。
| ルーター | 判断材料 | 動き方 |
|---|---|---|
| LLM classifier | judge 役の LLM による評価 | リクエストを評価して振り分け、セッション内は判定を維持 |
| stage router | エージェントの進行段階 | 探索・エラー対応の段階は高性能側、実装段階は効率側 |
| escalation router | 実行中のつまずきの証拠 | 低コスト側で開始し、反復エラーや停滞を検知して昇格 |
| prefill router(学習ベース) | LLM 内部の残差ストリーム | 入力の複雑さから各モデルの成功確率を予測 |
本記事で扱うのは LLM classifier で、v0.2.0 では capability と escalation の 2 つのモードを持ちます。ちょうど上の表の 1 行目と 3 行目に対応する考え方が、1 つの route タイプに統合された形です。
学習ベースの prefill router は記事執筆時点では概念の紹介のみで、まだリリースには入っていないようです。ただ、この枠には見覚えがあります。以前 DGX Spark で検証した NVIDIA LLM Router、ルーティング専用の分類モデルを自分のワークロードに合わせて訓練する系譜です。Switchyard はもともと「LLM Router のアルゴリズムも取り込んでいく」基盤として教えてもらった経緯があるので、学習型の考え方はこの prefill router の枠に合流してくるのだと見ています。当時の検証は基礎編と訓練編の 2 本にまとめています(いずれも 2026-06-21 時点の記事です)。
v0.2.0 から Rust サーバーに生まれ変わっていた
v0.1.0 の Switchyard は Python パッケージでしたが、v0.2.0 ではこの Python ルーティング実装が main から削除され、後継は standalone の Rust バイナリ switchyard-server になっています。ルーティングロジックはプロバイダ非依存の SDK として切り出され、サーバーはその参照実装という位置づけで、OpenAI Chat Completions、Responses、Anthropic Messages の 3 形式のリクエストを受けられます。
旧版の知識がどう対応するのか、主な変更点を表にまとめました。
| 観点 | v0.1.0(Python) | v0.2.0(Rust ネイティブ) |
|---|---|---|
| 導入 | pip + ソース同梱、または git からビルド | cargo install switchyard-server(crates.io) |
| 設定 | route.yaml(route-bundle 形式) | routes.toml |
| 分類器 | 4 カテゴリ分類(SIMPLE〜REASONING)+ プリセット | p_solve 能力推定 + 閾値(capability モード) |
| 判定の出力 | tool calling | 構造化出力 |
| 較正手段 | 分類プロンプト全文の差し替え | 閾値 2 つ(base_threshold / threshold_step) |
| エスカレーション | 専用の escalation_router | llm_classifier の escalation モード |
設定ファイルの形式から分類のアルゴリズムまで変わっているので、移行というより作り直しに近い変化です。ただ後述するとおり、較正の考え方は旧版よりだいぶ整理されており、個人的には良い作り直しだったと思っています。
cargo install から 84 秒で立ち上がる
v0.2.0 の一番うれしい変化はここです。これまで Rust サーバーを動かすには git の特定コミットをピンしてワークスペース全体をビルドするしかなく、初回ビルドに 10〜20 分かかっていました。crates.io に公開されたことで、リリース版を指定した cargo install だけで済むようになります。
ARG RUST_VERSION=1.96.1
FROM rust:${RUST_VERSION}-bookworm AS builder
ARG SWITCHYARD_VERSION=0.2.0
RUN cargo install --locked switchyard-server \
--version "${SWITCHYARD_VERSION}" \
--root /opt/out
FROM debian:bookworm-slim
COPY --from=builder /opt/out/bin/switchyard-server /usr/local/bin/switchyard-server
COPY routes.toml /app/routes.toml
EXPOSE 4100
ENTRYPOINT ["switchyard-server"]
CMD ["--config", "/app/routes.toml", "--host", "0.0.0.0", "--port", "4100"]
手元の実測で、このイメージのビルドは全体で 84 秒、うち cargo install が 70 秒でした。イメージサイズは 187MB です。10〜20 分かかっていた頃と比べると、設定を変えて作り直す心理的ハードルが別物ですね。
設定を書いたら、起動前に --dry-run で検証できます。routes.toml は未知のフィールドを黙って無視せずエラーにする方針なので、typo が実行時の謎挙動ではなく起動前のエラーとして出てくれます。地味にありがたいですね。
switchyard-server --config routes.toml --dry-run
routes.toml は 3 層で書く
設定は llm_clients、targets、routes の 3 層構造です。接続先の定義、モデルの定義、ルーティングの定義が分かれているので、v0.1.0 の route.yaml より見通しは良くなったと感じます。公式ドキュメントにも v0.2.0 で TOML スキーマのリファレンスが新設されました。
自分が実運用している設定から、要点を抜き出したものがこちらです。
schema_version = 1
[llm_clients.fireworks]
format = "openai_chat"
base_url = "https://api.fireworks.ai/inference/v1"
api_key_env = "FIREWORKS_API_KEY"
# 分類器(judge)専用のクライアント定義。target は (llm_client, モデル ID) の組で
# 管理されるため、weak と同じモデルを judge に使うなら client 名を分けて両方を残す
[llm_clients.fireworks_judge]
format = "openai_chat"
base_url = "https://api.fireworks.ai/inference/v1"
api_key_env = "FIREWORKS_API_KEY"
[targets.classifier]
id = "accounts/fireworks/models/deepseek-v4-flash-0731"
llm_client = "fireworks_judge"
extra_body = { reasoning_effort = "none", temperature = 0 }
[targets.strong]
id = "accounts/fireworks/models/kimi-k3"
llm_client = "fireworks"
[targets.weak]
id = "accounts/fireworks/models/deepseek-v4-flash-0731"
llm_client = "fireworks"
[routes.auto]
id = "auto"
type = "llm_classifier"
mode = "capability"
classifier_target = "classifier"
strong_target = "strong"
weak_target = "weak"
base_threshold = 0.75
threshold_step = 0.1
session_affinity = true
recent_turn_window = 4
[routes.auto-esc]
id = "auto-esc"
type = "llm_classifier"
mode = "escalation"
classifier_target = "classifier"
strong_target = "strong"
weak_target = "weak"
escalation = { confirmations = 2 }
[routes.strong-only]
id = "strong-only"
type = "passthrough"
target = "strong"
[routes.weak-only]
id = "weak-only"
type = "passthrough"
target = "weak"
route の id がそのままクライアントから見えるモデル名になります。opencode 側からは switchyard/auto のように選ぶだけで、自動ルーティングと固定ルートを切り替えられます。passthrough の固定ルートを同居させておくと、分類器を通さない挙動と比較できるので、トラブルの切り分けにも効きます。
targets の extra_body はそのターゲットへのリクエストに追加のパラメータを差し込むノブです。ここでは分類器の思考を止めるために使っています。判定は確率を 1 つ返すだけの仕事なので、思考モデルに長考させる意味がないんですね。この 1 行の効果は較正の章で実測します。
capability classifier は「解ける確率」を見積もっている
v0.1.0 の分類器はタスクを SIMPLE から REASONING の 4 カテゴリに分類し、プリセットの対応表で tier に振っていました。v0.2.0 の capability モードは発想が変わっていて、judge 役の LLM に「weak モデルがこのタスクを完遂できる確率」、内部でいう p_solve を見積もらせ、閾値と比較して振り分けます。
閾値は一律ではありません。judge はタスクの種類も同時に判定していて、weak が得意なはずの領域は base_threshold がそのまま、判定に自信が持てない領域は threshold_step 分だけ厳しい閾値が適用されます。自分の設定だと、得意領域 0.75、不確かな領域 0.85、不得意領域 0.95 という階段になります。「怪しければ strong に倒す」が設計に組み込まれている形です。
もうひとつ重要なのが、judge に会話のどこを見せるかです。既定では最初の user メッセージと最新の user メッセージしか見ません。ここで recent_turn_window を設定すると、直近 N メッセージ、つまり assistant の応答やツールの実行結果まで judge に渡ります。
これが効きました。コーディングエージェントの実会話 40 ケースに対して窓の有無で 320 判定を取った結果がこちらです(実行順の交絡を潰すため、アーム順を逆にした再実行込み)。
| 設定 | judge の思考 | 判定レイテンシ p50 | 判定の行き先 |
|---|---|---|---|
| 窓なし(既定) | 1,713 tokens | 17.5 秒 | 基準 |
| recent_turn_window = 4 | 254 tokens | 3.1 秒 | 差は 40 ケース中 1 件以内 |
判定の行き先はほぼ変わらないのに、judge の思考が約 1/7 になり、判定は約 6 倍速くなりました。既定の入力では judge が「最初の依頼文だけを見て完遂確率を当てる」ことになり、材料が足りないぶん長考するようです。ツールの実行結果という決定的な材料が入ると即断できる、と解釈しています。
ただしこれはコード作業での結果です。設計相談のような壁打ち会話で同じ窓を開けると、weak の応答がそれらしく見えるせいか judge が weak 寄りに倒れる現象も確認しました。この記事の数値はコーディングエージェントのワークロード前提で読んでください。
session_affinity = true も実質必須の設定項目です。エージェントは 1 タスクで何十回も LLM を呼ぶので、毎ターン判定するとコストがターン数に比例します。affinity を有効にするとセッション冒頭で tier が決まり、以降の判定はスキップされます。
閾値に既定値はなく、自分のワークロードで較正する
capability モードの base_threshold は必須フィールドで、上流は推奨値を出していません。最初は不親切に感じたのですが、較正してみて考えが変わりました。この値はワークロードによって最適点が違いすぎて、既定値を置くほうが不誠実なんですね。
較正には、実運用の会話形状 40 件と単発プロンプト 47 件、計 87 判定のセットを使いました。まず p_solve の分布を測ると、コーディングの実運用形状は中央値 0.85〜0.86 に固まり、ビジネス寄りの深い壁打ちは 0.55 前後に落ちます。この分布に対して閾値をスイープした結果は次のとおりでした。
- ドキュメントの例にある 0.5 では、深い壁打ちの strong 到達が 13 問中 9 問にとどまる
- 0.75 に上げると 12 問まで拾い、コーディング実運用形状 40 件は全件 weak を維持
- 0.80 以降は実運用形状まで strong に流れ始め、コスト面で逆行
というわけで自分の環境の答えは base_threshold = 0.75 でした。ここで言いたいのは 0.75 という値ではなく、この数字はあなたの環境では別の値になるということです。p_solve の分布はモデルの組にもタスクの形にも依存するので、実トラフィックのサンプルで一度測るのが結局いちばん速いと思います。
分類器の思考抑制も較正とセットで測りました。routes.toml の章で仕込んだ extra_body = { reasoning_effort = "none", temperature = 0 } の有無を同じ 87 判定で比較すると、judge のレイテンシは p50 で 2.2 秒対 10.4 秒、p90 では 4.4 秒対 44.5 秒。実運用形状の行き先は 30 件中 30 件で一致したので、思考を止めてもコード作業の判定品質は落ちませんでした。temperature 0 は閾値付近の判定が再実行で反転するのを抑えるためです。
escalation モードは詰んだ軌跡だけを昇格させる
capability が「開始前の予測」だとすると、escalation モードは「実際に詰んだ証拠での昇格」です。全セッションを weak で開始し、軌跡を judge が監視して、escalate 判定が 2 連続したらそのセッションを strong に固定します。
詰み軌跡を作って実測しました。同一の ImportError を 4 回繰り返し、無関係なファイルを編集し、直っていないのに完了を宣言する、という露骨にまずい会話です。
| ターン | 選ばれたモデル | レイテンシ |
|---|---|---|
| 1 | weak | 3.66 秒 |
| 2 | strong(ラッチ確定、weak の応答は破棄して再生成) | 2.29 秒 |
| 3 以降 | strong 直行(judge 停止) | 0.98 秒 |
判定品質は思ったより良い形でした。関連のある修正を重ねながらテストに落ち続ける「健全な摩擦」の会話では一度も昇格せず、証拠が揃った詰み軌跡だけが 2 ターンでラッチします。judge のプロンプトが「明確に詰んだパターンのみ、単発の失敗では昇格しない、迷ったら昇格しない」という基準になっていて、実測でもそのとおりに動きました。
ただし代償もあります。capability モードとの違いを整理するとこうなります。
| 観点 | capability(auto) | escalation(auto-esc) |
|---|---|---|
| 判定の性質 | 開始前に難易度を予測 | 詰んだ証拠に反応 |
| weak の使われ方 | 閾値しだい | 全タスク weak 開始で構造的に最大 |
| ストリーミング | 通常どおり | ラッチ前は全文バッファ後に 1 チャンク |
| 判定コスト | セッション冒頭のみ | ラッチ前の毎ターン + 確定ターンは両 tier に二重払い |
| 戻り | セッション内ピン | 片道(weak には戻らない) |
ストリーミングの喪失は実測で確認できて、weak 固定が 361 チャンクで流れてくる応答が、auto-esc では 1 チャンクで届きます。対話利用では長文生成のあいだ画面が止まるので、個人的には cron やバッチのような非対話ワークロード向けの選択肢かなと思っています。
ルーティングの主体は router だけではない
ここからは、1 ヶ月半このルーターを運用して考えたことです。
旧版の運用で一番印象に残っているのは、分類器が静かに変わることでした。実例があります。分類器のモデルをマイナー更新版に差し替えたところ、同一の会話 50 本の判定が weak 39 本から weak 1 本へ全振れしました。エラーは 1 つも出ず、confidence も高いままです。モデル固有の癖なのかを確かめるため 8 モデル × 2 プロンプト × 87 判定の 1,392 判定を横断で取ると、同じプロンプト・同じ入力でも weak 判定率はモデルによって 0〜100% に散りました。judge に何をどう読ませているかが、閾値よりずっと大きな変数だったわけです。
だからこそ、判定の分布を観測し続ける運用が要ります。v0.2.0 では /v1/stats で tier 分布と分類器のオーバーヘッドが取れ、--routing-log-file にリクエスト単位の JSONL が残り、分類器の呼び出し自体もログに記録されます。自分は週次でこのログを集計していて、判定シフトに気づけたのもこの習慣のおかげでした。v0.2.0 の設計はこの経験からみると良い方向で、judge の入力が狭く定義され、較正も閾値 2 つに集約されているので、モデルを替えたときに測り直す範囲が明確です。
一方で、予測型ルーティングそのものの限界も見えてきます。競技プログラミング由来の難問 14 問を流したところ、capability judge は 14 問すべてを weak に振りました。出力形式が仕様として隅々まで定義され、テストで機械的に検証できるタスクは、judge の見積もりでは「解ける確率が高い」ように見えるためです。難しさが仕様の複雑さではなくアルゴリズムの洞察にあるタスクは、この方式では原理的に見抜きにくいのだと思います。
なので現在の自分の整理は「router に全部やらせる必要はない」です。エスカレーションの主体は 4 つあると考えています。router による事前予測が auto、router による軌跡判定が auto-esc、そして人間による明示的な切り替えが passthrough の固定ルートやエージェント側のモード設定です。さらにモデル自身がツール経由で上位モデルに相談する、という 4 つ目の経路も考えられます。自分は壁打ちとプランニングだけ人間判断で strong 固定にし、日常のコーディングは auto に任せる構成に落ち着きました。
この構成に落ち着いてから、strong と weak の捉え方も変わりました。strong は「難問の保険」というより「特定能力の調達先」です。公式のベンチでもタスク群ごとに最適モデルが違うという結果が出ていて、モデルの優劣は一次元の強弱ではなく能力の凸凹だ、という見方と整合します。weak を日常の主戦力に据え、足りない能力だけを明示的に調達する。auto の価値は「難タスクの品質保護」よりも「定型作業を strong に流さない過剰昇格の防止」にある、というのが今の実感です。
30B クラスの次の戦場は特化型エージェントになりそう
この「特定能力の調達先」という見方を延長すると、ルーターの先に並ぶモデルは strong と weak の縦の梯子だけではなくなりそうです。ちょうどこの記事を書いている時期に、Fastino が NVIDIA との協業で、Nemotron 3.5 Lightning を金融と医療それぞれに特化学習したモデルを Apache 2.0 で公開しました。ベースは 30B でアクティブ 3B の MoE(Mixture of Experts)という軽量モデルですが、Fastino の発表では金融ベンチマーク FinQA が 15.9% から 59.2% まで動いたとされています。ドメインを絞れば、軽いモデルがその領域では大型モデルに並ぶという実例です。重みが公開されているオープンモデルだからこそできる使い方で、プライバシーや規制の強い業界ほどこの路線は増えていくはずです。
そうなると面白いのは、業務ドメインや企業そのものを 1 つの大きなモデルに見立てる発想です。特化学習された軽量モデルたちはその中のエキスパートに、ルーターはどのエキスパートを呼ぶかを決めるゲーティングに相当します。MoE の構造が、1 つのモデルの内側からシステム全体のアーキテクチャへ外出しされていく形ですね。Nemotron 3.5 Lightning が「軽い実行担当」、Switchyard が「仕事を振り分ける監督役」という組み合わせを見ていると、NVIDIA はもう「最強モデルを 1 個選ぶ」競争ではなく、計画・実行・検証を別々のモデルへ分解するエージェント設計に踏み込んでいるのかもしれません。
先日 Meta Superintelligence Labs がオープンウェイトで公開した Muse Glimmer 30B もそうですが、このサイズのモデルが総合力の賢さ比べで最上位と張り合うのは難しくなっています。勝負どころは「どの領域のエキスパートになるか」に移っていて、30B クラスの次の戦場は特化型エージェントとしての場になりそうです。手元の Switchyard の routes.toml にドメイン特化の target が並ぶ日は、そう遠くない気がしています。
まとめ
コーディングエージェントの実タスクで 4 route を比較した実測がこちらです。コード修正 10 run とツール呼び出し 3 run を各 route で回し、全 52 run が失敗ゼロで完走しました。
| route | 品質 | 費用/run | 実時間中央値 | strong 固定比 |
|---|---|---|---|---|
| strong-only(Kimi K3 固定) | 満点 | $0.0352 | 49 秒 | — |
| auto | 満点 | $0.0007 | 12 秒 | −98.0% |
| auto-esc | 満点 | $0.0021 | 23 秒 | −94.0% |
| weak-only(DeepSeek V4 Flash 固定) | 満点 | $0.0008 | 12 秒 | −97.7% |
−98.0% という数字は魅力的ですが、このタスクセットは weak 単独でも満点が取れる難易度なので、この数字は「品質を守りながら削減した」証明ではなく「strong に流す必要のないタスクを weak に流し切った」証明です。auto の strong 呼び出しはゼロでした。難タスク混在の実運用では削減率は当然小さくなり、実際に自分のチームの opencode + Fireworks 検証は、NVIDIA 公式ブログの事例紹介で 27% 削減として言及しています。タスク構成しだいでこの間のどこかに落ちる、と読むのが誠実なところです。
v0.2.0 の感想をまとめると、cargo install で入る配布形態と閾値 2 つに集約された較正で、v0.1.0 より明確に「運用する道具」に近づきました。pre-alpha の但し書きどおり動きは速いプロジェクトなので、リリース版をピンして、バージョンを上げるたびに自分のワークロードで較正を測り直す、という付き合い方が現実的だと思います。
次はチームでの実運用ログが 1 ヶ月分たまるので、ベンチではなく実トラフィックでの tier 分布と削減率を確かめてみたいところです。escalation モードの非対話ワークロード投入も続編で扱う予定です。
参考リンク
- NVIDIA-NeMo/Switchyard
- switchyard-server - crates.io
- Route AI Agent Workloads Across Models with NVIDIA NeMo Switchyard — ルーティングの考え方と 4 分類の公式整理
- NVIDIA Blog - Nemotron Lightning and NeMo Switchyard — 導入事例の言及あり
- Fastino x NVIDIA Collaboration — Nemotron 3.5 Lightning のドメイン特化バリアント
- オープンウェイトモデルでチームの AI コーディング環境を組む(2026-08-08 時点の記事です)








