DGX Spark でローカル LLM を速くするには?Ollama・SGLang・投機的デコードを実測比較

DGX Spark でローカル LLM を速くするには、生成を速くする投機的デコード(先読み)の設定が最も効き、コーディングエージェントで測った結果では、先読みを長くするだけで所要時間が約 2 割縮みました。当社は DGX Spark 相当機 1 台で自作のコーディングエージェントの LLM を動かし、Ollama・llama-server・SGLang と先読みの組み合わせ 8 通りで同じ実タスクを実行して、所要時間と回答の正確さを比べました。量子化 3 種の比較と、長い会話でプロンプトキャッシュ(入力の計算結果を再利用する仕組み)がエラーなしに破棄される問題の直し方も測っています。
DGX Spark や Ollama でローカル LLM をエージェントに使っている開発者が、どのエンジンと設定を選べばよいかを数字で判断できるようにまとめました。
測定環境:DGX Spark 相当機 1 台で何をどう測ったのか?

測定は、DGX Spark 相当機 1 台に推論エンジンを 1 つずつ載せ、同じ読み取りのタスクを各構成で 3 回以上実行して、所要時間と回答の正確さを見る形で行いました。
機材・エンジン・モデル
| 項目 | 値 |
|---|---|
| 機材 | MSI EdgeXpert MS-C931(NVIDIA GB10、DGX Spark 相当) |
| メモリ | 128GB の統合メモリ(OS から見えるのは 121GiB) |
| 帯域(公称) | 273GB/s |
| OS など | Ubuntu 24.04.5(aarch64)、ドライバー 580.178.04、CUDA 13.0 |
| エンジン | Ollama 0.40.4、llama.cpp b11617(llama-server)、SGLang v0.5.21 |
モデルは、Qwen 系の Dense(密)モデルの 27B と、MoE の Flash-Next の 2 系統です。Dense モデルは 1 トークンごとにすべての重みを計算し、MoE は一部の専門家の重みだけを計算します。
| 略称 | 中身 | 大きさ |
|---|---|---|
| 27B Q8_0 | 27B の GGUF | 29.0GB |
| 27B Q6_K・Q4_K_M | 同じ配布元の GGUF(量子化の比較用) | 22.4GB・16.8GB |
| 27B NVFP4 | 27B を NVFP4 中心に量子化(MTP の層は BF16) | 約 22GB |
| Flash-Next | MoE の GGUF(UD-IQ4_XS) | GPU 側 約 67GB |
重みはどれもコミュニティが公開した量子化モデルです。27B は Ollama と llama-server が Q8_0、SGLang が NVFP4 なので、エンジンの比較には量子化の差も混ざっています。
実タスクと測り方
タスクは、手元の Web アプリのサーバー側のファイル 1 つから、API の一覧を作らせるものです。対象は if 文で書かれた 47 本のルーターで、エージェントはファイルの検索・読み込みのツールを呼んで中身を確かめ、「メソッド・パス・行番号」の表を書きます。LLM への 1 回のリクエストを 1 ステップと数えると、1 回のタスクは約 10 ステップ(ツール呼び出し 9 回と最後の回答)です。
回答は正解の表(行番号とメソッド)と機械的に照合し、各構成で 3 回、毎回新しい会話で同じ依頼を出しました。反復ループ(26 ステップ以上)やエラーで止まらずに完了した試行は、どれも 8〜16 ステップで、以下では「完了した試行」と呼びます。文脈長は 262,144、思考モードはなしで、サンプリング設定は全エンジンで同じです。
このエージェントは Ollama の API でしか LLM にリクエストを送れないので、OpenAI 互換の llama-server と SGLang の前には、API を変換する約 300 行のプロキシを置きました。27B のチャットテンプレートは system の発言を先頭の 1 つしか受け付けない(2 つ以上で HTTP 500)ため、プロキシで 1 つに結合しています。
先読みの長さを比べるときは、ステップ数のばらつきで差が埋もれないよう、完了した試行 1 回のリクエスト 11 件を記録し、各設定に 3 回ずつ再送しました。
エージェントでは生成の速さが時間を決める
エージェントは毎回、それまでの会話をすべて送り直します。入力の再利用(プレフィックスキャッシュ)が効けば、新しく計算する入力は少しで済みます。これまでの作業記録 44 件では、送った入力 2,117 万トークンのうち新しい分は 9.3% で、モデルの時間の約 75% は生成でした。今回のタスクでも、入力の速さ(600〜1,070 tok/s)の差は 1 回あたり十数秒ですが、生成の速さ(20〜38 tok/s)の差は数十秒以上に広がります。
生成を速くする手段が投機的デコードで、この記事では「先読み」と呼びます。数トークン先まで下書きし、本体のモデルが 1 回の計算でまとめて検証する仕組みで、採用されなかった下書きは捨てるので出力の分布は変わりません。今回は、重みに入っている予測用の層を使う MTP(Multi-Token Prediction)と、別の下書きモデルを使う DSpark・DFlash2 を試しました。
どのエンジンと先読みが速かったのか?

