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