Back openDesk Edu for a sovereign, open-source education β every vote counts.
Vote nowTwo NVIDIA DGX Sparks. One 685B-parameter MoE model. A 3Γ throughput improvement without a single crash. Here's how.
NVIDIA's DGX Spark (GB10 Grace Blackwell Superchip) is designed as a desktop AI workstation, but with NVLink-C2C and fast networking, it scales. Our cluster connects two DGX Sparks across a local network:
| Node | Hostname | IP | Role |
|---|---|---|---|
| DGX Spark 1 | ai1 | β | Primary (master) |
| DGX Spark 2 | ai2 | β | Secondary (worker) |
Each node carries a GB10 SoC with 128 GB unified memory (119β121 GB usable after OS/firmware reservations). The pair runs DeepSeek-V4-Flash β a Mixture-of-Experts model with 685B total parameters (37B active per token) β via vLLM 0.21.1rc1 in Docker, using tensor parallelism across both nodes (TP=2) over NCCL/RDMA.
The initial configuration was conservative:
| Parameter | Baseline Value |
|---|---|
max-num-seqs | 1 |
max-num-batched-tokens | 4096 |
gpu-memory-utilization | 0.70 |
| FlashInfer autotune | Disabled |
| CUDA graphs | Disabled (enforce-eager) |
Baseline performance:
| Metric | Value |
|---|---|
| Inter-token latency (ITL) | 182 ms/tok |
| Throughput | 5.5 tokens/second |
| KV cache capacity | ~482K tokens |
| TTFT (long context) | 88.5 seconds |
| GPU utilization (node 2) | 37% |
| Prefix cache hit rate | 72% |
The server handled 37 requests with 896K prompt tokens and 20K generation tokens β functional but far from the hardware's potential. GPU utilization sat at 37% on the secondary node. Clearly there was headroom.
Before touching anything, we ranked optimizations by expected impact:
max-num-seqs β single greatest throughput multiplier (3-6Γ)gpu-memory-utilization β more KV cache headroom for concurrent requestsmax-num-batched-tokens β larger batch capacityStep 1 on our list was enabling CUDA graphs β remove --enforce-eager. vLLM initialized successfully, performed distributed setup across both nodes, and then... complete system lockup. Both DGX Sparks became simultaneously unresponsive. SSH timed out. No ping. No recovery via network.
Root cause: The crash was caused by custom_ops: ["all"] in the --compilation-config, which triggers nvcc/cicc compilation of fusion kernels. On GB10 with only ~3 GiB RAM available during model load (CUDA allocates ~95 GiB), the TileLang JIT compilation exhausts system memory and causes a full system freeze.
Fix: Switch to PIECEWISE CUDA graph mode without custom_ops. Compile only the essential graphs (attention, MLP) β enough for the ~10-20% ITL improvement, without triggering the memory-exhausting fusion kernel compiler:
--compilation-config {"cudagraph_mode":"PIECEWISE"}
This enables CUDA graphs safely on GB10. The custom_ops: ["all"] path remains usable only if compiled kernels are cached (persistent volume mount). First-run compilation needs system RAM headroom.
Lesson: CUDA graphs are viable on GB10 with PIECEWISE mode. Skip custom_ops: ["all"] unless you have a persistent kernel cache.
After reverting, we applied the remaining optimizations in order:
| Parameter | Before | After | Rationale |
|---|---|---|---|
max-num-seqs | 1 | 2 | Double concurrent request capacity |
max-num-batched-tokens | 4096 | 8192 | Larger batch for prefix cache efficiency |
gpu-memory-utilization | 0.70 | 0.78 | 0.82 triggered OOM on first runs; 0.78 is the sweet spot for stability |
| FlashInfer autotune | disabled | enabled | Better attention kernel selection |
| CUDA graphs | disabled | PIECEWISE | PIECEWISE mode works safely; custom_ops: ["all"] caused the crash |
| Expert parallelism | disabled | enabled | Distributes MoE experts across both nodes for better memory balance |
| MTP speculation | 0 tokens | 2 tokens | DeepSeek's native MTP speculative decoding adds ~20-30% throughput |
The cluster was re-launched using vLLM's --no-ray distributed executor (PyTorch native distributed), which handled the --nnodes 2 --node-rank N --master-addr <master-ip> --master-port 29501 wiring automatically.
Startup metrics:
http://0.0.0.0:8000, health check 200 OK
Five runs with 12 prompt tokens β 200 generation tokens:
Run | Duration | Tokens | TPS | ms/tok
-----|----------|--------|------|-------
1 | 12.54s | 200 | 16.0 | 63
2 | 12.57s | 200 | 15.9 | 63
3 | 12.55s | 200 | 15.9 | 63
4 | 12.54s | 200 | 16.0 | 63
5 | 12.52s | 200 | 16.0 | 63
Before: 182 ms/tok β After: 63 ms/tok β 2.9Γ speedup.
The dominant factor was FlashInfer autotune, which optimized attention kernel selection for the DeepSeek V4 architecture. Combined with the higher batch token limit, the GPU is now running at significantly higher utilization.
DeepSeek V4 uses a shared prefix KV cache across the cluster. Testing with ~1200 prompt token contexts:
| Scenario | Duration | TPS | vs Cold |
|---|---|---|---|
| Cold (first run) | 17.56s | 2.8 | 1.0Γ |
| Same prompt (cached) | 8.38s | 6.0 | 2.1Γ |
| Similar prompt (deep cached) | 4.03s | 12.4 | 4.4Γ |
The prefix cache is highly effective. Repeated contexts (chat histories, system prompts, document templates) see massive speedups. For workloads with shared prefixes β like agentic systems where every request starts with the same system prompt β this is transformative.
With max-num-seqs=2, the cluster handles two concurrent requests efficiently:
4 concurrent requests completed in 14.24s total
- First pair: ~5-9s each (parallel)
- Second pair: queued behind first
- Effective throughput: 0.28 req/s for 100-token generations
For production use, increasing max-num-seqs further (4-8) would multiply throughput, provided the KV cache has capacity. At 52% KV cache utilization with max-num-seqs=2, there is headroom.
| Metric | Baseline | Optimized | Improvement |
|---|---|---|---|
| ITL | 182 ms/tok | 63 ms/tok | 2.9Γ |
| Throughput | 5.5 TPS | 15.9 TPS | 2.9Γ |
| With MTP speculation | β | ~17-20 TPS (estimated) | ~3.5Γ vs baseline |
| KV cache | 482K tokens | ~865K tokens | +79% |
| GPU utilization | ~37% | ~65%+ (est.) | ~1.8Γ |
| Prefix cache speedup | - | Up to 4.4Γ | massive |
custom_ops, PIECEWISE mode compiles only essential graph sections, avoiding the memory exhaustion that caused system freezes.--nnodes 2 is the correct multi-node path. V1's Gloo-based distributed init is incompatible with --network host on cross-node setups. V0 uses NCCL directly and works reliably.custom_ops: ["all"] + GB10 = system freeze. The TileLang nvcc compilation exhausts system RAM (~3 GiB available during model load). Use PIECEWISE mode without custom_ops instead.--nnodes 2 bypasses this entirely.--no-ray mode) was more reliable for small clusters.max-num-seqs=1 is unnecessarily conservative. Even max-num-seqs=2 nearly doubles throughput without stability issues.max-num-seqs to 4-8 when KV cache utilization permits.custom_ops: ["all"] for maximum fusion performance.For reference, the final stable wrapper script configuration (V0 engine, multi-node):
--served-model-name deepseek-v4-flash
--max-model-len 200000
--max-num-seqs 2
--max-num-batched-tokens 8192
--gpu-memory-utilization 0.78
--kv-cache-dtype fp8
--block-size 256
--prefix-caching
--enable-expert-parallel
--compilation-config '{"cudagraph_mode":"PIECEWISE"}'
--speculative-config '{"method":"deepseek_mtp","num_speculative_tokens":2}'
--disable-custom-all-reduce
--trust-remote-code
--host 0.0.0.0
--port 8000
--load-format safetensors
--tokenizer-mode deepseek_v4
--tool-call-parser deepseek_v4
--enable-auto-tool-choice
--reasoning-parser deepseek_v4
V0 engine is enforced by either:
VLLM_USE_V1 (defaults to V0).env β CONTAINER_VLLM_USE_V1=0 (passed by launch-cluster.sh)Distributed launch across two nodes:
# Node 1 (master):
--nnodes 2 --node-rank 0 --master-addr <master-ip> --master-port 29501
# Node 2 (worker):
--nnodes 2 --node-rank 1 --master-addr <master-ip> --master-port 29501 --headless
The DGX Spark's GB10 uses unified memory β GPU and CPU share the same 128 GB pool. Unlike discrete GPUs, OOM does not produce a graceful CUDA error. Instead, the entire machine freezes, SSH hangs, and a hard power cycle is required.
Critical mitigations applied to both nodes:
swapoff -a) β swap on UMA causes a death spiral, not graceful degradationvm.overcommit_memory=0) β malloc fails immediately instead of freezing--memory=100G β hard ceiling so the process gets killed before the kernel freezesvm.min_free_kbytes=1572864) β 1.5 GiB reserve prevents system freezeecho 3 > /proc/sys/vm/drop_cachesWithout these, a simple config change (like gpu_mem above 0.78 or custom_ops: ["all"]) can brick both nodes simultaneously.
A two-node DGX Spark cluster running DeepSeek V4 Flash is not only feasible β it's production-viable. With careful configuration (V0 engine, PIECEWISE cudagraph, gpu_mem 0.78, MTP speculation) and proper OOM safeguards, we moved from 5.5 TPS to ~16-20 TPS while maintaining stability.
The key takeaway: unified memory changes everything. What works on discrete GPUs (high gpu_mem, full fusion compilation) can crash the entire cluster on GB10. Test conservatively, verify with health checks, and always apply OOM mitigations first.
For anyone running DeepSeek V4 Flash on DGX Spark hardware: skip V1 (Gloo bug), use PIECEWISE cudagraph (not custom_ops), cap gpu_mem at 0.78, enable MTP speculation, and watch your throughput triple without crashes.
Enjoyed this deep-dive? Subscribe to get notified about new AI infrastructure and DevOps articles.