速さでは SGLang の MTP(先読み 8 トークン)と DFlash2 が並んで一番で、実タスクを各 6 回実行し直すと、完了した試行の所要時間の中央値は 60.2 秒と 62.5 秒でした。Ollama の 27B も、先読みを 4 から 12 に延ばすだけで、109.6 秒から 85.5 秒に縮みました。
8 通りの組み合わせの比較
先読みの長さを元の設定のまま(MTP は 4、DSpark・DFlash2 は 8)、各構成で 3 回実行した結果です。
| エンジン・先読み | モデル | 完了した試行の経過 | ループ・停止 | 生成 tok/s | 最後の回答 | 表が正しい |
|---|---|---|---|---|---|---|
| SGLang・DFlash2 | 27B NVFP4 | 55.1・70.4 秒 | 1 | 38.3 | 24.0 秒 | 2/2 |
| SGLang・MTP | 27B NVFP4 | 70.9・73.5 秒 | 1 | 34.8 | 33.6 秒 | 2/3 |
| SGLang・DSpark | 27B NVFP4 | 85.7 秒 | 2 | 28.6 | 38.9 秒 | 2/2 |
| Ollama・MTP | 27B Q8_0 | 102.6〜119.2 秒 | 0 | 23.6 | 45.7 秒 | 3/3 |
| llama-server | Flash-Next | 103.3・137.1 秒 | 1 | 24.8 | 49.8 秒 | 2/2 |
| Ollama | Flash-Next | 112.8・115.6 秒 | 1 | 20.1 | 65.2 秒 | 3/3 |
| llama-server・MTP | 27B Q8_0 | 114.3・147.1 秒 | 1 | 23.3 | 55.9 秒 | 2/2 |
| llama-server・先読みなし | 27B Q8_0 | 249.1〜295.0 秒 | 0 | 7.9 | 153.7 秒 | 2/3 |
生成 tok/s は 3 回すべての中央値、最後の回答は完了した試行の中央値です。「ループ・停止」は 3 回のうち反復ループやエラーで止まった回数、「表が正しい」は表を出した回のうち 47 本すべての行番号とメソッドが合った回の数です。ループに入っても最後に表を出した回があるため、分母は構成によって違います。前の節の 60.2 秒・62.5 秒は、この表とは別に各 6 回実行し直した値です。
先読みなしなら MoE の Flash-Next が速く、1 トークンで読む重みが約 5.8GB と小さいため、帯域からの上限は約 47 tok/s あります。Dense の 27B Q8_0 は毎回約 28.6GB を読むので上限は約 9.5 tok/s(実測 7.9)ですが、MTP がよく当たり、先読みを付けると逆転しました。
先読みを長くすると所要時間が約 2 割縮む
MTP は上限の 4 トークンにほぼ張り付いて当たっていたので、先読みを長くして、記録したリクエスト 11 件を再送しました。SGLang の MTP では、長い回答の速さが 8 トークンで頭打ちになりました。ツール呼び出しは 12 でも伸びましたが、1 回の生成時間では約 2 秒の差です。
| SGLang の設定 | ツール呼び出し | 長い回答 | 1 回の生成時間(計算) |
|---|---|---|---|
| MTP 4 | 36.0 tok/s | 31.9 tok/s | 約 52 秒 |
| MTP 8 | 54.3 tok/s | 44.5 tok/s | 約 37 秒 |
| MTP 12 | 61.5 tok/s | 43.7 tok/s | 約 35 秒 |
| DFlash2 | 31.4 tok/s | 46.8 tok/s | 約 45 秒 |
1 回の生成時間は、ツール呼び出し約 700 トークンと長い回答約 1,050 トークンを、それぞれの速さで割って足した計算値です。
Ollama では、先読みの長さはモデルの設定の draft_num_predict で決まります。次の Modelfile で別名を作れば、重みは元のモデルと共有されます。
FROM <元の 27B のモデル名> PARAMETER draft_num_predict 12
4 から 12 にすると、受け入れ率は 0.886 から 0.619 に下がる一方、1 回の検証で進むトークン数の平均は 4.70 から 9.95 に増え、ツール呼び出しは 25.9 から 37.4 tok/s になりました。実タスクでは、SGLang の MTP 8 が約 75 秒(MTP 4 の 6 回)から 60.2 秒に、Ollama の先読み 12 が 109.6 秒から 85.5 秒(8 回)に縮んでいます。MTP 8 と DFlash2(62.5 秒)はほぼ同じですが、MTP は下書きモデルを別に持たずに済みます。
SGLang の先読みの自動調整(adaptive)と連続デコードは、今回の測定では改善が見られませんでした。自動調整は固定の 8 より遅く、連続デコードは 1〜2% の差でした。
今回の日本語の文章生成で、先読みが効かなかったのはなぜか?

