Qwen3.8-Flash-Next-Dual-DGX-Sparks README

The project's README from github.com/MiaAI-Lab/Qwen3.8-Flash-Next-Dual-DGX-Sparks. As one markdown file: README.md.

Qwen3.8-Flash-Next-NVFP4-vLLM-Dual-DGX-Spark

by Mia'a AI Lab

Sponsor me on GitHub Follow Mia on X

Multi-node inference for RadixArk/Qwen3.8-Flash-Next-NVFP4 across 2 DGX Sparks using vLLM with TP2+EP+MTP3.

Based on getrefined/Qwen3.8-Flash-Next-NVFP4-vLLM-DGX-Spark.

All numbers in KV cache budget and Default runtime below were read from the running server (docker logs vllm-fn, docker inspect vllm-fn) — they are measurements, not estimates. That container runs GPU_MEMORY_UTILIZATION=0.835, i.e. the value .env / .env.sample ship today.

Prerequisites

Quick Start

# 1. Edit .env (set IPs, interface, IB HCA, etc.)
cp .env.sample .env
vim .env

# 2. Download weights onto the head
./download.sh
#    ./download.sh --fp8    # official FP8 instead
#    ABLIT=1 ./download.sh  # gated Keys house QSA L3-47 (HF_TOKEN + accept terms)

# 3. Sync to worker, apply patches, launch (NVFP4)
./start.sh --no-download
#    or ./start.sh       if you want start.sh to download as well
#    or ./start.sh --nfs to share the head cache over NFS instead of rsyncing
#    or ABLIT=1 ./start.sh --no-download

#    Optional — official FP8 instead of NVFP4 (see below):
#    ./download.sh --fp8 && ./start-fp8.sh --no-download

# 4. Confirm the KV cache pool that vLLM actually allocated (~11 min after launch)
docker logs vllm-fn 2>&1 | grep -E "Available KV cache memory|GPU KV cache size"

./start.sh and ./start-fp8.sh both launch containers named vllm-fn on the same port — stop first with ./stop.sh before switching checkpoints.

Flags

Flag Description
--no-download Skip HF download (weights already in the head cache)
--no-launch Download + distribute weights only, don't start the server
--launch Skip download + sync; apply patches and launch (weights already on both nodes)
--nfs Distribute weights over NFS instead of rsync (see below)
--no-nfs Force rsync distribution even if NFS_SHARE=true in .env
ABLIT=1 Env/.env flag: serve the gated Keys house QSA L3–47 checkpoint (see Abliterated checkpoint)

Official FP8 checkpoint (optional)

Same two-Spark launch as ./start.sh, but serve Qwen/Qwen3.8-Flash-Next-FP8 instead of NVFP4. Cluster IPs, TP/EP/MTP, image and ports still come from .env. Context is native 262K with YaRN off — FP8 weights leave too little KV for 1M on this kit. Available KV cache is around 500k tokens, not the 3.65M of the nvidia NVFP4 checkpoint with KV_CACHE_DTYPE=fp8.

./download.sh --fp8
./stop.sh                          # start.sh and start-fp8.sh share vllm-fn
./start-fp8.sh --no-download
# API name: qwen3.8-flash-next-fp8

This is not FP8_DENSE=true (hybrid NVFP4 experts + FP8 dense projections) and not fp8 KV on stock NVFP4. ABLIT=1 is ignored when ./start-fp8.sh sets OVERRIDE_MODEL_ID.

What Happens

  1. Download — ./download.sh pulls MODEL_ID from .env (stock NVFP4) to the head HF cache (./download.sh --fp8 for official FP8; ABLIT=1 ./download.sh for the gated Keys house checkpoint).
  2. Distribute — by default rsync copies the checkpoint into the worker's own ~/.cache/huggingface/hub over the ConnectX link, skipped when the worker already has it. With NFS_SHARE=true / --nfs this is replaced by the NFS share.
  3. Image sync — ensures vllm/vllm-openai:qwen38-flash-next is on both nodes
  4. PLE patch — extracts ple_layer.py from the image and patches it into files/ple_layer_patched.py (no image rebuild; bind-mounted at runtime)
  5. MXFP8 patch — extracts modelopt.py and patches it into files/modelopt_patched.py, routing the MXFP8 shapes FlashInfer's mm_mxfp8 cannot run to the BF16 emulation kernel (also bind-mounted)
  6. Preflight + overlays — refuses to launch if another process holds a GPU on either node (REQUIRE_IDLE_GPU=false to override); prepares the FP8_DENSE / QSA_PROFILE bind-mounts
  7. Launch — worker (rank 1) starts first, then head (rank 0) serves on :8888. Both launch scripts render the same VLLM_ARGS array (EXTRA_VLLM_ARGS really is appended last now; ENABLE_EXPERT_PARALLEL=false and MTP_NUM_SPECULATIVE_TOKENS=0 are honored). The head's rendered script is kept as .last_head_launch.sh for inspection.

