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}
32
33pub struct DflashLayer {
34 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>, }
46
47pub struct DflashDraft {
48 pub cfg: DflashCfg,
49 pub layers: Vec<DflashLayer>,
50 pub fc: GpuTensor, pub hidden_norm: CudaSlice<f32>, pub norm: CudaSlice<f32>, pub markov: Option<MarkovHead>,
58 pub confidence: Option<ConfidenceHead>,
62 pub rope_yarn: Option<(CudaSlice<f32>, f32)>,
69}
70
71pub struct ConfidenceHead {
74 pub w: Vec<f32>, pub b: f32,
76 pub in_dim: usize,
77 pub with_markov: bool,
78}
79
80pub struct MarkovHead {
81 pub w1_bf16: CudaSlice<u8>, pub w2: GpuTensor, pub rank: usize,
84 pub vocab: usize,
85}
86
87fn bf16_to_f32(bytes: &[u8]) -> Vec<f32> {
88 bytes
89 .chunks_exact(2)
90 .map(|c| f32::from_bits((u16::from_le_bytes([c[0], c[1]]) as u32) << 16))
91 .collect()
92}
93
94fn encode_q8_0(vals: &[f32]) -> Vec<u8> {
99 let mut out = Vec::with_capacity(vals.len() / 32 * 34);
100 for blk in vals.chunks_exact(32) {
101 let amax = blk.iter().fold(0f32, |a, v| a.max(v.abs()));
102 let d = amax / 127.0;
103 let id = if d > 0.0 { 1.0 / d } else { 0.0 };
104 let dh = half_from_f32(d);
105 out.extend_from_slice(&dh.to_le_bytes());
106 for &v in blk {
107 out.push(((v * id).round().clamp(-127.0, 127.0)) as i8 as u8);
108 }
109 }
110 out
111}
112
113fn encode_q4_0(vals: &[f32]) -> Vec<u8> {
118 let mut out = Vec::with_capacity(vals.len() / 32 * 18);
119 for blk in vals.chunks_exact(32) {
120 let mut amax = 0f32;
122 let mut mx = 0f32;
123 for &v in blk {
124 if v.abs() > amax {
125 amax = v.abs();
126 mx = v;
127 }
128 }
129 let d = mx / -8.0;
130 let id = if d != 0.0 { 1.0 / d } else { 0.0 };
131 out.extend_from_slice(&half_from_f32(d).to_le_bytes());
132 for j in 0..16 {
133 let x0 = (blk[j] * id + 8.5).clamp(0.0, 15.0) as u8;
134 let x1 = (blk[j + 16] * id + 8.5).clamp(0.0, 15.0) as u8;
135 out.push(x0 | (x1 << 4));
136 }
137 }
138 out
139}
140
141fn half_from_f32(v: f32) -> u16 {
142 let b = v.to_bits();
144 let sign = ((b >> 16) & 0x8000) as u16;
145 let exp = ((b >> 23) & 0xff) as i32 - 127 + 15;
146 let man = b & 0x7fffff;
147 if exp <= 0 {
148 return sign;
149 } if exp >= 31 {
151 return sign | 0x7c00;
152 } let mut h = sign | ((exp as u16) << 10) | ((man >> 13) as u16);
154 let rem = man & 0x1fff;
156 if rem > 0x1000 || (rem == 0x1000 && (h & 1) == 1) {
157 h += 1;
158 }
159 h
160}
161
162impl DflashDraft {
163 pub fn load(e: &Engine, dir: &std::path::Path) -> Result<Self, Box<dyn std::error::Error>> {
166 let txt = std::fs::read_to_string(dir.join("config.json"))?;
167 fn num(txt: &str, key: &str) -> Option<f64> {
168 let i = txt.find(&format!("\"{key}\""))?;
169 let rest = &txt[i..];
170 let colon = rest.find(':')?;
171 let val: String = rest[colon + 1..]
172 .trim_start()
173 .chars()
174 .take_while(|c| {
175 c.is_ascii_digit()
176 || *c == '.'
177 || *c == '-'
178 || *c == 'e'
179 || *c == 'E'
180 || *c == '+'
181 })
182 .collect();
183 val.parse().ok()
184 }
185 fn num_list(txt: &str, key: &str) -> Vec<usize> {
186 let Some(i) = txt.find(&format!("\"{key}\"")) else {
187 return Vec::new();
188 };
189 let rest = &txt[i..];
190 let (Some(a), Some(b)) = (rest.find('['), rest.find(']')) else {
191 return Vec::new();
192 };
193 rest[a + 1..b]
194 .split(',')
195 .filter_map(|s| s.trim().parse().ok())
196 .collect()
197 }
198 let g = |k: &str| num(&txt, k).unwrap_or_else(|| panic!("config missing {k}")) as usize;
199 let layer_sliding: Vec<bool> = {
201 let i = txt.find("\"layer_types\"").expect("layer_types");
202 let rest = &txt[i..];
203 let (a, b) = (rest.find('[').unwrap(), rest.find(']').unwrap());
204 rest[a + 1..b]
205 .split(',')
206 .map(|s| s.contains("sliding_attention"))
207 .collect()
208 };
209 let sliding_window = if layer_sliding.iter().any(|&s| s) {
213 g("sliding_window")
214 } else {
215 num(&txt, "sliding_window")
216 .map(|v| v as usize)
217 .unwrap_or(usize::MAX)
218 };
219 let cfg = DflashCfg {
220 hidden: g("hidden_size"),
221 n_head: g("num_attention_heads"),
222 n_kv: g("num_key_value_heads"),
223 head_dim: g("head_dim"),
224 n_ff: g("intermediate_size"),
225 n_layer: g("num_hidden_layers"),
226 eps: num(&txt, "rms_norm_eps").expect("rms_norm_eps") as f32,
227 rope_theta: num(&txt, "rope_theta").expect("rope_theta") as f32,
228 block_size: g("block_size"),
229 mask_token_id: g("mask_token_id") as u32,
230 target_layer_ids: num_list(&txt, "target_layer_ids"),
231 sliding_window,
232 layer_sliding,
233 };
234 let st = memra_gguf::safetensors::StModel::open(&dir.join("model.safetensors"))?;
235 let up = |name: &str| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
238 let (_info, bytes) = st
239 .raw(name)
240 .ok_or_else(|| format!("missing tensor {name}"))?;
241 Ok(e.htod(&bf16_to_f32(bytes))?)
242 };
243 let prec = std::env::var("MEMRA_DFLASH_PREC").unwrap_or_else(|_| "q8".into());
247 let upw = |name: &str| -> Result<GpuTensor, Box<dyn std::error::Error>> {
248 let (info, bytes) = st
249 .raw(name)
250 .ok_or_else(|| format!("missing tensor {name}"))?;
251 let shape = info.ne(); let in_f = shape[0] as usize;
253 let is_ffn = name.contains(".mlp.");
254 let bf16 = prec == "bf16"
255 || (prec == "mixed" && !is_ffn)
256 || (prec == "fc" && name == "fc.weight");
257 if bf16 {
258 return Ok(GpuTensor::FloatBf16 {
259 data: e.upload_u8(bytes)?,
260 ne: shape.to_vec(),
261 });
262 }
263 let f32s = bf16_to_f32(bytes);
264 if prec == "q4" {
265 let q = encode_q4_0(&f32s);
266 return Ok(GpuTensor::Quant {
267 bytes: e.upload_u8(&q)?,
268 qtype: crate::QT_Q4_0,
269 row_bytes: in_f / 32 * 18,
270 ne: shape.to_vec(),
271 scale: 1.0,
272 rp: false,
273 #[cfg(memra_cutlass)]
274 cutlass: None,
275 fp8: None,
276 blk: None,
277 rp4: None,
278 f16: None,
279 });
280 }
281 let q = encode_q8_0(&f32s);
282 Ok(GpuTensor::Quant {
283 bytes: e.upload_u8(&q)?,
284 qtype: crate::QT_Q8_0,
285 row_bytes: in_f / 32 * 34,
286 ne: shape.to_vec(),
287 scale: 1.0,
288 rp: false,
289 #[cfg(memra_cutlass)]
290 cutlass: None,
291 fp8: None,
292 blk: None,
293 rp4: None,
294 f16: None,
295 })
296 };
297 let mut layers = Vec::with_capacity(cfg.n_layer);
298 for i in 0..cfg.n_layer {
299 let p = |s: &str| format!("layers.{i}.{s}");
300 layers.push(DflashLayer {
301 wq: upw(&p("self_attn.q_proj.weight"))?,
302 wk: upw(&p("self_attn.k_proj.weight"))?,
303 wv: upw(&p("self_attn.v_proj.weight"))?,
304 wo: upw(&p("self_attn.o_proj.weight"))?,
305 w_gate: upw(&p("mlp.gate_proj.weight"))?,
306 w_up: upw(&p("mlp.up_proj.weight"))?,
307 w_down: upw(&p("mlp.down_proj.weight"))?,
308 ln_in: up(&p("input_layernorm.weight"))?,
309 ln_post: up(&p("post_attention_layernorm.weight"))?,
310 q_norm: up(&p("self_attn.q_norm.weight"))?,
311 k_norm: up(&p("self_attn.k_norm.weight"))?,
312 });
313 }
314 let markov = if let Some((info, bytes)) = st.raw("markov_head.markov_w1.weight") {
315 let sh = info.ne(); let (rank, vocab) = (sh[0] as usize, sh[1] as usize);
317 let (i2, b2) = st
318 .raw("markov_head.markov_w2.weight")
319 .ok_or("markov_w2 missing beside markov_w1")?;
320 let w2 = if prec == "bf16" {
324 GpuTensor::FloatBf16 {
325 data: e.upload_u8(b2)?,
326 ne: i2.ne().to_vec(),
327 }
328 } else {
329 let w2f = bf16_to_f32(b2);
330 let w2q = encode_q8_0(&w2f);
331 GpuTensor::Quant {
332 bytes: e.upload_u8(&w2q)?,
333 qtype: crate::QT_Q8_0,
334 row_bytes: rank / 32 * 34,
335 ne: vec![rank as u64, vocab as u64],
336 scale: 1.0,
337 rp: false,
338 #[cfg(memra_cutlass)]
339 cutlass: None,
340 fp8: None,
341 blk: None,
342 rp4: None,
343 f16: None,
344 }
345 };
346 Some(MarkovHead {
347 w1_bf16: e.upload_u8(bytes)?,
348 w2,
349 rank,
350 vocab,
351 })
352 } else {
353 None
354 };
355 let confidence = if let Some((info, bytes)) = st.raw("confidence_head.proj.weight") {
356 let sh = info.ne(); let in_dim = sh[0] as usize;
358 let (_bi, bb) = st
359 .raw("confidence_head.proj.bias")
360 .ok_or("confidence bias missing beside weight")?;
361 let with_markov = markov
362 .as_ref()
363 .map(|m| in_dim == cfg.hidden + m.rank)
364 .unwrap_or(false);
365 if !with_markov && in_dim != cfg.hidden {
366 panic!(
367 "confidence_head in_dim {in_dim} matches neither hidden {} nor hidden+rank",
368 cfg.hidden
369 );
370 }
371 Some(ConfidenceHead {
372 w: bf16_to_f32(bytes),
373 b: bf16_to_f32(bb)[0],
374 in_dim,
375 with_markov,
376 })
377 } else {
378 None
379 };
380 {
385 let mut consumed: std::collections::HashSet<String> = std::collections::HashSet::new();
386 for i in 0..cfg.n_layer {
387 for s in [
388 "self_attn.q_proj.weight",
389 "self_attn.k_proj.weight",
390 "self_attn.v_proj.weight",
391 "self_attn.o_proj.weight",
392 "self_attn.q_norm.weight",
393 "self_attn.k_norm.weight",
394 "input_layernorm.weight",
395 "post_attention_layernorm.weight",
396 "mlp.gate_proj.weight",
397 "mlp.up_proj.weight",
398 "mlp.down_proj.weight",
399 ] {
400 consumed.insert(format!("layers.{i}.{s}"));
401 }
402 }
403 for s in [
404 "fc.weight",
405 "hidden_norm.weight",
406 "norm.weight",
407 "markov_head.markov_w1.weight",
408 "markov_head.markov_w2.weight",
409 "confidence_head.proj.weight",
410 "confidence_head.proj.bias",
411 ] {
412 consumed.insert(s.into());
413 }
414 let leftovers: Vec<&String> = st.names().filter(|n| !consumed.contains(*n)).collect();
415 if !leftovers.is_empty() {
416 if markov.is_some() {
417 panic!("dspark census: unrecognized tensors {leftovers:?}");
418 }
419 eprintln!("[dflash census] unmapped tensors (ignored): {leftovers:?}");
420 }
421 }
422 let rope_yarn =
425 if txt.contains("\"rope_type\": \"yarn\"") || txt.contains("\"rope_type\":\"yarn\"") {
426 let factor = num(&txt, "factor").expect("yarn factor") as f64;
427 let orig = num(&txt, "original_max_position_embeddings").expect("yarn orig");
428 let beta_fast = num(&txt, "beta_fast").expect("beta_fast");
429 let beta_slow = num(&txt, "beta_slow").expect("beta_slow");
430 let base = cfg.rope_theta as f64;
431 let d = cfg.head_dim as f64;
432 let corr =
433 |r: f64| d * (orig / (r * 2.0 * std::f64::consts::PI)).ln() / (2.0 * base.ln());
434 let low = corr(beta_fast).floor().max(0.0);
435 let high = corr(beta_slow).ceil().min(d - 1.0);
436 let half = cfg.head_dim / 2;
437 let mut ff = Vec::with_capacity(half);
438 for j in 0..half {
439 let base_inv = base.powf(-2.0 * j as f64 / d);
440 let ramp = (((j as f64) - low) / (high - low)).clamp(0.0, 1.0);
441 let ex = 1.0 - ramp; let yarn_inv = (base_inv / factor) * (1.0 - ex) + base_inv * ex;
443 ff.push((base_inv / yarn_inv) as f32);
444 }
445 let mscale = (0.1 * factor.ln() + 1.0) as f32;
446 Some((e.htod(&ff)?, mscale))
447 } else {
448 None
449 };
450 Ok(Self {
451 fc: upw("fc.weight")?,
452 hidden_norm: up("hidden_norm.weight")?,
453 norm: up("norm.weight")?,
454 cfg,
455 layers,
456 markov,
457 confidence,
458 rope_yarn,
459 })
460 }
461
462 fn rope_rows(
465 &self,
466 e: &Engine,
467 x: &mut CudaSlice<f32>,
468 pos_d: &CudaSlice<i32>,
469 n_heads: usize,
470 n_tokens: usize,
471 ) -> Result<(), Box<dyn std::error::Error>> {
472 let c = &self.cfg;
473 match &self.rope_yarn {
474 Some((ff, mscale)) => {
475 e.rope_neox_ff(
476 x,
477 pos_d,
478 c.head_dim,
479 c.head_dim,
480 n_heads,
481 n_tokens,
482 c.rope_theta,
483 1.0,
484 ff,
485 )?;
486 e.scale_inplace(x, *mscale, n_tokens * n_heads * c.head_dim)?;
487 }
488 None => {
489 e.rope_neox(
490 x,
491 pos_d,
492 c.head_dim,
493 c.head_dim,
494 n_heads,
495 n_tokens,
496 c.rope_theta,
497 1.0,
498 )?;
499 }
500 }
501 Ok(())
502 }
503
504 fn mm(
506 &self,
507 e: &Engine,
508 w: &GpuTensor,
509 x: &CudaSlice<f32>,
510 t: usize,
511 _in_f: usize,
512 _out_f: usize,
513 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
514 Ok(e.matmul(w, x, t)?)
515 }
516
517 pub fn ctx_features(
528 &self,
529 e: &Engine,
530 taps: &CudaSlice<f32>,
531 t: usize,
532 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
533 let c = &self.cfg;
534 let n_taps = c.target_layer_ids.len();
535 let fc_out = self.mm(e, &self.fc, taps, t, n_taps * c.hidden, c.hidden)?;
536 let mut out = e.uninit(t * c.hidden)?;
537 e.rms_norm(&fc_out, &self.hidden_norm, &mut out, c.hidden, t, c.eps)?;
538 Ok(out)
539 }
540
541 pub fn forward(
542 &self,
543 e: &Engine,
544 target_hidden: &CudaSlice<f32>,
545 noise_emb: &CudaSlice<f32>,
546 pos: &[i32],
547 ctx: usize,
548 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
549 let ctx_f = self.ctx_features(e, target_hidden, ctx)?;
550 if let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP") {
551 let v = e.dtoh(&ctx_f)?;
552 let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
553 std::fs::write(format!("{dir}/memra-ctx_features.f32"), bytes)?;
554 }
555 self.forward_block(e, &ctx_f, noise_emb, pos, ctx)
556 }
557
558 pub fn forward_block(
561 &self,
562 e: &Engine,
563 ctx_f: &CudaSlice<f32>,
564 noise_emb: &CudaSlice<f32>,
565 pos: &[i32],
566 ctx: usize,
567 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
568 let c = &self.cfg;
569 let (h, nh, nkv, hd) = (c.hidden, c.n_head, c.n_kv, c.head_dim);
570 let b = c.block_size;
571 assert_eq!(pos.len(), ctx + b, "pos covers ctx rows then block rows");
572
573 let pos_blk = e.htod_i32(&pos[ctx..])?;
574
575 let mut x = e.clone_dtod(noise_emb)?; for (li, l) in self.layers.iter().enumerate() {
577 let _ = li;
578 let mut xn = e.uninit(b * h)?;
581 e.rms_norm(&x, &l.ln_in, &mut xn, h, b, c.eps)?;
582
583 let q0 = self.mm(e, &l.wq, &xn, b, h, nh * hd)?;
585 let k0c = self.mm(e, &l.wk, ctx_f, ctx, h, nkv * hd)?;
586 let v0c = self.mm(e, &l.wv, ctx_f, ctx, h, nkv * hd)?;
587 let k0b = self.mm(e, &l.wk, &xn, b, h, nkv * hd)?;
588 let v0b = self.mm(e, &l.wv, &xn, b, h, nkv * hd)?;
589
590 let mut k0 = e.uninit((ctx + b) * nkv * hd)?;
593 e.copy_into(&mut k0, 0, &k0c, ctx * nkv * hd)?;
594 e.copy_into(&mut k0, ctx * nkv * hd, &k0b, b * nkv * hd)?;
595 let mut v = e.uninit((ctx + b) * nkv * hd)?;
596 e.copy_into(&mut v, 0, &v0c, ctx * nkv * hd)?;
597 e.copy_into(&mut v, ctx * nkv * hd, &v0b, b * nkv * hd)?;
598
599 if li == 0 {
600 if let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP") {
601 let v = e.dtoh(&q0)?;
602 let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
603 std::fs::write(format!("{dir}/memra-l0_q0.f32"), bytes)?;
604 }
605 }
606 let mut q = e.uninit(b * nh * hd)?;
607 let mut k = e.uninit((ctx + b) * nkv * hd)?;
608 e.rms_norm(&q0, &l.q_norm, &mut q, hd, b * nh, c.eps)?;
610 if li == 0 {
611 if let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP") {
612 let v = e.dtoh(&q)?;
613 let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
614 std::fs::write(format!("{dir}/memra-l0_qn.f32"), bytes)?;
615 }
616 }
617 e.rms_norm(&k0, &l.k_norm, &mut k, hd, (ctx + b) * nkv, c.eps)?;
618
619 let norope = std::env::var("MEMRA_DFLASH_NOROPE").is_ok();
621 if !norope {
622 self.rope_rows(e, &mut q, &pos_blk, nh, b)?;
623 }
624 if li == 0 {
625 if let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP") {
626 let dump = |name: &str,
627 t: &cudarc::driver::CudaSlice<f32>|
628 -> Result<(), Box<dyn std::error::Error>> {
629 let v = e.dtoh(t)?;
630 let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
631 std::fs::write(format!("{dir}/memra-l0_{name}.f32"), bytes)?;
632 Ok(())
633 };
634 dump("xn", &xn)?;
635 dump("q_prerope", &q)?;
636 }
637 }
638 let pos_all = e.htod_i32(pos)?;
641 if !norope {
642 self.rope_rows(e, &mut k, &pos_all, nkv, ctx + b)?;
643 }
644
645 let mut attn = e.uninit(b * nh * hd)?;
647 let scale = 1.0f32 / (hd as f32).sqrt();
648 if std::env::var("MEMRA_DFLASH_FA").is_ok() {
654 e.fa_prefill(&q, &k, &v, &mut attn, hd, nh, nkv, b, ctx + b, scale, false)?;
655 } else {
656 e.sdpa_naive(&q, &k, &v, &mut attn, hd, nh, nkv, b, ctx + b, scale, false)?;
657 }
658
659 let o = self.mm(e, &l.wo, &attn, b, nh * hd, h)?;
660 let mut x1 = e.uninit(b * h)?;
661 e.add(&o, &x, &mut x1, b * h)?;
662 if li == 0 {
663 if let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP") {
664 let dump = |name: &str,
665 t: &cudarc::driver::CudaSlice<f32>|
666 -> Result<(), Box<dyn std::error::Error>> {
667 let v = e.dtoh(t)?;
668 let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
669 std::fs::write(format!("{dir}/memra-l0_{name}.f32"), bytes)?;
670 Ok(())
671 };
672 dump("q", &q)?;
673 dump("k", &k)?;
674 dump("attn", &attn)?;
675 dump("x1", &x1)?;
676 }
677 }
678
679 let mut x1n = e.uninit(b * h)?;
681 e.rms_norm(&x1, &l.ln_post, &mut x1n, h, b, c.eps)?;
682 let gate = self.mm(e, &l.w_gate, &x1n, b, h, c.n_ff)?;
683 let up_ = self.mm(e, &l.w_up, &x1n, b, h, c.n_ff)?;
684 let mut act = e.uninit(b * c.n_ff)?;
685 e.silu_mul(&gate, &up_, &mut act, b * c.n_ff)?;
686 let down = self.mm(e, &l.w_down, &act, b, c.n_ff, h)?;
687 let mut x2 = e.uninit(b * h)?;
688 e.add(&down, &x1, &mut x2, b * h)?;
689 x = x2;
690 if let Ok(dir) = std::env::var("MEMRA_DFLASH_DUMP") {
691 let v = e.dtoh(&x)?;
692 let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
693 std::fs::write(format!("{dir}/memra-layer{li}_out.f32"), bytes)?;
694 }
695 }
696 let mut out = e.uninit(b * h)?;
697 e.rms_norm(&x, &self.norm, &mut out, h, b, c.eps)?;
698 Ok(out)
699 }
700}
701
702pub struct DflashKv {
707 pub k: Vec<CudaSlice<f32>>, pub v: Vec<CudaSlice<f32>>,
709 pub len: usize,
710 pub cap: usize,
711}
712
713impl DflashKv {
714 pub fn new(
715 e: &Engine,
716 cfg: &DflashCfg,
717 cap: usize,
718 ) -> Result<Self, Box<dyn std::error::Error>> {
719 let rowsz = cfg.n_kv * cfg.head_dim;
720 let mut k = Vec::with_capacity(cfg.n_layer);
721 let mut v = Vec::with_capacity(cfg.n_layer);
722 for _ in 0..cfg.n_layer {
723 k.push(e.uninit((cap + cfg.block_size) * rowsz)?);
724 v.push(e.uninit((cap + cfg.block_size) * rowsz)?);
725 }
726 Ok(Self { k, v, len: 0, cap })
727 }
728}
729
730impl DflashDraft {
731 pub fn ingest_ctx(
734 &self,
735 e: &Engine,
736 kv: &mut DflashKv,
737 feats: &CudaSlice<f32>,
738 pos_new: &[i32],
739 t: usize,
740 ) -> Result<(), Box<dyn std::error::Error>> {
741 let c = &self.cfg;
742 let (h, nkv, hd) = (c.hidden, c.n_kv, c.head_dim);
743 assert!(kv.len + t <= kv.cap, "draft kv overflow");
744 let pos_d = e.htod_i32(pos_new)?;
745 for (li, l) in self.layers.iter().enumerate() {
746 let k0 = self.mm(e, &l.wk, feats, t, h, nkv * hd)?;
747 let v0 = self.mm(e, &l.wv, feats, t, h, nkv * hd)?;
748 let mut kn = e.uninit(t * nkv * hd)?;
749 e.rms_norm(&k0, &l.k_norm, &mut kn, hd, t * nkv, c.eps)?;
750 self.rope_rows(e, &mut kn, &pos_d, nkv, t)?;
751 e.copy_into(&mut kv.k[li], kv.len * nkv * hd, &kn, t * nkv * hd)?;
752 e.copy_into(&mut kv.v[li], kv.len * nkv * hd, &v0, t * nkv * hd)?;
753 }
754 kv.len += t;
755 Ok(())
756 }
757
758 pub fn forward_round(
762 &self,
763 e: &Engine,
764 kv: &mut DflashKv,
765 noise_emb: &CudaSlice<f32>,
766 pos_block: &[i32],
767 ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
768 let c = &self.cfg;
769 let (h, nh, nkv, hd) = (c.hidden, c.n_head, c.n_kv, c.head_dim);
770 let b = c.block_size;
771 assert_eq!(pos_block.len(), b);
772 let ctx = kv.len;
773 let pos_blk = e.htod_i32(pos_block)?;
774 let mut x = e.clone_dtod(noise_emb)?;
775 for (li, l) in self.layers.iter().enumerate() {
776 let mut xn = e.uninit(b * h)?;
777 e.rms_norm(&x, &l.ln_in, &mut xn, h, b, c.eps)?;
778 let q0 = self.mm(e, &l.wq, &xn, b, h, nh * hd)?;
779 let k0b = self.mm(e, &l.wk, &xn, b, h, nkv * hd)?;
780 let v0b = self.mm(e, &l.wv, &xn, b, h, nkv * hd)?;
781 let mut q = e.uninit(b * nh * hd)?;
782 let mut kb = e.uninit(b * nkv * hd)?;
783 e.rms_norm(&q0, &l.q_norm, &mut q, hd, b * nh, c.eps)?;
784 e.rms_norm(&k0b, &l.k_norm, &mut kb, hd, b * nkv, c.eps)?;
785 self.rope_rows(e, &mut q, &pos_blk, nh, b)?;
786 self.rope_rows(e, &mut kb, &pos_blk, nkv, b)?;
787 e.copy_into(&mut kv.k[li], ctx * nkv * hd, &kb, b * nkv * hd)?;
788 e.copy_into(&mut kv.v[li], ctx * nkv * hd, &v0b, b * nkv * hd)?;
789 let mut attn = e.uninit(b * nh * hd)?;
790 let scale = 1.0f32 / (hd as f32).sqrt();
791 if std::env::var("MEMRA_DFLASH_FA").is_ok() {
792 e.fa_prefill(
793 &q,
794 &kv.k[li],
795 &kv.v[li],
796 &mut attn,
797 hd,
798 nh,
799 nkv,
800 b,
801 ctx + b,
802 scale,
803 false,
804 )?;
805 } else {
806 e.sdpa_naive(
807 &q,
808 &kv.k[li],
809 &kv.v[li],
810 &mut attn,
811 hd,
812 nh,
813 nkv,
814 b,
815 ctx + b,
816 scale,
817 false,
818 )?;
819 }
820 let o = self.mm(e, &l.wo, &attn, b, nh * hd, h)?;
821 let mut x1 = e.uninit(b * h)?;
822 e.add(&o, &x, &mut x1, b * h)?;
823 let mut x1n = e.uninit(b * h)?;
824 e.rms_norm(&x1, &l.ln_post, &mut x1n, h, b, c.eps)?;
825 let gate = self.mm(e, &l.w_gate, &x1n, b, h, c.n_ff)?;
826 let up_ = self.mm(e, &l.w_up, &x1n, b, h, c.n_ff)?;
827 let mut act = e.uninit(b * c.n_ff)?;
828 e.silu_mul(&gate, &up_, &mut act, b * c.n_ff)?;
829 let down = self.mm(e, &l.w_down, &act, b, c.n_ff, h)?;
830 let mut x2 = e.uninit(b * h)?;
831 e.add(&down, &x1, &mut x2, b * h)?;
832 x = x2;
833 }
834 let mut out = e.uninit(b * h)?;
835 e.rms_norm(&x, &self.norm, &mut out, h, b, c.eps)?;
836 Ok(out)
837 }
838}
839
840impl crate::hybrid::HybridModel {
846 pub fn generate_spec_dflash(
847 &self,
848 e: &Engine,
849 draft: &DflashDraft,
850 prompt: &[u32],
851 max_new: usize,
852 eos: &[u32],
853 ) -> Result<Vec<u32>, Box<dyn std::error::Error>> {
854 use crate::cache::{Cache, DflashTapSink};
855 let n_embd = self.cfg.n_embd as usize;
856 let c = &draft.cfg;
857 assert_eq!(n_embd, c.hidden, "draft hidden must match target n_embd");
858 let b = c.block_size;
859 let n_taps = c.target_layer_ids.len();
860 let max_ctx = prompt.len() + max_new + b + 8;
861 assert!(
865 max_ctx <= c.sliding_window,
866 "first-light dflash round is windowless — ctx cap {} exceeds the draft window {}",
867 max_ctx,
868 c.sliding_window
869 );
870 let mut cache = Cache::new(e, &self.cfg, max_ctx)?;
871
872 let tp = prompt.len();
874 cache.dflash_taps = Some(DflashTapSink {
875 layer_ids: c.target_layer_ids.clone(),
876 buf: e.uninit(tp * n_taps * n_embd)?,
877 hidden: n_embd,
878 t: tp,
879 base: 0,
880 });
881 let t_prime = std::time::Instant::now();
882 let (logits, _h_seed, _hiddens) = self.prime_cache(e, prompt, &mut cache, 0)?;
883 let mut last = crate::forward::argmax(&logits) as u32;
884 let mut dkv = DflashKv::new(e, &draft.cfg, max_ctx)?;
887 {
888 let taps = cache.dflash_taps.take().unwrap();
893 let n_taps_h = n_taps * n_embd;
894 let mut r0 = 0usize;
895 while r0 < tp {
896 let t_c = (tp - r0).min(256);
897 let tv = e.view(&taps.buf, tp * n_taps_h);
898 let win = tv.slice(r0 * n_taps_h..(r0 + t_c) * n_taps_h);
899 let mut chunk = e.uninit(t_c * n_taps_h)?;
900 e.copy_view_into(&mut chunk, 0, &win, t_c * n_taps_h)?;
901 let f = draft.ctx_features(e, &chunk, t_c)?;
902 let pos_c: Vec<i32> = ((r0 as i32)..(r0 + t_c) as i32).collect();
903 draft.ingest_ctx(e, &mut dkv, &f, &pos_c, t_c)?;
904 r0 += t_c;
905 }
906 }
907 let mut ctx_len = tp;
908 e.stream().synchronize()?;
909 crate::PRIME_NANOS.store(
911 t_prime.elapsed().as_nanos() as u64,
912 std::sync::atomic::Ordering::Relaxed,
913 );
914
915 let emb_scale = if std::env::var("MEMRA_DFLASH_EMB_SCALE").as_deref() == Ok("1") {
920 (n_embd as f32).sqrt()
921 } else {
922 1.0
923 };
924
925 let mut out = Vec::with_capacity(max_new);
926 let n_vocab = self.output.out_features();
927 let vt_cap: usize = std::env::var("MEMRA_DFLASH_VERIFY_T")
934 .ok()
935 .and_then(|v| v.parse().ok())
936 .unwrap_or(8)
937 .clamp(2, b);
938 let adapt = std::env::var("MEMRA_DFLASH_ADAPT").as_deref() != Ok("0");
941 let mut vt = vt_cap;
942 let mut attempted = 0usize;
943 let mut accepted = 0usize;
944 e.set_verify_exact(true);
948 'outer: while out.len() < max_new {
949 let start = cache.pos; let mut block: Vec<u32> = vec![c.mask_token_id; b];
952 block[0] = last;
953 let mut noise = e.htod(&self.embd.gather(n_embd, &block))?;
954 if emb_scale != 1.0 {
955 e.scale_inplace(&mut noise, emb_scale, b * n_embd)?;
956 }
957 if std::env::var("MEMRA_DFLASH_DEBUG").as_deref() == Ok("1") && start == cache.pos {
958 let nv = e.dtoh(&noise)?;
959 let r0: f32 = nv[..n_embd].iter().map(|x| x * x).sum::<f32>().sqrt();
960 let r1: f32 = nv[n_embd..2 * n_embd]
961 .iter()
962 .map(|x| x * x)
963 .sum::<f32>()
964 .sqrt();
965 eprintln!(
966 "[dflash noise] |row0(last)|={r0:.3} |row1(MASK id {})|={r1:.3}",
967 c.mask_token_id
968 );
969 }
970 let pos_block: Vec<i32> = ((start as i32)..(start + b) as i32).collect();
971 let dh = draft.forward_round(e, &mut dkv, &noise, &pos_block)?;
972 let mut rows = e.uninit((b - 1) * n_embd)?;
974 {
975 let dv = e.view(&dh, b * n_embd);
976 let tail = dv.slice(n_embd..b * n_embd);
977 e.copy_view_into(&mut rows, 0, &tail, (b - 1) * n_embd)?;
978 }
979 let mut dl = e.matmul(&self.output, &rows, b - 1)?;
980 let markov_on = std::env::var("MEMRA_DFLASH_MARKOV").as_deref() != Ok("0");
986 let mut chain_d = e.stream().alloc_zeros::<u32>(b)?;
987 if let (Some(mk), true) = (&draft.markov, markov_on) {
988 e.set_u32_one(&mut chain_d, last)?;
989 for k in 0..(b - 1) {
990 let mut f = e.uninit(mk.rank)?;
991 e.gather_row_bf16(&mk.w1_bf16, &chain_d, k, &mut f, mk.rank)?;
992 let bias = e.matmul(&mk.w2, &f, 1)?;
993 e.add_row_inplace(&mut dl, &bias, n_vocab, k * n_vocab)?;
994 e.argmax_token_device_col(&dl, k, n_vocab, &mut chain_d, k + 1)?;
995 }
996 } else {
997 for i in 0..(b - 1) {
998 e.argmax_token_device_col(&dl, i, n_vocab, &mut chain_d, i + 1)?;
999 }
1000 }
1001 let chain = e.dtoh_u32(&chain_d)?;
1002 let dtoks = &chain[1..];
1003 for (i, &dt) in dtoks.iter().enumerate() {
1004 block[i + 1] = dt;
1005 }
1006 let dbg = std::env::var("MEMRA_DFLASH_DEBUG").as_deref() == Ok("1");
1007
1008 let vblock = &block[..vt];
1010 cache.dflash_taps = Some(DflashTapSink {
1011 layer_ids: c.target_layer_ids.clone(),
1012 buf: e.uninit(vt * n_taps * n_embd)?,
1013 hidden: n_embd,
1014 t: vt,
1015 base: 0,
1016 });
1017 let (vam, _vh) = self.gemma4_decode_step_t_am(e, vblock, start, &mut cache)?;
1018 let taps = cache.dflash_taps.take().unwrap();
1019 if dbg {
1020 eprintln!(
1021 "[dflash r] start={start} last={last}\n draft={:?}\n vam ={:?}",
1022 &block[1..],
1023 &vam
1024 );
1025 }
1026
1027 let mut m = 0usize;
1029 while m < vt - 1 && block[m + 1] as usize == vam[m] as usize {
1030 m += 1;
1031 }
1032 attempted += vt - 1;
1033 accepted += m;
1034 out.push(last);
1035 if eos.contains(&last) {
1036 break 'outer;
1037 }
1038 for &dt in &block[1..=m] {
1039 out.push(dt);
1040 if eos.contains(&dt) {
1041 break 'outer;
1042 }
1043 if out.len() >= max_new {
1044 break 'outer;
1045 }
1046 }
1047 let next = vam[m] as u32;
1048
1049 let keep = m + 1;
1051 for kvl in cache.kv.iter_mut().flatten() {
1052 kvl.len -= vt - keep;
1053 e.set_i32_one(&mut kvl.len_d, kvl.len as i32)?;
1054 }
1055 cache.pos -= vt - keep;
1056
1057 {
1059 let tv = e.view(&taps.buf, vt * n_taps * n_embd);
1060 let keep_view = tv.slice(0..keep * n_taps * n_embd);
1061 let mut kept = e.uninit(keep * n_taps * n_embd)?;
1062 e.copy_view_into(&mut kept, 0, &keep_view, keep * n_taps * n_embd)?;
1063 let f = draft.ctx_features(e, &kept, keep)?;
1064 let pos_k: Vec<i32> = ((ctx_len as i32)..(ctx_len + keep) as i32).collect();
1065 draft.ingest_ctx(e, &mut dkv, &f, &pos_k, keep)?;
1066 ctx_len += keep;
1067 }
1068 last = next;
1069 if adapt {
1070 vt = (m + 2).clamp(3, vt_cap);
1071 }
1072 }
1073 e.set_verify_exact(false);
1074 if std::env::var("MEMRA_SPEC_STATS").as_deref() == Ok("1") {
1075 eprintln!(
1076 "[dflash] acceptance {accepted}/{attempted} = {:.3}",
1077 accepted as f64 / attempted.max(1) as f64
1078 );
1079 }
1080 Ok(out)
1081 }
1082}
1083
1084impl crate::hybrid::HybridModel {
1092 pub fn generate_spec_dspark(
1093 &self,
1094 e: &Engine,
1095 draft: &DflashDraft,
1096 prompt: &[u32],
1097 max_new: usize,
1098 eos: &[u32],
1099 ) -> Result<Vec<u32>, Box<dyn std::error::Error>> {
1100 use crate::cache::{Cache, DflashTapSink};
1101 assert!(
1102 self.cfg.gemma4.is_none(),
1103 "gemma4 targets use generate_spec_dflash; this is the qwen-hybrid arm"
1104 );
1105 let n_embd = self.cfg.n_embd as usize;
1106 let c = &draft.cfg;
1107 assert_eq!(n_embd, c.hidden, "draft hidden must match target n_embd");
1108 let b = c.block_size;
1109 let n_taps = c.target_layer_ids.len();
1110 let max_ctx = prompt.len() + max_new + b + 8;
1111 assert!(
1112 max_ctx <= c.sliding_window,
1113 "dspark round is windowless — ctx cap {} exceeds the draft window {}",
1114 max_ctx,
1115 c.sliding_window
1116 );
1117 let mut cache = Cache::new(e, &self.cfg, max_ctx)?;
1118
1119 let tp = prompt.len();
1121 cache.dflash_taps = Some(DflashTapSink {
1122 layer_ids: c.target_layer_ids.clone(),
1123 buf: e.uninit(tp * n_taps * n_embd)?,
1124 hidden: n_embd,
1125 t: tp,
1126 base: 0,
1127 });
1128 let t_prime = std::time::Instant::now();
1129 let (logits, _h_seed, _hiddens) = self.prime_cache(e, prompt, &mut cache, 0)?;
1130 let mut last = crate::forward::argmax(&logits) as u32;
1131 let mut dkv = DflashKv::new(e, &draft.cfg, max_ctx)?;
1132 {
1133 let taps = cache.dflash_taps.take().unwrap();
1134 let n_taps_h = n_taps * n_embd;
1135 let mut r0 = 0usize;
1136 while r0 < tp {
1137 let t_c = (tp - r0).min(256);
1138 let tv = e.view(&taps.buf, tp * n_taps_h);
1139 let win = tv.slice(r0 * n_taps_h..(r0 + t_c) * n_taps_h);
1140 let mut chunk = e.uninit(t_c * n_taps_h)?;
1141 e.copy_view_into(&mut chunk, 0, &win, t_c * n_taps_h)?;
1142 let f = draft.ctx_features(e, &chunk, t_c)?;
1143 let pos_c: Vec<i32> = ((r0 as i32)..(r0 + t_c) as i32).collect();
1144 draft.ingest_ctx(e, &mut dkv, &f, &pos_c, t_c)?;
1145 r0 += t_c;
1146 }
1147 }
1148 let mut ctx_len = tp;
1149 e.stream().synchronize()?;
1150 crate::PRIME_NANOS.store(
1151 t_prime.elapsed().as_nanos() as u64,
1152 std::sync::atomic::Ordering::Relaxed,
1153 );
1154
1155 let mut out = Vec::with_capacity(max_new);
1156 let n_vocab = self.output.out_features();
1157 let vt_cap: usize = std::env::var("MEMRA_DFLASH_VERIFY_T")
1158 .ok()
1159 .and_then(|v| v.parse().ok())
1160 .unwrap_or(b)
1161 .clamp(2, b);
1162 let adapt = std::env::var("MEMRA_DFLASH_ADAPT").as_deref() != Ok("0");
1163 let mut vt = vt_cap;
1164 let mut attempted = 0usize;
1165 let mut accepted = 0usize;
1166 let (mut ns_draft, mut ns_snap, mut ns_verify, mut ns_roll, mut ns_ingest) =
1168 (0u64, 0u64, 0u64, 0u64, 0u64);
1169 let mut rounds = 0usize;
1170 let stats = std::env::var("MEMRA_SPEC_STATS").as_deref() == Ok("1");
1171 let clock = |on: bool, e: &Engine| -> std::time::Instant {
1172 if on {
1173 let _ = e.stream().synchronize();
1174 }
1175 std::time::Instant::now()
1176 };
1177 'outer: while out.len() < max_new {
1178 rounds += 1;
1179 let start = cache.pos; let t0 = clock(stats, e);
1182 e.set_verify_exact(true);
1183 let mut block: Vec<u32> = vec![c.mask_token_id; b];
1184 block[0] = last;
1185 let noise = e.htod(&self.embd.gather(n_embd, &block))?;
1186 let pos_block: Vec<i32> = ((start as i32)..(start + b) as i32).collect();
1187 let dh = draft.forward_round(e, &mut dkv, &noise, &pos_block)?;
1188 let mut rows = e.uninit((b - 1) * n_embd)?;
1189 {
1190 let dv = e.view(&dh, b * n_embd);
1191 let tail = dv.slice(n_embd..b * n_embd);
1192 e.copy_view_into(&mut rows, 0, &tail, (b - 1) * n_embd)?;
1193 }
1194 let mut dl = e.matmul(&self.output, &rows, b - 1)?;
1195 let markov_on = std::env::var("MEMRA_DFLASH_MARKOV").as_deref() != Ok("0");
1196 let mut chain_d = e.stream().alloc_zeros::<u32>(b)?;
1197 if let (Some(mk), true) = (&draft.markov, markov_on) {
1198 e.set_u32_one(&mut chain_d, last)?;
1199 for k in 0..(b - 1) {
1200 let mut f = e.uninit(mk.rank)?;
1201 e.gather_row_bf16(&mk.w1_bf16, &chain_d, k, &mut f, mk.rank)?;
1202 let bias = e.matmul(&mk.w2, &f, 1)?;
1203 e.add_row_inplace(&mut dl, &bias, n_vocab, k * n_vocab)?;
1204 e.argmax_token_device_col(&dl, k, n_vocab, &mut chain_d, k + 1)?;
1205 }
1206 } else {
1207 for i in 0..(b - 1) {
1208 e.argmax_token_device_col(&dl, i, n_vocab, &mut chain_d, i + 1)?;
1209 }
1210 }
1211 e.set_verify_exact(false);
1212 let chain = e.dtoh_u32(&chain_d)?;
1213 for (i, &dt) in chain[1..].iter().enumerate() {
1214 block[i + 1] = dt;
1215 }
1216 ns_draft += clock(stats, e).duration_since(t0).as_nanos() as u64;
1217
1218 let t1 = std::time::Instant::now();
1220 let snap = cache.snapshot(e)?;
1221 ns_snap += clock(stats, e).duration_since(t1).as_nanos() as u64;
1222 let t2 = std::time::Instant::now();
1223 let vblock = &block[..vt];
1224 cache.dflash_taps = Some(DflashTapSink {
1225 layer_ids: c.target_layer_ids.clone(),
1226 buf: e.uninit(vt * n_taps * n_embd)?,
1227 hidden: n_embd,
1228 t: vt,
1229 base: 0,
1230 });
1231 let ckpt_on = std::env::var("MEMRA_DSPARK_CKPT").as_deref() != Ok("0");
1236 let ckpt_gate = std::env::var("MEMRA_DSPARK_CKPT_GATE").as_deref() == Ok("1");
1237 let (vam, vck) = if ckpt_on || ckpt_gate {
1238 let (vam, vck) = self.dspark_verify_t_am_ckpt(e, vblock, start, &mut cache)?;
1239 (vam, Some(vck))
1240 } else {
1241 (self.dspark_verify_t_am(e, vblock, start, &mut cache)?, None)
1242 };
1243 let taps = cache.dflash_taps.take().unwrap();
1244 ns_verify += clock(stats, e).duration_since(t2).as_nanos() as u64;
1245
1246 let mut m = 0usize;
1248 while m < vt - 1 && block[m + 1] == vam[m] {
1249 m += 1;
1250 }
1251 attempted += vt - 1;
1252 accepted += m;
1253 out.push(last);
1254 if eos.contains(&last) {
1255 break 'outer;
1256 }
1257 for &dt in &block[1..=m] {
1258 if out.len() >= max_new {
1263 break 'outer;
1264 }
1265 out.push(dt);
1266 if eos.contains(&dt) {
1267 break 'outer;
1268 }
1269 }
1270 let next = vam[m];
1271
1272 let keep = m + 1;
1274 let t3 = std::time::Instant::now();
1275 if keep < vt {
1276 if ckpt_gate {
1277 let vck = vck.as_ref().expect("gate arm always fills the ckpt");
1281 self.dspark_commit_prefix(e, &mut cache, &snap, vck, keep)?;
1282 let capture = |cache: &Cache| -> Result<
1285 (usize, Vec<Option<usize>>, Vec<(Vec<f32>, Vec<f32>)>),
1286 Box<dyn std::error::Error>,
1287 > {
1288 let mut lens = Vec::new();
1289 let mut states = Vec::new();
1290 for il in 0..cache.kv.len() {
1291 lens.push(cache.kv[il].as_ref().map(|k| k.len));
1292 if let Some(rl) = &cache.recur[il] {
1293 states.push((e.dtoh(&rl.conv_state)?, e.dtoh(&rl.ssm_state)?));
1294 }
1295 }
1296 Ok((cache.pos, lens, states))
1297 };
1298 let (p1, l1, st1) = capture(&cache)?;
1299 crate::pp::restore_cache_checkpoint(e, &self.cfg, None, &mut cache, &snap)?;
1300 let ram = self.dspark_verify_t_am(e, &block[..keep], start, &mut cache)?;
1301 assert_eq!(
1302 &ram[..],
1303 &vam[..keep],
1304 "prefix replay must reproduce the verify argmaxes"
1305 );
1306 let (p2, l2, st2) = capture(&cache)?;
1307 assert_eq!(p1, p2, "ckpt-gate: pos mismatch");
1308 assert_eq!(l1, l2, "ckpt-gate: kv_len mismatch");
1309 for (il, ((c1, s1v), (c2, s2v))) in st1.iter().zip(&st2).enumerate() {
1310 let bits = |a: &[f32], b: &[f32]| {
1311 a.iter().zip(b).all(|(x, y)| x.to_bits() == y.to_bits())
1312 };
1313 assert!(
1314 bits(c1, c2),
1315 "ckpt-gate: linear layer {il} conv state differs"
1316 );
1317 assert!(
1318 bits(s1v, s2v),
1319 "ckpt-gate: linear layer {il} ssm state differs"
1320 );
1321 }
1322 } else if let Some(vck) = vck.as_ref() {
1323 self.dspark_commit_prefix(e, &mut cache, &snap, vck, keep)?;
1325 } else {
1326 crate::pp::restore_cache_checkpoint(e, &self.cfg, None, &mut cache, &snap)?;
1328 debug_assert_eq!(cache.pos, start, "rollback landed off the round start");
1329 let ram = self.dspark_verify_t_am(e, &block[..keep], start, &mut cache)?;
1330 debug_assert_eq!(
1331 &ram[..],
1332 &vam[..keep],
1333 "prefix replay must reproduce the verify argmaxes"
1334 );
1335 }
1336 }
1337 ns_roll += clock(stats, e).duration_since(t3).as_nanos() as u64;
1338
1339 let t4 = std::time::Instant::now();
1341 {
1342 let tv = e.view(&taps.buf, vt * n_taps * n_embd);
1343 let keep_view = tv.slice(0..keep * n_taps * n_embd);
1344 let mut kept = e.uninit(keep * n_taps * n_embd)?;
1345 e.copy_view_into(&mut kept, 0, &keep_view, keep * n_taps * n_embd)?;
1346 let f = draft.ctx_features(e, &kept, keep)?;
1347 let pos_k: Vec<i32> = ((ctx_len as i32)..(ctx_len + keep) as i32).collect();
1348 draft.ingest_ctx(e, &mut dkv, &f, &pos_k, keep)?;
1349 ctx_len += keep;
1350 }
1351 ns_ingest += clock(stats, e).duration_since(t4).as_nanos() as u64;
1352 last = next;
1353 if adapt {
1354 vt = (m + 2).clamp(3, vt_cap);
1355 }
1356 }
1357 if stats {
1358 let ms = |n: u64| n as f64 / 1e6;
1359 eprintln!(
1360 "[dspark-q38] acceptance {accepted}/{attempted} = {:.3} rounds={rounds} \
1361 draft={:.1}ms snap={:.1}ms verify={:.1}ms rollback+replay={:.1}ms ingest={:.1}ms",
1362 accepted as f64 / attempted.max(1) as f64,
1363 ms(ns_draft),
1364 ms(ns_snap),
1365 ms(ns_verify),
1366 ms(ns_roll),
1367 ms(ns_ingest)
1368 );
1369 }
1370 Ok(out)
1371 }
1372}
1373
1374pub struct DsparkSpecSession {
1384 pub cache: crate::cache::Cache,
1385 dkv: DflashKv,
1386 last: u32,
1387 ctx_len: usize,
1388 vt: usize,
1389 pub rounds: usize,
1390 max_ctx: usize,
1391 done: bool,
1392}
1393
1394impl DsparkSpecSession {
1395 pub fn cache_max_ctx(&self) -> usize {
1396 self.max_ctx
1397 }
1398 pub fn finished(&self) -> bool {
1399 self.done
1400 }
1401 pub fn pos(&self) -> usize {
1402 self.cache.pos
1403 }
1404}
1405
1406impl crate::hybrid::HybridModel {
1407 pub fn dspark_spec_session_new(
1411 &self,
1412 e: &Engine,
1413 draft: &DflashDraft,
1414 prompt: &[u32],
1415 ctx_cap: usize,
1416 ) -> Result<DsparkSpecSession, Box<dyn std::error::Error>> {
1417 use crate::cache::{Cache, DflashTapSink};
1418 assert!(
1419 self.cfg.gemma4.is_none(),
1420 "gemma4 targets use the assistant-drafter route; dspark is the qwen-hybrid arm"
1421 );
1422 let n_embd = self.cfg.n_embd as usize;
1423 let c = &draft.cfg;
1424 assert_eq!(n_embd, c.hidden, "draft hidden must match target n_embd");
1425 let b = c.block_size;
1426 let n_taps = c.target_layer_ids.len();
1427 let max_ctx = ctx_cap.min(c.sliding_window);
1431 if prompt.len() + b + 8 > max_ctx {
1432 return Err(format!(
1433 "dspark session needs {} ctx (prompt {} + block {b} + 8), cap {max_ctx}",
1434 prompt.len() + b + 8,
1435 prompt.len()
1436 )
1437 .into());
1438 }
1439 let mut cache = Cache::new(e, &self.cfg, max_ctx)?;
1440 let tp = prompt.len();
1441 cache.dflash_taps = Some(DflashTapSink {
1442 layer_ids: c.target_layer_ids.clone(),
1443 buf: e.uninit(tp * n_taps * n_embd)?,
1444 hidden: n_embd,
1445 t: tp,
1446 base: 0,
1447 });
1448 let (logits, _h_seed, _hiddens) = self.prime_cache(e, prompt, &mut cache, 0)?;
1449 let last = crate::forward::argmax(&logits) as u32;
1450 let mut dkv = DflashKv::new(e, &draft.cfg, max_ctx)?;
1451 {
1452 let taps = cache.dflash_taps.take().unwrap();
1453 let n_taps_h = n_taps * n_embd;
1454 let mut r0 = 0usize;
1455 while r0 < tp {
1456 let t_c = (tp - r0).min(256);
1457 let tv = e.view(&taps.buf, tp * n_taps_h);
1458 let win = tv.slice(r0 * n_taps_h..(r0 + t_c) * n_taps_h);
1459 let mut chunk = e.uninit(t_c * n_taps_h)?;
1460 e.copy_view_into(&mut chunk, 0, &win, t_c * n_taps_h)?;
1461 let f = draft.ctx_features(e, &chunk, t_c)?;
1462 let pos_c: Vec<i32> = ((r0 as i32)..(r0 + t_c) as i32).collect();
1463 draft.ingest_ctx(e, &mut dkv, &f, &pos_c, t_c)?;
1464 r0 += t_c;
1465 }
1466 }
1467 e.stream().synchronize()?;
1468 let vt_cap: usize = std::env::var("MEMRA_DFLASH_VERIFY_T")
1469 .ok()
1470 .and_then(|v| v.parse().ok())
1471 .unwrap_or(b)
1472 .clamp(2, b);
1473 Ok(DsparkSpecSession {
1474 cache,
1475 dkv,
1476 last,
1477 ctx_len: tp,
1478 vt: vt_cap,
1479 rounds: 0,
1480 max_ctx,
1481 done: false,
1482 })
1483 }
1484
1485 pub fn dspark_spec_session_burst(
1490 &self,
1491 e: &Engine,
1492 draft: &DflashDraft,
1493 sess: &mut DsparkSpecSession,
1494 burst_target: usize,
1495 eos: &[u32],
1496 ) -> Result<(Vec<u32>, usize, usize), Box<dyn std::error::Error>> {
1497 use crate::cache::DflashTapSink;
1498 let n_embd = self.cfg.n_embd as usize;
1499 let c = &draft.cfg;
1500 let b = c.block_size;
1501 let n_taps = c.target_layer_ids.len();
1502 let n_vocab = self.output.out_features();
1503 let vt_cap: usize = std::env::var("MEMRA_DFLASH_VERIFY_T")
1504 .ok()
1505 .and_then(|v| v.parse().ok())
1506 .unwrap_or(b)
1507 .clamp(2, b);
1508 let adapt = std::env::var("MEMRA_DFLASH_ADAPT").as_deref() != Ok("0");
1509 let mut out: Vec<u32> = Vec::with_capacity(burst_target + b);
1510 let mut drafted = 0usize;
1511 let mut accepted_n = 0usize;
1512 'outer: while out.len() < burst_target && !sess.done {
1513 let start = sess.cache.pos;
1514 if start + b + 1 > sess.max_ctx {
1515 sess.done = true;
1516 break;
1517 }
1518 sess.rounds += 1;
1519 let vt = sess.vt;
1520 e.set_verify_exact(true);
1522 let mut block: Vec<u32> = vec![c.mask_token_id; b];
1523 block[0] = sess.last;
1524 let noise = e.htod(&self.embd.gather(n_embd, &block))?;
1525 let pos_block: Vec<i32> = ((start as i32)..(start + b) as i32).collect();
1526 let dh = draft.forward_round(e, &mut sess.dkv, &noise, &pos_block)?;
1527 let mut rows = e.uninit((b - 1) * n_embd)?;
1528 {
1529 let dv = e.view(&dh, b * n_embd);
1530 let tail = dv.slice(n_embd..b * n_embd);
1531 e.copy_view_into(&mut rows, 0, &tail, (b - 1) * n_embd)?;
1532 }
1533 let mut dl = e.matmul(&self.output, &rows, b - 1)?;
1534 let markov_on = std::env::var("MEMRA_DFLASH_MARKOV").as_deref() != Ok("0");
1535 let mut chain_d = e.stream().alloc_zeros::<u32>(b)?;
1536 if let (Some(mk), true) = (&draft.markov, markov_on) {
1537 e.set_u32_one(&mut chain_d, sess.last)?;
1538 for k in 0..(b - 1) {
1539 let mut f = e.uninit(mk.rank)?;
1540 e.gather_row_bf16(&mk.w1_bf16, &chain_d, k, &mut f, mk.rank)?;
1541 let bias = e.matmul(&mk.w2, &f, 1)?;
1542 e.add_row_inplace(&mut dl, &bias, n_vocab, k * n_vocab)?;
1543 e.argmax_token_device_col(&dl, k, n_vocab, &mut chain_d, k + 1)?;
1544 }
1545 } else {
1546 for i in 0..(b - 1) {
1547 e.argmax_token_device_col(&dl, i, n_vocab, &mut chain_d, i + 1)?;
1548 }
1549 }
1550 e.set_verify_exact(false);
1551 let chain = e.dtoh_u32(&chain_d)?;
1552 for (i, &dt) in chain[1..].iter().enumerate() {
1553 block[i + 1] = dt;
1554 }
1555
1556 let snap = sess.cache.snapshot(e)?;
1558 let vblock = &block[..vt];
1559 sess.cache.dflash_taps = Some(DflashTapSink {
1560 layer_ids: c.target_layer_ids.clone(),
1561 buf: e.uninit(vt * n_taps * n_embd)?,
1562 hidden: n_embd,
1563 t: vt,
1564 base: 0,
1565 });
1566 let ckpt_on = std::env::var("MEMRA_DSPARK_CKPT").as_deref() != Ok("0");
1567 let (vam, vck) = if ckpt_on {
1568 let (vam, vck) = self.dspark_verify_t_am_ckpt(e, vblock, start, &mut sess.cache)?;
1569 (vam, Some(vck))
1570 } else {
1571 (
1572 self.dspark_verify_t_am(e, vblock, start, &mut sess.cache)?,
1573 None,
1574 )
1575 };
1576 let taps = sess.cache.dflash_taps.take().unwrap();
1577
1578 let mut m = 0usize;
1580 while m < vt - 1 && block[m + 1] == vam[m] {
1581 m += 1;
1582 }
1583 drafted += vt - 1;
1584 accepted_n += m;
1585 out.push(sess.last);
1586 if eos.contains(&sess.last) {
1587 sess.done = true;
1588 break 'outer;
1589 }
1590 for &dt in &block[1..=m] {
1591 out.push(dt);
1592 if eos.contains(&dt) {
1593 sess.done = true;
1594 break 'outer;
1595 }
1596 }
1597 let next = vam[m];
1598
1599 let keep = m + 1;
1601 if keep < vt {
1602 if let Some(vck) = vck.as_ref() {
1603 self.dspark_commit_prefix(e, &mut sess.cache, &snap, vck, keep)?;
1604 } else {
1605 crate::pp::restore_cache_checkpoint(
1606 e,
1607 &self.cfg,
1608 None,
1609 &mut sess.cache,
1610 &snap,
1611 )?;
1612 debug_assert_eq!(sess.cache.pos, start, "rollback landed off the round start");
1613 let ram = self.dspark_verify_t_am(e, &block[..keep], start, &mut sess.cache)?;
1614 debug_assert_eq!(
1615 &ram[..],
1616 &vam[..keep],
1617 "prefix replay must reproduce the verify argmaxes"
1618 );
1619 }
1620 }
1621
1622 {
1624 let tv = e.view(&taps.buf, vt * n_taps * n_embd);
1625 let keep_view = tv.slice(0..keep * n_taps * n_embd);
1626 let mut kept = e.uninit(keep * n_taps * n_embd)?;
1627 e.copy_view_into(&mut kept, 0, &keep_view, keep * n_taps * n_embd)?;
1628 let f = draft.ctx_features(e, &kept, keep)?;
1629 let pos_k: Vec<i32> =
1630 ((sess.ctx_len as i32)..(sess.ctx_len + keep) as i32).collect();
1631 draft.ingest_ctx(e, &mut sess.dkv, &f, &pos_k, keep)?;
1632 sess.ctx_len += keep;
1633 }
1634 sess.last = next;
1635 if adapt {
1636 sess.vt = (m + 2).clamp(3, vt_cap);
1637 }
1638 }
1639 Ok((out, drafted, accepted_n))
1640 }
1641}