memra_engine/dflash.rs
1//! DFlash block-diffusion drafter (DFLASH-BRINGUP-PLAN.md, 2026-07-13).
2//!
3//! 5-layer qwen3-class mini-transformer that drafts a 16-token block in ONE non-causal
4//! forward, conditioned on the TARGET's hidden states at 6 tapped layers (concatenated
5//! through `fc` + `hidden_norm`). No embed / lm_head of its own — the round reuses the
6//! target's. Reference: z-lab/dflash `dflash/model.py` (semantics frozen in the plan doc);
7//! oracle: tools/dflash_oracle.py -> /data/cache/dflash-oracle.npz.
8//!
9//! FIRST LIGHT = f32-resident weights + fresh full-context forward (no draft KV cache) —
10//! correctness vs the oracle, then the cache/quant/window arms land measurement-gated.
11
12use crate::Engine;
13use crate::model::GpuTensor;
14use cudarc::driver::CudaSlice;
15
16pub struct DflashCfg {
17 pub hidden: usize, // 5376
18 pub n_head: usize, // 64
19 pub n_kv: usize, // 8
20 pub head_dim: usize, // 128
21 pub n_ff: usize, // 10752
22 pub n_layer: usize, // 5
23 pub eps: f32, // 1e-6
24 pub rope_theta: f32, // 1e6
25 pub block_size: usize, // 16
26 pub mask_token_id: u32, // 4
27 pub target_layer_ids: Vec<usize>, // [1,12,23,35,46,57]
28 pub sliding_window: usize, // 2048
29 /// true = sliding_attention for that layer (4x true + 1x false on the 31B draft).
30 pub layer_sliding: Vec<bool>,
31 /// Checkpoint training-strategy census (`dspark_strategy_census` over the raw
32 /// config.json): true = a SpecForge DSPARK-strategy export (shifted labels, ALL rows
33 /// supervised — the q38 arm-a family). Keys the HARVEST DEFAULT strategy-keyed,
34 /// never env-keyed (owner-ratified 2026-08-20 after B1 confirmed H1 ×5;
35 /// DSPARK-POSTMORTEM-20260820.md B0 default-flip plan).
36 pub strategy_dspark: bool,
37 /// Explicit top-level `is_causal` from config.json (z-lab reference: an explicit
38 /// value OVERRIDES the per-layer-type default). The DFlash2 q38 checkpoint carries
39 /// `"is_causal": false` — every sliding layer is NON-causal with a symmetric
40 /// +/-2048 window (model.py `_attention_mask`). None = key absent (historical
41 /// exports; the windowless-assert arm keeps handling those byte-identically).
42 pub is_causal: Option<bool>,
43}
44
45pub struct DflashLayer {
46 pub wq: GpuTensor, // [nh*hd, hidden] row-major (out_f rows)
47 pub wk: GpuTensor, // [nkv*hd, hidden]
48 pub wv: GpuTensor, // [nkv*hd, hidden]
49 pub wo: GpuTensor, // [hidden, nh*hd]
50 pub w_gate: GpuTensor, // [n_ff, hidden]
51 pub w_up: GpuTensor, // [n_ff, hidden]
52 pub w_down: GpuTensor, // [hidden, n_ff]
53 pub ln_in: CudaSlice<f32>, // [hidden]
54 pub ln_post: CudaSlice<f32>, // [hidden]
55 pub q_norm: CudaSlice<f32>, // [hd]
56 pub k_norm: CudaSlice<f32>, // [hd]
57}
58
59pub struct DflashDraft {
60 pub cfg: DflashCfg,
61 pub layers: Vec<DflashLayer>,
62 pub fc: GpuTensor, // [hidden, n_taps*hidden]
63 pub hidden_norm: CudaSlice<f32>, // [hidden]
64 pub norm: CudaSlice<f32>, // [hidden]
65 /// DSpark semi-AR markov head (present in the repo-root checkpoint variant):
66 /// draft logits at position k get + W2(W1[prev_realized_token]) — left-to-right
67 /// within the block (the patch's _markov_semiar_sample_block semantics, greedy).
68 /// w1 = raw bf16 [V, rank] (row-gathered by device token id); w2 = q8_0 [rank->V].
69 pub markov: Option<MarkovHead>,
70 /// DSpark accept-rate head (trained with confidence loss). sglang's DSPARK planner
71 /// consumes it to SIZE VERIFY WINDOWS (cumprod survival — v0.5.16 headline; the
72 /// earlier "reference serving loop never consumes it" note matched SpecForge's
73 /// legacy spec_generate only). memra schedules with it under
74 /// `MEMRA_DSPARK_VT=confidence` (the H4 fix, DSPARK-POSTMORTEM-20260820.md:
75 /// per-round verify window from cumprod survival, `dspark_confidence_vt`) and
76 /// keeps it census+parity-only under the default ladder. Host-resident (5k floats).
77 pub confidence: Option<ConfidenceHead>,
78 /// YaRN rope (q38 arm-a inherits the target's rope_parameters: rope_type yarn,
79 /// factor 32, original 8192, beta 32/1). ff = per-dim divisors for rope_neox_ff
80 /// (effective inv_freq_j = base^(-2j/d)/ff[j] = the HF-yarn remapped frequency,
81 /// verified vs Qwen3RotaryEmbedding to 1.6e-7), mscale = attention_scaling
82 /// (0.1*ln(factor)+1) applied to q/k post-rope — cos/sin scaling distributes onto
83 /// the rotated vector exactly. None = plain rope (gemma/z-lab drafters).
84 pub rope_yarn: Option<(CudaSlice<f32>, f32)>,
85 /// DFlash2 head (z-lab `DFlash2DraftModel`, DFLASH2-EVAL-20260820.md): grouped
86 /// dynamic causal convs around EVERY sublayer + the candidate path selector that
87 /// replaces the markov chain. A DISTINCT semantic program from the DSpark head
88 /// (no-generic-support law): present iff config `architectures` names
89 /// `DFlash2DraftModel`, and then ALL 23 family tensors are REQUIRED — loading the
90 /// 58 backbone tensors alone computes an untrained model (the census trap).
91 pub dflash2: Option<Dflash2Head>,
92}
93
94/// One `GroupedDynamicCausalConv` module (reference model.py): a causal 2-tap
95/// depthwise conv over the BLOCK rows (block-local — row 0 zero-pads its missing
96/// predecessor; stateless across rounds), with per-position dynamic per-group
97/// coefficients projected from the module INPUT. `prepare` convolves the sublayer
98/// input with base_kernel[0] + dyn half 0; `finish` convolves the sublayer OUTPUT
99/// with base_kernel[1] + dyn half 1 (both dyn halves come from the SAME projection
100/// of the pre-conv input).
101pub struct Dflash2Conv {
102 /// base_kernel [2, k, hidden] flattened f32 (half-major: prepare then finish).
103 pub base: CudaSlice<f32>,
104 /// kernel_projection.weight [2*k*groups, hidden] (row layout = view(2, k, groups)).
105 pub proj: GpuTensor,
106}
107
108pub struct Dflash2Head {
109 pub attn_conv: Vec<Dflash2Conv>, // per layer
110 pub mlp_conv: Vec<Dflash2Conv>, // per layer
111 /// candidate_selector.hidden_projection.weight [rank, hidden].
112 pub hidden_proj: GpuTensor,
113 /// Codebooks [V, rank] raw bf16, HOST-resident: the walk gathers ~1+16 rows per
114 /// draft slot (~70KB/round) — host math beside the round's existing chain dtoh,
115 /// no device residency for 2x127MB tables. Checkpoint quirk: stored WITHOUT the
116 /// `.weight` suffix (reference from_pretrained installs a key_mapping).
117 pub pred_codebook: Vec<u8>,
118 pub succ_codebook: Vec<u8>,
119 pub rank: usize, // selector_rank 256
120 pub top_k: usize, // selector_top_k 16
121 pub conv_k: usize, // conv_kernel_size 2
122 pub group_size: usize, // conv_group_size 16
123 pub vocab: usize, // codebook rows (248320)
124}
125
126/// Resolve the named DFlash weight program. Keep this separate from loading so a typo cannot
127/// silently select q8 and invalidate a performance/default receipt.
128fn dflash_precision(raw: Option<&str>) -> Result<&str, String> {
129 let prec = raw.unwrap_or("q4");
130 match prec {
131 "q4" | "q8" | "mixed" | "bf16" | "fc" => Ok(prec),
132 other => Err(format!(
133 "MEMRA_DFLASH_PREC={other:?}: want q4, q8, mixed, bf16, or fc \
134 (q5 was measured defective and is not a serving mode)"
135 )),
136 }
137}
138
139/// One bf16 codebook row -> f32 (exact widening).
140fn cb_row(cb: &[u8], tok: usize, rank: usize) -> Vec<f32> {
141 bf16_to_f32(&cb[tok * rank * 2..(tok + 1) * rank * 2])
142}
143
144/// The per-row top-k selector's EXHAUSTED-SLOT sentinel. `topk_rows_f32` and its sharded twin
145/// (`cu/kernels.cu`) fill a slot they could not fill with `0xffffffff`: a row with fewer than
146/// `k` FINITE values — in practice an all-NaN logits row, since every comparison against NaN
147/// is false and a NaN therefore never enters a candidate list.
148pub const TOPK_EMPTY_SLOT: u32 = u32::MAX;
149
150/// Refuse a candidate buffer the selector could not fill (memra#95).
151///
152/// A sentinel row reaching the walk means the DRAFT LOGITS were not a number, which is a
153/// broken invariant upstream (memra#95: a restored full-cover glm5 session read its drafter
154/// ctx KV on a pp stage stream before the caller's import had landed). Two reasons this is a
155/// guard at the proposal seam and not a clamp:
156///
157/// * clamping would silently draft a wrong token, and the drafts feed the verify batch — a
158/// worse outcome than any failure, per the exactness law;
159/// * the walk's own `assert!(c < vocab)` is the last line of defence inside a PURE function,
160/// and it kills the GPU WORKER THREAD, which is fleet-fatal (one respawn, then
161/// the process exits and every in-flight session on the box dies). Returning `Err` here
162/// fails ONE request instead, the same trade `crate::spec::guard_vocab_token` makes for the
163/// native-MTP chain's sentinel (memra#87). The assert stays where it is.
164///
165/// `bound` is the selector's own column space: the `n_vocab` the caller handed `topk_rows`,
166/// which on a trimmed FR-Spec head is the trim's rank count and on a full head is the target
167/// vocabulary. Checked BEFORE any d2t remap, because remapping a sentinel would index the map
168/// out of bounds and panic before the walk is ever reached; the map is pre-checked to cover
169/// `n_vocab`, so passing this bound also proves the remap safe.
170///
171/// ASSUMES UNMASKED DRAFT LOGITS, which is what both proposal seams feed it today: `dl` is a
172/// raw `matmul` output and constrained requests never take the spec route, so a row cannot
173/// legitimately hold fewer than `top_k` finite values. A future masked-draft-logits arm would
174/// have to revisit this, and would want a shorter round rather than a refusal.
175///
176/// NOTE THE SCOPE: both proposal seams are shared with the LIVE dspark/q38 serve arm, not
177/// only the flagged glm5 restore. This changes that route's failure mode too, from a
178/// fleet-fatal worker panic to one refused request. That is the intended direction, and it
179/// is the only behaviour change outside the default-OFF flag.
180pub fn dflash2_guard_candidates(cand: &[u32], bound: usize, ctx: &str) -> Result<(), String> {
181 for (i, &c) in cand.iter().enumerate() {
182 if c as usize >= bound {
183 let why = if c == TOPK_EMPTY_SLOT {
184 " (the top-k selector's exhausted-slot sentinel: the draft logits row carried \
185 fewer than top_k finite values, i.e. it was NaN)"
186 } else {
187 ""
188 };
189 return Err(format!(
190 "{ctx}: DFlash2 candidate slot {i} is {c}, outside the selector's {bound} \
191 columns{why}"
192 ));
193 }
194 }
195 Ok(())
196}
197
198/// Greedy selector walk (reference `CandidateSelector.select` at T=0): per draft
199/// slot p, score(k) = unary[p,k] + <pred_codebook[prev] .* hidden_proj_row[p],
200/// succ_codebook[cand[p,k]]>, argmax over the top-k candidate set; the CHOSEN
201/// candidate seeds the next slot (sequential — the chain is the semantics, not an
202/// optimization). Host math (~nd*k*rank fused ops per round) over host-resident bf16
203/// codebooks; ties break to the LOWEST k (torch argmax convention). Pure so the
204/// selector semantics are CPU-gateable.
205///
206/// `unary`/`cand`: [nd, top_k] row-major; `hproj`: [nd, rank] row-major.
207#[allow(clippy::too_many_arguments)]
208pub fn dflash2_walk_greedy(
209 pred_codebook: &[u8],
210 succ_codebook: &[u8],
211 vocab: usize,
212 rank: usize,
213 top_k: usize,
214 unary: &[f32],
215 cand: &[u32],
216 hproj: &[f32],
217 anchor: u32,
218 nd: usize,
219) -> Vec<u32> {
220 dflash2_walk_greedy_q(
221 pred_codebook,
222 succ_codebook,
223 vocab,
224 rank,
225 top_k,
226 unary,
227 cand,
228 hproj,
229 anchor,
230 nd,
231 )
232 .0
233}
234
235/// [`dflash2_walk_greedy`] with the per-slot CONFIDENCE recorded (lane/glm5-loop-port,
236/// 2026-08-30): q[p] = softmax over the slot's candidate-set scores at T=1, of the chosen
237/// candidate — the greedy twin of `dflash2_walk_sampled`'s recorded `q_chosen` (same
238/// statistic family the owner's "take only high confidence offers" tau gate thresholds on
239/// the dspark route). The argmax selection is UNCHANGED (q is bookkeeping over the same
240/// scores, ~top_k exps per slot on host), so every existing greedy caller is byte-identical
241/// through the delegating wrapper. Pure, CPU-gateable like its siblings.
242#[allow(clippy::too_many_arguments)]
243pub fn dflash2_walk_greedy_q(
244 pred_codebook: &[u8],
245 succ_codebook: &[u8],
246 vocab: usize,
247 rank: usize,
248 top_k: usize,
249 unary: &[f32],
250 cand: &[u32],
251 hproj: &[f32],
252 anchor: u32,
253 nd: usize,
254) -> (Vec<u32>, Vec<f32>) {
255 let (kk, r) = (top_k, rank);
256 assert_eq!(unary.len(), nd * kk, "walk: unary shape");
257 assert_eq!(cand.len(), nd * kk, "walk: candidate shape");
258 assert_eq!(hproj.len(), nd * r, "walk: hidden-projection shape");
259 let mut path = Vec::with_capacity(nd);
260 let mut q_chosen = Vec::with_capacity(nd);
261 let mut prev = anchor;
262 for p in 0..nd {
263 assert!(
264 (prev as usize) < vocab,
265 "walk: predecessor token {prev} outside codebook vocab {vocab}"
266 );
267 let pr = cb_row(pred_codebook, prev as usize, r);
268 let hp = &hproj[p * r..(p + 1) * r];
269 // gate = pred_row .* hidden_proj (shared across the candidate set)
270 let gate: Vec<f32> = pr.iter().zip(hp).map(|(a, b)| a * b).collect();
271 let mut scores = vec![0f32; kk];
272 let (mut best, mut bi) = (f32::NEG_INFINITY, 0usize);
273 for (k, s) in scores.iter_mut().enumerate() {
274 let c = cand[p * kk + k] as usize;
275 assert!(c < vocab, "walk: candidate {c} outside codebook vocab");
276 let sr = cb_row(succ_codebook, c, r);
277 let mut acc = unary[p * kk + k];
278 for j in 0..r {
279 acc += gate[j] * sr[j];
280 }
281 *s = acc;
282 if acc > best {
283 best = acc;
284 bi = k;
285 }
286 }
287 // Recorded confidence: softmax at T=1 over the candidate set (f64 accumulation,
288 // the sampled walk's numeric discipline), of the argmaxed candidate.
289 let mut z = 0f64;
290 for &s in &scores {
291 z += ((s - best) as f64).exp();
292 }
293 q_chosen.push(if z > 0.0 { (1.0 / z) as f32 } else { 1.0 });
294 prev = cand[p * kk + bi];
295 path.push(prev);
296 }
297 (path, q_chosen)
298}
299
300impl Dflash2Head {
301 /// Greedy selector walk over this head's codebooks — see `dflash2_walk_greedy`.
302 pub fn walk_greedy(
303 &self,
304 unary: &[f32],
305 cand: &[u32],
306 hproj: &[f32],
307 anchor: u32,
308 nd: usize,
309 ) -> Vec<u32> {
310 dflash2_walk_greedy(
311 &self.pred_codebook,
312 &self.succ_codebook,
313 self.vocab,
314 self.rank,
315 self.top_k,
316 unary,
317 cand,
318 hproj,
319 anchor,
320 nd,
321 )
322 }
323
324 /// Greedy walk with the per-slot confidence recorded — see `dflash2_walk_greedy_q`.
325 pub fn walk_greedy_q(
326 &self,
327 unary: &[f32],
328 cand: &[u32],
329 hproj: &[f32],
330 anchor: u32,
331 nd: usize,
332 ) -> (Vec<u32>, Vec<f32>) {
333 dflash2_walk_greedy_q(
334 &self.pred_codebook,
335 &self.succ_codebook,
336 self.vocab,
337 self.rank,
338 self.top_k,
339 unary,
340 cand,
341 hproj,
342 anchor,
343 nd,
344 )
345 }
346
347 /// Sampled (T>0) selector walk — see `dflash2_walk_sampled`.
348 #[allow(clippy::too_many_arguments)]
349 pub fn walk_sampled(
350 &self,
351 unary: &[f32],
352 cand: &[u32],
353 hproj: &[f32],
354 anchor: u32,
355 nd: usize,
356 temp: f32,
357 uniforms: &mut dyn FnMut() -> f32,
358 ) -> (Vec<u32>, Vec<f32>, Vec<f32>) {
359 dflash2_walk_sampled(
360 &self.pred_codebook,
361 &self.succ_codebook,
362 self.vocab,
363 self.rank,
364 self.top_k,
365 unary,
366 cand,
367 hproj,
368 anchor,
369 nd,
370 temp,
371 uniforms,
372 )
373 }
374}
375
376/// AcceptRatePredictor: raw linear proj over [hidden ; markov_prev_embedding(rank)]
377/// (with_markov=true on the q38 arm-a export) — output is the PRE-sigmoid scalar.
378pub struct ConfidenceHead {
379 pub w: Vec<f32>, // [in_dim]
380 pub b: f32,
381 pub in_dim: usize,
382 pub with_markov: bool,
383}
384
385impl ConfidenceHead {
386 /// Host dot: the PRE-sigmoid accept score for one draft slot. `hidden` = the
387 /// drafter output row the slot is harvested from (the same row its logits use);
388 /// `emb` = the markov `w1` row of the slot's PREVIOUS chain token (required iff
389 /// `with_markov`) — the exact input contract the parity gate pins (prev ids =
390 /// `[anchor, chain[..nd-1]]`, dspark_q38_parity.rs stage 5).
391 pub fn raw_score(&self, hidden: &[f32], emb: Option<&[f32]>) -> f32 {
392 let mut acc = self.b;
393 for (w, x) in self.w.iter().zip(hidden) {
394 acc += w * x;
395 }
396 if self.with_markov {
397 let emb = emb.expect("with_markov confidence head scored without the markov embedding");
398 debug_assert_eq!(hidden.len() + emb.len(), self.in_dim);
399 for (w, x) in self.w[hidden.len()..].iter().zip(emb) {
400 acc += w * x;
401 }
402 } else {
403 debug_assert_eq!(hidden.len(), self.in_dim);
404 }
405 acc
406 }
407}
408
409pub struct MarkovHead {
410 pub w1_bf16: CudaSlice<u8>, // [V, rank] bf16 raw
411 pub w2: GpuTensor, // [rank -> V] q8_0
412 pub rank: usize,
413 pub vocab: usize,
414}
415
416/// Draft-row harvest convention for DFlash-family block drafters
417/// (darklanes research/deepseek-flash-20260818/DSPARK-POSTMORTEM-20260820.md).
418///
419/// The DFlash and DSpark SpecForge training strategies supervise DIFFERENT rows of the
420/// same `[anchor, MASK x b-1]` block, so the row -> trunk-position mapping is a property
421/// of the CHECKPOINT's training strategy, not of the loader:
422///
423/// - **Dflash** (mask-fill; z-lab dflash / SpecForge `OnlineDFlashModel`): row k is
424/// trained to predict the token AT position anchor+k — "Labels: same-position
425/// prediction", `weight_mask *= (pos_in_block > 0)` excludes the anchor row
426/// (SpecForge `specforge/algorithms/common/dflash_family_model.py:453-472`).
427/// Drafts = rows 1..b-1; the anchor row's output is untrained.
428/// - **Dspark** (shifted; SpecForge `OnlineDSparkModel`, `training.strategy: dspark` —
429/// the q38 arm-a export): row k is trained to predict the token at anchor+k+1, ALL
430/// rows supervised INCLUDING the anchor row (`label_offsets = arange(1,
431/// block_size+1)`, `dflash_family_model.py:816`). sglang's DSPARK worker — the stack
432/// every arm-a bank number was measured on — harvests gamma = block_size drafts with
433/// the anchor row's output as draft 1 (verified on the v0.5.17 eval-pin tag:
434/// `dspark_components/dspark_draft.py:248,260,318`; `dspark_config.py:269`).
435///
436/// Mismatching the convention verifies every slot against a position the row was never
437/// trained for — the q38 accept collapse (2.9 -> 1.43) in the postmortem.
438#[derive(Clone, Copy, PartialEq, Eq, Debug)]
439pub enum DsparkHarvest {
440 /// mask-fill: drafts = rows 1..b-1, row k fills position anchor+k.
441 Dflash,
442 /// shifted: drafts = rows 0..b-1, row k predicts position anchor+k+1.
443 Dspark,
444}
445
446impl DsparkHarvest {
447 /// The served resolution: explicit `MEMRA_DSPARK_HARVEST={dflash|dspark}` wins
448 /// (unknown values REFUSE loudly — a typo silently reverting the convention would
449 /// re-open the postmortem's misalignment); UNSET defers to the CHECKPOINT's own
450 /// training-strategy census — the owner-ratified default flip (2026-08-20, after
451 /// B1 confirmed H1 interleaved ×5 on serving-class hardware: accept 1.38→2.41
452 /// agentic / 1.53→3.66 math, E2E ALL EXACT both arms). Strategy-keyed, not
453 /// env-keyed, per the B0 plan: a DSPARK-strategy export harvests shifted
454 /// (all-rows), a mask-fill export keeps the historical dflash arm byte-identical.
455 pub fn resolve(cfg: &DflashCfg) -> Self {
456 Self::resolve_value(
457 std::env::var("MEMRA_DSPARK_HARVEST").ok().as_deref(),
458 cfg.strategy_dspark,
459 )
460 }
461
462 pub fn resolve_value(v: Option<&str>, strategy_dspark: bool) -> Self {
463 match v {
464 None | Some("") => {
465 if strategy_dspark {
466 DsparkHarvest::Dspark
467 } else {
468 DsparkHarvest::Dflash
469 }
470 }
471 set => Self::from_env_value(set),
472 }
473 }
474
475 /// ENV-ONLY parser (no checkpoint census): unset = `Dflash`, the historical arm.
476 /// Kept for the explicit-value path of [`Self::resolve_value`] and the seam tests;
477 /// round arms resolve through [`Self::resolve`] so the default stays strategy-keyed.
478 pub fn from_env_value(v: Option<&str>) -> Self {
479 match v {
480 None | Some("") | Some("dflash") => DsparkHarvest::Dflash,
481 Some("dspark") => DsparkHarvest::Dspark,
482 Some(other) => panic!(
483 "MEMRA_DSPARK_HARVEST={other}: unknown harvest convention (dflash|dspark); \
484 refusing — a wrong convention verifies every draft slot against a position \
485 the drafter row was not trained for (DSPARK-POSTMORTEM-20260820.md)"
486 ),
487 }
488 }
489
490 /// Resolve the harvest convention for a LOADED drafter — FAMILY-keyed first, then
491 /// STRATEGY-keyed (v0.100 train merge of the two ratified keyings):
492 /// - DFlash2 is a mask-fill-family drafter by construction (reference
493 /// `dflash_generate` harvests rows `1-verify_size:`; the card says "block size 8
494 /// (7 draft tokens per verification step)" — DFLASH2-EVAL-20260820.md §3). An env
495 /// value that CONTRADICTS the census REFUSES rather than silently re-keying the
496 /// round.
497 /// - Every other checkpoint rides [`Self::resolve_value`]: explicit env wins (typos
498 /// refuse loudly), unset defers to the checkpoint's own training-strategy census
499 /// (the owner-ratified 2026-08-20 default flip).
500 pub fn for_draft(draft: &DflashDraft) -> Self {
501 Self::for_family_value(
502 draft.dflash2.is_some(),
503 std::env::var("MEMRA_DSPARK_HARVEST").ok().as_deref(),
504 draft.cfg.strategy_dspark,
505 )
506 }
507
508 pub fn for_family_value(is_dflash2: bool, env: Option<&str>, strategy_dspark: bool) -> Self {
509 if is_dflash2 {
510 if env == Some("dspark") {
511 panic!(
512 "MEMRA_DSPARK_HARVEST=dspark with a DFlash2 checkpoint: DFlash2 \
513 is mask-fill (b-1 drafts, anchor row is not a draft — reference \
514 dflash_generate rows 1-verify_size:); the shifted harvest would \
515 verify every slot one position early. Refusing (census-keyed, \
516 not env-keyed)."
517 );
518 }
519 return DsparkHarvest::Dflash;
520 }
521 Self::resolve_value(env, strategy_dspark)
522 }
523
524 /// Manifest/serialized name (the oracle geometry manifest's `harvest` field).
525 pub fn name(self) -> &'static str {
526 match self {
527 DsparkHarvest::Dflash => "dflash",
528 DsparkHarvest::Dspark => "dspark",
529 }
530 }
531
532 pub fn from_name(v: &str) -> Option<Self> {
533 match v {
534 "dflash" => Some(DsparkHarvest::Dflash),
535 "dspark" => Some(DsparkHarvest::Dspark),
536 _ => None,
537 }
538 }
539
540 /// First drafter OUTPUT row consumed as a draft candidate.
541 pub fn first_row(self) -> usize {
542 match self {
543 DsparkHarvest::Dflash => 1,
544 DsparkHarvest::Dspark => 0,
545 }
546 }
547
548 /// Drafted tokens harvested per round from a `b`-row block.
549 pub fn n_drafts(self, b: usize) -> usize {
550 match self {
551 DsparkHarvest::Dflash => b - 1,
552 DsparkHarvest::Dspark => b,
553 }
554 }
555
556 /// The position offset (relative to the round anchor at the block's row 0) that
557 /// drafter output row `row` is TRAINED to predict under this convention.
558 pub fn trained_offset_of_row(self, row: usize) -> usize {
559 match self {
560 DsparkHarvest::Dflash => row,
561 DsparkHarvest::Dspark => row + 1,
562 }
563 }
564}
565
566/// Checkpoint training-strategy census over the raw config.json text (the loader's
567/// minimal-extractor idiom — no json dep in-tree). TRUE iff the export declares the
568/// DSPARK strategy: `architectures` naming a DSpark model class (`Qwen3DSparkModel`,
569/// the SpecForge OnlineDSparkModel export form) or `dflash_config.projector_type ==
570/// "dspark"`. z-lab / OnlineDFlashModel mask-fill exports carry neither signal. Pure,
571/// so the census is testable against config fragments without files.
572pub fn dspark_strategy_census(txt: &str) -> bool {
573 let arch = txt
574 .find("\"architectures\"")
575 .and_then(|i| {
576 let rest = &txt[i..];
577 let a = rest.find('[')?;
578 let b = rest.find(']')?;
579 Some(rest[a..b].contains("DSpark"))
580 })
581 .unwrap_or(false);
582 let proj = txt
583 .find("\"projector_type\"")
584 .map(|i| {
585 let rest = &txt[i..];
586 let after = rest.find(':').map(|c| &rest[c + 1..]).unwrap_or("");
587 after.trim_start().starts_with("\"dspark\"")
588 })
589 .unwrap_or(false);
590 arch || proj
591}
592
593/// Accepted-prefix length of a round's candidates against the trunk's verify argmaxes:
594/// `cand[0]` = the round anchor (already decided), `cand[1..]` = the drafts;
595/// `vam[j]` = the trunk's argmax prediction for position anchor+j+1. Returns m =
596/// number of accepted drafts (`cand[1..=m]` committed, `vam[m]` becomes the next
597/// anchor). Pure so the harvest-alignment fixture can exercise it CPU-side.
598pub fn dspark_accept_prefix(cand: &[u32], vam: &[u32], vt: usize) -> usize {
599 let mut m = 0usize;
600 while m < vt - 1 && cand[m + 1] == vam[m] {
601 m += 1;
602 }
603 m
604}
605
606/// Verify-window policy for the dspark round (H4, DSPARK-POSTMORTEM-20260820.md §3).
607///
608/// B2 measured the structural fork: the fixed full-block window (vt=8) buys 95–100%
609/// of the sglang accept bank but LOSES wall speed to the reactive ladder everywhere
610/// except math — at 0.2–0.5 slot rates, full-block verify pays 5–6 empty rows per
611/// round. The confidence policy is the mechanism both leading engines schedule with
612/// (sglang v0.5.16 `dspark_planner.py` cumprod survival; vLLM #47808): size EACH
613/// round's window from the drafter's own trained accept-rate head, so windows open
614/// on confident streaks (math/code) and shrink on bursty text without a 4-round
615/// ladder climb.
616#[derive(Clone, Copy, PartialEq, Debug)]
617pub enum DsparkVtPolicy {
618 /// The shipped reactive ladder: `vt = (m+2).clamp(3, vt_cap)` per round
619 /// (`MEMRA_DFLASH_ADAPT=0` pins vt at `vt_cap` = the fixed-window arm).
620 Ladder,
621 /// `MEMRA_DSPARK_VT=confidence`: per-round window from cumprod survival of the
622 /// confidence head's sigmoid scores, thresholded at `tau`
623 /// (`MEMRA_DSPARK_VT_TAU`, default 0.5). Raw sigmoid — no STS sidecar
624 /// calibration exists for this export; the postmortem names this the starting
625 /// policy.
626 Confidence { tau: f32 },
627 /// `MEMRA_DSPARK_VT=confidence-slot` (owner directive, 2026-08-20: "take only
628 /// high confidence offers"): submit only the longest draft PREFIX whose every
629 /// slot clears `tau` on its own sigmoid — the low-confidence tail never enters
630 /// verify. Same tau env. vs `Confidence`: if the head's per-row score is the
631 /// MARGINAL accept probability (it already sinks with depth), cumprod survival
632 /// double-counts the decay and over-truncates; if it is the CONDITIONAL,
633 /// per-slot under-truncates. Which statistic the q38 head emits is empirical —
634 /// both arms ride the A/B.
635 ConfidenceSlot { tau: f32 },
636}
637
638impl DsparkVtPolicy {
639 /// The served resolution: explicit `MEMRA_DSPARK_VT={ladder|confidence|
640 /// confidence-slot}` wins (unknown values REFUSE loudly — a typo silently
641 /// reverting the window policy would invalidate an A/B without a trace); UNSET
642 /// defaults to **`confidence-slot` at τ = `MEMRA_DSPARK_VT_TAU` (default 0.5)** —
643 /// the owner-ratified H4 flip (2026-08-20; cell 2's 4-arm A/B ×5 + cell 3's tau
644 /// ladder put the knee at τ=.5 for the slot arm: 94–98% of the fixed-8 accept bank
645 /// at wall ≥ the reactive ladder, exactness 11/11 ALL EXACT). Census-keyed per the
646 /// capacity-keyed-defaults law: a checkpoint WITHOUT an accept-rate head has no
647 /// signal to schedule with, so unset-env resolves to the ladder there (loudly, at
648 /// load) instead of panicking on a default; `MEMRA_DFLASH_ADAPT=0` (an explicit
649 /// fixed-window request) also keeps the ladder-family arm.
650 pub fn resolve(has_confidence_head: bool) -> Self {
651 Self::resolve_value(
652 std::env::var("MEMRA_DSPARK_VT").ok().as_deref(),
653 std::env::var("MEMRA_DSPARK_VT_TAU").ok().as_deref(),
654 std::env::var("MEMRA_DFLASH_ADAPT").ok().as_deref(),
655 has_confidence_head,
656 )
657 }
658
659 pub fn resolve_value(
660 vt: Option<&str>,
661 tau: Option<&str>,
662 adapt: Option<&str>,
663 has_confidence_head: bool,
664 ) -> Self {
665 match vt {
666 None | Some("") => {
667 if adapt == Some("0") || !has_confidence_head {
668 DsparkVtPolicy::Ladder
669 } else {
670 // The ratified default rides the SAME tau parse as the explicit
671 // arm (a bad MEMRA_DSPARK_VT_TAU refuses, never silently ignored).
672 Self::from_env_value(Some("confidence-slot"), tau, adapt)
673 }
674 }
675 set => Self::from_env_value(set, tau, adapt),
676 }
677 }
678
679 /// ENV-ONLY parser (no head census): unset = `Ladder`. Kept for the explicit-value
680 /// path of [`Self::resolve_value`] and the policy-gate tests; round arms resolve
681 /// through [`Self::resolve`] so the default stays head-census-keyed.
682 pub fn from_env_value(vt: Option<&str>, tau: Option<&str>, adapt: Option<&str>) -> Self {
683 match vt {
684 None | Some("") | Some("ladder") => DsparkVtPolicy::Ladder,
685 Some(mode @ ("confidence" | "confidence-slot")) => {
686 if adapt == Some("0") {
687 panic!(
688 "MEMRA_DSPARK_VT={mode} together with MEMRA_DFLASH_ADAPT=0 is \
689 contradictory (a pinned fixed window vs a per-round confidence \
690 window); unset one — refuse-on-ambiguity"
691 );
692 }
693 let tau = tau
694 .map(|t| {
695 t.parse::<f32>()
696 .unwrap_or_else(|_| panic!("MEMRA_DSPARK_VT_TAU={t}: not a float"))
697 })
698 .unwrap_or(0.5);
699 assert!(
700 tau > 0.0 && tau < 1.0,
701 "MEMRA_DSPARK_VT_TAU={tau}: confidence threshold must be in (0,1)"
702 );
703 if mode == "confidence" {
704 DsparkVtPolicy::Confidence { tau }
705 } else {
706 DsparkVtPolicy::ConfidenceSlot { tau }
707 }
708 }
709 Some(other) => panic!(
710 "MEMRA_DSPARK_VT={other}: unknown verify-window policy \
711 (ladder|confidence|confidence-slot); refusing — a wrong policy \
712 silently reverts the H4 arm (DSPARK-POSTMORTEM-20260820.md)"
713 ),
714 }
715 }
716
717 /// True for every head-scheduled arm (the loops gate the head requirement and
718 /// the embedding stash on this).
719 pub fn is_confidence(&self) -> bool {
720 !matches!(self, DsparkVtPolicy::Ladder)
721 }
722
723 /// Size this round's verify window from the head's pre-sigmoid slot scores.
724 /// `None` under the ladder (the caller keeps its carried vt).
725 pub fn size_window(&self, raws: &[f32], vt_cap: usize) -> Option<usize> {
726 match *self {
727 DsparkVtPolicy::Ladder => None,
728 DsparkVtPolicy::Confidence { tau } => Some(dspark_confidence_vt(raws, tau, vt_cap)),
729 DsparkVtPolicy::ConfidenceSlot { tau } => {
730 Some(dspark_slot_confidence_vt(raws, tau, vt_cap))
731 }
732 }
733 }
734}
735
736/// H4 window sizing (the sglang-planner/vLLM-#47808 mechanism, thresholded): `raws[k]`
737/// = the accept-rate head's PRE-sigmoid score for draft slot k+1; survival
738/// `S_k = prod_{j<=k} sigmoid(raws[j])`; the window keeps leading slots while
739/// `S_k >= tau`. Returns `vt` = 1 (anchor) + kept drafts, clamped to `[2, vt_cap]`:
740/// the draft forward is already paid, so at least one draft rides every verify — one
741/// extra verify row costs less than a guaranteed empty round. Pure, so the policy's
742/// knee is testable CPU-side like `dspark_accept_prefix`.
743pub fn dspark_confidence_vt(raws: &[f32], tau: f32, vt_cap: usize) -> usize {
744 let mut surv = 1.0f32;
745 let mut kept = 0usize;
746 for &r in raws {
747 surv *= 1.0 / (1.0 + (-r).exp());
748 if surv < tau {
749 break;
750 }
751 kept += 1;
752 }
753 (1 + kept).clamp(2, vt_cap.max(2))
754}
755
756/// Owner-directive arm (2026-08-20, "take only high confidence offers"): keep the
757/// longest draft PREFIX whose EVERY slot clears `tau` on its own sigmoid — truncate
758/// at the first sub-threshold slot, so the low-confidence tail (B2 measured 0.2–0.5
759/// slot rates at depth) never enters verify. Prefix truncation is forced by the
760/// accept rule anyway (`dspark_accept_prefix` stops at the first miss — a kept slot
761/// after a dropped one could never commit); the policy fork vs `dspark_confidence_vt`
762/// is only the stopping statistic (per-slot marginal vs cumulative survival). Same
763/// floor/cap contract.
764pub fn dspark_slot_confidence_vt(raws: &[f32], tau: f32, vt_cap: usize) -> usize {
765 let mut kept = 0usize;
766 for &r in raws {
767 let p = 1.0 / (1.0 + (-r).exp());
768 if p < tau {
769 break;
770 }
771 kept += 1;
772 }
773 (1 + kept).clamp(2, vt_cap.max(2))
774}
775
776// ================= SAMPLED ADMISSION (T>0) — lane/dspark-sampled-admission-20260820 =====
777// True rejection sampling for the dspark route (mystery A of DSPARK-POSTMORTEM-20260820):
778// draft slot j is DRAWN from a recorded proposal distribution q_j, the trunk's verify column
779// arbitrates with the Leviathan/Chen rule (accept x_j while u_j*q_j(x_j) < p_j(x_j); on
780// reject resample from norm(max(0, p-q)); on full accept the bonus ~ p at the last column),
781// so the committed stream's distribution equals trunk-only sampling from the FILTERED target
782// p — the same contract the frspec/MTP route ships (spec.rs sampled accept walk; kernels
783// oracled by sample_check). T==0/None keeps every greedy path byte-identical (the exactness
784// instrument and the kill-switch are the same code).
785//
786// Two proposal families, each recording the TRUE distribution its drafts were drawn from:
787// - Rows (dspark/dflash strategy checkpoints): per-slot FILTERED softmax of the draft-logits
788// row — markov-corrected in place when the head is present (the sglang DSPARK worker's
789// "chain rejection sampling over markov-corrected draft probs"), plain rows otherwise
790// (the z-lab reference's independent-row T>0 arm).
791// - Selector (DFlash2): the candidate-path selector's per-slot softmax over its top-k
792// candidate set at temperature ONLY — the reference applies no top-k/top-p to selector
793// scores (z-lab model.py `CandidateSelector.select`: `_sampling_probs(scores, temperature)`
794// with default filters) — with the candidate-set residual (`scatter_add_` of -q, clamped).
795
796/// Rejection-sampling prefix walk: accept draft j while `u_j * q_j < p_j` (strict, f64 —
797/// byte-identical to the frspec accept test). `p`/`q` are the FILTERED target/proposal
798/// probabilities of the drafted tokens; `u` the per-slot uniforms. Pure so the composition
799/// gate can pin the rule on CPU.
800pub fn rejection_accept_len(p: &[f32], q: &[f32], u: &[f32]) -> usize {
801 assert!(
802 q.len() >= p.len() && u.len() >= p.len(),
803 "accept walk shape"
804 );
805 let mut m = 0usize;
806 while m < p.len() && (u[m] as f64) * (q[m] as f64) < p[m] as f64 {
807 m += 1;
808 }
809 m
810}
811
812/// Sampled selector walk (reference `CandidateSelector.select`, temperature>0 arm): per
813/// draft slot the pair scores over the top-k candidate set become a softmax at `temp`
814/// (temperature ONLY — the reference passes no top-k/top-p here), one uniform draws the
815/// candidate (fixed-order CDF walk), and the CHOSEN candidate seeds the next slot exactly
816/// like the greedy chain. Returns (path, q_chosen[nd], q_rows[nd*top_k]) — q_rows are the
817/// recorded per-slot candidate probabilities (the residual's `scatter_add_` input), and
818/// q_chosen[j] == q_rows[j*top_k + chosen_j] is the accept-test q. Pure (uniforms injected)
819/// so the T->0 limit, the chain conditioning, and the recorded-q contract are CPU-gateable.
820#[allow(clippy::too_many_arguments)]
821pub fn dflash2_walk_sampled(
822 pred_codebook: &[u8],
823 succ_codebook: &[u8],
824 vocab: usize,
825 rank: usize,
826 top_k: usize,
827 unary: &[f32],
828 cand: &[u32],
829 hproj: &[f32],
830 anchor: u32,
831 nd: usize,
832 temp: f32,
833 uniforms: &mut dyn FnMut() -> f32,
834) -> (Vec<u32>, Vec<f32>, Vec<f32>) {
835 assert!(
836 temp > 0.0,
837 "sampled walk is the T>0 arm; T=0 is walk_greedy"
838 );
839 let (kk, r) = (top_k, rank);
840 assert_eq!(unary.len(), nd * kk, "walk: unary shape");
841 assert_eq!(cand.len(), nd * kk, "walk: candidate shape");
842 assert_eq!(hproj.len(), nd * r, "walk: hidden-projection shape");
843 let mut path = Vec::with_capacity(nd);
844 let mut q_chosen = Vec::with_capacity(nd);
845 let mut q_rows = Vec::with_capacity(nd * kk);
846 let mut prev = anchor;
847 for p in 0..nd {
848 assert!(
849 (prev as usize) < vocab,
850 "walk: predecessor token {prev} outside codebook vocab {vocab}"
851 );
852 let pr = cb_row(pred_codebook, prev as usize, r);
853 let hp = &hproj[p * r..(p + 1) * r];
854 let gate: Vec<f32> = pr.iter().zip(hp).map(|(a, b)| a * b).collect();
855 let mut scores = vec![0f32; kk];
856 for (k, s) in scores.iter_mut().enumerate() {
857 let c = cand[p * kk + k] as usize;
858 assert!(c < vocab, "walk: candidate {c} outside codebook vocab");
859 let sr = cb_row(succ_codebook, c, r);
860 let mut acc = unary[p * kk + k];
861 for j in 0..r {
862 acc += gate[j] * sr[j];
863 }
864 *s = acc;
865 }
866 // softmax over the candidate set at temp (f64 internals; recorded probs are the
867 // f32 values the CDF walk actually samples from — recorded q IS the proposal).
868 let mx = scores.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
869 let mut z = 0f64;
870 let ex: Vec<f64> = scores
871 .iter()
872 .map(|&s| {
873 let e0 = (((s - mx) / temp) as f64).exp();
874 z += e0;
875 e0
876 })
877 .collect();
878 let probs: Vec<f32> = ex.iter().map(|&e0| (e0 / z) as f32).collect();
879 let u = uniforms() as f64;
880 let mut acc = 0f64;
881 // fp-residue fallback (u >= f32-accumulated mass, ~2^-24 events): the max-prob
882 // candidate — never a zero-prob one (host_u01's range includes 1.0 exactly).
883 let mut bi = probs
884 .iter()
885 .enumerate()
886 .max_by(|a, b| a.1.total_cmp(b.1))
887 .map(|(k, _)| k)
888 .unwrap_or(0);
889 for (k, &pk) in probs.iter().enumerate() {
890 acc += pk as f64;
891 if u < acc {
892 bi = k;
893 break;
894 }
895 }
896 prev = cand[p * kk + bi];
897 path.push(prev);
898 q_chosen.push(probs[bi]);
899 q_rows.extend_from_slice(&probs);
900 }
901 (path, q_chosen, q_rows)
902}
903
904/// `dflash2_propose_sampled`'s wire: (path, q_chosen, candidate ids, q_rows).
905pub(crate) type Dflash2SampledProposal = (Vec<u32>, Vec<f32>, Vec<u32>, Vec<f32>);
906
907/// Per-round proposal record for the sampled dspark round — everything the rejection
908/// walk needs to evaluate the TRUE per-slot proposal distribution q.
909pub(crate) enum DsparkDraftSample {
910 /// q lives in the round's draft-logits buffer `dl` (markov-biased in place when the
911 /// head is armed); per-slot FILTERED stats retained device-contiguous for the accept
912 /// gather + host-mirrored for the reject-slot residual.
913 Rows {
914 th: CudaSlice<f32>, // [nd] filter thresholds (e-units), slot-indexed
915 z: CudaSlice<f32>, // [nd] renorm masses
916 stats: Vec<(f32, f32, f32)>, // host (mx, th, z) per slot
917 },
918 /// DFlash2 candidate-path selector: q is the recorded candidate-set distribution.
919 Selector {
920 cand: Vec<u32>, // [nd*top_k] candidate ids
921 q_rows: Vec<f32>, // [nd*top_k] per-slot candidate probs
922 q_chosen: Vec<f32>, // [nd] prob of the drawn candidate (accept-test q)
923 top_k: usize,
924 },
925}
926
927/// The sampled round's verify+accept: filtered p gathered from the trunk's verify logits
928/// (row j arbitrates draft `cand[j+1]` — the position mapping the greedy prefix walk uses),
929/// the rejection walk over host uniforms, then `next` = bonus (full accept: filtered-Gumbel
930/// from the LAST verify row with its OWN fresh stats — the sampfix-20260805 law: that row is
931/// one past the gathered set) or the residual sample at the reject slot (family-keyed q:
932/// full-row logits for Rows, sparse candidate-set probs for Selector). Returns (m, next) —
933/// the exact (accepted-drafts, next-anchor) contract of the greedy `dspark_accept_prefix` +
934/// `vam[m]` pair, so both round bodies commit identically downstream.
935///
936/// PENALIZED SAMPLED (lane/dspark-penalized-sampled-20260821): when the request carries
937/// non-identity penalties, the vt verify columns are materialized ONCE into a penalized
938/// copy where row j's Keskar pass runs over `pen_win ++ cand[1..=j]` (window-capped) —
939/// the tokens committed before position j ON EVERY PATH WHERE ROW j IS CONSULTED,
940/// same-round accepts included (row j is only read when drafts 1..j were all accepted,
941/// i.e. exactly when `cand[1..=j]` is the committed prefix; the bonus row vt-1 is only
942/// read on full accept, when all nq drafts are committed). Every p read — the batched
943/// stats+gather, the bonus draw, the reject-slot residual column — points at that buffer,
944/// so p is the true penalized per-state target and the committed stream equals plain
945/// penalized sampling (the composition gate's penalty fixtures, self-hit included).
946/// q stays the RECORDED proposal the drafts were actually drawn from (unpenalized):
947/// rejection sampling is unbiased for ANY proposal with `u·q(x) < p(x)` + residual
948/// `norm(max(0, p−q))`; penalizing q would only buy acceptance overlap and would cost an
949/// evolving-history pass inside the sync-free device chain. `pen_win` is the caller's
950/// session window ALREADY trimmed to `min(penalty_last_n, PEN_WINDOW_MAX)` (empty when
951/// penalties are off — the unpenalized path is byte-untouched).
952#[allow(clippy::too_many_arguments)]
953pub(crate) fn dspark_accept_sampled(
954 e: &Engine,
955 tlogits: &CudaSlice<f32>,
956 cand: &[u32],
957 vt: usize,
958 n_vocab: usize,
959 dl: &CudaSlice<f32>,
960 prop: &DsparkDraftSample,
961 sp: &crate::spec::SpecSampling,
962 pen_win: &[u32],
963 sctr: &mut u32,
964 uctr: &mut u32,
965) -> Result<(usize, u32), Box<dyn std::error::Error>> {
966 let nq = vt - 1; // drafts under this round's verify window
967 debug_assert!(nq >= 1 && cand.len() > nq, "sampled accept shape");
968 // --- penalized verify columns (identity penalties: no copy, no launch, raw tlogits) ---
969 let pen_on = sp.pen_on();
970 let ptl: Option<CudaSlice<f32>> = if pen_on {
971 let win = sp.penalty_last_n.min(crate::spec::PEN_WINDOW_MAX);
972 debug_assert!(pen_win.len() <= win, "pen_win must arrive pre-trimmed");
973 let mut hist: Vec<u32> = Vec::with_capacity(pen_win.len() + nq);
974 hist.extend_from_slice(pen_win);
975 hist.extend_from_slice(&cand[1..=nq]); // drafted tokens: row j reads the first j
976 let hd = e.htod_u32_v(&hist)?;
977 let mut buf = e.clone_dtod(tlogits)?;
978 e.penalize_logits_rows_inc(
979 &mut buf,
980 &hd,
981 pen_win.len(),
982 sp.penalty_repeat,
983 sp.penalty_freq,
984 sp.penalty_present,
985 n_vocab,
986 vt,
987 win,
988 )?;
989 Some(buf)
990 } else {
991 None
992 };
993 let p_src: &CudaSlice<f32> = ptl.as_ref().unwrap_or(tlogits);
994 // --- filtered p at the drafted tokens (one batched stats + gather over rows 0..nq-1) ---
995 let rows: Vec<i32> = (0..nq as i32).collect();
996 let ids: Vec<u32> = cand[1..=nq].to_vec();
997 let rowsd = e.htod_i32(&rows)?;
998 let idsd = e.htod_u32_v(&ids)?;
999 let (mut pth, mut pz, mut pmx) = (e.zeros(nq)?, e.zeros(nq)?, e.zeros(nq)?);
1000 e.filter_stats(
1001 p_src, n_vocab, &rowsd, &mut pth, &mut pz, &mut pmx, n_vocab, nq, sp.temp, sp.top_k,
1002 sp.top_p, sp.min_p,
1003 )?;
1004 let mut pj_d = e.zeros(nq)?;
1005 e.softmax_gather_filtered(
1006 p_src, n_vocab, &idsd, &rowsd, &pth, &pz, &mut pj_d, n_vocab, nq, sp.temp,
1007 )?;
1008 let pj = e.dtoh(&pj_d)?;
1009 let (pthv, pzv, pmxv) = (e.dtoh(&pth)?, e.dtoh(&pz)?, e.dtoh(&pmx)?);
1010 // --- q at the drafted tokens (the recorded proposal distribution) ---
1011 let qj: Vec<f32> = match prop {
1012 DsparkDraftSample::Rows { th, z, .. } => {
1013 // dl row j is draft j's (bias-corrected) logits row; th/z are slot-indexed, and
1014 // rows 0..nq-1 index both the buffer rows and the stat pairs.
1015 let mut qd = e.zeros(nq)?;
1016 e.softmax_gather_filtered(
1017 dl, n_vocab, &idsd, &rowsd, th, z, &mut qd, n_vocab, nq, sp.temp,
1018 )?;
1019 e.dtoh(&qd)?
1020 }
1021 DsparkDraftSample::Selector { q_chosen, .. } => q_chosen[..nq].to_vec(),
1022 };
1023 // --- the rejection walk ---
1024 let mut us = Vec::with_capacity(nq);
1025 for _ in 0..nq {
1026 us.push(crate::spec::host_u01(sp.seed, *uctr));
1027 *uctr = uctr.wrapping_add(1);
1028 }
1029 let m = rejection_accept_len(&pj[..nq], &qj[..nq], &us);
1030 // --- next anchor: bonus or residual ---
1031 let next = if m == nq {
1032 // FULL ACCEPT: bonus ~ filtered p at verify row vt-1 — fresh stats for THIS row.
1033 // Under penalties p_src row vt-1 carries the FULL drafted block in its window
1034 // (all nq drafts are committed on this path — the "drafted token penalizes its
1035 // own successor" case the composition gate's self-hit fixture pins).
1036 let rows_l = e.htod_i32(&[(vt - 1) as i32])?;
1037 let (mut th1, mut z1, mut mx1) = (e.zeros(1)?, e.zeros(1)?, e.zeros(1)?);
1038 e.filter_stats(
1039 p_src, n_vocab, &rows_l, &mut th1, &mut z1, &mut mx1, n_vocab, 1, sp.temp, sp.top_k,
1040 sp.top_p, sp.min_p,
1041 )?;
1042 let mut pb = e.zeros(n_vocab)?;
1043 e.gumbel_perturb_filtered_col(
1044 p_src,
1045 vt - 1,
1046 &mut pb,
1047 n_vocab,
1048 sp.seed,
1049 *sctr,
1050 sp.temp,
1051 &mx1,
1052 &th1,
1053 0,
1054 )?;
1055 *sctr = sctr.wrapping_add(1);
1056 let td = e.argmax_token_device(&pb, n_vocab)?;
1057 e.dtoh_u32_one(&td)?
1058 } else {
1059 // REJECT at slot m: token ~ norm(max(0, p_m - q_m)); p row m's stats come from the
1060 // gathered set (rows 0..nq-1 cover every reject slot). Under penalties the column
1061 // copy MUST come from p_src (the penalized buffer) — a raw-tlogits residual is the
1062 // composition gate's "residual reads unpenalized p" tooth.
1063 let mut col = e.zeros(n_vocab)?;
1064 e.copy_view_into(
1065 &mut col,
1066 0,
1067 &p_src.slice(m * n_vocab..(m + 1) * n_vocab),
1068 n_vocab,
1069 )?;
1070 let p_stats = (pmxv[m], pthv[m], pzv[m]);
1071 let mut tok_d = e.alloc_u32_zeroed(1)?;
1072 let sc = *sctr;
1073 *sctr = sctr.wrapping_add(1);
1074 match prop {
1075 DsparkDraftSample::Rows { stats, .. } => {
1076 let mut qbuf = e.zeros(n_vocab)?;
1077 e.copy_view_into(
1078 &mut qbuf,
1079 0,
1080 &dl.slice(m * n_vocab..(m + 1) * n_vocab),
1081 n_vocab,
1082 )?;
1083 e.residual_sample_filtered(
1084 &col,
1085 Some(&qbuf),
1086 n_vocab,
1087 sp.temp,
1088 sp.seed,
1089 sc,
1090 p_stats,
1091 stats[m],
1092 &mut tok_d,
1093 )?;
1094 }
1095 DsparkDraftSample::Selector {
1096 cand: cids,
1097 q_rows,
1098 top_k,
1099 ..
1100 } => {
1101 let k = *top_k;
1102 let ids_m = e.htod_u32_v(&cids[m * k..(m + 1) * k])?;
1103 let qs_m = e.htod(&q_rows[m * k..(m + 1) * k])?;
1104 e.residual_sample_sparse_q(
1105 &col, &ids_m, &qs_m, k, n_vocab, sp.temp, sp.seed, sc, p_stats, &mut tok_d,
1106 )?;
1107 }
1108 }
1109 e.dtoh_u32(&tok_d)?[0]
1110 };
1111 Ok((m, next))
1112}
1113
1114/// Clip door for the DFlash2 windowed round attention (lane/dflash2-longctx, §10.6(c)).
1115/// Default ON: the lo-clipped kernel — byte-identical output (kernel_check
1116/// `sdpa_naive_w_lo`), O(window) key scan, and no T_kv*4-byte shared-mem launch bound, so
1117/// the route survives past ~12k ctx (GATES-SMOKE-20260821 B2: DriverError(
1118/// CUDA_ERROR_INVALID_VALUE) at ctx 16,571/30,157, last success 9,510).
1119/// MEMRA_DFLASH2_SDPA_CLIP=0 = the legacy full-scan kernel byte-for-byte — the rollback
1120/// seam and the long-ctx gate's crash-reproduction arm.
1121fn dflash2_sdpa_clip_on() -> bool {
1122 std::env::var("MEMRA_DFLASH2_SDPA_CLIP")
1123 .map(|v| v != "0")
1124 .unwrap_or(true)
1125}
1126
1127/// The DFlash2 round attention over the non-causal symmetric window: one seam for both the
1128/// first-light (`forward_block`) and cached (`forward_round`) arms, dispatching the clipped
1129/// kernel unless the rollback door is thrown.
1130///
1131/// `floor` (lane/spec-exclusions-20260902): the KV's first EXISTING ctx row
1132/// (`DflashKv::floor`, 0 on every cold-primed or full-tail KV). A COLD-DRAFTER session
1133/// (`DflashKv::new_cold_at`) or a short-tail import owns rows only from `floor` up; the
1134/// clipped kernel raises its window floor to it and the drafter attends a shorter context,
1135/// exactly the program a shorter prompt runs. The legacy full-scan kernel has no floor arm
1136/// (it would score the zero rows below the floor at e^0 each and dilute the real context),
1137/// so a binding floor under `MEMRA_DFLASH2_SDPA_CLIP=0` refuses by name instead of drafting
1138/// silently worse — the cold-drafter constructors refuse the same combination up front.
1139#[allow(clippy::too_many_arguments)]
1140fn d2_windowed_attn(
1141 e: &Engine,
1142 q: &CudaSlice<f32>,
1143 k: &CudaSlice<f32>,
1144 v: &CudaSlice<f32>,
1145 attn: &mut CudaSlice<f32>,
1146 hd: usize,
1147 nh: usize,
1148 nkv: usize,
1149 t: usize,
1150 t_kv: usize,
1151 scale: f32,
1152 c: &DflashCfg,
1153 floor: usize,
1154) -> Result<(), Box<dyn std::error::Error>> {
1155 if dflash2_sdpa_clip_on() {
1156 e.sdpa_naive_w_lo(
1157 q,
1158 k,
1159 v,
1160 attn,
1161 hd,
1162 nh,
1163 nkv,
1164 t,
1165 t_kv,
1166 scale,
1167 false,
1168 c.sliding_window,
1169 floor,
1170 )
1171 } else {
1172 if floor > 0 {
1173 return Err(
1174 "DFlash2 round attention: the drafter KV carries a context floor (cold-drafter \
1175 or short-tail import) and MEMRA_DFLASH2_SDPA_CLIP=0 selects the full-scan \
1176 kernel, which has no floor arm; unset the clip rollback or serve plain"
1177 .into(),
1178 );
1179 }
1180 e.sdpa_naive_w(
1181 q,
1182 k,
1183 v,
1184 attn,
1185 hd,
1186 nh,
1187 nkv,
1188 t,
1189 t_kv,
1190 scale,
1191 false,
1192 c.sliding_window,
1193 )
1194 }
1195}
1196
1197fn bf16_to_f32(bytes: &[u8]) -> Vec<f32> {
1198 bytes
1199 .chunks_exact(2)
1200 .map(|c| f32::from_bits((u16::from_le_bytes([c[0], c[1]]) as u32) << 16))
1201 .collect()
1202}
1203
1204fn validate_dflash_tensor(
1205 name: &str,
1206 info: &memra_gguf::safetensors::StInfo,
1207 expected: &[u64],
1208) -> Result<(), String> {
1209 if info.dtype != "BF16" {
1210 return Err(format!(
1211 "DFlash tensor {name} has dtype {}, expected BF16",
1212 info.dtype
1213 ));
1214 }
1215 let found = info.ne();
1216 if found != expected {
1217 return Err(format!(
1218 "DFlash tensor {name} has shape {found:?}, expected {expected:?}"
1219 ));
1220 }
1221 Ok(())
1222}
1223
1224fn validate_dflash_attention_geometry(
1225 n_head: usize,
1226 n_kv: usize,
1227 head_dim: usize,
1228) -> Result<(), String> {
1229 if n_head == 0 || n_kv == 0 || head_dim == 0 || !n_head.is_multiple_of(n_kv) {
1230 return Err(format!(
1231 "DFlash attention geometry requires nonzero n_head divisible by n_kv; got n_head={n_head}, n_kv={n_kv}, head_dim={head_dim}"
1232 ));
1233 }
1234 n_head
1235 .checked_mul(head_dim)
1236 .ok_or("DFlash query-head geometry overflow")?;
1237 n_kv.checked_mul(head_dim)
1238 .ok_or("DFlash key/value-head geometry overflow")?;
1239 Ok(())
1240}
1241
1242fn validate_selector_top_k(top_k: usize, vocab: usize) -> Result<(), String> {
1243 if top_k == 0 || top_k > vocab {
1244 return Err(format!(
1245 "DFlash2 selector_top_k {top_k} is outside codebook vocabulary 1..={vocab}"
1246 ));
1247 }
1248 Ok(())
1249}
1250
1251fn validate_layer_layout(layer_sliding: &[bool], n_layer: usize) -> Result<(), String> {
1252 if layer_sliding.len() != n_layer {
1253 return Err(format!(
1254 "DFlash layer_types has {} entries, expected num_hidden_layers {n_layer}",
1255 layer_sliding.len()
1256 ));
1257 }
1258 Ok(())
1259}
1260
1261/// Host q8_0 encode (ggml block layout: [d f16][32 x i8] = 34B/32 vals). The drafter's
1262/// weights ride the dp4a fast path at 1.6GB resident (bf16 3.1GB + the 31B trunk OOM'd
1263/// 24GB; f32 6.2GB worse). Drafter quantization moves ACCEPTANCE only — verify exactness
1264/// is structural.
1265fn encode_q8_0(vals: &[f32]) -> Vec<u8> {
1266 let mut out = Vec::with_capacity(vals.len() / 32 * 34);
1267 for blk in vals.chunks_exact(32) {
1268 let amax = blk.iter().fold(0f32, |a, v| a.max(v.abs()));
1269 let d = amax / 127.0;
1270 let id = if d > 0.0 { 1.0 / d } else { 0.0 };
1271 let dh = half_from_f32(d);
1272 out.extend_from_slice(&dh.to_le_bytes());
1273 for &v in blk {
1274 out.push(((v * id).round().clamp(-127.0, 127.0)) as i8 as u8);
1275 }
1276 }
1277 out
1278}
1279
1280/// Host q4_0 encode (ggml: [d f16][16B packed nibbles] = 18B/32 vals; q = round(v/d)+8,
1281/// d = amax/-7 sign trick NOT used — plain amax/7? ggml uses d = max/-8 .. follow ggml:
1282/// d = amax / -8 when the max is negative-dominant; reference quantize_row_q4_0: d =
1283/// max(|v|)/-8 signed-max form). Implemented to match ggml quantize_row_q4_0_ref.
1284fn encode_q4_0(vals: &[f32]) -> Vec<u8> {
1285 let mut out = Vec::with_capacity(vals.len() / 32 * 18);
1286 for blk in vals.chunks_exact(32) {
1287 // ggml ref: pick the value with the LARGEST |v| (keeping sign), d = that / -8
1288 let mut amax = 0f32;
1289 let mut mx = 0f32;
1290 for &v in blk {
1291 if v.abs() > amax {
1292 amax = v.abs();
1293 mx = v;
1294 }
1295 }
1296 let d = mx / -8.0;
1297 let id = if d != 0.0 { 1.0 / d } else { 0.0 };
1298 out.extend_from_slice(&half_from_f32(d).to_le_bytes());
1299 for j in 0..16 {
1300 let x0 = (blk[j] * id + 8.5).clamp(0.0, 15.0) as u8;
1301 let x1 = (blk[j + 16] * id + 8.5).clamp(0.0, 15.0) as u8;
1302 out.push(x0 | (x1 << 4));
1303 }
1304 }
1305 out
1306}
1307
1308fn half_from_f32(v: f32) -> u16 {
1309 // f32 -> IEEE f16 (round-to-nearest-even; range of q8_0 d values is tame)
1310 let b = v.to_bits();
1311 let sign = ((b >> 16) & 0x8000) as u16;
1312 let exp = ((b >> 23) & 0xff) as i32 - 127 + 15;
1313 let man = b & 0x7fffff;
1314 if exp <= 0 {
1315 return sign;
1316 } // flush tiny d to zero
1317 if exp >= 31 {
1318 return sign | 0x7c00;
1319 } // inf (unreachable for sane d)
1320 let mut h = sign | ((exp as u16) << 10) | ((man >> 13) as u16);
1321 // round to nearest even on the truncated 13 bits
1322 let rem = man & 0x1fff;
1323 if rem > 0x1000 || (rem == 0x1000 && (h & 1) == 1) {
1324 h += 1;
1325 }
1326 h
1327}
1328
1329impl DflashDraft {
1330 /// Load the backbone-only checkpoint dir (config.json + model.safetensors, bf16).
1331 /// Config scalars ride a minimal extractor (no json dep in-tree — HfConfig precedent).
1332 pub fn load(e: &Engine, dir: &std::path::Path) -> Result<Self, Box<dyn std::error::Error>> {
1333 let txt = std::fs::read_to_string(dir.join("config.json"))?;
1334 fn num(txt: &str, key: &str) -> Option<f64> {
1335 let i = txt.find(&format!("\"{key}\""))?;
1336 let rest = &txt[i..];
1337 let colon = rest.find(':')?;
1338 let val: String = rest[colon + 1..]
1339 .trim_start()
1340 .chars()
1341 .take_while(|c| {
1342 c.is_ascii_digit()
1343 || *c == '.'
1344 || *c == '-'
1345 || *c == 'e'
1346 || *c == 'E'
1347 || *c == '+'
1348 })
1349 .collect();
1350 val.parse().ok()
1351 }
1352 fn num_list(txt: &str, key: &str) -> Vec<usize> {
1353 let Some(i) = txt.find(&format!("\"{key}\"")) else {
1354 return Vec::new();
1355 };
1356 let rest = &txt[i..];
1357 let (Some(a), Some(b)) = (rest.find('['), rest.find(']')) else {
1358 return Vec::new();
1359 };
1360 rest[a + 1..b]
1361 .split(',')
1362 .filter_map(|s| s.trim().parse().ok())
1363 .collect()
1364 }
1365 /// Substring of the JSON OBJECT value of a top-level key (brace-balanced) —
1366 /// the explicit scoped parse the DFlash2 census demands: `dflash_config` and
1367 /// `rope_parameters` are nested objects, and finding their keys by global
1368 /// `txt.find` is luck, not a contract (DFLASH2-EVAL-20260820.md §5.1).
1369 fn scope<'a>(txt: &'a str, key: &str) -> Option<&'a str> {
1370 let i = txt.find(&format!("\"{key}\""))?;
1371 let rest = &txt[i..];
1372 let open = rest.find('{')?;
1373 let mut depth = 0usize;
1374 for (j, ch) in rest[open..].char_indices() {
1375 match ch {
1376 '{' => depth += 1,
1377 '}' => {
1378 depth -= 1;
1379 if depth == 0 {
1380 return Some(&rest[open..open + j + 1]);
1381 }
1382 }
1383 _ => {}
1384 }
1385 }
1386 None
1387 }
1388 // Family detection is the ARCHITECTURES string, not tensor presence: a DFlash2
1389 // checkpoint whose new tensors were stripped must REFUSE, not degrade into the
1390 // 58-tensor untrained program (DFLASH2-EVAL-20260820.md §3).
1391 let is_dflash2 = {
1392 let arch = scope_list(&txt, "architectures");
1393 arch.contains("DFlash2DraftModel")
1394 };
1395 fn scope_list(txt: &str, key: &str) -> String {
1396 let Some(i) = txt.find(&format!("\"{key}\"")) else {
1397 return String::new();
1398 };
1399 let rest = &txt[i..];
1400 match (rest.find('['), rest.find(']')) {
1401 (Some(a), Some(b)) if a < b => rest[a + 1..b].to_string(),
1402 _ => String::new(),
1403 }
1404 }
1405 // DFlash2 scalars parse from their OWN scopes; other families keep the
1406 // historical global-find behavior byte-identically.
1407 let d2_cfg_txt: Option<&str> = if is_dflash2 {
1408 Some(
1409 scope(&txt, "dflash_config")
1410 .ok_or("DFlash2DraftModel config.json has no dflash_config object")?,
1411 )
1412 } else {
1413 None
1414 };
1415 let required_usize =
1416 |scope: &str, k: &str, label: &str| -> Result<usize, Box<dyn std::error::Error>> {
1417 let value = num(scope, k).ok_or_else(|| format!("{label} missing {k}"))?;
1418 if !value.is_finite()
1419 || value < 0.0
1420 || value.fract() != 0.0
1421 || value > usize::MAX as f64
1422 {
1423 return Err(format!("{label} {k}={value} is not a non-negative usize").into());
1424 }
1425 Ok(value as usize)
1426 };
1427 let g = |k: &str| required_usize(&txt, k, "config");
1428 let g2 = |k: &str| {
1429 required_usize(
1430 d2_cfg_txt.ok_or("DFlash2 config scope is unavailable")?,
1431 k,
1432 "dflash_config",
1433 )
1434 };
1435 // layer_types order: count entries, mark sliding ones
1436 let layer_sliding: Vec<bool> = {
1437 let i = txt
1438 .find("\"layer_types\"")
1439 .ok_or("config missing layer_types")?;
1440 let rest = &txt[i..];
1441 let a = rest.find('[').ok_or("layer_types is not an array")?;
1442 let b = rest.find(']').ok_or("layer_types array is unterminated")?;
1443 rest[a + 1..b]
1444 .split(',')
1445 .map(|s| s.contains("sliding_attention"))
1446 .collect()
1447 };
1448 // sliding_window is null on all-full-attention exports (q38 arm-a); the window
1449 // only constrains rounds when a sliding layer exists (reference: resolve_dflash_
1450 // attention_layout returns None when no layer slides).
1451 let sliding_window = if layer_sliding.iter().any(|&s| s) {
1452 g("sliding_window")?
1453 } else {
1454 num(&txt, "sliding_window")
1455 .map(|v| v as usize)
1456 .unwrap_or(usize::MAX)
1457 };
1458 // Explicit top-level is_causal (z-lab reference: overrides the layer-type
1459 // default). Parsed as a bare bool; absent = None (historical arms unchanged).
1460 let is_causal = txt
1461 .find("\"is_causal\"")
1462 .and_then(|i| txt[i..].find(':').map(|c| i + c + 1))
1463 .map(|v| txt[v..].trim_start().starts_with("true"));
1464 let cfg = DflashCfg {
1465 hidden: g("hidden_size")?,
1466 n_head: g("num_attention_heads")?,
1467 n_kv: g("num_key_value_heads")?,
1468 head_dim: g("head_dim")?,
1469 n_ff: g("intermediate_size")?,
1470 n_layer: g("num_hidden_layers")?,
1471 eps: num(&txt, "rms_norm_eps").ok_or("config missing rms_norm_eps")? as f32,
1472 // DFlash2 (transformers-5 style): rope_theta lives in the nested
1473 // rope_parameters object — parse it from its scope, not by global find.
1474 rope_theta: if is_dflash2 {
1475 let rp = scope(&txt, "rope_parameters")
1476 .ok_or("DFlash2 config has no rope_parameters")?;
1477 if !rp.contains("\"default\"") {
1478 return Err(format!(
1479 "DFlash2 rope_parameters rope_type is not default; refusing {rp}"
1480 )
1481 .into());
1482 }
1483 num(rp, "rope_theta").ok_or("rope_parameters missing rope_theta")? as f32
1484 } else {
1485 num(&txt, "rope_theta").ok_or("config missing rope_theta")? as f32
1486 },
1487 block_size: if is_dflash2 {
1488 g2("block_size")?
1489 } else {
1490 g("block_size")?
1491 },
1492 mask_token_id: u32::try_from(if is_dflash2 {
1493 g2("mask_token_id")?
1494 } else {
1495 g("mask_token_id")?
1496 })
1497 .map_err(|_| "DFlash mask_token_id does not fit u32")?,
1498 target_layer_ids: if is_dflash2 {
1499 num_list(
1500 d2_cfg_txt.ok_or("DFlash2 config scope is unavailable")?,
1501 "target_layer_ids",
1502 )
1503 } else {
1504 num_list(&txt, "target_layer_ids")
1505 },
1506 sliding_window,
1507 layer_sliding,
1508 strategy_dspark: dspark_strategy_census(&txt),
1509 is_causal,
1510 };
1511 if cfg.n_layer == 0 || cfg.n_layer > 1_024 {
1512 return Err(format!(
1513 "DFlash num_hidden_layers {} is outside 1..=1024",
1514 cfg.n_layer
1515 )
1516 .into());
1517 }
1518 if cfg.hidden == 0
1519 || cfg.n_head == 0
1520 || cfg.n_kv == 0
1521 || cfg.head_dim == 0
1522 || cfg.n_ff == 0
1523 || !cfg.eps.is_finite()
1524 || cfg.eps <= 0.0
1525 || !cfg.rope_theta.is_finite()
1526 || cfg.rope_theta <= 0.0
1527 {
1528 return Err("DFlash config carries zero or non-finite model geometry".into());
1529 }
1530 validate_dflash_attention_geometry(cfg.n_head, cfg.n_kv, cfg.head_dim)?;
1531 validate_layer_layout(&cfg.layer_sliding, cfg.n_layer)?;
1532 if is_dflash2 {
1533 // The windowed round arm implements the reference's NON-causal symmetric
1534 // window only (config `is_causal: false` on the q38 DFlash2 export). A
1535 // causal DFlash2 variant is a different mask program — refuse it rather
1536 // than run the wrong one fluently.
1537 if cfg.is_causal != Some(false) {
1538 return Err(format!(
1539 "DFlash2 requires explicit is_causal=false; got {:?}",
1540 cfg.is_causal
1541 )
1542 .into());
1543 }
1544 if !cfg.layer_sliding.iter().all(|&sliding| sliding) {
1545 return Err(format!(
1546 "DFlash2 expects all layers sliding_attention; got {:?}",
1547 cfg.layer_sliding
1548 )
1549 .into());
1550 }
1551 if cfg.block_size > cfg.sliding_window {
1552 return Err(format!(
1553 "DFlash2 block {} exceeds sliding window {}",
1554 cfg.block_size, cfg.sliding_window
1555 )
1556 .into());
1557 }
1558 }
1559 let st = memra_gguf::safetensors::StModel::open(&dir.join("model.safetensors"))?;
1560 let validate = |name: &str,
1561 info: &memra_gguf::safetensors::StInfo,
1562 expected: &[u64]|
1563 -> Result<(), Box<dyn std::error::Error>> {
1564 validate_dflash_tensor(name, info, expected).map_err(Into::into)
1565 };
1566 // 1D norm weights ride raw slices; 2D matmul weights ride GpuTensor::Float
1567 // (cuBLASLt f32 arm — the Stage-A numeric class, right for oracle parity).
1568 let up =
1569 |name: &str, expected: &[u64]| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1570 let (info, bytes) = st
1571 .raw(name)
1572 .ok_or_else(|| format!("missing tensor {name}"))?;
1573 validate(name, info, expected)?;
1574 e.htod(&bf16_to_f32(bytes))
1575 };
1576 // Precision policy (MEMRA_DFLASH_PREC seam): "q4" = all q4_0 (DEFAULT since
1577 // lane/dflash2-head-trim 2026-08-25, owner-ratified): measured on BOTH engaging
1578 // card classes at unchanged acceptance — RTX PRO 6000 dspark_q38_gate x3
1579 // interleaved 157.9 vs q8 152.4 spec tok/s (accept 0.662 vs 0.656, ALL EXACT;
1580 // darklanes research/dflash2-pro6000-20260824/prec-ladder + trim cells) and the
1581 // 5090 rig cell that shipped the arm (PR #41). "q8" = all q8_0 (1.6GB, the
1582 // pre-flip default = the rollback seam); "mixed" = bf16 attn+fc (the
1583 // ctx-conditioning path) + q8_0 ffn (~2.2GB — fits the ~2.8GB headroom beside
1584 // the 31B trunk); "bf16" = all bf16 (parity runs, no target). The asymmetric
1585 // "q5" arm was measured DEFECTIVE (acceptance 0.656 -> 0.424) and never landed.
1586 let prec_env = std::env::var("MEMRA_DFLASH_PREC").ok();
1587 let prec = dflash_precision(prec_env.as_deref())?;
1588 let upw = |name: &str, expected: &[u64]| -> Result<GpuTensor, Box<dyn std::error::Error>> {
1589 let (info, bytes) = st
1590 .raw(name)
1591 .ok_or_else(|| format!("missing tensor {name}"))?;
1592 validate(name, info, expected)?;
1593 let shape = info.ne(); // ggml order: ne[0]=in_f, ne[1]=out_f
1594 let in_f = shape[0] as usize;
1595 let is_ffn = name.contains(".mlp.");
1596 let bf16 = prec == "bf16"
1597 || (prec == "mixed" && !is_ffn)
1598 || (prec == "fc" && name == "fc.weight");
1599 if bf16 {
1600 return Ok(GpuTensor::FloatBf16 {
1601 data: e.upload_u8(bytes)?,
1602 ne: shape.to_vec(),
1603 });
1604 }
1605 let f32s = bf16_to_f32(bytes);
1606 if prec == "q4" {
1607 let q = encode_q4_0(&f32s);
1608 return Ok(GpuTensor::Quant {
1609 bytes: e.upload_u8(&q)?,
1610 qtype: crate::QT_Q4_0,
1611 row_bytes: in_f / 32 * 18,
1612 ne: shape.to_vec(),
1613 scale: 1.0,
1614 rp: false,
1615 #[cfg(memra_cutlass)]
1616 cutlass: None,
1617 fp8: None,
1618 blk: None,
1619 rp4: None,
1620 f16: None,
1621 });
1622 }
1623 let q = encode_q8_0(&f32s);
1624 Ok(GpuTensor::Quant {
1625 bytes: e.upload_u8(&q)?,
1626 qtype: crate::QT_Q8_0,
1627 row_bytes: in_f / 32 * 34,
1628 ne: shape.to_vec(),
1629 scale: 1.0,
1630 rp: false,
1631 #[cfg(memra_cutlass)]
1632 cutlass: None,
1633 fp8: None,
1634 blk: None,
1635 rp4: None,
1636 f16: None,
1637 })
1638 };
1639 let hidden = cfg.hidden as u64;
1640 let q_width = cfg
1641 .n_head
1642 .checked_mul(cfg.head_dim)
1643 .ok_or("DFlash q geometry overflow")? as u64;
1644 let kv_width = cfg
1645 .n_kv
1646 .checked_mul(cfg.head_dim)
1647 .ok_or("DFlash kv geometry overflow")? as u64;
1648 let ff = cfg.n_ff as u64;
1649 let head_dim = cfg.head_dim as u64;
1650 let mut layers = Vec::with_capacity(cfg.n_layer);
1651 for i in 0..cfg.n_layer {
1652 let p = |s: &str| format!("layers.{i}.{s}");
1653 layers.push(DflashLayer {
1654 wq: upw(&p("self_attn.q_proj.weight"), &[hidden, q_width])?,
1655 wk: upw(&p("self_attn.k_proj.weight"), &[hidden, kv_width])?,
1656 wv: upw(&p("self_attn.v_proj.weight"), &[hidden, kv_width])?,
1657 wo: upw(&p("self_attn.o_proj.weight"), &[q_width, hidden])?,
1658 w_gate: upw(&p("mlp.gate_proj.weight"), &[hidden, ff])?,
1659 w_up: upw(&p("mlp.up_proj.weight"), &[hidden, ff])?,
1660 w_down: upw(&p("mlp.down_proj.weight"), &[ff, hidden])?,
1661 ln_in: up(&p("input_layernorm.weight"), &[hidden])?,
1662 ln_post: up(&p("post_attention_layernorm.weight"), &[hidden])?,
1663 q_norm: up(&p("self_attn.q_norm.weight"), &[head_dim])?,
1664 k_norm: up(&p("self_attn.k_norm.weight"), &[head_dim])?,
1665 });
1666 }
1667 let markov = if let Some((info, bytes)) = st.raw("markov_head.markov_w1.weight") {
1668 let sh = info.ne(); // [rank, vocab] in ggml order (safetensors [V, rank] reversed)
1669 if info.dtype != "BF16" || sh.len() != 2 {
1670 return Err("markov_head.markov_w1.weight must be rank-2 BF16".into());
1671 }
1672 let (rank, vocab) = (sh[0] as usize, sh[1] as usize);
1673 let (i2, b2) = st
1674 .raw("markov_head.markov_w2.weight")
1675 .ok_or("markov_w2 missing beside markov_w1")?;
1676 validate("markov_head.markov_w2.weight", i2, &sh)?;
1677 // w2 follows the precision seam: bf16 for parity runs (the q8_0 encode is a
1678 // serving-size choice and would put quant error inside the markov-logits gate),
1679 // q8_0 otherwise (acceptance-only impact, like the trunk weights).
1680 let w2 = if prec == "bf16" {
1681 GpuTensor::FloatBf16 {
1682 data: e.upload_u8(b2)?,
1683 ne: i2.ne().to_vec(),
1684 }
1685 } else {
1686 let w2f = bf16_to_f32(b2);
1687 let w2q = encode_q8_0(&w2f);
1688 GpuTensor::Quant {
1689 bytes: e.upload_u8(&w2q)?,
1690 qtype: crate::QT_Q8_0,
1691 row_bytes: rank / 32 * 34,
1692 ne: vec![rank as u64, vocab as u64],
1693 scale: 1.0,
1694 rp: false,
1695 #[cfg(memra_cutlass)]
1696 cutlass: None,
1697 fp8: None,
1698 blk: None,
1699 rp4: None,
1700 f16: None,
1701 }
1702 };
1703 Some(MarkovHead {
1704 w1_bf16: e.upload_u8(bytes)?,
1705 w2,
1706 rank,
1707 vocab,
1708 })
1709 } else {
1710 None
1711 };
1712 let confidence = if let Some((info, bytes)) = st.raw("confidence_head.proj.weight") {
1713 let sh = info.ne(); // ggml order: ne[0]=in_dim, ne[1]=1
1714 if info.dtype != "BF16" || sh.len() != 2 || sh[1] != 1 {
1715 return Err("confidence_head.proj.weight must be BF16 [in_dim, 1]".into());
1716 }
1717 let in_dim = sh[0] as usize;
1718 let (bi, bb) = st
1719 .raw("confidence_head.proj.bias")
1720 .ok_or("confidence bias missing beside weight")?;
1721 validate("confidence_head.proj.bias", bi, &[1])?;
1722 let with_markov = markov
1723 .as_ref()
1724 .map(|m| in_dim == cfg.hidden + m.rank)
1725 .unwrap_or(false);
1726 if !with_markov && in_dim != cfg.hidden {
1727 return Err(format!(
1728 "confidence_head in_dim {in_dim} matches neither hidden {} nor hidden+rank",
1729 cfg.hidden
1730 )
1731 .into());
1732 }
1733 Some(ConfidenceHead {
1734 w: bf16_to_f32(bytes),
1735 b: bf16_to_f32(bb)[0],
1736 in_dim,
1737 with_markov,
1738 })
1739 } else {
1740 None
1741 };
1742 // ---- DFlash2 family tensors (DFLASH2-EVAL-20260820.md §2): 10 conv modules
1743 // (base_kernel + kernel_projection around attention AND mlp in EVERY layer) +
1744 // the candidate path selector (hidden_projection + two codebooks). REQUIRED
1745 // when the arch says DFlash2DraftModel: a missing tensor is a refusal (`?`),
1746 // never a degraded program.
1747 let dflash2 = if is_dflash2 {
1748 if markov.is_some() || confidence.is_some() {
1749 return Err(
1750 "DFlash2 checkpoint carries unsupported markov/confidence tensors".into(),
1751 );
1752 }
1753 let rank = g2("selector_rank")?;
1754 let top_k = g2("selector_top_k")?;
1755 let conv_k = g2("conv_kernel_size")?;
1756 let group_size = g2("conv_group_size")?;
1757 if rank == 0
1758 || top_k == 0
1759 || conv_k == 0
1760 || group_size == 0
1761 || !cfg.hidden.is_multiple_of(group_size)
1762 {
1763 return Err(
1764 "DFlash2 selector/convolution geometry is zero or not divisible".into(),
1765 );
1766 }
1767 let groups = cfg.hidden / group_size;
1768 let load_conv = |name: &str| -> Result<Dflash2Conv, Box<dyn std::error::Error>> {
1769 let (bi, bb) = st
1770 .raw(&format!("{name}.base_kernel"))
1771 .ok_or_else(|| format!("DFlash2 census: missing {name}.base_kernel"))?;
1772 // safetensors [2, k, hidden] -> ggml ne reversed [hidden, k, 2]
1773 validate(
1774 &format!("{name}.base_kernel"),
1775 bi,
1776 &[cfg.hidden as u64, conv_k as u64, 2],
1777 )?;
1778 let pname = format!("{name}.kernel_projection.weight");
1779 let (pi, _pb) = st
1780 .raw(&pname)
1781 .ok_or_else(|| format!("DFlash2 census: missing {pname}"))?;
1782 let projected = 2usize
1783 .checked_mul(conv_k)
1784 .and_then(|value| value.checked_mul(groups))
1785 .ok_or("DFlash2 convolution projection geometry overflow")?;
1786 let expected = [cfg.hidden as u64, projected as u64];
1787 validate(&pname, pi, &expected)?;
1788 Ok(Dflash2Conv {
1789 base: e.htod(&bf16_to_f32(bb))?,
1790 proj: upw(&pname, &expected)?,
1791 })
1792 };
1793 let mut attn_conv = Vec::with_capacity(cfg.n_layer);
1794 let mut mlp_conv = Vec::with_capacity(cfg.n_layer);
1795 for i in 0..cfg.n_layer {
1796 attn_conv.push(load_conv(&format!("layers.{i}.attention_conv"))?);
1797 mlp_conv.push(load_conv(&format!("layers.{i}.mlp_conv"))?);
1798 }
1799 // Codebooks: stored WITHOUT `.weight` (checkpoint quirk; reference
1800 // from_pretrained maps the keys). Host-resident raw bf16.
1801 let cb = |name: &str| -> Result<(Vec<u8>, usize), Box<dyn std::error::Error>> {
1802 let (ci, cbytes) = st
1803 .raw(&format!("candidate_selector.{name}"))
1804 .ok_or_else(|| format!("DFlash2 census: missing candidate_selector.{name}"))?;
1805 let ne = ci.ne(); // ggml: [rank, V]
1806 if ci.dtype != "BF16" || ne.len() != 2 || ne[0] as usize != rank {
1807 return Err(format!(
1808 "candidate_selector.{name} must be rank-2 BF16 with inner rank {rank}; found {:?} {}",
1809 ne, ci.dtype
1810 )
1811 .into());
1812 }
1813 Ok((cbytes.to_vec(), ne[1] as usize))
1814 };
1815 let (pred_codebook, v1) = cb("predecessor_codebook")?;
1816 let (succ_codebook, v2) = cb("successor_codebook")?;
1817 if v1 != v2 {
1818 return Err(format!("DFlash2 codebook vocab mismatch: {v1} != {v2}").into());
1819 }
1820 validate_selector_top_k(top_k, v1)?;
1821 let hp_name = "candidate_selector.hidden_projection.weight";
1822 let (hi, _hb) = st
1823 .raw(hp_name)
1824 .ok_or_else(|| format!("DFlash2 census: missing {hp_name}"))?;
1825 let hp_expected = [cfg.hidden as u64, rank as u64];
1826 validate(hp_name, hi, &hp_expected)?;
1827 Some(Dflash2Head {
1828 attn_conv,
1829 mlp_conv,
1830 hidden_proj: upw(hp_name, &hp_expected)?,
1831 pred_codebook,
1832 succ_codebook,
1833 rank,
1834 top_k,
1835 conv_k,
1836 group_size,
1837 vocab: v1,
1838 })
1839 } else {
1840 None
1841 };
1842 // CENSUS GATE: every tensor in the export must be consumed by the map above.
1843 // DSpark-class checkpoints (markov head present) and DFlash2 checkpoints
1844 // REFUSE on unrecognized names — an unmapped tensor is a semantic program we
1845 // would silently drop (house law). Plain dflash checkpoints keep the
1846 // historical warn-only behavior.
1847 {
1848 let mut consumed: std::collections::HashSet<String> = std::collections::HashSet::new();
1849 for i in 0..cfg.n_layer {
1850 for s in [
1851 "self_attn.q_proj.weight",
1852 "self_attn.k_proj.weight",
1853 "self_attn.v_proj.weight",
1854 "self_attn.o_proj.weight",
1855 "self_attn.q_norm.weight",
1856 "self_attn.k_norm.weight",
1857 "input_layernorm.weight",
1858 "post_attention_layernorm.weight",
1859 "mlp.gate_proj.weight",
1860 "mlp.up_proj.weight",
1861 "mlp.down_proj.weight",
1862 ] {
1863 consumed.insert(format!("layers.{i}.{s}"));
1864 }
1865 if dflash2.is_some() {
1866 for s in [
1867 "attention_conv.base_kernel",
1868 "attention_conv.kernel_projection.weight",
1869 "mlp_conv.base_kernel",
1870 "mlp_conv.kernel_projection.weight",
1871 ] {
1872 consumed.insert(format!("layers.{i}.{s}"));
1873 }
1874 }
1875 }
1876 for s in [
1877 "fc.weight",
1878 "hidden_norm.weight",
1879 "norm.weight",
1880 "markov_head.markov_w1.weight",
1881 "markov_head.markov_w2.weight",
1882 "confidence_head.proj.weight",
1883 "confidence_head.proj.bias",
1884 ] {
1885 consumed.insert(s.into());
1886 }
1887 if dflash2.is_some() {
1888 for s in [
1889 "candidate_selector.hidden_projection.weight",
1890 "candidate_selector.predecessor_codebook",
1891 "candidate_selector.successor_codebook",
1892 ] {
1893 consumed.insert(s.into());
1894 }
1895 }
1896 let leftovers: Vec<&String> = st.names().filter(|n| !consumed.contains(*n)).collect();
1897 if !leftovers.is_empty() {
1898 if markov.is_some() || dflash2.is_some() {
1899 return Err(format!(
1900 "dspark/dflash2 census: unrecognized tensors {leftovers:?}"
1901 )
1902 .into());
1903 }
1904 eprintln!("[dflash census] unmapped tensors (ignored): {leftovers:?}");
1905 }
1906 }
1907 // YaRN rope from config rope_parameters (HF _compute_yarn_parameters, verified
1908 // numerically vs Qwen3RotaryEmbedding on the arm-a export).
1909 let rope_yarn =
1910 if txt.contains("\"rope_type\": \"yarn\"") || txt.contains("\"rope_type\":\"yarn\"") {
1911 let factor = num(&txt, "factor").ok_or("yarn missing factor")?;
1912 let orig = num(&txt, "original_max_position_embeddings")
1913 .ok_or("yarn missing original_max_position_embeddings")?;
1914 let beta_fast = num(&txt, "beta_fast").ok_or("yarn missing beta_fast")?;
1915 let beta_slow = num(&txt, "beta_slow").ok_or("yarn missing beta_slow")?;
1916 if !factor.is_finite()
1917 || factor <= 0.0
1918 || !orig.is_finite()
1919 || orig <= 0.0
1920 || !beta_fast.is_finite()
1921 || !beta_slow.is_finite()
1922 {
1923 return Err("yarn parameters must be finite and positive".into());
1924 }
1925 let base = cfg.rope_theta as f64;
1926 let d = cfg.head_dim as f64;
1927 let corr =
1928 |r: f64| d * (orig / (r * 2.0 * std::f64::consts::PI)).ln() / (2.0 * base.ln());
1929 let low = corr(beta_fast).floor().max(0.0);
1930 let high = corr(beta_slow).ceil().min(d - 1.0);
1931 let half = cfg.head_dim / 2;
1932 let mut ff = Vec::with_capacity(half);
1933 for j in 0..half {
1934 let base_inv = base.powf(-2.0 * j as f64 / d);
1935 let ramp = (((j as f64) - low) / (high - low)).clamp(0.0, 1.0);
1936 let ex = 1.0 - ramp; // extrapolation share
1937 let yarn_inv = (base_inv / factor) * (1.0 - ex) + base_inv * ex;
1938 ff.push((base_inv / yarn_inv) as f32);
1939 }
1940 let mscale = (0.1 * factor.ln() + 1.0) as f32;
1941 Some((e.htod(&ff)?, mscale))
1942 } else {
1943 None
1944 };
1945 let fc_in = cfg
1946 .target_layer_ids
1947 .len()
1948 .checked_mul(cfg.hidden)
1949 .ok_or("DFlash fc geometry overflow")? as u64;
1950 let fc = upw("fc.weight", &[fc_in, hidden])?;
1951 // Ratified-default receipts (capacity-keyed-defaults law: the active program is
1952 // NAMED at load, never inferred from silence). The boot output-sample gate greps
1953 // these lines; a run whose log lacks them did not load this code.
1954 eprintln!(
1955 "[dspark] precision={prec} (MEMRA_DFLASH_PREC {})",
1956 if prec_env.is_some() { "set" } else { "unset" },
1957 );
1958 eprintln!(
1959 "[dspark] harvest={} (checkpoint census dflash2={} strategy_dspark={}, \
1960 MEMRA_DSPARK_HARVEST {})",
1961 DsparkHarvest::for_family_value(
1962 dflash2.is_some(),
1963 std::env::var("MEMRA_DSPARK_HARVEST").ok().as_deref(),
1964 cfg.strategy_dspark,
1965 )
1966 .name(),
1967 dflash2.is_some(),
1968 cfg.strategy_dspark,
1969 match std::env::var("MEMRA_DSPARK_HARVEST") {
1970 Ok(v) if !v.is_empty() => "set",
1971 _ => "unset",
1972 },
1973 );
1974 eprintln!(
1975 "[dspark] verify-window={:?} (accept-rate head {}, MEMRA_DSPARK_VT {})",
1976 DsparkVtPolicy::resolve(confidence.is_some()),
1977 if confidence.is_some() {
1978 "present"
1979 } else {
1980 "ABSENT -> ladder"
1981 },
1982 match std::env::var("MEMRA_DSPARK_VT") {
1983 Ok(v) if !v.is_empty() => "set",
1984 _ => "unset",
1985 },
1986 );
1987 Ok(Self {
1988 fc,
1989 hidden_norm: up("hidden_norm.weight", &[hidden])?,
1990 norm: up("norm.weight", &[hidden])?,
1991 cfg,
1992 layers,
1993 markov,
1994 confidence,
1995 rope_yarn,
1996 dflash2,
1997 })
1998 }
1999
2000 /// Rope q or k rows in place: yarn (ff divisors + post-rope mscale) when the config
2001 /// carries it, plain neox otherwise. One primitive for all five drafter rope sites.
2002 fn rope_rows(
2003 &self,
2004 e: &Engine,
2005 x: &mut CudaSlice<f32>,
2006 pos_d: &CudaSlice<i32>,
2007 n_heads: usize,
2008 n_tokens: usize,
2009 ) -> Result<(), Box<dyn std::error::Error>> {
2010 let c = &self.cfg;
2011 match &self.rope_yarn {
2012 Some((ff, mscale)) => {
2013 e.rope_neox_ff(
2014 x,
2015 pos_d,
2016 c.head_dim,
2017 c.head_dim,
2018 n_heads,
2019 n_tokens,
2020 c.rope_theta,
2021 1.0,
2022 ff,
2023 )?;
2024 e.scale_inplace(x, *mscale, n_tokens * n_heads * c.head_dim)?;
2025 }
2026 None => {
2027 e.rope_neox(
2028 x,
2029 pos_d,
2030 c.head_dim,
2031 c.head_dim,
2032 n_heads,
2033 n_tokens,
2034 c.rope_theta,
2035 1.0,
2036 )?;
2037 }
2038 }
2039 Ok(())
2040 }
2041
2042 /// f32 GEMM helper via the engine Float arm (cuBLASLt): y[t, out_f].
2043 fn mm(
2044 &self,
2045 e: &Engine,
2046 w: &GpuTensor,
2047 x: &CudaSlice<f32>,
2048 t: usize,
2049 _in_f: usize,
2050 _out_f: usize,
2051 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2052 e.matmul(w, x, t)
2053 }
2054
2055 /// DFlash2 conv `prepare` (reference GroupedDynamicCausalConv.prepare): projects
2056 /// the pre-conv rows to BOTH dynamic kernels, convolves the rows with base half 0
2057 /// + dyn half 0, and returns (convolved rows, the dyn projection) — `finish`
2058 /// reuses the SAME projection's half 1. Block-local causal shift (row 0 zero-pads).
2059 pub fn d2_conv_prepare(
2060 &self,
2061 e: &Engine,
2062 conv: &Dflash2Conv,
2063 xn: &CudaSlice<f32>,
2064 rows: usize,
2065 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
2066 let d2 = self
2067 .dflash2
2068 .as_ref()
2069 .expect("d2_conv on a non-dflash2 draft");
2070 let h = self.cfg.hidden;
2071 let groups = h / d2.group_size;
2072 let dyn_ = self.mm(e, &conv.proj, xn, rows, h, 2 * d2.conv_k * groups)?;
2073 let mut out = e.uninit(rows * h)?;
2074 e.dflash2_dynconv(
2075 xn,
2076 &dyn_,
2077 &conv.base,
2078 &mut out,
2079 rows,
2080 h,
2081 d2.group_size,
2082 d2.conv_k,
2083 0,
2084 )?;
2085 Ok((out, dyn_))
2086 }
2087
2088 /// DFlash2 conv `finish`: convolves the sublayer OUTPUT rows with base half 1 +
2089 /// dyn half 1 (dyn from the matching `prepare`).
2090 pub fn d2_conv_finish(
2091 &self,
2092 e: &Engine,
2093 conv: &Dflash2Conv,
2094 y: &CudaSlice<f32>,
2095 dyn_: &CudaSlice<f32>,
2096 rows: usize,
2097 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2098 let d2 = self
2099 .dflash2
2100 .as_ref()
2101 .expect("d2_conv on a non-dflash2 draft");
2102 let h = self.cfg.hidden;
2103 let mut out = e.uninit(rows * h)?;
2104 e.dflash2_dynconv(
2105 y,
2106 dyn_,
2107 &conv.base,
2108 &mut out,
2109 rows,
2110 h,
2111 d2.group_size,
2112 d2.conv_k,
2113 1,
2114 )?;
2115 Ok(out)
2116 }
2117
2118 /// DFlash2 proposal (reference `DFlash2DraftModel.propose`, greedy arm): device
2119 /// top-k over the draft logits + the rank-`r` hidden projection, ONE small dtoh
2120 /// (~nd*(2k+rank) floats — the same per-round sync slot the markov chain's token
2121 /// readback occupies), then the host codebook walk. Returns the nd drafted tokens
2122 /// (mask-fill rows 1..b-1; the anchor row is not a draft).
2123 #[allow(clippy::too_many_arguments)] // allow: the parameter list mirrors the kernel/FFI/call contract; bundling into a struct is a refactor, not a lint fix
2124 pub fn dflash2_propose_greedy(
2125 &self,
2126 e: &Engine,
2127 dl: &CudaSlice<f32>,
2128 rows: &CudaSlice<f32>,
2129 nd: usize,
2130 n_vocab: usize,
2131 anchor: u32,
2132 d2t: Option<&[u32]>,
2133 ) -> Result<Vec<u32>, Box<dyn std::error::Error>> {
2134 Ok(self
2135 .dflash2_propose_greedy_q(e, dl, rows, nd, n_vocab, anchor, d2t)?
2136 .0)
2137 }
2138
2139 /// [`Self::dflash2_propose_greedy`] with the walk's per-slot confidence returned
2140 /// (lane/glm5-loop-port, 2026-08-30): q[p] = the chosen candidate's softmax mass over
2141 /// its slot's candidate set at T=1 — the statistic the glm5 loop's MEMRA_SPEC_PMIN
2142 /// tau-slot truncation thresholds on. Same walk, same path, same one-DtoH sync slot.
2143 #[allow(clippy::too_many_arguments)]
2144 // allow: mirrors the greedy propose contract it wraps
2145 pub fn dflash2_propose_greedy_q(
2146 &self,
2147 e: &Engine,
2148 dl: &CudaSlice<f32>,
2149 rows: &CudaSlice<f32>,
2150 nd: usize,
2151 n_vocab: usize,
2152 anchor: u32,
2153 d2t: Option<&[u32]>,
2154 ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
2155 let d2 = self
2156 .dflash2
2157 .as_ref()
2158 .expect("dflash2_propose on a non-dflash2 draft");
2159 if n_vocab > d2.vocab || d2.top_k > n_vocab {
2160 return Err(format!(
2161 "DFlash2 proposal geometry invalid: target vocab {n_vocab}, selector vocab {}, top_k {}",
2162 d2.vocab, d2.top_k
2163 )
2164 .into());
2165 }
2166 if let Some(map) = d2t
2167 && map.len() < n_vocab
2168 {
2169 return Err(format!(
2170 "DFlash2 d2t has {} entries, fewer than proposal vocab {n_vocab}",
2171 map.len()
2172 )
2173 .into());
2174 }
2175 let (vals_d, idx_d) = e.topk_rows(dl, nd, n_vocab, d2.top_k)?;
2176 let hproj_d = e.matmul(&d2.hidden_proj, rows, nd)?;
2177 let unary = e.dtoh(&vals_d)?;
2178 let mut cand = e.dtoh_u32(&idx_d)?;
2179 // memra#95: refuse a selector row the top-k could not fill, BEFORE the d2t remap.
2180 dflash2_guard_candidates(&cand, n_vocab, "dflash2 greedy proposal")?;
2181 // TRIMMED draft head (lane/dflash2-head-trim, 2026-08-25): `dl` was scored over the
2182 // FR-Spec-gathered rows, so candidate index i names trimmed row i — remap to the true
2183 // token id BEFORE the selector walk (the codebooks and the verify block index the full
2184 // vocabulary). Same permute-the-proposal law as the MTP arm's spec.rs d2t map; verify
2185 // stays full-vocab, so the trim moves acceptance only, never output.
2186 if let Some(map) = d2t {
2187 for c in cand.iter_mut() {
2188 *c = map[*c as usize];
2189 }
2190 }
2191 let hproj = e.dtoh(&hproj_d)?;
2192 Ok(d2.walk_greedy_q(&unary, &cand, &hproj, anchor, nd))
2193 }
2194
2195 /// DFlash2 proposal, SAMPLED arm (reference `DFlash2DraftModel.propose` at T>0): same
2196 /// device top-k + hidden projection + one dtoh as the greedy arm, then the host
2197 /// candidate-set softmax walk (`dflash2_walk_sampled`) drawing one host-Philox uniform
2198 /// per slot from the session's `uctr` stream. Returns (path, q_chosen, cand, q_rows).
2199 #[allow(clippy::too_many_arguments)]
2200 pub(crate) fn dflash2_propose_sampled(
2201 &self,
2202 e: &Engine,
2203 dl: &CudaSlice<f32>,
2204 rows: &CudaSlice<f32>,
2205 nd: usize,
2206 n_vocab: usize,
2207 anchor: u32,
2208 temp: f32,
2209 seed: u64,
2210 uctr: &mut u32,
2211 d2t: Option<&[u32]>,
2212 ) -> Result<Dflash2SampledProposal, Box<dyn std::error::Error>> {
2213 let d2 = self
2214 .dflash2
2215 .as_ref()
2216 .expect("dflash2_propose on a non-dflash2 draft");
2217 if n_vocab > d2.vocab || d2.top_k > n_vocab {
2218 return Err(format!(
2219 "DFlash2 proposal geometry invalid: target vocab {n_vocab}, selector vocab {}, top_k {}",
2220 d2.vocab, d2.top_k
2221 )
2222 .into());
2223 }
2224 if let Some(map) = d2t
2225 && map.len() < n_vocab
2226 {
2227 return Err(format!(
2228 "DFlash2 d2t has {} entries, fewer than proposal vocab {n_vocab}",
2229 map.len()
2230 )
2231 .into());
2232 }
2233 let (vals_d, idx_d) = e.topk_rows(dl, nd, n_vocab, d2.top_k)?;
2234 let hproj_d = e.matmul(&d2.hidden_proj, rows, nd)?;
2235 let unary = e.dtoh(&vals_d)?;
2236 let mut cand = e.dtoh_u32(&idx_d)?;
2237 // memra#95: refuse a selector row the top-k could not fill, BEFORE the d2t remap.
2238 dflash2_guard_candidates(&cand, n_vocab, "dflash2 sampled proposal")?;
2239 // Trimmed-head remap — see the greedy arm. The q the walk reports is the softmax
2240 // over the candidate SET it actually proposed (ids are labels, not indices into a
2241 // distribution), so the rejection-verify contract is unchanged by the remap.
2242 if let Some(map) = d2t {
2243 for c in cand.iter_mut() {
2244 *c = map[*c as usize];
2245 }
2246 }
2247 let hproj = e.dtoh(&hproj_d)?;
2248 let mut draw = || {
2249 let u = crate::spec::host_u01(seed, *uctr);
2250 *uctr = uctr.wrapping_add(1);
2251 u
2252 };
2253 let (path, q_chosen, q_rows) =
2254 d2.walk_sampled(&unary, &cand, &hproj, anchor, nd, temp, &mut draw);
2255 Ok((path, q_chosen, cand, q_rows))
2256 }
2257
2258 /// Sampled draft chain for the Rows families (T>0 twin of the greedy markov chain):
2259 /// slot k gets the markov bias of the PREVIOUS chain token added in place (when the
2260 /// head is armed — the sglang DSPARK worker's markov-corrected draft probs), then ONE
2261 /// draw from the row's FILTERED softmax (filter_stats -> device-stat gumbel perturb ->
2262 /// argmax into the chain buffer — the frspec eager-chain composition, stats kept on
2263 /// device so the chain stays sync-free like the greedy arm). Without a markov head the
2264 /// rows sample independently (the z-lab reference's T>0 arm for plain DFlash). `dl` is
2265 /// biased IN PLACE and retained by the caller: it is the accept walk's q source.
2266 #[allow(clippy::too_many_arguments)]
2267 pub(crate) fn dspark_chain_sampled(
2268 &self,
2269 e: &Engine,
2270 dl: &mut CudaSlice<f32>,
2271 nd: usize,
2272 n_vocab: usize,
2273 anchor: u32,
2274 sp: &crate::spec::SpecSampling,
2275 sctr: &mut u32,
2276 // H4 confidence-policy stash (v0.100 train merge): Some = copy each slot's
2277 // markov prev-token embedding (the exact `w1` row the chain gathers) into a
2278 // [nd, rank] buffer — the same d2d stash the greedy chain carries, so the
2279 // confidence window sizes identically at T>0.
2280 mut conf_emb: Option<&mut CudaSlice<f32>>,
2281 ) -> Result<(Vec<u32>, DsparkDraftSample), Box<dyn std::error::Error>> {
2282 let markov_on = std::env::var("MEMRA_DFLASH_MARKOV").as_deref() != Ok("0");
2283 let mut chain_d = e.stream().alloc_zeros::<u32>(nd + 1)?;
2284 e.set_u32_one(&mut chain_d, anchor)?;
2285 let mut th_all = e.zeros(nd)?;
2286 let mut z_all = e.zeros(nd)?;
2287 let mut mx_all = e.zeros(nd)?;
2288 let mut pb = e.zeros(n_vocab)?;
2289 for k in 0..nd {
2290 if let (Some(mk), true) = (&self.markov, markov_on) {
2291 let mut f = e.uninit(mk.rank)?;
2292 e.gather_row_bf16(&mk.w1_bf16, &chain_d, k, &mut f, mk.rank)?;
2293 if let Some(ce) = conf_emb.as_deref_mut() {
2294 let fv = e.view(&f, mk.rank);
2295 e.copy_view_into(ce, k * mk.rank, &fv, mk.rank)?;
2296 }
2297 let bias = e.matmul(&mk.w2, &f, 1)?;
2298 e.add_row_inplace(dl, &bias, n_vocab, k * n_vocab)?;
2299 } else if let (Some(ce), Some(mk)) = (conf_emb.as_deref_mut(), &self.markov) {
2300 // MARKOV=0 arm still stashes the embedding for the confidence head —
2301 // the greedy chain's exact behavior.
2302 let mut f = e.uninit(mk.rank)?;
2303 e.gather_row_bf16(&mk.w1_bf16, &chain_d, k, &mut f, mk.rank)?;
2304 let fv = e.view(&f, mk.rank);
2305 e.copy_view_into(ce, k * mk.rank, &fv, mk.rank)?;
2306 }
2307 let rows_k = e.htod_i32(&[k as i32])?;
2308 let (mut th1, mut z1, mut mx1) = (e.zeros(1)?, e.zeros(1)?, e.zeros(1)?);
2309 e.filter_stats(
2310 dl, n_vocab, &rows_k, &mut th1, &mut z1, &mut mx1, n_vocab, 1, sp.temp, sp.top_k,
2311 sp.top_p, sp.min_p,
2312 )?;
2313 e.gumbel_perturb_filtered_col(
2314 dl, k, &mut pb, n_vocab, sp.seed, *sctr, sp.temp, &mx1, &th1, 0,
2315 )?;
2316 *sctr = sctr.wrapping_add(1);
2317 e.argmax_token_device_col(&pb, 0, n_vocab, &mut chain_d, k + 1)?;
2318 e.copy_into(&mut th_all, k, &th1, 1)?;
2319 e.copy_into(&mut z_all, k, &z1, 1)?;
2320 e.copy_into(&mut mx_all, k, &mx1, 1)?;
2321 }
2322 let chain = e.dtoh_u32(&chain_d)?;
2323 let (thv, zv, mxv) = (e.dtoh(&th_all)?, e.dtoh(&z_all)?, e.dtoh(&mx_all)?);
2324 let stats = (0..nd).map(|i| (mxv[i], thv[i], zv[i])).collect();
2325 Ok((
2326 chain[1..].to_vec(),
2327 DsparkDraftSample::Rows {
2328 th: th_all,
2329 z: z_all,
2330 stats,
2331 },
2332 ))
2333 }
2334
2335 /// Family dispatch for the sampled proposal: Selector for DFlash2, Rows otherwise.
2336 /// Returns the drafted tokens (the round's `cand` tail) + the proposal record.
2337 #[allow(clippy::too_many_arguments)]
2338 pub(crate) fn dspark_propose_sampled(
2339 &self,
2340 e: &Engine,
2341 dl: &mut CudaSlice<f32>,
2342 rows: &CudaSlice<f32>,
2343 nd: usize,
2344 n_vocab: usize,
2345 anchor: u32,
2346 sp: &crate::spec::SpecSampling,
2347 sctr: &mut u32,
2348 uctr: &mut u32,
2349 conf_emb: Option<&mut CudaSlice<f32>>,
2350 d2t: Option<&[u32]>,
2351 ) -> Result<(Vec<u32>, DsparkDraftSample), Box<dyn std::error::Error>> {
2352 if let Some(d2) = self.dflash2.as_ref() {
2353 // The confidence stash is a markov-family program; DFlash2 has no
2354 // accept-rate head (the policy resolver never arms it for this family).
2355 debug_assert!(
2356 conf_emb.is_none(),
2357 "conf_emb stash requested on a DFlash2 selector proposal"
2358 );
2359 let (path, q_chosen, cand, q_rows) = self.dflash2_propose_sampled(
2360 e, dl, rows, nd, n_vocab, anchor, sp.temp, sp.seed, uctr, d2t,
2361 )?;
2362 Ok((
2363 path,
2364 DsparkDraftSample::Selector {
2365 cand,
2366 q_rows,
2367 q_chosen,
2368 top_k: d2.top_k,
2369 },
2370 ))
2371 } else {
2372 self.dspark_chain_sampled(e, dl, nd, n_vocab, anchor, sp, sctr, conf_emb)
2373 }
2374 }
2375
2376 /// FIRST-LIGHT forward (oracle contract): full non-causal attention over
2377 /// [ctx_features ; block], NO draft KV cache, NO sliding window (the oracle bypasses
2378 /// the reference mask machinery the same way — window/caching land in the round arm).
2379 ///
2380 /// `target_hidden`: [ctx, n_taps*hidden] (f32, device) — raw tapped states.
2381 /// `noise_emb`: [block, hidden] — target embed rows for [accepted, MASK x b-1].
2382 /// `pos`: absolute positions for ctx rows THEN block rows (ctx+block i32).
2383 /// Returns final normed hidden [block, hidden] (feed target lm_head for draft logits).
2384 /// ctx features for `t` tapped rows: hidden_norm(fc(taps)) — the drafter's context
2385 /// representation, cacheable across rounds (append-only in committed-token order).
2386 pub fn ctx_features(
2387 &self,
2388 e: &Engine,
2389 taps: &CudaSlice<f32>,
2390 t: usize,
2391 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2392 let c = &self.cfg;
2393 let n_taps = c.target_layer_ids.len();
2394 let fc_out = self.mm(e, &self.fc, taps, t, n_taps * c.hidden, c.hidden)?;
2395 let mut out = e.uninit(t * c.hidden)?;
2396 e.rms_norm(&fc_out, &self.hidden_norm, &mut out, c.hidden, t, c.eps)?;
2397 Ok(out)
2398 }
2399
2400 pub fn forward(
2401 &self,
2402 e: &Engine,
2403 target_hidden: &CudaSlice<f32>,
2404 noise_emb: &CudaSlice<f32>,
2405 pos: &[i32],
2406 ctx: usize,
2407 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2408 let ctx_f = self.ctx_features(e, target_hidden, ctx)?;
2409 if let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP") {
2410 let v = e.dtoh(&ctx_f)?;
2411 let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
2412 std::fs::write(format!("{dir}/memra-ctx_features.f32"), bytes)?;
2413 }
2414 self.forward_block(e, &ctx_f, noise_emb, pos, ctx)
2415 }
2416
2417 /// Block forward over PRECOMPUTED ctx features (the round arm's entry: features are
2418 /// cached across rounds; only the block work repeats).
2419 pub fn forward_block(
2420 &self,
2421 e: &Engine,
2422 ctx_f: &CudaSlice<f32>,
2423 noise_emb: &CudaSlice<f32>,
2424 pos: &[i32],
2425 ctx: usize,
2426 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2427 let c = &self.cfg;
2428 let (h, nh, nkv, hd) = (c.hidden, c.n_head, c.n_kv, c.head_dim);
2429 let b = c.block_size;
2430 assert_eq!(pos.len(), ctx + b, "pos covers ctx rows then block rows");
2431
2432 let pos_blk = e.htod_i32(&pos[ctx..])?;
2433
2434 let mut x = e.clone_dtod(noise_emb)?; // [b, hidden] residual stream
2435 for (li, l) in self.layers.iter().enumerate() {
2436 // input_layernorm on the block rows only (ctx features are norm-free per ref:
2437 // k/v project the SAME ctx_f every layer, un-layernormed).
2438 let mut xn = e.uninit(b * h)?;
2439 e.rms_norm(&x, &l.ln_in, &mut xn, h, b, c.eps)?;
2440 // DFlash2: dynamic conv WRAPS attention — q/k_noise/v_noise all project the
2441 // CONVOLVED block rows (reference decoder layer: prepare -> self_attn ->
2442 // finish, all inside the residual branch). ctx_f is never convolved.
2443 let mut attn_dyn: Option<CudaSlice<f32>> = None;
2444 if let Some(d2) = &self.dflash2 {
2445 let (xc, dyn_) = self.d2_conv_prepare(e, &d2.attn_conv[li], &xn, b)?;
2446 xn = xc;
2447 attn_dyn = Some(dyn_);
2448 }
2449
2450 // q from block; k/v from [ctx_f ; block-normed]
2451 let q0 = self.mm(e, &l.wq, &xn, b, h, nh * hd)?;
2452 let k0c = self.mm(e, &l.wk, ctx_f, ctx, h, nkv * hd)?;
2453 let v0c = self.mm(e, &l.wv, ctx_f, ctx, h, nkv * hd)?;
2454 let k0b = self.mm(e, &l.wk, &xn, b, h, nkv * hd)?;
2455 let v0b = self.mm(e, &l.wv, &xn, b, h, nkv * hd)?;
2456
2457 // per-head q/k rms norm (v passes through: ones weight trick not needed — the
2458 // qkv kernel norms rq+rk rows; concatenate k first).
2459 let mut k0 = e.uninit((ctx + b) * nkv * hd)?;
2460 e.copy_into(&mut k0, 0, &k0c, ctx * nkv * hd)?;
2461 e.copy_into(&mut k0, ctx * nkv * hd, &k0b, b * nkv * hd)?;
2462 let mut v = e.uninit((ctx + b) * nkv * hd)?;
2463 e.copy_into(&mut v, 0, &v0c, ctx * nkv * hd)?;
2464 e.copy_into(&mut v, ctx * nkv * hd, &v0b, b * nkv * hd)?;
2465
2466 if li == 0
2467 && let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP")
2468 {
2469 let v = e.dtoh(&q0)?;
2470 let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
2471 std::fs::write(format!("{dir}/memra-l0_q0.f32"), bytes)?;
2472 }
2473 let mut q = e.uninit(b * nh * hd)?;
2474 let mut k = e.uninit((ctx + b) * nkv * hd)?;
2475 // rms over head_dim rows: q has b*nh rows, k has (ctx+b)*nkv rows.
2476 e.rms_norm(&q0, &l.q_norm, &mut q, hd, b * nh, c.eps)?;
2477 if li == 0
2478 && let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP")
2479 {
2480 let v = e.dtoh(&q)?;
2481 let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
2482 std::fs::write(format!("{dir}/memra-l0_qn.f32"), bytes)?;
2483 }
2484 e.rms_norm(&k0, &l.k_norm, &mut k, hd, (ctx + b) * nkv, c.eps)?;
2485
2486 // rope: q at block positions, k at ctx-then-block positions (absolute).
2487 let norope = std::env::var("MEMRA_DFLASH_NOROPE").is_ok();
2488 if !norope {
2489 self.rope_rows(e, &mut q, &pos_blk, nh, b)?;
2490 }
2491 if li == 0
2492 && let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP")
2493 {
2494 let dump = |name: &str,
2495 t: &cudarc::driver::CudaSlice<f32>|
2496 -> Result<(), Box<dyn std::error::Error>> {
2497 let v = e.dtoh(t)?;
2498 let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
2499 std::fs::write(format!("{dir}/memra-l0_{name}.f32"), bytes)?;
2500 Ok(())
2501 };
2502 dump("xn", &xn)?;
2503 dump("q_prerope", &q)?;
2504 }
2505 // k rows are laid out [row, nkv, hd] with row-major tokens — rope_neox expects
2506 // (n_heads, n_tokens); ctx and block ropes run as one call over ctx+b tokens.
2507 let pos_all = e.htod_i32(pos)?;
2508 if !norope {
2509 self.rope_rows(e, &mut k, &pos_all, nkv, ctx + b)?;
2510 }
2511
2512 // full non-causal attention: every block query sees all ctx+b keys.
2513 let mut attn = e.uninit(b * nh * hd)?;
2514 let scale = 1.0f32 / (hd as f32).sqrt();
2515 // NAIVE SDPA for first light: fa_prefill's NON-CAUSAL arm with T != T_kv is
2516 // BROKEN (attn maxdiff 0.34 vs the torch oracle; q/k inputs bit-close — no
2517 // existing caller exercises that shape class, jsonl 2026-07-13). The 16 x
2518 // (ctx+16) block attention is tiny; the fa arm returns behind this seam once
2519 // its kernel is fixed + parity-gated.
2520 if self.dflash2.is_some() && c.layer_sliding[li] {
2521 // DFlash2 non-causal symmetric window (config is_causal=false, all
2522 // layers sliding). The kernel masks only keys OLDER than
2523 // q_pos-(window-1); the future side (k - q < window) never binds
2524 // because keys reach at most q_pos + block <= q_pos + window
2525 // (asserted at load). Positions must be contiguous — q_pos is derived
2526 // in-kernel as (T_kv - T) + qt.
2527 debug_assert!(pos.windows(2).all(|w| w[1] == w[0] + 1));
2528 d2_windowed_attn(
2529 e,
2530 &q,
2531 &k,
2532 &v,
2533 &mut attn,
2534 hd,
2535 nh,
2536 nkv,
2537 b,
2538 ctx + b,
2539 scale,
2540 c,
2541 0,
2542 )?;
2543 } else if std::env::var("MEMRA_DFLASH_FA").is_ok() {
2544 e.fa_prefill(&q, &k, &v, &mut attn, hd, nh, nkv, b, ctx + b, scale, false)?;
2545 } else {
2546 e.sdpa_naive(&q, &k, &v, &mut attn, hd, nh, nkv, b, ctx + b, scale, false)?;
2547 }
2548
2549 let mut o = self.mm(e, &l.wo, &attn, b, nh * hd, h)?;
2550 if let (Some(d2), Some(dyn_)) = (&self.dflash2, &attn_dyn) {
2551 o = self.d2_conv_finish(e, &d2.attn_conv[li], &o, dyn_, b)?;
2552 }
2553 let mut x1 = e.uninit(b * h)?;
2554 e.add(&o, &x, &mut x1, b * h)?;
2555 if li == 0
2556 && let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP")
2557 {
2558 let dump = |name: &str,
2559 t: &cudarc::driver::CudaSlice<f32>|
2560 -> Result<(), Box<dyn std::error::Error>> {
2561 let v = e.dtoh(t)?;
2562 let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
2563 std::fs::write(format!("{dir}/memra-l0_{name}.f32"), bytes)?;
2564 Ok(())
2565 };
2566 dump("q", &q)?;
2567 dump("k", &k)?;
2568 dump("attn", &attn)?;
2569 dump("x1", &x1)?;
2570 }
2571
2572 // mlp (DFlash2: the same conv wrap — prepare on the post-ln rows, mlp on
2573 // the convolved rows, finish on the mlp output, then the residual add)
2574 let mut x1n = e.uninit(b * h)?;
2575 e.rms_norm(&x1, &l.ln_post, &mut x1n, h, b, c.eps)?;
2576 let mut mlp_dyn: Option<CudaSlice<f32>> = None;
2577 if let Some(d2) = &self.dflash2 {
2578 let (xc, dyn_) = self.d2_conv_prepare(e, &d2.mlp_conv[li], &x1n, b)?;
2579 x1n = xc;
2580 mlp_dyn = Some(dyn_);
2581 }
2582 let gate = self.mm(e, &l.w_gate, &x1n, b, h, c.n_ff)?;
2583 let up_ = self.mm(e, &l.w_up, &x1n, b, h, c.n_ff)?;
2584 let mut act = e.uninit(b * c.n_ff)?;
2585 e.silu_mul(&gate, &up_, &mut act, b * c.n_ff)?;
2586 let mut down = self.mm(e, &l.w_down, &act, b, c.n_ff, h)?;
2587 if let (Some(d2), Some(dyn_)) = (&self.dflash2, &mlp_dyn) {
2588 down = self.d2_conv_finish(e, &d2.mlp_conv[li], &down, dyn_, b)?;
2589 }
2590 let mut x2 = e.uninit(b * h)?;
2591 e.add(&down, &x1, &mut x2, b * h)?;
2592 x = x2;
2593 if let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP") {
2594 let v = e.dtoh(&x)?;
2595 let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
2596 std::fs::write(format!("{dir}/memra-layer{li}_out.f32"), bytes)?;
2597 }
2598 }
2599 let mut out = e.uninit(b * h)?;
2600 e.rms_norm(&x, &self.norm, &mut out, h, b, c.eps)?;
2601 Ok(out)
2602 }
2603}
2604
2605/// Draft KV cache (round-cost fix, 2026-07-13): per-layer normed+roped ctx K and raw ctx V,
2606/// append-only in committed order. Block K/V land TRANSIENTLY at [len..len+b] each round
2607/// (never committed — the reference crops them identically). Kills the per-round full-ctx
2608/// projection recompute (first light was O(ctx)/round -> 7 tok/s).
2609pub struct DflashKv {
2610 pub k: Vec<CudaSlice<f32>>, // per layer [cap + block, nkv*hd]
2611 pub v: Vec<CudaSlice<f32>>,
2612 pub len: usize,
2613 pub cap: usize,
2614 /// Trailing rows the drafter can still observe: `sliding_window + block_size`. Carried on
2615 /// the KV (not recomputed at call sites) so an export and an import cannot disagree about
2616 /// the geometry — see `DsparkSpecSession::draft_tail_rows`.
2617 window_rows: usize,
2618 /// `n_kv * head_dim * size_of::<f32>()` — the row unit for tail copies.
2619 row_bytes: usize,
2620 /// CONTEXT FLOOR (lane/spec-exclusions-20260902): the first ctx row that EXISTS. `0` on
2621 /// every cold-primed KV and every full-tail import (the pre-lane shape). A COLD-DRAFTER
2622 /// KV (`new_cold_at`) or a short-tail import (`from_tail` of a tail whose exporter had
2623 /// a floor) owns rows only from here up; the round attention's window floor is raised
2624 /// to it (`d2_windowed_attn`), so the rows below are never read, and an export never
2625 /// publishes them (`export_tail` starts at the floor). The drafter then simply sees a
2626 /// shorter context, which can move ACCEPTANCE, never output — verify arbitrates.
2627 floor: usize,
2628}
2629
2630impl DflashKv {
2631 pub fn new(
2632 e: &Engine,
2633 cfg: &DflashCfg,
2634 cap: usize,
2635 ) -> Result<Self, Box<dyn std::error::Error>> {
2636 let rowsz = cfg.n_kv * cfg.head_dim;
2637 let mut k = Vec::with_capacity(cfg.n_layer);
2638 let mut v = Vec::with_capacity(cfg.n_layer);
2639 for _ in 0..cfg.n_layer {
2640 k.push(e.uninit((cap + cfg.block_size) * rowsz)?);
2641 v.push(e.uninit((cap + cfg.block_size) * rowsz)?);
2642 }
2643 Ok(Self {
2644 k,
2645 v,
2646 len: 0,
2647 cap,
2648 window_rows: cfg.sliding_window.saturating_add(cfg.block_size),
2649 row_bytes: rowsz * std::mem::size_of::<f32>(),
2650 floor: 0,
2651 })
2652 }
2653
2654 /// A COLD DRAFTER at a restored trunk boundary (lane/spec-exclusions-20260902, the
2655 /// `MEMRA_SPEC_WARM=1` arm): a fresh KV whose logical length is already `pos` (the
2656 /// restored prefix the trunk cache holds) but which owns NO ctx rows below it —
2657 /// `floor == len == pos`. The restored prefix's tap features do not exist (the trunk
2658 /// planes hold K/V latents, not the tapped residual rows), and re-running the trunk to
2659 /// recover them is the prime the restore exists to skip; so instead the drafter starts
2660 /// with an empty context at the right absolute position and fills it from the suffix
2661 /// prime's taps and every committed round from there, exactly as a cold session over a
2662 /// shorter prompt would. Row addressing (rope positions, `kv.len == cache.pos` at every
2663 /// round boundary) is identical to a tail import; only the attention floor differs.
2664 ///
2665 /// The `window_rows` below the floor are zero-filled so a later `export_tail` (which
2666 /// starts at the floor anyway) can never publish uninitialised bytes even under a
2667 /// future geometry mistake — finite zeros are the same belt `from_tail` wears.
2668 ///
2669 /// REFUSES under `MEMRA_DFLASH2_SDPA_CLIP=0`: the legacy full-scan kernel has no floor
2670 /// arm (`d2_windowed_attn`), and a cold drafter that scored 2048 zero rows at e^0 each
2671 /// would draft silently worse; the caller serves the plain hit by name instead.
2672 pub fn new_cold_at(
2673 e: &Engine,
2674 cfg: &DflashCfg,
2675 cap: usize,
2676 pos: usize,
2677 ) -> Result<Self, Box<dyn std::error::Error>> {
2678 if !dflash2_sdpa_clip_on() {
2679 return Err(
2680 "cold drafter needs the clipped round attention (MEMRA_DFLASH2_SDPA_CLIP=0 \
2681 selects the full-scan kernel, which has no context-floor arm)"
2682 .into(),
2683 );
2684 }
2685 if pos > cap {
2686 return Err(
2687 format!("cold drafter position {pos} exceeds the session cap {cap}").into(),
2688 );
2689 }
2690 let mut kv = Self::new(e, cfg, cap)?;
2691 let rowsz = kv.row_bytes / std::mem::size_of::<f32>();
2692 let zero_from = pos.saturating_sub(kv.window_rows);
2693 if zero_from < pos {
2694 for li in 0..kv.k.len() {
2695 e.memset_zeros_view(&mut kv.k[li].slice_mut(zero_from * rowsz..pos * rowsz))?;
2696 e.memset_zeros_view(&mut kv.v[li].slice_mut(zero_from * rowsz..pos * rowsz))?;
2697 }
2698 }
2699 kv.len = pos;
2700 kv.floor = pos;
2701 Ok(kv)
2702 }
2703
2704 /// The first ctx row this KV owns (doc on the field): `0` unless the KV was born as a
2705 /// cold drafter or imported from a short (floor-bearing) tail.
2706 pub fn floor(&self) -> usize {
2707 self.floor
2708 }
2709}
2710
2711impl DflashDraft {
2712 /// Ingest `t` NEW ctx-feature rows (committed order, absolute positions `pos_new`) into
2713 /// the draft KV: per layer k/v projections + k head-norm + rope, appended at kv.len.
2714 pub fn ingest_ctx(
2715 &self,
2716 e: &Engine,
2717 kv: &mut DflashKv,
2718 feats: &CudaSlice<f32>,
2719 pos_new: &[i32],
2720 t: usize,
2721 ) -> Result<(), Box<dyn std::error::Error>> {
2722 let c = &self.cfg;
2723 let (h, nkv, hd) = (c.hidden, c.n_kv, c.head_dim);
2724 assert!(kv.len + t <= kv.cap, "draft kv overflow");
2725 let pos_d = e.htod_i32(pos_new)?;
2726 for (li, l) in self.layers.iter().enumerate() {
2727 let k0 = self.mm(e, &l.wk, feats, t, h, nkv * hd)?;
2728 let v0 = self.mm(e, &l.wv, feats, t, h, nkv * hd)?;
2729 let mut kn = e.uninit(t * nkv * hd)?;
2730 e.rms_norm(&k0, &l.k_norm, &mut kn, hd, t * nkv, c.eps)?;
2731 self.rope_rows(e, &mut kn, &pos_d, nkv, t)?;
2732 e.copy_into(&mut kv.k[li], kv.len * nkv * hd, &kn, t * nkv * hd)?;
2733 e.copy_into(&mut kv.v[li], kv.len * nkv * hd, &v0, t * nkv * hd)?;
2734 }
2735 kv.len += t;
2736 Ok(())
2737 }
2738
2739 /// Block forward over the CACHED ctx KV: only the 16 block rows are projected per layer;
2740 /// block K/V land transiently at kv[len..len+b]. Bit-class-identical to forward_block
2741 /// (same kernels, same per-row programs; ONLY the ctx K/V recompute is cached).
2742 pub fn forward_round(
2743 &self,
2744 e: &Engine,
2745 kv: &mut DflashKv,
2746 noise_emb: &CudaSlice<f32>,
2747 pos_block: &[i32],
2748 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2749 let c = &self.cfg;
2750 let (h, nh, nkv, hd) = (c.hidden, c.n_head, c.n_kv, c.head_dim);
2751 let b = c.block_size;
2752 assert_eq!(pos_block.len(), b);
2753 let ctx = kv.len;
2754 let pos_blk = e.htod_i32(pos_block)?;
2755 let mut x = e.clone_dtod(noise_emb)?;
2756 for (li, l) in self.layers.iter().enumerate() {
2757 let mut xn = e.uninit(b * h)?;
2758 e.rms_norm(&x, &l.ln_in, &mut xn, h, b, c.eps)?;
2759 // DFlash2: dynamic conv wraps attention (see forward_block).
2760 let mut attn_dyn: Option<CudaSlice<f32>> = None;
2761 if let Some(d2) = &self.dflash2 {
2762 let (xc, dyn_) = self.d2_conv_prepare(e, &d2.attn_conv[li], &xn, b)?;
2763 xn = xc;
2764 attn_dyn = Some(dyn_);
2765 }
2766 let q0 = self.mm(e, &l.wq, &xn, b, h, nh * hd)?;
2767 let k0b = self.mm(e, &l.wk, &xn, b, h, nkv * hd)?;
2768 let v0b = self.mm(e, &l.wv, &xn, b, h, nkv * hd)?;
2769 let mut q = e.uninit(b * nh * hd)?;
2770 let mut kb = e.uninit(b * nkv * hd)?;
2771 e.rms_norm(&q0, &l.q_norm, &mut q, hd, b * nh, c.eps)?;
2772 e.rms_norm(&k0b, &l.k_norm, &mut kb, hd, b * nkv, c.eps)?;
2773 self.rope_rows(e, &mut q, &pos_blk, nh, b)?;
2774 self.rope_rows(e, &mut kb, &pos_blk, nkv, b)?;
2775 e.copy_into(&mut kv.k[li], ctx * nkv * hd, &kb, b * nkv * hd)?;
2776 e.copy_into(&mut kv.v[li], ctx * nkv * hd, &v0b, b * nkv * hd)?;
2777 let mut attn = e.uninit(b * nh * hd)?;
2778 let scale = 1.0f32 / (hd as f32).sqrt();
2779 if self.dflash2.is_some() && c.layer_sliding[li] {
2780 // Non-causal symmetric window (config is_causal=false): kv row index
2781 // == absolute position for BOTH ctx rows (committed order) and the
2782 // transient block rows, so the kernel's q_pos = (T_kv - T) + qt is the
2783 // absolute position and the old-side mask is exact. The future side
2784 // never binds (block <= window, asserted at load).
2785 d2_windowed_attn(
2786 e,
2787 &q,
2788 &kv.k[li],
2789 &kv.v[li],
2790 &mut attn,
2791 hd,
2792 nh,
2793 nkv,
2794 b,
2795 ctx + b,
2796 scale,
2797 c,
2798 kv.floor,
2799 )?;
2800 } else if std::env::var("MEMRA_DFLASH_FA").is_ok() {
2801 e.fa_prefill(
2802 &q,
2803 &kv.k[li],
2804 &kv.v[li],
2805 &mut attn,
2806 hd,
2807 nh,
2808 nkv,
2809 b,
2810 ctx + b,
2811 scale,
2812 false,
2813 )?;
2814 } else {
2815 e.sdpa_naive(
2816 &q,
2817 &kv.k[li],
2818 &kv.v[li],
2819 &mut attn,
2820 hd,
2821 nh,
2822 nkv,
2823 b,
2824 ctx + b,
2825 scale,
2826 false,
2827 )?;
2828 }
2829 let mut o = self.mm(e, &l.wo, &attn, b, nh * hd, h)?;
2830 if let (Some(d2), Some(dyn_)) = (&self.dflash2, &attn_dyn) {
2831 o = self.d2_conv_finish(e, &d2.attn_conv[li], &o, dyn_, b)?;
2832 }
2833 let mut x1 = e.uninit(b * h)?;
2834 e.add(&o, &x, &mut x1, b * h)?;
2835 let mut x1n = e.uninit(b * h)?;
2836 e.rms_norm(&x1, &l.ln_post, &mut x1n, h, b, c.eps)?;
2837 let mut mlp_dyn: Option<CudaSlice<f32>> = None;
2838 if let Some(d2) = &self.dflash2 {
2839 let (xc, dyn_) = self.d2_conv_prepare(e, &d2.mlp_conv[li], &x1n, b)?;
2840 x1n = xc;
2841 mlp_dyn = Some(dyn_);
2842 }
2843 let gate = self.mm(e, &l.w_gate, &x1n, b, h, c.n_ff)?;
2844 let up_ = self.mm(e, &l.w_up, &x1n, b, h, c.n_ff)?;
2845 let mut act = e.uninit(b * c.n_ff)?;
2846 e.silu_mul(&gate, &up_, &mut act, b * c.n_ff)?;
2847 let mut down = self.mm(e, &l.w_down, &act, b, c.n_ff, h)?;
2848 if let (Some(d2), Some(dyn_)) = (&self.dflash2, &mlp_dyn) {
2849 down = self.d2_conv_finish(e, &d2.mlp_conv[li], &down, dyn_, b)?;
2850 }
2851 let mut x2 = e.uninit(b * h)?;
2852 e.add(&down, &x1, &mut x2, b * h)?;
2853 x = x2;
2854 }
2855 let mut out = e.uninit(b * h)?;
2856 e.rms_norm(&x, &self.norm, &mut out, h, b, c.eps)?;
2857 Ok(out)
2858 }
2859}
2860
2861/// Emit an accepted draft run under the `max_new` budget: check BEFORE each push — at
2862/// real acceptance the final round often accepts a draft at the boundary, and
2863/// push-then-check emitted max_new+1 tokens (plain emits exactly max_new; the E2E gate
2864/// read it as a length divergence at index max_new with the shared prefix
2865/// byte-identical). f8300340cd fixed generate_spec_dspark this way; generate_spec_dflash
2866/// kept the buggy shape until the hermes sweep (fixed 2026-08-23) — both now share this
2867/// one helper. Returns true when the caller must break (budget reached or EOS emitted).
2868fn emit_accepted_run(out: &mut Vec<u32>, accepted: &[u32], eos: &[u32], max_new: usize) -> bool {
2869 for &dt in accepted {
2870 if out.len() >= max_new {
2871 return true;
2872 }
2873 out.push(dt);
2874 if eos.contains(&dt) {
2875 return true;
2876 }
2877 }
2878 false
2879}
2880
2881// ===== THE DRAFT-SOURCE SEAM (lane/glm5-extract2, phase 2 of the extraction program) ======
2882//
2883// `DraftSourcePlan` (memra-gguf `model_plan.rs`) has always been general: it is the PLAN's
2884// statement of where a family's drafts come from. What was glm5-named was everything on the
2885// ENGINE side of it — the loaded-drafter holder, the flag-to-drafter load contract, and the
2886// tap-layer resolution. All three are family-agnostic by content, so they live here, in the
2887// general DFlash module, and glm5 is a CONSUMER.
2888//
2889// WHAT IS DELIBERATELY *NOT* HERE, and why (phase-1 discipline, restated):
2890// the PER-SESSION draft state (`glm_spec::Glm5DraftState`) and the source-keyed round /
2891// maintenance walks. Those are not family-agnostic today: each arm reaches into the family's
2892// own cache planes (glm5's MLA latent plane, `HcTapSink` hc-contract taps, the KDA rollback
2893// stash) and the retained-q type carries the family's rank space. A trait over them would have
2894// exactly ONE implementor whose associated types are all glm5 types — a decorative trait cut
2895// blind, on the hottest file in the lane program. The trigger for that cut is the SECOND
2896// hybrid spec family's session state, which is what tells us which half of the state is
2897// shared. The trait sketch is banked in the lane doc so the second consumer starts from it,
2898// not from scratch.
2899
2900/// A loaded alternate draft source: the drafter weights plus the byte identity they were
2901/// pinned by. Model-level (loaded ONCE per model, on the head engine where the trunk lm_head
2902/// it projects through lives — the MTP-head placement law); per-session state is the family's.
2903///
2904/// Generalized from `glm_spec::Glm5DflashDrafter`, which stays re-exported under its old name
2905/// for the glm5 call sites and gates.
2906pub struct DflashDrafter {
2907 pub draft: DflashDraft,
2908 /// First 8 hex of sha256(model.safetensors) — the boot-receipt identity pin
2909 /// (`b33c0347` for the probe-pinned incoai/GLM-5.3-Flash-DFlash2 @ dc77ff1c bytes).
2910 pub sha8: String,
2911}
2912
2913/// Resolve a drafter's tap layers against the trunk it will read features from.
2914///
2915/// PURE (no env, no engine): the drafter's own `target_layer_ids`, plus a caller-supplied
2916/// `shift` and the trunk bound. `shift` exists because the tap-shift RED ARM is a GATE
2917/// INSTRUMENT owned by the family that runs the gate (`MEMRA_GLM5_*_GATE_RED` is classified
2918/// as an instrument, never a serving flag, and never generalized) — the family reads its own
2919/// red-arm env, prints its own tag, and passes the shift in here.
2920pub fn resolve_tap_layers(
2921 target_layer_ids: &[usize],
2922 n_trunk: usize,
2923 shift: usize,
2924 what: &str,
2925) -> Result<Vec<usize>, String> {
2926 if target_layer_ids.is_empty() {
2927 return Err(format!("{what} drafter config carries no target_layer_ids"));
2928 }
2929 let taps: Vec<usize> = target_layer_ids.iter().map(|t| t + shift).collect();
2930 if let Some(&bad) = taps.iter().find(|&&t| t >= n_trunk) {
2931 return Err(format!(
2932 "{what} tap layer {bad} is outside the {n_trunk}-layer trunk"
2933 ));
2934 }
2935 Ok(taps)
2936}
2937
2938/// Load a DFlash2 drafter named by `flag` from `dir`, validating every contract that binds a
2939/// drafter to a TARGET — family-agnostic, because each one is a property of the pair, not of
2940/// the family:
2941///
2942/// * the checkpoint is a `DFlash2DraftModel` (the selector family is the only draft source
2943/// this seam serves);
2944/// * `cfg.hidden == n_embd` (the drafter consumes the target's features and projects through
2945/// the target's embed/lm_head);
2946/// * `cfg.target_layer_ids` name valid trunk layers;
2947/// * `cfg.mask_token_id` is inside the target vocab.
2948///
2949/// A set flag that cannot load is a LOUD failure, never a silent plain fallback. Every error
2950/// is prefixed `{flag}={dir}` so the operator sees the flag they typed; the glm5 call site's
2951/// message bytes are unchanged by construction.
2952pub fn load_drafter(
2953 e: &Engine,
2954 dir: &std::path::Path,
2955 flag: &str,
2956 n_trunk: usize,
2957 n_embd: usize,
2958 n_vocab: usize,
2959) -> Result<DflashDrafter, String> {
2960 let dpath = dir.display();
2961 let draft = DflashDraft::load(e, dir)
2962 .map_err(|err| format!("{flag}={dpath}: drafter load failed: {err}"))?;
2963 if draft.dflash2.is_none() {
2964 return Err(format!(
2965 "{flag}={dpath}: checkpoint is not a DFlash2DraftModel \
2966 (the glm5 draft source is the selector family only)"
2967 ));
2968 }
2969 if draft.cfg.hidden != n_embd {
2970 return Err(format!(
2971 "{flag}={dpath}: drafter hidden {} != target n_embd {n_embd} \
2972 (the drafter consumes target features and the target's embed/lm_head)",
2973 draft.cfg.hidden
2974 ));
2975 }
2976 if draft.cfg.target_layer_ids.is_empty()
2977 || draft.cfg.target_layer_ids.iter().any(|&t| t >= n_trunk)
2978 {
2979 return Err(format!(
2980 "{flag}={dpath}: target_layer_ids {:?} do not name valid \
2981 trunk layers (n_trunk {n_trunk})",
2982 draft.cfg.target_layer_ids
2983 ));
2984 }
2985 if draft.cfg.mask_token_id as usize >= n_vocab {
2986 return Err(format!(
2987 "{flag}={dpath}: mask token {} outside the target vocab {n_vocab}",
2988 draft.cfg.mask_token_id
2989 ));
2990 }
2991 let sha8 = crate::hybrid::sha256_file_hex8(&dir.join("model.safetensors"))
2992 .map_err(|err| format!("{flag}={dpath}: sha256 pin: {err}"))?;
2993 Ok(DflashDrafter { draft, sha8 })
2994}
2995
2996#[cfg(test)]
2997mod draft_source_seam_tests {
2998 use super::resolve_tap_layers;
2999
3000 #[test]
3001 fn taps_resolve_and_the_shift_is_the_callers() {
3002 assert_eq!(
3003 resolve_tap_layers(&[1, 12, 23], 46, 0, "glm5 DFlash2").unwrap(),
3004 vec![1, 12, 23]
3005 );
3006 // the red arm's +1 rides in as a parameter, not as an env read in here
3007 assert_eq!(
3008 resolve_tap_layers(&[1, 12, 23], 46, 1, "glm5 DFlash2").unwrap(),
3009 vec![2, 13, 24]
3010 );
3011 }
3012
3013 #[test]
3014 fn empty_and_out_of_trunk_taps_refuse_by_name() {
3015 let err = resolve_tap_layers(&[], 46, 0, "glm5 DFlash2").unwrap_err();
3016 assert!(err.contains("no target_layer_ids"), "{err}");
3017 let err = resolve_tap_layers(&[1, 46], 46, 0, "glm5 DFlash2").unwrap_err();
3018 assert!(
3019 err.contains("tap layer 46 is outside the 46-layer trunk"),
3020 "{err}"
3021 );
3022 // the SHIFTED tap is what gets bounds-checked — the red arm must not be able to
3023 // walk off the trunk silently
3024 let err = resolve_tap_layers(&[45], 46, 1, "glm5 DFlash2").unwrap_err();
3025 assert!(err.contains("tap layer 46 is outside"), "{err}");
3026 }
3027}
3028
3029#[cfg(test)]
3030mod emit_budget_tests {
3031 use super::emit_accepted_run;
3032
3033 #[test]
3034 fn accepted_run_never_exceeds_max_new() {
3035 // TOOTH (hermes finding, fixed 2026-08-23): the dflash accept loop pushed THEN
3036 // checked, emitting max_new+1 whenever the final round accepted at the boundary.
3037 let mut out = vec![1, 2, 3]; // 3 committed, budget 4: exactly ONE slot left
3038 let stop = emit_accepted_run(&mut out, &[10, 11, 12], &[], 4);
3039 assert!(stop, "hitting the budget must break the round loop");
3040 assert_eq!(
3041 out,
3042 vec![1, 2, 3, 10],
3043 "exactly max_new tokens, never max_new+1"
3044 );
3045 // EOS inside the run stops after emitting it (unchanged semantics).
3046 let mut out = vec![1];
3047 let stop = emit_accepted_run(&mut out, &[10, 99, 12], &[99], 8);
3048 assert!(stop);
3049 assert_eq!(out, vec![1, 10, 99]);
3050 // A run fitting the budget with no EOS lets the round continue.
3051 let mut out = vec![1];
3052 assert!(!emit_accepted_run(&mut out, &[10, 11], &[], 8));
3053 assert_eq!(out, vec![1, 10, 11]);
3054 }
3055}
3056
3057// ================= DFlash spec round (greedy, first light) =================
3058// Exact contract: identical output stream to plain greedy decode BY CONSTRUCTION — the
3059// target's batched verify argmax decides every committed token; the drafter only proposes.
3060// (Same verify+rewind pattern as generate_spec_gemma's eager round; t=16 verify rides the
3061// straddle-split-safe fa_decode_rows.)
3062impl crate::hybrid::HybridModel {
3063 pub fn generate_spec_dflash(
3064 &self,
3065 e: &Engine,
3066 draft: &DflashDraft,
3067 prompt: &[u32],
3068 max_new: usize,
3069 eos: &[u32],
3070 ) -> Result<Vec<u32>, Box<dyn std::error::Error>> {
3071 self.refuse_hyper("generate_spec_dflash")?;
3072 use crate::cache::{Cache, DflashTapSink};
3073 let n_embd = self.cfg.n_embd as usize;
3074 let c = &draft.cfg;
3075 assert!(
3076 draft.dflash2.is_none(),
3077 "DFlash2 drafters ride the qwen-hybrid dspark round (selector + windowed \
3078 attention); the gemma arm has no consumer for the family's ops"
3079 );
3080 assert_eq!(n_embd, c.hidden, "draft hidden must match target n_embd");
3081 let b = c.block_size;
3082 let n_taps = c.target_layer_ids.len();
3083 let max_ctx = prompt.len() + max_new + b + 8;
3084 // First light holds ctx <= sliding_window: the draft was trained with 4 sliding
3085 // layers (window 2048) and the first-light attention is windowless full — inside
3086 // the window the two are identical. The depth cell (1736 + 128) fits.
3087 assert!(
3088 max_ctx <= c.sliding_window,
3089 "first-light dflash round is windowless — ctx cap {} exceeds the draft window {}",
3090 max_ctx,
3091 c.sliding_window
3092 );
3093 let mut cache = Cache::new(e, &self.cfg, max_ctx)?;
3094
3095 // ---- prime with taps armed ----
3096 let tp = prompt.len();
3097 cache.dflash_taps = Some(DflashTapSink {
3098 layer_ids: c.target_layer_ids.clone(),
3099 buf: e.uninit(tp * n_taps * n_embd)?,
3100 hidden: n_embd,
3101 t: tp,
3102 base: 0,
3103 });
3104 let t_prime = std::time::Instant::now();
3105 let (logits, _h_seed, _hiddens) = self.prime_cache(e, prompt, &mut cache, 0)?;
3106 let mut last = crate::forward::argmax(&logits) as u32;
3107 // draft KV cache: ingest the prompt's ctx features once; per round only the kept
3108 // rows ingest + the block projects (round cost O(block), not O(ctx)).
3109 let mut dkv = DflashKv::new(e, &draft.cfg, max_ctx)?;
3110 {
3111 // CHUNKED ingest (depth OOM fix): the 1736-row prompt tap buffer is ~224MB f32;
3112 // running fc + 5-layer k/v projection over it in one shot stacks another
3113 // ~300MB of transients on the ~21.3GB trunk peak. 256-row windows bound the
3114 // transient set; identical values (row-independent ops).
3115 let taps = cache.dflash_taps.take().unwrap();
3116 let n_taps_h = n_taps * n_embd;
3117 let mut r0 = 0usize;
3118 while r0 < tp {
3119 let t_c = (tp - r0).min(256);
3120 let tv = e.view(&taps.buf, tp * n_taps_h);
3121 let win = tv.slice(r0 * n_taps_h..(r0 + t_c) * n_taps_h);
3122 let mut chunk = e.uninit(t_c * n_taps_h)?;
3123 e.copy_view_into(&mut chunk, 0, &win, t_c * n_taps_h)?;
3124 let f = draft.ctx_features(e, &chunk, t_c)?;
3125 let pos_c: Vec<i32> = ((r0 as i32)..(r0 + t_c) as i32).collect();
3126 draft.ingest_ctx(e, &mut dkv, &f, &pos_c, t_c)?;
3127 r0 += t_c;
3128 }
3129 }
3130 let mut ctx_len = tp;
3131 e.stream().synchronize()?;
3132 // published prime wall (the run-spec/gemma-gate timing contract subtracts it)
3133 crate::PRIME_NANOS.store(
3134 t_prime.elapsed().as_nanos() as u64,
3135 std::sync::atomic::Ordering::Relaxed,
3136 );
3137
3138 // embed-scale seam (MEMRA_DFLASH_EMB_SCALE): gemma trunks scale embeddings by
3139 // sqrt(n_embd) INSIDE the forward; whether the z-lab gemma4 training fed the
3140 // drafter scaled or raw embed rows is not visible from the reference (qwen path
3141 // uses raw embed_tokens). Acceptance arbitrates; default raw.
3142 let emb_scale = if std::env::var("MEMRA_DFLASH_EMB_SCALE").as_deref() == Ok("1") {
3143 (n_embd as f32).sqrt()
3144 } else {
3145 1.0
3146 };
3147
3148 let mut out = Vec::with_capacity(max_new);
3149 let n_vocab = self.output.out_features();
3150 // VERIFY WIDTH (MEMRA_DFLASH_VERIFY_T, default 8): the drafter always drafts a full
3151 // block (its trained mask pattern) but only the first vt rows go through the target
3152 // verify — the t=16 verify rides the untuned b16 tier at ~32% of the byte wall
3153 // (65ms/verify) while b8 rides the tuned r2 tier; with ~2.7 committed/round the
3154 // deep block positions almost never survive anyway. Exactness unaffected (verify
3155 // still decides every committed token).
3156 let vt_cap: usize = std::env::var("MEMRA_DFLASH_VERIFY_T")
3157 .ok()
3158 .and_then(|v| v.parse().ok())
3159 .unwrap_or(8)
3160 .clamp(2, b);
3161 // adaptive verify width (MEMRA_DFLASH_ADAPT!=0, MTP accepted+1 recipe): next round
3162 // verifies one past this round's accepted run, clamped [3, cap].
3163 let adapt = std::env::var("MEMRA_DFLASH_ADAPT").as_deref() != Ok("0");
3164 let mut vt = vt_cap;
3165 let mut attempted = 0usize;
3166 let mut accepted = 0usize;
3167 // The whole round runs in the decode-exact matmul scope: the m=16 draft mms were
3168 // otherwise falling into the prefill-GEMM class (770us/matmul, 17% of the depth
3169 // round). Prime (before this loop) keeps the prefill GEMM path. RAII: a `?` exit
3170 // anywhere in the loop restores the pre-scope value instead of latching exact ON
3171 // engine-wide (hermes finding, fixed 2026-08-23).
3172 let exact_scope = e.exact_scope(true);
3173 'outer: while out.len() < max_new {
3174 let start = cache.pos; // committed length
3175 // ---- draft: block = [last, MASK x b-1] ----
3176 let mut block: Vec<u32> = vec![c.mask_token_id; b];
3177 block[0] = last;
3178 let mut noise = e.htod(&self.embd.try_gather(n_embd, &block)?)?;
3179 if emb_scale != 1.0 {
3180 e.scale_inplace(&mut noise, emb_scale, b * n_embd)?;
3181 }
3182 if std::env::var("MEMRA_DFLASH_DEBUG").as_deref() == Ok("1") && start == cache.pos {
3183 let nv = e.dtoh(&noise)?;
3184 let r0: f32 = nv[..n_embd].iter().map(|x| x * x).sum::<f32>().sqrt();
3185 let r1: f32 = nv[n_embd..2 * n_embd]
3186 .iter()
3187 .map(|x| x * x)
3188 .sum::<f32>()
3189 .sqrt();
3190 eprintln!(
3191 "[dflash noise] |row0(last)|={r0:.3} |row1(MASK id {})|={r1:.3}",
3192 c.mask_token_id
3193 );
3194 }
3195 let pos_block: Vec<i32> = ((start as i32)..(start + b) as i32).collect();
3196 let dh = draft.forward_round(e, &mut dkv, &noise, &pos_block)?;
3197 // draft tokens = argmax(lm_head(h rows 1..b))
3198 let mut rows = e.uninit((b - 1) * n_embd)?;
3199 {
3200 let dv = e.view(&dh, b * n_embd);
3201 let tail = dv.slice(n_embd..b * n_embd);
3202 e.copy_view_into(&mut rows, 0, &tail, (b - 1) * n_embd)?;
3203 }
3204 let mut dl = e.matmul(&self.output, &rows, b - 1)?;
3205 // SEMI-AR MARKOV CHAIN (DSpark head, when present + MEMRA_DFLASH_MARKOV!=0):
3206 // left-to-right, logits_k += W2(W1[prev realized token]) — the whole chain
3207 // stays on-device (chain_d[0] = the pending token; argmax k writes
3208 // chain_d[k+1], the k+1 bias gathers from it). Greedy mirror of the patch's
3209 // _markov_semiar_sample_block.
3210 let markov_on = std::env::var("MEMRA_DFLASH_MARKOV").as_deref() != Ok("0");
3211 let mut chain_d = e.stream().alloc_zeros::<u32>(b)?;
3212 if let (Some(mk), true) = (&draft.markov, markov_on) {
3213 e.set_u32_one(&mut chain_d, last)?;
3214 for k in 0..(b - 1) {
3215 let mut f = e.uninit(mk.rank)?;
3216 e.gather_row_bf16(&mk.w1_bf16, &chain_d, k, &mut f, mk.rank)?;
3217 let bias = e.matmul(&mk.w2, &f, 1)?;
3218 e.add_row_inplace(&mut dl, &bias, n_vocab, k * n_vocab)?;
3219 e.argmax_token_device_col(&dl, k, n_vocab, &mut chain_d, k + 1)?;
3220 }
3221 } else {
3222 for i in 0..(b - 1) {
3223 e.argmax_token_device_col(&dl, i, n_vocab, &mut chain_d, i + 1)?;
3224 }
3225 }
3226 let chain = e.dtoh_u32(&chain_d)?;
3227 let dtoks = &chain[1..];
3228 for (i, &dt) in dtoks.iter().enumerate() {
3229 block[i + 1] = dt;
3230 }
3231 let dbg = std::env::var("MEMRA_DFLASH_DEBUG").as_deref() == Ok("1");
3232
3233 // ---- verify: one t=vt target forward with taps armed ----
3234 let vblock = &block[..vt];
3235 cache.dflash_taps = Some(DflashTapSink {
3236 layer_ids: c.target_layer_ids.clone(),
3237 buf: e.uninit(vt * n_taps * n_embd)?,
3238 hidden: n_embd,
3239 t: vt,
3240 base: 0,
3241 });
3242 let (vam, _vh) = self.gemma4_decode_step_t_am(e, vblock, start, &mut cache)?;
3243 let taps = cache.dflash_taps.take().unwrap();
3244 if dbg {
3245 eprintln!(
3246 "[dflash r] start={start} last={last}\n draft={:?}\n vam ={:?}",
3247 &block[1..],
3248 vam
3249 );
3250 }
3251
3252 // ---- accept ----
3253 let mut m = 0usize;
3254 while m < vt - 1 && block[m + 1] as usize == vam[m] as usize {
3255 m += 1;
3256 }
3257 attempted += vt - 1;
3258 accepted += m;
3259 out.push(last);
3260 if eos.contains(&last) {
3261 break 'outer;
3262 }
3263 if emit_accepted_run(&mut out, &block[1..=m], eos, max_new) {
3264 break 'outer;
3265 }
3266 let next = vam[m];
3267
3268 // ---- commit/rollback: keep m+1 of the b appended rows ----
3269 let keep = m + 1;
3270 for kvl in cache.kv.iter_mut().flatten() {
3271 kvl.len -= vt - keep;
3272 e.set_i32_one(&mut kvl.len_d, kvl.len as i32)?;
3273 }
3274 cache.pos -= vt - keep;
3275
3276 // ---- ingest the kept rows' ctx features into the draft KV ----
3277 {
3278 let tv = e.view(&taps.buf, vt * n_taps * n_embd);
3279 let keep_view = tv.slice(0..keep * n_taps * n_embd);
3280 let mut kept = e.uninit(keep * n_taps * n_embd)?;
3281 e.copy_view_into(&mut kept, 0, &keep_view, keep * n_taps * n_embd)?;
3282 let f = draft.ctx_features(e, &kept, keep)?;
3283 let pos_k: Vec<i32> = ((ctx_len as i32)..(ctx_len + keep) as i32).collect();
3284 draft.ingest_ctx(e, &mut dkv, &f, &pos_k, keep)?;
3285 ctx_len += keep;
3286 }
3287 last = next;
3288 if adapt {
3289 vt = (m + 2).clamp(3, vt_cap);
3290 }
3291 }
3292 drop(exact_scope);
3293 if std::env::var("MEMRA_SPEC_STATS").as_deref() == Ok("1") {
3294 eprintln!(
3295 "[dflash] acceptance {accepted}/{attempted} = {:.3}",
3296 accepted as f64 / attempted.max(1) as f64
3297 );
3298 }
3299 Ok(out)
3300 }
3301}
3302
3303// ================= Engine-bundle slice 1: batched GDN state snapshot ====================
3304// DSF-ROUNDCOST-20260820 §1.1 measured the dspark round's `cache.snapshot(e)` at 0.67 ms
3305// native wall — 48 linear layers x {conv, ssm} x (alloc_zeros + memcpy_dtod) of pure
3306// dispatch serialization, zero kernels. This batcher holds ONE persistent CacheSnapshot
3307// (buffers allocated on round 1, reused every round — kills the per-round alloc/memset
3308// churn) plus device pointer tables, so a round's snap is one small H2D table refresh
3309// (the ssm handles ping-pong per verify row, so live pointers are re-read each round;
3310// conv handles are rolled in place and never move) + TWO `copy_batch_uniform_f32`
3311// launches. Bytes, buffers and stream order are identical to `Cache::snapshot`; only the
3312// dispatch count changes, so acceptance and streams stay bit-identical (E2E-gated).
3313// `MEMRA_STATE_COPY_BATCH=0` reverts to the legacy per-layer snapshot.
3314
3315pub(crate) struct DsparkSnapBatch {
3316 pub(crate) snap: crate::cache::CacheSnapshot,
3317 /// Linear-attention layer indices, in `conv_table`/`ssm_table` order.
3318 lin: Vec<usize>,
3319 /// [src_0..src_{n-1}, dst_0..dst_{n-1}] — live conv states -> snapshot conv buffers.
3320 conv_table: CudaSlice<u64>,
3321 ssm_table: CudaSlice<u64>,
3322 host_ssm: Vec<u64>,
3323 conv_words: usize,
3324 ssm_words: usize,
3325}
3326
3327impl DsparkSnapBatch {
3328 /// Build from a fresh full snapshot (this IS round 1's snap — the caller uses
3329 /// `self.snap` directly after `new`). Returns None when the cache has no linear
3330 /// layers or their state sizes are non-uniform (a future hybrid shape) — the caller
3331 /// then stays on the legacy per-layer snapshot rather than copying wrong byte counts.
3332 pub(crate) fn new(
3333 e: &Engine,
3334 cache: &crate::cache::Cache,
3335 ) -> Result<Option<Self>, Box<dyn std::error::Error>> {
3336 use cudarc::driver::DevicePtr;
3337 let snap = cache.snapshot(e)?;
3338 let lin: Vec<usize> = (0..cache.recur.len())
3339 .filter(|&il| cache.recur[il].is_some())
3340 .collect();
3341 if lin.is_empty() {
3342 return Ok(None);
3343 }
3344 let first = cache.recur[lin[0]].as_ref().unwrap();
3345 let (conv_words, ssm_words) = (first.conv_state.len(), first.ssm_state.len());
3346 for &il in &lin {
3347 let rl = cache.recur[il].as_ref().unwrap();
3348 if rl.conv_state.len() != conv_words || rl.ssm_state.len() != ssm_words {
3349 return Ok(None);
3350 }
3351 }
3352 let n = lin.len();
3353 let mut host_conv = vec![0u64; 2 * n];
3354 let mut host_ssm = vec![0u64; 2 * n];
3355 {
3356 let s = &e.gpu.stream();
3357 for (k, &il) in lin.iter().enumerate() {
3358 let rl = cache.recur[il].as_ref().unwrap();
3359 let (pc, _g0) = rl.conv_state.device_ptr(s);
3360 let (ps, _g1) = rl.ssm_state.device_ptr(s);
3361 let (dc, _g2) = snap.conv[il].as_ref().unwrap().device_ptr(s);
3362 let (ds, _g3) = snap.ssm[il].as_ref().unwrap().device_ptr(s);
3363 host_conv[k] = pc;
3364 host_conv[n + k] = dc;
3365 host_ssm[k] = ps;
3366 host_ssm[n + k] = ds;
3367 }
3368 }
3369 let conv_table = e.htod_u64(&host_conv)?;
3370 let ssm_table = e.htod_u64(&host_ssm)?;
3371 Ok(Some(Self {
3372 snap,
3373 lin,
3374 conv_table,
3375 ssm_table,
3376 host_ssm,
3377 conv_words,
3378 ssm_words,
3379 }))
3380 }
3381
3382 /// The per-round snap: refresh kv lens/pos host-side (as `snapshot_into` does),
3383 /// re-read the live ssm handles into the table (gdn ping-pong moves them; the conv
3384 /// handles and every snapshot dst are stable), then two batched-copy launches.
3385 pub(crate) fn refresh(
3386 &mut self,
3387 e: &Engine,
3388 cache: &crate::cache::Cache,
3389 ) -> Result<(), Box<dyn std::error::Error>> {
3390 use cudarc::driver::DevicePtr;
3391 for il in 0..cache.kv.len() {
3392 self.snap.kv_len[il] = cache.kv[il].as_ref().map(|kvl| kvl.len);
3393 }
3394 self.snap.pos = cache.pos;
3395 let n = self.lin.len();
3396 {
3397 let s = &e.gpu.stream();
3398 for (k, &il) in self.lin.iter().enumerate() {
3399 let rl = cache.recur[il].as_ref().unwrap();
3400 let (ps, _g) = rl.ssm_state.device_ptr(s);
3401 self.host_ssm[k] = ps;
3402 }
3403 }
3404 e.htod_u64_into(&self.host_ssm, &mut self.ssm_table)?;
3405 e.copy_batch_uniform_f32(&self.conv_table, n, self.conv_words)?;
3406 e.copy_batch_uniform_f32(&self.ssm_table, n, self.ssm_words)?;
3407 Ok(())
3408 }
3409}
3410
3411// ================= DSpark spec round, QWEN-HYBRID target (lane/dspark-q38-recover) =====
3412// The q38 twin of generate_spec_dflash. Same drafter machinery (rounds, markov chain,
3413// draft KV, adaptive verify width); the TARGET side swaps gemma4's dense verify for the
3414// qwen serving-class verify funnel (dspark_verify_t_am) + snapshot/rollback, because the
3415// hybrid GDN conv/ssm state mutates in place — dense KV truncation cannot roll it back.
3416// Exactness contract unchanged: identical stream to plain greedy BY CONSTRUCTION (the
3417// target's verify argmax decides every committed token).
3418impl crate::hybrid::HybridModel {
3419 #[allow(clippy::type_complexity)] // allow: one-shot composite type; naming it would hide the shape that matters at the call site
3420 pub fn generate_spec_dspark(
3421 &self,
3422 e: &Engine,
3423 draft: &DflashDraft,
3424 prompt: &[u32],
3425 max_new: usize,
3426 eos: &[u32],
3427 sampling: Option<&crate::spec::SpecSampling>,
3428 ) -> Result<Vec<u32>, Box<dyn std::error::Error>> {
3429 self.refuse_hyper("generate_spec_dspark")?;
3430 use crate::cache::{Cache, DflashTapSink};
3431 assert!(
3432 !self.uses_gemma_program(),
3433 "gemma4 targets use generate_spec_dflash; this is the qwen-hybrid arm"
3434 );
3435 // SAMPLED ADMISSION (T>0, lane/dspark-sampled-admission-20260820): Some+temp>0
3436 // routes the round's proposal/accept through the rejection-sampling arms; None or
3437 // temp==0 keeps every greedy path byte-identical (the exactness instrument).
3438 let sp_on: Option<&crate::spec::SpecSampling> = sampling.filter(|s| s.temp > 0.0);
3439 // PENALTIES AT T==0 ARE A LOUD REFUSAL (lane/dspark-penalized-sampled-20260821):
3440 // the greedy walk argmaxes RAW verify columns, so a temp==0 config carrying
3441 // non-identity penalties would silently serve the UNPENALIZED greedy stream —
3442 // exactly the H-class silent-program-switch this route refuses everywhere else.
3443 // Penalized greedy stays on the plain path (worker admission owns the exclusion).
3444 if let Some(s) = sampling
3445 && s.temp <= 0.0
3446 && s.pen_on()
3447 {
3448 return Err(
3449 "dspark spec at temp==0 is the greedy route and would silently drop \
3450 the request's penalties; penalized greedy is served on the plain path"
3451 .into(),
3452 );
3453 }
3454 // Penalized-sampled state: the session window (pen_window_seed — one definition
3455 // across both spec routes), extended with every committed token; each round's
3456 // accept receives the trimmed tail (min(penalty_last_n, PEN_WINDOW_MAX)).
3457 let pen_on = sp_on.is_some_and(|s| s.pen_on());
3458 let mut pen_hist: Vec<u32> = if pen_on {
3459 crate::spec::pen_window_seed(&[], prompt, sp_on.unwrap().penalty_last_n)
3460 } else {
3461 Vec::new()
3462 };
3463 let (mut sctr, mut uctr) = (0u32, 0u32);
3464 let n_embd = self.cfg.n_embd as usize;
3465 let c = &draft.cfg;
3466 assert_eq!(n_embd, c.hidden, "draft hidden must match target n_embd");
3467 let b = c.block_size;
3468 let n_taps = c.target_layer_ids.len();
3469 let max_ctx = prompt.len() + max_new + b + 8;
3470 // DFlash2 implements the reference's non-causal symmetric sliding window in
3471 // the round attention (sdpa_naive_w), so depth past the window is admitted;
3472 // other families keep the historical windowless contract.
3473 assert!(
3474 draft.dflash2.is_some() || max_ctx <= c.sliding_window,
3475 "dspark round is windowless — ctx cap {} exceeds the draft window {}",
3476 max_ctx,
3477 c.sliding_window
3478 );
3479 let mut cache = Cache::new(e, &self.cfg, max_ctx)?;
3480
3481 // ---- prime with taps armed (chunked prime writes at chunk offsets via sink.base) ----
3482 let tp = prompt.len();
3483 cache.dflash_taps = Some(DflashTapSink {
3484 layer_ids: c.target_layer_ids.clone(),
3485 buf: e.uninit(tp * n_taps * n_embd)?,
3486 hidden: n_embd,
3487 t: tp,
3488 base: 0,
3489 });
3490 let t_prime = std::time::Instant::now();
3491 let (logits, _h_seed, _hiddens) = self.prime_cache(e, prompt, &mut cache, 0)?;
3492 // Boundary token: greedy takes the argmax (byte contract); sampled draws it from
3493 // the request's own filtered target through the session Philox stream — the same
3494 // shipped composition the frspec route uses (sample_check arm 9 oracles it).
3495 let mut last = match sp_on {
3496 Some(sp) => crate::spec::sample_boundary_token(
3497 e,
3498 &logits,
3499 sp,
3500 &pen_hist,
3501 &mut sctr,
3502 "dspark-prime",
3503 )?,
3504 None => crate::forward::argmax(&logits) as u32,
3505 };
3506 let mut dkv = DflashKv::new(e, &draft.cfg, max_ctx)?;
3507 {
3508 let taps = cache.dflash_taps.take().unwrap();
3509 let n_taps_h = n_taps * n_embd;
3510 let mut r0 = 0usize;
3511 while r0 < tp {
3512 let t_c = (tp - r0).min(256);
3513 let tv = e.view(&taps.buf, tp * n_taps_h);
3514 let win = tv.slice(r0 * n_taps_h..(r0 + t_c) * n_taps_h);
3515 let mut chunk = e.uninit(t_c * n_taps_h)?;
3516 e.copy_view_into(&mut chunk, 0, &win, t_c * n_taps_h)?;
3517 let f = draft.ctx_features(e, &chunk, t_c)?;
3518 let pos_c: Vec<i32> = ((r0 as i32)..(r0 + t_c) as i32).collect();
3519 draft.ingest_ctx(e, &mut dkv, &f, &pos_c, t_c)?;
3520 r0 += t_c;
3521 }
3522 }
3523 let mut ctx_len = tp;
3524 e.stream().synchronize()?;
3525 crate::PRIME_NANOS.store(
3526 t_prime.elapsed().as_nanos() as u64,
3527 std::sync::atomic::Ordering::Relaxed,
3528 );
3529
3530 let mut out = Vec::with_capacity(max_new);
3531 let n_vocab = self.output.out_features();
3532 // Harvest convention (DSPARK-POSTMORTEM-20260820.md): which drafter output rows
3533 // become draft candidates. nd = drafts/round; verify carries [anchor, drafts]
3534 // = up to nd+1 rows. FAMILY-keyed for DFlash2 (mask-fill by construction),
3535 // else default = the CHECKPOINT's own strategy census (owner-ratified flip,
3536 // 2026-08-20); explicit env still wins (contradiction refuses).
3537 let harvest = DsparkHarvest::for_draft(draft);
3538 let nd = harvest.n_drafts(b);
3539 let r0 = harvest.first_row();
3540 let vt_cap: usize = std::env::var("MEMRA_DFLASH_VERIFY_T")
3541 .ok()
3542 .and_then(|v| v.parse().ok())
3543 .unwrap_or(nd + 1)
3544 .clamp(2, nd + 1);
3545 let adapt = std::env::var("MEMRA_DFLASH_ADAPT").as_deref() != Ok("0");
3546 // Verify-window policy (H4, DSPARK-POSTMORTEM-20260820.md): default =
3547 // confidence-slot tau=.5 when the checkpoint carries an accept-rate head
3548 // (owner-ratified flip 2026-08-20; cell-3 tau ladder knee) — each round's
3549 // window is sized from the head's own slot scores, post-draft pre-verify.
3550 // Head-less checkpoints and MEMRA_DFLASH_ADAPT=0 keep the reactive ladder.
3551 let vt_policy = DsparkVtPolicy::resolve(draft.confidence.is_some());
3552 if vt_policy.is_confidence() {
3553 assert!(
3554 draft.confidence.is_some(),
3555 "MEMRA_DSPARK_VT={vt_policy:?} needs a checkpoint with an accept-rate \
3556 head (confidence_head.* absent in this export)"
3557 );
3558 }
3559 let mut vt = vt_cap;
3560 let mut attempted = 0usize;
3561 let mut accepted = 0usize;
3562 // Engine-bundle slice 1: persistent batched snapshot (None until round 1; stays
3563 // None — legacy per-layer snapshot — under MEMRA_STATE_COPY_BATCH=0 or when the
3564 // batcher declines the cache shape).
3565 let mut snapb: Option<DsparkSnapBatch> = None;
3566 let mut snapb_off = !crate::spec::state_copy_batch_on();
3567 // Engine-bundle slice 2: deferred chain readback needs the resident embed table
3568 // (verify then embeds chain_d directly). Ladder/stash arms only — the confidence
3569 // policies size vt from a pre-verify head readback and keep the legacy order.
3570 let defer_rb = crate::spec::dspark_defer_readback_on() && !vt_policy.is_confidence();
3571 let (embd_qt, embd_rb) = self.embd.qt_and_row_bytes(n_embd);
3572 let embd_gpu = if !defer_rb || crate::spec::spec_host_embd() {
3573 None
3574 } else {
3575 Some(
3576 self.embd_gpu
3577 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload")),
3578 )
3579 };
3580 // Engine-bundle slice 3: per-(segment, vt) verify graphs for the linear-layer runs
3581 // (rides the slice-2 deferred path only — device tokens keep the whole verify off
3582 // the host). PERSISTENT across generations on the model (rebuilding per call
3583 // re-captured ~80 graphs per prompt — measured 97.8 -> 79.1 tok/s e2e); the
3584 // captured bodies are cache-independent: all state reads go through per-round
3585 // refreshed pointer tables and ctx-owned slabs. None = eager walk, byte-identical.
3586 let mut vg_guard = self.dspark_vgraphs.lock().unwrap();
3587 if vg_guard.is_none() && embd_gpu.is_some() && crate::spec::dspark_verify_graph_on() {
3588 *vg_guard = crate::spec::DsparkVerifyGraphs::new(e, &cache, vt_cap, n_embd)?;
3589 }
3590 let vgraphs: &mut Option<crate::spec::DsparkVerifyGraphs> = &mut vg_guard;
3591 // per-phase economics counters (ns) — the verify-toll dataset
3592 let (mut ns_draft, mut ns_snap, mut ns_verify, mut ns_roll, mut ns_ingest) =
3593 (0u64, 0u64, 0u64, 0u64, 0u64);
3594 let mut rounds = 0usize;
3595 let stats = std::env::var("MEMRA_SPEC_STATS").as_deref() == Ok("1");
3596 let clock = |on: bool, e: &Engine| -> std::time::Instant {
3597 if on {
3598 let _ = e.stream().synchronize();
3599 }
3600 std::time::Instant::now()
3601 };
3602 'outer: while out.len() < max_new {
3603 rounds += 1;
3604 let start = cache.pos; // committed length
3605 // ---- draft: block = [last, MASK x b-1] (decode-exact class for the m=b mms) ----
3606 let t0 = clock(stats, e);
3607 // RAII: a `?` exit restores the pre-scope value instead of latching exact
3608 // ON engine-wide (hermes finding, fixed 2026-08-23).
3609 let exact_scope = e.exact_scope(true);
3610 let mut block: Vec<u32> = vec![c.mask_token_id; b];
3611 block[0] = last;
3612 let noise = e.htod(&self.embd.try_gather(n_embd, &block)?)?;
3613 let pos_block: Vec<i32> = ((start as i32)..(start + b) as i32).collect();
3614 let dh = draft.forward_round(e, &mut dkv, &noise, &pos_block)?;
3615 // Harvest: logits over rows r0..r0+nd (Dflash: mask rows 1..b-1, fill
3616 // semantics; Dspark: ALL b rows, shifted semantics — row k predicts
3617 // anchor+k+1, so col k of `dl` is the draft for position start+k+1).
3618 let mut rows = e.uninit(nd * n_embd)?;
3619 {
3620 let dv = e.view(&dh, b * n_embd);
3621 let src = dv.slice(r0 * n_embd..(r0 + nd) * n_embd);
3622 e.copy_view_into(&mut rows, 0, &src, nd * n_embd)?;
3623 }
3624 // TRIMMED DRAFT HEAD (lane/dflash2-head-trim, 2026-08-25): DFlash2 family
3625 // only — the selector consumes (value, candidate-id) pairs, so a d2t remap
3626 // after top-k restores true ids; the markov/chain arms argmax dl columns
3627 // into token ids DIRECTLY and must keep the full head. Reuses the FR-Spec
3628 // self-trim the load path builds on the MTP struct (MEMRA_FRSPEC_TRIM):
3629 // gathered rows of the target's own head, zero requant. Verify stays
3630 // full-vocab, so the trim moves draft acceptance only, never output.
3631 let trim = if draft.dflash2.is_some() {
3632 self.mtp
3633 .as_ref()
3634 .filter(|m| m.d2t_from_target_head)
3635 .and_then(|m| m.shared_head_head.as_ref().zip(m.d2t.as_ref()))
3636 // MEMRA_MTP_SKIP stub: the same target-head trimmed rows, parked in
3637 // `dflash_trim` because the embedded MTP block was skipped (hybrid.rs;
3638 // rows are target-head by construction; the loader refuses otherwise).
3639 .or_else(|| self.dflash_trim.as_ref().map(|t| (&t.head, &t.d2t)))
3640 .filter(|(_, d2t)| !d2t.is_empty())
3641 } else {
3642 None
3643 };
3644 let (dl_head, dl_vocab) = match trim {
3645 Some((head, d2t)) => (head, d2t.len()),
3646 None => (&self.output, n_vocab),
3647 };
3648 let trim_d2t = trim.map(|(_, d2t)| d2t.as_slice());
3649 let mut dl = e.matmul(dl_head, &rows, nd)?;
3650 // Family/sampling-keyed proposal (v0.100 train merge of the port and H4/
3651 // engine-bundle stacks — BOTH programs preserved):
3652 // - SAMPLED (sp_on): rejection-sampling proposal, records the true per-slot
3653 // q (family-keyed inside: selector for DFlash2, markov-corrected rows
3654 // otherwise). Host CDF/readback syncs inside — slice-2 deferral N/A.
3655 // - DFlash2 greedy: the candidate path selector REPLACES the markov chain
3656 // (reference DFlash2DraftModel.propose — greedy arm).
3657 // - markov/plain greedy chain: the engine-bundle arm; slice-2 readback
3658 // deferral decided below (needs the ckpt arm reads).
3659 // Confidence policy: stash each slot's markov prev-token embedding (the
3660 // exact `w1` row the chain gathers) into a [nd, rank] buffer — d2d async,
3661 // read back beside `rows` in one host sync after the chain.
3662 let want_conf_emb = vt_policy.is_confidence()
3663 && draft.confidence.as_ref().is_some_and(|ch| ch.with_markov);
3664 let mut conf_emb: Option<CudaSlice<f32>> = match (&draft.markov, want_conf_emb) {
3665 (Some(mk), true) => Some(e.uninit(nd * mk.rank)?),
3666 (None, true) => unreachable!(
3667 "with_markov confidence head without a markov table — the loader forbids it"
3668 ),
3669 _ => None,
3670 };
3671 let mut cand: Vec<u32> = Vec::with_capacity(nd + 1);
3672 let mut prop: Option<DsparkDraftSample> = None;
3673 let mut chain_dev: Option<CudaSlice<u32>> = None;
3674 if let Some(sp) = sp_on {
3675 let (tail, ds) = draft.dspark_propose_sampled(
3676 e,
3677 &mut dl,
3678 &rows,
3679 nd,
3680 dl_vocab,
3681 last,
3682 sp,
3683 &mut sctr,
3684 &mut uctr,
3685 conf_emb.as_mut(),
3686 trim_d2t,
3687 )?;
3688 drop(exact_scope);
3689 cand.push(last);
3690 cand.extend_from_slice(&tail);
3691 prop = Some(ds);
3692 } else if draft.dflash2.is_some() {
3693 let path =
3694 draft.dflash2_propose_greedy(e, &dl, &rows, nd, dl_vocab, last, trim_d2t)?;
3695 drop(exact_scope);
3696 cand.push(last);
3697 cand.extend_from_slice(&path);
3698 } else {
3699 let markov_on = std::env::var("MEMRA_DFLASH_MARKOV").as_deref() != Ok("0");
3700 let mut chain_d = e.stream().alloc_zeros::<u32>(nd + 1)?;
3701 if let (Some(mk), true) = (&draft.markov, markov_on) {
3702 e.set_u32_one(&mut chain_d, last)?;
3703 for k in 0..nd {
3704 let mut f = e.uninit(mk.rank)?;
3705 e.gather_row_bf16(&mk.w1_bf16, &chain_d, k, &mut f, mk.rank)?;
3706 if let Some(ce) = conf_emb.as_mut() {
3707 let fv = e.view(&f, mk.rank);
3708 e.copy_view_into(ce, k * mk.rank, &fv, mk.rank)?;
3709 }
3710 let bias = e.matmul(&mk.w2, &f, 1)?;
3711 e.add_row_inplace(&mut dl, &bias, n_vocab, k * n_vocab)?;
3712 e.argmax_token_device_col(&dl, k, n_vocab, &mut chain_d, k + 1)?;
3713 }
3714 } else {
3715 if want_conf_emb {
3716 // chain_d[0] must carry the anchor — slot 0's prev token.
3717 e.set_u32_one(&mut chain_d, last)?;
3718 }
3719 for i in 0..nd {
3720 if let (Some(ce), Some(mk)) = (conf_emb.as_mut(), &draft.markov) {
3721 let mut f = e.uninit(mk.rank)?;
3722 e.gather_row_bf16(&mk.w1_bf16, &chain_d, i, &mut f, mk.rank)?;
3723 let fv = e.view(&f, mk.rank);
3724 e.copy_view_into(ce, i * mk.rank, &fv, mk.rank)?;
3725 }
3726 e.argmax_token_device_col(&dl, i, n_vocab, &mut chain_d, i + 1)?;
3727 }
3728 }
3729 drop(exact_scope);
3730 chain_dev = Some(chain_d);
3731 }
3732 // MEMRA_DSPARK_CKPT (default 1): verify with the MTP column-stash armed so a
3733 // partial accept restores state directly. =0 keeps the snapshot+replay arm
3734 // (the oracle the stash arm is gated against — MEMRA_DSPARK_CKPT_GATE=1 runs
3735 // BOTH per partial round and byte-compares the resulting cache state).
3736 // Read here (was at the verify site) — slice 2's deferral needs the arm
3737 // choice before deciding whether the chain readback can move past verify.
3738 let ckpt_on = std::env::var("MEMRA_DSPARK_CKPT").as_deref() != Ok("0");
3739 let ckpt_gate = std::env::var("MEMRA_DSPARK_CKPT_GATE").as_deref() == Ok("1");
3740 // SAMPLED x ckpt-gate refusal: the gate compares verify argmaxes across a
3741 // replay — a greedy-exactness instrument (port lane). Refuse loudly.
3742 if sp_on.is_some() && ckpt_gate {
3743 return Err(
3744 "MEMRA_DSPARK_CKPT_GATE compares verify argmaxes across a replay \
3745 — a greedy-exactness instrument; unset it for T>0 dspark rounds"
3746 .into(),
3747 );
3748 }
3749 // Slice 2: under the stash/gate arms with a resident embed table, the GREEDY
3750 // chain readback is DEFERRED past verify dispatch and merged with the argmax
3751 // readback into one sync. The replay arm (CKPT=0) verifies host tokens and
3752 // keeps the legacy order; the sampled and DFlash2 proposals already synced
3753 // at the walk (chain_dev is None there).
3754 let deferred = chain_dev.is_some() && embd_gpu.is_some() && (ckpt_on || ckpt_gate);
3755 // ---- H4 confidence window: size THIS round's verify from the head ----
3756 if vt_policy.is_confidence() {
3757 let ch = draft.confidence.as_ref().expect("asserted at loop entry");
3758 let (rows_h, emb_h) = match conf_emb.as_ref() {
3759 Some(ce) => {
3760 let (a, b2) = e.dtoh_pair(&rows, ce)?;
3761 (a, Some(b2))
3762 }
3763 None => (e.dtoh(&rows)?, None),
3764 };
3765 let rank = draft.markov.as_ref().map(|m| m.rank).unwrap_or(0);
3766 let mut raws = Vec::with_capacity(nd);
3767 for k in 0..nd {
3768 let hrow = &rows_h[k * n_embd..(k + 1) * n_embd];
3769 let emb = emb_h.as_ref().map(|eh| &eh[k * rank..(k + 1) * rank]);
3770 raws.push(ch.raw_score(hrow, emb));
3771 }
3772 vt = vt_policy
3773 .size_window(&raws, vt_cap)
3774 .expect("confidence policies always size the window");
3775 }
3776 // Verify candidates: [anchor, draft 1..nd]. Under Dflash this is the
3777 // historical `block` content; under Dspark it is one longer than the
3778 // drafter's input block (nd = b drafts + the anchor). The sampled/DFlash2
3779 // proposals built `cand` at the walk; deferred greedy rounds build it after
3780 // the merged readback — the bytes are identical (chain_d is written before
3781 // either sync).
3782 if let Some(chain_d) = chain_dev.as_ref()
3783 && !deferred
3784 {
3785 let chain = e.dtoh_u32(chain_d)?;
3786 cand.push(last);
3787 cand.extend_from_slice(&chain[1..]);
3788 }
3789 ns_draft += clock(stats, e).duration_since(t0).as_nanos() as u64;
3790
3791 // ---- snapshot (GDN conv/ssm state + KV lens), then verify t=vt ----
3792 let t1 = std::time::Instant::now();
3793 // Slice 1: batched snap (one table refresh + two copy launches) with the
3794 // legacy per-layer snapshot as the kill-switch / non-uniform fallback.
3795 let mut snap_legacy: Option<crate::cache::CacheSnapshot> = None;
3796 if !snapb_off && snapb.is_none() {
3797 snapb = DsparkSnapBatch::new(e, &cache)?;
3798 snapb_off = snapb.is_none();
3799 } else if let Some(sb) = snapb.as_mut() {
3800 sb.refresh(e, &cache)?;
3801 }
3802 let snap: &crate::cache::CacheSnapshot = match snapb.as_ref() {
3803 Some(sb) => &sb.snap,
3804 None => {
3805 snap_legacy = Some(cache.snapshot(e)?);
3806 snap_legacy.as_ref().unwrap()
3807 }
3808 };
3809 let _ = &snap_legacy;
3810 ns_snap += clock(stats, e).duration_since(t1).as_nanos() as u64;
3811 let t2 = std::time::Instant::now();
3812 // Slice 3: the tap-sink buffer is persistent per vt in the graphs ctx
3813 // (captured segments bake its address); fully rewritten by every verify.
3814 let tap_buf = match vgraphs.as_mut().and_then(|g| g.tap_bufs.remove(&vt)) {
3815 Some(buf) => buf,
3816 None => e.uninit(vt * n_taps * n_embd)?,
3817 };
3818 cache.dflash_taps = Some(DflashTapSink {
3819 layer_ids: c.target_layer_ids.clone(),
3820 buf: tap_buf,
3821 hidden: n_embd,
3822 t: vt,
3823 base: 0,
3824 });
3825 // The whole fallible verify window runs inside a closure so the Err path can
3826 // return the sink buffer to the ctx pool before propagating (v0.98 review
3827 // carry-over): five `?`s span the window, and an early return would drop
3828 // `cache.dflash_taps` — freeing the buffer whose ADDRESS the model-persistent
3829 // captured graphs bake, so the next generation's replayed tap copies would
3830 // write freed memory. The never-orphan invariant below now holds on EVERY
3831 // exit, not just the EOS/budget break.
3832 let verify_res = (|cache: &mut crate::cache::Cache,
3833 cand: &mut Vec<u32>,
3834 vgraphs: &mut Option<crate::spec::DsparkVerifyGraphs>|
3835 -> Result<
3836 (
3837 Vec<u32>,
3838 Option<CudaSlice<f32>>,
3839 Option<crate::spec::DsparkVerifyCkpt>,
3840 ),
3841 Box<dyn std::error::Error>,
3842 > {
3843 if sp_on.is_some() {
3844 // SAMPLED: keep the raw verify logits — the accept walk gathers
3845 // filtered p from them (argmaxes are the greedy arm's instrument,
3846 // not this one's).
3847 if ckpt_on {
3848 let (tl, vck) =
3849 self.dspark_verify_t_logits_ckpt(e, &cand[..vt], start, cache)?;
3850 Ok((Vec::new(), Some(tl), Some(vck)))
3851 } else {
3852 Ok((
3853 Vec::new(),
3854 Some(self.dspark_verify_t_logits(e, &cand[..vt], start, cache)?),
3855 None,
3856 ))
3857 }
3858 } else if deferred {
3859 // Slice 2: verify embeds the DEVICE chain (cand layout by construction:
3860 // chain_d[0] = anchor, chain_d[1..] = drafts), then ONE host sync reads
3861 // chain + verify argmaxes together — the host dispatched snap + all of
3862 // verify while the draft was still executing.
3863 let chain_d = chain_dev.as_ref().expect("deferred implies greedy chain");
3864 let g = embd_gpu.expect("deferred implies resident embed");
3865 let (am_d, vck) = self.dspark_verify_t_am_ckpt_dev(
3866 e,
3867 chain_d,
3868 vt,
3869 start,
3870 cache,
3871 (g, embd_qt, embd_rb),
3872 vgraphs.as_mut(),
3873 )?;
3874 let ch = e.stream().clone_dtoh(chain_d)?;
3875 let am = e.stream().clone_dtoh(&am_d)?;
3876 e.stream().synchronize()?;
3877 cand.push(last);
3878 cand.extend_from_slice(&ch[1..]);
3879 Ok((am, None, Some(vck)))
3880 } else if ckpt_on || ckpt_gate {
3881 let (vam, vck) = self.dspark_verify_t_am_ckpt(e, &cand[..vt], start, cache)?;
3882 Ok((vam, None, Some(vck)))
3883 } else {
3884 Ok((
3885 self.dspark_verify_t_am(e, &cand[..vt], start, cache)?,
3886 None,
3887 None,
3888 ))
3889 }
3890 })(&mut cache, &mut cand, vgraphs);
3891 let (vam, tl, vck) = match verify_res {
3892 Ok(v) => v,
3893 Err(err) => {
3894 if let (Some(g), Some(taps)) = (vgraphs.as_mut(), cache.dflash_taps.take()) {
3895 g.tap_bufs.insert(vt, taps.buf);
3896 }
3897 return Err(err);
3898 }
3899 };
3900 let taps = cache.dflash_taps.take().unwrap();
3901 // Return the tap buffer to the ctx pool IMMEDIATELY — an EOS/budget break
3902 // between accept and ingest must never orphan an address the captured
3903 // graphs bake (the next generation would alloc a fresh buffer and the
3904 // replayed tap copies would write freed memory). Ingest reads it borrowed.
3905 let tap_local: Option<CudaSlice<f32>> = match vgraphs.as_mut() {
3906 Some(g) => {
3907 g.tap_bufs.insert(vt, taps.buf);
3908 None
3909 }
3910 None => Some(taps.buf),
3911 };
3912 let tap_ref: &CudaSlice<f32> = match &tap_local {
3913 Some(b) => b,
3914 None => &vgraphs.as_ref().expect("ctx present above").tap_bufs[&vt],
3915 };
3916 ns_verify += clock(stats, e).duration_since(t2).as_nanos() as u64;
3917
3918 // ---- accept ----
3919 // Penalized-sampled: the anchor `last` is committed THIS round unconditionally
3920 // (the out.push below), so it joins the window before the accept walk — verify
3921 // row 0's state includes it. Accepted drafts extend the window after the walk;
3922 // `next` joins as the anchor of ITS round.
3923 if pen_on {
3924 pen_hist.push(last);
3925 }
3926 let (m, next) = match (sp_on, tl.as_ref()) {
3927 (Some(sp), Some(tl)) => {
3928 let w0 = pen_hist
3929 .len()
3930 .saturating_sub(sp.penalty_last_n.min(crate::spec::PEN_WINDOW_MAX));
3931 dspark_accept_sampled(
3932 e,
3933 tl,
3934 &cand,
3935 vt,
3936 n_vocab,
3937 &dl,
3938 prop.as_ref()
3939 .expect("sampled round without a proposal record"),
3940 sp,
3941 &pen_hist[w0..],
3942 &mut sctr,
3943 &mut uctr,
3944 )?
3945 }
3946 _ => {
3947 let m = dspark_accept_prefix(&cand, &vam, vt);
3948 (m, vam[m])
3949 }
3950 };
3951 if pen_on {
3952 pen_hist.extend_from_slice(&cand[1..=m]);
3953 }
3954 attempted += vt - 1;
3955 accepted += m;
3956 out.push(last);
3957 if eos.contains(&last) {
3958 break 'outer;
3959 }
3960 if emit_accepted_run(&mut out, &cand[1..=m], eos, max_new) {
3961 break 'outer;
3962 }
3963
3964 // ---- commit/rollback: hybrid state cannot truncate — restore + replay kept ----
3965 let keep = m + 1;
3966 let t3 = std::time::Instant::now();
3967 // Slice 3: rounds whose linear column stash lives in the graphs ctx's slabs
3968 // commit through the slab twin (same semantics, slab-addressed sources).
3969 let slab_commit = vgraphs.as_ref().map(|g| g.round_slab).unwrap_or(false);
3970 if keep < vt {
3971 if ckpt_gate {
3972 // GATE ARM: stash-restore, snapshot S1; then the replay oracle, snapshot
3973 // S2; the two cache states must match BIT-FOR-BIT (kv lens, pos, every
3974 // conv/ssm buffer). Continue from the replay state (proven identical).
3975 if slab_commit {
3976 self.dspark_commit_prefix_slab(
3977 e,
3978 &mut cache,
3979 snap,
3980 vgraphs.as_ref().expect("slab_commit implies ctx"),
3981 keep,
3982 )?;
3983 } else {
3984 let vck = vck.as_ref().expect("gate arm always fills the ckpt");
3985 self.dspark_commit_prefix(e, &mut cache, snap, vck, keep)?;
3986 }
3987 // host-side state capture (NO device snapshot copies — two extra
3988 // device snapshots per round OOM'd beside the 15GB trunk)
3989 #[allow(clippy::type_complexity)]
3990 // allow: one-shot composite type; naming it would hide the shape that matters at the call site
3991 let capture = |cache: &Cache| -> Result<
3992 (usize, Vec<Option<usize>>, Vec<(Vec<f32>, Vec<f32>)>),
3993 Box<dyn std::error::Error>,
3994 > {
3995 let mut lens = Vec::new();
3996 let mut states = Vec::new();
3997 for il in 0..cache.kv.len() {
3998 lens.push(cache.kv[il].as_ref().map(|k| k.len));
3999 if let Some(rl) = &cache.recur[il] {
4000 states.push((e.dtoh(&rl.conv_state)?, e.dtoh(&rl.ssm_state)?));
4001 }
4002 }
4003 Ok((cache.pos, lens, states))
4004 };
4005 let (p1, l1, st1) = capture(&cache)?;
4006 crate::pp::restore_cache_checkpoint(e, self, None, &mut cache, snap)?;
4007 let ram = self.dspark_verify_t_am(e, &cand[..keep], start, &mut cache)?;
4008 assert_eq!(
4009 &ram[..],
4010 &vam[..keep],
4011 "prefix replay must reproduce the verify argmaxes"
4012 );
4013 let (p2, l2, st2) = capture(&cache)?;
4014 assert_eq!(p1, p2, "ckpt-gate: pos mismatch");
4015 assert_eq!(l1, l2, "ckpt-gate: kv_len mismatch");
4016 for (il, ((c1, s1v), (c2, s2v))) in st1.iter().zip(&st2).enumerate() {
4017 let bits = |a: &[f32], b: &[f32]| {
4018 a.iter().zip(b).all(|(x, y)| x.to_bits() == y.to_bits())
4019 };
4020 assert!(
4021 bits(c1, c2),
4022 "ckpt-gate: linear layer {il} conv state differs"
4023 );
4024 assert!(
4025 bits(s1v, s2v),
4026 "ckpt-gate: linear layer {il} ssm state differs"
4027 );
4028 }
4029 } else if slab_commit {
4030 // STASH ARM, slab twin (slice 3): same restore, slab-addressed.
4031 self.dspark_commit_prefix_slab(
4032 e,
4033 &mut cache,
4034 snap,
4035 vgraphs.as_ref().expect("slab_commit implies ctx"),
4036 keep,
4037 )?;
4038 } else if let Some(vck) = vck.as_ref() {
4039 // STASH ARM (default): column-state restore, no replay forward.
4040 self.dspark_commit_prefix(e, &mut cache, snap, vck, keep)?;
4041 } else {
4042 // REPLAY ARM (MEMRA_DSPARK_CKPT=0): the original snapshot+replay oracle.
4043 crate::pp::restore_cache_checkpoint(e, self, None, &mut cache, snap)?;
4044 debug_assert_eq!(cache.pos, start, "rollback landed off the round start");
4045 let ram = self.dspark_verify_t_am(e, &cand[..keep], start, &mut cache)?;
4046 if sp_on.is_none() {
4047 // the argmax-reproduction oracle is greedy-only; the sampled arm
4048 // replays purely to rebuild the cache state.
4049 debug_assert_eq!(
4050 &ram[..],
4051 &vam[..keep],
4052 "prefix replay must reproduce the verify argmaxes"
4053 );
4054 }
4055 }
4056 }
4057 ns_roll += clock(stats, e).duration_since(t3).as_nanos() as u64;
4058
4059 // ---- ingest the kept rows' ctx features into the draft KV ----
4060 let t4 = std::time::Instant::now();
4061 {
4062 let tv = e.view(tap_ref, vt * n_taps * n_embd);
4063 let keep_view = tv.slice(0..keep * n_taps * n_embd);
4064 let mut kept = e.uninit(keep * n_taps * n_embd)?;
4065 e.copy_view_into(&mut kept, 0, &keep_view, keep * n_taps * n_embd)?;
4066 let f = draft.ctx_features(e, &kept, keep)?;
4067 let pos_k: Vec<i32> = ((ctx_len as i32)..(ctx_len + keep) as i32).collect();
4068 draft.ingest_ctx(e, &mut dkv, &f, &pos_k, keep)?;
4069 ctx_len += keep;
4070 }
4071 ns_ingest += clock(stats, e).duration_since(t4).as_nanos() as u64;
4072 last = next;
4073 // Ladder update only — under the confidence policies vt is recomputed
4074 // from the head every round, post-draft pre-verify.
4075 if !vt_policy.is_confidence() && adapt {
4076 vt = (m + 2).clamp(3, vt_cap);
4077 }
4078 }
4079 if stats {
4080 let ms = |n: u64| n as f64 / 1e6;
4081 eprintln!(
4082 "[dspark-q38] acceptance {accepted}/{attempted} = {:.3} rounds={rounds} \
4083 draft={:.1}ms snap={:.1}ms verify={:.1}ms rollback+replay={:.1}ms ingest={:.1}ms",
4084 accepted as f64 / attempted.max(1) as f64,
4085 ms(ns_draft),
4086 ms(ns_snap),
4087 ms(ns_verify),
4088 ms(ns_roll),
4089 ms(ns_ingest)
4090 );
4091 }
4092 Ok(out)
4093 }
4094}
4095
4096// ================= DSpark SERVING session (lane/dspark-q38-recover serve route) =========
4097// Burst-scoped state for the worker's dspark spec arm — the qwen-hybrid twin of
4098// GemmaSpecSession. Holds the trunk cache + draft KV + the round loop's carry state
4099// (`last`, ctx_len, adaptive vt) so the scheduler round-robins other sessions between
4100// bursts. The round body is generate_spec_dspark's loop, hoisted; that bin arm stays the
4101// banked oracle (E2E gate), and the serve-route smoke gates this twin byte-identical to
4102// a spec-off boot over the real HTTP surface. Exactness contract unchanged: the target's
4103// verify argmax decides every committed token, so the stream equals plain greedy BY
4104// CONSTRUCTION on every accept path (ckpt stash, gate, replay).
4105fn take_dspark_prefix_capture(
4106 slot: &mut Option<crate::spec::SpecBoundaryCapture>,
4107) -> Option<crate::spec::SpecBoundaryCapture> {
4108 slot.take()
4109}
4110
4111/// Deterministic preflight for the serving session's prompt-headroom requirement. Kept pure so
4112/// the worker can make the same decision before choosing whether to consume a prefix entry.
4113pub fn dspark_spec_prompt_fits(
4114 prompt_len: usize,
4115 ctx_cap: usize,
4116 block_size: usize,
4117 sliding_window: usize,
4118 is_dflash2: bool,
4119) -> bool {
4120 // PRIME FLOOR (incident 2026-08-25, second hit — the one that actually took prod down
4121 // twice). This predicate is the ONE admission gate the worker consumes for the dspark
4122 // route, and it only ever checked the ctx CEILING. A prompt shorter than
4123 // `PRIME_MIN_T` was therefore admitted and then panicked inside the cold prime, because
4124 // `prime_cache`'s batched arm asserts `T >= PRIME_MIN_T` and has no tokenwise twin that
4125 // fills the DFlash tap sink. A panic there is not a failed request: the GPU worker
4126 // exits 70 (poisoned-context contract) and every live session on the box dies, then the
4127 // guard relaunches into the same prompt — 20 panics and ~5 min of edge 502s on box10,
4128 // and a second loop on BOTH boxes when the route was redeployed. The trigger is
4129 // ordinary traffic: "Say OK." is 5 tokens, and our own watchdog sends that class.
4130 // Below the floor the route simply declines and the request serves on the plain path.
4131 if prompt_len < crate::hybrid_forward::PRIME_MIN_T {
4132 return false;
4133 }
4134 let max_ctx = if is_dflash2 {
4135 ctx_cap
4136 } else {
4137 ctx_cap.min(sliding_window)
4138 };
4139 prompt_len
4140 .checked_add(block_size)
4141 .and_then(|n| n.checked_add(8))
4142 .is_some_and(|need| need <= max_ctx)
4143}
4144
4145pub struct DsparkSpecSession {
4146 pub cache: crate::cache::Cache,
4147 /// One-shot prompt-end state for the worker's cross-request prefix cache. DFlash has no
4148 /// restorable draft plane, so this capture deliberately carries trunk snapshot + logits
4149 /// only; low-load DFlash requests ignore the resulting trunk-only entry while a later
4150 /// shed-to-plain request can consume it.
4151 prefix_capture: Option<crate::spec::SpecBoundaryCapture>,
4152 dkv: DflashKv,
4153 last: u32,
4154 ctx_len: usize,
4155 vt: usize,
4156 pub rounds: usize,
4157 max_ctx: usize,
4158 done: bool,
4159 /// Engine-bundle slice 1: persistent batched snapshot (buffers + pointer tables live
4160 /// with the session so bursts reuse them). None until the first round; stays None —
4161 /// legacy per-layer snapshot — when `snapb_off`.
4162 snapb: Option<DsparkSnapBatch>,
4163 snapb_off: bool,
4164 /// SAMPLED ADMISSION (T>0, lane/dspark-sampled-admission-20260820): the request's
4165 /// sampling config (None/temp==0 = the greedy route, byte-identical). Fixed for the
4166 /// session — the worker's admission owns the sampler identity.
4167 sampling: Option<crate::spec::SpecSampling>,
4168 /// Philox event counters, session-owned so randomness never repeats across bursts
4169 /// (the frspec session-continuity law): `sctr` = device sampling events (boundary,
4170 /// draft chain, bonus, residual), `uctr` = host uniforms (selector walk, accept tests).
4171 sctr: u32,
4172 uctr: u32,
4173 /// Penalized-sampled window (lane/dspark-penalized-sampled-20260821): seeded from
4174 /// the prompt tail (`pen_window_seed`), extended with every committed token, carried
4175 /// across bursts so a burst boundary never resets the stream the client asked us to
4176 /// penalize. Empty (and never touched) when the request carries no penalties.
4177 pen_hist: Vec<u32>,
4178}
4179
4180fn dspark_commit_limit(
4181 accepted_keep: usize,
4182 burst_out_len: usize,
4183 request_room: usize,
4184) -> (usize, bool) {
4185 let public_room = request_room.saturating_sub(burst_out_len);
4186 debug_assert!(public_room > 0);
4187 let keep = accepted_keep.min(public_room);
4188 (keep, keep < accepted_keep)
4189}
4190
4191impl DsparkSpecSession {
4192 /// How many trailing draft-KV rows a restore must carry for the drafter to be
4193 /// indistinguishable from one that cold-primed: the sliding window plus one block.
4194 ///
4195 /// WHY A TAIL IS SUFFICIENT, and why this is a fact about THIS export rather than a hope:
4196 /// every DFlash2 draft layer is `sliding_attention` (the port asserts
4197 /// `cfg.layer_sliding.iter().all(|&s| s)` at load and refuses otherwise), so the windowed
4198 /// SDPA never reads a key below the current block's window floor
4199 /// (`sdpa_naive_w_lo`, whose bit-identity at Tkv 4104 and legacy launch failure are both
4200 /// pinned by kernel_check). A round at context `pos` therefore reads rows
4201 /// `[pos - window + 1, pos + block)` and nothing older. Storing that tail is storing
4202 /// everything the drafter can observe.
4203 ///
4204 /// SIZE, the reason this is affordable at all: 5 layers x (2048 + 16) rows x 8 kv x 128
4205 /// dim x 4 B x 2 (k+v) is ~85 MB, against ~1,057 MB for the trunk planes of a
4206 /// 30k-token entry. Storing the FULL draft history instead would be ~1,229 MB — more than
4207 /// the trunk entry itself — which is what makes the tail the only viable form.
4208 pub fn draft_tail_rows(&self) -> usize {
4209 self.dkv.cfg_window_rows()
4210 }
4211
4212 /// The drafter's KV, for a worker publishing the tail into its cross-request prefix cache.
4213 pub fn draft_kv(&self) -> &DflashKv {
4214 &self.dkv
4215 }
4216}
4217
4218/// The tail-import refusal arms, PURE so they are testable without CUDA. These are the fence
4219/// in front of the deliberate uninitialised-rows-below-`base` design: rows the import does not
4220/// copy are unreadable ONLY if the tail actually covers the drafter's window ending exactly at
4221/// the logical length — every arm here is what makes that "only if" hold. A refusal that
4222/// silently stopped firing would let a session attend garbage without crashing, which is the
4223/// silent-quality-loss class, so each arm names itself.
4224///
4225/// `tail_floor` (lane/spec-exclusions-20260902): the EXPORTER's context floor, `0` for
4226/// every tail a cold-primed drafter publishes (the pre-lane rule verbatim). A floor-bearing
4227/// exporter (`DflashKv::new_cold_at`, or itself a short-tail import) never owned rows below
4228/// it, so its tail legitimately covers only `[floor, len)`; the coverage rule then asks for
4229/// everything readable ABOVE the floor. A short tail whose exporter had NO floor is still the
4230/// refusal it always was.
4231#[allow(clippy::too_many_arguments)]
4232pub fn tail_geometry_ok(
4233 tail_layers: usize,
4234 tail_row_bytes: usize,
4235 tail_base: usize,
4236 tail_rows: usize,
4237 tail_len: usize,
4238 tail_floor: usize,
4239 kv_layers: usize,
4240 kv_row_bytes: usize,
4241 kv_window_rows: usize,
4242 cap: usize,
4243) -> Result<(), &'static str> {
4244 if tail_layers != kv_layers {
4245 return Err("layer count differs from the live drafter");
4246 }
4247 if tail_row_bytes != kv_row_bytes {
4248 return Err("row geometry differs from the live drafter");
4249 }
4250 if tail_len > cap {
4251 return Err("logical length exceeds the session cap");
4252 }
4253 if tail_base + tail_rows != tail_len {
4254 return Err("tail does not end at its own logical length");
4255 }
4256 if tail_floor > tail_base {
4257 return Err("tail starts below its exporter's context floor");
4258 }
4259 // The whole point of the tail: it must cover everything a round can read. A shorter
4260 // tail than the window is only acceptable when the tail IS the entire history above the
4261 // exporter's floor (floor 0: the entire history).
4262 if tail_rows < kv_window_rows.min(tail_len - tail_floor) {
4263 return Err("tail shorter than the drafter's readable window");
4264 }
4265 Ok(())
4266}
4267
4268/// A DFlash draft-KV tail, per drafter layer, ready to ride a cross-request prefix-cache
4269/// entry: `(k, v)` f32 rows covering absolute positions `[base, base + rows)`.
4270///
4271/// Only the tail travels, and that is a fact about this export rather than an optimisation:
4272/// every DFlash2 draft layer is `sliding_attention` (the port asserts it at load), so a round
4273/// at context `pos` reads rows `[pos - window + 1, pos + block)` and nothing older. Storing
4274/// the whole history for a 30k-token prompt would be ~1,229 MB — MORE than the ~1,057 MB of
4275/// trunk planes it would ride with; the tail is ~85 MB.
4276pub struct DflashKvTail {
4277 pub layers: Vec<(CudaSlice<f32>, CudaSlice<f32>)>,
4278 /// Absolute position of the first stored row.
4279 pub base: usize,
4280 /// Rows stored per layer.
4281 pub rows: usize,
4282 /// Logical length the KV had when exported (`= pos`), so an import can restore the same
4283 /// absolute row addressing the rope positions were baked against.
4284 pub len: usize,
4285 /// Bytes per row per layer, carried so an import cannot disagree about the geometry.
4286 pub row_bytes: usize,
4287 /// The exporter's context floor (`DflashKv::floor`): `0` for every tail a cold-primed
4288 /// drafter publishes; the first row the exporter ever owned otherwise. Travels with the
4289 /// tail so `tail_geometry_ok` can tell a legitimately short tail (nothing below the
4290 /// floor ever existed) from a truncated one, and so the import inherits the floor.
4291 pub floor: usize,
4292}
4293
4294impl DflashKvTail {
4295 pub fn bytes(&self) -> usize {
4296 self.layers.len() * self.rows * self.row_bytes * 2
4297 }
4298}
4299
4300impl DflashKv {
4301 /// Copy out the readable tail ending at `upto` (see `DflashKvTail`). `None` when there is
4302 /// nothing to publish or an allocation fails — publication is always optional.
4303 ///
4304 /// `upto` IS NOT `self.len`, and conflating them was the bug the first exactness-gate run
4305 /// caught: publication happens at the scheduler's drain sweep, by which time the session
4306 /// has committed generated rows, so `len` had run 35 rows past the capture boundary and
4307 /// every restore was refused with `draft KV len 30364 != prompt 30329`. The trunk planes
4308 /// are copied at the capture `pos` for the same reason; the tail must agree with them.
4309 pub fn export_tail(&self, e: &Engine, upto: usize) -> Option<DflashKvTail> {
4310 if upto == 0 || upto > self.len {
4311 return None;
4312 }
4313 let rowsz = self.row_bytes / std::mem::size_of::<f32>();
4314 // Never below the floor: a floor-bearing KV owns no rows there (doc on `floor`), and
4315 // a tail with nothing above the floor has nothing to publish.
4316 let floor = self.floor.min(upto);
4317 let rows = self.window_rows.min(upto - floor);
4318 if rows == 0 {
4319 return None;
4320 }
4321 let base = upto - rows;
4322 let mut layers = Vec::with_capacity(self.k.len());
4323 for li in 0..self.k.len() {
4324 let (Ok(mut k), Ok(mut v)) = (e.uninit(rows * rowsz), e.uninit(rows * rowsz)) else {
4325 return None;
4326 };
4327 if e.copy_range_into(&mut k, 0, &self.k[li], base * rowsz, rows * rowsz)
4328 .is_err()
4329 || e.copy_range_into(&mut v, 0, &self.v[li], base * rowsz, rows * rowsz)
4330 .is_err()
4331 {
4332 return None;
4333 }
4334 layers.push((k, v));
4335 }
4336 Some(DflashKvTail {
4337 layers,
4338 base,
4339 rows,
4340 len: upto,
4341 row_bytes: self.row_bytes,
4342 floor,
4343 })
4344 }
4345
4346 /// Rebuild a draft KV from a published tail: a fresh allocation at `cap`, the tail copied
4347 /// back to the SAME absolute rows it came from, and `len` restored so the next round
4348 /// addresses positions exactly as a cold-primed session would.
4349 ///
4350 /// Rows below `tail.base` are ZEROED, not left uninitialised. The clipped SDPA never reads
4351 /// below the block's window floor, but the legacy full-scan kernel
4352 /// (`MEMRA_DFLASH2_SDPA_CLIP=0`, the rollback seam) scans EVERY row into the score and the
4353 /// output, relying on masked rows contributing exactly zero — an identity that holds only
4354 /// for finite data (`0.0 * NaN = NaN`, and an uninit K row can produce a NaN score that
4355 /// poisons the softmax sum). Zeros keep that identity on both kernel arms, so a clip
4356 /// rollback on a restore-armed box stays byte-exact instead of decoding silent garbage
4357 /// (review round 3). Rows above `tail.len` stay uninit — equally unwritten and unread in
4358 /// the cold path, so restored matches cold there.
4359 ///
4360 /// This function still REFUSES rather than trusts the window math — if the tail does not
4361 /// cover the window, the caller gets `None` and must cold-prime.
4362 ///
4363 /// FLOOR-BEARING TAILS (lane/spec-exclusions-20260902): a tail whose exporter had a
4364 /// context floor covers only `[floor, len)` and the import inherits `floor = tail.base`
4365 /// (the first row it actually owns), so the round attention never reads the zero rows
4366 /// below it. That needs the clipped kernel (`d2_windowed_attn`), so a floor-bearing tail
4367 /// under `MEMRA_DFLASH2_SDPA_CLIP=0` refuses here by name. Full tails keep `floor = 0`
4368 /// and the pre-lane program exactly.
4369 pub fn from_tail(e: &Engine, cfg: &DflashCfg, cap: usize, tail: &DflashKvTail) -> Option<Self> {
4370 let mut kv = Self::new(e, cfg, cap).ok()?;
4371 if let Err(why) = tail_geometry_ok(
4372 tail.layers.len(),
4373 tail.row_bytes,
4374 tail.base,
4375 tail.rows,
4376 tail.len,
4377 tail.floor,
4378 kv.k.len(),
4379 kv.row_bytes,
4380 kv.window_rows,
4381 cap,
4382 ) {
4383 eprintln!("[dspark] tail import refused: {why}");
4384 return None;
4385 }
4386 // A short tail (rows below the window because the exporter never owned them) makes
4387 // this KV floor-bearing; a full tail from a floored exporter does not need the floor
4388 // (every readable row is present) and keeps the pre-lane program.
4389 let short = tail.rows < kv.window_rows.min(tail.len);
4390 if short && !dflash2_sdpa_clip_on() {
4391 eprintln!(
4392 "[dspark] tail import refused: floor-bearing tail needs the clipped round \
4393 attention (MEMRA_DFLASH2_SDPA_CLIP=0 has no context-floor arm)"
4394 );
4395 return None;
4396 }
4397 let rowsz = kv.row_bytes / std::mem::size_of::<f32>();
4398 for li in 0..kv.k.len() {
4399 let (src_k, src_v) = &tail.layers[li];
4400 if tail.base > 0 {
4401 // Finite zeros below the tail: the legacy full-scan kernel reads these rows
4402 // (see the doc above); NaN in either K or V poisons the row's contribution.
4403 e.memset_zeros_view(&mut kv.k[li].slice_mut(0..tail.base * rowsz))
4404 .ok()?;
4405 e.memset_zeros_view(&mut kv.v[li].slice_mut(0..tail.base * rowsz))
4406 .ok()?;
4407 }
4408 e.copy_range_into(
4409 &mut kv.k[li],
4410 tail.base * rowsz,
4411 src_k,
4412 0,
4413 tail.rows * rowsz,
4414 )
4415 .ok()?;
4416 e.copy_range_into(
4417 &mut kv.v[li],
4418 tail.base * rowsz,
4419 src_v,
4420 0,
4421 tail.rows * rowsz,
4422 )
4423 .ok()?;
4424 }
4425 kv.len = tail.len;
4426 if short {
4427 kv.floor = tail.base;
4428 }
4429 Some(kv)
4430 }
4431
4432 /// Rows a restore must carry (see `DsparkSpecSession::draft_tail_rows`). Stored here
4433 /// because `DflashKv` owns the row geometry; the value comes from the drafter cfg.
4434 pub fn cfg_window_rows(&self) -> usize {
4435 self.window_rows
4436 }
4437
4438 /// Bytes per row per layer (`n_kv * head_dim * 4`), the unit both the export and the
4439 /// import address rows in.
4440 pub fn row_bytes(&self) -> usize {
4441 self.row_bytes
4442 }
4443
4444 /// Number of draft layers, i.e. how many per-layer planes an export produces.
4445 pub fn n_layer(&self) -> usize {
4446 self.k.len()
4447 }
4448}
4449
4450impl DsparkSpecSession {
4451 pub fn cache_max_ctx(&self) -> usize {
4452 self.max_ctx
4453 }
4454 pub fn finished(&self) -> bool {
4455 self.done
4456 }
4457 pub fn pos(&self) -> usize {
4458 self.cache.pos
4459 }
4460 /// Drain the prompt-end prefix capture exactly once. Publication is worker-owned so it can
4461 /// apply namespace isolation, dedupe and the shared byte budget at the scheduler boundary.
4462 pub fn take_prefix_capture(&mut self) -> Option<crate::spec::SpecBoundaryCapture> {
4463 take_dspark_prefix_capture(&mut self.prefix_capture)
4464 }
4465 /// DEMOTION HANDOFF (lane/dspark-spec-gate-demote, 2026-08-24): consume this session and
4466 /// hand its trunk cache + next-token prediction to the plain batched-decode path — the
4467 /// dspark twin of [`crate::spec::SpecSession::into_demoted`].
4468 ///
4469 /// WHY THIS IS EXACT (greedy). The burst-boundary invariant is `cache.pos == prompt rows
4470 /// + emitted tokens`: each round commits exactly `m+1` trunk rows (anchor + accepted
4471 /// drafts) and emits exactly those `m+1` tokens, so every emitted token has its KV row
4472 /// and nothing else does. `last` is the verify argmax at the LAST committed row — and
4473 /// verify-column argmax equality with plain decode is the very property the dspark E2E
4474 /// byte-identity gate pins (`dspark_q38_gate`: ALL EXACT). Handing (cache, last) to the
4475 /// batched path therefore continues the stream from a state indistinguishable from one
4476 /// the batched path produced itself.
4477 ///
4478 /// Unlike the MTP twin there is no carried-pending shape: the round commits its bonus
4479 /// inside the burst, so a session at a burst boundary is ALWAYS in handoff shape. The
4480 /// caller still cross-checks `pos()` against its fed-token count (a budget-clamped
4481 /// overshoot leaves cache rows past the public stream — those sessions finish, never
4482 /// demote). The draft KV, snapshot buffers and philox counters are DROPPED here
4483 /// (freeing their VRAM): the batched path never drafts, and the handoff is one-way.
4484 ///
4485 /// Sampled sessions must not be demoted (the caller excludes them, mirroring the MTP
4486 /// gate): their committed stream depends on the session-owned philox counters, and the
4487 /// plain batched sampler is a different random program mid-request.
4488 pub fn into_demoted(self) -> (crate::cache::Cache, u32) {
4489 (self.cache, self.last)
4490 }
4491}
4492
4493impl crate::hybrid::HybridModel {
4494 /// Turn-1 prime: trunk prefill with taps armed + chunked ctx ingest into the draft KV.
4495 /// Mirrors generate_spec_dspark's prime block exactly (chunk offsets via sink.base are
4496 /// handled inside prime_cache's tick loop; the 256-row ingest chunks match the bin arm).
4497 pub fn dspark_spec_session_new(
4498 &self,
4499 e: &Engine,
4500 draft: &DflashDraft,
4501 prompt: &[u32],
4502 ctx_cap: usize,
4503 sampling: Option<crate::spec::SpecSampling>,
4504 capture_prefix: bool,
4505 ) -> Result<DsparkSpecSession, Box<dyn std::error::Error>> {
4506 use crate::cache::{Cache, DflashTapSink};
4507 assert!(
4508 !self.uses_gemma_program(),
4509 "gemma4 targets use the assistant-drafter route; dspark is the qwen-hybrid arm"
4510 );
4511 // Penalized SAMPLED requests are IN scope (lane/dspark-penalized-sampled-20260821:
4512 // p-side penalties over the true per-state window, q the recorded proposal — the
4513 // accept walk's penalty arm). Penalties at temp==0 stay a LOUD refusal: the greedy
4514 // walk argmaxes RAW columns and would silently drop them — penalized greedy is
4515 // served exactly on the plain path (worker admission owns that exclusion).
4516 if let Some(sp) = sampling.as_ref()
4517 && sp.temp <= 0.0
4518 && sp.pen_on()
4519 {
4520 return Err(
4521 "dspark spec at temp==0 is the greedy route and would silently drop \
4522 the request's penalties; penalized greedy is served on the plain path"
4523 .into(),
4524 );
4525 }
4526 let n_embd = self.cfg.n_embd as usize;
4527 let c = &draft.cfg;
4528 assert_eq!(n_embd, c.hidden, "draft hidden must match target n_embd");
4529 let b = c.block_size;
4530 let n_taps = c.target_layer_ids.len();
4531 // The dspark round is windowless: every position the session will ever hold must
4532 // fit the draft window. Clamp the session ctx to it and refuse prompts that
4533 // cannot take even one round — admission falls back to the plain path.
4534 // DFlash2 rounds implement the reference's symmetric sliding window
4535 // (sdpa_naive_w), so its sessions take the full ctx cap.
4536 let is_dflash2 = draft.dflash2.is_some();
4537 let max_ctx = if is_dflash2 {
4538 ctx_cap
4539 } else {
4540 ctx_cap.min(c.sliding_window)
4541 };
4542 if !dspark_spec_prompt_fits(prompt.len(), ctx_cap, b, c.sliding_window, is_dflash2) {
4543 let need = prompt.len().saturating_add(b).saturating_add(8);
4544 return Err(format!(
4545 "dspark session needs {need} ctx (prompt {} + block {b} + 8), cap {max_ctx}",
4546 prompt.len()
4547 )
4548 .into());
4549 }
4550 let mut cache = Cache::new(e, &self.cfg, max_ctx)?;
4551 let tp = prompt.len();
4552 cache.dflash_taps = Some(DflashTapSink {
4553 layer_ids: c.target_layer_ids.clone(),
4554 buf: e.uninit(tp * n_taps * n_embd)?,
4555 hidden: n_embd,
4556 t: tp,
4557 base: 0,
4558 });
4559 let (logits, _h_seed, _hiddens) = self.prime_cache(e, prompt, &mut cache, 0)?;
4560 // Boundary token: greedy argmax (byte contract) or the request's own filtered
4561 // draw through the session Philox stream (the frspec boundary composition) —
4562 // penalized over the prompt window when the request carries penalties.
4563 let mut sctr0 = 0u32;
4564 let pen_hist: Vec<u32> = match sampling.as_ref().filter(|s| s.temp > 0.0 && s.pen_on()) {
4565 Some(sp) => crate::spec::pen_window_seed(&[], prompt, sp.penalty_last_n),
4566 None => Vec::new(),
4567 };
4568 let last = match sampling.as_ref().filter(|s| s.temp > 0.0) {
4569 Some(sp) => crate::spec::sample_boundary_token(
4570 e,
4571 &logits,
4572 sp,
4573 &pen_hist,
4574 &mut sctr0,
4575 "dspark-prime",
4576 )?,
4577 None => crate::forward::argmax(&logits) as u32,
4578 };
4579 let mut dkv = DflashKv::new(e, &draft.cfg, max_ctx)?;
4580 {
4581 let taps = cache.dflash_taps.take().unwrap();
4582 let n_taps_h = n_taps * n_embd;
4583 let mut r0 = 0usize;
4584 while r0 < tp {
4585 let t_c = (tp - r0).min(256);
4586 let tv = e.view(&taps.buf, tp * n_taps_h);
4587 let win = tv.slice(r0 * n_taps_h..(r0 + t_c) * n_taps_h);
4588 let mut chunk = e.uninit(t_c * n_taps_h)?;
4589 e.copy_view_into(&mut chunk, 0, &win, t_c * n_taps_h)?;
4590 let f = draft.ctx_features(e, &chunk, t_c)?;
4591 let pos_c: Vec<i32> = ((r0 as i32)..(r0 + t_c) as i32).collect();
4592 draft.ingest_ctx(e, &mut dkv, &f, &pos_c, t_c)?;
4593 r0 += t_c;
4594 }
4595 }
4596 e.stream().synchronize()?;
4597 // FULL-PROMPT ONLY. Unlike MTP, DFlash cannot restore its draft plane from a trunk
4598 // prefix, so there is no LCP/message-boundary split arm here. Mandatory draft-KV
4599 // allocation + ingest has already succeeded; the optional snapshot can no longer turn
4600 // a session that would have fit into a draft-allocation failure. Capture remains before
4601 // any speculative burst mutates the recurrent state.
4602 let prefix_capture = if capture_prefix {
4603 cache
4604 .snapshot(e)
4605 .ok()
4606 .map(|snap| crate::spec::SpecBoundaryCapture {
4607 snap,
4608 pos: tp,
4609 logits: logits.clone(),
4610 last_h: Vec::new(),
4611 latent_tails: Vec::new(),
4612 })
4613 } else {
4614 None
4615 };
4616 // Verify carries [anchor, drafts] = up to n_drafts+1 rows (harvest-dependent;
4617 // DSPARK-POSTMORTEM-20260820.md; family-keyed for DFlash2, else checkpoint
4618 // strategy census).
4619 let nd = DsparkHarvest::for_draft(draft).n_drafts(b);
4620 let vt_cap: usize = std::env::var("MEMRA_DFLASH_VERIFY_T")
4621 .ok()
4622 .and_then(|v| v.parse().ok())
4623 .unwrap_or(nd + 1)
4624 .clamp(2, nd + 1);
4625 Ok(DsparkSpecSession {
4626 cache,
4627 prefix_capture,
4628 dkv,
4629 last,
4630 ctx_len: tp,
4631 vt: vt_cap,
4632 rounds: 0,
4633 max_ctx,
4634 done: false,
4635 snapb: None,
4636 snapb_off: !crate::spec::state_copy_batch_on(),
4637 sampling,
4638 sctr: sctr0,
4639 uctr: 0,
4640 pen_hist,
4641 })
4642 }
4643
4644 /// One scheduler burst: dspark rounds until >= `burst_target` tokens are committed,
4645 /// EOS lands, or the ctx cap is reached. `request_room` is the request's remaining
4646 /// public budget, which may be larger than the per-tick scheduler quantum. Returns
4647 /// (tokens, drafted, accepted) for this burst — mid-request quantum overshoot stays
4648 /// public, while only the true request boundary clamps the committed cache prefix.
4649 /// ADMISSION DEBT of this model's verify-graph pool, in bytes (lane/
4650 /// hermes-perf-fixes, 2026-08-23): the projected remaining growth the serve admission
4651 /// gate must reserve so sessions admitted while the pool is cold do not overcommit VRAM
4652 /// the pool will hold (it grows monotonically with no eviction by design — the pool's
4653 /// high-water is per-export and unknown until observed on the serving box; the 1.5 GiB
4654 /// SPEC_SHRINK_RESERVE never covered it). Projection contract and the self-measuring
4655 /// arithmetic live on [`crate::spec::dspark_vg_debt_projection`]; the observed bytes
4656 /// come from the device graph mem pool (`Engine::device_graph_mem_reserved`).
4657 ///
4658 /// CHARGED BY STRUCT, not by which route filled it (lane/graph-launch-guard-sweep-
4659 /// 20260831, fleet-peer refuted-read fix): the MTP spec route's verify-graph door
4660 /// (`MEMRA_SPEC_VERIFY_GRAPH`, family default for GDN+MoE) fills the SAME
4661 /// `dspark_vgraphs` pool with the same monotonic growth, and used to escape charging
4662 /// because the door check named only the dspark flags. 0 when EVERY door is closed
4663 /// (`MEMRA_DSPARK_VERIFY_GRAPH=0` and the MTP door off), frozen
4664 /// (`MEMRA_DSPARK_VG_MAX=0`), or the pool has not captured yet.
4665 pub fn dspark_vg_admission_debt(&self, e: &Engine) -> usize {
4666 let dspark_door =
4667 crate::spec::dspark_verify_graph_serve_on() || crate::spec::dspark_verify_graph_on();
4668 let mtp_door =
4669 crate::spec::spec_verify_graph_env().unwrap_or_else(|| self.vgraph_family_default());
4670 if !dspark_door && !mtp_door {
4671 return 0;
4672 }
4673 let reserved = e.device_graph_mem_reserved();
4674 self.dspark_vgraphs
4675 .lock()
4676 .unwrap()
4677 .as_mut()
4678 .map(|g| g.admission_debt(reserved))
4679 .unwrap_or(0)
4680 }
4681
4682 /// MULTI-TURN RESUME (lane/dflash2-session-reuse, 2026-08-25): continue a parked
4683 /// dspark session with the next turn's suffix — the dspark twin of the MTP pool
4684 /// resume. Trunk rows for the committed stream are already resident in `cache` and
4685 /// their ctx features in `dkv`, so turn N+1 primes ONLY its delta instead of
4686 /// re-priming the whole conversation (the route previously served every turn cold —
4687 /// a full-prompt prime whose cost grows with the conversation).
4688 ///
4689 /// EXACTNESS. The suffix prime is the same session-continuation `prime_cache` the
4690 /// serve path uses for split prompts and LCP restores (chunk N+1 attends chunk N's
4691 /// resident KV); the tap sink collects the suffix rows prompt-relative and the dkv
4692 /// ingest lands them at their absolute positions, exactly as the burst's per-round
4693 /// keep-ingest does. The boundary token re-derives as the cold prime does: greedy
4694 /// argmax of the suffix's last row, or the request's filtered draw through the
4695 /// SESSION's own Philox stream (`sctr` continues — the frspec session-continuity
4696 /// law), penalized over the session+suffix window. A resumed stream is therefore
4697 /// byte-identical to the stream a cold prime of the full concatenation produces —
4698 /// the verify arbitrates every committed token either way.
4699 ///
4700 /// EOS in the committed history is fine (a finished turn parks with EOS committed;
4701 /// the new user turn continues past it) — `done` resets here. Callers must pass a
4702 /// NON-EMPTY suffix for a `done` session (an empty-suffix continuation of a finished
4703 /// stream would re-emit from a terminal state); the worker's probe enforces it.
4704 /// Re-arm a dspark session from a RESTORED trunk cache plus a published draft tail —
4705 /// the long-answer half of lane/dspark-draft-plane-20260827.
4706 ///
4707 /// WHY THIS EXISTS. `dspark_spec_session_new` must prime the full prompt, because the draft
4708 /// KV derives from trunk hidden FEATURES the prime produces as a side effect. A cache hit
4709 /// returns trunk K/V, not features, so before this a speculating request had to discard even
4710 /// a full-prompt hit and re-prefill (~10 s at 30k tokens). With the drafter's readable tail
4711 /// travelling on the entry, both halves are restorable and the discard is unnecessary.
4712 ///
4713 /// WHY IT IS EQUIVALENT TO A COLD PRIME, field by field:
4714 /// * `cache` — the caller's restored trunk cache, already at `prompt.len()` with recurrent
4715 /// state, which is why only WHOLE-ENTRY hits are eligible (a GDN trunk cannot rebuild
4716 /// recurrent state mid-sequence, so there is no LCP arm here — same restriction as the
4717 /// cold path's full-prompt-only rule).
4718 /// * `dkv` — byte-copied from the tail into the SAME absolute rows, so rope positions and
4719 /// every row the windowed SDPA can read are identical to what the prime produced.
4720 /// * `last` — drawn from the entry's boundary logits with the request's own sampler, the
4721 /// same composition the cold path applies to its prime logits.
4722 /// * `pen_hist` / `sctr` / `uctr` — seeded exactly as a cold session's are: the penalty
4723 /// window from this prompt, the Philox counters fresh, because randomness is
4724 /// session-owned by the frspec continuity law and a restore is a NEW session.
4725 /// * `prefix_capture` — `None`: the entry this restored FROM already exists, so
4726 /// republishing the same key would be dropped by the worker's dedupe anyway.
4727 ///
4728 /// Refuses (rather than asserting) whenever the rebuilt draft KV and the cache disagree, so
4729 /// a caller that gets `Err` simply cold-primes.
4730 #[allow(clippy::too_many_arguments)]
4731 pub fn dspark_spec_session_from_restored(
4732 &self,
4733 e: &Engine,
4734 draft: &DflashDraft,
4735 cache: crate::cache::Cache,
4736 prompt: &[u32],
4737 // Draft KV ALREADY rebuilt from the entry's tail by the caller (`DflashKv::from_tail`)
4738 // while the prefix cache was borrowable. Taking the built KV rather than the tail is
4739 // what keeps the ~85 MB tail in the entry for other requests — `from_tail` copies OUT
4740 // of it, so no clone of the tail is ever needed.
4741 dkv: DflashKv,
4742 boundary_logits: &[f32],
4743 sampling: Option<crate::spec::SpecSampling>,
4744 ctx_cap: usize,
4745 ) -> Result<DsparkSpecSession, Box<dyn std::error::Error>> {
4746 assert!(
4747 !self.uses_gemma_program(),
4748 "gemma4 targets use the assistant-drafter route; dspark is the qwen-hybrid arm"
4749 );
4750 if let Some(sp) = sampling.as_ref()
4751 && sp.temp <= 0.0
4752 && sp.pen_on()
4753 {
4754 return Err("penalized greedy is served on the plain path".into());
4755 }
4756 let c = &draft.cfg;
4757 let b = c.block_size;
4758 let is_dflash2 = draft.dflash2.is_some();
4759 let max_ctx = if is_dflash2 {
4760 ctx_cap
4761 } else {
4762 ctx_cap.min(c.sliding_window)
4763 };
4764 let tp = prompt.len();
4765 if !dspark_spec_prompt_fits(tp, ctx_cap, b, c.sliding_window, is_dflash2) {
4766 return Err(format!("restored dspark session does not fit ctx {max_ctx}").into());
4767 }
4768 if cache.pos != tp {
4769 return Err(format!(
4770 "restored dspark session needs a whole-entry trunk cache: cache.pos {} != prompt {tp}",
4771 cache.pos
4772 )
4773 .into());
4774 }
4775 if dkv.len != tp {
4776 return Err(format!("restored draft KV len {} != prompt {tp}", dkv.len).into());
4777 }
4778 if dkv.cap != max_ctx {
4779 return Err(
4780 format!("restored draft KV cap {} != session ctx {max_ctx}", dkv.cap).into(),
4781 );
4782 }
4783 if boundary_logits.is_empty() {
4784 return Err("restored dspark session needs the entry's boundary logits".into());
4785 }
4786 let mut sctr0 = 0u32;
4787 let pen_hist: Vec<u32> = match sampling.as_ref().filter(|s| s.temp > 0.0 && s.pen_on()) {
4788 Some(sp) => crate::spec::pen_window_seed(&[], prompt, sp.penalty_last_n),
4789 None => Vec::new(),
4790 };
4791 let last = match sampling.as_ref().filter(|s| s.temp > 0.0) {
4792 Some(sp) => crate::spec::sample_boundary_token(
4793 e,
4794 boundary_logits,
4795 sp,
4796 &pen_hist,
4797 &mut sctr0,
4798 "dspark-restore",
4799 )?,
4800 None => crate::forward::argmax(boundary_logits) as u32,
4801 };
4802 let nd = DsparkHarvest::for_draft(draft).n_drafts(b);
4803 let vt_cap: usize = std::env::var("MEMRA_DFLASH_VERIFY_T")
4804 .ok()
4805 .and_then(|v| v.parse().ok())
4806 .unwrap_or(nd + 1)
4807 .clamp(2, nd + 1);
4808 Ok(DsparkSpecSession {
4809 cache,
4810 prefix_capture: None,
4811 dkv,
4812 last,
4813 ctx_len: tp,
4814 vt: vt_cap,
4815 rounds: 0,
4816 max_ctx,
4817 done: false,
4818 snapb: None,
4819 snapb_off: !crate::spec::state_copy_batch_on(),
4820 sampling,
4821 sctr: sctr0,
4822 uctr: 0,
4823 pen_hist,
4824 })
4825 }
4826
4827 pub fn dspark_spec_session_resume(
4828 &self,
4829 e: &Engine,
4830 draft: &DflashDraft,
4831 sess: &mut DsparkSpecSession,
4832 suffix: &[u32],
4833 ) -> Result<(), Box<dyn std::error::Error>> {
4834 use crate::cache::DflashTapSink;
4835 let n_embd = self.cfg.n_embd as usize;
4836 let c = &draft.cfg;
4837 let b = c.block_size;
4838 let n_taps = c.target_layer_ids.len();
4839 let pos0 = sess.cache.pos;
4840 debug_assert_eq!(
4841 sess.ctx_len, pos0,
4842 "dspark resume: draft KV rows != trunk cache rows"
4843 );
4844 if suffix.is_empty() {
4845 return Err(
4846 "dspark resume needs a non-empty suffix (worker probe owns the \
4847 empty-suffix exact-continuation case)"
4848 .into(),
4849 );
4850 }
4851 // SHORT-SUFFIX FLOOR (incident 2026-08-25, box10 crash loop). The suffix prime goes
4852 // through `prime_cache`, which asserts `T >= PRIME_MIN_T` — the batched prefill arm
4853 // has no tokenwise twin that also fills the DFlash tap sink. A resumed turn shorter
4854 // than that floor (the watchdog's "Say OK." class, and any brief agent follow-up)
4855 // therefore PANICKED the GPU worker, which exits 70 and takes every session on the
4856 // box with it: 20 panics and ~5 minutes of 502s on box10 before MEMRA_REUSE_POOL=0
4857 // stopped it. The worker probe declines these before it ever gets here (its own
4858 // guard is the one that keeps the request on the cold path, which is exactly the
4859 // pre-lane behavior); this is the engine-side backstop so no future caller can
4860 // reintroduce the panic, and it is a refusal rather than an assert because a
4861 // too-short turn is ordinary traffic, not a bug.
4862 if suffix.len() < crate::hybrid_forward::PRIME_MIN_T {
4863 return Err(format!(
4864 "dspark resume suffix {} < PRIME_MIN_T {} (prime_cache has no tokenwise \
4865 tap-filling twin); serve this turn cold",
4866 suffix.len(),
4867 crate::hybrid_forward::PRIME_MIN_T
4868 )
4869 .into());
4870 }
4871 let need = pos0
4872 .saturating_add(suffix.len())
4873 .saturating_add(b)
4874 .saturating_add(8);
4875 if need > sess.max_ctx {
4876 return Err(format!(
4877 "dspark resume needs {need} ctx (resident {pos0} + suffix {} + block {b} + 8), \
4878 cap {}",
4879 suffix.len(),
4880 sess.max_ctx
4881 )
4882 .into());
4883 }
4884 let tp = suffix.len();
4885 sess.cache.dflash_taps = Some(DflashTapSink {
4886 layer_ids: c.target_layer_ids.clone(),
4887 buf: e.uninit(tp * n_taps * n_embd)?,
4888 hidden: n_embd,
4889 t: tp,
4890 base: 0,
4891 });
4892 let (logits, _h_seed, _hiddens) = self.prime_cache(e, suffix, &mut sess.cache, 0)?;
4893 let sp_pen = sess.sampling.filter(|s| s.temp > 0.0 && s.pen_on());
4894 if let Some(sp) = sp_pen.as_ref() {
4895 sess.pen_hist = crate::spec::pen_window_seed(&sess.pen_hist, suffix, sp.penalty_last_n);
4896 }
4897 let last = match sess.sampling.filter(|s| s.temp > 0.0) {
4898 Some(sp) => crate::spec::sample_boundary_token(
4899 e,
4900 &logits,
4901 &sp,
4902 &sess.pen_hist,
4903 &mut sess.sctr,
4904 "dspark-resume",
4905 )?,
4906 None => crate::forward::argmax(&logits) as u32,
4907 };
4908 {
4909 let taps = sess.cache.dflash_taps.take().unwrap();
4910 let n_taps_h = n_taps * n_embd;
4911 let mut r0 = 0usize;
4912 while r0 < tp {
4913 let t_c = (tp - r0).min(256);
4914 let tv = e.view(&taps.buf, tp * n_taps_h);
4915 let win = tv.slice(r0 * n_taps_h..(r0 + t_c) * n_taps_h);
4916 let mut chunk = e.uninit(t_c * n_taps_h)?;
4917 e.copy_view_into(&mut chunk, 0, &win, t_c * n_taps_h)?;
4918 let f = draft.ctx_features(e, &chunk, t_c)?;
4919 let pos_c: Vec<i32> = (((pos0 + r0) as i32)..((pos0 + r0 + t_c) as i32)).collect();
4920 draft.ingest_ctx(e, &mut sess.dkv, &f, &pos_c, t_c)?;
4921 r0 += t_c;
4922 }
4923 }
4924 e.stream().synchronize()?;
4925 sess.ctx_len += tp;
4926 sess.last = last;
4927 sess.done = false;
4928 Ok(())
4929 }
4930
4931 pub fn dspark_spec_session_burst(
4932 &self,
4933 e: &Engine,
4934 draft: &DflashDraft,
4935 sess: &mut DsparkSpecSession,
4936 burst_target: usize,
4937 request_room: usize,
4938 eos: &[u32],
4939 ) -> Result<(Vec<u32>, usize, usize), Box<dyn std::error::Error>> {
4940 use crate::cache::DflashTapSink;
4941 let n_embd = self.cfg.n_embd as usize;
4942 let c = &draft.cfg;
4943 let b = c.block_size;
4944 let n_taps = c.target_layer_ids.len();
4945 let n_vocab = self.output.out_features();
4946 // Harvest convention (DSPARK-POSTMORTEM-20260820.md) — identical to the bin arm
4947 // (family-keyed for DFlash2, else checkpoint strategy census; owner-ratified
4948 // flip 2026-08-20).
4949 let harvest = DsparkHarvest::for_draft(draft);
4950 let nd = harvest.n_drafts(b);
4951 let r0 = harvest.first_row();
4952 let vt_cap: usize = std::env::var("MEMRA_DFLASH_VERIFY_T")
4953 .ok()
4954 .and_then(|v| v.parse().ok())
4955 .unwrap_or(nd + 1)
4956 .clamp(2, nd + 1);
4957 let adapt = std::env::var("MEMRA_DFLASH_ADAPT").as_deref() != Ok("0");
4958 // Verify-window policy (H4, DSPARK-POSTMORTEM-20260820.md) — identical to the
4959 // bin arm: default = confidence-slot tau=.5 on a head-carrying checkpoint
4960 // (owner-ratified flip 2026-08-20); head-less (incl. the DFlash2 family) and
4961 // ADAPT=0 keep the ladder.
4962 let vt_policy = DsparkVtPolicy::resolve(draft.confidence.is_some());
4963 if vt_policy.is_confidence() {
4964 assert!(
4965 draft.confidence.is_some(),
4966 "MEMRA_DSPARK_VT={vt_policy:?} needs a checkpoint with an accept-rate \
4967 head (confidence_head.* absent in this export)"
4968 );
4969 }
4970 // SAMPLED ADMISSION (T>0): session-fixed config; counters live on the session so
4971 // randomness never repeats across bursts. None/temp==0 = the greedy route.
4972 let sp_on: Option<crate::spec::SpecSampling> = sess.sampling.filter(|s| s.temp > 0.0);
4973 let pen_on = sp_on.as_ref().is_some_and(|s| s.pen_on());
4974 let mut out: Vec<u32> = Vec::with_capacity(burst_target + b);
4975 let mut drafted = 0usize;
4976 let mut accepted_n = 0usize;
4977 // Engine-bundle slice 2 — identical to the bin arm: deferred chain readback under
4978 // the stash arm with a resident embed table (ladder policy only).
4979 let defer_rb = crate::spec::dspark_defer_readback_on() && !vt_policy.is_confidence();
4980 let (embd_qt, embd_rb) = self.embd.qt_and_row_bytes(n_embd);
4981 let embd_gpu = if !defer_rb || crate::spec::spec_host_embd() {
4982 None
4983 } else {
4984 Some(
4985 self.embd_gpu
4986 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload")),
4987 )
4988 };
4989 // Slice 3/4c SERVE ENGAGEMENT (graphs-serve lane; DSF-ROUNDCOST §9.3 -> §10): the
4990 // verify-graph pool lives on the MODEL (`dspark_vgraphs`, one per process) and its
4991 // keys — (segment, vt) and (vt, rung, hi) — carry NOTHING session-scoped, so ANY
4992 // session whose round matches a key replays the same capture (this is the
4993 // cache-reuse-pool the old bin-arm-only note asked for). Sharing is sound because
4994 // every per-session-varying address the captured bodies touch is indirect:
4995 // conv/ssm state and the ckpt stash resolve through the per-verify refreshed
4996 // pointer table (refresh_tables + copy_indirect_src_f32 — the slice-3
4997 // parity/lifetime law; a baked address is the known 12/12-divergence class), kv
4998 // bases through fa_table, residual/pos/tap through ctx-owned staging rewritten
4999 // every round; per-row t_kv derives in-kernel from pos_seq, and the per-round
5000 // host bookkeeping (parity swap, len bump) runs on THIS session's cache. The
5001 // guard spans the burst: the slab stash is live verify -> commit inside each
5002 // round, and the worker drives bursts from one scheduler thread
5003 // (step_dspark_spec), so sessions interleave at burst boundaries only.
5004 // DEFAULT ON on the serve route since the v0.103 train (owner-ratified
5005 // 2026-08-22, §10 re-gate at flip): MEMRA_DSPARK_VERIFY_GRAPH=0 is the
5006 // kill-switch that keeps this None — the eager walk, byte-identical (the
5007 // kill-switch arm of the serve battery). The bin arm keeps its own opt-in.
5008 let mut vg_guard = self.dspark_vgraphs.lock().unwrap();
5009 if vg_guard.is_none() && embd_gpu.is_some() && crate::spec::dspark_verify_graph_serve_on() {
5010 *vg_guard = crate::spec::DsparkVerifyGraphs::new(e, &sess.cache, vt_cap, n_embd)?;
5011 if vg_guard.is_some() {
5012 // Engagement receipt (the §8 dead-arm lesson): prove the door is LIVE on
5013 // the serve surface — S6b banked the tip server carrying zero door strings.
5014 eprintln!("[dspark-vg] serve pool ENGAGED (vt_cap={vt_cap})");
5015 }
5016 }
5017 let vgraphs: &mut Option<crate::spec::DsparkVerifyGraphs> = &mut vg_guard;
5018 'outer: while out.len() < burst_target && !sess.done {
5019 let start = sess.cache.pos;
5020 if start + nd + 1 > sess.max_ctx {
5021 sess.done = true;
5022 break;
5023 }
5024 sess.rounds += 1;
5025 let mut vt = sess.vt;
5026 // ---- draft: block = [last, MASK x b-1] (identical to the bin arm) ----
5027 // RAII: a `?` exit restores the pre-scope value instead of latching exact
5028 // ON engine-wide across every later request (hermes finding, fixed
5029 // 2026-08-23 — this burst had several `?`s between the manual true/false).
5030 let exact_scope = e.exact_scope(true);
5031 let mut block: Vec<u32> = vec![c.mask_token_id; b];
5032 block[0] = sess.last;
5033 let noise = e.htod(&self.embd.try_gather(n_embd, &block)?)?;
5034 let pos_block: Vec<i32> = ((start as i32)..(start + b) as i32).collect();
5035 let dh = draft.forward_round(e, &mut sess.dkv, &noise, &pos_block)?;
5036 // Harvest: logits over rows r0..r0+nd (see the bin arm / the postmortem).
5037 let mut rows = e.uninit(nd * n_embd)?;
5038 {
5039 let dv = e.view(&dh, b * n_embd);
5040 let src = dv.slice(r0 * n_embd..(r0 + nd) * n_embd);
5041 e.copy_view_into(&mut rows, 0, &src, nd * n_embd)?;
5042 }
5043 // TRIMMED DRAFT HEAD (lane/dflash2-head-trim, 2026-08-25): DFlash2 family
5044 // only — the selector consumes (value, candidate-id) pairs, so a d2t remap
5045 // after top-k restores true ids; the markov/chain arms argmax dl columns
5046 // into token ids DIRECTLY and must keep the full head. Reuses the FR-Spec
5047 // self-trim the load path builds on the MTP struct (MEMRA_FRSPEC_TRIM):
5048 // gathered rows of the target's own head, zero requant. Verify stays
5049 // full-vocab, so the trim moves draft acceptance only, never output.
5050 let trim = if draft.dflash2.is_some() {
5051 self.mtp
5052 .as_ref()
5053 .filter(|m| m.d2t_from_target_head)
5054 .and_then(|m| m.shared_head_head.as_ref().zip(m.d2t.as_ref()))
5055 // MEMRA_MTP_SKIP stub: the same target-head trimmed rows, parked in
5056 // `dflash_trim` because the embedded MTP block was skipped (hybrid.rs;
5057 // rows are target-head by construction; the loader refuses otherwise).
5058 .or_else(|| self.dflash_trim.as_ref().map(|t| (&t.head, &t.d2t)))
5059 .filter(|(_, d2t)| !d2t.is_empty())
5060 } else {
5061 None
5062 };
5063 let (dl_head, dl_vocab) = match trim {
5064 Some((head, d2t)) => (head, d2t.len()),
5065 None => (&self.output, n_vocab),
5066 };
5067 let trim_d2t = trim.map(|(_, d2t)| d2t.as_slice());
5068 let mut dl = e.matmul(dl_head, &rows, nd)?;
5069 // Family/sampling-keyed proposal — identical to the bin arm (see there for
5070 // the program law: sampled records the true q, DFlash2 rides the selector,
5071 // the markov/plain greedy chain keeps the slice-2 deferral). Confidence
5072 // policy: stash markov prev-token embeddings d2d during the chain, one host
5073 // readback after — identical to the bin arm.
5074 let want_conf_emb = vt_policy.is_confidence()
5075 && draft.confidence.as_ref().is_some_and(|ch| ch.with_markov);
5076 let mut conf_emb: Option<CudaSlice<f32>> = match (&draft.markov, want_conf_emb) {
5077 (Some(mk), true) => Some(e.uninit(nd * mk.rank)?),
5078 (None, true) => unreachable!(
5079 "with_markov confidence head without a markov table — the loader forbids it"
5080 ),
5081 _ => None,
5082 };
5083 let mut cand: Vec<u32> = Vec::with_capacity(nd + 1);
5084 let mut prop: Option<DsparkDraftSample> = None;
5085 let mut chain_dev: Option<CudaSlice<u32>> = None;
5086 // Slice 2: arm choice read before the chain readback (see the bin arm; the
5087 // serve arm has no CKPT_GATE oracle — the bin arm carries it).
5088 let ckpt_on = std::env::var("MEMRA_DSPARK_CKPT").as_deref() != Ok("0");
5089 let mut deferred = false;
5090 if let Some(sp) = sp_on.as_ref() {
5091 // SAMPLED proposal (family-keyed; identical to the bin arm).
5092 let (tail, ds) = draft.dspark_propose_sampled(
5093 e,
5094 &mut dl,
5095 &rows,
5096 nd,
5097 dl_vocab,
5098 sess.last,
5099 sp,
5100 &mut sess.sctr,
5101 &mut sess.uctr,
5102 conf_emb.as_mut(),
5103 trim_d2t,
5104 )?;
5105 drop(exact_scope);
5106 cand.push(sess.last);
5107 cand.extend_from_slice(&tail);
5108 prop = Some(ds);
5109 } else if draft.dflash2.is_some() {
5110 // DFlash2: candidate path selector replaces the markov chain
5111 // (identical to the bin arm).
5112 let path = draft
5113 .dflash2_propose_greedy(e, &dl, &rows, nd, dl_vocab, sess.last, trim_d2t)?;
5114 drop(exact_scope);
5115 cand.push(sess.last);
5116 cand.extend_from_slice(&path);
5117 } else {
5118 let markov_on = std::env::var("MEMRA_DFLASH_MARKOV").as_deref() != Ok("0");
5119 let mut chain_d = e.stream().alloc_zeros::<u32>(nd + 1)?;
5120 if let (Some(mk), true) = (&draft.markov, markov_on) {
5121 e.set_u32_one(&mut chain_d, sess.last)?;
5122 for k in 0..nd {
5123 let mut f = e.uninit(mk.rank)?;
5124 e.gather_row_bf16(&mk.w1_bf16, &chain_d, k, &mut f, mk.rank)?;
5125 if let Some(ce) = conf_emb.as_mut() {
5126 let fv = e.view(&f, mk.rank);
5127 e.copy_view_into(ce, k * mk.rank, &fv, mk.rank)?;
5128 }
5129 let bias = e.matmul(&mk.w2, &f, 1)?;
5130 e.add_row_inplace(&mut dl, &bias, n_vocab, k * n_vocab)?;
5131 e.argmax_token_device_col(&dl, k, n_vocab, &mut chain_d, k + 1)?;
5132 }
5133 } else {
5134 if want_conf_emb {
5135 // chain_d[0] must carry the anchor — slot 0's prev token.
5136 e.set_u32_one(&mut chain_d, sess.last)?;
5137 }
5138 for i in 0..nd {
5139 if let (Some(ce), Some(mk)) = (conf_emb.as_mut(), &draft.markov) {
5140 let mut f = e.uninit(mk.rank)?;
5141 e.gather_row_bf16(&mk.w1_bf16, &chain_d, i, &mut f, mk.rank)?;
5142 let fv = e.view(&f, mk.rank);
5143 e.copy_view_into(ce, i * mk.rank, &fv, mk.rank)?;
5144 }
5145 e.argmax_token_device_col(&dl, i, n_vocab, &mut chain_d, i + 1)?;
5146 }
5147 }
5148 drop(exact_scope);
5149 deferred = embd_gpu.is_some() && ckpt_on;
5150 chain_dev = Some(chain_d);
5151 }
5152 // ---- H4 confidence window: size THIS round's verify from the head ----
5153 if vt_policy.is_confidence() {
5154 let ch = draft.confidence.as_ref().expect("asserted at burst entry");
5155 let (rows_h, emb_h) = match conf_emb.as_ref() {
5156 Some(ce) => {
5157 let (a, b2) = e.dtoh_pair(&rows, ce)?;
5158 (a, Some(b2))
5159 }
5160 None => (e.dtoh(&rows)?, None),
5161 };
5162 let rank = draft.markov.as_ref().map(|m| m.rank).unwrap_or(0);
5163 let mut raws = Vec::with_capacity(nd);
5164 for k in 0..nd {
5165 let hrow = &rows_h[k * n_embd..(k + 1) * n_embd];
5166 let emb = emb_h.as_ref().map(|eh| &eh[k * rank..(k + 1) * rank]);
5167 raws.push(ch.raw_score(hrow, emb));
5168 }
5169 vt = vt_policy
5170 .size_window(&raws, vt_cap)
5171 .expect("confidence policies always size the window");
5172 }
5173 // Non-deferred greedy chain readback (the sampled and DFlash2 proposals
5174 // built `cand` at the walk; deferred rounds build it after the merged
5175 // readback — bytes identical, chain_d written before either sync).
5176 if let Some(chain_d) = chain_dev.as_ref()
5177 && !deferred
5178 {
5179 let chain = e.dtoh_u32(chain_d)?;
5180 cand.push(sess.last);
5181 cand.extend_from_slice(&chain[1..]);
5182 }
5183
5184 // ---- snapshot, then verify t=vt (ckpt stash default; oracle arms kept) ----
5185 // Slice 1: batched snap (see DsparkSnapBatch) with the legacy per-layer
5186 // snapshot as the kill-switch / non-uniform fallback.
5187 let mut snap_legacy: Option<crate::cache::CacheSnapshot> = None;
5188 if !sess.snapb_off && sess.snapb.is_none() {
5189 sess.snapb = DsparkSnapBatch::new(e, &sess.cache)?;
5190 sess.snapb_off = sess.snapb.is_none();
5191 } else if let Some(sb) = sess.snapb.as_mut() {
5192 sb.refresh(e, &sess.cache)?;
5193 }
5194 let snap: &crate::cache::CacheSnapshot = match sess.snapb.as_ref() {
5195 Some(sb) => &sb.snap,
5196 None => {
5197 snap_legacy = Some(sess.cache.snapshot(e)?);
5198 snap_legacy.as_ref().unwrap()
5199 }
5200 };
5201 let _ = &snap_legacy;
5202 // Slice 3: the tap-sink buffer is persistent per vt in the graphs ctx
5203 // (captured segments bake its address — a per-round alloc here would make
5204 // every session's replayed tap copies write freed memory); fully rewritten
5205 // by every verify, so pool ownership changes no bytes.
5206 let tap_buf = match vgraphs.as_mut().and_then(|g| g.tap_bufs.remove(&vt)) {
5207 Some(buf) => buf,
5208 None => e.uninit(vt * n_taps * n_embd)?,
5209 };
5210 sess.cache.dflash_taps = Some(DflashTapSink {
5211 layer_ids: c.target_layer_ids.clone(),
5212 buf: tap_buf,
5213 hidden: n_embd,
5214 t: vt,
5215 base: 0,
5216 });
5217 // Composition guard (sampled admission × model-owned pool, this train's
5218 // cross-product): the slab flag is a per-round statement, but only the
5219 // graphs-aware verify (`_am_ckpt_dev`) clears it. Serve sessions MIX arms
5220 // within one process-lifetime pool — a SAMPLED round rides the raw-logits
5221 // twins (no graphs param) and must not inherit `round_slab=true` from a
5222 // previous greedy session's captured round, or its commit is steered at
5223 // slabs the round never wrote. Clear at the round boundary; the deferred
5224 // arm re-derives it inside the verify. (The bin arm has the same shape but
5225 // fixes its sampling mode per process, so no mixed rounds exist there.)
5226 if let Some(g) = vgraphs.as_mut() {
5227 g.round_slab = false;
5228 }
5229 // The whole fallible verify window runs inside a closure so the Err path
5230 // can return the sink buffer to the ctx pool before propagating — the
5231 // serve-surface twin of the EOS-orphan lesson: a mid-verify error
5232 // propagates OUT of the burst, the request dies, the session's cache is
5233 // dropped — but the PROCESS (and the pool, with the tap-buffer address
5234 // baked into its captures) lives on. Recover the ctx-owned buffer before
5235 // the error escapes, or the next session's replayed tap copies write
5236 // freed memory. The bin arm has no such path (a gate-binary error ends
5237 // the process).
5238 #[allow(clippy::type_complexity)] // allow: one-shot composite type; naming it would hide the shape that matters at the call site
5239 let verify_out = (|| -> Result<
5240 (
5241 Vec<u32>,
5242 Option<CudaSlice<f32>>,
5243 Option<crate::spec::DsparkVerifyCkpt>,
5244 ),
5245 Box<dyn std::error::Error>,
5246 > {
5247 if sp_on.is_some() {
5248 // SAMPLED: raw verify logits for the rejection walk (bin-arm twin).
5249 if ckpt_on {
5250 let (tl, vck) = self.dspark_verify_t_logits_ckpt(
5251 e,
5252 &cand[..vt],
5253 start,
5254 &mut sess.cache,
5255 )?;
5256 Ok((Vec::new(), Some(tl), Some(vck)))
5257 } else {
5258 Ok((
5259 Vec::new(),
5260 Some(self.dspark_verify_t_logits(
5261 e,
5262 &cand[..vt],
5263 start,
5264 &mut sess.cache,
5265 )?),
5266 None,
5267 ))
5268 }
5269 } else if deferred {
5270 // Slice 2: device-token verify + ONE merged readback (see the bin arm).
5271 let chain_d = chain_dev.as_ref().expect("deferred implies greedy chain");
5272 let g = embd_gpu.expect("deferred implies resident embed");
5273 let (am_d, vck) = self.dspark_verify_t_am_ckpt_dev(
5274 e,
5275 chain_d,
5276 vt,
5277 start,
5278 &mut sess.cache,
5279 (g, embd_qt, embd_rb),
5280 vgraphs.as_mut(),
5281 )?;
5282 let ch = e.stream().clone_dtoh(chain_d)?;
5283 let am = e.stream().clone_dtoh(&am_d)?;
5284 e.stream().synchronize()?;
5285 cand.push(sess.last);
5286 cand.extend_from_slice(&ch[1..]);
5287 Ok((am, None, Some(vck)))
5288 } else if ckpt_on {
5289 let (vam, vck) =
5290 self.dspark_verify_t_am_ckpt(e, &cand[..vt], start, &mut sess.cache)?;
5291 Ok((vam, None, Some(vck)))
5292 } else {
5293 Ok((
5294 self.dspark_verify_t_am(e, &cand[..vt], start, &mut sess.cache)?,
5295 None,
5296 None,
5297 ))
5298 }
5299 })();
5300 let (vam, tl, vck) = match verify_out {
5301 Ok(v) => v,
5302 Err(err) => {
5303 if let Some(taps) = sess.cache.dflash_taps.take()
5304 && let Some(g) = vgraphs.as_mut()
5305 {
5306 g.tap_bufs.insert(vt, taps.buf);
5307 }
5308 return Err(err);
5309 }
5310 };
5311 let taps = sess.cache.dflash_taps.take().unwrap();
5312 // Return the tap buffer to the ctx pool IMMEDIATELY — an EOS/budget break
5313 // between accept and ingest must never orphan an address the captured graphs
5314 // bake (the bin arm's lesson, and it holds doubly here: the pool outlives
5315 // the SESSION, not just the round). Ingest reads it borrowed.
5316 let tap_local: Option<CudaSlice<f32>> = match vgraphs.as_mut() {
5317 Some(g) => {
5318 g.tap_bufs.insert(vt, taps.buf);
5319 None
5320 }
5321 None => Some(taps.buf),
5322 };
5323 let tap_ref: &CudaSlice<f32> = match &tap_local {
5324 Some(b) => b,
5325 None => &vgraphs.as_ref().expect("ctx present above").tap_bufs[&vt],
5326 };
5327
5328 // ---- accept ----
5329 // Penalized-sampled: anchor joins the window before the walk (committed this
5330 // round via the out.push below); accepted drafts extend it after — identical
5331 // to the bin arm.
5332 if pen_on {
5333 sess.pen_hist.push(sess.last);
5334 }
5335 let (m, next) = match (sp_on.as_ref(), tl.as_ref()) {
5336 (Some(sp), Some(tl)) => {
5337 let w0 = sess
5338 .pen_hist
5339 .len()
5340 .saturating_sub(sp.penalty_last_n.min(crate::spec::PEN_WINDOW_MAX));
5341 dspark_accept_sampled(
5342 e,
5343 tl,
5344 &cand,
5345 vt,
5346 n_vocab,
5347 &dl,
5348 prop.as_ref()
5349 .expect("sampled round without a proposal record"),
5350 sp,
5351 &sess.pen_hist[w0..],
5352 &mut sess.sctr,
5353 &mut sess.uctr,
5354 )?
5355 }
5356 _ => {
5357 let m = dspark_accept_prefix(&cand, &vam, vt);
5358 (m, vam[m])
5359 }
5360 };
5361 drafted += vt - 1;
5362 accepted_n += m;
5363 // keep = the rows this round adds to the PUBLIC stream. Without eos that is
5364 // the anchor + all accepted drafts (m+1). With eos it is the anchor + drafts
5365 // UP TO AND INCLUDING eos: the walk may accept real tokens past eos (they are
5366 // the model's own continuation), but emission stops at eos, and a parked
5367 // session whose cache holds rows past the public stream can never resume —
5368 // the park gate `pos() == fed` would refuse every eos-terminated stream
5369 // (measured: 7/8 turns on the mtreuse gate, overshoot 1-6 rows). Truncating
5370 // the commit at eos uses the SAME prefix-commit machinery as a mid-round
5371 // rejection, so the hybrid (GDN) state is exact by the same argument.
5372 // Emitted bytes are untouched — this only changes post-eos cache state.
5373 let mut keep = m + 1;
5374 let mut terminal = false;
5375 if eos.contains(&sess.last) {
5376 terminal = true;
5377 keep = 1;
5378 } else {
5379 for (j, &dt) in cand[1..=m].iter().enumerate() {
5380 if eos.contains(&dt) {
5381 terminal = true;
5382 keep = j + 2; // anchor + drafts through eos
5383 break;
5384 }
5385 }
5386 }
5387 // The request's max_tokens boundary is also a commit boundary, not merely an
5388 // output slice. It is NOT the scheduler's smaller per-tick burst quantum: accepted
5389 // surplus crossing that quantum stays public and the session remains live. Only at
5390 // the true request boundary do we keep the publishable prefix so cache.pos == fed at
5391 // retire and mark the session terminal until a non-empty next-turn suffix resumes
5392 // it. This uses the same prefix-commit machinery as EOS/rejection and makes
5393 // max-token sessions safe to park instead of permanently cold (Hermes
5394 // `f22a180d1638b95a`).
5395 let (bounded_keep, budget_terminal) =
5396 dspark_commit_limit(keep, out.len(), request_room);
5397 keep = bounded_keep;
5398 terminal |= budget_terminal;
5399 out.push(sess.last);
5400 out.extend_from_slice(&cand[1..keep]);
5401 sess.done = terminal;
5402 if pen_on {
5403 // Only the PUBLIC drafts feed the penalty window — tokens accepted past
5404 // eos never reach the stream, and a resumed session must not penalize
5405 // ghosts (the parked pen_hist seeds the resume's window).
5406 sess.pen_hist.extend_from_slice(&cand[1..keep]);
5407 }
5408
5409 // ---- commit/rollback (stash arm default; replay oracle kept) ----
5410 // Slice 3: rounds whose linear column stash lives in the graphs ctx's slabs
5411 // commit through the slab twin (same semantics, slab-addressed sources) —
5412 // identical to the bin arm's dispatch.
5413 let slab_commit = vgraphs.as_ref().map(|g| g.round_slab).unwrap_or(false);
5414 if keep < vt {
5415 if slab_commit {
5416 self.dspark_commit_prefix_slab(
5417 e,
5418 &mut sess.cache,
5419 snap,
5420 vgraphs.as_ref().expect("slab_commit implies ctx"),
5421 keep,
5422 )?;
5423 } else if let Some(vck) = vck.as_ref() {
5424 self.dspark_commit_prefix(e, &mut sess.cache, snap, vck, keep)?;
5425 } else {
5426 crate::pp::restore_cache_checkpoint(e, self, None, &mut sess.cache, snap)?;
5427 debug_assert_eq!(sess.cache.pos, start, "rollback landed off the round start");
5428 let ram = self.dspark_verify_t_am(e, &cand[..keep], start, &mut sess.cache)?;
5429 if sp_on.is_none() {
5430 // greedy-only oracle; the sampled arm replays to rebuild state.
5431 debug_assert_eq!(
5432 &ram[..],
5433 &vam[..keep],
5434 "prefix replay must reproduce the verify argmaxes"
5435 );
5436 }
5437 }
5438 }
5439
5440 // ---- ingest the kept rows' ctx features into the draft KV ----
5441 {
5442 let tv = e.view(tap_ref, vt * n_taps * n_embd);
5443 let keep_view = tv.slice(0..keep * n_taps * n_embd);
5444 let mut kept = e.uninit(keep * n_taps * n_embd)?;
5445 e.copy_view_into(&mut kept, 0, &keep_view, keep * n_taps * n_embd)?;
5446 let f = draft.ctx_features(e, &kept, keep)?;
5447 let pos_k: Vec<i32> =
5448 ((sess.ctx_len as i32)..(sess.ctx_len + keep) as i32).collect();
5449 draft.ingest_ctx(e, &mut sess.dkv, &f, &pos_k, keep)?;
5450 sess.ctx_len += keep;
5451 }
5452 if sess.done {
5453 // EOS or the public budget landed this round: cache, draft KV and ctx_len are
5454 // all clamped to the public stream (park shape); `next` is beyond the terminal
5455 // boundary and must not become the anchor of a resumed session.
5456 break 'outer;
5457 }
5458 sess.last = next;
5459 // Ladder update only — the confidence policies recompute vt from the
5460 // head every round, post-draft pre-verify; their carry just keeps
5461 // observability (sess.vt = the last confidence-sized window).
5462 if vt_policy.is_confidence() {
5463 sess.vt = vt;
5464 } else if adapt {
5465 sess.vt = (m + 2).clamp(3, vt_cap);
5466 }
5467 }
5468 Ok((out, drafted, accepted_n))
5469 }
5470}
5471
5472// ================= Harvest-convention gate (CPU; DSPARK-POSTMORTEM-20260820.md) =========
5473// The parity oracle is row-count-agnostic (it reproduces the markov MODULE on whatever
5474// rows it is fed) and the E2E gate is harvest-independent (verify-side truth), so
5475// NEITHER can catch a wrong row->position mapping — that blindness is how the q38
5476// misalignment shipped. These tests pin the convention itself as logic the round
5477// consumes, so a mutation back to the mask-fill harvest under the Dspark variant fails
5478// HERE, naming the convention.
5479#[cfg(test)]
5480mod dflash2_tests {
5481
5482 /// The tail-import refusal arms (lane/dspark-draft-plane-20260827 review finding: these
5483 /// were claimed tested and were not). Pure, so they run everywhere; the geometry mirrors
5484 /// the served DFlash2 drafter (5 layers, 8 kv x 128 dim f32 rows, window 2048 + block 8).
5485 #[test]
5486 fn tail_import_refuses_every_geometry_disagreement_and_accepts_the_exported_shape() {
5487 let rb = 8 * 128 * 4; // n_kv * head_dim * f32
5488 let win = 2048 + 8; // window_rows = sliding_window + block
5489 // THE EXPORTED SHAPE: window_rows ending exactly at len, same geometry — accepted.
5490 assert!(
5491 super::tail_geometry_ok(5, rb, 30_329 - win, win, 30_329, 0, 5, rb, win, 34_433)
5492 .is_ok()
5493 );
5494 // A short history where the tail IS the whole history — accepted.
5495 assert!(super::tail_geometry_ok(5, rb, 0, 100, 100, 0, 5, rb, win, 34_433).is_ok());
5496 // FLOOR-BEARING (lane/spec-exclusions-20260902): a cold-drafter exporter at floor
5497 // 30_000 owns rows [30_000, 30_329) only, so its 329-row tail is everything readable
5498 // above the floor — accepted; the same 329 rows from a floor-0 exporter are the
5499 // truncation the pre-lane rule refuses, verbatim; a tail claiming rows BELOW its
5500 // exporter's floor is a geometry lie and refuses by name.
5501 assert!(
5502 super::tail_geometry_ok(5, rb, 30_000, 329, 30_329, 30_000, 5, rb, win, 34_433).is_ok()
5503 );
5504 assert_eq!(
5505 super::tail_geometry_ok(5, rb, 30_000, 329, 30_329, 0, 5, rb, win, 34_433).unwrap_err(),
5506 "tail shorter than the drafter's readable window"
5507 );
5508 assert_eq!(
5509 super::tail_geometry_ok(5, rb, 29_990, 339, 30_329, 30_000, 5, rb, win, 34_433)
5510 .unwrap_err(),
5511 "tail starts below its exporter's context floor"
5512 );
5513 // Every refusal arm, each by name:
5514 let arm = |l, r, b, rows, len, cap| {
5515 super::tail_geometry_ok(l, r, b, rows, len, 0, 5, rb, win, cap)
5516 };
5517 assert_eq!(
5518 arm(4, rb, 30_329 - win, win, 30_329, 34_433).unwrap_err(),
5519 "layer count differs from the live drafter"
5520 );
5521 assert_eq!(
5522 arm(5, rb - 4, 30_329 - win, win, 30_329, 34_433).unwrap_err(),
5523 "row geometry differs from the live drafter"
5524 );
5525 assert_eq!(
5526 arm(5, rb, 30_329 - win, win, 30_329, 30_000).unwrap_err(),
5527 "logical length exceeds the session cap"
5528 );
5529 // THE RUN-2 BUG, pinned: a tail whose base+rows lands past its own logical length —
5530 // the export-at-current-length defect the gate caught on the box.
5531 assert_eq!(
5532 arm(5, rb, 30_364 - win, win, 30_329, 34_433).unwrap_err(),
5533 "tail does not end at its own logical length"
5534 );
5535 assert_eq!(
5536 arm(5, rb, 30_329 - (win - 100), win - 100, 30_329, 34_433).unwrap_err(),
5537 "tail shorter than the drafter's readable window"
5538 );
5539 }
5540
5541 use super::{
5542 DsparkHarvest, dflash2_walk_greedy, dflash2_walk_sampled, dspark_commit_limit,
5543 rejection_accept_len,
5544 };
5545
5546 // ================= memra#95: the full-cover restore panic =================
5547 //
5548 // `walk: candidate 4294967295 outside codebook vocab` (the greedy and sampled walk
5549 // asserts) on the FIRST round of a `MEMRA_GLM5_SPEC_FULLCOVER=1` restored session,
5550 // fleet-fatal. `4294967295` is the top-k selector's exhausted-slot sentinel, so the
5551 // draft logits row was NaN. The row was NaN because the drafter ctx KV is imported on
5552 // the CALLER's stream at admission (`DflashKv::from_tail`) while round 1 reads it
5553 // through `glm5_head_engine`, which under a live ppN split is the last stage's OWN
5554 // Engine used OUTSIDE any `rt.enter` scope, i.e. that Engine's own stream. Nothing
5555 // ordered the two.
5556 //
5557 // THE RETRACTED THEORY, kept because it is what made a wrong fix look right (review
5558 // round 1 on PR #100): "the full-cover arm never calls `prime_cache` and therefore
5559 // never inherits `prime_cache_hyper_ppn`'s `fence_stages_behind`". That fence has the
5560 // SAME blind spot — it orders `StageRt::stream`, the enter-scope stream, not a stage
5561 // ENGINE's own stream — so inheriting it would have fixed nothing. What actually shields
5562 // the suffix arm is that `prime_cache` returns its logits through `Engine::dtoh`, which
5563 // is `stream().synchronize()` on the caller. The full-cover arm has no prime and no
5564 // device readback at all between the import and the round (its anchor comes from the
5565 // entry's host-side boundary logits), which is why it is the only exposed path, and why
5566 // only round 1 dies: from round 2 on, `dflash2_propose_*`'s own `dtoh_u32` drains the
5567 // head engine.
5568 //
5569 // Two gates below: the ordering seam (asserted on the ENGINE-ordering helper, so it
5570 // rejects the `fence_stages_behind` shape too), and the blast radius.
5571
5572 /// The sentinel a top-k row that could not be filled carries into the walk.
5573 #[test]
5574 fn an_unfilled_selector_slot_is_refused_by_name_not_clamped() {
5575 // top_k = 4, one draft slot; the selector filled two slots and gave up, which is what
5576 // `topk_rows_f32` writes for a partially finite row.
5577 let cand = [7u32, 11, super::TOPK_EMPTY_SLOT, super::TOPK_EMPTY_SLOT];
5578 let why = super::dflash2_guard_candidates(&cand, 1000, "gate")
5579 .expect_err("an exhausted slot must be refused");
5580 assert!(why.contains("slot 2"), "{why}");
5581 assert!(why.contains("4294967295"), "{why}");
5582 assert!(
5583 why.contains("exhausted-slot sentinel") && why.contains("NaN"),
5584 "the refusal must name the mechanism, not just the number: {why}"
5585 );
5586 // THE OBSERVED SHAPE (memra#95): a fully NaN row fills EVERY slot with the sentinel,
5587 // so the refusal has to bite at slot 0, before the walk's `prev` chain even starts.
5588 let all_nan = [super::TOPK_EMPTY_SLOT; 4];
5589 let why0 = super::dflash2_guard_candidates(&all_nan, 1000, "gate")
5590 .expect_err("an all-sentinel row must be refused");
5591 assert!(why0.contains("slot 0"), "{why0}");
5592 // An ordinary out-of-range id is refused too (the trimmed head's rank space is
5593 // narrower than the vocab, and a remap of a bad rank would panic in the map).
5594 assert!(super::dflash2_guard_candidates(&[7, 1000], 1000, "gate").is_err());
5595 // A well-formed row passes, and the bound is exclusive.
5596 super::dflash2_guard_candidates(&[0, 999, 500, 1], 1000, "gate").expect("clean row");
5597 }
5598
5599 /// The walk's own assert is the LAST line of defence and stays exactly where it is:
5600 /// the guard above refuses one request, this refuses the round.
5601 #[test]
5602 #[should_panic(expected = "walk: candidate")]
5603 fn the_walk_assert_survives_the_guard() {
5604 const VOCAB: usize = 8;
5605 const RANK: usize = 2;
5606 const TOPK: usize = 2;
5607 let pred = vec![0u8; VOCAB * RANK * 2];
5608 let succ = vec![0u8; VOCAB * RANK * 2];
5609 let cand = [1u32, super::TOPK_EMPTY_SLOT];
5610 let _ = dflash2_walk_greedy(
5611 &pred,
5612 &succ,
5613 VOCAB,
5614 RANK,
5615 TOPK,
5616 &[0.0; TOPK],
5617 &cand,
5618 &[0.0; RANK],
5619 0,
5620 1,
5621 );
5622 }
5623
5624 /// WIRING GATE (invocations in comment-stripped source, never prose, the
5625 /// wiring-assertions-match-prose law). Both halves of the memra#95 fix are LIVE code.
5626 ///
5627 /// The ordering half is asserted on the ENGINE-ordering seam, not on
5628 /// `fence_stages_behind`: that distinction IS the defect (a stage's enter-scope stream is
5629 /// not the stage Engine's own stream, and the draft phase runs on the latter), so a gate
5630 /// that accepted either would accept the broken shape.
5631 #[test]
5632 fn the_fullcover_restore_ordering_seam_is_live_in_comment_stripped_source() {
5633 let strip = |src: &str| -> String {
5634 src.lines()
5635 .map(|l| l.split("//").next().unwrap_or(""))
5636 .collect::<Vec<_>>()
5637 .join("\n")
5638 };
5639
5640 // 1. THE CAUSE. `glm5_spec_session_from_restored` orders the head/drafter engine
5641 // behind the caller BEFORE the suffix branch, so it covers the full-cover arm
5642 // (which has no prime, and therefore no incidental host sync, to hide behind).
5643 let glm5 = strip(include_str!("glm_spec.rs"));
5644 let body = glm5
5645 .find("pub fn glm5_spec_session_from_restored(")
5646 .expect("the restored-session builder exists");
5647 let end = glm5[body..]
5648 .find("fn glm5_d2t(")
5649 .expect("the builder's end anchor exists")
5650 + body;
5651 let scope = &glm5[body..end];
5652 let eh = scope
5653 .find("let eh = self.glm5_head_engine(e)?;")
5654 .expect("the builder resolves the head engine");
5655 let order = scope
5656 .find("crate::pp::PpNRt::order_engine_behind(e, eh)?;")
5657 .expect(
5658 "the restored-session builder must order the head engine's own stream behind \
5659 the caller (fence_stages_behind is the WRONG seam: it orders enter-scope \
5660 stage streams, and the draft phase never enters a stage)",
5661 );
5662 let branch = scope
5663 .find("let (logits_s, tap_rows, prefix_capture) = if suffix.is_empty()")
5664 .expect("the full-cover branch exists");
5665 assert!(
5666 eh < order && order < branch,
5667 "the ordering must sit between the head-engine binding and the full-cover/suffix \
5668 branch: after it so it names the right engine, before it so the prime-free arm \
5669 is covered"
5670 );
5671
5672 // 1b. The helper it calls really orders the ENGINE streams, not the stage streams.
5673 let pp = strip(include_str!("pp.rs"));
5674 let helper = pp
5675 .find("pub fn order_engine_behind(")
5676 .expect("the engine-ordering helper exists");
5677 let rest = &pp[helper..];
5678 let hbody = &rest[..rest.find("\n pub fn ").unwrap_or(rest.len().min(2000))];
5679 assert!(
5680 hbody.contains("let s = src.stream();") && hbody.contains("dst.gpu.main_stream()"),
5681 "the helper must order the two ENGINES' own streams, source side through the \
5682 ambient-aware accessor and destination side through the override-blind one"
5683 );
5684 assert!(
5685 hbody.contains("if src.ctx() == dst.ctx() {"),
5686 "the context test must be VALUE equality: CudaContext::new allocates a fresh Arc \
5687 per call for the same primary context, so Arc::ptr_eq would make the async event \
5688 path dead code and every restored session would pay a host sync"
5689 );
5690
5691 // 2. THE BLAST RADIUS. Both proposal seams guard the candidate buffer BEFORE the
5692 // d2t remap (a sentinel would index the map out of bounds) and before the walk.
5693 let d2 = strip(include_str!("dflash.rs"));
5694 // Cut at the FIRST test module so no assertion can be satisfied by this test's own
5695 // string literals (the self-match trap the wiring-assertions law names). Note this
5696 // leaves only the pre-test part of the file live: a seam added AFTER the first
5697 // `#[cfg(test)]` would be invisible here and would need its own anchor.
5698 let live = &d2[..d2.find("#[cfg(test)]").expect("this file has test modules")];
5699 for (seam, arm) in [
5700 ("pub fn dflash2_propose_greedy_q(", "greedy"),
5701 ("pub(crate) fn dflash2_propose_sampled(", "sampled"),
5702 ] {
5703 let at = live
5704 .find(seam)
5705 .unwrap_or_else(|| panic!("{arm} proposal seam exists"));
5706 let window = live.get(at..at + 4000).unwrap_or(&live[at..]);
5707 let guard = window
5708 .find("dflash2_guard_candidates(&cand, n_vocab,")
5709 .unwrap_or_else(|| panic!("the {arm} proposal must guard its candidates"));
5710 let remap = window
5711 .find("*c = map[*c as usize];")
5712 .unwrap_or_else(|| panic!("the {arm} proposal still remaps through d2t"));
5713 assert!(
5714 guard < remap,
5715 "the {arm} guard must run before the d2t remap"
5716 );
5717 }
5718 }
5719
5720 #[test]
5721 fn max_tokens_caps_the_committed_prefix_not_only_the_visible_slice() {
5722 // A round crossing the scheduler's 32-token quantum is not terminal when the
5723 // request still has room. The whole accepted prefix stays public and committed.
5724 assert_eq!(dspark_commit_limit(5, 30, 100), (5, false));
5725 // The same round at the true request boundary is clamped and terminal so the
5726 // parked cache cannot contain rows the worker did not publish.
5727 assert_eq!(dspark_commit_limit(5, 30, 33), (3, true));
5728 assert_eq!(dspark_commit_limit(2, 3, 10), (2, false));
5729 assert_eq!(dspark_commit_limit(1, 0, 1), (1, false));
5730 }
5731
5732 /// f32 -> bf16 bytes (truncation; test values are bf16-exact small integers).
5733 fn bf16(vals: &[f32]) -> Vec<u8> {
5734 vals.iter()
5735 .flat_map(|v| ((v.to_bits() >> 16) as u16).to_le_bytes())
5736 .collect()
5737 }
5738
5739 const V: usize = 8; // test vocab
5740 const R: usize = 2; // selector rank
5741 const K: usize = 2; // top_k
5742
5743 /// Codebooks for the chain tests: pred rows are one-hot-ish, succ rows chosen so
5744 /// the slot-1 winner FLIPS with the slot-0 choice.
5745 #[allow(clippy::identity_op)] // allow: the explicit +0/*1/>>0 terms document the lane/byte symmetry of the reference layout
5746 fn books() -> (Vec<u8>, Vec<u8>) {
5747 let mut pred = vec![0f32; V * R];
5748 pred[0] = 1.0; // tok 0: [1, 0] (the anchor)
5749 pred[1 * R + 1] = 1.0; // tok 1: [0, 1]
5750 pred[2 * R] = 1.0; // tok 2: [1, 0]
5751 let mut succ = vec![0f32; V * R];
5752 succ[1 * R] = 2.0; // tok 1: [2, 0]
5753 succ[2 * R + 1] = 5.0; // tok 2: [0, 5]
5754 succ[3 * R + 1] = 3.0; // tok 3: [0, 3]
5755 succ[4 * R] = 10.0; // tok 4: [10, 0]
5756 (bf16(&pred), bf16(&succ))
5757 }
5758
5759 #[test]
5760 fn selector_walk_is_a_chain_not_per_slot_argmax() {
5761 let (pred, succ) = books();
5762 // slot 0 candidates {1, 2}, slot 1 candidates {3, 4}; hproj all-ones.
5763 let cand: Vec<u32> = vec![1, 2, 3, 4];
5764 let hproj = vec![1.0f32; 2 * R];
5765 // Anchor 0 (pred [1,0]): slot 0 scores = <[1,0],succ> -> tok1: 2, tok2: 0
5766 // -> picks 1. Slot 1 must then walk from pred[1]=[0,1]: tok3 scores 3,
5767 // tok4 scores 0 -> picks 3. A mutation that seeds every slot from the ANCHOR
5768 // (pred[0]=[1,0]) scores tok3: 0 / tok4: 10 and picks 4 instead — the chain
5769 // IS the semantics (reference CandidateSelector.select: `predecessor` is the
5770 // previously CHOSEN candidate, seeded by anchor_ids).
5771 let path = dflash2_walk_greedy(&pred, &succ, V, R, K, &[0.0; 4], &cand, &hproj, 0, 2);
5772 assert_eq!(
5773 path,
5774 vec![1, 3],
5775 "walk must seed slot p from slot p-1's CHOSEN candidate \
5776 (z-lab model.py CandidateSelector.select)"
5777 );
5778 }
5779
5780 #[test]
5781 fn selector_walk_unary_term_participates() {
5782 let (pred, succ) = books();
5783 let cand: Vec<u32> = vec![1, 2, 3, 4];
5784 let hproj = vec![1.0f32; 2 * R];
5785 // unary +10 on slot-0 candidate 2 overrides the bilinear 2-vs-0 margin;
5786 // the chain then walks from pred[2]=[1,0] and slot 1 flips to tok 4.
5787 let path = dflash2_walk_greedy(
5788 &pred,
5789 &succ,
5790 V,
5791 R,
5792 K,
5793 &[0.0, 10.0, 0.0, 0.0],
5794 &cand,
5795 &hproj,
5796 0,
5797 2,
5798 );
5799 assert_eq!(
5800 path,
5801 vec![2, 4],
5802 "score = unary + bilinear (reference: `unary[:, position] + einsum(...)`); \
5803 dropping the unary term picks tok 1 here"
5804 );
5805 }
5806
5807 #[test]
5808 fn selector_walk_hidden_gate_participates() {
5809 let (pred, succ) = books();
5810 let cand: Vec<u32> = vec![1, 2, 3, 4];
5811 // hproj [0, .] zeroes the pred[0]=[1,0] gate for slot 0: tok1's bilinear 2
5812 // vanishes, and the unary tiebreak (+1 on tok2) decides. The chain from tok2
5813 // (pred [1,0]) with slot-1 hproj [1,1] then picks tok4 (10 vs 0).
5814 let hproj = vec![0.0f32, 1.0, 1.0, 1.0];
5815 let path = dflash2_walk_greedy(
5816 &pred,
5817 &succ,
5818 V,
5819 R,
5820 K,
5821 &[0.0, 1.0, 0.0, 0.0],
5822 &cand,
5823 &hproj,
5824 0,
5825 2,
5826 );
5827 assert_eq!(
5828 path,
5829 vec![2, 4],
5830 "the bilinear gate is pred_row .* HIDDEN_PROJECTION (reference: \
5831 `predecessor_codebook(predecessor) * hidden[:, position]`); ignoring \
5832 hproj leaves tok1's margin standing"
5833 );
5834 }
5835
5836 #[test]
5837 fn dflash2_harvest_is_census_keyed() {
5838 // DFlash2 is mask-fill BY CONSTRUCTION (reference dflash_generate harvests
5839 // rows 1-verify_size:; card: "7 draft tokens per verification step").
5840 assert_eq!(
5841 DsparkHarvest::for_family_value(true, None, false),
5842 DsparkHarvest::Dflash
5843 );
5844 assert_eq!(
5845 DsparkHarvest::for_family_value(true, Some("dflash"), false),
5846 DsparkHarvest::Dflash
5847 );
5848 // The family key BEATS the strategy census: a (hypothetical) DFlash2 export
5849 // whose config also strategy-censuses dspark still harvests mask-fill.
5850 assert_eq!(
5851 DsparkHarvest::for_family_value(true, None, true),
5852 DsparkHarvest::Dflash
5853 );
5854 // An env override to the SHIFTED harvest contradicts the census — REFUSE,
5855 // never re-key (the postmortem's misalignment class in reverse).
5856 assert!(
5857 std::panic::catch_unwind(|| DsparkHarvest::for_family_value(
5858 true,
5859 Some("dspark"),
5860 false
5861 ))
5862 .is_err(),
5863 "MEMRA_DSPARK_HARVEST=dspark on a DFlash2 checkpoint must refuse"
5864 );
5865 // Non-DFlash2 checkpoints ride the strategy-keyed resolution (env wins).
5866 assert_eq!(
5867 DsparkHarvest::for_family_value(false, Some("dspark"), false),
5868 DsparkHarvest::Dspark
5869 );
5870 assert_eq!(
5871 DsparkHarvest::for_family_value(false, None, false),
5872 DsparkHarvest::Dflash
5873 );
5874 assert_eq!(
5875 DsparkHarvest::for_family_value(false, None, true),
5876 DsparkHarvest::Dspark,
5877 "unset env on a DSPARK-strategy export must keep the ratified census flip"
5878 );
5879 }
5880
5881 // ============ SAMPLED ADMISSION (T>0) gates — lane/dspark-sampled-admission-20260820 =
5882 // The device kernels are oracled by sample_check (filter_stats/gumbel/residual arms);
5883 // these pin the HOST math the route ships — the selector's sampled walk, the accept
5884 // rule, and the round COMPOSITION (accept + residual + bonus must reproduce the target
5885 // distribution p exactly; a mis-composition leaves every kernel individually correct,
5886 // which is why the composition arm exists — sample_check arm 6's lesson).
5887
5888 #[test]
5889 fn sampled_walk_tiny_temp_matches_greedy() {
5890 // T->0 continuity: at tiny temperature the candidate softmax concentrates on the
5891 // argmax and the sampled walk must reproduce the greedy chain token-for-token
5892 // (the frspec gate-(1) shape). Same fixture as the chain test.
5893 let (pred, succ) = books();
5894 let cand: Vec<u32> = vec![1, 2, 3, 4];
5895 let hproj = vec![1.0f32; 2 * R];
5896 let greedy = dflash2_walk_greedy(&pred, &succ, V, R, K, &[0.0; 4], &cand, &hproj, 0, 2);
5897 let mut u = || 0.5f32;
5898 let (path, q_chosen, q_rows) = dflash2_walk_sampled(
5899 &pred, &succ, V, R, K, &[0.0; 4], &cand, &hproj, 0, 2, 1e-6, &mut u,
5900 );
5901 assert_eq!(
5902 path, greedy,
5903 "tiny-T sampled walk must equal the greedy chain"
5904 );
5905 assert_eq!(q_rows.len(), 2 * K);
5906 for (p, &q) in path.iter().zip(&q_chosen) {
5907 let _ = p;
5908 assert!(
5909 q > 0.999,
5910 "tiny-T chosen-candidate prob must be ~1, got {q}"
5911 );
5912 }
5913 }
5914
5915 #[test]
5916 fn sampled_walk_records_the_distribution_it_samples() {
5917 // The recorded q IS the proposal: per slot the q_rows sum to ~1, q_chosen is the
5918 // row value at the drawn candidate, and the CDF walk picks the candidate whose
5919 // cumulative bracket contains the uniform.
5920 let (pred, succ) = books();
5921 let cand: Vec<u32> = vec![1, 2, 3, 4];
5922 let hproj = vec![1.0f32; 2 * R];
5923 // slot-0 scores at anchor 0: tok1 = 2.0, tok2 = 0.0; at T=2.0 the softmax is
5924 // e^1/(e^1+e^0) ~= 0.731 for tok1.
5925 let q1 = (1f64.exp() / (1f64.exp() + 1.0)) as f32;
5926 for (u0, want0) in [(q1 - 0.01, 1u32), (q1 + 0.01, 2u32)] {
5927 let mut seq = vec![u0, 0.0f32].into_iter();
5928 let mut u = move || seq.next().unwrap();
5929 let (path, q_chosen, q_rows) = dflash2_walk_sampled(
5930 &pred, &succ, V, R, K, &[0.0; 4], &cand, &hproj, 0, 2, 2.0, &mut u,
5931 );
5932 assert_eq!(
5933 path[0], want0,
5934 "CDF walk must place u={u0} in the right candidate bracket"
5935 );
5936 let row0: f32 = q_rows[..K].iter().sum();
5937 assert!(
5938 (row0 - 1.0).abs() < 1e-5,
5939 "slot-0 q must sum to 1, got {row0}"
5940 );
5941 let ci = cand[..K].iter().position(|&c| c == path[0]).unwrap();
5942 assert_eq!(
5943 q_chosen[0], q_rows[ci],
5944 "q_chosen must be the recorded row prob of the drawn candidate"
5945 );
5946 assert!(
5947 (q_rows[0] - q1).abs() < 1e-4,
5948 "slot-0 tok1 prob must be softmax(scores/T), got {} want {q1}",
5949 q_rows[0]
5950 );
5951 }
5952 }
5953
5954 #[test]
5955 fn sampled_walk_chains_the_drawn_candidate() {
5956 // The chain conditions on the DRAWN candidate, not the argmax: forcing the
5957 // low-prob slot-0 candidate (tok 2) flips slot 1's winner (tok 4 over tok 3),
5958 // exactly like the greedy chain test — a walk that seeds every slot from the
5959 // anchor (or the argmax) fails here.
5960 let (pred, succ) = books();
5961 let cand: Vec<u32> = vec![1, 2, 3, 4];
5962 let hproj = vec![1.0f32; 2 * R];
5963 let mut seq = vec![0.99f32, 0.01].into_iter();
5964 let mut u = move || seq.next().unwrap();
5965 let (path, _, _) = dflash2_walk_sampled(
5966 &pred, &succ, V, R, K, &[0.0; 4], &cand, &hproj, 0, 2, 2.0, &mut u,
5967 );
5968 assert_eq!(path[0], 2, "u=0.99 must draw the low-prob candidate");
5969 assert_eq!(
5970 path[1], 4,
5971 "slot 1 must walk from pred[2] (the DRAWN token), which scores tok4 at 10 \
5972 — chaining from the anchor or the argmax picks tok3"
5973 );
5974 }
5975
5976 #[test]
5977 fn rejection_accept_walk_is_the_leviathan_rule() {
5978 // accept while u*q < p, strict, prefix-stop at the first reject.
5979 assert_eq!(
5980 rejection_accept_len(&[0.5, 0.5], &[0.5, 0.5], &[0.9, 0.9]),
5981 2
5982 );
5983 assert_eq!(
5984 rejection_accept_len(&[0.5, 0.5], &[0.5, 0.5], &[1.0, 0.0]),
5985 0
5986 );
5987 // u*q == p is a REJECT (strict <) — the frspec test byte-for-byte.
5988 assert_eq!(rejection_accept_len(&[0.25], &[0.5], &[0.5]), 0);
5989 // q == 0 with p > 0 accepts unconditionally (the skey exactness signature).
5990 assert_eq!(rejection_accept_len(&[1e-6], &[0.0], &[0.999]), 1);
5991 // prefix stop: slot 1 rejects, slot 2 never tested.
5992 assert_eq!(
5993 rejection_accept_len(&[0.9, 0.0, 0.9], &[0.1, 0.9, 0.1], &[0.5, 0.5, 0.5]),
5994 1
5995 );
5996 }
5997
5998 // ---- round composition: the committed-token distribution must equal the target p ----
5999 // CPU mirror of the shipped rule for the FIRST post-anchor slot: draft x ~ q, accept
6000 // iff u*q(x) < p(x) (rejection_accept_len — the shipped fn), else commit a residual
6001 // sample ~ norm(max(0, p - q)). The marginal of the committed token is exactly p —
6002 // for ANY q — which is the whole correctness claim of the route's sampled admission.
6003
6004 fn tv(a: &[f64], b: &[f64]) -> f64 {
6005 a.iter().zip(b).map(|(x, y)| (x - y).abs()).sum::<f64>() / 2.0
6006 }
6007
6008 /// One composed trial with an injectable accept rule; returns the committed token.
6009 #[allow(clippy::neg_cmp_op_on_partial_ord)] // allow: NaN must take this branch; !(a > b) is not a <= b under IEEE comparisons
6010 fn compose_once(
6011 p: &[f32],
6012 q: &[f32],
6013 u_draw: f32,
6014 u_accept: f32,
6015 u_resid: f32,
6016 invert_accept: bool,
6017 skip_q_in_residual: bool,
6018 ) -> usize {
6019 let n = p.len();
6020 // draft ~ q (CDF walk, the walk_sampled convention)
6021 let mut acc = 0f64;
6022 let mut x = n - 1;
6023 for (i, &qi) in q.iter().enumerate() {
6024 acc += qi as f64;
6025 if (u_draw as f64) < acc {
6026 x = i;
6027 break;
6028 }
6029 }
6030 let accepted = if invert_accept {
6031 !((u_accept as f64) * (q[x] as f64) < p[x] as f64)
6032 } else {
6033 rejection_accept_len(&p[x..=x], &q[x..=x], &[u_accept]) == 1
6034 };
6035 if accepted {
6036 return x;
6037 }
6038 // residual ~ norm(max(0, p - q)) (the device kernel's fixed-order CDF walk)
6039 let r: Vec<f64> = p
6040 .iter()
6041 .zip(q)
6042 .map(|(&pi, &qi)| {
6043 let qq = if skip_q_in_residual { 0.0 } else { qi as f64 };
6044 (pi as f64 - qq).max(0.0)
6045 })
6046 .collect();
6047 let total: f64 = r.iter().sum();
6048 let mut acc = 0f64;
6049 let target = u_resid as f64 * total;
6050 for (i, &ri) in r.iter().enumerate() {
6051 acc += ri;
6052 if acc >= target && ri > 0.0 {
6053 return i;
6054 }
6055 }
6056 n - 1
6057 }
6058
6059 fn compose_tv(q: &[f32], invert_accept: bool, skip_q_in_residual: bool) -> f64 {
6060 // target p: a spread-out 8-token distribution
6061 let p: Vec<f32> = vec![0.30, 0.22, 0.15, 0.12, 0.09, 0.06, 0.04, 0.02];
6062 let trials = 200_000usize;
6063 let mut counts = [0f64; V];
6064 for t in 0..trials {
6065 // three independent uniforms per trial off the host Philox stream
6066 let u_draw = crate::spec::host_u01(7, (t * 3) as u32);
6067 let u_accept = crate::spec::host_u01(7, (t * 3 + 1) as u32);
6068 let u_resid = crate::spec::host_u01(7, (t * 3 + 2) as u32);
6069 counts[compose_once(
6070 &p,
6071 q,
6072 u_draw,
6073 u_accept,
6074 u_resid,
6075 invert_accept,
6076 skip_q_in_residual,
6077 )] += 1.0;
6078 }
6079 let emp: Vec<f64> = counts.iter().map(|c| c / trials as f64).collect();
6080 let pf: Vec<f64> = p.iter().map(|&v| v as f64).collect();
6081 tv(&emp, &pf)
6082 }
6083
6084 #[test]
6085 fn sampled_round_composition_matches_the_target() {
6086 // Monte-Carlo floor at 200k draws over 8 tokens ~ 0.004 TV; bound 0.01.
6087 // (a) full-vocab q (the Rows families' shape), far from p;
6088 let q_rows: Vec<f32> = vec![0.02, 0.04, 0.06, 0.09, 0.12, 0.15, 0.22, 0.30];
6089 // (b) SPARSE candidate-set q (the DFlash2 selector shape: support on 2 of 8).
6090 let q_sparse: Vec<f32> = vec![0.0, 0.7, 0.0, 0.3, 0.0, 0.0, 0.0, 0.0];
6091 for (name, q) in [("rows", &q_rows), ("sparse", &q_sparse)] {
6092 let d = compose_tv(q, false, false);
6093 assert!(
6094 d < 0.01,
6095 "composition[{name}]: committed-token distribution must equal p \
6096 (TV {d:.4} >= 0.01)"
6097 );
6098 }
6099 }
6100
6101 #[test]
6102 fn composition_teeth_inverted_accept_fails() {
6103 // DECISIVE teeth: the same harness with the accept inequality inverted must
6104 // MISS the target — otherwise the composition gate is vacuous.
6105 let q: Vec<f32> = vec![0.02, 0.04, 0.06, 0.09, 0.12, 0.15, 0.22, 0.30];
6106 let d = compose_tv(&q, true, false);
6107 assert!(
6108 d > 0.05,
6109 "inverted accept rule must fail the composition bound (TV {d:.4})"
6110 );
6111 }
6112
6113 #[test]
6114 fn composition_teeth_residual_without_q_fails() {
6115 // Sampling the reject slot from p instead of norm(max(0, p-q)) double-counts
6116 // the overlap mass min(p,q) — the committed distribution leaves p.
6117 let q: Vec<f32> = vec![0.0, 0.7, 0.0, 0.3, 0.0, 0.0, 0.0, 0.0];
6118 let d = compose_tv(&q, false, true);
6119 assert!(
6120 d > 0.05,
6121 "residual that skips the q subtraction must fail the bound (TV {d:.4})"
6122 );
6123 }
6124
6125 // ---- PENALIZED round composition (lane/dspark-penalized-sampled-20260821) ----
6126 // Multi-slot rounds where the penalty state EVOLVES within the round. The base trunk
6127 // logits are state-independent, so ALL context dependence flows through penalties —
6128 // the sharpest fixture for "verify row j's target is penalized by the tokens accepted
6129 // before j in the same round", and the proposal concentrates on ONE token, so the
6130 // dominant drafted block is a self-hit (a drafted token penalizing its own successor
6131 // — the within-round case a frozen round-start window cannot see). The reference is
6132 // EXACT (analytic chain p1(a)·p2(b|a), the plain sampler's semantics); the spec arm
6133 // is the shipped round rule — chain draw from q, `rejection_accept_len`, residual
6134 // norm(max(0, p−q)) at the reject slot, bonus from the one-past row on full accept —
6135 // with per-slot penalized p (mirroring penalize_logits_rows_inc_f32's window rule).
6136
6137 const PV: usize = 6;
6138 const PEN_REP: f32 = 1.6;
6139 const PEN_FREQ: f32 = 0.8;
6140 const PEN_PRESENT: f32 = 1.2;
6141
6142 fn pen_base() -> Vec<f32> {
6143 vec![1.5, 0.8, 0.3, -0.2, -0.7, -1.2]
6144 }
6145
6146 /// The proposal: heavy on token 0 so drafted blocks repeat it (the self-hit case).
6147 fn pen_q() -> Vec<f32> {
6148 vec![0.85, 0.06, 0.04, 0.03, 0.01, 0.01]
6149 }
6150
6151 /// CPU mirror of penalize_logits_f32 / the plain sampler's apply_penalties: first
6152 /// occurrence does the whole adjustment, cnt = occurrences in the window, rep
6153 /// divides positive logits and multiplies negative ones.
6154 fn pen_apply(logits: &mut [f32], window: &[u32]) {
6155 let mut seen: Vec<u32> = Vec::new();
6156 for &id in window {
6157 if seen.contains(&id) {
6158 continue;
6159 }
6160 seen.push(id);
6161 let cnt = window.iter().filter(|&&h| h == id).count() as f32;
6162 let v = &mut logits[id as usize];
6163 if *v > 0.0 {
6164 *v /= PEN_REP;
6165 } else {
6166 *v *= PEN_REP;
6167 }
6168 *v -= PEN_FREQ * cnt + PEN_PRESENT;
6169 }
6170 }
6171
6172 /// Penalized target at history `window` (temp 1.0, no truncation filters — those are
6173 /// orthogonal and covered by the unpenalized composition tests + kernel oracles).
6174 fn pen_target(window: &[u32]) -> Vec<f64> {
6175 let mut l = pen_base();
6176 pen_apply(&mut l, window);
6177 let mx = l.iter().cloned().fold(f32::NEG_INFINITY, f32::max) as f64;
6178 let ex: Vec<f64> = l.iter().map(|&v| ((v as f64) - mx).exp()).collect();
6179 let z: f64 = ex.iter().sum();
6180 ex.iter().map(|v| v / z).collect()
6181 }
6182
6183 /// Which penalty-arm mutation the harness runs — `None` is the shipped rule.
6184 #[derive(Clone, Copy, PartialEq)]
6185 enum PenMutation {
6186 None,
6187 /// All rows (walk + bonus) penalized with the ROUND-START window only — the
6188 /// within-round update dropped (the frspec per-round posture; what a flat
6189 /// `penalize_logits_rows` launch would ship).
6190 FrozenWindow,
6191 /// Reject-slot residual computed from the UNPENALIZED p (a raw-tlogits column
6192 /// copy instead of the penalized buffer).
6193 UnpenalizedResidual,
6194 /// Full-accept bonus drawn from the UNPENALIZED one-past row.
6195 UnpenalizedBonus,
6196 }
6197
6198 /// Emit `want` committed tokens through spec rounds of `k` drafts (the shipped round
6199 /// rule, penalty-aware) and return them. `hist0` = the pre-stream window (the prompt
6200 /// seed); uniforms come off the injected stream.
6201 fn pen_round_stream(
6202 hist0: &[u32],
6203 k: usize,
6204 want: usize,
6205 mutation: PenMutation,
6206 next_u: &mut dyn FnMut() -> f32,
6207 ) -> Vec<u32> {
6208 let q = pen_q();
6209 let mut hist: Vec<u32> = hist0.to_vec();
6210 let mut committed: Vec<u32> = Vec::new();
6211 while committed.len() < want {
6212 // draft k tokens ~ q (fixed-order CDF walk, the walk_sampled convention)
6213 let drafted: Vec<u32> = (0..k)
6214 .map(|_| {
6215 let u = next_u() as f64;
6216 let mut acc = 0f64;
6217 let mut bi = 0usize;
6218 for (i, &qi) in q.iter().enumerate() {
6219 acc += qi as f64;
6220 if u < acc {
6221 bi = i;
6222 break;
6223 }
6224 }
6225 bi as u32
6226 })
6227 .collect();
6228 // per-slot penalized p at the drafted ids (row j's window = hist ++ drafted[..j])
6229 let pj: Vec<f32> = (0..k)
6230 .map(|j| {
6231 let win: Vec<u32> = if mutation == PenMutation::FrozenWindow {
6232 hist.clone()
6233 } else {
6234 hist.iter()
6235 .copied()
6236 .chain(drafted[..j].iter().copied())
6237 .collect()
6238 };
6239 pen_target(&win)[drafted[j] as usize] as f32
6240 })
6241 .collect();
6242 let qj: Vec<f32> = drafted.iter().map(|&d| q[d as usize]).collect();
6243 let us: Vec<f32> = (0..k).map(|_| next_u()).collect();
6244 let m = rejection_accept_len(&pj, &qj, &us);
6245 committed.extend_from_slice(&drafted[..m]);
6246 hist.extend_from_slice(&drafted[..m]);
6247 let next: u32 = if m == k {
6248 // bonus ~ p at the one-past row (window carries the WHOLE drafted block)
6249 let win: Vec<u32> = if matches!(
6250 mutation,
6251 PenMutation::FrozenWindow | PenMutation::UnpenalizedBonus
6252 ) {
6253 if mutation == PenMutation::UnpenalizedBonus {
6254 Vec::new() // raw row: no penalties at all
6255 } else {
6256 hist[..hist.len() - m].to_vec() // round-start window
6257 }
6258 } else {
6259 hist.clone()
6260 };
6261 let p = pen_target(&win);
6262 let u = next_u() as f64;
6263 let mut acc = 0f64;
6264 let mut bi = PV - 1;
6265 for (i, &pi) in p.iter().enumerate() {
6266 acc += pi;
6267 if u < acc {
6268 bi = i;
6269 break;
6270 }
6271 }
6272 bi as u32
6273 } else {
6274 // residual ~ norm(max(0, p_m − q)) at the reject slot's state
6275 let win: Vec<u32> = match mutation {
6276 PenMutation::UnpenalizedResidual => Vec::new(),
6277 PenMutation::FrozenWindow => hist[..hist.len() - m].to_vec(),
6278 _ => hist.clone(),
6279 };
6280 let p = pen_target(&win);
6281 let r: Vec<f64> = p
6282 .iter()
6283 .zip(&q)
6284 .map(|(&pi, &qi)| (pi - qi as f64).max(0.0))
6285 .collect();
6286 let total: f64 = r.iter().sum();
6287 let target = next_u() as f64 * total;
6288 let mut acc = 0f64;
6289 let mut bi = PV - 1;
6290 for (i, &ri) in r.iter().enumerate() {
6291 acc += ri;
6292 if acc >= target && ri > 0.0 {
6293 bi = i;
6294 break;
6295 }
6296 }
6297 bi as u32
6298 };
6299 committed.push(next);
6300 hist.push(next);
6301 }
6302 committed.truncate(want);
6303 committed
6304 }
6305
6306 /// Joint TV of the spec arm's first two committed tokens vs the EXACT penalized
6307 /// chain p1(a)·p2(b|a) — the plain sampler's distribution over the same two steps.
6308 fn pen_compose_tv(hist0: &[u32], k: usize, mutation: PenMutation) -> f64 {
6309 let trials = 300_000usize;
6310 let mut counts = vec![0f64; PV * PV];
6311 for t in 0..trials {
6312 // stride 64: a k<=2 round consumes <=2k+1 uniforms, <=2 rounds per trial
6313 let mut ctr = (t as u32) * 64;
6314 let mut next_u = move || {
6315 let u = crate::spec::host_u01(11, ctr);
6316 ctr = ctr.wrapping_add(1);
6317 u
6318 };
6319 let s = pen_round_stream(hist0, k, 2, mutation, &mut next_u);
6320 counts[s[0] as usize * PV + s[1] as usize] += 1.0;
6321 }
6322 let p1 = pen_target(hist0);
6323 let mut tv = 0f64;
6324 for a in 0..PV {
6325 let mut w: Vec<u32> = hist0.to_vec();
6326 w.push(a as u32);
6327 let p2 = pen_target(&w);
6328 for b in 0..PV {
6329 let refp = p1[a] * p2[b];
6330 tv += (counts[a * PV + b] / trials as f64 - refp).abs();
6331 }
6332 }
6333 tv / 2.0
6334 }
6335
6336 #[test]
6337 fn penalized_round_composition_matches_the_penalized_chain() {
6338 // MC floor at 300k trials over 36 cells ~ 0.004 TV; bound 0.01. Fixture (a):
6339 // k=2, empty prompt window — the drafted pair (0,0) dominates, so slot 2's
6340 // accept is the SELF-HIT case (its own predecessor was drafted this round).
6341 // Fixture (b): k=1, prompt window [1,1] — the bonus is the successor of a
6342 // same-round accepted draft, and cnt>1 exercises the freq×count path.
6343 for (name, hist0, k) in [
6344 ("k2-selfhit", vec![], 2usize),
6345 ("k1-bonus-successor", vec![1u32, 1u32], 1usize),
6346 ] {
6347 let d = pen_compose_tv(&hist0, k, PenMutation::None);
6348 eprintln!("penalized composition[{name}]: TV {d:.4} (bound 0.01)");
6349 assert!(
6350 d < 0.01,
6351 "penalized composition[{name}]: committed-token distribution must equal \
6352 the penalized chain (TV {d:.4} >= 0.01)"
6353 );
6354 }
6355 }
6356
6357 #[test]
6358 fn penalized_composition_teeth_frozen_window_fails() {
6359 // DECISIVE teeth: penalizing every verify row with the ROUND-START window —
6360 // dropping the within-round penalty update, i.e. a flat penalize_logits_rows
6361 // launch where the route ships penalize_logits_rows_inc — must MISS the
6362 // penalized chain, or the composition gate cannot see the one thing this lane
6363 // adds over the frozen-window prior art.
6364 let d = pen_compose_tv(&[], 2, PenMutation::FrozenWindow);
6365 eprintln!("penalized teeth[frozen-window]: TV {d:.4} (must exceed 0.05)");
6366 assert!(
6367 d > 0.05,
6368 "within-round penalty update dropped (frozen round-start window) must FAIL \
6369 the composition bound (TV {d:.4})"
6370 );
6371 }
6372
6373 #[test]
6374 fn penalized_composition_teeth_unpenalized_residual_fails() {
6375 // The reject-slot residual must read the PENALIZED column: a raw-tlogits column
6376 // copy (p_raw − q) commits from the wrong measure. Non-empty prompt window so
6377 // even round-start reject slots hit the mutation (an empty-window fixture only
6378 // sees it on within-round rejects and the margin thins to ~0.055).
6379 let d = pen_compose_tv(&[1, 1], 2, PenMutation::UnpenalizedResidual);
6380 eprintln!("penalized teeth[unpenalized-residual]: TV {d:.4} (must exceed 0.05)");
6381 assert!(
6382 d > 0.05,
6383 "residual computed from the unpenalized p must FAIL the composition bound \
6384 (TV {d:.4})"
6385 );
6386 }
6387
6388 #[test]
6389 fn penalized_composition_teeth_unpenalized_bonus_fails() {
6390 // The full-accept bonus row must carry the whole drafted block in its window:
6391 // a raw one-past row draw commits the unpenalized measure right after a
6392 // same-round accept.
6393 let d = pen_compose_tv(&[1, 1], 1, PenMutation::UnpenalizedBonus);
6394 eprintln!("penalized teeth[unpenalized-bonus]: TV {d:.4} (must exceed 0.05)");
6395 assert!(
6396 d > 0.05,
6397 "bonus drawn from the unpenalized one-past row must FAIL the composition \
6398 bound (TV {d:.4})"
6399 );
6400 }
6401}
6402
6403#[cfg(test)]
6404mod dspark_harvest_tests {
6405 use super::{DsparkHarvest, DsparkVtPolicy, dspark_accept_prefix, dspark_strategy_census};
6406
6407 const B: usize = 7; // q38 arm-a block_size
6408
6409 #[test]
6410 fn dspark_strategy_requires_shifted_harvest() {
6411 let h = DsparkHarvest::Dspark;
6412 assert_eq!(
6413 h.first_row(),
6414 0,
6415 "DSPARK-strategy checkpoints (SpecForge OnlineDSparkModel, \
6416 training.strategy=dspark — the q38 arm-a export) supervise ALL rows with \
6417 SHIFTED labels: label_offsets = arange(1, block_size+1), i.e. the ANCHOR \
6418 row's output is draft 1 (specforge/algorithms/common/\
6419 dflash_family_model.py:816; sglang v0.5.17 dspark_draft.py:248,260). \
6420 Harvesting from row 1 re-opens the DSPARK-POSTMORTEM-20260820 slot \
6421 misalignment (accept 2.9 -> 1.43)."
6422 );
6423 assert_eq!(
6424 h.n_drafts(B),
6425 B,
6426 "DSpark harvests gamma = block_size drafts per round (sglang \
6427 dspark_config.py:269, verify_num_draft_tokens = gamma+1); b-1 is the \
6428 DFlash mask-fill count and drops the best-trained slot \
6429 (DSPARK-POSTMORTEM-20260820.md §3-H1)."
6430 );
6431 for row in 0..B {
6432 assert_eq!(
6433 h.trained_offset_of_row(row),
6434 row + 1,
6435 "OnlineDSparkModel trains row k to predict anchor+k+1 \
6436 (dflash_family_model.py:816); a same-position (mask-fill) mapping \
6437 here verifies every slot one position early — the postmortem's \
6438 collapse."
6439 );
6440 }
6441 }
6442
6443 #[test]
6444 fn dflash_strategy_keeps_mask_fill_harvest() {
6445 // Guards the reverse mutation: z-lab dflash checkpoints (the gemma arm) are
6446 // mask-fill — row k FILLS anchor+k, the anchor row is loss-excluded
6447 // (dflash_family_model.py:453-472). Shifting THEM would break the gemma arm.
6448 let h = DsparkHarvest::Dflash;
6449 assert_eq!(h.first_row(), 1, "DFlash drafts start at mask row 1");
6450 assert_eq!(h.n_drafts(B), B - 1, "DFlash harvests block_size-1 drafts");
6451 for row in 1..B {
6452 assert_eq!(h.trained_offset_of_row(row), row);
6453 }
6454 }
6455
6456 #[test]
6457 fn every_candidate_verifies_the_position_its_row_was_trained_for() {
6458 // The round's invariant: draft candidate i (1-based; verified against the
6459 // trunk's prediction for anchor+i) is filled from drafter output row
6460 // first_row + i - 1. Alignment == that row was TRAINED for offset i.
6461 for h in [DsparkHarvest::Dflash, DsparkHarvest::Dspark] {
6462 for i in 1..=h.n_drafts(B) {
6463 let row = h.first_row() + i - 1;
6464 assert_eq!(
6465 h.trained_offset_of_row(row),
6466 i,
6467 "{h:?}: candidate {i} rides row {row}, which is trained for \
6468 offset {} — harvest misaligned",
6469 h.trained_offset_of_row(row)
6470 );
6471 }
6472 }
6473 }
6474
6475 #[test]
6476 fn env_seam_parses_and_refuses() {
6477 assert_eq!(
6478 DsparkHarvest::from_env_value(None),
6479 DsparkHarvest::Dflash,
6480 "the ENV-ONLY parser keeps the historical arm; the ratified strategy-keyed \
6481 default lives in resolve_value (checkpoint census), not here"
6482 );
6483 assert_eq!(
6484 DsparkHarvest::from_env_value(Some("dspark")),
6485 DsparkHarvest::Dspark
6486 );
6487 assert_eq!(
6488 DsparkHarvest::from_env_value(Some("dflash")),
6489 DsparkHarvest::Dflash
6490 );
6491 assert!(
6492 std::panic::catch_unwind(|| DsparkHarvest::from_env_value(Some("shifted"))).is_err(),
6493 "unknown harvest values must REFUSE, not default"
6494 );
6495 assert_eq!(
6496 DsparkHarvest::from_name("dspark"),
6497 Some(DsparkHarvest::Dspark)
6498 );
6499 assert_eq!(
6500 DsparkHarvest::from_name("dflash"),
6501 Some(DsparkHarvest::Dflash)
6502 );
6503 assert_eq!(DsparkHarvest::from_name("mask-fill"), None);
6504 }
6505
6506 /// The owner-ratified default flips (2026-08-20). Each assertion names its
6507 /// evidence; mutating either resolve back to the old default fails these.
6508 #[test]
6509 fn ratified_default_harvest_is_strategy_keyed() {
6510 // DSPARK-strategy checkpoint + unset env = the shifted harvest (B1: accept
6511 // 1.38->2.41 agentic / 1.53->3.66 math, E2E ALL EXACT x5, interleaved x5).
6512 assert_eq!(
6513 DsparkHarvest::resolve_value(None, true),
6514 DsparkHarvest::Dspark,
6515 "owner-ratified 2026-08-20: unset env defaults a DSPARK-strategy \
6516 checkpoint to the shifted harvest (DSPARK-POSTMORTEM-20260820.md B1)"
6517 );
6518 // mask-fill checkpoint + unset env = the historical arm, byte-identical.
6519 assert_eq!(
6520 DsparkHarvest::resolve_value(None, false),
6521 DsparkHarvest::Dflash
6522 );
6523 assert_eq!(
6524 DsparkHarvest::resolve_value(Some(""), false),
6525 DsparkHarvest::Dflash
6526 );
6527 // Explicit env overrides the census in BOTH directions (the A/B seam).
6528 assert_eq!(
6529 DsparkHarvest::resolve_value(Some("dflash"), true),
6530 DsparkHarvest::Dflash
6531 );
6532 assert_eq!(
6533 DsparkHarvest::resolve_value(Some("dspark"), false),
6534 DsparkHarvest::Dspark
6535 );
6536 // Unknown values still REFUSE through the resolve path.
6537 assert!(
6538 std::panic::catch_unwind(|| DsparkHarvest::resolve_value(Some("shifted"), true))
6539 .is_err()
6540 );
6541 }
6542
6543 #[test]
6544 fn strategy_census_reads_the_checkpoint_not_the_env() {
6545 // The q38 arm-a export shape: both signals present.
6546 let q38 = r#"{"architectures": ["Qwen3DSparkModel"], "block_size": 7,
6547 "dflash_config": {"projector_type": "dspark", "markov_rank": 256}}"#;
6548 assert!(dspark_strategy_census(q38));
6549 // Either signal alone suffices.
6550 assert!(dspark_strategy_census(
6551 r#"{"architectures": ["Qwen3DSparkModel"]}"#
6552 ));
6553 assert!(dspark_strategy_census(
6554 r#"{"dflash_config": {"projector_type": "dspark"}}"#
6555 ));
6556 // A mask-fill DFlash export carries neither -> historical default.
6557 let dflash = r#"{"architectures": ["Qwen3DFlashModel"],
6558 "dflash_config": {"attention_mode": "gqa"}}"#;
6559 assert!(!dspark_strategy_census(dflash));
6560 assert!(!dspark_strategy_census("{}"));
6561 }
6562
6563 #[test]
6564 fn ratified_default_vt_is_confidence_slot_tau_half() {
6565 // Head-carrying checkpoint + unset env = confidence-slot tau=.5 (H4 cell 3:
6566 // the tau ladder's knee; cell 2: 93.9%/97.7% of fixed-8 accept at wall >=
6567 // the reactive ladder, exactness 11/11 ALL EXACT).
6568 assert_eq!(
6569 DsparkVtPolicy::resolve_value(None, None, None, true),
6570 DsparkVtPolicy::ConfidenceSlot { tau: 0.5 },
6571 "owner-ratified 2026-08-20: unset MEMRA_DSPARK_VT defaults to \
6572 confidence-slot tau=.5 on a head-carrying checkpoint (H4 cells 2-3)"
6573 );
6574 // tau env still steers the default arm (and a bad tau still refuses).
6575 assert_eq!(
6576 DsparkVtPolicy::resolve_value(None, Some("0.35"), None, true),
6577 DsparkVtPolicy::ConfidenceSlot { tau: 0.35 }
6578 );
6579 assert!(
6580 std::panic::catch_unwind(|| DsparkVtPolicy::resolve_value(
6581 None,
6582 Some("nan-ish"),
6583 None,
6584 true
6585 ))
6586 .is_err()
6587 );
6588 // Census: no accept-rate head -> nothing to schedule with -> ladder.
6589 assert_eq!(
6590 DsparkVtPolicy::resolve_value(None, None, None, false),
6591 DsparkVtPolicy::Ladder
6592 );
6593 // MEMRA_DFLASH_ADAPT=0 is an explicit fixed-window request: honored.
6594 assert_eq!(
6595 DsparkVtPolicy::resolve_value(None, None, Some("0"), true),
6596 DsparkVtPolicy::Ladder
6597 );
6598 // Explicit values keep their exact prior semantics through resolve.
6599 assert_eq!(
6600 DsparkVtPolicy::resolve_value(Some("ladder"), None, None, true),
6601 DsparkVtPolicy::Ladder
6602 );
6603 assert_eq!(
6604 DsparkVtPolicy::resolve_value(Some("confidence"), Some("0.35"), None, true),
6605 DsparkVtPolicy::Confidence { tau: 0.35 }
6606 );
6607 // Explicit confidence mode with ADAPT=0 stays a refusal.
6608 assert!(
6609 std::panic::catch_unwind(|| DsparkVtPolicy::resolve_value(
6610 Some("confidence-slot"),
6611 None,
6612 Some("0"),
6613 true
6614 ))
6615 .is_err()
6616 );
6617 }
6618
6619 /// End-to-end alignment fixture in miniature: a mock drafter whose row r argmaxes
6620 /// to token BASE + (its trained offset under the DSPARK strategy), and a mock trunk
6621 /// whose prediction for anchor+j is BASE + j. The DSpark harvest accepts the whole
6622 /// block; feeding the same drafter through the mask-fill harvest accepts ZERO —
6623 /// the postmortem's collapse reproduced as pure logic.
6624 #[test]
6625 fn dspark_trained_rows_through_mask_fill_harvest_accept_nothing() {
6626 const BASE: u32 = 1000;
6627 let anchor: u32 = BASE; // token at the round anchor position (offset 0)
6628 // trunk verify argmaxes: vam[j] = prediction for anchor offset j+1
6629 let vam: Vec<u32> = (1..=B as u32 + 1).map(|j| BASE + j).collect();
6630 // drafter rows trained under the DSPARK strategy: row r predicts offset r+1
6631 let dspark_trained_row_argmax =
6632 |r: usize| BASE + DsparkHarvest::Dspark.trained_offset_of_row(r) as u32;
6633
6634 // Correct (shifted) harvest: candidate i <- row i-1.
6635 let h = DsparkHarvest::Dspark;
6636 let mut cand = vec![anchor];
6637 for i in 1..=h.n_drafts(B) {
6638 cand.push(dspark_trained_row_argmax(h.first_row() + i - 1));
6639 }
6640 let vt = h.n_drafts(B) + 1;
6641 assert_eq!(
6642 dspark_accept_prefix(&cand, &vam, vt),
6643 vt - 1,
6644 "aligned harvest must accept the full block"
6645 );
6646
6647 // Mask-fill harvest of the SAME dspark-trained drafter: candidate i <- row i,
6648 // which was trained for offset i+1 — every slot one position late.
6649 let wrong = DsparkHarvest::Dflash;
6650 let mut cand_wrong = vec![anchor];
6651 for i in 1..=wrong.n_drafts(B) {
6652 cand_wrong.push(dspark_trained_row_argmax(wrong.first_row() + i - 1));
6653 }
6654 let vt_wrong = wrong.n_drafts(B) + 1;
6655 assert_eq!(
6656 dspark_accept_prefix(&cand_wrong, &vam, vt_wrong),
6657 0,
6658 "mask-fill harvest of a dspark-trained drafter verifies every slot against \
6659 a position the row was not trained for (DSPARK-POSTMORTEM-20260820.md)"
6660 );
6661 }
6662}
6663
6664// ================= Verify-window policy gate (CPU; H4, DSPARK-POSTMORTEM-20260820.md) ===
6665// Pins the confidence-vt semantics as logic the round consumes: cumprod survival over
6666// sigmoid scores, thresholded, anchor + kept drafts, floor 2 / cap vt_cap — and the env
6667// seam's refuse-on-ambiguity. Mutating the policy (per-slot threshold instead of
6668// survival, off-by-one on the anchor, silent unknown-value fallback) fails HERE.
6669#[cfg(test)]
6670mod dspark_vt_tests {
6671 use super::{ConfidenceHead, DsparkVtPolicy, dspark_confidence_vt, dspark_slot_confidence_vt};
6672
6673 /// Pre-sigmoid logit for a target probability: sigmoid(logit(p)) == p.
6674 fn logit(p: f32) -> f32 {
6675 (p / (1.0 - p)).ln()
6676 }
6677
6678 #[test]
6679 fn confidence_vt_is_cumprod_survival_not_per_slot_threshold() {
6680 // sigmoids = [0.9, 0.8, 0.9, ...]: every PER-SLOT score clears tau=0.5, but
6681 // cumulative survival sinks below it at slot 6 (0.9, 0.72, 0.648, 0.583,
6682 // 0.525, then 0.472 < 0.5) — the window must stop where the EXPECTED
6683 // accepted-prefix stops paying, not where a slot looks locally fine.
6684 let raws: Vec<f32> = [0.9, 0.8, 0.9, 0.9, 0.9, 0.9, 0.9]
6685 .iter()
6686 .map(|&p| logit(p))
6687 .collect();
6688 assert_eq!(
6689 dspark_confidence_vt(&raws, 0.5, 8),
6690 6,
6691 "keeps 5 drafts + anchor"
6692 );
6693 // Tighter threshold closes the window sooner; looser opens it to the cap.
6694 assert_eq!(
6695 dspark_confidence_vt(&raws, 0.7, 8),
6696 3,
6697 "tau=0.7 keeps 2 drafts"
6698 );
6699 assert_eq!(
6700 dspark_confidence_vt(&raws, 0.05, 8),
6701 8,
6702 "tau→0 = full block"
6703 );
6704 }
6705
6706 #[test]
6707 fn slot_arm_truncates_at_first_low_confidence_slot() {
6708 // Owner directive (2026-08-20): submit only the longest prefix whose EVERY
6709 // slot clears tau on its own sigmoid. On the survival test's raws
6710 // ([0.9, 0.8, 0.9 x5], tau=0.5) every slot clears per-slot, so the slot arm
6711 // opens the full block where survival stopped at 6 — the two stopping
6712 // statistics must stay distinct arms.
6713 let raws: Vec<f32> = [0.9, 0.8, 0.9, 0.9, 0.9, 0.9, 0.9]
6714 .iter()
6715 .map(|&p| logit(p))
6716 .collect();
6717 assert_eq!(dspark_slot_confidence_vt(&raws, 0.5, 8), 8);
6718 assert_eq!(dspark_confidence_vt(&raws, 0.5, 8), 6);
6719 // A low-confidence tail never enters verify: [0.9, 0.9, 0.3, 0.9, ...]
6720 // truncates at slot 3 REGARDLESS of the confident slots behind it — a kept
6721 // slot after a dropped one could never commit (prefix accept rule).
6722 let tail: Vec<f32> = [0.9, 0.9, 0.3, 0.9, 0.9, 0.9, 0.9]
6723 .iter()
6724 .map(|&p| logit(p))
6725 .collect();
6726 assert_eq!(
6727 dspark_slot_confidence_vt(&tail, 0.5, 8),
6728 3,
6729 "2 drafts + anchor"
6730 );
6731 // Tighter tau keeps less.
6732 assert_eq!(
6733 dspark_slot_confidence_vt(&tail, 0.95, 8),
6734 2,
6735 "floor at tau=0.95"
6736 );
6737 }
6738
6739 #[test]
6740 fn confidence_vt_floor_and_cap() {
6741 // A hopeless round still verifies ONE draft (the draft forward is paid;
6742 // vt=1 would guarantee an empty round at the same cost class).
6743 let cold: Vec<f32> = [0.1f32, 0.1, 0.1].iter().map(|&p| logit(p)).collect();
6744 assert_eq!(
6745 dspark_confidence_vt(&cold, 0.5, 8),
6746 2,
6747 "floor = anchor + 1 draft"
6748 );
6749 assert_eq!(
6750 dspark_slot_confidence_vt(&cold, 0.5, 8),
6751 2,
6752 "slot arm same floor"
6753 );
6754 // The MEMRA_DFLASH_VERIFY_T cap still binds a confident round.
6755 let hot: Vec<f32> = vec![logit(0.99); 7];
6756 assert_eq!(dspark_confidence_vt(&hot, 0.5, 5), 5, "vt_cap binds");
6757 assert_eq!(
6758 dspark_confidence_vt(&hot, 0.5, 8),
6759 8,
6760 "full block when confident"
6761 );
6762 assert_eq!(
6763 dspark_slot_confidence_vt(&hot, 0.5, 5),
6764 5,
6765 "slot arm same cap"
6766 );
6767 // No scores (defensive): floor.
6768 assert_eq!(dspark_confidence_vt(&[], 0.5, 8), 2);
6769 assert_eq!(dspark_slot_confidence_vt(&[], 0.5, 8), 2);
6770 }
6771
6772 #[test]
6773 fn vt_policy_env_seam_parses_and_refuses() {
6774 assert_eq!(
6775 DsparkVtPolicy::from_env_value(None, None, None),
6776 DsparkVtPolicy::Ladder,
6777 "default stays the shipped ladder — the H4 arm is opt-in"
6778 );
6779 assert_eq!(
6780 DsparkVtPolicy::from_env_value(Some(""), None, None),
6781 DsparkVtPolicy::Ladder
6782 );
6783 assert_eq!(
6784 DsparkVtPolicy::from_env_value(Some("ladder"), None, Some("0")),
6785 DsparkVtPolicy::Ladder,
6786 "ladder + ADAPT=0 = the fixed-window arm, untouched"
6787 );
6788 assert_eq!(
6789 DsparkVtPolicy::from_env_value(Some("confidence"), None, None),
6790 DsparkVtPolicy::Confidence { tau: 0.5 },
6791 "tau defaults to 0.5 (raw sigmoid, no STS sidecar — postmortem §3-H4)"
6792 );
6793 assert_eq!(
6794 DsparkVtPolicy::from_env_value(Some("confidence"), Some("0.35"), Some("1")),
6795 DsparkVtPolicy::Confidence { tau: 0.35 }
6796 );
6797 assert_eq!(
6798 DsparkVtPolicy::from_env_value(Some("confidence-slot"), Some("0.6"), None),
6799 DsparkVtPolicy::ConfidenceSlot { tau: 0.6 },
6800 "the owner-directive per-slot arm parses with the same tau env"
6801 );
6802 assert!(
6803 std::panic::catch_unwind(|| DsparkVtPolicy::from_env_value(
6804 Some("confidence-slot"),
6805 None,
6806 Some("0")
6807 ))
6808 .is_err(),
6809 "confidence-slot + MEMRA_DFLASH_ADAPT=0 must REFUSE like confidence"
6810 );
6811 assert!(
6812 std::panic::catch_unwind(|| DsparkVtPolicy::from_env_value(Some("static"), None, None))
6813 .is_err(),
6814 "unknown policy values must REFUSE, not default — a typo silently \
6815 reverting the window policy invalidates an A/B"
6816 );
6817 assert!(
6818 std::panic::catch_unwind(|| DsparkVtPolicy::from_env_value(
6819 Some("confidence"),
6820 None,
6821 Some("0")
6822 ))
6823 .is_err(),
6824 "confidence + MEMRA_DFLASH_ADAPT=0 is contradictory and must REFUSE"
6825 );
6826 for bad in ["0", "1", "1.5", "-0.1", "nan"] {
6827 assert!(
6828 std::panic::catch_unwind(|| DsparkVtPolicy::from_env_value(
6829 Some("confidence"),
6830 Some(bad),
6831 None
6832 ))
6833 .is_err(),
6834 "tau={bad} must REFUSE (survival threshold lives in (0,1))"
6835 );
6836 }
6837 }
6838
6839 #[test]
6840 fn raw_score_matches_the_parity_gate_dot() {
6841 // The head is a raw linear proj over [hidden ; markov_prev_embedding] + b —
6842 // the exact stage-5 contract in dspark_q38_parity.rs.
6843 let ch = ConfidenceHead {
6844 w: vec![0.5, -1.0, 2.0, 0.25, -0.5],
6845 b: 0.125,
6846 in_dim: 5,
6847 with_markov: true,
6848 };
6849 let hidden = [1.0f32, 2.0, 3.0];
6850 let emb = [4.0f32, 8.0];
6851 let want = 0.125 + 0.5 * 1.0 - 1.0 * 2.0 + 2.0 * 3.0 + 0.25 * 4.0 - 0.5 * 8.0;
6852 assert_eq!(ch.raw_score(&hidden, Some(&emb)), want);
6853 let ch_plain = ConfidenceHead {
6854 w: vec![0.5, -1.0, 2.0],
6855 b: -0.25,
6856 in_dim: 3,
6857 with_markov: false,
6858 };
6859 let want_plain = -0.25 + 0.5 * 1.0 - 1.0 * 2.0 + 2.0 * 3.0;
6860 assert_eq!(ch_plain.raw_score(&hidden, None), want_plain);
6861 }
6862}
6863
6864#[cfg(test)]
6865mod dspark_prefix_capture_tests {
6866 use super::{dspark_spec_prompt_fits, take_dspark_prefix_capture};
6867
6868 /// INCIDENT REGRESSION (2026-08-25). This gate is the only admission check the dspark
6869 /// route has, and it checked the ctx ceiling ONLY — so a short prompt was admitted and
6870 /// then panicked in the cold prime (`prime_cache needs T >= 16`), inside the GPU worker
6871 /// thread, which exits 70 and kills every live session on the box. Two crash loops and
6872 /// ~5 minutes of customer 502s came from a 5-token "Say OK." — the class our own
6873 /// watchdog sends. The floor belongs HERE, in the gate, not in each caller.
6874 #[test]
6875 fn a_prompt_below_the_prime_floor_never_enters_the_dspark_route() {
6876 let floor = crate::hybrid_forward::PRIME_MIN_T;
6877 for short in [1usize, 5, floor - 1] {
6878 assert!(
6879 !dspark_spec_prompt_fits(short, 262_144, 8, 2_048, true),
6880 "a {short}-token prompt must decline to the plain path, not prime"
6881 );
6882 }
6883 // At and above the floor the route admits exactly as before (ceiling still applies).
6884 assert!(dspark_spec_prompt_fits(floor, 262_144, 8, 2_048, true));
6885 assert!(dspark_spec_prompt_fits(512, 262_144, 8, 2_048, true));
6886 assert!(!dspark_spec_prompt_fits(512, 300, 8, 2_048, true));
6887 }
6888
6889 #[test]
6890 fn session_prompt_preflight_matches_dflash2_and_windowed_caps() {
6891 // DFlash2 uses the request ctx cap: prompt + block + 8 fits exactly, one row less does not.
6892 assert!(dspark_spec_prompt_fits(96, 111, 7, 2_048, true));
6893 assert!(!dspark_spec_prompt_fits(96, 110, 7, 2_048, true));
6894
6895 // Legacy/windowed drafts are additionally bounded by their own sliding window.
6896 assert!(dspark_spec_prompt_fits(113, 8_192, 7, 128, false));
6897 assert!(!dspark_spec_prompt_fits(114, 8_192, 7, 128, false));
6898 assert!(!dspark_spec_prompt_fits(
6899 usize::MAX,
6900 usize::MAX,
6901 7,
6902 usize::MAX,
6903 true,
6904 ));
6905 }
6906
6907 #[test]
6908 fn prompt_end_capture_is_full_prompt_and_one_shot() {
6909 let prompt_len = 96;
6910 let mut slot = Some(crate::spec::SpecBoundaryCapture {
6911 snap: crate::cache::CacheSnapshot {
6912 kv_len: Vec::new(),
6913 tp_kv_len: Vec::new(),
6914 conv: Vec::new(),
6915 ssm: Vec::new(),
6916 pos: prompt_len,
6917 },
6918 pos: prompt_len,
6919 logits: vec![1.0, 2.0],
6920 last_h: Vec::new(),
6921 latent_tails: Vec::new(),
6922 });
6923
6924 let capture = take_dspark_prefix_capture(&mut slot).expect("first drain gets capture");
6925 assert_eq!(capture.pos, prompt_len, "capture is at full prompt end");
6926 assert_eq!(capture.snap.pos, prompt_len);
6927 assert!(
6928 capture.last_h.is_empty(),
6929 "DFlash publishes no hidden anchor"
6930 );
6931 assert!(
6932 take_dspark_prefix_capture(&mut slot).is_none(),
6933 "capture drains exactly once",
6934 );
6935 }
6936}
6937
6938#[cfg(test)]
6939mod dflash_precision_tests {
6940 use super::dflash_precision;
6941
6942 #[test]
6943 fn default_and_supported_precision_programs_are_explicit() {
6944 assert_eq!(dflash_precision(None), Ok("q4"));
6945 for prec in ["q4", "q8", "mixed", "bf16", "fc"] {
6946 assert_eq!(dflash_precision(Some(prec)), Ok(prec));
6947 }
6948 }
6949
6950 #[test]
6951 fn q5_and_typos_refuse_instead_of_silently_selecting_q8() {
6952 for prec in ["q5", "Q4", "", "typo"] {
6953 let err = dflash_precision(Some(prec)).unwrap_err();
6954 assert!(err.contains("want q4, q8, mixed, bf16, or fc"));
6955 }
6956 }
6957}
6958
6959#[cfg(test)]
6960mod dflash_tensor_contract_tests {
6961 use super::{
6962 validate_dflash_attention_geometry, validate_dflash_tensor, validate_layer_layout,
6963 validate_selector_top_k,
6964 };
6965 use memra_gguf::safetensors::StInfo;
6966
6967 #[test]
6968 fn named_tensor_contract_refuses_wrong_dtype_rank_and_shape_before_cuda() {
6969 let valid = StInfo {
6970 dtype: "BF16".into(),
6971 shape: vec![8, 4],
6972 data_offsets: [0, 64],
6973 };
6974 assert!(validate_dflash_tensor("w", &valid, &[4, 8]).is_ok());
6975
6976 let mut bad = valid.clone();
6977 bad.dtype = "F32".into();
6978 assert!(
6979 validate_dflash_tensor("w", &bad, &[4, 8])
6980 .unwrap_err()
6981 .contains("dtype")
6982 );
6983 bad = valid.clone();
6984 bad.shape = vec![32];
6985 assert!(
6986 validate_dflash_tensor("w", &bad, &[4, 8])
6987 .unwrap_err()
6988 .contains("shape")
6989 );
6990 assert!(
6991 validate_dflash_tensor("w", &valid, &[8, 4])
6992 .unwrap_err()
6993 .contains("expected")
6994 );
6995 }
6996
6997 #[test]
6998 fn attention_and_selector_geometry_refuse_before_cuda() {
6999 assert!(validate_dflash_attention_geometry(64, 8, 128).is_ok());
7000 assert!(validate_dflash_attention_geometry(63, 8, 128).is_err());
7001 assert!(validate_dflash_attention_geometry(64, 0, 128).is_err());
7002 assert!(validate_dflash_attention_geometry(usize::MAX, 1, 2).is_err());
7003 assert!(validate_selector_top_k(16, 128).is_ok());
7004 assert!(validate_selector_top_k(0, 128).is_err());
7005 assert!(validate_selector_top_k(129, 128).is_err());
7006 assert!(validate_layer_layout(&[true, true], 2).is_ok());
7007 assert!(validate_layer_layout(&[true], 2).is_err());
7008 }
7009}