Downloads · 30 days
0
pocharlies/Qwen3.8-Flash-Next
Qwen3.8-Flash-Next is a machine learning model from pocharlies. Use it for the machine learning task on the model card, and read the license before you ship it in a product. The card lists the license as apache-2.0.
A production-verified recipe (2026-08-27) for serving RadixArk/Qwen3.8-Flash-Next-NVFP4 (125B MoE / 6B active + 51B n-gram PLE + 4B MTP, 135 GB) on two NVIDIA DGX Spark machines with TP=2 over RoCEv2 and SGLang. The R…
Downloads · 30 days
0
Access
Public
Updated Aug 27, 2026
Repo size
—
Likes
0
Public
Click a slice to open those files.
.yaml30.9 KB · 49%
From the Hugging Face model README
A production-verified recipe (2026-08-27) for serving RadixArk/Qwen3.8-Flash-Next-NVFP4 (125B MoE / 6B active + 51B n-gram PLE + 4B MTP, 135 GB) on two NVIDIA DGX Spark machines with TP=2 over RoCEv2 and SGLang. The RadixArk NVFP4 checkpoint is validated upstream only on GB300/B300; this recipe is the sm_121a (GB10) verification.
| Metric | Value |
|---|---|
| Single-stream | ~41–42 tok/s |
| 8 concurrent streams (aggregate) | 153 tok/s average (139–166), no degradation |
| NEXTN speculative accept length | ~2.3 sustained (2.0–2.7) |
| Thermals under sustained load | 83–85 °C plateau on both nodes, recovery to <60 °C |
| Context | 262,144 tokens (native 256K, no YaRN) |
| Concurrency / KV | tunable trade-off; shipping 8 concurrent / ~1.37M-token KV pool (see the config table) |
NCCL_IB_* in the manifest).
No switch: a straight port-to-port cable with static IPs (10.0.0.1/10.0.0.2).image/Dockerfile — SGLang with the qwen4_exp build
(PR sgl-project/sglang#36497,
branch qwen4-main-squashed@73a2552), modelopt_fp4 quantization,
flashinfer with NVFP4 cutlass kernels for sm_121a, arm64. The branch
requires cargo (Rust extensions) — the Dockerfile installs rustup.k8s/qwen38-flash-next-nvfp4-sglang.yaml — two Deployments
(head rank 0 + worker rank 1), hostNetwork (the ranks talk over the RoCE
fabric IPs), and a ConfigMap with the full launch.sh. Every parameter is
commented in the yaml itself with the reasoning behind it.| Parameter | Value | Why |
|---|---|---|
--mem-fraction-static | 0.90 (shipping) | Steers the KV pool (see the config table). Early on, 0.85 OOM'd during CUDA graph capture — but that was the nvcc/inductor spike, now capped by MAX_JOBS=1; with it capped, 0.90 boots clean and 0.94 is the hard edge. On GB10 the GPU memory IS system memory: the pinned static pool plus the host-side compilation spike cross the cgroup limit (and with less margin, they take down the whole node). |
MAX_JOBS | 1 | The flashinfer JIT compiles the FP4 fused_moe kernels with nvcc via ninja, which defaults to one job per core. A single cutlass FP4 cicc reaches 7.7 GB of RSS: 20 in parallel = global node OOM. With 1, the spike stays around ~8 GB. Only the first capture is affected: the JIT cache persists. |
TORCHINDUCTOR_COMPILE_THREADS | 4 | Bounds inductor's compile workers during capture. |
--page-size | 64 | Mandatory: QSA (sparse attention) selects at micro-block level, 64-token blocks with a budget of 512 blocks / 2048 tokens per query (model card). |
--mamba-scheduler-strategy extra_buffer + --mamba-track-interval 64 | — | Mandatory for the radix cache over the hybrid GDN state. |
| NEXTN MTP | steps=3, topk=1, draft=4 | Measured accept length ~2.3. Switchable with ENABLE_MTP=false without touching the image. |
--chunked-prefill-size | 2048 | A long prefill sinks concurrent decodes on this hardware. |
--disable-flashinfer-autotune | FIRST boot only | With a cold JIT cache the autotune hangs the startup (GPU at 0%, spinning). Once the cache is warm it can be re-enabled (we measured no performance difference, but it boots cleanly). |
| Pod memory limit | 112Gi out of ~120 GiB | Leaves air for the system. The OOM that matters here is the GLOBAL node one, not the cgroup one — no cgroup protects you from page cache plus unified memory. |
The KV cache of this model is almost free (12 KB/token/rank: only 12 of 48
layers carry KV -- the whole pool at fraction 0.78 was ~4.4 GB), while the
mamba/GDN state costs ~0.5 GB per concurrent request (5 slots/request with
NEXTN x ~0.1 GB/slot). The KV pool has no direct knob: it is the remainder
fraction x 120 - weights(74) - mamba - graphs, so you steer it with
--mem-fraction-static and --max-mamba-cache-size (always identical on
both ranks). Three configurations measured on the same pair of Sparks:
| Config | Concurrency | KV pool | Aggregate | Per stream | Node headroom |
|---|---|---|---|---|---|
| 0.78 / mamba auto | 8 | 365K tok | 153 tok/s | 19.7 | ~24 GB |
| 0.86 / mamba 80 | 16 | 737K tok | 160 tok/s | 10.2 | ~11 GB |
| 0.85 / mamba 30 | 6 | 1.02M tok | 124 tok/s | 22.8 | ~15 GB |
| 0.90 / mamba 40 (SHIPPING) | 8 | 1.37M tok | -- | -- | ~7 GB |
| 0.94 / mamba 40 (the edge, not recommended) | 8 | 1.70M tok | -- | -- | ~2 GB |
The shipping config is 0.90 / 8 concurrent / ~1.37M-token KV pool (~5 full 262K sessions in parallel) with flashinfer autotune ON and ~7 GB of node headroom. It boots clean in ~8 min.
0.90 is the ceiling with autotune enabled. 0.94 does reach a ~1.7M KV pool
but leaves the node with 2 GB, which hangs the flashinfer autotune (no buffer
memory) for ~27 min and drags CUDA graph capture out to ~25 min; it only boots
with --disable-flashinfer-autotune. Not worth the fragility.
Always check max_total_num_tokens in the boot log after touching either
knob: one intermediate config left the KV pool at 67K tokens -- below the
262K context -- and long agent sessions silently stopped fitting.
Large-context concurrency on the 6cc/1M profile (prefix-cache-friendly corpus, so treat prefill figures as upper bounds):
| Load | Total prompt tokens | Wall | Aggregate prefill |
|---|---|---|---|
| 6 x ~30K | 198K | 25.9 s | 7.7K tok/s |
| 4 x ~60K | 264K | 22.4 s | 11.8K tok/s |
| 2 x ~120K | 264K | 34.9 s | 7.6K tok/s |
A cold 72K-token single prompt (no prefix reuse) prefills in ~29 s (~2.5K tok/s).
There is an official vLLM recipe for this model. We evaluated it and stayed on SGLang for this hardware:
VLLM_PLE_CPU_OFFLOAD
moves the 51 GB n-gram table "to host RAM" -- on unified memory that IS
the same LPDDR. It only helps on discrete-GPU systems (GB300/H200).If an official NVFP4 checkpoint that vLLM loads ever ships, it is worth re-evaluating: vLLM on these Sparks is otherwise well-trodden ground.
run/head.sh and run/worker.sh reproduce the whole deployment with plain
docker run on the two Sparks -- same image, same NCCL/RoCE environment,
same engine flags, with the boot-order and GID-resolution notes inline.
The image built from the open branch ships two landmines that the ConfigMap's
launch.sh patches before exec (search for QWEN_FA_STUB and
QWEN_QSA_GATE in the yaml):
c10_cuda_check_implementation against the image's torch) whose
__init__.py imports it unconditionally, killing the FA4 cute path too.
The patch stubs the __init__ so only the cute path survives.is_sm100_supported(), which excludes GB10 (sm_121). The patch
opens the gate to sm120+ — with trtllm resolved, the backend never calls
the broken varlen. (Related: sgl-project/sglang#36531.)Both patches are now BAKED into the image as build layers (v0.2.0, see
image/Dockerfile.baked-patches): the launch script is clean again. The
definitive fix is still upstream (a flash-attn wheel built against the
image's torch, and sglang accepting sm120 in the gate).
The n-gram PLE tables (51 GB) are FP8 on disk. The RadixArk model card says
they dequantize to BF16 on load (which would not fit); measured on sm_121a:
they stay FP8 — ~74 GiB of weights per rank, with plenty of pool left for
the 262K context. Verify it on your boot: Load weight end ... avail mem
should leave ~33–35 GB.
folio_wait_bit_common (a page-cache
reclaim storm). Avoid large concurrent downloads.sglang:* prefix (including
sglang:spec_accept_length to watch the speculative decoding).--reasoning-parser qwen3 and
--tool-call-parser qwen3_coder (the template emits XML tool calls).
Reasoning arrives in reasoning_content, tool calls come structured, and
sglang#36537 (thinking + qwen3_coder looping) did not reproduce on this
build — tested with thinking, tools, both combined, and streaming.The model thinks by default, emitting <think>\n...</think>\n\n before
the final answer. Recommended sampling, per mode:
| Mode | temperature | top_p | top_k | min_p | presence_penalty | repetition_penalty |
|---|---|---|---|---|---|---|
| Thinking | 1.0 | 0.95 | 20 | 0.0 | 0.0 | 1.0 |
| Instruct (non-thinking) | 0.7 | 0.80 | 20 | 0.0 | 1.5 | 1.0 |
presence_penalty can be raised between 0 and 2 to curb endless repetition,
at the cost of occasional language mixing and a slight quality drop.
Thinking behaviour is controlled through the chat template (works through any
OpenAI-compatible gateway via chat_template_kwargs):
enable_thinking: false — direct answers, no <think> block.preserve_thinking (default true) — keeps the thinking blocks of ALL
previous turns in the prompt. This is deliberate: it maximises decision
consistency in agents and radix/KV-cache reuse. Set it to false to keep
only the latest turn's thinking.reasoning_effort — supported levels: xhigh, medium, low. Note from
the card, confirmed by our agent experience: in multi-turn agentic tasks a
LOWER effort does not necessarily lower total task time — weaker analysis
causes failures and retries that cost more than the faster turns save.For agentic workloads the card recommends generous output budgets (reasoning
up to 262,144 tokens and final response up to 131,072 where the serving stack
splits the two). Size your gateway's max_output_tokens accordingly — our
deployment uses a much smaller cap and it is a deliberate trade-off, not a
model limit.
We deliberately run at the native 262,144 (static YaRN penalises short
contexts, and on 2× GB10 there is no memory for the 1M KV pool anyway). If
you need more, the official recipe is to change rope_parameters inside
text_config in config.json:
{
"mrope_interleaved": true,
"mrope_section": [11, 11, 10],
"rope_type": "yarn",
"rope_theta": 10000000,
"partial_rotary_factor": 0.25,
"factor": 4.0,
"original_max_position_embeddings": 262144
}
Set factor to what you actually need (e.g. 2.0 for 512K): all open-source
frameworks implement static YaRN, so an oversized factor degrades short
prompts all the time.
Attention/GDN/vision run in BF16 in this checkpoint, so the multimodal path
is intact. For hour-scale video the card recommends raising longest_edge
in video_preprocessor_config.json to 469762048 (≈224k video tokens);
see sglang PR #18467 for engine-side overrides. We have not benchmarked video
on the 2× Spark setup — budget KV accordingly before trying.
If you have a single DGX Spark (or want CPU offload of the 51 GB n-gram PLE), the Unsloth Dynamic GGUFs run through their llama.cpp PR ggml-org/llama.cpp#27742 (the n-gram embedding is exactly the kind of parameter mass that offloads well — it is indexed, not scanned). That path trades our TP=2 throughput and native FP4 experts for a one-box setup; this recipe is the full-quality, full-context two-node route.
The catalogue of every failure mode we hit (with diagnosis and fix) is in
docs/TROUBLESHOOTING.md.