Skip to main content

memra_engine/
dflash.rs

1//! DFlash block-diffusion drafter (DFLASH-BRINGUP-PLAN.md, 2026-07-13).
2//!
3//! 5-layer qwen3-class mini-transformer that drafts a 16-token block in ONE non-causal
4//! forward, conditioned on the TARGET's hidden states at 6 tapped layers (concatenated
5//! through `fc` + `hidden_norm`). No embed / lm_head of its own — the round reuses the
6//! target's. Reference: z-lab/dflash `dflash/model.py` (semantics frozen in the plan doc);
7//! oracle: tools/dflash_oracle.py -> /data/cache/dflash-oracle.npz.
8//!
9//! FIRST LIGHT = f32-resident weights + fresh full-context forward (no draft KV cache) —
10//! correctness vs the oracle, then the cache/quant/window arms land measurement-gated.
11
12use crate::Engine;
13use crate::model::GpuTensor;
14use cudarc::driver::CudaSlice;
15
16pub struct DflashCfg {
17    pub hidden: usize,                // 5376
18    pub n_head: usize,                // 64
19    pub n_kv: usize,                  // 8
20    pub head_dim: usize,              // 128
21    pub n_ff: usize,                  // 10752
22    pub n_layer: usize,               // 5
23    pub eps: f32,                     // 1e-6
24    pub rope_theta: f32,              // 1e6
25    pub block_size: usize,            // 16
26    pub mask_token_id: u32,           // 4
27    pub target_layer_ids: Vec<usize>, // [1,12,23,35,46,57]
28    pub sliding_window: usize,        // 2048
29    /// true = sliding_attention for that layer (4x true + 1x false on the 31B draft).
30    pub layer_sliding: Vec<bool>,
31    /// Checkpoint training-strategy census (`dspark_strategy_census` over the raw
32    /// config.json): true = a SpecForge DSPARK-strategy export (shifted labels, ALL rows
33    /// supervised — the q38 arm-a family). Keys the HARVEST DEFAULT strategy-keyed,
34    /// never env-keyed (owner-ratified 2026-08-20 after B1 confirmed H1 ×5;
35    /// DSPARK-POSTMORTEM-20260820.md B0 default-flip plan).
36    pub strategy_dspark: bool,
37    /// Explicit top-level `is_causal` from config.json (z-lab reference: an explicit
38    /// value OVERRIDES the per-layer-type default). The DFlash2 q38 checkpoint carries
39    /// `"is_causal": false` — every sliding layer is NON-causal with a symmetric
40    /// +/-2048 window (model.py `_attention_mask`). None = key absent (historical
41    /// exports; the windowless-assert arm keeps handling those byte-identically).
42    pub is_causal: Option<bool>,
43}
44
45pub struct DflashLayer {
46    pub wq: GpuTensor,           // [nh*hd, hidden] row-major (out_f rows)
47    pub wk: GpuTensor,           // [nkv*hd, hidden]
48    pub wv: GpuTensor,           // [nkv*hd, hidden]
49    pub wo: GpuTensor,           // [hidden, nh*hd]
50    pub w_gate: GpuTensor,       // [n_ff, hidden]
51    pub w_up: GpuTensor,         // [n_ff, hidden]
52    pub w_down: GpuTensor,       // [hidden, n_ff]
53    pub ln_in: CudaSlice<f32>,   // [hidden]
54    pub ln_post: CudaSlice<f32>, // [hidden]
55    pub q_norm: CudaSlice<f32>,  // [hd]
56    pub k_norm: CudaSlice<f32>,  // [hd]
57}
58
59pub struct DflashDraft {
60    pub cfg: DflashCfg,
61    pub layers: Vec<DflashLayer>,
62    pub fc: GpuTensor,               // [hidden, n_taps*hidden]
63    pub hidden_norm: CudaSlice<f32>, // [hidden]
64    pub norm: CudaSlice<f32>,        // [hidden]
65    /// DSpark semi-AR markov head (present in the repo-root checkpoint variant):
66    /// draft logits at position k get + W2(W1[prev_realized_token]) — left-to-right
67    /// within the block (the patch's _markov_semiar_sample_block semantics, greedy).
68    /// w1 = raw bf16 [V, rank] (row-gathered by device token id); w2 = q8_0 [rank->V].
69    pub markov: Option<MarkovHead>,
70    /// DSpark accept-rate head (trained with confidence loss). sglang's DSPARK planner
71    /// consumes it to SIZE VERIFY WINDOWS (cumprod survival — v0.5.16 headline; the
72    /// earlier "reference serving loop never consumes it" note matched SpecForge's
73    /// legacy spec_generate only). memra schedules with it under
74    /// `MEMRA_DSPARK_VT=confidence` (the H4 fix, DSPARK-POSTMORTEM-20260820.md:
75    /// per-round verify window from cumprod survival, `dspark_confidence_vt`) and
76    /// keeps it census+parity-only under the default ladder. Host-resident (5k floats).
77    pub confidence: Option<ConfidenceHead>,
78    /// YaRN rope (q38 arm-a inherits the target's rope_parameters: rope_type yarn,
79    /// factor 32, original 8192, beta 32/1). ff = per-dim divisors for rope_neox_ff
80    /// (effective inv_freq_j = base^(-2j/d)/ff[j] = the HF-yarn remapped frequency,
81    /// verified vs Qwen3RotaryEmbedding to 1.6e-7), mscale = attention_scaling
82    /// (0.1*ln(factor)+1) applied to q/k post-rope — cos/sin scaling distributes onto
83    /// the rotated vector exactly. None = plain rope (gemma/z-lab drafters).
84    pub rope_yarn: Option<(CudaSlice<f32>, f32)>,
85    /// DFlash2 head (z-lab `DFlash2DraftModel`, DFLASH2-EVAL-20260820.md): grouped
86    /// dynamic causal convs around EVERY sublayer + the candidate path selector that
87    /// replaces the markov chain. A DISTINCT semantic program from the DSpark head
88    /// (no-generic-support law): present iff config `architectures` names
89    /// `DFlash2DraftModel`, and then ALL 23 family tensors are REQUIRED — loading the
90    /// 58 backbone tensors alone computes an untrained model (the census trap).
91    pub dflash2: Option<Dflash2Head>,
92}
93
94/// One `GroupedDynamicCausalConv` module (reference model.py): a causal 2-tap
95/// depthwise conv over the BLOCK rows (block-local — row 0 zero-pads its missing
96/// predecessor; stateless across rounds), with per-position dynamic per-group
97/// coefficients projected from the module INPUT. `prepare` convolves the sublayer
98/// input with base_kernel[0] + dyn half 0; `finish` convolves the sublayer OUTPUT
99/// with base_kernel[1] + dyn half 1 (both dyn halves come from the SAME projection
100/// of the pre-conv input).
101pub struct Dflash2Conv {
102    /// base_kernel [2, k, hidden] flattened f32 (half-major: prepare then finish).
103    pub base: CudaSlice<f32>,
104    /// kernel_projection.weight [2*k*groups, hidden] (row layout = view(2, k, groups)).
105    pub proj: GpuTensor,
106}
107
108pub struct Dflash2Head {
109    pub attn_conv: Vec<Dflash2Conv>, // per layer
110    pub mlp_conv: Vec<Dflash2Conv>,  // per layer
111    /// candidate_selector.hidden_projection.weight [rank, hidden].
112    pub hidden_proj: GpuTensor,
113    /// Codebooks [V, rank] raw bf16, HOST-resident: the walk gathers ~1+16 rows per
114    /// draft slot (~70KB/round) — host math beside the round's existing chain dtoh,
115    /// no device residency for 2x127MB tables. Checkpoint quirk: stored WITHOUT the
116    /// `.weight` suffix (reference from_pretrained installs a key_mapping).
117    pub pred_codebook: Vec<u8>,
118    pub succ_codebook: Vec<u8>,
119    pub rank: usize,       // selector_rank 256
120    pub top_k: usize,      // selector_top_k 16
121    pub conv_k: usize,     // conv_kernel_size 2
122    pub group_size: usize, // conv_group_size 16
123    pub vocab: usize,      // codebook rows (248320)
124}
125
126/// One bf16 codebook row -> f32 (exact widening).
127fn cb_row(cb: &[u8], tok: usize, rank: usize) -> Vec<f32> {
128    bf16_to_f32(&cb[tok * rank * 2..(tok + 1) * rank * 2])
129}
130
131/// Greedy selector walk (reference `CandidateSelector.select` at T=0): per draft
132/// slot p, score(k) = unary[p,k] + <pred_codebook[prev] .* hidden_proj_row[p],
133/// succ_codebook[cand[p,k]]>, argmax over the top-k candidate set; the CHOSEN
134/// candidate seeds the next slot (sequential — the chain is the semantics, not an
135/// optimization). Host math (~nd*k*rank fused ops per round) over host-resident bf16
136/// codebooks; ties break to the LOWEST k (torch argmax convention). Pure so the
137/// selector semantics are CPU-gateable.
138///
139/// `unary`/`cand`: [nd, top_k] row-major; `hproj`: [nd, rank] row-major.
140#[allow(clippy::too_many_arguments)]
141pub fn dflash2_walk_greedy(
142    pred_codebook: &[u8],
143    succ_codebook: &[u8],
144    vocab: usize,
145    rank: usize,
146    top_k: usize,
147    unary: &[f32],
148    cand: &[u32],
149    hproj: &[f32],
150    anchor: u32,
151    nd: usize,
152) -> Vec<u32> {
153    let (kk, r) = (top_k, rank);
154    assert_eq!(unary.len(), nd * kk, "walk: unary shape");
155    assert_eq!(cand.len(), nd * kk, "walk: candidate shape");
156    assert_eq!(hproj.len(), nd * r, "walk: hidden-projection shape");
157    let mut path = Vec::with_capacity(nd);
158    let mut prev = anchor;
159    for p in 0..nd {
160        assert!(
161            (prev as usize) < vocab,
162            "walk: predecessor token {prev} outside codebook vocab {vocab}"
163        );
164        let pr = cb_row(pred_codebook, prev as usize, r);
165        let hp = &hproj[p * r..(p + 1) * r];
166        // gate = pred_row .* hidden_proj (shared across the candidate set)
167        let gate: Vec<f32> = pr.iter().zip(hp).map(|(a, b)| a * b).collect();
168        let (mut best, mut bi) = (f32::NEG_INFINITY, 0usize);
169        for k in 0..kk {
170            let c = cand[p * kk + k] as usize;
171            assert!(c < vocab, "walk: candidate {c} outside codebook vocab");
172            let sr = cb_row(succ_codebook, c, r);
173            let mut s = unary[p * kk + k];
174            for j in 0..r {
175                s += gate[j] * sr[j];
176            }
177            if s > best {
178                best = s;
179                bi = k;
180            }
181        }
182        prev = cand[p * kk + bi];
183        path.push(prev);
184    }
185    path
186}
187
188impl Dflash2Head {
189    /// Greedy selector walk over this head's codebooks — see `dflash2_walk_greedy`.
190    pub fn walk_greedy(
191        &self,
192        unary: &[f32],
193        cand: &[u32],
194        hproj: &[f32],
195        anchor: u32,
196        nd: usize,
197    ) -> Vec<u32> {
198        dflash2_walk_greedy(
199            &self.pred_codebook,
200            &self.succ_codebook,
201            self.vocab,
202            self.rank,
203            self.top_k,
204            unary,
205            cand,
206            hproj,
207            anchor,
208            nd,
209        )
210    }
211
212    /// Sampled (T>0) selector walk — see `dflash2_walk_sampled`.
213    #[allow(clippy::too_many_arguments)]
214    pub fn walk_sampled(
215        &self,
216        unary: &[f32],
217        cand: &[u32],
218        hproj: &[f32],
219        anchor: u32,
220        nd: usize,
221        temp: f32,
222        uniforms: &mut dyn FnMut() -> f32,
223    ) -> (Vec<u32>, Vec<f32>, Vec<f32>) {
224        dflash2_walk_sampled(
225            &self.pred_codebook,
226            &self.succ_codebook,
227            self.vocab,
228            self.rank,
229            self.top_k,
230            unary,
231            cand,
232            hproj,
233            anchor,
234            nd,
235            temp,
236            uniforms,
237        )
238    }
239}
240
241/// AcceptRatePredictor: raw linear proj over [hidden ; markov_prev_embedding(rank)]
242/// (with_markov=true on the q38 arm-a export) — output is the PRE-sigmoid scalar.
243pub struct ConfidenceHead {
244    pub w: Vec<f32>, // [in_dim]
245    pub b: f32,
246    pub in_dim: usize,
247    pub with_markov: bool,
248}
249
250impl ConfidenceHead {
251    /// Host dot: the PRE-sigmoid accept score for one draft slot. `hidden` = the
252    /// drafter output row the slot is harvested from (the same row its logits use);
253    /// `emb` = the markov `w1` row of the slot's PREVIOUS chain token (required iff
254    /// `with_markov`) — the exact input contract the parity gate pins (prev ids =
255    /// `[anchor, chain[..nd-1]]`, dspark_q38_parity.rs stage 5).
256    pub fn raw_score(&self, hidden: &[f32], emb: Option<&[f32]>) -> f32 {
257        let mut acc = self.b;
258        for (w, x) in self.w.iter().zip(hidden) {
259            acc += w * x;
260        }
261        if self.with_markov {
262            let emb = emb.expect("with_markov confidence head scored without the markov embedding");
263            debug_assert_eq!(hidden.len() + emb.len(), self.in_dim);
264            for (w, x) in self.w[hidden.len()..].iter().zip(emb) {
265                acc += w * x;
266            }
267        } else {
268            debug_assert_eq!(hidden.len(), self.in_dim);
269        }
270        acc
271    }
272}
273
274pub struct MarkovHead {
275    pub w1_bf16: CudaSlice<u8>, // [V, rank] bf16 raw
276    pub w2: GpuTensor,          // [rank -> V] q8_0
277    pub rank: usize,
278    pub vocab: usize,
279}
280
281/// Draft-row harvest convention for DFlash-family block drafters
282/// (darklanes research/deepseek-flash-20260818/DSPARK-POSTMORTEM-20260820.md).
283///
284/// The DFlash and DSpark SpecForge training strategies supervise DIFFERENT rows of the
285/// same `[anchor, MASK x b-1]` block, so the row -> trunk-position mapping is a property
286/// of the CHECKPOINT's training strategy, not of the loader:
287///
288/// - **Dflash** (mask-fill; z-lab dflash / SpecForge `OnlineDFlashModel`): row k is
289///   trained to predict the token AT position anchor+k — "Labels: same-position
290///   prediction", `weight_mask *= (pos_in_block > 0)` excludes the anchor row
291///   (SpecForge `specforge/algorithms/common/dflash_family_model.py:453-472`).
292///   Drafts = rows 1..b-1; the anchor row's output is untrained.
293/// - **Dspark** (shifted; SpecForge `OnlineDSparkModel`, `training.strategy: dspark` —
294///   the q38 arm-a export): row k is trained to predict the token at anchor+k+1, ALL
295///   rows supervised INCLUDING the anchor row (`label_offsets = arange(1,
296///   block_size+1)`, `dflash_family_model.py:816`). sglang's DSPARK worker — the stack
297///   every arm-a bank number was measured on — harvests gamma = block_size drafts with
298///   the anchor row's output as draft 1 (verified on the v0.5.17 eval-pin tag:
299///   `dspark_components/dspark_draft.py:248,260,318`; `dspark_config.py:269`).
300///
301/// Mismatching the convention verifies every slot against a position the row was never
302/// trained for — the q38 accept collapse (2.9 -> 1.43) in the postmortem.
303#[derive(Clone, Copy, PartialEq, Eq, Debug)]
304pub enum DsparkHarvest {
305    /// mask-fill: drafts = rows 1..b-1, row k fills position anchor+k.
306    Dflash,
307    /// shifted: drafts = rows 0..b-1, row k predicts position anchor+k+1.
308    Dspark,
309}
310
311impl DsparkHarvest {
312    /// The served resolution: explicit `MEMRA_DSPARK_HARVEST={dflash|dspark}` wins
313    /// (unknown values REFUSE loudly — a typo silently reverting the convention would
314    /// re-open the postmortem's misalignment); UNSET defers to the CHECKPOINT's own
315    /// training-strategy census — the owner-ratified default flip (2026-08-20, after
316    /// B1 confirmed H1 interleaved ×5 on serving-class hardware: accept 1.38→2.41
317    /// agentic / 1.53→3.66 math, E2E ALL EXACT both arms). Strategy-keyed, not
318    /// env-keyed, per the B0 plan: a DSPARK-strategy export harvests shifted
319    /// (all-rows), a mask-fill export keeps the historical dflash arm byte-identical.
320    pub fn resolve(cfg: &DflashCfg) -> Self {
321        Self::resolve_value(
322            std::env::var("MEMRA_DSPARK_HARVEST").ok().as_deref(),
323            cfg.strategy_dspark,
324        )
325    }
326
327    pub fn resolve_value(v: Option<&str>, strategy_dspark: bool) -> Self {
328        match v {
329            None | Some("") => {
330                if strategy_dspark {
331                    DsparkHarvest::Dspark
332                } else {
333                    DsparkHarvest::Dflash
334                }
335            }
336            set => Self::from_env_value(set),
337        }
338    }
339
340    /// ENV-ONLY parser (no checkpoint census): unset = `Dflash`, the historical arm.
341    /// Kept for the explicit-value path of [`Self::resolve_value`] and the seam tests;
342    /// round arms resolve through [`Self::resolve`] so the default stays strategy-keyed.
343    pub fn from_env_value(v: Option<&str>) -> Self {
344        match v {
345            None | Some("") | Some("dflash") => DsparkHarvest::Dflash,
346            Some("dspark") => DsparkHarvest::Dspark,
347            Some(other) => panic!(
348                "MEMRA_DSPARK_HARVEST={other}: unknown harvest convention (dflash|dspark); \
349                 refusing — a wrong convention verifies every draft slot against a position \
350                 the drafter row was not trained for (DSPARK-POSTMORTEM-20260820.md)"
351            ),
352        }
353    }
354
355    /// Resolve the harvest convention for a LOADED drafter — FAMILY-keyed first, then
356    /// STRATEGY-keyed (v0.100 train merge of the two ratified keyings):
357    /// - DFlash2 is a mask-fill-family drafter by construction (reference
358    ///   `dflash_generate` harvests rows `1-verify_size:`; the card says "block size 8
359    ///   (7 draft tokens per verification step)" — DFLASH2-EVAL-20260820.md §3). An env
360    ///   value that CONTRADICTS the census REFUSES rather than silently re-keying the
361    ///   round.
362    /// - Every other checkpoint rides [`Self::resolve_value`]: explicit env wins (typos
363    ///   refuse loudly), unset defers to the checkpoint's own training-strategy census
364    ///   (the owner-ratified 2026-08-20 default flip).
365    pub fn for_draft(draft: &DflashDraft) -> Self {
366        Self::for_family_value(
367            draft.dflash2.is_some(),
368            std::env::var("MEMRA_DSPARK_HARVEST").ok().as_deref(),
369            draft.cfg.strategy_dspark,
370        )
371    }
372
373    pub fn for_family_value(is_dflash2: bool, env: Option<&str>, strategy_dspark: bool) -> Self {
374        if is_dflash2 {
375            if env == Some("dspark") {
376                panic!(
377                    "MEMRA_DSPARK_HARVEST=dspark with a DFlash2 checkpoint: DFlash2 \
378                     is mask-fill (b-1 drafts, anchor row is not a draft — reference \
379                     dflash_generate rows 1-verify_size:); the shifted harvest would \
380                     verify every slot one position early. Refusing (census-keyed, \
381                     not env-keyed)."
382                );
383            }
384            return DsparkHarvest::Dflash;
385        }
386        Self::resolve_value(env, strategy_dspark)
387    }
388
389    /// Manifest/serialized name (the oracle geometry manifest's `harvest` field).
390    pub fn name(self) -> &'static str {
391        match self {
392            DsparkHarvest::Dflash => "dflash",
393            DsparkHarvest::Dspark => "dspark",
394        }
395    }
396
397    pub fn from_name(v: &str) -> Option<Self> {
398        match v {
399            "dflash" => Some(DsparkHarvest::Dflash),
400            "dspark" => Some(DsparkHarvest::Dspark),
401            _ => None,
402        }
403    }
404
405    /// First drafter OUTPUT row consumed as a draft candidate.
406    pub fn first_row(self) -> usize {
407        match self {
408            DsparkHarvest::Dflash => 1,
409            DsparkHarvest::Dspark => 0,
410        }
411    }
412
413    /// Drafted tokens harvested per round from a `b`-row block.
414    pub fn n_drafts(self, b: usize) -> usize {
415        match self {
416            DsparkHarvest::Dflash => b - 1,
417            DsparkHarvest::Dspark => b,
418        }
419    }
420
421    /// The position offset (relative to the round anchor at the block's row 0) that
422    /// drafter output row `row` is TRAINED to predict under this convention.
423    pub fn trained_offset_of_row(self, row: usize) -> usize {
424        match self {
425            DsparkHarvest::Dflash => row,
426            DsparkHarvest::Dspark => row + 1,
427        }
428    }
429}
430
431/// Checkpoint training-strategy census over the raw config.json text (the loader's
432/// minimal-extractor idiom — no json dep in-tree). TRUE iff the export declares the
433/// DSPARK strategy: `architectures` naming a DSpark model class (`Qwen3DSparkModel`,
434/// the SpecForge OnlineDSparkModel export form) or `dflash_config.projector_type ==
435/// "dspark"`. z-lab / OnlineDFlashModel mask-fill exports carry neither signal. Pure,
436/// so the census is testable against config fragments without files.
437pub fn dspark_strategy_census(txt: &str) -> bool {
438    let arch = txt
439        .find("\"architectures\"")
440        .and_then(|i| {
441            let rest = &txt[i..];
442            let a = rest.find('[')?;
443            let b = rest.find(']')?;
444            Some(rest[a..b].contains("DSpark"))
445        })
446        .unwrap_or(false);
447    let proj = txt
448        .find("\"projector_type\"")
449        .map(|i| {
450            let rest = &txt[i..];
451            let after = rest.find(':').map(|c| &rest[c + 1..]).unwrap_or("");
452            after.trim_start().starts_with("\"dspark\"")
453        })
454        .unwrap_or(false);
455    arch || proj
456}
457
458/// Accepted-prefix length of a round's candidates against the trunk's verify argmaxes:
459/// `cand[0]` = the round anchor (already decided), `cand[1..]` = the drafts;
460/// `vam[j]` = the trunk's argmax prediction for position anchor+j+1. Returns m =
461/// number of accepted drafts (`cand[1..=m]` committed, `vam[m]` becomes the next
462/// anchor). Pure so the harvest-alignment fixture can exercise it CPU-side.
463pub fn dspark_accept_prefix(cand: &[u32], vam: &[u32], vt: usize) -> usize {
464    let mut m = 0usize;
465    while m < vt - 1 && cand[m + 1] == vam[m] {
466        m += 1;
467    }
468    m
469}
470
471/// Verify-window policy for the dspark round (H4, DSPARK-POSTMORTEM-20260820.md §3).
472///
473/// B2 measured the structural fork: the fixed full-block window (vt=8) buys 95–100%
474/// of the sglang accept bank but LOSES wall speed to the reactive ladder everywhere
475/// except math — at 0.2–0.5 slot rates, full-block verify pays 5–6 empty rows per
476/// round. The confidence policy is the mechanism both leading engines schedule with
477/// (sglang v0.5.16 `dspark_planner.py` cumprod survival; vLLM #47808): size EACH
478/// round's window from the drafter's own trained accept-rate head, so windows open
479/// on confident streaks (math/code) and shrink on bursty text without a 4-round
480/// ladder climb.
481#[derive(Clone, Copy, PartialEq, Debug)]
482pub enum DsparkVtPolicy {
483    /// The shipped reactive ladder: `vt = (m+2).clamp(3, vt_cap)` per round
484    /// (`MEMRA_DFLASH_ADAPT=0` pins vt at `vt_cap` = the fixed-window arm).
485    Ladder,
486    /// `MEMRA_DSPARK_VT=confidence`: per-round window from cumprod survival of the
487    /// confidence head's sigmoid scores, thresholded at `tau`
488    /// (`MEMRA_DSPARK_VT_TAU`, default 0.5). Raw sigmoid — no STS sidecar
489    /// calibration exists for this export; the postmortem names this the starting
490    /// policy.
491    Confidence { tau: f32 },
492    /// `MEMRA_DSPARK_VT=confidence-slot` (owner directive, 2026-08-20: "take only
493    /// high confidence offers"): submit only the longest draft PREFIX whose every
494    /// slot clears `tau` on its own sigmoid — the low-confidence tail never enters
495    /// verify. Same tau env. vs `Confidence`: if the head's per-row score is the
496    /// MARGINAL accept probability (it already sinks with depth), cumprod survival
497    /// double-counts the decay and over-truncates; if it is the CONDITIONAL,
498    /// per-slot under-truncates. Which statistic the q38 head emits is empirical —
499    /// both arms ride the A/B.
500    ConfidenceSlot { tau: f32 },
501}
502
503impl DsparkVtPolicy {
504    /// The served resolution: explicit `MEMRA_DSPARK_VT={ladder|confidence|
505    /// confidence-slot}` wins (unknown values REFUSE loudly — a typo silently
506    /// reverting the window policy would invalidate an A/B without a trace); UNSET
507    /// defaults to **`confidence-slot` at τ = `MEMRA_DSPARK_VT_TAU` (default 0.5)** —
508    /// the owner-ratified H4 flip (2026-08-20; cell 2's 4-arm A/B ×5 + cell 3's tau
509    /// ladder put the knee at τ=.5 for the slot arm: 94–98% of the fixed-8 accept bank
510    /// at wall ≥ the reactive ladder, exactness 11/11 ALL EXACT). Census-keyed per the
511    /// capacity-keyed-defaults law: a checkpoint WITHOUT an accept-rate head has no
512    /// signal to schedule with, so unset-env resolves to the ladder there (loudly, at
513    /// load) instead of panicking on a default; `MEMRA_DFLASH_ADAPT=0` (an explicit
514    /// fixed-window request) also keeps the ladder-family arm.
515    pub fn resolve(has_confidence_head: bool) -> Self {
516        Self::resolve_value(
517            std::env::var("MEMRA_DSPARK_VT").ok().as_deref(),
518            std::env::var("MEMRA_DSPARK_VT_TAU").ok().as_deref(),
519            std::env::var("MEMRA_DFLASH_ADAPT").ok().as_deref(),
520            has_confidence_head,
521        )
522    }
523
524    pub fn resolve_value(
525        vt: Option<&str>,
526        tau: Option<&str>,
527        adapt: Option<&str>,
528        has_confidence_head: bool,
529    ) -> Self {
530        match vt {
531            None | Some("") => {
532                if adapt == Some("0") || !has_confidence_head {
533                    DsparkVtPolicy::Ladder
534                } else {
535                    // The ratified default rides the SAME tau parse as the explicit
536                    // arm (a bad MEMRA_DSPARK_VT_TAU refuses, never silently ignored).
537                    Self::from_env_value(Some("confidence-slot"), tau, adapt)
538                }
539            }
540            set => Self::from_env_value(set, tau, adapt),
541        }
542    }
543
544    /// ENV-ONLY parser (no head census): unset = `Ladder`. Kept for the explicit-value
545    /// path of [`Self::resolve_value`] and the policy-gate tests; round arms resolve
546    /// through [`Self::resolve`] so the default stays head-census-keyed.
547    pub fn from_env_value(vt: Option<&str>, tau: Option<&str>, adapt: Option<&str>) -> Self {
548        match vt {
549            None | Some("") | Some("ladder") => DsparkVtPolicy::Ladder,
550            Some(mode @ ("confidence" | "confidence-slot")) => {
551                if adapt == Some("0") {
552                    panic!(
553                        "MEMRA_DSPARK_VT={mode} together with MEMRA_DFLASH_ADAPT=0 is \
554                         contradictory (a pinned fixed window vs a per-round confidence \
555                         window); unset one — refuse-on-ambiguity"
556                    );
557                }
558                let tau = tau
559                    .map(|t| {
560                        t.parse::<f32>()
561                            .unwrap_or_else(|_| panic!("MEMRA_DSPARK_VT_TAU={t}: not a float"))
562                    })
563                    .unwrap_or(0.5);
564                assert!(
565                    tau > 0.0 && tau < 1.0,
566                    "MEMRA_DSPARK_VT_TAU={tau}: confidence threshold must be in (0,1)"
567                );
568                if mode == "confidence" {
569                    DsparkVtPolicy::Confidence { tau }
570                } else {
571                    DsparkVtPolicy::ConfidenceSlot { tau }
572                }
573            }
574            Some(other) => panic!(
575                "MEMRA_DSPARK_VT={other}: unknown verify-window policy \
576                 (ladder|confidence|confidence-slot); refusing — a wrong policy \
577                 silently reverts the H4 arm (DSPARK-POSTMORTEM-20260820.md)"
578            ),
579        }
580    }
581
582    /// True for every head-scheduled arm (the loops gate the head requirement and
583    /// the embedding stash on this).
584    pub fn is_confidence(&self) -> bool {
585        !matches!(self, DsparkVtPolicy::Ladder)
586    }
587
588    /// Size this round's verify window from the head's pre-sigmoid slot scores.
589    /// `None` under the ladder (the caller keeps its carried vt).
590    pub fn size_window(&self, raws: &[f32], vt_cap: usize) -> Option<usize> {
591        match *self {
592            DsparkVtPolicy::Ladder => None,
593            DsparkVtPolicy::Confidence { tau } => Some(dspark_confidence_vt(raws, tau, vt_cap)),
594            DsparkVtPolicy::ConfidenceSlot { tau } => {
595                Some(dspark_slot_confidence_vt(raws, tau, vt_cap))
596            }
597        }
598    }
599}
600
601/// H4 window sizing (the sglang-planner/vLLM-#47808 mechanism, thresholded): `raws[k]`
602/// = the accept-rate head's PRE-sigmoid score for draft slot k+1; survival
603/// `S_k = prod_{j<=k} sigmoid(raws[j])`; the window keeps leading slots while
604/// `S_k >= tau`. Returns `vt` = 1 (anchor) + kept drafts, clamped to `[2, vt_cap]`:
605/// the draft forward is already paid, so at least one draft rides every verify — one
606/// extra verify row costs less than a guaranteed empty round. Pure, so the policy's
607/// knee is testable CPU-side like `dspark_accept_prefix`.
608pub fn dspark_confidence_vt(raws: &[f32], tau: f32, vt_cap: usize) -> usize {
609    let mut surv = 1.0f32;
610    let mut kept = 0usize;
611    for &r in raws {
612        surv *= 1.0 / (1.0 + (-r).exp());
613        if surv < tau {
614            break;
615        }
616        kept += 1;
617    }
618    (1 + kept).clamp(2, vt_cap.max(2))
619}
620
621/// Owner-directive arm (2026-08-20, "take only high confidence offers"): keep the
622/// longest draft PREFIX whose EVERY slot clears `tau` on its own sigmoid — truncate
623/// at the first sub-threshold slot, so the low-confidence tail (B2 measured 0.2–0.5
624/// slot rates at depth) never enters verify. Prefix truncation is forced by the
625/// accept rule anyway (`dspark_accept_prefix` stops at the first miss — a kept slot
626/// after a dropped one could never commit); the policy fork vs `dspark_confidence_vt`
627/// is only the stopping statistic (per-slot marginal vs cumulative survival). Same
628/// floor/cap contract.
629pub fn dspark_slot_confidence_vt(raws: &[f32], tau: f32, vt_cap: usize) -> usize {
630    let mut kept = 0usize;
631    for &r in raws {
632        let p = 1.0 / (1.0 + (-r).exp());
633        if p < tau {
634            break;
635        }
636        kept += 1;
637    }
638    (1 + kept).clamp(2, vt_cap.max(2))
639}
640
641// ================= SAMPLED ADMISSION (T>0) — lane/dspark-sampled-admission-20260820 =====
642// True rejection sampling for the dspark route (mystery A of DSPARK-POSTMORTEM-20260820):
643// draft slot j is DRAWN from a recorded proposal distribution q_j, the trunk's verify column
644// arbitrates with the Leviathan/Chen rule (accept x_j while u_j*q_j(x_j) < p_j(x_j); on
645// reject resample from norm(max(0, p-q)); on full accept the bonus ~ p at the last column),
646// so the committed stream's distribution equals trunk-only sampling from the FILTERED target
647// p — the same contract the frspec/MTP route ships (spec.rs sampled accept walk; kernels
648// oracled by sample_check). T==0/None keeps every greedy path byte-identical (the exactness
649// instrument and the kill-switch are the same code).
650//
651// Two proposal families, each recording the TRUE distribution its drafts were drawn from:
652// - Rows (dspark/dflash strategy checkpoints): per-slot FILTERED softmax of the draft-logits
653//   row — markov-corrected in place when the head is present (the sglang DSPARK worker's
654//   "chain rejection sampling over markov-corrected draft probs"), plain rows otherwise
655//   (the z-lab reference's independent-row T>0 arm).
656// - Selector (DFlash2): the candidate-path selector's per-slot softmax over its top-k
657//   candidate set at temperature ONLY — the reference applies no top-k/top-p to selector
658//   scores (z-lab model.py `CandidateSelector.select`: `_sampling_probs(scores, temperature)`
659//   with default filters) — with the candidate-set residual (`scatter_add_` of -q, clamped).
660
661/// Rejection-sampling prefix walk: accept draft j while `u_j * q_j < p_j` (strict, f64 —
662/// byte-identical to the frspec accept test). `p`/`q` are the FILTERED target/proposal
663/// probabilities of the drafted tokens; `u` the per-slot uniforms. Pure so the composition
664/// gate can pin the rule on CPU.
665pub fn rejection_accept_len(p: &[f32], q: &[f32], u: &[f32]) -> usize {
666    assert!(
667        q.len() >= p.len() && u.len() >= p.len(),
668        "accept walk shape"
669    );
670    let mut m = 0usize;
671    while m < p.len() && (u[m] as f64) * (q[m] as f64) < p[m] as f64 {
672        m += 1;
673    }
674    m
675}
676
677/// Sampled selector walk (reference `CandidateSelector.select`, temperature>0 arm): per
678/// draft slot the pair scores over the top-k candidate set become a softmax at `temp`
679/// (temperature ONLY — the reference passes no top-k/top-p here), one uniform draws the
680/// candidate (fixed-order CDF walk), and the CHOSEN candidate seeds the next slot exactly
681/// like the greedy chain. Returns (path, q_chosen[nd], q_rows[nd*top_k]) — q_rows are the
682/// recorded per-slot candidate probabilities (the residual's `scatter_add_` input), and
683/// q_chosen[j] == q_rows[j*top_k + chosen_j] is the accept-test q. Pure (uniforms injected)
684/// so the T->0 limit, the chain conditioning, and the recorded-q contract are CPU-gateable.
685#[allow(clippy::too_many_arguments)]
686pub fn dflash2_walk_sampled(
687    pred_codebook: &[u8],
688    succ_codebook: &[u8],
689    vocab: usize,
690    rank: usize,
691    top_k: usize,
692    unary: &[f32],
693    cand: &[u32],
694    hproj: &[f32],
695    anchor: u32,
696    nd: usize,
697    temp: f32,
698    uniforms: &mut dyn FnMut() -> f32,
699) -> (Vec<u32>, Vec<f32>, Vec<f32>) {
700    assert!(
701        temp > 0.0,
702        "sampled walk is the T>0 arm; T=0 is walk_greedy"
703    );
704    let (kk, r) = (top_k, rank);
705    assert_eq!(unary.len(), nd * kk, "walk: unary shape");
706    assert_eq!(cand.len(), nd * kk, "walk: candidate shape");
707    assert_eq!(hproj.len(), nd * r, "walk: hidden-projection shape");
708    let mut path = Vec::with_capacity(nd);
709    let mut q_chosen = Vec::with_capacity(nd);
710    let mut q_rows = Vec::with_capacity(nd * kk);
711    let mut prev = anchor;
712    for p in 0..nd {
713        assert!(
714            (prev as usize) < vocab,
715            "walk: predecessor token {prev} outside codebook vocab {vocab}"
716        );
717        let pr = cb_row(pred_codebook, prev as usize, r);
718        let hp = &hproj[p * r..(p + 1) * r];
719        let gate: Vec<f32> = pr.iter().zip(hp).map(|(a, b)| a * b).collect();
720        let mut scores = vec![0f32; kk];
721        for (k, s) in scores.iter_mut().enumerate() {
722            let c = cand[p * kk + k] as usize;
723            assert!(c < vocab, "walk: candidate {c} outside codebook vocab");
724            let sr = cb_row(succ_codebook, c, r);
725            let mut acc = unary[p * kk + k];
726            for j in 0..r {
727                acc += gate[j] * sr[j];
728            }
729            *s = acc;
730        }
731        // softmax over the candidate set at temp (f64 internals; recorded probs are the
732        // f32 values the CDF walk actually samples from — recorded q IS the proposal).
733        let mx = scores.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
734        let mut z = 0f64;
735        let ex: Vec<f64> = scores
736            .iter()
737            .map(|&s| {
738                let e0 = (((s - mx) / temp) as f64).exp();
739                z += e0;
740                e0
741            })
742            .collect();
743        let probs: Vec<f32> = ex.iter().map(|&e0| (e0 / z) as f32).collect();
744        let u = uniforms() as f64;
745        let mut acc = 0f64;
746        // fp-residue fallback (u >= f32-accumulated mass, ~2^-24 events): the max-prob
747        // candidate — never a zero-prob one (host_u01's range includes 1.0 exactly).
748        let mut bi = probs
749            .iter()
750            .enumerate()
751            .max_by(|a, b| a.1.total_cmp(b.1))
752            .map(|(k, _)| k)
753            .unwrap_or(0);
754        for (k, &pk) in probs.iter().enumerate() {
755            acc += pk as f64;
756            if u < acc {
757                bi = k;
758                break;
759            }
760        }
761        prev = cand[p * kk + bi];
762        path.push(prev);
763        q_chosen.push(probs[bi]);
764        q_rows.extend_from_slice(&probs);
765    }
766    (path, q_chosen, q_rows)
767}
768
769/// `dflash2_propose_sampled`'s wire: (path, q_chosen, candidate ids, q_rows).
770pub(crate) type Dflash2SampledProposal = (Vec<u32>, Vec<f32>, Vec<u32>, Vec<f32>);
771
772/// Per-round proposal record for the sampled dspark round — everything the rejection
773/// walk needs to evaluate the TRUE per-slot proposal distribution q.
774pub(crate) enum DsparkDraftSample {
775    /// q lives in the round's draft-logits buffer `dl` (markov-biased in place when the
776    /// head is armed); per-slot FILTERED stats retained device-contiguous for the accept
777    /// gather + host-mirrored for the reject-slot residual.
778    Rows {
779        th: CudaSlice<f32>,          // [nd] filter thresholds (e-units), slot-indexed
780        z: CudaSlice<f32>,           // [nd] renorm masses
781        stats: Vec<(f32, f32, f32)>, // host (mx, th, z) per slot
782    },
783    /// DFlash2 candidate-path selector: q is the recorded candidate-set distribution.
784    Selector {
785        cand: Vec<u32>,     // [nd*top_k] candidate ids
786        q_rows: Vec<f32>,   // [nd*top_k] per-slot candidate probs
787        q_chosen: Vec<f32>, // [nd] prob of the drawn candidate (accept-test q)
788        top_k: usize,
789    },
790}
791
792/// The sampled round's verify+accept: filtered p gathered from the trunk's verify logits
793/// (row j arbitrates draft `cand[j+1]` — the position mapping the greedy prefix walk uses),
794/// the rejection walk over host uniforms, then `next` = bonus (full accept: filtered-Gumbel
795/// from the LAST verify row with its OWN fresh stats — the sampfix-20260805 law: that row is
796/// one past the gathered set) or the residual sample at the reject slot (family-keyed q:
797/// full-row logits for Rows, sparse candidate-set probs for Selector). Returns (m, next) —
798/// the exact (accepted-drafts, next-anchor) contract of the greedy `dspark_accept_prefix` +
799/// `vam[m]` pair, so both round bodies commit identically downstream.
800#[allow(clippy::too_many_arguments)]
801pub(crate) fn dspark_accept_sampled(
802    e: &Engine,
803    tlogits: &CudaSlice<f32>,
804    cand: &[u32],
805    vt: usize,
806    n_vocab: usize,
807    dl: &CudaSlice<f32>,
808    prop: &DsparkDraftSample,
809    sp: &crate::spec::SpecSampling,
810    sctr: &mut u32,
811    uctr: &mut u32,
812) -> Result<(usize, u32), Box<dyn std::error::Error>> {
813    let nq = vt - 1; // drafts under this round's verify window
814    debug_assert!(nq >= 1 && cand.len() > nq, "sampled accept shape");
815    // --- filtered p at the drafted tokens (one batched stats + gather over rows 0..nq-1) ---
816    let rows: Vec<i32> = (0..nq as i32).collect();
817    let ids: Vec<u32> = cand[1..=nq].to_vec();
818    let rowsd = e.htod_i32(&rows)?;
819    let idsd = e.htod_u32_v(&ids)?;
820    let (mut pth, mut pz, mut pmx) = (e.zeros(nq)?, e.zeros(nq)?, e.zeros(nq)?);
821    e.filter_stats(
822        tlogits, n_vocab, &rowsd, &mut pth, &mut pz, &mut pmx, n_vocab, nq, sp.temp, sp.top_k,
823        sp.top_p, sp.min_p,
824    )?;
825    let mut pj_d = e.zeros(nq)?;
826    e.softmax_gather_filtered(
827        tlogits, n_vocab, &idsd, &rowsd, &pth, &pz, &mut pj_d, n_vocab, nq, sp.temp,
828    )?;
829    let pj = e.dtoh(&pj_d)?;
830    let (pthv, pzv, pmxv) = (e.dtoh(&pth)?, e.dtoh(&pz)?, e.dtoh(&pmx)?);
831    // --- q at the drafted tokens (the recorded proposal distribution) ---
832    let qj: Vec<f32> = match prop {
833        DsparkDraftSample::Rows { th, z, .. } => {
834            // dl row j is draft j's (bias-corrected) logits row; th/z are slot-indexed, and
835            // rows 0..nq-1 index both the buffer rows and the stat pairs.
836            let mut qd = e.zeros(nq)?;
837            e.softmax_gather_filtered(
838                dl, n_vocab, &idsd, &rowsd, th, z, &mut qd, n_vocab, nq, sp.temp,
839            )?;
840            e.dtoh(&qd)?
841        }
842        DsparkDraftSample::Selector { q_chosen, .. } => q_chosen[..nq].to_vec(),
843    };
844    // --- the rejection walk ---
845    let mut us = Vec::with_capacity(nq);
846    for _ in 0..nq {
847        us.push(crate::spec::host_u01(sp.seed, *uctr));
848        *uctr = uctr.wrapping_add(1);
849    }
850    let m = rejection_accept_len(&pj[..nq], &qj[..nq], &us);
851    // --- next anchor: bonus or residual ---
852    let next = if m == nq {
853        // FULL ACCEPT: bonus ~ filtered p at verify row vt-1 — fresh stats for THIS row.
854        let rows_l = e.htod_i32(&[(vt - 1) as i32])?;
855        let (mut th1, mut z1, mut mx1) = (e.zeros(1)?, e.zeros(1)?, e.zeros(1)?);
856        e.filter_stats(
857            tlogits, n_vocab, &rows_l, &mut th1, &mut z1, &mut mx1, n_vocab, 1, sp.temp, sp.top_k,
858            sp.top_p, sp.min_p,
859        )?;
860        let mut pb = e.zeros(n_vocab)?;
861        e.gumbel_perturb_filtered_col(
862            tlogits,
863            vt - 1,
864            &mut pb,
865            n_vocab,
866            sp.seed,
867            *sctr,
868            sp.temp,
869            &mx1,
870            &th1,
871            0,
872        )?;
873        *sctr = sctr.wrapping_add(1);
874        let td = e.argmax_token_device(&pb, n_vocab)?;
875        e.dtoh_u32_one(&td)?
876    } else {
877        // REJECT at slot m: token ~ norm(max(0, p_m - q_m)); p row m's stats come from the
878        // gathered set (rows 0..nq-1 cover every reject slot).
879        let mut col = e.zeros(n_vocab)?;
880        e.copy_view_into(
881            &mut col,
882            0,
883            &tlogits.slice(m * n_vocab..(m + 1) * n_vocab),
884            n_vocab,
885        )?;
886        let p_stats = (pmxv[m], pthv[m], pzv[m]);
887        let mut tok_d = e.alloc_u32_zeroed(1)?;
888        let sc = *sctr;
889        *sctr = sctr.wrapping_add(1);
890        match prop {
891            DsparkDraftSample::Rows { stats, .. } => {
892                let mut qbuf = e.zeros(n_vocab)?;
893                e.copy_view_into(
894                    &mut qbuf,
895                    0,
896                    &dl.slice(m * n_vocab..(m + 1) * n_vocab),
897                    n_vocab,
898                )?;
899                e.residual_sample_filtered(
900                    &col,
901                    Some(&qbuf),
902                    n_vocab,
903                    sp.temp,
904                    sp.seed,
905                    sc,
906                    p_stats,
907                    stats[m],
908                    &mut tok_d,
909                )?;
910            }
911            DsparkDraftSample::Selector {
912                cand: cids,
913                q_rows,
914                top_k,
915                ..
916            } => {
917                let k = *top_k;
918                let ids_m = e.htod_u32_v(&cids[m * k..(m + 1) * k])?;
919                let qs_m = e.htod(&q_rows[m * k..(m + 1) * k])?;
920                e.residual_sample_sparse_q(
921                    &col, &ids_m, &qs_m, k, n_vocab, sp.temp, sp.seed, sc, p_stats, &mut tok_d,
922                )?;
923            }
924        }
925        e.dtoh_u32(&tok_d)?[0]
926    };
927    Ok((m, next))
928}
929
930/// Clip door for the DFlash2 windowed round attention (lane/dflash2-longctx, §10.6(c)).
931/// Default ON: the lo-clipped kernel — byte-identical output (kernel_check
932/// `sdpa_naive_w_lo`), O(window) key scan, and no T_kv*4-byte shared-mem launch bound, so
933/// the route survives past ~12k ctx (GATES-SMOKE-20260821 B2: DriverError(
934/// CUDA_ERROR_INVALID_VALUE) at ctx 16,571/30,157, last success 9,510).
935/// MEMRA_DFLASH2_SDPA_CLIP=0 = the legacy full-scan kernel byte-for-byte — the rollback
936/// seam and the long-ctx gate's crash-reproduction arm.
937fn dflash2_sdpa_clip_on() -> bool {
938    std::env::var("MEMRA_DFLASH2_SDPA_CLIP")
939        .map(|v| v != "0")
940        .unwrap_or(true)
941}
942
943/// The DFlash2 round attention over the non-causal symmetric window: one seam for both the
944/// first-light (`forward_block`) and cached (`forward_round`) arms, dispatching the clipped
945/// kernel unless the rollback door is thrown.
946#[allow(clippy::too_many_arguments)]
947fn d2_windowed_attn(
948    e: &Engine,
949    q: &CudaSlice<f32>,
950    k: &CudaSlice<f32>,
951    v: &CudaSlice<f32>,
952    attn: &mut CudaSlice<f32>,
953    hd: usize,
954    nh: usize,
955    nkv: usize,
956    t: usize,
957    t_kv: usize,
958    scale: f32,
959    c: &DflashCfg,
960) -> Result<(), Box<dyn std::error::Error>> {
961    if dflash2_sdpa_clip_on() {
962        e.sdpa_naive_w_lo(
963            q,
964            k,
965            v,
966            attn,
967            hd,
968            nh,
969            nkv,
970            t,
971            t_kv,
972            scale,
973            false,
974            c.sliding_window,
975        )
976    } else {
977        e.sdpa_naive_w(
978            q,
979            k,
980            v,
981            attn,
982            hd,
983            nh,
984            nkv,
985            t,
986            t_kv,
987            scale,
988            false,
989            c.sliding_window,
990        )
991    }
992}
993
994fn bf16_to_f32(bytes: &[u8]) -> Vec<f32> {
995    bytes
996        .chunks_exact(2)
997        .map(|c| f32::from_bits((u16::from_le_bytes([c[0], c[1]]) as u32) << 16))
998        .collect()
999}
1000
1001/// Host q8_0 encode (ggml block layout: [d f16][32 x i8] = 34B/32 vals). The drafter's
1002/// weights ride the dp4a fast path at 1.6GB resident (bf16 3.1GB + the 31B trunk OOM'd
1003/// 24GB; f32 6.2GB worse). Drafter quantization moves ACCEPTANCE only — verify exactness
1004/// is structural.
1005fn encode_q8_0(vals: &[f32]) -> Vec<u8> {
1006    let mut out = Vec::with_capacity(vals.len() / 32 * 34);
1007    for blk in vals.chunks_exact(32) {
1008        let amax = blk.iter().fold(0f32, |a, v| a.max(v.abs()));
1009        let d = amax / 127.0;
1010        let id = if d > 0.0 { 1.0 / d } else { 0.0 };
1011        let dh = half_from_f32(d);
1012        out.extend_from_slice(&dh.to_le_bytes());
1013        for &v in blk {
1014            out.push(((v * id).round().clamp(-127.0, 127.0)) as i8 as u8);
1015        }
1016    }
1017    out
1018}
1019
1020/// Host q4_0 encode (ggml: [d f16][16B packed nibbles] = 18B/32 vals; q = round(v/d)+8,
1021/// d = amax/-7 sign trick NOT used — plain amax/7? ggml uses d = max/-8 .. follow ggml:
1022/// d = amax / -8 when the max is negative-dominant; reference quantize_row_q4_0: d =
1023/// max(|v|)/-8 signed-max form). Implemented to match ggml quantize_row_q4_0_ref.
1024fn encode_q4_0(vals: &[f32]) -> Vec<u8> {
1025    let mut out = Vec::with_capacity(vals.len() / 32 * 18);
1026    for blk in vals.chunks_exact(32) {
1027        // ggml ref: pick the value with the LARGEST |v| (keeping sign), d = that / -8
1028        let mut amax = 0f32;
1029        let mut mx = 0f32;
1030        for &v in blk {
1031            if v.abs() > amax {
1032                amax = v.abs();
1033                mx = v;
1034            }
1035        }
1036        let d = mx / -8.0;
1037        let id = if d != 0.0 { 1.0 / d } else { 0.0 };
1038        out.extend_from_slice(&half_from_f32(d).to_le_bytes());
1039        for j in 0..16 {
1040            let x0 = (blk[j] * id + 8.5).clamp(0.0, 15.0) as u8;
1041            let x1 = (blk[j + 16] * id + 8.5).clamp(0.0, 15.0) as u8;
1042            out.push(x0 | (x1 << 4));
1043        }
1044    }
1045    out
1046}
1047
1048fn half_from_f32(v: f32) -> u16 {
1049    // f32 -> IEEE f16 (round-to-nearest-even; range of q8_0 d values is tame)
1050    let b = v.to_bits();
1051    let sign = ((b >> 16) & 0x8000) as u16;
1052    let exp = ((b >> 23) & 0xff) as i32 - 127 + 15;
1053    let man = b & 0x7fffff;
1054    if exp <= 0 {
1055        return sign;
1056    } // flush tiny d to zero
1057    if exp >= 31 {
1058        return sign | 0x7c00;
1059    } // inf (unreachable for sane d)
1060    let mut h = sign | ((exp as u16) << 10) | ((man >> 13) as u16);
1061    // round to nearest even on the truncated 13 bits
1062    let rem = man & 0x1fff;
1063    if rem > 0x1000 || (rem == 0x1000 && (h & 1) == 1) {
1064        h += 1;
1065    }
1066    h
1067}
1068
1069impl DflashDraft {
1070    /// Load the backbone-only checkpoint dir (config.json + model.safetensors, bf16).
1071    /// Config scalars ride a minimal extractor (no json dep in-tree — HfConfig precedent).
1072    pub fn load(e: &Engine, dir: &std::path::Path) -> Result<Self, Box<dyn std::error::Error>> {
1073        let txt = std::fs::read_to_string(dir.join("config.json"))?;
1074        fn num(txt: &str, key: &str) -> Option<f64> {
1075            let i = txt.find(&format!("\"{key}\""))?;
1076            let rest = &txt[i..];
1077            let colon = rest.find(':')?;
1078            let val: String = rest[colon + 1..]
1079                .trim_start()
1080                .chars()
1081                .take_while(|c| {
1082                    c.is_ascii_digit()
1083                        || *c == '.'
1084                        || *c == '-'
1085                        || *c == 'e'
1086                        || *c == 'E'
1087                        || *c == '+'
1088                })
1089                .collect();
1090            val.parse().ok()
1091        }
1092        fn num_list(txt: &str, key: &str) -> Vec<usize> {
1093            let Some(i) = txt.find(&format!("\"{key}\"")) else {
1094                return Vec::new();
1095            };
1096            let rest = &txt[i..];
1097            let (Some(a), Some(b)) = (rest.find('['), rest.find(']')) else {
1098                return Vec::new();
1099            };
1100            rest[a + 1..b]
1101                .split(',')
1102                .filter_map(|s| s.trim().parse().ok())
1103                .collect()
1104        }
1105        /// Substring of the JSON OBJECT value of a top-level key (brace-balanced) —
1106        /// the explicit scoped parse the DFlash2 census demands: `dflash_config` and
1107        /// `rope_parameters` are nested objects, and finding their keys by global
1108        /// `txt.find` is luck, not a contract (DFLASH2-EVAL-20260820.md §5.1).
1109        fn scope<'a>(txt: &'a str, key: &str) -> Option<&'a str> {
1110            let i = txt.find(&format!("\"{key}\""))?;
1111            let rest = &txt[i..];
1112            let open = rest.find('{')?;
1113            let mut depth = 0usize;
1114            for (j, ch) in rest[open..].char_indices() {
1115                match ch {
1116                    '{' => depth += 1,
1117                    '}' => {
1118                        depth -= 1;
1119                        if depth == 0 {
1120                            return Some(&rest[open..open + j + 1]);
1121                        }
1122                    }
1123                    _ => {}
1124                }
1125            }
1126            None
1127        }
1128        // Family detection is the ARCHITECTURES string, not tensor presence: a DFlash2
1129        // checkpoint whose new tensors were stripped must REFUSE, not degrade into the
1130        // 58-tensor untrained program (DFLASH2-EVAL-20260820.md §3).
1131        let is_dflash2 = {
1132            let arch = scope_list(&txt, "architectures");
1133            arch.contains("DFlash2DraftModel")
1134        };
1135        fn scope_list(txt: &str, key: &str) -> String {
1136            let Some(i) = txt.find(&format!("\"{key}\"")) else {
1137                return String::new();
1138            };
1139            let rest = &txt[i..];
1140            match (rest.find('['), rest.find(']')) {
1141                (Some(a), Some(b)) if a < b => rest[a + 1..b].to_string(),
1142                _ => String::new(),
1143            }
1144        }
1145        // DFlash2 scalars parse from their OWN scopes; other families keep the
1146        // historical global-find behavior byte-identically.
1147        let d2_cfg_txt: Option<&str> = if is_dflash2 {
1148            Some(scope(&txt, "dflash_config").unwrap_or_else(|| {
1149                panic!("DFlash2DraftModel config.json has no dflash_config object — refusing")
1150            }))
1151        } else {
1152            None
1153        };
1154        let g = |k: &str| num(&txt, k).unwrap_or_else(|| panic!("config missing {k}")) as usize;
1155        let g2 = |k: &str| -> usize {
1156            let t = d2_cfg_txt.expect("dflash2 scope");
1157            num(t, k).unwrap_or_else(|| panic!("dflash_config missing {k} — refusing")) as usize
1158        };
1159        // layer_types order: count entries, mark sliding ones
1160        let layer_sliding: Vec<bool> = {
1161            let i = txt.find("\"layer_types\"").expect("layer_types");
1162            let rest = &txt[i..];
1163            let (a, b) = (rest.find('[').unwrap(), rest.find(']').unwrap());
1164            rest[a + 1..b]
1165                .split(',')
1166                .map(|s| s.contains("sliding_attention"))
1167                .collect()
1168        };
1169        // sliding_window is null on all-full-attention exports (q38 arm-a); the window
1170        // only constrains rounds when a sliding layer exists (reference: resolve_dflash_
1171        // attention_layout returns None when no layer slides).
1172        let sliding_window = if layer_sliding.iter().any(|&s| s) {
1173            g("sliding_window")
1174        } else {
1175            num(&txt, "sliding_window")
1176                .map(|v| v as usize)
1177                .unwrap_or(usize::MAX)
1178        };
1179        // Explicit top-level is_causal (z-lab reference: overrides the layer-type
1180        // default). Parsed as a bare bool; absent = None (historical arms unchanged).
1181        let is_causal = txt
1182            .find("\"is_causal\"")
1183            .and_then(|i| txt[i..].find(':').map(|c| i + c + 1))
1184            .map(|v| txt[v..].trim_start().starts_with("true"));
1185        let cfg = DflashCfg {
1186            hidden: g("hidden_size"),
1187            n_head: g("num_attention_heads"),
1188            n_kv: g("num_key_value_heads"),
1189            head_dim: g("head_dim"),
1190            n_ff: g("intermediate_size"),
1191            n_layer: g("num_hidden_layers"),
1192            eps: num(&txt, "rms_norm_eps").expect("rms_norm_eps") as f32,
1193            // DFlash2 (transformers-5 style): rope_theta lives in the nested
1194            // rope_parameters object — parse it from its scope, not by global find.
1195            rope_theta: if is_dflash2 {
1196                let rp = scope(&txt, "rope_parameters")
1197                    .unwrap_or_else(|| panic!("DFlash2 config has no rope_parameters — refusing"));
1198                assert!(
1199                    rp.contains("\"default\""),
1200                    "DFlash2 rope_parameters rope_type is not \"default\" — the port \
1201                     implements plain neox rope only; refusing ({rp})"
1202                );
1203                num(rp, "rope_theta").expect("rope_parameters.rope_theta") as f32
1204            } else {
1205                num(&txt, "rope_theta").expect("rope_theta") as f32
1206            },
1207            block_size: if is_dflash2 {
1208                g2("block_size")
1209            } else {
1210                g("block_size")
1211            },
1212            mask_token_id: if is_dflash2 {
1213                g2("mask_token_id")
1214            } else {
1215                g("mask_token_id")
1216            } as u32,
1217            target_layer_ids: if is_dflash2 {
1218                num_list(d2_cfg_txt.expect("dflash2 scope"), "target_layer_ids")
1219            } else {
1220                num_list(&txt, "target_layer_ids")
1221            },
1222            sliding_window,
1223            layer_sliding,
1224            strategy_dspark: dspark_strategy_census(&txt),
1225            is_causal,
1226        };
1227        if is_dflash2 {
1228            // The windowed round arm implements the reference's NON-causal symmetric
1229            // window only (config `is_causal: false` on the q38 DFlash2 export). A
1230            // causal DFlash2 variant is a different mask program — refuse it rather
1231            // than run the wrong one fluently.
1232            assert_eq!(
1233                cfg.is_causal,
1234                Some(false),
1235                "DFlash2 port requires explicit config is_causal=false \
1236                 (non-causal symmetric sliding window); got {is_causal:?} — refusing"
1237            );
1238            assert!(
1239                cfg.layer_sliding.iter().all(|&s| s),
1240                "DFlash2 port expects all layers sliding_attention (q38 export); \
1241                 got {:?} — refusing (unverified mask program)",
1242                cfg.layer_sliding
1243            );
1244            assert!(
1245                cfg.block_size <= cfg.sliding_window,
1246                "DFlash2 block {} exceeds the sliding window {} — the windowed SDPA \
1247                 omits the future-side mask because block rows stay within the window",
1248                cfg.block_size,
1249                cfg.sliding_window
1250            );
1251        }
1252        let st = memra_gguf::safetensors::StModel::open(&dir.join("model.safetensors"))?;
1253        // 1D norm weights ride raw slices; 2D matmul weights ride GpuTensor::Float
1254        // (cuBLASLt f32 arm — the Stage-A numeric class, right for oracle parity).
1255        let up = |name: &str| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1256            let (_info, bytes) = st
1257                .raw(name)
1258                .ok_or_else(|| format!("missing tensor {name}"))?;
1259            Ok(e.htod(&bf16_to_f32(bytes))?)
1260        };
1261        // Precision policy (MEMRA_DFLASH_PREC seam): "q8" = all q8_0 (1.6GB, default);
1262        // "mixed" = bf16 attn+fc (the ctx-conditioning path) + q8_0 ffn (~2.2GB — fits the
1263        // ~2.8GB headroom beside the 31B trunk); "bf16" = all bf16 (parity runs, no target).
1264        let prec = std::env::var("MEMRA_DFLASH_PREC").unwrap_or_else(|_| "q8".into());
1265        let upw = |name: &str| -> Result<GpuTensor, Box<dyn std::error::Error>> {
1266            let (info, bytes) = st
1267                .raw(name)
1268                .ok_or_else(|| format!("missing tensor {name}"))?;
1269            let shape = info.ne(); // ggml order: ne[0]=in_f, ne[1]=out_f
1270            let in_f = shape[0] as usize;
1271            let is_ffn = name.contains(".mlp.");
1272            let bf16 = prec == "bf16"
1273                || (prec == "mixed" && !is_ffn)
1274                || (prec == "fc" && name == "fc.weight");
1275            if bf16 {
1276                return Ok(GpuTensor::FloatBf16 {
1277                    data: e.upload_u8(bytes)?,
1278                    ne: shape.to_vec(),
1279                });
1280            }
1281            let f32s = bf16_to_f32(bytes);
1282            if prec == "q4" {
1283                let q = encode_q4_0(&f32s);
1284                return Ok(GpuTensor::Quant {
1285                    bytes: e.upload_u8(&q)?,
1286                    qtype: crate::QT_Q4_0,
1287                    row_bytes: in_f / 32 * 18,
1288                    ne: shape.to_vec(),
1289                    scale: 1.0,
1290                    rp: false,
1291                    #[cfg(memra_cutlass)]
1292                    cutlass: None,
1293                    fp8: None,
1294                    blk: None,
1295                    rp4: None,
1296                    f16: None,
1297                });
1298            }
1299            let q = encode_q8_0(&f32s);
1300            Ok(GpuTensor::Quant {
1301                bytes: e.upload_u8(&q)?,
1302                qtype: crate::QT_Q8_0,
1303                row_bytes: in_f / 32 * 34,
1304                ne: shape.to_vec(),
1305                scale: 1.0,
1306                rp: false,
1307                #[cfg(memra_cutlass)]
1308                cutlass: None,
1309                fp8: None,
1310                blk: None,
1311                rp4: None,
1312                f16: None,
1313            })
1314        };
1315        let mut layers = Vec::with_capacity(cfg.n_layer);
1316        for i in 0..cfg.n_layer {
1317            let p = |s: &str| format!("layers.{i}.{s}");
1318            layers.push(DflashLayer {
1319                wq: upw(&p("self_attn.q_proj.weight"))?,
1320                wk: upw(&p("self_attn.k_proj.weight"))?,
1321                wv: upw(&p("self_attn.v_proj.weight"))?,
1322                wo: upw(&p("self_attn.o_proj.weight"))?,
1323                w_gate: upw(&p("mlp.gate_proj.weight"))?,
1324                w_up: upw(&p("mlp.up_proj.weight"))?,
1325                w_down: upw(&p("mlp.down_proj.weight"))?,
1326                ln_in: up(&p("input_layernorm.weight"))?,
1327                ln_post: up(&p("post_attention_layernorm.weight"))?,
1328                q_norm: up(&p("self_attn.q_norm.weight"))?,
1329                k_norm: up(&p("self_attn.k_norm.weight"))?,
1330            });
1331        }
1332        let markov = if let Some((info, bytes)) = st.raw("markov_head.markov_w1.weight") {
1333            let sh = info.ne(); // [rank, vocab] in ggml order (safetensors [V, rank] reversed)
1334            let (rank, vocab) = (sh[0] as usize, sh[1] as usize);
1335            let (i2, b2) = st
1336                .raw("markov_head.markov_w2.weight")
1337                .ok_or("markov_w2 missing beside markov_w1")?;
1338            // w2 follows the precision seam: bf16 for parity runs (the q8_0 encode is a
1339            // serving-size choice and would put quant error inside the markov-logits gate),
1340            // q8_0 otherwise (acceptance-only impact, like the trunk weights).
1341            let w2 = if prec == "bf16" {
1342                GpuTensor::FloatBf16 {
1343                    data: e.upload_u8(b2)?,
1344                    ne: i2.ne().to_vec(),
1345                }
1346            } else {
1347                let w2f = bf16_to_f32(b2);
1348                let w2q = encode_q8_0(&w2f);
1349                GpuTensor::Quant {
1350                    bytes: e.upload_u8(&w2q)?,
1351                    qtype: crate::QT_Q8_0,
1352                    row_bytes: rank / 32 * 34,
1353                    ne: vec![rank as u64, vocab as u64],
1354                    scale: 1.0,
1355                    rp: false,
1356                    #[cfg(memra_cutlass)]
1357                    cutlass: None,
1358                    fp8: None,
1359                    blk: None,
1360                    rp4: None,
1361                    f16: None,
1362                }
1363            };
1364            Some(MarkovHead {
1365                w1_bf16: e.upload_u8(bytes)?,
1366                w2,
1367                rank,
1368                vocab,
1369            })
1370        } else {
1371            None
1372        };
1373        let confidence = if let Some((info, bytes)) = st.raw("confidence_head.proj.weight") {
1374            let sh = info.ne(); // ggml order: ne[0]=in_dim, ne[1]=1
1375            let in_dim = sh[0] as usize;
1376            let (_bi, bb) = st
1377                .raw("confidence_head.proj.bias")
1378                .ok_or("confidence bias missing beside weight")?;
1379            let with_markov = markov
1380                .as_ref()
1381                .map(|m| in_dim == cfg.hidden + m.rank)
1382                .unwrap_or(false);
1383            if !with_markov && in_dim != cfg.hidden {
1384                panic!(
1385                    "confidence_head in_dim {in_dim} matches neither hidden {} nor hidden+rank",
1386                    cfg.hidden
1387                );
1388            }
1389            Some(ConfidenceHead {
1390                w: bf16_to_f32(bytes),
1391                b: bf16_to_f32(bb)[0],
1392                in_dim,
1393                with_markov,
1394            })
1395        } else {
1396            None
1397        };
1398        // ---- DFlash2 family tensors (DFLASH2-EVAL-20260820.md §2): 10 conv modules
1399        // (base_kernel + kernel_projection around attention AND mlp in EVERY layer) +
1400        // the candidate path selector (hidden_projection + two codebooks). REQUIRED
1401        // when the arch says DFlash2DraftModel: a missing tensor is a refusal (`?`),
1402        // never a degraded program.
1403        let dflash2 = if is_dflash2 {
1404            assert!(
1405                markov.is_none() && confidence.is_none(),
1406                "DFlash2 checkpoint carries markov/confidence tensors — no such \
1407                 variant exists in the family (census refuses the ambiguity)"
1408            );
1409            let rank = g2("selector_rank");
1410            let top_k = g2("selector_top_k");
1411            let conv_k = g2("conv_kernel_size");
1412            let group_size = g2("conv_group_size");
1413            let groups = cfg.hidden / group_size;
1414            let load_conv = |name: &str| -> Result<Dflash2Conv, Box<dyn std::error::Error>> {
1415                let (bi, bb) = st
1416                    .raw(&format!("{name}.base_kernel"))
1417                    .ok_or_else(|| format!("DFlash2 census: missing {name}.base_kernel"))?;
1418                // safetensors [2, k, hidden] -> ggml ne reversed [hidden, k, 2]
1419                let bne = bi.ne();
1420                assert_eq!(
1421                    (bne[0] as usize, bne[1] as usize, bne[2] as usize),
1422                    (cfg.hidden, conv_k, 2),
1423                    "{name}.base_kernel shape != [2, conv_kernel_size, hidden]"
1424                );
1425                let pname = format!("{name}.kernel_projection.weight");
1426                let (pi, _pb) = st
1427                    .raw(&pname)
1428                    .ok_or_else(|| format!("DFlash2 census: missing {pname}"))?;
1429                let pne = pi.ne(); // ggml: [in_f=hidden, out_f=2*k*groups]
1430                assert_eq!(
1431                    (pne[0] as usize, pne[1] as usize),
1432                    (cfg.hidden, 2 * conv_k * groups),
1433                    "{pname} shape != [2*conv_kernel_size*groups, hidden]"
1434                );
1435                Ok(Dflash2Conv {
1436                    base: e.htod(&bf16_to_f32(bb))?,
1437                    proj: upw(&pname)?,
1438                })
1439            };
1440            let mut attn_conv = Vec::with_capacity(cfg.n_layer);
1441            let mut mlp_conv = Vec::with_capacity(cfg.n_layer);
1442            for i in 0..cfg.n_layer {
1443                attn_conv.push(load_conv(&format!("layers.{i}.attention_conv"))?);
1444                mlp_conv.push(load_conv(&format!("layers.{i}.mlp_conv"))?);
1445            }
1446            // Codebooks: stored WITHOUT `.weight` (checkpoint quirk; reference
1447            // from_pretrained maps the keys). Host-resident raw bf16.
1448            let cb = |name: &str| -> Result<(Vec<u8>, usize), Box<dyn std::error::Error>> {
1449                let (ci, cbytes) = st
1450                    .raw(&format!("candidate_selector.{name}"))
1451                    .ok_or_else(|| format!("DFlash2 census: missing candidate_selector.{name}"))?;
1452                let ne = ci.ne(); // ggml: [rank, V]
1453                assert_eq!(ne[0] as usize, rank, "candidate_selector.{name} rank");
1454                Ok((cbytes.to_vec(), ne[1] as usize))
1455            };
1456            let (pred_codebook, v1) = cb("predecessor_codebook")?;
1457            let (succ_codebook, v2) = cb("successor_codebook")?;
1458            assert_eq!(v1, v2, "codebook vocab mismatch");
1459            let hp_name = "candidate_selector.hidden_projection.weight";
1460            let (hi, _hb) = st
1461                .raw(hp_name)
1462                .ok_or_else(|| format!("DFlash2 census: missing {hp_name}"))?;
1463            assert_eq!(
1464                (hi.ne()[0] as usize, hi.ne()[1] as usize),
1465                (cfg.hidden, rank),
1466                "{hp_name} shape != [rank, hidden]"
1467            );
1468            Some(Dflash2Head {
1469                attn_conv,
1470                mlp_conv,
1471                hidden_proj: upw(hp_name)?,
1472                pred_codebook,
1473                succ_codebook,
1474                rank,
1475                top_k,
1476                conv_k,
1477                group_size,
1478                vocab: v1,
1479            })
1480        } else {
1481            None
1482        };
1483        // CENSUS GATE: every tensor in the export must be consumed by the map above.
1484        // DSpark-class checkpoints (markov head present) and DFlash2 checkpoints
1485        // REFUSE on unrecognized names — an unmapped tensor is a semantic program we
1486        // would silently drop (house law). Plain dflash checkpoints keep the
1487        // historical warn-only behavior.
1488        {
1489            let mut consumed: std::collections::HashSet<String> = std::collections::HashSet::new();
1490            for i in 0..cfg.n_layer {
1491                for s in [
1492                    "self_attn.q_proj.weight",
1493                    "self_attn.k_proj.weight",
1494                    "self_attn.v_proj.weight",
1495                    "self_attn.o_proj.weight",
1496                    "self_attn.q_norm.weight",
1497                    "self_attn.k_norm.weight",
1498                    "input_layernorm.weight",
1499                    "post_attention_layernorm.weight",
1500                    "mlp.gate_proj.weight",
1501                    "mlp.up_proj.weight",
1502                    "mlp.down_proj.weight",
1503                ] {
1504                    consumed.insert(format!("layers.{i}.{s}"));
1505                }
1506                if dflash2.is_some() {
1507                    for s in [
1508                        "attention_conv.base_kernel",
1509                        "attention_conv.kernel_projection.weight",
1510                        "mlp_conv.base_kernel",
1511                        "mlp_conv.kernel_projection.weight",
1512                    ] {
1513                        consumed.insert(format!("layers.{i}.{s}"));
1514                    }
1515                }
1516            }
1517            for s in [
1518                "fc.weight",
1519                "hidden_norm.weight",
1520                "norm.weight",
1521                "markov_head.markov_w1.weight",
1522                "markov_head.markov_w2.weight",
1523                "confidence_head.proj.weight",
1524                "confidence_head.proj.bias",
1525            ] {
1526                consumed.insert(s.into());
1527            }
1528            if dflash2.is_some() {
1529                for s in [
1530                    "candidate_selector.hidden_projection.weight",
1531                    "candidate_selector.predecessor_codebook",
1532                    "candidate_selector.successor_codebook",
1533                ] {
1534                    consumed.insert(s.into());
1535                }
1536            }
1537            let leftovers: Vec<&String> = st.names().filter(|n| !consumed.contains(*n)).collect();
1538            if !leftovers.is_empty() {
1539                if markov.is_some() || dflash2.is_some() {
1540                    panic!("dspark/dflash2 census: unrecognized tensors {leftovers:?}");
1541                }
1542                eprintln!("[dflash census] unmapped tensors (ignored): {leftovers:?}");
1543            }
1544        }
1545        // YaRN rope from config rope_parameters (HF _compute_yarn_parameters, verified
1546        // numerically vs Qwen3RotaryEmbedding on the arm-a export).
1547        let rope_yarn =
1548            if txt.contains("\"rope_type\": \"yarn\"") || txt.contains("\"rope_type\":\"yarn\"") {
1549                let factor = num(&txt, "factor").expect("yarn factor") as f64;
1550                let orig = num(&txt, "original_max_position_embeddings").expect("yarn orig");
1551                let beta_fast = num(&txt, "beta_fast").expect("beta_fast");
1552                let beta_slow = num(&txt, "beta_slow").expect("beta_slow");
1553                let base = cfg.rope_theta as f64;
1554                let d = cfg.head_dim as f64;
1555                let corr =
1556                    |r: f64| d * (orig / (r * 2.0 * std::f64::consts::PI)).ln() / (2.0 * base.ln());
1557                let low = corr(beta_fast).floor().max(0.0);
1558                let high = corr(beta_slow).ceil().min(d - 1.0);
1559                let half = cfg.head_dim / 2;
1560                let mut ff = Vec::with_capacity(half);
1561                for j in 0..half {
1562                    let base_inv = base.powf(-2.0 * j as f64 / d);
1563                    let ramp = (((j as f64) - low) / (high - low)).clamp(0.0, 1.0);
1564                    let ex = 1.0 - ramp; // extrapolation share
1565                    let yarn_inv = (base_inv / factor) * (1.0 - ex) + base_inv * ex;
1566                    ff.push((base_inv / yarn_inv) as f32);
1567                }
1568                let mscale = (0.1 * factor.ln() + 1.0) as f32;
1569                Some((e.htod(&ff)?, mscale))
1570            } else {
1571                None
1572            };
1573        // Ratified-default receipts (capacity-keyed-defaults law: the active program is
1574        // NAMED at load, never inferred from silence). The boot output-sample gate greps
1575        // these two lines; a run whose log lacks them did not load this code.
1576        eprintln!(
1577            "[dspark] harvest={} (checkpoint census dflash2={} strategy_dspark={}, \
1578             MEMRA_DSPARK_HARVEST {})",
1579            DsparkHarvest::for_family_value(
1580                dflash2.is_some(),
1581                std::env::var("MEMRA_DSPARK_HARVEST").ok().as_deref(),
1582                cfg.strategy_dspark,
1583            )
1584            .name(),
1585            dflash2.is_some(),
1586            cfg.strategy_dspark,
1587            match std::env::var("MEMRA_DSPARK_HARVEST") {
1588                Ok(v) if !v.is_empty() => "set",
1589                _ => "unset",
1590            },
1591        );
1592        eprintln!(
1593            "[dspark] verify-window={:?} (accept-rate head {}, MEMRA_DSPARK_VT {})",
1594            DsparkVtPolicy::resolve(confidence.is_some()),
1595            if confidence.is_some() {
1596                "present"
1597            } else {
1598                "ABSENT -> ladder"
1599            },
1600            match std::env::var("MEMRA_DSPARK_VT") {
1601                Ok(v) if !v.is_empty() => "set",
1602                _ => "unset",
1603            },
1604        );
1605        Ok(Self {
1606            fc: upw("fc.weight")?,
1607            hidden_norm: up("hidden_norm.weight")?,
1608            norm: up("norm.weight")?,
1609            cfg,
1610            layers,
1611            markov,
1612            confidence,
1613            rope_yarn,
1614            dflash2,
1615        })
1616    }
1617
1618    /// Rope q or k rows in place: yarn (ff divisors + post-rope mscale) when the config
1619    /// carries it, plain neox otherwise. One primitive for all five drafter rope sites.
1620    fn rope_rows(
1621        &self,
1622        e: &Engine,
1623        x: &mut CudaSlice<f32>,
1624        pos_d: &CudaSlice<i32>,
1625        n_heads: usize,
1626        n_tokens: usize,
1627    ) -> Result<(), Box<dyn std::error::Error>> {
1628        let c = &self.cfg;
1629        match &self.rope_yarn {
1630            Some((ff, mscale)) => {
1631                e.rope_neox_ff(
1632                    x,
1633                    pos_d,
1634                    c.head_dim,
1635                    c.head_dim,
1636                    n_heads,
1637                    n_tokens,
1638                    c.rope_theta,
1639                    1.0,
1640                    ff,
1641                )?;
1642                e.scale_inplace(x, *mscale, n_tokens * n_heads * c.head_dim)?;
1643            }
1644            None => {
1645                e.rope_neox(
1646                    x,
1647                    pos_d,
1648                    c.head_dim,
1649                    c.head_dim,
1650                    n_heads,
1651                    n_tokens,
1652                    c.rope_theta,
1653                    1.0,
1654                )?;
1655            }
1656        }
1657        Ok(())
1658    }
1659
1660    /// f32 GEMM helper via the engine Float arm (cuBLASLt): y[t, out_f].
1661    fn mm(
1662        &self,
1663        e: &Engine,
1664        w: &GpuTensor,
1665        x: &CudaSlice<f32>,
1666        t: usize,
1667        _in_f: usize,
1668        _out_f: usize,
1669    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1670        Ok(e.matmul(w, x, t)?)
1671    }
1672
1673    /// DFlash2 conv `prepare` (reference GroupedDynamicCausalConv.prepare): projects
1674    /// the pre-conv rows to BOTH dynamic kernels, convolves the rows with base half 0
1675    /// + dyn half 0, and returns (convolved rows, the dyn projection) — `finish`
1676    /// reuses the SAME projection's half 1. Block-local causal shift (row 0 zero-pads).
1677    pub fn d2_conv_prepare(
1678        &self,
1679        e: &Engine,
1680        conv: &Dflash2Conv,
1681        xn: &CudaSlice<f32>,
1682        rows: usize,
1683    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
1684        let d2 = self
1685            .dflash2
1686            .as_ref()
1687            .expect("d2_conv on a non-dflash2 draft");
1688        let h = self.cfg.hidden;
1689        let groups = h / d2.group_size;
1690        let dyn_ = self.mm(e, &conv.proj, xn, rows, h, 2 * d2.conv_k * groups)?;
1691        let mut out = e.uninit(rows * h)?;
1692        e.dflash2_dynconv(
1693            xn,
1694            &dyn_,
1695            &conv.base,
1696            &mut out,
1697            rows,
1698            h,
1699            d2.group_size,
1700            d2.conv_k,
1701            0,
1702        )?;
1703        Ok((out, dyn_))
1704    }
1705
1706    /// DFlash2 conv `finish`: convolves the sublayer OUTPUT rows with base half 1 +
1707    /// dyn half 1 (dyn from the matching `prepare`).
1708    pub fn d2_conv_finish(
1709        &self,
1710        e: &Engine,
1711        conv: &Dflash2Conv,
1712        y: &CudaSlice<f32>,
1713        dyn_: &CudaSlice<f32>,
1714        rows: usize,
1715    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1716        let d2 = self
1717            .dflash2
1718            .as_ref()
1719            .expect("d2_conv on a non-dflash2 draft");
1720        let h = self.cfg.hidden;
1721        let mut out = e.uninit(rows * h)?;
1722        e.dflash2_dynconv(
1723            y,
1724            dyn_,
1725            &conv.base,
1726            &mut out,
1727            rows,
1728            h,
1729            d2.group_size,
1730            d2.conv_k,
1731            1,
1732        )?;
1733        Ok(out)
1734    }
1735
1736    /// DFlash2 proposal (reference `DFlash2DraftModel.propose`, greedy arm): device
1737    /// top-k over the draft logits + the rank-`r` hidden projection, ONE small dtoh
1738    /// (~nd*(2k+rank) floats — the same per-round sync slot the markov chain's token
1739    /// readback occupies), then the host codebook walk. Returns the nd drafted tokens
1740    /// (mask-fill rows 1..b-1; the anchor row is not a draft).
1741    pub fn dflash2_propose_greedy(
1742        &self,
1743        e: &Engine,
1744        dl: &CudaSlice<f32>,
1745        rows: &CudaSlice<f32>,
1746        nd: usize,
1747        n_vocab: usize,
1748        anchor: u32,
1749    ) -> Result<Vec<u32>, Box<dyn std::error::Error>> {
1750        let d2 = self
1751            .dflash2
1752            .as_ref()
1753            .expect("dflash2_propose on a non-dflash2 draft");
1754        assert!(
1755            n_vocab <= d2.vocab,
1756            "target head vocab {n_vocab} exceeds the selector codebooks ({})",
1757            d2.vocab
1758        );
1759        let (vals_d, idx_d) = e.topk_rows(dl, nd, n_vocab, d2.top_k)?;
1760        let hproj_d = e.matmul(&d2.hidden_proj, rows, nd)?;
1761        let unary = e.dtoh(&vals_d)?;
1762        let cand = e.dtoh_u32(&idx_d)?;
1763        let hproj = e.dtoh(&hproj_d)?;
1764        Ok(d2.walk_greedy(&unary, &cand, &hproj, anchor, nd))
1765    }
1766
1767    /// DFlash2 proposal, SAMPLED arm (reference `DFlash2DraftModel.propose` at T>0): same
1768    /// device top-k + hidden projection + one dtoh as the greedy arm, then the host
1769    /// candidate-set softmax walk (`dflash2_walk_sampled`) drawing one host-Philox uniform
1770    /// per slot from the session's `uctr` stream. Returns (path, q_chosen, cand, q_rows).
1771    #[allow(clippy::too_many_arguments)]
1772    pub(crate) fn dflash2_propose_sampled(
1773        &self,
1774        e: &Engine,
1775        dl: &CudaSlice<f32>,
1776        rows: &CudaSlice<f32>,
1777        nd: usize,
1778        n_vocab: usize,
1779        anchor: u32,
1780        temp: f32,
1781        seed: u64,
1782        uctr: &mut u32,
1783    ) -> Result<Dflash2SampledProposal, Box<dyn std::error::Error>> {
1784        let d2 = self
1785            .dflash2
1786            .as_ref()
1787            .expect("dflash2_propose on a non-dflash2 draft");
1788        assert!(
1789            n_vocab <= d2.vocab,
1790            "target head vocab {n_vocab} exceeds the selector codebooks ({})",
1791            d2.vocab
1792        );
1793        let (vals_d, idx_d) = e.topk_rows(dl, nd, n_vocab, d2.top_k)?;
1794        let hproj_d = e.matmul(&d2.hidden_proj, rows, nd)?;
1795        let unary = e.dtoh(&vals_d)?;
1796        let cand = e.dtoh_u32(&idx_d)?;
1797        let hproj = e.dtoh(&hproj_d)?;
1798        let mut draw = || {
1799            let u = crate::spec::host_u01(seed, *uctr);
1800            *uctr = uctr.wrapping_add(1);
1801            u
1802        };
1803        let (path, q_chosen, q_rows) =
1804            d2.walk_sampled(&unary, &cand, &hproj, anchor, nd, temp, &mut draw);
1805        Ok((path, q_chosen, cand, q_rows))
1806    }
1807
1808    /// Sampled draft chain for the Rows families (T>0 twin of the greedy markov chain):
1809    /// slot k gets the markov bias of the PREVIOUS chain token added in place (when the
1810    /// head is armed — the sglang DSPARK worker's markov-corrected draft probs), then ONE
1811    /// draw from the row's FILTERED softmax (filter_stats -> device-stat gumbel perturb ->
1812    /// argmax into the chain buffer — the frspec eager-chain composition, stats kept on
1813    /// device so the chain stays sync-free like the greedy arm). Without a markov head the
1814    /// rows sample independently (the z-lab reference's T>0 arm for plain DFlash). `dl` is
1815    /// biased IN PLACE and retained by the caller: it is the accept walk's q source.
1816    #[allow(clippy::too_many_arguments)]
1817    pub(crate) fn dspark_chain_sampled(
1818        &self,
1819        e: &Engine,
1820        dl: &mut CudaSlice<f32>,
1821        nd: usize,
1822        n_vocab: usize,
1823        anchor: u32,
1824        sp: &crate::spec::SpecSampling,
1825        sctr: &mut u32,
1826        // H4 confidence-policy stash (v0.100 train merge): Some = copy each slot's
1827        // markov prev-token embedding (the exact `w1` row the chain gathers) into a
1828        // [nd, rank] buffer — the same d2d stash the greedy chain carries, so the
1829        // confidence window sizes identically at T>0.
1830        mut conf_emb: Option<&mut CudaSlice<f32>>,
1831    ) -> Result<(Vec<u32>, DsparkDraftSample), Box<dyn std::error::Error>> {
1832        let markov_on = std::env::var("MEMRA_DFLASH_MARKOV").as_deref() != Ok("0");
1833        let mut chain_d = e.stream().alloc_zeros::<u32>(nd + 1)?;
1834        e.set_u32_one(&mut chain_d, anchor)?;
1835        let mut th_all = e.zeros(nd)?;
1836        let mut z_all = e.zeros(nd)?;
1837        let mut mx_all = e.zeros(nd)?;
1838        let mut pb = e.zeros(n_vocab)?;
1839        for k in 0..nd {
1840            if let (Some(mk), true) = (&self.markov, markov_on) {
1841                let mut f = e.uninit(mk.rank)?;
1842                e.gather_row_bf16(&mk.w1_bf16, &chain_d, k, &mut f, mk.rank)?;
1843                if let Some(ce) = conf_emb.as_deref_mut() {
1844                    let fv = e.view(&f, mk.rank);
1845                    e.copy_view_into(ce, k * mk.rank, &fv, mk.rank)?;
1846                }
1847                let bias = e.matmul(&mk.w2, &f, 1)?;
1848                e.add_row_inplace(dl, &bias, n_vocab, k * n_vocab)?;
1849            } else if let (Some(ce), Some(mk)) = (conf_emb.as_deref_mut(), &self.markov) {
1850                // MARKOV=0 arm still stashes the embedding for the confidence head —
1851                // the greedy chain's exact behavior.
1852                let mut f = e.uninit(mk.rank)?;
1853                e.gather_row_bf16(&mk.w1_bf16, &chain_d, k, &mut f, mk.rank)?;
1854                let fv = e.view(&f, mk.rank);
1855                e.copy_view_into(ce, k * mk.rank, &fv, mk.rank)?;
1856            }
1857            let rows_k = e.htod_i32(&[k as i32])?;
1858            let (mut th1, mut z1, mut mx1) = (e.zeros(1)?, e.zeros(1)?, e.zeros(1)?);
1859            e.filter_stats(
1860                dl, n_vocab, &rows_k, &mut th1, &mut z1, &mut mx1, n_vocab, 1, sp.temp, sp.top_k,
1861                sp.top_p, sp.min_p,
1862            )?;
1863            e.gumbel_perturb_filtered_col(
1864                dl, k, &mut pb, n_vocab, sp.seed, *sctr, sp.temp, &mx1, &th1, 0,
1865            )?;
1866            *sctr = sctr.wrapping_add(1);
1867            e.argmax_token_device_col(&pb, 0, n_vocab, &mut chain_d, k + 1)?;
1868            e.copy_into(&mut th_all, k, &th1, 1)?;
1869            e.copy_into(&mut z_all, k, &z1, 1)?;
1870            e.copy_into(&mut mx_all, k, &mx1, 1)?;
1871        }
1872        let chain = e.dtoh_u32(&chain_d)?;
1873        let (thv, zv, mxv) = (e.dtoh(&th_all)?, e.dtoh(&z_all)?, e.dtoh(&mx_all)?);
1874        let stats = (0..nd).map(|i| (mxv[i], thv[i], zv[i])).collect();
1875        Ok((
1876            chain[1..].to_vec(),
1877            DsparkDraftSample::Rows {
1878                th: th_all,
1879                z: z_all,
1880                stats,
1881            },
1882        ))
1883    }
1884
1885    /// Family dispatch for the sampled proposal: Selector for DFlash2, Rows otherwise.
1886    /// Returns the drafted tokens (the round's `cand` tail) + the proposal record.
1887    #[allow(clippy::too_many_arguments)]
1888    pub(crate) fn dspark_propose_sampled(
1889        &self,
1890        e: &Engine,
1891        dl: &mut CudaSlice<f32>,
1892        rows: &CudaSlice<f32>,
1893        nd: usize,
1894        n_vocab: usize,
1895        anchor: u32,
1896        sp: &crate::spec::SpecSampling,
1897        sctr: &mut u32,
1898        uctr: &mut u32,
1899        conf_emb: Option<&mut CudaSlice<f32>>,
1900    ) -> Result<(Vec<u32>, DsparkDraftSample), Box<dyn std::error::Error>> {
1901        if let Some(d2) = self.dflash2.as_ref() {
1902            // The confidence stash is a markov-family program; DFlash2 has no
1903            // accept-rate head (the policy resolver never arms it for this family).
1904            debug_assert!(
1905                conf_emb.is_none(),
1906                "conf_emb stash requested on a DFlash2 selector proposal"
1907            );
1908            let (path, q_chosen, cand, q_rows) = self.dflash2_propose_sampled(
1909                e, dl, rows, nd, n_vocab, anchor, sp.temp, sp.seed, uctr,
1910            )?;
1911            Ok((
1912                path,
1913                DsparkDraftSample::Selector {
1914                    cand,
1915                    q_rows,
1916                    q_chosen,
1917                    top_k: d2.top_k,
1918                },
1919            ))
1920        } else {
1921            self.dspark_chain_sampled(e, dl, nd, n_vocab, anchor, sp, sctr, conf_emb)
1922        }
1923    }
1924
1925    /// FIRST-LIGHT forward (oracle contract): full non-causal attention over
1926    /// [ctx_features ; block], NO draft KV cache, NO sliding window (the oracle bypasses
1927    /// the reference mask machinery the same way — window/caching land in the round arm).
1928    ///
1929    /// `target_hidden`: [ctx, n_taps*hidden] (f32, device)  — raw tapped states.
1930    /// `noise_emb`:     [block, hidden] — target embed rows for [accepted, MASK x b-1].
1931    /// `pos`:           absolute positions for ctx rows THEN block rows (ctx+block i32).
1932    /// Returns final normed hidden [block, hidden] (feed target lm_head for draft logits).
1933    /// ctx features for `t` tapped rows: hidden_norm(fc(taps)) — the drafter's context
1934    /// representation, cacheable across rounds (append-only in committed-token order).
1935    pub fn ctx_features(
1936        &self,
1937        e: &Engine,
1938        taps: &CudaSlice<f32>,
1939        t: usize,
1940    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1941        let c = &self.cfg;
1942        let n_taps = c.target_layer_ids.len();
1943        let fc_out = self.mm(e, &self.fc, taps, t, n_taps * c.hidden, c.hidden)?;
1944        let mut out = e.uninit(t * c.hidden)?;
1945        e.rms_norm(&fc_out, &self.hidden_norm, &mut out, c.hidden, t, c.eps)?;
1946        Ok(out)
1947    }
1948
1949    pub fn forward(
1950        &self,
1951        e: &Engine,
1952        target_hidden: &CudaSlice<f32>,
1953        noise_emb: &CudaSlice<f32>,
1954        pos: &[i32],
1955        ctx: usize,
1956    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1957        let ctx_f = self.ctx_features(e, target_hidden, ctx)?;
1958        if let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP") {
1959            let v = e.dtoh(&ctx_f)?;
1960            let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
1961            std::fs::write(format!("{dir}/memra-ctx_features.f32"), bytes)?;
1962        }
1963        self.forward_block(e, &ctx_f, noise_emb, pos, ctx)
1964    }
1965
1966    /// Block forward over PRECOMPUTED ctx features (the round arm's entry: features are
1967    /// cached across rounds; only the block work repeats).
1968    pub fn forward_block(
1969        &self,
1970        e: &Engine,
1971        ctx_f: &CudaSlice<f32>,
1972        noise_emb: &CudaSlice<f32>,
1973        pos: &[i32],
1974        ctx: usize,
1975    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1976        let c = &self.cfg;
1977        let (h, nh, nkv, hd) = (c.hidden, c.n_head, c.n_kv, c.head_dim);
1978        let b = c.block_size;
1979        assert_eq!(pos.len(), ctx + b, "pos covers ctx rows then block rows");
1980
1981        let pos_blk = e.htod_i32(&pos[ctx..])?;
1982
1983        let mut x = e.clone_dtod(noise_emb)?; // [b, hidden] residual stream
1984        for (li, l) in self.layers.iter().enumerate() {
1985            // input_layernorm on the block rows only (ctx features are norm-free per ref:
1986            // k/v project the SAME ctx_f every layer, un-layernormed).
1987            let mut xn = e.uninit(b * h)?;
1988            e.rms_norm(&x, &l.ln_in, &mut xn, h, b, c.eps)?;
1989            // DFlash2: dynamic conv WRAPS attention — q/k_noise/v_noise all project the
1990            // CONVOLVED block rows (reference decoder layer: prepare -> self_attn ->
1991            // finish, all inside the residual branch). ctx_f is never convolved.
1992            let mut attn_dyn: Option<CudaSlice<f32>> = None;
1993            if let Some(d2) = &self.dflash2 {
1994                let (xc, dyn_) = self.d2_conv_prepare(e, &d2.attn_conv[li], &xn, b)?;
1995                xn = xc;
1996                attn_dyn = Some(dyn_);
1997            }
1998
1999            // q from block; k/v from [ctx_f ; block-normed]
2000            let q0 = self.mm(e, &l.wq, &xn, b, h, nh * hd)?;
2001            let k0c = self.mm(e, &l.wk, ctx_f, ctx, h, nkv * hd)?;
2002            let v0c = self.mm(e, &l.wv, ctx_f, ctx, h, nkv * hd)?;
2003            let k0b = self.mm(e, &l.wk, &xn, b, h, nkv * hd)?;
2004            let v0b = self.mm(e, &l.wv, &xn, b, h, nkv * hd)?;
2005
2006            // per-head q/k rms norm (v passes through: ones weight trick not needed — the
2007            // qkv kernel norms rq+rk rows; concatenate k first).
2008            let mut k0 = e.uninit((ctx + b) * nkv * hd)?;
2009            e.copy_into(&mut k0, 0, &k0c, ctx * nkv * hd)?;
2010            e.copy_into(&mut k0, ctx * nkv * hd, &k0b, b * nkv * hd)?;
2011            let mut v = e.uninit((ctx + b) * nkv * hd)?;
2012            e.copy_into(&mut v, 0, &v0c, ctx * nkv * hd)?;
2013            e.copy_into(&mut v, ctx * nkv * hd, &v0b, b * nkv * hd)?;
2014
2015            if li == 0 {
2016                if let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP") {
2017                    let v = e.dtoh(&q0)?;
2018                    let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
2019                    std::fs::write(format!("{dir}/memra-l0_q0.f32"), bytes)?;
2020                }
2021            }
2022            let mut q = e.uninit(b * nh * hd)?;
2023            let mut k = e.uninit((ctx + b) * nkv * hd)?;
2024            // rms over head_dim rows: q has b*nh rows, k has (ctx+b)*nkv rows.
2025            e.rms_norm(&q0, &l.q_norm, &mut q, hd, b * nh, c.eps)?;
2026            if li == 0 {
2027                if let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP") {
2028                    let v = e.dtoh(&q)?;
2029                    let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
2030                    std::fs::write(format!("{dir}/memra-l0_qn.f32"), bytes)?;
2031                }
2032            }
2033            e.rms_norm(&k0, &l.k_norm, &mut k, hd, (ctx + b) * nkv, c.eps)?;
2034
2035            // rope: q at block positions, k at ctx-then-block positions (absolute).
2036            let norope = std::env::var("MEMRA_DFLASH_NOROPE").is_ok();
2037            if !norope {
2038                self.rope_rows(e, &mut q, &pos_blk, nh, b)?;
2039            }
2040            if li == 0 {
2041                if let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP") {
2042                    let dump = |name: &str,
2043                                t: &cudarc::driver::CudaSlice<f32>|
2044                     -> Result<(), Box<dyn std::error::Error>> {
2045                        let v = e.dtoh(t)?;
2046                        let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
2047                        std::fs::write(format!("{dir}/memra-l0_{name}.f32"), bytes)?;
2048                        Ok(())
2049                    };
2050                    dump("xn", &xn)?;
2051                    dump("q_prerope", &q)?;
2052                }
2053            }
2054            // k rows are laid out [row, nkv, hd] with row-major tokens — rope_neox expects
2055            // (n_heads, n_tokens); ctx and block ropes run as one call over ctx+b tokens.
2056            let pos_all = e.htod_i32(pos)?;
2057            if !norope {
2058                self.rope_rows(e, &mut k, &pos_all, nkv, ctx + b)?;
2059            }
2060
2061            // full non-causal attention: every block query sees all ctx+b keys.
2062            let mut attn = e.uninit(b * nh * hd)?;
2063            let scale = 1.0f32 / (hd as f32).sqrt();
2064            // NAIVE SDPA for first light: fa_prefill's NON-CAUSAL arm with T != T_kv is
2065            // BROKEN (attn maxdiff 0.34 vs the torch oracle; q/k inputs bit-close — no
2066            // existing caller exercises that shape class, jsonl 2026-07-13). The 16 x
2067            // (ctx+16) block attention is tiny; the fa arm returns behind this seam once
2068            // its kernel is fixed + parity-gated.
2069            if self.dflash2.is_some() && c.layer_sliding[li] {
2070                // DFlash2 non-causal symmetric window (config is_causal=false, all
2071                // layers sliding). The kernel masks only keys OLDER than
2072                // q_pos-(window-1); the future side (k - q < window) never binds
2073                // because keys reach at most q_pos + block <= q_pos + window
2074                // (asserted at load). Positions must be contiguous — q_pos is derived
2075                // in-kernel as (T_kv - T) + qt.
2076                debug_assert!(pos.windows(2).all(|w| w[1] == w[0] + 1));
2077                d2_windowed_attn(e, &q, &k, &v, &mut attn, hd, nh, nkv, b, ctx + b, scale, c)?;
2078            } else if std::env::var("MEMRA_DFLASH_FA").is_ok() {
2079                e.fa_prefill(&q, &k, &v, &mut attn, hd, nh, nkv, b, ctx + b, scale, false)?;
2080            } else {
2081                e.sdpa_naive(&q, &k, &v, &mut attn, hd, nh, nkv, b, ctx + b, scale, false)?;
2082            }
2083
2084            let mut o = self.mm(e, &l.wo, &attn, b, nh * hd, h)?;
2085            if let (Some(d2), Some(dyn_)) = (&self.dflash2, &attn_dyn) {
2086                o = self.d2_conv_finish(e, &d2.attn_conv[li], &o, dyn_, b)?;
2087            }
2088            let mut x1 = e.uninit(b * h)?;
2089            e.add(&o, &x, &mut x1, b * h)?;
2090            if li == 0 {
2091                if let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP") {
2092                    let dump = |name: &str,
2093                                t: &cudarc::driver::CudaSlice<f32>|
2094                     -> Result<(), Box<dyn std::error::Error>> {
2095                        let v = e.dtoh(t)?;
2096                        let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
2097                        std::fs::write(format!("{dir}/memra-l0_{name}.f32"), bytes)?;
2098                        Ok(())
2099                    };
2100                    dump("q", &q)?;
2101                    dump("k", &k)?;
2102                    dump("attn", &attn)?;
2103                    dump("x1", &x1)?;
2104                }
2105            }
2106
2107            // mlp (DFlash2: the same conv wrap — prepare on the post-ln rows, mlp on
2108            // the convolved rows, finish on the mlp output, then the residual add)
2109            let mut x1n = e.uninit(b * h)?;
2110            e.rms_norm(&x1, &l.ln_post, &mut x1n, h, b, c.eps)?;
2111            let mut mlp_dyn: Option<CudaSlice<f32>> = None;
2112            if let Some(d2) = &self.dflash2 {
2113                let (xc, dyn_) = self.d2_conv_prepare(e, &d2.mlp_conv[li], &x1n, b)?;
2114                x1n = xc;
2115                mlp_dyn = Some(dyn_);
2116            }
2117            let gate = self.mm(e, &l.w_gate, &x1n, b, h, c.n_ff)?;
2118            let up_ = self.mm(e, &l.w_up, &x1n, b, h, c.n_ff)?;
2119            let mut act = e.uninit(b * c.n_ff)?;
2120            e.silu_mul(&gate, &up_, &mut act, b * c.n_ff)?;
2121            let mut down = self.mm(e, &l.w_down, &act, b, c.n_ff, h)?;
2122            if let (Some(d2), Some(dyn_)) = (&self.dflash2, &mlp_dyn) {
2123                down = self.d2_conv_finish(e, &d2.mlp_conv[li], &down, dyn_, b)?;
2124            }
2125            let mut x2 = e.uninit(b * h)?;
2126            e.add(&down, &x1, &mut x2, b * h)?;
2127            x = x2;
2128            if let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP") {
2129                let v = e.dtoh(&x)?;
2130                let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
2131                std::fs::write(format!("{dir}/memra-layer{li}_out.f32"), bytes)?;
2132            }
2133        }
2134        let mut out = e.uninit(b * h)?;
2135        e.rms_norm(&x, &self.norm, &mut out, h, b, c.eps)?;
2136        Ok(out)
2137    }
2138}
2139
2140/// Draft KV cache (round-cost fix, 2026-07-13): per-layer normed+roped ctx K and raw ctx V,
2141/// append-only in committed order. Block K/V land TRANSIENTLY at [len..len+b] each round
2142/// (never committed — the reference crops them identically). Kills the per-round full-ctx
2143/// projection recompute (first light was O(ctx)/round -> 7 tok/s).
2144pub struct DflashKv {
2145    pub k: Vec<CudaSlice<f32>>, // per layer [cap + block, nkv*hd]
2146    pub v: Vec<CudaSlice<f32>>,
2147    pub len: usize,
2148    pub cap: usize,
2149}
2150
2151impl DflashKv {
2152    pub fn new(
2153        e: &Engine,
2154        cfg: &DflashCfg,
2155        cap: usize,
2156    ) -> Result<Self, Box<dyn std::error::Error>> {
2157        let rowsz = cfg.n_kv * cfg.head_dim;
2158        let mut k = Vec::with_capacity(cfg.n_layer);
2159        let mut v = Vec::with_capacity(cfg.n_layer);
2160        for _ in 0..cfg.n_layer {
2161            k.push(e.uninit((cap + cfg.block_size) * rowsz)?);
2162            v.push(e.uninit((cap + cfg.block_size) * rowsz)?);
2163        }
2164        Ok(Self { k, v, len: 0, cap })
2165    }
2166}
2167
2168impl DflashDraft {
2169    /// Ingest `t` NEW ctx-feature rows (committed order, absolute positions `pos_new`) into
2170    /// the draft KV: per layer k/v projections + k head-norm + rope, appended at kv.len.
2171    pub fn ingest_ctx(
2172        &self,
2173        e: &Engine,
2174        kv: &mut DflashKv,
2175        feats: &CudaSlice<f32>,
2176        pos_new: &[i32],
2177        t: usize,
2178    ) -> Result<(), Box<dyn std::error::Error>> {
2179        let c = &self.cfg;
2180        let (h, nkv, hd) = (c.hidden, c.n_kv, c.head_dim);
2181        assert!(kv.len + t <= kv.cap, "draft kv overflow");
2182        let pos_d = e.htod_i32(pos_new)?;
2183        for (li, l) in self.layers.iter().enumerate() {
2184            let k0 = self.mm(e, &l.wk, feats, t, h, nkv * hd)?;
2185            let v0 = self.mm(e, &l.wv, feats, t, h, nkv * hd)?;
2186            let mut kn = e.uninit(t * nkv * hd)?;
2187            e.rms_norm(&k0, &l.k_norm, &mut kn, hd, t * nkv, c.eps)?;
2188            self.rope_rows(e, &mut kn, &pos_d, nkv, t)?;
2189            e.copy_into(&mut kv.k[li], kv.len * nkv * hd, &kn, t * nkv * hd)?;
2190            e.copy_into(&mut kv.v[li], kv.len * nkv * hd, &v0, t * nkv * hd)?;
2191        }
2192        kv.len += t;
2193        Ok(())
2194    }
2195
2196    /// Block forward over the CACHED ctx KV: only the 16 block rows are projected per layer;
2197    /// block K/V land transiently at kv[len..len+b]. Bit-class-identical to forward_block
2198    /// (same kernels, same per-row programs; ONLY the ctx K/V recompute is cached).
2199    pub fn forward_round(
2200        &self,
2201        e: &Engine,
2202        kv: &mut DflashKv,
2203        noise_emb: &CudaSlice<f32>,
2204        pos_block: &[i32],
2205    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2206        let c = &self.cfg;
2207        let (h, nh, nkv, hd) = (c.hidden, c.n_head, c.n_kv, c.head_dim);
2208        let b = c.block_size;
2209        assert_eq!(pos_block.len(), b);
2210        let ctx = kv.len;
2211        let pos_blk = e.htod_i32(pos_block)?;
2212        let mut x = e.clone_dtod(noise_emb)?;
2213        for (li, l) in self.layers.iter().enumerate() {
2214            let mut xn = e.uninit(b * h)?;
2215            e.rms_norm(&x, &l.ln_in, &mut xn, h, b, c.eps)?;
2216            // DFlash2: dynamic conv wraps attention (see forward_block).
2217            let mut attn_dyn: Option<CudaSlice<f32>> = None;
2218            if let Some(d2) = &self.dflash2 {
2219                let (xc, dyn_) = self.d2_conv_prepare(e, &d2.attn_conv[li], &xn, b)?;
2220                xn = xc;
2221                attn_dyn = Some(dyn_);
2222            }
2223            let q0 = self.mm(e, &l.wq, &xn, b, h, nh * hd)?;
2224            let k0b = self.mm(e, &l.wk, &xn, b, h, nkv * hd)?;
2225            let v0b = self.mm(e, &l.wv, &xn, b, h, nkv * hd)?;
2226            let mut q = e.uninit(b * nh * hd)?;
2227            let mut kb = e.uninit(b * nkv * hd)?;
2228            e.rms_norm(&q0, &l.q_norm, &mut q, hd, b * nh, c.eps)?;
2229            e.rms_norm(&k0b, &l.k_norm, &mut kb, hd, b * nkv, c.eps)?;
2230            self.rope_rows(e, &mut q, &pos_blk, nh, b)?;
2231            self.rope_rows(e, &mut kb, &pos_blk, nkv, b)?;
2232            e.copy_into(&mut kv.k[li], ctx * nkv * hd, &kb, b * nkv * hd)?;
2233            e.copy_into(&mut kv.v[li], ctx * nkv * hd, &v0b, b * nkv * hd)?;
2234            let mut attn = e.uninit(b * nh * hd)?;
2235            let scale = 1.0f32 / (hd as f32).sqrt();
2236            if self.dflash2.is_some() && c.layer_sliding[li] {
2237                // Non-causal symmetric window (config is_causal=false): kv row index
2238                // == absolute position for BOTH ctx rows (committed order) and the
2239                // transient block rows, so the kernel's q_pos = (T_kv - T) + qt is the
2240                // absolute position and the old-side mask is exact. The future side
2241                // never binds (block <= window, asserted at load).
2242                d2_windowed_attn(
2243                    e,
2244                    &q,
2245                    &kv.k[li],
2246                    &kv.v[li],
2247                    &mut attn,
2248                    hd,
2249                    nh,
2250                    nkv,
2251                    b,
2252                    ctx + b,
2253                    scale,
2254                    c,
2255                )?;
2256            } else if std::env::var("MEMRA_DFLASH_FA").is_ok() {
2257                e.fa_prefill(
2258                    &q,
2259                    &kv.k[li],
2260                    &kv.v[li],
2261                    &mut attn,
2262                    hd,
2263                    nh,
2264                    nkv,
2265                    b,
2266                    ctx + b,
2267                    scale,
2268                    false,
2269                )?;
2270            } else {
2271                e.sdpa_naive(
2272                    &q,
2273                    &kv.k[li],
2274                    &kv.v[li],
2275                    &mut attn,
2276                    hd,
2277                    nh,
2278                    nkv,
2279                    b,
2280                    ctx + b,
2281                    scale,
2282                    false,
2283                )?;
2284            }
2285            let mut o = self.mm(e, &l.wo, &attn, b, nh * hd, h)?;
2286            if let (Some(d2), Some(dyn_)) = (&self.dflash2, &attn_dyn) {
2287                o = self.d2_conv_finish(e, &d2.attn_conv[li], &o, dyn_, b)?;
2288            }
2289            let mut x1 = e.uninit(b * h)?;
2290            e.add(&o, &x, &mut x1, b * h)?;
2291            let mut x1n = e.uninit(b * h)?;
2292            e.rms_norm(&x1, &l.ln_post, &mut x1n, h, b, c.eps)?;
2293            let mut mlp_dyn: Option<CudaSlice<f32>> = None;
2294            if let Some(d2) = &self.dflash2 {
2295                let (xc, dyn_) = self.d2_conv_prepare(e, &d2.mlp_conv[li], &x1n, b)?;
2296                x1n = xc;
2297                mlp_dyn = Some(dyn_);
2298            }
2299            let gate = self.mm(e, &l.w_gate, &x1n, b, h, c.n_ff)?;
2300            let up_ = self.mm(e, &l.w_up, &x1n, b, h, c.n_ff)?;
2301            let mut act = e.uninit(b * c.n_ff)?;
2302            e.silu_mul(&gate, &up_, &mut act, b * c.n_ff)?;
2303            let mut down = self.mm(e, &l.w_down, &act, b, c.n_ff, h)?;
2304            if let (Some(d2), Some(dyn_)) = (&self.dflash2, &mlp_dyn) {
2305                down = self.d2_conv_finish(e, &d2.mlp_conv[li], &down, dyn_, b)?;
2306            }
2307            let mut x2 = e.uninit(b * h)?;
2308            e.add(&down, &x1, &mut x2, b * h)?;
2309            x = x2;
2310        }
2311        let mut out = e.uninit(b * h)?;
2312        e.rms_norm(&x, &self.norm, &mut out, h, b, c.eps)?;
2313        Ok(out)
2314    }
2315}
2316
2317// ================= DFlash spec round (greedy, first light) =================
2318// Exact contract: identical output stream to plain greedy decode BY CONSTRUCTION — the
2319// target's batched verify argmax decides every committed token; the drafter only proposes.
2320// (Same verify+rewind pattern as generate_spec_gemma's eager round; t=16 verify rides the
2321// straddle-split-safe fa_decode_rows.)
2322impl crate::hybrid::HybridModel {
2323    pub fn generate_spec_dflash(
2324        &self,
2325        e: &Engine,
2326        draft: &DflashDraft,
2327        prompt: &[u32],
2328        max_new: usize,
2329        eos: &[u32],
2330    ) -> Result<Vec<u32>, Box<dyn std::error::Error>> {
2331        use crate::cache::{Cache, DflashTapSink};
2332        let n_embd = self.cfg.n_embd as usize;
2333        let c = &draft.cfg;
2334        assert!(
2335            draft.dflash2.is_none(),
2336            "DFlash2 drafters ride the qwen-hybrid dspark round (selector + windowed \
2337             attention); the gemma arm has no consumer for the family's ops"
2338        );
2339        assert_eq!(n_embd, c.hidden, "draft hidden must match target n_embd");
2340        let b = c.block_size;
2341        let n_taps = c.target_layer_ids.len();
2342        let max_ctx = prompt.len() + max_new + b + 8;
2343        // First light holds ctx <= sliding_window: the draft was trained with 4 sliding
2344        // layers (window 2048) and the first-light attention is windowless full — inside
2345        // the window the two are identical. The depth cell (1736 + 128) fits.
2346        assert!(
2347            max_ctx <= c.sliding_window,
2348            "first-light dflash round is windowless — ctx cap {} exceeds the draft window {}",
2349            max_ctx,
2350            c.sliding_window
2351        );
2352        let mut cache = Cache::new(e, &self.cfg, max_ctx)?;
2353
2354        // ---- prime with taps armed ----
2355        let tp = prompt.len();
2356        cache.dflash_taps = Some(DflashTapSink {
2357            layer_ids: c.target_layer_ids.clone(),
2358            buf: e.uninit(tp * n_taps * n_embd)?,
2359            hidden: n_embd,
2360            t: tp,
2361            base: 0,
2362        });
2363        let t_prime = std::time::Instant::now();
2364        let (logits, _h_seed, _hiddens) = self.prime_cache(e, prompt, &mut cache, 0)?;
2365        let mut last = crate::forward::argmax(&logits) as u32;
2366        // draft KV cache: ingest the prompt's ctx features once; per round only the kept
2367        // rows ingest + the block projects (round cost O(block), not O(ctx)).
2368        let mut dkv = DflashKv::new(e, &draft.cfg, max_ctx)?;
2369        {
2370            // CHUNKED ingest (depth OOM fix): the 1736-row prompt tap buffer is ~224MB f32;
2371            // running fc + 5-layer k/v projection over it in one shot stacks another
2372            // ~300MB of transients on the ~21.3GB trunk peak. 256-row windows bound the
2373            // transient set; identical values (row-independent ops).
2374            let taps = cache.dflash_taps.take().unwrap();
2375            let n_taps_h = n_taps * n_embd;
2376            let mut r0 = 0usize;
2377            while r0 < tp {
2378                let t_c = (tp - r0).min(256);
2379                let tv = e.view(&taps.buf, tp * n_taps_h);
2380                let win = tv.slice(r0 * n_taps_h..(r0 + t_c) * n_taps_h);
2381                let mut chunk = e.uninit(t_c * n_taps_h)?;
2382                e.copy_view_into(&mut chunk, 0, &win, t_c * n_taps_h)?;
2383                let f = draft.ctx_features(e, &chunk, t_c)?;
2384                let pos_c: Vec<i32> = ((r0 as i32)..(r0 + t_c) as i32).collect();
2385                draft.ingest_ctx(e, &mut dkv, &f, &pos_c, t_c)?;
2386                r0 += t_c;
2387            }
2388        }
2389        let mut ctx_len = tp;
2390        e.stream().synchronize()?;
2391        // published prime wall (the run-spec/gemma-gate timing contract subtracts it)
2392        crate::PRIME_NANOS.store(
2393            t_prime.elapsed().as_nanos() as u64,
2394            std::sync::atomic::Ordering::Relaxed,
2395        );
2396
2397        // embed-scale seam (MEMRA_DFLASH_EMB_SCALE): gemma trunks scale embeddings by
2398        // sqrt(n_embd) INSIDE the forward; whether the z-lab gemma4 training fed the
2399        // drafter scaled or raw embed rows is not visible from the reference (qwen path
2400        // uses raw embed_tokens). Acceptance arbitrates; default raw.
2401        let emb_scale = if std::env::var("MEMRA_DFLASH_EMB_SCALE").as_deref() == Ok("1") {
2402            (n_embd as f32).sqrt()
2403        } else {
2404            1.0
2405        };
2406
2407        let mut out = Vec::with_capacity(max_new);
2408        let n_vocab = self.output.out_features();
2409        // VERIFY WIDTH (MEMRA_DFLASH_VERIFY_T, default 8): the drafter always drafts a full
2410        // block (its trained mask pattern) but only the first vt rows go through the target
2411        // verify — the t=16 verify rides the untuned b16 tier at ~32% of the byte wall
2412        // (65ms/verify) while b8 rides the tuned r2 tier; with ~2.7 committed/round the
2413        // deep block positions almost never survive anyway. Exactness unaffected (verify
2414        // still decides every committed token).
2415        let vt_cap: usize = std::env::var("MEMRA_DFLASH_VERIFY_T")
2416            .ok()
2417            .and_then(|v| v.parse().ok())
2418            .unwrap_or(8)
2419            .clamp(2, b);
2420        // adaptive verify width (MEMRA_DFLASH_ADAPT!=0, MTP accepted+1 recipe): next round
2421        // verifies one past this round's accepted run, clamped [3, cap].
2422        let adapt = std::env::var("MEMRA_DFLASH_ADAPT").as_deref() != Ok("0");
2423        let mut vt = vt_cap;
2424        let mut attempted = 0usize;
2425        let mut accepted = 0usize;
2426        // The whole round runs in the decode-exact matmul scope: the m=16 draft mms were
2427        // otherwise falling into the prefill-GEMM class (770us/matmul, 17% of the depth
2428        // round). Prime (before this loop) keeps the prefill GEMM path.
2429        e.set_verify_exact(true);
2430        'outer: while out.len() < max_new {
2431            let start = cache.pos; // committed length
2432            // ---- draft: block = [last, MASK x b-1] ----
2433            let mut block: Vec<u32> = vec![c.mask_token_id; b];
2434            block[0] = last;
2435            let mut noise = e.htod(&self.embd.gather(n_embd, &block))?;
2436            if emb_scale != 1.0 {
2437                e.scale_inplace(&mut noise, emb_scale, b * n_embd)?;
2438            }
2439            if std::env::var("MEMRA_DFLASH_DEBUG").as_deref() == Ok("1") && start == cache.pos {
2440                let nv = e.dtoh(&noise)?;
2441                let r0: f32 = nv[..n_embd].iter().map(|x| x * x).sum::<f32>().sqrt();
2442                let r1: f32 = nv[n_embd..2 * n_embd]
2443                    .iter()
2444                    .map(|x| x * x)
2445                    .sum::<f32>()
2446                    .sqrt();
2447                eprintln!(
2448                    "[dflash noise] |row0(last)|={r0:.3} |row1(MASK id {})|={r1:.3}",
2449                    c.mask_token_id
2450                );
2451            }
2452            let pos_block: Vec<i32> = ((start as i32)..(start + b) as i32).collect();
2453            let dh = draft.forward_round(e, &mut dkv, &noise, &pos_block)?;
2454            // draft tokens = argmax(lm_head(h rows 1..b))
2455            let mut rows = e.uninit((b - 1) * n_embd)?;
2456            {
2457                let dv = e.view(&dh, b * n_embd);
2458                let tail = dv.slice(n_embd..b * n_embd);
2459                e.copy_view_into(&mut rows, 0, &tail, (b - 1) * n_embd)?;
2460            }
2461            let mut dl = e.matmul(&self.output, &rows, b - 1)?;
2462            // SEMI-AR MARKOV CHAIN (DSpark head, when present + MEMRA_DFLASH_MARKOV!=0):
2463            // left-to-right, logits_k += W2(W1[prev realized token]) — the whole chain
2464            // stays on-device (chain_d[0] = the pending token; argmax k writes
2465            // chain_d[k+1], the k+1 bias gathers from it). Greedy mirror of the patch's
2466            // _markov_semiar_sample_block.
2467            let markov_on = std::env::var("MEMRA_DFLASH_MARKOV").as_deref() != Ok("0");
2468            let mut chain_d = e.stream().alloc_zeros::<u32>(b)?;
2469            if let (Some(mk), true) = (&draft.markov, markov_on) {
2470                e.set_u32_one(&mut chain_d, last)?;
2471                for k in 0..(b - 1) {
2472                    let mut f = e.uninit(mk.rank)?;
2473                    e.gather_row_bf16(&mk.w1_bf16, &chain_d, k, &mut f, mk.rank)?;
2474                    let bias = e.matmul(&mk.w2, &f, 1)?;
2475                    e.add_row_inplace(&mut dl, &bias, n_vocab, k * n_vocab)?;
2476                    e.argmax_token_device_col(&dl, k, n_vocab, &mut chain_d, k + 1)?;
2477                }
2478            } else {
2479                for i in 0..(b - 1) {
2480                    e.argmax_token_device_col(&dl, i, n_vocab, &mut chain_d, i + 1)?;
2481                }
2482            }
2483            let chain = e.dtoh_u32(&chain_d)?;
2484            let dtoks = &chain[1..];
2485            for (i, &dt) in dtoks.iter().enumerate() {
2486                block[i + 1] = dt;
2487            }
2488            let dbg = std::env::var("MEMRA_DFLASH_DEBUG").as_deref() == Ok("1");
2489
2490            // ---- verify: one t=vt target forward with taps armed ----
2491            let vblock = &block[..vt];
2492            cache.dflash_taps = Some(DflashTapSink {
2493                layer_ids: c.target_layer_ids.clone(),
2494                buf: e.uninit(vt * n_taps * n_embd)?,
2495                hidden: n_embd,
2496                t: vt,
2497                base: 0,
2498            });
2499            let (vam, _vh) = self.gemma4_decode_step_t_am(e, vblock, start, &mut cache)?;
2500            let taps = cache.dflash_taps.take().unwrap();
2501            if dbg {
2502                eprintln!(
2503                    "[dflash r] start={start} last={last}\n  draft={:?}\n  vam  ={:?}",
2504                    &block[1..],
2505                    &vam
2506                );
2507            }
2508
2509            // ---- accept ----
2510            let mut m = 0usize;
2511            while m < vt - 1 && block[m + 1] as usize == vam[m] as usize {
2512                m += 1;
2513            }
2514            attempted += vt - 1;
2515            accepted += m;
2516            out.push(last);
2517            if eos.contains(&last) {
2518                break 'outer;
2519            }
2520            for &dt in &block[1..=m] {
2521                out.push(dt);
2522                if eos.contains(&dt) {
2523                    break 'outer;
2524                }
2525                if out.len() >= max_new {
2526                    break 'outer;
2527                }
2528            }
2529            let next = vam[m] as u32;
2530
2531            // ---- commit/rollback: keep m+1 of the b appended rows ----
2532            let keep = m + 1;
2533            for kvl in cache.kv.iter_mut().flatten() {
2534                kvl.len -= vt - keep;
2535                e.set_i32_one(&mut kvl.len_d, kvl.len as i32)?;
2536            }
2537            cache.pos -= vt - keep;
2538
2539            // ---- ingest the kept rows' ctx features into the draft KV ----
2540            {
2541                let tv = e.view(&taps.buf, vt * n_taps * n_embd);
2542                let keep_view = tv.slice(0..keep * n_taps * n_embd);
2543                let mut kept = e.uninit(keep * n_taps * n_embd)?;
2544                e.copy_view_into(&mut kept, 0, &keep_view, keep * n_taps * n_embd)?;
2545                let f = draft.ctx_features(e, &kept, keep)?;
2546                let pos_k: Vec<i32> = ((ctx_len as i32)..(ctx_len + keep) as i32).collect();
2547                draft.ingest_ctx(e, &mut dkv, &f, &pos_k, keep)?;
2548                ctx_len += keep;
2549            }
2550            last = next;
2551            if adapt {
2552                vt = (m + 2).clamp(3, vt_cap);
2553            }
2554        }
2555        e.set_verify_exact(false);
2556        if std::env::var("MEMRA_SPEC_STATS").as_deref() == Ok("1") {
2557            eprintln!(
2558                "[dflash] acceptance {accepted}/{attempted} = {:.3}",
2559                accepted as f64 / attempted.max(1) as f64
2560            );
2561        }
2562        Ok(out)
2563    }
2564}
2565
2566// ================= Engine-bundle slice 1: batched GDN state snapshot ====================
2567// DSF-ROUNDCOST-20260820 §1.1 measured the dspark round's `cache.snapshot(e)` at 0.67 ms
2568// native wall — 48 linear layers x {conv, ssm} x (alloc_zeros + memcpy_dtod) of pure
2569// dispatch serialization, zero kernels. This batcher holds ONE persistent CacheSnapshot
2570// (buffers allocated on round 1, reused every round — kills the per-round alloc/memset
2571// churn) plus device pointer tables, so a round's snap is one small H2D table refresh
2572// (the ssm handles ping-pong per verify row, so live pointers are re-read each round;
2573// conv handles are rolled in place and never move) + TWO `copy_batch_uniform_f32`
2574// launches. Bytes, buffers and stream order are identical to `Cache::snapshot`; only the
2575// dispatch count changes, so acceptance and streams stay bit-identical (E2E-gated).
2576// `MEMRA_STATE_COPY_BATCH=0` reverts to the legacy per-layer snapshot.
2577
2578pub(crate) struct DsparkSnapBatch {
2579    pub(crate) snap: crate::cache::CacheSnapshot,
2580    /// Linear-attention layer indices, in `conv_table`/`ssm_table` order.
2581    lin: Vec<usize>,
2582    /// [src_0..src_{n-1}, dst_0..dst_{n-1}] — live conv states -> snapshot conv buffers.
2583    conv_table: CudaSlice<u64>,
2584    ssm_table: CudaSlice<u64>,
2585    host_ssm: Vec<u64>,
2586    conv_words: usize,
2587    ssm_words: usize,
2588}
2589
2590impl DsparkSnapBatch {
2591    /// Build from a fresh full snapshot (this IS round 1's snap — the caller uses
2592    /// `self.snap` directly after `new`). Returns None when the cache has no linear
2593    /// layers or their state sizes are non-uniform (a future hybrid shape) — the caller
2594    /// then stays on the legacy per-layer snapshot rather than copying wrong byte counts.
2595    pub(crate) fn new(
2596        e: &Engine,
2597        cache: &crate::cache::Cache,
2598    ) -> Result<Option<Self>, Box<dyn std::error::Error>> {
2599        use cudarc::driver::DevicePtr;
2600        let snap = cache.snapshot(e)?;
2601        let lin: Vec<usize> = (0..cache.recur.len())
2602            .filter(|&il| cache.recur[il].is_some())
2603            .collect();
2604        if lin.is_empty() {
2605            return Ok(None);
2606        }
2607        let first = cache.recur[lin[0]].as_ref().unwrap();
2608        let (conv_words, ssm_words) = (first.conv_state.len(), first.ssm_state.len());
2609        for &il in &lin {
2610            let rl = cache.recur[il].as_ref().unwrap();
2611            if rl.conv_state.len() != conv_words || rl.ssm_state.len() != ssm_words {
2612                return Ok(None);
2613            }
2614        }
2615        let n = lin.len();
2616        let mut host_conv = vec![0u64; 2 * n];
2617        let mut host_ssm = vec![0u64; 2 * n];
2618        {
2619            let s = &e.gpu.stream();
2620            for (k, &il) in lin.iter().enumerate() {
2621                let rl = cache.recur[il].as_ref().unwrap();
2622                let (pc, _g0) = rl.conv_state.device_ptr(s);
2623                let (ps, _g1) = rl.ssm_state.device_ptr(s);
2624                let (dc, _g2) = snap.conv[il].as_ref().unwrap().device_ptr(s);
2625                let (ds, _g3) = snap.ssm[il].as_ref().unwrap().device_ptr(s);
2626                host_conv[k] = pc as u64;
2627                host_conv[n + k] = dc as u64;
2628                host_ssm[k] = ps as u64;
2629                host_ssm[n + k] = ds as u64;
2630            }
2631        }
2632        let conv_table = e.htod_u64(&host_conv)?;
2633        let ssm_table = e.htod_u64(&host_ssm)?;
2634        Ok(Some(Self {
2635            snap,
2636            lin,
2637            conv_table,
2638            ssm_table,
2639            host_ssm,
2640            conv_words,
2641            ssm_words,
2642        }))
2643    }
2644
2645    /// The per-round snap: refresh kv lens/pos host-side (as `snapshot_into` does),
2646    /// re-read the live ssm handles into the table (gdn ping-pong moves them; the conv
2647    /// handles and every snapshot dst are stable), then two batched-copy launches.
2648    pub(crate) fn refresh(
2649        &mut self,
2650        e: &Engine,
2651        cache: &crate::cache::Cache,
2652    ) -> Result<(), Box<dyn std::error::Error>> {
2653        use cudarc::driver::DevicePtr;
2654        for il in 0..cache.kv.len() {
2655            self.snap.kv_len[il] = cache.kv[il].as_ref().map(|kvl| kvl.len);
2656        }
2657        self.snap.pos = cache.pos;
2658        let n = self.lin.len();
2659        {
2660            let s = &e.gpu.stream();
2661            for (k, &il) in self.lin.iter().enumerate() {
2662                let rl = cache.recur[il].as_ref().unwrap();
2663                let (ps, _g) = rl.ssm_state.device_ptr(s);
2664                self.host_ssm[k] = ps as u64;
2665            }
2666        }
2667        e.htod_u64_into(&self.host_ssm, &mut self.ssm_table)?;
2668        e.copy_batch_uniform_f32(&self.conv_table, n, self.conv_words)?;
2669        e.copy_batch_uniform_f32(&self.ssm_table, n, self.ssm_words)?;
2670        Ok(())
2671    }
2672}
2673
2674// ================= DSpark spec round, QWEN-HYBRID target (lane/dspark-q38-recover) =====
2675// The q38 twin of generate_spec_dflash. Same drafter machinery (rounds, markov chain,
2676// draft KV, adaptive verify width); the TARGET side swaps gemma4's dense verify for the
2677// qwen serving-class verify funnel (dspark_verify_t_am) + snapshot/rollback, because the
2678// hybrid GDN conv/ssm state mutates in place — dense KV truncation cannot roll it back.
2679// Exactness contract unchanged: identical stream to plain greedy BY CONSTRUCTION (the
2680// target's verify argmax decides every committed token).
2681impl crate::hybrid::HybridModel {
2682    pub fn generate_spec_dspark(
2683        &self,
2684        e: &Engine,
2685        draft: &DflashDraft,
2686        prompt: &[u32],
2687        max_new: usize,
2688        eos: &[u32],
2689        sampling: Option<&crate::spec::SpecSampling>,
2690    ) -> Result<Vec<u32>, Box<dyn std::error::Error>> {
2691        use crate::cache::{Cache, DflashTapSink};
2692        assert!(
2693            self.cfg.gemma4.is_none(),
2694            "gemma4 targets use generate_spec_dflash; this is the qwen-hybrid arm"
2695        );
2696        // SAMPLED ADMISSION (T>0, lane/dspark-sampled-admission-20260820): Some+temp>0
2697        // routes the round's proposal/accept through the rejection-sampling arms; None or
2698        // temp==0 keeps every greedy path byte-identical (the exactness instrument).
2699        let sp_on: Option<&crate::spec::SpecSampling> = sampling.filter(|s| s.temp > 0.0);
2700        let (mut sctr, mut uctr) = (0u32, 0u32);
2701        let n_embd = self.cfg.n_embd as usize;
2702        let c = &draft.cfg;
2703        assert_eq!(n_embd, c.hidden, "draft hidden must match target n_embd");
2704        let b = c.block_size;
2705        let n_taps = c.target_layer_ids.len();
2706        let max_ctx = prompt.len() + max_new + b + 8;
2707        // DFlash2 implements the reference's non-causal symmetric sliding window in
2708        // the round attention (sdpa_naive_w), so depth past the window is admitted;
2709        // other families keep the historical windowless contract.
2710        assert!(
2711            draft.dflash2.is_some() || max_ctx <= c.sliding_window,
2712            "dspark round is windowless — ctx cap {} exceeds the draft window {}",
2713            max_ctx,
2714            c.sliding_window
2715        );
2716        let mut cache = Cache::new(e, &self.cfg, max_ctx)?;
2717
2718        // ---- prime with taps armed (chunked prime writes at chunk offsets via sink.base) ----
2719        let tp = prompt.len();
2720        cache.dflash_taps = Some(DflashTapSink {
2721            layer_ids: c.target_layer_ids.clone(),
2722            buf: e.uninit(tp * n_taps * n_embd)?,
2723            hidden: n_embd,
2724            t: tp,
2725            base: 0,
2726        });
2727        let t_prime = std::time::Instant::now();
2728        let (logits, _h_seed, _hiddens) = self.prime_cache(e, prompt, &mut cache, 0)?;
2729        // Boundary token: greedy takes the argmax (byte contract); sampled draws it from
2730        // the request's own filtered target through the session Philox stream — the same
2731        // shipped composition the frspec route uses (sample_check arm 9 oracles it).
2732        let mut last = match sp_on {
2733            Some(sp) => {
2734                crate::spec::sample_boundary_token(e, &logits, sp, &[], &mut sctr, "dspark-prime")?
2735            }
2736            None => crate::forward::argmax(&logits) as u32,
2737        };
2738        let mut dkv = DflashKv::new(e, &draft.cfg, max_ctx)?;
2739        {
2740            let taps = cache.dflash_taps.take().unwrap();
2741            let n_taps_h = n_taps * n_embd;
2742            let mut r0 = 0usize;
2743            while r0 < tp {
2744                let t_c = (tp - r0).min(256);
2745                let tv = e.view(&taps.buf, tp * n_taps_h);
2746                let win = tv.slice(r0 * n_taps_h..(r0 + t_c) * n_taps_h);
2747                let mut chunk = e.uninit(t_c * n_taps_h)?;
2748                e.copy_view_into(&mut chunk, 0, &win, t_c * n_taps_h)?;
2749                let f = draft.ctx_features(e, &chunk, t_c)?;
2750                let pos_c: Vec<i32> = ((r0 as i32)..(r0 + t_c) as i32).collect();
2751                draft.ingest_ctx(e, &mut dkv, &f, &pos_c, t_c)?;
2752                r0 += t_c;
2753            }
2754        }
2755        let mut ctx_len = tp;
2756        e.stream().synchronize()?;
2757        crate::PRIME_NANOS.store(
2758            t_prime.elapsed().as_nanos() as u64,
2759            std::sync::atomic::Ordering::Relaxed,
2760        );
2761
2762        let mut out = Vec::with_capacity(max_new);
2763        let n_vocab = self.output.out_features();
2764        // Harvest convention (DSPARK-POSTMORTEM-20260820.md): which drafter output rows
2765        // become draft candidates. nd = drafts/round; verify carries [anchor, drafts]
2766        // = up to nd+1 rows. FAMILY-keyed for DFlash2 (mask-fill by construction),
2767        // else default = the CHECKPOINT's own strategy census (owner-ratified flip,
2768        // 2026-08-20); explicit env still wins (contradiction refuses).
2769        let harvest = DsparkHarvest::for_draft(draft);
2770        let nd = harvest.n_drafts(b);
2771        let r0 = harvest.first_row();
2772        let vt_cap: usize = std::env::var("MEMRA_DFLASH_VERIFY_T")
2773            .ok()
2774            .and_then(|v| v.parse().ok())
2775            .unwrap_or(nd + 1)
2776            .clamp(2, nd + 1);
2777        let adapt = std::env::var("MEMRA_DFLASH_ADAPT").as_deref() != Ok("0");
2778        // Verify-window policy (H4, DSPARK-POSTMORTEM-20260820.md): default =
2779        // confidence-slot tau=.5 when the checkpoint carries an accept-rate head
2780        // (owner-ratified flip 2026-08-20; cell-3 tau ladder knee) — each round's
2781        // window is sized from the head's own slot scores, post-draft pre-verify.
2782        // Head-less checkpoints and MEMRA_DFLASH_ADAPT=0 keep the reactive ladder.
2783        let vt_policy = DsparkVtPolicy::resolve(draft.confidence.is_some());
2784        if vt_policy.is_confidence() {
2785            assert!(
2786                draft.confidence.is_some(),
2787                "MEMRA_DSPARK_VT={vt_policy:?} needs a checkpoint with an accept-rate \
2788                 head (confidence_head.* absent in this export)"
2789            );
2790        }
2791        let mut vt = vt_cap;
2792        let mut attempted = 0usize;
2793        let mut accepted = 0usize;
2794        // Engine-bundle slice 1: persistent batched snapshot (None until round 1; stays
2795        // None — legacy per-layer snapshot — under MEMRA_STATE_COPY_BATCH=0 or when the
2796        // batcher declines the cache shape).
2797        let mut snapb: Option<DsparkSnapBatch> = None;
2798        let mut snapb_off = !crate::spec::state_copy_batch_on();
2799        // Engine-bundle slice 2: deferred chain readback needs the resident embed table
2800        // (verify then embeds chain_d directly). Ladder/stash arms only — the confidence
2801        // policies size vt from a pre-verify head readback and keep the legacy order.
2802        let defer_rb = crate::spec::dspark_defer_readback_on() && !vt_policy.is_confidence();
2803        let (embd_qt, embd_rb) = self.embd.qt_and_row_bytes(n_embd);
2804        let embd_gpu = if !defer_rb || crate::spec::spec_host_embd() {
2805            None
2806        } else {
2807            Some(
2808                self.embd_gpu
2809                    .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload")),
2810            )
2811        };
2812        // Engine-bundle slice 3: per-(segment, vt) verify graphs for the linear-layer runs
2813        // (rides the slice-2 deferred path only — device tokens keep the whole verify off
2814        // the host). PERSISTENT across generations on the model (rebuilding per call
2815        // re-captured ~80 graphs per prompt — measured 97.8 -> 79.1 tok/s e2e); the
2816        // captured bodies are cache-independent: all state reads go through per-round
2817        // refreshed pointer tables and ctx-owned slabs. None = eager walk, byte-identical.
2818        let mut vg_guard = self.dspark_vgraphs.lock().unwrap();
2819        if vg_guard.is_none() && embd_gpu.is_some() && crate::spec::dspark_verify_graph_on() {
2820            *vg_guard = crate::spec::DsparkVerifyGraphs::new(e, &cache, vt_cap, n_embd)?;
2821        }
2822        let vgraphs: &mut Option<crate::spec::DsparkVerifyGraphs> = &mut vg_guard;
2823        // per-phase economics counters (ns) — the verify-toll dataset
2824        let (mut ns_draft, mut ns_snap, mut ns_verify, mut ns_roll, mut ns_ingest) =
2825            (0u64, 0u64, 0u64, 0u64, 0u64);
2826        let mut rounds = 0usize;
2827        let stats = std::env::var("MEMRA_SPEC_STATS").as_deref() == Ok("1");
2828        let clock = |on: bool, e: &Engine| -> std::time::Instant {
2829            if on {
2830                let _ = e.stream().synchronize();
2831            }
2832            std::time::Instant::now()
2833        };
2834        'outer: while out.len() < max_new {
2835            rounds += 1;
2836            let start = cache.pos; // committed length
2837            // ---- draft: block = [last, MASK x b-1] (decode-exact class for the m=b mms) ----
2838            let t0 = clock(stats, e);
2839            e.set_verify_exact(true);
2840            let mut block: Vec<u32> = vec![c.mask_token_id; b];
2841            block[0] = last;
2842            let noise = e.htod(&self.embd.gather(n_embd, &block))?;
2843            let pos_block: Vec<i32> = ((start as i32)..(start + b) as i32).collect();
2844            let dh = draft.forward_round(e, &mut dkv, &noise, &pos_block)?;
2845            // Harvest: logits over rows r0..r0+nd (Dflash: mask rows 1..b-1, fill
2846            // semantics; Dspark: ALL b rows, shifted semantics — row k predicts
2847            // anchor+k+1, so col k of `dl` is the draft for position start+k+1).
2848            let mut rows = e.uninit(nd * n_embd)?;
2849            {
2850                let dv = e.view(&dh, b * n_embd);
2851                let src = dv.slice(r0 * n_embd..(r0 + nd) * n_embd);
2852                e.copy_view_into(&mut rows, 0, &src, nd * n_embd)?;
2853            }
2854            let mut dl = e.matmul(&self.output, &rows, nd)?;
2855            // Family/sampling-keyed proposal (v0.100 train merge of the port and H4/
2856            // engine-bundle stacks — BOTH programs preserved):
2857            //  - SAMPLED (sp_on): rejection-sampling proposal, records the true per-slot
2858            //    q (family-keyed inside: selector for DFlash2, markov-corrected rows
2859            //    otherwise). Host CDF/readback syncs inside — slice-2 deferral N/A.
2860            //  - DFlash2 greedy: the candidate path selector REPLACES the markov chain
2861            //    (reference DFlash2DraftModel.propose — greedy arm).
2862            //  - markov/plain greedy chain: the engine-bundle arm; slice-2 readback
2863            //    deferral decided below (needs the ckpt arm reads).
2864            // Confidence policy: stash each slot's markov prev-token embedding (the
2865            // exact `w1` row the chain gathers) into a [nd, rank] buffer — d2d async,
2866            // read back beside `rows` in one host sync after the chain.
2867            let want_conf_emb = vt_policy.is_confidence()
2868                && draft.confidence.as_ref().is_some_and(|ch| ch.with_markov);
2869            let mut conf_emb: Option<CudaSlice<f32>> = match (&draft.markov, want_conf_emb) {
2870                (Some(mk), true) => Some(e.uninit(nd * mk.rank)?),
2871                (None, true) => unreachable!(
2872                    "with_markov confidence head without a markov table — the loader forbids it"
2873                ),
2874                _ => None,
2875            };
2876            let mut cand: Vec<u32> = Vec::with_capacity(nd + 1);
2877            let mut prop: Option<DsparkDraftSample> = None;
2878            let mut chain_dev: Option<CudaSlice<u32>> = None;
2879            if let Some(sp) = sp_on {
2880                let (tail, ds) = draft.dspark_propose_sampled(
2881                    e,
2882                    &mut dl,
2883                    &rows,
2884                    nd,
2885                    n_vocab,
2886                    last,
2887                    sp,
2888                    &mut sctr,
2889                    &mut uctr,
2890                    conf_emb.as_mut(),
2891                )?;
2892                e.set_verify_exact(false);
2893                cand.push(last);
2894                cand.extend_from_slice(&tail);
2895                prop = Some(ds);
2896            } else if draft.dflash2.is_some() {
2897                let path = draft.dflash2_propose_greedy(e, &dl, &rows, nd, n_vocab, last)?;
2898                e.set_verify_exact(false);
2899                cand.push(last);
2900                cand.extend_from_slice(&path);
2901            } else {
2902                let markov_on = std::env::var("MEMRA_DFLASH_MARKOV").as_deref() != Ok("0");
2903                let mut chain_d = e.stream().alloc_zeros::<u32>(nd + 1)?;
2904                if let (Some(mk), true) = (&draft.markov, markov_on) {
2905                    e.set_u32_one(&mut chain_d, last)?;
2906                    for k in 0..nd {
2907                        let mut f = e.uninit(mk.rank)?;
2908                        e.gather_row_bf16(&mk.w1_bf16, &chain_d, k, &mut f, mk.rank)?;
2909                        if let Some(ce) = conf_emb.as_mut() {
2910                            let fv = e.view(&f, mk.rank);
2911                            e.copy_view_into(ce, k * mk.rank, &fv, mk.rank)?;
2912                        }
2913                        let bias = e.matmul(&mk.w2, &f, 1)?;
2914                        e.add_row_inplace(&mut dl, &bias, n_vocab, k * n_vocab)?;
2915                        e.argmax_token_device_col(&dl, k, n_vocab, &mut chain_d, k + 1)?;
2916                    }
2917                } else {
2918                    if want_conf_emb {
2919                        // chain_d[0] must carry the anchor — slot 0's prev token.
2920                        e.set_u32_one(&mut chain_d, last)?;
2921                    }
2922                    for i in 0..nd {
2923                        if let (Some(ce), Some(mk)) = (conf_emb.as_mut(), &draft.markov) {
2924                            let mut f = e.uninit(mk.rank)?;
2925                            e.gather_row_bf16(&mk.w1_bf16, &chain_d, i, &mut f, mk.rank)?;
2926                            let fv = e.view(&f, mk.rank);
2927                            e.copy_view_into(ce, i * mk.rank, &fv, mk.rank)?;
2928                        }
2929                        e.argmax_token_device_col(&dl, i, n_vocab, &mut chain_d, i + 1)?;
2930                    }
2931                }
2932                e.set_verify_exact(false);
2933                chain_dev = Some(chain_d);
2934            }
2935            // MEMRA_DSPARK_CKPT (default 1): verify with the MTP column-stash armed so a
2936            // partial accept restores state directly. =0 keeps the snapshot+replay arm
2937            // (the oracle the stash arm is gated against — MEMRA_DSPARK_CKPT_GATE=1 runs
2938            // BOTH per partial round and byte-compares the resulting cache state).
2939            // Read here (was at the verify site) — slice 2's deferral needs the arm
2940            // choice before deciding whether the chain readback can move past verify.
2941            let ckpt_on = std::env::var("MEMRA_DSPARK_CKPT").as_deref() != Ok("0");
2942            let ckpt_gate = std::env::var("MEMRA_DSPARK_CKPT_GATE").as_deref() == Ok("1");
2943            // SAMPLED x ckpt-gate refusal: the gate compares verify argmaxes across a
2944            // replay — a greedy-exactness instrument (port lane). Refuse loudly.
2945            if sp_on.is_some() && ckpt_gate {
2946                return Err(
2947                    "MEMRA_DSPARK_CKPT_GATE compares verify argmaxes across a replay \
2948                            — a greedy-exactness instrument; unset it for T>0 dspark rounds"
2949                        .into(),
2950                );
2951            }
2952            // Slice 2: under the stash/gate arms with a resident embed table, the GREEDY
2953            // chain readback is DEFERRED past verify dispatch and merged with the argmax
2954            // readback into one sync. The replay arm (CKPT=0) verifies host tokens and
2955            // keeps the legacy order; the sampled and DFlash2 proposals already synced
2956            // at the walk (chain_dev is None there).
2957            let deferred = chain_dev.is_some() && embd_gpu.is_some() && (ckpt_on || ckpt_gate);
2958            // ---- H4 confidence window: size THIS round's verify from the head ----
2959            if vt_policy.is_confidence() {
2960                let ch = draft.confidence.as_ref().expect("asserted at loop entry");
2961                let (rows_h, emb_h) = match conf_emb.as_ref() {
2962                    Some(ce) => {
2963                        let (a, b2) = e.dtoh_pair(&rows, ce)?;
2964                        (a, Some(b2))
2965                    }
2966                    None => (e.dtoh(&rows)?, None),
2967                };
2968                let rank = draft.markov.as_ref().map(|m| m.rank).unwrap_or(0);
2969                let mut raws = Vec::with_capacity(nd);
2970                for k in 0..nd {
2971                    let hrow = &rows_h[k * n_embd..(k + 1) * n_embd];
2972                    let emb = emb_h.as_ref().map(|eh| &eh[k * rank..(k + 1) * rank]);
2973                    raws.push(ch.raw_score(hrow, emb));
2974                }
2975                vt = vt_policy
2976                    .size_window(&raws, vt_cap)
2977                    .expect("confidence policies always size the window");
2978            }
2979            // Verify candidates: [anchor, draft 1..nd]. Under Dflash this is the
2980            // historical `block` content; under Dspark it is one longer than the
2981            // drafter's input block (nd = b drafts + the anchor). The sampled/DFlash2
2982            // proposals built `cand` at the walk; deferred greedy rounds build it after
2983            // the merged readback — the bytes are identical (chain_d is written before
2984            // either sync).
2985            if let Some(chain_d) = chain_dev.as_ref() {
2986                if !deferred {
2987                    let chain = e.dtoh_u32(chain_d)?;
2988                    cand.push(last);
2989                    cand.extend_from_slice(&chain[1..]);
2990                }
2991            }
2992            ns_draft += clock(stats, e).duration_since(t0).as_nanos() as u64;
2993
2994            // ---- snapshot (GDN conv/ssm state + KV lens), then verify t=vt ----
2995            let t1 = std::time::Instant::now();
2996            // Slice 1: batched snap (one table refresh + two copy launches) with the
2997            // legacy per-layer snapshot as the kill-switch / non-uniform fallback.
2998            let mut snap_legacy: Option<crate::cache::CacheSnapshot> = None;
2999            if !snapb_off && snapb.is_none() {
3000                snapb = DsparkSnapBatch::new(e, &cache)?;
3001                snapb_off = snapb.is_none();
3002            } else if let Some(sb) = snapb.as_mut() {
3003                sb.refresh(e, &cache)?;
3004            }
3005            let snap: &crate::cache::CacheSnapshot = match snapb.as_ref() {
3006                Some(sb) => &sb.snap,
3007                None => {
3008                    snap_legacy = Some(cache.snapshot(e)?);
3009                    snap_legacy.as_ref().unwrap()
3010                }
3011            };
3012            let _ = &snap_legacy;
3013            ns_snap += clock(stats, e).duration_since(t1).as_nanos() as u64;
3014            let t2 = std::time::Instant::now();
3015            // Slice 3: the tap-sink buffer is persistent per vt in the graphs ctx
3016            // (captured segments bake its address); fully rewritten by every verify.
3017            let tap_buf = match vgraphs.as_mut().and_then(|g| g.tap_bufs.remove(&vt)) {
3018                Some(buf) => buf,
3019                None => e.uninit(vt * n_taps * n_embd)?,
3020            };
3021            cache.dflash_taps = Some(DflashTapSink {
3022                layer_ids: c.target_layer_ids.clone(),
3023                buf: tap_buf,
3024                hidden: n_embd,
3025                t: vt,
3026                base: 0,
3027            });
3028            // The whole fallible verify window runs inside a closure so the Err path can
3029            // return the sink buffer to the ctx pool before propagating (v0.98 review
3030            // carry-over): five `?`s span the window, and an early return would drop
3031            // `cache.dflash_taps` — freeing the buffer whose ADDRESS the model-persistent
3032            // captured graphs bake, so the next generation's replayed tap copies would
3033            // write freed memory. The never-orphan invariant below now holds on EVERY
3034            // exit, not just the EOS/budget break.
3035            let verify_res = (|cache: &mut crate::cache::Cache,
3036                               cand: &mut Vec<u32>,
3037                               vgraphs: &mut Option<crate::spec::DsparkVerifyGraphs>|
3038             -> Result<
3039                (
3040                    Vec<u32>,
3041                    Option<CudaSlice<f32>>,
3042                    Option<crate::spec::DsparkVerifyCkpt>,
3043                ),
3044                Box<dyn std::error::Error>,
3045            > {
3046                if sp_on.is_some() {
3047                    // SAMPLED: keep the raw verify logits — the accept walk gathers
3048                    // filtered p from them (argmaxes are the greedy arm's instrument,
3049                    // not this one's).
3050                    if ckpt_on {
3051                        let (tl, vck) =
3052                            self.dspark_verify_t_logits_ckpt(e, &cand[..vt], start, cache)?;
3053                        Ok((Vec::new(), Some(tl), Some(vck)))
3054                    } else {
3055                        Ok((
3056                            Vec::new(),
3057                            Some(self.dspark_verify_t_logits(e, &cand[..vt], start, cache)?),
3058                            None,
3059                        ))
3060                    }
3061                } else if deferred {
3062                    // Slice 2: verify embeds the DEVICE chain (cand layout by construction:
3063                    // chain_d[0] = anchor, chain_d[1..] = drafts), then ONE host sync reads
3064                    // chain + verify argmaxes together — the host dispatched snap + all of
3065                    // verify while the draft was still executing.
3066                    let chain_d = chain_dev.as_ref().expect("deferred implies greedy chain");
3067                    let g = embd_gpu.expect("deferred implies resident embed");
3068                    let (am_d, vck) = self.dspark_verify_t_am_ckpt_dev(
3069                        e,
3070                        chain_d,
3071                        vt,
3072                        start,
3073                        cache,
3074                        (g, embd_qt, embd_rb),
3075                        vgraphs.as_mut(),
3076                    )?;
3077                    let ch = e.stream().clone_dtoh(chain_d)?;
3078                    let am = e.stream().clone_dtoh(&am_d)?;
3079                    e.stream().synchronize()?;
3080                    cand.push(last);
3081                    cand.extend_from_slice(&ch[1..]);
3082                    Ok((am, None, Some(vck)))
3083                } else if ckpt_on || ckpt_gate {
3084                    let (vam, vck) = self.dspark_verify_t_am_ckpt(e, &cand[..vt], start, cache)?;
3085                    Ok((vam, None, Some(vck)))
3086                } else {
3087                    Ok((
3088                        self.dspark_verify_t_am(e, &cand[..vt], start, cache)?,
3089                        None,
3090                        None,
3091                    ))
3092                }
3093            })(&mut cache, &mut cand, vgraphs);
3094            let (vam, tl, vck) = match verify_res {
3095                Ok(v) => v,
3096                Err(err) => {
3097                    if let (Some(g), Some(taps)) = (vgraphs.as_mut(), cache.dflash_taps.take()) {
3098                        g.tap_bufs.insert(vt, taps.buf);
3099                    }
3100                    return Err(err);
3101                }
3102            };
3103            let taps = cache.dflash_taps.take().unwrap();
3104            // Return the tap buffer to the ctx pool IMMEDIATELY — an EOS/budget break
3105            // between accept and ingest must never orphan an address the captured
3106            // graphs bake (the next generation would alloc a fresh buffer and the
3107            // replayed tap copies would write freed memory). Ingest reads it borrowed.
3108            let tap_local: Option<CudaSlice<f32>> = match vgraphs.as_mut() {
3109                Some(g) => {
3110                    g.tap_bufs.insert(vt, taps.buf);
3111                    None
3112                }
3113                None => Some(taps.buf),
3114            };
3115            let tap_ref: &CudaSlice<f32> = match &tap_local {
3116                Some(b) => b,
3117                None => &vgraphs.as_ref().expect("ctx present above").tap_bufs[&vt],
3118            };
3119            ns_verify += clock(stats, e).duration_since(t2).as_nanos() as u64;
3120
3121            // ---- accept ----
3122            let (m, next) = match (sp_on, tl.as_ref()) {
3123                (Some(sp), Some(tl)) => dspark_accept_sampled(
3124                    e,
3125                    tl,
3126                    &cand,
3127                    vt,
3128                    n_vocab,
3129                    &dl,
3130                    prop.as_ref()
3131                        .expect("sampled round without a proposal record"),
3132                    sp,
3133                    &mut sctr,
3134                    &mut uctr,
3135                )?,
3136                _ => {
3137                    let m = dspark_accept_prefix(&cand, &vam, vt);
3138                    (m, vam[m])
3139                }
3140            };
3141            attempted += vt - 1;
3142            accepted += m;
3143            out.push(last);
3144            if eos.contains(&last) {
3145                break 'outer;
3146            }
3147            for &dt in &cand[1..=m] {
3148                // budget check BEFORE the push: at real acceptance the final round often
3149                // accepts a draft at the boundary, and push-then-check emitted max_new+1
3150                // tokens (plain emits exactly max_new — the E2E gate read it as a length
3151                // divergence at index max_new with the shared prefix byte-identical).
3152                if out.len() >= max_new {
3153                    break 'outer;
3154                }
3155                out.push(dt);
3156                if eos.contains(&dt) {
3157                    break 'outer;
3158                }
3159            }
3160
3161            // ---- commit/rollback: hybrid state cannot truncate — restore + replay kept ----
3162            let keep = m + 1;
3163            let t3 = std::time::Instant::now();
3164            // Slice 3: rounds whose linear column stash lives in the graphs ctx's slabs
3165            // commit through the slab twin (same semantics, slab-addressed sources).
3166            let slab_commit = vgraphs.as_ref().map(|g| g.round_slab).unwrap_or(false);
3167            if keep < vt {
3168                if ckpt_gate {
3169                    // GATE ARM: stash-restore, snapshot S1; then the replay oracle, snapshot
3170                    // S2; the two cache states must match BIT-FOR-BIT (kv lens, pos, every
3171                    // conv/ssm buffer). Continue from the replay state (proven identical).
3172                    if slab_commit {
3173                        self.dspark_commit_prefix_slab(
3174                            e,
3175                            &mut cache,
3176                            snap,
3177                            vgraphs.as_ref().expect("slab_commit implies ctx"),
3178                            keep,
3179                        )?;
3180                    } else {
3181                        let vck = vck.as_ref().expect("gate arm always fills the ckpt");
3182                        self.dspark_commit_prefix(e, &mut cache, snap, vck, keep)?;
3183                    }
3184                    // host-side state capture (NO device snapshot copies — two extra
3185                    // device snapshots per round OOM'd beside the 15GB trunk)
3186                    let capture = |cache: &Cache| -> Result<
3187                        (usize, Vec<Option<usize>>, Vec<(Vec<f32>, Vec<f32>)>),
3188                        Box<dyn std::error::Error>,
3189                    > {
3190                        let mut lens = Vec::new();
3191                        let mut states = Vec::new();
3192                        for il in 0..cache.kv.len() {
3193                            lens.push(cache.kv[il].as_ref().map(|k| k.len));
3194                            if let Some(rl) = &cache.recur[il] {
3195                                states.push((e.dtoh(&rl.conv_state)?, e.dtoh(&rl.ssm_state)?));
3196                            }
3197                        }
3198                        Ok((cache.pos, lens, states))
3199                    };
3200                    let (p1, l1, st1) = capture(&cache)?;
3201                    crate::pp::restore_cache_checkpoint(e, &self.cfg, None, &mut cache, snap)?;
3202                    let ram = self.dspark_verify_t_am(e, &cand[..keep], start, &mut cache)?;
3203                    assert_eq!(
3204                        &ram[..],
3205                        &vam[..keep],
3206                        "prefix replay must reproduce the verify argmaxes"
3207                    );
3208                    let (p2, l2, st2) = capture(&cache)?;
3209                    assert_eq!(p1, p2, "ckpt-gate: pos mismatch");
3210                    assert_eq!(l1, l2, "ckpt-gate: kv_len mismatch");
3211                    for (il, ((c1, s1v), (c2, s2v))) in st1.iter().zip(&st2).enumerate() {
3212                        let bits = |a: &[f32], b: &[f32]| {
3213                            a.iter().zip(b).all(|(x, y)| x.to_bits() == y.to_bits())
3214                        };
3215                        assert!(
3216                            bits(c1, c2),
3217                            "ckpt-gate: linear layer {il} conv state differs"
3218                        );
3219                        assert!(
3220                            bits(s1v, s2v),
3221                            "ckpt-gate: linear layer {il} ssm state differs"
3222                        );
3223                    }
3224                } else if slab_commit {
3225                    // STASH ARM, slab twin (slice 3): same restore, slab-addressed.
3226                    self.dspark_commit_prefix_slab(
3227                        e,
3228                        &mut cache,
3229                        snap,
3230                        vgraphs.as_ref().expect("slab_commit implies ctx"),
3231                        keep,
3232                    )?;
3233                } else if let Some(vck) = vck.as_ref() {
3234                    // STASH ARM (default): column-state restore, no replay forward.
3235                    self.dspark_commit_prefix(e, &mut cache, snap, vck, keep)?;
3236                } else {
3237                    // REPLAY ARM (MEMRA_DSPARK_CKPT=0): the original snapshot+replay oracle.
3238                    crate::pp::restore_cache_checkpoint(e, &self.cfg, None, &mut cache, snap)?;
3239                    debug_assert_eq!(cache.pos, start, "rollback landed off the round start");
3240                    let ram = self.dspark_verify_t_am(e, &cand[..keep], start, &mut cache)?;
3241                    if sp_on.is_none() {
3242                        // the argmax-reproduction oracle is greedy-only; the sampled arm
3243                        // replays purely to rebuild the cache state.
3244                        debug_assert_eq!(
3245                            &ram[..],
3246                            &vam[..keep],
3247                            "prefix replay must reproduce the verify argmaxes"
3248                        );
3249                    }
3250                }
3251            }
3252            ns_roll += clock(stats, e).duration_since(t3).as_nanos() as u64;
3253
3254            // ---- ingest the kept rows' ctx features into the draft KV ----
3255            let t4 = std::time::Instant::now();
3256            {
3257                let tv = e.view(tap_ref, vt * n_taps * n_embd);
3258                let keep_view = tv.slice(0..keep * n_taps * n_embd);
3259                let mut kept = e.uninit(keep * n_taps * n_embd)?;
3260                e.copy_view_into(&mut kept, 0, &keep_view, keep * n_taps * n_embd)?;
3261                let f = draft.ctx_features(e, &kept, keep)?;
3262                let pos_k: Vec<i32> = ((ctx_len as i32)..(ctx_len + keep) as i32).collect();
3263                draft.ingest_ctx(e, &mut dkv, &f, &pos_k, keep)?;
3264                ctx_len += keep;
3265            }
3266            ns_ingest += clock(stats, e).duration_since(t4).as_nanos() as u64;
3267            last = next;
3268            // Ladder update only — under the confidence policies vt is recomputed
3269            // from the head every round, post-draft pre-verify.
3270            if !vt_policy.is_confidence() && adapt {
3271                vt = (m + 2).clamp(3, vt_cap);
3272            }
3273        }
3274        if stats {
3275            let ms = |n: u64| n as f64 / 1e6;
3276            eprintln!(
3277                "[dspark-q38] acceptance {accepted}/{attempted} = {:.3} rounds={rounds} \
3278                 draft={:.1}ms snap={:.1}ms verify={:.1}ms rollback+replay={:.1}ms ingest={:.1}ms",
3279                accepted as f64 / attempted.max(1) as f64,
3280                ms(ns_draft),
3281                ms(ns_snap),
3282                ms(ns_verify),
3283                ms(ns_roll),
3284                ms(ns_ingest)
3285            );
3286        }
3287        Ok(out)
3288    }
3289}
3290
3291// ================= DSpark SERVING session (lane/dspark-q38-recover serve route) =========
3292// Burst-scoped state for the worker's dspark spec arm — the qwen-hybrid twin of
3293// GemmaSpecSession. Holds the trunk cache + draft KV + the round loop's carry state
3294// (`last`, ctx_len, adaptive vt) so the scheduler round-robins other sessions between
3295// bursts. The round body is generate_spec_dspark's loop, hoisted; that bin arm stays the
3296// banked oracle (E2E gate), and the serve-route smoke gates this twin byte-identical to
3297// a spec-off boot over the real HTTP surface. Exactness contract unchanged: the target's
3298// verify argmax decides every committed token, so the stream equals plain greedy BY
3299// CONSTRUCTION on every accept path (ckpt stash, gate, replay).
3300pub struct DsparkSpecSession {
3301    pub cache: crate::cache::Cache,
3302    dkv: DflashKv,
3303    last: u32,
3304    ctx_len: usize,
3305    vt: usize,
3306    pub rounds: usize,
3307    max_ctx: usize,
3308    done: bool,
3309    /// Engine-bundle slice 1: persistent batched snapshot (buffers + pointer tables live
3310    /// with the session so bursts reuse them). None until the first round; stays None —
3311    /// legacy per-layer snapshot — when `snapb_off`.
3312    snapb: Option<DsparkSnapBatch>,
3313    snapb_off: bool,
3314    /// SAMPLED ADMISSION (T>0, lane/dspark-sampled-admission-20260820): the request's
3315    /// sampling config (None/temp==0 = the greedy route, byte-identical). Fixed for the
3316    /// session — the worker's admission owns the sampler identity.
3317    sampling: Option<crate::spec::SpecSampling>,
3318    /// Philox event counters, session-owned so randomness never repeats across bursts
3319    /// (the frspec session-continuity law): `sctr` = device sampling events (boundary,
3320    /// draft chain, bonus, residual), `uctr` = host uniforms (selector walk, accept tests).
3321    sctr: u32,
3322    uctr: u32,
3323}
3324
3325impl DsparkSpecSession {
3326    pub fn cache_max_ctx(&self) -> usize {
3327        self.max_ctx
3328    }
3329    pub fn finished(&self) -> bool {
3330        self.done
3331    }
3332    pub fn pos(&self) -> usize {
3333        self.cache.pos
3334    }
3335}
3336
3337impl crate::hybrid::HybridModel {
3338    /// Turn-1 prime: trunk prefill with taps armed + chunked ctx ingest into the draft KV.
3339    /// Mirrors generate_spec_dspark's prime block exactly (chunk offsets via sink.base are
3340    /// handled inside prime_cache's tick loop; the 256-row ingest chunks match the bin arm).
3341    pub fn dspark_spec_session_new(
3342        &self,
3343        e: &Engine,
3344        draft: &DflashDraft,
3345        prompt: &[u32],
3346        ctx_cap: usize,
3347        sampling: Option<crate::spec::SpecSampling>,
3348    ) -> Result<DsparkSpecSession, Box<dyn std::error::Error>> {
3349        use crate::cache::{Cache, DflashTapSink};
3350        assert!(
3351            self.cfg.gemma4.is_none(),
3352            "gemma4 targets use the assistant-drafter route; dspark is the qwen-hybrid arm"
3353        );
3354        // Penalties are OUT of this route's sampled-admission scope (the worker's gate
3355        // keeps penalized requests on the plain path); a config that smuggles them in
3356        // would silently sample from an unpenalized target — refuse loudly instead.
3357        if let Some(sp) = sampling.as_ref().filter(|s| s.temp > 0.0) {
3358            if sp.penalty_repeat != 1.0 || sp.penalty_freq != 0.0 || sp.penalty_present != 0.0 {
3359                return Err(
3360                    "dspark sampled admission excludes penalties (admission gate \
3361                            keeps penalized requests on the plain path)"
3362                        .into(),
3363                );
3364            }
3365        }
3366        let n_embd = self.cfg.n_embd as usize;
3367        let c = &draft.cfg;
3368        assert_eq!(n_embd, c.hidden, "draft hidden must match target n_embd");
3369        let b = c.block_size;
3370        let n_taps = c.target_layer_ids.len();
3371        // The dspark round is windowless: every position the session will ever hold must
3372        // fit the draft window. Clamp the session ctx to it and refuse prompts that
3373        // cannot take even one round — admission falls back to the plain path.
3374        // DFlash2 rounds implement the reference's symmetric sliding window
3375        // (sdpa_naive_w), so its sessions take the full ctx cap.
3376        let max_ctx = if draft.dflash2.is_some() {
3377            ctx_cap
3378        } else {
3379            ctx_cap.min(c.sliding_window)
3380        };
3381        if prompt.len() + b + 8 > max_ctx {
3382            return Err(format!(
3383                "dspark session needs {} ctx (prompt {} + block {b} + 8), cap {max_ctx}",
3384                prompt.len() + b + 8,
3385                prompt.len()
3386            )
3387            .into());
3388        }
3389        let mut cache = Cache::new(e, &self.cfg, max_ctx)?;
3390        let tp = prompt.len();
3391        cache.dflash_taps = Some(DflashTapSink {
3392            layer_ids: c.target_layer_ids.clone(),
3393            buf: e.uninit(tp * n_taps * n_embd)?,
3394            hidden: n_embd,
3395            t: tp,
3396            base: 0,
3397        });
3398        let (logits, _h_seed, _hiddens) = self.prime_cache(e, prompt, &mut cache, 0)?;
3399        // Boundary token: greedy argmax (byte contract) or the request's own filtered
3400        // draw through the session Philox stream (the frspec boundary composition).
3401        let mut sctr0 = 0u32;
3402        let last = match sampling.as_ref().filter(|s| s.temp > 0.0) {
3403            Some(sp) => {
3404                crate::spec::sample_boundary_token(e, &logits, sp, &[], &mut sctr0, "dspark-prime")?
3405            }
3406            None => crate::forward::argmax(&logits) as u32,
3407        };
3408        let mut dkv = DflashKv::new(e, &draft.cfg, max_ctx)?;
3409        {
3410            let taps = cache.dflash_taps.take().unwrap();
3411            let n_taps_h = n_taps * n_embd;
3412            let mut r0 = 0usize;
3413            while r0 < tp {
3414                let t_c = (tp - r0).min(256);
3415                let tv = e.view(&taps.buf, tp * n_taps_h);
3416                let win = tv.slice(r0 * n_taps_h..(r0 + t_c) * n_taps_h);
3417                let mut chunk = e.uninit(t_c * n_taps_h)?;
3418                e.copy_view_into(&mut chunk, 0, &win, t_c * n_taps_h)?;
3419                let f = draft.ctx_features(e, &chunk, t_c)?;
3420                let pos_c: Vec<i32> = ((r0 as i32)..(r0 + t_c) as i32).collect();
3421                draft.ingest_ctx(e, &mut dkv, &f, &pos_c, t_c)?;
3422                r0 += t_c;
3423            }
3424        }
3425        e.stream().synchronize()?;
3426        // Verify carries [anchor, drafts] = up to n_drafts+1 rows (harvest-dependent;
3427        // DSPARK-POSTMORTEM-20260820.md; family-keyed for DFlash2, else checkpoint
3428        // strategy census).
3429        let nd = DsparkHarvest::for_draft(draft).n_drafts(b);
3430        let vt_cap: usize = std::env::var("MEMRA_DFLASH_VERIFY_T")
3431            .ok()
3432            .and_then(|v| v.parse().ok())
3433            .unwrap_or(nd + 1)
3434            .clamp(2, nd + 1);
3435        Ok(DsparkSpecSession {
3436            cache,
3437            dkv,
3438            last,
3439            ctx_len: tp,
3440            vt: vt_cap,
3441            rounds: 0,
3442            max_ctx,
3443            done: false,
3444            snapb: None,
3445            snapb_off: !crate::spec::state_copy_batch_on(),
3446            sampling,
3447            sctr: sctr0,
3448            uctr: 0,
3449        })
3450    }
3451
3452    /// One scheduler burst: dspark rounds until >= `burst_target` tokens are committed,
3453    /// EOS lands, or the ctx cap is reached. Returns (tokens, drafted, accepted) for this
3454    /// burst — the worker clamps the public slice (engine overshoot within a round stays
3455    /// in the session cache, exactly the gemma-burst contract).
3456    pub fn dspark_spec_session_burst(
3457        &self,
3458        e: &Engine,
3459        draft: &DflashDraft,
3460        sess: &mut DsparkSpecSession,
3461        burst_target: usize,
3462        eos: &[u32],
3463    ) -> Result<(Vec<u32>, usize, usize), Box<dyn std::error::Error>> {
3464        use crate::cache::DflashTapSink;
3465        let n_embd = self.cfg.n_embd as usize;
3466        let c = &draft.cfg;
3467        let b = c.block_size;
3468        let n_taps = c.target_layer_ids.len();
3469        let n_vocab = self.output.out_features();
3470        // Harvest convention (DSPARK-POSTMORTEM-20260820.md) — identical to the bin arm
3471        // (family-keyed for DFlash2, else checkpoint strategy census; owner-ratified
3472        // flip 2026-08-20).
3473        let harvest = DsparkHarvest::for_draft(draft);
3474        let nd = harvest.n_drafts(b);
3475        let r0 = harvest.first_row();
3476        let vt_cap: usize = std::env::var("MEMRA_DFLASH_VERIFY_T")
3477            .ok()
3478            .and_then(|v| v.parse().ok())
3479            .unwrap_or(nd + 1)
3480            .clamp(2, nd + 1);
3481        let adapt = std::env::var("MEMRA_DFLASH_ADAPT").as_deref() != Ok("0");
3482        // Verify-window policy (H4, DSPARK-POSTMORTEM-20260820.md) — identical to the
3483        // bin arm: default = confidence-slot tau=.5 on a head-carrying checkpoint
3484        // (owner-ratified flip 2026-08-20); head-less (incl. the DFlash2 family) and
3485        // ADAPT=0 keep the ladder.
3486        let vt_policy = DsparkVtPolicy::resolve(draft.confidence.is_some());
3487        if vt_policy.is_confidence() {
3488            assert!(
3489                draft.confidence.is_some(),
3490                "MEMRA_DSPARK_VT={vt_policy:?} needs a checkpoint with an accept-rate \
3491                 head (confidence_head.* absent in this export)"
3492            );
3493        }
3494        // SAMPLED ADMISSION (T>0): session-fixed config; counters live on the session so
3495        // randomness never repeats across bursts. None/temp==0 = the greedy route.
3496        let sp_on: Option<crate::spec::SpecSampling> = sess.sampling.filter(|s| s.temp > 0.0);
3497        let mut out: Vec<u32> = Vec::with_capacity(burst_target + b);
3498        let mut drafted = 0usize;
3499        let mut accepted_n = 0usize;
3500        // Engine-bundle slice 2 — identical to the bin arm: deferred chain readback under
3501        // the stash arm with a resident embed table (ladder policy only).
3502        let defer_rb = crate::spec::dspark_defer_readback_on() && !vt_policy.is_confidence();
3503        let (embd_qt, embd_rb) = self.embd.qt_and_row_bytes(n_embd);
3504        let embd_gpu = if !defer_rb || crate::spec::spec_host_embd() {
3505            None
3506        } else {
3507            Some(
3508                self.embd_gpu
3509                    .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload")),
3510            )
3511        };
3512        'outer: while out.len() < burst_target && !sess.done {
3513            let start = sess.cache.pos;
3514            if start + nd + 1 > sess.max_ctx {
3515                sess.done = true;
3516                break;
3517            }
3518            sess.rounds += 1;
3519            let mut vt = sess.vt;
3520            // ---- draft: block = [last, MASK x b-1] (identical to the bin arm) ----
3521            e.set_verify_exact(true);
3522            let mut block: Vec<u32> = vec![c.mask_token_id; b];
3523            block[0] = sess.last;
3524            let noise = e.htod(&self.embd.gather(n_embd, &block))?;
3525            let pos_block: Vec<i32> = ((start as i32)..(start + b) as i32).collect();
3526            let dh = draft.forward_round(e, &mut sess.dkv, &noise, &pos_block)?;
3527            // Harvest: logits over rows r0..r0+nd (see the bin arm / the postmortem).
3528            let mut rows = e.uninit(nd * n_embd)?;
3529            {
3530                let dv = e.view(&dh, b * n_embd);
3531                let src = dv.slice(r0 * n_embd..(r0 + nd) * n_embd);
3532                e.copy_view_into(&mut rows, 0, &src, nd * n_embd)?;
3533            }
3534            let mut dl = e.matmul(&self.output, &rows, nd)?;
3535            // Family/sampling-keyed proposal — identical to the bin arm (see there for
3536            // the program law: sampled records the true q, DFlash2 rides the selector,
3537            // the markov/plain greedy chain keeps the slice-2 deferral). Confidence
3538            // policy: stash markov prev-token embeddings d2d during the chain, one host
3539            // readback after — identical to the bin arm.
3540            let want_conf_emb = vt_policy.is_confidence()
3541                && draft.confidence.as_ref().is_some_and(|ch| ch.with_markov);
3542            let mut conf_emb: Option<CudaSlice<f32>> = match (&draft.markov, want_conf_emb) {
3543                (Some(mk), true) => Some(e.uninit(nd * mk.rank)?),
3544                (None, true) => unreachable!(
3545                    "with_markov confidence head without a markov table — the loader forbids it"
3546                ),
3547                _ => None,
3548            };
3549            let mut cand: Vec<u32> = Vec::with_capacity(nd + 1);
3550            let mut prop: Option<DsparkDraftSample> = None;
3551            let mut chain_dev: Option<CudaSlice<u32>> = None;
3552            // Slice 2: arm choice read before the chain readback (see the bin arm; the
3553            // serve arm has no CKPT_GATE oracle — the bin arm carries it).
3554            let ckpt_on = std::env::var("MEMRA_DSPARK_CKPT").as_deref() != Ok("0");
3555            let mut deferred = false;
3556            if let Some(sp) = sp_on.as_ref() {
3557                // SAMPLED proposal (family-keyed; identical to the bin arm).
3558                let (tail, ds) = draft.dspark_propose_sampled(
3559                    e,
3560                    &mut dl,
3561                    &rows,
3562                    nd,
3563                    n_vocab,
3564                    sess.last,
3565                    sp,
3566                    &mut sess.sctr,
3567                    &mut sess.uctr,
3568                    conf_emb.as_mut(),
3569                )?;
3570                e.set_verify_exact(false);
3571                cand.push(sess.last);
3572                cand.extend_from_slice(&tail);
3573                prop = Some(ds);
3574            } else if draft.dflash2.is_some() {
3575                // DFlash2: candidate path selector replaces the markov chain
3576                // (identical to the bin arm).
3577                let path = draft.dflash2_propose_greedy(e, &dl, &rows, nd, n_vocab, sess.last)?;
3578                e.set_verify_exact(false);
3579                cand.push(sess.last);
3580                cand.extend_from_slice(&path);
3581            } else {
3582                let markov_on = std::env::var("MEMRA_DFLASH_MARKOV").as_deref() != Ok("0");
3583                let mut chain_d = e.stream().alloc_zeros::<u32>(nd + 1)?;
3584                if let (Some(mk), true) = (&draft.markov, markov_on) {
3585                    e.set_u32_one(&mut chain_d, sess.last)?;
3586                    for k in 0..nd {
3587                        let mut f = e.uninit(mk.rank)?;
3588                        e.gather_row_bf16(&mk.w1_bf16, &chain_d, k, &mut f, mk.rank)?;
3589                        if let Some(ce) = conf_emb.as_mut() {
3590                            let fv = e.view(&f, mk.rank);
3591                            e.copy_view_into(ce, k * mk.rank, &fv, mk.rank)?;
3592                        }
3593                        let bias = e.matmul(&mk.w2, &f, 1)?;
3594                        e.add_row_inplace(&mut dl, &bias, n_vocab, k * n_vocab)?;
3595                        e.argmax_token_device_col(&dl, k, n_vocab, &mut chain_d, k + 1)?;
3596                    }
3597                } else {
3598                    if want_conf_emb {
3599                        // chain_d[0] must carry the anchor — slot 0's prev token.
3600                        e.set_u32_one(&mut chain_d, sess.last)?;
3601                    }
3602                    for i in 0..nd {
3603                        if let (Some(ce), Some(mk)) = (conf_emb.as_mut(), &draft.markov) {
3604                            let mut f = e.uninit(mk.rank)?;
3605                            e.gather_row_bf16(&mk.w1_bf16, &chain_d, i, &mut f, mk.rank)?;
3606                            let fv = e.view(&f, mk.rank);
3607                            e.copy_view_into(ce, i * mk.rank, &fv, mk.rank)?;
3608                        }
3609                        e.argmax_token_device_col(&dl, i, n_vocab, &mut chain_d, i + 1)?;
3610                    }
3611                }
3612                e.set_verify_exact(false);
3613                deferred = embd_gpu.is_some() && ckpt_on;
3614                chain_dev = Some(chain_d);
3615            }
3616            // ---- H4 confidence window: size THIS round's verify from the head ----
3617            if vt_policy.is_confidence() {
3618                let ch = draft.confidence.as_ref().expect("asserted at burst entry");
3619                let (rows_h, emb_h) = match conf_emb.as_ref() {
3620                    Some(ce) => {
3621                        let (a, b2) = e.dtoh_pair(&rows, ce)?;
3622                        (a, Some(b2))
3623                    }
3624                    None => (e.dtoh(&rows)?, None),
3625                };
3626                let rank = draft.markov.as_ref().map(|m| m.rank).unwrap_or(0);
3627                let mut raws = Vec::with_capacity(nd);
3628                for k in 0..nd {
3629                    let hrow = &rows_h[k * n_embd..(k + 1) * n_embd];
3630                    let emb = emb_h.as_ref().map(|eh| &eh[k * rank..(k + 1) * rank]);
3631                    raws.push(ch.raw_score(hrow, emb));
3632                }
3633                vt = vt_policy
3634                    .size_window(&raws, vt_cap)
3635                    .expect("confidence policies always size the window");
3636            }
3637            // Non-deferred greedy chain readback (the sampled and DFlash2 proposals
3638            // built `cand` at the walk; deferred rounds build it after the merged
3639            // readback — bytes identical, chain_d written before either sync).
3640            if let Some(chain_d) = chain_dev.as_ref() {
3641                if !deferred {
3642                    let chain = e.dtoh_u32(chain_d)?;
3643                    cand.push(sess.last);
3644                    cand.extend_from_slice(&chain[1..]);
3645                }
3646            }
3647
3648            // ---- snapshot, then verify t=vt (ckpt stash default; oracle arms kept) ----
3649            // Slice 1: batched snap (see DsparkSnapBatch) with the legacy per-layer
3650            // snapshot as the kill-switch / non-uniform fallback.
3651            let mut snap_legacy: Option<crate::cache::CacheSnapshot> = None;
3652            if !sess.snapb_off && sess.snapb.is_none() {
3653                sess.snapb = DsparkSnapBatch::new(e, &sess.cache)?;
3654                sess.snapb_off = sess.snapb.is_none();
3655            } else if let Some(sb) = sess.snapb.as_mut() {
3656                sb.refresh(e, &sess.cache)?;
3657            }
3658            let snap: &crate::cache::CacheSnapshot = match sess.snapb.as_ref() {
3659                Some(sb) => &sb.snap,
3660                None => {
3661                    snap_legacy = Some(sess.cache.snapshot(e)?);
3662                    snap_legacy.as_ref().unwrap()
3663                }
3664            };
3665            let _ = &snap_legacy;
3666            sess.cache.dflash_taps = Some(DflashTapSink {
3667                layer_ids: c.target_layer_ids.clone(),
3668                buf: e.uninit(vt * n_taps * n_embd)?,
3669                hidden: n_embd,
3670                t: vt,
3671                base: 0,
3672            });
3673            let (vam, tl, vck) = if sp_on.is_some() {
3674                // SAMPLED: raw verify logits for the rejection walk (bin-arm twin).
3675                if ckpt_on {
3676                    let (tl, vck) =
3677                        self.dspark_verify_t_logits_ckpt(e, &cand[..vt], start, &mut sess.cache)?;
3678                    (Vec::new(), Some(tl), Some(vck))
3679                } else {
3680                    (
3681                        Vec::new(),
3682                        Some(self.dspark_verify_t_logits(
3683                            e,
3684                            &cand[..vt],
3685                            start,
3686                            &mut sess.cache,
3687                        )?),
3688                        None,
3689                    )
3690                }
3691            } else if deferred {
3692                // Slice 2: device-token verify + ONE merged readback (see the bin arm).
3693                let chain_d = chain_dev.as_ref().expect("deferred implies greedy chain");
3694                let g = embd_gpu.expect("deferred implies resident embed");
3695                // Slice 3 stays bin-arm-only for now: session lifetime (per-request
3696                // caches, capture storms) needs the cache-reuse-pool design first.
3697                let (am_d, vck) = self.dspark_verify_t_am_ckpt_dev(
3698                    e,
3699                    chain_d,
3700                    vt,
3701                    start,
3702                    &mut sess.cache,
3703                    (g, embd_qt, embd_rb),
3704                    None,
3705                )?;
3706                let ch = e.stream().clone_dtoh(chain_d)?;
3707                let am = e.stream().clone_dtoh(&am_d)?;
3708                e.stream().synchronize()?;
3709                cand.push(sess.last);
3710                cand.extend_from_slice(&ch[1..]);
3711                (am, None, Some(vck))
3712            } else if ckpt_on {
3713                let (vam, vck) =
3714                    self.dspark_verify_t_am_ckpt(e, &cand[..vt], start, &mut sess.cache)?;
3715                (vam, None, Some(vck))
3716            } else {
3717                (
3718                    self.dspark_verify_t_am(e, &cand[..vt], start, &mut sess.cache)?,
3719                    None,
3720                    None,
3721                )
3722            };
3723            let taps = sess.cache.dflash_taps.take().unwrap();
3724
3725            // ---- accept ----
3726            let (m, next) = match (sp_on.as_ref(), tl.as_ref()) {
3727                (Some(sp), Some(tl)) => dspark_accept_sampled(
3728                    e,
3729                    tl,
3730                    &cand,
3731                    vt,
3732                    n_vocab,
3733                    &dl,
3734                    prop.as_ref()
3735                        .expect("sampled round without a proposal record"),
3736                    sp,
3737                    &mut sess.sctr,
3738                    &mut sess.uctr,
3739                )?,
3740                _ => {
3741                    let m = dspark_accept_prefix(&cand, &vam, vt);
3742                    (m, vam[m])
3743                }
3744            };
3745            drafted += vt - 1;
3746            accepted_n += m;
3747            out.push(sess.last);
3748            if eos.contains(&sess.last) {
3749                sess.done = true;
3750                break 'outer;
3751            }
3752            for &dt in &cand[1..=m] {
3753                out.push(dt);
3754                if eos.contains(&dt) {
3755                    sess.done = true;
3756                    break 'outer;
3757                }
3758            }
3759
3760            // ---- commit/rollback (stash arm default; replay oracle kept) ----
3761            let keep = m + 1;
3762            if keep < vt {
3763                if let Some(vck) = vck.as_ref() {
3764                    self.dspark_commit_prefix(e, &mut sess.cache, snap, vck, keep)?;
3765                } else {
3766                    crate::pp::restore_cache_checkpoint(e, &self.cfg, None, &mut sess.cache, snap)?;
3767                    debug_assert_eq!(sess.cache.pos, start, "rollback landed off the round start");
3768                    let ram = self.dspark_verify_t_am(e, &cand[..keep], start, &mut sess.cache)?;
3769                    if sp_on.is_none() {
3770                        // greedy-only oracle; the sampled arm replays to rebuild state.
3771                        debug_assert_eq!(
3772                            &ram[..],
3773                            &vam[..keep],
3774                            "prefix replay must reproduce the verify argmaxes"
3775                        );
3776                    }
3777                }
3778            }
3779
3780            // ---- ingest the kept rows' ctx features into the draft KV ----
3781            {
3782                let tv = e.view(&taps.buf, vt * n_taps * n_embd);
3783                let keep_view = tv.slice(0..keep * n_taps * n_embd);
3784                let mut kept = e.uninit(keep * n_taps * n_embd)?;
3785                e.copy_view_into(&mut kept, 0, &keep_view, keep * n_taps * n_embd)?;
3786                let f = draft.ctx_features(e, &kept, keep)?;
3787                let pos_k: Vec<i32> =
3788                    ((sess.ctx_len as i32)..(sess.ctx_len + keep) as i32).collect();
3789                draft.ingest_ctx(e, &mut sess.dkv, &f, &pos_k, keep)?;
3790                sess.ctx_len += keep;
3791            }
3792            sess.last = next;
3793            // Ladder update only — the confidence policies recompute vt from the
3794            // head every round, post-draft pre-verify; their carry just keeps
3795            // observability (sess.vt = the last confidence-sized window).
3796            if vt_policy.is_confidence() {
3797                sess.vt = vt;
3798            } else if adapt {
3799                sess.vt = (m + 2).clamp(3, vt_cap);
3800            }
3801        }
3802        Ok((out, drafted, accepted_n))
3803    }
3804}
3805
3806// ================= Harvest-convention gate (CPU; DSPARK-POSTMORTEM-20260820.md) =========
3807// The parity oracle is row-count-agnostic (it reproduces the markov MODULE on whatever
3808// rows it is fed) and the E2E gate is harvest-independent (verify-side truth), so
3809// NEITHER can catch a wrong row->position mapping — that blindness is how the q38
3810// misalignment shipped. These tests pin the convention itself as logic the round
3811// consumes, so a mutation back to the mask-fill harvest under the Dspark variant fails
3812// HERE, naming the convention.
3813#[cfg(test)]
3814mod dflash2_tests {
3815    use super::{DsparkHarvest, dflash2_walk_greedy, dflash2_walk_sampled, rejection_accept_len};
3816
3817    /// f32 -> bf16 bytes (truncation; test values are bf16-exact small integers).
3818    fn bf16(vals: &[f32]) -> Vec<u8> {
3819        vals.iter()
3820            .flat_map(|v| ((v.to_bits() >> 16) as u16).to_le_bytes())
3821            .collect()
3822    }
3823
3824    const V: usize = 8; // test vocab
3825    const R: usize = 2; // selector rank
3826    const K: usize = 2; // top_k
3827
3828    /// Codebooks for the chain tests: pred rows are one-hot-ish, succ rows chosen so
3829    /// the slot-1 winner FLIPS with the slot-0 choice.
3830    fn books() -> (Vec<u8>, Vec<u8>) {
3831        let mut pred = vec![0f32; V * R];
3832        pred[0] = 1.0; // tok 0: [1, 0]  (the anchor)
3833        pred[1 * R + 1] = 1.0; // tok 1: [0, 1]
3834        pred[2 * R] = 1.0; // tok 2: [1, 0]
3835        let mut succ = vec![0f32; V * R];
3836        succ[1 * R] = 2.0; // tok 1: [2, 0]
3837        succ[2 * R + 1] = 5.0; // tok 2: [0, 5]
3838        succ[3 * R + 1] = 3.0; // tok 3: [0, 3]
3839        succ[4 * R] = 10.0; // tok 4: [10, 0]
3840        (bf16(&pred), bf16(&succ))
3841    }
3842
3843    #[test]
3844    fn selector_walk_is_a_chain_not_per_slot_argmax() {
3845        let (pred, succ) = books();
3846        // slot 0 candidates {1, 2}, slot 1 candidates {3, 4}; hproj all-ones.
3847        let cand: Vec<u32> = vec![1, 2, 3, 4];
3848        let hproj = vec![1.0f32; 2 * R];
3849        // Anchor 0 (pred [1,0]): slot 0 scores = <[1,0],succ> -> tok1: 2, tok2: 0
3850        // -> picks 1. Slot 1 must then walk from pred[1]=[0,1]: tok3 scores 3,
3851        // tok4 scores 0 -> picks 3. A mutation that seeds every slot from the ANCHOR
3852        // (pred[0]=[1,0]) scores tok3: 0 / tok4: 10 and picks 4 instead — the chain
3853        // IS the semantics (reference CandidateSelector.select: `predecessor` is the
3854        // previously CHOSEN candidate, seeded by anchor_ids).
3855        let path = dflash2_walk_greedy(&pred, &succ, V, R, K, &[0.0; 4], &cand, &hproj, 0, 2);
3856        assert_eq!(
3857            path,
3858            vec![1, 3],
3859            "walk must seed slot p from slot p-1's CHOSEN candidate \
3860             (z-lab model.py CandidateSelector.select)"
3861        );
3862    }
3863
3864    #[test]
3865    fn selector_walk_unary_term_participates() {
3866        let (pred, succ) = books();
3867        let cand: Vec<u32> = vec![1, 2, 3, 4];
3868        let hproj = vec![1.0f32; 2 * R];
3869        // unary +10 on slot-0 candidate 2 overrides the bilinear 2-vs-0 margin;
3870        // the chain then walks from pred[2]=[1,0] and slot 1 flips to tok 4.
3871        let path = dflash2_walk_greedy(
3872            &pred,
3873            &succ,
3874            V,
3875            R,
3876            K,
3877            &[0.0, 10.0, 0.0, 0.0],
3878            &cand,
3879            &hproj,
3880            0,
3881            2,
3882        );
3883        assert_eq!(
3884            path,
3885            vec![2, 4],
3886            "score = unary + bilinear (reference: `unary[:, position] + einsum(...)`); \
3887             dropping the unary term picks tok 1 here"
3888        );
3889    }
3890
3891    #[test]
3892    fn selector_walk_hidden_gate_participates() {
3893        let (pred, succ) = books();
3894        let cand: Vec<u32> = vec![1, 2, 3, 4];
3895        // hproj [0, .] zeroes the pred[0]=[1,0] gate for slot 0: tok1's bilinear 2
3896        // vanishes, and the unary tiebreak (+1 on tok2) decides. The chain from tok2
3897        // (pred [1,0]) with slot-1 hproj [1,1] then picks tok4 (10 vs 0).
3898        let hproj = vec![0.0f32, 1.0, 1.0, 1.0];
3899        let path = dflash2_walk_greedy(
3900            &pred,
3901            &succ,
3902            V,
3903            R,
3904            K,
3905            &[0.0, 1.0, 0.0, 0.0],
3906            &cand,
3907            &hproj,
3908            0,
3909            2,
3910        );
3911        assert_eq!(
3912            path,
3913            vec![2, 4],
3914            "the bilinear gate is pred_row .* HIDDEN_PROJECTION (reference: \
3915             `predecessor_codebook(predecessor) * hidden[:, position]`); ignoring \
3916             hproj leaves tok1's margin standing"
3917        );
3918    }
3919
3920    #[test]
3921    fn dflash2_harvest_is_census_keyed() {
3922        // DFlash2 is mask-fill BY CONSTRUCTION (reference dflash_generate harvests
3923        // rows 1-verify_size:; card: "7 draft tokens per verification step").
3924        assert_eq!(
3925            DsparkHarvest::for_family_value(true, None, false),
3926            DsparkHarvest::Dflash
3927        );
3928        assert_eq!(
3929            DsparkHarvest::for_family_value(true, Some("dflash"), false),
3930            DsparkHarvest::Dflash
3931        );
3932        // The family key BEATS the strategy census: a (hypothetical) DFlash2 export
3933        // whose config also strategy-censuses dspark still harvests mask-fill.
3934        assert_eq!(
3935            DsparkHarvest::for_family_value(true, None, true),
3936            DsparkHarvest::Dflash
3937        );
3938        // An env override to the SHIFTED harvest contradicts the census — REFUSE,
3939        // never re-key (the postmortem's misalignment class in reverse).
3940        assert!(
3941            std::panic::catch_unwind(|| DsparkHarvest::for_family_value(
3942                true,
3943                Some("dspark"),
3944                false
3945            ))
3946            .is_err(),
3947            "MEMRA_DSPARK_HARVEST=dspark on a DFlash2 checkpoint must refuse"
3948        );
3949        // Non-DFlash2 checkpoints ride the strategy-keyed resolution (env wins).
3950        assert_eq!(
3951            DsparkHarvest::for_family_value(false, Some("dspark"), false),
3952            DsparkHarvest::Dspark
3953        );
3954        assert_eq!(
3955            DsparkHarvest::for_family_value(false, None, false),
3956            DsparkHarvest::Dflash
3957        );
3958        assert_eq!(
3959            DsparkHarvest::for_family_value(false, None, true),
3960            DsparkHarvest::Dspark,
3961            "unset env on a DSPARK-strategy export must keep the ratified census flip"
3962        );
3963    }
3964
3965    // ============ SAMPLED ADMISSION (T>0) gates — lane/dspark-sampled-admission-20260820 =
3966    // The device kernels are oracled by sample_check (filter_stats/gumbel/residual arms);
3967    // these pin the HOST math the route ships — the selector's sampled walk, the accept
3968    // rule, and the round COMPOSITION (accept + residual + bonus must reproduce the target
3969    // distribution p exactly; a mis-composition leaves every kernel individually correct,
3970    // which is why the composition arm exists — sample_check arm 6's lesson).
3971
3972    #[test]
3973    fn sampled_walk_tiny_temp_matches_greedy() {
3974        // T->0 continuity: at tiny temperature the candidate softmax concentrates on the
3975        // argmax and the sampled walk must reproduce the greedy chain token-for-token
3976        // (the frspec gate-(1) shape). Same fixture as the chain test.
3977        let (pred, succ) = books();
3978        let cand: Vec<u32> = vec![1, 2, 3, 4];
3979        let hproj = vec![1.0f32; 2 * R];
3980        let greedy = dflash2_walk_greedy(&pred, &succ, V, R, K, &[0.0; 4], &cand, &hproj, 0, 2);
3981        let mut u = || 0.5f32;
3982        let (path, q_chosen, q_rows) = dflash2_walk_sampled(
3983            &pred, &succ, V, R, K, &[0.0; 4], &cand, &hproj, 0, 2, 1e-6, &mut u,
3984        );
3985        assert_eq!(
3986            path, greedy,
3987            "tiny-T sampled walk must equal the greedy chain"
3988        );
3989        assert_eq!(q_rows.len(), 2 * K);
3990        for (p, &q) in path.iter().zip(&q_chosen) {
3991            let _ = p;
3992            assert!(
3993                q > 0.999,
3994                "tiny-T chosen-candidate prob must be ~1, got {q}"
3995            );
3996        }
3997    }
3998
3999    #[test]
4000    fn sampled_walk_records_the_distribution_it_samples() {
4001        // The recorded q IS the proposal: per slot the q_rows sum to ~1, q_chosen is the
4002        // row value at the drawn candidate, and the CDF walk picks the candidate whose
4003        // cumulative bracket contains the uniform.
4004        let (pred, succ) = books();
4005        let cand: Vec<u32> = vec![1, 2, 3, 4];
4006        let hproj = vec![1.0f32; 2 * R];
4007        // slot-0 scores at anchor 0: tok1 = 2.0, tok2 = 0.0; at T=2.0 the softmax is
4008        // e^1/(e^1+e^0) ~= 0.731 for tok1.
4009        let q1 = (1f64.exp() / (1f64.exp() + 1.0)) as f32;
4010        for (u0, want0) in [(q1 - 0.01, 1u32), (q1 + 0.01, 2u32)] {
4011            let mut seq = vec![u0, 0.0f32].into_iter();
4012            let mut u = move || seq.next().unwrap();
4013            let (path, q_chosen, q_rows) = dflash2_walk_sampled(
4014                &pred, &succ, V, R, K, &[0.0; 4], &cand, &hproj, 0, 2, 2.0, &mut u,
4015            );
4016            assert_eq!(
4017                path[0], want0,
4018                "CDF walk must place u={u0} in the right candidate bracket"
4019            );
4020            let row0: f32 = q_rows[..K].iter().sum();
4021            assert!(
4022                (row0 - 1.0).abs() < 1e-5,
4023                "slot-0 q must sum to 1, got {row0}"
4024            );
4025            let ci = cand[..K].iter().position(|&c| c == path[0]).unwrap();
4026            assert_eq!(
4027                q_chosen[0], q_rows[ci],
4028                "q_chosen must be the recorded row prob of the drawn candidate"
4029            );
4030            assert!(
4031                (q_rows[0] - q1).abs() < 1e-4,
4032                "slot-0 tok1 prob must be softmax(scores/T), got {} want {q1}",
4033                q_rows[0]
4034            );
4035        }
4036    }
4037
4038    #[test]
4039    fn sampled_walk_chains_the_drawn_candidate() {
4040        // The chain conditions on the DRAWN candidate, not the argmax: forcing the
4041        // low-prob slot-0 candidate (tok 2) flips slot 1's winner (tok 4 over tok 3),
4042        // exactly like the greedy chain test — a walk that seeds every slot from the
4043        // anchor (or the argmax) fails here.
4044        let (pred, succ) = books();
4045        let cand: Vec<u32> = vec![1, 2, 3, 4];
4046        let hproj = vec![1.0f32; 2 * R];
4047        let mut seq = vec![0.99f32, 0.01].into_iter();
4048        let mut u = move || seq.next().unwrap();
4049        let (path, _, _) = dflash2_walk_sampled(
4050            &pred, &succ, V, R, K, &[0.0; 4], &cand, &hproj, 0, 2, 2.0, &mut u,
4051        );
4052        assert_eq!(path[0], 2, "u=0.99 must draw the low-prob candidate");
4053        assert_eq!(
4054            path[1], 4,
4055            "slot 1 must walk from pred[2] (the DRAWN token), which scores tok4 at 10 \
4056             — chaining from the anchor or the argmax picks tok3"
4057        );
4058    }
4059
4060    #[test]
4061    fn rejection_accept_walk_is_the_leviathan_rule() {
4062        // accept while u*q < p, strict, prefix-stop at the first reject.
4063        assert_eq!(
4064            rejection_accept_len(&[0.5, 0.5], &[0.5, 0.5], &[0.9, 0.9]),
4065            2
4066        );
4067        assert_eq!(
4068            rejection_accept_len(&[0.5, 0.5], &[0.5, 0.5], &[1.0, 0.0]),
4069            0
4070        );
4071        // u*q == p is a REJECT (strict <) — the frspec test byte-for-byte.
4072        assert_eq!(rejection_accept_len(&[0.25], &[0.5], &[0.5]), 0);
4073        // q == 0 with p > 0 accepts unconditionally (the skey exactness signature).
4074        assert_eq!(rejection_accept_len(&[1e-6], &[0.0], &[0.999]), 1);
4075        // prefix stop: slot 1 rejects, slot 2 never tested.
4076        assert_eq!(
4077            rejection_accept_len(&[0.9, 0.0, 0.9], &[0.1, 0.9, 0.1], &[0.5, 0.5, 0.5]),
4078            1
4079        );
4080    }
4081
4082    // ---- round composition: the committed-token distribution must equal the target p ----
4083    // CPU mirror of the shipped rule for the FIRST post-anchor slot: draft x ~ q, accept
4084    // iff u*q(x) < p(x) (rejection_accept_len — the shipped fn), else commit a residual
4085    // sample ~ norm(max(0, p - q)). The marginal of the committed token is exactly p —
4086    // for ANY q — which is the whole correctness claim of the route's sampled admission.
4087
4088    fn tv(a: &[f64], b: &[f64]) -> f64 {
4089        a.iter().zip(b).map(|(x, y)| (x - y).abs()).sum::<f64>() / 2.0
4090    }
4091
4092    /// One composed trial with an injectable accept rule; returns the committed token.
4093    fn compose_once(
4094        p: &[f32],
4095        q: &[f32],
4096        u_draw: f32,
4097        u_accept: f32,
4098        u_resid: f32,
4099        invert_accept: bool,
4100        skip_q_in_residual: bool,
4101    ) -> usize {
4102        let n = p.len();
4103        // draft ~ q (CDF walk, the walk_sampled convention)
4104        let mut acc = 0f64;
4105        let mut x = n - 1;
4106        for (i, &qi) in q.iter().enumerate() {
4107            acc += qi as f64;
4108            if (u_draw as f64) < acc {
4109                x = i;
4110                break;
4111            }
4112        }
4113        let accepted = if invert_accept {
4114            !((u_accept as f64) * (q[x] as f64) < p[x] as f64)
4115        } else {
4116            rejection_accept_len(&p[x..=x], &q[x..=x], &[u_accept]) == 1
4117        };
4118        if accepted {
4119            return x;
4120        }
4121        // residual ~ norm(max(0, p - q)) (the device kernel's fixed-order CDF walk)
4122        let r: Vec<f64> = p
4123            .iter()
4124            .zip(q)
4125            .map(|(&pi, &qi)| {
4126                let qq = if skip_q_in_residual { 0.0 } else { qi as f64 };
4127                (pi as f64 - qq).max(0.0)
4128            })
4129            .collect();
4130        let total: f64 = r.iter().sum();
4131        let mut acc = 0f64;
4132        let target = u_resid as f64 * total;
4133        for (i, &ri) in r.iter().enumerate() {
4134            acc += ri;
4135            if acc >= target && ri > 0.0 {
4136                return i;
4137            }
4138        }
4139        n - 1
4140    }
4141
4142    fn compose_tv(q: &[f32], invert_accept: bool, skip_q_in_residual: bool) -> f64 {
4143        // target p: a spread-out 8-token distribution
4144        let p: Vec<f32> = vec![0.30, 0.22, 0.15, 0.12, 0.09, 0.06, 0.04, 0.02];
4145        let trials = 200_000usize;
4146        let mut counts = vec![0f64; V];
4147        for t in 0..trials {
4148            // three independent uniforms per trial off the host Philox stream
4149            let u_draw = crate::spec::host_u01(7, (t * 3) as u32);
4150            let u_accept = crate::spec::host_u01(7, (t * 3 + 1) as u32);
4151            let u_resid = crate::spec::host_u01(7, (t * 3 + 2) as u32);
4152            counts[compose_once(
4153                &p,
4154                q,
4155                u_draw,
4156                u_accept,
4157                u_resid,
4158                invert_accept,
4159                skip_q_in_residual,
4160            )] += 1.0;
4161        }
4162        let emp: Vec<f64> = counts.iter().map(|c| c / trials as f64).collect();
4163        let pf: Vec<f64> = p.iter().map(|&v| v as f64).collect();
4164        tv(&emp, &pf)
4165    }
4166
4167    #[test]
4168    fn sampled_round_composition_matches_the_target() {
4169        // Monte-Carlo floor at 200k draws over 8 tokens ~ 0.004 TV; bound 0.01.
4170        // (a) full-vocab q (the Rows families' shape), far from p;
4171        let q_rows: Vec<f32> = vec![0.02, 0.04, 0.06, 0.09, 0.12, 0.15, 0.22, 0.30];
4172        // (b) SPARSE candidate-set q (the DFlash2 selector shape: support on 2 of 8).
4173        let q_sparse: Vec<f32> = vec![0.0, 0.7, 0.0, 0.3, 0.0, 0.0, 0.0, 0.0];
4174        for (name, q) in [("rows", &q_rows), ("sparse", &q_sparse)] {
4175            let d = compose_tv(q, false, false);
4176            assert!(
4177                d < 0.01,
4178                "composition[{name}]: committed-token distribution must equal p \
4179                 (TV {d:.4} >= 0.01)"
4180            );
4181        }
4182    }
4183
4184    #[test]
4185    fn composition_teeth_inverted_accept_fails() {
4186        // DECISIVE teeth: the same harness with the accept inequality inverted must
4187        // MISS the target — otherwise the composition gate is vacuous.
4188        let q: Vec<f32> = vec![0.02, 0.04, 0.06, 0.09, 0.12, 0.15, 0.22, 0.30];
4189        let d = compose_tv(&q, true, false);
4190        assert!(
4191            d > 0.05,
4192            "inverted accept rule must fail the composition bound (TV {d:.4})"
4193        );
4194    }
4195
4196    #[test]
4197    fn composition_teeth_residual_without_q_fails() {
4198        // Sampling the reject slot from p instead of norm(max(0, p-q)) double-counts
4199        // the overlap mass min(p,q) — the committed distribution leaves p.
4200        let q: Vec<f32> = vec![0.0, 0.7, 0.0, 0.3, 0.0, 0.0, 0.0, 0.0];
4201        let d = compose_tv(&q, false, true);
4202        assert!(
4203            d > 0.05,
4204            "residual that skips the q subtraction must fail the bound (TV {d:.4})"
4205        );
4206    }
4207}
4208
4209#[cfg(test)]
4210mod dspark_harvest_tests {
4211    use super::{DsparkHarvest, DsparkVtPolicy, dspark_accept_prefix, dspark_strategy_census};
4212
4213    const B: usize = 7; // q38 arm-a block_size
4214
4215    #[test]
4216    fn dspark_strategy_requires_shifted_harvest() {
4217        let h = DsparkHarvest::Dspark;
4218        assert_eq!(
4219            h.first_row(),
4220            0,
4221            "DSPARK-strategy checkpoints (SpecForge OnlineDSparkModel, \
4222             training.strategy=dspark — the q38 arm-a export) supervise ALL rows with \
4223             SHIFTED labels: label_offsets = arange(1, block_size+1), i.e. the ANCHOR \
4224             row's output is draft 1 (specforge/algorithms/common/\
4225             dflash_family_model.py:816; sglang v0.5.17 dspark_draft.py:248,260). \
4226             Harvesting from row 1 re-opens the DSPARK-POSTMORTEM-20260820 slot \
4227             misalignment (accept 2.9 -> 1.43)."
4228        );
4229        assert_eq!(
4230            h.n_drafts(B),
4231            B,
4232            "DSpark harvests gamma = block_size drafts per round (sglang \
4233             dspark_config.py:269, verify_num_draft_tokens = gamma+1); b-1 is the \
4234             DFlash mask-fill count and drops the best-trained slot \
4235             (DSPARK-POSTMORTEM-20260820.md §3-H1)."
4236        );
4237        for row in 0..B {
4238            assert_eq!(
4239                h.trained_offset_of_row(row),
4240                row + 1,
4241                "OnlineDSparkModel trains row k to predict anchor+k+1 \
4242                 (dflash_family_model.py:816); a same-position (mask-fill) mapping \
4243                 here verifies every slot one position early — the postmortem's \
4244                 collapse."
4245            );
4246        }
4247    }
4248
4249    #[test]
4250    fn dflash_strategy_keeps_mask_fill_harvest() {
4251        // Guards the reverse mutation: z-lab dflash checkpoints (the gemma arm) are
4252        // mask-fill — row k FILLS anchor+k, the anchor row is loss-excluded
4253        // (dflash_family_model.py:453-472). Shifting THEM would break the gemma arm.
4254        let h = DsparkHarvest::Dflash;
4255        assert_eq!(h.first_row(), 1, "DFlash drafts start at mask row 1");
4256        assert_eq!(h.n_drafts(B), B - 1, "DFlash harvests block_size-1 drafts");
4257        for row in 1..B {
4258            assert_eq!(h.trained_offset_of_row(row), row);
4259        }
4260    }
4261
4262    #[test]
4263    fn every_candidate_verifies_the_position_its_row_was_trained_for() {
4264        // The round's invariant: draft candidate i (1-based; verified against the
4265        // trunk's prediction for anchor+i) is filled from drafter output row
4266        // first_row + i - 1. Alignment == that row was TRAINED for offset i.
4267        for h in [DsparkHarvest::Dflash, DsparkHarvest::Dspark] {
4268            for i in 1..=h.n_drafts(B) {
4269                let row = h.first_row() + i - 1;
4270                assert_eq!(
4271                    h.trained_offset_of_row(row),
4272                    i,
4273                    "{h:?}: candidate {i} rides row {row}, which is trained for \
4274                     offset {} — harvest misaligned",
4275                    h.trained_offset_of_row(row)
4276                );
4277            }
4278        }
4279    }
4280
4281    #[test]
4282    fn env_seam_parses_and_refuses() {
4283        assert_eq!(
4284            DsparkHarvest::from_env_value(None),
4285            DsparkHarvest::Dflash,
4286            "the ENV-ONLY parser keeps the historical arm; the ratified strategy-keyed \
4287             default lives in resolve_value (checkpoint census), not here"
4288        );
4289        assert_eq!(
4290            DsparkHarvest::from_env_value(Some("dspark")),
4291            DsparkHarvest::Dspark
4292        );
4293        assert_eq!(
4294            DsparkHarvest::from_env_value(Some("dflash")),
4295            DsparkHarvest::Dflash
4296        );
4297        assert!(
4298            std::panic::catch_unwind(|| DsparkHarvest::from_env_value(Some("shifted"))).is_err(),
4299            "unknown harvest values must REFUSE, not default"
4300        );
4301        assert_eq!(
4302            DsparkHarvest::from_name("dspark"),
4303            Some(DsparkHarvest::Dspark)
4304        );
4305        assert_eq!(
4306            DsparkHarvest::from_name("dflash"),
4307            Some(DsparkHarvest::Dflash)
4308        );
4309        assert_eq!(DsparkHarvest::from_name("mask-fill"), None);
4310    }
4311
4312    /// The owner-ratified default flips (2026-08-20). Each assertion names its
4313    /// evidence; mutating either resolve back to the old default fails these.
4314    #[test]
4315    fn ratified_default_harvest_is_strategy_keyed() {
4316        // DSPARK-strategy checkpoint + unset env = the shifted harvest (B1: accept
4317        // 1.38->2.41 agentic / 1.53->3.66 math, E2E ALL EXACT x5, interleaved x5).
4318        assert_eq!(
4319            DsparkHarvest::resolve_value(None, true),
4320            DsparkHarvest::Dspark,
4321            "owner-ratified 2026-08-20: unset env defaults a DSPARK-strategy \
4322             checkpoint to the shifted harvest (DSPARK-POSTMORTEM-20260820.md B1)"
4323        );
4324        // mask-fill checkpoint + unset env = the historical arm, byte-identical.
4325        assert_eq!(
4326            DsparkHarvest::resolve_value(None, false),
4327            DsparkHarvest::Dflash
4328        );
4329        assert_eq!(
4330            DsparkHarvest::resolve_value(Some(""), false),
4331            DsparkHarvest::Dflash
4332        );
4333        // Explicit env overrides the census in BOTH directions (the A/B seam).
4334        assert_eq!(
4335            DsparkHarvest::resolve_value(Some("dflash"), true),
4336            DsparkHarvest::Dflash
4337        );
4338        assert_eq!(
4339            DsparkHarvest::resolve_value(Some("dspark"), false),
4340            DsparkHarvest::Dspark
4341        );
4342        // Unknown values still REFUSE through the resolve path.
4343        assert!(
4344            std::panic::catch_unwind(|| DsparkHarvest::resolve_value(Some("shifted"), true))
4345                .is_err()
4346        );
4347    }
4348
4349    #[test]
4350    fn strategy_census_reads_the_checkpoint_not_the_env() {
4351        // The q38 arm-a export shape: both signals present.
4352        let q38 = r#"{"architectures": ["Qwen3DSparkModel"], "block_size": 7,
4353            "dflash_config": {"projector_type": "dspark", "markov_rank": 256}}"#;
4354        assert!(dspark_strategy_census(q38));
4355        // Either signal alone suffices.
4356        assert!(dspark_strategy_census(
4357            r#"{"architectures": ["Qwen3DSparkModel"]}"#
4358        ));
4359        assert!(dspark_strategy_census(
4360            r#"{"dflash_config": {"projector_type": "dspark"}}"#
4361        ));
4362        // A mask-fill DFlash export carries neither -> historical default.
4363        let dflash = r#"{"architectures": ["Qwen3DFlashModel"],
4364            "dflash_config": {"attention_mode": "gqa"}}"#;
4365        assert!(!dspark_strategy_census(dflash));
4366        assert!(!dspark_strategy_census("{}"));
4367    }
4368
4369    #[test]
4370    fn ratified_default_vt_is_confidence_slot_tau_half() {
4371        // Head-carrying checkpoint + unset env = confidence-slot tau=.5 (H4 cell 3:
4372        // the tau ladder's knee; cell 2: 93.9%/97.7% of fixed-8 accept at wall >=
4373        // the reactive ladder, exactness 11/11 ALL EXACT).
4374        assert_eq!(
4375            DsparkVtPolicy::resolve_value(None, None, None, true),
4376            DsparkVtPolicy::ConfidenceSlot { tau: 0.5 },
4377            "owner-ratified 2026-08-20: unset MEMRA_DSPARK_VT defaults to \
4378             confidence-slot tau=.5 on a head-carrying checkpoint (H4 cells 2-3)"
4379        );
4380        // tau env still steers the default arm (and a bad tau still refuses).
4381        assert_eq!(
4382            DsparkVtPolicy::resolve_value(None, Some("0.35"), None, true),
4383            DsparkVtPolicy::ConfidenceSlot { tau: 0.35 }
4384        );
4385        assert!(
4386            std::panic::catch_unwind(|| DsparkVtPolicy::resolve_value(
4387                None,
4388                Some("nan-ish"),
4389                None,
4390                true
4391            ))
4392            .is_err()
4393        );
4394        // Census: no accept-rate head -> nothing to schedule with -> ladder.
4395        assert_eq!(
4396            DsparkVtPolicy::resolve_value(None, None, None, false),
4397            DsparkVtPolicy::Ladder
4398        );
4399        // MEMRA_DFLASH_ADAPT=0 is an explicit fixed-window request: honored.
4400        assert_eq!(
4401            DsparkVtPolicy::resolve_value(None, None, Some("0"), true),
4402            DsparkVtPolicy::Ladder
4403        );
4404        // Explicit values keep their exact prior semantics through resolve.
4405        assert_eq!(
4406            DsparkVtPolicy::resolve_value(Some("ladder"), None, None, true),
4407            DsparkVtPolicy::Ladder
4408        );
4409        assert_eq!(
4410            DsparkVtPolicy::resolve_value(Some("confidence"), Some("0.35"), None, true),
4411            DsparkVtPolicy::Confidence { tau: 0.35 }
4412        );
4413        // Explicit confidence mode with ADAPT=0 stays a refusal.
4414        assert!(
4415            std::panic::catch_unwind(|| DsparkVtPolicy::resolve_value(
4416                Some("confidence-slot"),
4417                None,
4418                Some("0"),
4419                true
4420            ))
4421            .is_err()
4422        );
4423    }
4424
4425    /// End-to-end alignment fixture in miniature: a mock drafter whose row r argmaxes
4426    /// to token BASE + (its trained offset under the DSPARK strategy), and a mock trunk
4427    /// whose prediction for anchor+j is BASE + j. The DSpark harvest accepts the whole
4428    /// block; feeding the same drafter through the mask-fill harvest accepts ZERO —
4429    /// the postmortem's collapse reproduced as pure logic.
4430    #[test]
4431    fn dspark_trained_rows_through_mask_fill_harvest_accept_nothing() {
4432        const BASE: u32 = 1000;
4433        let anchor: u32 = BASE; // token at the round anchor position (offset 0)
4434        // trunk verify argmaxes: vam[j] = prediction for anchor offset j+1
4435        let vam: Vec<u32> = (1..=B as u32 + 1).map(|j| BASE + j).collect();
4436        // drafter rows trained under the DSPARK strategy: row r predicts offset r+1
4437        let dspark_trained_row_argmax =
4438            |r: usize| BASE + DsparkHarvest::Dspark.trained_offset_of_row(r) as u32;
4439
4440        // Correct (shifted) harvest: candidate i <- row i-1.
4441        let h = DsparkHarvest::Dspark;
4442        let mut cand = vec![anchor];
4443        for i in 1..=h.n_drafts(B) {
4444            cand.push(dspark_trained_row_argmax(h.first_row() + i - 1));
4445        }
4446        let vt = h.n_drafts(B) + 1;
4447        assert_eq!(
4448            dspark_accept_prefix(&cand, &vam, vt),
4449            vt - 1,
4450            "aligned harvest must accept the full block"
4451        );
4452
4453        // Mask-fill harvest of the SAME dspark-trained drafter: candidate i <- row i,
4454        // which was trained for offset i+1 — every slot one position late.
4455        let wrong = DsparkHarvest::Dflash;
4456        let mut cand_wrong = vec![anchor];
4457        for i in 1..=wrong.n_drafts(B) {
4458            cand_wrong.push(dspark_trained_row_argmax(wrong.first_row() + i - 1));
4459        }
4460        let vt_wrong = wrong.n_drafts(B) + 1;
4461        assert_eq!(
4462            dspark_accept_prefix(&cand_wrong, &vam, vt_wrong),
4463            0,
4464            "mask-fill harvest of a dspark-trained drafter verifies every slot against \
4465             a position the row was not trained for (DSPARK-POSTMORTEM-20260820.md)"
4466        );
4467    }
4468}
4469
4470// ================= Verify-window policy gate (CPU; H4, DSPARK-POSTMORTEM-20260820.md) ===
4471// Pins the confidence-vt semantics as logic the round consumes: cumprod survival over
4472// sigmoid scores, thresholded, anchor + kept drafts, floor 2 / cap vt_cap — and the env
4473// seam's refuse-on-ambiguity. Mutating the policy (per-slot threshold instead of
4474// survival, off-by-one on the anchor, silent unknown-value fallback) fails HERE.
4475#[cfg(test)]
4476mod dspark_vt_tests {
4477    use super::{ConfidenceHead, DsparkVtPolicy, dspark_confidence_vt, dspark_slot_confidence_vt};
4478
4479    /// Pre-sigmoid logit for a target probability: sigmoid(logit(p)) == p.
4480    fn logit(p: f32) -> f32 {
4481        (p / (1.0 - p)).ln()
4482    }
4483
4484    #[test]
4485    fn confidence_vt_is_cumprod_survival_not_per_slot_threshold() {
4486        // sigmoids = [0.9, 0.8, 0.9, ...]: every PER-SLOT score clears tau=0.5, but
4487        // cumulative survival sinks below it at slot 6 (0.9, 0.72, 0.648, 0.583,
4488        // 0.525, then 0.472 < 0.5) — the window must stop where the EXPECTED
4489        // accepted-prefix stops paying, not where a slot looks locally fine.
4490        let raws: Vec<f32> = [0.9, 0.8, 0.9, 0.9, 0.9, 0.9, 0.9]
4491            .iter()
4492            .map(|&p| logit(p))
4493            .collect();
4494        assert_eq!(
4495            dspark_confidence_vt(&raws, 0.5, 8),
4496            6,
4497            "keeps 5 drafts + anchor"
4498        );
4499        // Tighter threshold closes the window sooner; looser opens it to the cap.
4500        assert_eq!(
4501            dspark_confidence_vt(&raws, 0.7, 8),
4502            3,
4503            "tau=0.7 keeps 2 drafts"
4504        );
4505        assert_eq!(
4506            dspark_confidence_vt(&raws, 0.05, 8),
4507            8,
4508            "tau→0 = full block"
4509        );
4510    }
4511
4512    #[test]
4513    fn slot_arm_truncates_at_first_low_confidence_slot() {
4514        // Owner directive (2026-08-20): submit only the longest prefix whose EVERY
4515        // slot clears tau on its own sigmoid. On the survival test's raws
4516        // ([0.9, 0.8, 0.9 x5], tau=0.5) every slot clears per-slot, so the slot arm
4517        // opens the full block where survival stopped at 6 — the two stopping
4518        // statistics must stay distinct arms.
4519        let raws: Vec<f32> = [0.9, 0.8, 0.9, 0.9, 0.9, 0.9, 0.9]
4520            .iter()
4521            .map(|&p| logit(p))
4522            .collect();
4523        assert_eq!(dspark_slot_confidence_vt(&raws, 0.5, 8), 8);
4524        assert_eq!(dspark_confidence_vt(&raws, 0.5, 8), 6);
4525        // A low-confidence tail never enters verify: [0.9, 0.9, 0.3, 0.9, ...]
4526        // truncates at slot 3 REGARDLESS of the confident slots behind it — a kept
4527        // slot after a dropped one could never commit (prefix accept rule).
4528        let tail: Vec<f32> = [0.9, 0.9, 0.3, 0.9, 0.9, 0.9, 0.9]
4529            .iter()
4530            .map(|&p| logit(p))
4531            .collect();
4532        assert_eq!(
4533            dspark_slot_confidence_vt(&tail, 0.5, 8),
4534            3,
4535            "2 drafts + anchor"
4536        );
4537        // Tighter tau keeps less.
4538        assert_eq!(
4539            dspark_slot_confidence_vt(&tail, 0.95, 8),
4540            2,
4541            "floor at tau=0.95"
4542        );
4543    }
4544
4545    #[test]
4546    fn confidence_vt_floor_and_cap() {
4547        // A hopeless round still verifies ONE draft (the draft forward is paid;
4548        // vt=1 would guarantee an empty round at the same cost class).
4549        let cold: Vec<f32> = [0.1f32, 0.1, 0.1].iter().map(|&p| logit(p)).collect();
4550        assert_eq!(
4551            dspark_confidence_vt(&cold, 0.5, 8),
4552            2,
4553            "floor = anchor + 1 draft"
4554        );
4555        assert_eq!(
4556            dspark_slot_confidence_vt(&cold, 0.5, 8),
4557            2,
4558            "slot arm same floor"
4559        );
4560        // The MEMRA_DFLASH_VERIFY_T cap still binds a confident round.
4561        let hot: Vec<f32> = vec![logit(0.99); 7];
4562        assert_eq!(dspark_confidence_vt(&hot, 0.5, 5), 5, "vt_cap binds");
4563        assert_eq!(
4564            dspark_confidence_vt(&hot, 0.5, 8),
4565            8,
4566            "full block when confident"
4567        );
4568        assert_eq!(
4569            dspark_slot_confidence_vt(&hot, 0.5, 5),
4570            5,
4571            "slot arm same cap"
4572        );
4573        // No scores (defensive): floor.
4574        assert_eq!(dspark_confidence_vt(&[], 0.5, 8), 2);
4575        assert_eq!(dspark_slot_confidence_vt(&[], 0.5, 8), 2);
4576    }
4577
4578    #[test]
4579    fn vt_policy_env_seam_parses_and_refuses() {
4580        assert_eq!(
4581            DsparkVtPolicy::from_env_value(None, None, None),
4582            DsparkVtPolicy::Ladder,
4583            "default stays the shipped ladder — the H4 arm is opt-in"
4584        );
4585        assert_eq!(
4586            DsparkVtPolicy::from_env_value(Some(""), None, None),
4587            DsparkVtPolicy::Ladder
4588        );
4589        assert_eq!(
4590            DsparkVtPolicy::from_env_value(Some("ladder"), None, Some("0")),
4591            DsparkVtPolicy::Ladder,
4592            "ladder + ADAPT=0 = the fixed-window arm, untouched"
4593        );
4594        assert_eq!(
4595            DsparkVtPolicy::from_env_value(Some("confidence"), None, None),
4596            DsparkVtPolicy::Confidence { tau: 0.5 },
4597            "tau defaults to 0.5 (raw sigmoid, no STS sidecar — postmortem §3-H4)"
4598        );
4599        assert_eq!(
4600            DsparkVtPolicy::from_env_value(Some("confidence"), Some("0.35"), Some("1")),
4601            DsparkVtPolicy::Confidence { tau: 0.35 }
4602        );
4603        assert_eq!(
4604            DsparkVtPolicy::from_env_value(Some("confidence-slot"), Some("0.6"), None),
4605            DsparkVtPolicy::ConfidenceSlot { tau: 0.6 },
4606            "the owner-directive per-slot arm parses with the same tau env"
4607        );
4608        assert!(
4609            std::panic::catch_unwind(|| DsparkVtPolicy::from_env_value(
4610                Some("confidence-slot"),
4611                None,
4612                Some("0")
4613            ))
4614            .is_err(),
4615            "confidence-slot + MEMRA_DFLASH_ADAPT=0 must REFUSE like confidence"
4616        );
4617        assert!(
4618            std::panic::catch_unwind(|| DsparkVtPolicy::from_env_value(Some("static"), None, None))
4619                .is_err(),
4620            "unknown policy values must REFUSE, not default — a typo silently \
4621             reverting the window policy invalidates an A/B"
4622        );
4623        assert!(
4624            std::panic::catch_unwind(|| DsparkVtPolicy::from_env_value(
4625                Some("confidence"),
4626                None,
4627                Some("0")
4628            ))
4629            .is_err(),
4630            "confidence + MEMRA_DFLASH_ADAPT=0 is contradictory and must REFUSE"
4631        );
4632        for bad in ["0", "1", "1.5", "-0.1", "nan"] {
4633            assert!(
4634                std::panic::catch_unwind(|| DsparkVtPolicy::from_env_value(
4635                    Some("confidence"),
4636                    Some(bad),
4637                    None
4638                ))
4639                .is_err(),
4640                "tau={bad} must REFUSE (survival threshold lives in (0,1))"
4641            );
4642        }
4643    }
4644
4645    #[test]
4646    fn raw_score_matches_the_parity_gate_dot() {
4647        // The head is a raw linear proj over [hidden ; markov_prev_embedding] + b —
4648        // the exact stage-5 contract in dspark_q38_parity.rs.
4649        let ch = ConfidenceHead {
4650            w: vec![0.5, -1.0, 2.0, 0.25, -0.5],
4651            b: 0.125,
4652            in_dim: 5,
4653            with_markov: true,
4654        };
4655        let hidden = [1.0f32, 2.0, 3.0];
4656        let emb = [4.0f32, 8.0];
4657        let want = 0.125 + 0.5 * 1.0 - 1.0 * 2.0 + 2.0 * 3.0 + 0.25 * 4.0 - 0.5 * 8.0;
4658        assert_eq!(ch.raw_score(&hidden, Some(&emb)), want);
4659        let ch_plain = ConfidenceHead {
4660            w: vec![0.5, -1.0, 2.0],
4661            b: -0.25,
4662            in_dim: 3,
4663            with_markov: false,
4664        };
4665        let want_plain = -0.25 + 0.5 * 1.0 - 1.0 * 2.0 + 2.0 * 3.0;
4666        assert_eq!(ch_plain.raw_score(&hidden, None), want_plain);
4667    }
4668}