ツール呼び出しのように形の決まった出力では先読みがよく当たる一方、今回試した日本語の文章(3 種類の依頼・計 6 件)ではほとんど当たらず、先読みなしと同じ速さでした。そこで、量子化で日本語の文章生成をどこまで速くできるかも測りました。
出力の種類ごとの先読みの当たり
Ollama の 27B(Q8_0・先読み 12)で、内部の llama-server のログから、出力の長さ別に数えました(試験と普段の利用を合わせた 408 件)。日本語の文章は、600 字程度を見出しなしで書かせる依頼 3 種類を各 2 回です。
| 出力 | 件数 | 生成 tok/s | 受け入れ率 |
|---|---|---|---|
| 120 トークン未満(ツール呼び出しなど) | 383 | 35.6 | 0.83 |
| 120〜500 トークン | 5 | 25.1 | 0.47 |
| 500 トークン以上(表を含む回答) | 20 | 24.1 | 0.52 |
| 日本語の文章 | 6 | 7.7 | 0.12 |
日本語の文章の行は、6 件の生成トークンの合計を生成時間の合計で割った速さと、6 件分の受け入れた下書きトークンを作った下書きトークンで割った受け入れ率(2,331 / 19,272)です。それ以外の行は中央値です。7.7 tok/s は、先読みなしの 7.9 tok/s と変わりません。ツール呼び出しや表は形が決まっているので下書きが当たり、自由に書く文章は当たりにくい、という差だとみています。自信のない先読みを打ち切る llama-server の --spec-draft-p-min は Ollama のモデルの設定から渡せないため、試していません。
Q8_0・Q6_K・Q4_K_M の速さと回答の正確さ
27B Q8_0 と同じ配布元の Q6_K・Q4_K_M に先読み 12 を付けて比べました。Q4_K_M にすると、実タスクの所要時間は 0.88 倍に縮み、日本語の文章の生成速度は 1.39 倍になりました。
| Q8_0 | Q6_K | Q4_K_M | |
|---|---|---|---|
| 再送・ツール呼び出し | 37.3 tok/s | 41.2(1.10 倍) | 48.6(1.30 倍) |
| 日本語の文章 | 7.7 tok/s | 8.7(1.13 倍) | 10.7(1.39 倍) |
| 完了した試行の経過(中央値) | 88.0 秒 | 81.1 秒 | 77.5 秒 |
| 表が正しかった回(6 回中) | 4 | 5 | 5 |
| メモリ使用量(載せた直後 → 実タスク 6 回の後) | 47 → 65GiB | 41 → 58GiB | 38 → 53GiB |
メモリ使用量はシステム全体の値です。速度の伸びは 1.1〜1.4 倍で、重みの大きさの比から見積もった上限(約 1.3 倍・1.7 倍)より小さく、理由は測っていません。一番伸びた日本語の文章は、先読みが当たらない分、重みを読む量の差がそのまま効いたとみています。
47/47 でなかった回は、Q8_0 が 2 回(表の代わりに別のファイルの中身を書いた回と、26 ステップの反復ループの末に 17/47 の回)、Q6_K が 1 回(24 ステップで 13/47)、Q4_K_M が 1 回(12 ステップ以内で完了し、1 行だけ違う 46/47)でした。6 回ずつの比較なので、量子化の差かばらつきかは言えません。
量子化を替える前には、GGUF に MTP の層があるかを確かめます。配布元から本体の先頭 12MB(テンソルの一覧)だけを取り寄せ、層の名前(このモデルでは blk.64.nextn.*)があるかを見れば分かります。
長い会話でプロンプトキャッシュが破棄される問題とは?

