Qwen3.8-Flash-Next NVFP4 2xDGX Spark config: 50t/s decode, 2,900t/s prefill
Mirrored from r/LocalLLaMA for archival readability. Support the source by reading on the original site.
After a couple of days of faffing around, here's my current config in case it helps anyone out. Some of this is also valuable for a single Spark.
Benchmarks:
Dual-Spark TP2, eager + MTP k=3, 262k context (warmed medians, exact tokenizer counts): ┌─────────┬──────────────────┬───────────────────┬─────────────────────────────────┐ │ Streams │ Decode aggregate │ Decode per-stream │ Prefill aggregate (10k prompts) │ ├─────────┼──────────────────┼───────────────────┼─────────────────────────────────┤ │ 1 │ 45.9 t/s │ 45.9 t/s │ 2,940 t/s │ ├─────────┼──────────────────┼───────────────────┼─────────────────────────────────┤ │ 4 │ 120.2 t/s │ 30.0 t/s │ 2,524 t/s │ ├─────────┼──────────────────┼───────────────────┼─────────────────────────────────┤ │ 8 │ 222.2 t/s │ 27.8 t/s │ 3,098 t/s │ ├─────────┼──────────────────┼───────────────────┼─────────────────────────────────┤ │ 13 │ 265.9 t/s │ 20.5 t/s │ 2,960 t/s │ └─────────┴──────────────────┴───────────────────┴─────────────────────────────────┘ Prefill vs prompt depth (single stream, exact tokens): ┌─────────────┬───────────┐ │ Prompt size │ Prefill │ ├─────────────┼───────────┤ │ 11k │ 2,875 t/s │ ├─────────────┼───────────┤ │ 100k │ 2,655 t/s │ ├─────────────┼───────────┤ │ 200k │ 2,463 t/s │ └─────────────┴───────────┘ AI slop below for you to read and paste into your own agent :)
Stack
- vLLM PR #53896 (
release/qwen38nextbranch) — NOT main; main doesn't have the model, and the recipe image's vLLM commit isn't in the public repo - sm_121 support is a 2-file patch (details below) — GB10 is arch 12.1 and the NVFP4 E2M1 conversion needs a software fallback
- PLE n-gram table served from internal NVMe via mmap:(48 GB,
MADV_RANDOMis essential — it's a 30× read-amplification difference on hash-scattered row lookups), with a node-local CPU-worker process doing gathers over ZMQ + pinned buffers + CUDA-IPC outputs so the gather is graph-safe and TP2-safe - TP2 across both Sparks: native venv (NOT docker — see dead ends), eager + MTP k=3
Numbers (TP2, warmed medians, count-20 structured / 3-paragraph prose)
| config | structured | prose | prefill (11k tokens) |
|---|---|---|---|
| eager + MTP k=3 | 49.7 | 34.8 | ~2,875 |
Dead ends
- PIECEWISE cudagraphs cost ~28% decode under MTP+TP2.
- Same tree, same everything, graphs on vs off: 36 vs 50 tok/s. Graphs help single-node no-MTP decode (+80%, 9→16.5), but combined with MTP at TP2 they're a straight loss — MTP already amortizes the launch overhead the graphs were eliminating. We chased a "tree-level MTP regression" for a day before realizing the config was the regression.
- Docker silently degrades NCCL to TCP sockets.
- Default containers get no IB device →
NET/Socket→ TP2 prefill at ~40% of native. You need--privileged(or IB device passthrough) AND the right HCA name — see next point. Native venv runs just get RoCE. - RoCE device names are not stable across reboots, and can differ between two identical machines
- (ours:
rocep1s0f1on one,rocep1s0f0on the other, resolved dynamically). HardcodedNCCL_IB_HCAwill silently fail on one node. Enumerate withibdev2netdev, match port-ACTIVE to your rail netdev, and verify withNCCL_DEBUG=INFOthat you seeNET/IB, notNET/Socket. - flashinfer must be 0.6.18
- on GB10 — 0.6.17 crashes the NVFP4 MoE fallback kernel.
- vLLM's MoE
shuffleInputRowsKernelhas an uninitialized-permutation OOB read that shows up as a fake CUTLASS status=7 GEMM failure — a two-line bounds guard fixes it (already in PR #53896).
The 2-file sm_121 patch (vs PR #53896 base)
CMakeLists.txt: add12.1toCUDA_SUPPORTED_ARCHS,SCALED_MM_ARCHS,CUTLASS_MOE_DATA_ARCHS,FP4_SM120_ARCHScsrc/libtorch_stable/quantization/fp4/nvfp4_utils.cuh: ~90-line software E2M1 conversion guarded by__CUDA_ARCH__ == 1210(GB10 has nocvt.rn.satfinite.e2m1x2.f32)
That's the whole delta. Build offline with the usual dependency cache; CUDA arch 121.
Startup command(s)
Head node (rank 0):
export VLLM_PLE_MMAP=1 VLLM_PLE_MMAP_WORKERS=64 VLLM_PLE_MMAP_PREWARM=1 export VLLM_PLE_MMAP_DIR=$HOME/ple-table # internal NVMe copy of the table export VLLM_PLE_CPU_OFFLOAD=1 # activates the IPC CPU-worker gather path export NCCL_SOCKET_IFNAME=<your-rail-if> # bootstrap only; NCCL finds RoCE itself export GLOO_SOCKET_IFNAME=<your-rail-if> python -m vllm.entrypoints.openai.api_server \ --model <path-to-qwen38-flashnext-nvfp4> \ --host 0.0.0.0 --port 8086 --load-format safetensors \ --tensor-parallel-size 2 --nnodes 2 --node-rank 0 \ --master-addr <head-rail-ip> --master-port 29511 \ --distributed-executor-backend mp \ --max-model-len 262144 --max-num-seqs 13 --gpu-memory-utilization 0.85 \ --no-enable-prefix-caching --enable-chunked-prefill --max-num-batched-tokens 8192 \ --long-prefill-token-threshold 4096 --enforce-eager \ --enable-auto-tool-choice --tool-call-parser qwen3_xml --reasoning-parser qwen3 \ --speculative-config '{"method":"mtp","num_speculative_tokens":3}' Worker node (rank 1): identical, but --node-rank 1 --headless, launched ~30s after the head. Note --enforce-eager — that's not a compromise, it's the fast path (see dead end #1).
[link] [comments]
More from r/LocalLLaMA
-
Oh so that's where my PCIe lanes went...
Aug 30
-
Uncensored Multi-Model Releases, LongCat-Flash-Lite-Sparse with MTPs and LSAs, Qwen3.8-27B with MTPs, Qwen3.5-122B-A10B with MTPs, Qwen3-Coder-Next and Laguna-S2.1 with Vision, All Available in GGUF Format! Bonus: Links to my llama.cpp Fork for LongCat-Flash-Lite Support and…
Aug 30
-
Got MiniMax H3 video generation running in TensorSharp
Aug 30
-
Don't Sleep on EXL3 Quants
Aug 30
Discussion (0)
Sign in to join the discussion. Free account, 30 seconds — email code or GitHub.
Sign in →No comments yet. Sign in and be the first to say something.