File size: 14,653 Bytes
8e8ae7d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# Deployment Guide — MiniMax-M2.7-NVFP4-GB10-AC on 2× DGX Spark

**Purpose**: operator-focused reference for running this model on 2× NVIDIA DGX Spark (GB10) with vLLM. Covers two deployment profiles, measured numbers, and known hardware/framework quirks.

**Target hardware**: 2× DGX Spark (NVIDIA GB10, SM 12.1, 128 GB LPDDR5X unified per node), connected via QSFP56 (ConnectX-7) RoCE.

**Reference runtime**: [`eugr/spark-vllm-docker`](https://github.com/eugr/spark-vllm-docker) nightly (`ghcr.io/spark-arena/dgx-vllm-eugr-nightly-tf5`), vLLM 0.19.1rc1.dev241, Ray multi-node TP.

**Dates measured**: 2026-04-19, post DGX Spark SoC firmware upgrade to 2.148.24 (gives ~+2 GiB unified memory per node vs 2.144.9).

---

## Two deployment profiles

We tested five tuning phases and converged on two practically useful configs:

| Profile | Config | Best for |
|---|---|---|
| **Agentic** | Marlin NVFP4 MoE + ngram speculative decoding | Tool-use, code generation, chat with repeated tokens (the common case for openclaw-style agent frameworks) |
| **Throughput-stable** | Marlin NVFP4 MoE, no speculative decoding | Batch workloads, long-form novel text, scenarios where predictable per-token latency matters more than wall-clock |

**Default**: [`run_vllm.sh`](./run_vllm.sh) ships with the Agentic profile active. The Throughput-stable profile is documented inline in the script as a commented-out block you can swap in.

---

## Why Marlin MoE on GB10 (both profiles)

GB10's compute capability is SM 12.1. As of April 2026, vLLM's FlashInfer CUTLASS NVFP4 MoE path emits a warning on this GPU:

```
Your GPU does not have native support for FP4 computation but FP4 quantization is being used.
Weight-only FP4 compression will be used leveraging the Marlin kernel.
```

In practice this means the fastest current path on SM 12.1 is the **Marlin** NVFP4 kernel (4-bit weights, FP16 compute, single highly-tuned GEMM per expert). Enabled via these environment variables:

```bash
export VLLM_NVFP4_GEMM_BACKEND=marlin
export VLLM_USE_FLASHINFER_MOE_FP4=0
export VLLM_TEST_FORCE_FP8_MARLIN=1
export VLLM_MARLIN_USE_ATOMIC_ADD=1
```

The `eugr/spark-vllm-docker` nightly image ships the patched kernels that make this reliable. Community documentation of this unlock: [Avarok-Cybersecurity/dgx-vllm — `NVFP4_BREAKTHROUGH_DGX_SPARK.md`](https://github.com/Avarok-Cybersecurity/dgx-vllm/blob/main/NVFP4_BREAKTHROUGH_DGX_SPARK.md) and [NVIDIA DGX Spark forums](https://forums.developer.nvidia.com/c/dgx-spark).

---

## Measured throughput — `llama-benchy`

Runs: [`llama-benchy`](https://github.com/mostlygeek/llama-bench) v0.3.3, `--runs 3 --no-cache`, single client (concurrency=1), warm model, prefix caching disabled for the benchmark.

### Throughput-stable profile (Marlin, no speculation)

| PP (tok) | TG (tok) | Prefill (tok/s) | Decode (tok/s) | TTFT (ms) |
|---:|---:|---:|---:|---:|
| 512 | 128 | 1,128 | **35.44** | 454 |
| 512 | 256 | 1,248 | **35.86** | 410 |
| 1024 | 128 | 2,049 | 35.03 | 500 |
| 1024 | 256 | 2,132 | 34.50 | 480 |
| 4096 | 128 | 2,817 | 33.76 | 1,454 |
| 4096 | 256 | 3,314 | 33.45 | 1,236 |

API latency (warm): 1.50 ms. Decode peak: **35.86 tok/s** at short prompt length.

### Agentic profile (Marlin + ngram speculative decoding, `num_speculative_tokens=5`)

| PP (tok) | TG (tok) | Prefill (tok/s) | Decode (tok/s) | TTFT (ms) |
|---:|---:|---:|---:|---:|
| 512 | 128 | 1,140 | 27.87 | 449 |
| 512 | 256 | 1,264 | 26.55 | 405 |
| 1024 | 128 | 2,018 | 26.36 | 508 |
| 1024 | 256 | 2,141 | 26.28 | 478 |
| 4096 | 128 | 2,647 | 25.88 | 1,548 |
| 4096 | 256 | 2,888 | 24.88 | 1,419 |

API latency (warm): 1.07 ms. Decode on llama-benchy text: **regresses** vs the Throughput-stable profile.

**Why the regression on this benchmark**: `llama-benchy` generates synthetic prompts with low n-gram repetition. When speculative decoding's acceptance rate is near zero, the cost of running the ngram matcher + parallel verification exceeds the savings. This is the expected "floor" behavior of ngram speculation — it looks like a regression on a synthetic benchmark, and looks very different on real workloads (see next section).

---

## Measured behavior — agentic prompt set (12 hand-crafted prompts)

This is **not a standardized benchmark**. It is 12 prompts we wrote across code-generation, tool-calling, and short chat — designed to approximate the actual traffic an agent framework sends this model. Comparison is: same prompts, same sampling (temp=1.0, top_p=0.95, top_k=40, min_p=0.01 where honored), three profiles:

- **GB10**: the sibling general-chat-calibrated variant, [`saricles/MiniMax-M2.7-NVFP4-GB10`](https://huggingface.co/saricles/MiniMax-M2.7-NVFP4-GB10), Phase 0 config.
- **AC / Throughput-stable**: this repo, Phase 2 config (reported in prior bench; pre-Marlin).
- **AC / Agentic**: this repo, Phase 5 config (current default in `run_vllm.sh`).

### Per-task wall-clock time (lower is better)

| Task | GB10 (s) | AC Throughput-stable (s) | **AC Agentic (s)** |
|---|---:|---:|---:|
| code-01 (HumanEval-style) | 57.3 | 36.3 | **29.0** |
| code-02 (MBPP-style) | 27.6 | 41.0 | **19.3** |
| code-03 (debug) | 41.7 | 40.7 | **32.3** |
| code-04 (async pattern) | 42.7 | 40.3 | **21.2** |
| tool-01 (weather) | 5.7 | 2.3 | **1.8** |
| tool-02 (multi-step booking) | 15.6 | 3.4 | 7.6 ⁎ |
| tool-03 (parallel calls) | 7.1 | 5.0 | **3.3** |
| tool-04 (don't-call-tool trap) | 30.2 | 2.1 | **1.1** |
| chat-01 (reasoning) | 8.2 | 15.5 | **9.9** |
| chat-02 (summarization) | 21.0 | 15.5 | **9.4** |
| chat-03 (creative haiku) | 38.1 | 39.8 | **19.3** |
| chat-04 (instruction following) | 7.9 | 8.9 | 8.8 |
| **Totals** | **303.2** | **250.8** | **162.7** |

**Totals translate to wall-clock time required to complete all 12 tasks serially**, one request at a time. AC Agentic finishes the full set in ~2:43 vs GB10 at 5:03 and AC Throughput-stable at 4:11.

⁎ `tool-02` appears to "regress" for AC Agentic — but the model generated 226 completion tokens there vs 81 on AC Throughput-stable. Per-token decode was still +26% faster (29.8 vs 23.7 tok/s); the model simply chose to say more in response to the same prompt, likely because `min_p=0.01` is silently dropped under speculative decoding (see Caveats).

### Peak decode on the prompt set

- **AC Agentic peak decode: 48.34 tok/s** on `code-04` (async-pattern code generation).
- **AC Agentic average decode: 36.44 tok/s** across all 12 prompts.
- **AC Throughput-stable average decode: 25.20 tok/s** across the same 12 prompts.

These are realized token rates observed by the client over the wire, not kernel-internal numbers.

---

## When to pick which profile

**Pick Agentic (ngram speculation on) when:**
- Traffic is tool-call-heavy or code-generation-heavy
- System prompts and prior turns contain structured patterns (tool names, file paths, JSON keys, variable names) that get repeated in responses
- You care about wall-clock time per agent task, not per-token rate on synthetic benchmarks

**Pick Throughput-stable (ngram off) when:**
- Traffic is long-form novel text (creative writing without repetition, arbitrary Q&A)
- You're publishing tok/s benchmarks against other models on synthetic text
- You want predictable per-token latency irrespective of prompt content
- You need `min_p` sampling to be respected exactly (see Caveats)

**Rule of thumb**: if the model is backing an agent framework, pick Agentic. If it's backing a public chat UI with no tool layer, pick Throughput-stable. You can run both and A/B on your own traffic.

---

## Caveats, observations, known quirks

### 1. GB10 (SM 12.1) does not currently expose native FP4 math to vLLM's Marlin backend

When the Marlin NVFP4 kernel initializes, it emits this warning on DGX Spark:

```
Your GPU does not have native support for FP4 computation but FP4 quantization is being used.
Weight-only FP4 compression will be used leveraging the Marlin kernel.
This may degrade performance for compute-heavy workloads.
```

In practice on GB10 this is the *faster* path — the alternative (FlashInfer CUTLASS MoE FP4) has its own SM 12.1 maturity issues. Upstream work that may change this: [vLLM PR #30906 (NVFP4 W4A16 MoE via Marlin)](https://github.com/vllm-project/vllm/pull/30906) and [vLLM issue #31085 (SM120 native NVFP4 MoE kernels)](https://github.com/vllm-project/vllm/issues/31085).

### 2. `cudagraph_mode: none` is mandatory on multi-node Ray TP

We tested `cudagraph_mode: PIECEWISE` on this exact topology. Capture completed cleanly (the old MiniMax MoE deadlock bug is fixed in this build), but decode **regressed 12-20%** across every prompt/generation size. Root cause: PIECEWISE captures op chunks; on multi-node TP each chunk boundary requires a cross-node sync over QSFP56, and that sync cost exceeds the launch-overhead savings. Conclusion: `cudagraph_mode: none` is the correct setting on dual-Spark Ray. Single-Spark or non-Ray backends may behave differently.

### 3. Under speculative decoding, `min_p` is silently disabled

vLLM logs this explicitly at startup:

```
WARNING: min_p and logit_bias parameters won't work with speculative decoding.
```

MiniMax's recommended sampling includes `min_p=0.01`. When the Agentic profile is active, that parameter has no effect; the sampler falls back to `temperature + top_p + top_k`. Observed consequence: response length distribution shifts slightly (see `tool-02` note above). Output quality in our testing remained within acceptable bounds, but this is worth a side-by-side quality check if your use case is sensitive to `min_p`.

### 4. Async scheduling is disabled on Ray-backed multi-node TP

From vLLM's log: `Async scheduling will be disabled because it is not supported with the 'ray' distributed executor backend.` This is a ~5-10% throughput tax and is structural to cross-node TP on this build. Not user-configurable.

### 5. Expert parallelism (EP) is not recommended on 2-Spark

MiniMax's own documentation recommends `--enable_expert_parallel` for 8-GPU deployments. That advice does not port to 2 nodes. On dual-Spark the forum has observed uneven memory distribution and per-token latency regression when EP is enabled over QSFP56. Stick with `tensor-parallel-size=2`.

### 6. NVFP4 KV cache is not yet supported in vLLM

NVIDIA has documented this capability ([blog post](https://developer.nvidia.com/blog/optimizing-inference-for-long-context-and-large-batch-sizes-with-nvfp4-kv-cache/)), but it's currently TensorRT-LLM only. vLLM tracking issue: [#32220](https://github.com/vllm-project/vllm/issues/32220). Use `--kv-cache-dtype fp8_e4m3` (what the provided `run_vllm.sh` sets).

---

## Reference configuration

### Environment variables (tuned set — both profiles)

```bash
# Model loading
export SAFETENSORS_FAST_GPU=1
export OMP_NUM_THREADS=8
export TORCHINDUCTOR_MAX_AUTOTUNE=0

# Forum-validated Blackwell/SM121 env
export VLLM_ALLOW_LONG_MAX_MODEL_LEN=1
export VLLM_FLOAT32_MATMUL_PRECISION=high
export VLLM_FLASHINFER_MOE_BACKEND=throughput
export VLLM_MEMORY_PROFILER_ESTIMATE_CUDAGRAPHS=1

# Marlin NVFP4 MoE path (bypasses SM121 FlashInfer CUTLASS MoE maturity issues)
export VLLM_NVFP4_GEMM_BACKEND=marlin
export VLLM_USE_FLASHINFER_MOE_FP4=0
export VLLM_TEST_FORCE_FP8_MARLIN=1
export VLLM_MARLIN_USE_ATOMIC_ADD=1
```

### vLLM serve CLI (both profiles, differ only in `--speculative-config`)

```bash
vllm serve /models/MiniMax-M2.7-NVFP4-GB10-AC \
  --host 0.0.0.0 --port 30000 \
  --served-model-name minimax-m2.7-ac \
  --tensor-parallel-size 2 \
  --distributed-executor-backend ray \
  --gpu-memory-utilization 0.88 \
  --max-model-len 196608 \
  --max-num-seqs 12 \
  --max-num-batched-tokens 32768 \
  --kv-cache-dtype fp8_e4m3 \
  --attention-backend flashinfer \
  --attention-config.use_trtllm_attention=0 \
  --enable-prefix-caching \
  --enable-chunked-prefill \
  --trust-remote-code \
  --enable-auto-tool-choice --tool-call-parser minimax_m2 \
  --reasoning-parser minimax_m2_append_think \
  --compilation-config '{"cudagraph_mode":"none","inductor_compile_config":{"combo_kernels":false,"benchmark_combo_kernel":false,"max_autotune":false,"max_autotune_gemm":false}}' \
  # Agentic profile only — add this line:
  --speculative-config '{"method":"ngram","num_speculative_tokens":5,"prompt_lookup_max":4,"prompt_lookup_min":2}'
```

### Client-side coordination

- `max_tokens` per request: **≥ 16384**. The OpenAI SDK default of 4096 silently truncates tool-call JSON mid-string in MoE models and looks like "the model forgot how to call tools." Bump every client that hits this endpoint.
- Sampling (when not using Agentic speculative path): `temperature=1.0, top_p=0.95, top_k=40, min_p=0.01`.

---

## References and sources

**Forum threads (primary signal for DGX Spark-specific behavior)**:
- [MiniMax M2.7 NVFP4 Recipe & Benchmarks (NVIDIA DGX Spark forum)](https://forums.developer.nvidia.com/t/minimax-m2-7-nfvp4-recipe-benchmarks/366324)
- [Multi-node DGX Spark Cluster benchmarks](https://forums.developer.nvidia.com/t/multi-node-dgx-spark-cluster-4x-k3s-sglang-vllm-connectx-7-sr-iov-full-benchmark-matrix/365555)
- [DGX Spark / GB10 category (landing)](https://forums.developer.nvidia.com/c/dgx-spark)

**Runtime + patches**:
- [Avarok-Cybersecurity/dgx-vllm — NVFP4 breakthrough](https://github.com/Avarok-Cybersecurity/dgx-vllm/blob/main/NVFP4_BREAKTHROUGH_DGX_SPARK.md)
- [eugr/spark-vllm-docker](https://github.com/eugr/spark-vllm-docker)

**vLLM kernel path / issue trackers**:
- [PR #30906 — NVFP4 W4A16 MoE via Marlin](https://github.com/vllm-project/vllm/pull/30906)
- [Issue #31085 — SM120/121 native NVFP4 MoE](https://github.com/vllm-project/vllm/issues/31085)
- [Issue #32220 — NVFP4 KV cache request](https://github.com/vllm-project/vllm/issues/32220)
- [Issue #38971 — NVFP4 MoE backend override](https://github.com/vllm-project/vllm/issues/38971)

**MiniMax official**:
- [MiniMax-M2.7 base model](https://huggingface.co/MiniMaxAI/MiniMax-M2.7)
- [MiniMax vLLM deploy guide](https://huggingface.co/MiniMaxAI/MiniMax-M2.7/blob/main/docs/vllm_deploy_guide.md)
- [vLLM Recipes: MiniMax-M2](https://docs.vllm.ai/projects/recipes/en/latest/MiniMax/MiniMax-M2.html)

**NVIDIA technical**:
- [NVFP4 KV cache blog](https://developer.nvidia.com/blog/optimizing-inference-for-long-context-and-large-batch-sizes-with-nvfp4-kv-cache/)
- [Introducing NVFP4](https://developer.nvidia.com/blog/introducing-nvfp4-for-efficient-and-accurate-low-precision-inference/)

---

## Changelog

- **2026-04-19**: Initial document. Profiles established from five-phase tuning pass on post-firmware-upgrade GB10.