Not done for you: dropping page caches. start.sh needs no root and does not drop page caches. Do it yourself on both nodes before a launch — it matters on GB10 unified memory (see Gotchas):

sync && echo 3 | sudo tee /proc/sys/vm/drop_caches

Both containers are named vllm-fn (head and worker); ./stop.sh removes both.

NFS weight sharing (optional)

Off by default. By default each node keeps its own copy of the checkpoint in ~/.cache/huggingface, and start.sh rsyncs the worker's copy from the head once (subsequent launches detect it and skip the transfer). That costs ~126 GiB on the worker and one long copy the first time, but the worker is then self-sufficient.

Turn NFS sharing on to skip the worker copy entirely — the head exports its HF cache and the worker mounts it read-only:

NFS_SHARE=true ./start.sh --launch     # or set NFS_SHARE=true in .env
./start.sh --nfs                       # same, per-run flag
./start.sh --no-nfs                    # force rsync even with NFS_SHARE=true in .env

A privileged vllm-fn-nfs container on the head exports $HF_CACHE_DIR over NFSv4 on the ConnectX address (NFS_SERVER_IP, auto-detected from IFACE — do not use the 10.0.0.1 loopback alias). The worker Docker volume vllm-fn-hf mounts it read-only at /root/.cache/huggingface.

rsync (default) NFS_SHARE=true
Worker disk ~126 GiB none
First launch one full copy, then free no copy
Every cold start reads local disk streams ~126 GiB over ConnectX
Head must stay up only to launch for the whole serving run
Failure mode worker cache goes stale silently share dies → worker loses its weights

Prefer NFS when worker disk is tight or you re-pull checkpoints often; prefer the default when you want the two nodes decoupled. Switching checkpoints is where NFS earns its keep — no re-sync.

Notes when it is on:

.env Reference

sample = value in .env.sample · live = value in the .env on this box, which produced every measurement in this README.

