1use crate::Engine;
13use crate::model::GpuTensor;
14use cudarc::driver::CudaSlice;
15
16pub struct DflashCfg {
17 pub hidden: usize, pub n_head: usize, pub n_kv: usize, pub head_dim: usize, pub n_ff: usize, pub n_layer: usize, pub eps: f32, pub rope_theta: f32, pub block_size: usize, pub mask_token_id: u32, pub target_layer_ids: Vec<usize>, pub sliding_window: usize, pub layer_sliding: Vec<bool>,
31 pub strategy_dspark: bool,
37 pub is_causal: Option<bool>,
43}
44
45pub struct DflashLayer {
46 pub wq: GpuTensor, pub wk: GpuTensor, pub wv: GpuTensor, pub wo: GpuTensor, pub w_gate: GpuTensor, pub w_up: GpuTensor, pub w_down: GpuTensor, pub ln_in: CudaSlice<f32>, pub ln_post: CudaSlice<f32>, pub q_norm: CudaSlice<f32>, pub k_norm: CudaSlice<f32>, }
58
59pub struct DflashDraft {
60 pub cfg: DflashCfg,
61 pub layers: Vec<DflashLayer>,
62 pub fc: GpuTensor, pub hidden_norm: CudaSlice<f32>, pub norm: CudaSlice<f32>, pub markov: Option<MarkovHead>,
70 pub confidence: Option<ConfidenceHead>,
78 pub rope_yarn: Option<(CudaSlice<f32>, f32)>,
85 pub dflash2: Option<Dflash2Head>,
92}
93
94pub struct Dflash2Conv {
102 pub base: CudaSlice<f32>,
104 pub proj: GpuTensor,
106}
107
108pub struct Dflash2Head {
109 pub attn_conv: Vec<Dflash2Conv>, pub mlp_conv: Vec<Dflash2Conv>, pub hidden_proj: GpuTensor,
113 pub pred_codebook: Vec<u8>,
118 pub succ_codebook: Vec<u8>,
119 pub rank: usize, pub top_k: usize, pub conv_k: usize, pub group_size: usize, pub vocab: usize, }
125
126fn cb_row(cb: &[u8], tok: usize, rank: usize) -> Vec<f32> {
128 bf16_to_f32(&cb[tok * rank * 2..(tok + 1) * rank * 2])
129}
130
131#[allow(clippy::too_many_arguments)]
141pub fn dflash2_walk_greedy(
142 pred_codebook: &[u8],
143 succ_codebook: &[u8],
144 vocab: usize,
145 rank: usize,
146 top_k: usize,
147 unary: &[f32],
148 cand: &[u32],
149 hproj: &[f32],
150 anchor: u32,
151 nd: usize,
152) -> Vec<u32> {
153 let (kk, r) = (top_k, rank);
154 assert_eq!(unary.len(), nd * kk, "walk: unary shape");
155 assert_eq!(cand.len(), nd * kk, "walk: candidate shape");
156 assert_eq!(hproj.len(), nd * r, "walk: hidden-projection shape");
157 let mut path = Vec::with_capacity(nd);
158 let mut prev = anchor;
159 for p in 0..nd {
160 assert!(
161 (prev as usize) < vocab,
162 "walk: predecessor token {prev} outside codebook vocab {vocab}"
163 );
164 let pr = cb_row(pred_codebook, prev as usize, r);
165 let hp = &hproj[p * r..(p + 1) * r];
166 let gate: Vec<f32> = pr.iter().zip(hp).map(|(a, b)| a * b).collect();
168 let (mut best, mut bi) = (f32::NEG_INFINITY, 0usize);
169 for k in 0..kk {
170 let c = cand[p * kk + k] as usize;
171 assert!(c < vocab, "walk: candidate {c} outside codebook vocab");
172 let sr = cb_row(succ_codebook, c, r);
173 let mut s = unary[p * kk + k];
174 for j in 0..r {
175 s += gate[j] * sr[j];
176 }
177 if s > best {
178 best = s;
179 bi = k;
180 }
181 }
182 prev = cand[p * kk + bi];
183 path.push(prev);
184 }
185 path
186}
187
188impl Dflash2Head {
189 pub fn walk_greedy(
191 &self,
192 unary: &[f32],
193 cand: &[u32],
194 hproj: &[f32],
195 anchor: u32,
196 nd: usize,
197 ) -> Vec<u32> {
198 dflash2_walk_greedy(
199 &self.pred_codebook,
200 &self.succ_codebook,
201 self.vocab,
202 self.rank,
203 self.top_k,
204 unary,
205 cand,
206 hproj,
207 anchor,
208 nd,
209 )
210 }
211
212 #[allow(clippy::too_many_arguments)]
214 pub fn walk_sampled(
215 &self,
216 unary: &[f32],
217 cand: &[u32],
218 hproj: &[f32],
219 anchor: u32,
220 nd: usize,
221 temp: f32,
222 uniforms: &mut dyn FnMut() -> f32,
223 ) -> (Vec<u32>, Vec<f32>, Vec<f32>) {
224 dflash2_walk_sampled(
225 &self.pred_codebook,
226 &self.succ_codebook,
227 self.vocab,
228 self.rank,
229 self.top_k,
230 unary,
231 cand,
232 hproj,
233 anchor,
234 nd,
235 temp,
236 uniforms,
237 )
238 }
239}
240
241pub struct ConfidenceHead {
244 pub w: Vec<f32>, pub b: f32,
246 pub in_dim: usize,
247 pub with_markov: bool,
248}
249
250impl ConfidenceHead {
251 pub fn raw_score(&self, hidden: &[f32], emb: Option<&[f32]>) -> f32 {
257 let mut acc = self.b;
258 for (w, x) in self.w.iter().zip(hidden) {
259 acc += w * x;
260 }
261 if self.with_markov {
262 let emb = emb.expect("with_markov confidence head scored without the markov embedding");
263 debug_assert_eq!(hidden.len() + emb.len(), self.in_dim);
264 for (w, x) in self.w[hidden.len()..].iter().zip(emb) {
265 acc += w * x;
266 }
267 } else {
268 debug_assert_eq!(hidden.len(), self.in_dim);
269 }
270 acc
271 }
272}
273
274pub struct MarkovHead {
275 pub w1_bf16: CudaSlice<u8>, pub w2: GpuTensor, pub rank: usize,
278 pub vocab: usize,
279}
280
281#[derive(Clone, Copy, PartialEq, Eq, Debug)]
304pub enum DsparkHarvest {
305 Dflash,
307 Dspark,
309}
310
311impl DsparkHarvest {
312 pub fn resolve(cfg: &DflashCfg) -> Self {
321 Self::resolve_value(
322 std::env::var("MEMRA_DSPARK_HARVEST").ok().as_deref(),
323 cfg.strategy_dspark,
324 )
325 }
326
327 pub fn resolve_value(v: Option<&str>, strategy_dspark: bool) -> Self {
328 match v {
329 None | Some("") => {
330 if strategy_dspark {
331 DsparkHarvest::Dspark
332 } else {
333 DsparkHarvest::Dflash
334 }
335 }
336 set => Self::from_env_value(set),
337 }
338 }
339
340 pub fn from_env_value(v: Option<&str>) -> Self {
344 match v {
345 None | Some("") | Some("dflash") => DsparkHarvest::Dflash,
346 Some("dspark") => DsparkHarvest::Dspark,
347 Some(other) => panic!(
348 "MEMRA_DSPARK_HARVEST={other}: unknown harvest convention (dflash|dspark); \
349 refusing — a wrong convention verifies every draft slot against a position \
350 the drafter row was not trained for (DSPARK-POSTMORTEM-20260820.md)"
351 ),
352 }
353 }
354
355 pub fn for_draft(draft: &DflashDraft) -> Self {
366 Self::for_family_value(
367 draft.dflash2.is_some(),
368 std::env::var("MEMRA_DSPARK_HARVEST").ok().as_deref(),
369 draft.cfg.strategy_dspark,
370 )
371 }
372
373 pub fn for_family_value(is_dflash2: bool, env: Option<&str>, strategy_dspark: bool) -> Self {
374 if is_dflash2 {
375 if env == Some("dspark") {
376 panic!(
377 "MEMRA_DSPARK_HARVEST=dspark with a DFlash2 checkpoint: DFlash2 \
378 is mask-fill (b-1 drafts, anchor row is not a draft — reference \
379 dflash_generate rows 1-verify_size:); the shifted harvest would \
380 verify every slot one position early. Refusing (census-keyed, \
381 not env-keyed)."
382 );
383 }
384 return DsparkHarvest::Dflash;
385 }
386 Self::resolve_value(env, strategy_dspark)
387 }
388
389 pub fn name(self) -> &'static str {
391 match self {
392 DsparkHarvest::Dflash => "dflash",
393 DsparkHarvest::Dspark => "dspark",
394 }
395 }
396
397 pub fn from_name(v: &str) -> Option<Self> {
398 match v {
399 "dflash" => Some(DsparkHarvest::Dflash),
400 "dspark" => Some(DsparkHarvest::Dspark),
401 _ => None,
402 }
403 }
404
405 pub fn first_row(self) -> usize {
407 match self {
408 DsparkHarvest::Dflash => 1,
409 DsparkHarvest::Dspark => 0,
410 }
411 }
412
413 pub fn n_drafts(self, b: usize) -> usize {
415 match self {
416 DsparkHarvest::Dflash => b - 1,
417 DsparkHarvest::Dspark => b,
418 }
419 }
420
421 pub fn trained_offset_of_row(self, row: usize) -> usize {
424 match self {
425 DsparkHarvest::Dflash => row,
426 DsparkHarvest::Dspark => row + 1,
427 }
428 }
429}
430
431pub fn dspark_strategy_census(txt: &str) -> bool {
438 let arch = txt
439 .find("\"architectures\"")
440 .and_then(|i| {
441 let rest = &txt[i..];
442 let a = rest.find('[')?;
443 let b = rest.find(']')?;
444 Some(rest[a..b].contains("DSpark"))
445 })
446 .unwrap_or(false);
447 let proj = txt
448 .find("\"projector_type\"")
449 .map(|i| {
450 let rest = &txt[i..];
451 let after = rest.find(':').map(|c| &rest[c + 1..]).unwrap_or("");
452 after.trim_start().starts_with("\"dspark\"")
453 })
454 .unwrap_or(false);
455 arch || proj
456}
457
458pub fn dspark_accept_prefix(cand: &[u32], vam: &[u32], vt: usize) -> usize {
464 let mut m = 0usize;
465 while m < vt - 1 && cand[m + 1] == vam[m] {
466 m += 1;
467 }
468 m
469}
470
471#[derive(Clone, Copy, PartialEq, Debug)]
482pub enum DsparkVtPolicy {
483 Ladder,
486 Confidence { tau: f32 },
492 ConfidenceSlot { tau: f32 },
501}
502
503impl DsparkVtPolicy {
504 pub fn resolve(has_confidence_head: bool) -> Self {
516 Self::resolve_value(
517 std::env::var("MEMRA_DSPARK_VT").ok().as_deref(),
518 std::env::var("MEMRA_DSPARK_VT_TAU").ok().as_deref(),
519 std::env::var("MEMRA_DFLASH_ADAPT").ok().as_deref(),
520 has_confidence_head,
521 )
522 }
523
524 pub fn resolve_value(
525 vt: Option<&str>,
526 tau: Option<&str>,
527 adapt: Option<&str>,
528 has_confidence_head: bool,
529 ) -> Self {
530 match vt {
531 None | Some("") => {
532 if adapt == Some("0") || !has_confidence_head {
533 DsparkVtPolicy::Ladder
534 } else {
535 Self::from_env_value(Some("confidence-slot"), tau, adapt)
538 }
539 }
540 set => Self::from_env_value(set, tau, adapt),
541 }
542 }
543
544 pub fn from_env_value(vt: Option<&str>, tau: Option<&str>, adapt: Option<&str>) -> Self {
548 match vt {
549 None | Some("") | Some("ladder") => DsparkVtPolicy::Ladder,
550 Some(mode @ ("confidence" | "confidence-slot")) => {
551 if adapt == Some("0") {
552 panic!(
553 "MEMRA_DSPARK_VT={mode} together with MEMRA_DFLASH_ADAPT=0 is \
554 contradictory (a pinned fixed window vs a per-round confidence \
555 window); unset one — refuse-on-ambiguity"
556 );
557 }
558 let tau = tau
559 .map(|t| {
560 t.parse::<f32>()
561 .unwrap_or_else(|_| panic!("MEMRA_DSPARK_VT_TAU={t}: not a float"))
562 })
563 .unwrap_or(0.5);
564 assert!(
565 tau > 0.0 && tau < 1.0,
566 "MEMRA_DSPARK_VT_TAU={tau}: confidence threshold must be in (0,1)"
567 );
568 if mode == "confidence" {
569 DsparkVtPolicy::Confidence { tau }
570 } else {
571 DsparkVtPolicy::ConfidenceSlot { tau }
572 }
573 }
574 Some(other) => panic!(
575 "MEMRA_DSPARK_VT={other}: unknown verify-window policy \
576 (ladder|confidence|confidence-slot); refusing — a wrong policy \
577 silently reverts the H4 arm (DSPARK-POSTMORTEM-20260820.md)"
578 ),
579 }
580 }
581
582 pub fn is_confidence(&self) -> bool {
585 !matches!(self, DsparkVtPolicy::Ladder)
586 }
587
588 pub fn size_window(&self, raws: &[f32], vt_cap: usize) -> Option<usize> {
591 match *self {
592 DsparkVtPolicy::Ladder => None,
593 DsparkVtPolicy::Confidence { tau } => Some(dspark_confidence_vt(raws, tau, vt_cap)),
594 DsparkVtPolicy::ConfidenceSlot { tau } => {
595 Some(dspark_slot_confidence_vt(raws, tau, vt_cap))
596 }
597 }
598 }
599}
600
601pub fn dspark_confidence_vt(raws: &[f32], tau: f32, vt_cap: usize) -> usize {
609 let mut surv = 1.0f32;
610 let mut kept = 0usize;
611 for &r in raws {
612 surv *= 1.0 / (1.0 + (-r).exp());
613 if surv < tau {
614 break;
615 }
616 kept += 1;
617 }
618 (1 + kept).clamp(2, vt_cap.max(2))
619}
620
621pub fn dspark_slot_confidence_vt(raws: &[f32], tau: f32, vt_cap: usize) -> usize {
630 let mut kept = 0usize;
631 for &r in raws {
632 let p = 1.0 / (1.0 + (-r).exp());
633 if p < tau {
634 break;
635 }
636 kept += 1;
637 }
638 (1 + kept).clamp(2, vt_cap.max(2))
639}
640
641pub fn rejection_accept_len(p: &[f32], q: &[f32], u: &[f32]) -> usize {
666 assert!(
667 q.len() >= p.len() && u.len() >= p.len(),
668 "accept walk shape"
669 );
670 let mut m = 0usize;
671 while m < p.len() && (u[m] as f64) * (q[m] as f64) < p[m] as f64 {
672 m += 1;
673 }
674 m
675}
676
677#[allow(clippy::too_many_arguments)]
686pub fn dflash2_walk_sampled(
687 pred_codebook: &[u8],
688 succ_codebook: &[u8],
689 vocab: usize,
690 rank: usize,
691 top_k: usize,
692 unary: &[f32],
693 cand: &[u32],
694 hproj: &[f32],
695 anchor: u32,
696 nd: usize,
697 temp: f32,
698 uniforms: &mut dyn FnMut() -> f32,
699) -> (Vec<u32>, Vec<f32>, Vec<f32>) {
700 assert!(
701 temp > 0.0,
702 "sampled walk is the T>0 arm; T=0 is walk_greedy"
703 );
704 let (kk, r) = (top_k, rank);
705 assert_eq!(unary.len(), nd * kk, "walk: unary shape");
706 assert_eq!(cand.len(), nd * kk, "walk: candidate shape");
707 assert_eq!(hproj.len(), nd * r, "walk: hidden-projection shape");
708 let mut path = Vec::with_capacity(nd);
709 let mut q_chosen = Vec::with_capacity(nd);
710 let mut q_rows = Vec::with_capacity(nd * kk);
711 let mut prev = anchor;
712 for p in 0..nd {
713 assert!(
714 (prev as usize) < vocab,
715 "walk: predecessor token {prev} outside codebook vocab {vocab}"
716 );
717 let pr = cb_row(pred_codebook, prev as usize, r);
718 let hp = &hproj[p * r..(p + 1) * r];
719 let gate: Vec<f32> = pr.iter().zip(hp).map(|(a, b)| a * b).collect();
720 let mut scores = vec![0f32; kk];
721 for (k, s) in scores.iter_mut().enumerate() {
722 let c = cand[p * kk + k] as usize;
723 assert!(c < vocab, "walk: candidate {c} outside codebook vocab");
724 let sr = cb_row(succ_codebook, c, r);
725 let mut acc = unary[p * kk + k];
726 for j in 0..r {
727 acc += gate[j] * sr[j];
728 }
729 *s = acc;
730 }
731 let mx = scores.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
734 let mut z = 0f64;
735 let ex: Vec<f64> = scores
736 .iter()
737 .map(|&s| {
738 let e0 = (((s - mx) / temp) as f64).exp();
739 z += e0;
740 e0
741 })
742 .collect();
743 let probs: Vec<f32> = ex.iter().map(|&e0| (e0 / z) as f32).collect();
744 let u = uniforms() as f64;
745 let mut acc = 0f64;
746 let mut bi = probs
749 .iter()
750 .enumerate()
751 .max_by(|a, b| a.1.total_cmp(b.1))
752 .map(|(k, _)| k)
753 .unwrap_or(0);
754 for (k, &pk) in probs.iter().enumerate() {
755 acc += pk as f64;
756 if u < acc {
757 bi = k;
758 break;
759 }
760 }
761 prev = cand[p * kk + bi];
762 path.push(prev);
763 q_chosen.push(probs[bi]);
764 q_rows.extend_from_slice(&probs);
765 }
766 (path, q_chosen, q_rows)
767}
768
769pub(crate) type Dflash2SampledProposal = (Vec<u32>, Vec<f32>, Vec<u32>, Vec<f32>);
771
772pub(crate) enum DsparkDraftSample {
775 Rows {
779 th: CudaSlice<f32>, z: CudaSlice<f32>, stats: Vec<(f32, f32, f32)>, },
783 Selector {
785 cand: Vec<u32>, q_rows: Vec<f32>, q_chosen: Vec<f32>, top_k: usize,
789 },
790}
791
792#[allow(clippy::too_many_arguments)]
801pub(crate) fn dspark_accept_sampled(
802 e: &Engine,
803 tlogits: &CudaSlice<f32>,
804 cand: &[u32],
805 vt: usize,
806 n_vocab: usize,
807 dl: &CudaSlice<f32>,
808 prop: &DsparkDraftSample,
809 sp: &crate::spec::SpecSampling,
810 sctr: &mut u32,
811 uctr: &mut u32,
812) -> Result<(usize, u32), Box<dyn std::error::Error>> {
813 let nq = vt - 1; debug_assert!(nq >= 1 && cand.len() > nq, "sampled accept shape");
815 let rows: Vec<i32> = (0..nq as i32).collect();
817 let ids: Vec<u32> = cand[1..=nq].to_vec();
818 let rowsd = e.htod_i32(&rows)?;
819 let idsd = e.htod_u32_v(&ids)?;
820 let (mut pth, mut pz, mut pmx) = (e.zeros(nq)?, e.zeros(nq)?, e.zeros(nq)?);
821 e.filter_stats(
822 tlogits, n_vocab, &rowsd, &mut pth, &mut pz, &mut pmx, n_vocab, nq, sp.temp, sp.top_k,
823 sp.top_p, sp.min_p,
824 )?;
825 let mut pj_d = e.zeros(nq)?;
826 e.softmax_gather_filtered(
827 tlogits, n_vocab, &idsd, &rowsd, &pth, &pz, &mut pj_d, n_vocab, nq, sp.temp,
828 )?;
829 let pj = e.dtoh(&pj_d)?;
830 let (pthv, pzv, pmxv) = (e.dtoh(&pth)?, e.dtoh(&pz)?, e.dtoh(&pmx)?);
831 let qj: Vec<f32> = match prop {
833 DsparkDraftSample::Rows { th, z, .. } => {
834 let mut qd = e.zeros(nq)?;
837 e.softmax_gather_filtered(
838 dl, n_vocab, &idsd, &rowsd, th, z, &mut qd, n_vocab, nq, sp.temp,
839 )?;
840 e.dtoh(&qd)?
841 }
842 DsparkDraftSample::Selector { q_chosen, .. } => q_chosen[..nq].to_vec(),
843 };
844 let mut us = Vec::with_capacity(nq);
846 for _ in 0..nq {
847 us.push(crate::spec::host_u01(sp.seed, *uctr));
848 *uctr = uctr.wrapping_add(1);
849 }
850 let m = rejection_accept_len(&pj[..nq], &qj[..nq], &us);
851 let next = if m == nq {
853 let rows_l = e.htod_i32(&[(vt - 1) as i32])?;
855 let (mut th1, mut z1, mut mx1) = (e.zeros(1)?, e.zeros(1)?, e.zeros(1)?);
856 e.filter_stats(
857 tlogits, n_vocab, &rows_l, &mut th1, &mut z1, &mut mx1, n_vocab, 1, sp.temp, sp.top_k,
858 sp.top_p, sp.min_p,
859 )?;
860 let mut pb = e.zeros(n_vocab)?;
861 e.gumbel_perturb_filtered_col(
862 tlogits,
863 vt - 1,
864 &mut pb,
865 n_vocab,
866 sp.seed,
867 *sctr,
868 sp.temp,
869 &mx1,
870 &th1,
871 0,
872 )?;
873 *sctr = sctr.wrapping_add(1);
874 let td = e.argmax_token_device(&pb, n_vocab)?;
875 e.dtoh_u32_one(&td)?
876 } else {
877 let mut col = e.zeros(n_vocab)?;
880 e.copy_view_into(
881 &mut col,
882 0,
883 &tlogits.slice(m * n_vocab..(m + 1) * n_vocab),
884 n_vocab,
885 )?;
886 let p_stats = (pmxv[m], pthv[m], pzv[m]);
887 let mut tok_d = e.alloc_u32_zeroed(1)?;
888 let sc = *sctr;
889 *sctr = sctr.wrapping_add(1);
890 match prop {
891 DsparkDraftSample::Rows { stats, .. } => {
892 let mut qbuf = e.zeros(n_vocab)?;
893 e.copy_view_into(
894 &mut qbuf,
895 0,
896 &dl.slice(m * n_vocab..(m + 1) * n_vocab),
897 n_vocab,
898 )?;
899 e.residual_sample_filtered(
900 &col,
901 Some(&qbuf),
902 n_vocab,
903 sp.temp,
904 sp.seed,
905 sc,
906 p_stats,
907 stats[m],
908 &mut tok_d,
909 )?;
910 }
911 DsparkDraftSample::Selector {
912 cand: cids,
913 q_rows,
914 top_k,
915 ..
916 } => {
917 let k = *top_k;
918 let ids_m = e.htod_u32_v(&cids[m * k..(m + 1) * k])?;
919 let qs_m = e.htod(&q_rows[m * k..(m + 1) * k])?;
920 e.residual_sample_sparse_q(
921 &col, &ids_m, &qs_m, k, n_vocab, sp.temp, sp.seed, sc, p_stats, &mut tok_d,
922 )?;
923 }
924 }
925 e.dtoh_u32(&tok_d)?[0]
926 };
927 Ok((m, next))
928}
929
930fn dflash2_sdpa_clip_on() -> bool {
938 std::env::var("MEMRA_DFLASH2_SDPA_CLIP")
939 .map(|v| v != "0")
940 .unwrap_or(true)
941}
942
943#[allow(clippy::too_many_arguments)]
947fn d2_windowed_attn(
948 e: &Engine,
949 q: &CudaSlice<f32>,
950 k: &CudaSlice<f32>,
951 v: &CudaSlice<f32>,
952 attn: &mut CudaSlice<f32>,
953 hd: usize,
954 nh: usize,
955 nkv: usize,
956 t: usize,
957 t_kv: usize,
958 scale: f32,
959 c: &DflashCfg,
960) -> Result<(), Box<dyn std::error::Error>> {
961 if dflash2_sdpa_clip_on() {
962 e.sdpa_naive_w_lo(
963 q,
964 k,
965 v,
966 attn,
967 hd,
968 nh,
969 nkv,
970 t,
971 t_kv,
972 scale,
973 false,
974 c.sliding_window,
975 )
976 } else {
977 e.sdpa_naive_w(
978 q,
979 k,
980 v,
981 attn,
982 hd,
983 nh,
984 nkv,
985 t,
986 t_kv,
987 scale,
988 false,
989 c.sliding_window,
990 )
991 }
992}
993
994fn bf16_to_f32(bytes: &[u8]) -> Vec<f32> {
995 bytes
996 .chunks_exact(2)
997 .map(|c| f32::from_bits((u16::from_le_bytes([c[0], c[1]]) as u32) << 16))
998 .collect()
999}
1000
1001fn encode_q8_0(vals: &[f32]) -> Vec<u8> {
1006 let mut out = Vec::with_capacity(vals.len() / 32 * 34);
1007 for blk in vals.chunks_exact(32) {
1008 let amax = blk.iter().fold(0f32, |a, v| a.max(v.abs()));
1009 let d = amax / 127.0;
1010 let id = if d > 0.0 { 1.0 / d } else { 0.0 };
1011 let dh = half_from_f32(d);
1012 out.extend_from_slice(&dh.to_le_bytes());
1013 for &v in blk {
1014 out.push(((v * id).round().clamp(-127.0, 127.0)) as i8 as u8);
1015 }
1016 }
1017 out
1018}
1019
1020fn encode_q4_0(vals: &[f32]) -> Vec<u8> {
1025 let mut out = Vec::with_capacity(vals.len() / 32 * 18);
1026 for blk in vals.chunks_exact(32) {
1027 let mut amax = 0f32;
1029 let mut mx = 0f32;
1030 for &v in blk {
1031 if v.abs() > amax {
1032 amax = v.abs();
1033 mx = v;
1034 }
1035 }
1036 let d = mx / -8.0;
1037 let id = if d != 0.0 { 1.0 / d } else { 0.0 };
1038 out.extend_from_slice(&half_from_f32(d).to_le_bytes());
1039 for j in 0..16 {
1040 let x0 = (blk[j] * id + 8.5).clamp(0.0, 15.0) as u8;
1041 let x1 = (blk[j + 16] * id + 8.5).clamp(0.0, 15.0) as u8;
1042 out.push(x0 | (x1 << 4));
1043 }
1044 }
1045 out
1046}
1047
1048fn half_from_f32(v: f32) -> u16 {
1049 let b = v.to_bits();
1051 let sign = ((b >> 16) & 0x8000) as u16;
1052 let exp = ((b >> 23) & 0xff) as i32 - 127 + 15;
1053 let man = b & 0x7fffff;
1054 if exp <= 0 {
1055 return sign;
1056 } if exp >= 31 {
1058 return sign | 0x7c00;
1059 } let mut h = sign | ((exp as u16) << 10) | ((man >> 13) as u16);
1061 let rem = man & 0x1fff;
1063 if rem > 0x1000 || (rem == 0x1000 && (h & 1) == 1) {
1064 h += 1;
1065 }
1066 h
1067}
1068
1069impl DflashDraft {
1070 pub fn load(e: &Engine, dir: &std::path::Path) -> Result<Self, Box<dyn std::error::Error>> {
1073 let txt = std::fs::read_to_string(dir.join("config.json"))?;
1074 fn num(txt: &str, key: &str) -> Option<f64> {
1075 let i = txt.find(&format!("\"{key}\""))?;
1076 let rest = &txt[i..];
1077 let colon = rest.find(':')?;
1078 let val: String = rest[colon + 1..]
1079 .trim_start()
1080 .chars()
1081 .take_while(|c| {
1082 c.is_ascii_digit()
1083 || *c == '.'
1084 || *c == '-'
1085 || *c == 'e'
1086 || *c == 'E'
1087 || *c == '+'
1088 })
1089 .collect();
1090 val.parse().ok()
1091 }
1092 fn num_list(txt: &str, key: &str) -> Vec<usize> {
1093 let Some(i) = txt.find(&format!("\"{key}\"")) else {
1094 return Vec::new();
1095 };
1096 let rest = &txt[i..];
1097 let (Some(a), Some(b)) = (rest.find('['), rest.find(']')) else {
1098 return Vec::new();
1099 };
1100 rest[a + 1..b]
1101 .split(',')
1102 .filter_map(|s| s.trim().parse().ok())
1103 .collect()
1104 }
1105 fn scope<'a>(txt: &'a str, key: &str) -> Option<&'a str> {
1110 let i = txt.find(&format!("\"{key}\""))?;
1111 let rest = &txt[i..];
1112 let open = rest.find('{')?;
1113 let mut depth = 0usize;
1114 for (j, ch) in rest[open..].char_indices() {
1115 match ch {
1116 '{' => depth += 1,
1117 '}' => {
1118 depth -= 1;
1119 if depth == 0 {
1120 return Some(&rest[open..open + j + 1]);
1121 }
1122 }
1123 _ => {}
1124 }
1125 }
1126 None
1127 }
1128 let is_dflash2 = {
1132 let arch = scope_list(&txt, "architectures");
1133 arch.contains("DFlash2DraftModel")
1134 };
1135 fn scope_list(txt: &str, key: &str) -> String {
1136 let Some(i) = txt.find(&format!("\"{key}\"")) else {
1137 return String::new();
1138 };
1139 let rest = &txt[i..];
1140 match (rest.find('['), rest.find(']')) {
1141 (Some(a), Some(b)) if a < b => rest[a + 1..b].to_string(),
1142 _ => String::new(),
1143 }
1144 }
1145 let d2_cfg_txt: Option<&str> = if is_dflash2 {
1148 Some(scope(&txt, "dflash_config").unwrap_or_else(|| {
1149 panic!("DFlash2DraftModel config.json has no dflash_config object — refusing")
1150 }))
1151 } else {
1152 None
1153 };
1154 let g = |k: &str| num(&txt, k).unwrap_or_else(|| panic!("config missing {k}")) as usize;
1155 let g2 = |k: &str| -> usize {
1156 let t = d2_cfg_txt.expect("dflash2 scope");
1157 num(t, k).unwrap_or_else(|| panic!("dflash_config missing {k} — refusing")) as usize
1158 };
1159 let layer_sliding: Vec<bool> = {
1161 let i = txt.find("\"layer_types\"").expect("layer_types");
1162 let rest = &txt[i..];
1163 let (a, b) = (rest.find('[').unwrap(), rest.find(']').unwrap());
1164 rest[a + 1..b]
1165 .split(',')
1166 .map(|s| s.contains("sliding_attention"))
1167 .collect()
1168 };
1169 let sliding_window = if layer_sliding.iter().any(|&s| s) {
1173 g("sliding_window")
1174 } else {
1175 num(&txt, "sliding_window")
1176 .map(|v| v as usize)
1177 .unwrap_or(usize::MAX)
1178 };
1179 let is_causal = txt
1182 .find("\"is_causal\"")
1183 .and_then(|i| txt[i..].find(':').map(|c| i + c + 1))
1184 .map(|v| txt[v..].trim_start().starts_with("true"));
1185 let cfg = DflashCfg {
1186 hidden: g("hidden_size"),
1187 n_head: g("num_attention_heads"),
1188 n_kv: g("num_key_value_heads"),
1189 head_dim: g("head_dim"),
1190 n_ff: g("intermediate_size"),
1191 n_layer: g("num_hidden_layers"),
1192 eps: num(&txt, "rms_norm_eps").expect("rms_norm_eps") as f32,
1193 rope_theta: if is_dflash2 {
1196 let rp = scope(&txt, "rope_parameters")
1197 .unwrap_or_else(|| panic!("DFlash2 config has no rope_parameters — refusing"));
1198 assert!(
1199 rp.contains("\"default\""),
1200 "DFlash2 rope_parameters rope_type is not \"default\" — the port \
1201 implements plain neox rope only; refusing ({rp})"
1202 );
1203 num(rp, "rope_theta").expect("rope_parameters.rope_theta") as f32
1204 } else {
1205 num(&txt, "rope_theta").expect("rope_theta") as f32
1206 },
1207 block_size: if is_dflash2 {
1208 g2("block_size")
1209 } else {
1210 g("block_size")
1211 },
1212 mask_token_id: if is_dflash2 {
1213 g2("mask_token_id")
1214 } else {
1215 g("mask_token_id")
1216 } as u32,
1217 target_layer_ids: if is_dflash2 {
1218 num_list(d2_cfg_txt.expect("dflash2 scope"), "target_layer_ids")
1219 } else {
1220 num_list(&txt, "target_layer_ids")
1221 },
1222 sliding_window,
1223 layer_sliding,
1224 strategy_dspark: dspark_strategy_census(&txt),
1225 is_causal,
1226 };
1227 if is_dflash2 {
1228 assert_eq!(
1233 cfg.is_causal,
1234 Some(false),
1235 "DFlash2 port requires explicit config is_causal=false \
1236 (non-causal symmetric sliding window); got {is_causal:?} — refusing"
1237 );
1238 assert!(
1239 cfg.layer_sliding.iter().all(|&s| s),
1240 "DFlash2 port expects all layers sliding_attention (q38 export); \
1241 got {:?} — refusing (unverified mask program)",
1242 cfg.layer_sliding
1243 );
1244 assert!(
1245 cfg.block_size <= cfg.sliding_window,
1246 "DFlash2 block {} exceeds the sliding window {} — the windowed SDPA \
1247 omits the future-side mask because block rows stay within the window",
1248 cfg.block_size,
1249 cfg.sliding_window
1250 );
1251 }
1252 let st = memra_gguf::safetensors::StModel::open(&dir.join("model.safetensors"))?;
1253 let up = |name: &str| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1256 let (_info, bytes) = st
1257 .raw(name)
1258 .ok_or_else(|| format!("missing tensor {name}"))?;
1259 Ok(e.htod(&bf16_to_f32(bytes))?)
1260 };
1261 let prec = std::env::var("MEMRA_DFLASH_PREC").unwrap_or_else(|_| "q8".into());
1265 let upw = |name: &str| -> Result<GpuTensor, Box<dyn std::error::Error>> {
1266 let (info, bytes) = st
1267 .raw(name)
1268 .ok_or_else(|| format!("missing tensor {name}"))?;
1269 let shape = info.ne(); let in_f = shape[0] as usize;
1271 let is_ffn = name.contains(".mlp.");
1272 let bf16 = prec == "bf16"
1273 || (prec == "mixed" && !is_ffn)
1274 || (prec == "fc" && name == "fc.weight");
1275 if bf16 {
1276 return Ok(GpuTensor::FloatBf16 {
1277 data: e.upload_u8(bytes)?,
1278 ne: shape.to_vec(),
1279 });
1280 }
1281 let f32s = bf16_to_f32(bytes);
1282 if prec == "q4" {
1283 let q = encode_q4_0(&f32s);
1284 return Ok(GpuTensor::Quant {
1285 bytes: e.upload_u8(&q)?,
1286 qtype: crate::QT_Q4_0,
1287 row_bytes: in_f / 32 * 18,
1288 ne: shape.to_vec(),
1289 scale: 1.0,
1290 rp: false,
1291 #[cfg(memra_cutlass)]
1292 cutlass: None,
1293 fp8: None,
1294 blk: None,
1295 rp4: None,
1296 f16: None,
1297 });
1298 }
1299 let q = encode_q8_0(&f32s);
1300 Ok(GpuTensor::Quant {
1301 bytes: e.upload_u8(&q)?,
1302 qtype: crate::QT_Q8_0,
1303 row_bytes: in_f / 32 * 34,
1304 ne: shape.to_vec(),
1305 scale: 1.0,
1306 rp: false,
1307 #[cfg(memra_cutlass)]
1308 cutlass: None,
1309 fp8: None,
1310 blk: None,
1311 rp4: None,
1312 f16: None,
1313 })
1314 };
1315 let mut layers = Vec::with_capacity(cfg.n_layer);
1316 for i in 0..cfg.n_layer {
1317 let p = |s: &str| format!("layers.{i}.{s}");
1318 layers.push(DflashLayer {
1319 wq: upw(&p("self_attn.q_proj.weight"))?,
1320 wk: upw(&p("self_attn.k_proj.weight"))?,
1321 wv: upw(&p("self_attn.v_proj.weight"))?,
1322 wo: upw(&p("self_attn.o_proj.weight"))?,
1323 w_gate: upw(&p("mlp.gate_proj.weight"))?,
1324 w_up: upw(&p("mlp.up_proj.weight"))?,
1325 w_down: upw(&p("mlp.down_proj.weight"))?,
1326 ln_in: up(&p("input_layernorm.weight"))?,
1327 ln_post: up(&p("post_attention_layernorm.weight"))?,
1328 q_norm: up(&p("self_attn.q_norm.weight"))?,
1329 k_norm: up(&p("self_attn.k_norm.weight"))?,
1330 });
1331 }
1332 let markov = if let Some((info, bytes)) = st.raw("markov_head.markov_w1.weight") {
1333 let sh = info.ne(); let (rank, vocab) = (sh[0] as usize, sh[1] as usize);
1335 let (i2, b2) = st
1336 .raw("markov_head.markov_w2.weight")
1337 .ok_or("markov_w2 missing beside markov_w1")?;
1338 let w2 = if prec == "bf16" {
1342 GpuTensor::FloatBf16 {
1343 data: e.upload_u8(b2)?,
1344 ne: i2.ne().to_vec(),
1345 }
1346 } else {
1347 let w2f = bf16_to_f32(b2);
1348 let w2q = encode_q8_0(&w2f);
1349 GpuTensor::Quant {
1350 bytes: e.upload_u8(&w2q)?,
1351 qtype: crate::QT_Q8_0,
1352 row_bytes: rank / 32 * 34,
1353 ne: vec![rank as u64, vocab as u64],
1354 scale: 1.0,
1355 rp: false,
1356 #[cfg(memra_cutlass)]
1357 cutlass: None,
1358 fp8: None,
1359 blk: None,
1360 rp4: None,
1361 f16: None,
1362 }
1363 };
1364 Some(MarkovHead {
1365 w1_bf16: e.upload_u8(bytes)?,
1366 w2,
1367 rank,
1368 vocab,
1369 })
1370 } else {
1371 None
1372 };
1373 let confidence = if let Some((info, bytes)) = st.raw("confidence_head.proj.weight") {
1374 let sh = info.ne(); let in_dim = sh[0] as usize;
1376 let (_bi, bb) = st
1377 .raw("confidence_head.proj.bias")
1378 .ok_or("confidence bias missing beside weight")?;
1379 let with_markov = markov
1380 .as_ref()
1381 .map(|m| in_dim == cfg.hidden + m.rank)
1382 .unwrap_or(false);
1383 if !with_markov && in_dim != cfg.hidden {
1384 panic!(
1385 "confidence_head in_dim {in_dim} matches neither hidden {} nor hidden+rank",
1386 cfg.hidden
1387 );
1388 }
1389 Some(ConfidenceHead {
1390 w: bf16_to_f32(bytes),
1391 b: bf16_to_f32(bb)[0],
1392 in_dim,
1393 with_markov,
1394 })
1395 } else {
1396 None
1397 };
1398 let dflash2 = if is_dflash2 {
1404 assert!(
1405 markov.is_none() && confidence.is_none(),
1406 "DFlash2 checkpoint carries markov/confidence tensors — no such \
1407 variant exists in the family (census refuses the ambiguity)"
1408 );
1409 let rank = g2("selector_rank");
1410 let top_k = g2("selector_top_k");
1411 let conv_k = g2("conv_kernel_size");
1412 let group_size = g2("conv_group_size");
1413 let groups = cfg.hidden / group_size;
1414 let load_conv = |name: &str| -> Result<Dflash2Conv, Box<dyn std::error::Error>> {
1415 let (bi, bb) = st
1416 .raw(&format!("{name}.base_kernel"))
1417 .ok_or_else(|| format!("DFlash2 census: missing {name}.base_kernel"))?;
1418 let bne = bi.ne();
1420 assert_eq!(
1421 (bne[0] as usize, bne[1] as usize, bne[2] as usize),
1422 (cfg.hidden, conv_k, 2),
1423 "{name}.base_kernel shape != [2, conv_kernel_size, hidden]"
1424 );
1425 let pname = format!("{name}.kernel_projection.weight");
1426 let (pi, _pb) = st
1427 .raw(&pname)
1428 .ok_or_else(|| format!("DFlash2 census: missing {pname}"))?;
1429 let pne = pi.ne(); assert_eq!(
1431 (pne[0] as usize, pne[1] as usize),
1432 (cfg.hidden, 2 * conv_k * groups),
1433 "{pname} shape != [2*conv_kernel_size*groups, hidden]"
1434 );
1435 Ok(Dflash2Conv {
1436 base: e.htod(&bf16_to_f32(bb))?,
1437 proj: upw(&pname)?,
1438 })
1439 };
1440 let mut attn_conv = Vec::with_capacity(cfg.n_layer);
1441 let mut mlp_conv = Vec::with_capacity(cfg.n_layer);
1442 for i in 0..cfg.n_layer {
1443 attn_conv.push(load_conv(&format!("layers.{i}.attention_conv"))?);
1444 mlp_conv.push(load_conv(&format!("layers.{i}.mlp_conv"))?);
1445 }
1446 let cb = |name: &str| -> Result<(Vec<u8>, usize), Box<dyn std::error::Error>> {
1449 let (ci, cbytes) = st
1450 .raw(&format!("candidate_selector.{name}"))
1451 .ok_or_else(|| format!("DFlash2 census: missing candidate_selector.{name}"))?;
1452 let ne = ci.ne(); assert_eq!(ne[0] as usize, rank, "candidate_selector.{name} rank");
1454 Ok((cbytes.to_vec(), ne[1] as usize))
1455 };
1456 let (pred_codebook, v1) = cb("predecessor_codebook")?;
1457 let (succ_codebook, v2) = cb("successor_codebook")?;
1458 assert_eq!(v1, v2, "codebook vocab mismatch");
1459 let hp_name = "candidate_selector.hidden_projection.weight";
1460 let (hi, _hb) = st
1461 .raw(hp_name)
1462 .ok_or_else(|| format!("DFlash2 census: missing {hp_name}"))?;
1463 assert_eq!(
1464 (hi.ne()[0] as usize, hi.ne()[1] as usize),
1465 (cfg.hidden, rank),
1466 "{hp_name} shape != [rank, hidden]"
1467 );
1468 Some(Dflash2Head {
1469 attn_conv,
1470 mlp_conv,
1471 hidden_proj: upw(hp_name)?,
1472 pred_codebook,
1473 succ_codebook,
1474 rank,
1475 top_k,
1476 conv_k,
1477 group_size,
1478 vocab: v1,
1479 })
1480 } else {
1481 None
1482 };
1483 {
1489 let mut consumed: std::collections::HashSet<String> = std::collections::HashSet::new();
1490 for i in 0..cfg.n_layer {
1491 for s in [
1492 "self_attn.q_proj.weight",
1493 "self_attn.k_proj.weight",
1494 "self_attn.v_proj.weight",
1495 "self_attn.o_proj.weight",
1496 "self_attn.q_norm.weight",
1497 "self_attn.k_norm.weight",
1498 "input_layernorm.weight",
1499 "post_attention_layernorm.weight",
1500 "mlp.gate_proj.weight",
1501 "mlp.up_proj.weight",
1502 "mlp.down_proj.weight",
1503 ] {
1504 consumed.insert(format!("layers.{i}.{s}"));
1505 }
1506 if dflash2.is_some() {
1507 for s in [
1508 "attention_conv.base_kernel",
1509 "attention_conv.kernel_projection.weight",
1510 "mlp_conv.base_kernel",
1511 "mlp_conv.kernel_projection.weight",
1512 ] {
1513 consumed.insert(format!("layers.{i}.{s}"));
1514 }
1515 }
1516 }
1517 for s in [
1518 "fc.weight",
1519 "hidden_norm.weight",
1520 "norm.weight",
1521 "markov_head.markov_w1.weight",
1522 "markov_head.markov_w2.weight",
1523 "confidence_head.proj.weight",
1524 "confidence_head.proj.bias",
1525 ] {
1526 consumed.insert(s.into());
1527 }
1528 if dflash2.is_some() {
1529 for s in [
1530 "candidate_selector.hidden_projection.weight",
1531 "candidate_selector.predecessor_codebook",
1532 "candidate_selector.successor_codebook",
1533 ] {
1534 consumed.insert(s.into());
1535 }
1536 }
1537 let leftovers: Vec<&String> = st.names().filter(|n| !consumed.contains(*n)).collect();
1538 if !leftovers.is_empty() {
1539 if markov.is_some() || dflash2.is_some() {
1540 panic!("dspark/dflash2 census: unrecognized tensors {leftovers:?}");
1541 }
1542 eprintln!("[dflash census] unmapped tensors (ignored): {leftovers:?}");
1543 }
1544 }
1545 let rope_yarn =
1548 if txt.contains("\"rope_type\": \"yarn\"") || txt.contains("\"rope_type\":\"yarn\"") {
1549 let factor = num(&txt, "factor").expect("yarn factor") as f64;
1550 let orig = num(&txt, "original_max_position_embeddings").expect("yarn orig");
1551 let beta_fast = num(&txt, "beta_fast").expect("beta_fast");
1552 let beta_slow = num(&txt, "beta_slow").expect("beta_slow");
1553 let base = cfg.rope_theta as f64;
1554 let d = cfg.head_dim as f64;
1555 let corr =
1556 |r: f64| d * (orig / (r * 2.0 * std::f64::consts::PI)).ln() / (2.0 * base.ln());
1557 let low = corr(beta_fast).floor().max(0.0);
1558 let high = corr(beta_slow).ceil().min(d - 1.0);
1559 let half = cfg.head_dim / 2;
1560 let mut ff = Vec::with_capacity(half);
1561 for j in 0..half {
1562 let base_inv = base.powf(-2.0 * j as f64 / d);
1563 let ramp = (((j as f64) - low) / (high - low)).clamp(0.0, 1.0);
1564 let ex = 1.0 - ramp; let yarn_inv = (base_inv / factor) * (1.0 - ex) + base_inv * ex;
1566 ff.push((base_inv / yarn_inv) as f32);
1567 }
1568 let mscale = (0.1 * factor.ln() + 1.0) as f32;
1569 Some((e.htod(&ff)?, mscale))
1570 } else {
1571 None
1572 };
1573 eprintln!(
1577 "[dspark] harvest={} (checkpoint census dflash2={} strategy_dspark={}, \
1578 MEMRA_DSPARK_HARVEST {})",
1579 DsparkHarvest::for_family_value(
1580 dflash2.is_some(),
1581 std::env::var("MEMRA_DSPARK_HARVEST").ok().as_deref(),
1582 cfg.strategy_dspark,
1583 )
1584 .name(),
1585 dflash2.is_some(),
1586 cfg.strategy_dspark,
1587 match std::env::var("MEMRA_DSPARK_HARVEST") {
1588 Ok(v) if !v.is_empty() => "set",
1589 _ => "unset",
1590 },
1591 );
1592 eprintln!(
1593 "[dspark] verify-window={:?} (accept-rate head {}, MEMRA_DSPARK_VT {})",
1594 DsparkVtPolicy::resolve(confidence.is_some()),
1595 if confidence.is_some() {
1596 "present"
1597 } else {
1598 "ABSENT -> ladder"
1599 },
1600 match std::env::var("MEMRA_DSPARK_VT") {
1601 Ok(v) if !v.is_empty() => "set",
1602 _ => "unset",
1603 },
1604 );
1605 Ok(Self {
1606 fc: upw("fc.weight")?,
1607 hidden_norm: up("hidden_norm.weight")?,
1608 norm: up("norm.weight")?,
1609 cfg,
1610 layers,
1611 markov,
1612 confidence,
1613 rope_yarn,
1614 dflash2,
1615 })
1616 }
1617
1618 fn rope_rows(
1621 &self,
1622 e: &Engine,
1623 x: &mut CudaSlice<f32>,
1624 pos_d: &CudaSlice<i32>,
1625 n_heads: usize,
1626 n_tokens: usize,
1627 ) -> Result<(), Box<dyn std::error::Error>> {
1628 let c = &self.cfg;
1629 match &self.rope_yarn {
1630 Some((ff, mscale)) => {
1631 e.rope_neox_ff(
1632 x,
1633 pos_d,
1634 c.head_dim,
1635 c.head_dim,
1636 n_heads,
1637 n_tokens,
1638 c.rope_theta,
1639 1.0,
1640 ff,
1641 )?;
1642 e.scale_inplace(x, *mscale, n_tokens * n_heads * c.head_dim)?;
1643 }
1644 None => {
1645 e.rope_neox(
1646 x,
1647 pos_d,
1648 c.head_dim,
1649 c.head_dim,
1650 n_heads,
1651 n_tokens,
1652 c.rope_theta,
1653 1.0,
1654 )?;
1655 }
1656 }
1657 Ok(())
1658 }
1659
1660 fn mm(
1662 &self,
1663 e: &Engine,
1664 w: &GpuTensor,
1665 x: &CudaSlice<f32>,
1666 t: usize,
1667 _in_f: usize,
1668 _out_f: usize,
1669 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1670 Ok(e.matmul(w, x, t)?)
1671 }
1672
1673 pub fn d2_conv_prepare(
1678 &self,
1679 e: &Engine,
1680 conv: &Dflash2Conv,
1681 xn: &CudaSlice<f32>,
1682 rows: usize,
1683 ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
1684 let d2 = self
1685 .dflash2
1686 .as_ref()
1687 .expect("d2_conv on a non-dflash2 draft");
1688 let h = self.cfg.hidden;
1689 let groups = h / d2.group_size;
1690 let dyn_ = self.mm(e, &conv.proj, xn, rows, h, 2 * d2.conv_k * groups)?;
1691 let mut out = e.uninit(rows * h)?;
1692 e.dflash2_dynconv(
1693 xn,
1694 &dyn_,
1695 &conv.base,
1696 &mut out,
1697 rows,
1698 h,
1699 d2.group_size,
1700 d2.conv_k,
1701 0,
1702 )?;
1703 Ok((out, dyn_))
1704 }
1705
1706 pub fn d2_conv_finish(
1709 &self,
1710 e: &Engine,
1711 conv: &Dflash2Conv,
1712 y: &CudaSlice<f32>,
1713 dyn_: &CudaSlice<f32>,
1714 rows: usize,
1715 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1716 let d2 = self
1717 .dflash2
1718 .as_ref()
1719 .expect("d2_conv on a non-dflash2 draft");
1720 let h = self.cfg.hidden;
1721 let mut out = e.uninit(rows * h)?;
1722 e.dflash2_dynconv(
1723 y,
1724 dyn_,
1725 &conv.base,
1726 &mut out,
1727 rows,
1728 h,
1729 d2.group_size,
1730 d2.conv_k,
1731 1,
1732 )?;
1733 Ok(out)
1734 }
1735
1736 pub fn dflash2_propose_greedy(
1742 &self,
1743 e: &Engine,
1744 dl: &CudaSlice<f32>,
1745 rows: &CudaSlice<f32>,
1746 nd: usize,
1747 n_vocab: usize,
1748 anchor: u32,
1749 ) -> Result<Vec<u32>, Box<dyn std::error::Error>> {
1750 let d2 = self
1751 .dflash2
1752 .as_ref()
1753 .expect("dflash2_propose on a non-dflash2 draft");
1754 assert!(
1755 n_vocab <= d2.vocab,
1756 "target head vocab {n_vocab} exceeds the selector codebooks ({})",
1757 d2.vocab
1758 );
1759 let (vals_d, idx_d) = e.topk_rows(dl, nd, n_vocab, d2.top_k)?;
1760 let hproj_d = e.matmul(&d2.hidden_proj, rows, nd)?;
1761 let unary = e.dtoh(&vals_d)?;
1762 let cand = e.dtoh_u32(&idx_d)?;
1763 let hproj = e.dtoh(&hproj_d)?;
1764 Ok(d2.walk_greedy(&unary, &cand, &hproj, anchor, nd))
1765 }
1766
1767 #[allow(clippy::too_many_arguments)]
1772 pub(crate) fn dflash2_propose_sampled(
1773 &self,
1774 e: &Engine,
1775 dl: &CudaSlice<f32>,
1776 rows: &CudaSlice<f32>,
1777 nd: usize,
1778 n_vocab: usize,
1779 anchor: u32,
1780 temp: f32,
1781 seed: u64,
1782 uctr: &mut u32,
1783 ) -> Result<Dflash2SampledProposal, Box<dyn std::error::Error>> {
1784 let d2 = self
1785 .dflash2
1786 .as_ref()
1787 .expect("dflash2_propose on a non-dflash2 draft");
1788 assert!(
1789 n_vocab <= d2.vocab,
1790 "target head vocab {n_vocab} exceeds the selector codebooks ({})",
1791 d2.vocab
1792 );
1793 let (vals_d, idx_d) = e.topk_rows(dl, nd, n_vocab, d2.top_k)?;
1794 let hproj_d = e.matmul(&d2.hidden_proj, rows, nd)?;
1795 let unary = e.dtoh(&vals_d)?;
1796 let cand = e.dtoh_u32(&idx_d)?;
1797 let hproj = e.dtoh(&hproj_d)?;
1798 let mut draw = || {
1799 let u = crate::spec::host_u01(seed, *uctr);
1800 *uctr = uctr.wrapping_add(1);
1801 u
1802 };
1803 let (path, q_chosen, q_rows) =
1804 d2.walk_sampled(&unary, &cand, &hproj, anchor, nd, temp, &mut draw);
1805 Ok((path, q_chosen, cand, q_rows))
1806 }
1807
1808 #[allow(clippy::too_many_arguments)]
1817 pub(crate) fn dspark_chain_sampled(
1818 &self,
1819 e: &Engine,
1820 dl: &mut CudaSlice<f32>,
1821 nd: usize,
1822 n_vocab: usize,
1823 anchor: u32,
1824 sp: &crate::spec::SpecSampling,
1825 sctr: &mut u32,
1826 mut conf_emb: Option<&mut CudaSlice<f32>>,
1831 ) -> Result<(Vec<u32>, DsparkDraftSample), Box<dyn std::error::Error>> {
1832 let markov_on = std::env::var("MEMRA_DFLASH_MARKOV").as_deref() != Ok("0");
1833 let mut chain_d = e.stream().alloc_zeros::<u32>(nd + 1)?;
1834 e.set_u32_one(&mut chain_d, anchor)?;
1835 let mut th_all = e.zeros(nd)?;
1836 let mut z_all = e.zeros(nd)?;
1837 let mut mx_all = e.zeros(nd)?;
1838 let mut pb = e.zeros(n_vocab)?;
1839 for k in 0..nd {
1840 if let (Some(mk), true) = (&self.markov, markov_on) {
1841 let mut f = e.uninit(mk.rank)?;
1842 e.gather_row_bf16(&mk.w1_bf16, &chain_d, k, &mut f, mk.rank)?;
1843 if let Some(ce) = conf_emb.as_deref_mut() {
1844 let fv = e.view(&f, mk.rank);
1845 e.copy_view_into(ce, k * mk.rank, &fv, mk.rank)?;
1846 }
1847 let bias = e.matmul(&mk.w2, &f, 1)?;
1848 e.add_row_inplace(dl, &bias, n_vocab, k * n_vocab)?;
1849 } else if let (Some(ce), Some(mk)) = (conf_emb.as_deref_mut(), &self.markov) {
1850 let mut f = e.uninit(mk.rank)?;
1853 e.gather_row_bf16(&mk.w1_bf16, &chain_d, k, &mut f, mk.rank)?;
1854 let fv = e.view(&f, mk.rank);
1855 e.copy_view_into(ce, k * mk.rank, &fv, mk.rank)?;
1856 }
1857 let rows_k = e.htod_i32(&[k as i32])?;
1858 let (mut th1, mut z1, mut mx1) = (e.zeros(1)?, e.zeros(1)?, e.zeros(1)?);
1859 e.filter_stats(
1860 dl, n_vocab, &rows_k, &mut th1, &mut z1, &mut mx1, n_vocab, 1, sp.temp, sp.top_k,
1861 sp.top_p, sp.min_p,
1862 )?;
1863 e.gumbel_perturb_filtered_col(
1864 dl, k, &mut pb, n_vocab, sp.seed, *sctr, sp.temp, &mx1, &th1, 0,
1865 )?;
1866 *sctr = sctr.wrapping_add(1);
1867 e.argmax_token_device_col(&pb, 0, n_vocab, &mut chain_d, k + 1)?;
1868 e.copy_into(&mut th_all, k, &th1, 1)?;
1869 e.copy_into(&mut z_all, k, &z1, 1)?;
1870 e.copy_into(&mut mx_all, k, &mx1, 1)?;
1871 }
1872 let chain = e.dtoh_u32(&chain_d)?;
1873 let (thv, zv, mxv) = (e.dtoh(&th_all)?, e.dtoh(&z_all)?, e.dtoh(&mx_all)?);
1874 let stats = (0..nd).map(|i| (mxv[i], thv[i], zv[i])).collect();
1875 Ok((
1876 chain[1..].to_vec(),
1877 DsparkDraftSample::Rows {
1878 th: th_all,
1879 z: z_all,
1880 stats,
1881 },
1882 ))
1883 }
1884
1885 #[allow(clippy::too_many_arguments)]
1888 pub(crate) fn dspark_propose_sampled(
1889 &self,
1890 e: &Engine,
1891 dl: &mut CudaSlice<f32>,
1892 rows: &CudaSlice<f32>,
1893 nd: usize,
1894 n_vocab: usize,
1895 anchor: u32,
1896 sp: &crate::spec::SpecSampling,
1897 sctr: &mut u32,
1898 uctr: &mut u32,
1899 conf_emb: Option<&mut CudaSlice<f32>>,
1900 ) -> Result<(Vec<u32>, DsparkDraftSample), Box<dyn std::error::Error>> {
1901 if let Some(d2) = self.dflash2.as_ref() {
1902 debug_assert!(
1905 conf_emb.is_none(),
1906 "conf_emb stash requested on a DFlash2 selector proposal"
1907 );
1908 let (path, q_chosen, cand, q_rows) = self.dflash2_propose_sampled(
1909 e, dl, rows, nd, n_vocab, anchor, sp.temp, sp.seed, uctr,
1910 )?;
1911 Ok((
1912 path,
1913 DsparkDraftSample::Selector {
1914 cand,
1915 q_rows,
1916 q_chosen,
1917 top_k: d2.top_k,
1918 },
1919 ))
1920 } else {
1921 self.dspark_chain_sampled(e, dl, nd, n_vocab, anchor, sp, sctr, conf_emb)
1922 }
1923 }
1924
1925 pub fn ctx_features(
1936 &self,
1937 e: &Engine,
1938 taps: &CudaSlice<f32>,
1939 t: usize,
1940 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1941 let c = &self.cfg;
1942 let n_taps = c.target_layer_ids.len();
1943 let fc_out = self.mm(e, &self.fc, taps, t, n_taps * c.hidden, c.hidden)?;
1944 let mut out = e.uninit(t * c.hidden)?;
1945 e.rms_norm(&fc_out, &self.hidden_norm, &mut out, c.hidden, t, c.eps)?;
1946 Ok(out)
1947 }
1948
1949 pub fn forward(
1950 &self,
1951 e: &Engine,
1952 target_hidden: &CudaSlice<f32>,
1953 noise_emb: &CudaSlice<f32>,
1954 pos: &[i32],
1955 ctx: usize,
1956 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1957 let ctx_f = self.ctx_features(e, target_hidden, ctx)?;
1958 if let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP") {
1959 let v = e.dtoh(&ctx_f)?;
1960 let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
1961 std::fs::write(format!("{dir}/memra-ctx_features.f32"), bytes)?;
1962 }
1963 self.forward_block(e, &ctx_f, noise_emb, pos, ctx)
1964 }
1965
1966 pub fn forward_block(
1969 &self,
1970 e: &Engine,
1971 ctx_f: &CudaSlice<f32>,
1972 noise_emb: &CudaSlice<f32>,
1973 pos: &[i32],
1974 ctx: usize,
1975 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1976 let c = &self.cfg;
1977 let (h, nh, nkv, hd) = (c.hidden, c.n_head, c.n_kv, c.head_dim);
1978 let b = c.block_size;
1979 assert_eq!(pos.len(), ctx + b, "pos covers ctx rows then block rows");
1980
1981 let pos_blk = e.htod_i32(&pos[ctx..])?;
1982
1983 let mut x = e.clone_dtod(noise_emb)?; for (li, l) in self.layers.iter().enumerate() {
1985 let mut xn = e.uninit(b * h)?;
1988 e.rms_norm(&x, &l.ln_in, &mut xn, h, b, c.eps)?;
1989 let mut attn_dyn: Option<CudaSlice<f32>> = None;
1993 if let Some(d2) = &self.dflash2 {
1994 let (xc, dyn_) = self.d2_conv_prepare(e, &d2.attn_conv[li], &xn, b)?;
1995 xn = xc;
1996 attn_dyn = Some(dyn_);
1997 }
1998
1999 let q0 = self.mm(e, &l.wq, &xn, b, h, nh * hd)?;
2001 let k0c = self.mm(e, &l.wk, ctx_f, ctx, h, nkv * hd)?;
2002 let v0c = self.mm(e, &l.wv, ctx_f, ctx, h, nkv * hd)?;
2003 let k0b = self.mm(e, &l.wk, &xn, b, h, nkv * hd)?;
2004 let v0b = self.mm(e, &l.wv, &xn, b, h, nkv * hd)?;
2005
2006 let mut k0 = e.uninit((ctx + b) * nkv * hd)?;
2009 e.copy_into(&mut k0, 0, &k0c, ctx * nkv * hd)?;
2010 e.copy_into(&mut k0, ctx * nkv * hd, &k0b, b * nkv * hd)?;
2011 let mut v = e.uninit((ctx + b) * nkv * hd)?;
2012 e.copy_into(&mut v, 0, &v0c, ctx * nkv * hd)?;
2013 e.copy_into(&mut v, ctx * nkv * hd, &v0b, b * nkv * hd)?;
2014
2015 if li == 0 {
2016 if let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP") {
2017 let v = e.dtoh(&q0)?;
2018 let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
2019 std::fs::write(format!("{dir}/memra-l0_q0.f32"), bytes)?;
2020 }
2021 }
2022 let mut q = e.uninit(b * nh * hd)?;
2023 let mut k = e.uninit((ctx + b) * nkv * hd)?;
2024 e.rms_norm(&q0, &l.q_norm, &mut q, hd, b * nh, c.eps)?;
2026 if li == 0 {
2027 if let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP") {
2028 let v = e.dtoh(&q)?;
2029 let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
2030 std::fs::write(format!("{dir}/memra-l0_qn.f32"), bytes)?;
2031 }
2032 }
2033 e.rms_norm(&k0, &l.k_norm, &mut k, hd, (ctx + b) * nkv, c.eps)?;
2034
2035 let norope = std::env::var("MEMRA_DFLASH_NOROPE").is_ok();
2037 if !norope {
2038 self.rope_rows(e, &mut q, &pos_blk, nh, b)?;
2039 }
2040 if li == 0 {
2041 if let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP") {
2042 let dump = |name: &str,
2043 t: &cudarc::driver::CudaSlice<f32>|
2044 -> Result<(), Box<dyn std::error::Error>> {
2045 let v = e.dtoh(t)?;
2046 let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
2047 std::fs::write(format!("{dir}/memra-l0_{name}.f32"), bytes)?;
2048 Ok(())
2049 };
2050 dump("xn", &xn)?;
2051 dump("q_prerope", &q)?;
2052 }
2053 }
2054 let pos_all = e.htod_i32(pos)?;
2057 if !norope {
2058 self.rope_rows(e, &mut k, &pos_all, nkv, ctx + b)?;
2059 }
2060
2061 let mut attn = e.uninit(b * nh * hd)?;
2063 let scale = 1.0f32 / (hd as f32).sqrt();
2064 if self.dflash2.is_some() && c.layer_sliding[li] {
2070 debug_assert!(pos.windows(2).all(|w| w[1] == w[0] + 1));
2077 d2_windowed_attn(e, &q, &k, &v, &mut attn, hd, nh, nkv, b, ctx + b, scale, c)?;
2078 } else if std::env::var("MEMRA_DFLASH_FA").is_ok() {
2079 e.fa_prefill(&q, &k, &v, &mut attn, hd, nh, nkv, b, ctx + b, scale, false)?;
2080 } else {
2081 e.sdpa_naive(&q, &k, &v, &mut attn, hd, nh, nkv, b, ctx + b, scale, false)?;
2082 }
2083
2084 let mut o = self.mm(e, &l.wo, &attn, b, nh * hd, h)?;
2085 if let (Some(d2), Some(dyn_)) = (&self.dflash2, &attn_dyn) {
2086 o = self.d2_conv_finish(e, &d2.attn_conv[li], &o, dyn_, b)?;
2087 }
2088 let mut x1 = e.uninit(b * h)?;
2089 e.add(&o, &x, &mut x1, b * h)?;
2090 if li == 0 {
2091 if let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP") {
2092 let dump = |name: &str,
2093 t: &cudarc::driver::CudaSlice<f32>|
2094 -> Result<(), Box<dyn std::error::Error>> {
2095 let v = e.dtoh(t)?;
2096 let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
2097 std::fs::write(format!("{dir}/memra-l0_{name}.f32"), bytes)?;
2098 Ok(())
2099 };
2100 dump("q", &q)?;
2101 dump("k", &k)?;
2102 dump("attn", &attn)?;
2103 dump("x1", &x1)?;
2104 }
2105 }
2106
2107 let mut x1n = e.uninit(b * h)?;
2110 e.rms_norm(&x1, &l.ln_post, &mut x1n, h, b, c.eps)?;
2111 let mut mlp_dyn: Option<CudaSlice<f32>> = None;
2112 if let Some(d2) = &self.dflash2 {
2113 let (xc, dyn_) = self.d2_conv_prepare(e, &d2.mlp_conv[li], &x1n, b)?;
2114 x1n = xc;
2115 mlp_dyn = Some(dyn_);
2116 }
2117 let gate = self.mm(e, &l.w_gate, &x1n, b, h, c.n_ff)?;
2118 let up_ = self.mm(e, &l.w_up, &x1n, b, h, c.n_ff)?;
2119 let mut act = e.uninit(b * c.n_ff)?;
2120 e.silu_mul(&gate, &up_, &mut act, b * c.n_ff)?;
2121 let mut down = self.mm(e, &l.w_down, &act, b, c.n_ff, h)?;
2122 if let (Some(d2), Some(dyn_)) = (&self.dflash2, &mlp_dyn) {
2123 down = self.d2_conv_finish(e, &d2.mlp_conv[li], &down, dyn_, b)?;
2124 }
2125 let mut x2 = e.uninit(b * h)?;
2126 e.add(&down, &x1, &mut x2, b * h)?;
2127 x = x2;
2128 if let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP") {
2129 let v = e.dtoh(&x)?;
2130 let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
2131 std::fs::write(format!("{dir}/memra-layer{li}_out.f32"), bytes)?;
2132 }
2133 }
2134 let mut out = e.uninit(b * h)?;
2135 e.rms_norm(&x, &self.norm, &mut out, h, b, c.eps)?;
2136 Ok(out)
2137 }
2138}
2139
2140pub struct DflashKv {
2145 pub k: Vec<CudaSlice<f32>>, pub v: Vec<CudaSlice<f32>>,
2147 pub len: usize,
2148 pub cap: usize,
2149}
2150
2151impl DflashKv {
2152 pub fn new(
2153 e: &Engine,
2154 cfg: &DflashCfg,
2155 cap: usize,
2156 ) -> Result<Self, Box<dyn std::error::Error>> {
2157 let rowsz = cfg.n_kv * cfg.head_dim;
2158 let mut k = Vec::with_capacity(cfg.n_layer);
2159 let mut v = Vec::with_capacity(cfg.n_layer);
2160 for _ in 0..cfg.n_layer {
2161 k.push(e.uninit((cap + cfg.block_size) * rowsz)?);
2162 v.push(e.uninit((cap + cfg.block_size) * rowsz)?);
2163 }
2164 Ok(Self { k, v, len: 0, cap })
2165 }
2166}
2167
2168impl DflashDraft {
2169 pub fn ingest_ctx(
2172 &self,
2173 e: &Engine,
2174 kv: &mut DflashKv,
2175 feats: &CudaSlice<f32>,
2176 pos_new: &[i32],
2177 t: usize,
2178 ) -> Result<(), Box<dyn std::error::Error>> {
2179 let c = &self.cfg;
2180 let (h, nkv, hd) = (c.hidden, c.n_kv, c.head_dim);
2181 assert!(kv.len + t <= kv.cap, "draft kv overflow");
2182 let pos_d = e.htod_i32(pos_new)?;
2183 for (li, l) in self.layers.iter().enumerate() {
2184 let k0 = self.mm(e, &l.wk, feats, t, h, nkv * hd)?;
2185 let v0 = self.mm(e, &l.wv, feats, t, h, nkv * hd)?;
2186 let mut kn = e.uninit(t * nkv * hd)?;
2187 e.rms_norm(&k0, &l.k_norm, &mut kn, hd, t * nkv, c.eps)?;
2188 self.rope_rows(e, &mut kn, &pos_d, nkv, t)?;
2189 e.copy_into(&mut kv.k[li], kv.len * nkv * hd, &kn, t * nkv * hd)?;
2190 e.copy_into(&mut kv.v[li], kv.len * nkv * hd, &v0, t * nkv * hd)?;
2191 }
2192 kv.len += t;
2193 Ok(())
2194 }
2195
2196 pub fn forward_round(
2200 &self,
2201 e: &Engine,
2202 kv: &mut DflashKv,
2203 noise_emb: &CudaSlice<f32>,
2204 pos_block: &[i32],
2205 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2206 let c = &self.cfg;
2207 let (h, nh, nkv, hd) = (c.hidden, c.n_head, c.n_kv, c.head_dim);
2208 let b = c.block_size;
2209 assert_eq!(pos_block.len(), b);
2210 let ctx = kv.len;
2211 let pos_blk = e.htod_i32(pos_block)?;
2212 let mut x = e.clone_dtod(noise_emb)?;
2213 for (li, l) in self.layers.iter().enumerate() {
2214 let mut xn = e.uninit(b * h)?;
2215 e.rms_norm(&x, &l.ln_in, &mut xn, h, b, c.eps)?;
2216 let mut attn_dyn: Option<CudaSlice<f32>> = None;
2218 if let Some(d2) = &self.dflash2 {
2219 let (xc, dyn_) = self.d2_conv_prepare(e, &d2.attn_conv[li], &xn, b)?;
2220 xn = xc;
2221 attn_dyn = Some(dyn_);
2222 }
2223 let q0 = self.mm(e, &l.wq, &xn, b, h, nh * hd)?;
2224 let k0b = self.mm(e, &l.wk, &xn, b, h, nkv * hd)?;
2225 let v0b = self.mm(e, &l.wv, &xn, b, h, nkv * hd)?;
2226 let mut q = e.uninit(b * nh * hd)?;
2227 let mut kb = e.uninit(b * nkv * hd)?;
2228 e.rms_norm(&q0, &l.q_norm, &mut q, hd, b * nh, c.eps)?;
2229 e.rms_norm(&k0b, &l.k_norm, &mut kb, hd, b * nkv, c.eps)?;
2230 self.rope_rows(e, &mut q, &pos_blk, nh, b)?;
2231 self.rope_rows(e, &mut kb, &pos_blk, nkv, b)?;
2232 e.copy_into(&mut kv.k[li], ctx * nkv * hd, &kb, b * nkv * hd)?;
2233 e.copy_into(&mut kv.v[li], ctx * nkv * hd, &v0b, b * nkv * hd)?;
2234 let mut attn = e.uninit(b * nh * hd)?;
2235 let scale = 1.0f32 / (hd as f32).sqrt();
2236 if self.dflash2.is_some() && c.layer_sliding[li] {
2237 d2_windowed_attn(
2243 e,
2244 &q,
2245 &kv.k[li],
2246 &kv.v[li],
2247 &mut attn,
2248 hd,
2249 nh,
2250 nkv,
2251 b,
2252 ctx + b,
2253 scale,
2254 c,
2255 )?;
2256 } else if std::env::var("MEMRA_DFLASH_FA").is_ok() {
2257 e.fa_prefill(
2258 &q,
2259 &kv.k[li],
2260 &kv.v[li],
2261 &mut attn,
2262 hd,
2263 nh,
2264 nkv,
2265 b,
2266 ctx + b,
2267 scale,
2268 false,
2269 )?;
2270 } else {
2271 e.sdpa_naive(
2272 &q,
2273 &kv.k[li],
2274 &kv.v[li],
2275 &mut attn,
2276 hd,
2277 nh,
2278 nkv,
2279 b,
2280 ctx + b,
2281 scale,
2282 false,
2283 )?;
2284 }
2285 let mut o = self.mm(e, &l.wo, &attn, b, nh * hd, h)?;
2286 if let (Some(d2), Some(dyn_)) = (&self.dflash2, &attn_dyn) {
2287 o = self.d2_conv_finish(e, &d2.attn_conv[li], &o, dyn_, b)?;
2288 }
2289 let mut x1 = e.uninit(b * h)?;
2290 e.add(&o, &x, &mut x1, b * h)?;
2291 let mut x1n = e.uninit(b * h)?;
2292 e.rms_norm(&x1, &l.ln_post, &mut x1n, h, b, c.eps)?;
2293 let mut mlp_dyn: Option<CudaSlice<f32>> = None;
2294 if let Some(d2) = &self.dflash2 {
2295 let (xc, dyn_) = self.d2_conv_prepare(e, &d2.mlp_conv[li], &x1n, b)?;
2296 x1n = xc;
2297 mlp_dyn = Some(dyn_);
2298 }
2299 let gate = self.mm(e, &l.w_gate, &x1n, b, h, c.n_ff)?;
2300 let up_ = self.mm(e, &l.w_up, &x1n, b, h, c.n_ff)?;
2301 let mut act = e.uninit(b * c.n_ff)?;
2302 e.silu_mul(&gate, &up_, &mut act, b * c.n_ff)?;
2303 let mut down = self.mm(e, &l.w_down, &act, b, c.n_ff, h)?;
2304 if let (Some(d2), Some(dyn_)) = (&self.dflash2, &mlp_dyn) {
2305 down = self.d2_conv_finish(e, &d2.mlp_conv[li], &down, dyn_, b)?;
2306 }
2307 let mut x2 = e.uninit(b * h)?;
2308 e.add(&down, &x1, &mut x2, b * h)?;
2309 x = x2;
2310 }
2311 let mut out = e.uninit(b * h)?;
2312 e.rms_norm(&x, &self.norm, &mut out, h, b, c.eps)?;
2313 Ok(out)
2314 }
2315}
2316
2317impl crate::hybrid::HybridModel {
2323 pub fn generate_spec_dflash(
2324 &self,
2325 e: &Engine,
2326 draft: &DflashDraft,
2327 prompt: &[u32],
2328 max_new: usize,
2329 eos: &[u32],
2330 ) -> Result<Vec<u32>, Box<dyn std::error::Error>> {
2331 use crate::cache::{Cache, DflashTapSink};
2332 let n_embd = self.cfg.n_embd as usize;
2333 let c = &draft.cfg;
2334 assert!(
2335 draft.dflash2.is_none(),
2336 "DFlash2 drafters ride the qwen-hybrid dspark round (selector + windowed \
2337 attention); the gemma arm has no consumer for the family's ops"
2338 );
2339 assert_eq!(n_embd, c.hidden, "draft hidden must match target n_embd");
2340 let b = c.block_size;
2341 let n_taps = c.target_layer_ids.len();
2342 let max_ctx = prompt.len() + max_new + b + 8;
2343 assert!(
2347 max_ctx <= c.sliding_window,
2348 "first-light dflash round is windowless — ctx cap {} exceeds the draft window {}",
2349 max_ctx,
2350 c.sliding_window
2351 );
2352 let mut cache = Cache::new(e, &self.cfg, max_ctx)?;
2353
2354 let tp = prompt.len();
2356 cache.dflash_taps = Some(DflashTapSink {
2357 layer_ids: c.target_layer_ids.clone(),
2358 buf: e.uninit(tp * n_taps * n_embd)?,
2359 hidden: n_embd,
2360 t: tp,
2361 base: 0,
2362 });
2363 let t_prime = std::time::Instant::now();
2364 let (logits, _h_seed, _hiddens) = self.prime_cache(e, prompt, &mut cache, 0)?;
2365 let mut last = crate::forward::argmax(&logits) as u32;
2366 let mut dkv = DflashKv::new(e, &draft.cfg, max_ctx)?;
2369 {
2370 let taps = cache.dflash_taps.take().unwrap();
2375 let n_taps_h = n_taps * n_embd;
2376 let mut r0 = 0usize;
2377 while r0 < tp {
2378 let t_c = (tp - r0).min(256);
2379 let tv = e.view(&taps.buf, tp * n_taps_h);
2380 let win = tv.slice(r0 * n_taps_h..(r0 + t_c) * n_taps_h);
2381 let mut chunk = e.uninit(t_c * n_taps_h)?;
2382 e.copy_view_into(&mut chunk, 0, &win, t_c * n_taps_h)?;
2383 let f = draft.ctx_features(e, &chunk, t_c)?;
2384 let pos_c: Vec<i32> = ((r0 as i32)..(r0 + t_c) as i32).collect();
2385 draft.ingest_ctx(e, &mut dkv, &f, &pos_c, t_c)?;
2386 r0 += t_c;
2387 }
2388 }
2389 let mut ctx_len = tp;
2390 e.stream().synchronize()?;
2391 crate::PRIME_NANOS.store(
2393 t_prime.elapsed().as_nanos() as u64,
2394 std::sync::atomic::Ordering::Relaxed,
2395 );
2396
2397 let emb_scale = if std::env::var("MEMRA_DFLASH_EMB_SCALE").as_deref() == Ok("1") {
2402 (n_embd as f32).sqrt()
2403 } else {
2404 1.0
2405 };
2406
2407 let mut out = Vec::with_capacity(max_new);
2408 let n_vocab = self.output.out_features();
2409 let vt_cap: usize = std::env::var("MEMRA_DFLASH_VERIFY_T")
2416 .ok()
2417 .and_then(|v| v.parse().ok())
2418 .unwrap_or(8)
2419 .clamp(2, b);
2420 let adapt = std::env::var("MEMRA_DFLASH_ADAPT").as_deref() != Ok("0");
2423 let mut vt = vt_cap;
2424 let mut attempted = 0usize;
2425 let mut accepted = 0usize;
2426 e.set_verify_exact(true);
2430 'outer: while out.len() < max_new {
2431 let start = cache.pos; let mut block: Vec<u32> = vec![c.mask_token_id; b];
2434 block[0] = last;
2435 let mut noise = e.htod(&self.embd.gather(n_embd, &block))?;
2436 if emb_scale != 1.0 {
2437 e.scale_inplace(&mut noise, emb_scale, b * n_embd)?;
2438 }
2439 if std::env::var("MEMRA_DFLASH_DEBUG").as_deref() == Ok("1") && start == cache.pos {
2440 let nv = e.dtoh(&noise)?;
2441 let r0: f32 = nv[..n_embd].iter().map(|x| x * x).sum::<f32>().sqrt();
2442 let r1: f32 = nv[n_embd..2 * n_embd]
2443 .iter()
2444 .map(|x| x * x)
2445 .sum::<f32>()
2446 .sqrt();
2447 eprintln!(
2448 "[dflash noise] |row0(last)|={r0:.3} |row1(MASK id {})|={r1:.3}",
2449 c.mask_token_id
2450 );
2451 }
2452 let pos_block: Vec<i32> = ((start as i32)..(start + b) as i32).collect();
2453 let dh = draft.forward_round(e, &mut dkv, &noise, &pos_block)?;
2454 let mut rows = e.uninit((b - 1) * n_embd)?;
2456 {
2457 let dv = e.view(&dh, b * n_embd);
2458 let tail = dv.slice(n_embd..b * n_embd);
2459 e.copy_view_into(&mut rows, 0, &tail, (b - 1) * n_embd)?;
2460 }
2461 let mut dl = e.matmul(&self.output, &rows, b - 1)?;
2462 let markov_on = std::env::var("MEMRA_DFLASH_MARKOV").as_deref() != Ok("0");
2468 let mut chain_d = e.stream().alloc_zeros::<u32>(b)?;
2469 if let (Some(mk), true) = (&draft.markov, markov_on) {
2470 e.set_u32_one(&mut chain_d, last)?;
2471 for k in 0..(b - 1) {
2472 let mut f = e.uninit(mk.rank)?;
2473 e.gather_row_bf16(&mk.w1_bf16, &chain_d, k, &mut f, mk.rank)?;
2474 let bias = e.matmul(&mk.w2, &f, 1)?;
2475 e.add_row_inplace(&mut dl, &bias, n_vocab, k * n_vocab)?;
2476 e.argmax_token_device_col(&dl, k, n_vocab, &mut chain_d, k + 1)?;
2477 }
2478 } else {
2479 for i in 0..(b - 1) {
2480 e.argmax_token_device_col(&dl, i, n_vocab, &mut chain_d, i + 1)?;
2481 }
2482 }
2483 let chain = e.dtoh_u32(&chain_d)?;
2484 let dtoks = &chain[1..];
2485 for (i, &dt) in dtoks.iter().enumerate() {
2486 block[i + 1] = dt;
2487 }
2488 let dbg = std::env::var("MEMRA_DFLASH_DEBUG").as_deref() == Ok("1");
2489
2490 let vblock = &block[..vt];
2492 cache.dflash_taps = Some(DflashTapSink {
2493 layer_ids: c.target_layer_ids.clone(),
2494 buf: e.uninit(vt * n_taps * n_embd)?,
2495 hidden: n_embd,
2496 t: vt,
2497 base: 0,
2498 });
2499 let (vam, _vh) = self.gemma4_decode_step_t_am(e, vblock, start, &mut cache)?;
2500 let taps = cache.dflash_taps.take().unwrap();
2501 if dbg {
2502 eprintln!(
2503 "[dflash r] start={start} last={last}\n draft={:?}\n vam ={:?}",
2504 &block[1..],
2505 &vam
2506 );
2507 }
2508
2509 let mut m = 0usize;
2511 while m < vt - 1 && block[m + 1] as usize == vam[m] as usize {
2512 m += 1;
2513 }
2514 attempted += vt - 1;
2515 accepted += m;
2516 out.push(last);
2517 if eos.contains(&last) {
2518 break 'outer;
2519 }
2520 for &dt in &block[1..=m] {
2521 out.push(dt);
2522 if eos.contains(&dt) {
2523 break 'outer;
2524 }
2525 if out.len() >= max_new {
2526 break 'outer;
2527 }
2528 }
2529 let next = vam[m] as u32;
2530
2531 let keep = m + 1;
2533 for kvl in cache.kv.iter_mut().flatten() {
2534 kvl.len -= vt - keep;
2535 e.set_i32_one(&mut kvl.len_d, kvl.len as i32)?;
2536 }
2537 cache.pos -= vt - keep;
2538
2539 {
2541 let tv = e.view(&taps.buf, vt * n_taps * n_embd);
2542 let keep_view = tv.slice(0..keep * n_taps * n_embd);
2543 let mut kept = e.uninit(keep * n_taps * n_embd)?;
2544 e.copy_view_into(&mut kept, 0, &keep_view, keep * n_taps * n_embd)?;
2545 let f = draft.ctx_features(e, &kept, keep)?;
2546 let pos_k: Vec<i32> = ((ctx_len as i32)..(ctx_len + keep) as i32).collect();
2547 draft.ingest_ctx(e, &mut dkv, &f, &pos_k, keep)?;
2548 ctx_len += keep;
2549 }
2550 last = next;
2551 if adapt {
2552 vt = (m + 2).clamp(3, vt_cap);
2553 }
2554 }
2555 e.set_verify_exact(false);
2556 if std::env::var("MEMRA_SPEC_STATS").as_deref() == Ok("1") {
2557 eprintln!(
2558 "[dflash] acceptance {accepted}/{attempted} = {:.3}",
2559 accepted as f64 / attempted.max(1) as f64
2560 );
2561 }
2562 Ok(out)
2563 }
2564}
2565
2566pub(crate) struct DsparkSnapBatch {
2579 pub(crate) snap: crate::cache::CacheSnapshot,
2580 lin: Vec<usize>,
2582 conv_table: CudaSlice<u64>,
2584 ssm_table: CudaSlice<u64>,
2585 host_ssm: Vec<u64>,
2586 conv_words: usize,
2587 ssm_words: usize,
2588}
2589
2590impl DsparkSnapBatch {
2591 pub(crate) fn new(
2596 e: &Engine,
2597 cache: &crate::cache::Cache,
2598 ) -> Result<Option<Self>, Box<dyn std::error::Error>> {
2599 use cudarc::driver::DevicePtr;
2600 let snap = cache.snapshot(e)?;
2601 let lin: Vec<usize> = (0..cache.recur.len())
2602 .filter(|&il| cache.recur[il].is_some())
2603 .collect();
2604 if lin.is_empty() {
2605 return Ok(None);
2606 }
2607 let first = cache.recur[lin[0]].as_ref().unwrap();
2608 let (conv_words, ssm_words) = (first.conv_state.len(), first.ssm_state.len());
2609 for &il in &lin {
2610 let rl = cache.recur[il].as_ref().unwrap();
2611 if rl.conv_state.len() != conv_words || rl.ssm_state.len() != ssm_words {
2612 return Ok(None);
2613 }
2614 }
2615 let n = lin.len();
2616 let mut host_conv = vec![0u64; 2 * n];
2617 let mut host_ssm = vec![0u64; 2 * n];
2618 {
2619 let s = &e.gpu.stream();
2620 for (k, &il) in lin.iter().enumerate() {
2621 let rl = cache.recur[il].as_ref().unwrap();
2622 let (pc, _g0) = rl.conv_state.device_ptr(s);
2623 let (ps, _g1) = rl.ssm_state.device_ptr(s);
2624 let (dc, _g2) = snap.conv[il].as_ref().unwrap().device_ptr(s);
2625 let (ds, _g3) = snap.ssm[il].as_ref().unwrap().device_ptr(s);
2626 host_conv[k] = pc as u64;
2627 host_conv[n + k] = dc as u64;
2628 host_ssm[k] = ps as u64;
2629 host_ssm[n + k] = ds as u64;
2630 }
2631 }
2632 let conv_table = e.htod_u64(&host_conv)?;
2633 let ssm_table = e.htod_u64(&host_ssm)?;
2634 Ok(Some(Self {
2635 snap,
2636 lin,
2637 conv_table,
2638 ssm_table,
2639 host_ssm,
2640 conv_words,
2641 ssm_words,
2642 }))
2643 }
2644
2645 pub(crate) fn refresh(
2649 &mut self,
2650 e: &Engine,
2651 cache: &crate::cache::Cache,
2652 ) -> Result<(), Box<dyn std::error::Error>> {
2653 use cudarc::driver::DevicePtr;
2654 for il in 0..cache.kv.len() {
2655 self.snap.kv_len[il] = cache.kv[il].as_ref().map(|kvl| kvl.len);
2656 }
2657 self.snap.pos = cache.pos;
2658 let n = self.lin.len();
2659 {
2660 let s = &e.gpu.stream();
2661 for (k, &il) in self.lin.iter().enumerate() {
2662 let rl = cache.recur[il].as_ref().unwrap();
2663 let (ps, _g) = rl.ssm_state.device_ptr(s);
2664 self.host_ssm[k] = ps as u64;
2665 }
2666 }
2667 e.htod_u64_into(&self.host_ssm, &mut self.ssm_table)?;
2668 e.copy_batch_uniform_f32(&self.conv_table, n, self.conv_words)?;
2669 e.copy_batch_uniform_f32(&self.ssm_table, n, self.ssm_words)?;
2670 Ok(())
2671 }
2672}
2673
2674impl crate::hybrid::HybridModel {
2682 pub fn generate_spec_dspark(
2683 &self,
2684 e: &Engine,
2685 draft: &DflashDraft,
2686 prompt: &[u32],
2687 max_new: usize,
2688 eos: &[u32],
2689 sampling: Option<&crate::spec::SpecSampling>,
2690 ) -> Result<Vec<u32>, Box<dyn std::error::Error>> {
2691 use crate::cache::{Cache, DflashTapSink};
2692 assert!(
2693 self.cfg.gemma4.is_none(),
2694 "gemma4 targets use generate_spec_dflash; this is the qwen-hybrid arm"
2695 );
2696 let sp_on: Option<&crate::spec::SpecSampling> = sampling.filter(|s| s.temp > 0.0);
2700 let (mut sctr, mut uctr) = (0u32, 0u32);
2701 let n_embd = self.cfg.n_embd as usize;
2702 let c = &draft.cfg;
2703 assert_eq!(n_embd, c.hidden, "draft hidden must match target n_embd");
2704 let b = c.block_size;
2705 let n_taps = c.target_layer_ids.len();
2706 let max_ctx = prompt.len() + max_new + b + 8;
2707 assert!(
2711 draft.dflash2.is_some() || max_ctx <= c.sliding_window,
2712 "dspark round is windowless — ctx cap {} exceeds the draft window {}",
2713 max_ctx,
2714 c.sliding_window
2715 );
2716 let mut cache = Cache::new(e, &self.cfg, max_ctx)?;
2717
2718 let tp = prompt.len();
2720 cache.dflash_taps = Some(DflashTapSink {
2721 layer_ids: c.target_layer_ids.clone(),
2722 buf: e.uninit(tp * n_taps * n_embd)?,
2723 hidden: n_embd,
2724 t: tp,
2725 base: 0,
2726 });
2727 let t_prime = std::time::Instant::now();
2728 let (logits, _h_seed, _hiddens) = self.prime_cache(e, prompt, &mut cache, 0)?;
2729 let mut last = match sp_on {
2733 Some(sp) => {
2734 crate::spec::sample_boundary_token(e, &logits, sp, &[], &mut sctr, "dspark-prime")?
2735 }
2736 None => crate::forward::argmax(&logits) as u32,
2737 };
2738 let mut dkv = DflashKv::new(e, &draft.cfg, max_ctx)?;
2739 {
2740 let taps = cache.dflash_taps.take().unwrap();
2741 let n_taps_h = n_taps * n_embd;
2742 let mut r0 = 0usize;
2743 while r0 < tp {
2744 let t_c = (tp - r0).min(256);
2745 let tv = e.view(&taps.buf, tp * n_taps_h);
2746 let win = tv.slice(r0 * n_taps_h..(r0 + t_c) * n_taps_h);
2747 let mut chunk = e.uninit(t_c * n_taps_h)?;
2748 e.copy_view_into(&mut chunk, 0, &win, t_c * n_taps_h)?;
2749 let f = draft.ctx_features(e, &chunk, t_c)?;
2750 let pos_c: Vec<i32> = ((r0 as i32)..(r0 + t_c) as i32).collect();
2751 draft.ingest_ctx(e, &mut dkv, &f, &pos_c, t_c)?;
2752 r0 += t_c;
2753 }
2754 }
2755 let mut ctx_len = tp;
2756 e.stream().synchronize()?;
2757 crate::PRIME_NANOS.store(
2758 t_prime.elapsed().as_nanos() as u64,
2759 std::sync::atomic::Ordering::Relaxed,
2760 );
2761
2762 let mut out = Vec::with_capacity(max_new);
2763 let n_vocab = self.output.out_features();
2764 let harvest = DsparkHarvest::for_draft(draft);
2770 let nd = harvest.n_drafts(b);
2771 let r0 = harvest.first_row();
2772 let vt_cap: usize = std::env::var("MEMRA_DFLASH_VERIFY_T")
2773 .ok()
2774 .and_then(|v| v.parse().ok())
2775 .unwrap_or(nd + 1)
2776 .clamp(2, nd + 1);
2777 let adapt = std::env::var("MEMRA_DFLASH_ADAPT").as_deref() != Ok("0");
2778 let vt_policy = DsparkVtPolicy::resolve(draft.confidence.is_some());
2784 if vt_policy.is_confidence() {
2785 assert!(
2786 draft.confidence.is_some(),
2787 "MEMRA_DSPARK_VT={vt_policy:?} needs a checkpoint with an accept-rate \
2788 head (confidence_head.* absent in this export)"
2789 );
2790 }
2791 let mut vt = vt_cap;
2792 let mut attempted = 0usize;
2793 let mut accepted = 0usize;
2794 let mut snapb: Option<DsparkSnapBatch> = None;
2798 let mut snapb_off = !crate::spec::state_copy_batch_on();
2799 let defer_rb = crate::spec::dspark_defer_readback_on() && !vt_policy.is_confidence();
2803 let (embd_qt, embd_rb) = self.embd.qt_and_row_bytes(n_embd);
2804 let embd_gpu = if !defer_rb || crate::spec::spec_host_embd() {
2805 None
2806 } else {
2807 Some(
2808 self.embd_gpu
2809 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload")),
2810 )
2811 };
2812 let mut vg_guard = self.dspark_vgraphs.lock().unwrap();
2819 if vg_guard.is_none() && embd_gpu.is_some() && crate::spec::dspark_verify_graph_on() {
2820 *vg_guard = crate::spec::DsparkVerifyGraphs::new(e, &cache, vt_cap, n_embd)?;
2821 }
2822 let vgraphs: &mut Option<crate::spec::DsparkVerifyGraphs> = &mut vg_guard;
2823 let (mut ns_draft, mut ns_snap, mut ns_verify, mut ns_roll, mut ns_ingest) =
2825 (0u64, 0u64, 0u64, 0u64, 0u64);
2826 let mut rounds = 0usize;
2827 let stats = std::env::var("MEMRA_SPEC_STATS").as_deref() == Ok("1");
2828 let clock = |on: bool, e: &Engine| -> std::time::Instant {
2829 if on {
2830 let _ = e.stream().synchronize();
2831 }
2832 std::time::Instant::now()
2833 };
2834 'outer: while out.len() < max_new {
2835 rounds += 1;
2836 let start = cache.pos; let t0 = clock(stats, e);
2839 e.set_verify_exact(true);
2840 let mut block: Vec<u32> = vec![c.mask_token_id; b];
2841 block[0] = last;
2842 let noise = e.htod(&self.embd.gather(n_embd, &block))?;
2843 let pos_block: Vec<i32> = ((start as i32)..(start + b) as i32).collect();
2844 let dh = draft.forward_round(e, &mut dkv, &noise, &pos_block)?;
2845 let mut rows = e.uninit(nd * n_embd)?;
2849 {
2850 let dv = e.view(&dh, b * n_embd);
2851 let src = dv.slice(r0 * n_embd..(r0 + nd) * n_embd);
2852 e.copy_view_into(&mut rows, 0, &src, nd * n_embd)?;
2853 }
2854 let mut dl = e.matmul(&self.output, &rows, nd)?;
2855 let want_conf_emb = vt_policy.is_confidence()
2868 && draft.confidence.as_ref().is_some_and(|ch| ch.with_markov);
2869 let mut conf_emb: Option<CudaSlice<f32>> = match (&draft.markov, want_conf_emb) {
2870 (Some(mk), true) => Some(e.uninit(nd * mk.rank)?),
2871 (None, true) => unreachable!(
2872 "with_markov confidence head without a markov table — the loader forbids it"
2873 ),
2874 _ => None,
2875 };
2876 let mut cand: Vec<u32> = Vec::with_capacity(nd + 1);
2877 let mut prop: Option<DsparkDraftSample> = None;
2878 let mut chain_dev: Option<CudaSlice<u32>> = None;
2879 if let Some(sp) = sp_on {
2880 let (tail, ds) = draft.dspark_propose_sampled(
2881 e,
2882 &mut dl,
2883 &rows,
2884 nd,
2885 n_vocab,
2886 last,
2887 sp,
2888 &mut sctr,
2889 &mut uctr,
2890 conf_emb.as_mut(),
2891 )?;
2892 e.set_verify_exact(false);
2893 cand.push(last);
2894 cand.extend_from_slice(&tail);
2895 prop = Some(ds);
2896 } else if draft.dflash2.is_some() {
2897 let path = draft.dflash2_propose_greedy(e, &dl, &rows, nd, n_vocab, last)?;
2898 e.set_verify_exact(false);
2899 cand.push(last);
2900 cand.extend_from_slice(&path);
2901 } else {
2902 let markov_on = std::env::var("MEMRA_DFLASH_MARKOV").as_deref() != Ok("0");
2903 let mut chain_d = e.stream().alloc_zeros::<u32>(nd + 1)?;
2904 if let (Some(mk), true) = (&draft.markov, markov_on) {
2905 e.set_u32_one(&mut chain_d, last)?;
2906 for k in 0..nd {
2907 let mut f = e.uninit(mk.rank)?;
2908 e.gather_row_bf16(&mk.w1_bf16, &chain_d, k, &mut f, mk.rank)?;
2909 if let Some(ce) = conf_emb.as_mut() {
2910 let fv = e.view(&f, mk.rank);
2911 e.copy_view_into(ce, k * mk.rank, &fv, mk.rank)?;
2912 }
2913 let bias = e.matmul(&mk.w2, &f, 1)?;
2914 e.add_row_inplace(&mut dl, &bias, n_vocab, k * n_vocab)?;
2915 e.argmax_token_device_col(&dl, k, n_vocab, &mut chain_d, k + 1)?;
2916 }
2917 } else {
2918 if want_conf_emb {
2919 e.set_u32_one(&mut chain_d, last)?;
2921 }
2922 for i in 0..nd {
2923 if let (Some(ce), Some(mk)) = (conf_emb.as_mut(), &draft.markov) {
2924 let mut f = e.uninit(mk.rank)?;
2925 e.gather_row_bf16(&mk.w1_bf16, &chain_d, i, &mut f, mk.rank)?;
2926 let fv = e.view(&f, mk.rank);
2927 e.copy_view_into(ce, i * mk.rank, &fv, mk.rank)?;
2928 }
2929 e.argmax_token_device_col(&dl, i, n_vocab, &mut chain_d, i + 1)?;
2930 }
2931 }
2932 e.set_verify_exact(false);
2933 chain_dev = Some(chain_d);
2934 }
2935 let ckpt_on = std::env::var("MEMRA_DSPARK_CKPT").as_deref() != Ok("0");
2942 let ckpt_gate = std::env::var("MEMRA_DSPARK_CKPT_GATE").as_deref() == Ok("1");
2943 if sp_on.is_some() && ckpt_gate {
2946 return Err(
2947 "MEMRA_DSPARK_CKPT_GATE compares verify argmaxes across a replay \
2948 — a greedy-exactness instrument; unset it for T>0 dspark rounds"
2949 .into(),
2950 );
2951 }
2952 let deferred = chain_dev.is_some() && embd_gpu.is_some() && (ckpt_on || ckpt_gate);
2958 if vt_policy.is_confidence() {
2960 let ch = draft.confidence.as_ref().expect("asserted at loop entry");
2961 let (rows_h, emb_h) = match conf_emb.as_ref() {
2962 Some(ce) => {
2963 let (a, b2) = e.dtoh_pair(&rows, ce)?;
2964 (a, Some(b2))
2965 }
2966 None => (e.dtoh(&rows)?, None),
2967 };
2968 let rank = draft.markov.as_ref().map(|m| m.rank).unwrap_or(0);
2969 let mut raws = Vec::with_capacity(nd);
2970 for k in 0..nd {
2971 let hrow = &rows_h[k * n_embd..(k + 1) * n_embd];
2972 let emb = emb_h.as_ref().map(|eh| &eh[k * rank..(k + 1) * rank]);
2973 raws.push(ch.raw_score(hrow, emb));
2974 }
2975 vt = vt_policy
2976 .size_window(&raws, vt_cap)
2977 .expect("confidence policies always size the window");
2978 }
2979 if let Some(chain_d) = chain_dev.as_ref() {
2986 if !deferred {
2987 let chain = e.dtoh_u32(chain_d)?;
2988 cand.push(last);
2989 cand.extend_from_slice(&chain[1..]);
2990 }
2991 }
2992 ns_draft += clock(stats, e).duration_since(t0).as_nanos() as u64;
2993
2994 let t1 = std::time::Instant::now();
2996 let mut snap_legacy: Option<crate::cache::CacheSnapshot> = None;
2999 if !snapb_off && snapb.is_none() {
3000 snapb = DsparkSnapBatch::new(e, &cache)?;
3001 snapb_off = snapb.is_none();
3002 } else if let Some(sb) = snapb.as_mut() {
3003 sb.refresh(e, &cache)?;
3004 }
3005 let snap: &crate::cache::CacheSnapshot = match snapb.as_ref() {
3006 Some(sb) => &sb.snap,
3007 None => {
3008 snap_legacy = Some(cache.snapshot(e)?);
3009 snap_legacy.as_ref().unwrap()
3010 }
3011 };
3012 let _ = &snap_legacy;
3013 ns_snap += clock(stats, e).duration_since(t1).as_nanos() as u64;
3014 let t2 = std::time::Instant::now();
3015 let tap_buf = match vgraphs.as_mut().and_then(|g| g.tap_bufs.remove(&vt)) {
3018 Some(buf) => buf,
3019 None => e.uninit(vt * n_taps * n_embd)?,
3020 };
3021 cache.dflash_taps = Some(DflashTapSink {
3022 layer_ids: c.target_layer_ids.clone(),
3023 buf: tap_buf,
3024 hidden: n_embd,
3025 t: vt,
3026 base: 0,
3027 });
3028 let verify_res = (|cache: &mut crate::cache::Cache,
3036 cand: &mut Vec<u32>,
3037 vgraphs: &mut Option<crate::spec::DsparkVerifyGraphs>|
3038 -> Result<
3039 (
3040 Vec<u32>,
3041 Option<CudaSlice<f32>>,
3042 Option<crate::spec::DsparkVerifyCkpt>,
3043 ),
3044 Box<dyn std::error::Error>,
3045 > {
3046 if sp_on.is_some() {
3047 if ckpt_on {
3051 let (tl, vck) =
3052 self.dspark_verify_t_logits_ckpt(e, &cand[..vt], start, cache)?;
3053 Ok((Vec::new(), Some(tl), Some(vck)))
3054 } else {
3055 Ok((
3056 Vec::new(),
3057 Some(self.dspark_verify_t_logits(e, &cand[..vt], start, cache)?),
3058 None,
3059 ))
3060 }
3061 } else if deferred {
3062 let chain_d = chain_dev.as_ref().expect("deferred implies greedy chain");
3067 let g = embd_gpu.expect("deferred implies resident embed");
3068 let (am_d, vck) = self.dspark_verify_t_am_ckpt_dev(
3069 e,
3070 chain_d,
3071 vt,
3072 start,
3073 cache,
3074 (g, embd_qt, embd_rb),
3075 vgraphs.as_mut(),
3076 )?;
3077 let ch = e.stream().clone_dtoh(chain_d)?;
3078 let am = e.stream().clone_dtoh(&am_d)?;
3079 e.stream().synchronize()?;
3080 cand.push(last);
3081 cand.extend_from_slice(&ch[1..]);
3082 Ok((am, None, Some(vck)))
3083 } else if ckpt_on || ckpt_gate {
3084 let (vam, vck) = self.dspark_verify_t_am_ckpt(e, &cand[..vt], start, cache)?;
3085 Ok((vam, None, Some(vck)))
3086 } else {
3087 Ok((
3088 self.dspark_verify_t_am(e, &cand[..vt], start, cache)?,
3089 None,
3090 None,
3091 ))
3092 }
3093 })(&mut cache, &mut cand, vgraphs);
3094 let (vam, tl, vck) = match verify_res {
3095 Ok(v) => v,
3096 Err(err) => {
3097 if let (Some(g), Some(taps)) = (vgraphs.as_mut(), cache.dflash_taps.take()) {
3098 g.tap_bufs.insert(vt, taps.buf);
3099 }
3100 return Err(err);
3101 }
3102 };
3103 let taps = cache.dflash_taps.take().unwrap();
3104 let tap_local: Option<CudaSlice<f32>> = match vgraphs.as_mut() {
3109 Some(g) => {
3110 g.tap_bufs.insert(vt, taps.buf);
3111 None
3112 }
3113 None => Some(taps.buf),
3114 };
3115 let tap_ref: &CudaSlice<f32> = match &tap_local {
3116 Some(b) => b,
3117 None => &vgraphs.as_ref().expect("ctx present above").tap_bufs[&vt],
3118 };
3119 ns_verify += clock(stats, e).duration_since(t2).as_nanos() as u64;
3120
3121 let (m, next) = match (sp_on, tl.as_ref()) {
3123 (Some(sp), Some(tl)) => dspark_accept_sampled(
3124 e,
3125 tl,
3126 &cand,
3127 vt,
3128 n_vocab,
3129 &dl,
3130 prop.as_ref()
3131 .expect("sampled round without a proposal record"),
3132 sp,
3133 &mut sctr,
3134 &mut uctr,
3135 )?,
3136 _ => {
3137 let m = dspark_accept_prefix(&cand, &vam, vt);
3138 (m, vam[m])
3139 }
3140 };
3141 attempted += vt - 1;
3142 accepted += m;
3143 out.push(last);
3144 if eos.contains(&last) {
3145 break 'outer;
3146 }
3147 for &dt in &cand[1..=m] {
3148 if out.len() >= max_new {
3153 break 'outer;
3154 }
3155 out.push(dt);
3156 if eos.contains(&dt) {
3157 break 'outer;
3158 }
3159 }
3160
3161 let keep = m + 1;
3163 let t3 = std::time::Instant::now();
3164 let slab_commit = vgraphs.as_ref().map(|g| g.round_slab).unwrap_or(false);
3167 if keep < vt {
3168 if ckpt_gate {
3169 if slab_commit {
3173 self.dspark_commit_prefix_slab(
3174 e,
3175 &mut cache,
3176 snap,
3177 vgraphs.as_ref().expect("slab_commit implies ctx"),
3178 keep,
3179 )?;
3180 } else {
3181 let vck = vck.as_ref().expect("gate arm always fills the ckpt");
3182 self.dspark_commit_prefix(e, &mut cache, snap, vck, keep)?;
3183 }
3184 let capture = |cache: &Cache| -> Result<
3187 (usize, Vec<Option<usize>>, Vec<(Vec<f32>, Vec<f32>)>),
3188 Box<dyn std::error::Error>,
3189 > {
3190 let mut lens = Vec::new();
3191 let mut states = Vec::new();
3192 for il in 0..cache.kv.len() {
3193 lens.push(cache.kv[il].as_ref().map(|k| k.len));
3194 if let Some(rl) = &cache.recur[il] {
3195 states.push((e.dtoh(&rl.conv_state)?, e.dtoh(&rl.ssm_state)?));
3196 }
3197 }
3198 Ok((cache.pos, lens, states))
3199 };
3200 let (p1, l1, st1) = capture(&cache)?;
3201 crate::pp::restore_cache_checkpoint(e, &self.cfg, None, &mut cache, snap)?;
3202 let ram = self.dspark_verify_t_am(e, &cand[..keep], start, &mut cache)?;
3203 assert_eq!(
3204 &ram[..],
3205 &vam[..keep],
3206 "prefix replay must reproduce the verify argmaxes"
3207 );
3208 let (p2, l2, st2) = capture(&cache)?;
3209 assert_eq!(p1, p2, "ckpt-gate: pos mismatch");
3210 assert_eq!(l1, l2, "ckpt-gate: kv_len mismatch");
3211 for (il, ((c1, s1v), (c2, s2v))) in st1.iter().zip(&st2).enumerate() {
3212 let bits = |a: &[f32], b: &[f32]| {
3213 a.iter().zip(b).all(|(x, y)| x.to_bits() == y.to_bits())
3214 };
3215 assert!(
3216 bits(c1, c2),
3217 "ckpt-gate: linear layer {il} conv state differs"
3218 );
3219 assert!(
3220 bits(s1v, s2v),
3221 "ckpt-gate: linear layer {il} ssm state differs"
3222 );
3223 }
3224 } else if slab_commit {
3225 self.dspark_commit_prefix_slab(
3227 e,
3228 &mut cache,
3229 snap,
3230 vgraphs.as_ref().expect("slab_commit implies ctx"),
3231 keep,
3232 )?;
3233 } else if let Some(vck) = vck.as_ref() {
3234 self.dspark_commit_prefix(e, &mut cache, snap, vck, keep)?;
3236 } else {
3237 crate::pp::restore_cache_checkpoint(e, &self.cfg, None, &mut cache, snap)?;
3239 debug_assert_eq!(cache.pos, start, "rollback landed off the round start");
3240 let ram = self.dspark_verify_t_am(e, &cand[..keep], start, &mut cache)?;
3241 if sp_on.is_none() {
3242 debug_assert_eq!(
3245 &ram[..],
3246 &vam[..keep],
3247 "prefix replay must reproduce the verify argmaxes"
3248 );
3249 }
3250 }
3251 }
3252 ns_roll += clock(stats, e).duration_since(t3).as_nanos() as u64;
3253
3254 let t4 = std::time::Instant::now();
3256 {
3257 let tv = e.view(tap_ref, vt * n_taps * n_embd);
3258 let keep_view = tv.slice(0..keep * n_taps * n_embd);
3259 let mut kept = e.uninit(keep * n_taps * n_embd)?;
3260 e.copy_view_into(&mut kept, 0, &keep_view, keep * n_taps * n_embd)?;
3261 let f = draft.ctx_features(e, &kept, keep)?;
3262 let pos_k: Vec<i32> = ((ctx_len as i32)..(ctx_len + keep) as i32).collect();
3263 draft.ingest_ctx(e, &mut dkv, &f, &pos_k, keep)?;
3264 ctx_len += keep;
3265 }
3266 ns_ingest += clock(stats, e).duration_since(t4).as_nanos() as u64;
3267 last = next;
3268 if !vt_policy.is_confidence() && adapt {
3271 vt = (m + 2).clamp(3, vt_cap);
3272 }
3273 }
3274 if stats {
3275 let ms = |n: u64| n as f64 / 1e6;
3276 eprintln!(
3277 "[dspark-q38] acceptance {accepted}/{attempted} = {:.3} rounds={rounds} \
3278 draft={:.1}ms snap={:.1}ms verify={:.1}ms rollback+replay={:.1}ms ingest={:.1}ms",
3279 accepted as f64 / attempted.max(1) as f64,
3280 ms(ns_draft),
3281 ms(ns_snap),
3282 ms(ns_verify),
3283 ms(ns_roll),
3284 ms(ns_ingest)
3285 );
3286 }
3287 Ok(out)
3288 }
3289}
3290
3291pub struct DsparkSpecSession {
3301 pub cache: crate::cache::Cache,
3302 dkv: DflashKv,
3303 last: u32,
3304 ctx_len: usize,
3305 vt: usize,
3306 pub rounds: usize,
3307 max_ctx: usize,
3308 done: bool,
3309 snapb: Option<DsparkSnapBatch>,
3313 snapb_off: bool,
3314 sampling: Option<crate::spec::SpecSampling>,
3318 sctr: u32,
3322 uctr: u32,
3323}
3324
3325impl DsparkSpecSession {
3326 pub fn cache_max_ctx(&self) -> usize {
3327 self.max_ctx
3328 }
3329 pub fn finished(&self) -> bool {
3330 self.done
3331 }
3332 pub fn pos(&self) -> usize {
3333 self.cache.pos
3334 }
3335}
3336
3337impl crate::hybrid::HybridModel {
3338 pub fn dspark_spec_session_new(
3342 &self,
3343 e: &Engine,
3344 draft: &DflashDraft,
3345 prompt: &[u32],
3346 ctx_cap: usize,
3347 sampling: Option<crate::spec::SpecSampling>,
3348 ) -> Result<DsparkSpecSession, Box<dyn std::error::Error>> {
3349 use crate::cache::{Cache, DflashTapSink};
3350 assert!(
3351 self.cfg.gemma4.is_none(),
3352 "gemma4 targets use the assistant-drafter route; dspark is the qwen-hybrid arm"
3353 );
3354 if let Some(sp) = sampling.as_ref().filter(|s| s.temp > 0.0) {
3358 if sp.penalty_repeat != 1.0 || sp.penalty_freq != 0.0 || sp.penalty_present != 0.0 {
3359 return Err(
3360 "dspark sampled admission excludes penalties (admission gate \
3361 keeps penalized requests on the plain path)"
3362 .into(),
3363 );
3364 }
3365 }
3366 let n_embd = self.cfg.n_embd as usize;
3367 let c = &draft.cfg;
3368 assert_eq!(n_embd, c.hidden, "draft hidden must match target n_embd");
3369 let b = c.block_size;
3370 let n_taps = c.target_layer_ids.len();
3371 let max_ctx = if draft.dflash2.is_some() {
3377 ctx_cap
3378 } else {
3379 ctx_cap.min(c.sliding_window)
3380 };
3381 if prompt.len() + b + 8 > max_ctx {
3382 return Err(format!(
3383 "dspark session needs {} ctx (prompt {} + block {b} + 8), cap {max_ctx}",
3384 prompt.len() + b + 8,
3385 prompt.len()
3386 )
3387 .into());
3388 }
3389 let mut cache = Cache::new(e, &self.cfg, max_ctx)?;
3390 let tp = prompt.len();
3391 cache.dflash_taps = Some(DflashTapSink {
3392 layer_ids: c.target_layer_ids.clone(),
3393 buf: e.uninit(tp * n_taps * n_embd)?,
3394 hidden: n_embd,
3395 t: tp,
3396 base: 0,
3397 });
3398 let (logits, _h_seed, _hiddens) = self.prime_cache(e, prompt, &mut cache, 0)?;
3399 let mut sctr0 = 0u32;
3402 let last = match sampling.as_ref().filter(|s| s.temp > 0.0) {
3403 Some(sp) => {
3404 crate::spec::sample_boundary_token(e, &logits, sp, &[], &mut sctr0, "dspark-prime")?
3405 }
3406 None => crate::forward::argmax(&logits) as u32,
3407 };
3408 let mut dkv = DflashKv::new(e, &draft.cfg, max_ctx)?;
3409 {
3410 let taps = cache.dflash_taps.take().unwrap();
3411 let n_taps_h = n_taps * n_embd;
3412 let mut r0 = 0usize;
3413 while r0 < tp {
3414 let t_c = (tp - r0).min(256);
3415 let tv = e.view(&taps.buf, tp * n_taps_h);
3416 let win = tv.slice(r0 * n_taps_h..(r0 + t_c) * n_taps_h);
3417 let mut chunk = e.uninit(t_c * n_taps_h)?;
3418 e.copy_view_into(&mut chunk, 0, &win, t_c * n_taps_h)?;
3419 let f = draft.ctx_features(e, &chunk, t_c)?;
3420 let pos_c: Vec<i32> = ((r0 as i32)..(r0 + t_c) as i32).collect();
3421 draft.ingest_ctx(e, &mut dkv, &f, &pos_c, t_c)?;
3422 r0 += t_c;
3423 }
3424 }
3425 e.stream().synchronize()?;
3426 let nd = DsparkHarvest::for_draft(draft).n_drafts(b);
3430 let vt_cap: usize = std::env::var("MEMRA_DFLASH_VERIFY_T")
3431 .ok()
3432 .and_then(|v| v.parse().ok())
3433 .unwrap_or(nd + 1)
3434 .clamp(2, nd + 1);
3435 Ok(DsparkSpecSession {
3436 cache,
3437 dkv,
3438 last,
3439 ctx_len: tp,
3440 vt: vt_cap,
3441 rounds: 0,
3442 max_ctx,
3443 done: false,
3444 snapb: None,
3445 snapb_off: !crate::spec::state_copy_batch_on(),
3446 sampling,
3447 sctr: sctr0,
3448 uctr: 0,
3449 })
3450 }
3451
3452 pub fn dspark_spec_session_burst(
3457 &self,
3458 e: &Engine,
3459 draft: &DflashDraft,
3460 sess: &mut DsparkSpecSession,
3461 burst_target: usize,
3462 eos: &[u32],
3463 ) -> Result<(Vec<u32>, usize, usize), Box<dyn std::error::Error>> {
3464 use crate::cache::DflashTapSink;
3465 let n_embd = self.cfg.n_embd as usize;
3466 let c = &draft.cfg;
3467 let b = c.block_size;
3468 let n_taps = c.target_layer_ids.len();
3469 let n_vocab = self.output.out_features();
3470 let harvest = DsparkHarvest::for_draft(draft);
3474 let nd = harvest.n_drafts(b);
3475 let r0 = harvest.first_row();
3476 let vt_cap: usize = std::env::var("MEMRA_DFLASH_VERIFY_T")
3477 .ok()
3478 .and_then(|v| v.parse().ok())
3479 .unwrap_or(nd + 1)
3480 .clamp(2, nd + 1);
3481 let adapt = std::env::var("MEMRA_DFLASH_ADAPT").as_deref() != Ok("0");
3482 let vt_policy = DsparkVtPolicy::resolve(draft.confidence.is_some());
3487 if vt_policy.is_confidence() {
3488 assert!(
3489 draft.confidence.is_some(),
3490 "MEMRA_DSPARK_VT={vt_policy:?} needs a checkpoint with an accept-rate \
3491 head (confidence_head.* absent in this export)"
3492 );
3493 }
3494 let sp_on: Option<crate::spec::SpecSampling> = sess.sampling.filter(|s| s.temp > 0.0);
3497 let mut out: Vec<u32> = Vec::with_capacity(burst_target + b);
3498 let mut drafted = 0usize;
3499 let mut accepted_n = 0usize;
3500 let defer_rb = crate::spec::dspark_defer_readback_on() && !vt_policy.is_confidence();
3503 let (embd_qt, embd_rb) = self.embd.qt_and_row_bytes(n_embd);
3504 let embd_gpu = if !defer_rb || crate::spec::spec_host_embd() {
3505 None
3506 } else {
3507 Some(
3508 self.embd_gpu
3509 .get_or_init(|| e.upload_u8(&self.embd.raw).expect("embed table upload")),
3510 )
3511 };
3512 'outer: while out.len() < burst_target && !sess.done {
3513 let start = sess.cache.pos;
3514 if start + nd + 1 > sess.max_ctx {
3515 sess.done = true;
3516 break;
3517 }
3518 sess.rounds += 1;
3519 let mut vt = sess.vt;
3520 e.set_verify_exact(true);
3522 let mut block: Vec<u32> = vec![c.mask_token_id; b];
3523 block[0] = sess.last;
3524 let noise = e.htod(&self.embd.gather(n_embd, &block))?;
3525 let pos_block: Vec<i32> = ((start as i32)..(start + b) as i32).collect();
3526 let dh = draft.forward_round(e, &mut sess.dkv, &noise, &pos_block)?;
3527 let mut rows = e.uninit(nd * n_embd)?;
3529 {
3530 let dv = e.view(&dh, b * n_embd);
3531 let src = dv.slice(r0 * n_embd..(r0 + nd) * n_embd);
3532 e.copy_view_into(&mut rows, 0, &src, nd * n_embd)?;
3533 }
3534 let mut dl = e.matmul(&self.output, &rows, nd)?;
3535 let want_conf_emb = vt_policy.is_confidence()
3541 && draft.confidence.as_ref().is_some_and(|ch| ch.with_markov);
3542 let mut conf_emb: Option<CudaSlice<f32>> = match (&draft.markov, want_conf_emb) {
3543 (Some(mk), true) => Some(e.uninit(nd * mk.rank)?),
3544 (None, true) => unreachable!(
3545 "with_markov confidence head without a markov table — the loader forbids it"
3546 ),
3547 _ => None,
3548 };
3549 let mut cand: Vec<u32> = Vec::with_capacity(nd + 1);
3550 let mut prop: Option<DsparkDraftSample> = None;
3551 let mut chain_dev: Option<CudaSlice<u32>> = None;
3552 let ckpt_on = std::env::var("MEMRA_DSPARK_CKPT").as_deref() != Ok("0");
3555 let mut deferred = false;
3556 if let Some(sp) = sp_on.as_ref() {
3557 let (tail, ds) = draft.dspark_propose_sampled(
3559 e,
3560 &mut dl,
3561 &rows,
3562 nd,
3563 n_vocab,
3564 sess.last,
3565 sp,
3566 &mut sess.sctr,
3567 &mut sess.uctr,
3568 conf_emb.as_mut(),
3569 )?;
3570 e.set_verify_exact(false);
3571 cand.push(sess.last);
3572 cand.extend_from_slice(&tail);
3573 prop = Some(ds);
3574 } else if draft.dflash2.is_some() {
3575 let path = draft.dflash2_propose_greedy(e, &dl, &rows, nd, n_vocab, sess.last)?;
3578 e.set_verify_exact(false);
3579 cand.push(sess.last);
3580 cand.extend_from_slice(&path);
3581 } else {
3582 let markov_on = std::env::var("MEMRA_DFLASH_MARKOV").as_deref() != Ok("0");
3583 let mut chain_d = e.stream().alloc_zeros::<u32>(nd + 1)?;
3584 if let (Some(mk), true) = (&draft.markov, markov_on) {
3585 e.set_u32_one(&mut chain_d, sess.last)?;
3586 for k in 0..nd {
3587 let mut f = e.uninit(mk.rank)?;
3588 e.gather_row_bf16(&mk.w1_bf16, &chain_d, k, &mut f, mk.rank)?;
3589 if let Some(ce) = conf_emb.as_mut() {
3590 let fv = e.view(&f, mk.rank);
3591 e.copy_view_into(ce, k * mk.rank, &fv, mk.rank)?;
3592 }
3593 let bias = e.matmul(&mk.w2, &f, 1)?;
3594 e.add_row_inplace(&mut dl, &bias, n_vocab, k * n_vocab)?;
3595 e.argmax_token_device_col(&dl, k, n_vocab, &mut chain_d, k + 1)?;
3596 }
3597 } else {
3598 if want_conf_emb {
3599 e.set_u32_one(&mut chain_d, sess.last)?;
3601 }
3602 for i in 0..nd {
3603 if let (Some(ce), Some(mk)) = (conf_emb.as_mut(), &draft.markov) {
3604 let mut f = e.uninit(mk.rank)?;
3605 e.gather_row_bf16(&mk.w1_bf16, &chain_d, i, &mut f, mk.rank)?;
3606 let fv = e.view(&f, mk.rank);
3607 e.copy_view_into(ce, i * mk.rank, &fv, mk.rank)?;
3608 }
3609 e.argmax_token_device_col(&dl, i, n_vocab, &mut chain_d, i + 1)?;
3610 }
3611 }
3612 e.set_verify_exact(false);
3613 deferred = embd_gpu.is_some() && ckpt_on;
3614 chain_dev = Some(chain_d);
3615 }
3616 if vt_policy.is_confidence() {
3618 let ch = draft.confidence.as_ref().expect("asserted at burst entry");
3619 let (rows_h, emb_h) = match conf_emb.as_ref() {
3620 Some(ce) => {
3621 let (a, b2) = e.dtoh_pair(&rows, ce)?;
3622 (a, Some(b2))
3623 }
3624 None => (e.dtoh(&rows)?, None),
3625 };
3626 let rank = draft.markov.as_ref().map(|m| m.rank).unwrap_or(0);
3627 let mut raws = Vec::with_capacity(nd);
3628 for k in 0..nd {
3629 let hrow = &rows_h[k * n_embd..(k + 1) * n_embd];
3630 let emb = emb_h.as_ref().map(|eh| &eh[k * rank..(k + 1) * rank]);
3631 raws.push(ch.raw_score(hrow, emb));
3632 }
3633 vt = vt_policy
3634 .size_window(&raws, vt_cap)
3635 .expect("confidence policies always size the window");
3636 }
3637 if let Some(chain_d) = chain_dev.as_ref() {
3641 if !deferred {
3642 let chain = e.dtoh_u32(chain_d)?;
3643 cand.push(sess.last);
3644 cand.extend_from_slice(&chain[1..]);
3645 }
3646 }
3647
3648 let mut snap_legacy: Option<crate::cache::CacheSnapshot> = None;
3652 if !sess.snapb_off && sess.snapb.is_none() {
3653 sess.snapb = DsparkSnapBatch::new(e, &sess.cache)?;
3654 sess.snapb_off = sess.snapb.is_none();
3655 } else if let Some(sb) = sess.snapb.as_mut() {
3656 sb.refresh(e, &sess.cache)?;
3657 }
3658 let snap: &crate::cache::CacheSnapshot = match sess.snapb.as_ref() {
3659 Some(sb) => &sb.snap,
3660 None => {
3661 snap_legacy = Some(sess.cache.snapshot(e)?);
3662 snap_legacy.as_ref().unwrap()
3663 }
3664 };
3665 let _ = &snap_legacy;
3666 sess.cache.dflash_taps = Some(DflashTapSink {
3667 layer_ids: c.target_layer_ids.clone(),
3668 buf: e.uninit(vt * n_taps * n_embd)?,
3669 hidden: n_embd,
3670 t: vt,
3671 base: 0,
3672 });
3673 let (vam, tl, vck) = if sp_on.is_some() {
3674 if ckpt_on {
3676 let (tl, vck) =
3677 self.dspark_verify_t_logits_ckpt(e, &cand[..vt], start, &mut sess.cache)?;
3678 (Vec::new(), Some(tl), Some(vck))
3679 } else {
3680 (
3681 Vec::new(),
3682 Some(self.dspark_verify_t_logits(
3683 e,
3684 &cand[..vt],
3685 start,
3686 &mut sess.cache,
3687 )?),
3688 None,
3689 )
3690 }
3691 } else if deferred {
3692 let chain_d = chain_dev.as_ref().expect("deferred implies greedy chain");
3694 let g = embd_gpu.expect("deferred implies resident embed");
3695 let (am_d, vck) = self.dspark_verify_t_am_ckpt_dev(
3698 e,
3699 chain_d,
3700 vt,
3701 start,
3702 &mut sess.cache,
3703 (g, embd_qt, embd_rb),
3704 None,
3705 )?;
3706 let ch = e.stream().clone_dtoh(chain_d)?;
3707 let am = e.stream().clone_dtoh(&am_d)?;
3708 e.stream().synchronize()?;
3709 cand.push(sess.last);
3710 cand.extend_from_slice(&ch[1..]);
3711 (am, None, Some(vck))
3712 } else if ckpt_on {
3713 let (vam, vck) =
3714 self.dspark_verify_t_am_ckpt(e, &cand[..vt], start, &mut sess.cache)?;
3715 (vam, None, Some(vck))
3716 } else {
3717 (
3718 self.dspark_verify_t_am(e, &cand[..vt], start, &mut sess.cache)?,
3719 None,
3720 None,
3721 )
3722 };
3723 let taps = sess.cache.dflash_taps.take().unwrap();
3724
3725 let (m, next) = match (sp_on.as_ref(), tl.as_ref()) {
3727 (Some(sp), Some(tl)) => dspark_accept_sampled(
3728 e,
3729 tl,
3730 &cand,
3731 vt,
3732 n_vocab,
3733 &dl,
3734 prop.as_ref()
3735 .expect("sampled round without a proposal record"),
3736 sp,
3737 &mut sess.sctr,
3738 &mut sess.uctr,
3739 )?,
3740 _ => {
3741 let m = dspark_accept_prefix(&cand, &vam, vt);
3742 (m, vam[m])
3743 }
3744 };
3745 drafted += vt - 1;
3746 accepted_n += m;
3747 out.push(sess.last);
3748 if eos.contains(&sess.last) {
3749 sess.done = true;
3750 break 'outer;
3751 }
3752 for &dt in &cand[1..=m] {
3753 out.push(dt);
3754 if eos.contains(&dt) {
3755 sess.done = true;
3756 break 'outer;
3757 }
3758 }
3759
3760 let keep = m + 1;
3762 if keep < vt {
3763 if let Some(vck) = vck.as_ref() {
3764 self.dspark_commit_prefix(e, &mut sess.cache, snap, vck, keep)?;
3765 } else {
3766 crate::pp::restore_cache_checkpoint(e, &self.cfg, None, &mut sess.cache, snap)?;
3767 debug_assert_eq!(sess.cache.pos, start, "rollback landed off the round start");
3768 let ram = self.dspark_verify_t_am(e, &cand[..keep], start, &mut sess.cache)?;
3769 if sp_on.is_none() {
3770 debug_assert_eq!(
3772 &ram[..],
3773 &vam[..keep],
3774 "prefix replay must reproduce the verify argmaxes"
3775 );
3776 }
3777 }
3778 }
3779
3780 {
3782 let tv = e.view(&taps.buf, vt * n_taps * n_embd);
3783 let keep_view = tv.slice(0..keep * n_taps * n_embd);
3784 let mut kept = e.uninit(keep * n_taps * n_embd)?;
3785 e.copy_view_into(&mut kept, 0, &keep_view, keep * n_taps * n_embd)?;
3786 let f = draft.ctx_features(e, &kept, keep)?;
3787 let pos_k: Vec<i32> =
3788 ((sess.ctx_len as i32)..(sess.ctx_len + keep) as i32).collect();
3789 draft.ingest_ctx(e, &mut sess.dkv, &f, &pos_k, keep)?;
3790 sess.ctx_len += keep;
3791 }
3792 sess.last = next;
3793 if vt_policy.is_confidence() {
3797 sess.vt = vt;
3798 } else if adapt {
3799 sess.vt = (m + 2).clamp(3, vt_cap);
3800 }
3801 }
3802 Ok((out, drafted, accepted_n))
3803 }
3804}
3805
3806#[cfg(test)]
3814mod dflash2_tests {
3815 use super::{DsparkHarvest, dflash2_walk_greedy, dflash2_walk_sampled, rejection_accept_len};
3816
3817 fn bf16(vals: &[f32]) -> Vec<u8> {
3819 vals.iter()
3820 .flat_map(|v| ((v.to_bits() >> 16) as u16).to_le_bytes())
3821 .collect()
3822 }
3823
3824 const V: usize = 8; const R: usize = 2; const K: usize = 2; fn books() -> (Vec<u8>, Vec<u8>) {
3831 let mut pred = vec![0f32; V * R];
3832 pred[0] = 1.0; pred[1 * R + 1] = 1.0; pred[2 * R] = 1.0; let mut succ = vec![0f32; V * R];
3836 succ[1 * R] = 2.0; succ[2 * R + 1] = 5.0; succ[3 * R + 1] = 3.0; succ[4 * R] = 10.0; (bf16(&pred), bf16(&succ))
3841 }
3842
3843 #[test]
3844 fn selector_walk_is_a_chain_not_per_slot_argmax() {
3845 let (pred, succ) = books();
3846 let cand: Vec<u32> = vec![1, 2, 3, 4];
3848 let hproj = vec![1.0f32; 2 * R];
3849 let path = dflash2_walk_greedy(&pred, &succ, V, R, K, &[0.0; 4], &cand, &hproj, 0, 2);
3856 assert_eq!(
3857 path,
3858 vec![1, 3],
3859 "walk must seed slot p from slot p-1's CHOSEN candidate \
3860 (z-lab model.py CandidateSelector.select)"
3861 );
3862 }
3863
3864 #[test]
3865 fn selector_walk_unary_term_participates() {
3866 let (pred, succ) = books();
3867 let cand: Vec<u32> = vec![1, 2, 3, 4];
3868 let hproj = vec![1.0f32; 2 * R];
3869 let path = dflash2_walk_greedy(
3872 &pred,
3873 &succ,
3874 V,
3875 R,
3876 K,
3877 &[0.0, 10.0, 0.0, 0.0],
3878 &cand,
3879 &hproj,
3880 0,
3881 2,
3882 );
3883 assert_eq!(
3884 path,
3885 vec![2, 4],
3886 "score = unary + bilinear (reference: `unary[:, position] + einsum(...)`); \
3887 dropping the unary term picks tok 1 here"
3888 );
3889 }
3890
3891 #[test]
3892 fn selector_walk_hidden_gate_participates() {
3893 let (pred, succ) = books();
3894 let cand: Vec<u32> = vec![1, 2, 3, 4];
3895 let hproj = vec![0.0f32, 1.0, 1.0, 1.0];
3899 let path = dflash2_walk_greedy(
3900 &pred,
3901 &succ,
3902 V,
3903 R,
3904 K,
3905 &[0.0, 1.0, 0.0, 0.0],
3906 &cand,
3907 &hproj,
3908 0,
3909 2,
3910 );
3911 assert_eq!(
3912 path,
3913 vec![2, 4],
3914 "the bilinear gate is pred_row .* HIDDEN_PROJECTION (reference: \
3915 `predecessor_codebook(predecessor) * hidden[:, position]`); ignoring \
3916 hproj leaves tok1's margin standing"
3917 );
3918 }
3919
3920 #[test]
3921 fn dflash2_harvest_is_census_keyed() {
3922 assert_eq!(
3925 DsparkHarvest::for_family_value(true, None, false),
3926 DsparkHarvest::Dflash
3927 );
3928 assert_eq!(
3929 DsparkHarvest::for_family_value(true, Some("dflash"), false),
3930 DsparkHarvest::Dflash
3931 );
3932 assert_eq!(
3935 DsparkHarvest::for_family_value(true, None, true),
3936 DsparkHarvest::Dflash
3937 );
3938 assert!(
3941 std::panic::catch_unwind(|| DsparkHarvest::for_family_value(
3942 true,
3943 Some("dspark"),
3944 false
3945 ))
3946 .is_err(),
3947 "MEMRA_DSPARK_HARVEST=dspark on a DFlash2 checkpoint must refuse"
3948 );
3949 assert_eq!(
3951 DsparkHarvest::for_family_value(false, Some("dspark"), false),
3952 DsparkHarvest::Dspark
3953 );
3954 assert_eq!(
3955 DsparkHarvest::for_family_value(false, None, false),
3956 DsparkHarvest::Dflash
3957 );
3958 assert_eq!(
3959 DsparkHarvest::for_family_value(false, None, true),
3960 DsparkHarvest::Dspark,
3961 "unset env on a DSPARK-strategy export must keep the ratified census flip"
3962 );
3963 }
3964
3965 #[test]
3973 fn sampled_walk_tiny_temp_matches_greedy() {
3974 let (pred, succ) = books();
3978 let cand: Vec<u32> = vec![1, 2, 3, 4];
3979 let hproj = vec![1.0f32; 2 * R];
3980 let greedy = dflash2_walk_greedy(&pred, &succ, V, R, K, &[0.0; 4], &cand, &hproj, 0, 2);
3981 let mut u = || 0.5f32;
3982 let (path, q_chosen, q_rows) = dflash2_walk_sampled(
3983 &pred, &succ, V, R, K, &[0.0; 4], &cand, &hproj, 0, 2, 1e-6, &mut u,
3984 );
3985 assert_eq!(
3986 path, greedy,
3987 "tiny-T sampled walk must equal the greedy chain"
3988 );
3989 assert_eq!(q_rows.len(), 2 * K);
3990 for (p, &q) in path.iter().zip(&q_chosen) {
3991 let _ = p;
3992 assert!(
3993 q > 0.999,
3994 "tiny-T chosen-candidate prob must be ~1, got {q}"
3995 );
3996 }
3997 }
3998
3999 #[test]
4000 fn sampled_walk_records_the_distribution_it_samples() {
4001 let (pred, succ) = books();
4005 let cand: Vec<u32> = vec![1, 2, 3, 4];
4006 let hproj = vec![1.0f32; 2 * R];
4007 let q1 = (1f64.exp() / (1f64.exp() + 1.0)) as f32;
4010 for (u0, want0) in [(q1 - 0.01, 1u32), (q1 + 0.01, 2u32)] {
4011 let mut seq = vec![u0, 0.0f32].into_iter();
4012 let mut u = move || seq.next().unwrap();
4013 let (path, q_chosen, q_rows) = dflash2_walk_sampled(
4014 &pred, &succ, V, R, K, &[0.0; 4], &cand, &hproj, 0, 2, 2.0, &mut u,
4015 );
4016 assert_eq!(
4017 path[0], want0,
4018 "CDF walk must place u={u0} in the right candidate bracket"
4019 );
4020 let row0: f32 = q_rows[..K].iter().sum();
4021 assert!(
4022 (row0 - 1.0).abs() < 1e-5,
4023 "slot-0 q must sum to 1, got {row0}"
4024 );
4025 let ci = cand[..K].iter().position(|&c| c == path[0]).unwrap();
4026 assert_eq!(
4027 q_chosen[0], q_rows[ci],
4028 "q_chosen must be the recorded row prob of the drawn candidate"
4029 );
4030 assert!(
4031 (q_rows[0] - q1).abs() < 1e-4,
4032 "slot-0 tok1 prob must be softmax(scores/T), got {} want {q1}",
4033 q_rows[0]
4034 );
4035 }
4036 }
4037
4038 #[test]
4039 fn sampled_walk_chains_the_drawn_candidate() {
4040 let (pred, succ) = books();
4045 let cand: Vec<u32> = vec![1, 2, 3, 4];
4046 let hproj = vec![1.0f32; 2 * R];
4047 let mut seq = vec![0.99f32, 0.01].into_iter();
4048 let mut u = move || seq.next().unwrap();
4049 let (path, _, _) = dflash2_walk_sampled(
4050 &pred, &succ, V, R, K, &[0.0; 4], &cand, &hproj, 0, 2, 2.0, &mut u,
4051 );
4052 assert_eq!(path[0], 2, "u=0.99 must draw the low-prob candidate");
4053 assert_eq!(
4054 path[1], 4,
4055 "slot 1 must walk from pred[2] (the DRAWN token), which scores tok4 at 10 \
4056 — chaining from the anchor or the argmax picks tok3"
4057 );
4058 }
4059
4060 #[test]
4061 fn rejection_accept_walk_is_the_leviathan_rule() {
4062 assert_eq!(
4064 rejection_accept_len(&[0.5, 0.5], &[0.5, 0.5], &[0.9, 0.9]),
4065 2
4066 );
4067 assert_eq!(
4068 rejection_accept_len(&[0.5, 0.5], &[0.5, 0.5], &[1.0, 0.0]),
4069 0
4070 );
4071 assert_eq!(rejection_accept_len(&[0.25], &[0.5], &[0.5]), 0);
4073 assert_eq!(rejection_accept_len(&[1e-6], &[0.0], &[0.999]), 1);
4075 assert_eq!(
4077 rejection_accept_len(&[0.9, 0.0, 0.9], &[0.1, 0.9, 0.1], &[0.5, 0.5, 0.5]),
4078 1
4079 );
4080 }
4081
4082 fn tv(a: &[f64], b: &[f64]) -> f64 {
4089 a.iter().zip(b).map(|(x, y)| (x - y).abs()).sum::<f64>() / 2.0
4090 }
4091
4092 fn compose_once(
4094 p: &[f32],
4095 q: &[f32],
4096 u_draw: f32,
4097 u_accept: f32,
4098 u_resid: f32,
4099 invert_accept: bool,
4100 skip_q_in_residual: bool,
4101 ) -> usize {
4102 let n = p.len();
4103 let mut acc = 0f64;
4105 let mut x = n - 1;
4106 for (i, &qi) in q.iter().enumerate() {
4107 acc += qi as f64;
4108 if (u_draw as f64) < acc {
4109 x = i;
4110 break;
4111 }
4112 }
4113 let accepted = if invert_accept {
4114 !((u_accept as f64) * (q[x] as f64) < p[x] as f64)
4115 } else {
4116 rejection_accept_len(&p[x..=x], &q[x..=x], &[u_accept]) == 1
4117 };
4118 if accepted {
4119 return x;
4120 }
4121 let r: Vec<f64> = p
4123 .iter()
4124 .zip(q)
4125 .map(|(&pi, &qi)| {
4126 let qq = if skip_q_in_residual { 0.0 } else { qi as f64 };
4127 (pi as f64 - qq).max(0.0)
4128 })
4129 .collect();
4130 let total: f64 = r.iter().sum();
4131 let mut acc = 0f64;
4132 let target = u_resid as f64 * total;
4133 for (i, &ri) in r.iter().enumerate() {
4134 acc += ri;
4135 if acc >= target && ri > 0.0 {
4136 return i;
4137 }
4138 }
4139 n - 1
4140 }
4141
4142 fn compose_tv(q: &[f32], invert_accept: bool, skip_q_in_residual: bool) -> f64 {
4143 let p: Vec<f32> = vec![0.30, 0.22, 0.15, 0.12, 0.09, 0.06, 0.04, 0.02];
4145 let trials = 200_000usize;
4146 let mut counts = vec![0f64; V];
4147 for t in 0..trials {
4148 let u_draw = crate::spec::host_u01(7, (t * 3) as u32);
4150 let u_accept = crate::spec::host_u01(7, (t * 3 + 1) as u32);
4151 let u_resid = crate::spec::host_u01(7, (t * 3 + 2) as u32);
4152 counts[compose_once(
4153 &p,
4154 q,
4155 u_draw,
4156 u_accept,
4157 u_resid,
4158 invert_accept,
4159 skip_q_in_residual,
4160 )] += 1.0;
4161 }
4162 let emp: Vec<f64> = counts.iter().map(|c| c / trials as f64).collect();
4163 let pf: Vec<f64> = p.iter().map(|&v| v as f64).collect();
4164 tv(&emp, &pf)
4165 }
4166
4167 #[test]
4168 fn sampled_round_composition_matches_the_target() {
4169 let q_rows: Vec<f32> = vec![0.02, 0.04, 0.06, 0.09, 0.12, 0.15, 0.22, 0.30];
4172 let q_sparse: Vec<f32> = vec![0.0, 0.7, 0.0, 0.3, 0.0, 0.0, 0.0, 0.0];
4174 for (name, q) in [("rows", &q_rows), ("sparse", &q_sparse)] {
4175 let d = compose_tv(q, false, false);
4176 assert!(
4177 d < 0.01,
4178 "composition[{name}]: committed-token distribution must equal p \
4179 (TV {d:.4} >= 0.01)"
4180 );
4181 }
4182 }
4183
4184 #[test]
4185 fn composition_teeth_inverted_accept_fails() {
4186 let q: Vec<f32> = vec![0.02, 0.04, 0.06, 0.09, 0.12, 0.15, 0.22, 0.30];
4189 let d = compose_tv(&q, true, false);
4190 assert!(
4191 d > 0.05,
4192 "inverted accept rule must fail the composition bound (TV {d:.4})"
4193 );
4194 }
4195
4196 #[test]
4197 fn composition_teeth_residual_without_q_fails() {
4198 let q: Vec<f32> = vec![0.0, 0.7, 0.0, 0.3, 0.0, 0.0, 0.0, 0.0];
4201 let d = compose_tv(&q, false, true);
4202 assert!(
4203 d > 0.05,
4204 "residual that skips the q subtraction must fail the bound (TV {d:.4})"
4205 );
4206 }
4207}
4208
4209#[cfg(test)]
4210mod dspark_harvest_tests {
4211 use super::{DsparkHarvest, DsparkVtPolicy, dspark_accept_prefix, dspark_strategy_census};
4212
4213 const B: usize = 7; #[test]
4216 fn dspark_strategy_requires_shifted_harvest() {
4217 let h = DsparkHarvest::Dspark;
4218 assert_eq!(
4219 h.first_row(),
4220 0,
4221 "DSPARK-strategy checkpoints (SpecForge OnlineDSparkModel, \
4222 training.strategy=dspark — the q38 arm-a export) supervise ALL rows with \
4223 SHIFTED labels: label_offsets = arange(1, block_size+1), i.e. the ANCHOR \
4224 row's output is draft 1 (specforge/algorithms/common/\
4225 dflash_family_model.py:816; sglang v0.5.17 dspark_draft.py:248,260). \
4226 Harvesting from row 1 re-opens the DSPARK-POSTMORTEM-20260820 slot \
4227 misalignment (accept 2.9 -> 1.43)."
4228 );
4229 assert_eq!(
4230 h.n_drafts(B),
4231 B,
4232 "DSpark harvests gamma = block_size drafts per round (sglang \
4233 dspark_config.py:269, verify_num_draft_tokens = gamma+1); b-1 is the \
4234 DFlash mask-fill count and drops the best-trained slot \
4235 (DSPARK-POSTMORTEM-20260820.md §3-H1)."
4236 );
4237 for row in 0..B {
4238 assert_eq!(
4239 h.trained_offset_of_row(row),
4240 row + 1,
4241 "OnlineDSparkModel trains row k to predict anchor+k+1 \
4242 (dflash_family_model.py:816); a same-position (mask-fill) mapping \
4243 here verifies every slot one position early — the postmortem's \
4244 collapse."
4245 );
4246 }
4247 }
4248
4249 #[test]
4250 fn dflash_strategy_keeps_mask_fill_harvest() {
4251 let h = DsparkHarvest::Dflash;
4255 assert_eq!(h.first_row(), 1, "DFlash drafts start at mask row 1");
4256 assert_eq!(h.n_drafts(B), B - 1, "DFlash harvests block_size-1 drafts");
4257 for row in 1..B {
4258 assert_eq!(h.trained_offset_of_row(row), row);
4259 }
4260 }
4261
4262 #[test]
4263 fn every_candidate_verifies_the_position_its_row_was_trained_for() {
4264 for h in [DsparkHarvest::Dflash, DsparkHarvest::Dspark] {
4268 for i in 1..=h.n_drafts(B) {
4269 let row = h.first_row() + i - 1;
4270 assert_eq!(
4271 h.trained_offset_of_row(row),
4272 i,
4273 "{h:?}: candidate {i} rides row {row}, which is trained for \
4274 offset {} — harvest misaligned",
4275 h.trained_offset_of_row(row)
4276 );
4277 }
4278 }
4279 }
4280
4281 #[test]
4282 fn env_seam_parses_and_refuses() {
4283 assert_eq!(
4284 DsparkHarvest::from_env_value(None),
4285 DsparkHarvest::Dflash,
4286 "the ENV-ONLY parser keeps the historical arm; the ratified strategy-keyed \
4287 default lives in resolve_value (checkpoint census), not here"
4288 );
4289 assert_eq!(
4290 DsparkHarvest::from_env_value(Some("dspark")),
4291 DsparkHarvest::Dspark
4292 );
4293 assert_eq!(
4294 DsparkHarvest::from_env_value(Some("dflash")),
4295 DsparkHarvest::Dflash
4296 );
4297 assert!(
4298 std::panic::catch_unwind(|| DsparkHarvest::from_env_value(Some("shifted"))).is_err(),
4299 "unknown harvest values must REFUSE, not default"
4300 );
4301 assert_eq!(
4302 DsparkHarvest::from_name("dspark"),
4303 Some(DsparkHarvest::Dspark)
4304 );
4305 assert_eq!(
4306 DsparkHarvest::from_name("dflash"),
4307 Some(DsparkHarvest::Dflash)
4308 );
4309 assert_eq!(DsparkHarvest::from_name("mask-fill"), None);
4310 }
4311
4312 #[test]
4315 fn ratified_default_harvest_is_strategy_keyed() {
4316 assert_eq!(
4319 DsparkHarvest::resolve_value(None, true),
4320 DsparkHarvest::Dspark,
4321 "owner-ratified 2026-08-20: unset env defaults a DSPARK-strategy \
4322 checkpoint to the shifted harvest (DSPARK-POSTMORTEM-20260820.md B1)"
4323 );
4324 assert_eq!(
4326 DsparkHarvest::resolve_value(None, false),
4327 DsparkHarvest::Dflash
4328 );
4329 assert_eq!(
4330 DsparkHarvest::resolve_value(Some(""), false),
4331 DsparkHarvest::Dflash
4332 );
4333 assert_eq!(
4335 DsparkHarvest::resolve_value(Some("dflash"), true),
4336 DsparkHarvest::Dflash
4337 );
4338 assert_eq!(
4339 DsparkHarvest::resolve_value(Some("dspark"), false),
4340 DsparkHarvest::Dspark
4341 );
4342 assert!(
4344 std::panic::catch_unwind(|| DsparkHarvest::resolve_value(Some("shifted"), true))
4345 .is_err()
4346 );
4347 }
4348
4349 #[test]
4350 fn strategy_census_reads_the_checkpoint_not_the_env() {
4351 let q38 = r#"{"architectures": ["Qwen3DSparkModel"], "block_size": 7,
4353 "dflash_config": {"projector_type": "dspark", "markov_rank": 256}}"#;
4354 assert!(dspark_strategy_census(q38));
4355 assert!(dspark_strategy_census(
4357 r#"{"architectures": ["Qwen3DSparkModel"]}"#
4358 ));
4359 assert!(dspark_strategy_census(
4360 r#"{"dflash_config": {"projector_type": "dspark"}}"#
4361 ));
4362 let dflash = r#"{"architectures": ["Qwen3DFlashModel"],
4364 "dflash_config": {"attention_mode": "gqa"}}"#;
4365 assert!(!dspark_strategy_census(dflash));
4366 assert!(!dspark_strategy_census("{}"));
4367 }
4368
4369 #[test]
4370 fn ratified_default_vt_is_confidence_slot_tau_half() {
4371 assert_eq!(
4375 DsparkVtPolicy::resolve_value(None, None, None, true),
4376 DsparkVtPolicy::ConfidenceSlot { tau: 0.5 },
4377 "owner-ratified 2026-08-20: unset MEMRA_DSPARK_VT defaults to \
4378 confidence-slot tau=.5 on a head-carrying checkpoint (H4 cells 2-3)"
4379 );
4380 assert_eq!(
4382 DsparkVtPolicy::resolve_value(None, Some("0.35"), None, true),
4383 DsparkVtPolicy::ConfidenceSlot { tau: 0.35 }
4384 );
4385 assert!(
4386 std::panic::catch_unwind(|| DsparkVtPolicy::resolve_value(
4387 None,
4388 Some("nan-ish"),
4389 None,
4390 true
4391 ))
4392 .is_err()
4393 );
4394 assert_eq!(
4396 DsparkVtPolicy::resolve_value(None, None, None, false),
4397 DsparkVtPolicy::Ladder
4398 );
4399 assert_eq!(
4401 DsparkVtPolicy::resolve_value(None, None, Some("0"), true),
4402 DsparkVtPolicy::Ladder
4403 );
4404 assert_eq!(
4406 DsparkVtPolicy::resolve_value(Some("ladder"), None, None, true),
4407 DsparkVtPolicy::Ladder
4408 );
4409 assert_eq!(
4410 DsparkVtPolicy::resolve_value(Some("confidence"), Some("0.35"), None, true),
4411 DsparkVtPolicy::Confidence { tau: 0.35 }
4412 );
4413 assert!(
4415 std::panic::catch_unwind(|| DsparkVtPolicy::resolve_value(
4416 Some("confidence-slot"),
4417 None,
4418 Some("0"),
4419 true
4420 ))
4421 .is_err()
4422 );
4423 }
4424
4425 #[test]
4431 fn dspark_trained_rows_through_mask_fill_harvest_accept_nothing() {
4432 const BASE: u32 = 1000;
4433 let anchor: u32 = BASE; let vam: Vec<u32> = (1..=B as u32 + 1).map(|j| BASE + j).collect();
4436 let dspark_trained_row_argmax =
4438 |r: usize| BASE + DsparkHarvest::Dspark.trained_offset_of_row(r) as u32;
4439
4440 let h = DsparkHarvest::Dspark;
4442 let mut cand = vec![anchor];
4443 for i in 1..=h.n_drafts(B) {
4444 cand.push(dspark_trained_row_argmax(h.first_row() + i - 1));
4445 }
4446 let vt = h.n_drafts(B) + 1;
4447 assert_eq!(
4448 dspark_accept_prefix(&cand, &vam, vt),
4449 vt - 1,
4450 "aligned harvest must accept the full block"
4451 );
4452
4453 let wrong = DsparkHarvest::Dflash;
4456 let mut cand_wrong = vec![anchor];
4457 for i in 1..=wrong.n_drafts(B) {
4458 cand_wrong.push(dspark_trained_row_argmax(wrong.first_row() + i - 1));
4459 }
4460 let vt_wrong = wrong.n_drafts(B) + 1;
4461 assert_eq!(
4462 dspark_accept_prefix(&cand_wrong, &vam, vt_wrong),
4463 0,
4464 "mask-fill harvest of a dspark-trained drafter verifies every slot against \
4465 a position the row was not trained for (DSPARK-POSTMORTEM-20260820.md)"
4466 );
4467 }
4468}
4469
4470#[cfg(test)]
4476mod dspark_vt_tests {
4477 use super::{ConfidenceHead, DsparkVtPolicy, dspark_confidence_vt, dspark_slot_confidence_vt};
4478
4479 fn logit(p: f32) -> f32 {
4481 (p / (1.0 - p)).ln()
4482 }
4483
4484 #[test]
4485 fn confidence_vt_is_cumprod_survival_not_per_slot_threshold() {
4486 let raws: Vec<f32> = [0.9, 0.8, 0.9, 0.9, 0.9, 0.9, 0.9]
4491 .iter()
4492 .map(|&p| logit(p))
4493 .collect();
4494 assert_eq!(
4495 dspark_confidence_vt(&raws, 0.5, 8),
4496 6,
4497 "keeps 5 drafts + anchor"
4498 );
4499 assert_eq!(
4501 dspark_confidence_vt(&raws, 0.7, 8),
4502 3,
4503 "tau=0.7 keeps 2 drafts"
4504 );
4505 assert_eq!(
4506 dspark_confidence_vt(&raws, 0.05, 8),
4507 8,
4508 "tau→0 = full block"
4509 );
4510 }
4511
4512 #[test]
4513 fn slot_arm_truncates_at_first_low_confidence_slot() {
4514 let raws: Vec<f32> = [0.9, 0.8, 0.9, 0.9, 0.9, 0.9, 0.9]
4520 .iter()
4521 .map(|&p| logit(p))
4522 .collect();
4523 assert_eq!(dspark_slot_confidence_vt(&raws, 0.5, 8), 8);
4524 assert_eq!(dspark_confidence_vt(&raws, 0.5, 8), 6);
4525 let tail: Vec<f32> = [0.9, 0.9, 0.3, 0.9, 0.9, 0.9, 0.9]
4529 .iter()
4530 .map(|&p| logit(p))
4531 .collect();
4532 assert_eq!(
4533 dspark_slot_confidence_vt(&tail, 0.5, 8),
4534 3,
4535 "2 drafts + anchor"
4536 );
4537 assert_eq!(
4539 dspark_slot_confidence_vt(&tail, 0.95, 8),
4540 2,
4541 "floor at tau=0.95"
4542 );
4543 }
4544
4545 #[test]
4546 fn confidence_vt_floor_and_cap() {
4547 let cold: Vec<f32> = [0.1f32, 0.1, 0.1].iter().map(|&p| logit(p)).collect();
4550 assert_eq!(
4551 dspark_confidence_vt(&cold, 0.5, 8),
4552 2,
4553 "floor = anchor + 1 draft"
4554 );
4555 assert_eq!(
4556 dspark_slot_confidence_vt(&cold, 0.5, 8),
4557 2,
4558 "slot arm same floor"
4559 );
4560 let hot: Vec<f32> = vec![logit(0.99); 7];
4562 assert_eq!(dspark_confidence_vt(&hot, 0.5, 5), 5, "vt_cap binds");
4563 assert_eq!(
4564 dspark_confidence_vt(&hot, 0.5, 8),
4565 8,
4566 "full block when confident"
4567 );
4568 assert_eq!(
4569 dspark_slot_confidence_vt(&hot, 0.5, 5),
4570 5,
4571 "slot arm same cap"
4572 );
4573 assert_eq!(dspark_confidence_vt(&[], 0.5, 8), 2);
4575 assert_eq!(dspark_slot_confidence_vt(&[], 0.5, 8), 2);
4576 }
4577
4578 #[test]
4579 fn vt_policy_env_seam_parses_and_refuses() {
4580 assert_eq!(
4581 DsparkVtPolicy::from_env_value(None, None, None),
4582 DsparkVtPolicy::Ladder,
4583 "default stays the shipped ladder — the H4 arm is opt-in"
4584 );
4585 assert_eq!(
4586 DsparkVtPolicy::from_env_value(Some(""), None, None),
4587 DsparkVtPolicy::Ladder
4588 );
4589 assert_eq!(
4590 DsparkVtPolicy::from_env_value(Some("ladder"), None, Some("0")),
4591 DsparkVtPolicy::Ladder,
4592 "ladder + ADAPT=0 = the fixed-window arm, untouched"
4593 );
4594 assert_eq!(
4595 DsparkVtPolicy::from_env_value(Some("confidence"), None, None),
4596 DsparkVtPolicy::Confidence { tau: 0.5 },
4597 "tau defaults to 0.5 (raw sigmoid, no STS sidecar — postmortem §3-H4)"
4598 );
4599 assert_eq!(
4600 DsparkVtPolicy::from_env_value(Some("confidence"), Some("0.35"), Some("1")),
4601 DsparkVtPolicy::Confidence { tau: 0.35 }
4602 );
4603 assert_eq!(
4604 DsparkVtPolicy::from_env_value(Some("confidence-slot"), Some("0.6"), None),
4605 DsparkVtPolicy::ConfidenceSlot { tau: 0.6 },
4606 "the owner-directive per-slot arm parses with the same tau env"
4607 );
4608 assert!(
4609 std::panic::catch_unwind(|| DsparkVtPolicy::from_env_value(
4610 Some("confidence-slot"),
4611 None,
4612 Some("0")
4613 ))
4614 .is_err(),
4615 "confidence-slot + MEMRA_DFLASH_ADAPT=0 must REFUSE like confidence"
4616 );
4617 assert!(
4618 std::panic::catch_unwind(|| DsparkVtPolicy::from_env_value(Some("static"), None, None))
4619 .is_err(),
4620 "unknown policy values must REFUSE, not default — a typo silently \
4621 reverting the window policy invalidates an A/B"
4622 );
4623 assert!(
4624 std::panic::catch_unwind(|| DsparkVtPolicy::from_env_value(
4625 Some("confidence"),
4626 None,
4627 Some("0")
4628 ))
4629 .is_err(),
4630 "confidence + MEMRA_DFLASH_ADAPT=0 is contradictory and must REFUSE"
4631 );
4632 for bad in ["0", "1", "1.5", "-0.1", "nan"] {
4633 assert!(
4634 std::panic::catch_unwind(|| DsparkVtPolicy::from_env_value(
4635 Some("confidence"),
4636 Some(bad),
4637 None
4638 ))
4639 .is_err(),
4640 "tau={bad} must REFUSE (survival threshold lives in (0,1))"
4641 );
4642 }
4643 }
4644
4645 #[test]
4646 fn raw_score_matches_the_parity_gate_dot() {
4647 let ch = ConfidenceHead {
4650 w: vec![0.5, -1.0, 2.0, 0.25, -0.5],
4651 b: 0.125,
4652 in_dim: 5,
4653 with_markov: true,
4654 };
4655 let hidden = [1.0f32, 2.0, 3.0];
4656 let emb = [4.0f32, 8.0];
4657 let want = 0.125 + 0.5 * 1.0 - 1.0 * 2.0 + 2.0 * 3.0 + 0.25 * 4.0 - 0.5 * 8.0;
4658 assert_eq!(ch.raw_score(&hidden, Some(&emb)), want);
4659 let ch_plain = ConfidenceHead {
4660 w: vec![0.5, -1.0, 2.0],
4661 b: -0.25,
4662 in_dim: 3,
4663 with_markov: false,
4664 };
4665 let want_plain = -0.25 + 0.5 * 1.0 - 1.0 * 2.0 + 2.0 * 3.0;
4666 assert_eq!(ch_plain.raw_score(&hidden, None), want_plain);
4667 }
4668}