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}
38
39pub struct DflashLayer {
40    pub wq: GpuTensor,           // [nh*hd, hidden] row-major (out_f rows)
41    pub wk: GpuTensor,           // [nkv*hd, hidden]
42    pub wv: GpuTensor,           // [nkv*hd, hidden]
43    pub wo: GpuTensor,           // [hidden, nh*hd]
44    pub w_gate: GpuTensor,       // [n_ff, hidden]
45    pub w_up: GpuTensor,         // [n_ff, hidden]
46    pub w_down: GpuTensor,       // [hidden, n_ff]
47    pub ln_in: CudaSlice<f32>,   // [hidden]
48    pub ln_post: CudaSlice<f32>, // [hidden]
49    pub q_norm: CudaSlice<f32>,  // [hd]
50    pub k_norm: CudaSlice<f32>,  // [hd]
51}
52
53pub struct DflashDraft {
54    pub cfg: DflashCfg,
55    pub layers: Vec<DflashLayer>,
56    pub fc: GpuTensor,               // [hidden, n_taps*hidden]
57    pub hidden_norm: CudaSlice<f32>, // [hidden]
58    pub norm: CudaSlice<f32>,        // [hidden]
59    /// DSpark semi-AR markov head (present in the repo-root checkpoint variant):
60    /// draft logits at position k get + W2(W1[prev_realized_token]) — left-to-right
61    /// within the block (the patch's _markov_semiar_sample_block semantics, greedy).
62    /// w1 = raw bf16 [V, rank] (row-gathered by device token id); w2 = q8_0 [rank->V].
63    pub markov: Option<MarkovHead>,
64    /// DSpark accept-rate head (trained with confidence loss). sglang's DSPARK planner
65    /// consumes it to SIZE VERIFY WINDOWS (cumprod survival — v0.5.16 headline; the
66    /// earlier "reference serving loop never consumes it" note matched SpecForge's
67    /// legacy spec_generate only). memra schedules with it under
68    /// `MEMRA_DSPARK_VT=confidence` (the H4 fix, DSPARK-POSTMORTEM-20260820.md:
69    /// per-round verify window from cumprod survival, `dspark_confidence_vt`) and
70    /// keeps it census+parity-only under the default ladder. Host-resident (5k floats).
71    pub confidence: Option<ConfidenceHead>,
72    /// YaRN rope (q38 arm-a inherits the target's rope_parameters: rope_type yarn,
73    /// factor 32, original 8192, beta 32/1). ff = per-dim divisors for rope_neox_ff
74    /// (effective inv_freq_j = base^(-2j/d)/ff[j] = the HF-yarn remapped frequency,
75    /// verified vs Qwen3RotaryEmbedding to 1.6e-7), mscale = attention_scaling
76    /// (0.1*ln(factor)+1) applied to q/k post-rope — cos/sin scaling distributes onto
77    /// the rotated vector exactly. None = plain rope (gemma/z-lab drafters).
78    pub rope_yarn: Option<(CudaSlice<f32>, f32)>,
79}
80
81/// AcceptRatePredictor: raw linear proj over [hidden ; markov_prev_embedding(rank)]
82/// (with_markov=true on the q38 arm-a export) — output is the PRE-sigmoid scalar.
83pub struct ConfidenceHead {
84    pub w: Vec<f32>, // [in_dim]
85    pub b: f32,
86    pub in_dim: usize,
87    pub with_markov: bool,
88}
89
90impl ConfidenceHead {
91    /// Host dot: the PRE-sigmoid accept score for one draft slot. `hidden` = the
92    /// drafter output row the slot is harvested from (the same row its logits use);
93    /// `emb` = the markov `w1` row of the slot's PREVIOUS chain token (required iff
94    /// `with_markov`) — the exact input contract the parity gate pins (prev ids =
95    /// `[anchor, chain[..nd-1]]`, dspark_q38_parity.rs stage 5).
96    pub fn raw_score(&self, hidden: &[f32], emb: Option<&[f32]>) -> f32 {
97        let mut acc = self.b;
98        for (w, x) in self.w.iter().zip(hidden) {
99            acc += w * x;
100        }
101        if self.with_markov {
102            let emb = emb.expect("with_markov confidence head scored without the markov embedding");
103            debug_assert_eq!(hidden.len() + emb.len(), self.in_dim);
104            for (w, x) in self.w[hidden.len()..].iter().zip(emb) {
105                acc += w * x;
106            }
107        } else {
108            debug_assert_eq!(hidden.len(), self.in_dim);
109        }
110        acc
111    }
112}
113
114pub struct MarkovHead {
115    pub w1_bf16: CudaSlice<u8>, // [V, rank] bf16 raw
116    pub w2: GpuTensor,          // [rank -> V] q8_0
117    pub rank: usize,
118    pub vocab: usize,
119}
120
121/// Draft-row harvest convention for DFlash-family block drafters
122/// (darklanes research/deepseek-flash-20260818/DSPARK-POSTMORTEM-20260820.md).
123///
124/// The DFlash and DSpark SpecForge training strategies supervise DIFFERENT rows of the
125/// same `[anchor, MASK x b-1]` block, so the row -> trunk-position mapping is a property
126/// of the CHECKPOINT's training strategy, not of the loader:
127///
128/// - **Dflash** (mask-fill; z-lab dflash / SpecForge `OnlineDFlashModel`): row k is
129///   trained to predict the token AT position anchor+k — "Labels: same-position
130///   prediction", `weight_mask *= (pos_in_block > 0)` excludes the anchor row
131///   (SpecForge `specforge/algorithms/common/dflash_family_model.py:453-472`).
132///   Drafts = rows 1..b-1; the anchor row's output is untrained.
133/// - **Dspark** (shifted; SpecForge `OnlineDSparkModel`, `training.strategy: dspark` —
134///   the q38 arm-a export): row k is trained to predict the token at anchor+k+1, ALL
135///   rows supervised INCLUDING the anchor row (`label_offsets = arange(1,
136///   block_size+1)`, `dflash_family_model.py:816`). sglang's DSPARK worker — the stack
137///   every arm-a bank number was measured on — harvests gamma = block_size drafts with
138///   the anchor row's output as draft 1 (verified on the v0.5.17 eval-pin tag:
139///   `dspark_components/dspark_draft.py:248,260,318`; `dspark_config.py:269`).
140///
141/// Mismatching the convention verifies every slot against a position the row was never
142/// trained for — the q38 accept collapse (2.9 -> 1.43) in the postmortem.
143#[derive(Clone, Copy, PartialEq, Eq, Debug)]
144pub enum DsparkHarvest {
145    /// mask-fill: drafts = rows 1..b-1, row k fills position anchor+k.
146    Dflash,
147    /// shifted: drafts = rows 0..b-1, row k predicts position anchor+k+1.
148    Dspark,
149}
150
151impl DsparkHarvest {
152    /// The served resolution: explicit `MEMRA_DSPARK_HARVEST={dflash|dspark}` wins
153    /// (unknown values REFUSE loudly — a typo silently reverting the convention would
154    /// re-open the postmortem's misalignment); UNSET defers to the CHECKPOINT's own
155    /// training-strategy census — the owner-ratified default flip (2026-08-20, after
156    /// B1 confirmed H1 interleaved ×5 on serving-class hardware: accept 1.38→2.41
157    /// agentic / 1.53→3.66 math, E2E ALL EXACT both arms). Strategy-keyed, not
158    /// env-keyed, per the B0 plan: a DSPARK-strategy export harvests shifted
159    /// (all-rows), a mask-fill export keeps the historical dflash arm byte-identical.
160    pub fn resolve(cfg: &DflashCfg) -> Self {
161        Self::resolve_value(
162            std::env::var("MEMRA_DSPARK_HARVEST").ok().as_deref(),
163            cfg.strategy_dspark,
164        )
165    }
166
167    pub fn resolve_value(v: Option<&str>, strategy_dspark: bool) -> Self {
168        match v {
169            None | Some("") => {
170                if strategy_dspark {
171                    DsparkHarvest::Dspark
172                } else {
173                    DsparkHarvest::Dflash
174                }
175            }
176            set => Self::from_env_value(set),
177        }
178    }
179
180    /// ENV-ONLY parser (no checkpoint census): unset = `Dflash`, the historical arm.
181    /// Kept for the explicit-value path of [`Self::resolve_value`] and the seam tests;
182    /// round arms resolve through [`Self::resolve`] so the default stays strategy-keyed.
183    pub fn from_env_value(v: Option<&str>) -> Self {
184        match v {
185            None | Some("") | Some("dflash") => DsparkHarvest::Dflash,
186            Some("dspark") => DsparkHarvest::Dspark,
187            Some(other) => panic!(
188                "MEMRA_DSPARK_HARVEST={other}: unknown harvest convention (dflash|dspark); \
189                 refusing — a wrong convention verifies every draft slot against a position \
190                 the drafter row was not trained for (DSPARK-POSTMORTEM-20260820.md)"
191            ),
192        }
193    }
194
195    /// Manifest/serialized name (the oracle geometry manifest's `harvest` field).
196    pub fn name(self) -> &'static str {
197        match self {
198            DsparkHarvest::Dflash => "dflash",
199            DsparkHarvest::Dspark => "dspark",
200        }
201    }
202
203    pub fn from_name(v: &str) -> Option<Self> {
204        match v {
205            "dflash" => Some(DsparkHarvest::Dflash),
206            "dspark" => Some(DsparkHarvest::Dspark),
207            _ => None,
208        }
209    }
210
211    /// First drafter OUTPUT row consumed as a draft candidate.
212    pub fn first_row(self) -> usize {
213        match self {
214            DsparkHarvest::Dflash => 1,
215            DsparkHarvest::Dspark => 0,
216        }
217    }
218
219    /// Drafted tokens harvested per round from a `b`-row block.
220    pub fn n_drafts(self, b: usize) -> usize {
221        match self {
222            DsparkHarvest::Dflash => b - 1,
223            DsparkHarvest::Dspark => b,
224        }
225    }
226
227    /// The position offset (relative to the round anchor at the block's row 0) that
228    /// drafter output row `row` is TRAINED to predict under this convention.
229    pub fn trained_offset_of_row(self, row: usize) -> usize {
230        match self {
231            DsparkHarvest::Dflash => row,
232            DsparkHarvest::Dspark => row + 1,
233        }
234    }
235}
236
237/// Checkpoint training-strategy census over the raw config.json text (the loader's
238/// minimal-extractor idiom — no json dep in-tree). TRUE iff the export declares the
239/// DSPARK strategy: `architectures` naming a DSpark model class (`Qwen3DSparkModel`,
240/// the SpecForge OnlineDSparkModel export form) or `dflash_config.projector_type ==
241/// "dspark"`. z-lab / OnlineDFlashModel mask-fill exports carry neither signal. Pure,
242/// so the census is testable against config fragments without files.
243pub fn dspark_strategy_census(txt: &str) -> bool {
244    let arch = txt
245        .find("\"architectures\"")
246        .and_then(|i| {
247            let rest = &txt[i..];
248            let a = rest.find('[')?;
249            let b = rest.find(']')?;
250            Some(rest[a..b].contains("DSpark"))
251        })
252        .unwrap_or(false);
253    let proj = txt
254        .find("\"projector_type\"")
255        .map(|i| {
256            let rest = &txt[i..];
257            let after = rest.find(':').map(|c| &rest[c + 1..]).unwrap_or("");
258            after.trim_start().starts_with("\"dspark\"")
259        })
260        .unwrap_or(false);
261    arch || proj
262}
263
264/// Accepted-prefix length of a round's candidates against the trunk's verify argmaxes:
265/// `cand[0]` = the round anchor (already decided), `cand[1..]` = the drafts;
266/// `vam[j]` = the trunk's argmax prediction for position anchor+j+1. Returns m =
267/// number of accepted drafts (`cand[1..=m]` committed, `vam[m]` becomes the next
268/// anchor). Pure so the harvest-alignment fixture can exercise it CPU-side.
269pub fn dspark_accept_prefix(cand: &[u32], vam: &[u32], vt: usize) -> usize {
270    let mut m = 0usize;
271    while m < vt - 1 && cand[m + 1] == vam[m] {
272        m += 1;
273    }
274    m
275}
276
277/// Verify-window policy for the dspark round (H4, DSPARK-POSTMORTEM-20260820.md §3).
278///
279/// B2 measured the structural fork: the fixed full-block window (vt=8) buys 95–100%
280/// of the sglang accept bank but LOSES wall speed to the reactive ladder everywhere
281/// except math — at 0.2–0.5 slot rates, full-block verify pays 5–6 empty rows per
282/// round. The confidence policy is the mechanism both leading engines schedule with
283/// (sglang v0.5.16 `dspark_planner.py` cumprod survival; vLLM #47808): size EACH
284/// round's window from the drafter's own trained accept-rate head, so windows open
285/// on confident streaks (math/code) and shrink on bursty text without a 4-round
286/// ladder climb.
287#[derive(Clone, Copy, PartialEq, Debug)]
288pub enum DsparkVtPolicy {
289    /// The shipped reactive ladder: `vt = (m+2).clamp(3, vt_cap)` per round
290    /// (`MEMRA_DFLASH_ADAPT=0` pins vt at `vt_cap` = the fixed-window arm).
291    Ladder,
292    /// `MEMRA_DSPARK_VT=confidence`: per-round window from cumprod survival of the
293    /// confidence head's sigmoid scores, thresholded at `tau`
294    /// (`MEMRA_DSPARK_VT_TAU`, default 0.5). Raw sigmoid — no STS sidecar
295    /// calibration exists for this export; the postmortem names this the starting
296    /// policy.
297    Confidence { tau: f32 },
298    /// `MEMRA_DSPARK_VT=confidence-slot` (owner directive, 2026-08-20: "take only
299    /// high confidence offers"): submit only the longest draft PREFIX whose every
300    /// slot clears `tau` on its own sigmoid — the low-confidence tail never enters
301    /// verify. Same tau env. vs `Confidence`: if the head's per-row score is the
302    /// MARGINAL accept probability (it already sinks with depth), cumprod survival
303    /// double-counts the decay and over-truncates; if it is the CONDITIONAL,
304    /// per-slot under-truncates. Which statistic the q38 head emits is empirical —
305    /// both arms ride the A/B.
306    ConfidenceSlot { tau: f32 },
307}
308
309impl DsparkVtPolicy {
310    /// The served resolution: explicit `MEMRA_DSPARK_VT={ladder|confidence|
311    /// confidence-slot}` wins (unknown values REFUSE loudly — a typo silently
312    /// reverting the window policy would invalidate an A/B without a trace); UNSET
313    /// defaults to **`confidence-slot` at τ = `MEMRA_DSPARK_VT_TAU` (default 0.5)** —
314    /// the owner-ratified H4 flip (2026-08-20; cell 2's 4-arm A/B ×5 + cell 3's tau
315    /// ladder put the knee at τ=.5 for the slot arm: 94–98% of the fixed-8 accept bank
316    /// at wall ≥ the reactive ladder, exactness 11/11 ALL EXACT). Census-keyed per the
317    /// capacity-keyed-defaults law: a checkpoint WITHOUT an accept-rate head has no
318    /// signal to schedule with, so unset-env resolves to the ladder there (loudly, at
319    /// load) instead of panicking on a default; `MEMRA_DFLASH_ADAPT=0` (an explicit
320    /// fixed-window request) also keeps the ladder-family arm.
321    pub fn resolve(has_confidence_head: bool) -> Self {
322        Self::resolve_value(
323            std::env::var("MEMRA_DSPARK_VT").ok().as_deref(),
324            std::env::var("MEMRA_DSPARK_VT_TAU").ok().as_deref(),
325            std::env::var("MEMRA_DFLASH_ADAPT").ok().as_deref(),
326            has_confidence_head,
327        )
328    }
329
330    pub fn resolve_value(
331        vt: Option<&str>,
332        tau: Option<&str>,
333        adapt: Option<&str>,
334        has_confidence_head: bool,
335    ) -> Self {
336        match vt {
337            None | Some("") => {
338                if adapt == Some("0") || !has_confidence_head {
339                    DsparkVtPolicy::Ladder
340                } else {
341                    // The ratified default rides the SAME tau parse as the explicit
342                    // arm (a bad MEMRA_DSPARK_VT_TAU refuses, never silently ignored).
343                    Self::from_env_value(Some("confidence-slot"), tau, adapt)
344                }
345            }
346            set => Self::from_env_value(set, tau, adapt),
347        }
348    }
349
350    /// ENV-ONLY parser (no head census): unset = `Ladder`. Kept for the explicit-value
351    /// path of [`Self::resolve_value`] and the policy-gate tests; round arms resolve
352    /// through [`Self::resolve`] so the default stays head-census-keyed.
353    pub fn from_env_value(vt: Option<&str>, tau: Option<&str>, adapt: Option<&str>) -> Self {
354        match vt {
355            None | Some("") | Some("ladder") => DsparkVtPolicy::Ladder,
356            Some(mode @ ("confidence" | "confidence-slot")) => {
357                if adapt == Some("0") {
358                    panic!(
359                        "MEMRA_DSPARK_VT={mode} together with MEMRA_DFLASH_ADAPT=0 is \
360                         contradictory (a pinned fixed window vs a per-round confidence \
361                         window); unset one — refuse-on-ambiguity"
362                    );
363                }
364                let tau = tau
365                    .map(|t| {
366                        t.parse::<f32>()
367                            .unwrap_or_else(|_| panic!("MEMRA_DSPARK_VT_TAU={t}: not a float"))
368                    })
369                    .unwrap_or(0.5);
370                assert!(
371                    tau > 0.0 && tau < 1.0,
372                    "MEMRA_DSPARK_VT_TAU={tau}: confidence threshold must be in (0,1)"
373                );
374                if mode == "confidence" {
375                    DsparkVtPolicy::Confidence { tau }
376                } else {
377                    DsparkVtPolicy::ConfidenceSlot { tau }
378                }
379            }
380            Some(other) => panic!(
381                "MEMRA_DSPARK_VT={other}: unknown verify-window policy \
382                 (ladder|confidence|confidence-slot); refusing — a wrong policy \
383                 silently reverts the H4 arm (DSPARK-POSTMORTEM-20260820.md)"
384            ),
385        }
386    }
387
388    /// True for every head-scheduled arm (the loops gate the head requirement and
389    /// the embedding stash on this).
390    pub fn is_confidence(&self) -> bool {
391        !matches!(self, DsparkVtPolicy::Ladder)
392    }
393
394    /// Size this round's verify window from the head's pre-sigmoid slot scores.
395    /// `None` under the ladder (the caller keeps its carried vt).
396    pub fn size_window(&self, raws: &[f32], vt_cap: usize) -> Option<usize> {
397        match *self {
398            DsparkVtPolicy::Ladder => None,
399            DsparkVtPolicy::Confidence { tau } => Some(dspark_confidence_vt(raws, tau, vt_cap)),
400            DsparkVtPolicy::ConfidenceSlot { tau } => {
401                Some(dspark_slot_confidence_vt(raws, tau, vt_cap))
402            }
403        }
404    }
405}
406
407/// H4 window sizing (the sglang-planner/vLLM-#47808 mechanism, thresholded): `raws[k]`
408/// = the accept-rate head's PRE-sigmoid score for draft slot k+1; survival
409/// `S_k = prod_{j<=k} sigmoid(raws[j])`; the window keeps leading slots while
410/// `S_k >= tau`. Returns `vt` = 1 (anchor) + kept drafts, clamped to `[2, vt_cap]`:
411/// the draft forward is already paid, so at least one draft rides every verify — one
412/// extra verify row costs less than a guaranteed empty round. Pure, so the policy's
413/// knee is testable CPU-side like `dspark_accept_prefix`.
414pub fn dspark_confidence_vt(raws: &[f32], tau: f32, vt_cap: usize) -> usize {
415    let mut surv = 1.0f32;
416    let mut kept = 0usize;
417    for &r in raws {
418        surv *= 1.0 / (1.0 + (-r).exp());
419        if surv < tau {
420            break;
421        }
422        kept += 1;
423    }
424    (1 + kept).clamp(2, vt_cap.max(2))
425}
426
427/// Owner-directive arm (2026-08-20, "take only high confidence offers"): keep the
428/// longest draft PREFIX whose EVERY slot clears `tau` on its own sigmoid — truncate
429/// at the first sub-threshold slot, so the low-confidence tail (B2 measured 0.2–0.5
430/// slot rates at depth) never enters verify. Prefix truncation is forced by the
431/// accept rule anyway (`dspark_accept_prefix` stops at the first miss — a kept slot
432/// after a dropped one could never commit); the policy fork vs `dspark_confidence_vt`
433/// is only the stopping statistic (per-slot marginal vs cumulative survival). Same
434/// floor/cap contract.
435pub fn dspark_slot_confidence_vt(raws: &[f32], tau: f32, vt_cap: usize) -> usize {
436    let mut kept = 0usize;
437    for &r in raws {
438        let p = 1.0 / (1.0 + (-r).exp());
439        if p < tau {
440            break;
441        }
442        kept += 1;
443    }
444    (1 + kept).clamp(2, vt_cap.max(2))
445}
446
447fn bf16_to_f32(bytes: &[u8]) -> Vec<f32> {
448    bytes
449        .chunks_exact(2)
450        .map(|c| f32::from_bits((u16::from_le_bytes([c[0], c[1]]) as u32) << 16))
451        .collect()
452}
453
454/// Host q8_0 encode (ggml block layout: [d f16][32 x i8] = 34B/32 vals). The drafter's
455/// weights ride the dp4a fast path at 1.6GB resident (bf16 3.1GB + the 31B trunk OOM'd
456/// 24GB; f32 6.2GB worse). Drafter quantization moves ACCEPTANCE only — verify exactness
457/// is structural.
458fn encode_q8_0(vals: &[f32]) -> Vec<u8> {
459    let mut out = Vec::with_capacity(vals.len() / 32 * 34);
460    for blk in vals.chunks_exact(32) {
461        let amax = blk.iter().fold(0f32, |a, v| a.max(v.abs()));
462        let d = amax / 127.0;
463        let id = if d > 0.0 { 1.0 / d } else { 0.0 };
464        let dh = half_from_f32(d);
465        out.extend_from_slice(&dh.to_le_bytes());
466        for &v in blk {
467            out.push(((v * id).round().clamp(-127.0, 127.0)) as i8 as u8);
468        }
469    }
470    out
471}
472
473/// Host q4_0 encode (ggml: [d f16][16B packed nibbles] = 18B/32 vals; q = round(v/d)+8,
474/// d = amax/-7 sign trick NOT used — plain amax/7? ggml uses d = max/-8 .. follow ggml:
475/// d = amax / -8 when the max is negative-dominant; reference quantize_row_q4_0: d =
476/// max(|v|)/-8 signed-max form). Implemented to match ggml quantize_row_q4_0_ref.
477fn encode_q4_0(vals: &[f32]) -> Vec<u8> {
478    let mut out = Vec::with_capacity(vals.len() / 32 * 18);
479    for blk in vals.chunks_exact(32) {
480        // ggml ref: pick the value with the LARGEST |v| (keeping sign), d = that / -8
481        let mut amax = 0f32;
482        let mut mx = 0f32;
483        for &v in blk {
484            if v.abs() > amax {
485                amax = v.abs();
486                mx = v;
487            }
488        }
489        let d = mx / -8.0;
490        let id = if d != 0.0 { 1.0 / d } else { 0.0 };
491        out.extend_from_slice(&half_from_f32(d).to_le_bytes());
492        for j in 0..16 {
493            let x0 = (blk[j] * id + 8.5).clamp(0.0, 15.0) as u8;
494            let x1 = (blk[j + 16] * id + 8.5).clamp(0.0, 15.0) as u8;
495            out.push(x0 | (x1 << 4));
496        }
497    }
498    out
499}
500
501fn half_from_f32(v: f32) -> u16 {
502    // f32 -> IEEE f16 (round-to-nearest-even; range of q8_0 d values is tame)
503    let b = v.to_bits();
504    let sign = ((b >> 16) & 0x8000) as u16;
505    let exp = ((b >> 23) & 0xff) as i32 - 127 + 15;
506    let man = b & 0x7fffff;
507    if exp <= 0 {
508        return sign;
509    } // flush tiny d to zero
510    if exp >= 31 {
511        return sign | 0x7c00;
512    } // inf (unreachable for sane d)
513    let mut h = sign | ((exp as u16) << 10) | ((man >> 13) as u16);
514    // round to nearest even on the truncated 13 bits
515    let rem = man & 0x1fff;
516    if rem > 0x1000 || (rem == 0x1000 && (h & 1) == 1) {
517        h += 1;
518    }
519    h
520}
521
522impl DflashDraft {
523    /// Load the backbone-only checkpoint dir (config.json + model.safetensors, bf16).
524    /// Config scalars ride a minimal extractor (no json dep in-tree — HfConfig precedent).
525    pub fn load(e: &Engine, dir: &std::path::Path) -> Result<Self, Box<dyn std::error::Error>> {
526        let txt = std::fs::read_to_string(dir.join("config.json"))?;
527        fn num(txt: &str, key: &str) -> Option<f64> {
528            let i = txt.find(&format!("\"{key}\""))?;
529            let rest = &txt[i..];
530            let colon = rest.find(':')?;
531            let val: String = rest[colon + 1..]
532                .trim_start()
533                .chars()
534                .take_while(|c| {
535                    c.is_ascii_digit()
536                        || *c == '.'
537                        || *c == '-'
538                        || *c == 'e'
539                        || *c == 'E'
540                        || *c == '+'
541                })
542                .collect();
543            val.parse().ok()
544        }
545        fn num_list(txt: &str, key: &str) -> Vec<usize> {
546            let Some(i) = txt.find(&format!("\"{key}\"")) else {
547                return Vec::new();
548            };
549            let rest = &txt[i..];
550            let (Some(a), Some(b)) = (rest.find('['), rest.find(']')) else {
551                return Vec::new();
552            };
553            rest[a + 1..b]
554                .split(',')
555                .filter_map(|s| s.trim().parse().ok())
556                .collect()
557        }
558        let g = |k: &str| num(&txt, k).unwrap_or_else(|| panic!("config missing {k}")) as usize;
559        // layer_types order: count entries, mark sliding ones
560        let layer_sliding: Vec<bool> = {
561            let i = txt.find("\"layer_types\"").expect("layer_types");
562            let rest = &txt[i..];
563            let (a, b) = (rest.find('[').unwrap(), rest.find(']').unwrap());
564            rest[a + 1..b]
565                .split(',')
566                .map(|s| s.contains("sliding_attention"))
567                .collect()
568        };
569        // sliding_window is null on all-full-attention exports (q38 arm-a); the window
570        // only constrains rounds when a sliding layer exists (reference: resolve_dflash_
571        // attention_layout returns None when no layer slides).
572        let sliding_window = if layer_sliding.iter().any(|&s| s) {
573            g("sliding_window")
574        } else {
575            num(&txt, "sliding_window")
576                .map(|v| v as usize)
577                .unwrap_or(usize::MAX)
578        };
579        let cfg = DflashCfg {
580            hidden: g("hidden_size"),
581            n_head: g("num_attention_heads"),
582            n_kv: g("num_key_value_heads"),
583            head_dim: g("head_dim"),
584            n_ff: g("intermediate_size"),
585            n_layer: g("num_hidden_layers"),
586            eps: num(&txt, "rms_norm_eps").expect("rms_norm_eps") as f32,
587            rope_theta: num(&txt, "rope_theta").expect("rope_theta") as f32,
588            block_size: g("block_size"),
589            mask_token_id: g("mask_token_id") as u32,
590            target_layer_ids: num_list(&txt, "target_layer_ids"),
591            sliding_window,
592            layer_sliding,
593            strategy_dspark: dspark_strategy_census(&txt),
594        };
595        let st = memra_gguf::safetensors::StModel::open(&dir.join("model.safetensors"))?;
596        // 1D norm weights ride raw slices; 2D matmul weights ride GpuTensor::Float
597        // (cuBLASLt f32 arm — the Stage-A numeric class, right for oracle parity).
598        let up = |name: &str| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
599            let (_info, bytes) = st
600                .raw(name)
601                .ok_or_else(|| format!("missing tensor {name}"))?;
602            Ok(e.htod(&bf16_to_f32(bytes))?)
603        };
604        // Precision policy (MEMRA_DFLASH_PREC seam): "q8" = all q8_0 (1.6GB, default);
605        // "mixed" = bf16 attn+fc (the ctx-conditioning path) + q8_0 ffn (~2.2GB — fits the
606        // ~2.8GB headroom beside the 31B trunk); "bf16" = all bf16 (parity runs, no target).
607        let prec = std::env::var("MEMRA_DFLASH_PREC").unwrap_or_else(|_| "q8".into());
608        let upw = |name: &str| -> Result<GpuTensor, Box<dyn std::error::Error>> {
609            let (info, bytes) = st
610                .raw(name)
611                .ok_or_else(|| format!("missing tensor {name}"))?;
612            let shape = info.ne(); // ggml order: ne[0]=in_f, ne[1]=out_f
613            let in_f = shape[0] as usize;
614            let is_ffn = name.contains(".mlp.");
615            let bf16 = prec == "bf16"
616                || (prec == "mixed" && !is_ffn)
617                || (prec == "fc" && name == "fc.weight");
618            if bf16 {
619                return Ok(GpuTensor::FloatBf16 {
620                    data: e.upload_u8(bytes)?,
621                    ne: shape.to_vec(),
622                });
623            }
624            let f32s = bf16_to_f32(bytes);
625            if prec == "q4" {
626                let q = encode_q4_0(&f32s);
627                return Ok(GpuTensor::Quant {
628                    bytes: e.upload_u8(&q)?,
629                    qtype: crate::QT_Q4_0,
630                    row_bytes: in_f / 32 * 18,
631                    ne: shape.to_vec(),
632                    scale: 1.0,
633                    rp: false,
634                    #[cfg(memra_cutlass)]
635                    cutlass: None,
636                    fp8: None,
637                    blk: None,
638                    rp4: None,
639                    f16: None,
640                });
641            }
642            let q = encode_q8_0(&f32s);
643            Ok(GpuTensor::Quant {
644                bytes: e.upload_u8(&q)?,
645                qtype: crate::QT_Q8_0,
646                row_bytes: in_f / 32 * 34,
647                ne: shape.to_vec(),
648                scale: 1.0,
649                rp: false,
650                #[cfg(memra_cutlass)]
651                cutlass: None,
652                fp8: None,
653                blk: None,
654                rp4: None,
655                f16: None,
656            })
657        };
658        let mut layers = Vec::with_capacity(cfg.n_layer);
659        for i in 0..cfg.n_layer {
660            let p = |s: &str| format!("layers.{i}.{s}");
661            layers.push(DflashLayer {
662                wq: upw(&p("self_attn.q_proj.weight"))?,
663                wk: upw(&p("self_attn.k_proj.weight"))?,
664                wv: upw(&p("self_attn.v_proj.weight"))?,
665                wo: upw(&p("self_attn.o_proj.weight"))?,
666                w_gate: upw(&p("mlp.gate_proj.weight"))?,
667                w_up: upw(&p("mlp.up_proj.weight"))?,
668                w_down: upw(&p("mlp.down_proj.weight"))?,
669                ln_in: up(&p("input_layernorm.weight"))?,
670                ln_post: up(&p("post_attention_layernorm.weight"))?,
671                q_norm: up(&p("self_attn.q_norm.weight"))?,
672                k_norm: up(&p("self_attn.k_norm.weight"))?,
673            });
674        }
675        let markov = if let Some((info, bytes)) = st.raw("markov_head.markov_w1.weight") {
676            let sh = info.ne(); // [rank, vocab] in ggml order (safetensors [V, rank] reversed)
677            let (rank, vocab) = (sh[0] as usize, sh[1] as usize);
678            let (i2, b2) = st
679                .raw("markov_head.markov_w2.weight")
680                .ok_or("markov_w2 missing beside markov_w1")?;
681            // w2 follows the precision seam: bf16 for parity runs (the q8_0 encode is a
682            // serving-size choice and would put quant error inside the markov-logits gate),
683            // q8_0 otherwise (acceptance-only impact, like the trunk weights).
684            let w2 = if prec == "bf16" {
685                GpuTensor::FloatBf16 {
686                    data: e.upload_u8(b2)?,
687                    ne: i2.ne().to_vec(),
688                }
689            } else {
690                let w2f = bf16_to_f32(b2);
691                let w2q = encode_q8_0(&w2f);
692                GpuTensor::Quant {
693                    bytes: e.upload_u8(&w2q)?,
694                    qtype: crate::QT_Q8_0,
695                    row_bytes: rank / 32 * 34,
696                    ne: vec![rank as u64, vocab as u64],
697                    scale: 1.0,
698                    rp: false,
699                    #[cfg(memra_cutlass)]
700                    cutlass: None,
701                    fp8: None,
702                    blk: None,
703                    rp4: None,
704                    f16: None,
705                }
706            };
707            Some(MarkovHead {
708                w1_bf16: e.upload_u8(bytes)?,
709                w2,
710                rank,
711                vocab,
712            })
713        } else {
714            None
715        };
716        let confidence = if let Some((info, bytes)) = st.raw("confidence_head.proj.weight") {
717            let sh = info.ne(); // ggml order: ne[0]=in_dim, ne[1]=1
718            let in_dim = sh[0] as usize;
719            let (_bi, bb) = st
720                .raw("confidence_head.proj.bias")
721                .ok_or("confidence bias missing beside weight")?;
722            let with_markov = markov
723                .as_ref()
724                .map(|m| in_dim == cfg.hidden + m.rank)
725                .unwrap_or(false);
726            if !with_markov && in_dim != cfg.hidden {
727                panic!(
728                    "confidence_head in_dim {in_dim} matches neither hidden {} nor hidden+rank",
729                    cfg.hidden
730                );
731            }
732            Some(ConfidenceHead {
733                w: bf16_to_f32(bytes),
734                b: bf16_to_f32(bb)[0],
735                in_dim,
736                with_markov,
737            })
738        } else {
739            None
740        };
741        // CENSUS GATE: every tensor in the export must be consumed by the map above.
742        // DSpark-class checkpoints (markov head present) REFUSE on unrecognized names —
743        // an unmapped tensor is a semantic program we would silently drop (house law).
744        // Plain dflash checkpoints keep the historical warn-only behavior.
745        {
746            let mut consumed: std::collections::HashSet<String> = std::collections::HashSet::new();
747            for i in 0..cfg.n_layer {
748                for s in [
749                    "self_attn.q_proj.weight",
750                    "self_attn.k_proj.weight",
751                    "self_attn.v_proj.weight",
752                    "self_attn.o_proj.weight",
753                    "self_attn.q_norm.weight",
754                    "self_attn.k_norm.weight",
755                    "input_layernorm.weight",
756                    "post_attention_layernorm.weight",
757                    "mlp.gate_proj.weight",
758                    "mlp.up_proj.weight",
759                    "mlp.down_proj.weight",
760                ] {
761                    consumed.insert(format!("layers.{i}.{s}"));
762                }
763            }
764            for s in [
765                "fc.weight",
766                "hidden_norm.weight",
767                "norm.weight",
768                "markov_head.markov_w1.weight",
769                "markov_head.markov_w2.weight",
770                "confidence_head.proj.weight",
771                "confidence_head.proj.bias",
772            ] {
773                consumed.insert(s.into());
774            }
775            let leftovers: Vec<&String> = st.names().filter(|n| !consumed.contains(*n)).collect();
776            if !leftovers.is_empty() {
777                if markov.is_some() {
778                    panic!("dspark census: unrecognized tensors {leftovers:?}");
779                }
780                eprintln!("[dflash census] unmapped tensors (ignored): {leftovers:?}");
781            }
782        }
783        // YaRN rope from config rope_parameters (HF _compute_yarn_parameters, verified
784        // numerically vs Qwen3RotaryEmbedding on the arm-a export).
785        let rope_yarn =
786            if txt.contains("\"rope_type\": \"yarn\"") || txt.contains("\"rope_type\":\"yarn\"") {
787                let factor = num(&txt, "factor").expect("yarn factor") as f64;
788                let orig = num(&txt, "original_max_position_embeddings").expect("yarn orig");
789                let beta_fast = num(&txt, "beta_fast").expect("beta_fast");
790                let beta_slow = num(&txt, "beta_slow").expect("beta_slow");
791                let base = cfg.rope_theta as f64;
792                let d = cfg.head_dim as f64;
793                let corr =
794                    |r: f64| d * (orig / (r * 2.0 * std::f64::consts::PI)).ln() / (2.0 * base.ln());
795                let low = corr(beta_fast).floor().max(0.0);
796                let high = corr(beta_slow).ceil().min(d - 1.0);
797                let half = cfg.head_dim / 2;
798                let mut ff = Vec::with_capacity(half);
799                for j in 0..half {
800                    let base_inv = base.powf(-2.0 * j as f64 / d);
801                    let ramp = (((j as f64) - low) / (high - low)).clamp(0.0, 1.0);
802                    let ex = 1.0 - ramp; // extrapolation share
803                    let yarn_inv = (base_inv / factor) * (1.0 - ex) + base_inv * ex;
804                    ff.push((base_inv / yarn_inv) as f32);
805                }
806                let mscale = (0.1 * factor.ln() + 1.0) as f32;
807                Some((e.htod(&ff)?, mscale))
808            } else {
809                None
810            };
811        // Ratified-default receipts (capacity-keyed-defaults law: the active program is
812        // NAMED at load, never inferred from silence). The boot output-sample gate greps
813        // these two lines; a run whose log lacks them did not load this code.
814        eprintln!(
815            "[dspark] harvest={} (checkpoint census strategy_dspark={}, MEMRA_DSPARK_HARVEST {})",
816            DsparkHarvest::resolve_value(
817                std::env::var("MEMRA_DSPARK_HARVEST").ok().as_deref(),
818                cfg.strategy_dspark,
819            )
820            .name(),
821            cfg.strategy_dspark,
822            match std::env::var("MEMRA_DSPARK_HARVEST") {
823                Ok(v) if !v.is_empty() => "set",
824                _ => "unset",
825            },
826        );
827        eprintln!(
828            "[dspark] verify-window={:?} (accept-rate head {}, MEMRA_DSPARK_VT {})",
829            DsparkVtPolicy::resolve(confidence.is_some()),
830            if confidence.is_some() {
831                "present"
832            } else {
833                "ABSENT -> ladder"
834            },
835            match std::env::var("MEMRA_DSPARK_VT") {
836                Ok(v) if !v.is_empty() => "set",
837                _ => "unset",
838            },
839        );
840        Ok(Self {
841            fc: upw("fc.weight")?,
842            hidden_norm: up("hidden_norm.weight")?,
843            norm: up("norm.weight")?,
844            cfg,
845            layers,
846            markov,
847            confidence,
848            rope_yarn,
849        })
850    }
851
852    /// Rope q or k rows in place: yarn (ff divisors + post-rope mscale) when the config
853    /// carries it, plain neox otherwise. One primitive for all five drafter rope sites.
854    fn rope_rows(
855        &self,
856        e: &Engine,
857        x: &mut CudaSlice<f32>,
858        pos_d: &CudaSlice<i32>,
859        n_heads: usize,
860        n_tokens: usize,
861    ) -> Result<(), Box<dyn std::error::Error>> {
862        let c = &self.cfg;
863        match &self.rope_yarn {
864            Some((ff, mscale)) => {
865                e.rope_neox_ff(
866                    x,
867                    pos_d,
868                    c.head_dim,
869                    c.head_dim,
870                    n_heads,
871                    n_tokens,
872                    c.rope_theta,
873                    1.0,
874                    ff,
875                )?;
876                e.scale_inplace(x, *mscale, n_tokens * n_heads * c.head_dim)?;
877            }
878            None => {
879                e.rope_neox(
880                    x,
881                    pos_d,
882                    c.head_dim,
883                    c.head_dim,
884                    n_heads,
885                    n_tokens,
886                    c.rope_theta,
887                    1.0,
888                )?;
889            }
890        }
891        Ok(())
892    }
893
894    /// f32 GEMM helper via the engine Float arm (cuBLASLt): y[t, out_f].
895    fn mm(
896        &self,
897        e: &Engine,
898        w: &GpuTensor,
899        x: &CudaSlice<f32>,
900        t: usize,
901        _in_f: usize,
902        _out_f: usize,
903    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
904        Ok(e.matmul(w, x, t)?)
905    }
906
907    /// FIRST-LIGHT forward (oracle contract): full non-causal attention over
908    /// [ctx_features ; block], NO draft KV cache, NO sliding window (the oracle bypasses
909    /// the reference mask machinery the same way — window/caching land in the round arm).
910    ///
911    /// `target_hidden`: [ctx, n_taps*hidden] (f32, device)  — raw tapped states.
912    /// `noise_emb`:     [block, hidden] — target embed rows for [accepted, MASK x b-1].
913    /// `pos`:           absolute positions for ctx rows THEN block rows (ctx+block i32).
914    /// Returns final normed hidden [block, hidden] (feed target lm_head for draft logits).
915    /// ctx features for `t` tapped rows: hidden_norm(fc(taps)) — the drafter's context
916    /// representation, cacheable across rounds (append-only in committed-token order).
917    pub fn ctx_features(
918        &self,
919        e: &Engine,
920        taps: &CudaSlice<f32>,
921        t: usize,
922    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
923        let c = &self.cfg;
924        let n_taps = c.target_layer_ids.len();
925        let fc_out = self.mm(e, &self.fc, taps, t, n_taps * c.hidden, c.hidden)?;
926        let mut out = e.uninit(t * c.hidden)?;
927        e.rms_norm(&fc_out, &self.hidden_norm, &mut out, c.hidden, t, c.eps)?;
928        Ok(out)
929    }
930
931    pub fn forward(
932        &self,
933        e: &Engine,
934        target_hidden: &CudaSlice<f32>,
935        noise_emb: &CudaSlice<f32>,
936        pos: &[i32],
937        ctx: usize,
938    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
939        let ctx_f = self.ctx_features(e, target_hidden, ctx)?;
940        if let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP") {
941            let v = e.dtoh(&ctx_f)?;
942            let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
943            std::fs::write(format!("{dir}/memra-ctx_features.f32"), bytes)?;
944        }
945        self.forward_block(e, &ctx_f, noise_emb, pos, ctx)
946    }
947
948    /// Block forward over PRECOMPUTED ctx features (the round arm's entry: features are
949    /// cached across rounds; only the block work repeats).
950    pub fn forward_block(
951        &self,
952        e: &Engine,
953        ctx_f: &CudaSlice<f32>,
954        noise_emb: &CudaSlice<f32>,
955        pos: &[i32],
956        ctx: usize,
957    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
958        let c = &self.cfg;
959        let (h, nh, nkv, hd) = (c.hidden, c.n_head, c.n_kv, c.head_dim);
960        let b = c.block_size;
961        assert_eq!(pos.len(), ctx + b, "pos covers ctx rows then block rows");
962
963        let pos_blk = e.htod_i32(&pos[ctx..])?;
964
965        let mut x = e.clone_dtod(noise_emb)?; // [b, hidden] residual stream
966        for (li, l) in self.layers.iter().enumerate() {
967            let _ = li;
968            // input_layernorm on the block rows only (ctx features are norm-free per ref:
969            // k/v project the SAME ctx_f every layer, un-layernormed).
970            let mut xn = e.uninit(b * h)?;
971            e.rms_norm(&x, &l.ln_in, &mut xn, h, b, c.eps)?;
972
973            // q from block; k/v from [ctx_f ; block-normed]
974            let q0 = self.mm(e, &l.wq, &xn, b, h, nh * hd)?;
975            let k0c = self.mm(e, &l.wk, ctx_f, ctx, h, nkv * hd)?;
976            let v0c = self.mm(e, &l.wv, ctx_f, ctx, h, nkv * hd)?;
977            let k0b = self.mm(e, &l.wk, &xn, b, h, nkv * hd)?;
978            let v0b = self.mm(e, &l.wv, &xn, b, h, nkv * hd)?;
979
980            // per-head q/k rms norm (v passes through: ones weight trick not needed — the
981            // qkv kernel norms rq+rk rows; concatenate k first).
982            let mut k0 = e.uninit((ctx + b) * nkv * hd)?;
983            e.copy_into(&mut k0, 0, &k0c, ctx * nkv * hd)?;
984            e.copy_into(&mut k0, ctx * nkv * hd, &k0b, b * nkv * hd)?;
985            let mut v = e.uninit((ctx + b) * nkv * hd)?;
986            e.copy_into(&mut v, 0, &v0c, ctx * nkv * hd)?;
987            e.copy_into(&mut v, ctx * nkv * hd, &v0b, b * nkv * hd)?;
988
989            if li == 0 {
990                if let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP") {
991                    let v = e.dtoh(&q0)?;
992                    let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
993                    std::fs::write(format!("{dir}/memra-l0_q0.f32"), bytes)?;
994                }
995            }
996            let mut q = e.uninit(b * nh * hd)?;
997            let mut k = e.uninit((ctx + b) * nkv * hd)?;
998            // rms over head_dim rows: q has b*nh rows, k has (ctx+b)*nkv rows.
999            e.rms_norm(&q0, &l.q_norm, &mut q, hd, b * nh, c.eps)?;
1000            if li == 0 {
1001                if let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP") {
1002                    let v = e.dtoh(&q)?;
1003                    let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
1004                    std::fs::write(format!("{dir}/memra-l0_qn.f32"), bytes)?;
1005                }
1006            }
1007            e.rms_norm(&k0, &l.k_norm, &mut k, hd, (ctx + b) * nkv, c.eps)?;
1008
1009            // rope: q at block positions, k at ctx-then-block positions (absolute).
1010            let norope = std::env::var("MEMRA_DFLASH_NOROPE").is_ok();
1011            if !norope {
1012                self.rope_rows(e, &mut q, &pos_blk, nh, b)?;
1013            }
1014            if li == 0 {
1015                if let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP") {
1016                    let dump = |name: &str,
1017                                t: &cudarc::driver::CudaSlice<f32>|
1018                     -> Result<(), Box<dyn std::error::Error>> {
1019                        let v = e.dtoh(t)?;
1020                        let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
1021                        std::fs::write(format!("{dir}/memra-l0_{name}.f32"), bytes)?;
1022                        Ok(())
1023                    };
1024                    dump("xn", &xn)?;
1025                    dump("q_prerope", &q)?;
1026                }
1027            }
1028            // k rows are laid out [row, nkv, hd] with row-major tokens — rope_neox expects
1029            // (n_heads, n_tokens); ctx and block ropes run as one call over ctx+b tokens.
1030            let pos_all = e.htod_i32(pos)?;
1031            if !norope {
1032                self.rope_rows(e, &mut k, &pos_all, nkv, ctx + b)?;
1033            }
1034
1035            // full non-causal attention: every block query sees all ctx+b keys.
1036            let mut attn = e.uninit(b * nh * hd)?;
1037            let scale = 1.0f32 / (hd as f32).sqrt();
1038            // NAIVE SDPA for first light: fa_prefill's NON-CAUSAL arm with T != T_kv is
1039            // BROKEN (attn maxdiff 0.34 vs the torch oracle; q/k inputs bit-close — no
1040            // existing caller exercises that shape class, jsonl 2026-07-13). The 16 x
1041            // (ctx+16) block attention is tiny; the fa arm returns behind this seam once
1042            // its kernel is fixed + parity-gated.
1043            if std::env::var("MEMRA_DFLASH_FA").is_ok() {
1044                e.fa_prefill(&q, &k, &v, &mut attn, hd, nh, nkv, b, ctx + b, scale, false)?;
1045            } else {
1046                e.sdpa_naive(&q, &k, &v, &mut attn, hd, nh, nkv, b, ctx + b, scale, false)?;
1047            }
1048
1049            let o = self.mm(e, &l.wo, &attn, b, nh * hd, h)?;
1050            let mut x1 = e.uninit(b * h)?;
1051            e.add(&o, &x, &mut x1, b * h)?;
1052            if li == 0 {
1053                if let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP") {
1054                    let dump = |name: &str,
1055                                t: &cudarc::driver::CudaSlice<f32>|
1056                     -> Result<(), Box<dyn std::error::Error>> {
1057                        let v = e.dtoh(t)?;
1058                        let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
1059                        std::fs::write(format!("{dir}/memra-l0_{name}.f32"), bytes)?;
1060                        Ok(())
1061                    };
1062                    dump("q", &q)?;
1063                    dump("k", &k)?;
1064                    dump("attn", &attn)?;
1065                    dump("x1", &x1)?;
1066                }
1067            }
1068
1069            // mlp
1070            let mut x1n = e.uninit(b * h)?;
1071            e.rms_norm(&x1, &l.ln_post, &mut x1n, h, b, c.eps)?;
1072            let gate = self.mm(e, &l.w_gate, &x1n, b, h, c.n_ff)?;
1073            let up_ = self.mm(e, &l.w_up, &x1n, b, h, c.n_ff)?;
1074            let mut act = e.uninit(b * c.n_ff)?;
1075            e.silu_mul(&gate, &up_, &mut act, b * c.n_ff)?;
1076            let down = self.mm(e, &l.w_down, &act, b, c.n_ff, h)?;
1077            let mut x2 = e.uninit(b * h)?;
1078            e.add(&down, &x1, &mut x2, b * h)?;
1079            x = x2;
1080            if let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP") {
1081                let v = e.dtoh(&x)?;
1082                let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
1083                std::fs::write(format!("{dir}/memra-layer{li}_out.f32"), bytes)?;
1084            }
1085        }
1086        let mut out = e.uninit(b * h)?;
1087        e.rms_norm(&x, &self.norm, &mut out, h, b, c.eps)?;
1088        Ok(out)
1089    }
1090}
1091
1092/// Draft KV cache (round-cost fix, 2026-07-13): per-layer normed+roped ctx K and raw ctx V,
1093/// append-only in committed order. Block K/V land TRANSIENTLY at [len..len+b] each round
1094/// (never committed — the reference crops them identically). Kills the per-round full-ctx
1095/// projection recompute (first light was O(ctx)/round -> 7 tok/s).
1096pub struct DflashKv {
1097    pub k: Vec<CudaSlice<f32>>, // per layer [cap + block, nkv*hd]
1098    pub v: Vec<CudaSlice<f32>>,
1099    pub len: usize,
1100    pub cap: usize,
1101}
1102
1103impl DflashKv {
1104    pub fn new(
1105        e: &Engine,
1106        cfg: &DflashCfg,
1107        cap: usize,
1108    ) -> Result<Self, Box<dyn std::error::Error>> {
1109        let rowsz = cfg.n_kv * cfg.head_dim;
1110        let mut k = Vec::with_capacity(cfg.n_layer);
1111        let mut v = Vec::with_capacity(cfg.n_layer);
1112        for _ in 0..cfg.n_layer {
1113            k.push(e.uninit((cap + cfg.block_size) * rowsz)?);
1114            v.push(e.uninit((cap + cfg.block_size) * rowsz)?);
1115        }
1116        Ok(Self { k, v, len: 0, cap })
1117    }
1118}
1119
1120impl DflashDraft {
1121    /// Ingest `t` NEW ctx-feature rows (committed order, absolute positions `pos_new`) into
1122    /// the draft KV: per layer k/v projections + k head-norm + rope, appended at kv.len.
1123    pub fn ingest_ctx(
1124        &self,
1125        e: &Engine,
1126        kv: &mut DflashKv,
1127        feats: &CudaSlice<f32>,
1128        pos_new: &[i32],
1129        t: usize,
1130    ) -> Result<(), Box<dyn std::error::Error>> {
1131        let c = &self.cfg;
1132        let (h, nkv, hd) = (c.hidden, c.n_kv, c.head_dim);
1133        assert!(kv.len + t <= kv.cap, "draft kv overflow");
1134        let pos_d = e.htod_i32(pos_new)?;
1135        for (li, l) in self.layers.iter().enumerate() {
1136            let k0 = self.mm(e, &l.wk, feats, t, h, nkv * hd)?;
1137            let v0 = self.mm(e, &l.wv, feats, t, h, nkv * hd)?;
1138            let mut kn = e.uninit(t * nkv * hd)?;
1139            e.rms_norm(&k0, &l.k_norm, &mut kn, hd, t * nkv, c.eps)?;
1140            self.rope_rows(e, &mut kn, &pos_d, nkv, t)?;
1141            e.copy_into(&mut kv.k[li], kv.len * nkv * hd, &kn, t * nkv * hd)?;
1142            e.copy_into(&mut kv.v[li], kv.len * nkv * hd, &v0, t * nkv * hd)?;
1143        }
1144        kv.len += t;
1145        Ok(())
1146    }
1147
1148    /// Block forward over the CACHED ctx KV: only the 16 block rows are projected per layer;
1149    /// block K/V land transiently at kv[len..len+b]. Bit-class-identical to forward_block
1150    /// (same kernels, same per-row programs; ONLY the ctx K/V recompute is cached).
1151    pub fn forward_round(
1152        &self,
1153        e: &Engine,
1154        kv: &mut DflashKv,
1155        noise_emb: &CudaSlice<f32>,
1156        pos_block: &[i32],
1157    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1158        let c = &self.cfg;
1159        let (h, nh, nkv, hd) = (c.hidden, c.n_head, c.n_kv, c.head_dim);
1160        let b = c.block_size;
1161        assert_eq!(pos_block.len(), b);
1162        let ctx = kv.len;
1163        let pos_blk = e.htod_i32(pos_block)?;
1164        let mut x = e.clone_dtod(noise_emb)?;
1165        for (li, l) in self.layers.iter().enumerate() {
1166            let mut xn = e.uninit(b * h)?;
1167            e.rms_norm(&x, &l.ln_in, &mut xn, h, b, c.eps)?;
1168            let q0 = self.mm(e, &l.wq, &xn, b, h, nh * hd)?;
1169            let k0b = self.mm(e, &l.wk, &xn, b, h, nkv * hd)?;
1170            let v0b = self.mm(e, &l.wv, &xn, b, h, nkv * hd)?;
1171            let mut q = e.uninit(b * nh * hd)?;
1172            let mut kb = e.uninit(b * nkv * hd)?;
1173            e.rms_norm(&q0, &l.q_norm, &mut q, hd, b * nh, c.eps)?;
1174            e.rms_norm(&k0b, &l.k_norm, &mut kb, hd, b * nkv, c.eps)?;
1175            self.rope_rows(e, &mut q, &pos_blk, nh, b)?;
1176            self.rope_rows(e, &mut kb, &pos_blk, nkv, b)?;
1177            e.copy_into(&mut kv.k[li], ctx * nkv * hd, &kb, b * nkv * hd)?;
1178            e.copy_into(&mut kv.v[li], ctx * nkv * hd, &v0b, b * nkv * hd)?;
1179            let mut attn = e.uninit(b * nh * hd)?;
1180            let scale = 1.0f32 / (hd as f32).sqrt();
1181            if std::env::var("MEMRA_DFLASH_FA").is_ok() {
1182                e.fa_prefill(
1183                    &q,
1184                    &kv.k[li],
1185                    &kv.v[li],
1186                    &mut attn,
1187                    hd,
1188                    nh,
1189                    nkv,
1190                    b,
1191                    ctx + b,
1192                    scale,
1193                    false,
1194                )?;
1195            } else {
1196                e.sdpa_naive(
1197                    &q,
1198                    &kv.k[li],
1199                    &kv.v[li],
1200                    &mut attn,
1201                    hd,
1202                    nh,
1203                    nkv,
1204                    b,
1205                    ctx + b,
1206                    scale,
1207                    false,
1208                )?;
1209            }
1210            let o = self.mm(e, &l.wo, &attn, b, nh * hd, h)?;
1211            let mut x1 = e.uninit(b * h)?;
1212            e.add(&o, &x, &mut x1, b * h)?;
1213            let mut x1n = e.uninit(b * h)?;
1214            e.rms_norm(&x1, &l.ln_post, &mut x1n, h, b, c.eps)?;
1215            let gate = self.mm(e, &l.w_gate, &x1n, b, h, c.n_ff)?;
1216            let up_ = self.mm(e, &l.w_up, &x1n, b, h, c.n_ff)?;
1217            let mut act = e.uninit(b * c.n_ff)?;
1218            e.silu_mul(&gate, &up_, &mut act, b * c.n_ff)?;
1219            let down = self.mm(e, &l.w_down, &act, b, c.n_ff, h)?;
1220            let mut x2 = e.uninit(b * h)?;
1221            e.add(&down, &x1, &mut x2, b * h)?;
1222            x = x2;
1223        }
1224        let mut out = e.uninit(b * h)?;
1225        e.rms_norm(&x, &self.norm, &mut out, h, b, c.eps)?;
1226        Ok(out)
1227    }
1228}
1229
1230// ================= DFlash spec round (greedy, first light) =================
1231// Exact contract: identical output stream to plain greedy decode BY CONSTRUCTION — the
1232// target's batched verify argmax decides every committed token; the drafter only proposes.
1233// (Same verify+rewind pattern as generate_spec_gemma's eager round; t=16 verify rides the
1234// straddle-split-safe fa_decode_rows.)
1235impl crate::hybrid::HybridModel {
1236    pub fn generate_spec_dflash(
1237        &self,
1238        e: &Engine,
1239        draft: &DflashDraft,
1240        prompt: &[u32],
1241        max_new: usize,
1242        eos: &[u32],
1243    ) -> Result<Vec<u32>, Box<dyn std::error::Error>> {
1244        use crate::cache::{Cache, DflashTapSink};
1245        let n_embd = self.cfg.n_embd as usize;
1246        let c = &draft.cfg;
1247        assert_eq!(n_embd, c.hidden, "draft hidden must match target n_embd");
1248        let b = c.block_size;
1249        let n_taps = c.target_layer_ids.len();
1250        let max_ctx = prompt.len() + max_new + b + 8;
1251        // First light holds ctx <= sliding_window: the draft was trained with 4 sliding
1252        // layers (window 2048) and the first-light attention is windowless full — inside
1253        // the window the two are identical. The depth cell (1736 + 128) fits.
1254        assert!(
1255            max_ctx <= c.sliding_window,
1256            "first-light dflash round is windowless — ctx cap {} exceeds the draft window {}",
1257            max_ctx,
1258            c.sliding_window
1259        );
1260        let mut cache = Cache::new(e, &self.cfg, max_ctx)?;
1261
1262        // ---- prime with taps armed ----
1263        let tp = prompt.len();
1264        cache.dflash_taps = Some(DflashTapSink {
1265            layer_ids: c.target_layer_ids.clone(),
1266            buf: e.uninit(tp * n_taps * n_embd)?,
1267            hidden: n_embd,
1268            t: tp,
1269            base: 0,
1270        });
1271        let t_prime = std::time::Instant::now();
1272        let (logits, _h_seed, _hiddens) = self.prime_cache(e, prompt, &mut cache, 0)?;
1273        let mut last = crate::forward::argmax(&logits) as u32;
1274        // draft KV cache: ingest the prompt's ctx features once; per round only the kept
1275        // rows ingest + the block projects (round cost O(block), not O(ctx)).
1276        let mut dkv = DflashKv::new(e, &draft.cfg, max_ctx)?;
1277        {
1278            // CHUNKED ingest (depth OOM fix): the 1736-row prompt tap buffer is ~224MB f32;
1279            // running fc + 5-layer k/v projection over it in one shot stacks another
1280            // ~300MB of transients on the ~21.3GB trunk peak. 256-row windows bound the
1281            // transient set; identical values (row-independent ops).
1282            let taps = cache.dflash_taps.take().unwrap();
1283            let n_taps_h = n_taps * n_embd;
1284            let mut r0 = 0usize;
1285            while r0 < tp {
1286                let t_c = (tp - r0).min(256);
1287                let tv = e.view(&taps.buf, tp * n_taps_h);
1288                let win = tv.slice(r0 * n_taps_h..(r0 + t_c) * n_taps_h);
1289                let mut chunk = e.uninit(t_c * n_taps_h)?;
1290                e.copy_view_into(&mut chunk, 0, &win, t_c * n_taps_h)?;
1291                let f = draft.ctx_features(e, &chunk, t_c)?;
1292                let pos_c: Vec<i32> = ((r0 as i32)..(r0 + t_c) as i32).collect();
1293                draft.ingest_ctx(e, &mut dkv, &f, &pos_c, t_c)?;
1294                r0 += t_c;
1295            }
1296        }
1297        let mut ctx_len = tp;
1298        e.stream().synchronize()?;
1299        // published prime wall (the run-spec/gemma-gate timing contract subtracts it)
1300        crate::PRIME_NANOS.store(
1301            t_prime.elapsed().as_nanos() as u64,
1302            std::sync::atomic::Ordering::Relaxed,
1303        );
1304
1305        // embed-scale seam (MEMRA_DFLASH_EMB_SCALE): gemma trunks scale embeddings by
1306        // sqrt(n_embd) INSIDE the forward; whether the z-lab gemma4 training fed the
1307        // drafter scaled or raw embed rows is not visible from the reference (qwen path
1308        // uses raw embed_tokens). Acceptance arbitrates; default raw.
1309        let emb_scale = if std::env::var("MEMRA_DFLASH_EMB_SCALE").as_deref() == Ok("1") {
1310            (n_embd as f32).sqrt()
1311        } else {
1312            1.0
1313        };
1314
1315        let mut out = Vec::with_capacity(max_new);
1316        let n_vocab = self.output.out_features();
1317        // VERIFY WIDTH (MEMRA_DFLASH_VERIFY_T, default 8): the drafter always drafts a full
1318        // block (its trained mask pattern) but only the first vt rows go through the target
1319        // verify — the t=16 verify rides the untuned b16 tier at ~32% of the byte wall
1320        // (65ms/verify) while b8 rides the tuned r2 tier; with ~2.7 committed/round the
1321        // deep block positions almost never survive anyway. Exactness unaffected (verify
1322        // still decides every committed token).
1323        let vt_cap: usize = std::env::var("MEMRA_DFLASH_VERIFY_T")
1324            .ok()
1325            .and_then(|v| v.parse().ok())
1326            .unwrap_or(8)
1327            .clamp(2, b);
1328        // adaptive verify width (MEMRA_DFLASH_ADAPT!=0, MTP accepted+1 recipe): next round
1329        // verifies one past this round's accepted run, clamped [3, cap].
1330        let adapt = std::env::var("MEMRA_DFLASH_ADAPT").as_deref() != Ok("0");
1331        let mut vt = vt_cap;
1332        let mut attempted = 0usize;
1333        let mut accepted = 0usize;
1334        // The whole round runs in the decode-exact matmul scope: the m=16 draft mms were
1335        // otherwise falling into the prefill-GEMM class (770us/matmul, 17% of the depth
1336        // round). Prime (before this loop) keeps the prefill GEMM path.
1337        e.set_verify_exact(true);
1338        'outer: while out.len() < max_new {
1339            let start = cache.pos; // committed length
1340            // ---- draft: block = [last, MASK x b-1] ----
1341            let mut block: Vec<u32> = vec![c.mask_token_id; b];
1342            block[0] = last;
1343            let mut noise = e.htod(&self.embd.gather(n_embd, &block))?;
1344            if emb_scale != 1.0 {
1345                e.scale_inplace(&mut noise, emb_scale, b * n_embd)?;
1346            }
1347            if std::env::var("MEMRA_DFLASH_DEBUG").as_deref() == Ok("1") && start == cache.pos {
1348                let nv = e.dtoh(&noise)?;
1349                let r0: f32 = nv[..n_embd].iter().map(|x| x * x).sum::<f32>().sqrt();
1350                let r1: f32 = nv[n_embd..2 * n_embd]
1351                    .iter()
1352                    .map(|x| x * x)
1353                    .sum::<f32>()
1354                    .sqrt();
1355                eprintln!(
1356                    "[dflash noise] |row0(last)|={r0:.3} |row1(MASK id {})|={r1:.3}",
1357                    c.mask_token_id
1358                );
1359            }
1360            let pos_block: Vec<i32> = ((start as i32)..(start + b) as i32).collect();
1361            let dh = draft.forward_round(e, &mut dkv, &noise, &pos_block)?;
1362            // draft tokens = argmax(lm_head(h rows 1..b))
1363            let mut rows = e.uninit((b - 1) * n_embd)?;
1364            {
1365                let dv = e.view(&dh, b * n_embd);
1366                let tail = dv.slice(n_embd..b * n_embd);
1367                e.copy_view_into(&mut rows, 0, &tail, (b - 1) * n_embd)?;
1368            }
1369            let mut dl = e.matmul(&self.output, &rows, b - 1)?;
1370            // SEMI-AR MARKOV CHAIN (DSpark head, when present + MEMRA_DFLASH_MARKOV!=0):
1371            // left-to-right, logits_k += W2(W1[prev realized token]) — the whole chain
1372            // stays on-device (chain_d[0] = the pending token; argmax k writes
1373            // chain_d[k+1], the k+1 bias gathers from it). Greedy mirror of the patch's
1374            // _markov_semiar_sample_block.
1375            let markov_on = std::env::var("MEMRA_DFLASH_MARKOV").as_deref() != Ok("0");
1376            let mut chain_d = e.stream().alloc_zeros::<u32>(b)?;
1377            if let (Some(mk), true) = (&draft.markov, markov_on) {
1378                e.set_u32_one(&mut chain_d, last)?;
1379                for k in 0..(b - 1) {
1380                    let mut f = e.uninit(mk.rank)?;
1381                    e.gather_row_bf16(&mk.w1_bf16, &chain_d, k, &mut f, mk.rank)?;
1382                    let bias = e.matmul(&mk.w2, &f, 1)?;
1383                    e.add_row_inplace(&mut dl, &bias, n_vocab, k * n_vocab)?;
1384                    e.argmax_token_device_col(&dl, k, n_vocab, &mut chain_d, k + 1)?;
1385                }
1386            } else {
1387                for i in 0..(b - 1) {
1388                    e.argmax_token_device_col(&dl, i, n_vocab, &mut chain_d, i + 1)?;
1389                }
1390            }
1391            let chain = e.dtoh_u32(&chain_d)?;
1392            let dtoks = &chain[1..];
1393            for (i, &dt) in dtoks.iter().enumerate() {
1394                block[i + 1] = dt;
1395            }
1396            let dbg = std::env::var("MEMRA_DFLASH_DEBUG").as_deref() == Ok("1");
1397
1398            // ---- verify: one t=vt target forward with taps armed ----
1399            let vblock = &block[..vt];
1400            cache.dflash_taps = Some(DflashTapSink {
1401                layer_ids: c.target_layer_ids.clone(),
1402                buf: e.uninit(vt * n_taps * n_embd)?,
1403                hidden: n_embd,
1404                t: vt,
1405                base: 0,
1406            });
1407            let (vam, _vh) = self.gemma4_decode_step_t_am(e, vblock, start, &mut cache)?;
1408            let taps = cache.dflash_taps.take().unwrap();
1409            if dbg {
1410                eprintln!(
1411                    "[dflash r] start={start} last={last}\n  draft={:?}\n  vam  ={:?}",
1412                    &block[1..],
1413                    &vam
1414                );
1415            }
1416
1417            // ---- accept ----
1418            let mut m = 0usize;
1419            while m < vt - 1 && block[m + 1] as usize == vam[m] as usize {
1420                m += 1;
1421            }
1422            attempted += vt - 1;
1423            accepted += m;
1424            out.push(last);
1425            if eos.contains(&last) {
1426                break 'outer;
1427            }
1428            for &dt in &block[1..=m] {
1429                out.push(dt);
1430                if eos.contains(&dt) {
1431                    break 'outer;
1432                }
1433                if out.len() >= max_new {
1434                    break 'outer;
1435                }
1436            }
1437            let next = vam[m] as u32;
1438
1439            // ---- commit/rollback: keep m+1 of the b appended rows ----
1440            let keep = m + 1;
1441            for kvl in cache.kv.iter_mut().flatten() {
1442                kvl.len -= vt - keep;
1443                e.set_i32_one(&mut kvl.len_d, kvl.len as i32)?;
1444            }
1445            cache.pos -= vt - keep;
1446
1447            // ---- ingest the kept rows' ctx features into the draft KV ----
1448            {
1449                let tv = e.view(&taps.buf, vt * n_taps * n_embd);
1450                let keep_view = tv.slice(0..keep * n_taps * n_embd);
1451                let mut kept = e.uninit(keep * n_taps * n_embd)?;
1452                e.copy_view_into(&mut kept, 0, &keep_view, keep * n_taps * n_embd)?;
1453                let f = draft.ctx_features(e, &kept, keep)?;
1454                let pos_k: Vec<i32> = ((ctx_len as i32)..(ctx_len + keep) as i32).collect();
1455                draft.ingest_ctx(e, &mut dkv, &f, &pos_k, keep)?;
1456                ctx_len += keep;
1457            }
1458            last = next;
1459            if adapt {
1460                vt = (m + 2).clamp(3, vt_cap);
1461            }
1462        }
1463        e.set_verify_exact(false);
1464        if std::env::var("MEMRA_SPEC_STATS").as_deref() == Ok("1") {
1465            eprintln!(
1466                "[dflash] acceptance {accepted}/{attempted} = {:.3}",
1467                accepted as f64 / attempted.max(1) as f64
1468            );
1469        }
1470        Ok(out)
1471    }
1472}
1473
1474// ================= Engine-bundle slice 1: batched GDN state snapshot ====================
1475// DSF-ROUNDCOST-20260820 §1.1 measured the dspark round's `cache.snapshot(e)` at 0.67 ms
1476// native wall — 48 linear layers x {conv, ssm} x (alloc_zeros + memcpy_dtod) of pure
1477// dispatch serialization, zero kernels. This batcher holds ONE persistent CacheSnapshot
1478// (buffers allocated on round 1, reused every round — kills the per-round alloc/memset
1479// churn) plus device pointer tables, so a round's snap is one small H2D table refresh
1480// (the ssm handles ping-pong per verify row, so live pointers are re-read each round;
1481// conv handles are rolled in place and never move) + TWO `copy_batch_uniform_f32`
1482// launches. Bytes, buffers and stream order are identical to `Cache::snapshot`; only the
1483// dispatch count changes, so acceptance and streams stay bit-identical (E2E-gated).
1484// `MEMRA_STATE_COPY_BATCH=0` reverts to the legacy per-layer snapshot.
1485
1486pub(crate) struct DsparkSnapBatch {
1487    pub(crate) snap: crate::cache::CacheSnapshot,
1488    /// Linear-attention layer indices, in `conv_table`/`ssm_table` order.
1489    lin: Vec<usize>,
1490    /// [src_0..src_{n-1}, dst_0..dst_{n-1}] — live conv states -> snapshot conv buffers.
1491    conv_table: CudaSlice<u64>,
1492    ssm_table: CudaSlice<u64>,
1493    host_ssm: Vec<u64>,
1494    conv_words: usize,
1495    ssm_words: usize,
1496}
1497
1498impl DsparkSnapBatch {
1499    /// Build from a fresh full snapshot (this IS round 1's snap — the caller uses
1500    /// `self.snap` directly after `new`). Returns None when the cache has no linear
1501    /// layers or their state sizes are non-uniform (a future hybrid shape) — the caller
1502    /// then stays on the legacy per-layer snapshot rather than copying wrong byte counts.
1503    pub(crate) fn new(
1504        e: &Engine,
1505        cache: &crate::cache::Cache,
1506    ) -> Result<Option<Self>, Box<dyn std::error::Error>> {
1507        use cudarc::driver::DevicePtr;
1508        let snap = cache.snapshot(e)?;
1509        let lin: Vec<usize> = (0..cache.recur.len())
1510            .filter(|&il| cache.recur[il].is_some())
1511            .collect();
1512        if lin.is_empty() {
1513            return Ok(None);
1514        }
1515        let first = cache.recur[lin[0]].as_ref().unwrap();
1516        let (conv_words, ssm_words) = (first.conv_state.len(), first.ssm_state.len());
1517        for &il in &lin {
1518            let rl = cache.recur[il].as_ref().unwrap();
1519            if rl.conv_state.len() != conv_words || rl.ssm_state.len() != ssm_words {
1520                return Ok(None);
1521            }
1522        }
1523        let n = lin.len();
1524        let mut host_conv = vec![0u64; 2 * n];
1525        let mut host_ssm = vec![0u64; 2 * n];
1526        {
1527            let s = &e.gpu.stream();
1528            for (k, &il) in lin.iter().enumerate() {
1529                let rl = cache.recur[il].as_ref().unwrap();
1530                let (pc, _g0) = rl.conv_state.device_ptr(s);
1531                let (ps, _g1) = rl.ssm_state.device_ptr(s);
1532                let (dc, _g2) = snap.conv[il].as_ref().unwrap().device_ptr(s);
1533                let (ds, _g3) = snap.ssm[il].as_ref().unwrap().device_ptr(s);
1534                host_conv[k] = pc as u64;
1535                host_conv[n + k] = dc as u64;
1536                host_ssm[k] = ps as u64;
1537                host_ssm[n + k] = ds as u64;
1538            }
1539        }
1540        let conv_table = e.htod_u64(&host_conv)?;
1541        let ssm_table = e.htod_u64(&host_ssm)?;
1542        Ok(Some(Self {
1543            snap,
1544            lin,
1545            conv_table,
1546            ssm_table,
1547            host_ssm,
1548            conv_words,
1549            ssm_words,
1550        }))
1551    }
1552
1553    /// The per-round snap: refresh kv lens/pos host-side (as `snapshot_into` does),
1554    /// re-read the live ssm handles into the table (gdn ping-pong moves them; the conv
1555    /// handles and every snapshot dst are stable), then two batched-copy launches.
1556    pub(crate) fn refresh(
1557        &mut self,
1558        e: &Engine,
1559        cache: &crate::cache::Cache,
1560    ) -> Result<(), Box<dyn std::error::Error>> {
1561        use cudarc::driver::DevicePtr;
1562        for il in 0..cache.kv.len() {
1563            self.snap.kv_len[il] = cache.kv[il].as_ref().map(|kvl| kvl.len);
1564        }
1565        self.snap.pos = cache.pos;
1566        let n = self.lin.len();
1567        {
1568            let s = &e.gpu.stream();
1569            for (k, &il) in self.lin.iter().enumerate() {
1570                let rl = cache.recur[il].as_ref().unwrap();
1571                let (ps, _g) = rl.ssm_state.device_ptr(s);
1572                self.host_ssm[k] = ps as u64;
1573            }
1574        }
1575        e.htod_u64_into(&self.host_ssm, &mut self.ssm_table)?;
1576        e.copy_batch_uniform_f32(&self.conv_table, n, self.conv_words)?;
1577        e.copy_batch_uniform_f32(&self.ssm_table, n, self.ssm_words)?;
1578        Ok(())
1579    }
1580}
1581
1582// ================= DSpark spec round, QWEN-HYBRID target (lane/dspark-q38-recover) =====
1583// The q38 twin of generate_spec_dflash. Same drafter machinery (rounds, markov chain,
1584// draft KV, adaptive verify width); the TARGET side swaps gemma4's dense verify for the
1585// qwen serving-class verify funnel (dspark_verify_t_am) + snapshot/rollback, because the
1586// hybrid GDN conv/ssm state mutates in place — dense KV truncation cannot roll it back.
1587// Exactness contract unchanged: identical stream to plain greedy BY CONSTRUCTION (the
1588// target's verify argmax decides every committed token).
1589impl crate::hybrid::HybridModel {
1590    pub fn generate_spec_dspark(
1591        &self,
1592        e: &Engine,
1593        draft: &DflashDraft,
1594        prompt: &[u32],
1595        max_new: usize,
1596        eos: &[u32],
1597    ) -> Result<Vec<u32>, Box<dyn std::error::Error>> {
1598        use crate::cache::{Cache, DflashTapSink};
1599        assert!(
1600            self.cfg.gemma4.is_none(),
1601            "gemma4 targets use generate_spec_dflash; this is the qwen-hybrid arm"
1602        );
1603        let n_embd = self.cfg.n_embd as usize;
1604        let c = &draft.cfg;
1605        assert_eq!(n_embd, c.hidden, "draft hidden must match target n_embd");
1606        let b = c.block_size;
1607        let n_taps = c.target_layer_ids.len();
1608        let max_ctx = prompt.len() + max_new + b + 8;
1609        assert!(
1610            max_ctx <= c.sliding_window,
1611            "dspark round is windowless — ctx cap {} exceeds the draft window {}",
1612            max_ctx,
1613            c.sliding_window
1614        );
1615        let mut cache = Cache::new(e, &self.cfg, max_ctx)?;
1616
1617        // ---- prime with taps armed (chunked prime writes at chunk offsets via sink.base) ----
1618        let tp = prompt.len();
1619        cache.dflash_taps = Some(DflashTapSink {
1620            layer_ids: c.target_layer_ids.clone(),
1621            buf: e.uninit(tp * n_taps * n_embd)?,
1622            hidden: n_embd,
1623            t: tp,
1624            base: 0,
1625        });
1626        let t_prime = std::time::Instant::now();
1627        let (logits, _h_seed, _hiddens) = self.prime_cache(e, prompt, &mut cache, 0)?;
1628        let mut last = crate::forward::argmax(&logits) as u32;
1629        let mut dkv = DflashKv::new(e, &draft.cfg, max_ctx)?;
1630        {
1631            let taps = cache.dflash_taps.take().unwrap();
1632            let n_taps_h = n_taps * n_embd;
1633            let mut r0 = 0usize;
1634            while r0 < tp {
1635                let t_c = (tp - r0).min(256);
1636                let tv = e.view(&taps.buf, tp * n_taps_h);
1637                let win = tv.slice(r0 * n_taps_h..(r0 + t_c) * n_taps_h);
1638                let mut chunk = e.uninit(t_c * n_taps_h)?;
1639                e.copy_view_into(&mut chunk, 0, &win, t_c * n_taps_h)?;
1640                let f = draft.ctx_features(e, &chunk, t_c)?;
1641                let pos_c: Vec<i32> = ((r0 as i32)..(r0 + t_c) as i32).collect();
1642                draft.ingest_ctx(e, &mut dkv, &f, &pos_c, t_c)?;
1643                r0 += t_c;
1644            }
1645        }
1646        let mut ctx_len = tp;
1647        e.stream().synchronize()?;
1648        crate::PRIME_NANOS.store(
1649            t_prime.elapsed().as_nanos() as u64,
1650            std::sync::atomic::Ordering::Relaxed,
1651        );
1652
1653        let mut out = Vec::with_capacity(max_new);
1654        let n_vocab = self.output.out_features();
1655        // Harvest convention (DSPARK-POSTMORTEM-20260820.md): which drafter output rows
1656        // become draft candidates. nd = drafts/round; verify carries [anchor, drafts]
1657        // = up to nd+1 rows. Default = the CHECKPOINT's own strategy census
1658        // (owner-ratified flip, 2026-08-20); explicit env still wins.
1659        let harvest = DsparkHarvest::resolve(&draft.cfg);
1660        let nd = harvest.n_drafts(b);
1661        let r0 = harvest.first_row();
1662        let vt_cap: usize = std::env::var("MEMRA_DFLASH_VERIFY_T")
1663            .ok()
1664            .and_then(|v| v.parse().ok())
1665            .unwrap_or(nd + 1)
1666            .clamp(2, nd + 1);
1667        let adapt = std::env::var("MEMRA_DFLASH_ADAPT").as_deref() != Ok("0");
1668        // Verify-window policy (H4, DSPARK-POSTMORTEM-20260820.md): default =
1669        // confidence-slot tau=.5 when the checkpoint carries an accept-rate head
1670        // (owner-ratified flip 2026-08-20; cell-3 tau ladder knee) — each round's
1671        // window is sized from the head's own slot scores, post-draft pre-verify.
1672        // Head-less checkpoints and MEMRA_DFLASH_ADAPT=0 keep the reactive ladder.
1673        let vt_policy = DsparkVtPolicy::resolve(draft.confidence.is_some());
1674        if vt_policy.is_confidence() {
1675            assert!(
1676                draft.confidence.is_some(),
1677                "MEMRA_DSPARK_VT={vt_policy:?} needs a checkpoint with an accept-rate \
1678                 head (confidence_head.* absent in this export)"
1679            );
1680        }
1681        let mut vt = vt_cap;
1682        let mut attempted = 0usize;
1683        let mut accepted = 0usize;
1684        // Engine-bundle slice 1: persistent batched snapshot (None until round 1; stays
1685        // None — legacy per-layer snapshot — under MEMRA_STATE_COPY_BATCH=0 or when the
1686        // batcher declines the cache shape).
1687        let mut snapb: Option<DsparkSnapBatch> = None;
1688        let mut snapb_off = !crate::spec::state_copy_batch_on();
1689        // Engine-bundle slice 2: deferred chain readback needs the resident embed table
1690        // (verify then embeds chain_d directly). Ladder/stash arms only — the confidence
1691        // policies size vt from a pre-verify head readback and keep the legacy order.
1692        let defer_rb = crate::spec::dspark_defer_readback_on() && !vt_policy.is_confidence();
1693        let (embd_qt, embd_rb) = self.embd.qt_and_row_bytes(n_embd);
1694        let embd_gpu = if !defer_rb || crate::spec::spec_host_embd() {
1695            None
1696        } else {
1697            Some(
1698                self.embd_gpu
1699                    .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload")),
1700            )
1701        };
1702        // Engine-bundle slice 3: per-(segment, vt) verify graphs for the linear-layer runs
1703        // (rides the slice-2 deferred path only — device tokens keep the whole verify off
1704        // the host). PERSISTENT across generations on the model (rebuilding per call
1705        // re-captured ~80 graphs per prompt — measured 97.8 -> 79.1 tok/s e2e); the
1706        // captured bodies are cache-independent: all state reads go through per-round
1707        // refreshed pointer tables and ctx-owned slabs. None = eager walk, byte-identical.
1708        let mut vg_guard = self.dspark_vgraphs.lock().unwrap();
1709        if vg_guard.is_none() && embd_gpu.is_some() && crate::spec::dspark_verify_graph_on() {
1710            *vg_guard = crate::spec::DsparkVerifyGraphs::new(e, &cache, vt_cap, n_embd)?;
1711        }
1712        let vgraphs: &mut Option<crate::spec::DsparkVerifyGraphs> = &mut vg_guard;
1713        // per-phase economics counters (ns) — the verify-toll dataset
1714        let (mut ns_draft, mut ns_snap, mut ns_verify, mut ns_roll, mut ns_ingest) =
1715            (0u64, 0u64, 0u64, 0u64, 0u64);
1716        let mut rounds = 0usize;
1717        let stats = std::env::var("MEMRA_SPEC_STATS").as_deref() == Ok("1");
1718        let clock = |on: bool, e: &Engine| -> std::time::Instant {
1719            if on {
1720                let _ = e.stream().synchronize();
1721            }
1722            std::time::Instant::now()
1723        };
1724        'outer: while out.len() < max_new {
1725            rounds += 1;
1726            let start = cache.pos; // committed length
1727            // ---- draft: block = [last, MASK x b-1] (decode-exact class for the m=b mms) ----
1728            let t0 = clock(stats, e);
1729            e.set_verify_exact(true);
1730            let mut block: Vec<u32> = vec![c.mask_token_id; b];
1731            block[0] = last;
1732            let noise = e.htod(&self.embd.gather(n_embd, &block))?;
1733            let pos_block: Vec<i32> = ((start as i32)..(start + b) as i32).collect();
1734            let dh = draft.forward_round(e, &mut dkv, &noise, &pos_block)?;
1735            // Harvest: logits over rows r0..r0+nd (Dflash: mask rows 1..b-1, fill
1736            // semantics; Dspark: ALL b rows, shifted semantics — row k predicts
1737            // anchor+k+1, so col k of `dl` is the draft for position start+k+1).
1738            let mut rows = e.uninit(nd * n_embd)?;
1739            {
1740                let dv = e.view(&dh, b * n_embd);
1741                let src = dv.slice(r0 * n_embd..(r0 + nd) * n_embd);
1742                e.copy_view_into(&mut rows, 0, &src, nd * n_embd)?;
1743            }
1744            let mut dl = e.matmul(&self.output, &rows, nd)?;
1745            let markov_on = std::env::var("MEMRA_DFLASH_MARKOV").as_deref() != Ok("0");
1746            let mut chain_d = e.stream().alloc_zeros::<u32>(nd + 1)?;
1747            // Confidence policy: stash each slot's markov prev-token embedding (the
1748            // exact `w1` row the chain gathers) into a [nd, rank] buffer — d2d async,
1749            // read back beside `rows` in one host sync after the chain.
1750            let want_conf_emb = vt_policy.is_confidence()
1751                && draft.confidence.as_ref().is_some_and(|ch| ch.with_markov);
1752            let mut conf_emb: Option<CudaSlice<f32>> = match (&draft.markov, want_conf_emb) {
1753                (Some(mk), true) => Some(e.uninit(nd * mk.rank)?),
1754                (None, true) => unreachable!(
1755                    "with_markov confidence head without a markov table — the loader forbids it"
1756                ),
1757                _ => None,
1758            };
1759            if let (Some(mk), true) = (&draft.markov, markov_on) {
1760                e.set_u32_one(&mut chain_d, last)?;
1761                for k in 0..nd {
1762                    let mut f = e.uninit(mk.rank)?;
1763                    e.gather_row_bf16(&mk.w1_bf16, &chain_d, k, &mut f, mk.rank)?;
1764                    if let Some(ce) = conf_emb.as_mut() {
1765                        let fv = e.view(&f, mk.rank);
1766                        e.copy_view_into(ce, k * mk.rank, &fv, mk.rank)?;
1767                    }
1768                    let bias = e.matmul(&mk.w2, &f, 1)?;
1769                    e.add_row_inplace(&mut dl, &bias, n_vocab, k * n_vocab)?;
1770                    e.argmax_token_device_col(&dl, k, n_vocab, &mut chain_d, k + 1)?;
1771                }
1772            } else {
1773                if want_conf_emb {
1774                    // chain_d[0] must carry the anchor — slot 0's prev token.
1775                    e.set_u32_one(&mut chain_d, last)?;
1776                }
1777                for i in 0..nd {
1778                    if let (Some(ce), Some(mk)) = (conf_emb.as_mut(), &draft.markov) {
1779                        let mut f = e.uninit(mk.rank)?;
1780                        e.gather_row_bf16(&mk.w1_bf16, &chain_d, i, &mut f, mk.rank)?;
1781                        let fv = e.view(&f, mk.rank);
1782                        e.copy_view_into(ce, i * mk.rank, &fv, mk.rank)?;
1783                    }
1784                    e.argmax_token_device_col(&dl, i, n_vocab, &mut chain_d, i + 1)?;
1785                }
1786            }
1787            e.set_verify_exact(false);
1788            // MEMRA_DSPARK_CKPT (default 1): verify with the MTP column-stash armed so a
1789            // partial accept restores state directly. =0 keeps the snapshot+replay arm
1790            // (the oracle the stash arm is gated against — MEMRA_DSPARK_CKPT_GATE=1 runs
1791            // BOTH per partial round and byte-compares the resulting cache state).
1792            // Read here (was at the verify site) — slice 2's deferral needs the arm
1793            // choice before deciding whether the chain readback can move past verify.
1794            let ckpt_on = std::env::var("MEMRA_DSPARK_CKPT").as_deref() != Ok("0");
1795            let ckpt_gate = std::env::var("MEMRA_DSPARK_CKPT_GATE").as_deref() == Ok("1");
1796            // Slice 2: under the stash/gate arms with a resident embed table, the chain
1797            // readback is DEFERRED past verify dispatch and merged with the argmax
1798            // readback into one sync. The replay arm (CKPT=0) verifies host tokens and
1799            // keeps the legacy order.
1800            let deferred = embd_gpu.is_some() && (ckpt_on || ckpt_gate);
1801            // ---- H4 confidence window: size THIS round's verify from the head ----
1802            if vt_policy.is_confidence() {
1803                let ch = draft.confidence.as_ref().expect("asserted at loop entry");
1804                let (rows_h, emb_h) = match conf_emb.as_ref() {
1805                    Some(ce) => {
1806                        let (a, b2) = e.dtoh_pair(&rows, ce)?;
1807                        (a, Some(b2))
1808                    }
1809                    None => (e.dtoh(&rows)?, None),
1810                };
1811                let rank = draft.markov.as_ref().map(|m| m.rank).unwrap_or(0);
1812                let mut raws = Vec::with_capacity(nd);
1813                for k in 0..nd {
1814                    let hrow = &rows_h[k * n_embd..(k + 1) * n_embd];
1815                    let emb = emb_h.as_ref().map(|eh| &eh[k * rank..(k + 1) * rank]);
1816                    raws.push(ch.raw_score(hrow, emb));
1817                }
1818                vt = vt_policy
1819                    .size_window(&raws, vt_cap)
1820                    .expect("confidence policies always size the window");
1821            }
1822            // Verify candidates: [anchor, draft 1..nd]. Under Dflash this is the
1823            // historical `block` content; under Dspark it is one longer than the
1824            // drafter's input block (nd = b drafts + the anchor). Deferred rounds build
1825            // this after the merged readback — the bytes are identical (chain_d is
1826            // written before either sync).
1827            let mut cand: Vec<u32> = Vec::with_capacity(nd + 1);
1828            if !deferred {
1829                let chain = e.dtoh_u32(&chain_d)?;
1830                cand.push(last);
1831                cand.extend_from_slice(&chain[1..]);
1832            }
1833            ns_draft += clock(stats, e).duration_since(t0).as_nanos() as u64;
1834
1835            // ---- snapshot (GDN conv/ssm state + KV lens), then verify t=vt ----
1836            let t1 = std::time::Instant::now();
1837            // Slice 1: batched snap (one table refresh + two copy launches) with the
1838            // legacy per-layer snapshot as the kill-switch / non-uniform fallback.
1839            let mut snap_legacy: Option<crate::cache::CacheSnapshot> = None;
1840            if !snapb_off && snapb.is_none() {
1841                snapb = DsparkSnapBatch::new(e, &cache)?;
1842                snapb_off = snapb.is_none();
1843            } else if let Some(sb) = snapb.as_mut() {
1844                sb.refresh(e, &cache)?;
1845            }
1846            let snap: &crate::cache::CacheSnapshot = match snapb.as_ref() {
1847                Some(sb) => &sb.snap,
1848                None => {
1849                    snap_legacy = Some(cache.snapshot(e)?);
1850                    snap_legacy.as_ref().unwrap()
1851                }
1852            };
1853            let _ = &snap_legacy;
1854            ns_snap += clock(stats, e).duration_since(t1).as_nanos() as u64;
1855            let t2 = std::time::Instant::now();
1856            // Slice 3: the tap-sink buffer is persistent per vt in the graphs ctx
1857            // (captured segments bake its address); fully rewritten by every verify.
1858            let tap_buf = match vgraphs.as_mut().and_then(|g| g.tap_bufs.remove(&vt)) {
1859                Some(buf) => buf,
1860                None => e.uninit(vt * n_taps * n_embd)?,
1861            };
1862            cache.dflash_taps = Some(DflashTapSink {
1863                layer_ids: c.target_layer_ids.clone(),
1864                buf: tap_buf,
1865                hidden: n_embd,
1866                t: vt,
1867                base: 0,
1868            });
1869            // The whole fallible verify window runs inside a closure so the Err path can
1870            // return the sink buffer to the ctx pool before propagating (v0.98 review
1871            // carry-over): five `?`s span the window, and an early return would drop
1872            // `cache.dflash_taps` — freeing the buffer whose ADDRESS the model-persistent
1873            // captured graphs bake, so the next generation's replayed tap copies would
1874            // write freed memory. The never-orphan invariant below now holds on EVERY
1875            // exit, not just the EOS/budget break.
1876            let verify_res = (|cache: &mut crate::cache::Cache,
1877                               cand: &mut Vec<u32>,
1878                               vgraphs: &mut Option<crate::spec::DsparkVerifyGraphs>|
1879             -> Result<
1880                (Vec<u32>, Option<crate::spec::DsparkVerifyCkpt>),
1881                Box<dyn std::error::Error>,
1882            > {
1883                if deferred {
1884                    // Slice 2: verify embeds the DEVICE chain (cand layout by construction:
1885                    // chain_d[0] = anchor, chain_d[1..] = drafts), then ONE host sync reads
1886                    // chain + verify argmaxes together — the host dispatched snap + all of
1887                    // verify while the draft was still executing.
1888                    let g = embd_gpu.expect("deferred implies resident embed");
1889                    let (am_d, vck) = self.dspark_verify_t_am_ckpt_dev(
1890                        e,
1891                        &chain_d,
1892                        vt,
1893                        start,
1894                        cache,
1895                        (g, embd_qt, embd_rb),
1896                        vgraphs.as_mut(),
1897                    )?;
1898                    let ch = e.stream().clone_dtoh(&chain_d)?;
1899                    let am = e.stream().clone_dtoh(&am_d)?;
1900                    e.stream().synchronize()?;
1901                    cand.push(last);
1902                    cand.extend_from_slice(&ch[1..]);
1903                    Ok((am, Some(vck)))
1904                } else if ckpt_on || ckpt_gate {
1905                    let (vam, vck) = self.dspark_verify_t_am_ckpt(e, &cand[..vt], start, cache)?;
1906                    Ok((vam, Some(vck)))
1907                } else {
1908                    Ok((self.dspark_verify_t_am(e, &cand[..vt], start, cache)?, None))
1909                }
1910            })(&mut cache, &mut cand, vgraphs);
1911            let (vam, vck) = match verify_res {
1912                Ok(v) => v,
1913                Err(err) => {
1914                    if let (Some(g), Some(taps)) = (vgraphs.as_mut(), cache.dflash_taps.take()) {
1915                        g.tap_bufs.insert(vt, taps.buf);
1916                    }
1917                    return Err(err);
1918                }
1919            };
1920            let taps = cache.dflash_taps.take().unwrap();
1921            // Return the tap buffer to the ctx pool IMMEDIATELY — an EOS/budget break
1922            // between accept and ingest must never orphan an address the captured
1923            // graphs bake (the next generation would alloc a fresh buffer and the
1924            // replayed tap copies would write freed memory). Ingest reads it borrowed.
1925            let tap_local: Option<CudaSlice<f32>> = match vgraphs.as_mut() {
1926                Some(g) => {
1927                    g.tap_bufs.insert(vt, taps.buf);
1928                    None
1929                }
1930                None => Some(taps.buf),
1931            };
1932            let tap_ref: &CudaSlice<f32> = match &tap_local {
1933                Some(b) => b,
1934                None => &vgraphs.as_ref().expect("ctx present above").tap_bufs[&vt],
1935            };
1936            ns_verify += clock(stats, e).duration_since(t2).as_nanos() as u64;
1937
1938            // ---- accept ----
1939            let m = dspark_accept_prefix(&cand, &vam, vt);
1940            attempted += vt - 1;
1941            accepted += m;
1942            out.push(last);
1943            if eos.contains(&last) {
1944                break 'outer;
1945            }
1946            for &dt in &cand[1..=m] {
1947                // budget check BEFORE the push: at real acceptance the final round often
1948                // accepts a draft at the boundary, and push-then-check emitted max_new+1
1949                // tokens (plain emits exactly max_new — the E2E gate read it as a length
1950                // divergence at index max_new with the shared prefix byte-identical).
1951                if out.len() >= max_new {
1952                    break 'outer;
1953                }
1954                out.push(dt);
1955                if eos.contains(&dt) {
1956                    break 'outer;
1957                }
1958            }
1959            let next = vam[m];
1960
1961            // ---- commit/rollback: hybrid state cannot truncate — restore + replay kept ----
1962            let keep = m + 1;
1963            let t3 = std::time::Instant::now();
1964            // Slice 3: rounds whose linear column stash lives in the graphs ctx's slabs
1965            // commit through the slab twin (same semantics, slab-addressed sources).
1966            let slab_commit = vgraphs.as_ref().map(|g| g.round_slab).unwrap_or(false);
1967            if keep < vt {
1968                if ckpt_gate {
1969                    // GATE ARM: stash-restore, snapshot S1; then the replay oracle, snapshot
1970                    // S2; the two cache states must match BIT-FOR-BIT (kv lens, pos, every
1971                    // conv/ssm buffer). Continue from the replay state (proven identical).
1972                    if slab_commit {
1973                        self.dspark_commit_prefix_slab(
1974                            e,
1975                            &mut cache,
1976                            snap,
1977                            vgraphs.as_ref().expect("slab_commit implies ctx"),
1978                            keep,
1979                        )?;
1980                    } else {
1981                        let vck = vck.as_ref().expect("gate arm always fills the ckpt");
1982                        self.dspark_commit_prefix(e, &mut cache, snap, vck, keep)?;
1983                    }
1984                    // host-side state capture (NO device snapshot copies — two extra
1985                    // device snapshots per round OOM'd beside the 15GB trunk)
1986                    let capture = |cache: &Cache| -> Result<
1987                        (usize, Vec<Option<usize>>, Vec<(Vec<f32>, Vec<f32>)>),
1988                        Box<dyn std::error::Error>,
1989                    > {
1990                        let mut lens = Vec::new();
1991                        let mut states = Vec::new();
1992                        for il in 0..cache.kv.len() {
1993                            lens.push(cache.kv[il].as_ref().map(|k| k.len));
1994                            if let Some(rl) = &cache.recur[il] {
1995                                states.push((e.dtoh(&rl.conv_state)?, e.dtoh(&rl.ssm_state)?));
1996                            }
1997                        }
1998                        Ok((cache.pos, lens, states))
1999                    };
2000                    let (p1, l1, st1) = capture(&cache)?;
2001                    crate::pp::restore_cache_checkpoint(e, &self.cfg, None, &mut cache, snap)?;
2002                    let ram = self.dspark_verify_t_am(e, &cand[..keep], start, &mut cache)?;
2003                    assert_eq!(
2004                        &ram[..],
2005                        &vam[..keep],
2006                        "prefix replay must reproduce the verify argmaxes"
2007                    );
2008                    let (p2, l2, st2) = capture(&cache)?;
2009                    assert_eq!(p1, p2, "ckpt-gate: pos mismatch");
2010                    assert_eq!(l1, l2, "ckpt-gate: kv_len mismatch");
2011                    for (il, ((c1, s1v), (c2, s2v))) in st1.iter().zip(&st2).enumerate() {
2012                        let bits = |a: &[f32], b: &[f32]| {
2013                            a.iter().zip(b).all(|(x, y)| x.to_bits() == y.to_bits())
2014                        };
2015                        assert!(
2016                            bits(c1, c2),
2017                            "ckpt-gate: linear layer {il} conv state differs"
2018                        );
2019                        assert!(
2020                            bits(s1v, s2v),
2021                            "ckpt-gate: linear layer {il} ssm state differs"
2022                        );
2023                    }
2024                } else if slab_commit {
2025                    // STASH ARM, slab twin (slice 3): same restore, slab-addressed.
2026                    self.dspark_commit_prefix_slab(
2027                        e,
2028                        &mut cache,
2029                        snap,
2030                        vgraphs.as_ref().expect("slab_commit implies ctx"),
2031                        keep,
2032                    )?;
2033                } else if let Some(vck) = vck.as_ref() {
2034                    // STASH ARM (default): column-state restore, no replay forward.
2035                    self.dspark_commit_prefix(e, &mut cache, snap, vck, keep)?;
2036                } else {
2037                    // REPLAY ARM (MEMRA_DSPARK_CKPT=0): the original snapshot+replay oracle.
2038                    crate::pp::restore_cache_checkpoint(e, &self.cfg, None, &mut cache, snap)?;
2039                    debug_assert_eq!(cache.pos, start, "rollback landed off the round start");
2040                    let ram = self.dspark_verify_t_am(e, &cand[..keep], start, &mut cache)?;
2041                    debug_assert_eq!(
2042                        &ram[..],
2043                        &vam[..keep],
2044                        "prefix replay must reproduce the verify argmaxes"
2045                    );
2046                }
2047            }
2048            ns_roll += clock(stats, e).duration_since(t3).as_nanos() as u64;
2049
2050            // ---- ingest the kept rows' ctx features into the draft KV ----
2051            let t4 = std::time::Instant::now();
2052            {
2053                let tv = e.view(tap_ref, vt * n_taps * n_embd);
2054                let keep_view = tv.slice(0..keep * n_taps * n_embd);
2055                let mut kept = e.uninit(keep * n_taps * n_embd)?;
2056                e.copy_view_into(&mut kept, 0, &keep_view, keep * n_taps * n_embd)?;
2057                let f = draft.ctx_features(e, &kept, keep)?;
2058                let pos_k: Vec<i32> = ((ctx_len as i32)..(ctx_len + keep) as i32).collect();
2059                draft.ingest_ctx(e, &mut dkv, &f, &pos_k, keep)?;
2060                ctx_len += keep;
2061            }
2062            ns_ingest += clock(stats, e).duration_since(t4).as_nanos() as u64;
2063            last = next;
2064            // Ladder update only — under the confidence policies vt is recomputed
2065            // from the head every round, post-draft pre-verify.
2066            if !vt_policy.is_confidence() && adapt {
2067                vt = (m + 2).clamp(3, vt_cap);
2068            }
2069        }
2070        if stats {
2071            let ms = |n: u64| n as f64 / 1e6;
2072            eprintln!(
2073                "[dspark-q38] acceptance {accepted}/{attempted} = {:.3} rounds={rounds} \
2074                 draft={:.1}ms snap={:.1}ms verify={:.1}ms rollback+replay={:.1}ms ingest={:.1}ms",
2075                accepted as f64 / attempted.max(1) as f64,
2076                ms(ns_draft),
2077                ms(ns_snap),
2078                ms(ns_verify),
2079                ms(ns_roll),
2080                ms(ns_ingest)
2081            );
2082        }
2083        Ok(out)
2084    }
2085}
2086
2087// ================= DSpark SERVING session (lane/dspark-q38-recover serve route) =========
2088// Burst-scoped state for the worker's dspark spec arm — the qwen-hybrid twin of
2089// GemmaSpecSession. Holds the trunk cache + draft KV + the round loop's carry state
2090// (`last`, ctx_len, adaptive vt) so the scheduler round-robins other sessions between
2091// bursts. The round body is generate_spec_dspark's loop, hoisted; that bin arm stays the
2092// banked oracle (E2E gate), and the serve-route smoke gates this twin byte-identical to
2093// a spec-off boot over the real HTTP surface. Exactness contract unchanged: the target's
2094// verify argmax decides every committed token, so the stream equals plain greedy BY
2095// CONSTRUCTION on every accept path (ckpt stash, gate, replay).
2096pub struct DsparkSpecSession {
2097    pub cache: crate::cache::Cache,
2098    dkv: DflashKv,
2099    last: u32,
2100    ctx_len: usize,
2101    vt: usize,
2102    pub rounds: usize,
2103    max_ctx: usize,
2104    done: bool,
2105    /// Engine-bundle slice 1: persistent batched snapshot (buffers + pointer tables live
2106    /// with the session so bursts reuse them). None until the first round; stays None —
2107    /// legacy per-layer snapshot — when `snapb_off`.
2108    snapb: Option<DsparkSnapBatch>,
2109    snapb_off: bool,
2110}
2111
2112impl DsparkSpecSession {
2113    pub fn cache_max_ctx(&self) -> usize {
2114        self.max_ctx
2115    }
2116    pub fn finished(&self) -> bool {
2117        self.done
2118    }
2119    pub fn pos(&self) -> usize {
2120        self.cache.pos
2121    }
2122}
2123
2124impl crate::hybrid::HybridModel {
2125    /// Turn-1 prime: trunk prefill with taps armed + chunked ctx ingest into the draft KV.
2126    /// Mirrors generate_spec_dspark's prime block exactly (chunk offsets via sink.base are
2127    /// handled inside prime_cache's tick loop; the 256-row ingest chunks match the bin arm).
2128    pub fn dspark_spec_session_new(
2129        &self,
2130        e: &Engine,
2131        draft: &DflashDraft,
2132        prompt: &[u32],
2133        ctx_cap: usize,
2134    ) -> Result<DsparkSpecSession, Box<dyn std::error::Error>> {
2135        use crate::cache::{Cache, DflashTapSink};
2136        assert!(
2137            self.cfg.gemma4.is_none(),
2138            "gemma4 targets use the assistant-drafter route; dspark is the qwen-hybrid arm"
2139        );
2140        let n_embd = self.cfg.n_embd as usize;
2141        let c = &draft.cfg;
2142        assert_eq!(n_embd, c.hidden, "draft hidden must match target n_embd");
2143        let b = c.block_size;
2144        let n_taps = c.target_layer_ids.len();
2145        // The dspark round is windowless: every position the session will ever hold must
2146        // fit the draft window. Clamp the session ctx to it and refuse prompts that
2147        // cannot take even one round — admission falls back to the plain path.
2148        let max_ctx = ctx_cap.min(c.sliding_window);
2149        if prompt.len() + b + 8 > max_ctx {
2150            return Err(format!(
2151                "dspark session needs {} ctx (prompt {} + block {b} + 8), cap {max_ctx}",
2152                prompt.len() + b + 8,
2153                prompt.len()
2154            )
2155            .into());
2156        }
2157        let mut cache = Cache::new(e, &self.cfg, max_ctx)?;
2158        let tp = prompt.len();
2159        cache.dflash_taps = Some(DflashTapSink {
2160            layer_ids: c.target_layer_ids.clone(),
2161            buf: e.uninit(tp * n_taps * n_embd)?,
2162            hidden: n_embd,
2163            t: tp,
2164            base: 0,
2165        });
2166        let (logits, _h_seed, _hiddens) = self.prime_cache(e, prompt, &mut cache, 0)?;
2167        let last = crate::forward::argmax(&logits) as u32;
2168        let mut dkv = DflashKv::new(e, &draft.cfg, max_ctx)?;
2169        {
2170            let taps = cache.dflash_taps.take().unwrap();
2171            let n_taps_h = n_taps * n_embd;
2172            let mut r0 = 0usize;
2173            while r0 < tp {
2174                let t_c = (tp - r0).min(256);
2175                let tv = e.view(&taps.buf, tp * n_taps_h);
2176                let win = tv.slice(r0 * n_taps_h..(r0 + t_c) * n_taps_h);
2177                let mut chunk = e.uninit(t_c * n_taps_h)?;
2178                e.copy_view_into(&mut chunk, 0, &win, t_c * n_taps_h)?;
2179                let f = draft.ctx_features(e, &chunk, t_c)?;
2180                let pos_c: Vec<i32> = ((r0 as i32)..(r0 + t_c) as i32).collect();
2181                draft.ingest_ctx(e, &mut dkv, &f, &pos_c, t_c)?;
2182                r0 += t_c;
2183            }
2184        }
2185        e.stream().synchronize()?;
2186        // Verify carries [anchor, drafts] = up to n_drafts+1 rows (harvest-dependent;
2187        // DSPARK-POSTMORTEM-20260820.md; default = checkpoint strategy census).
2188        let nd = DsparkHarvest::resolve(&draft.cfg).n_drafts(b);
2189        let vt_cap: usize = std::env::var("MEMRA_DFLASH_VERIFY_T")
2190            .ok()
2191            .and_then(|v| v.parse().ok())
2192            .unwrap_or(nd + 1)
2193            .clamp(2, nd + 1);
2194        Ok(DsparkSpecSession {
2195            cache,
2196            dkv,
2197            last,
2198            ctx_len: tp,
2199            vt: vt_cap,
2200            rounds: 0,
2201            max_ctx,
2202            done: false,
2203            snapb: None,
2204            snapb_off: !crate::spec::state_copy_batch_on(),
2205        })
2206    }
2207
2208    /// One scheduler burst: dspark rounds until >= `burst_target` tokens are committed,
2209    /// EOS lands, or the ctx cap is reached. Returns (tokens, drafted, accepted) for this
2210    /// burst — the worker clamps the public slice (engine overshoot within a round stays
2211    /// in the session cache, exactly the gemma-burst contract).
2212    pub fn dspark_spec_session_burst(
2213        &self,
2214        e: &Engine,
2215        draft: &DflashDraft,
2216        sess: &mut DsparkSpecSession,
2217        burst_target: usize,
2218        eos: &[u32],
2219    ) -> Result<(Vec<u32>, usize, usize), Box<dyn std::error::Error>> {
2220        use crate::cache::DflashTapSink;
2221        let n_embd = self.cfg.n_embd as usize;
2222        let c = &draft.cfg;
2223        let b = c.block_size;
2224        let n_taps = c.target_layer_ids.len();
2225        let n_vocab = self.output.out_features();
2226        // Harvest convention (DSPARK-POSTMORTEM-20260820.md) — identical to the bin arm
2227        // (default = checkpoint strategy census; owner-ratified flip 2026-08-20).
2228        let harvest = DsparkHarvest::resolve(&draft.cfg);
2229        let nd = harvest.n_drafts(b);
2230        let r0 = harvest.first_row();
2231        let vt_cap: usize = std::env::var("MEMRA_DFLASH_VERIFY_T")
2232            .ok()
2233            .and_then(|v| v.parse().ok())
2234            .unwrap_or(nd + 1)
2235            .clamp(2, nd + 1);
2236        let adapt = std::env::var("MEMRA_DFLASH_ADAPT").as_deref() != Ok("0");
2237        // Verify-window policy (H4, DSPARK-POSTMORTEM-20260820.md) — identical to the
2238        // bin arm: default = confidence-slot tau=.5 on a head-carrying checkpoint
2239        // (owner-ratified flip 2026-08-20); head-less/ADAPT=0 keep the ladder.
2240        let vt_policy = DsparkVtPolicy::resolve(draft.confidence.is_some());
2241        if vt_policy.is_confidence() {
2242            assert!(
2243                draft.confidence.is_some(),
2244                "MEMRA_DSPARK_VT={vt_policy:?} needs a checkpoint with an accept-rate \
2245                 head (confidence_head.* absent in this export)"
2246            );
2247        }
2248        let mut out: Vec<u32> = Vec::with_capacity(burst_target + b);
2249        let mut drafted = 0usize;
2250        let mut accepted_n = 0usize;
2251        // Engine-bundle slice 2 — identical to the bin arm: deferred chain readback under
2252        // the stash arm with a resident embed table (ladder policy only).
2253        let defer_rb = crate::spec::dspark_defer_readback_on() && !vt_policy.is_confidence();
2254        let (embd_qt, embd_rb) = self.embd.qt_and_row_bytes(n_embd);
2255        let embd_gpu = if !defer_rb || crate::spec::spec_host_embd() {
2256            None
2257        } else {
2258            Some(
2259                self.embd_gpu
2260                    .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload")),
2261            )
2262        };
2263        'outer: while out.len() < burst_target && !sess.done {
2264            let start = sess.cache.pos;
2265            if start + nd + 1 > sess.max_ctx {
2266                sess.done = true;
2267                break;
2268            }
2269            sess.rounds += 1;
2270            let mut vt = sess.vt;
2271            // ---- draft: block = [last, MASK x b-1] (identical to the bin arm) ----
2272            e.set_verify_exact(true);
2273            let mut block: Vec<u32> = vec![c.mask_token_id; b];
2274            block[0] = sess.last;
2275            let noise = e.htod(&self.embd.gather(n_embd, &block))?;
2276            let pos_block: Vec<i32> = ((start as i32)..(start + b) as i32).collect();
2277            let dh = draft.forward_round(e, &mut sess.dkv, &noise, &pos_block)?;
2278            // Harvest: logits over rows r0..r0+nd (see the bin arm / the postmortem).
2279            let mut rows = e.uninit(nd * n_embd)?;
2280            {
2281                let dv = e.view(&dh, b * n_embd);
2282                let src = dv.slice(r0 * n_embd..(r0 + nd) * n_embd);
2283                e.copy_view_into(&mut rows, 0, &src, nd * n_embd)?;
2284            }
2285            let mut dl = e.matmul(&self.output, &rows, nd)?;
2286            let markov_on = std::env::var("MEMRA_DFLASH_MARKOV").as_deref() != Ok("0");
2287            let mut chain_d = e.stream().alloc_zeros::<u32>(nd + 1)?;
2288            // Confidence policy: stash markov prev-token embeddings d2d during the
2289            // chain, one host readback after — identical to the bin arm.
2290            let want_conf_emb = vt_policy.is_confidence()
2291                && draft.confidence.as_ref().is_some_and(|ch| ch.with_markov);
2292            let mut conf_emb: Option<CudaSlice<f32>> = match (&draft.markov, want_conf_emb) {
2293                (Some(mk), true) => Some(e.uninit(nd * mk.rank)?),
2294                (None, true) => unreachable!(
2295                    "with_markov confidence head without a markov table — the loader forbids it"
2296                ),
2297                _ => None,
2298            };
2299            if let (Some(mk), true) = (&draft.markov, markov_on) {
2300                e.set_u32_one(&mut chain_d, sess.last)?;
2301                for k in 0..nd {
2302                    let mut f = e.uninit(mk.rank)?;
2303                    e.gather_row_bf16(&mk.w1_bf16, &chain_d, k, &mut f, mk.rank)?;
2304                    if let Some(ce) = conf_emb.as_mut() {
2305                        let fv = e.view(&f, mk.rank);
2306                        e.copy_view_into(ce, k * mk.rank, &fv, mk.rank)?;
2307                    }
2308                    let bias = e.matmul(&mk.w2, &f, 1)?;
2309                    e.add_row_inplace(&mut dl, &bias, n_vocab, k * n_vocab)?;
2310                    e.argmax_token_device_col(&dl, k, n_vocab, &mut chain_d, k + 1)?;
2311                }
2312            } else {
2313                if want_conf_emb {
2314                    // chain_d[0] must carry the anchor — slot 0's prev token.
2315                    e.set_u32_one(&mut chain_d, sess.last)?;
2316                }
2317                for i in 0..nd {
2318                    if let (Some(ce), Some(mk)) = (conf_emb.as_mut(), &draft.markov) {
2319                        let mut f = e.uninit(mk.rank)?;
2320                        e.gather_row_bf16(&mk.w1_bf16, &chain_d, i, &mut f, mk.rank)?;
2321                        let fv = e.view(&f, mk.rank);
2322                        e.copy_view_into(ce, i * mk.rank, &fv, mk.rank)?;
2323                    }
2324                    e.argmax_token_device_col(&dl, i, n_vocab, &mut chain_d, i + 1)?;
2325                }
2326            }
2327            e.set_verify_exact(false);
2328            // Slice 2: arm choice read before the chain readback (see the bin arm; the
2329            // serve arm has no CKPT_GATE oracle — the bin arm carries it).
2330            let ckpt_on = std::env::var("MEMRA_DSPARK_CKPT").as_deref() != Ok("0");
2331            let deferred = embd_gpu.is_some() && ckpt_on;
2332            // ---- H4 confidence window: size THIS round's verify from the head ----
2333            if vt_policy.is_confidence() {
2334                let ch = draft.confidence.as_ref().expect("asserted at burst entry");
2335                let (rows_h, emb_h) = match conf_emb.as_ref() {
2336                    Some(ce) => {
2337                        let (a, b2) = e.dtoh_pair(&rows, ce)?;
2338                        (a, Some(b2))
2339                    }
2340                    None => (e.dtoh(&rows)?, None),
2341                };
2342                let rank = draft.markov.as_ref().map(|m| m.rank).unwrap_or(0);
2343                let mut raws = Vec::with_capacity(nd);
2344                for k in 0..nd {
2345                    let hrow = &rows_h[k * n_embd..(k + 1) * n_embd];
2346                    let emb = emb_h.as_ref().map(|eh| &eh[k * rank..(k + 1) * rank]);
2347                    raws.push(ch.raw_score(hrow, emb));
2348                }
2349                vt = vt_policy
2350                    .size_window(&raws, vt_cap)
2351                    .expect("confidence policies always size the window");
2352            }
2353            let mut cand: Vec<u32> = Vec::with_capacity(nd + 1);
2354            if !deferred {
2355                let chain = e.dtoh_u32(&chain_d)?;
2356                cand.push(sess.last);
2357                cand.extend_from_slice(&chain[1..]);
2358            }
2359
2360            // ---- snapshot, then verify t=vt (ckpt stash default; oracle arms kept) ----
2361            // Slice 1: batched snap (see DsparkSnapBatch) with the legacy per-layer
2362            // snapshot as the kill-switch / non-uniform fallback.
2363            let mut snap_legacy: Option<crate::cache::CacheSnapshot> = None;
2364            if !sess.snapb_off && sess.snapb.is_none() {
2365                sess.snapb = DsparkSnapBatch::new(e, &sess.cache)?;
2366                sess.snapb_off = sess.snapb.is_none();
2367            } else if let Some(sb) = sess.snapb.as_mut() {
2368                sb.refresh(e, &sess.cache)?;
2369            }
2370            let snap: &crate::cache::CacheSnapshot = match sess.snapb.as_ref() {
2371                Some(sb) => &sb.snap,
2372                None => {
2373                    snap_legacy = Some(sess.cache.snapshot(e)?);
2374                    snap_legacy.as_ref().unwrap()
2375                }
2376            };
2377            let _ = &snap_legacy;
2378            sess.cache.dflash_taps = Some(DflashTapSink {
2379                layer_ids: c.target_layer_ids.clone(),
2380                buf: e.uninit(vt * n_taps * n_embd)?,
2381                hidden: n_embd,
2382                t: vt,
2383                base: 0,
2384            });
2385            let (vam, vck) = if deferred {
2386                // Slice 2: device-token verify + ONE merged readback (see the bin arm).
2387                let g = embd_gpu.expect("deferred implies resident embed");
2388                // Slice 3 stays bin-arm-only for now: session lifetime (per-request
2389                // caches, capture storms) needs the cache-reuse-pool design first.
2390                let (am_d, vck) = self.dspark_verify_t_am_ckpt_dev(
2391                    e,
2392                    &chain_d,
2393                    vt,
2394                    start,
2395                    &mut sess.cache,
2396                    (g, embd_qt, embd_rb),
2397                    None,
2398                )?;
2399                let ch = e.stream().clone_dtoh(&chain_d)?;
2400                let am = e.stream().clone_dtoh(&am_d)?;
2401                e.stream().synchronize()?;
2402                cand.push(sess.last);
2403                cand.extend_from_slice(&ch[1..]);
2404                (am, Some(vck))
2405            } else if ckpt_on {
2406                let (vam, vck) =
2407                    self.dspark_verify_t_am_ckpt(e, &cand[..vt], start, &mut sess.cache)?;
2408                (vam, Some(vck))
2409            } else {
2410                (
2411                    self.dspark_verify_t_am(e, &cand[..vt], start, &mut sess.cache)?,
2412                    None,
2413                )
2414            };
2415            let taps = sess.cache.dflash_taps.take().unwrap();
2416
2417            // ---- accept ----
2418            let m = dspark_accept_prefix(&cand, &vam, vt);
2419            drafted += vt - 1;
2420            accepted_n += m;
2421            out.push(sess.last);
2422            if eos.contains(&sess.last) {
2423                sess.done = true;
2424                break 'outer;
2425            }
2426            for &dt in &cand[1..=m] {
2427                out.push(dt);
2428                if eos.contains(&dt) {
2429                    sess.done = true;
2430                    break 'outer;
2431                }
2432            }
2433            let next = vam[m];
2434
2435            // ---- commit/rollback (stash arm default; replay oracle kept) ----
2436            let keep = m + 1;
2437            if keep < vt {
2438                if let Some(vck) = vck.as_ref() {
2439                    self.dspark_commit_prefix(e, &mut sess.cache, snap, vck, keep)?;
2440                } else {
2441                    crate::pp::restore_cache_checkpoint(e, &self.cfg, None, &mut sess.cache, snap)?;
2442                    debug_assert_eq!(sess.cache.pos, start, "rollback landed off the round start");
2443                    let ram = self.dspark_verify_t_am(e, &cand[..keep], start, &mut sess.cache)?;
2444                    debug_assert_eq!(
2445                        &ram[..],
2446                        &vam[..keep],
2447                        "prefix replay must reproduce the verify argmaxes"
2448                    );
2449                }
2450            }
2451
2452            // ---- ingest the kept rows' ctx features into the draft KV ----
2453            {
2454                let tv = e.view(&taps.buf, vt * n_taps * n_embd);
2455                let keep_view = tv.slice(0..keep * n_taps * n_embd);
2456                let mut kept = e.uninit(keep * n_taps * n_embd)?;
2457                e.copy_view_into(&mut kept, 0, &keep_view, keep * n_taps * n_embd)?;
2458                let f = draft.ctx_features(e, &kept, keep)?;
2459                let pos_k: Vec<i32> =
2460                    ((sess.ctx_len as i32)..(sess.ctx_len + keep) as i32).collect();
2461                draft.ingest_ctx(e, &mut sess.dkv, &f, &pos_k, keep)?;
2462                sess.ctx_len += keep;
2463            }
2464            sess.last = next;
2465            // Ladder update only — the confidence policies recompute vt from the
2466            // head every round, post-draft pre-verify; their carry just keeps
2467            // observability (sess.vt = the last confidence-sized window).
2468            if vt_policy.is_confidence() {
2469                sess.vt = vt;
2470            } else if adapt {
2471                sess.vt = (m + 2).clamp(3, vt_cap);
2472            }
2473        }
2474        Ok((out, drafted, accepted_n))
2475    }
2476}
2477
2478// ================= Harvest-convention gate (CPU; DSPARK-POSTMORTEM-20260820.md) =========
2479// The parity oracle is row-count-agnostic (it reproduces the markov MODULE on whatever
2480// rows it is fed) and the E2E gate is harvest-independent (verify-side truth), so
2481// NEITHER can catch a wrong row->position mapping — that blindness is how the q38
2482// misalignment shipped. These tests pin the convention itself as logic the round
2483// consumes, so a mutation back to the mask-fill harvest under the Dspark variant fails
2484// HERE, naming the convention.
2485#[cfg(test)]
2486mod dspark_harvest_tests {
2487    use super::{DsparkHarvest, DsparkVtPolicy, dspark_accept_prefix, dspark_strategy_census};
2488
2489    const B: usize = 7; // q38 arm-a block_size
2490
2491    #[test]
2492    fn dspark_strategy_requires_shifted_harvest() {
2493        let h = DsparkHarvest::Dspark;
2494        assert_eq!(
2495            h.first_row(),
2496            0,
2497            "DSPARK-strategy checkpoints (SpecForge OnlineDSparkModel, \
2498             training.strategy=dspark — the q38 arm-a export) supervise ALL rows with \
2499             SHIFTED labels: label_offsets = arange(1, block_size+1), i.e. the ANCHOR \
2500             row's output is draft 1 (specforge/algorithms/common/\
2501             dflash_family_model.py:816; sglang v0.5.17 dspark_draft.py:248,260). \
2502             Harvesting from row 1 re-opens the DSPARK-POSTMORTEM-20260820 slot \
2503             misalignment (accept 2.9 -> 1.43)."
2504        );
2505        assert_eq!(
2506            h.n_drafts(B),
2507            B,
2508            "DSpark harvests gamma = block_size drafts per round (sglang \
2509             dspark_config.py:269, verify_num_draft_tokens = gamma+1); b-1 is the \
2510             DFlash mask-fill count and drops the best-trained slot \
2511             (DSPARK-POSTMORTEM-20260820.md §3-H1)."
2512        );
2513        for row in 0..B {
2514            assert_eq!(
2515                h.trained_offset_of_row(row),
2516                row + 1,
2517                "OnlineDSparkModel trains row k to predict anchor+k+1 \
2518                 (dflash_family_model.py:816); a same-position (mask-fill) mapping \
2519                 here verifies every slot one position early — the postmortem's \
2520                 collapse."
2521            );
2522        }
2523    }
2524
2525    #[test]
2526    fn dflash_strategy_keeps_mask_fill_harvest() {
2527        // Guards the reverse mutation: z-lab dflash checkpoints (the gemma arm) are
2528        // mask-fill — row k FILLS anchor+k, the anchor row is loss-excluded
2529        // (dflash_family_model.py:453-472). Shifting THEM would break the gemma arm.
2530        let h = DsparkHarvest::Dflash;
2531        assert_eq!(h.first_row(), 1, "DFlash drafts start at mask row 1");
2532        assert_eq!(h.n_drafts(B), B - 1, "DFlash harvests block_size-1 drafts");
2533        for row in 1..B {
2534            assert_eq!(h.trained_offset_of_row(row), row);
2535        }
2536    }
2537
2538    #[test]
2539    fn every_candidate_verifies_the_position_its_row_was_trained_for() {
2540        // The round's invariant: draft candidate i (1-based; verified against the
2541        // trunk's prediction for anchor+i) is filled from drafter output row
2542        // first_row + i - 1. Alignment == that row was TRAINED for offset i.
2543        for h in [DsparkHarvest::Dflash, DsparkHarvest::Dspark] {
2544            for i in 1..=h.n_drafts(B) {
2545                let row = h.first_row() + i - 1;
2546                assert_eq!(
2547                    h.trained_offset_of_row(row),
2548                    i,
2549                    "{h:?}: candidate {i} rides row {row}, which is trained for \
2550                     offset {} — harvest misaligned",
2551                    h.trained_offset_of_row(row)
2552                );
2553            }
2554        }
2555    }
2556
2557    #[test]
2558    fn env_seam_parses_and_refuses() {
2559        assert_eq!(
2560            DsparkHarvest::from_env_value(None),
2561            DsparkHarvest::Dflash,
2562            "the ENV-ONLY parser keeps the historical arm; the ratified strategy-keyed \
2563             default lives in resolve_value (checkpoint census), not here"
2564        );
2565        assert_eq!(
2566            DsparkHarvest::from_env_value(Some("dspark")),
2567            DsparkHarvest::Dspark
2568        );
2569        assert_eq!(
2570            DsparkHarvest::from_env_value(Some("dflash")),
2571            DsparkHarvest::Dflash
2572        );
2573        assert!(
2574            std::panic::catch_unwind(|| DsparkHarvest::from_env_value(Some("shifted"))).is_err(),
2575            "unknown harvest values must REFUSE, not default"
2576        );
2577        assert_eq!(
2578            DsparkHarvest::from_name("dspark"),
2579            Some(DsparkHarvest::Dspark)
2580        );
2581        assert_eq!(
2582            DsparkHarvest::from_name("dflash"),
2583            Some(DsparkHarvest::Dflash)
2584        );
2585        assert_eq!(DsparkHarvest::from_name("mask-fill"), None);
2586    }
2587
2588    /// The owner-ratified default flips (2026-08-20). Each assertion names its
2589    /// evidence; mutating either resolve back to the old default fails these.
2590    #[test]
2591    fn ratified_default_harvest_is_strategy_keyed() {
2592        // DSPARK-strategy checkpoint + unset env = the shifted harvest (B1: accept
2593        // 1.38->2.41 agentic / 1.53->3.66 math, E2E ALL EXACT x5, interleaved x5).
2594        assert_eq!(
2595            DsparkHarvest::resolve_value(None, true),
2596            DsparkHarvest::Dspark,
2597            "owner-ratified 2026-08-20: unset env defaults a DSPARK-strategy \
2598             checkpoint to the shifted harvest (DSPARK-POSTMORTEM-20260820.md B1)"
2599        );
2600        // mask-fill checkpoint + unset env = the historical arm, byte-identical.
2601        assert_eq!(
2602            DsparkHarvest::resolve_value(None, false),
2603            DsparkHarvest::Dflash
2604        );
2605        assert_eq!(
2606            DsparkHarvest::resolve_value(Some(""), false),
2607            DsparkHarvest::Dflash
2608        );
2609        // Explicit env overrides the census in BOTH directions (the A/B seam).
2610        assert_eq!(
2611            DsparkHarvest::resolve_value(Some("dflash"), true),
2612            DsparkHarvest::Dflash
2613        );
2614        assert_eq!(
2615            DsparkHarvest::resolve_value(Some("dspark"), false),
2616            DsparkHarvest::Dspark
2617        );
2618        // Unknown values still REFUSE through the resolve path.
2619        assert!(
2620            std::panic::catch_unwind(|| DsparkHarvest::resolve_value(Some("shifted"), true))
2621                .is_err()
2622        );
2623    }
2624
2625    #[test]
2626    fn strategy_census_reads_the_checkpoint_not_the_env() {
2627        // The q38 arm-a export shape: both signals present.
2628        let q38 = r#"{"architectures": ["Qwen3DSparkModel"], "block_size": 7,
2629            "dflash_config": {"projector_type": "dspark", "markov_rank": 256}}"#;
2630        assert!(dspark_strategy_census(q38));
2631        // Either signal alone suffices.
2632        assert!(dspark_strategy_census(
2633            r#"{"architectures": ["Qwen3DSparkModel"]}"#
2634        ));
2635        assert!(dspark_strategy_census(
2636            r#"{"dflash_config": {"projector_type": "dspark"}}"#
2637        ));
2638        // A mask-fill DFlash export carries neither -> historical default.
2639        let dflash = r#"{"architectures": ["Qwen3DFlashModel"],
2640            "dflash_config": {"attention_mode": "gqa"}}"#;
2641        assert!(!dspark_strategy_census(dflash));
2642        assert!(!dspark_strategy_census("{}"));
2643    }
2644
2645    #[test]
2646    fn ratified_default_vt_is_confidence_slot_tau_half() {
2647        // Head-carrying checkpoint + unset env = confidence-slot tau=.5 (H4 cell 3:
2648        // the tau ladder's knee; cell 2: 93.9%/97.7% of fixed-8 accept at wall >=
2649        // the reactive ladder, exactness 11/11 ALL EXACT).
2650        assert_eq!(
2651            DsparkVtPolicy::resolve_value(None, None, None, true),
2652            DsparkVtPolicy::ConfidenceSlot { tau: 0.5 },
2653            "owner-ratified 2026-08-20: unset MEMRA_DSPARK_VT defaults to \
2654             confidence-slot tau=.5 on a head-carrying checkpoint (H4 cells 2-3)"
2655        );
2656        // tau env still steers the default arm (and a bad tau still refuses).
2657        assert_eq!(
2658            DsparkVtPolicy::resolve_value(None, Some("0.35"), None, true),
2659            DsparkVtPolicy::ConfidenceSlot { tau: 0.35 }
2660        );
2661        assert!(
2662            std::panic::catch_unwind(|| DsparkVtPolicy::resolve_value(
2663                None,
2664                Some("nan-ish"),
2665                None,
2666                true
2667            ))
2668            .is_err()
2669        );
2670        // Census: no accept-rate head -> nothing to schedule with -> ladder.
2671        assert_eq!(
2672            DsparkVtPolicy::resolve_value(None, None, None, false),
2673            DsparkVtPolicy::Ladder
2674        );
2675        // MEMRA_DFLASH_ADAPT=0 is an explicit fixed-window request: honored.
2676        assert_eq!(
2677            DsparkVtPolicy::resolve_value(None, None, Some("0"), true),
2678            DsparkVtPolicy::Ladder
2679        );
2680        // Explicit values keep their exact prior semantics through resolve.
2681        assert_eq!(
2682            DsparkVtPolicy::resolve_value(Some("ladder"), None, None, true),
2683            DsparkVtPolicy::Ladder
2684        );
2685        assert_eq!(
2686            DsparkVtPolicy::resolve_value(Some("confidence"), Some("0.35"), None, true),
2687            DsparkVtPolicy::Confidence { tau: 0.35 }
2688        );
2689        // Explicit confidence mode with ADAPT=0 stays a refusal.
2690        assert!(
2691            std::panic::catch_unwind(|| DsparkVtPolicy::resolve_value(
2692                Some("confidence-slot"),
2693                None,
2694                Some("0"),
2695                true
2696            ))
2697            .is_err()
2698        );
2699    }
2700
2701    /// End-to-end alignment fixture in miniature: a mock drafter whose row r argmaxes
2702    /// to token BASE + (its trained offset under the DSPARK strategy), and a mock trunk
2703    /// whose prediction for anchor+j is BASE + j. The DSpark harvest accepts the whole
2704    /// block; feeding the same drafter through the mask-fill harvest accepts ZERO —
2705    /// the postmortem's collapse reproduced as pure logic.
2706    #[test]
2707    fn dspark_trained_rows_through_mask_fill_harvest_accept_nothing() {
2708        const BASE: u32 = 1000;
2709        let anchor: u32 = BASE; // token at the round anchor position (offset 0)
2710        // trunk verify argmaxes: vam[j] = prediction for anchor offset j+1
2711        let vam: Vec<u32> = (1..=B as u32 + 1).map(|j| BASE + j).collect();
2712        // drafter rows trained under the DSPARK strategy: row r predicts offset r+1
2713        let dspark_trained_row_argmax =
2714            |r: usize| BASE + DsparkHarvest::Dspark.trained_offset_of_row(r) as u32;
2715
2716        // Correct (shifted) harvest: candidate i <- row i-1.
2717        let h = DsparkHarvest::Dspark;
2718        let mut cand = vec![anchor];
2719        for i in 1..=h.n_drafts(B) {
2720            cand.push(dspark_trained_row_argmax(h.first_row() + i - 1));
2721        }
2722        let vt = h.n_drafts(B) + 1;
2723        assert_eq!(
2724            dspark_accept_prefix(&cand, &vam, vt),
2725            vt - 1,
2726            "aligned harvest must accept the full block"
2727        );
2728
2729        // Mask-fill harvest of the SAME dspark-trained drafter: candidate i <- row i,
2730        // which was trained for offset i+1 — every slot one position late.
2731        let wrong = DsparkHarvest::Dflash;
2732        let mut cand_wrong = vec![anchor];
2733        for i in 1..=wrong.n_drafts(B) {
2734            cand_wrong.push(dspark_trained_row_argmax(wrong.first_row() + i - 1));
2735        }
2736        let vt_wrong = wrong.n_drafts(B) + 1;
2737        assert_eq!(
2738            dspark_accept_prefix(&cand_wrong, &vam, vt_wrong),
2739            0,
2740            "mask-fill harvest of a dspark-trained drafter verifies every slot against \
2741             a position the row was not trained for (DSPARK-POSTMORTEM-20260820.md)"
2742        );
2743    }
2744}
2745
2746// ================= Verify-window policy gate (CPU; H4, DSPARK-POSTMORTEM-20260820.md) ===
2747// Pins the confidence-vt semantics as logic the round consumes: cumprod survival over
2748// sigmoid scores, thresholded, anchor + kept drafts, floor 2 / cap vt_cap — and the env
2749// seam's refuse-on-ambiguity. Mutating the policy (per-slot threshold instead of
2750// survival, off-by-one on the anchor, silent unknown-value fallback) fails HERE.
2751#[cfg(test)]
2752mod dspark_vt_tests {
2753    use super::{ConfidenceHead, DsparkVtPolicy, dspark_confidence_vt, dspark_slot_confidence_vt};
2754
2755    /// Pre-sigmoid logit for a target probability: sigmoid(logit(p)) == p.
2756    fn logit(p: f32) -> f32 {
2757        (p / (1.0 - p)).ln()
2758    }
2759
2760    #[test]
2761    fn confidence_vt_is_cumprod_survival_not_per_slot_threshold() {
2762        // sigmoids = [0.9, 0.8, 0.9, ...]: every PER-SLOT score clears tau=0.5, but
2763        // cumulative survival sinks below it at slot 6 (0.9, 0.72, 0.648, 0.583,
2764        // 0.525, then 0.472 < 0.5) — the window must stop where the EXPECTED
2765        // accepted-prefix stops paying, not where a slot looks locally fine.
2766        let raws: Vec<f32> = [0.9, 0.8, 0.9, 0.9, 0.9, 0.9, 0.9]
2767            .iter()
2768            .map(|&p| logit(p))
2769            .collect();
2770        assert_eq!(
2771            dspark_confidence_vt(&raws, 0.5, 8),
2772            6,
2773            "keeps 5 drafts + anchor"
2774        );
2775        // Tighter threshold closes the window sooner; looser opens it to the cap.
2776        assert_eq!(
2777            dspark_confidence_vt(&raws, 0.7, 8),
2778            3,
2779            "tau=0.7 keeps 2 drafts"
2780        );
2781        assert_eq!(
2782            dspark_confidence_vt(&raws, 0.05, 8),
2783            8,
2784            "tau→0 = full block"
2785        );
2786    }
2787
2788    #[test]
2789    fn slot_arm_truncates_at_first_low_confidence_slot() {
2790        // Owner directive (2026-08-20): submit only the longest prefix whose EVERY
2791        // slot clears tau on its own sigmoid. On the survival test's raws
2792        // ([0.9, 0.8, 0.9 x5], tau=0.5) every slot clears per-slot, so the slot arm
2793        // opens the full block where survival stopped at 6 — the two stopping
2794        // statistics must stay distinct arms.
2795        let raws: Vec<f32> = [0.9, 0.8, 0.9, 0.9, 0.9, 0.9, 0.9]
2796            .iter()
2797            .map(|&p| logit(p))
2798            .collect();
2799        assert_eq!(dspark_slot_confidence_vt(&raws, 0.5, 8), 8);
2800        assert_eq!(dspark_confidence_vt(&raws, 0.5, 8), 6);
2801        // A low-confidence tail never enters verify: [0.9, 0.9, 0.3, 0.9, ...]
2802        // truncates at slot 3 REGARDLESS of the confident slots behind it — a kept
2803        // slot after a dropped one could never commit (prefix accept rule).
2804        let tail: Vec<f32> = [0.9, 0.9, 0.3, 0.9, 0.9, 0.9, 0.9]
2805            .iter()
2806            .map(|&p| logit(p))
2807            .collect();
2808        assert_eq!(
2809            dspark_slot_confidence_vt(&tail, 0.5, 8),
2810            3,
2811            "2 drafts + anchor"
2812        );
2813        // Tighter tau keeps less.
2814        assert_eq!(
2815            dspark_slot_confidence_vt(&tail, 0.95, 8),
2816            2,
2817            "floor at tau=0.95"
2818        );
2819    }
2820
2821    #[test]
2822    fn confidence_vt_floor_and_cap() {
2823        // A hopeless round still verifies ONE draft (the draft forward is paid;
2824        // vt=1 would guarantee an empty round at the same cost class).
2825        let cold: Vec<f32> = [0.1f32, 0.1, 0.1].iter().map(|&p| logit(p)).collect();
2826        assert_eq!(
2827            dspark_confidence_vt(&cold, 0.5, 8),
2828            2,
2829            "floor = anchor + 1 draft"
2830        );
2831        assert_eq!(
2832            dspark_slot_confidence_vt(&cold, 0.5, 8),
2833            2,
2834            "slot arm same floor"
2835        );
2836        // The MEMRA_DFLASH_VERIFY_T cap still binds a confident round.
2837        let hot: Vec<f32> = vec![logit(0.99); 7];
2838        assert_eq!(dspark_confidence_vt(&hot, 0.5, 5), 5, "vt_cap binds");
2839        assert_eq!(
2840            dspark_confidence_vt(&hot, 0.5, 8),
2841            8,
2842            "full block when confident"
2843        );
2844        assert_eq!(
2845            dspark_slot_confidence_vt(&hot, 0.5, 5),
2846            5,
2847            "slot arm same cap"
2848        );
2849        // No scores (defensive): floor.
2850        assert_eq!(dspark_confidence_vt(&[], 0.5, 8), 2);
2851        assert_eq!(dspark_slot_confidence_vt(&[], 0.5, 8), 2);
2852    }
2853
2854    #[test]
2855    fn vt_policy_env_seam_parses_and_refuses() {
2856        assert_eq!(
2857            DsparkVtPolicy::from_env_value(None, None, None),
2858            DsparkVtPolicy::Ladder,
2859            "default stays the shipped ladder — the H4 arm is opt-in"
2860        );
2861        assert_eq!(
2862            DsparkVtPolicy::from_env_value(Some(""), None, None),
2863            DsparkVtPolicy::Ladder
2864        );
2865        assert_eq!(
2866            DsparkVtPolicy::from_env_value(Some("ladder"), None, Some("0")),
2867            DsparkVtPolicy::Ladder,
2868            "ladder + ADAPT=0 = the fixed-window arm, untouched"
2869        );
2870        assert_eq!(
2871            DsparkVtPolicy::from_env_value(Some("confidence"), None, None),
2872            DsparkVtPolicy::Confidence { tau: 0.5 },
2873            "tau defaults to 0.5 (raw sigmoid, no STS sidecar — postmortem §3-H4)"
2874        );
2875        assert_eq!(
2876            DsparkVtPolicy::from_env_value(Some("confidence"), Some("0.35"), Some("1")),
2877            DsparkVtPolicy::Confidence { tau: 0.35 }
2878        );
2879        assert_eq!(
2880            DsparkVtPolicy::from_env_value(Some("confidence-slot"), Some("0.6"), None),
2881            DsparkVtPolicy::ConfidenceSlot { tau: 0.6 },
2882            "the owner-directive per-slot arm parses with the same tau env"
2883        );
2884        assert!(
2885            std::panic::catch_unwind(|| DsparkVtPolicy::from_env_value(
2886                Some("confidence-slot"),
2887                None,
2888                Some("0")
2889            ))
2890            .is_err(),
2891            "confidence-slot + MEMRA_DFLASH_ADAPT=0 must REFUSE like confidence"
2892        );
2893        assert!(
2894            std::panic::catch_unwind(|| DsparkVtPolicy::from_env_value(Some("static"), None, None))
2895                .is_err(),
2896            "unknown policy values must REFUSE, not default — a typo silently \
2897             reverting the window policy invalidates an A/B"
2898        );
2899        assert!(
2900            std::panic::catch_unwind(|| DsparkVtPolicy::from_env_value(
2901                Some("confidence"),
2902                None,
2903                Some("0")
2904            ))
2905            .is_err(),
2906            "confidence + MEMRA_DFLASH_ADAPT=0 is contradictory and must REFUSE"
2907        );
2908        for bad in ["0", "1", "1.5", "-0.1", "nan"] {
2909            assert!(
2910                std::panic::catch_unwind(|| DsparkVtPolicy::from_env_value(
2911                    Some("confidence"),
2912                    Some(bad),
2913                    None
2914                ))
2915                .is_err(),
2916                "tau={bad} must REFUSE (survival threshold lives in (0,1))"
2917            );
2918        }
2919    }
2920
2921    #[test]
2922    fn raw_score_matches_the_parity_gate_dot() {
2923        // The head is a raw linear proj over [hidden ; markov_prev_embedding] + b —
2924        // the exact stage-5 contract in dspark_q38_parity.rs.
2925        let ch = ConfidenceHead {
2926            w: vec![0.5, -1.0, 2.0, 0.25, -0.5],
2927            b: 0.125,
2928            in_dim: 5,
2929            with_markov: true,
2930        };
2931        let hidden = [1.0f32, 2.0, 3.0];
2932        let emb = [4.0f32, 8.0];
2933        let want = 0.125 + 0.5 * 1.0 - 1.0 * 2.0 + 2.0 * 3.0 + 0.25 * 4.0 - 0.5 * 8.0;
2934        assert_eq!(ch.raw_score(&hidden, Some(&emb)), want);
2935        let ch_plain = ConfidenceHead {
2936            w: vec![0.5, -1.0, 2.0],
2937            b: -0.25,
2938            in_dim: 3,
2939            with_markov: false,
2940        };
2941        let want_plain = -0.25 + 0.5 * 1.0 - 1.0 * 2.0 + 2.0 * 3.0;
2942        assert_eq!(ch_plain.raw_score(&hidden, None), want_plain);
2943    }
2944}