
DGX Spark 2 台で DeepSeek V4 Flash-DSpark を動かしてみた
はじめに
こんにちは、クラスメソッド製造ビジネステクノロジー部の森茂です。
2026 年 6 月後半、DeepSeek から DeepSeek-V4-Flash-DSpark という、ちょっと変わった命名のモデルが HuggingFace に公開されました。ベースは 4 月公開の DeepSeek-V4-Flash(284B total / 13B active の MoE)と同じチェックポイントなのですが、ファイル名と config に dspark がそのまま入っています。
DeepSeek 公式が、DGX Spark という特定ハードウェアを名指しで命名・最適化したモデルを出してきた。これだけでも個人的にはちょっと気になるニュースなのですが、ベース版との差分が MTP(Multi-Token Prediction)speculative decoding module を attach した DGX Spark ターゲット版という、推論加速の中身に踏み込んだ違いになっていて、なおさら触ってみたくなりました。
もう一つ、今回触ってみた背景には個人的な事情もあります。最近 NVIDIA LLM Router v3 を常用していて、9 model pool での routing 実績を観察すると、自分の普段のタスクはほとんど V4 Flash あたりに振られていることに気づきました。Hermes Agent / Codex / Claude Code 経由の使い心地も、品質的に気にならないレベルで回ってくれています。そこに DSpark 版がタイミングよく登場したので、「同じ V4 Flash を手元の DGX Spark で動かせれば API コストを考えなくて済むし、現実的な常用環境になりそうだ」と踏んで早速持ち込んでみた、というのが今回の流れです。
DGX Spark 2 台という前提条件はもちろん軽くないので、すべての人にとって現実的な選択肢とは言いません。ただ 2 台持ちが許せる環境であれば、最近のローカル LLM 事情と LLM Router の振る舞いを踏まえる限り、十分に検討に値する組み合わせかなと思っています。
当初は NVIDIA が公開している nvidia/DeepSeek-V4-Flash-NVFP4 を 2 ノードで動かす想定で素材を集めていました。途中で DSpark 版が出てきたので主役を差し替えています。NVFP4 との並列比較は別記事に分けることにして、本記事は DSpark 単独で「DGX Spark 2 台 + vLLM」で動くところまでを追います。
結論を先に書くと、reasoning タスクで thinking を切ったときに decode 55.17 tok/s / MTP acceptance 40.4% まで出てくれて、DGX Spark 2 台で 13B active の MoE をそれなりに実用的な速度で回せました。一方で thinking を入れた瞬間にスループットが 1.32 倍ぶん落ちる、context を 900K まで伸ばすと decode が attention 計算で頭打ちになる、といった現実も見えてきました。
DSpark の正体を config から読む
まず気になるのは「ベース版 V4 Flash と DSpark 版で何が違うのか」というところです。HuggingFace のファイル一覧を見ると、本体 48 shards / 合計 167.57 GB で、ベースの V4 Flash と同じ容量帯。ただし encoding/ と inference/ というディレクトリが付いていて、ここに DSML(DeepSeek が定義している tool-call markup)のエンコーダと、minimal な参考実装が同梱されています。
差分の本体は config.json の末尾 2 行にあります。
{
"model_type": "deepseek_v4",
"num_hidden_layers": 43,
"n_routed_experts": 256,
"num_experts_per_tok": 6,
"max_position_embeddings": 1048576,
"rope_scaling": {"type": "yarn", "factor": 16},
"quantization_config": {
"quant_method": "fp8",
"fmt": "e4m3",
"scale_fmt": "ue8m0",
"weight_block_size": [128, 128]
},
"num_nextn_predict_layers": 1,
"dspark_target_layer_ids": [40, 41, 42]
}
ポイントは dspark_target_layer_ids: [40, 41, 42] と num_nextn_predict_layers: 1 の組み合わせです。43 層構成の MoE のうち末尾 3 層を DGX Spark 向けの speculation 用途として扱う、という宣言になっています。MTP は draft model を別に持つのではなく、本体ネットワークの末尾レイヤーをそのまま speculative decoding の予測層として転用する設計で、draft の重みは別 shard ではなく本体 48 shards に統合済みです。
量子化は FP8 (E4M3) ベース + MoE 部だけ FP4 の mixed、weight_block_size は [128, 128]、scale_fmt は ue8m0 という珍しい形式です。Expert ウェイトを FP4 まで圧縮することで、unified memory 128 GB の DGX Spark に 13B active の MoE を載せきるためのチューニングだと読めます。実際 2 ノード(TP=2)で --gpu-memory-utilization 0.82 を指定して、KV cache 込みで余裕を持って収まりました。
ベースの V4 Flash 自体は Hybrid Attention(Compressed Sparse Attention + Heavily Compressed Attention の組み合わせ)+ Manifold-Constrained Hyper-Connections という、DeepSeek が論文で出している新しい構造を使っています。1M context(max_position_embeddings: 1048576、yarn factor 16)まで設計上の上限が伸びていて、推論の深さを切り替える reasoning_effort も使えます。モデルカード側の表記は non-think / high / max の 3 段階ですが、vLLM 経由の API では OpenAI 標準の none / low / medium / high / xhigh / max を受け付ける形になっており、non-think をそのまま投げると 400 が返るので注意が要ります。DSpark 版はこれらの機能をそのまま継承していて、encoding_dsv4 というカスタムトークナイザ拡張を使うため、vLLM 起動時に --trust-remote-code が必須になります。
ライセンスは MIT。tool calling と reasoning を備えた 13B active の MoE が公式に DGX Spark 名指しで配布されている、というのが DSpark 版の立ち位置です。
2 ノード環境と tonyd2wild Recipe を持ち込む
DSpark の重み 167 GB を素直に 1 台の DGX Spark に載せようとすると、unified memory 128 GB では足りません。2 台に分散させて TP=2 で起動するのが現実解で、ノード間通信が cluster の速度を決めることになります。
今回の構成は、DGX Spark 2 台(node1 / node2)の QSFP ポートを直結ケーブル 1 本でつなぐだけのシンプルな構成です。QSFP のスペックは 200 Gbps ですが、iperf3 で実測すると single stream で 14.9 Gbps、4 並列で 16.4 Gbps あたりが上限でした。カーネルのストリーミング能力で頭打ちになっていて、200 Gbps を活かしきれない、という前提で組んでいます。それでも Wi-Fi 経由(数百 Mbps)や Tailscale 経由(〜90 Mbps)と比べれば 2 桁速いので、TP all-reduce の通信には十分です。ping の RTT は 0.5〜0.7 ms と、直結リンクらしい応答が返ってきます。
緑が QSFP 直結(vLLM の TP all-reduce で使いたい経路)、紫が Wi-Fi、黄が Tailscale です。default route は Wi-Fi 側に向いているので、vLLM が何も指定しないとそちらを掴みにいきます。これが後で罠になりますが、ひとまず物理層はこの形で揃えました。
Recipe 選びでひと回りした話
ソフト側は、最初は Aiden さんの Recipe(aidendle94/sparkrun-vllm-ds4-gb10:production-ready)から始めました。これはベースの DeepSeek-V4-Flash を 2 ノードで動かす用途で広く使われている image で、--speculative-config '{"method":"mtp", ...}' を指定すると MTP を有効にできる、という建付けです。
ところが DSpark の重みを食わせて method=mtp で起動すると、vLLM のロード処理が次のように落ちます。
File ".../vllm/models/deepseek_v4/nvidia/mtp.py", line 448, in load_weights
params_dict[name]
KeyError: ...
DSpark 版の MTP weight 命名と、Aiden image に同梱されている vLLM の MTP loader が一致していないようです。素の method=mtp 経路は、DSpark の MTP module をそのままでは読めません。
そこで切り替えたのが、tonyd2wild の Recipe(tonyd2wild/DeepSeek-v4-Flash-DSpark-60-tok-s-900K-ctx-2x-DGX-Spark)です。リポジトリ名が「60 tok/s / 900K context / 2x DGX Spark」と威勢のいい文字列で攻めていますが、中身はベース image (ghcr.io/bjk110/vllm-spark:unholy-fusion-prod-ready) に Rafael Caricio さんの vLLM PR (rafaelcaricio/vllm#1) を含む runtime overlay を重ねて build する、堅実な作りでした。この overlay 込みの vLLM だと --speculative-config '{"method":"dspark", "num_speculative_tokens":5}' という DSpark 専用の method を選べるようになって、DSpark の MTP weight が読めます。
build-dspark-vllm-runtime.sh を流すと、両ノードで vllm-dspark-runtime:clean(22.7 GB)が出来上がります。ここまではすんなり通りました。
vLLM が Wi-Fi を掴む罠と必須パッチ 3 点
build したのに起動すると、vLLM の TP all-reduce が始まったところでこんなエラーで落ちました。
RuntimeError: ifa != nullptr. Unable to find address for: enP7s7
Gloo backend が enP7s7 という存在しない interface を探しに行って失敗しています。原因を辿ると、vLLM の multiproc_executor.py が default route の Wi-Fi 側 IP(192.168.64.198)を mq_connect_ip に選んでいて、そこから到達できる interface を名前で推測しているのが原因でした。
DGX Spark の interface 構成は冒頭の Mermaid のとおりで、enp1s0f1np1(QSFP)を使ってほしいのですが、これを vLLM に明示しないと Wi-Fi 側に行ってしまいます。Recipe に対して次の 3 つのパッチを当てました。
必須パッチ 3 点(クリックで展開)
A. .env.dspark に追加
WORKER_IP=192.168.0.14 # worker QSFP IP(head は MASTER_ADDR を流用)
NCCL_IB_GID_INDEX=3 # default 0 だと RoCEv2 で詰まるため 3 に
B. docker-compose.dspark.yml の environment: セクション
GLOO_SOCKET_IFNAME: '${GLOO_SOCKET_IFNAME:-${NCCL_SOCKET_IFNAME}}'
TP_SOCKET_IFNAME: '${TP_SOCKET_IFNAME:-${NCCL_SOCKET_IFNAME}}'
MN_IF_NAME: '${MN_IF_NAME:-${NCCL_SOCKET_IFNAME}}'
OMPI_MCA_btl_tcp_if_include: '${OMPI_MCA_btl_tcp_if_include:-${NCCL_SOCKET_IFNAME}}'
WORKER_IP: '${WORKER_IP:-}'
C. command: 冒頭に NODE_RANK 別 VLLM_HOST_IP 分岐
if [ "${NODE_RANK:-0}" = "0" ]; then
export VLLM_HOST_IP="${MASTER_ADDR}"
else
export VLLM_HOST_IP="${WORKER_IP:-}"
fi;
ここでやっていることは、結局のところ「ノード間通信に使う interface を QSFP 側に固定する」というだけです。MN_IF_NAME / GLOO_SOCKET_IFNAME / TP_SOCKET_IFNAME / OMPI_MCA_btl_tcp_if_include の 4 つで、それぞれ vLLM の multiproc、Gloo、TP、OpenMPI の経路を抑え、NODE_RANK に応じて VLLM_HOST_IP を head=MASTER_ADDR / worker=WORKER_IP に分岐させます。
これを当ててから build → 起動の流れに戻ると、TP 初期化のところで詰まらなくなりました。
公式ベンチで実力をざっくり把握する
実機で速度を測る前に、V4 Flash 自体がどのくらいの実力なのかを公式の数字で把握しておきます。本記事の DSpark 版はベース V4 Flash と同じチェックポイントなので、ベンチ上の品質は V4 Flash の値がそのまま当てはまります。
DeepSeek が公開している V4 Flash Max(Think Max モード)のコーディングと長文理解の数字は次のとおりです。
| ベンチマーク | V4-Flash Max | Gemini 3.1 Pro | Opus 4.6 Max |
|---|---|---|---|
| LiveCodeBench | 91.6 | 91.7 | 88.8 |
| Codeforces Rating | 3052 | 3052 | — |
| SWE Verified | 79.0 | — | — |
| MRCR 1M | 78.7 | — | — |
| Terminal Bench 2.0 | 56.9 | — | — |
LiveCodeBench は Gemini 3.1 Pro と同水準、Codeforces レーティング 3052 も並びです。SWE Verified 79.0 や MRCR 1M 78.7 まで合わせて見ると、Active 13B の MoE で重みも MIT で配布されているモデル、というスペックを考えるとかなり攻めた数字に見えます。
ここに DSpark の加速分が乗ります。論文 §5.4 と公式 README によれば、DSpark-5(γ=5 + Markov head)を有効にした DeepSeek 公式の serving 環境では、MTP-1 ベースラインに対して per-user の生成速度が +60〜85% 上がるという主張です。今回 DGX Spark 2 ノードで動かす目的のひとつは、この 「同じ重みでより速く」 が手元でどれくらい実感できるか、を確かめることになります。
vLLM で起動するまでに 19 分かかった内訳
パッチを当てて worker → head の順に docker run すると、port 8888 で API が応答するまで約 19 分かかりました。ログを追うと、待ち時間の正体は次のように分かれていました。
48 shards のロード 162 秒と mHC kernel warmup 24 秒は想定範囲内で、残り 15 分強はほぼ全部 sparse MLA の autotune と TileLang JIT、FlashInfer autotune に消えていました。DGX Spark の sm_121a(GB10)向けに最速パラメータを実機で探しにいく工程で、初回起動だけはどうしても重くなります。
幸い結果はホスト側の ~/.cache/huggingface 配下に永続化されるので、2 回目以降の起動は約 7 分程度まで縮みます。Hermes Agent や Codex から常時 API を叩く運用なら 1 回起動して常駐させるのが基本なので、初回だけ覚悟しておけば運用上の負担にはなりません。
ここでようやく curl http://127.0.0.1:8888/v1/models で DeepSeek-V4-Flash-DSpark が返ってきて、軽い数式 smoke も <think> ブロック付きで通りました。max_model_len は tonyd2wild default の 262,144(262K) で、Hermes の 256K context 常用にちょうどよい設定です。
短〜中コンテキストで decode tok/s を測る
ここからは実機の速度を見ていきます。MTP の効きを比較したいので、3 種類のプロンプトを用意しました。
- medium:200 字程度の自然言語の質問(一般的な雑談・知識質問)
- long:500 字程度の自然言語の説明依頼
- reasoning_code:「BFS で迷路を解く Python コードを書いて」という 42 token の短いコード生成依頼
それぞれに対して、<think> ブロックを使う(thinking ON)/ 使わない(thinking OFF)の 2 通りで decode tok/s を計測しました。コンテキストは prod の 262K 設定(max_num_seqs=1、single user)です。
| プロンプト | thinking ON | thinking OFF | speed-up |
|---|---|---|---|
| medium(自然言語 200 字) | 35.44 | 35.00 | 0.99x |
| long(自然言語 500 字) | 37.81 | 39.05 | 1.03x |
| reasoning_code(BFS Python) | 41.90 | 55.17 | 1.32x |
数字の単位は tok/s(decode 段階の出力トークン速度)です。medium / long は thinking の有無で差がほとんど出ません。35〜39 tok/s というのは、unified memory 128 GB × 2 ノードの DGX Spark で 13B active の MoE を回したときの素直な実力で、ローカル LLM としては実用的な速度に入っています。Hermes Agent や Codex から普通の質問応答を投げる用途なら、ここがベースラインになります。
一方で reasoning_code だけは様子が違います。thinking ON の 41.90 tok/s から thinking OFF にすると 55.17 tok/s まで跳ねます。同じプロンプトで、reasoning_effort= を切り替えただけでこの差です。TTFT(最初のトークンが返るまでの時間)は短文プロンプトで 0.6〜2.1 秒、中文で 4.7〜8.8 秒ほどで、これは prompt token を一気に prefill する重さに比例して動きました。
ここで気になるのは「なぜ reasoning_code だけ thinking off で大きく伸びるのか」というところです。普通に考えると、<think> ブロックを挟む分だけ thinking ON は出力長が増えて遅くなりそうな気もしますが、これは MTP の acceptance rate の話に直結していて、次の章の核になります。
なお、tonyd2wild の README は同じ DSpark 構成で code_completion 系の 512 トークンプロンプトを使い、62.48 tok/s という数字を出しています。同条件にしていないので単純比較はできませんが、prompt 構造を draft 向きに揃えればこのレンジまで届く可能性は十分残っていそうです。これは別記事の話題として残しておきます。
参考までに、OpenRouter 経由で同じ V4 Flash を引いている主要プロバイダの throughput 中央値(過去 30 分集計)は、Baidu 66 / Fireworks 63 / Alibaba 62 / DeepSeek 公式 61 / SiliconFlow 61 tok/s あたりに並びます。ただ OpenRouter の V4 Flash は reasoning_effort の default が high で、low / off の指定はサポートされません。同じ thinking ON 同士なら DSpark の 41 tok/s に対して cloud 上位は 60 tok/s 強なので、ここはまだ差があります。
なお thinking OFF の 55 tok/s 側は、curl で reasoning_effort=none を直接叩く前提の数字です。vLLM の DSpark 実装はこのモードだと content を null にして最終出力を reasoning フィールドに入れる挙動なので、OpenAI 互換クライアント(Hermes Agent / Codex / Claude Code など message.content を読みに行く側)からは空応答に見えます。Agent 経由で常用する場合は、現状そのまま使うと thinking ON 側の 41 tok/s が実効値、と考えておくのが安全です。
MTP の効き方を thinking on / off で見る
ここからが本記事の核です。なぜ reasoning_code で thinking を切ると 1.32 倍も速くなるのか。深掘りしてみると、DSpark の MTP(Multi-Token Prediction)が、<think> ブロックを含む推論トレースに対して「draft が当てづらい」という性質を持っていることが見えてきます。
vLLM の /metrics エンドポイントには、DSpark 用の speculative decoding stats(num_drafts_total / num_draft_tokens_total / num_accepted_tokens_total / num_accepted_tokens_per_pos_total)が出ています。reasoning_code を thinking ON / OFF で 1 サンプルずつ動かして、起動直後(before)と完了後(after)の差分を取りました。
| drafts | accepted/draft (mean) | per-token acceptance | |
|---|---|---|---|
| thinking ON | 2124 | 1.21 | 24.2% |
| thinking OFF | 1731 | 2.02 | 40.4% |
thinking を切ると、1 回の draft で受け入れられるトークン数の平均が 1.21 → 2.02 と +67% 改善し、per-token acceptance も 24.2% → 40.4% に跳ねます。これは DSpark の draft(本体ネットワークの末尾 3 層 + Markov head)が、reasoning トレースのような 「次に何が来るか毎ステップ揺れる文章」を予測するのが苦手であることをそのまま示しています。逆にコード本体のような構造が決まっている文字列だと draft が当たりやすく、γ=5 の static draft でも acceptance が大きく取れます。
DSpark 論文の §1 でも「Math/code naturally sustain higher acceptance rates」という記述があって、reasoning トレース部分はその限りではない、というのが今回手元で確認できた事実です。「reasoning を本当に必要としないタスクでは、thinking を切るほうがむしろ MTP の恩恵を大きく受けられる」というのは、運用上わりと使い分けの目安になります。
なお、vLLM で動いている DSpark は論文の完全版ではなく、論文 §3.2 で提案されている動的 verification scheduler はオフ(VLLM_DSPARK_CONFIDENCE_SCHEDULER=off)で、Markov head + γ=5 の static draft 部分だけが効いている状態です。DeepSeek 公式 serving の「MTP-1 比 +60〜85%」はこの scheduler を含めた数字なので、scheduler が vLLM に移植されれば、ここからまだ伸びる余地は残っていると言えます。
long context を素朴に伸ばすとどうなるか
DSpark は設計上 1M tokens まで扱える前提のモデルです。262K prod のまま入力 context を 64K / 128K / 256K に変えて decode tok/s を測ると、次のような形になりました。
| 入力 context | decode tok/s(推定) |
|---|---|
| 64K | 8.7 |
| 128K | 8.4 |
| 256K | 3.2 |
章 6 の短文プロンプトで出た 30〜55 tok/s と比べると、入力が膨らむだけで decode は二段階で落ちていきます。64K と 128K はほぼ横ばいですが、256K まで来ると一段下がる、というカーブです。
ここから max_model_len を 900K に上げて再起動し、247K トークンほどの長いテキストに「続きの Python コードを書いて」と乗せて 500 token 出力させると、総 elapsed 約 4 分 32 秒、decode は 2.4 tok/s 前後でした。それでも DSpark の accept rate は 46.8% で短文時(40.4%)から落ちず、出力されたコードも入力 context の関数名やシグネチャを汲んだ内容になっていて、draft 品質と出力品質は long context でも保たれることが確認できました。decode が落ちるのは attention 計算が context 長に応じて重くなるからで、品質が壊れて使えなくなる話ではない、というのが現実です。
ということで個人的には、Hermes Agent や Codex のようにコンテキストを切らさずに常駐させる用途では、max_model_len=262144(262K)で構えつつ毎ターンの実コンテキストを数 K 〜 数十 K に収める運用が一番現実的かなと思っています。900K を使うメリットがある場面は、長文 1 ショットの要約やコード読みに閉じてくる印象です。
まとめ
DeepSeek 公式の DGX Spark 名指しモデル DeepSeek-V4-Flash-DSpark を、2 台の DGX Spark に tonyd2wild Recipe と独自パッチを組み合わせて持ち込み、reasoning コード生成で 55.17 tok/s / MTP acceptance 40.4% まで実機で見られました。
冒頭で触れたとおり、LLM Router v3 を常用していると普段のタスクの多くは V4 Flash 周辺に振られる、というのが最近の手元の実感でした。今回見えた速度域も、章 6 末で触れたとおり OpenRouter 上位プロバイダ(thinking ON で 60 tok/s 帯)と比較できるラインまで近づいてきています。Agent 経由運用の現実値は thinking ON 側の 41 tok/s で、curl 直叩きで thinking OFF の 55 tok/s を引き出せるとは言え常用クライアントとして使うにはあと一息というところでしょうか。それでも LLM Router v3 で V4 Flash に振られている経路を、そのまま DSpark 側にすげ替えるという発想は十分視野に入ってくる距離感です。
クラウド API の応答に慣れていると少し速度が気になる場面はあるものの、常用できなくもない、という程度の体感です。2 台持ちのハードルが軽くないのは間違いないので、誰にでも薦められる構成ではないですが、環境さえ整えばかなり面白い立ち位置ではないでしょうか。