Variable sample live Description
HEAD_IP 10.0.0.1 10.0.0.1 Head node inter-node IP
WORKER_IP 10.0.0.2 10.0.0.2 Worker node inter-node IP
WORKER_USER (empty) zurih SSH user on worker (blank = same user)
IFACE enp1s0f0np0 enp1s0f1np1 Head-side inter-node interface
WORKER_IFACE (unset → IFACE) enp1s0f0np0 Worker-side interface (nodes are cross-wired)
IB_HCA =rocep1s0f0 =rocep1s0f1 NCCL IB HCA (leading = = exact match, one device)
WORKER_IB_HCA (unset → IB_HCA) =rocep1s0f0 Worker-side HCA (cross-wired link)
IB_GID_INDEX 3 3 IB GID index (3 for ConnectX + RoCE)
MODEL_ID nvidia/Qwen3.8-Flash-Next-NVFP4 same HuggingFace model when ABLIT=0 (the snapshot in the head cache)
ABLIT 0 0 0 = stock MODEL_ID. 1 = gated Keys house QSA o_proj L3–47 (drowzeys/keys-Qwen3.8-Flash-Next-NVFP4-dual-ablit-house-qsa-L3-47). Environment wins over .env for this flag. See Abliterated checkpoint
FP8_DENSE false false true → serve the hybrid NVFP4+FP8-dense checkpoint with the files/overlay loader patches (see below)
QSA_PROFILE stock stock gb10 or a JSON from files/qsa_gb10/bench_qsa_kernels.py
REQUIRE_IDLE_GPU true true abort if nvidia-smi shows a compute process on either node
SERVED_MODEL_NAME qwen3.8-flash-next same Name in /v1/models
MAX_MODEL_LEN 1000000 1000000 Context length (262144 = native, no YaRN)
YARN_ENABLE true true Extend context via YaRN rope scaling — auto force-disabled when MAX_MODEL_LEN ≤ 262144
YARN_FACTOR 4.0 4.0 262144 × 4.0 ≈ 1M
GPU_MEMORY_UTILIZATION 0.835 0.835 Fraction of the 121.69 GiB budgeted by vLLM (see KV cache budget)
MAX_NUM_SEQS 8 8 Max concurrent sequences
MAX_NUM_BATCHED_TOKENS 8192 8192 Prefill chunk / cudagraph ceiling
PORT 8888 8888 API server port (--network host)
KV_CACHE_DTYPE fp8 fp8 Default. 3.65M cache tokens (13.93× at 262K) via files/patch_qsa_fp8_kv.py, applied automatically. auto = bf16 = 2.13M (8.13×)
TENSOR_PARALLEL_SIZE 2 2 1 GB10 per node × 2 nodes
ENABLE_EXPERT_PARALLEL true true EP for the NVFP4 experts (required)
MTP_NUM_SPECULATIVE_TOKENS 3 3 MTP draft tokens (0 = disable)
PLE_OFFLOAD false false true → CPU-RAM offload of the 51 GB PLE table (see gotcha)
IMAGE vllm/vllm-openai:qwen38-flash-next same Day-0 image
VLLM_ALLOW_LONG_MAX_MODEL_LEN 1 1 Required when MAX_MODEL_LEN > 262144
MASTER_PORT 50000 50000 Distributed coordination port
NFS_SHARE false false true → share the head HF cache over NFS instead of rsyncing a worker copy (details)
NFS_SERVER_IP (unset → IFACE IPv4) (unset → 10.0.22.1) Head ConnectX address that exports the HF cache. Only used when NFS_SHARE=true. Do not use the 10.0.0.1 loopback alias
EXTRA_VLLM_ARGS / EXTRA_DOCKER_ARGS unset unset Escape hatches (EXTRA_VLLM_ARGS is appended last)
HF_TOKEN unset unset Required for ABLIT=1 (gated Hugging Face repo). Environment wins over .env

KV cache dtype: the default is now fp8, which needs files/patch_qsa_fp8_kv.py — the stock QSA kernels declare supported_kv_cache_dtypes = ["auto", "bfloat16"] and raise Qwen3.8-Flash-Next QSA requires a BF16 main KV cache otherwise. start.sh applies the patch automatically whenever KV_CACHE_DTYPE starts with fp8. Set KV_CACHE_DTYPE=auto for bf16. See KV cache budget for both measured sets.

Abliterated checkpoint (ABLIT)

ABLIT=0 serves stock MODEL_ID (nvidia NVFP4 by default). ABLIT=1 serves the gated Keys checkpoint drowzeys/keys-Qwen3.8-Flash-Next-NVFP4-dual-ablit-house-qsa-L3-47: the same nvidia dual-Spark NVFP4 layout with a house residual-writer projection on 12 QSA self_attn.o_proj tensors at layers 3, 7, 11, 15, 19, 23, 27, 31, 35, 39, 43 and 47. GDN out_proj, routed experts, MTP, PLE, vision and the chat template stay stock. It is valid only on this recipe — do not use it with the single-Spark MXFP8 tree, official FP8, or FP8_DENSE=true. ./start-fp8.sh and FP8_DENSE=true still win if you set them, and ABLIT=1 is ignored for checkpoint selection in those cases.

That Hugging Face repo is gated. ABLIT=1 ./download.sh fails immediately if HF_TOKEN is unset and prints the steps below. Set the token, Accept the terms on that page, then download the full snapshot (same nvidia NVFP4 size class, ~133 GiB). Stock and ablit caches sit side by side; flipping ABLIT is the only switch. start.sh rsyncs whichever checkpoint ABLIT selected onto the worker (or shares it over NFS). An interrupted download is not treated as ready — rerun ABLIT=1 ./download.sh to resume.

# 1. In .env: set ABLIT=1 and uncomment HF_TOKEN (or export both for this shell)
# 2. In the browser, accept the terms on:
#    https://huggingface.co/drowzeys/keys-Qwen3.8-Flash-Next-NVFP4-dual-ablit-house-qsa-L3-47
HF_TOKEN=hf_... ABLIT=1 ./download.sh
ABLIT=1 ./start.sh

