この記事についてClaude(Anthropic)との共同編集により作成されました。
要約
- 前回、DGX Spark 2台の約198万円は個人には元が取れないと計算した。その結論は撤回しない。今回変わったのは結論ではなく適用範囲のほうで、個人には微妙なまま、企業では有用そうという形に分かれた
- 分岐点は「そもそも外に出せないデータ」の有無である。それが要件になると、比較相手がAPI課金から「やらない」に変わる。個人にはこの制約がほぼ発生しないので、前回の計算がそのまま効いて負ける
- 業務でやったのは、コードレビューのLLM担当部分をローカルに寄せること。静的解析で機械的に拾える箇所と、文脈を読まないと拾えない箇所を分けて、後者をQwen3.8 Flash Nextに投げた
- 構築は MiaAI-Lab/Qwen3.8-Flash-Next-Dual-DGX-Sparks で通った。現時点でこれが最良の選択肢である。チューニング系の数値はひとつも変えていない。手で書いたのはIPと結線情報だけで、性能に効く変数はデフォルトのまま
- 速度は体感で明確に速い。ただし公開実測値と突き合わせると、単流デコードはDeepSeek V4 Flash 0731のほうが速い。差が出ているのはTTFT・プレフィル・並列時の集約スループットのほうだった
- そしてコードレビューという仕事は、長いプロンプトを読ませて短く返させる形をしている。体感を支配するのがまさにその3つなので、数字と体感は矛盾していない
- ただしDeepSeekを回していた時期とのあいだにDGX OSのバンドル更新を挟んでいるので、これは厳密なA/Bではない。速くなった原因がプラットフォーム側にある可能性は切り分けられていない
- 画像も入力できる。エラー画面のスクショや設計図をそのまま添えられるのは、業務適用の幅を明確に広げる
- ただし本格的な開発をこれで回すべきではない。設計から書かせる仕事はClaudeやGPTのほうが上である。ローカルに寄せる価値があるのは「出せないから今までやれていなかった仕事」だけだ
- 企業側にはもうひとつ条件が効く。複数人が同時に投げるという使われ方である。8本並列で207 tok/sという集約は、1人で1本ずつ使うぶんには活きない。設備として持つ意味があるのは企業側だけだと思う
はじめに
前回、DeepSeek-V4-Flash-0731 をDGX Spark 2台でローカルに立てる話を書いた1。結論は明快で、個人が安く使いたいという動機で198万円を出すなら答えはNOである。同じ金額をAPIに突っ込めば6.4倍のトークンが買えるし、しかも公式APIのほうが速い。計算結果としてそうなった。
ただ、あの記事にはひとつ扱っていない項目があった。
そもそも外に出せないデータの存在である。
自社開発のソースコードがまさにそれだ。外部APIに投げてよいかを法務とセキュリティに確認して、確認が通らなければそこで終わる。この場合、ローカルLLMの比較相手はAPI課金ではない。比較相手は「やらない」である。
やらないことのコストはゼロに見えるが、実際にはゼロではない。レビューされないまま通るコードの分だけ、後で誰かが払っている。ここが金額の土俵と噛み合わない部分で、前回の計算がまるごと通用しない領域になる。
というわけで今回は業務側の話を書く。Qwen3.8 Flash NextをDGX Spark 2台で動かして、コードレビューのLLM部分を担当させた。動かしてみた記事は既に山ほどあるので、ここではユースケースのほうを中心に書く。
ソースコードそのものは当然載せられない。載せられるのは、どういう形で使ったかと、使ってみて何が分かったかである。
何をやらせたか
やったことは一言でいうと、コードレビューを二段構えにして、その後段をローカルLLMに任せたというものだ。
前段はプログラムである。構文木を舐めて機械的に拾える性質のもの、たとえば未使用変数、例外を握りつぶしている except、明らかに到達しない分岐、そういうものは静的解析で確定的に出る。ここにLLMを使う理由はひとつもない。
問題は後段で、機械的な規則では書き下せないが、読めば分かるという層がある。ここが今まで人間のレビュー待ちで詰まっていた部分だった。
代表例がゼロ割である。Pythonでこう書いてあったとする。
def summarize(records): total = sum(r.amount for r in records) return total / len(records)構文としては何も間違っていない。テストも、records が空でないケースだけ書いてあれば通る。それでも records が空で呼ばれる経路が上流に1本でもあれば、ここは ZeroDivisionError で落ちる。
これを静的解析だけで拾おうとすると、呼び出し元を全部辿って空リストが渡りうるかを判定する羽目になり、現実的な精度で回らない。かといって「割り算があったら全部警告」にすると、ノイズが多すぎて誰も読まなくなる。
ここでLLMに、変更されたファイルと周辺の呼び出し元をまとめて渡して、「落ちる可能性が高い箇所を、なぜそう思うかつきで挙げてくれ」と頼む。すると、上流で空リストが渡りうる経路が実在するときだけ指摘が返ってくる。この使い方は精度が実用域に入っていた。
同じ形で拾えるものはほかにもある。
- 例外の握りつぶしのうち、握りつぶした結果が呼び出し元に伝わらないもの
- ループの中で毎回同じクエリを投げている箇所
- 型ヒントと実際に返している値がずれている箇所
- 排他制御なしで共有状態を書き換えている箇所
セキュリティ観点を混ぜることもできそうだと思った。ここは実際に本番運用したわけではないので推測として書くが、外部入力がサニタイズされずにクエリ組み立てへ流れている経路や、ログに認証情報が落ちている箇所は、上のゼロ割と同じ構造をしている。つまり規則で書き下すのは難しいが、周辺を一緒に読ませれば当たる。同じプロンプト設計がそのまま効くはずだ。
重要なのは、この仕事のためにソースコードが1バイトも社外に出ていないという点である。今回の全部の価値がここに乗っている。
構築は既存リポジトリで通った
構築は MiaAI-Lab/Qwen3.8-Flash-Next-Dual-DGX-Sparks2 でそのまま通った。現時点でこれが最良の選択肢だと思う。前回のDeepSeekのときも同じ人のリポジトリを見ていたが、今回も完成度が高い。
やることは実質3行に畳まれている。
cp .env.sample .envvim .env # IP、インターフェース、IB HCA を書く./start.sh裏では、重みのダウンロード、ワーカーノードへの同期、vLLMイメージの配布、ple_layer.py と modelopt.py へのパッチ当て、GPU占有のプリフライトチェック、2ノード分の起動までを全部やってくれる。手で踏むと確実に事故る工程が全部包まれている。
前提条件のほうは、素直に言って軽くない。
| 項目 | 必要なもの |
|---|---|
| ノード | DGX Spark(GB10、128GB統合メモリ)が2台 |
| ノード間接続 | ConnectX による RoCE / IB 直結 |
| SSH | 2台間でパスワードなしログイン |
| ディスク | チェックポイント用に各ノード約126GiB |
| コンテナ | 両ノードにDocker |
つまり初期コストは高い。前回の記事で計算したとおり本体だけで約196万円、3年の総保有コストで227万円あたりに着地する。ここは何をどう工夫しても下がらない。
ハマりどころとして書いておくと、うちの2台はクロス結線になっていた。ヘッド側が f1、ワーカー側が f0 である。リポジトリ側に WORKER_IFACE と WORKER_IB_HCA の個別指定が用意されているので、そこを埋めれば済む。READMEにも「別クラスタに繋がったHCAを指すとNCCLが無言でハングする」と書いてあり、実際そのとおりなので素直に従うのがいい。
なお、チューニング系の数値はひとつも変えていない。.env で自分の手が入っているのは、IPアドレス・インターフェース名・IB HCAというこの2台に固有の結線情報だけである。性能に効く変数はすべて .env.sample の値のまま動かしている。
そのデフォルトが何を意味しているかは、把握しておいたほうがいい。以下は自分で設定した値ではなく、リポジトリが最初から入れてくれている値である。
| 変数 | デフォルト値 | 何が起きているか |
|---|---|---|
TENSOR_PARALLEL_SIZE | 2 | 1ノード1GPU × 2 |
ENABLE_EXPERT_PARALLEL | true | NVFP4のエキスパート並列。必須 |
KV_CACHE_DTYPE | fp8 | KVキャッシュが365万トークンまで伸びる |
MTP_NUM_SPECULATIVE_TOKENS | 3 | 投機デコード。デコード速度が2.13倍になる |
MAX_MODEL_LEN | 262144 | ネイティブ。YaRNで1Mまで伸ばせる |
KVキャッシュがfp8なので、32.02 GiBのプールに3,652,200トークン入る。262Kコンテキストの同時実行が13.93本ぶんという計算になり、MAX_NUM_SEQS=8 のほうが先に頭打ちになる。このモデルで詰まるのはKVではなく重みのほうである。1ノードあたり62.72 GiBを重みが占める。
MTPについては、リポジトリ側にオンオフのA/Bが載っている2。バッチ1のデコードが24.5 tok/sから52.1 tok/sになり、ドラフト受理率は72.8%、位置ごとに89% / 74.5% / 60%と素直に減衰していく。自分でオフにして測り直したわけではないので、ここは公開値をそのまま引く。
この減衰カーブの読み方はなるほどと思った。カーブが素直かどうかがそのまま動作確認になる。ブロック形状を間違えていれば受理率はランダムに近づくので、クラッシュしなくても気づけるという話である。
速度:体感は速い。数字を見ると理由が違う
ここが今回いちばん書いておきたい部分である。
同じ2台構成で DeepSeek-V4-Flash-0731 を動かしていたときと比べて、Qwen3.8 Flash Nextは体感で明確に速かった。マルチモーダルなのにサクサク動く、というのが最初の印象で、これは業務に使えるレベルだと素直に思った。
ただ、この体感には先に差し引いておくべき条件がひとつある。DeepSeekを回していた時期とのあいだに、DGX OSのバンドル更新を挟んでいる。ハードは同じ2台のままだが、ドライバやライブラリの層は同じではない。つまりこれは厳密なA/Bではなく、モデルの差とプラットフォームの差が分離できていない比較である。速くなった原因がバンドル更新側にある可能性は、普通に残っている。
そのうえで公開されている実測値を突き合わせると、話はさらに単純ではなくなる。
| Qwen3.8 Flash Next2 | DeepSeek V4 Flash 07313 | |
|---|---|---|
| デコード(1本) | 54.4 tok/s | 62〜83 tok/s |
| デコード集約(多本) | 207.0 tok/s(8本) | 160〜190 tok/s(6本) |
| TTFT(1本) | 160 ms | — |
| プレフィル 32k | 2,962 tok/s | — |
| プレフィル 128k | 2,727 tok/s(TTFT 48秒) | 128KでTTFT 75秒 |
単純なデコード速度では、DeepSeekのほうが速い。1本流したときの tok/s は62〜83に対して54.4である。ここだけ見れば体感は間違っていることになる。
ただ、差が出ているのは別の3か所だった。
- TTFT が160ms。最初の1トークンが返るまでが速い
- プレフィルが16kから64kまでほぼ一定で約2,960 tok/s。128kまで伸ばしても8%しか落ちない
- 並列に投げたときの集約が207 tok/s。8本流して1本あたり26.7 tok/sを維持する
そしてコードレビューという仕事の形を思い出すと、これが全部刺さっていることに気づく。
レビューは、長いプロンプトを読ませて、短く返させる仕事である。
変更されたファイルと周辺の呼び出し元を渡すと、入力は簡単に数千から数万トークンになる。対して返ってくるのは「この関数のlen(records)は空リストで落ちる可能性がある」という数百トークンの指摘だ。入力が大きく、出力が小さい。
この形だと、体感速度を支配するのはデコード速度ではない。プレフィル速度とTTFTである。出力が数百トークンしかないなら、54.4 tok/sと83 tok/sの差は数秒にしかならない。一方で3万トークンのプレフィルにかかる時間は10秒単位で効いてくる。
さらに、レビューは複数ファイルを同時に投げる。ここで効くのが集約スループットのほうで、8本並列で207 tok/sという数字がそのまま体感に乗る。
つまり体感と数字は矛盾していない。自分がやらせている仕事の形が、たまたまQwenの得意な軸と一致していたというだけである。逆に、長文を延々と生成させる用途なら評価は変わるはずで、そこは正直に書いておきたい。
整理すると、体感が速かった理由の候補は3つある。
| 候補 | 効いていそうか |
|---|---|
| ワークロードの形(入力大・出力小)がQwen有利の軸と一致 | 効いている。プレフィルとTTFTと集約は数字でも優位 |
| モデル自体が速い | 単流デコードでは逆。ここだけならDeepSeekが上 |
| DGX OSバンドル更新 | 切り分けできていない。可能性は残る |
3つ目を潰すには、更新後のプラットフォームでDeepSeekをもう一度立てて測り直すしかない。今回それはやっていないので、この記事の速度の話は「ワークロードの形で説明がつく範囲まで」と読んでほしい。
ただ、この切り分けができていなくても、実務上の結論は変わらなかった。必要な速度が出ているかどうかが知りたいことであって、誰のおかげで出ているかは業務判断には効かないからだ。切り分けが要るのは、次にモデルを乗り換えるかを決めるときである。
そのうえで一般化できることがひとつある。ベンチマークの数字を見るときは、自分のワークロードが入力寄りか出力寄りかを先に決めてから見たほうがいい。単流デコードの tok/s は、いちばん目立つのに、いちばん自分の用途と関係ない数字であることがある。
画像を入れられるのが効く
Qwen3.8 Flash Nextはテキスト・画像・動画を入力に取れる。vLLM側が設定から自動検出するので、フラグは要らない。
これが業務適用の幅を明確に広げる。開発の現場で共有される情報は、そもそもテキストだけではないからだ。
- 落ちた画面のスクリーンショット
- ホワイトボードに書いたシーケンス図の写真
- 既存システムの構成図(PDFやスライドから切り出したもの)
- 再現手順を撮った短い動画
こういうものを、書き起こさずにそのまま添えられる。レビュー依頼に「この画面でこう落ちる」というスクショを1枚足せるかどうかは、実際に指摘の精度に効く。
リポジトリ側で確認されている挙動として、3840×2160のフレームに含まれる48px程度の文字も読めるとある2。加えて、画像に写っていない文字を読んだことにしない(正しく拒否する)ことも確認されている。ここは業務で使うときに地味に重要で、読めなかったときに黙って作り話をされるほうが困る。
一方で、使う前に知っておいたほうがいい制約もある。
| 制約 | 内容 |
|---|---|
| 入力形式 | URLかbase64のデータURLのみ。ローカルパスは渡せない |
| 枚数 | デフォルトで1プロンプトあたり画像1枚+動画1本 |
| ビジョン側の予算 | 16,384トークン。超えると自動でリサイズ・間引きされる |
| 思考の消費 | 推論モデルなのでthinkingが先に max_tokens を食う |
| MTP | 画像プロンプトはテキストのみからドラフトするので受理率が落ちる |
いちばん引っかかりやすいのは4つ目だと思う。content が空で finish_reason が length で返ってきたら、それは失敗ではなく思考だけで枠を使い切ったという意味である。max_tokens を2000から4000あたりに上げると素直に返るようになる。なお、このビルドでは chat_template_kwargs.thinking_budget は効かない。
向いていない仕事のほうも書いておく
正直に書くと、本格的な開発をこれで回すべきではないと思っている。
設計から起こす、大きめのリファクタを一気にかける、仕様の曖昧なところを詰めながら書く。こういう仕事はClaudeやGPTのほうが明確に上である。ここで意地を張ってローカルに寄せる理由はない。
線の引き方はシンプルで、こうなる。
| 仕事 | どこでやるか | 理由 |
|---|---|---|
| 設計・大規模リファクタ・詰めながら書く開発 | Claude / GPT | 単純に品質が上 |
| 社外に出せないソースの読解・指摘出し | ローカル | 出せない、が要件 |
| 機微情報を含むログ・障害情報の一次整理 | ローカル | 同上 |
| 公開OSSの調査、一般的な技術質問 | Claude / GPT | ローカルに寄せる価値がない |
ローカルに寄せる価値があるのは、「出せないから今までやれていなかった仕事」だけである。 それ以外を無理に寄せると、遅くて品質の低い選択肢をわざわざ選んでいることになる。
個人には微妙なまま。企業だと話が変わりそう
ここが今回の着地点になる。
前回の記事の結論は、撤回する必要がなかった。 安く使いたいという動機で198万円を出すなら、答えはNOのままである。個人が趣味で買って元が取れる金額ではないし、業務で使ってみた今もそこは動かない。
変わったのは結論ではなく、適用範囲のほうだ。同じ構成でも、個人と企業では損得の計算式そのものが違っていた。
| 個人 | 企業 | |
|---|---|---|
| 比較相手 | API課金 | 「やらない」 |
| 初期費用の重さ | 198万円を1人で被る | 台数ぶんの単価として吸収されうる |
| 出せないデータの有無 | ほぼ無い | 自社ソース・顧客データ・障害ログ |
| 使われ方 | 1人が1本ずつ | 複数人が同時に投げる |
| 結論 | 微妙。素直にAPIでいい | 有用そう |
個人の場合、機微データという制約がそもそも発生しない。手元にあるのは自分のコードで、外に出して困るものは大して無い。だから比較相手がAPI課金のままになり、前回の計算がそのまま効いて負ける。
企業の場合、そこに出せないという制約が先に立つ。すると比較相手がAPI課金から「やらない」に変わる。やらないことのコストは請求書に出てこないので見えにくいが、レビューされないまま通ったコードの分だけ、後で誰かが払っている。この見えないコストと198万円を並べるなら、検討可能な数字の側に入ってくる。
さらに企業側には、同時に複数人が投げるという条件が付く。前の節で見たとおり、この構成は8本並列で207 tok/sという集約が出る形をしている。1人が1本ずつ使うぶんには活きない性質が、人数が増えたところで初めて効く。設備として持つ意味があるのは、こちら側だけである。
なので現時点の見立てはこうなる。個人で安く済ませたいなら微妙、外部にソースを出せない厳しい制約のある企業なら有用そう。 前者は計算結果として言い切れるが、後者は「そう」を付けざるを得ない。うちで回したのは1つのユースケースだけで、組織全体の投資判断を裏付けるほどの運用実績はまだ無い。
おわりに
やってみて分かったことを3つにまとめる。
- ローカルLLMの価値は、速度でも価格でもなく制約のほうにある。 外に出せないという条件が先にあるとき、初めて土俵が成立する。逆にその条件がないなら、素直にAPIを使ったほうが速くて安い。個人にこの条件はめったに立たず、企業には立つ。だから同じ198万円が、片方では微妙で片方では検討可能になる
- ベンチマークは、自分のワークロードの形を決めてから読む。 コードレビューは入力が大きく出力が小さい仕事なので、効いたのはデコード速度ではなくプレフィルとTTFTと集約スループットだった。体感が数字と食い違って見えたのは、見ていた数字が用途と関係なかったからである。なお今回はOSバンドル更新を挟んでいるので、モデルの差とプラットフォームの差は切り分けられていない。比較を主張するなら、更新後の環境で旧モデルを立て直すところまでやる必要がある
- マルチモーダルが業務適用の幅を広げる。 スクショや図をそのまま添えられるのは、テキストに書き起こす手間を省く以上の効果がある。制約(ローカルパス不可、思考が枠を食う)を先に把握しておけば普通に実用になる
構築のハードルは、リポジトリ2のおかげでほぼ消えている。チューニングもしていない。残っているハードルは金と結線だけである。
そのうえで、あらためて書いておく。個人が安く済ませたいなら、これは微妙である。前回の計算はそのまま生きている。外部にソースを出せない制約を抱えた企業なら、有用そうだと思う。前者は言い切れて、後者に「そう」が付くのは、まだ1つのユースケースしか回していないからだ。
参考リンク
- 【計算した】DeepSeek V4 Flashをローカルで動かしても元が取れない──DGX Spark 2台198万円とAPI課金を比べた https://yurudeep.com/posts/deeplearning/2026/20260802/
- MiaAI-Lab/Qwen3.8-Flash-Next-Dual-DGX-Sparks(2台構成レシピ・実測値) https://github.com/MiaAI-Lab/Qwen3.8-Flash-Next-Dual-DGX-Sparks
- MiaAI-Lab/DeepSeek-v4-Flash-DSpark-2x-DGX-Spark(比較対象の2台構成レシピ・実測値) https://github.com/MiaAI-Lab/DeepSeek-v4-Flash-DSpark-2x-DGX-Spark
- RadixArk/Qwen3.8-Flash-Next-NVFP4(今回動かしたチェックポイント) https://huggingface.co/RadixArk/Qwen3.8-Flash-Next-NVFP4
- NVIDIA DGX Spark 製品ページ https://www.nvidia.com/en-us/products/workstations/dgx-spark/