NVIDIA Nemotron 3.5 Lightning の 3 種類の投機的デコーディング(DSpark / DFlash / MTP)を DGX Spark で比較してみた

NVIDIA Nemotron 3.5 Lightning の 3 種類の投機的デコーディング(DSpark / DFlash / MTP)を DGX Spark で比較してみた

NVIDIA の新オープンモデル Nemotron 3.5 Lightning に同梱された 3 種類の投機的デコーディング手法を、DGX Spark で実際に比較してみました。
2026.08.12

ウィスキー、シガー、パイプをこよなく愛する大栗です。最近は製造ビジネステクノロジー部に所属しています。

2026年8月11日に NVIDIA が新しいオープンモデル Nemotron 3.5 Lightning を発表しました。このモデルには投機的デコーディング(speculative decoding)の実装が 3 種類も同梱されており、しかも公式は「DGX Spark には DSpark を推奨」というハードウェア別の使い分けを示しています。手元の DGX Spark で 3 方式を実際に比較してみました。

NVIDIA Nemotron 3.5 Lightning とは

Nemotron 3.5 Lightning は、常駐型の AI エージェント(always-on agents)における「実行層」を担うことを目的に設計されたオープンモデルです。

長時間稼働するエージェントは、実行時間の大半をツール呼び出し・結果の検証・サブエージェントへの委譲といった高頻度で定型的な処理に費やしています。こうしたステップの一つ一つにフロンティア推論モデルを使用するとコストとレイテンシがかさんでしまいます。そのため計画や複雑な判断は Nemotron 3 Ultra のようなフロンティアモデルで担当し、高頻度の実行処理は Lightning のような小型モデルが担当する、という役割分担が想定されています。

モデルの仕様は以下の通りです。

項目 内容
パラメータ 30B(アクティブ 3B)
アーキテクチャ Mamba-2 + MoE + Attention のハイブリッド MoE
コンテキスト長 最大 1M トークン
事前学習 NVFP4 レシピで 20T トークン以上
量子化 NVFP4 / BF16
単一 GPU での動作 1x DGX Spark (GB10) または 1x H100
対応ハードウェア Blackwell(GB10、GB200、GeForce RTX 5090)、Hopper(H100、H200)、Ampere は W4A16
対応言語 英語(およびコーディング言語)、スペイン語、フランス語、ドイツ語、イタリア語、日本語
ライセンス OpenMDW-1.1
リリース日 2026年8月11日

注目したいのは、モデルカードの「Single-GPU Deployment」欄が 「1× DGX Spark (GB10) or 1× H100」 となっている点です。データセンター向けの H100 と、机の上に置けるミニ PC である DGX Spark が並記されています。Quick Start の記述順も DGX Spark が先頭で、H100 や GB200 はその後に続きます。DGX Spark がメインのターゲットとして扱われているモデルです。

弊社の森茂が Nemotron 3.5 Lightning 30B-A3B-NVFP4 の詳細についてブログを書いていますので、詳細はこちらをご覧ください。

https://dev.classmethod.jp/articles/dgx-spark-nemotron-3-5-lightning-first-touch/

なぜ DGX Spark で投機的デコーディングが効くのか

実際に測る前に、効果的である理由を整理しておきます。ここが今回の比較の重要な点になります。

DGX Spark の GB10 Grace Blackwell Superchip は、128GB の LPDDR5x を CPU と GPU で共有する統合メモリ構成を取っています。128GB という大容量のメモリなのですが LPDDR5x であるためメモリ帯域は 273 GB/s に制限されます。データセンター向けの HBM 搭載 GPU と比べると、ここがボトルネックになります。

LLM のデコード処理は、1 トークン生成するたびにモデルのウェイトをメモリから読み出す必要があるため、メモリ帯域で律速されやすい性質があります。仮に NVFP4 に量子化した 30B のモデルを dense(全パラメータを毎回使う)で動かすと、ウェイトはおよそ 15GB になります。273 GB/s でトークン毎に 15GB を読むと、理論上は最大 18 トークン/秒程度です。軽量モデルとしては物足りません。