HF_TOKEN is required for ABLIT=1. A 403 means access has not been granted yet — Accept the terms on that page, then retry with HF_TOKEN set. Unlike most knobs in this launcher, ABLIT and HF_TOKEN honour the environment over .env, so ABLIT=1 ./start.sh works even when .env still has ABLIT=0. Switch back with ABLIT=0 ./start.sh (stock MODEL_ID; no re-download if that cache is already complete).

The gate is a binding agreement, not a download button. The checkpoint ships its own responsible-use terms, and requesting access means accepting them. In summary: you must be 18 or older; you are accountable for harm from generated recipes or knowledge; and the terms prohibit sexual content involving minors, material promoting self-harm or suicide, harassment, doxxing or fraud targeting real people, anything illegal in your jurisdiction, and any use barred by the NVIDIA Open Model License or the Qwen Community License 1.0. The weights are provided as-is with no warranty and inherit their licence from nvidia/Qwen3.8-Flash-Next-NVFP4. Read the repo's own terms before requesting access — this paragraph is a summary, and those terms are what bind.

Safety refusals are removed in this checkpoint, which moves the guardrails onto you: filtering, human review and access control are yours to supply. That matters more here than on stock, because start.sh binds the server to 0.0.0.0 — anything that can reach the port can reach an unfiltered model.

The abliteration splice is by Keys (drowzeys), a house projection on NVIDIA stock (axis recovered per QSA layer from Dealign, not a Dealign weight dump). See the checkpoint's model card, and Credits below.

The Keys config.json mislabels MTP routed experts as FP8_PB_WO; nvidia and this snapshot's own hf_quant_config.json record FP8_BLOCK_SCALES (same 128×128 tensors). start.sh rewrites that overlay the same way it adds the mtp.layers.48 alias, so MTP 3 still works.

KV cache budget

Measured on the running container at the shipped defaults (KV_CACHE_DTYPE=fp8, GPU_MEMORY_UTILIZATION=0.835, MAX_MODEL_LEN=262144, MTP3, TP2, nvidia NVFP4 checkpoint). Official FP8 (./start-fp8.sh) is a different checkpoint — see Official FP8 checkpoint; its available KV cache is around 500k tokens, not the 3.65M below.

[gpu_worker.py:693]   Available KV cache memory: 32.02 GiB
[kv_cache_utils.py]   GPU KV cache size: 3,652,200 tokens,
                      Maximum concurrency for 262,144 tokens per request: 13.93x

FP8 vs bf16 on this kit, same GMU, same checkpoint, KV dtype the only change:

auto (bf16) fp8 (default)
KV pool 31.7 GiB 32.02 GiB
Cache size 2,131,159 tok 3,652,200 tok
Concurrency at 262,144 8.13× 13.93×
Tokens per GiB 67,229 114,060 (1.70×)
Reasoning + needle suite 12/12 12/12 (no regressions)

1.70×, not 2×, because the QSA indexer and compressor stay BF16 — only the main K/V halve. A 1M-token context now fits 3.5× over. Weights (68.52 GiB/GPU), not KV, are the binding constraint on this box.

Credit. The FP8-KV kernel work in files/patch_qsa_fp8_kv.py is vendored from MiaAI-Lab/Qwen3.8-Flash-Next-Single-DGX-Spark (AGPL-3.0-or-later), which credits the underlying approach to lancelind/qwen3.8-Flash-DGX (Apache-2.0). Applied here unchanged — every anchor matched this image's QSA sources.

How to read those two lines. They are the same number in two units: 32.02 GiB is the byte budget, 3,652,200 tokens is what fits in it (~9.2 KB/token at fp8). The token figure is the capacity of the whole block pool, shared by every concurrent request — it is not per-request, and it is not summed over TP (TP2 shards each token across both GPUs, so ~64 GiB of VRAM is committed while the usable token space is 3.65M once). The third figure is the practical one: 13.93× = 13.93 resident requests at the full 262,144 context — more than MAX_NUM_SEQS=8 can even admit, so at these defaults the scheduler, not the cache, is the limit. At a 1M-token context (YaRN) the same pool is 3.5×.

Per-GPU memory accounting (GB10, per node — from gpu_worker.py:919):

Line GiB
Total memory visible to CUDA 121.69
Free at startup 108.79
Budgeted at GMU 0.835 101.61
Weights (model_runner.py:407) 64.46
Weights + non-torch 68.52
Peak activation 1.07
CUDA graphs 0.54
KV cache 32.02

vLLM reports headroom for --kv-cache-memory=41360080896 (38.52 GiB) if you want to push GMU higher.

