Files
esh-pfi-infrastructure/stacks/flash-next-seat/README.md
T
vh 1b5d6ba23a docs(flash-next-seat): dealignai weights deleted — record that no local rollback exists
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.
2026-09-14 02:51:54 -07:00

187 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```bash
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 float32` matters.
- **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.1rc0` and 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_scope` gate,
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-memory` in bytes** from the first boot's budget line, replacing
the 0.90 ratio. Same discipline as `stacks/mog-sec` and `stacks/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.