Nemotron 3.5 Lightning は、この上限を複数の手段により回避する設計になっています。

Lightning の設計 メモリ帯域の制約への効き方
MoE 30B / アクティブ 3B ルーターが各トークンを一部のエキスパートにのみ送るため、1 トークンあたりに読み出すウェイトが総量の 10 分の 1 程度で済む
Mamba-2 ハイブリッド Mamba 層は固定サイズの状態を持つだけで KV キャッシュが伸びない。Attention は一部の層のみなので、長いコンテキストでもメモリ消費が線形に爆発しない
投機的デコーディング 1 回のウェイト読み出しで複数トークンを確定させる。読み出し回数そのものを減らす

そして重要なのが 3 つ目の投機的デコーディングです。投機的デコーディングは、軽量なドラフトモデルが先の数トークンを予測し、本体モデルがそれをまとめて検証する仕組みです。DGX Spark と低並行性の環境で効果が大きくなります。

DGX Spark は、まさにこの「帯域が細く、並行数が低い」条件に当てはまります。DSpark は北京大学のチームと DeepSeek が発表した手法ですが丁度 DGX Spark っぽい名前がついており、公式が「DGX Spark に推奨」としているのは、単なる名前合わせではないということになります。

3 種類の投機的デコーディング

Nemotron 3.5 Lightning には、MTP・DFlash・DSpark という 3 つの手法が同梱されています。まず全体像を整理します。

方式 ドラフトの出どころ ドラフトの生成方式 別チェックポイント
MTP モデル内蔵の MTP 層 各位置で複数の未来トークンを予測する 不要(本体に内蔵)
DFlash 別のドラフトモデル ブロック拡散モデルが 1 回の forward で 1 ブロックを生成する ...-NVFP4-DFlash
DSpark 別のドラフトモデル 半自己回帰。並列バックボーンと軽量な逐次モジュールを組み合わせる ...-NVFP4-DSpark

Hugging Face には Lightning 関連のチェックポイントが以下のように公開されています。

チェックポイント 用途
nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-NVFP4 本体(NVFP4)
nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-BF16 本体(BF16)
nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-Base-BF16 ベースモデル
nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-NVFP4-DSpark DSpark 用ドラフトモデル
nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-NVFP4-DFlash DFlash 用ドラフトモデル

MTP(Multi-Token Prediction)

MTP は事前学習の段階でモデル本体に組み込まれています。Nemotron 3.5 Lightning では、通常の事前学習の後に MTP 層を学習させる継続事前学習フェーズが設けられ、さらにその後 MTP の精度を高める専用の強化フェーズも実施されています。Nemotron 3 Super や Nemotron 3 Ultra でも採用されている手法です。

別途ドラフトモデルを用意する必要がないのが最大の利点です。公式ブログで、中~高並行性に最適で、並行性が高まるにつれて最適なドラフト長は短くなると説明しています。

DFlash

カリフォルニア大学サンディエゴ校の Z-lab で開発された投機的デコーディング手法です。NVIDIA の公式ブログで DFlash を使用して NVIDIA Blackwell の推論パフォーマンスが最大15倍向上したというエントリも出ています。DGX Spark も Blackwell ファミリーであるためパフォーマンス向上が十分に期待できます。

公式ブログで、他のモデルと比較して、ワークロードに最適なパフォーマンスを発揮する可能性があると説明しています。

DSpark

DSpark は 2026年7月に arXiv で公開された手法です。論文のタイトルは「DSpark: Confidence-Scheduled Speculative Decoding with Semi-Autoregressive Generation」となっています。

