Read from docker inspect, nvidia-smi and each model's own config.json rather than from compose files or the gateway config, so the document records what is deployed rather than what was intended. Findings worth naming: - char-rp drift. The LiteLLM config documents char-rp as G4-MeroMero-v2-31B on ana-ml2 GPU 0; :8016 actually serves gemma4-26b-a4b-it-nvfp4, a 26B 30-layer 128-expert stock Gemma-4, on fv-ml1 GPU 1. Host renaming explains part of it, but 31B MeroMero-v2 and 26B stock gemma4 are different models and the comment block carries sampler defaults keyed to the model it names. - Two gateway aliases are dead: erp-tune-v2 (:8098) and gemma4-26b-a4b-it-base (:8099). Nothing is listening on either; callers get a connection failure rather than a clear retirement error. - Three different speculative-decoding methods coexist -- dflash k=7 with a separate drafter on mog-sec, qwen3_5_mtp k=3 on gen, mtp k=3 on flash-next. The method string is architecture-specific; copying a --speculative-config between seats without changing it will not work. - Three seats run the floating :latest tag, so what they would pull on recreate is not what is running and was never recorded. - Seven aliases resolve to vllm-gen on :8015, which is why GPU 0 shows draw during ordinary OpenWebUI use -- its auto-titling fires summarizer per turn. Also commits the flash-next-seat MTP k=3 change made today: compose gains the speculative-config with its measured rationale, and .env.example documents the KV-cache reduction MTP requires (14 GiB OOMs, 10 GiB verified in production).
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 | dealignai/Qwen3.8-Flash-Next-ABLITERATED-NVFP4 @ be794b99… (126.0 GiB) |
| On the card | ~78 GiB of 95.6 GiB — routed experts NVFP4 W4A4, rest at source precision |
| In host RAM | 47.7 GiB pinned, FP8 E4M3, 10 model-plefp8-* shards + per-table scalar scale |
| Context | 131,072 to start (native ceiling 262,144) — see Raising context |
| Speculative decoding | none — see Why MTP is off |
| Gateway wiring | none yet — this seat is not in LiteLLM; gen is untouched |
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 and preserved byte-identically. Deliberately unused.
Why this checkpoint, and the trap that disqualifies most others
vLLM selects the PLE table's weight format from text_config.ple_embedding_dtype,
as the first branch of Qwen4ExpPLEEmbeddingMethod.from_quant_config. This
checkpoint declares "float8_e4m3fn".
A build that ships an FP8 PLE table without that declaration resolves instead
through ModelOpt's *.ple.* exclude to the unquantized method, never registers
the weight_scale parameter, and dies on load with no module or parameter named 'ngram_embedding.weight_scale'. gorbatjovy/qwen3.8-flash-next-abliterated-NVFP4-plefp8
is exactly this case. Check that field before trying another build.
Chosen over better-liked alternatives because its provenance states protocols and
repeat counts rather than adjectives. From its own qualification-notes.md and
metrics files, kept in the model directory:
- HarmBench, 240 genuinely-harmful behaviours, greedy: 100% compliance at reasoning off / low / xhigh.
- MMLU 82.11% → 81.93% (−0.18 pp) on an identical harness, 2,280 questions.
- GSM8K 97.27% (1283/1319), full set, single-shot, temp 0.6 — inside the stated BF16 reference band 97.12–97.50.
- AIME26 pass@1 98.75% (237/240, SEM 0.61 pp, 30 problems × 8 repeats),
majority@8 100%,
max_tokens130,000, 4.9M completion tokens, stop_rate 99.17%. - Byte-equality audit of unchanged tensors: 1,562 tensors / 118.4 GB compared, all passed, including all 31 MTP tensors.
⚠ Two honest gaps in that evidence. The routed experts are NVFP4 W4A4, and
nobody — including the publisher — has measured this checkpoint at the full 262K
context; AIME26's 130K-token generations are the deepest evidence that exists.
Separately, validate_checkpoint_report.json in the repo describes the earlier
BF16-PLE revision (204 shards / 173.6 GiB), not the published FP8-PLE one.
Rejected alternatives, for the record:
orcarouter/…-Uncensored-NVFP4 is gated (access request pending nothing — not
requested); nvidia/…-NVFP4 is the cleanest ModelOpt MIXED_PRECISION build but is
not abliterated; lovedheart/…-Pruned-RTXPRO-6000 prunes to 448 of 512 experts.
Why MTP is off
Against our house graft-MTP habit, and on purpose.
vLLM's own recipe for this model measured MTP on 4×H100 as worse at every
concurrency tested — 8–36% lower request throughput, 32–173% higher per-token
latency, driven by ~36% acceptance — and says do not enable it by default. Open
issue #55357 reports episodic 0% draft acceptance with repetition collapse
inside thinking blocks. Open #55496 reports ModelOpt MIXED_PRECISION failing
to load FP8_BLOCK_SCALES MTP experts.
Turning it on is two lines in compose.yaml (documented in place). If you do,
measure it here, with repeats, against this seat's own baseline — the numbers
above are someone else's hardware.
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.