Skip to main content

memra_engine/
glm_spec.rs

1//! glm5_next T-parallel speculative verify: the acceptance/rollback machinery that turns the
2//! native MTP draft head (`mtp_head_forward_mla_cached`, lane/glm5-mtp-remint) into served
3//! speculative decoding on the HyperConnections trunk.
4//!
5//! THE WALK-REUSE DECISION, stated once and load-bearing everywhere below: the verify of K
6//! drafted tokens is a t=K+1 walk over the SAME trunk, and it rides the BATCHED-DECODE walk's
7//! kernel classes (`hyper_batch_range_decode`, lane/glm53-batched-decode), NOT the prefill /
8//! prime walk. Why:
9//!
10//!   * The batched walk's row-parallel ops are per-row BIT-EXACT vs the isolated t=1 decode
11//!     step at every width in `1..=hyper_batch_cap()` (= `PRIME_MIN_T - 1` = 15, the shexp
12//!     decode-exact knee, measured in `batched-decode-gate/31-KNEE-b16-forced.log`): the hc
13//!     glue is block-per-token, the hc-mix GEMM runs per-row m=1 (`hyper::pre_exact`, the
14//!     lt_ndep law), the MoE FFN's router/experts/shexp are per-row programs below
15//!     `PRIME_MIN_T`, and the lm_head is `matmul_decode_exact`. That per-row exactness is
16//!     what makes spec-vs-plain BYTE IDENTITY achievable at all.
17//!   * The prime walk is a different numeric class: `hyper::pre` batches the mix GEMM
18//!     (cuBLASLt n-dependent reduction), the FFN takes the prefill dispatch
19//!     (`moe_ffn_il_prefill` / grouped GEMMs), and KDA's prefill conv arm reads the chunk
20//!     window instead of the decode ring program. None of those are per-row bit-identical
21//!     to the t=1 decode chain, so a prime-walk verify could never pass a byte-identity
22//!     gate against plain decode.
23//!   * The knee bounds K: K+1 <= 15, i.e. K <= 14 — the DFlash2 probe's K<=7 drafter and
24//!     upstream's 5/7-draft MTP configs fit with margin.
25//!
26//! The ONE difference from `hyper_batch_range_decode`: the batched walk runs B independent
27//! sessions (each row -> its OWN cache at its own single position); the verify walk runs
28//! K+1 SEQUENTIAL positions of ONE session, so the mixers chain state row -> row through the
29//! one cache (KDA: the t=1 recurrent step per row, exactly the serving decode program; MLA:
30//! the t=1 `mla_attn_cached` append+attend per row, so row r attends rows `0..pos0+r+1` —
31//! causal within the drafted block by construction). This is the "hyper rows-walk with
32//! causal verify appends" the batched-decode lane's standing refusals named as missing.
33//!
34//! VERIFY-ROW BATCHING (lane/glm5-verify-batch, 2026-08-30 — the flip re-battery's named
35//! flip condition: "the verify walk must stop paying one plain-step per row", ~24-26 ms/row
36//! measured): `MEMRA_GLM5_VERIFY_BATCH` (default ON) restructures the mixer walk PER LAYER
37//! while keeping the sequential contract exactly where the math demands it. Each KDA layer
38//! runs ONE t=K+1 `kda_core` call — projections/gates/conv batched through the decode-exact
39//! matmul classes (`matmul_rows_exact`; the bf16 tcols twin reads each weight ONCE for all
40//! t rows) with the recurrence SEQUENTIAL INSIDE one `memra_kda_scan_s128` launch (the
41//! in-kernel T-loop over register state IS the chained t=1 program). Each MLA layer runs
42//! ONE t=K+1 `mla_attn_cached_rows_exact` call (per-query causal kpool selection + gathered
43//! attention by construction). Rollback on the batched arm: conv ring = pre-round snapshot
44//! + re-roll(T=keep) over stolen raw rows; ssm = ONE scan replay at T=keep from the
45//! pre-round snapshot over the stolen batched inputs (`kda::KdaRowsStash`). `0` = the
46//! per-row walk below, byte-for-byte — the rollback seam. Per-row byte identity vs the
47//! plain tape is held by this file's standing batteries running the batched arm, plus the
48//! kernel bit-gates in `tests/glm5_verify_batch_gpu.rs`.
49//!
50//! ROLLBACK (the hard part, per the engine survey's upstream reading — vLLM keeps
51//! num_spec+1 KDA state columns and commits the last accepted one; SGLang's ReplaySSM keeps
52//! an input ring and replays the accepted prefix):
53//!
54//!   * KDA recurrent state: SNAPSHOT + SCAN-INPUT REPLAY (lane/glm5-loop-port port 3 —
55//!     the GdnStash/ReplaySSM diet this doc used to name as the follow-up, landed). The
56//!     walk clones the resident ssm state ONCE per layer per round (before row 0) and
57//!     STEALS each row's scan-input buffers (`kda::KdaScanInputs`, ~160 KB/row/layer,
58//!     zero copies — the step allocated them either way); accept-j REBUILDS the state by
59//!     re-issuing rows 0..=j's original t=1 `memra_kda_scan_s128` launches from the
60//!     snapshot (`kda::kda_scan_replay`) — byte-identical to the retired per-row clone
61//!     by construction, since each replay is the very launch that produced it. Full
62//!     accept keeps the resident state, no work. Memory, stated: one glm5_next KDA state
63//!     is 64 heads x 128 x 128 f32 = 4 MiB, x34 KDA layers = 136 MiB per round (was 136
64//!     MiB PER COLUMN — ~0.95 GiB of transient clones at K=7, retired to ~136 MiB + K x
65//!     ~160 KB x 34 of stolen stash). The conv ring stays per-row cloned (288 KiB/row/
66//!     layer, 1.4% of the ssm plane — not worth a replay arm).
67//!   * MLA latent rows: TRUNCATE (append-only, position-addressed): `len = snap + keep`,
68//!     device mirror in lock-step.
69//!   * kpool index planes: the tail ring drains IN-CALL (`mla_kpool_indices`), so pool keys
70//!     over drafted rows may FINALIZE during the verify walk. Rollback clamps
71//!     `index_pools_ready` via `truncate_index_pool_keys` (the clamp the field's own doc
72//!     demands of "the same code that shortens len"); the next call rebuilds keys for the
73//!     re-appended rows from `[ready, len/pool)`. The residency tripwire in
74//!     `mla_kpool_indices` fails LOUDLY if any rewind forgets this — that tripwire is the
75//!     red arm of the rollback gate.
76//!   * MTP draft plane (il = n_trunk): len reset by the loop (LANE.md contract: "rollback =
77//!     plane len reset"), same pool-key clamp.
78//!
79//! Gate: `tests/glm5_tparallel_verify_gpu.rs` — accept-j-then-continue byte identity vs the
80//! never-drafted sequential path for every j in 0..=K, red-proven with a stale-KDA-state
81//! mutation and a pool-key-finalized-past-j mutation; plus end-to-end spec-vs-plain greedy
82//! tape identity at K=1..7 with forced-rejection positions, red-proven by disabling
83//! rollback. The SERVED shape (worker-sized bursts over one [`Glm5SpecSession`], state
84//! carried across burst boundaries, sampled twin, EOS, receipt log red/green) is gated by
85//! `tests/glm5_spec_session_gpu.rs`.
86//!
87//! SAMPLED ACCEPTANCE (landed, lane/glm5-spec-routing 2026-08-30): greedy
88//! longest-matching-prefix below is the byte-deterministic instrument
89//! (greedy-is-the-instrument law). The sampled arm applies memra's existing spec sampled
90//! contract — `spec::SpecSampling` with the rejection-sampling accept walk
91//! (`u_j * q_j(x_j) < p_j(x_j)`, host Philox4x32-10 stream tag 0xFFFF_FFFE via
92//! `spec::host_u01`, residual resampling on the first rejection) — the same contract
93//! `generate_spec_inner2`'s MEMRA_SPEC_TEMP>0 route and the dspark sampled-admission walk
94//! consume. It plugs in at exactly one seam (`glm5_sampled_accept`, the accept rule over
95//! the verify logit rows, with the draft chain drawn from the SAME filtered distribution
96//! the q gather reads); nothing in the walk or the rollback changes. Philox counters live
97//! ON the [`Glm5SpecSession`] so randomness never repeats across serve bursts (the
98//! session-continuity law; burst-split invariance is pinned by `glm5_spec_session_gpu`).
99//! PENALIZED requests (greedy and sampled) refuse UNLESS `MEMRA_SPEC_PENALTY=1`
100//! (lane/spec-exclusions-20260902, doc on [`glm5_spec_penalty_on`]): under the arm every
101//! verify round penalizes a copy of the target rows over the per-row evolving session
102//! window with the host sampler's exact arithmetic and the accept reads only that copy; the
103//! worker's admission keeps penalized requests plain while the arm is dark.
104//!
105//! FR-SPEC VOCAB MASKING (owner addition, 2026-08-30 — the house spec recipe, the q38 way):
106//! the loop consumes the existing `MEMRA_FRSPEC_TRIM` contract, no new flag. The loader
107//! already reaches this head: the trim match in `hybrid.rs` consumes the embedded head
108//! regardless of arch, `frspec_trim_own_head_name(n_trunk)` misses (glm5_next ships no
109//! private MTP lm_head) and the gather falls back to the trunk `output.weight` /
110//! `token_embd.weight` — which for glm5_next is EXACT BY CONTRACT, not merely by tying:
111//! the MTP block projects through the trunk lm_head (LANE.md; the draft gate pins
112//! `shared_head_head.is_none()` untrimmed). With a ranks artifact loaded,
113//! `mtp_head_forward_mla_cached` already projects through the gathered rows (its head is
114//! `shared_head_head.unwrap_or(trunk)`), so the DRAFT logits arrive `[n_ranks]`; this
115//! loop's seam is the remap: every draft argmax is a RANK id and maps through `d2t` back
116//! to the true vocab BEFORE it is drafted, chained (e_tok drives the embedding gather),
117//! or verified. THE VERIFY WALK STAYS FULL-VOCAB AND UNTOUCHED — a trimmed draft can only
118//! change WHICH tokens get drafted, never how they verify; that invariant is the whole
119//! design (q38's measured skipped-remap defect was 0/248 acceptance with every exactness
120//! gate green — silent, which is why the gate below makes it loud). Rank artifacts for
121//! glm5 are an INPUT DEPENDENCY: the corpus mint (SXC pools through GLM's tokenizer,
122//! per traffic class, the q38 plain-text format) is the owner's CPU-only lane; the
123//! self-trim d2t arm needs no external artifact and lands here first.
124//!
125//! PPN (lane/glm5-ppn-verify, 2026-08-30): the verify walk owns its stage split exactly as
126//! the batched decode walk does — `glm5_verify_rows_ppn` mirrors
127//! `decode_step_batch_hyper_ppn` (per-stage engine, per-stage pos_rows, ONE
128//! `[t, streams, n_embd]` boundary payload per cut; row chaining is per-LAYER through the
129//! one cache, so a straight layer-range split preserves it exactly). Rollback restores each
130//! stage's layers through that stage's engine on its stream; the MTP block, its latent
131//! plane and every draft-chain/accept-side op ride the LAST stage's engine
132//! (`glm5_head_engine` — where the loader puts the lm head and `pp::new_cache*` puts the
133//! trailing MTP plane), so the h_seed carrier never bounces devices. Gate:
134//! `glm5-spec-ppn-gate` (the tparallel battery under the split, stages=2 and 3, red-proven;
135//! the cross-device twin is the box arm). Worker admission bounds sharded placements to the
136//! GATED stage set (`glm5_sharded_placement_admits`, worker.rs) — everything else stays
137//! fail-closed by name.
138//!
139//! DRAFT SOURCE SEAM (lane/glm5-dflash-draft-src, 2026-08-30): the session's drafts come
140//! from ONE of two sources, selected at load and pinned for the session — everything from
141//! the verify walk on (accept, rollback, commit, receipts, K policy) is SHARED and
142//! source-blind, which is where the exactness invariant lives (a draft source can only
143//! move acceptance, never output):
144//!
145//!   * `NativeMtp` (existing): the embedded NextN head chains K drafts through
146//!     `mtp_head_forward_mla_cached`; requires `MEMRA_GLM5_MTP=1`.
147//!   * `Dflash2` (`MEMRA_GLM5_DFLASH=<dir-or-hf-spec>`): the pinned
148//!     incoai/GLM-5.3-Flash-DFlash2 block-diffusion drafter (owner holds WRITTEN APPROVAL
149//!     from the DFlash2 owners, 2026-08-30, for use beyond probe/eval). It REUSES the
150//!     shipped q38 DFlash2 machinery verbatim (`DflashDraft`: `ctx_features` ->
151//!     `ingest_ctx` -> `forward_round` -> `dflash2_propose_*`, mask-fill harvest, selector
152//!     walk); the ONE glm5-specific input is the drafter's measured feature contract — the
153//!     STREAM-MEAN (`hc_contract`) of the COMPLETED trunk layer output at the drafter
154//!     config's `target_layer_ids` (plan layers 5,14,24,33,42 on the real artifact), the
155//!     exact definition the probe's `MEMRA_TRACE_LAYER_ROWS` capture seam banked 0.73
156//!     acc@1 / 3.06 tokens-per-cycle against
157//!     (research/glm53-flash-bringup-20260827/dflash2-probe-20260829/RECEIPTS.md). The
158//!     features flow through [`crate::cache::HcTapSink`], a HOST sink filled by the hc
159//!     prime walks and this file's verify walk (host-staged so a ppN split needs no
160//!     cross-device tap plumbing; the drafter itself runs on the HEAD engine, where the
161//!     trunk lm_head it projects through lives). THE NATIVE MTP HEAD IS NOT LOADED for
162//!     this source (the q38 pattern — a full MoE trunk layer of VRAM back); the plan's
163//!     trailing MTP cache plane still allocates (plan-structural, ~`ctx * latent_width`
164//!     f32 per declared block — named cost, not forked). Sampled route: the drafter's
165//!     selector proposal records its true q (`DsparkDraftSample::Selector`) and the accept
166//!     rides the SAME `dspark_accept_sampled` rejection walk the q38 serve route ships,
167//!     with this session's Philox counters (`uctr` selector/accept draws, `sctr`
168//!     bonus/residual) so randomness never repeats across bursts. K is bounded by the
169//!     drafter block (K <= block_size-1 = 7): the worker clamps, the burst refuses loudly.
170//!     Selection receipt: boot logs `[glm5-spec] draft source = native-mtp` or
171//!     `[glm5-spec] draft source = dflash2 @ <sha8>`; both flags off = plain serving
172//!     (fail-closed warn). `MEMRA_GLM5_DFLASH_GATE_RED=tap-shift` is a GATE INSTRUMENT
173//!     (never a serving flag): it shifts every tap layer +1 to red-prove that a wrong
174//!     feature input collapses acceptance while the tape stays byte-identical.
175//!
176//! SERVING EXPOSURE (lane/glm5-spec-routing, 2026-08-30): `MEMRA_GLM5_SPEC` (default OFF,
177//! FLAGS.md row) is the ONE master flag — it routes `generate_spec` here for hc trunks
178//! with a loaded MTP head AND arms the worker route (`glm5_spec_capable` +
179//! `step_glm5_spec` driving [`Glm5SpecSession`] bursts). OFF = the named `refuse_hyper`
180//! refusal and zero `[glm5-spec]` log lines, byte-identical serving. The MTP_SPEC
181//! capability manifest remains deliberately UNEXTENDED — worker `mtp_spec_capable` stays
182//! false for glm5_next plans; the serving capability lives in its OWN manifest
183//! (`GLM5_SPEC`, execution_manifest.rs) whose table names exactly the glm5_next class, and
184//! a SEALED production bundle still fails closed until it banks a `glm5-spec.v1` rewrite
185//! receipt (the real-artifact qualification lane's job).
186
187use crate::Engine;
188use crate::cache::{Cache, HcTapSink};
189use crate::dflash::{DflashDraft, DflashKv, DsparkDraftSample};
190use crate::forward::argmax;
191use crate::hybrid::{HybridModel, Mixer};
192use crate::spec::SpecSampling;
193use crate::spec_phase::{
194    ProfClock, SPEC_PROF_ROUNDS, SpecFirstTokenProf, SpecPhaseNs, SpecRoundProf, SpecRoundsLog,
195    V_SEQ_ROWS, spec_prof_on,
196};
197use cudarc::driver::CudaSlice;
198
199type Res<T> = Result<T, Box<dyn std::error::Error>>;
200/// Round-cadence commit hook of `glm5_spec_session_burst_streamed` (lane/b200-spec-ttft-
201/// 20260902): called with every newly committed slice of a burst, in order, disjoint.
202pub type CommitHook<'a> = &'a mut dyn FnMut(&[u32]);
203
204/// `MEMRA_GLM5_SPEC=1` routes `generate_spec` to the glm5 T-parallel loop. Default OFF:
205/// unset/0 keeps the standing `refuse_hyper` refusal, so serving is byte-identical to the
206/// pre-lane binary. Read once (worker chunk policies read their flags the same way).
207pub fn glm5_spec_on() -> bool {
208    use std::sync::OnceLock;
209    static ON: OnceLock<bool> = OnceLock::new();
210    *ON.get_or_init(|| std::env::var("MEMRA_GLM5_SPEC").as_deref() == Ok("1"))
211}
212
213// Per-burst phase attribution moved to `crate::spec_phase` (lane/glm5-extract-general):
214// the draft/verify/accept/roll/maint split is spec-family-generic. This loop consumes
215// `MEMRA_SPEC_TRACE` (glm5 alias `MEMRA_GLM5_SPEC_TRACE` stays honored) and passes its
216// own `[glm5-phase]` / `[glm5-phase-v]` tags so every banked receipt keeps its shape.
217
218/// `MEMRA_GLM5_VERIFY_BATCH` (default ON, lane/glm5-verify-batch): the per-LAYER batched
219/// mixer walk — one t=K+1 KDA call per layer (projections/conv/gates batched through the
220/// decode-exact classes, the recurrence sequential INSIDE one scan launch) and one
221/// t=K+1 rows-exact MLA call per layer, replacing the per-row mixer loop. `0` restores
222/// the per-row walk byte-for-byte — the rollback seam. Deliberate default (new-flags
223/// law): the walk only exists behind `MEMRA_GLM5_SPEC` (default OFF in prod), per-row
224/// byte identity is bit-gated on the rig (`glm5_tparallel_verify_gpu` +
225/// `glm5_verify_batch_gpu`), and the box re-battery A/Bs this seam in one build. Read
226/// PER CALL (the `MEMRA_KDA_FUSED_PROJ` per-call precedent) so gates drive both arms in
227/// one process; one env read per verify walk.
228pub fn glm5_verify_batch_on() -> bool {
229    std::env::var("MEMRA_GLM5_VERIFY_BATCH").as_deref() != Ok("0")
230}
231
232/// `MEMRA_GLM5_DRAFT_TAPS_DEVICE` (DEFAULT OFF; `=1` arms it; lane/spec-route-depth-20260902):
233/// the DEVICE-RESIDENT drafter
234/// prime. Boot D on the 2x B200 pair (this arm) against boot C (host taps), TTFT s at
235/// 4k / 42k / 128k / 256k: 4.89 / 16.84 / 56.62 / 129.42 vs 5.32 / 21.23 / 72.90 /
236/// 162.26, drafter prime ms 17.9 / 59.1 / 180.7 / 362.4 vs 91.4 / 561.6 / 4430.8 /
237/// 8868.4, steady rounds and decode-after unchanged (FLAGS row, receipt path there). The trunk prime stays the ONE whole-prompt call (the
238/// chunked arm's per-range calls re-entered the PP-2 microchunk geometry and made the trunk
239/// prime itself far slower — boot B); the tap rows never leave the device: the walk stages
240/// each range's five contracted tap planes on the writing stage's device (a chunk-sized
241/// ring, `HcTapSink::new_device_staged_at`), and at the range boundary the prime's own loop
242/// hands the range to `glm5_taps_range_done`, which interleaves the planes into the fc
243/// layout with 2D device copies (a peer copy first for planes written on another stage),
244/// runs `ctx_features` + `ingest_ctx` at the range width (4096 rows: the batched GEMM
245/// class), and appends to the drafter KV. No DtoH in the prime, no HtoD in the drafter
246/// prime; the eager arm's 7.5 s of pageable HtoD at 256k (boot B's split: h2d 7505.8 of
247/// 8870 ms) goes away outright, and its `tap_dtoh` with it. Read per session creation
248/// (a gate flips it in-process). The host-tap arm (`MEMRA_GLM5_DRAFT_PRIME_LAZY`) is the
249/// default and is reachable whenever this is unset.
250///
251/// DEFAULT OFF DELIBERATELY, and the flip is blocked on two named defects that a review of
252/// the ON default found. Both are properties of the device-staged ring, so neither can bite
253/// while this is unset. FIRST, the `MEMRA_B200_PRIME_V2` arm-2 branch
254/// (`prime_cache_hyper_pp2_pipelined`) never calls
255/// `glm5_taps_range_begin`/`glm5_taps_range_done`, and hands each stage-chunk an ABSOLUTE
256/// base, so from the second range on `glm5_hc_tap_slot` slices past the `sink.t * h` slot
257/// buffer: `slice_mut` panics and takes the worker with it. SECOND, the same-`ordinal`
258/// ingest reads the ring `buf` from the head engine's stream while the next range rewrites
259/// it on the stage engine's stream, with cudarc implicit event tracking disabled in this
260/// repo, so the tap planes can tear into silently wrong drafter ctx KV; verify still
261/// arbitrates the served tape, so the tape-identity gates stay green and would NOT catch
262/// it. Flipping the default needs both fixed AND a gate that fails on each before it
263/// passes.
264pub fn glm5_draft_taps_device_on() -> bool {
265    std::env::var("MEMRA_GLM5_DRAFT_TAPS_DEVICE").as_deref() == Ok("1")
266}
267
268/// The device-resident drafter prime's in-flight state (doc on
269/// [`glm5_draft_taps_device_on`]), carried type-erased on the prime's tap sink
270/// (`HcTapSink::ingest_state`) so the prime's range loop can hand it each range.
271struct Glm5DraftPrimeInflight {
272    kv: DflashKv,
273    taps: Vec<usize>,
274    n_embd: usize,
275    /// Rows the sink ring covers (the largest range the schedule emits).
276    ring: usize,
277    /// Head-device staging for planes written on another stage (lazily sized `ring x h`).
278    stage: Vec<Option<CudaSlice<f32>>>,
279    /// The interleaved `[ring, n_taps * h]` fc input on the head device (lazy).
280    rows_dev: Option<CudaSlice<f32>>,
281    prof_on: bool,
282    copy_ms: f64,
283    feat_ms: f64,
284    kv_ms: f64,
285    chunks: usize,
286}
287
288/// `MEMRA_GLM5_DRAFT_PRIME_LAZY` (default OFF, lane/spec-route-depth-20260902): `=1`
289/// restores the pre-lane placement of the EAGER arm's drafter ingest — inside round 1 of
290/// the first burst, i.e. AFTER the prime's anchor token has been emitted under the
291/// round-cadence door. Default (unset): the ingest runs at session creation, before the
292/// session is returned and before any token is emitted. WHY THE FLIP: the 2x B200 pair's
293/// boot A (`MEMRA_SPEC_PROF=1`, main + PR #101) measured the round-0 wall at 0.63 / 4.5 /
294/// 8.9 s for 42k / 128k / 256k prompts (about 35 us per prompt token, the eager ingest)
295/// while every later round sits at 55-64 ms; with the anchor already streamed, that one
296/// round lands INSIDE decode, which is the bimodal "15.4 vs 32.1 tok/s" the pair saw
297/// (256 tokens over 8.9 s + 255 rounds/37 tok/s = 16 tok/s; without the stall, 37). The
298/// work is identical either way (same rows, same GEMMs, same KV bytes: the KV is
299/// position-addressed and nothing touches it between creation and round 1); only WHEN it
300/// runs moves, from the second token's latency to TTFT. Read per session creation.
301pub fn glm5_draft_prime_lazy_on() -> bool {
302    std::env::var("MEMRA_GLM5_DRAFT_PRIME_LAZY").as_deref() == Ok("1")
303}
304
305/// Drafter ctx KV bytes at `cap` rows (the `DflashKv::new` geometry), for the profile.
306fn dflash_kv_bytes(cfg: &crate::dflash::DflashCfg, cap: usize) -> usize {
307    2 * cfg.n_layer * (cap + cfg.block_size) * cfg.n_kv * cfg.head_dim * std::mem::size_of::<f32>()
308}
309
310/// `MEMRA_GLM5_SPEC_PREFIX` (default OFF, lane/glm5-prefix-latent2 2026-09-01): the glm5
311/// spec x prefix-cache interplay — BOTH sides, one flag (the `MEMRA_DSPARK_PREFIX_RESTORE`
312/// precedent): (capture) DFlash2-source sessions take a prompt-boundary capture at creation
313/// for the worker's deferred prefix publication, and (restore) the worker may convert a
314/// prefix hit into `glm5_spec_session_from_restored` instead of demoting it to the plain
315/// route. Requires `MEMRA_PREFIX_LATENT=1` too — a capture the worker's publisher would
316/// refuse (latent entries need the plane flag) is pure waste, so this predicate ANDs both
317/// env reads. DEFAULT OFF BY DESIGN (new-flags law): the restored-session program is
318/// unmeasured until the box battery banks restored-vs-cold byte identity on the
319/// continuation; unset restores the pre-lane posture exactly (spec sessions never capture,
320/// hits demote to plain). Read once per process.
321pub fn glm5_spec_prefix_on() -> bool {
322    use std::sync::OnceLock;
323    static ON: OnceLock<bool> = OnceLock::new();
324    *ON.get_or_init(|| {
325        let sp = std::env::var("MEMRA_GLM5_SPEC_PREFIX").as_deref() == Ok("1");
326        let pl = std::env::var("MEMRA_PREFIX_LATENT").as_deref() == Ok("1");
327        if sp && !pl {
328            // A mis-built recipe would otherwise only surface through the battery's
329            // receipt gates (PR #96 review round 2, minor) — say it at first read.
330            eprintln!(
331                "[glm5-spec] MEMRA_GLM5_SPEC_PREFIX=1 is INERT: it requires \
332                 MEMRA_PREFIX_LATENT=1 (latent entries could never publish without it)"
333            );
334        }
335        sp && pl
336    })
337}
338
339/// `MEMRA_GLM5_SPEC_FULLCOVER` (default OFF, lane/glm5-fullcover-spec-route 2026-09-02):
340/// admit the glm5 spec route on a FULL-COVER prefix hit, i.e. a hit whose restored prefix
341/// already covers the whole prompt so there is no suffix left to prime.
342///
343/// WHY IT EXISTS (memra#74): the parent lane refused an empty suffix on the premise that
344/// "the plain boundary-logits resume is faster than any prime". That is true of PREFILL and
345/// says nothing about DECODE: with the drafter left un-armed the whole generation then ran
346/// at plain speed. Measured on the live glm5 box 2026-09-02, same minute and vantage,
347/// vendor-default sampled, 67-token prompt, 512 max_tokens: repeated (full-cover hit)
348/// 29.46 s wall median / 30.8 tok/s decode vs fresh-nonce (cold, route=spec) 57.75 / 69.7.
349/// A cache hit cost the customer half the decode speed on that shape.
350///
351/// WHY IT IS WELL-FORMED: with an empty suffix the restored session's state is exactly a
352/// cold session's at the same boundary: trunk cache at `fed.len()`, drafter ctx KV at
353/// `fed.len()` (`DflashKv::from_tail`, the same rows a cold prime's tap ingest would have
354/// produced), no pending tap rows, and the anchor drawn from the ENTRY's boundary logits by
355/// the same rule the cold burst applies to its own first token. The Dflash2 round invariant
356/// `kv.len == cache.pos` therefore holds at round 1 with `pending` empty. This is the same
357/// full-cover shape the MTP restore has served since lane/spec-cache
358/// (`spec_restore_refusal`: a full-cover hit is admitted whenever the entry carries its
359/// boundary hidden + logits).
360///
361/// DEFAULT OFF BY DESIGN (new-flags law): the arm has NO GPU receipt yet. Byte identity of
362/// the full-cover restored tape against plain decode is GATE 13 of
363/// `glm5_dflash_session_gpu` (`MEMRA_GLM5_SPEC_FULLCOVER=1`), and the serving win needs the
364/// repeated-prompt cell on the glm5 box. Unset restores the pre-lane posture exactly
365/// (full-cover hits serve plain, now with `reason=full-cover-hit` on the route line).
366/// Read PER CALL (the `MEMRA_KDA_FUSED_PROJ` / `MEMRA_GLM5_VERIFY_BATCH` precedent) so one
367/// gate process can drive both arms. Rollback seam: unset.
368pub fn glm5_spec_fullcover_on() -> bool {
369    std::env::var("MEMRA_GLM5_SPEC_FULLCOVER").as_deref() == Ok("1")
370}
371
372/// `MEMRA_GLM5_SPEC_TP` (default OFF, lane/glm5-composition 2026-09-01): admit glm5 spec
373/// SESSIONS on a `MEMRA_GLM5_TP`-armed model. DEFAULT OFF BY DESIGN (new-flags law): the
374/// composition's verify/rollback wiring is rig-gated for correctness (per-rank KDA
375/// snapshot/replay, per-replica MLA latent truncation — `glm5-tp-gate` arms S*), but it has
376/// ZERO real-artifact receipts and the TP serving wiring is still the named box increment;
377/// an unmeasured composition does not default ON. `=1` lifts ONLY the session co-refusal —
378/// every other admission law holds (draft source required, batched verify walk required:
379/// the per-row rollback seam carries no TP arm and refuses by name). Read per session
380/// creation. Rollback seam: unset (the co-refusal is restored verbatim).
381pub fn glm5_spec_tp_on() -> bool {
382    std::env::var("MEMRA_GLM5_SPEC_TP").as_deref() == Ok("1")
383}
384
385/// `MEMRA_SPEC_PENALTY` (default OFF, lane/spec-exclusions-20260902): admit PENALIZED
386/// requests (repetition / frequency / presence, greedy AND sampled) to the glm5 spec route.
387/// Pre-lane every penalized request served plain (`reason=penalized`), which on agent
388/// harnesses that ship a vendor-default `presence_penalty` was the whole route's 1.48x lost
389/// on the most common request shape there is. THE ARM: every verify round penalizes a COPY of
390/// the target's verify rows on device over the per-row evolving window (`penalize_logits_rows_inc`
391/// — row r sees the session window ++ the drafts before r, exactly the plain sampler's history
392/// at that position), and the accept reads only that copy: greedy argmaxes it, the sampled
393/// walk takes its p from it, the bonus and the residual draw from it; the anchor is drawn the
394/// same way from the prime's boundary row. The DRAFT stays unpenalized (it only proposes;
395/// rejection sampling is unbiased for any proposal). NUMERIC CLASS: the device pass is the
396/// host sampler's arithmetic bit for bit (`keskar_penalize_rn`, gated by
397/// `gpu_device_penalties_are_bit_identical_to_the_host_sampler`), so greedy+penalties keeps
398/// the spec-vs-plain tape identity (gate 15) and sampled+penalties keeps the route's
399/// distribution-exact claim: the accepted stream equals the plain penalized sampler's
400/// distribution. DEFAULT OFF BY DESIGN (new-flags law): the arm has rig receipts only; the
401/// box battery (sampled vendor-default + penalties, 8-turn twin) flips it. Unset = the
402/// pre-lane refusal verbatim (the worker keeps penalized requests plain, by name). Read per
403/// session creation (the `MEMRA_GLM5_SPEC_FULLCOVER` precedent) so one gate process drives
404/// both arms.
405pub fn glm5_spec_penalty_on() -> bool {
406    std::env::var("MEMRA_SPEC_PENALTY").as_deref() == Ok("1")
407}
408
409/// `MEMRA_SPEC_WARM` (default OFF, lane/spec-exclusions-20260902): admit a prefix-hit
410/// CARRIER whose entry carries NO usable drafter tail (`reason=no-drafter-tail`: the entry
411/// was published by a plain session — a K-shed turn, a constrained or vision turn — or its
412/// tail did not cover the drafter window) by re-arming the drafter COLD at the restored
413/// boundary (`DflashKv::new_cold_at`): the trunk restores, the drafter starts with an empty
414/// context at the right absolute position and fills it from the suffix prime's taps and every
415/// committed round. Rows below the floor are never attended (the clipped round attention's
416/// floor arm), so the drafter sees exactly the program a shorter prompt runs. Can only move
417/// ACCEPTANCE, never output (verify arbitrates; gate 18 pins the restored tape against plain
418/// decode). Lives UNDER `MEMRA_GLM5_SPEC_PREFIX` (x `MEMRA_PREFIX_LATENT`): without the restore
419/// door there is no carrier to warm. DEFAULT OFF BY DESIGN: the acceptance cost of the empty
420/// context on the first rounds is unmeasured on the real artifact. Read per request.
421pub fn glm5_spec_warm_on() -> bool {
422    std::env::var("MEMRA_SPEC_WARM").as_deref() == Ok("1")
423}
424
425/// The penalty arm's per-session config (doc on [`glm5_spec_penalty_on`]): the request's
426/// Keskar coefficients and window, INDEPENDENT of the sampling regime — a greedy request
427/// with penalties admits too, which is why this is not folded into `Option<SpecSampling>`
428/// (whose `None` means greedy everywhere else on the route).
429#[derive(Clone, Copy, Debug)]
430pub struct Glm5Penalty {
431    last_n: usize,
432    rep: f32,
433    freq: f32,
434    present: f32,
435}
436
437impl Glm5Penalty {
438    /// `Some` iff the request carries a non-identity penalty with an armed window — THE
439    /// `pen_on` predicate (one definition, `SpecSampling::pen_on`).
440    pub fn of(sp: &SpecSampling) -> Option<Self> {
441        sp.pen_on().then_some(Self {
442            last_n: sp.penalty_last_n,
443            rep: sp.penalty_repeat,
444            freq: sp.penalty_freq,
445            present: sp.penalty_present,
446        })
447    }
448    /// The device window: `penalty_last_n` under the `PEN_WINDOW_MAX` cost bound — the same
449    /// trim the dspark accept walk and the boundary draw apply.
450    fn win(&self) -> usize {
451        self.last_n.min(crate::spec::PEN_WINDOW_MAX)
452    }
453}
454
455/// Print-once latch for the penalty arm's engagement line (one per process).
456static PENALTY_ARM_ANNOUNCED: std::sync::atomic::AtomicBool =
457    std::sync::atomic::AtomicBool::new(false);
458
459/// The penalty admission law, shared by the cold and restored session constructors: a
460/// penalized request opens a session ONLY under `MEMRA_SPEC_PENALTY=1`; otherwise it refuses
461/// loudly (worker admission owns the exclusion and keeps such requests plain — silently
462/// dropping a request's penalties is the failure class the refusal prevents). Logs the
463/// engagement receipt once per process on the first penalized session that opens.
464fn glm5_penalty_admit(sampling: Option<&SpecSampling>) -> Res<Option<Glm5Penalty>> {
465    let Some(pen) = sampling.and_then(Glm5Penalty::of) else {
466        return Ok(None);
467    };
468    if !glm5_spec_penalty_on() {
469        return Err(
470            "glm5 spec penalty arm is DARK (MEMRA_SPEC_PENALTY unset): penalized requests serve \
471             on the plain path (worker admission owns the exclusion; silently dropping the \
472             request's penalties is the failure class this refusal prevents)"
473                .into(),
474        );
475    }
476    if !PENALTY_ARM_ANNOUNCED.swap(true, std::sync::atomic::Ordering::AcqRel) {
477        eprintln!(
478            "[glm5-spec] penalty arm ENGAGED (MEMRA_SPEC_PENALTY=1): verify rows penalized on \
479             device over the session window (rep={} freq={} present={} last_n={}); printed \
480             once per process, every penalized request carries penalized=1 on its route line",
481            pen.rep, pen.freq, pen.present, pen.last_n,
482        );
483    }
484    Ok(Some(pen))
485}
486
487/// Seed the session's penalty window from its committed prompt (`spec::pen_window_seed`, one
488/// definition of "the window" across every spec route). Empty with penalties off.
489fn glm5_pen_window_seed(pen: Option<&Glm5Penalty>, committed: &[u32]) -> Vec<u32> {
490    match pen {
491        Some(p) => crate::spec::pen_window_seed(&[], committed, p.last_n),
492        None => Vec::new(),
493    }
494}
495
496/// The session's FIRST token from a host boundary-logits row (the prime's, or the entry's on
497/// a full-cover restore) — the plain route's own first-token rule, regime by regime:
498///   * sampled: `sample_boundary_token` (penalize over the window, filter, Gumbel at the
499///     session's Philox counter) — with the window it was always handed empty before;
500///   * greedy + penalties: the plain sampler's `sample` is penalize-then-argmax, so the row
501///     goes to the device, takes the one penalty pass (`penalize_logits`, the same bits the
502///     host produces), and the device argmax (host tie-break contract) picks;
503///   * greedy: the host argmax, byte for byte the pre-lane literal.
504#[allow(clippy::too_many_arguments)]
505// allow: the three regimes' inputs, listed rather than bundled, so the OFF arm reads as the
506// pre-lane literal at the call site
507fn glm5_anchor(
508    eh: &Engine,
509    logits: &[f32],
510    sampling: Option<&SpecSampling>,
511    pen: Option<&Glm5Penalty>,
512    pen_hist: &[u32],
513    sctr: &mut u32,
514    site: &str,
515) -> Res<u32> {
516    match (sampling, pen) {
517        (Some(sp), _) => crate::spec::sample_boundary_token(eh, logits, sp, pen_hist, sctr, site),
518        (None, Some(p)) => {
519            let n = logits.len();
520            let mut col = eh.htod(logits)?;
521            let w0 = pen_hist.len().saturating_sub(p.win());
522            let hist = &pen_hist[w0..];
523            if !hist.is_empty() {
524                let hd = eh.htod_u32_v(hist)?;
525                eh.penalize_logits(&mut col, &hd, hist.len(), p.rep, p.freq, p.present, n)?;
526            }
527            let td = eh.argmax_token_device(&col, n)?;
528            crate::spec::guard_vocab_token(
529                eh.dtoh_u32_one(&td)?,
530                n,
531                &format!("glm5 penalized greedy anchor ({site})"),
532            )
533        }
534        (None, None) => Ok(argmax(logits) as u32),
535    }
536}
537
538/// `MEMRA_SPEC_PMIN`, honored by the glm5 loop (loop-port 2 — the step37 shipping family,
539/// `MEMRA_SPEC_PMIN=0.5 MEMRA_SPEC_PMIN0=1` is what step37 serves; NO new flag): stop the
540/// draft chain early when the drafter's confidence in its own pick drops below p_min.
541/// Native chain: p = the head's softmax confidence in its pick (the spec.rs `g_p`
542/// statistic, `prob_of_token_device`). DFlash2: q = the selector's recorded per-slot
543/// candidate-set confidence (`q_chosen`; T=1 twin on the greedy walk) — the owner's
544/// "take only high confidence offers" tau-slot form, truncated PRE-verify.
545/// Unset/0 = OFF (today's rounds, byte-identical). The VALUE is a per-model measurement
546/// (spec.rs bank: q27 PMIN=0.3 was -1.9% on one pack; step37 ships 0.5) — the box-B tau
547/// ladder prices glm5's.
548pub(crate) fn glm5_pmin() -> f32 {
549    use std::sync::OnceLock;
550    static P: OnceLock<f32> = OnceLock::new();
551    *P.get_or_init(|| {
552        std::env::var("MEMRA_SPEC_PMIN")
553            .ok()
554            .and_then(|v| v.parse().ok())
555            .unwrap_or(0.0)
556    })
557}
558
559/// `MEMRA_SPEC_PMIN0=1` (llama.cpp's draft gating, vendored via spec.rs): the p-min gate
560/// applies at slot 0 too, so a low-confidence round drafts NOTHING and the verify batch is
561/// just the anchor row — m=1 = a plain decode step. Always legal for glm5 (the anchor row
562/// exists every round). "llama's 35B win rides exactly this — draft acceptance 76% at mean
563/// len 2.5 because unpredictable stretches never pay draft+verify overhead" (spec.rs).
564pub(crate) fn glm5_pmin0() -> bool {
565    use std::sync::OnceLock;
566    static P: OnceLock<bool> = OnceLock::new();
567    *P.get_or_init(|| std::env::var("MEMRA_SPEC_PMIN0").as_deref() == Ok("1"))
568}
569
570/// MEMRA_SPEC_PMIN break semantics — hoisted to the shared K-policy surface
571/// ([`crate::spec::spec_conf_keep`], lane/glm5-extract-general); re-exported here so the
572/// glm5 gates and call sites keep their name.
573pub use crate::spec::spec_conf_keep as glm5_conf_keep;
574
575/// The loaded DFlash2 drafter (module doc, DRAFT SOURCE SEAM): the model-level half of the
576/// `Dflash2` draft source — weights loaded ONCE per model on the head engine (`hybrid.rs`,
577/// `MEMRA_GLM5_DFLASH`); per-session state lives in [`Glm5DraftState`].
578///
579/// HOISTED to the general seam ([`crate::dflash::DflashDrafter`], lane/glm5-extract2): the
580/// holder is `{ drafter weights, byte-identity pin }` with nothing glm5 in it. Re-exported
581/// here under its old name so glm5's call sites and gates keep the name they were written
582/// against, exactly as `glm5_conf_keep` does above.
583pub use crate::dflash::DflashDrafter as Glm5DflashDrafter;
584
585/// Process-wide count of verify rounds whose drafts came through a RANK-TRIMMED draft head
586/// (either source; lane/frspec-dflash2-20260902). Gates read it beside the per-session
587/// [`Glm5SpecSession::rank_trimmed_rounds`]; the worker's `[glm5-acc]` line carries the
588/// session's own count.
589static GLM5_RANK_TRIMMED_DRAFT_ROUNDS: std::sync::atomic::AtomicU64 =
590    std::sync::atomic::AtomicU64::new(0);
591
592/// Read [`GLM5_RANK_TRIMMED_DRAFT_ROUNDS`] (doc on the static).
593pub fn glm5_rank_trimmed_draft_rounds() -> u64 {
594    GLM5_RANK_TRIMMED_DRAFT_ROUNDS.load(std::sync::atomic::Ordering::Relaxed)
595}
596
597/// Per-session draft-source state (module doc, DRAFT SOURCE SEAM). Selected at session
598/// creation from the model's loaded sources and pinned for the session's lifetime.
599pub(crate) enum Glm5DraftState {
600    /// Embedded NextN head: state = the MTP latent plane + `Glm5SpecSession::pending`
601    /// (token, h_seed) pairs — the pre-seam program, byte-identical.
602    NativeMtp,
603    /// DFlash2 block-diffusion drafter: state = the drafter's own ctx-feature KV cache
604    /// plus host feature rows not yet ingested. Invariant at every round boundary:
605    /// `kv.len + pending.len()/(taps.len()*n_embd) == committed.len()` — the drafter's
606    /// context is exactly the committed tokens (the probe's `F_feat[new_lo:start]` walk).
607    Dflash2 {
608        kv: DflashKv,
609        /// Committed-position feature rows awaiting ingest, `[n, n_taps*n_embd]` host
610        /// (the prompt's prime taps at session start; each round's kept verify taps after).
611        pending: Vec<f32>,
612        /// Resolved tap layers (drafter config `target_layer_ids`, red-arm shift applied).
613        taps: Vec<usize>,
614    },
615}
616
617/// The retained q side of one round's draft chain — what the sampled accept walk consumes.
618/// Greedy rounds carry `None` (the accept is the byte-deterministic prefix walk).
619enum Glm5DraftQ {
620    None,
621    /// Native MTP chain: per-slot retained draft logits (rank space under a trim) + the
622    /// filtered stats of the distribution each draft was drawn from.
623    Mtp {
624        draft_idx: Vec<u32>,
625        draft_logits: Vec<CudaSlice<f32>>,
626        draft_stats: Vec<(f32, f32, f32)>,
627    },
628    /// DFlash2 selector proposal (the recorded candidate-set q) + the retained draft-logit
629    /// rows `dl` (`dspark_accept_sampled`'s buffer contract; unread on the Selector q path).
630    Selector {
631        prop: DsparkDraftSample,
632        dl: CudaSlice<f32>,
633    },
634}
635
636/// Resolve the drafter's tap layers against the trunk: the drafter config's
637/// `target_layer_ids` are memra PLAN layer indices whose COMPLETED output feeds the fc
638/// (the probe's capture convention: `MEMRA_TRACE_LAYER_ROWS_LAYERS=5,14,24,33,42` == the
639/// drafter's own `target_layer_ids`, asserted 1:1 in `score_dflash2.py`).
640/// `MEMRA_GLM5_DFLASH_GATE_RED=tap-shift` is the RED-ARM INSTRUMENT: every tap moves +1
641/// layer — deliberately wrong features whose acceptance collapse the gate asserts while
642/// the output tape stays byte-identical. Unknown values refuse loudly.
643///
644/// The resolution itself is the general seam ([`crate::dflash::resolve_tap_layers`],
645/// lane/glm5-extract2); what stays here is glm5's OWN red arm — the gate instrument reads its
646/// env, prints its `[glm5-spec]` tag, and hands the shift in as a parameter. Error bytes are
647/// unchanged ("glm5 DFlash2" is the `what` label).
648fn glm5_dflash_tap_layers(draft: &DflashDraft, n_trunk: usize) -> Res<Vec<usize>> {
649    let shift = match std::env::var("MEMRA_GLM5_DFLASH_GATE_RED").ok().as_deref() {
650        Some("tap-shift") => {
651            eprintln!(
652                "[glm5-spec] RED-ARM tap-shift: drafter tap layers shifted +1 (gate \
653                 instrument, never a serving flag)"
654            );
655            1
656        }
657        Some("") | None => 0,
658        Some(other) => {
659            return Err(format!(
660                "MEMRA_GLM5_DFLASH_GATE_RED={other:?}: unknown red arm (want tap-shift)"
661            )
662            .into());
663        }
664    };
665    Ok(crate::dflash::resolve_tap_layers(
666        &draft.cfg.target_layer_ids,
667        n_trunk,
668        shift,
669        "glm5 DFlash2",
670    )?)
671}
672
673/// Pre-round state checkpoint for one glm5 verify round. Captured by `glm5_verify_rows`
674/// BEFORE any row runs; consumed by `glm5_verify_rollback`.
675///
676/// Covers exactly the state planes a glm5_next trunk mutates in a verify round:
677/// - `latent_len`: per-layer MLA latent length at round start (rollback = truncate).
678/// - KDA state (loop-port 3, the module doc's GdnStash/ReplaySSM diet LANDED): the old
679///   per-row (conv, ssm) column clones — 4 MiB x 34 layers per COLUMN, ~0.95 GiB of
680///   transient at K=7 — are replaced by
681///   * `kda_ssm_snap[il]`: ONE recurrent-state clone per layer per round (the state
682///     BEFORE row 0),
683///   * `kda_scan_stash[il][r]`: row r's scan-input buffers, STOLEN from the step (zero
684///     copies, ~160 KB/row/layer — `kda::KdaScanInputs`), rows `0..t-1` except the last
685///     (`keep == t` needs no restore, so row `t-1` is never a replay target),
686///   * `kda_conv_cols[il][r]`: the conv ring stays PER-ROW CLONED (288 KiB, 1.4% of the
687///     ssm plane it rode beside — not worth a replay arm).
688///
689///   Partial-accept rollback REPLAYS rows `0..keep` from the snapshot
690///   (`kda::kda_scan_replay`): each replay is the original t=1 scan launch re-issued over
691///   the very buffers that row consumed, so the rebuilt state is byte-identical to the
692///   clone it replaces by construction. Under a ppN split every clone/stash lives on its
693///   layer's OWNING stage engine; rollback restores through the same per-stage seam.
694/// - `pos`: `cache.pos` at round start.
695///
696/// glm5_next has no Full/Linear trunk mixers (the walk refuses them by name), so `kv`,
697/// `tp_kv` and GDN stashes have no arm here — growing one is a deliberate extension with
698/// its own gate, not a silent default.
699pub struct Glm5VerifyCkpt {
700    pos: usize,
701    latent_len: Vec<Option<usize>>,
702    /// Per-row conv-ring clones, rows `0..t-1` except the last (doc above). PER-ROW walk
703    /// only (`MEMRA_GLM5_VERIFY_BATCH=0`); the batched walk fills `kda_rows` instead.
704    kda_conv_cols: Vec<Option<Vec<CudaSlice<f32>>>>,
705    /// The recurrent state BEFORE row 0, one clone per KDA layer per round (doc above).
706    /// BOTH walks fill this — it is the batched replay's scan base too.
707    kda_ssm_snap: Vec<Option<CudaSlice<f32>>>,
708    /// Stolen per-row scan inputs, rows `0..t-1` except the last (doc above). PER-ROW
709    /// walk only.
710    kda_scan_stash: Vec<Option<Vec<crate::kda::KdaScanInputs>>>,
711    /// BATCHED walk (lane/glm5-verify-batch): one [`crate::kda::KdaRowsStash`] per KDA
712    /// layer per round — ring snapshot + stolen raw conv rows + stolen batched scan
713    /// inputs; rollback re-rolls the ring and replays the scan ONCE at T=keep.
714    kda_rows: Vec<Option<crate::kda::KdaRowsStash>>,
715    /// glm5 TP composition (lane/glm5-composition): per-rank rollback material of each
716    /// SHARDED KDA layer's batched verify call — the rank-indexed twin of
717    /// (`kda_ssm_snap`, `kda_rows`), restored through each rank's own engine. `None` on
718    /// every unsharded layer.
719    kda_tp: Vec<Option<crate::glm5_tp::Glm5TpKdaVerifyStash>>,
720    /// Row count of the walk that filled this ckpt; rollback validates `keep` against it.
721    rows: usize,
722}
723
724impl Glm5VerifyCkpt {
725    /// GATE RECEIPT (wiring anchor, not a serving surface): how many KDA layers filled
726    /// the BATCHED rows stash vs the PER-ROW column stash — the flag A/B gate asserts
727    /// the arm it set actually ran (wiring-assertions-match-prose law: anchor on the
728    /// invocation's artifact, never the log prose).
729    pub fn kda_stash_kinds(&self) -> (usize, usize) {
730        (
731            self.kda_rows.iter().filter(|s| s.is_some()).count(),
732            self.kda_conv_cols.iter().filter(|s| s.is_some()).count(),
733        )
734    }
735}
736
737/// Position buffers for one verify walk range (per stage engine under a split — the
738/// per-stage pos_d law): `all` = the `[t]` vector the BATCHED per-layer mixer calls
739/// consume; `rows` = the per-row single-position buffers of the per-row arm, built only
740/// when that arm can run (flag off) — the batched arm never reads them.
741struct Glm5VerifyPos {
742    pos0: usize,
743    t: usize,
744    all: CudaSlice<i32>,
745    rows: Vec<CudaSlice<i32>>,
746}
747
748impl Glm5VerifyPos {
749    fn new(e: &Engine, pos0: usize, t: usize) -> Res<Self> {
750        let v: Vec<i32> = (0..t as i32).map(|r| pos0 as i32 + r).collect();
751        let all = e.htod_i32(&v)?;
752        let rows = if glm5_verify_batch_on() && t > 1 {
753            Vec::new()
754        } else {
755            (0..t)
756                .map(|r| e.htod_i32(&[(pos0 + r) as i32]))
757                .collect::<Result<_, _>>()?
758        };
759        Ok(Self { pos0, t, all, rows })
760    }
761}
762
763impl HybridModel {
764    /// THE T-PARALLEL VERIFY WALK: score `tokens` (row 0 = the last committed token, rows
765    /// 1..t = the K drafted tokens) in ONE forward over the hc trunk at positions
766    /// `cache.pos .. cache.pos + t`, in the batched-decode kernel classes (module doc).
767    ///
768    /// Returns `(logits [t, n_vocab] device, collapsed [t, n_embd] device, ckpt)`:
769    /// `logits` row r is bit-identical to the plain `decode_step_hyper` logits after
770    /// consuming `tokens[r]` at that position (the gate's bar); `collapsed` row r is the
771    /// pre-output_norm hidden — the MTP `h_seed` for position `cache.pos + r`.
772    ///
773    /// State effects: every trunk MLA plane appends `t` rows; every trunk KDA state
774    /// advances `t` steps (per-step columns stashed in the ckpt); `cache.pos` is NOT moved
775    /// (rollback owns it). The MTP block's plane (il = n_trunk) is untouched.
776    pub fn glm5_verify_rows(
777        &self,
778        e: &Engine,
779        tokens: &[u32],
780        cache: &mut Cache,
781    ) -> Res<(CudaSlice<f32>, CudaSlice<f32>, Glm5VerifyCkpt)> {
782        let topology = *self
783            .hyper
784            .as_ref()
785            .ok_or("glm5_verify_rows on a model with no HyperConnections topology")?;
786        let t = tokens.len();
787        let cap = Self::hyper_batch_cap();
788        if t == 0 {
789            return Err("glm5_verify_rows: empty verify row set".into());
790        }
791        if t > cap {
792            return Err(format!(
793                "glm5_verify_rows: t={t} > cap {cap} — at t >= PRIME_MIN_T (16) the MoE \
794                 shared-expert trio crosses off the decode-exact class (the batched-decode \
795                 gate's measured B=16 knee), so per-row bit-identity vs plain decode breaks. \
796                 K <= cap-1 drafts per round"
797            )
798            .into());
799        }
800        let mut any_sharded = false;
801        for (il, layer) in self.layers.iter().enumerate() {
802            match &layer.mixer {
803                Mixer::Kda(la) => any_sharded |= la.tp.is_some(),
804                Mixer::Mla(mla) => any_sharded |= mla.tp.is_some(),
805                _ => {
806                    return Err(format!(
807                        "glm5_verify_rows: trunk layer {il} is not a KDA or MLA mixer — the \
808                         rollback contract below is built and gated for glm5_next's two state \
809                         classes only; a Full/Linear arm needs its own ckpt plane and gate"
810                    )
811                    .into());
812                }
813            }
814        }
815        // spec x TP composition (lane/glm5-composition): the per-row walk carries no TP
816        // rollback arm — a sharded trunk demands the BATCHED walk at t > 1 (t = 1 rounds
817        // ride the TP decode walk below; full accept is the only legal outcome there).
818        if any_sharded && t > 1 && !glm5_verify_batch_on() {
819            return Err(
820                "glm5_verify_rows: the trunk is glm5-TP-SHARDED and MEMRA_GLM5_VERIFY_BATCH=0 — \
821                 the per-row rollback seam carries no TP arm; the spec x TP composition \
822                 requires the batched verify walk (unset MEMRA_GLM5_VERIFY_BATCH or run \
823                 without the TP door)"
824                    .into(),
825            );
826        }
827
828        let n_embd = self.cfg.n_embd as usize;
829        let pos0 = cache.pos;
830
831        // Ckpt BEFORE any state moves.
832        let mut ckpt = Glm5VerifyCkpt {
833            pos: pos0,
834            latent_len: cache
835                .latent
836                .iter()
837                .take(self.layers.len())
838                .map(|plane| plane.as_ref().map(|plane| plane.len))
839                .collect(),
840            kda_conv_cols: (0..self.layers.len()).map(|_| None).collect(),
841            kda_ssm_snap: (0..self.layers.len()).map(|_| None).collect(),
842            kda_scan_stash: (0..self.layers.len()).map(|_| None).collect(),
843            kda_rows: (0..self.layers.len()).map(|_| None).collect(),
844            kda_tp: (0..self.layers.len()).map(|_| None).collect(),
845            rows: t,
846        };
847
848        // ppN door — the verify walk owns its stage split exactly as the batched decode
849        // walk does (`decode_step_batch_hyper_ppn`, decode_batch.rs). Loud refusal on an
850        // unqualified pipeline rewrite, never a single-engine walk over stage-sharded
851        // weights.
852        if let Some(fence) = crate::pp::pp_cuts(self.layers.len()) {
853            if !self.rewrite_allowed(memra_gguf::execution_manifest::RewriteSurface::Pipeline) {
854                return Err("pipeline rewrite is not qualified for this ModelPlan".into());
855            }
856            return self.glm5_verify_rows_ppn(e, tokens, cache, ckpt, &topology, &fence);
857        }
858
859        let pos = Glm5VerifyPos::new(e, pos0, t)?;
860        let embedded = e.htod(&self.embd.try_gather(n_embd, tokens)?)?;
861        let x = crate::hyper::expand(e, &topology, &embedded, t, n_embd)?;
862        let x = self.glm5_verify_range(
863            e,
864            &topology,
865            x,
866            0,
867            self.layers.len(),
868            &pos,
869            cache,
870            &mut ckpt,
871        )?;
872        let (logits, collapsed) = self.glm5_verify_head(e, &topology, &x, t)?;
873        Ok((logits, collapsed, ckpt))
874    }
875
876    /// One hc layer RANGE `[lo, hi)` of the verify walk — the body `glm5_verify_rows` ran
877    /// inline before the ppN twin landed, extracted so the unsplit walk and every pipeline
878    /// stage run the SAME code over their own range (the `hyper_range_decode` /
879    /// `decode_batch_layers` precedent: bit-identity between the arms is then structural,
880    /// not a coincidence of two maintained copies). At `lo=0, hi=n_layers` the launch
881    /// sequence is identical to the pre-extraction walk.
882    ///
883    /// KDA ckpt columns are cloned THROUGH `e` — under a split that is the owning stage's
884    /// engine, so each column lives on the device (and is ordered on the stream) that owns
885    /// its layer's state; `glm5_verify_rollback` restores through the same per-stage seam.
886    #[allow(clippy::too_many_arguments)]
887    // allow: the parameter list mirrors the range-walk call contract its siblings share
888    fn glm5_verify_range(
889        &self,
890        e: &Engine,
891        topology: &crate::hyper::HyperTopology,
892        mut x: CudaSlice<f32>,
893        lo: usize,
894        hi: usize,
895        pos: &Glm5VerifyPos,
896        cache: &mut Cache,
897        ckpt: &mut Glm5VerifyCkpt,
898    ) -> Res<CudaSlice<f32>> {
899        let t = pos.t;
900        let n_embd = self.cfg.n_embd as usize;
901        let eps = self.cfg.rms_eps;
902        // THE BATCHED MIXER ARM (lane/glm5-verify-batch, default ON): one t=K+1 call per
903        // layer per class instead of the per-row loop — KDA batches projections/conv/
904        // gates through the decode-exact classes with the recurrence sequential INSIDE
905        // one scan launch; MLA runs the SAME cached core at t rows on the rows-exact
906        // matmul classes (per-query causal selection + attention by construction).
907        // `0` = the per-row walk below, byte-for-byte (the rollback seam). Engagement is
908        // a receipt, announced once per process.
909        let batch = glm5_verify_batch_on() && t > 1;
910        {
911            static SAID: std::sync::Once = std::sync::Once::new();
912            SAID.call_once(|| {
913                if batch {
914                    eprintln!(
915                        "[glm5-spec] verify walk BATCHED per layer: kda=one t-call (scan \
916                         sequential in-kernel), mla=rows-exact t-call, head=rows-exact, \
917                         moe=pairs rows-call where qualified \
918                         (MEMRA_GLM5_VERIFY_BATCH default ON)"
919                    );
920                } else {
921                    eprintln!("[glm5-spec] verify walk PER-ROW (MEMRA_GLM5_VERIFY_BATCH=0 or t=1)");
922                }
923            });
924        }
925        let trace_v = crate::spec_phase::spec_trace_level() >= 2;
926        // Sub-phase clock (trace level 2 only): drain the walking stream so the elapsed
927        // ns lands in the mixer-class bucket — shares, never walls.
928        let vclock = |on: bool| -> Option<std::time::Instant> {
929            on.then(|| {
930                let _ = e.stream().synchronize();
931                std::time::Instant::now()
932            })
933        };
934        for il in lo..hi {
935            let layer = &self.layers[il];
936            let hyper = layer.hyper.as_ref().ok_or_else(|| {
937                format!("layer {il} carries no hyper-connection weights under an hc plan")
938            })?;
939
940            let (y, mix) = crate::hyper::pre_exact(e, topology, &hyper.attn, &x, t, n_embd)?;
941            let mut h = e.uninit(t * n_embd)?;
942            e.rms_norm(&y, layer.attn_norm.float_data(), &mut h, n_embd, t, eps)?;
943            // The batched arm refuses per-layer only for an MLA layer WITHOUT the DSA
944            // indexer: the absorbed t>1 attention arm has no per-row bit-identity claim
945            // at this seam, so it stays on the per-row loop by name (glm5_next always
946            // carries the indexer, so this is a foreign-geometry guard, not a live path).
947            let layer_batched = batch
948                && match &layer.mixer {
949                    Mixer::Kda(_) => true,
950                    Mixer::Mla(mla) => mla.index.is_some(),
951                    Mixer::Full(_) | Mixer::Linear(_) => unreachable!("refused at walk entry"),
952                };
953            let mixed = if layer_batched {
954                match &layer.mixer {
955                    // spec x TP composition: sharded mixers ride the TP verify walks —
956                    // per-rank batched rows calls, column-parallel-over-gather joins on
957                    // the rows-exact classes, per-rank rollback stash into the ckpt.
958                    Mixer::Kda(la) if la.tp.is_some() => {
959                        let t0 = vclock(trace_v);
960                        let mut scan_ns = 0u64;
961                        let (out, stash) = crate::glm5_tp::kda_tp_verify_rows(
962                            e,
963                            la,
964                            &h,
965                            t,
966                            eps,
967                            cache,
968                            il,
969                            trace_v.then_some(&mut scan_ns),
970                        )?;
971                        ckpt.kda_tp[il] = Some(stash);
972                        if let Some(t0) = t0 {
973                            let _ = e.stream().synchronize();
974                            use std::sync::atomic::Ordering;
975                            crate::spec_phase::V_KDA_NS
976                                .fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
977                            crate::spec_phase::V_KDA_SCAN_NS.fetch_add(scan_ns, Ordering::Relaxed);
978                        }
979                        out
980                    }
981                    Mixer::Mla(mla) if mla.tp.is_some() => {
982                        let t0 = vclock(trace_v);
983                        let out =
984                            self.mla_tp_attn_cached(e, mla, &h, &pos.all, t, il, cache, true)?;
985                        if let Some(t0) = t0 {
986                            let _ = e.stream().synchronize();
987                            use std::sync::atomic::Ordering;
988                            crate::spec_phase::V_MLA_NS
989                                .fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
990                        }
991                        out
992                    }
993                    Mixer::Kda(la) => {
994                        // Pre-round snapshot: ONE ssm clone per layer per round, BEFORE
995                        // the batched call advances the resident state (ckpt doc; also
996                        // the batched rollback's scan-replay base).
997                        {
998                            let rl = cache.recur[il]
999                                .as_ref()
1000                                .ok_or("glm5 verify KDA layer has no recurrent state")?;
1001                            ckpt.kda_ssm_snap[il] = Some(e.clone_dtod(&rl.ssm_state)?);
1002                        }
1003                        let t0 = vclock(trace_v);
1004                        let mut scan_ns = 0u64;
1005                        let (out, stash) = crate::kda::kda_verify_rows_cached(
1006                            e,
1007                            la,
1008                            &h,
1009                            t,
1010                            eps,
1011                            cache,
1012                            il,
1013                            trace_v.then_some(&mut scan_ns),
1014                        )?;
1015                        ckpt.kda_rows[il] = Some(stash);
1016                        if let Some(t0) = t0 {
1017                            let _ = e.stream().synchronize();
1018                            use std::sync::atomic::Ordering;
1019                            crate::spec_phase::V_KDA_NS
1020                                .fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
1021                            crate::spec_phase::V_KDA_SCAN_NS.fetch_add(scan_ns, Ordering::Relaxed);
1022                        }
1023                        out
1024                    }
1025                    Mixer::Mla(mla) => {
1026                        let t0 = vclock(trace_v);
1027                        let out =
1028                            self.mla_attn_cached_rows_exact(e, mla, &h, &pos.all, t, il, cache)?;
1029                        if let Some(t0) = t0 {
1030                            let _ = e.stream().synchronize();
1031                            use std::sync::atomic::Ordering;
1032                            crate::spec_phase::V_MLA_NS
1033                                .fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
1034                        }
1035                        out
1036                    }
1037                    Mixer::Full(_) | Mixer::Linear(_) => unreachable!("refused at walk entry"),
1038                }
1039            } else {
1040                // ---- PER-ROW mixer walk (the rollback seam; also t=1 rounds and the
1041                // no-indexer MLA guard): row r's state input is row r-1's state output
1042                // (KDA) / rows 0..pos0+r (MLA latent) — each row the SAME t=1 call its
1043                // plain decode step makes.
1044                // h_row is hoisted out of the row loop (loop-port 3): the mixer consumes
1045                // it in stream order before the next row's overwrite, so ONE buffer per
1046                // layer replaces t allocations (stream-ordered pool churn is the dsv4
1047                // lesson).
1048                // Depth attribution (lane/spec-route-depth-20260902): every row that walks
1049                // this arm is counted; the per-round log samples the counter.
1050                V_SEQ_ROWS.fetch_add(t as u64, std::sync::atomic::Ordering::Relaxed);
1051                let mut mixed = e.uninit(t * n_embd)?;
1052                let mut h_row = e.uninit(n_embd)?;
1053                #[allow(clippy::needless_range_loop)]
1054                // allow: r is the sequential row cursor (slices h, offsets pos); iterating pos buffers would hide the row-chaining contract
1055                for r in 0..t {
1056                    e.dtod_copy_view(&h.slice(r * n_embd..(r + 1) * n_embd), &mut h_row)?;
1057                    // The per-row position buffer: prebuilt when the per-row arm owns the
1058                    // walk; built on demand for the rare per-layer refusal under batch.
1059                    let pos_row: CudaSlice<i32>;
1060                    let pos_r = if let Some(p) = pos.rows.get(r) {
1061                        p
1062                    } else {
1063                        pos_row = e.htod_i32(&[(pos.pos0 + r) as i32])?;
1064                        &pos_row
1065                    };
1066                    let out_row = match &layer.mixer {
1067                        // spec x TP composition on the per-row arm. KDA shards reach
1068                        // here at t == 1 ONLY, asserted locally: the walk-entry guard is
1069                        // a FLAG check two frames up (MEMRA_GLM5_VERIFY_BATCH=0 at t>1
1070                        // refuses), and under the batched flag layer_batched is
1071                        // unconditionally true for KDA — but neither is a structural
1072                        // invariant of THIS arm (#80 review's latent-trap finding). A
1073                        // sharded NO-INDEXER MLA layer legally lands here at any t
1074                        // (append + truncate rollback covers every keep; foreign
1075                        // geometry, never a live glm5_next path).
1076                        Mixer::Kda(la) if la.tp.is_some() => {
1077                            if t > 1 {
1078                                return Err(format!(
1079                                    "glm5 verify per-row arm reached a sharded KDA \
1080                                     layer {il} at t={t}: no per-rank rollback stash \
1081                                     exists on this arm (walk-entry guard bypassed?)"
1082                                )
1083                                .into());
1084                            }
1085                            crate::glm5_tp::kda_tp_cached(
1086                                e,
1087                                la,
1088                                &h_row,
1089                                1,
1090                                eps,
1091                                cache,
1092                                il,
1093                                crate::kda::ConvArm::Decode,
1094                            )?
1095                        }
1096                        Mixer::Mla(mla) if mla.tp.is_some() => {
1097                            self.mla_tp_attn_cached(e, mla, &h_row, pos_r, 1, il, cache, false)?
1098                        }
1099                        Mixer::Kda(la) => {
1100                            // Pre-round snapshot: ONE ssm clone per layer per round taken
1101                            // before row 0 mutates the resident state (loop-port 3).
1102                            if r == 0 && t > 1 {
1103                                let rl = cache.recur[il]
1104                                    .as_ref()
1105                                    .ok_or("glm5 verify KDA layer has no recurrent state")?;
1106                                ckpt.kda_ssm_snap[il] = Some(e.clone_dtod(&rl.ssm_state)?);
1107                            }
1108                            if r + 1 < t {
1109                                // Steal the row's scan inputs for the replay stash (zero
1110                                // copies); clone only the small conv ring per row.
1111                                let (out, inputs) = crate::kda::kda_decode_cached_stash(
1112                                    e, la, &h_row, eps, cache, il,
1113                                )?;
1114                                let rl = cache.recur[il]
1115                                    .as_ref()
1116                                    .ok_or("glm5 verify KDA layer has no recurrent state")?;
1117                                ckpt.kda_conv_cols[il]
1118                                    .get_or_insert_with(Vec::new)
1119                                    .push(e.clone_dtod(&rl.conv_state)?);
1120                                ckpt.kda_scan_stash[il]
1121                                    .get_or_insert_with(Vec::new)
1122                                    .push(inputs);
1123                                out
1124                            } else {
1125                                crate::kda::kda_decode_cached(e, la, &h_row, eps, cache, il)?
1126                            }
1127                        }
1128                        Mixer::Mla(mla) => {
1129                            self.mla_attn_cached(e, mla, &h_row, pos_r, 1, il, cache)?
1130                        }
1131                        // Refused at entry; unreachable keeps the match total without a silent arm.
1132                        Mixer::Full(_) | Mixer::Linear(_) => unreachable!("refused at walk entry"),
1133                    };
1134                    e.copy_into(&mut mixed, r * n_embd, &out_row, n_embd)?;
1135                }
1136                mixed
1137            };
1138            x = crate::hyper::post(e, topology, &mixed, &x, &mix, t, n_embd)?;
1139
1140            let (y, mix) = crate::hyper::pre_exact(e, topology, &hyper.mlp, &x, t, n_embd)?;
1141            let mut z = e.uninit(t * n_embd)?;
1142            e.rms_norm(
1143                &y,
1144                layer.post_attn_norm.float_data(),
1145                &mut z,
1146                n_embd,
1147                t,
1148                eps,
1149            )?;
1150            // FFN branch: `batch` arms the pairs-shaped batched MoE across the t rows
1151            // (lane/glm5-vrest — fail-closed inside to the byte-identical sequential
1152            // loop); the =0 arm keeps the pre-lane per-(token,expert) class. Clocked
1153            // into the vffn sub-bucket at trace level 2 (batched arm only, like vkda).
1154            let t0 = vclock(trace_v && batch);
1155            let ffn_out = self.hyper_ffn_branch_batch(e, layer, &z, t, il, batch)?;
1156            if let Some(t0) = t0 {
1157                let _ = e.stream().synchronize();
1158                use std::sync::atomic::Ordering;
1159                crate::spec_phase::V_FFN_NS
1160                    .fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
1161            }
1162            x = crate::hyper::post(e, topology, &ffn_out, &x, &mix, t, n_embd)?;
1163            // glm5 DFlash2 feature tap (module doc, DRAFT SOURCE SEAM): the verify rows'
1164            // contracted completed-layer outputs are next round's drafter context.
1165            self.glm5_hc_tap(e, cache, topology, il, &x, t)?;
1166        }
1167        Ok(x)
1168    }
1169
1170    /// Write one tapped layer's CONTRACTED completed output into the armed
1171    /// [`HcTapSink`] — the glm5 DFlash2 drafter's measured feature contract (stream-mean
1172    /// over the hyper streams, the probe's `hc_contract` capture definition). Staged
1173    /// through the WALKING engine `e` (the owning stage engine under a ppN split), so the
1174    /// sink is placement-invariant. One Option check when unarmed; nothing else pays.
1175    ///
1176    /// Two staging arms (loop-port 1):
1177    ///   * `device_stage` (the verify-round sink): ONE async D2D into the slot's device
1178    ///     buffer — the walk never blocks; the round drains all slots post-walk in its
1179    ///     single sync point (`glm5_tap_drain`). Kills the five in-walk DtoHs the 3way
1180    ///     window priced into the 31.6 ms fixed round cost (map row #17).
1181    ///   * host-staged (prime sinks): the pre-port behavior — per-chunk DtoH, amortized
1182    ///     over the prime's >= 256-row chunks.
1183    pub(crate) fn glm5_hc_tap(
1184        &self,
1185        e: &Engine,
1186        cache: &mut Cache,
1187        topology: &crate::hyper::HyperTopology,
1188        il: usize,
1189        x: &CudaSlice<f32>,
1190        t: usize,
1191    ) -> Res<()> {
1192        let Some(sink) = cache.hc_taps.as_mut() else {
1193            return Ok(());
1194        };
1195        let base = sink.base;
1196        self.glm5_hc_tap_into(e, sink, base, topology, il, x, t)
1197    }
1198
1199    /// [`Self::glm5_hc_tap`] with the sink and its row base passed EXPLICITLY instead of read
1200    /// from the cache.
1201    ///
1202    /// The pipelined mHC prime needs this seam. Its two stage threads run different CHUNKS at
1203    /// the same moment, so a single `sink.base` field cannot describe both, and
1204    /// [`PrimeCacheStages`] hands each stage a cache shell with `hc_taps: None` — which is why
1205    /// arm 2 used to refuse outright when the DFlash2 drafter had armed a sink. Passing the
1206    /// base per call is what makes one shared sink correct for two concurrent walks.
1207    #[allow(clippy::too_many_arguments)] // allow: the list is the tap contract, base included
1208    pub(crate) fn glm5_hc_tap_into(
1209        &self,
1210        e: &Engine,
1211        sink: &mut HcTapSink,
1212        base_abs: usize,
1213        topology: &crate::hyper::HyperTopology,
1214        il: usize,
1215        x: &CudaSlice<f32>,
1216        t: usize,
1217    ) -> Res<()> {
1218        let Some(slot) = sink.layer_ids.iter().position(|&l| l == il) else {
1219            return Ok(());
1220        };
1221        let saved = sink.base;
1222        sink.base = base_abs;
1223        let out = self.glm5_hc_tap_slot(e, sink, slot, topology, x, t);
1224        sink.base = saved;
1225        out
1226    }
1227
1228    fn glm5_hc_tap_slot(
1229        &self,
1230        e: &Engine,
1231        sink: &mut HcTapSink,
1232        slot: usize,
1233        topology: &crate::hyper::HyperTopology,
1234        x: &CudaSlice<f32>,
1235        t: usize,
1236    ) -> Res<()> {
1237        let h = sink.hidden;
1238        let n_taps = sink.layer_ids.len();
1239        // Sink-relative row of this walk's row 0 (doc on `HcTapSink::origin`): fresh-prompt
1240        // sinks have origin 0 and this is exactly the pre-field arithmetic; a suffix-prime
1241        // sink is anchored at the restored boundary. A base below the origin is a caller
1242        // bug (a walk over rows the sink does not cover) — refuse, never wrap.
1243        let base = sink.base.checked_sub(sink.origin).ok_or_else(|| {
1244            format!(
1245                "hc tap base {} below sink origin {} (walk outside the sink's window)",
1246                sink.base, sink.origin,
1247            )
1248        })?;
1249        debug_assert!(
1250            base + t <= sink.t,
1251            "hc tap window {base}+{t} exceeds sink {}",
1252            sink.t
1253        );
1254        let contracted = crate::hyper::contract_mean(e, topology, x, t, h)?;
1255        if sink.device_stage {
1256            // Lazy slot buffer on the WRITING engine (this layer always walks on one
1257            // stage, so the buffer's device is stable for the sink's lifetime). Every
1258            // walk row writes every tapped layer, so the buffer is fully covered by the
1259            // walk that armed the sink.
1260            if sink.dev[slot].is_none() {
1261                sink.dev[slot] = Some(e.uninit(sink.t * h)?);
1262            }
1263            let buf = sink.dev[slot].as_mut().expect("just filled");
1264            e.copy_into(buf, base * h, &contracted, t * h)?;
1265            return Ok(());
1266        }
1267        let t_dtoh = std::time::Instant::now();
1268        let host = e.dtoh(&contracted)?;
1269        for r in 0..t {
1270            let dst = (base + r) * n_taps * h + slot * h;
1271            sink.rows[dst..dst + h].copy_from_slice(&host[r * h..(r + 1) * h]);
1272        }
1273        sink.dtoh_ns += t_dtoh.elapsed().as_nanos() as u64;
1274        Ok(())
1275    }
1276
1277    /// Drain a device-staged tap sink into its host `rows` — the round's ONE post-walk
1278    /// sync point for tap features (loop-port 1). Each slot reads back through its
1279    /// layer's OWNING engine (the stage engine under a live split, the caller's engine
1280    /// otherwise); the verify walk's terminal drain has already retired every stage's
1281    /// writes (stream program order: the slot copy precedes its stage's TX, and the
1282    /// TX-wait chain covers it transitively — the pp.rs multi-stream law).
1283    fn glm5_tap_drain(&self, e: &Engine, sink: &mut HcTapSink) -> Res<()> {
1284        if !sink.device_stage {
1285            return Ok(());
1286        }
1287        let h = sink.hidden;
1288        let n_taps = sink.layer_ids.len();
1289        let split = match crate::pp::pp_cuts(self.layers.len()) {
1290            Some(fence) if !crate::pp::pp2_streams_off() => {
1291                Some((crate::pp::PpNRt::get(e)?, fence))
1292            }
1293            _ => None,
1294        };
1295        for slot in 0..n_taps {
1296            let Some(buf) = sink.dev[slot].take() else {
1297                continue;
1298            };
1299            let il = sink.layer_ids[slot];
1300            let es = match split.as_ref() {
1301                Some((rt, fence)) => {
1302                    let stage = fence
1303                        .windows(2)
1304                        .position(|w| il >= w[0] && il < w[1])
1305                        .ok_or_else(|| format!("tap layer {il} outside every stage range"))?;
1306                    rt.engine(stage, e)
1307                }
1308                None => e,
1309            };
1310            let host = es.dtoh(&buf)?;
1311            for r in 0..sink.t {
1312                let dst = r * n_taps * h + slot * h;
1313                sink.rows[dst..dst + h].copy_from_slice(&host[r * h..(r + 1) * h]);
1314            }
1315        }
1316        Ok(())
1317    }
1318
1319    /// Trunk exit of the verify walk, the batched head's decode-exact form
1320    /// (`hyper_batch_head_logits`), with the collapsed pre-output_norm rows kept — they are
1321    /// the h_seeds the MTP head re-seeds from (LANE.md §A). Under a split this runs on the
1322    /// LAST stage's engine, where the loader put `output_norm` + the lm head.
1323    fn glm5_verify_head(
1324        &self,
1325        e: &Engine,
1326        topology: &crate::hyper::HyperTopology,
1327        x: &CudaSlice<f32>,
1328        t: usize,
1329    ) -> Res<(CudaSlice<f32>, CudaSlice<f32>)> {
1330        let n_embd = self.cfg.n_embd as usize;
1331        let eps = self.cfg.rms_eps;
1332        let collapsed =
1333            crate::hyper::collapse(e, topology, self.hyper_head.as_ref(), x, t, n_embd)?;
1334        let mut hn = e.uninit(t * n_embd)?;
1335        e.rms_norm(
1336            &collapsed,
1337            self.output_norm.float_data(),
1338            &mut hn,
1339            n_embd,
1340            t,
1341            eps,
1342        )?;
1343        // Under the batched walk the lm head rides the rows-exact classes too (the bf16
1344        // tcols twin reads the 1.27 GB head ONCE per round instead of once per row);
1345        // per-row bits unchanged by contract, the tcols bit-gate holds it.
1346        let logits = if glm5_verify_batch_on() && t > 1 {
1347            e.matmul_rows_exact(&self.output, &hn, t)?
1348        } else {
1349            e.matmul_decode_exact(&self.output, &hn, t)?
1350        };
1351        Ok((logits, collapsed))
1352    }
1353
1354    /// ppN twin of the verify walk (lane/glm5-ppn-verify, 2026-08-30), mirroring
1355    /// `decode_step_batch_hyper_ppn` (decode_batch.rs): the t=K+1 rows walk as N stage
1356    /// subgraphs — per-stage engine, per-stage pos_rows uploads, ONE `[t, streams, n_embd]`
1357    /// boundary payload per fence cut. Row chaining is per-LAYER through the one cache
1358    /// (row r+1 at layer il depends only on row r at layer il), so a straight layer-range
1359    /// split preserves it exactly; no row ever crosses a boundary individually. Head +
1360    /// collapsed rows land on the LAST stage's engine — where the loader put the lm head
1361    /// and where `pp::new_cache*` places the MTP plane the re-seed feeds.
1362    ///
1363    /// DRAIN CONTRACT: this walk returns DEVICE buffers with no terminal dtoh (unlike its
1364    /// decode twins, whose epilogue reads back on the last stage's stream), so it owns the
1365    /// settle — the per-stage arm synchronizes the LAST stage's stream before returning.
1366    /// The TX-wait chain transitively covers every earlier stage (pp.rs multi-stream law),
1367    /// so the logits, the collapsed rows AND the ckpt's per-stage KDA columns are all safe
1368    /// for consumption from the caller's streams after this returns.
1369    #[allow(clippy::too_many_arguments)]
1370    // allow: the parameter list mirrors its decode twin's stage-walk contract
1371    fn glm5_verify_rows_ppn(
1372        &self,
1373        e: &Engine,
1374        tokens: &[u32],
1375        cache: &mut Cache,
1376        mut ckpt: Glm5VerifyCkpt,
1377        topology: &crate::hyper::HyperTopology,
1378        fence: &[usize],
1379    ) -> Res<(CudaSlice<f32>, CudaSlice<f32>, Glm5VerifyCkpt)> {
1380        let t = tokens.len();
1381        let n_embd = self.cfg.n_embd as usize;
1382        let pos0 = ckpt.pos;
1383        let payload = t * topology.streams * n_embd;
1384        // Position buffers through THIS stage's engine (the per-stage pos_d law:
1385        // allocated, consumed and freed on one stage's stream).
1386        let pos_on = |eng: &Engine| -> Res<Glm5VerifyPos> { Glm5VerifyPos::new(eng, pos0, t) };
1387
1388        // Same-stream seam (MEMRA_PP_STREAMS=0): one engine, boundary copies between
1389        // ranges — the shape every hc ppN walk uses for this knob.
1390        if crate::pp::pp2_streams_off() {
1391            let pos = pos_on(e)?;
1392            let embedded = e.htod(&self.embd.try_gather(n_embd, tokens)?)?;
1393            let mut x = crate::hyper::expand(e, topology, &embedded, t, n_embd)?;
1394            x =
1395                self.glm5_verify_range(e, topology, x, fence[0], fence[1], &pos, cache, &mut ckpt)?;
1396            for s in 1..fence.len() - 1 {
1397                let boundary_tx = e.clone_dtod(&x)?;
1398                let boundary_rx = e.clone_dtod(&boundary_tx)?;
1399                x = self.glm5_verify_range(
1400                    e,
1401                    topology,
1402                    boundary_rx,
1403                    fence[s],
1404                    fence[s + 1],
1405                    &pos,
1406                    cache,
1407                    &mut ckpt,
1408                )?;
1409            }
1410            let (logits, collapsed) = self.glm5_verify_head(e, topology, &x, t)?;
1411            return Ok((logits, collapsed, ckpt));
1412        }
1413
1414        let rt = crate::pp::PpNRt::get(e)?;
1415        let n_st = fence.len() - 1;
1416        assert_eq!(
1417            rt.n_stages(),
1418            n_st,
1419            "PpNRt stage count {} != fence stages {n_st}",
1420            rt.n_stages()
1421        );
1422        // #87 reverse publication (see decode_step_batch_ppn): order every stage stream
1423        // behind the caller before this body's first stage allocation.
1424        rt.fence_stages_behind(&e.stream())?;
1425
1426        // ---- STAGE 0: embed + expand (no weights) + layers [0, fence[1]) + TX ----
1427        let mut slot = {
1428            let _st0 = rt.enter(0);
1429            let e0 = rt.engine(0, e);
1430            let pos = pos_on(e0)?;
1431            let embedded = e0.htod(&self.embd.try_gather(n_embd, tokens)?)?;
1432            let x = crate::hyper::expand(e0, topology, &embedded, t, n_embd)?;
1433            let x = self
1434                .glm5_verify_range(e0, topology, x, fence[0], fence[1], &pos, cache, &mut ckpt)?;
1435            rt.tx(0, &x, payload)?
1436        };
1437
1438        // ---- MIDDLE STAGES: RX -> range -> TX ----
1439        for s in 1..n_st - 1 {
1440            let _st = rt.enter(s);
1441            let es = rt.engine(s, e);
1442            let pos = pos_on(es)?;
1443            let x = rt.rx(s - 1, slot, payload)?;
1444            let x = self.glm5_verify_range(
1445                es,
1446                topology,
1447                x,
1448                fence[s],
1449                fence[s + 1],
1450                &pos,
1451                cache,
1452                &mut ckpt,
1453            )?;
1454            slot = rt.tx(s, &x, payload)?;
1455        }
1456
1457        // ---- LAST STAGE: RX + final range + collapse/head + the drain (doc above) ----
1458        let _stl = rt.enter(n_st - 1);
1459        let el = rt.engine(n_st - 1, e);
1460        let pos = pos_on(el)?;
1461        let x = rt.rx(n_st - 2, slot, payload)?;
1462        let x = self.glm5_verify_range(
1463            el,
1464            topology,
1465            x,
1466            fence[n_st - 1],
1467            fence[n_st],
1468            &pos,
1469            cache,
1470            &mut ckpt,
1471        )?;
1472        let (logits, collapsed) = self.glm5_verify_head(el, topology, &x, t)?;
1473        // el.stream() under the enter-guard IS the stage stream (memra_runtime ambient
1474        // override) — this drain settles the whole walk transitively.
1475        el.stream().synchronize()?;
1476        drop(_stl);
1477        // EXIT PUBLICATION (lane/glm5-accrace): the drain above settles the LAST stage, and
1478        // the TX-wait chain covers every earlier stage's work only UP TO its `ev_tx`. Each
1479        // earlier stage's stream still holds the stage-scope tail its locals enqueue when
1480        // they drop under the override (`pos`, the boundary residual, the per-layer
1481        // transients, this round's ckpt clones). The caller resumes and allocates for the
1482        // accept walk and the MTP re-seed, so it must be ordered behind ALL stages.
1483        self.glm5_publish_stages(e)?;
1484        Ok((logits, collapsed, ckpt))
1485    }
1486
1487    /// The engine that owns the trunk exit (collapse + output_norm + lm head), the MTP
1488    /// block's weights AND its latent plane under a ppN split: the LAST stage's engine —
1489    /// `hybrid.rs` uploads the head there (`pp::layer_engine(e, n_trunk, n_trunk - 1)`)
1490    /// and `pp::new_cache*` maps trailing MTP/NextN planes to the last stage. Door shut or
1491    /// the same-stream seam: the caller's engine, unchanged (single-device callers pay
1492    /// nothing — `e` is returned by identity).
1493    fn glm5_head_engine<'e>(&self, e: &'e Engine) -> Res<&'e Engine> {
1494        match crate::pp::pp_cuts(self.layers.len()) {
1495            Some(fence) if !crate::pp::pp2_streams_off() => {
1496                let rt = crate::pp::PpNRt::get(e)?;
1497                Ok(rt.engine(fence.len() - 2, e))
1498            }
1499            _ => Ok(e),
1500        }
1501    }
1502
1503    /// Roll the trunk back to exactly `keep` accepted verify rows (1 <= keep <= t; keep =
1504    /// j+1: the always-committed anchor row plus j accepted drafts).
1505    ///
1506    /// - MLA latent planes: `len = snapshot + keep` (truncate; rows are position-addressed
1507    ///   and append-only, so the kept rows ARE what a plain decode chain would have
1508    ///   written — the decode-exact contract), device `len_d` in lock-step, and
1509    ///   `truncate_index_pool_keys(pool)` clamps pool-key finality to what the shortened
1510    ///   `len` still justifies (the tail-ring residency tripwire fires on the next call if
1511    ///   this clamp is ever skipped).
1512    /// - KDA state: restore column keep-1 (state after the last kept row); full accept
1513    ///   (keep == t) keeps the resident state — the columns are clones OF it.
1514    /// - `cache.pos = snapshot + keep`.
1515    ///
1516    /// Under a live ppN split each stage's layers restore THROUGH that stage's engine ON
1517    /// its stream: the state planes and the ckpt columns live on the owning stage's device
1518    /// (per-stage `KvDev` allocation; per-stage clones in the walk), and enqueuing the
1519    /// restores on the same stage streams the walk writes on orders them relative to the
1520    /// walk without any extra fence.
1521    ///
1522    /// THE EXIT PUBLICATION IS NOT OPTIONAL (lane/glm5-accrace 2026-09-01). This body used
1523    /// to return with the restores merely ENQUEUED on the stage streams, on the reasoning
1524    /// that "the next walk's own entry fence covers the primary-stream seam". It does not:
1525    /// `fence_stages_behind` orders the STAGE streams behind the CALLER, and everything the
1526    /// round does after this point — the MTP plane reset, the h_seed rows, the next round's
1527    /// whole draft chain, the next SESSION's cache allocation and prime — runs on the
1528    /// CALLER's stream and ALLOCATES. cudarc's drops carry no read guard, so the pool could
1529    /// hand the caller a block whose stage-stream lifetime had not retired and the caller's
1530    /// writes landed under queued rollback work.
1531    ///
1532    /// MEASURED CONSEQUENCE, and why a "rollback ordering" bug showed up as a PRIME bug:
1533    /// with per-stage streams on one device the hc ppN prime over a fixed 24-token prompt
1534    /// returned three distinct logit fingerprints inside one process (a third of all primes
1535    /// non-canonical); downstream, one glm5 spec round lost an acceptance silently
1536    /// (14/42 -> 13/42) and the e2e tape diverged. Publishing here took non-canonical primes
1537    /// from 20/110 to 2/110 in an interleaved A/B, and the walk's own exit publication
1538    /// closed the remainder. Receipts:
1539    /// `research/glm53-flash-bringup-20260827/accrace-20260901/LANE.md`.
1540    pub fn glm5_verify_rollback(
1541        &self,
1542        e: &Engine,
1543        cache: &mut Cache,
1544        ckpt: &Glm5VerifyCkpt,
1545        keep: usize,
1546    ) -> Res<()> {
1547        if keep == 0 || keep > ckpt.rows {
1548            return Err(format!(
1549                "glm5_verify_rollback: keep={keep} outside 1..={} (the anchor row is always \
1550                 committed; keep = accepted drafts + 1)",
1551                ckpt.rows
1552            )
1553            .into());
1554        }
1555        match crate::pp::pp_cuts(self.layers.len()) {
1556            Some(fence) if !crate::pp::pp2_streams_off() => {
1557                let rt = crate::pp::PpNRt::get(e)?;
1558                for s in 0..fence.len() - 1 {
1559                    let _st = rt.enter(s);
1560                    let es = rt.engine(s, e);
1561                    for il in fence[s]..fence[s + 1] {
1562                        self.glm5_rollback_layer(es, cache, ckpt, keep, il)?;
1563                    }
1564                }
1565                // EXIT PUBLICATION (doc above): every stage stream, to the caller's.
1566                self.glm5_publish_stages(e)?;
1567            }
1568            _ => {
1569                for il in 0..self.layers.len() {
1570                    self.glm5_rollback_layer(e, cache, ckpt, keep, il)?;
1571                }
1572            }
1573        }
1574        cache.pos = ckpt.pos + keep;
1575        Ok(())
1576    }
1577
1578    /// Restore ONE trunk layer to the ckpt's `keep`-row state (the per-plane contract in
1579    /// [`Self::glm5_verify_rollback`]'s doc). `e` is the layer's OWNING engine — the stage
1580    /// engine under a split, the caller's engine otherwise.
1581    fn glm5_rollback_layer(
1582        &self,
1583        e: &Engine,
1584        cache: &mut Cache,
1585        ckpt: &Glm5VerifyCkpt,
1586        keep: usize,
1587        il: usize,
1588    ) -> Res<()> {
1589        match &self.layers[il].mixer {
1590            Mixer::Mla(mla) => {
1591                if keep == ckpt.rows {
1592                    // Full accept: the walk already advanced len AND the len_d device
1593                    // mirror to saved + rows on the canonical plane and every replica
1594                    // (append-time stores), so the restore below would rewrite unchanged
1595                    // values — ~11 synchronizing pageable 4-byte copies per round on the
1596                    // HOT outcome (the KDA arms' early-out twin; #82 review).
1597                    return Ok(());
1598                }
1599                let saved = ckpt.latent_len[il].ok_or_else(|| {
1600                    format!("glm5_verify_rollback: MLA layer {il} missing from the ckpt")
1601                })?;
1602                let plane = cache.latent[il].as_mut().ok_or_else(|| {
1603                    format!("glm5_verify_rollback: MLA layer {il} has no latent plane")
1604                })?;
1605                plane.len = saved + keep;
1606                let len_i32 =
1607                    i32::try_from(plane.len).map_err(|_| "latent length exceeds i32 mirror")?;
1608                // Door H (`MEMRA_GLM5_HTOD_DIET`): async `i32_set_k` instead of the synchronizing
1609                // pageable 4-byte copy — 11 of these per round, and unconditional (unlike the
1610                // KDA arm, which short-circuits when `keep == rows`).
1611                e.i32_mirror_store(&mut plane.len_d, len_i32)?;
1612                if let Some(indexer) = mla.index.as_ref() {
1613                    plane.truncate_index_pool_keys(indexer.geom.pool);
1614                }
1615                // spec x TP composition: the PEER latent replicas append in lock-step with
1616                // the canonical plane (the TP walk's construction), so the same truncation
1617                // restores each of them — through its own rank's engine for the device
1618                // `len_d` mirror. Full accept skips the loop (lens already read
1619                // saved + rows — the KDA arm's early-out twin; each skipped store is a
1620                // synchronizing pageable copy per rank per layer on the HOT outcome).
1621                // Missing replicas after a verify walk are a wiring bug and refuse by
1622                // name, never a silent canonical-only restore (#80 review hardening).
1623                if let Some(tp) = mla.tp.as_ref() {
1624                    let replicas = cache.glm5_tp_latent_peer[il].as_mut().ok_or_else(|| {
1625                        format!(
1626                            "glm5_verify_rollback: sharded MLA layer {il} has no peer \
1627                             latent replicas (the TP verify walk hydrates them; a \
1628                             rollback without them would silently restore the canonical \
1629                             plane only)"
1630                        )
1631                    })?;
1632                    for (i, replica) in replicas.iter_mut().enumerate() {
1633                        replica.len = saved + keep;
1634                        tp.rt.peers[i].i32_mirror_store(&mut replica.len_d, len_i32)?;
1635                        if let Some(indexer) = mla.index.as_ref() {
1636                            replica.truncate_index_pool_keys(indexer.geom.pool);
1637                        }
1638                    }
1639                }
1640            }
1641            Mixer::Kda(la) if la.tp.is_some() => {
1642                if keep == ckpt.rows {
1643                    return Ok(()); // resident per-rank states ARE the post-keep states
1644                }
1645                let stash = ckpt.kda_tp[il].as_ref().ok_or_else(|| {
1646                    format!(
1647                        "glm5_verify_rollback: sharded KDA layer {il} has no per-rank stash \
1648                         (the batched TP verify walk fills it; the per-row arm is refused \
1649                         at walk entry)"
1650                    )
1651                })?;
1652                crate::glm5_tp::kda_tp_verify_rollback(e, la, stash, keep, cache, il)?;
1653            }
1654            Mixer::Kda(la) => {
1655                if keep == ckpt.rows {
1656                    return Ok(()); // resident state IS the state after the last kept row
1657                }
1658                // BATCHED-walk stash (lane/glm5-verify-batch): ring restore + re-roll,
1659                // then ONE scan replay at T=keep from the pre-round snapshot.
1660                if let Some(stash) = ckpt.kda_rows[il].as_ref() {
1661                    let snap = ckpt.kda_ssm_snap[il].as_ref().ok_or_else(|| {
1662                        format!("glm5_verify_rollback: KDA layer {il} has no ssm snapshot")
1663                    })?;
1664                    return crate::kda::kda_verify_rollback_rows(
1665                        e, la, snap, stash, keep, cache, il,
1666                    );
1667                }
1668                // Conv ring: restore the cloned column (unchanged — 288 KiB).
1669                let conv_cols = ckpt.kda_conv_cols[il].as_ref().ok_or_else(|| {
1670                    format!("glm5_verify_rollback: KDA layer {il} has no conv columns")
1671                })?;
1672                let conv = &conv_cols[keep - 1];
1673                {
1674                    let rl = cache.recur[il].as_mut().ok_or_else(|| {
1675                        format!("glm5_verify_rollback: KDA layer {il} has no recurrent state")
1676                    })?;
1677                    e.copy_into(&mut rl.conv_state, 0, conv, conv.len())?;
1678                }
1679                // Recurrent state: REPLAY rows 0..keep from the pre-round snapshot
1680                // (loop-port 3; ckpt doc) — each replay re-issues that row's original
1681                // t=1 scan over its stolen inputs, so the rebuilt state is byte-identical
1682                // to the per-row clone this retires.
1683                let snap = ckpt.kda_ssm_snap[il].as_ref().ok_or_else(|| {
1684                    format!("glm5_verify_rollback: KDA layer {il} has no ssm snapshot")
1685                })?;
1686                let stash = ckpt.kda_scan_stash[il].as_ref().ok_or_else(|| {
1687                    format!("glm5_verify_rollback: KDA layer {il} has no scan stash")
1688                })?;
1689                crate::kda::kda_scan_replay(e, la, snap, &stash[..keep], cache, il)?;
1690            }
1691            Mixer::Full(_) | Mixer::Linear(_) => {
1692                return Err(format!(
1693                    "glm5_verify_rollback: layer {il} mixer class was refused at walk \
1694                     entry and cannot appear in a ckpt"
1695                )
1696                .into());
1697            }
1698        }
1699        Ok(())
1700    }
1701
1702    /// Reset the MTP draft plane (il = n_trunk) to `len` rows — the LANE.md rollback
1703    /// contract ("one row per step, rollback = plane len reset") plus the same pool-key
1704    /// clamp every len-shortening path owes the tail ring. The plane lives on the LAST
1705    /// stage under a split (`pp::new_cache*` maps trailing MTP planes there), so the
1706    /// device mirror writes through the head engine.
1707    fn glm5_mtp_plane_reset(&self, e: &Engine, cache: &mut Cache, len: usize) -> Res<()> {
1708        let e = self.glm5_head_engine(e)?;
1709        let mtp = self
1710            .mtp
1711            .as_ref()
1712            .ok_or("glm5_mtp_plane_reset with no MTP head loaded")?;
1713        let il = self
1714            .plan
1715            .mtp_blocks
1716            .first()
1717            .ok_or("ModelPlan declares no MTP block")?
1718            .layer
1719            .index as usize;
1720        let plane = cache
1721            .latent
1722            .get_mut(il)
1723            .and_then(|plane| plane.as_mut())
1724            .ok_or_else(|| format!("MTP block layer {il} has no latent cache plane"))?;
1725        if len > plane.len {
1726            return Err(format!(
1727                "glm5_mtp_plane_reset: target {len} is past the plane's {} rows — a reset \
1728                 only ever shortens",
1729                plane.len
1730            )
1731            .into());
1732        }
1733        plane.len = len;
1734        let len_i32 = i32::try_from(len).map_err(|_| "latent length exceeds i32 mirror")?;
1735        e.stream().memcpy_htod(&[len_i32], &mut plane.len_d)?;
1736        if let Mixer::Mla(mla) = &mtp.mixer
1737            && let Some(indexer) = mla.index.as_ref()
1738        {
1739            plane.truncate_index_pool_keys(indexer.geom.pool);
1740        }
1741        Ok(())
1742    }
1743
1744    /// Single-shot glm5 speculative generation: draft (MTP head, K steps) -> verify (one
1745    /// t=K+1 walk) -> accept j (greedy longest matching prefix) -> rollback -> re-seed.
1746    /// Returns `(tokens, drafted, accepted)` — `generate_spec`'s contract. One-shot form:
1747    /// builds a [`Glm5SpecSession`] over a fresh cache and drives it to `max_new` — the
1748    /// SAME round machinery the serve worker bursts, so the tparallel gate's byte-identity
1749    /// pins cover the served path's rounds too.
1750    pub fn generate_spec_glm5(
1751        &self,
1752        e: &Engine,
1753        prompt: &[u32],
1754        max_new: usize,
1755        k: usize,
1756    ) -> Res<(Vec<u32>, usize, usize)> {
1757        self.generate_spec_glm5_gated(e, prompt, max_new, k, Glm5SpecKnobs::default())
1758    }
1759
1760    /// `generate_spec_glm5` with GATE INSTRUMENTS (never a serving surface): a draft
1761    /// override for deterministic forced-accept / forced-reject rounds, and a
1762    /// rollback-disable arm that red-proves the end-to-end byte-identity gate.
1763    pub fn generate_spec_glm5_gated(
1764        &self,
1765        e: &Engine,
1766        prompt: &[u32],
1767        max_new: usize,
1768        k: usize,
1769        mut knobs: Glm5SpecKnobs<'_>,
1770    ) -> Res<(Vec<u32>, usize, usize)> {
1771        let cap = Self::hyper_batch_cap();
1772        if k == 0 || k + 1 > cap {
1773            return Err(format!(
1774                "generate_spec_glm5: k={k} outside 1..={} (verify rows = k+1 must stay \
1775                 inside the decode-exact knee, cap {cap})",
1776                cap - 1
1777            )
1778            .into());
1779        }
1780        if max_new == 0 {
1781            return Ok((Vec::new(), 0, 0));
1782        }
1783        let max_ctx = prompt.len() + max_new + k + 8;
1784        let mut sess = self.glm5_spec_session_new(e, prompt, max_ctx, None)?;
1785        let mut out: Vec<u32> = Vec::with_capacity(max_new + k);
1786        let mut drafted = 0usize;
1787        let mut accepted = 0usize;
1788        while out.len() < max_new && !sess.finished() {
1789            let (burst, d, a) = self.glm5_spec_session_burst_gated(
1790                e,
1791                &mut sess,
1792                max_new - out.len(),
1793                k,
1794                &[],
1795                &mut knobs,
1796            )?;
1797            if burst.is_empty() {
1798                break; // ctx guard tripped with nothing new — never spin
1799            }
1800            out.extend(burst);
1801            drafted += d;
1802            accepted += a;
1803        }
1804        out.truncate(max_new);
1805        Ok((out, drafted, accepted))
1806    }
1807
1808    /// BATCHED MTP-PLANE WARM (loop-port fold-in; doc at the call site in
1809    /// `glm5_spec_session_new`): fill the NextN block's latent plane with rows for pairs
1810    /// `(tokens_next[i], hiddens row i)`, i in `0..t`, in chunked t-parallel passes —
1811    /// ops 1-7 of `mtp_head_forward_mla_cached` batched over the chunk (embed gather,
1812    /// enorm/hnorm, the eh_proj concat via `place_rows_strided`, attn_norm), then ONE
1813    /// `mla_attn_cached` append per chunk (the prime-class t>1 arm the trunk's own MLA
1814    /// layers warm through; its attention output is discarded — the plane rows are the
1815    /// product). The MoE FFN, final norm and lm-head of the per-token chain are never
1816    /// run: they fed nothing but the (discarded) draft logits of prompt positions.
1817    fn glm5_mtp_plane_fill(
1818        &self,
1819        e: &Engine,
1820        tokens_next: &[u32],
1821        hiddens: &CudaSlice<f32>,
1822        t: usize,
1823        cache: &mut Cache,
1824    ) -> Res<()> {
1825        let mtp = self
1826            .mtp
1827            .as_ref()
1828            .ok_or("glm5_mtp_plane_fill with no MTP head loaded")?;
1829        let il = self
1830            .plan
1831            .mtp_blocks
1832            .first()
1833            .ok_or("ModelPlan declares no MTP block")?
1834            .layer
1835            .index as usize;
1836        let Mixer::Mla(mla) = &mtp.mixer else {
1837            return Err("glm5_mtp_plane_fill serves MLA-mixer MTP blocks only".into());
1838        };
1839        if tokens_next.len() < t {
1840            return Err(format!(
1841                "glm5_mtp_plane_fill: {t} rows requested over {} successor tokens",
1842                tokens_next.len()
1843            )
1844            .into());
1845        }
1846        let n_embd = self.cfg.n_embd as usize;
1847        let eps = self.cfg.rms_eps;
1848        // Chunk bound: the trunk prime's workspace discipline — bounds the t>1 attention
1849        // workspace and the transient buffers below without changing the append semantics
1850        // (`mla_attn_cached` appends at the plane's running length either way).
1851        const CHUNK: usize = 512;
1852        let mut done = 0usize;
1853        while done < t {
1854            let tc = (t - done).min(CHUNK);
1855            let e_emb = e.htod(
1856                &self
1857                    .embd
1858                    .try_gather(n_embd, &tokens_next[done..done + tc])?,
1859            )?;
1860            let mut e_norm = e.uninit(tc * n_embd)?;
1861            e.rms_norm(&e_emb, mtp.enorm.float_data(), &mut e_norm, n_embd, tc, eps)?;
1862            // hnorm over the chunk's hidden rows (one contiguous view copy — rms_norm
1863            // takes an owned-slice operand).
1864            let hv = e.view(hiddens, (done + tc) * n_embd);
1865            let mut h_rows = e.uninit(tc * n_embd)?;
1866            e.copy_view_into(
1867                &mut h_rows,
1868                0,
1869                &hv.slice(done * n_embd..(done + tc) * n_embd),
1870                tc * n_embd,
1871            )?;
1872            let mut h_norm = e.uninit(tc * n_embd)?;
1873            e.rms_norm(
1874                &h_rows,
1875                mtp.hnorm.float_data(),
1876                &mut h_norm,
1877                n_embd,
1878                tc,
1879                eps,
1880            )?;
1881            // concat rows [tc, 2*n_embd] = [enorm ; hnorm] — two strided placements.
1882            let mut concat = e.uninit(tc * 2 * n_embd)?;
1883            e.place_rows_strided(&e_norm, &mut concat, n_embd, tc, 2 * n_embd, 0)?;
1884            e.place_rows_strided(&h_norm, &mut concat, n_embd, tc, 2 * n_embd, n_embd)?;
1885            let inp_sa = e.matmul(&mtp.eh_proj, &concat, tc)?;
1886            let mut a_norm = e.uninit(tc * n_embd)?;
1887            e.rms_norm(
1888                &inp_sa,
1889                mtp.attn_norm.float_data(),
1890                &mut a_norm,
1891                n_embd,
1892                tc,
1893                eps,
1894            )?;
1895            let pos: Vec<i32> = (done as i32..(done + tc) as i32).collect();
1896            let pos_d = e.htod_i32(&pos)?;
1897            let _ = self.mla_attn_cached(e, mla, &a_norm, &pos_d, tc, il, cache)?;
1898            done += tc;
1899        }
1900        Ok(())
1901    }
1902
1903    /// Row `row` of a `[rows, n_embd]` device stack, copied into its own `[n_embd]` buffer
1904    /// (the MTP `h_seed` handoff shape).
1905    fn glm5_seed_row(
1906        &self,
1907        e: &Engine,
1908        src: &CudaSlice<f32>,
1909        rows: usize,
1910        row: usize,
1911    ) -> Res<CudaSlice<f32>> {
1912        let n_embd = self.cfg.n_embd as usize;
1913        let stack = e.view(src, rows * n_embd);
1914        let view = stack.slice(row * n_embd..(row + 1) * n_embd);
1915        let mut seed = e.uninit(n_embd)?;
1916        e.copy_view_into(&mut seed, 0, &view, n_embd)?;
1917        Ok(seed)
1918    }
1919
1920    /// SERVED-SESSION ENTRY (lane/glm5-spec-routing): prime the prompt, warm the MTP plane,
1921    /// draw the boundary token, and hand back a [`Glm5SpecSession`] the worker bursts.
1922    ///
1923    /// `sampling`: `None` / `temp <= 0` = the greedy byte-contract route (the instrument);
1924    /// `Some` with `temp > 0` = the sampled route — the boundary token, the draft chain and
1925    /// the accept walk all draw through the session's own Philox counters (`sctr` device
1926    /// events, `uctr` host accept-test uniforms via `spec::host_u01`, tag 0xFFFF_FFFE), so a
1927    /// session's randomness never repeats across bursts (the session-continuity law).
1928    /// PENALIZED requests (greedy or sampled) open only under `MEMRA_SPEC_PENALTY=1`
1929    /// (`glm5_penalty_admit`; doc on [`glm5_spec_penalty_on`]) and refuse loudly otherwise —
1930    /// worker admission keeps them on the plain path while the arm is dark.
1931    pub fn glm5_spec_session_new(
1932        &self,
1933        e: &Engine,
1934        prompt: &[u32],
1935        ctx_cap: usize,
1936        sampling: Option<SpecSampling>,
1937    ) -> Res<Glm5SpecSession> {
1938        if self.hyper.is_none() {
1939            return Err("generate_spec_glm5 requires a HyperConnections trunk".into());
1940        }
1941        // Two parallel/spec programs on one model never silently coexist unless the
1942        // composition is EXPLICITLY armed: the spec x TP verify/rollback wiring
1943        // (lane/glm5-composition) exists and is rig-gated, but it has zero real-artifact
1944        // receipts, so sessions on a SHARDED model stay co-refused unless
1945        // MEMRA_GLM5_SPEC_TP=1 lifts the refusal (default OFF by design — the FLAGS row).
1946        // The predicate is the MODEL's own sharding (the same per-layer truth the verify
1947        // walk keys on), never the MEMRA_GLM5_TP env: sharding is a load-time property,
1948        // and an env read here is bypassable after load (set/load/unset) and spuriously
1949        // refuses an UNSHARDED model in a process that still carries the env (the #80
1950        // review's confirmed finding).
1951        let tp_sharded = self.layers.iter().any(|l| match &l.mixer {
1952            Mixer::Kda(la) => la.tp.is_some(),
1953            Mixer::Mla(mla) => mla.tp.is_some(),
1954            _ => false,
1955        });
1956        if tp_sharded {
1957            if !glm5_spec_tp_on() {
1958                return Err(
1959                    "glm5 spec is co-refused on a MEMRA_GLM5_TP-sharded model: set \
1960                     MEMRA_GLM5_SPEC_TP=1 to run the gated spec x TP composition \
1961                     (default OFF — zero real-artifact receipts; every other admission \
1962                     law still holds)"
1963                        .into(),
1964                );
1965            }
1966            if !glm5_verify_batch_on() {
1967                return Err("MEMRA_GLM5_SPEC_TP=1 requires the BATCHED verify walk \
1968                     (MEMRA_GLM5_VERIFY_BATCH must not be 0): the per-row rollback seam \
1969                     carries no TP arm"
1970                    .into());
1971            }
1972            // The ARMED announce prints AFTER the last admission law below — a session
1973            // refused later (draft source, ppN qualification, penalties, ctx) must never
1974            // log the engagement receipt (the fleet's prints-ARMED-then-serves-plain
1975            // trap class; rig-gates/03 caught SF3 logging it on a refused session).
1976        }
1977        // DRAFT-SOURCE SELECTION — through the GENERAL law
1978        // ([`crate::spec::resolve_draft_source_kind`], lane/glm5-extract2): DFlash2 when the
1979        // drafter is loaded (MEMRA_GLM5_DFLASH — it wins over a co-loaded MTP head, the boot
1980        // receipt states the selection), native MTP otherwise; neither = the loud refusal
1981        // below, whose bytes name the glm5 flags because the FAMILY owns the how-to-arm text.
1982        // The native MTP head is NOT required for the DFlash2 source (the q38 pattern).
1983        let dflash_src = self.glm5_dflash.as_ref();
1984        let source_kind = crate::spec::resolve_draft_source_kind(
1985            self.plan.draft_source,
1986            self.mtp.is_some(),
1987            dflash_src.is_some(),
1988        )
1989        .map_err(|why| {
1990            // The general law says WHY there is no usable source; the family owns the
1991            // how-to-arm text. Composed so the sentence is true on BOTH of the law's refusal
1992            // branches (nothing loaded / a head loaded under a plan that does not claim it),
1993            // rather than asserting "requires a draft source" at an operator whom the bracket
1994            // then tells a head IS loaded.
1995            format!(
1996                "generate_spec_glm5 cannot select a draft source ({why}). Arm one: the \
1997                 embedded MTP head (MEMRA_GLM5_MTP=1; a full MoE layer, unloaded by default) \
1998                 or the DFlash2 drafter (MEMRA_GLM5_DFLASH=<dir-or-hf-spec>)"
1999            )
2000        })?;
2001        if prompt.len() < 2 {
2002            return Err(
2003                "generate_spec_glm5 needs a prompt of >= 2 tokens (the MTP plane warms on \
2004                 (token[i+1], hidden[i]) pairs)"
2005                    .into(),
2006            );
2007        }
2008        if dflash_src.is_none() && crate::spec::spec_hpost() {
2009            // MTP-carrier-specific refusal: the DFlash2 source consumes tapped trunk
2010            // features, not the h_seed carrier, so the flag has nothing to flip there.
2011            return Err(
2012                "generate_spec_glm5 has no MEMRA_SPEC_HPOST arm: the flag flips the MTP \
2013                 carrier to the post-norm hidden, but this loop seeds every committed pair \
2014                 from the trunk's PRE-output_norm collapsed rows (LANE.md §A). Mixing the \
2015                 two silently degrades drafts; the HPOST twin needs its own gate before it \
2016                 may run"
2017                    .into(),
2018            );
2019        }
2020        // ppN split (lane/glm5-ppn-verify): the verify walk, the rollback and the MTP
2021        // chain all run under the split now — but an UNQUALIFIED pipeline rewrite still
2022        // refuses loudly at the session seam, before any cache is allocated over
2023        // stage-sharded weights (worker admission additionally bounds the stage count to
2024        // the gated set; see glm5_spec_capable).
2025        if crate::pp::pp_cuts(self.layers.len()).is_some()
2026            && !self.rewrite_allowed(memra_gguf::execution_manifest::RewriteSurface::Pipeline)
2027        {
2028            return Err("pipeline rewrite is not qualified for this ModelPlan".into());
2029        }
2030        // PENALTY ARM admission (lane/spec-exclusions-20260902): a penalized request opens
2031        // only under MEMRA_SPEC_PENALTY=1; `pen` rides the session for every round, and
2032        // `sampling` keeps its route meaning (`None` = greedy verify) — a greedy request
2033        // with penalties is `sampling: None, pen: Some`.
2034        let pen = glm5_penalty_admit(sampling.as_ref())?;
2035        let sampling = sampling.filter(|sp| sp.temp > 0.0);
2036        // Room for the prompt, the anchor row and at least one verify round.
2037        if prompt.len() + 4 > ctx_cap {
2038            return Err(format!(
2039                "glm5 spec session needs ctx for prompt {} + anchor + one verify round, \
2040                 cap {ctx_cap}",
2041                prompt.len()
2042            )
2043            .into());
2044        }
2045        let n_vocab = self.output.out_features();
2046        // FR-SPEC TRIM (module doc): a loaded `MEMRA_FRSPEC_TRIM` artifact means the draft
2047        // head projects over gathered top-N rows and every draft pick is a RANK id that
2048        // must remap through d2t to the true vocabulary BEFORE it is chained or verified.
2049        // The verify walk stays full-vocab regardless — the invariant under gate.
2050        if let Some(map) = self.glm5_d2t() {
2051            if map.iter().any(|&t| t as usize >= n_vocab) {
2052                return Err(format!(
2053                    "glm5 FR-Spec d2t carries a token id >= n_vocab {n_vocab} — the ranks \
2054                     artifact was minted for a different vocabulary"
2055                )
2056                .into());
2057            }
2058            // Engagement receipt (the dspark trim receipt's shape): the server-log line
2059            // the trim arm's per-session engagement is verified by.
2060            eprintln!("{}", self.glm5_trim_engagement_line(map));
2061        }
2062        // Confidence-gate engagement receipt (loop-port 2; the deploy-gate greps this —
2063        // never-serve-greedy law's receipt discipline): armed iff MEMRA_SPEC_PMIN > 0.
2064        if glm5_pmin() > 0.0 {
2065            eprintln!(
2066                "[glm5-spec] draft confidence gate armed: PMIN={:.3} PMIN0={} (native \
2067                 chain p-of-pick; DFlash2 selector-q tau-slot truncation)",
2068                glm5_pmin(),
2069                glm5_pmin0() as u8,
2070            );
2071        }
2072        // The MTP plane index is a NATIVE-arm need; the DFlash2 source never touches the
2073        // plane (it still allocates below — plan-structural, the named cost in the module
2074        // doc — but nothing reads or resets it).
2075        let mtp_il = match dflash_src {
2076            Some(_) => None,
2077            None => Some(
2078                self.plan
2079                    .mtp_blocks
2080                    .first()
2081                    .ok_or("ModelPlan declares no MTP block")?
2082                    .layer
2083                    .index as usize,
2084            ),
2085        };
2086        // FIRST-TOKEN PROFILE (lane/b200-spec-ttft-20260902, `MEMRA_SPEC_PROF=1`): the
2087        // head engine resolves before any device work (a pure placement lookup), so the
2088        // clock can bound every creation phase on both the primary and head streams.
2089        let eh = self.glm5_head_engine(e)?;
2090        let mut prof = spec_prof_on().then(SpecFirstTokenProf::default);
2091        let mut pclk = prof.as_ref().map(|_| ProfClock::start(e, eh));
2092        if let Some(pf) = prof.as_mut() {
2093            pf.free_mb_before = self.glm5_free_mb(e);
2094        }
2095        // Stage-owned allocation under a split (each layer's planes on its stage's device,
2096        // trailing MTP plane on the last stage); door shut = plain `Cache::new_planned`.
2097        let mut cache = crate::pp::new_cache_planned(e, &self.cfg, &self.plan, ctx_cap)?;
2098        if let (Some(pf), Some(ck)) = (prof.as_mut(), pclk.as_mut()) {
2099            pf.cache_alloc_ms = ck.lap(e, eh);
2100        }
2101
2102        // ---- prime, boundary token, draft-source warm over the prompt ----
2103        // `prime_cache` routes to its own ppN twin under the split; `hiddens` is owned by
2104        // the LAST stage's engine (its published contract) — exactly where the MTP chain
2105        // below runs, so the warm consumes it with no device bounce. DFlash2 source: the
2106        // prime walk fills the armed HcTapSink with every prompt row's contracted tap
2107        // features (the drafter's context; round 1 ingests them into its own KV).
2108        let plen = prompt.len();
2109        let n_embd = self.cfg.n_embd as usize;
2110        let tap_layers = match dflash_src {
2111            Some(dr) => Some(glm5_dflash_tap_layers(&dr.draft, self.layers.len())?),
2112            None => None,
2113        };
2114        // DRAFTER PRIME ARM (lane/spec-route-depth-20260902): the device-resident arm ingests
2115        // taps inside the prime at every range boundary; the eager arm (the pre-lane literal)
2116        // primes once over a whole-prompt host sink and leaves the ingest to round 1. (The
2117        // chunked host-tap arm, MEMRA_GLM5_DRAFT_PRIME_V2, was REJECTED on the pair and removed
2118        // 2026-09-05.) `hidden_rows` is the row count of the returned `hiddens` stack (the whole
2119        // prompt on the eager arm, the LAST chunk on the chunked arm) — the boundary
2120        // capture indexes its last row through it.
2121        let mut v2_kv: Option<DflashKv> = None;
2122        let (logits0, hiddens, hidden_rows) = match (dflash_src, tap_layers.as_ref()) {
2123            (Some(dr), Some(taps)) if glm5_draft_taps_device_on() => {
2124                // DEVICE-RESIDENT ARM (doc on `glm5_draft_taps_device_on`): ONE whole-prompt
2125                // prime; the ingest happens inside it at every range boundary.
2126                let kv = DflashKv::new(eh, &dr.draft.cfg, ctx_cap)?;
2127                if let (Some(pf), Some(ck)) = (prof.as_mut(), pclk.as_mut()) {
2128                    pf.draft_alloc_ms = ck.lap(e, eh);
2129                    pf.draft_kv_mb = dflash_kv_bytes(&dr.draft.cfg, ctx_cap) as f64 / 1e6;
2130                }
2131                let ring = crate::hybrid_forward::hyper_prime_call_rows(
2132                    plen,
2133                    self.layers.len(),
2134                    self.gdn_prime_grid_on(),
2135                );
2136                let mut sink = HcTapSink::new_device_staged_at(taps.clone(), n_embd, ring, 0);
2137                sink.ingest_state = Some(Box::new(Glm5DraftPrimeInflight {
2138                    kv,
2139                    taps: taps.clone(),
2140                    n_embd,
2141                    ring,
2142                    stage: (0..taps.len()).map(|_| None).collect(),
2143                    rows_dev: None,
2144                    prof_on: prof.is_some(),
2145                    copy_ms: 0.0,
2146                    feat_ms: 0.0,
2147                    kv_ms: 0.0,
2148                    chunks: 0,
2149                }));
2150                cache.hc_taps = Some(sink);
2151                let (l, _seed, h) = self.prime_cache(e, prompt, &mut cache, 0)?;
2152                let walk_ms = pclk.as_mut().map(|ck| ck.lap(e, eh));
2153                let mut sink = cache
2154                    .hc_taps
2155                    .take()
2156                    .ok_or("device-resident drafter prime: tap sink vanished")?;
2157                let st = sink
2158                    .ingest_state
2159                    .take()
2160                    .ok_or("device-resident drafter prime: ingest state vanished")?
2161                    .downcast::<Glm5DraftPrimeInflight>()
2162                    .map_err(|_| "device-resident drafter prime: ingest state of the wrong type")?;
2163                let st = *st;
2164                if st.kv.len != plen {
2165                    return Err(format!(
2166                        "device-resident drafter prime covered {} of {plen} prompt rows \
2167                         (the prime's range loop must hand every range to the ingest)",
2168                        st.kv.len
2169                    )
2170                    .into());
2171                }
2172                if let (Some(pf), Some(walk)) = (prof.as_mut(), walk_ms) {
2173                    // The ingest ran INSIDE the prime walk: split it back out so `prime`
2174                    // stays the trunk's share and `draft_prime` the drafter's.
2175                    let ingest = st.copy_ms + st.feat_ms + st.kv_ms;
2176                    pf.prime_ms = (walk - ingest).max(0.0);
2177                    pf.draft_prime_ms = ingest;
2178                    pf.draft_prime_h2d_ms = st.copy_ms;
2179                    pf.draft_prime_feat_ms = st.feat_ms;
2180                    pf.draft_prime_kv_ms = st.kv_ms;
2181                    pf.draft_prime_rows = plen;
2182                    pf.draft_prime_chunks = st.chunks;
2183                    pf.draft_prime_arm = "device";
2184                }
2185                v2_kv = Some(st.kv);
2186                (l, h, plen)
2187            }
2188            _ => {
2189                if let Some(taps) = tap_layers.as_ref() {
2190                    let t_sink = std::time::Instant::now();
2191                    cache.hc_taps = Some(HcTapSink::new(taps.clone(), n_embd, plen));
2192                    if let Some(pf) = prof.as_mut() {
2193                        pf.sink_alloc_ms = t_sink.elapsed().as_secs_f64() * 1e3;
2194                    }
2195                }
2196                let (l, _seed, h) = self.prime_cache(e, prompt, &mut cache, 0)?;
2197                if let (Some(pf), Some(ck)) = (prof.as_mut(), pclk.as_mut()) {
2198                    pf.prime_ms = ck.lap(e, eh);
2199                    pf.prime_tap_dtoh_ms = cache
2200                        .hc_taps
2201                        .as_ref()
2202                        .map(|sk| sk.dtoh_ns as f64 / 1e6)
2203                        .unwrap_or(0.0);
2204                }
2205                (l, h, plen)
2206            }
2207        };
2208        // Prompt-boundary capture (lane/glm5-prefix-latent2): taken NOW — after the prime
2209        // filled every plane to the boundary, before the anchor/draft machinery below and
2210        // before any burst mutates the conv/ssm state or laps the tail ring. DFlash2-only:
2211        // the native arm's plane fill moves the MTP latent layer past the boundary before a
2212        // capture could be taken, and restore refuses the native source anyway. A refusal
2213        // drops the capture loudly and the session serves regardless.
2214        let prefix_capture = if glm5_spec_prefix_on() && dflash_src.is_some() {
2215            self.glm5_prefix_boundary_capture(e, eh, &cache, &logits0, &hiddens, plen, hidden_rows)
2216        } else {
2217            None
2218        };
2219        if let (Some(pf), Some(ck)) = (prof.as_mut(), pclk.as_mut()) {
2220            pf.capture_ms = ck.lap(e, eh);
2221        }
2222        // The penalty window spans the SESSION (`pen_window_seed`): the prompt now, every
2223        // committed token from here. Empty with penalties off — the anchor rule below is
2224        // then the pre-lane literal in both regimes.
2225        let pen_hist = glm5_pen_window_seed(pen.as_ref(), prompt);
2226        let mut sctr = 0u32;
2227        let anchor = glm5_anchor(
2228            eh,
2229            &logits0,
2230            sampling.as_ref(),
2231            pen.as_ref(),
2232            &pen_hist,
2233            &mut sctr,
2234            "glm5-prime",
2235        )?;
2236        if let (Some(pf), Some(ck)) = (prof.as_mut(), pclk.as_mut()) {
2237            pf.anchor_ms = ck.lap(e, eh);
2238        }
2239
2240        // Keyed on the KIND the general law returned, not on a second local re-derivation:
2241        // a seam whose answer is recomputed by its consumer is decoration. (`tap_layers` is
2242        // `Some` exactly when `dflash_src` is, and the law returns `Dflash2` exactly then, so
2243        // this is the same program the pre-seam code ran — the `_` arm's refusal is the
2244        // never-taken proof of that rather than a silent fallback.)
2245        let (mut draft, pending) = match (source_kind, dflash_src, tap_layers) {
2246            (crate::spec::DraftSourceKind::Dflash2, Some(dr), Some(taps)) => {
2247                if let Some(kv) = v2_kv.take() {
2248                    // Chunked arm: the KV already holds every prompt row (kv.len == plen);
2249                    // round 1 finds nothing pending and walks straight to its block forward.
2250                    debug_assert_eq!(kv.len, plen, "chunked drafter prime must cover the prompt");
2251                    (
2252                        Glm5DraftState::Dflash2 {
2253                            kv,
2254                            pending: Vec::new(),
2255                            taps,
2256                        },
2257                        Vec::new(),
2258                    )
2259                } else {
2260                    let sink = cache
2261                        .hc_taps
2262                        .take()
2263                        .ok_or("glm5 dflash prime tap sink vanished")?;
2264                    // Drafter ctx KV on the HEAD engine (where the drafter weights loaded and
2265                    // every round's chain runs); prompt feature rows ride `pending` so round 1
2266                    // ingests them through the one chunked path.
2267                    let kv = DflashKv::new(eh, &dr.draft.cfg, ctx_cap)?;
2268                    if let Some(pf) = prof.as_mut() {
2269                        pf.draft_kv_mb = dflash_kv_bytes(&dr.draft.cfg, ctx_cap) as f64 / 1e6;
2270                    }
2271                    (
2272                        Glm5DraftState::Dflash2 {
2273                            kv,
2274                            pending: sink.rows,
2275                            taps,
2276                        },
2277                        Vec::new(),
2278                    )
2279                }
2280            }
2281            (crate::spec::DraftSourceKind::Dflash2, _, _) => {
2282                return Err(
2283                    "the draft-source law selected DFlash2 but this session resolved no tap \
2284                     layers — a load-path bug, refused instead of silently drafting from the \
2285                     MTP plane (a VANISHED tap sink is a different failure, caught by name in \
2286                     the Dflash2 arm itself)"
2287                        .into(),
2288                );
2289            }
2290            (crate::spec::DraftSourceKind::NativeMtp, _, _) => {
2291                // BATCHED PLANE WARM (loop-port fold-in — the map's #4, the spec.rs
2292                // `mtp_kv_fill_all` pattern re-aimed at the MLA plane): pairs
2293                // (prompt[i+1], h_i) at plane pos i, i in 0..P-1, filled in CHUNKED
2294                // t-parallel passes instead of P-1 sequential full-block forwards. The
2295                // sequential warm ran ~400 tok/s — the measured +2.5 s TTFT per 1k
2296                // prompt tokens, spec-battery flip condition 1 by name. MTP rows are
2297                // INDEPENDENT given the trunk hiddens (no row-to-row recurrence — the
2298                // plane is the only carrier), so the fill is exact in structure; the
2299                // t>1 attention takes the prime-class program, which can only move
2300                // DRAFTS, never output (verify arbitrates; the byte-identity batteries
2301                // stay the proof).
2302                self.glm5_mtp_plane_fill(eh, &prompt[1..], &hiddens, plen - 1, &mut cache)?;
2303                // pending = committed (token, h_seed) pairs not yet fed to the MTP plane.
2304                // The LAST pair's logits are the next round's first draft — the re-warm
2305                // doubles as draft 1.
2306                let pending = vec![(anchor, self.glm5_seed_row(eh, &hiddens, plen, plen - 1)?)];
2307                (Glm5DraftState::NativeMtp, pending)
2308            }
2309        };
2310        if let (Some(pf), Some(ck)) = (prof.as_mut(), pclk.as_mut()) {
2311            pf.draft_alloc_ms += ck.lap(e, eh);
2312        }
2313        // EAGER-ARM INGEST AT CREATION (doc on `glm5_draft_prime_lazy_on`): the prompt's tap
2314        // rows go into the drafter KV NOW, before the session (and its anchor) is handed to
2315        // the worker, unless the lazy seam asks for the round-1 placement. The chunked arm
2316        // arrives here with nothing pending.
2317        if !glm5_draft_prime_lazy_on()
2318            && let (Glm5DraftState::Dflash2 { kv, pending, taps }, Some(dr)) =
2319                (&mut draft, dflash_src)
2320            && !pending.is_empty()
2321        {
2322            let rows = std::mem::take(pending);
2323            let stats = self.glm5_dflash_ingest_rows(
2324                eh,
2325                &dr.draft,
2326                kv,
2327                &rows,
2328                taps.len() * n_embd,
2329                pclk.as_mut(),
2330            )?;
2331            if let Some(pf) = prof.as_mut() {
2332                stats.write(pf, "eager");
2333            }
2334        }
2335        if let Some(pf) = prof.as_mut() {
2336            pf.free_mb_after = self.glm5_free_mb(e);
2337        }
2338        // Composition engagement receipt — printed immediately before the session is
2339        // RETURNED (after every admission law, the d2t vocabulary check, the cache
2340        // allocation and the prompt prime), so a grep for this line counts sessions that
2341        // actually opened; a refusal or a failure anywhere above never logs it (the #82
2342        // review moved it here after finding four fallible steps below its first home).
2343        if tp_sharded {
2344            eprintln!(
2345                "[glm5-spec] spec x TP composition ARMED (MEMRA_GLM5_SPEC_TP=1): verify \
2346                 rows ride the TP shards; rollback restores per-rank planes \
2347                 performance_claim=false"
2348            );
2349        }
2350        Ok(Glm5SpecSession {
2351            cache,
2352            committed: prompt.to_vec(),
2353            anchor,
2354            anchor_emitted: false,
2355            pending,
2356            draft,
2357            sampling,
2358            pen,
2359            pen_hist,
2360            sctr,
2361            uctr: 0,
2362            rounds: 0,
2363            rank_trimmed_rounds: 0,
2364            done: false,
2365            max_ctx: ctx_cap,
2366            mtp_il,
2367            prefix_capture,
2368            prof_rounds: prof.as_ref().map(|_| SpecRoundsLog::default()),
2369            prof,
2370        })
2371    }
2372
2373    /// Range-begin hook of the device-resident drafter prime (called by the prime's range
2374    /// loop, `hybrid_forward.rs`): anchor the chunk ring at the range's first row so the
2375    /// walk's `base - origin` lands in `[0, ring)`. No-op on every other sink.
2376    pub(crate) fn glm5_taps_range_begin(&self, cache: &mut Cache, start: usize) {
2377        if let Some(sink) = cache.hc_taps.as_mut()
2378            && sink.ingest_state.is_some()
2379        {
2380            sink.origin = start;
2381        }
2382    }
2383
2384    /// Range-done hook of the device-resident drafter prime: the range's tap planes are
2385    /// complete on their writing devices (the range call returned host logits, so the last
2386    /// stage drained, and every earlier stage's rows were consumed by it), so ingest them
2387    /// now. No-op on every other sink; a state of another type is put back untouched.
2388    pub(crate) fn glm5_taps_range_done(
2389        &self,
2390        e: &Engine,
2391        cache: &mut Cache,
2392        start: usize,
2393        end: usize,
2394    ) -> Res<()> {
2395        let Some(sink) = cache.hc_taps.as_mut() else {
2396            return Ok(());
2397        };
2398        let Some(state) = sink.ingest_state.take() else {
2399            return Ok(());
2400        };
2401        let mut st = match state.downcast::<Glm5DraftPrimeInflight>() {
2402            Ok(st) => st,
2403            Err(other) => {
2404                sink.ingest_state = Some(other);
2405                return Ok(());
2406            }
2407        };
2408        let res = self.glm5_taps_ingest_range(e, sink, &mut st, start, end);
2409        sink.ingest_state = Some(st);
2410        res
2411    }
2412
2413    /// One range of the device-resident drafter prime: per tap slot, the plane on its
2414    /// writing device is interleaved into the head-device fc input with a 2D copy (after a
2415    /// peer copy into head-device staging when the slot's stage is another device; the
2416    /// source stream is drained before the plane is read), then `ctx_features` +
2417    /// `ingest_ctx` at the range width append the rows to the drafter KV.
2418    fn glm5_taps_ingest_range(
2419        &self,
2420        e: &Engine,
2421        sink: &mut HcTapSink,
2422        st: &mut Glm5DraftPrimeInflight,
2423        start: usize,
2424        end: usize,
2425    ) -> Res<()> {
2426        let t = end - start;
2427        if t == 0 {
2428            return Ok(());
2429        }
2430        if t > st.ring {
2431            return Err(format!(
2432                "device-resident tap ingest: range {start}..{end} ({t} rows) exceeds the \
2433                 sink ring of {} rows",
2434                st.ring
2435            )
2436            .into());
2437        }
2438        if st.kv.len != start {
2439            return Err(format!(
2440                "device-resident tap ingest: drafter KV holds {} rows but the range starts \
2441                 at {start} (a range was skipped or handed twice)",
2442                st.kv.len
2443            )
2444            .into());
2445        }
2446        let eh = self.glm5_head_engine(e)?;
2447        let dr = self
2448            .glm5_dflash
2449            .as_ref()
2450            .ok_or("device-resident tap ingest without a loaded drafter")?;
2451        let h = st.n_embd;
2452        let n_taps = st.taps.len();
2453        let mut pclk = st.prof_on.then(|| ProfClock::start(e, eh));
2454        if st.rows_dev.is_none() {
2455            st.rows_dev = Some(eh.uninit(st.ring * n_taps * h)?);
2456        }
2457        let rows_dev = st.rows_dev.as_mut().expect("just filled");
2458        for (slot, &il) in st.taps.iter().enumerate() {
2459            let buf = sink.dev[slot].as_ref().ok_or_else(|| {
2460                format!(
2461                    "device-resident tap ingest: slot {slot} (layer {il}) was never written \
2462                     over rows {start}..{end}"
2463                )
2464            })?;
2465            let es = self.glm5_tap_slot_engine(e, il)?;
2466            if es.ctx().ordinal() == eh.ctx().ordinal() {
2467                eh.copy_2d_dtod_async(rows_dev, slot * h, n_taps * h, buf, h, h, t)?;
2468            } else {
2469                if st.stage[slot].is_none() {
2470                    st.stage[slot] = Some(eh.uninit(st.ring * h)?);
2471                }
2472                let staging = st.stage[slot].as_mut().expect("just filled");
2473                eh.copy_peer_from_async(staging, es, buf, t * h)?;
2474                es.stream().synchronize()?;
2475                eh.copy_2d_dtod_async(rows_dev, slot * h, n_taps * h, staging, h, h, t)?;
2476            }
2477        }
2478        if let Some(ck) = pclk.as_mut() {
2479            st.copy_ms += ck.lap(e, eh);
2480        }
2481        let feats = dr.draft.ctx_features(eh, rows_dev, t)?;
2482        if let Some(ck) = pclk.as_mut() {
2483            st.feat_ms += ck.lap(e, eh);
2484        }
2485        let pos: Vec<i32> = ((st.kv.len as i32)..(st.kv.len + t) as i32).collect();
2486        dr.draft.ingest_ctx(eh, &mut st.kv, &feats, &pos, t)?;
2487        if let Some(ck) = pclk.as_mut() {
2488            st.kv_ms += ck.lap(e, eh);
2489        }
2490        st.chunks += 1;
2491        Ok(())
2492    }
2493
2494    /// The EAGER drafter ingest (one implementation for session creation and round 1):
2495    /// host feature rows `[n, row_w]` -> 256-row pageable HtoD chunks -> `ctx_features` ->
2496    /// `ingest_ctx`, appended at `kv.len`. Returns the profile buckets (all 0 with the
2497    /// clock absent).
2498    fn glm5_dflash_ingest_rows(
2499        &self,
2500        eh: &Engine,
2501        draft: &DflashDraft,
2502        kv: &mut DflashKv,
2503        rows: &[f32],
2504        row_w: usize,
2505        mut pclk: Option<&mut ProfClock>,
2506    ) -> Res<DraftIngestStats> {
2507        debug_assert_eq!(rows.len() % row_w, 0, "ragged feature rows");
2508        let n_new = rows.len() / row_w;
2509        let mut st = DraftIngestStats {
2510            rows: n_new,
2511            ..Default::default()
2512        };
2513        let mut r0 = 0usize;
2514        while r0 < n_new {
2515            let t_c = (n_new - r0).min(256);
2516            let chunk = eh.htod(&rows[r0 * row_w..(r0 + t_c) * row_w])?;
2517            if let Some(ck) = pclk.as_deref_mut() {
2518                st.h2d_ms += ck.lap(eh, eh);
2519            }
2520            let feats = draft.ctx_features(eh, &chunk, t_c)?;
2521            if let Some(ck) = pclk.as_deref_mut() {
2522                st.feat_ms += ck.lap(eh, eh);
2523            }
2524            let pos_c: Vec<i32> = ((kv.len as i32)..(kv.len + t_c) as i32).collect();
2525            draft.ingest_ctx(eh, kv, &feats, &pos_c, t_c)?;
2526            if let Some(ck) = pclk.as_deref_mut() {
2527                st.kv_ms += ck.lap(eh, eh);
2528            }
2529            r0 += t_c;
2530            st.chunks += 1;
2531        }
2532        Ok(st)
2533    }
2534
2535    /// Free device memory per device this model can hold state on (primary + ppN stages),
2536    /// for the depth profile's before/after samples.
2537    fn glm5_free_mb(&self, e: &Engine) -> Vec<(usize, u64)> {
2538        let mut out = vec![(e.ctx().ordinal(), e.free_mem_mb())];
2539        if let Ok(rt) = crate::pp::PpNRt::get(e) {
2540            for stage in 0..rt.n_stages() {
2541                let se = rt.engine(stage, e);
2542                let ord = se.ctx().ordinal();
2543                if !out.iter().any(|(o, _)| *o == ord) {
2544                    out.push((ord, se.free_mem_mb()));
2545                }
2546            }
2547        }
2548        out
2549    }
2550
2551    /// The engine that OWNS tap layer `il`'s device (the stage engine under a live split,
2552    /// the caller's engine otherwise) — the `glm5_tap_drain` placement rule, shared.
2553    fn glm5_tap_slot_engine<'e>(&self, e: &'e Engine, il: usize) -> Res<&'e Engine> {
2554        match crate::pp::pp_cuts(self.layers.len()) {
2555            Some(fence) if !crate::pp::pp2_streams_off() => {
2556                let rt = crate::pp::PpNRt::get(e)?;
2557                let stage = fence
2558                    .windows(2)
2559                    .position(|w| il >= w[0] && il < w[1])
2560                    .ok_or_else(|| format!("tap layer {il} outside every stage range"))?;
2561                Ok(rt.engine(stage, e))
2562            }
2563            _ => Ok(e),
2564        }
2565    }
2566
2567    /// Boundary capture for the DEFERRED prefix publication (lane/glm5-prefix-latent2,
2568    /// 2026-09-01): the generation-destroyed state at `pos == plen` — conv/ssm via
2569    /// `Cache::snapshot` (D2D copies), per-layer latent tails via `snapshot_tail`, the
2570    /// prime's boundary logits, the pre-output_norm boundary hidden. `None` (loud) on any
2571    /// refusal — a capture is an optimization the session must never fail on. The
2572    /// append-only planes (latent rows, final pool keys, full-attn KV) are NOT copied here:
2573    /// the worker slices them from the live cache at publish (`snapshot_plane_at`), which is
2574    /// legal because the glm5 verify rollback never truncates below the prime boundary.
2575    #[allow(clippy::too_many_arguments)]
2576    // allow: the parameter list is the capture contract (two engines, the cache, the
2577    // boundary logits and hidden stack, the boundary and the stack's own row count);
2578    // bundling would hide which row index is which
2579    fn glm5_prefix_boundary_capture(
2580        &self,
2581        e: &Engine,
2582        eh: &Engine,
2583        cache: &Cache,
2584        logits0: &[f32],
2585        hiddens: &CudaSlice<f32>,
2586        plen: usize,
2587        // Rows in `hiddens` (its last row is the boundary hidden): the whole prompt on the
2588        // eager cold prime, the last chunk on the chunked arm, the suffix on a restore.
2589        hidden_rows: usize,
2590    ) -> Option<crate::spec::SpecBoundaryCapture> {
2591        debug_assert_eq!(
2592            cache.pos, plen,
2593            "boundary capture must sit at the prime boundary"
2594        );
2595        let snap = match cache.snapshot(e) {
2596            Ok(s) => s,
2597            Err(err) => {
2598                eprintln!("[glm5-spec] prefix boundary capture SKIPPED (cache snapshot: {err})");
2599                return None;
2600            }
2601        };
2602        // The ONLY latent layer that may legitimately sit empty at the boundary is the
2603        // MTP/NextN plane (allocated, never executed by the trunk on the DFlash2 arm).
2604        // Identity-keyed, not length-keyed (PR #96 review round 2, finding 1): a TRUNK
2605        // plane empty at the boundary is a regression that must refuse the capture, or
2606        // three length-keyed layers downstream would each wave it through and reproduce
2607        // the parent lane's fabrication shape per-layer.
2608        let mtp_plane_il = self.plan.mtp_blocks.first().map(|b| b.layer.index as usize);
2609        let mut latent_tails = Vec::with_capacity(cache.latent.len());
2610        for (il, l) in cache.latent.iter().enumerate() {
2611            match l {
2612                Some(l) if l.len == 0 && cache.pos > 0 => {
2613                    if Some(il) != mtp_plane_il {
2614                        eprintln!(
2615                            "[glm5-spec] prefix boundary capture SKIPPED (trunk latent \
2616                             layer {il} is EMPTY at the boundary — not the MTP plane; a \
2617                             capture would publish an absent history for a live layer)"
2618                        );
2619                        return None;
2620                    }
2621                    latent_tails.push(None)
2622                }
2623                Some(l) => {
2624                    if l.len != plen {
2625                        eprintln!(
2626                            "[glm5-spec] prefix boundary capture SKIPPED (latent layer {il} \
2627                             len {} != boundary {plen})",
2628                            l.len,
2629                        );
2630                        return None;
2631                    }
2632                    match l.snapshot_tail(e) {
2633                        Ok(t) => latent_tails.push(Some(t)),
2634                        Err(err) => {
2635                            eprintln!(
2636                                "[glm5-spec] prefix boundary capture SKIPPED (latent layer \
2637                                 {il}: {err})"
2638                            );
2639                            return None;
2640                        }
2641                    }
2642                }
2643                None => latent_tails.push(None),
2644            }
2645        }
2646        let last_h = crate::spec::capture_boundary_hidden(
2647            eh,
2648            hiddens,
2649            hidden_rows,
2650            self.cfg.n_embd as usize,
2651        );
2652        Some(crate::spec::SpecBoundaryCapture {
2653            snap,
2654            pos: plen,
2655            logits: logits0.to_vec(),
2656            last_h,
2657            latent_tails,
2658        })
2659    }
2660
2661    /// Re-arm a glm5 spec session from a RESTORED trunk cache plus a published DFlash2
2662    /// drafter tail — the glm5 twin of `dspark_spec_session_from_restored`, EXTENDED with
2663    /// the suffix prime the multi-turn shape needs (lane/glm5-prefix-latent2, 2026-09-01).
2664    ///
2665    /// WHY IT IS EQUIVALENT TO A COLD PRIME, field by field:
2666    /// * `cache` — the caller's whole-entry restored trunk cache at `fed.len()` (KDA
2667    ///   conv/ssm + MLA latent rows + kpool keys + tail ring, the parent lane's restore),
2668    ///   and the SUFFIX primes onto it through `prime_cache` — the same continuation
2669    ///   program every chunk after the first of a cold prime runs.
2670    /// * drafter — `dkv` is rebuilt from the entry's tail into the SAME absolute rows
2671    ///   (`DflashKv::from_tail`, caller-side while the prefix cache is borrowable), and the
2672    ///   suffix's tap rows ride `pending` exactly as a cold session's prompt rows do — the
2673    ///   drafter's context is the committed tokens either way (a truncated tail below the
2674    ///   window can only move ACCEPTANCE, never output: verify arbitrates).
2675    /// * anchor — drawn from the SUFFIX prime's boundary logits with the request's own
2676    ///   sampler, exactly the cold composition; Philox counters fresh (a restore is a NEW
2677    ///   session — the frspec continuity law, the dspark restore's own convention).
2678    /// * republish — with `glm5_spec_prefix_on()` the session takes a NEW boundary capture
2679    ///   at `fed + suffix`, so the next turn hits a DEEPER prefix (the
2680    ///   MEMRA_SPEC_RESTORE_REPUBLISH posture; the worker's has_key dedupe drops equals).
2681    ///
2682    /// Refuses (never asserts) whenever the restored halves disagree — a caller that gets
2683    /// `Err` serves the plain hit (correct, slower).
2684    #[allow(clippy::too_many_arguments)]
2685    pub fn glm5_spec_session_from_restored(
2686        &self,
2687        e: &Engine,
2688        mut cache: Cache,
2689        fed: &[u32],
2690        suffix: &[u32],
2691        // The ENTRY's boundary logits (`ReuseEntry::last_logits`), read ONLY on the
2692        // full-cover arm (`suffix.is_empty()`), where there is no suffix prime to draw the
2693        // anchor from. A suffix-bearing restore ignores it and uses the prime's own row.
2694        boundary_logits: &[f32],
2695        dkv: DflashKv,
2696        ctx_cap: usize,
2697        sampling: Option<SpecSampling>,
2698    ) -> Res<Glm5SpecSession> {
2699        if self.hyper.is_none() {
2700            return Err("glm5_spec_session_from_restored requires a HyperConnections trunk".into());
2701        }
2702        // The composition laws a cold session enforces hold here too, fail-closed and by
2703        // name — a restored session must never be the door around an admission law.
2704        let tp_sharded = self.layers.iter().any(|l| match &l.mixer {
2705            Mixer::Kda(la) => la.tp.is_some(),
2706            Mixer::Mla(mla) => mla.tp.is_some(),
2707            _ => false,
2708        });
2709        if tp_sharded {
2710            return Err(
2711                "restored glm5 spec sessions carry no TP arm (the spec x TP composition is \
2712                 cold-session gated only); the plain hit serves"
2713                    .into(),
2714            );
2715        }
2716        if crate::pp::pp_cuts(self.layers.len()).is_some()
2717            && !self.rewrite_allowed(memra_gguf::execution_manifest::RewriteSurface::Pipeline)
2718        {
2719            return Err("pipeline rewrite is not qualified for this ModelPlan".into());
2720        }
2721        // DFlash2 source ONLY: the native MTP plane fill consumes trunk hiddens the restored
2722        // range does not have (re-running the trunk over it would be a second prime — the
2723        // whole cost this restore exists to avoid).
2724        let dr = self.glm5_dflash.as_ref().ok_or(
2725            "restored glm5 spec sessions require the DFlash2 drafter (MEMRA_GLM5_DFLASH): \
2726             the native MTP plane cannot be re-warmed from restored KV",
2727        )?;
2728        let source = crate::spec::resolve_draft_source_kind(
2729            self.plan.draft_source,
2730            self.mtp.is_some(),
2731            true,
2732        )
2733        .map_err(|why| format!("restored glm5 spec session has no draft source ({why})"))?;
2734        if !matches!(source, crate::spec::DraftSourceKind::Dflash2) {
2735            return Err(
2736                "restored glm5 spec sessions require the DFlash2 draft source; the plan \
2737                 selected another"
2738                    .into(),
2739            );
2740        }
2741        // PENALTY ARM admission: the cold constructor's law, verbatim (doc there).
2742        let pen = glm5_penalty_admit(sampling.as_ref())?;
2743        let sampling = sampling.filter(|sp| sp.temp > 0.0);
2744        if fed.is_empty() {
2745            return Err("restored glm5 spec session needs a non-empty restored prefix".into());
2746        }
2747        // FULL-COVER ARM (memra#74, lane/glm5-fullcover-spec-route): an empty suffix is the
2748        // repeated-prompt shape. It refuses unless the arm is armed AND the entry carried
2749        // its boundary logits: without them there is no anchor row and no way to start a
2750        // round, which is exactly the `spec_restore_refusal` full-cover rule for MTP.
2751        if suffix.is_empty() {
2752            if !glm5_spec_fullcover_on() {
2753                return Err(
2754                    "restored glm5 spec session needs a non-empty suffix unless \
2755                     MEMRA_GLM5_SPEC_FULLCOVER=1 (empty-suffix full-cover hits otherwise \
2756                     keep the plain boundary-logits resume)"
2757                        .into(),
2758                );
2759            }
2760            if boundary_logits.len() != self.output.out_features() {
2761                return Err(format!(
2762                    "full-cover glm5 spec restore needs the entry's boundary logits \
2763                     ({} rows, got {})",
2764                    self.output.out_features(),
2765                    boundary_logits.len(),
2766                )
2767                .into());
2768            }
2769        }
2770        if cache.pos != fed.len() {
2771            return Err(format!(
2772                "restored glm5 spec session needs a whole-entry trunk cache: cache.pos {} \
2773                 != restored prefix {}",
2774                cache.pos,
2775                fed.len(),
2776            )
2777            .into());
2778        }
2779        if dkv.len != fed.len() {
2780            return Err(format!(
2781                "restored draft KV len {} != restored prefix {}",
2782                dkv.len,
2783                fed.len(),
2784            )
2785            .into());
2786        }
2787        if dkv.cap != ctx_cap {
2788            return Err(
2789                format!("restored draft KV cap {} != session ctx {ctx_cap}", dkv.cap,).into(),
2790            );
2791        }
2792        // Room for prefix + suffix, the anchor row and at least one verify round.
2793        if fed.len() + suffix.len() + 4 > ctx_cap {
2794            return Err(format!(
2795                "restored glm5 spec session needs ctx for prefix {} + suffix {} + anchor + \
2796                 one verify round, cap {ctx_cap}",
2797                fed.len(),
2798                suffix.len(),
2799            )
2800            .into());
2801        }
2802        let n_vocab = self.output.out_features();
2803        if let Some(map) = self.glm5_d2t() {
2804            if map.iter().any(|&t| t as usize >= n_vocab) {
2805                return Err(format!(
2806                    "glm5 FR-Spec d2t carries a token id >= n_vocab {n_vocab} — the ranks \
2807                     artifact was minted for a different vocabulary"
2808                )
2809                .into());
2810            }
2811            eprintln!("{}", self.glm5_trim_engagement_line(map));
2812        }
2813        if glm5_pmin() > 0.0 {
2814            eprintln!(
2815                "[glm5-spec] draft confidence gate armed: PMIN={:.3} PMIN0={} (native \
2816                 chain p-of-pick; DFlash2 selector-q tau-slot truncation)",
2817                glm5_pmin(),
2818                glm5_pmin0() as u8,
2819            );
2820        }
2821        // ---- suffix prime over the restored planes (the continuation program), taps armed
2822        // for exactly the suffix rows (`HcTapSink::origin` anchors the sink at the restored
2823        // boundary; the chunked walk's absolute bases rebase through it).
2824        let n_embd = self.cfg.n_embd as usize;
2825        let taps = glm5_dflash_tap_layers(&dr.draft, self.layers.len())?;
2826        let eh = self.glm5_head_engine(e)?;
2827        // ---- STREAM ORDERING, UNCONDITIONAL AND BEFORE THE SUFFIX BRANCH (memra#95, the
2828        // fleet-fatal full-cover panic).
2829        //
2830        // Everything this session restores was written on the CALLER's stream, in the
2831        // worker's admission path: the trunk planes by `prefix_restore_at`, and the DFlash2
2832        // drafter ctx KV by `DflashKv::from_tail` (worker.rs, the "glm5 spec restore, half 1
2833        // of 2" block — a fresh allocation plus per-layer `memset_zeros_view` +
2834        // `copy_range_into`). The draft phase of round 1 then READS that KV through
2835        // `eh` = `glm5_head_engine`, which under a live ppN split is the last stage's OWN
2836        // Engine (`pp::PpNRt::build` gives every stage s>0 its own Engine even on the primary
2837        // device, for scratch-pool isolation) and is used OUTSIDE any `rt.enter` scope — so
2838        // it launches on that Engine's own stream, which is neither the caller's stream nor
2839        // the stage stream. Nothing ordered the two, and the drafter's first `forward_round`
2840        // could read the ctx rows before the import landed.
2841        //
2842        // `fence_stages_behind` is NOT the fix and was the first thing this lane got wrong:
2843        // it orders `StageRt::stream`, the enter-scope stream, and says nothing about a stage
2844        // ENGINE's own stream. `order_engine_behind` is the seam for a body that hands a
2845        // stage engine to a helper without entering the stage.
2846        //
2847        // Why only the FULL-COVER arm: the suffix arm calls `prime_cache` on `e`, whose
2848        // logits come back through `Engine::dtoh` (a `stream().synchronize()`), which drains
2849        // the caller's stream before round 1 ever runs. The full-cover arm has no prime and
2850        // no device readback at all between the import and the round — the anchor is drawn
2851        // from the entry's host-side boundary logits — so it is the only restored path with
2852        // nothing between the two. That is why production never saw this on the suffix path,
2853        // and it is why the ordering is done HERE, once per session and for both arms,
2854        // instead of relying on a host sync that belongs to the prime.
2855        //
2856        // The measured failure: a NaN drafter row -> the top-k selector's documented
2857        // exhausted-slot sentinel (`0xffffffff`, `cu/kernels.cu` `topk_rows_f32` and its
2858        // sharded twin) -> `walk: candidate 4294967295 outside codebook vocab` at
2859        // dflash.rs:221/:804, on the FIRST round of a restored session (the receipts put the
2860        // panic between the `RESTORED session` line and the round's first `[glm5-acc]`),
2861        // fleet-fatal after one respawn.
2862        crate::pp::PpNRt::order_engine_behind(e, eh)?;
2863        // FIRST-TOKEN PROFILE (`MEMRA_SPEC_PROF=1`): the restored shape pays no cache or
2864        // drafter allocation here (the caller restored both); its prime bucket is the
2865        // SUFFIX prime (0 on a full-cover hit), which is what makes the restore worth having.
2866        let mut prof = spec_prof_on().then(SpecFirstTokenProf::default);
2867        let mut pclk = prof.as_ref().map(|_| ProfClock::start(e, eh));
2868        // FULL COVER: no suffix, so no prime, no taps and no republish (the entry ALREADY
2869        // sits at this boundary, and a capture here would be the same key the worker's has_key
2870        // dedupe drops). The boundary row is the entry's own; the drafter ctx KV is already
2871        // at `cache.pos` from the tail, so `pending` is empty and round 1's
2872        // `kv.len == cache.pos` invariant holds without an ingest.
2873        let (logits_s, tap_rows, prefix_capture) = if suffix.is_empty() {
2874            (boundary_logits.to_vec(), Vec::new(), None)
2875        } else {
2876            cache.hc_taps = Some(HcTapSink::new_at(
2877                taps.clone(),
2878                n_embd,
2879                suffix.len(),
2880                fed.len(),
2881            ));
2882            let (logits_s, _seed, hiddens) = self.prime_cache(e, suffix, &mut cache, 0)?;
2883            if let (Some(pf), Some(ck)) = (prof.as_mut(), pclk.as_mut()) {
2884                pf.prime_ms = ck.lap(e, eh);
2885            }
2886            // Republish capture at the NEW (deeper) boundary — pos == fed + suffix here.
2887            let capture = if glm5_spec_prefix_on() {
2888                // `hiddens` is the SUFFIX stack: its last row is the boundary hidden
2889                // (indexed through `suffix.len()`, not the absolute boundary).
2890                self.glm5_prefix_boundary_capture(
2891                    e,
2892                    eh,
2893                    &cache,
2894                    &logits_s,
2895                    &hiddens,
2896                    cache.pos,
2897                    suffix.len(),
2898                )
2899            } else {
2900                None
2901            };
2902            let sink = cache
2903                .hc_taps
2904                .take()
2905                .ok_or("glm5 restored-session suffix tap sink vanished")?;
2906            (logits_s, sink.rows, capture)
2907        };
2908        if let (Some(pf), Some(ck)) = (prof.as_mut(), pclk.as_mut()) {
2909            pf.capture_ms = ck.lap(e, eh);
2910        }
2911        let mut committed = Vec::with_capacity(fed.len() + suffix.len());
2912        committed.extend_from_slice(fed);
2913        committed.extend_from_slice(suffix);
2914        // The penalty window is the whole committed prompt (restored prefix + suffix), which
2915        // is what the plain hit's sampler replays too ("penalty history replayed over the
2916        // whole prefix"). Empty with penalties off.
2917        let pen_hist = glm5_pen_window_seed(pen.as_ref(), &committed);
2918        let mut sctr = 0u32;
2919        let anchor = glm5_anchor(
2920            eh,
2921            &logits_s,
2922            sampling.as_ref(),
2923            pen.as_ref(),
2924            &pen_hist,
2925            &mut sctr,
2926            "glm5-restore",
2927        )?;
2928        if let (Some(pf), Some(ck)) = (prof.as_mut(), pclk.as_mut()) {
2929            pf.anchor_ms = ck.lap(e, eh);
2930        }
2931        // Engagement receipt (the dspark restore's shape — the deploy gate greps this; a
2932        // cached_tokens number alone cannot distinguish a spec restore from a plain hit).
2933        eprintln!(
2934            "[glm5-spec] RESTORED session: {} prefix tokens + {} suffix from cache — no \
2935             cold prime (drafter tail rows {}, arm {})",
2936            fed.len(),
2937            suffix.len(),
2938            dkv.len,
2939            if suffix.is_empty() {
2940                "full-cover"
2941            } else {
2942                "suffix-prime"
2943            },
2944        );
2945        Ok(Glm5SpecSession {
2946            cache,
2947            committed,
2948            anchor,
2949            anchor_emitted: false,
2950            pending: Vec::new(),
2951            draft: Glm5DraftState::Dflash2 {
2952                kv: dkv,
2953                pending: tap_rows,
2954                taps,
2955            },
2956            sampling,
2957            pen,
2958            pen_hist,
2959            sctr,
2960            uctr: 0,
2961            rounds: 0,
2962            rank_trimmed_rounds: 0,
2963            done: false,
2964            max_ctx: ctx_cap,
2965            mtp_il: None,
2966            prefix_capture,
2967            prof_rounds: prof.as_ref().map(|_| SpecRoundsLog::default()),
2968            prof,
2969        })
2970    }
2971
2972    /// The loaded FR-Spec draft->target map for the session's DRAFT SOURCE (None = full-vocab
2973    /// head, rank id == token id). A loaded DFlash2 drafter wins the source selection, so its
2974    /// trim ([`Self::glm5_dflash_trim`]) is the map; otherwise the embedded head's own map.
2975    fn glm5_d2t(&self) -> Option<&[u32]> {
2976        if self.glm5_dflash.is_some() {
2977            return self.glm5_dflash_trim().map(|(_, d2t)| d2t);
2978        }
2979        self.mtp
2980            .as_ref()
2981            .and_then(|head| head.d2t.as_deref())
2982            .filter(|map| !map.is_empty())
2983    }
2984
2985    /// The DFlash2 round's trimmed draft head `(rows, d2t)`, in the round's preference order:
2986    /// the MtpHead self-trim when its rows came from the TARGET head (an external student's
2987    /// head is never borrowed), else the `dflash_trim` slab (the MEMRA_MTP_SKIP stub, or the
2988    /// glm5 DFlash2 slab built when the NextN block is not loaded, lane/frspec-dflash2-
2989    /// 20260902). None = the full trunk head, stated in the boot receipt.
2990    pub fn glm5_dflash_trim(&self) -> Option<(&crate::model::GpuTensor, &[u32])> {
2991        self.mtp
2992            .as_ref()
2993            .filter(|m| m.d2t_from_target_head)
2994            .and_then(|m| m.shared_head_head.as_ref().zip(m.d2t.as_deref()))
2995            .or_else(|| {
2996                self.dflash_trim
2997                    .as_ref()
2998                    .map(|t| (&t.head, t.d2t.as_slice()))
2999            })
3000            .filter(|(_, d2t)| !d2t.is_empty())
3001    }
3002
3003    /// The trim engagement receipt at session admission (the dspark trim receipt's shape;
3004    /// the deploy gate greps the server log for it). Source-keyed wording: the DFlash2 route
3005    /// names the slab and the ranks file (`RANK-TRIMMED n_ranks=<n> src=<sha16>`), the native
3006    /// chain keeps its line.
3007    fn glm5_trim_engagement_line(&self, map: &[u32]) -> String {
3008        if self.glm5_dflash.is_some() {
3009            format!(
3010                "[glm5-spec] draft head RANK-TRIMMED n_ranks={} src={}",
3011                map.len(),
3012                self.frspec_src_sha16.as_deref().unwrap_or("unknown")
3013            )
3014        } else {
3015            format!(
3016                "[glm5-spec] draft head TRIMMED to {} rows (FR-Spec d2t engaged)",
3017                map.len()
3018            )
3019        }
3020    }
3021
3022    /// ONE serve burst (the worker's per-tick call, `step_glm5_spec`): rounds of
3023    /// draft(K) -> `glm5_verify_rows` -> accept -> rollback/commit until `target` new
3024    /// tokens are out, EOS commits, or the context guard trips. Returns
3025    /// `(burst, drafted, accepted)`; the burst may overshoot `target` by up to K (a
3026    /// round commits j+1 tokens atomically — the engine surplus stays committed in the
3027    /// session cache and the WORKER clamps public emission to the request budget, the
3028    /// SpecSession overshoot contract).
3029    pub fn glm5_spec_session_burst(
3030        &self,
3031        e: &Engine,
3032        sess: &mut Glm5SpecSession,
3033        target: usize,
3034        k: usize,
3035        eos: &[u32],
3036    ) -> Res<(Vec<u32>, usize, usize)> {
3037        self.glm5_spec_session_burst_inner(
3038            e,
3039            sess,
3040            target,
3041            k,
3042            eos,
3043            &mut Glm5SpecKnobs::default(),
3044            None,
3045        )
3046    }
3047
3048    /// [`glm5_spec_session_burst`] with a ROUND-CADENCE commit hook (lane/b200-spec-ttft-
3049    /// 20260902, the `MEMRA_SPEC_FIRST_TOKEN_EAGER` door's engine half; the spec.rs
3050    /// `on_commit` sse-cadence pattern): `on_commit` is called with every newly committed
3051    /// slice of the burst — first the prime's anchor alone, then each round's `j` accepted
3052    /// drafts + bonus — as DISJOINT, IN-ORDER slices whose concatenation IS the returned
3053    /// burst, byte for byte. The hook returns nothing and the loop's control flow never
3054    /// reads it, so the tokens produced are exactly `glm5_spec_session_burst`'s; only WHEN
3055    /// the caller learns them moves (from burst end to round end). Without a hook the
3056    /// first token of a cold session waits for the whole `target`-token burst.
3057    pub fn glm5_spec_session_burst_streamed(
3058        &self,
3059        e: &Engine,
3060        sess: &mut Glm5SpecSession,
3061        target: usize,
3062        k: usize,
3063        eos: &[u32],
3064        on_commit: CommitHook<'_>,
3065    ) -> Res<(Vec<u32>, usize, usize)> {
3066        self.glm5_spec_session_burst_inner(
3067            e,
3068            sess,
3069            target,
3070            k,
3071            eos,
3072            &mut Glm5SpecKnobs::default(),
3073            Some(on_commit),
3074        )
3075    }
3076
3077    /// [`glm5_spec_session_burst`] with GATE INSTRUMENTS (`Glm5SpecKnobs` — never a serving
3078    /// surface; no serving path constructs a non-default value).
3079    pub fn glm5_spec_session_burst_gated(
3080        &self,
3081        e: &Engine,
3082        sess: &mut Glm5SpecSession,
3083        target: usize,
3084        k: usize,
3085        eos: &[u32],
3086        knobs: &mut Glm5SpecKnobs<'_>,
3087    ) -> Res<(Vec<u32>, usize, usize)> {
3088        self.glm5_spec_session_burst_inner(e, sess, target, k, eos, knobs, None)
3089    }
3090
3091    /// The one burst loop behind the three public entries (plain / streamed / gated).
3092    #[allow(clippy::too_many_arguments)]
3093    // allow: the parameter list is the burst contract plus the two instruments (gate
3094    // knobs, commit hook); bundling would hide which inputs are serving vs instrument
3095    fn glm5_spec_session_burst_inner(
3096        &self,
3097        e: &Engine,
3098        sess: &mut Glm5SpecSession,
3099        target: usize,
3100        k: usize,
3101        eos: &[u32],
3102        knobs: &mut Glm5SpecKnobs<'_>,
3103        mut on_commit: Option<CommitHook<'_>>,
3104    ) -> Res<(Vec<u32>, usize, usize)> {
3105        let cap = Self::hyper_batch_cap();
3106        if k == 0 || k + 1 > cap {
3107            return Err(format!(
3108                "glm5_spec_session_burst: k={k} outside 1..={} (verify rows = k+1 must stay \
3109                 inside the decode-exact knee, cap {cap})",
3110                cap - 1
3111            )
3112            .into());
3113        }
3114        if let Glm5DraftState::Dflash2 { .. } = sess.draft {
3115            let b = self
3116                .glm5_dflash
3117                .as_ref()
3118                .ok_or("dflash session on a model with no loaded drafter")?
3119                .draft
3120                .cfg
3121                .block_size;
3122            if k + 1 > b {
3123                return Err(format!(
3124                    "glm5_spec_session_burst: k={k} exceeds the DFlash2 drafter's block \
3125                     (block_size {b} = anchor + {} drafts, the trained mask pattern) — \
3126                     the worker clamps operator K pins to {} for this source; refusing \
3127                     loudly rather than drafting an untrained shape",
3128                    b - 1,
3129                    b - 1
3130                )
3131                .into());
3132            }
3133        }
3134        let d2t = self.glm5_d2t();
3135        if d2t.is_some() && knobs.skip_d2t_remap {
3136            eprintln!("[glm5-spec] d2t REMAP SKIPPED — red-arm instrument, drafts are rank ids");
3137        }
3138        let sp_on: Option<SpecSampling> = sess.sampling.filter(|sp| sp.temp > 0.0);
3139        let mut out: Vec<u32> = Vec::with_capacity(target + k);
3140        let mut drafted = 0usize;
3141        let mut accepted = 0usize;
3142        let mut phase: Option<SpecPhaseNs> =
3143            crate::spec_phase::spec_trace_on().then(SpecPhaseNs::default);
3144        // FIRST-TOKEN PROFILE: the first burst's wall (its tokens are host-visible at
3145        // return, so no drain is needed to bound it).
3146        let first_burst = (!sess.anchor_emitted && sess.prof.is_some())
3147            .then(|| (std::time::Instant::now(), sess.rounds));
3148        // The hook's own share of the first burst's wall (host-only detext + sends), so
3149        // the profile can separate engine time from emission time under the eager door.
3150        let mut hook_ns: u64 = 0;
3151        // sse-cadence flush cursor: everything in out[..flushed] has been handed to on_commit.
3152        let mut flushed = 0usize;
3153        if !sess.anchor_emitted {
3154            // The prime's boundary token: emitted exactly once, by the first burst.
3155            out.push(sess.anchor);
3156            sess.anchor_emitted = true;
3157            if eos.contains(&sess.anchor) {
3158                sess.done = true;
3159            }
3160            if let Some(cb) = on_commit.as_mut() {
3161                let t_cb = first_burst.map(|_| std::time::Instant::now());
3162                cb(&out[flushed..]);
3163                if let Some(t_cb) = t_cb {
3164                    hook_ns += t_cb.elapsed().as_nanos() as u64;
3165                }
3166                flushed = out.len();
3167            }
3168        }
3169        while out.len() < target && !sess.done {
3170            // Context guard: a round appends up to k+1 trunk rows from `cache.pos` (and the
3171            // draft plane stays <= pos + k), so the next round must fit with one row slack.
3172            if sess.cache.pos + k + 2 > sess.max_ctx {
3173                sess.done = true;
3174                break;
3175            }
3176            // DEPTH LOG (lane/spec-route-depth-20260902, `MEMRA_SPEC_PROF=1`): the first
3177            // SPEC_PROF_ROUNDS rounds of a session get their own phase accumulator (the
3178            // trace's drains), a wall clock, and the per-row-arm row count — folded into
3179            // the per-burst trace accumulator afterwards so both instruments agree.
3180            let log_round = sess.prof_rounds.as_ref().is_some_and(|l| l.wants_more());
3181            let mut rp = log_round.then(SpecPhaseNs::default);
3182            let t_round = log_round.then(std::time::Instant::now);
3183            let seq0 = V_SEQ_ROWS.load(std::sync::atomic::Ordering::Relaxed);
3184            let ctx0 = sess.cache.pos;
3185            let (round_tokens, n_drafted) = {
3186                let ph: Option<&mut SpecPhaseNs> = match (rp.as_mut(), phase.as_mut()) {
3187                    (Some(r), _) => Some(r),
3188                    (None, p) => p,
3189                };
3190                self.glm5_spec_round(e, sess, k, d2t, sp_on.as_ref(), knobs, ph)?
3191            };
3192            if let (Some(r), Some(t0)) = (rp.as_ref(), t_round) {
3193                if let Some(p) = phase.as_mut() {
3194                    p.add(r);
3195                }
3196                if let Some(log) = sess.prof_rounds.as_mut() {
3197                    let ms = |ns: u64| ns as f32 / 1e6;
3198                    log.push(SpecRoundProf {
3199                        wall_ms: t0.elapsed().as_secs_f32() * 1e3,
3200                        draft_ms: ms(r.draft),
3201                        verify_ms: ms(r.verify),
3202                        accept_ms: ms(r.accept),
3203                        rest_ms: ms(r.roll + r.maint),
3204                        k: n_drafted as u16,
3205                        j: (round_tokens.len() - 1) as u16,
3206                        ctx: ctx0 as u32,
3207                        seq_rows: (V_SEQ_ROWS.load(std::sync::atomic::Ordering::Relaxed) - seq0)
3208                            as u32,
3209                    });
3210                }
3211            }
3212            drafted += n_drafted;
3213            accepted += round_tokens.len() - 1; // j accepted drafts + the bonus row
3214            for &tok in &round_tokens {
3215                if eos.contains(&tok) {
3216                    sess.done = true;
3217                }
3218            }
3219            out.extend_from_slice(&round_tokens);
3220            sess.rounds += 1;
3221            if let Some(cb) = on_commit.as_mut() {
3222                // sse-cadence: this round's accepted drafts + bonus are committed — hand
3223                // the caller exactly the not-yet-flushed tail (disjoint, in order).
3224                let t_cb = first_burst.map(|_| std::time::Instant::now());
3225                cb(&out[flushed..]);
3226                if let Some(t_cb) = t_cb {
3227                    hook_ns += t_cb.elapsed().as_nanos() as u64;
3228                }
3229                flushed = out.len();
3230            }
3231        }
3232        debug_assert!(
3233            on_commit.is_none() || flushed == out.len(),
3234            "every committed token must have been handed to on_commit"
3235        );
3236        if let Some(ph) = phase.as_ref() {
3237            ph.emit("glm5-phase", "glm5-phase-v", k);
3238        }
3239        if let (Some((t0, rounds0)), Some(pf)) = (first_burst, sess.prof.as_mut()) {
3240            pf.first_burst_ms = t0.elapsed().as_secs_f64() * 1e3;
3241            pf.first_burst_hook_ms = hook_ns as f64 / 1e6;
3242            pf.first_burst_rounds = sess.rounds - rounds0;
3243            pf.first_burst_tokens = out.len();
3244        }
3245        Ok((out, drafted, accepted))
3246    }
3247
3248    /// One draft->verify->accept->rollback->re-seed round over the session state. Returns
3249    /// `(round_tokens, n_drafted)`: the round's committed tokens (`j` accepted drafts + the
3250    /// bonus token) and how many drafts actually entered the verify (== `k` today; the
3251    /// confidence gate may truncate it below `k`).
3252    #[allow(clippy::too_many_arguments)]
3253    // allow: the parameter list mirrors the round contract (session + policy + gate knobs +
3254    // the trace accumulator); bundling would hide which inputs are serving vs instrument
3255    fn glm5_spec_round(
3256        &self,
3257        e: &Engine,
3258        sess: &mut Glm5SpecSession,
3259        k: usize,
3260        d2t: Option<&[u32]>,
3261        sp: Option<&SpecSampling>,
3262        knobs: &mut Glm5SpecKnobs<'_>,
3263        mut phase: Option<&mut SpecPhaseNs>,
3264    ) -> Res<(Vec<u32>, usize)> {
3265        let n_vocab = self.output.out_features();
3266        let n_embd = self.cfg.n_embd as usize;
3267        // The MTP block / DFlash2 drafter, the trunk lm head and the verify walk's returned
3268        // rows all live on the LAST stage under a split — every draft-chain and accept-side
3269        // op below runs through the head engine (identity when the door is shut).
3270        let eh = self.glm5_head_engine(e)?;
3271        let mut t_mark = phase.as_ref().map(|_| SpecPhaseNs::clock(e, eh));
3272        // FIRST-TOKEN PROFILE (`MEMRA_SPEC_PROF=1`): round 1 only — the same drains as the
3273        // trace, bucketed into the session's one-shot profile instead of the per-burst
3274        // accumulator. `pclk` rides into the DFlash2 draft fn so the prompt ingest (the
3275        // drafter prime) gets its own bucket before the draft bucket starts.
3276        let mut pclk = (sess.rounds == 0 && sess.prof.is_some()).then(|| ProfClock::start(e, eh));
3277        // Phase-boundary bump: drain, bucket the elapsed ns, restart the clock. No-op with
3278        // the trace off (t_mark is None and no stream is ever synchronized).
3279        macro_rules! bump {
3280            ($field:ident) => {
3281                if let (Some(ph), Some(t0)) = (phase.as_deref_mut(), t_mark.as_mut()) {
3282                    let now = SpecPhaseNs::clock(e, eh);
3283                    ph.$field += now.duration_since(*t0).as_nanos() as u64;
3284                    *t0 = now;
3285                }
3286            };
3287        }
3288        // Profile lap into the named first-round bucket (no-op with the profile off).
3289        macro_rules! plap {
3290            ($field:ident) => {
3291                if let (Some(ck), Some(pf)) = (pclk.as_mut(), sess.prof.as_mut()) {
3292                    pf.$field = ck.lap(e, eh);
3293                }
3294            };
3295        }
3296
3297        // CONFIDENCE GATE resolution (loop-port 2): the env pair is the serving surface
3298        // (the step37 family, no new flags); the knobs override is the gate instrument.
3299        let (p_min, pmin0) = knobs
3300            .pmin_override
3301            .unwrap_or_else(|| (glm5_pmin(), glm5_pmin0()));
3302
3303        // ---- 1+2. produce the K drafts (+ the retained q side), SOURCE-KEYED. Everything
3304        // after this point is shared and source-blind — the exactness seam (module doc).
3305        let (drafts, qside, mtp_committed_len) = match sess.draft {
3306            Glm5DraftState::Dflash2 { .. } => {
3307                let (d, q) = self.glm5_dflash_round_drafts(
3308                    eh,
3309                    sess,
3310                    k,
3311                    sp,
3312                    knobs,
3313                    p_min,
3314                    pmin0,
3315                    pclk.as_mut(),
3316                )?;
3317                (d, q, 0)
3318            }
3319            Glm5DraftState::NativeMtp => {
3320                let mtp_il = sess.mtp_il.ok_or("native-mtp arm without a plane index")?;
3321                // ---- feed pending committed pairs; the last call yields draft 1 ----
3322                let mut last: Option<(CudaSlice<f32>, CudaSlice<f32>)> = None;
3323                for (tok, h) in sess.pending.drain(..) {
3324                    let plane_len = sess.cache.latent[mtp_il]
3325                        .as_ref()
3326                        .ok_or("MTP plane missing")?
3327                        .len;
3328                    last = Some(self.mtp_head_forward_mla_cached(
3329                        eh,
3330                        0,
3331                        tok,
3332                        &h,
3333                        &mut sess.cache,
3334                        plane_len,
3335                    )?);
3336                }
3337                let (mut d_logits, mut carrier) =
3338                    last.ok_or("glm5 spec round started with no pending committed pair")?;
3339                let mtp_committed_len = sess.cache.latent[mtp_il]
3340                    .as_ref()
3341                    .ok_or("MTP plane missing")?
3342                    .len;
3343
3344                // ---- chain K drafts. Greedy route: argmax over the draft head. Sampled
3345                // route: filtered Gumbel draw through the session's device Philox stream
3346                // (`sctr`), with the per-step filtered stats + logits retained — they are
3347                // the q side of the accept walk. Trimmed heads yield RANK ids that remap
3348                // through d2t to true vocab before anything consumes them (chain feed,
3349                // verify, output); the q gather keeps the rank id.
3350                let d_vocab = d2t.map(|m| m.len()).unwrap_or(n_vocab);
3351                let mut drafts: Vec<u32> = Vec::with_capacity(k); // true-vocab tokens
3352                let mut draft_idx: Vec<u32> = Vec::with_capacity(k); // draft-head rank ids
3353                let mut draft_logits: Vec<CudaSlice<f32>> = Vec::new(); // sampled route only
3354                let mut draft_stats: Vec<(f32, f32, f32)> = Vec::new(); // (mx, th, z), sampled only
3355                for ki in 0..k {
3356                    let (idx, sampled_stats) = match sp {
3357                        Some(sp) => {
3358                            let (idx, stats) =
3359                                glm5_sampled_draft(eh, &d_logits, d_vocab, sp, &mut sess.sctr)?;
3360                            (idx, Some(stats))
3361                        }
3362                        None => {
3363                            // Device argmax + ONE 4-byte readback per draft (loop-port 1)
3364                            // — replaces the full d_vocab logits DtoH + host argmax the
3365                            // map names at this seam. Same tie-break contract
3366                            // (argmax_gate); drafts never decide exactness anyway, the
3367                            // verify arbitrates. The #87 sentinel guard mirrors the
3368                            // spec.rs graph chain: a device argmax may emit a sentinel
3369                            // on a NaN row — refuse loudly, never gather an OOB embed.
3370                            let td = eh.argmax_token_device(&d_logits, d_vocab)?;
3371                            let idx = crate::spec::guard_vocab_token(
3372                                eh.dtoh_u32_one(&td)?,
3373                                d_vocab,
3374                                &format!(
3375                                    "glm5 native draft argmax at round {} ki={ki}",
3376                                    sess.rounds
3377                                ),
3378                            )?;
3379                            (idx, None)
3380                        }
3381                    };
3382                    // P-MIN CONFIDENCE GATE (loop-port 2, the spec.rs chain break): p =
3383                    // the head's softmax confidence in its own pick (the `g_p` statistic,
3384                    // prob_of_token_device kernels), one 4-byte read — armed rounds only.
3385                    // Break BEFORE the pick is drafted or the next full-MoE-layer chain
3386                    // forward is paid; a discarded sampled draw's Philox advance stands
3387                    // (spec.rs eager parity: "counts the p-min-discarded token too").
3388                    if p_min > 0.0 {
3389                        let tok_d = eh.htod_u32_v(&[idx])?;
3390                        let p_d = eh.prob_of_token_device(&d_logits, &tok_d, d_vocab)?;
3391                        let p = eh.dtoh(&p_d)?[0];
3392                        if p < p_min && (ki > 0 || pmin0) {
3393                            break;
3394                        }
3395                    }
3396                    if let Some(stats) = sampled_stats {
3397                        draft_stats.push(stats);
3398                        draft_logits.push(eh.clone_dtod(&d_logits)?);
3399                    }
3400                    let mut d = match d2t {
3401                        Some(map) if !knobs.skip_d2t_remap => map[idx as usize],
3402                        _ => idx,
3403                    };
3404                    if let Some(over) = knobs.draft_override.as_mut() {
3405                        d = over(sess.rounds, ki, d);
3406                    }
3407                    drafts.push(d);
3408                    draft_idx.push(idx);
3409                    if ki + 1 < k {
3410                        let plane_len = sess.cache.latent[mtp_il]
3411                            .as_ref()
3412                            .ok_or("MTP plane missing")?
3413                            .len;
3414                        let (lg, ca) = self.mtp_head_forward_mla_cached(
3415                            eh,
3416                            0,
3417                            d,
3418                            &carrier,
3419                            &mut sess.cache,
3420                            plane_len,
3421                        )?;
3422                        d_logits = lg;
3423                        carrier = ca;
3424                    }
3425                }
3426                let q = match sp {
3427                    Some(_) => Glm5DraftQ::Mtp {
3428                        draft_idx,
3429                        draft_logits,
3430                        draft_stats,
3431                    },
3432                    None => Glm5DraftQ::None,
3433                };
3434                (drafts, q, mtp_committed_len)
3435            }
3436        };
3437        bump!(draft);
3438        plap!(first_draft_ms);
3439        // RANK-TRIM COUNTER (lane/frspec-dflash2-20260902): this round drafted through a
3440        // rank-trimmed head (`d2t` is the source-keyed map the drafts were produced under ,
3441        // the DFlash2 slab or the native trimmed chain). Counted on the head, not the remap:
3442        // the skip_d2t_remap red arm still ran over the slab.
3443        if d2t.is_some() {
3444            sess.rank_trimmed_rounds += 1;
3445            GLM5_RANK_TRIMMED_DRAFT_ROUNDS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3446        }
3447
3448        // DFlash2 source: arm the verify tap — the walk's rows are next round's drafter
3449        // context features (rows 0..keep survive the accept; the sink is taken in step 7).
3450        // DEVICE-STAGED (loop-port 1): the walk D2Ds each tapped layer's contracted rows
3451        // instead of blocking on five in-walk DtoHs; step 7 drains post-walk.
3452        if let Glm5DraftState::Dflash2 { taps, .. } = &sess.draft {
3453            sess.cache.hc_taps = Some(HcTapSink::new_device_staged(
3454                taps.clone(),
3455                n_embd,
3456                drafts.len() + 1,
3457            ));
3458        }
3459
3460        // ---- 3. verify: one t=K+1 walk over the trunk ----
3461        let mut rows: Vec<u32> = Vec::with_capacity(drafts.len() + 1);
3462        rows.push(sess.anchor);
3463        rows.extend_from_slice(&drafts);
3464        let (vlogits, collapsed, ckpt) = self.glm5_verify_rows(e, &rows, &mut sess.cache)?;
3465        bump!(verify);
3466        plap!(first_verify_ms);
3467
3468        // ---- 3b. PENALTY ARM (lane/spec-exclusions-20260902, MEMRA_SPEC_PENALTY=1) ----
3469        // The target rows are penalized IN A COPY over the per-row EVOLVING window: row r
3470        // (the target at drafts[r]'s slot; row 0 = the anchor's) sees `pen_win ++
3471        // drafts[..r]` — the tokens committed before that position on every path where the
3472        // row is consulted, same-round accepts included — through `penalize_logits_rows_inc`
3473        // (the dspark accept walk's kernel, host-order arithmetic). Every p read below —
3474        // the greedy argmaxes, the sampled stats/gather, the bonus row, the reject-slot
3475        // residual — points at that copy, so the accepted stream is the plain penalized
3476        // sampler's target: greedy byte-identical (gate 15), sampled distribution-exact.
3477        // The anchor joins the window first (it was emitted last round, committed by this
3478        // one: the dspark `pen_hist.push(last)` convention); accepted drafts join after the
3479        // accept. `pen: None` = no copy, no launch — `plogits` IS `vlogits`, the pre-lane
3480        // program byte for byte.
3481        let pvl: Option<CudaSlice<f32>> = match sess.pen {
3482            Some(p) => {
3483                sess.pen_hist.push(sess.anchor);
3484                let w0 = sess.pen_hist.len().saturating_sub(p.win());
3485                let pen_win = &sess.pen_hist[w0..];
3486                let mut hist: Vec<u32> = Vec::with_capacity(pen_win.len() + drafts.len());
3487                hist.extend_from_slice(pen_win);
3488                hist.extend_from_slice(&drafts);
3489                let n_win = pen_win.len();
3490                let hd = eh.htod_u32_v(&hist)?;
3491                let mut buf = eh.clone_dtod(&vlogits)?;
3492                eh.penalize_logits_rows_inc(
3493                    &mut buf,
3494                    &hd,
3495                    n_win,
3496                    p.rep,
3497                    p.freq,
3498                    p.present,
3499                    n_vocab,
3500                    rows.len(),
3501                    p.win(),
3502                )?;
3503                Some(buf)
3504            }
3505            None => None,
3506        };
3507        let plogits: &CudaSlice<f32> = pvl.as_ref().unwrap_or(&vlogits);
3508        // The dspark accept walk penalizes internally off `sp.pen_on()`; under the arm it
3509        // receives the ALREADY penalized rows and a penalty-neutral config, so the round has
3510        // exactly one penalty pass. With the arm off this is `sp` itself, untouched.
3511        let sp_accept: Option<SpecSampling> = sp.map(|s| match sess.pen {
3512            Some(_) => SpecSampling {
3513                penalty_last_n: 0,
3514                ..*s
3515            },
3516            None => *s,
3517        });
3518
3519        // ---- 4. accept ----
3520        // ZERO-DRAFT SAMPLED ROUND (PMIN0): the verify batch is just the anchor row —
3521        // m=1 = a plain decode step, exactly the llama.cpp gating spec.rs vendored. The
3522        // bonus is the full-accept filtered-Gumbel draw from that one row through the
3523        // session's Philox stream (identical in distribution to the plain sampled step
3524        // this round degenerates to). Greedy zero-draft rounds ride the general arm
3525        // below (j=0, bonus = the row-0 device argmax).
3526        let (j, bonus) = if let (true, Some(sp)) = (drafts.is_empty(), sp) {
3527            (
3528                0,
3529                self.glm5_sampled_bonus(eh, sess, sp, plogits, 0, n_vocab)?,
3530            )
3531        } else {
3532            match (sp, &qside) {
3533                (None, _) => {
3534                    // Greedy longest matching prefix (the DFlash2 probe's rule); bonus = the
3535                    // target's own argmax at the first non-accepted slot. Byte-deterministic —
3536                    // the instrument the spec-vs-plain identity gates pin.
3537                    //
3538                    // DEVICE ACCEPT ARGMAXES (loop-port 1, the K=1 flip): per verify row, a
3539                    // device argmax into one [t] slot buffer, then ONE tiny u32 readback —
3540                    // replacing the (K+1) x n_vocab logits DtoH + (K+1) host argmax scans
3541                    // (~2.4 MB + a host walk over 600k floats at K=3 on the real head; the
3542                    // 3way arithmetic needs 0.67 ms off the fixed round cost to flip K=1).
3543                    // `argmax_token_device_col` carries the host argmax's tie-break contract
3544                    // bit for bit (lowest index wins, argmax_gate-validated), so the accept
3545                    // walk commits the SAME tokens in the SAME order — the byte-identity
3546                    // batteries below stay the proof.
3547                    let t = rows.len();
3548                    let mut vam_d = eh.alloc_u32_zeroed(t)?;
3549                    for r in 0..t {
3550                        eh.argmax_token_device_col(plogits, r, n_vocab, &mut vam_d, r)?;
3551                    }
3552                    let vam = eh.dtoh_u32(&vam_d)?;
3553                    let mut j = 0usize;
3554                    while j < drafts.len() && drafts[j] == vam[j] {
3555                        j += 1;
3556                    }
3557                    if knobs.accept_probe {
3558                        self.glm5_accept_probe(eh, sess.rounds, plogits, &drafts, &vam, j)?;
3559                    }
3560                    (j, vam[j])
3561                }
3562                (
3563                    Some(sp),
3564                    Glm5DraftQ::Mtp {
3565                        draft_idx,
3566                        draft_logits,
3567                        draft_stats,
3568                    },
3569                ) => self.glm5_sampled_accept(
3570                    eh,
3571                    sess,
3572                    sp,
3573                    plogits,
3574                    &drafts,
3575                    draft_idx,
3576                    draft_logits,
3577                    draft_stats,
3578                    d2t,
3579                    drafts.len(),
3580                )?,
3581                (Some(_), Glm5DraftQ::Selector { prop, dl }) => {
3582                    // The q38 serve route's rejection walk, VERBATIM (`dspark_accept_sampled`):
3583                    // `rows` = [anchor, drafts..] is its cand contract, verify row j arbitrates
3584                    // rows[j+1], the bonus draws from row k on full accept, and the reject-slot
3585                    // residual uses the selector's sparse candidate-set q. Philox counters are
3586                    // this session's — randomness never repeats across bursts.
3587                    let (m, next) = crate::dflash::dspark_accept_sampled(
3588                        eh,
3589                        plogits,
3590                        &rows,
3591                        rows.len(),
3592                        n_vocab,
3593                        dl,
3594                        prop,
3595                        sp_accept.as_ref().expect("sampled arm carries its config"),
3596                        &[],
3597                        &mut sess.sctr,
3598                        &mut sess.uctr,
3599                    )?;
3600                    let next = crate::spec::guard_vocab_token(
3601                        next,
3602                        n_vocab,
3603                        &format!(
3604                            "glm5 dflash2 sampled verify bonus at round {} j={m}",
3605                            sess.rounds
3606                        ),
3607                    )?;
3608                    (m, next)
3609                }
3610                (Some(_), Glm5DraftQ::None) => {
3611                    unreachable!("sampled round without a retained q side")
3612                }
3613            }
3614        };
3615        // Accepted drafts join the penalty window (the bonus joins as next round's anchor).
3616        if sess.pen.is_some() {
3617            sess.pen_hist.extend_from_slice(&drafts[..j]);
3618        }
3619        bump!(accept);
3620        plap!(first_accept_ms);
3621
3622        // ---- 5. commit j drafts + the bonus token ----
3623        let mut round_tokens: Vec<u32> = Vec::with_capacity(j + 1);
3624        round_tokens.extend_from_slice(&drafts[..j]);
3625        round_tokens.push(bonus);
3626
3627        // ---- 6. rollback the trunk to the accepted prefix ----
3628        let keep = j + 1;
3629        if knobs.disable_rollback {
3630            // RED-ARM INSTRUMENT: move pos, leave every state plane at post-row-K.
3631            sess.cache.pos = ckpt.pos + keep;
3632        } else {
3633            self.glm5_verify_rollback(e, &mut sess.cache, &ckpt, keep)?;
3634        }
3635        bump!(roll);
3636        plap!(first_roll_ms);
3637
3638        // ---- 7+8. draft-source state maintenance, SOURCE-KEYED ----
3639        match &mut sess.draft {
3640            Glm5DraftState::NativeMtp => {
3641                // MTP plane: len reset to the committed boundary (chain rows out), then
3642                // re-seed the pending pairs (token at pos0+i, collapsed row i-1).
3643                self.glm5_mtp_plane_reset(e, &mut sess.cache, mtp_committed_len)?;
3644                for i in 1..=keep {
3645                    let tok = round_tokens[i - 1];
3646                    let h = self.glm5_seed_row(eh, &collapsed, rows.len(), i - 1)?;
3647                    sess.pending.push((tok, h));
3648                }
3649            }
3650            Glm5DraftState::Dflash2 { pending, taps, .. } => {
3651                // The kept verify rows' tap features (rows 0..keep = [anchor, accepted
3652                // drafts]) become next round's drafter context — the probe's
3653                // `F_feat[new_lo:start]` advance. The drafter's own KV block rows were
3654                // transient (forward_round never moves kv.len), so no drafter rollback
3655                // exists to run. The trunk-side MTP plane was never touched.
3656                // Device-staged rows drain HERE — the round's one post-walk sync point
3657                // for tap features (loop-port 1).
3658                let mut sink = sess
3659                    .cache
3660                    .hc_taps
3661                    .take()
3662                    .ok_or("glm5 dflash verify tap sink vanished")?;
3663                self.glm5_tap_drain(e, &mut sink)?;
3664                let row_w = taps.len() * n_embd;
3665                pending.extend_from_slice(&sink.rows[..keep * row_w]);
3666            }
3667        }
3668        // Cache-row bookkeeping: the trunk committed rows [anchor, drafts[..j]] (keep =
3669        // j+1), so `committed` gains exactly those tokens; the BONUS is the new live
3670        // anchor — emitted this round, consumed by the trunk as the NEXT round's row 0
3671        // (the dspark `last` convention). Invariant at every round boundary:
3672        // `cache.pos == committed.len()`, token-for-token.
3673        sess.committed.push(sess.anchor);
3674        sess.committed.extend_from_slice(&drafts[..j]);
3675        sess.anchor = bonus;
3676        bump!(maint);
3677        plap!(first_maint_ms);
3678        if let Some(pf) = sess.prof.as_mut()
3679            && pclk.is_some()
3680        {
3681            pf.first_round_tokens = round_tokens.len();
3682        }
3683        if let Some(ph) = phase {
3684            ph.rounds += 1;
3685        }
3686        Ok((round_tokens, drafts.len()))
3687    }
3688
3689    /// THE ACCEPTANCE-RACE FIX (lane/glm5-accrace 2026-09-01): order the CALLER's stream
3690    /// behind EVERY stage stream — the exit mirror of
3691    /// [`crate::pp::PpNRt::fence_stages_behind`], built from
3692    /// [`crate::pp::PpNRt::publish_all_to`] (event waits, never a device sync, so the stage
3693    /// streams keep running).
3694    ///
3695    /// Call OUTSIDE any `rt.enter` scope: `e.stream()` must resolve to the caller's stream,
3696    /// not a stage's. Door shut or the same-stream seam (`MEMRA_PP_STREAMS=0`): a no-op by
3697    /// construction, so single-device and STREAMS=0 behaviour is untouched.
3698    fn glm5_publish_stages(&self, e: &Engine) -> Res<()> {
3699        if crate::pp::pp_cuts(self.layers.len()).is_some() && !crate::pp::pp2_streams_off() {
3700            let rt = crate::pp::PpNRt::get(e)?;
3701            let dst = e.stream();
3702            rt.publish_all_to(&dst)?;
3703        }
3704        Ok(())
3705    }
3706
3707    /// GATE INSTRUMENT (lane/glm5-accrace; contract in [`Glm5SpecKnobs::accept_probe`]):
3708    /// one stderr line per greedy round pairing the DEVICE accept row against a HOST
3709    /// argmax over the same buffer, plus a per-row (argmax, row hash) census so two runs
3710    /// of the same deterministic fixture can be diffed round-for-round.
3711    fn glm5_accept_probe(
3712        &self,
3713        eh: &Engine,
3714        round: usize,
3715        vlogits: &CudaSlice<f32>,
3716        drafts: &[u32],
3717        vam: &[u32],
3718        j: usize,
3719    ) -> Res<()> {
3720        let n_vocab = self.output.out_features();
3721        let t = vam.len();
3722        let host = eh.dtoh(vlogits)?;
3723        let mut hvam: Vec<u32> = Vec::with_capacity(t);
3724        let mut rows_census: Vec<String> = Vec::with_capacity(t);
3725        for r in 0..t {
3726            let row = &host[r * n_vocab..(r + 1) * n_vocab];
3727            let am = argmax(row) as u32;
3728            hvam.push(am);
3729            // FNV-1a over the row's f32 BITS: a bit-level fingerprint, so a run-to-run
3730            // diff is exact rather than eyeballed at some print precision.
3731            let mut h: u64 = 0xcbf2_9ce4_8422_2325;
3732            for v in row {
3733                for b in v.to_bits().to_le_bytes() {
3734                    h ^= u64::from(b);
3735                    h = h.wrapping_mul(0x100_0000_01b3);
3736                }
3737            }
3738            rows_census.push(format!("{r}:{am}:{h:016x}"));
3739        }
3740        eprintln!(
3741            "[glm5-accrace] round={round} t={t} j={j} keep={} drafts={drafts:?} \
3742             dev_vam={vam:?} host_vam={hvam:?} agree={} rows=[{}]",
3743            j + 1,
3744            hvam == vam,
3745            rows_census.join(" ")
3746        );
3747        Ok(())
3748    }
3749
3750    /// ONE round's drafts from the DFlash2 source (module doc, DRAFT SOURCE SEAM) — the
3751    /// shipped q38 selector round, re-aimed at glm5's hc-contract features:
3752    ///
3753    ///   1. ingest pending committed feature rows into the drafter's own ctx KV (chunked
3754    ///      at 256 rows — the qwen depth-OOM bound; round 1 carries the whole prompt);
3755    ///   2. block forward `[anchor, MASK x b-1]` at absolute positions over the cached ctx
3756    ///      (`forward_round` — block K/V transient, exactly the reference crop);
3757    ///   3. draft logits = trunk lm_head over rows 1..b (mask-fill harvest — the DFlash2
3758    ///      family census; FR-Spec trim consumed exactly as the dspark serve arm does);
3759    ///   4. selector walk: greedy chain, or the sampled candidate-set walk whose recorded
3760    ///      q (`DsparkDraftSample::Selector`) the shared rejection accept consumes.
3761    ///
3762    /// Drafts are truncated to `k` (the chain is sequential, so a prefix is well-formed),
3763    /// then to the CONFIDENCE prefix when `p_min` is armed (loop-port 2, the tau-slot
3764    /// form): the selector's recorded per-slot q — `q_chosen` on the sampled walk, its
3765    /// T=1 twin on the greedy walk — gates each slot through `glm5_conf_keep`, so the
3766    /// low-confidence tail never enters the verify batch (a truncated round rides down
3767    /// the `31.6 + 20.1*K` line; rejection sampling stays exact for any proposal prefix).
3768    /// `knobs.draft_override` applies after — the gate instrument, never serving.
3769    #[allow(clippy::too_many_arguments)]
3770    // allow: the parameter list mirrors the round contract plus the resolved gate pair
3771    fn glm5_dflash_round_drafts(
3772        &self,
3773        eh: &Engine,
3774        sess: &mut Glm5SpecSession,
3775        k: usize,
3776        sp: Option<&SpecSampling>,
3777        knobs: &mut Glm5SpecKnobs<'_>,
3778        p_min: f32,
3779        pmin0: bool,
3780        pclk: Option<&mut ProfClock>,
3781    ) -> Res<(Vec<u32>, Glm5DraftQ)> {
3782        let dr = self
3783            .glm5_dflash
3784            .as_ref()
3785            .ok_or("glm5 dflash draft state without a loaded drafter")?;
3786        let draft = &dr.draft;
3787        let c = &draft.cfg;
3788        let b = c.block_size;
3789        let n_embd = self.cfg.n_embd as usize;
3790        let n_vocab = self.output.out_features();
3791        let Glm5SpecSession {
3792            draft: state,
3793            cache,
3794            anchor,
3795            sctr: _,
3796            uctr,
3797            rounds,
3798            prof,
3799            ..
3800        } = sess;
3801        let Glm5DraftState::Dflash2 { kv, pending, taps } = state else {
3802            return Err("glm5_dflash_round_drafts on a native-mtp session".into());
3803        };
3804        let anchor = *anchor;
3805
3806        // ---- 1. ingest pending committed feature rows (positions kv.len..) ----
3807        // Under the default placement the PROMPT rows were ingested at session creation
3808        // and only the kept verify rows of the previous round arrive here; under
3809        // `MEMRA_GLM5_DRAFT_PRIME_LAZY=1` round 1 carries the whole prompt (the pre-lane
3810        // literal, the round-0 wall boot A measured at depth).
3811        let row_w = taps.len() * n_embd;
3812        let mut pclk = pclk;
3813        let n_new = pending.len() / row_w;
3814        let st = if n_new > 0 {
3815            let rows = std::mem::take(pending);
3816            self.glm5_dflash_ingest_rows(eh, draft, kv, &rows, row_w, pclk.as_deref_mut())?
3817        } else {
3818            DraftIngestStats::default()
3819        };
3820        // FIRST-TOKEN PROFILE: the lazy arm's round-1 prompt ingest lands in the
3821        // drafter-prime buckets here (a creation-time ingest already wrote them and
3822        // leaves nothing prompt-sized pending); the clock is re-based either way.
3823        if let (Some(ck), Some(pf)) = (pclk, prof.as_mut()) {
3824            let tail = ck.lap(eh, eh);
3825            if *rounds == 0 && n_new > 0 && pf.draft_prime_arm.is_empty() {
3826                st.write(pf, "eager-lazy");
3827                pf.draft_prime_ms += tail;
3828            }
3829        }
3830        let start = cache.pos;
3831        debug_assert_eq!(
3832            kv.len, start,
3833            "drafter ctx rows must equal committed trunk rows at a round boundary"
3834        );
3835
3836        // ---- 2. block forward over the cached ctx (decode-exact matmul scope: the m=8
3837        // drafter GEMMs otherwise fall into the prefill-GEMM class — the dspark round's
3838        // measured fix; RAII so a `?` exit never latches exact engine-wide) ----
3839        let exact_scope = eh.exact_scope(true);
3840        let mut block: Vec<u32> = vec![c.mask_token_id; b];
3841        block[0] = anchor;
3842        let noise = eh.htod(&self.embd.try_gather(n_embd, &block)?)?;
3843        let pos_block: Vec<i32> = ((start as i32)..(start + b) as i32).collect();
3844        let dh = draft.forward_round(eh, kv, &noise, &pos_block)?;
3845
3846        // ---- 3. draft logits over the mask-fill harvest rows 1..b ----
3847        let nd = b - 1;
3848        let mut rows_buf = eh.uninit(nd * n_embd)?;
3849        {
3850            let dv = eh.view(&dh, b * n_embd);
3851            let tail = dv.slice(n_embd..b * n_embd);
3852            eh.copy_view_into(&mut rows_buf, 0, &tail, nd * n_embd)?;
3853        }
3854        // TRIMMED DRAFT HEAD (`glm5_dflash_trim`): the dspark serve arm's resolution, the
3855        // FR-Spec self-trim on the MTP struct when it loaded (gathered rows of the target's
3856        // own head), else the `dflash_trim` slab the loader builds for a DFlash2 route that
3857        // boots WITHOUT the NextN block (lane/frspec-dflash2-20260902, the serving shape;
3858        // before it the `MEMRA_FRSPEC_TRIM` contract was a silent no-op here). Neither = the
3859        // full target head, stated in the boot receipt. The draft logits `dl` arrive
3860        // `[nd x n_ranks]`; the propose fns remap candidate RANK ids through d2t before the
3861        // selector walk; verify stays full-vocab, so the slab moves acceptance only.
3862        let trim = self.glm5_dflash_trim();
3863        let (dl_head, dl_vocab) = match trim {
3864            Some((head, d2t)) => (head, d2t.len()),
3865            None => (&self.output, n_vocab),
3866        };
3867        // skip_d2t_remap red arm (the q38 defect made loud): candidates stay RANK ids.
3868        let trim_d2t = trim.filter(|_| !knobs.skip_d2t_remap).map(|(_, d2t)| d2t);
3869        let dl = eh.matmul(dl_head, &rows_buf, nd)?;
3870
3871        // ---- 4. selector walk (greedy chain / sampled candidate-set walk) ----
3872        let (mut drafts, slot_q, qside) = match sp {
3873            None => {
3874                let (path, q) = draft
3875                    .dflash2_propose_greedy_q(eh, &dl, &rows_buf, nd, dl_vocab, anchor, trim_d2t)?;
3876                (path, q, Glm5DraftQ::None)
3877            }
3878            Some(sp) => {
3879                let (path, q_chosen, cand, q_rows) = draft.dflash2_propose_sampled(
3880                    eh, &dl, &rows_buf, nd, dl_vocab, anchor, sp.temp, sp.seed, uctr, trim_d2t,
3881                )?;
3882                let top_k = draft
3883                    .dflash2
3884                    .as_ref()
3885                    .ok_or("glm5 dflash drafter lost its DFlash2 head")?
3886                    .top_k;
3887                (
3888                    path,
3889                    q_chosen.clone(),
3890                    Glm5DraftQ::Selector {
3891                        prop: DsparkDraftSample::Selector {
3892                            cand,
3893                            q_rows,
3894                            q_chosen,
3895                            top_k,
3896                        },
3897                        dl,
3898                    },
3899                )
3900            }
3901        };
3902        drop(exact_scope);
3903        drafts.truncate(k);
3904        // TAU-SLOT CONFIDENCE TRUNCATION (loop-port 2): the low-confidence tail never
3905        // enters verify. Slot-indexed prefix reads keep the retained Selector q side
3906        // consistent (cand/q_rows/q_chosen are per-slot; the accept walk reads slots
3907        // 0..drafts.len()-1 only). p_min unset = today's rounds, untouched.
3908        if p_min > 0.0 {
3909            let kc = glm5_conf_keep(&slot_q[..drafts.len()], p_min, pmin0);
3910            drafts.truncate(kc);
3911        }
3912        if let Some(over) = knobs.draft_override.as_mut() {
3913            for (ki, d) in drafts.iter_mut().enumerate() {
3914                *d = over(*rounds, ki, *d);
3915            }
3916        }
3917        Ok((drafts, qside))
3918    }
3919
3920    /// SAMPLED ACCEPT (module doc): the rejection-sampling walk `u_j * q_j(x_j) < p_j(x_j)`
3921    /// over the verify logit rows — memra's existing sampled spec contract (the
3922    /// MEMRA_SPEC_TEMP route / dspark sampled-admission walk), plugged in at exactly the
3923    /// accept seam; walk and rollback unchanged. p and q take the SAME filter transforms
3924    /// (`filter_stats` + `softmax_gather_filtered`, distribution-exact for the filtered
3925    /// target); the accept-test uniforms come from `spec::host_u01` on the session's `uctr`
3926    /// (tag 0xFFFF_FFFE) and every device draw (draft chain, full-accept bonus, residual
3927    /// resample) advances the session's `sctr` — counters persist on the session so
3928    /// randomness never repeats across bursts. Returns `(j, bonus)`. `e` is the HEAD
3929    /// engine (the round resolves it): the verify rows and retained draft logits live on
3930    /// the last stage under a split.
3931    #[allow(clippy::too_many_arguments)]
3932    // allow: the parameter list mirrors the accept seam's inputs (verify rows + the draft
3933    // chain's retained q side); bundling into a struct would hide the p/q pairing
3934    fn glm5_sampled_accept(
3935        &self,
3936        e: &Engine,
3937        sess: &mut Glm5SpecSession,
3938        sp: &SpecSampling,
3939        vlogits: &CudaSlice<f32>,
3940        drafts: &[u32],
3941        draft_idx: &[u32],
3942        draft_logits: &[CudaSlice<f32>],
3943        draft_stats: &[(f32, f32, f32)],
3944        d2t: Option<&[u32]>,
3945        k: usize,
3946    ) -> Res<(usize, u32)> {
3947        let n_vocab = self.output.out_features();
3948        let d_vocab = d2t.map(|m| m.len()).unwrap_or(n_vocab);
3949        // FILTERED p_j: one batched stats pass over verify rows 0..k-1 (row j is the target
3950        // distribution at draft j's slot), then one batched gather of the drafted tokens.
3951        let rows_i: Vec<i32> = (0..k as i32).collect();
3952        let rows_d = e.htod_i32(&rows_i)?;
3953        let (mut th_d, mut z_d, mut mx_d) = (e.zeros(k)?, e.zeros(k)?, e.zeros(k)?);
3954        e.filter_stats(
3955            vlogits, n_vocab, &rows_d, &mut th_d, &mut z_d, &mut mx_d, n_vocab, k, sp.temp,
3956            sp.top_k, sp.top_p, sp.min_p,
3957        )?;
3958        let ids_d = e.htod_u32_v(drafts)?;
3959        let mut pj_d = e.zeros(k)?;
3960        e.softmax_gather_filtered(
3961            vlogits, n_vocab, &ids_d, &rows_d, &th_d, &z_d, &mut pj_d, n_vocab, k, sp.temp,
3962        )?;
3963        let pj = e.dtoh(&pj_d)?;
3964        let (thv, zv, mxv) = (e.dtoh(&th_d)?, e.dtoh(&z_d)?, e.dtoh(&mx_d)?);
3965
3966        // The walk: FILTERED q_j from the retained draft logits (rank id for trimmed heads),
3967        // host Philox accept test per slot.
3968        let mut j = 0usize;
3969        while j < k {
3970            let (_qmx, qth, qz) = draft_stats[j];
3971            let idsd = e.htod_u32_v(&[draft_idx[j]])?;
3972            let rows0 = e.htod_i32(&[0])?;
3973            let thd = e.htod(&[qth])?;
3974            let zd = e.htod(&[qz])?;
3975            let mut outd = e.zeros(1)?;
3976            e.softmax_gather_filtered(
3977                &draft_logits[j],
3978                d_vocab,
3979                &idsd,
3980                &rows0,
3981                &thd,
3982                &zd,
3983                &mut outd,
3984                d_vocab,
3985                1,
3986                sp.temp,
3987            )?;
3988            let qj = e.dtoh(&outd)?[0];
3989            let u = crate::spec::host_u01(sp.seed, sess.uctr);
3990            sess.uctr = sess.uctr.wrapping_add(1);
3991            if (u as f64) * (qj as f64) < pj[j] as f64 {
3992                j += 1;
3993            } else {
3994                break;
3995            }
3996        }
3997
3998        // Bonus: full accept draws a filtered Gumbel sample from the LAST verify row
3999        // (`glm5_sampled_bonus` — shared with the PMIN0 zero-draft round); rejection at j
4000        // resamples the residual norm(max(0, fp_j - fq_j)) — with a trimmed draft head, q
4001        // scatters back to full vocab first (`scatter_trim_logits`).
4002        if j == k {
4003            return Ok((
4004                j,
4005                self.glm5_sampled_bonus(e, sess, sp, vlogits, k, n_vocab)?,
4006            ));
4007        }
4008        let mut col = e.zeros(n_vocab)?;
4009        let bonus = {
4010            let vv = e.view(vlogits, (k + 1) * n_vocab);
4011            let row = vv.slice(j * n_vocab..(j + 1) * n_vocab);
4012            e.copy_view_into(&mut col, 0, &row, n_vocab)?;
4013            let p_stats = (mxv[j], thv[j], zv[j]);
4014            let q_stats = draft_stats[j];
4015            let sc = sess.sctr;
4016            sess.sctr = sess.sctr.wrapping_add(1);
4017            let mut sample_tok = e.alloc_u32_zeroed(1)?;
4018            match d2t {
4019                Some(map) => {
4020                    let map_d = e.htod_u32_v(map)?;
4021                    let mut q_full = e.zeros(n_vocab)?;
4022                    e.scatter_trim_logits(&draft_logits[j], &map_d, &mut q_full, d_vocab, n_vocab)?;
4023                    e.residual_sample_filtered(
4024                        &col,
4025                        Some(&q_full),
4026                        n_vocab,
4027                        sp.temp,
4028                        sp.seed,
4029                        sc,
4030                        p_stats,
4031                        q_stats,
4032                        &mut sample_tok,
4033                    )?;
4034                }
4035                None => {
4036                    e.residual_sample_filtered(
4037                        &col,
4038                        Some(&draft_logits[j]),
4039                        n_vocab,
4040                        sp.temp,
4041                        sp.seed,
4042                        sc,
4043                        p_stats,
4044                        q_stats,
4045                        &mut sample_tok,
4046                    )?;
4047                }
4048            }
4049            e.dtoh_u32(&sample_tok)?[0]
4050        };
4051        let bonus = crate::spec::guard_vocab_token(
4052            bonus,
4053            n_vocab,
4054            &format!("glm5 sampled verify bonus at round {} j={j}", sess.rounds),
4055        )?;
4056        Ok((j, bonus))
4057    }
4058
4059    /// One filtered-Gumbel bonus draw from verify row `row` through the session's device
4060    /// Philox stream — the sampled FULL-ACCEPT bonus, and the entire accept of a PMIN0
4061    /// zero-draft round (whose verify batch is just the anchor row: m=1 = a plain sampled
4062    /// decode step). Advances `sctr` exactly once; byte-for-byte the pre-extraction
4063    /// full-accept arm of `glm5_sampled_accept`.
4064    fn glm5_sampled_bonus(
4065        &self,
4066        e: &Engine,
4067        sess: &mut Glm5SpecSession,
4068        sp: &SpecSampling,
4069        vlogits: &CudaSlice<f32>,
4070        row: usize,
4071        n_vocab: usize,
4072    ) -> Res<u32> {
4073        let mut col = e.zeros(n_vocab)?;
4074        let vv = e.view(vlogits, (row + 1) * n_vocab);
4075        let src = vv.slice(row * n_vocab..(row + 1) * n_vocab);
4076        e.copy_view_into(&mut col, 0, &src, n_vocab)?;
4077        let rows0 = e.htod_i32(&[0])?;
4078        let (mut bth, mut bz, mut bmx) = (e.zeros(1)?, e.zeros(1)?, e.zeros(1)?);
4079        e.filter_stats(
4080            &col, n_vocab, &rows0, &mut bth, &mut bz, &mut bmx, n_vocab, 1, sp.temp, sp.top_k,
4081            sp.top_p, sp.min_p,
4082        )?;
4083        let (th, mx) = (e.dtoh(&bth)?[0], e.dtoh(&bmx)?[0]);
4084        let mut pb = e.zeros(n_vocab)?;
4085        e.gumbel_perturb_filtered(&col, &mut pb, n_vocab, sp.seed, sess.sctr, sp.temp, mx, th)?;
4086        sess.sctr = sess.sctr.wrapping_add(1);
4087        let td = e.argmax_token_device(&pb, n_vocab)?;
4088        crate::spec::guard_vocab_token(
4089            e.dtoh_u32_one(&td)?,
4090            n_vocab,
4091            &format!(
4092                "glm5 sampled verify bonus at round {} (row {row})",
4093                sess.rounds
4094            ),
4095        )
4096    }
4097}
4098
4099/// One filtered Gumbel draw from a draft-head logit row through the session's device Philox
4100/// stream — the sampled route's PROPOSAL. Returns the drawn RANK id and the row's filtered
4101/// stats `(row_max, threshold_e, renorm_mass)`, which the accept walk's q gather and the
4102/// rejection residual both reuse (the q side must be the distribution the draft was actually
4103/// drawn from, or rejection sampling is not exact for the filtered target).
4104fn glm5_sampled_draft(
4105    e: &Engine,
4106    dl: &CudaSlice<f32>,
4107    d_vocab: usize,
4108    sp: &SpecSampling,
4109    sctr: &mut u32,
4110) -> Res<(u32, (f32, f32, f32))> {
4111    let rows0 = e.htod_i32(&[0])?;
4112    let (mut th_d, mut z_d, mut mx_d) = (e.zeros(1)?, e.zeros(1)?, e.zeros(1)?);
4113    e.filter_stats(
4114        dl, d_vocab, &rows0, &mut th_d, &mut z_d, &mut mx_d, d_vocab, 1, sp.temp, sp.top_k,
4115        sp.top_p, sp.min_p,
4116    )?;
4117    let (th, z, mx) = (e.dtoh(&th_d)?[0], e.dtoh(&z_d)?[0], e.dtoh(&mx_d)?[0]);
4118    let mut pb = e.zeros(d_vocab)?;
4119    e.gumbel_perturb_filtered(dl, &mut pb, d_vocab, sp.seed, *sctr, sp.temp, mx, th)?;
4120    *sctr = sctr.wrapping_add(1);
4121    let td = e.argmax_token_device(&pb, d_vocab)?;
4122    let idx =
4123        crate::spec::guard_vocab_token(e.dtoh_u32_one(&td)?, d_vocab, "glm5 sampled draft draw")?;
4124    Ok((idx, (mx, th, z)))
4125}
4126
4127/// glm5_next SERVED speculative session (lane/glm5-spec-routing, 2026-08-30): the state one
4128/// request's spec decoding carries across worker bursts — the dspark/gemma session twins'
4129/// shape. The session OWNS its trunk cache (the worker's `s.cache` stays `None`); at every
4130/// burst boundary the invariant is `cache.pos == committed.len()` with each committed row's
4131/// trunk state exactly what a plain prime of that sequence would hold (the accept walk's
4132/// basis, pinned by the tparallel gate), plus ONE emitted-but-uncommitted `anchor` token
4133/// (the next round's verify row 0 — the dspark `last` convention).
4134pub struct Glm5SpecSession {
4135    cache: Cache,
4136    /// Every token whose trunk state the cache holds, in order (prompt + committed
4137    /// generation). EXCLUDES the live `anchor`.
4138    pub committed: Vec<u32>,
4139    /// The last emitted token, not yet consumed by the trunk — round anchor / verify row 0.
4140    anchor: u32,
4141    /// The prime's boundary token is emitted exactly once, by the first burst.
4142    anchor_emitted: bool,
4143    /// Committed `(token, h_seed)` pairs not yet fed to the MTP draft plane; the last
4144    /// feed's logits double as the next round's first draft (the re-warm contract).
4145    /// NATIVE-MTP arm only; the DFlash2 source keeps its own pending rows in `draft`.
4146    pending: Vec<(u32, CudaSlice<f32>)>,
4147    /// The session's pinned draft source + its state (module doc, DRAFT SOURCE SEAM).
4148    draft: Glm5DraftState,
4149    /// `None` / `temp <= 0` = greedy byte-contract route. Fixed for the session — the
4150    /// worker's admission owns the sampler identity.
4151    sampling: Option<SpecSampling>,
4152    /// PENALTY ARM (lane/spec-exclusions-20260902, `MEMRA_SPEC_PENALTY=1`): the request's
4153    /// penalty config when it carries one — independent of `sampling` (a greedy request
4154    /// with penalties is `sampling: None, pen: Some`). `None` = no penalty pass anywhere on
4155    /// the round, the pre-lane program byte for byte.
4156    pen: Option<Glm5Penalty>,
4157    /// The session-spanning penalty history (`pen_window_seed` over the prompt, then the
4158    /// anchor at every round start and the accepted drafts after every accept — the dspark
4159    /// `pen_hist` convention); the verify rows penalize over its last `win()` entries.
4160    /// Empty with the arm off.
4161    pen_hist: Vec<u32>,
4162    /// Session-continuity Philox counters (never reset across bursts): `sctr` = device
4163    /// sampling events (boundary, draft chain, bonus, residual), `uctr` = host accept-test
4164    /// uniforms (`spec::host_u01`, tag 0xFFFF_FFFE).
4165    sctr: u32,
4166    uctr: u32,
4167    /// Verify rounds completed over the session lifetime (the worker's per-burst
4168    /// rounds-delta receipt, the dspark `rounds` convention).
4169    pub rounds: usize,
4170    /// Of `rounds`, those whose drafts came through a RANK-TRIMMED draft head (the DFlash2
4171    /// slab / MtpHead self-trim / native trimmed chain; lane/frspec-dflash2-20260902). Equals
4172    /// `rounds` when a trim is loaded, 0 otherwise, the counter the `[glm5-acc]` receipt
4173    /// and the rig gate read; a process-wide twin is `glm5_rank_trimmed_draft_rounds`.
4174    pub rank_trimmed_rounds: usize,
4175    done: bool,
4176    max_ctx: usize,
4177    /// MTP draft-plane layer index — `Some` on the native-MTP arm only.
4178    mtp_il: Option<usize>,
4179    /// Prompt-boundary capture for the DEFERRED prefix publication (lane/glm5-prefix-latent2,
4180    /// 2026-09-01; the dspark `prefix_capture` pattern): taken at session creation before any
4181    /// burst mutates the recurrent/tail state, drained by the worker's sweep. `None` when the
4182    /// worker did not request capture or when any boundary invariant refused.
4183    prefix_capture: Option<crate::spec::SpecBoundaryCapture>,
4184    /// FIRST-TOKEN PROFILE (lane/b200-spec-ttft-20260902): `Some` iff `MEMRA_SPEC_PROF=1`
4185    /// at creation; filled through session creation and round 1 of the first burst, then
4186    /// drained by the worker's one `[spec-prof]` line (`take_first_token_prof`).
4187    prof: Option<SpecFirstTokenProf>,
4188    /// DEPTH LOG (lane/spec-route-depth-20260902): the first `SPEC_PROF_ROUNDS` rounds'
4189    /// attribution rows, `Some` iff `MEMRA_SPEC_PROF=1`; the worker prints fresh rows after
4190    /// every burst and the summary once the log fills or the session ends.
4191    prof_rounds: Option<SpecRoundsLog>,
4192}
4193
4194impl Glm5SpecSession {
4195    /// The depth log, for the worker's `[spec-prof-rounds]` / `[spec-prof-summary]` lines.
4196    pub fn round_log_mut(&mut self) -> Option<&mut SpecRoundsLog> {
4197        self.prof_rounds.as_mut()
4198    }
4199    /// Rounds the depth log keeps (the print cadence's cap).
4200    pub fn round_log_cap() -> usize {
4201        SPEC_PROF_ROUNDS
4202    }
4203    /// Drafter ctx rows currently ingested (`None` on the native arm).
4204    pub fn draft_kv_len(&self) -> Option<usize> {
4205        match &self.draft {
4206            Glm5DraftState::Dflash2 { kv, .. } => Some(kv.len),
4207            _ => None,
4208        }
4209    }
4210    /// GATE ACCESSOR (rig gate 15): the first `rows` rows of every drafter K and V plane,
4211    /// host-side, `row_floats` = n_kv * head_dim. `None` on the native arm.
4212    #[allow(clippy::type_complexity)]
4213    // allow: (k planes, v planes) is the natural shape for a bit-identity diff
4214    pub fn draft_kv_rows_host(
4215        &self,
4216        e: &Engine,
4217        rows: usize,
4218        row_floats: usize,
4219    ) -> Option<(Vec<Vec<f32>>, Vec<Vec<f32>>)> {
4220        let Glm5DraftState::Dflash2 { kv, .. } = &self.draft else {
4221            return None;
4222        };
4223        let n = rows * row_floats;
4224        let take = |planes: &[CudaSlice<f32>]| -> Option<Vec<Vec<f32>>> {
4225            planes
4226                .iter()
4227                .map(|p| e.dtoh_view(&p.slice(0..n)).ok())
4228                .collect()
4229        };
4230        Some((take(&kv.k)?, take(&kv.v)?))
4231    }
4232    /// Context capacity of the session's cache (the server's ContextFull guard).
4233    pub fn cache_max_ctx(&self) -> usize {
4234        self.max_ctx
4235    }
4236    /// Drain the first-token profile (doc on the field). `None` with the profile off or
4237    /// once drained — the worker prints exactly one line per request.
4238    pub fn take_first_token_prof(&mut self) -> Option<SpecFirstTokenProf> {
4239        self.prof.take()
4240    }
4241    /// Drain the prompt-boundary capture (doc on the field; the dspark
4242    /// `take_prefix_capture` twin — the worker publishes it against `cache_ref`).
4243    pub fn take_prefix_capture(&mut self) -> Option<crate::spec::SpecBoundaryCapture> {
4244        self.prefix_capture.take()
4245    }
4246    /// True when the deferred prefix capture can publish NOW: a capture exists AND the
4247    /// DFlash2 drafter KV already covers the boundary. glm5 defers the prompt's feature
4248    /// ingest to round 1 (unlike dspark's at-creation ingest), so a drain that fired before
4249    /// the first burst would export an empty tail and waste the capture — the worker's
4250    /// sweep polls this instead.
4251    pub fn prefix_capture_ready(&self) -> bool {
4252        self.prefix_capture
4253            .as_ref()
4254            .is_some_and(|c| match &self.draft {
4255                Glm5DraftState::Dflash2 { kv, .. } => kv.len >= c.pos,
4256                _ => false,
4257            })
4258    }
4259    /// The drafter's readable KV tail at `upto` rows (the dspark `export_tail` seam) —
4260    /// `None` on the native arm or when the tail cannot cover the drafter window.
4261    pub fn export_draft_tail(
4262        &self,
4263        e: &Engine,
4264        upto: usize,
4265    ) -> Option<crate::dflash::DflashKvTail> {
4266        match &self.draft {
4267            Glm5DraftState::Dflash2 { kv, .. } => kv.export_tail(e, upto),
4268            _ => None,
4269        }
4270    }
4271    /// The session's trunk cache, for the worker's deferred prefix publication (the
4272    /// append-only-below-boundary slices) — never for mutation.
4273    pub fn cache_ref(&self) -> &Cache {
4274        &self.cache
4275    }
4276    /// Trunk rows currently committed (== `committed.len()` at burst boundaries).
4277    pub fn pos(&self) -> usize {
4278        self.cache.pos
4279    }
4280    /// EOS committed or the context guard tripped: the next burst would emit nothing.
4281    pub fn finished(&self) -> bool {
4282        self.done
4283    }
4284    /// True when the session is a legal demotion source (loop-port fold-in, map #8):
4285    /// GREEDY, UNPENALIZED only. A sampled session's committed stream depends on its
4286    /// session-owned Philox counters, and the plain batched sampler is a different random
4287    /// program mid-request (the exact exclusion the MTP and dspark sweeps carry). A
4288    /// penalized session (`pen: Some`, lane/spec-exclusions-20260902) is excluded the same
4289    /// way: the demotion flush (`glm5_spec_into_demoted`) argmaxes the boundary row RAW —
4290    /// no penalty pass — so demoting one would silently drop the request's penalties for
4291    /// that token, the exact failure class `glm5_penalty_admit`'s refusal exists to
4292    /// prevent, and dspark's structural reason for keeping penalized-greedy off spec
4293    /// entirely (revuto finding on the re-land, 2026-09-03). Penalized sessions therefore
4294    /// stay on spec until they end, like sampled ones.
4295    pub fn demote_eligible(&self) -> bool {
4296        self.sampling.is_none() && self.pen.is_none()
4297    }
4298}
4299
4300impl HybridModel {
4301    /// ONE-WAY DEMOTION HANDOFF for the glm5 session (loop-port fold-in — the map's #8,
4302    /// the `SpecSession::into_demoted` / `DsparkSpecSession::into_demoted` twin): consume
4303    /// the session and hand `(cache, next_pred)` to the plain batched-decode path, so a
4304    /// spec session admitted on a quiet box stops serializing the tick when load arrives
4305    /// (the spec-gate HIGH sweep's ship-safety lever; dspark receipt: "c=8 429.6 = parity
4306    /// (pre-lane -37%)").
4307    ///
4308    /// THE ANCHOR IS THE CARRIED-PENDING SHAPE: glm5 emits each round's bonus immediately
4309    /// (`round_tokens` include it) while the trunk consumes it only as the NEXT round's
4310    /// row 0 — so at every burst boundary the session holds ONE emitted-but-uncommitted
4311    /// token. Handing the cache over as-is would leave it one row short of the public
4312    /// stream, and `device_next` re-emitting the anchor would duplicate a served token.
4313    /// The flush below is `spec_flush_pending`'s exact analogue: ONE plain T=1 decode
4314    /// step commits the anchor (byte-identical to the never-drafted chain — the
4315    /// tparallel gate's accept-j-then-continue identity IS this claim), and its argmax
4316    /// becomes the handoff's `next_pred` — a token the batched path emits and feeds
4317    /// exactly as it would its own. One trunk pass, once per demotion, never per burst.
4318    ///
4319    /// ONE-WAY BY DESIGN: the draft state (MTP pending pairs / DFlash2 drafter KV and
4320    /// feature rows) and the Philox counters are DROPPED, freeing their VRAM; there is
4321    /// no cheap symmetric re-promotion (the spec.rs law, verbatim). Sampled sessions
4322    /// refuse loudly (`demote_eligible`; the worker's sweep excludes them first).
4323    pub fn glm5_spec_into_demoted(
4324        &self,
4325        e: &Engine,
4326        mut sess: Glm5SpecSession,
4327    ) -> Res<(Cache, u32)> {
4328        if !sess.demote_eligible() {
4329            let why = if sess.sampling.is_some() {
4330                "sampled sessions stay on spec until they end (session-owned Philox vs the \
4331                 worker sampler is an unmeasured distributional seam — the MTP sweep's \
4332                 exclusion, verbatim)"
4333            } else {
4334                "penalized sessions stay on spec until they end (this flush's plain argmax \
4335                 carries no penalty pass; demoting would silently drop the request's \
4336                 penalties for that token, lane/spec-exclusions-20260902)"
4337            };
4338            return Err(format!("glm5 demote: {why}").into());
4339        }
4340        if sess.cache.pos + 1 > sess.max_ctx {
4341            return Err(format!(
4342                "glm5 demote: no room to flush the live anchor ({} + 1 > ctx {})",
4343                sess.cache.pos, sess.max_ctx
4344            )
4345            .into());
4346        }
4347        let logits = self.decode_step(e, sess.anchor, &mut sess.cache)?;
4348        sess.committed.push(sess.anchor);
4349        let next = argmax(&logits) as u32;
4350        Ok((sess.cache, next))
4351    }
4352}
4353
4354/// Buckets of one eager drafter ingest (`glm5_dflash_ingest_rows`), written into the
4355/// first-token profile under the arm name that ran it.
4356#[derive(Default, Debug, Clone, Copy)]
4357struct DraftIngestStats {
4358    h2d_ms: f64,
4359    feat_ms: f64,
4360    kv_ms: f64,
4361    rows: usize,
4362    chunks: usize,
4363}
4364
4365impl DraftIngestStats {
4366    fn write(&self, pf: &mut SpecFirstTokenProf, arm: &'static str) {
4367        pf.draft_prime_ms = self.h2d_ms + self.feat_ms + self.kv_ms;
4368        pf.draft_prime_h2d_ms = self.h2d_ms;
4369        pf.draft_prime_feat_ms = self.feat_ms;
4370        pf.draft_prime_kv_ms = self.kv_ms;
4371        pf.draft_prime_rows = self.rows;
4372        pf.draft_prime_chunks = self.chunks;
4373        pf.draft_prime_arm = arm;
4374    }
4375}
4376
4377/// Gate instruments for `generate_spec_glm5_gated`. Documented as instruments: no serving
4378/// path constructs a non-default value.
4379#[derive(Default)]
4380pub struct Glm5SpecKnobs<'a> {
4381    /// `(round, draft_index, greedy_draft) -> draft` — deterministic forced-accept /
4382    /// forced-reject rounds for the end-to-end gate.
4383    pub draft_override: Option<&'a mut dyn FnMut(usize, usize, u32) -> u32>,
4384    /// RED ARM ONLY: skip the state rollback (pos still moves). A corrupted draft must then
4385    /// leave post-row-K KDA state and un-truncated latent rows behind — the end-to-end gate
4386    /// asserts the tape DIVERGES from plain decode (or the kpool residency tripwire fires).
4387    pub disable_rollback: bool,
4388    /// RED ARM ONLY: with an FR-Spec trim loaded, use the draft argmax RANK id as the vocab
4389    /// id (the q38 skipped-remap defect: 0/248 acceptance with every exactness gate green).
4390    /// The gate asserts the drafted sequence diverges from the untrimmed arm's while the
4391    /// output tape STAYS byte-identical to plain decode — the silent failure made loud.
4392    pub skip_d2t_remap: bool,
4393    /// GATE INSTRUMENT for the confidence gate (loop-port 2): `Some((p_min, pmin0))`
4394    /// overrides the `MEMRA_SPEC_PMIN`/`MEMRA_SPEC_PMIN0` env pair for this call — the
4395    /// env statics latch once per process, so the byte-identity gate drives its PMIN
4396    /// arms through here instead of the environment. `None` = the serving resolution.
4397    pub pmin_override: Option<(f32, bool)>,
4398    /// GATE INSTRUMENT (lane/glm5-accrace): trace every GREEDY round's accept decision to
4399    /// stderr as one `[glm5-accrace]` line — round, t, j, the drafts, the DEVICE argmax
4400    /// row (`argmax_token_device_col` + the one u32 readback the accept walk consumes), a
4401    /// HOST argmax over the same `vlogits` buffer, and a per-row (argmax, FNV-1a hash of
4402    /// the row's f32 bits) census.
4403    ///
4404    /// TWO THINGS IT SEPARATES, which is why it exists: (a) `dev != host` means the device
4405    /// accept path published a value the logits buffer does not justify (a readback/scratch
4406    /// race); (b) `dev == host` with a row hash that moves between two runs of the same
4407    /// deterministic fixture means the verify logits themselves were computed over
4408    /// corrupted state (an upstream walk/rollback race). The host read is issued AFTER the
4409    /// device path's own `dtoh_u32` has already synchronized the consuming stream, so the
4410    /// probe can only observe the race, never mask it.
4411    ///
4412    /// Never a serving surface: no serving path constructs a non-default value.
4413    pub accept_probe: bool,
4414}
4415
4416#[cfg(test)]
4417mod conf_keep_tests {
4418    use super::glm5_conf_keep;
4419
4420    /// The spec.rs chain-break semantics, pinned CPU-side (loop-port 2): break at the
4421    /// first sub-threshold slot; slot 0 survives a miss unless PMIN0.
4422    #[test]
4423    fn conf_keep_matches_the_spec_rs_break_semantics() {
4424        // Gate off: everything kept.
4425        assert_eq!(glm5_conf_keep(&[0.1, 0.1], 0.0, true), 2);
4426        // All confident: everything kept.
4427        assert_eq!(glm5_conf_keep(&[0.9, 0.8, 0.7], 0.5, false), 3);
4428        // Break mid-chain at the first miss; the confident tail after it never rides
4429        // (prefix truncation — the accept rule could never commit past the gap anyway).
4430        assert_eq!(glm5_conf_keep(&[0.9, 0.2, 0.9], 0.5, false), 1);
4431        assert_eq!(glm5_conf_keep(&[0.9, 0.2, 0.9], 0.5, true), 1);
4432        // Slot-0 miss: survives without PMIN0 (the j > 0 arm of the break condition), and
4433        // does NOT latch — slot 1 is judged on its own confidence (the spec.rs chain
4434        // evaluates each slot's p independently)...
4435        assert_eq!(glm5_conf_keep(&[0.2, 0.9], 0.5, false), 2);
4436        // ...but a sub-threshold slot past 0 still breaks.
4437        assert_eq!(glm5_conf_keep(&[0.2, 0.2], 0.5, false), 1);
4438        // PMIN0 arms the zero-draft round.
4439        assert_eq!(glm5_conf_keep(&[0.2, 0.9], 0.5, true), 0);
4440        // Boundary: q == p_min is NOT below it (strict <, the spec.rs test).
4441        assert_eq!(glm5_conf_keep(&[0.5, 0.5], 0.5, true), 2);
4442        // Empty chain: nothing to keep.
4443        assert_eq!(glm5_conf_keep(&[], 0.5, true), 0);
4444    }
4445}