Nemotron 3.5 Lightningの魅力
GPU 12GB × 2枚、合計24GBという「決して大きくない」手元の環境で、192Kコンテキスト×同時3本前後・単発101 tok/s・4並列合計211 tok/sが両立します。 これが個人ラボでNemotron 3.5 Lightningを本番投入した最大の理由です。
種を明かすと、KVキャッシュがとにかく軽いことに尽きます。52層中Attentionはたった6層、KVヘッド数2という強いGQA構成のMamba-hybrid MoE(30B-A3B)なので、同世代のdenseモデルと比べてKVキャッシュが1/8程度しか要りません。「コンテキストを伸ばすと速度が犠牲になる」というよくあるトレードオフを、この軽さがそのまま緩めてくれます。
| モデル | KV消費 | 0.5GBのKV予算で持てる文脈量 |
|---|---|---|
| Nemotron 3.5 Lightning | 923 B/token | 約54万トークン分 |
| Qwen3.8-27B(2.5bpw量子化) | 7,704 B/token | 約6.5万トークン分 |
- コンテキストが伸ばせる: 12GB×2枚のままで192Kコンテキストの会話を同時に3本前後さばける。0.5GBのKV予算だけで約54万トークン分
- 速度も犠牲にならない: 単発101 tok/s、4並列合計211 tok/s。長文プロンプト(24,889トークン投入)でもTTFT 4.7秒、decode速度の低下は-13%程度に収まる
- 安定して動く: Mamba-hybrid特有のクセやAphrodite Engine側の実装漏れバグに何度もぶつかったが、原因を追って全部潰した(本記事で全公開)。「なんとなく不安定」で終わる問題は無かった
-
思考時の英語化にも対処法がある: 数式・コード主体の問題で本文が英語化しやすい癖はあるが、原因(
</think>後の本文側の暴走。thinking_token_budget機構自体は正常)まで特定済みで、プロンプト側の一工夫でかなり緩和できる
同アーキテクチャの旧世代Nemotron-3-Nanoから乗り換えて、速度・KV効率・コーディング・エージェント耐性のすべてで上回ったため即日本番昇格させました。コンシューマGPUで大きめのコンテキストを扱いたい個人ラボ勢に、まっさきに勧めたいモデルです。
この記事について
個人ラボ(RTX 5070 12GB ×2、Tensor Parallel=2、WSL2、Aphrodite Engine)で NVIDIA Nemotron 3.5 Lightning-30B-A3B(W4A16量子化)を本番運用している中で溜まった知見をまとめました。
- 対象読者: 個人でLLMを自前運用している人、同じような構成(コンシューマGPU・小〜中規模VRAM)を組もうとしている人
- ゴール: まず動かす→次にハマりどころを踏まないように直す→最後に各フラグの意味を理解する、の順で読めるようにしています
- 検証環境: RTX 5070 12GB ×2(sm_120a、コンシューマ版Blackwell)、WSL2、Aphrodite Engine 0.22.0(vLLM系フォーク)
Nemotron 3.5 Lightning は Mamba2(SSM)とAttentionのハイブリッド構成(52層中Attentionはたった6層)で、公式ベンチマークでもSWE-bench 52.80/IFBench 72.88と強い性能も出ています。
この記事のフラグ値(block_size, batched-tokens等)はこのモデルのアーキテクチャに強く依存した実測値です。他モデルにそのまま使い回さないでください。同じMamba-hybrid系(Qwen3-Next等)でも層構成比が違えば最適値は変わります。
1. まずこれだけ: TL;DRの起動フラグ
初心者の方はまずこの節だけ見れば動きます。細かい理由は2節で解説します。
vllm serve <Nemotron-3.5-Lightning-30B-A3B の W4A16量子化モデルパス> \
--tensor-parallel-size 2 \
--kv-cache-dtype turboquant_k3v4_nc \
--mamba-ssm-cache-dtype float16 \
--mamba-cache-mode align \
--kv-cache-memory-bytes 500000000 \
--max-model-len 196608 \
--max-num-seqs 64 \
--max-num-batched-tokens 4608 \
--long-prefill-token-threshold 4608 \
--scheduling-policy priority \
--prefix-match-unit 16 \
--compilation-config '{"max_cudagraph_capture_size": 64}' \
--enable-prefix-caching \
--reasoning-parser nemotron_v3 \
--enable-auto-tool-choice \
--tool-call-parser qwen3_coder
この構成の実測値(RTX 5070 ×2、192K context構成):
| 指標 | 値 |
|---|---|
| 単発生成速度 | 約101 tok/s |
| 4並列合計 | 約209〜211 tok/s |
| TTFT(275トークン投入) | 0.14秒 |
| TTFT(24,889トークン投入) | 4.7秒 |
| 新規セッション割込み時の停止 | 104ms(体感ほぼ無し) |
| KVキャッシュ効率 | 923 B/token(fp8構成比で約2.1倍圧縮) |
| VRAM使用量 | 約11,200〜11,700 MiB(2枚とも) |
思考ON/OFFの切替は chat_template_kwargs: {"enable_thinking": true/false} で行います。ここに後述の重大な落とし穴があるので、思考OFF(nothink)を使うなら2節を必ず読んでください。
サンプリングパラメータは公式cookbookでは思考ON時 temperature 1.0 / top_p 0.95、思考OFF時 temperature 0.2 が推奨されていますが、うちでは一律 temperature 0.6 で運用しています(切替コストを避けるため。品質差は未検証)。
2. ハマったポイントと修正
時系列ではなく、症状ごとに整理しています。同じ構成を組む人は先にこの節を眺めて「自分もこれ踏みそうだな」を把握しておくのがおすすめです。
2-1. 【最重要】ツール呼び出しが不安定 → 原因は enable_thinking=False 側だけのバグだった
症状: ツール呼び出し(function calling)が「壊れたタグがそのままcontentに漏れる」「宣言だけして実際は呼ばずに結果を捏造する」「完全に無視して何も返さない」の3パターンで不安定になった。
最初は「Nemotronはツール呼び出しが下手なのでは」「専用LoRAが要るのでは」と疑い、実際にツール呼び出し特化LoRA(bf16専用、W4A16との組み合わせ未検証)まで発見して検討したのですが、経路別に切り分けたら一発で真因が判明しました。
| 経路 | 結果 |
|---|---|
| エンジン直・thinking ON | 4/4 完璧 |
| プロキシ経由・thinking ON | 8/8 完璧 |
| プロキシ経由・thinking OFF | 0/8 全滅 |
enable_thinking=False を送ると、chat templateが <think></think> と最初から閉じた状態でレンダリングされます。--reasoning-parser nemotron_v3 は </think> を境界にツール呼び出しパーサーへ処理を渡す実装のため、最初から閉じている入力では境界検出が破綻し、ツール呼び出しがまるごと壊れていました。ツール定義自体は正しく渡っていたので、パッと見ただけでは気づきにくいバグです。
結論: enable_thinking=False かつ tools を渡すリクエストでは、enable_thinking を強制OFFにしないこと。うちではプロキシ側で以下のように条件を1つ足すだけで直りました。
# tools有りのリクエストでは nothink を強制しない
if MODE == "nothink" and not body.get("tools"):
kw = body.setdefault("chat_template_kwargs", {})
kw.setdefault("enable_thinking", False)
修正前後で 0/8 → 8/8。ツール不要の通常チャットは従来どおり思考0字・0.19秒で応答するので副作用もありません。「ツール呼び出しが不安定」に遭遇したら、まずthinking ON/OFFの経路を分けて再現するかを確認してください。 モデルの実力不足ではなく実装側の配線ミスであるケースが多いです。
なお完全解決ではなく、まれに「宣言後の完全捏造」「呼び出し自体の無視」パターンが残る場合があります。プロキシ側で保留バッファリングによる検知を試みましたが、二重表示バグを引き起こしたため一旦撤去しました。実運用上の頻度は低いですが未解決の残課題です。
2-2. thinking_token_budget がまったく効かない → Aphrodite Engine本体の配線漏れバグ
症状: 「こんにちは」のような一言にも1分以上「思考」し続ける。thinking_token_budget(予算超過で強制的に思考を打ち切る機能)を設定しても無視される。
Aphrodite Engine 0.22.0 のソースを追ったところ、thinking_budget_state.py の中に どこからも呼ばれていない関数が2つ ありました。
-
update_state()(予算超過を検知する状態機械)—gpu_model_runner.py::_sample()から呼ばれていなかった -
apply_to_logits()(</think>をlogitsに強制注入する処理)— 投機的デコード専用の経路からしか呼ばれておらず、通常経路のSampler.forward()からは未接続だった
この2箇所を配線し直すと、thinking_token_budget=16 のような極端な値でも実際に数十文字で打ち切られるようになりました。さらに強制カットオフ直後に本文がダラダラ思考調で続いてしまう問題に対し、</think> 直後へ遷移フレーズを注入する機構(環境変数で有効/無効切替可)も追加しています。
その後の発見バグの発生条件が判明: async scheduling が有効(デフォルト)だと発生し、無効だと発生しません。公式の既存テストが --no-async-scheduling を明示していたため見逃されていたようです。この修正はupstream(vLLM/Aphroditeの元プロジェクト)へPRとして提出済みです。
自前パッチを当てる際の注意: Aphrodite EngineをアップグレードするとPython site-packagesへの直接パッチは全部消えます。アップグレード時はこのファイルの変更を再適用するか、upstreamへPRが取り込まれているか確認してください。
「オーバーラン」に見えた不具合の中には、文字数とトークン数を取り違えていただけのものもありました。日本語思考は1トークンあたり文字数が伸びやすく錯覚しやすいです。予算・長さ系の検証は必ずトークナイザで数えること。
2-3. 起動直後にウォームアップでクラッシュ(EngineDeadError)
症状: 本番投入直後、OWU(OpenWebUI)で4並列アクセスすると挙動がおかしくなる。TurboQuant系Tritonカーネルが実運用中にJITコンパイルされる警告が出ていたため、旧モデル時代に使っていた「実トラフィックを模した激しいバーストウォームアップ」をそのまま流用したところ、逆にエンジン全体をクラッシュさせてしまいました。
原因はGPU側のバグではなく、ウォームアップ負荷(36並列・意図的に長文を混ぜる)がTP=2ワーカー間RPCのタイムアウト(デフォルト300秒)を超えてしまったこと。「過去モデルで効いた対策パラメータをそのまま新モデルに移植する」のは危険という教訓です。並列数36→15、長文の量も縮小したところ全件成功するようになりました。
2-4. 「並列だと遅い」の正体は3種類あった(VRAM崖・スケジューリング・prefillの詰まり)
同じ「並列アクセスで遅くなる」という症状の裏に、実は複数の異なる原因が混在していました。切り分けが重要です。
| 疑ったこと | 実際 |
|---|---|
| VRAM崖(閾値付近で急激に速度低下する既知現象) | 一部は該当したが、後の再測定で測定バグ(速度をSSEチャンク数で数えて実際の約半分の値になっていた) と判明した部分もあり、崖を実際に踏んでいた証拠は薄かった |
| 新規リクエスト割込みでの一時停止 |
--scheduling-policy priority を1つ足すだけで解消(下記2-5参照) |
| prefillのブロックサイズによる詰まり | KV量子化するほどブロックサイズが大きくなる構造的トレードオフ(下記2-6参照) |
速度計測は必ず usage.completion_tokens を基準にすること。 ストリーミングのSSEチャンク数を数えると実速度の約半分になり、存在しない劣化を延々追いかけることになります。
2-5. 新規リクエストが割り込むと既存セッションが一瞬止まる → --scheduling-policy priority
継続的バッチング方式のエンジンでは、新規リクエストのprefill処理が1イテレーションに収まってしまうと、その間デコード中の他セッションが全部止まります。TurboQuant使用時はブロックサイズが大きいため、この停止が顕著でした。
--scheduling-policy priority を追加するだけで、2本の長時間ストリーミング中に新規リクエストを2回割り込ませてもトークン間隔の最大値47〜48ms・1秒超の詰まり0件まで改善しました。KV容量を犠牲にする必要はありません。
2-6. block_sizeの正体とフリーズ問題(最も構造的な話)
Mamba-hybridモデル特有の罠です。Aphrodite/vLLMはMamba状態とAttention KVを同じページサイズに揃える必要があり(Mamba align mode)、実効ブロックサイズは以下の式で決まります。
block_size = Mamba page bytes ÷ Attentionの1トークンあたりbytes
Mamba側(分子)は固定費が大きく、Attention側(分母)はKV量子化を強めるほど小さくなります。つまり KVを圧縮するほどblock_sizeが大きくなる という逆説的な構造です。ブロックサイズが実運用の平均プロンプト長(うちでは約4,700トークン)を下回ると、prefillが複数チャンクに割れてデコードを都度締め出すため割込み時のフリーズが発生します。
これは--block-sizeを直接指定しても上書きされる(強制的にモデル都合の値になる)ため、設定だけでは回避できません。最初は「フリーズを抑えるか並列性能を取るかはトレードオフ」と結論づけていたのですが、後日 分子側(Mamba state自体)を軽くする という見落としていた軸に気づき、突破しました。
--mamba-ssm-cache-dtype float16 を指定すると、Mamba stateのサイズが半分になりblock_sizeも半減します。KV dtypeには触っていないのでKV容量は1トークンも失いません。
| 構成 | 割込み時フリーズ | KVプール | 単発速度 |
|---|---|---|---|
| fp8(KV側のみ軽量化) | 785ms | 252,313 tok | 100.9 tok/s |
| turboquant_k3v4_nc + SSM float16(採用) | 104ms | 542,000 tok(+115%) | 101.3 tok/s |
フリーズ・KV容量・速度のすべてで上回り、精度(言語一貫性テスト)も15/15を維持しました。
bfloat16ではなくfloat16を使うこと。 bfloat16 を試すと言語一貫性が15/15→12/15に低下しました(仮数部7bitで精度不足)。float16 は仮数部10bitで、Mamba stateのように値域が限られる数値には有利に働きます。「16bitに落とす」で一括りにせず、指数部/仮数部の配分の違いを意識してください。
2-7. Stochastic Rounding は sm_120(コンシューマBlackwell)では使えない
公式cookbookは --enable-mamba-cache-stochastic-rounding を推奨していますが、うちのRTX 5070(sm_120a)では Tritonバックエンドだと起動不可 でした。
PTXAS error: Feature '.rs' not supported on .target 'sm_120a'
確率的丸めに使うPTX命令.rsがデータセンター版Blackwell専用のためです。FlashInferバックエンドなら起動はしますが、実測すると速度が16%低下し、しかも精度面の改善が一切ありませんでした(数列生成テストで誤り開始位置がむしろ悪化)。コンシューマGPUでは不採用が結論です。
| 構成 | 単発速度 | 精度改善 |
|---|---|---|
| triton・SR無し(採用) | 101.7 tok/s | — |
| flashinfer + SR + philox(公式推奨) | 85.6 tok/s(-16%) | 改善なし |
| triton + SR | 起動不可 | — |
2-8. 公式cookbook推奨フラグをそのまま試して本番を2回落とした
公式cookbookの推奨設定(H100/データセンターBlackwell前提)をコンシューマGPUにそのまま適用しようとして、本番サービスで直接検証してしまい、計2回・合計約12分サービスを停止させました(1回あたり約6分)。
| フラグ | 結果 |
|---|---|
--moe-backend humming / --linear-backend humming
|
重み並び替えカーネルのJITコンパイルで NVRTCCompiler が失敗し起動不可 |
--mamba-ssu-algorithm horizontal |
うちのAphrodite 0.22.0には未実装のフラグ。選択肢のうちhorizontalはデータセンターBlackwell(SM100+)向け最適化のため、実装されてもsm_120では恩恵が薄いとみられる |
--mamba-backend flashinfer |
起動はするが16%低速化(前述) |
--async-scheduling |
効果なし(実測済み) |
| MTP(投機デコード) | NVIDIA公式が推論での使用を非推奨と明記(学習専用) |
教訓: 「対応しているはずなのにコンシューマGPUでは通らない」機能が複数ありました。フラグの新規検証は必ず別ポートでテストインスタンスを立ててから行い、本番で直接試さないこと。バックアップスクリプトを残しておけば復旧は数分で済みます。
3. フラグの意味
TL;DR節で挙げたフラグを1つずつ解説します。
| フラグ | 意味・なぜこの値か |
|---|---|
--kv-cache-dtype turboquant_k3v4_nc |
KVキャッシュをKey 3bit / Value 4bitに量子化(TurboQuant)。fp8比でKV容量を大幅に節約できる。ただし単体だとblock_sizeが肥大化するので必ず次の行とセットで使う |
--mamba-ssm-cache-dtype float16 |
Mamba(SSM)状態を32bit→16bitに縮小。block_sizeの「分子」を小さくする鍵。bfloat16ではなくfloat16を使うこと(2-6参照) |
--mamba-cache-mode align |
AttentionのブロックサイズをMambaのページサイズに揃えるモード。Mamba-hybridアーキテクチャでは実質必須 |
--kv-cache-memory-bytes 500000000 |
KVキャッシュに割り当てる物理バイト数を固定値で指定。--gpu-memory-utilization(比率指定)は単独decode速度が約10%落ちる既知の問題があるため使わない |
--max-model-len 196608 |
最大コンテキスト長(192K)。伸ばすほどMamba状態の固定費(1本あたり約8.4ページ相当)が薄まり、皮肉にも総文脈量(同時本数×len)は増える。Transformer的直感と逆の挙動 |
--max-num-seqs 64 |
同時デコード可能なシーケンス数の上限。旧モデル時代は「64だと悪化する」という記録があったが、実際は別バグ(thinking_token_budget未修正時の思考暴走)の影響だったと判明し、修正後は64が最良だった |
--max-num-batched-tokens / --long-prefill-token-threshold
|
ともに実効block_size以上でないと起動時にAssertionErrorになる。うちでは4608(block_sizeちょうど)に設定 |
--scheduling-policy priority |
新規リクエストのprefillで既存デコードが止まる現象を緩和するスケジューラ設定(2-5参照) |
--prefix-match-unit 16 |
prefix cacheの検索粒度を16トークンに細かくする(vLLMの「hybridモデル向け部分prefix cacheヒット」機能)。ただし書き込み側の粒度は変わらないため、劇的な改善は期待しない方がよい(下記コラム参照) |
--compilation-config max_cudagraph_capture_size 64 |
CUDA Graphキャプチャ対象の最大バッチサイズ。--max-num-seqsと揃えておく |
--reasoning-parser nemotron_v3 |
思考(<think>...</think>)を解析するパーサー。NVIDIA公式推奨のnano_v3_official(DeepSeekR1ベースの移植版)も試したが、streamingで生の</think>がcontentに漏れる回帰があり不採用。Nemotron専用実装のnemotron_v3に確定 |
--enable-auto-tool-choice --tool-call-parser qwen3_coder
|
ツール呼び出しの自動判定とパーサー形式(Qwen3-Coder互換のXML/JSON形式)を有効化 |
コラム: prefix cacheが「全か無か」になる話
Mamba-hybridモデルはprefix cacheの再利用がAttentionモデルほど気軽ではありません。キャッシュの登録は「block_sizeの倍数の完全ブロック」と「リクエスト末尾の1点」のみで行われ、その中間の任意位置は再利用できません。--prefix-match-unitは検索側だけを細かくする機能で、書き込み側の粒度(block_size単位)自体は変えられないため、共有プレフィックスがあってもヒット率0%になるケースが構造的に残ります。これはバグではなく、「SSM状態は任意位置から再開できず、スナップショットが必要な箇所でしか保存できない」というMamba特有の制約に由来する設計です。実装漏れに見えて実は直しようがない、という珍しいパターンなので頭の片隅に置いておくと良いです。
4. 実測ベンチマークまとめ
TP=2のRTX 5070環境、192K構成での実測値です。
TTFT(プロンプト長別)
| 投入トークン数 | TTFT | decode速度 |
|---|---|---|
| 275 | 0.143s | 101.1 tok/s |
| 3,747 | 0.746s | 99.5 tok/s |
| 14,718 | 2.793s | 92.2 tok/s |
| 24,889 | 4.723s | 88.1 tok/s |
長文になってもdecode速度が-13%しか落ちないのはMamba-hybridの強みです。
並列スイープ(同時発射)
| 並列数 | 合計速度 | 1本あたり | TTFT中央値 |
|---|---|---|---|
| 1 | 87.8 tok/s | 104.3 | 0.73s |
| 4 | 211.3 tok/s | 82.7 | 2.73s |
| 8 | 281.3 tok/s | 66.8 | 5.37s |
| 16 | 339.4 tok/s | 46.1 | 10.11s |
KV効率の比較
同一世代の別モデル(Qwen3.8-27B、2.5bpw量子化)との比較です。
| モデル | KV消費 | 備考 |
|---|---|---|
| Nemotron 3.5 Lightning | 923 B/token | 52層中Attentionは6層のみ、KVヘッド数2という強いGQA構成が効いている |
| Qwen3.8-27B(2.5bpw) | 7,704 B/token | 全層Attention。約8.3倍重い |
Mamba-hybrid構成の恩恵で、コンシューマGPU2枚(合計24GB)でも192Kコンテキスト×同時3本前後を余裕を持って捌けています。
5. まとめ
- KVキャッシュの軽さ・速度・安定動作の3点だけでも、コンシューマGPUで動かす価値があるモデルです
- まず動かすだけなら1節のコマンドをそのままコピペすれば、フリーズもツール呼び出しの不安定さも踏まない状態でスタートできます
- 遭遇しやすい落とし穴は「thinking OFFでツール呼び出しが壊れる」「公式cookbookのデータセンター向けフラグがコンシューマGPUで通らない」「block_sizeの構造的な制約でフリーズが起きる」の3つ。いずれも本記事のフラグ構成で回避済みです
- 思考ON時に数式・コード主体の問題で本文が英語化しやすい癖は残っていますが、プロンプトへの一工夫(「回答の言語: このメッセージと同じ言語」のようなラベル形式の指示を末尾に添える)である程度制御できます。原因が
</think>後の本文側にあることまで特定済みなので、今後も改善の余地があります - 20項目前後の連番・数値列生成でモデルが静かに誤ることがあります(量子化とは無関係のモデル自体の癖)。数値列を生成させる用途では検算するか、コードを書かせて実行させることを推奨します
Mamba-hybridモデルは同じ30B級のdenseモデルと比べてKV効率・長文脈耐性で明確に優位です。コンシューマGPUで大きめのコンテキストを扱いたい個人ラボ勢には特におすすめできるモデルでした。