The nvidia checkpoint is 124 GiB on disk / 11 shards (NVFP4 experts, FP8 PLE n-gram table ≈ 51 GB, bf16 attention/dense/vision). With EP + TP the per-GPU weight footprint lands at 64.46 GiB — roughly experts 34 GB + PLE shard ~25 GB + bf16 dense ~10 GB. Weights, not KV, are what eats this box, and that is more true at fp8 than it was at bf16.

Why the cache is so cheap: of the 48 layers only every 4th is full_attention (full_attention_interval: 4) → 12 KV-bearing layers + 1 MTP draft layer = 13. The other 36 are Gated-DeltaNet linear attention: constant-size recurrent state per request, not per token. With num_key_value_heads: 2, head_dim: 256:

13 layers × 2 (K,V) × 2 kv_heads × 256 × 2 B (bf16) = 26,624 B/token  (= 13,312 B/GPU at TP2)
13 layers × 2 (K,V) × 2 kv_heads × 256 × 1 B (fp8)  = 13,312 B/token  (=  6,656 B/GPU at TP2)

At fp8 vLLM reports 32.02 GiB / 3,652,200 tokens = 9,418 B/token/GPU, ~41% above the pure atten

Qwen3.8-Flash-Next-Dual-DGX-Sparks (MiaAI-Lab/Qwen3.8-Flash-Next-Dual-DGX-Sparks)

Searching Database…

这是什么?一个多节点推理启动器,使用 vLLM 配合张量并行、专家并行和投机解码,在两台 NVIDIA DGX Spark 节点上运行 Qwen3.8-Flash-Next-NVFP4 语言模型。
这是独立应用,还是只是运行 vLLM 的脚本?它是一组 shell 脚本和 Docker 配置(start.sh、download.sh、stop.sh),用于在两个节点上做配置、同步权重并启动 vLLM 容器——不是独立应用。
怎么让它跑起来?运行 ./start.sh 下载权重、同步到工作节点并启动双节点 vLLM 服务器,或者运行 ./start-fp8.sh 改为部署官方 FP8 检查点。
这个需要特定硬件吗?需要——必须正好是 2 台通过 ConnectX RoCE/IB 互连的 DGX Spark 节点(GB10 GPU、128 GB 统一内存、sm121),两台都要配好免密 SSH 和 Docker,另外每个节点还要有约 126 GiB 空闲磁盘来放检查点。
启动之前有什么坑要注意?启动前必须在两个节点上手动清空页缓存;任一节点上有 GPU 被占用,服务器都会拒绝启动;start.sh 把 API 绑定到 0.0.0.0,任何能访问该端口的东西都能访问模型,使用无审查的 ABLIT 检查点时尤其危险。
这到底是给谁用的?面向已经拥有一对 DGX Spark 节点、想要大规模自托管大型 Qwen 模型的运维人员;它不是通用的单显卡推理工具。
许可证上有什么坑吗?启动器本身是 AGPL-3.0,但可选的受限“abliterated”检查点继承了 NVIDIA Open Model License 和 Qwen Community License 的限制性条款,包括要求年满 18 岁、禁止用于有害内容。
上下文窗口有多大?使用默认的原版 NVFP4 检查点和 fp8 KV 缓存时,上下文可以通过 YaRN 扩展到 1M token,可用的 KV 缓存约 365 万 token;官方 FP8 检查点则封顶在原生 262K 上下文,缓存约 50 万 token。
NFS 权重共享是做什么的?它让主节点通过 NFS 导出自己的 Hugging Face 缓存,工作节点以只读方式挂载权重,而不必保留自己约 126 GiB 的副本,代价是依赖主节点保持在线,换来的是更快的检查点切换。
ABLIT 检查点是什么?一个可选的受限检查点,通过对 12 个注意力输出投影张量施加自研的残差写入投影来移除安全拒绝;下载前需要 Hugging Face 令牌并接受负责任使用条款。
为什么要在原版 vLLM 之上打补丁?这些脚本把打过补丁的 PLE 和 MXFP8 内核文件绑定挂载进官方 vllm-openai 镜像,以修复 FlashInfer 跑不了的形状并启用 fp8 KV 缓存,而无需重建容器镜像。
Creator
MiaAI-Lab
Size
10 MB
Stars
410
Updated
CommitSep 29
Age
1mo
Commits · 90 Days