Conversation
7ce0d21 to
d4477bd
Compare
|
I ended up building the same thing but can confirm this strategy works and I was able to reproduce similar results. One finding worth folding in: the docs position |
d4477bd to
d866243
Compare
|
@jzinno — we built the same feature independently, three days apart, on the same base branch, and neither PR references the other: #36567 (2026-08-26) and #37068 (2026-08-29) both take the 47.7 GiB Qwen4-Exp PLE table out of resident memory and serve its rows from NVMe. Flagging it before a reviewer has to discover it, and proposing where they converge. They also collide mechanically: both edit Where they differ
Neither is a superset. Yours is portable and doesn't depend on the GPU being able to walk host page tables; it pays a reader, a staging buffer and a copy for that. Mine is zero-copy and small enough to be a flag value rather than a parallel model path, but it only works on coherent unified-memory parts, which today means GB10. A way to convergeThey compose better than they compete: the One data point for @benthecarman's RSS findingConfirming it independently on a GB10, and with a correction that cost me time: the RSS climb is real, This applies to my #37068 as much as to your Unrelated, but it saves you a rebase#36845 landed on |
Stack
This PR is stacked on #36497 and targets its
qwen4-main-squashedbranch. It should be rebased ontomainafter that PR lands.Motivation
The Qwen3.8 Flash Next NVFP4 checkpoint contains a 47.68 GiB FP8 PLE n-gram embedding table. On a 128 GiB unified-memory system such as DGX Spark, keeping that table resident prevents the rest of the model, Mamba state, and KV cache from fitting comfortably.
PLE selects a small number of rows per forward, so the full table does not need to be resident. This change leaves the table in its original sharded safetensors files and reads only the selected rows from local NVMe.
Modifications
io_uring, page-aligned storage, bounded submission batches, GIL-free reads, and preserved OS error codes.io_uringandmmaprow readers, optional page caching, pinned staging memory, asynchronous H2D conversion, and overlap with the decoder layer before PLE.The feature is opt-in through
SGLANG_QWEN4_PLE_NVME_PATH; existing model loading is unchanged when it is unset.Accuracy Tests
mmaptests cover sharded row mapping and malformed shard layouts.RadixArk/Qwen3.8-Flash-Next-NVFP4revision7b719225242aacd3dbd3f9407468c2ee9a9d2594, rows at the first shard, a shard boundary, and the final shard matchedsafetensors.safe_openbyte for byte through the Rust reader. Sixteen additional random rows also matched.Completed locally:
Speed Tests and Profiling
Hardware: NVIDIA DGX Spark GB10, SM121, internal Samsung NVMe, TP1, concurrency one.
The Rust reader measured 0.208 ms p50, 0.627 ms p95, and 0.944 ms p99 for 16 random logical PLE rows over 1,000 iterations while another storage-heavy workload was active. The sparse GQA kernel measured 0.0978 ms mean over 100 iterations at batch size one.
The exact branch was then run end to end with
RadixArk/Qwen3.8-Flash-Next-NVFP4, BF16 KV cache, a 32K context, eager execution, and the checkpoint's built-in NEXTN head using three speculative steps, top-k one, and four draft tokens. Each domain contains ten held-out prompts with 512 output tokens per request:All 40 requests completed without an API error, restart, or OOM. Overall TTFT is the median across all requests; overall TPOT is the mean of the four domain medians. After 7,000 logged gathers, the Rust reader had selected 703,568 rows at 4.475 ms mean read time across mixed prefill and speculative verification batches.
The target weights loaded in 466.10 seconds and occupied 80.06 GiB. The integrated NEXTN pass loaded in another 90.20 seconds. Target, draft, Mamba, and KV pools left 18.31 GiB available, with 447,040 KV-cache tokens allocated.
\n
Checklist
CI States
Latest PR Test (Base): ❌ Run #33088727474
Latest PR Test (Extra): ❌ Run #33088725886
Latest PR Test (AMD ROCm 10): ❌ Run #33088726382