Operator instruction: delete the displaced dealignai checkpoint. 125 GiB reclaimed from /tank (59% -> 57% used). Verified before removing: not mounted by any running or exited container, no symlinks, no inodes shared with the converted orcarouter directory. Every "rollback is two .env keys" statement across the stack README, the .env.example, persistent-memory and its detail file was true when written and is false now -- the .env backup still names paths that no longer exist. Corrected in place rather than left as false reassurance, since a stale rollback instruction is discovered precisely when it is needed. Reverting this seat now costs a 126 GiB re-download. The quality A/B against dealignai is likewise no longer runnable locally: its reference arm is gone. The pristine 170 GiB orcarouter download is retained deliberately -- it is what makes the PLE bf16->FP8 conversion reproducible without re-fetching -- and that is now recorded so a future session does not reclaim it as an obvious duplicate. Also notes that ~75 GiB of non-PLE shards are duplicated between the pristine and converted orca directories (the convert's hardlinks hit EXDEV across two container bind mounts); both now sit directly on /tank, so relinking would reclaim it if /tank ever tightens.
11 KiB
flash-next-seat — Qwen3.8-Flash-Next (abliterated), fv-ml1 GPU 2, :8022
The first seat on the fleet whose weights do not fit its card and run anyway.
Qwen3.8-Flash-Next is 176B total — a 125B main model plus a 51B n-gram (PLE)
lookup table — activating ~6B parameters per token. The n-gram table is a pure
embedding lookup with almost no compute per token, so it lives in pinned host
RAM and the GPU reads the rows it needs directly over CUDA UVA on a dedicated
stream with async prefetch.
| Checkpoint | orcarouter/Qwen3.8-Flash-Next-Uncensored-NVFP4, PLE converted bf16→FP8 in-house 2026-09-14 (123.2 GiB) |
| On the card | ~75 GiB of 95.6 GiB — weight-only on both axes: W4 float experts, W8 float attn, input_activations: null |
| In host RAM | 47.7 GiB pinned, FP8 E4M3, 8 model-plefp8-* shards + one global BF16 scale |
| Context | 262,144 (full native) — KV 344,155 tokens, 1.31x concurrency |
| Speculative decoding | MTP k=3 — 60.4% acceptance, mean acceptance length 2.81 (measured here, n=5) |
| Gateway wiring | 8 aliases — gen, gen-reasoning, summarizer(-large), classifier, chat-judge, image-judge, qwen-image-bench |
Deploy
scripts/deploy-stack.sh fv-ml1 flash-next-seat # diffs vs live, prompts y/N
# then on the host, first boot only:
ssh infra-ops@10.251.50.54 'cd /opt/docker/compose/flash-next-seat && docker compose config >/dev/null && docker compose up -d'
The .env lives on the host and is never committed. Copy .env.example, set
API_KEY, and read the FIRST-BOOT annotations before changing anything else.
Architecture, briefly
Four ideas, and three of them shape the serving config:
- GDN + QSA. 36 of 48 layers use Gated DeltaNet (linear attention) to compress
history; every fourth layer uses Qwen Sparse Attention for long-range retrieval.
This is why KV is cheap at depth and why
--mamba-cache-dtype float32matters. - N-gram embedding. The 51B lookup table that this seat offloads. Qwen's own framing: capacity with almost no per-token compute.
- Gated residual / hyper-connections. Four residual branches; excluded from quantization in this checkpoint.
- MTP head. Present, preserved byte-identically, and in use at k=3.
Why this checkpoint, and the two traps in front of it
Chosen for the activation axis: orcarouter's build is weight-only on both halves —
config_groups gives W8 float for attention/dense and W4 float for the experts, with
input_activations: null on each. The displaced dealignai build is ModelOpt W4A4
(4-bit activations), the long-context degradation mode. Same author as the gen seat.
It did not load out of the box, and there were two independent config-level blockers. Both are recorded here because each looks like a capability gap and neither is one.
Trap 1 — the PLE loader (and the claim we had wrong)
vLLM picks the PLE table's format in Qwen4ExpPLEEmbeddingMethod.from_quant_config:
1. ple_embedding_dtype == "float8_e4m3fn" -> FP8 method <-- BEFORE any type check
2. quant_config is None -> unquantized
3. ModelOptMixedPrecisionConfig -> FP8 / unquantized
4. ModelOptQuantConfigBase + excluded -> unquantized
5. not isinstance(quant_config, Fp8Config)-> NotImplementedError
⚠ This README previously said an FP8 PLE without the declaration is disqualifying, and
that compressed-tensors needs a vLLM source patch. Both were wrong (corrected 2026-09-14;
see the quantization playbook's superseded-claims table). Branch 1 is unconditional, and
the NotImplementedError is scoped to the PLE path only — experts and dense layers of a
compressed-tensors build load through vLLM's ordinary compressed-tensors paths. So declaring
an FP8 PLE bypasses the blocker on stock mainline.
orcarouter ships a bf16 PLE, so the honest fix was to make the declaration true: convert the table to FP8, then declare it. Its 128 PLE tensors sit in exactly one shard file with nothing else in it, which makes that a clean, cheap rewrite.
⚠ Declare only what is true. gorbatjovy/...-NVFP4-plefp8 ships an FP8 table with no
declaration and dies on ngram_embedding.weight_scale; declaring FP8 over a bf16 table is
that same failure in reverse. The declaration is a claim about the bytes, not a switch.
Trap 2 — Invalid layer_type qwen_sparse_attention
orcarouter labels its 12 QSA layers qwen_sparse_attention. vLLM accepts only
linear_attention and full_attention, and selects QSA within full_attention when
indexer_n_heads is present. The fix is renaming the 12 entries.
⚠⚠ Check indexer_n_heads before renaming. Without it the rename silently selects plain
Qwen3NextAttention instead of Qwen4ExpQSAAttention — a subtly wrong model that loads,
serves, and passes a healthcheck. Verified indexer_n_heads == 4 in both this checkpoint and
the dealignai one, along with every other indexer/QSA key, before touching it.
The conversion, and what it cost
Global amax 0.0894 with a per-shard outlier ratio of only 1.66x, so the single global
scale this method uses is well-conditioned here. The scale is chosen exactly representable
in bf16 (2.002716e-04) so no scale-rounding error stacks on the quantization error; amax
maps to 446.17 of 448, so nothing clips. Round-trip 2.655% RMS relative, 0.002% underflow,
zero saturation — and the same FP8-PLE treatment dealignai already shipped, so it is not a
regression against the seat it replaced. weight_scale is written BF16 [1] to match the
published format. MTP head (31 tensors) and the vision tower carry through untouched.
⚠⚠ THERE IS NO LOCAL ROLLBACK. The dealignai checkpoint was deleted on operator
instruction 2026-09-14 (125 GiB reclaimed). Reverting this seat now means re-downloading
126 GiB from dealignai/Qwen3.8-Flash-Next-ABLITERATED-NVFP4, not flipping two .env
keys. The .env backup (.env.bak-preorca-20260914-023408) still names the old paths, but
those paths no longer exist — treat it as a record of the old settings, not a working revert.
⚠ Still unmeasured, and now unbacked: a controlled quality A/B against dealignai — which was
the entire reason for the swap, and whose reference arm is gone — and a deep-prefill probe at
262K on this checkpoint. The 170 GiB pristine qwen38-flash-next-orcarouter-nvfp4 download is
retained; it is what lets the PLE conversion be redone without re-fetching.
Rejected alternatives, for the record: nvidia/…-NVFP4 is the cleanest ModelOpt build but is
not abliterated; lovedheart/…-Pruned-RTXPRO-6000 prunes to 448 of 512 experts;
windowsxp811203/…-Abliterated-NVFP4 stores its 95 GiB PLE as a single malformed
ple_embedding.shard_.weight instead of 128 ngram_embedding.shard_N.weight and has never
been served by its own author.
Why MTP is ON at k=3 (reversing this seat's original default)
This seat shipped with speculative decoding off, citing vLLM's recipe: on 4xH100 at TP=4 that recipe measured MTP worse at every concurrency (8-36% lower throughput, 32-173% higher per-token latency, ~36% acceptance) and says do not default it on. Open #55357 reports episodic 0% acceptance with repetition collapse inside thinking blocks.
Measured here, that inverted. The campaign in services/flash-next-mtp-bench/ found MTP
a win at every k and every concurrency tested on one Blackwell card (+29/41/27% at k=1,
+42/52/38% at k=2, +52/51/34% at k=3 across conc 1/4/8). k=3 is deployed because this is a
single-user fleet and conc=1 dominates.
On the current orcarouter checkpoint, measured 2026-09-14: 60.4% acceptance, mean acceptance length 2.81 (per-position 80.6 / 60.8 / 40.8%), warm decode median 167.5 tok/s at conc=1 (n=5, spread 12.2%).
⚠ MTP costs KV. The draft head adds ~5.08 GiB of weights and raises per-token KV cost
~16%; FN_KV_CACHE_MEMORY was cut 14 -> 10 GiB for it. At 14 GiB the engine OOMs at init
with MTP on. If it OOMs, drop to 8589934592.
⚠ The recipe's numbers are someone else's hardware, and so are ours to anyone else. Re-measure on the seat, warm, with repeats — the first decode bench during the reorg read 39 tok/s and that was a cold-boot + contention artifact, not a result.
The upstream situation, as of 2026-09-13
- #53896 — model support. Merged 2026-08-31. In v0.29.0.
- #54371 — UVA PLE-offload and Engram tensor parallelism. Merged
2026-09-09T14:32Z. This is the offload this seat uses. Not in v0.29.0,
which was cut ~6 h earlier; present in
v0.29.1rc0and in any nightly from 2026-09-10 onward. - #53899 — the older, worker-based PLE offload. Open and explicitly paused
in favour of #54371. Do not go back to it. Its whole bug family — the TP=1
startup deadlock (#53960), the
pidfd_getfd/kernel.yama.ptrace_scopegate, the shared-CUDA-event race under async scheduling, and silently one-step-stale PLE outputs under CUDA graphs — came from the separate worker process and the CUDA-IPC row transfer that the UVA path does not have.
Open issues worth knowing about on SM120, none of them blocking:
| Issue | What it does | Our exposure |
|---|---|---|
| #54173 | CUBLAS internal error / illegal memory access in the GDN path with prefix caching | We enable prefix caching. FN_PREFIX_CACHING= is the one-line rollback. |
| #54764 | PLE short-conv batched prefill pads every request to the batch-max query length | Why --max-num-batched-tokens is 8192, not 16384 |
| #54919 | Long prefill starves active decode for 3–7 minutes | Why context starts at 128K |
| #54521 | Greedy decoding non-deterministic from persistent_topk in prefill |
Affects any A/B on this seat — establish a noise floor before comparing |
| #54426 | fp8_e4m3 KV on the QSA path is an unmerged RFC | Why --kv-cache-dtype is not set to fp8 here |
Raising context
128K is a starting value, not a measured one. Before raising it, bisect with a
non-repeating prompt — a repeated one hashes to cached blocks and never
prefills deep, so it proves nothing. The stacks/mog-sec README records this the
hard way: three successive context cuts all sized the KV pool while the crashes
were governed by processing depth, which is a different number.
The point of a ceiling is the refusal. Below it the seat serves; above it vLLM returns a clean 400 naming the limit, instead of the engine dying and taking every in-flight request with it.
Not done yet
- Pin
--kv-cache-memoryin bytes from the first boot's budget line, replacing the 0.90 ratio. Same discipline asstacks/mog-secandstacks/erp-seat. - Gateway wiring is deliberately absent. Pointing any LiteLLM alias at this
seat — in particular displacing
gen/summarizer/classifier, which is the long-term intent recorded in henge item 49 — changes what every existing caller receives and is the operator's call, not a deploy-time default.