Ollama で長い作業の会話を続けると、短い別のリクエストをはさんだだけで会話のプロンプトキャッシュが破棄され、続きの依頼のたびに、これまでの入力をすべて計算し直していました。
なぜ破棄されるのか:線形注意のチェックポイントで 8GiB を超える
このエージェントは、依頼ごとに、作業や Web 検索が要るかを決める短い「判断のリクエスト」を、会話の履歴なしで先に送ります。
Ollama の 27B は、内部の llama-server をスロット 1 つ(-np 1。同時に処理する会話の枠)で動かしています。別のリクエストが来ると、llama-server はスロットにある会話の状態を RAM 上のプロンプトキャッシュへ退避し、次のリクエストで一番合うものを戻します。このプロンプトキャッシュの上限が、既定で 8GiB です。
線形注意を混ぜた 27B では、途中から計算し直せるように、線形注意の状態をチェックポイントとして保存します。1 つ約 150MiB で、1 つの会話に最大 32 個たまり、1 ステップごとにリクエストを送る作業エージェントでは、数十ステップで上限に届きます。
| 会話の長さ | チェックポイント | 退避する状態の大きさ |
|---|---|---|
| 28,591 トークン | 31 個 | 7.6GiB |
| 約 4.2 万トークン | — | 10.9GiB(破棄された) |
| 約 8.1 万トークン | — | 15.5GiB(同) |
大きさは llama-server のログの MiB を GiB に換算した値です。8GiB を超えた状態は、ログに exceeds cache size limit と出るだけで、エラーを返さずに破棄されます。
実測と直し方:続きの入力処理が 62.6 秒から 3.75 秒に
作業と同じ形で会話を伸ばす台本(1 ステップごとにソースを 6,000 バイト足し、30 ステップで約 4.8 万トークン)の後に、判断のリクエストと同じ形の短いリクエストを 1 本はさみ、会話の続きを送りました。
| プロンプトキャッシュの上限 | 続きで再利用した入力 | 入力の処理時間 | はさんだリクエスト |
|---|---|---|---|
| 8GiB(既定) | 0 / 49,626 トークン | 62.6 秒 | 0.96 秒 |
| 16GiB | 47,972 / 49,626 トークン | 3.75 秒 | 4.54 秒 |
代わりに、はさんだ短いリクエストは約 1 秒から 4.5 秒に延びました。約 11GiB の状態を RAM に退避する時間とみていますが、測ってはいません。
上限は llama-server の --cache-ram で決まりますが、Ollama のモデルの設定からは渡せません。llama-server は多くの引数を環境変数でも受け取り(LLAMA_ARG_CACHE_RAM)、Ollama は自分の環境変数をそのまま渡すので、サービスの設定に書けば効きます。
1# /etc/systemd/system/ollama.service.d/override.conf(daemon-reload と restart をする)
2[Service]
3Environment="LLAMA_ARG_CACHE_RAM=16384"上限は 16GiB にしました。約 8 万トークンの会話まで入る値です。スロットを 2 つにする OLLAMA_NUM_PARALLEL=2 も試しましたが、Ollama 0.40.4 はこの 27B の型(qwen35)で並列に対応しておらず、-np 1 のままでした。
エージェントの反復ループはどう止めたのか?