DSpark は DFlash を拡張した手法であり、実運用での成果として、DeepSeek-V4 のサービング環境に組み込んだところ、本番のベースラインである MTP-1 と比較して、同一スループット水準でユーザーあたりの生成速度が 60 〜 85% 向上したと報告されています。

Nemotron 3.5 Lightning のモデルカードでは、DSpark は DGX Spark および低並行性のデータセンターワークロードに推奨と位置づけられており、現時点ではすべてのケースでDSparkを推奨すると書かれています。

やってみる

前提条件

項目 内容
ハードウェア NVIDIA DGX Spark(GB10 / 統合メモリ 128GB / 273 GB/s)
推論エンジン vLLM(Docker イメージ vllm/vllm-openai:v0.27.1
本体モデル nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-NVFP4
ドラフトモデル nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-NVFP4-DSpark / nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-NVFP4-DFlash
計測条件 並行 1 と並行 8、コード生成タスクのみ

Docker の準備

vLLM はコンテナで動かします。DGX Spark 側の下準備は公式の vLLM Playbook、コンテナの起動方法は Nemotron 3.5 Lightning の vLLM cookbook に従います。

使用するコンテナイメージは、Nemotron 3.5 Lightning のモデルカードが指定している vllm/vllm-openai:v0.27.1 です。

$ docker pull vllm/vllm-openai:v0.27.1

次にコンテナを起動します。vLLM cookbook の手順に従い、--entrypoint /bin/bash でシェルに入り、コンテナの中で vllm serve を実行する形を取ります。

$ docker run --rm -it --gpus all --ipc=host --network=host \
    -v ~/.cache/huggingface:/root/.cache/huggingface \
    --entrypoint /bin/bash \
    vllm/vllm-openai:v0.27.1

以降のコマンドは、断りがない限りこのコンテナの中で実行します。まず使用するモデルを環境変数に入れておきます。

# コンテナ内
$ export MODEL_CKPT=nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-NVFP4
$ export DSPARK_CKPT=nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-NVFP4-DSpark
$ export DFLASH_CKPT=nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-NVFP4-DFlash

モデルカードの DGX Spark レシピをベースにする

vllm serve のオプションは、モデルカードの「1x DGX Spark (GB10)」のレシピをベースにしています。

Speculative Decoding Strategies

原文はこうなっています。

# モデルカード「1x DGX Spark (GB10)」/ Specdec method - DSpark
$ vllm serve --model $MODEL_CKPT \
    --moe-backend marlin \
    --kv-cache-dtype fp8 \
    --max-model-len 1048576 \
    --enable-prefix-caching \
    --gpu-memory-utilization 0.91 \
    --speculative_config.num_speculative_tokens 3 \
    --mamba-backend flashinfer \
    --mamba-cache-mode align \
    --reasoning-parser nemotron_v3 \
    --speculative_config.model $DSPARK_CKPT \
    --tool-call-parser qwen3_coder \
    --enable-auto-tool-choice

このレシピの --speculative_config.* の部分を差し替えて 4 構成を作り、トークンのスループットを計測します。

構成 --speculative_config.* の指定
ベース 2 行とも削除
MTP .method mtp + .num_speculative_tokens 3 + .moe_backend triton
DFlash .method dflash + .model $DFLASH_CKPT + .num_speculative_tokens 3
DSpark .method dspark + .model $DSPARK_CKPT + .num_speculative_tokens 3

--speculative_config.method はモデルカードの原文には書かれておらず、ドラフトモデルの指定だけで動作するようになっています。ただし MTP はドラフトモデルを持たないため method を書かないと区別できません。4 構成で書き方を揃えるため、DFlash と DSpark でも cookbook に倣って method を明示することにしました。

変更箇所

ベースから --max-model-len を変更しています。モデルカードは 1M トークン(1048576)を指定していますが、全構成で 65536 に下げます。

並行 8 の計測で KV キャッシュに余裕を持たせるためと、4 構成の比較条件を揃えるためです。モデルカード自身も、H100 と GB200 のレシピについて以下のように書いており、ワークロードに合わせて下げることを想定しています。

If you're memory-constrained — or want more KV-cache headroom at high concurrency — lower --max-model-len to match your workload.

ベース(投機的デコーディングなし)

まず比較対象となる素の構成を起動します。

# コンテナ内
$ vllm serve --model ${MODEL_CKPT} \
    --moe-backend marlin \
    --kv-cache-dtype fp8 \
    --max-model-len 65536 \
    --enable-prefix-caching \
    --gpu-memory-utilization 0.91 \
    --mamba-backend flashinfer \
    --mamba-cache-mode align \
    --reasoning-parser nemotron_v3 \
    --tool-call-parser qwen3_coder \
    --enable-auto-tool-choice

30B のモデルをロードするため、起動完了まで数分かかります。cookbook に従い、/v1/models が応答するまで待ちます。ホスト側の別のターミナルで実行してください。

# ホスト側
$ until curl -sf http://localhost:8000/v1/models > /dev/null 2>&1; do
    echo "Waiting for server..."; sleep 5
  done
  echo "Server is ready"
Waiting for server...
Waiting for server...
Waiting for server...
Waiting for server...
Waiting for server...
Server is ready

初回はモデルのダウンロードが走るため時間がかかります。

MTP

MTP はモデル本体に内蔵されているため、ドラフトモデルの指定が不要です。--speculative_config.method mtp--speculative_config.num_speculative_tokens 3--speculative_config.moe_backend triton を追加すると有効になります。初め --speculative_config.moe_backend を指定しておらず、--moe-backend marlin がドラフタで対応しておらずエラーとなっていました。--speculative_config.moe_backendtriton, batched_triton, flashinfer_trtllm, flashinfer_cutlass, aiter の中から選択する必要があります。

# コンテナ内
$ vllm serve --model ${MODEL_CKPT} \
    --moe-backend marlin \
    --kv-cache-dtype fp8 \
    --max-model-len 65536 \
    --enable-prefix-caching \
    --gpu-memory-utilization 0.91 \
    --speculative_config.method mtp \
    --speculative_config.num_speculative_tokens 3 \
    --speculative_config.moe_backend triton \
    --mamba-backend flashinfer \
    --mamba-cache-mode align \
    --reasoning-parser nemotron_v3 \
    --tool-call-parser qwen3_coder \
    --enable-auto-tool-choice

DFlash

DFlash はドラフトモデルを別途指定します。

# コンテナ内
$ vllm serve --model ${MODEL_CKPT} \
    --moe-backend marlin \
    --kv-cache-dtype fp8 \
    --max-model-len 65536 \
    --enable-prefix-caching \
    --gpu-memory-utilization 0.91 \
    --speculative_config.method dflash \
    --speculative_config.model ${DFLASH_CKPT} \
    --speculative_config.num_speculative_tokens 3 \
    --mamba-backend flashinfer \
    --mamba-cache-mode align \
    --reasoning-parser nemotron_v3 \
    --tool-call-parser qwen3_coder \
    --enable-auto-tool-choice

DSpark

DSpark も同様にドラフトモデルを指定します。

# コンテナ内
$ vllm serve --model ${MODEL_CKPT} \
    --moe-backend marlin \
    --kv-cache-dtype fp8 \
    --max-model-len 65536 \
    --enable-prefix-caching \
    --gpu-memory-utilization 0.91 \
    --speculative_config.method dspark \
    --speculative_config.model ${DSPARK_CKPT} \
    --speculative_config.num_speculative_tokens 3 \
    --mamba-backend flashinfer \
    --mamba-cache-mode align \
    --reasoning-parser nemotron_v3 \
    --tool-call-parser qwen3_coder \
    --enable-auto-tool-choice

なお、モデルカードの DGX Spark 用レシピには --speculative_config.method の指定がなく、ドラフトモデルの指定だけで動作するように書かれています。cookbook 側では明示的に method を指定しているため、ここでは cookbook に揃えて明示しました。

全ての計測が終わったら、Ctrl-C で vLLM を止めてから exit でコンテナを抜けます。--rm を付けて起動しているので、コンテナは自動的に削除されます。

計測する

クライアントは cookbook の書き方に合わせます。cookbook では openai==2.38.0 を使い、base_urlhttp://127.0.0.1:8000/v1api_keynull を指定しています。サンプリング設定も公式推奨の Temperature 1.0 / Top_P 0.95 に固定します。

$ pip install openai==2.38.0

計測用のスクリプトを bench.py として用意しました。コード生成タスクのプロンプトを指定した並行数で投げ、スループットを集計します。

import argparse
import time
from concurrent.futures import ThreadPoolExecutor
from openai import OpenAI

PROMPTS = [
    "Write a Python function that parses an nginx access log file and returns the top 10 IP addresses by request count. Include type hints and docstrings.",
    "Implement a thread-safe LRU cache in Python with get and put in O(1). Include type hints and docstrings.",
    "Write a Python script that walks a directory tree and reports the 20 largest files, with a --min-size option. Include type hints and docstrings.",
    "Implement binary search over a rotated sorted array in Python, handling duplicates. Include type hints and docstrings.",
]

def parse_args():
    p = argparse.ArgumentParser()
    p.add_argument("--base-url", default="http://127.0.0.1:8000/v1")
    p.add_argument("--model", default="nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-NVFP4")
    p.add_argument("--concurrency", type=int, default=1)
    p.add_argument("--requests", type=int, default=8)
    p.add_argument("--max-tokens", type=int, default=2048)
    p.add_argument("--warmup", type=int, default=2)
    return p.parse_args()

def one_request(client, args, index):
    prompt = PROMPTS[index % len(PROMPTS)]
    start = time.perf_counter()
    response = client.chat.completions.create(
        model=args.model,
        messages=[{"role": "user", "content": prompt}],
        max_tokens=args.max_tokens,
        temperature=1.0,
        top_p=0.95,
        extra_body={"chat_template_kwargs": {"enable_thinking": False}},
    )
    elapsed = time.perf_counter() - start
    return response.usage.completion_tokens, elapsed

def main():
    args = parse_args()
    client = OpenAI(base_url=args.base_url, api_key="null")

    for i in range(args.warmup):
        one_request(client, args, i)

    wall_start = time.perf_counter()
    with ThreadPoolExecutor(max_workers=args.concurrency) as executor:
        results = list(executor.map(lambda i: one_request(client, args, i), range(args.requests)))
    wall = time.perf_counter() - wall_start

    total_tokens = sum(tokens for tokens, _ in results)
    per_request_tps = [tokens / elapsed for tokens, elapsed in results]

    print(f"concurrency     : {args.concurrency}")
    print(f"requests        : {args.requests}")
    print(f"wall clock      : {wall:.2f} s")
    print(f"total tokens    : {total_tokens}")
    print(f"aggregate TPS   : {total_tokens / wall:.2f} tok/s")
    print(f"per-request TPS : {sum(per_request_tps) / len(per_request_tps):.2f} tok/s (avg)")
    print(f"tokens/request  : {total_tokens / len(results):.1f} (avg)")

if __name__ == "__main__":
    main()

ThreadPoolExecutor(max_workers=args.concurrency) で同時に飛ぶリクエスト数を抑え、--requests 件を流し切るまでの実時間を測ります。並行 1 と並行 8 はそれぞれ以下のように実行します。

$ python3 bench.py --concurrency 1 --requests 10
concurrency     : 1
requests        : 10
wall clock      : 145.77 s
total tokens    : 11853
aggregate TPS   : 81.31 tok/s
per-request TPS : 81.27 tok/s (avg)
tokens/request  : 1185.3 (avg)
$ python3 bench.py --concurrency 8 --requests 80
concurrency     : 8
requests        : 80
wall clock      : 406.28 s
total tokens    : 98185
aggregate TPS   : 241.67 tok/s
per-request TPS : 30.97 tok/s (avg)
tokens/request  : 1227.3 (avg)

並行 1 では 1 ユーザーあたりの体感速度を示す per-request TPS、並行 8 ではサーバー全体のさばく量を示す aggregate TPS を採ります。

計測条件について

スクリプトを組むにあたって 3 点調整しています。

  • enable_thinkingFalse にしています。
    • Nemotron 3.5 Lightning は既定で推論(thinking)が有効になっていますが、今回想定しているエージェントの実行層、つまりツール呼び出しや結果の整形といった高頻度ステップでは、思考を切ったほうが速くなります。
    • また、投機的デコーディングの比較のために出力トークン数を安定させるためでもあります。
  • プロンプトを 4 種類用意して巡回させています
    • 完全に同じプロンプトを投げるとプリフィルがキャッシュヒットするため、複数の異なるプロンプトにより、キャッシュの可能性を下げています。
  • ウォームアップを 2 回入れています
    • 起動直後の数リクエストは CUDA グラフのキャプチャなどで遅くなるため、--warmup の分は計測から除外しています。

計測結果

|方式|並行 1
per-request TPS|並行 8
aggregate TPS|ベースライン比
(並行 1)|ベースライン比
(並行 8)|
|---|---|---|---|---| 
|投機なし|81.27|241.67|100.00%|100.00%|
|MTP|111.36|302.27|137.02%|125.08%|
|DFlash|95.48|268.61|117.48%|111.15%|
|DSpark|124.24|354.56|152.87%|146.71%|

投機的デコーディングによるトークンスループット

投機的デコーディングの効果は出ており、特に DSpark が 並行 1 でも 並行 8 でも効果が高くなっています。並行度が上がる場合には MTP の効果が高いと記載がありましたが、DGX Spark で並行 8 では順位が変わりませんでした、今回の計測では投機的デコーディングの中で DFlash が一番遅い結果となっていますが、ワークロード次第と考えられます。

さいごに

同じモデルに 3 種類の投機的デコーディングが同梱され、しかもハードウェアごとに推奨手法が示されているというのは、今まであまり見ない構成だと思います。単に速いモデルということではなく、どの環境でどう動かせば速いのかまで込みで想定されているという点に、Nemotron 3.5 Lightning というモデルの性格がよく表れていると思います。

今回の検証で個人的に面白かったのは、DGX Spark のメモリ帯域 273 GB/s という制約が、そのまま Lightning の設計思想に直結しているように思われます。常駐型の AI エージェントを想定しているためローカルでの実行も視野に入れており、DGX Spark のような帯域の限られたハードウェアで 30B クラスのモデルが実用的な速度で動くようになっています。

個人で DGX Spark を手配するのはまだまだハードルが高いですが、常駐エージェントを手元のハードウェアに置くという構成がいよいよ現実的になってきました。データを外に出さずにエージェントを動かしたいという要件は製造業のお客様を中心に根強くありますので、この方向の進化はこれからも進んでいくと思います。

DGX Spark をお持ちの方は、まずは公式推奨の DSpark 構成から試してみるのが良いかと思います。Playbook がまだ追いついていないので少し手間はかかりますが、モデルカードのレシピはそのまま動くようになっていますので、それを利用するのが確実です。


AI白書2026 配布中

クラスメソッドが独自に行なったAI診断調査をもとに、企業のAI活用の現在地を調査レポートとしてまとめました。企業規模別の活用度傾向に加え、規模を超えてAI活用を進める企業に共通する取り組みまで、自社の現在地を捉えるためのヒントにぜひ。

AI白書2026

無料でダウンロードする

この記事をシェアする

関連記事