同じファイルを少しずつ範囲を変えて読み直す反復ループは、エンジンや量子化によらず約 3 割の試行で起きました。エージェントの側で読み直しの回数を数え、注意を添えると、ループから抜けられました。
反復ループはエンジンを替えても約 3 割で起きる
反復ループ(26 ステップ以上)かエラーで止まった試行の割合は、エンジンでほぼ同じでした。SGLang の 27B NVFP4 は 24 回中 7 回(29%)、Ollama の 27B Q8_0 は 11 回中 3 回(27%)、Flash-Next は 6 回中 2 回です。
ループの多くは、対象のファイルを read_file で少しずつ範囲を変えて読み直すものでした。150 回呼んで引数の違うものが 25 通り、という回もあり、「同じ呼び出しが続いたら止める」検知では止まりません。エンジンを替えても解決しないので、エージェントの側で手当てしました。
同じファイルの読み直しを数えて止める仕組み
この仕組みは、read_file と、そのファイルを指定した検索を、範囲や検索語が違っても同じファイルなら同じ相手として数えます(grep・sed などの読むだけのコマンドも数えます)。
- 12 回目からは、読み込みの結果に「このファイルを 12 回読みました。ここまでの結果で回答を書いてください」という趣旨の注意を添えます。
- 20 回目からは読み込みを実行せず、次のステップをツールなしで送って回答を書かせます。
しきい値は、完了した試行(34 件)での同じファイルの読み込みが多くて 10 回(1 回の応答に 9 件まとめた回を除く)、ループした試行が 20〜150 回だったことから決めました。この仕組みの無い版で 150 ステップまで回った会話を 30 ステップ目から引き継いで 3 回実行すると、2 回は 12 回目の注意の次のステップで回答に移り、1 回は注意の前に回答しました。3 回とも表は 47/47 です。
止めた後にも落とし穴がありました。Ollama は tools を送らないリクエストでも、モデルが書いたツール呼び出しを返します。そこで、その要求は実行せず「ツールは使えません」と添えてもう 1 回だけ送るようにしました。試験では、止めた 7 回のうち 5 回がツールを要求し、5 回とも次のステップで回答しました。
どれを選ぶべきか?用途別の選び方と落とし穴

速さを最優先するなら SGLang の MTP 8、Ollama で済ませたいなら 27B の先読み 12 が、実測からの答えです。どちらを選んでも、反復ループを止める仕組みと、プロンプトキャッシュの設定は別に要ります。
用途別の選び方と、手元の既定
- 速さを最優先するなら、SGLang の MTP 8。 完了した試行の所要時間は、Ollama の 27B(先読み 4)の約 0.55 倍でした。起動に約 3 分かかり、同じ機械で Ollama と同時に載せない運用が要ります。線形注意を混ぜたモデルでは
--mamba-radix-cache-strategy extra_bufferを付け、先読みと入力の再利用を両立させます。 - Ollama で済ませたいなら、27B の先読みを 12 に。 設定 1 つで所要時間が約 2 割縮み、SGLang の MTP 8 より 1 回あたり 25 秒ほど長い程度です。
- 日本語の文章を多く書かせるなら、Q4_K_M。 実タスクの所要時間が約 1 割縮み、日本語の文章の生成速度は約 1.4 倍になります。
- 長い作業の会話を続けるなら、プロンプトキャッシュの上限を上げる。
手元の既定は、Ollama の 27B Q8_0(先読み 12)、プロンプトキャッシュの上限は 16GiB にしました。SGLang より遅いものの、別のエンジンを常駐させずに済むからです。Q4_K_M は、所要時間の短縮が 1 割ほどで、回答の正確さを 6 回ずつしか見ていないので、モデルの一覧に足して使いながら比べています。
落とし穴と、この測定の限界
測定中に踏んだ落とし穴です。
- 同じ機械で Ollama と別のエンジンを動かしたとき、エージェントの読み込みのリクエストで Ollama が Flash-Next(約 67GB)を載せ始め、llama-server がメモリ不足で強制終了されました。
- SGLang の起動例によく出てくる
fa3は sm_90 専用で、GB10 では使えません。--attention-backend flashinferを使いました。 - Ollama のモデルの取り込みが 91% で 25 分以上止まったときは、やり直すと途中から再開して数秒で終わりました。
測定の限界もあります。タスクは読むだけのもの 1 種類で、試行は各構成 3〜8 回です。27B はエンジンごとに量子化が違い、SGLang の入力の速さは他と測り方が違います。16GiB にしても、約 8 万トークンを超える会話の状態は入りません。
よくある質問

DGX Spark や Ollama でエージェントを動かすときに出やすい疑問に、今回の測定で分かった範囲で答えます。
Q1: DGX Spark の Ollama で投機的デコード(MTP)を速くするには?
先読みの長さを延ばします。Ollama から先読みに渡せる設定は draft_num_predict だけで、今回の実タスクでは、4 から 12 にすると所要時間が約 2 割縮みました。量子化を替えるときは、GGUF に MTP の層が入っているかを先に確かめてください。
Q2: Ollama で長い会話の続きだけ遅くなるのはなぜ?
プロンプトキャッシュが上限(既定 8GiB)を超え、エラーを返さずに破棄されている可能性があります。llama-server のログに exceeds cache size limit が出ていれば、それが原因です。LLAMA_ARG_CACHE_RAM で上限を上げると、4.8 万トークンの続きの入力処理が 62.6 秒から 3.75 秒になりました。
まとめ

DGX Spark 相当機 1 台でローカル LLM をエージェントに使うなら、時間の大半を占める生成をどう速くするかが要点です。実測では、先読み(MTP)を長くするのが最も効き、SGLang の MTP 8 が最速、Ollama でも先読み 12 で所要時間が約 2 割縮みました。先読みが当たらなかった日本語の文章は、Q4_K_M で生成速度が約 1.4 倍になります。
見落としやすいのは、長い会話でプロンプトキャッシュがエラーなしに破棄される問題で、ログの exceeds cache size limit で確かめられます。反復ループはエンジンを替えても直らないので、エージェント側で読み直しを検知する仕組みと組み合わせて使ってください。ローカル LLM の選び方の全体像は、ローカル LLM / SLM 導入比較もあわせてご覧ください。
当社では、ローカル LLM やエージェントの検証・PoC も支援しています。ご相談は AI・DX 支援からお寄せください。
参考資料(執筆時点で参照): llama.cpp の RAM 上のプロンプトキャッシュ(#16391)・チェックポイント(#15293)、Ollama の Modelfile の資料、GB10 向けの SGLang の起動手順(MiaAI-Lab)。
著者・監修者
Yusuke Ishihara
13歳でMSXに触れプログラミングを開始。武蔵大学卒業後、航空会社の基幹システム開発や日本初のWindowsサーバホスティング・VPS基盤構築など、大規模システム開発に従事。 2008年にサイトエンジン株式会社を共同創業。2010年にユニモン株式会社、2025年にエニソン株式会社を設立し、業務システム・自然言語処理・プラットフォーム開発をリード。 現在は生成AI・大規模言語モデル(LLM)を活用したプロダクト開発およびAI・DX推進を手がける。


