1#![allow(
44 clippy::manual_is_multiple_of,
45 clippy::collapsible_if,
46 clippy::needless_range_loop,
47 clippy::too_many_arguments,
48 clippy::unnecessary_unwrap,
49 clippy::needless_question_mark,
50 clippy::needless_option_as_deref,
51 clippy::extend_with_drain,
52 clippy::type_complexity,
53 clippy::large_enum_variant
54)]
55
56use std::os::raw::c_void;
57
58use cudarc::driver::{CudaSlice, CudaView, DevicePtr, DevicePtrMut, LaunchConfig, PushKernelArg};
59use memra_gguf::model_plan::{
60 AttentionPlan, FullAttentionPlan, GatedDeltaNetPlan, GdnGateActivation, MicroBlockIndexPlan,
61 MlpPlan, ModelPlan, MoeMlpPlan, PleEmbeddingPlan, ResidualTopology, RopeFactors, RopePlan,
62 RouterPlan, TensorPresence, yarn_attention_factor, yarn_frequency_divisors,
63};
64use memra_gguf::tensor_contract::{LayerTensor, TensorId};
65use memra_reference::{ReferenceTensor, ReferenceWeights};
66
67use crate::Engine;
68
69type Res<T> = Result<T, Box<dyn std::error::Error>>;
70
71struct GateW {
77 norm: Vec<CudaSlice<f32>>,
79 norm_stack: CudaSlice<f32>,
82 down: Vec<CudaSlice<f32>>,
84 up: Vec<CudaSlice<f32>>,
86 inject: Option<CudaSlice<f32>>,
90 down_b16: Option<CudaSlice<u8>>,
95 up_b16: Option<CudaSlice<u8>>,
96 inject_b16: Option<CudaSlice<u8>>,
97}
98
99fn gate_rank(gate: &GateW, hidden: usize, streams: usize) -> Res<usize> {
102 if gate.down[0].len() >= hidden {
103 return Ok(gate.down[0].len() / hidden);
104 }
105 match gate.down_b16.as_ref() {
106 Some(w) => Ok(w.len() / (2 * streams * hidden)),
107 None => Err("qwen4exp_gpu: gate rank underivable (f32 dropped and no bf16 twin)".into()),
108 }
109}
110
111struct QsaW {
112 attn: FullAttentionPlan,
113 overlay: MicroBlockIndexPlan,
114 wq: CudaSlice<f32>, wk: CudaSlice<f32>, wv: CudaSlice<f32>, wo: CudaSlice<f32>, q_norm: Option<CudaSlice<f32>>,
119 k_norm: Option<CudaSlice<f32>>,
120 idx_proj: CudaSlice<f32>, idx_q_norm: Vec<f32>,
124 idx_k_norm: Vec<f32>,
125 proj_b16: Option<CudaSlice<u8>>,
128 wo_b16: Option<CudaSlice<u8>>,
129 yarn: Option<YarnRopeW>,
131}
132
133struct YarnRopeW {
141 ff: CudaSlice<f32>,
142 ff_host: Vec<f32>,
143 mscale: f32,
144}
145
146fn build_yarn(
151 e: &Engine,
152 rope: &RopePlan,
153 overlay: Option<&MicroBlockIndexPlan>,
154 layer: u32,
155) -> Res<Option<YarnRopeW>> {
156 match rope.factors {
157 RopeFactors::None => Ok(None),
158 RopeFactors::Yarn {
159 factor,
160 original_context,
161 beta_fast,
162 beta_slow,
163 } => {
164 if let Some(overlay) = overlay
165 && overlay.rope_dimensions != rope.dimensions
166 {
167 return Err(format!(
168 "qwen4exp_gpu: layer {layer} indexer rope width {} != attention rope \
169 width {} — the shared yarn table would be wrong",
170 overlay.rope_dimensions, rope.dimensions
171 )
172 .into());
173 }
174 let ff_host = yarn_frequency_divisors(
175 rope.dimensions,
176 rope.base,
177 factor,
178 original_context,
179 beta_fast,
180 beta_slow,
181 );
182 Ok(Some(YarnRopeW {
183 ff: e.htod(&ff_host)?,
184 ff_host,
185 mscale: yarn_attention_factor(factor),
186 }))
187 }
188 _ => Err(format!(
189 "qwen4exp_gpu: layer {layer}: only plain or yarn rope factors are supported"
190 )
191 .into()),
192 }
193}
194
195struct GdnW {
196 plan: GatedDeltaNetPlan,
197 qkv: CudaSlice<f32>, z: CudaSlice<f32>, beta: CudaSlice<f32>, alpha: CudaSlice<f32>, conv_w: CudaSlice<f32>, a: CudaSlice<f32>, dt: CudaSlice<f32>, norm: CudaSlice<f32>, out: CudaSlice<f32>, proj_b16: Option<CudaSlice<u8>>,
211 out_b16: Option<CudaSlice<u8>>,
212}
213
214enum MixerW {
215 Qsa(QsaW),
216 Gdn(GdnW),
217}
218
219enum BankHalf {
228 F32(CudaSlice<f32>),
229 Nvfp4 {
230 codes: CudaSlice<u8>,
231 scales: CudaSlice<u8>,
232 macros: Vec<f32>,
233 macros_dev: CudaSlice<f32>,
236 },
237 HostBf16(Vec<u8>),
244 DeviceBf16(CudaSlice<u8>),
251}
252
253struct ExpertBank {
254 gate: BankHalf, up: BankHalf, down: BankHalf, }
258
259struct MoeW {
260 plan: MoeMlpPlan,
261 router: CudaSlice<f32>, router_b16: Option<CudaSlice<u8>>,
264 bank: ExpertBank,
265 shared_gate: CudaSlice<f32>,
266 shared_up: CudaSlice<f32>,
267 shared_down: CudaSlice<f32>,
268 shared_input_gate: Option<CudaSlice<f32>>, shared_gu_b16: Option<CudaSlice<u8>>,
273 shared_down_b16: Option<CudaSlice<u8>>,
274}
275
276enum NgramTable {
278 F32(Vec<f32>),
279 Bf16(Vec<u8>),
280}
281
282impl NgramTable {
283 fn rows(&self, head_dim: usize) -> usize {
284 match self {
285 Self::F32(data) => data.len() / head_dim,
286 Self::Bf16(bytes) => bytes.len() / 2 / head_dim,
287 }
288 }
289
290 fn gather_into(&self, row: usize, head_dim: usize, dst: &mut [f32]) {
291 match self {
292 Self::F32(data) => {
293 dst.copy_from_slice(&data[row * head_dim..(row + 1) * head_dim]);
294 }
295 Self::Bf16(bytes) => {
296 let start = row * head_dim * 2;
297 for (i, out) in dst.iter_mut().enumerate() {
298 let b = u16::from_le_bytes([bytes[start + 2 * i], bytes[start + 2 * i + 1]]);
299 *out = f32::from_bits(u32::from(b) << 16);
300 }
301 }
302 }
303 }
304}
305
306struct PleW {
307 plan: PleEmbeddingPlan,
308 key_proj: Vec<CudaSlice<f32>>, value_proj: CudaSlice<f32>, norm_key: Vec<CudaSlice<f32>>, norm_query: Vec<CudaSlice<f32>>,
312 norm_conv: Vec<CudaSlice<f32>>,
313 conv_w: Vec<CudaSlice<f32>>, multipliers: Vec<i64>,
315 sizes: Vec<i64>,
316 offsets: Vec<i64>,
317 table: NgramTable,
318}
319
320struct LayerW {
321 index: u32,
322 eps_attn: f32,
323 eps_mlp: f32,
324 attn_gate: GateW,
325 mlp_gate: GateW,
326 mixer: MixerW,
327 moe: MoeW,
328 ple: Option<PleW>,
329}
330
331struct MtpW {
339 eps_embed: f32,
341 eps_hidden: f32,
342 pre_norm_embed: CudaSlice<f32>,
344 pre_norm_hidden: CudaSlice<f32>,
346 fc_embed: CudaSlice<f32>, fc_embed_b16: Option<CudaSlice<u8>>,
348 fc_hidden: CudaSlice<f32>, fc_hidden_b16: Option<CudaSlice<u8>>,
350 layer: LayerW,
353 mixer: GateW,
355}
356
357struct MtpDev1 {
368 dev: usize,
370 output: CudaSlice<f32>,
372 output_b16: Option<CudaSlice<u8>>,
376}
377
378struct DraftTrim {
386 n: usize,
388 d2t: Vec<u32>,
390 head_b16: Option<CudaSlice<u8>>,
395 head: Option<CudaSlice<f32>>,
398}
399
400fn linear_trim_into(
404 e: &Engine,
405 trim: &DraftTrim,
406 x: &CudaSlice<f32>,
407 y: &mut CudaSlice<f32>,
408 t: usize,
409 in_f: usize,
410) -> Res<()> {
411 if trunk_bf16_on() {
412 if let Some(w) = trim.head_b16.as_ref() {
413 if (2..=12).contains(&t) && verify_mt_on() {
414 return launch_qmatvec_bf16w_mt(e, w, 0, x, y, in_f, trim.n, t);
415 }
416 return launch_qmatvec_bf16w(e, w, x, y, in_f, trim.n, t, 1, 0, 0, in_f, 0);
417 }
418 }
419 let w = trim.head.as_ref().ok_or(
420 "qwen4exp_gpu: the draft trim was gathered bf16-only — the f32 head arm needs \
421 trunk_bf16 on (or a checkpoint without a bf16 lm-head twin)",
422 )?;
423 e.linear_device_into(x, w, y, t, in_f, trim.n)
424}
425
426pub struct Qwen4ExpGpu {
427 pub plan: ModelPlan,
428 hidden: usize,
429 streams: usize,
430 vocab: usize,
431 embed_host: Vec<f32>, output: CudaSlice<f32>, output_b16: Option<CudaSlice<u8>>,
435 layers: Vec<LayerW>,
436 exit_mixer: GateW,
437 exit_eps: f32,
438 mtp: Option<MtpW>,
441 mtp_dev1: Option<MtpDev1>,
446 draft_trim: Option<DraftTrim>,
450 draft_trim_parked: Option<DraftTrim>,
453 chain_embed: Option<ChainEmbed>,
458}
459
460struct ChainEmbed {
470 table: CudaSlice<u8>,
471 qt: i32,
472 row_bytes: usize,
473 rows: usize,
475 for_trim: bool,
477 dev: usize,
479}
480
481struct PleState {
484 conv_hist: Vec<CudaSlice<f32>>,
487 ngram_ids: Vec<i64>,
503 ngram_history: Vec<i64>,
504 ngram_last_eos: i64,
505}
506
507fn q8_row_bytes(dim: usize) -> usize {
511 dim.div_ceil(32) * 34
512}
513fn q5_row_bytes(dim: usize) -> usize {
515 dim.div_ceil(32) * 24
516}
517
518enum QsaKvStore {
523 F32 {
524 k: CudaSlice<f32>, v: CudaSlice<f32>, },
527 Q8Q5 {
528 k: CudaSlice<u8>, v: CudaSlice<u8>, },
531}
532
533impl QsaKvStore {
534 fn is_quant(&self) -> bool {
535 matches!(self, QsaKvStore::Q8Q5 { .. })
536 }
537 fn capacity_rows(&self, kv_dim: usize) -> usize {
538 match self {
539 QsaKvStore::F32 { k, .. } => k.len() / kv_dim,
540 QsaKvStore::Q8Q5 { k, .. } => k.len() / q8_row_bytes(kv_dim),
541 }
542 }
543}
544
545fn host_quant_q8_row(row: &[f32], dim: usize, out: &mut Vec<u8>) {
551 for b in 0..dim.div_ceil(32) {
552 let mut amax = 0.0f32;
553 for l in 0..32 {
554 let e = b * 32 + l;
555 let x = if e < dim { row[e] } else { 0.0 };
556 amax = amax.max(x.abs());
557 }
558 let d = amax / 127.0f32;
559 let mut id = if d != 0.0 { 1.0f32 / d } else { 0.0 };
560 if !id.is_finite() {
562 id = 0.0;
563 }
564 out.extend_from_slice(&memra_gguf::nvfp4_repack::f32_to_f16_bits(d).to_le_bytes());
565 for l in 0..32 {
566 let e = b * 32 + l;
567 let x = if e < dim { row[e] } else { 0.0 };
568 let q = ((x * id).round_ties_even() as i32).clamp(-127, 127);
569 out.push(q as i8 as u8);
570 }
571 }
572}
573
574fn host_deq_q8_rows(bytes: &[u8], row0: usize, rows: usize, dim: usize, out: &mut Vec<f32>) {
577 let rb = q8_row_bytes(dim);
578 for r in row0..row0 + rows {
579 let row = &bytes[r * rb..(r + 1) * rb];
580 for e in 0..dim {
581 let blk = &row[(e >> 5) * 34..];
582 let d = memra_gguf::dequant::fp16_to_f32(u16::from_le_bytes([blk[0], blk[1]]));
583 let q = blk[2 + (e & 31)] as i8 as f32;
584 out.push(d * q);
585 }
586 }
587}
588
589fn host_quant_q5_row(row: &[f32], dim: usize, out: &mut Vec<u8>) {
592 for b in 0..dim.div_ceil(32) {
593 let lane = |l: usize| -> f32 {
594 let e = b * 32 + l;
595 if e < dim { row[e] } else { 0.0 }
596 };
597 let mut mn = f32::INFINITY;
598 let mut mx = f32::NEG_INFINITY;
599 for l in 0..32 {
600 mn = mn.min(lane(l));
601 mx = mx.max(lane(l));
602 }
603 let d = (mx - mn) / 31.0f32;
604 let mut id = if d != 0.0 { 1.0f32 / d } else { 0.0 };
605 if !id.is_finite() {
607 id = 0.0;
608 }
609 let q5 = |l: usize| -> u32 {
610 (((lane(l) - mn) * id).round_ties_even() as i32).clamp(0, 31) as u32
611 };
612 let mut qh = 0u32;
613 for l in 0..32 {
614 qh |= ((q5(l) >> 4) & 1) << l;
615 }
616 out.extend_from_slice(&memra_gguf::nvfp4_repack::f32_to_f16_bits(d).to_le_bytes());
617 out.extend_from_slice(&memra_gguf::nvfp4_repack::f32_to_f16_bits(mn).to_le_bytes());
618 out.extend_from_slice(&qh.to_le_bytes());
619 for l in 0..16 {
620 out.push(((q5(l) & 0x0F) | ((q5(l + 16) & 0x0F) << 4)) as u8);
621 }
622 }
623}
624
625fn host_deq_q5_rows(bytes: &[u8], row0: usize, rows: usize, dim: usize, out: &mut Vec<f32>) {
627 let rb = q5_row_bytes(dim);
628 for r in row0..row0 + rows {
629 let row = &bytes[r * rb..(r + 1) * rb];
630 for e in 0..dim {
631 let blk = &row[(e >> 5) * 24..];
632 let d = memra_gguf::dequant::fp16_to_f32(u16::from_le_bytes([blk[0], blk[1]]));
633 let m = memra_gguf::dequant::fp16_to_f32(u16::from_le_bytes([blk[2], blk[3]]));
634 let qh = u32::from_le_bytes([blk[4], blk[5], blk[6], blk[7]]);
635 let lane = e & 31;
636 let lo = if lane < 16 {
637 blk[8 + lane] & 0x0F
638 } else {
639 blk[8 + lane - 16] >> 4
640 };
641 let q5 = (lo as u32) | (((qh >> lane) & 1) << 4);
642 out.push(d.mul_add(q5 as f32, m));
643 }
644 }
645}
646
647fn f32_to_bf16_rne(x: f32) -> u16 {
650 let bits = x.to_bits();
651 let rounding_bias = 0x7fff + ((bits >> 16) & 1);
652 (bits.wrapping_add(rounding_bias) >> 16) as u16
653}
654
655enum IdxRawCache {
659 F32(Vec<f32>),
660 Q8(Vec<u8>),
661 Bf16(Vec<u16>),
662}
663
664impl IdxRawCache {
665 fn new(mode: IdxQMode) -> Self {
666 match mode {
667 IdxQMode::F32 => IdxRawCache::F32(Vec::new()),
668 IdxQMode::Q8 => IdxRawCache::Q8(Vec::new()),
669 IdxQMode::Bf16 => IdxRawCache::Bf16(Vec::new()),
670 }
671 }
672 fn rows(&self, idx_dim: usize) -> usize {
673 match self {
674 IdxRawCache::F32(v) => v.len() / idx_dim,
675 IdxRawCache::Q8(v) => v.len() / q8_row_bytes(idx_dim),
676 IdxRawCache::Bf16(v) => v.len() / idx_dim,
677 }
678 }
679 fn truncate_rows(&mut self, rows: usize, idx_dim: usize) {
680 match self {
681 IdxRawCache::F32(v) => v.truncate(rows * idx_dim),
682 IdxRawCache::Q8(v) => v.truncate(rows * q8_row_bytes(idx_dim)),
683 IdxRawCache::Bf16(v) => v.truncate(rows * idx_dim),
684 }
685 }
686 fn append_rows_f32(&mut self, rows: &[f32], n: usize, idx_dim: usize) {
689 match self {
690 IdxRawCache::F32(v) => v.extend_from_slice(&rows[..n * idx_dim]),
691 IdxRawCache::Q8(v) => {
692 for r in 0..n {
693 host_quant_q8_row(&rows[r * idx_dim..(r + 1) * idx_dim], idx_dim, v);
694 }
695 }
696 IdxRawCache::Bf16(v) => {
697 v.extend(rows[..n * idx_dim].iter().map(|&x| f32_to_bf16_rne(x)));
698 }
699 }
700 }
701 fn rows_f32(&self, row0: usize, n: usize, idx_dim: usize, out: &mut Vec<f32>) {
703 out.clear();
704 match self {
705 IdxRawCache::F32(v) => out.extend_from_slice(&v[row0 * idx_dim..(row0 + n) * idx_dim]),
706 IdxRawCache::Q8(v) => host_deq_q8_rows(v, row0, n, idx_dim, out),
707 IdxRawCache::Bf16(v) => out.extend(
708 v[row0 * idx_dim..(row0 + n) * idx_dim]
709 .iter()
710 .map(|&b| memra_gguf::dequant::bf16_to_f32(b)),
711 ),
712 }
713 }
714}
715
716enum IdxRawDev {
720 F32(CudaSlice<f32>),
721 Q8(CudaSlice<u8>),
722 Bf16(CudaSlice<u16>),
723}
724
725fn idx_materialize_host(
729 e: &Engine,
730 raw_keys: &mut IdxRawCache,
731 raw_dev: &Option<IdxRawDev>,
732 raw_dev_rows: usize,
733 idx_dim: usize,
734) -> Res<()> {
735 let host_rows = raw_keys.rows(idx_dim);
736 if raw_dev_rows <= host_rows {
737 return Ok(());
738 }
739 let m = raw_dev
740 .as_ref()
741 .ok_or("idxcache: rows counted without a cache")?;
742 match (m, raw_keys) {
743 (IdxRawDev::F32(d), IdxRawCache::F32(h)) => {
744 let delta = e.dtoh_view(&d.slice(host_rows * idx_dim..raw_dev_rows * idx_dim))?;
745 h.extend_from_slice(&delta);
746 }
747 (IdxRawDev::Q8(d), IdxRawCache::Q8(h)) => {
748 let rb = q8_row_bytes(idx_dim);
749 let delta = e.dtoh_u8_view(&d.slice(host_rows * rb..raw_dev_rows * rb))?;
750 h.extend_from_slice(&delta);
751 }
752 (IdxRawDev::Bf16(d), IdxRawCache::Bf16(h)) => {
753 let delta = e
754 .gpu
755 .stream()
756 .clone_dtoh(&d.slice(host_rows * idx_dim..raw_dev_rows * idx_dim))?;
757 e.gpu.stream().synchronize()?;
758 h.extend_from_slice(&delta);
759 }
760 _ => return Err("idxcache: device/host raw-key formats disagree".into()),
761 }
762 Ok(())
763}
764
765struct IdxAudit {
768 raw_f32: IdxRawCache, pooled_f32: Vec<f32>,
770}
771
772enum MixerState {
773 Qsa {
774 kv: QsaKvStore,
775 raw_keys: IdxRawCache,
779 pooled_keys: Vec<f32>,
786 pooled_dev: Option<CudaSlice<f32>>,
790 pooled_dev_rows: usize,
792 raw_dev: Option<IdxRawDev>,
798 raw_dev_rows: usize,
800 idx_audit: Option<Box<IdxAudit>>,
802 },
803 Gdn {
804 conv: CudaSlice<f32>, state: CudaSlice<f32>, },
807}
808
809struct LayerState {
810 mixer: MixerState,
811 ple: Option<PleState>,
812}
813
814struct GdnStash {
817 states: CudaSlice<f32>,
819 conv_pre: CudaSlice<f32>,
821 qkv_rows: CudaSlice<f32>,
823 scan_graph: Option<(usize, GraphEntry)>,
830 scan_warm: Option<usize>,
834}
835
836struct PleStash {
839 hist_pre: Vec<CudaSlice<f32>>, normed_rows: Vec<CudaSlice<f32>>, }
842
843pub struct VerifyStash {
850 k_cap: usize,
851 chunk: Option<(usize, usize)>,
853 fused_chunk: Option<(usize, usize)>,
858 gdn: Vec<Option<GdnStash>>,
859 ple: Vec<Option<PleStash>>,
860 wide: CudaSlice<f32>,
865 ring_rows: usize,
866 wide_dev1: Option<CudaSlice<f32>>,
870 argmax: Vec<u32>,
872 toks: CudaSlice<u32>,
874 want_argmax: bool,
877 want_argmax_t1: bool,
882 last_row_only: bool,
886}
887
888pub struct Qwen4ExpState {
889 pos: usize,
890 capacity: usize,
891 reserve: usize,
897 tokens: Vec<u32>,
901 layers: Vec<LayerState>,
902 ws: StepPool,
904 graphs: StepGraphs,
906 tp2: Option<Tp2State>,
911 verify: Option<VerifyStash>,
913}
914
915#[derive(Default)]
926struct StepPool {
927 f32s: std::collections::BTreeMap<&'static str, CudaSlice<f32>>,
928 i32s: std::collections::BTreeMap<&'static str, CudaSlice<i32>>,
929 u8s: std::collections::BTreeMap<&'static str, CudaSlice<u8>>,
930 u64s: std::collections::BTreeMap<&'static str, CudaSlice<u64>>,
931}
932
933const EXIT_PLANE_SLOTS: [&str; 8] = [
938 "exit.p0", "exit.p1", "exit.p2", "exit.p3", "exit.p4", "exit.p5", "exit.p6", "exit.p7",
939];
940const PLANE_SLOTS: [&str; 8] = [
941 "plane.0", "plane.1", "plane.2", "plane.3", "plane.4", "plane.5", "plane.6", "plane.7",
942];
943const INJECT_SLOTS: [&str; 8] = [
944 "hc.inj.0", "hc.inj.1", "hc.inj.2", "hc.inj.3", "hc.inj.4", "hc.inj.5", "hc.inj.6", "hc.inj.7",
945];
946
947impl StepPool {
948 fn take_f32(
949 &mut self,
950 e: &Engine,
951 name: &'static str,
952 len: usize,
953 reserve: usize,
954 ) -> Res<CudaSlice<f32>> {
955 if step_ws_on() {
956 if let Some(buf) = self.f32s.remove(name) {
957 if buf.len() >= len {
958 return Ok(buf);
959 }
960 }
961 e.uninit(len.max(reserve))
962 } else {
963 e.uninit(len)
964 }
965 }
966
967 fn put_f32(&mut self, name: &'static str, buf: CudaSlice<f32>) {
968 if step_ws_on() {
969 self.f32s.insert(name, buf);
970 }
971 }
972
973 fn shed(&mut self) -> usize {
992 let bytes = self.f32s.values().map(|b| b.len() * 4).sum::<usize>()
993 + self.i32s.values().map(|b| b.len() * 4).sum::<usize>()
994 + self.u8s.values().map(|b| b.len()).sum::<usize>()
995 + self.u64s.values().map(|b| b.len() * 8).sum::<usize>();
996 self.f32s.clear();
997 self.i32s.clear();
998 self.u8s.clear();
999 self.u64s.clear();
1000 bytes
1001 }
1002
1003 fn take_i32(
1004 &mut self,
1005 e: &Engine,
1006 name: &'static str,
1007 host: &[i32],
1008 reserve: usize,
1009 ) -> Res<CudaSlice<i32>> {
1010 if step_ws_on() {
1011 let mut buf = match self.i32s.remove(name) {
1012 Some(buf) if buf.len() >= host.len() => buf,
1013 _ => e.alloc_uninit::<i32>(host.len().max(reserve))?,
1014 };
1015 let mut view = buf.slice_mut(0..host.len());
1016 e.gpu.stream().memcpy_htod(host, &mut view)?;
1017 Ok(buf)
1018 } else {
1019 e.htod_i32(host)
1020 }
1021 }
1022
1023 fn put_i32(&mut self, name: &'static str, buf: CudaSlice<i32>) {
1024 if step_ws_on() {
1025 self.i32s.insert(name, buf);
1026 }
1027 }
1028
1029 fn take_i32_slot(
1032 &mut self,
1033 e: &Engine,
1034 name: &'static str,
1035 len: usize,
1036 reserve: usize,
1037 ) -> Res<CudaSlice<i32>> {
1038 if step_ws_on() {
1039 if let Some(buf) = self.i32s.remove(name) {
1040 if buf.len() >= len {
1041 return Ok(buf);
1042 }
1043 }
1044 }
1045 e.alloc_uninit::<i32>(len.max(reserve))
1046 }
1047
1048 fn take_f32_h2d(
1049 &mut self,
1050 e: &Engine,
1051 name: &'static str,
1052 host: &[f32],
1053 reserve: usize,
1054 ) -> Res<CudaSlice<f32>> {
1055 if step_ws_on() {
1056 let mut buf = match self.f32s.remove(name) {
1057 Some(buf) if buf.len() >= host.len() => buf,
1058 _ => e.uninit(host.len().max(reserve))?,
1059 };
1060 let mut view = buf.slice_mut(0..host.len());
1061 e.gpu.stream().memcpy_htod(host, &mut view)?;
1062 Ok(buf)
1063 } else {
1064 e.htod(host)
1065 }
1066 }
1067
1068 fn take_u8_h2d(
1069 &mut self,
1070 e: &Engine,
1071 name: &'static str,
1072 host: &[u8],
1073 reserve: usize,
1074 ) -> Res<CudaSlice<u8>> {
1075 if step_ws_on() {
1076 let mut buf = match self.u8s.remove(name) {
1077 Some(buf) if buf.len() >= host.len() => buf,
1078 _ => e.alloc_u8_uninit(host.len().max(reserve))?,
1079 };
1080 let mut view = buf.slice_mut(0..host.len());
1081 e.gpu.stream().memcpy_htod(host, &mut view)?;
1082 Ok(buf)
1083 } else {
1084 e.htod_bytes(host)
1085 }
1086 }
1087
1088 fn put_u8(&mut self, name: &'static str, buf: CudaSlice<u8>) {
1089 if step_ws_on() {
1090 self.u8s.insert(name, buf);
1091 }
1092 }
1093
1094 fn take_u8(
1097 &mut self,
1098 e: &Engine,
1099 name: &'static str,
1100 len: usize,
1101 reserve: usize,
1102 ) -> Res<CudaSlice<u8>> {
1103 if step_ws_on() {
1104 if let Some(buf) = self.u8s.remove(name) {
1105 if buf.len() >= len {
1106 return Ok(buf);
1107 }
1108 }
1109 }
1110 e.alloc_u8_uninit(len.max(reserve))
1111 }
1112
1113 fn upsert_u8(
1117 &mut self,
1118 e: &Engine,
1119 name: &'static str,
1120 host: &[u8],
1121 reserve: usize,
1122 ) -> Res<()> {
1123 if !self.u8s.contains_key(name) {
1124 let buf = self.take_u8(e, name, host.len(), reserve)?;
1125 self.put_u8(name, buf);
1126 }
1127 let buf = self
1128 .u8s
1129 .get_mut(name)
1130 .ok_or_else(|| format!("step workspace: slot {name} is not parked"))?;
1131 if buf.len() < host.len() {
1132 return Err(format!("step workspace: slot {name} is too small").into());
1133 }
1134 let mut view = buf.slice_mut(0..host.len());
1135 e.gpu.stream().memcpy_htod(host, &mut view)?;
1136 Ok(())
1137 }
1138
1139 fn peek_u8(&self, name: &'static str) -> Res<&CudaSlice<u8>> {
1142 self.u8s
1143 .get(name)
1144 .ok_or_else(|| format!("step workspace: slot {name} is not parked").into())
1145 }
1146
1147 fn take_u64_h2d(
1148 &mut self,
1149 e: &Engine,
1150 name: &'static str,
1151 host: &[u64],
1152 reserve: usize,
1153 ) -> Res<CudaSlice<u64>> {
1154 if step_ws_on() {
1155 let mut buf = match self.u64s.remove(name) {
1156 Some(buf) if buf.len() >= host.len() => buf,
1157 _ => e.alloc_uninit::<u64>(host.len().max(reserve))?,
1158 };
1159 let mut view = buf.slice_mut(0..host.len());
1160 e.gpu.stream().memcpy_htod(host, &mut view)?;
1161 Ok(buf)
1162 } else {
1163 e.htod_u64(host)
1164 }
1165 }
1166
1167 fn put_u64(&mut self, name: &'static str, buf: CudaSlice<u64>) {
1168 if step_ws_on() {
1169 self.u64s.insert(name, buf);
1170 }
1171 }
1172
1173 fn peek_f32(&self, name: &'static str) -> Res<&CudaSlice<f32>> {
1176 self.f32s
1177 .get(name)
1178 .ok_or_else(|| format!("step workspace: slot {name} is not parked").into())
1179 }
1180
1181 fn write_i32(&mut self, e: &Engine, name: &'static str, host: &[i32]) -> Res<()> {
1185 let buf = self
1186 .i32s
1187 .get_mut(name)
1188 .ok_or_else(|| format!("step workspace: slot {name} is not parked"))?;
1189 if buf.len() < host.len() {
1190 return Err(format!("step workspace: slot {name} is too small").into());
1191 }
1192 let mut view = buf.slice_mut(0..host.len());
1193 e.gpu.stream().memcpy_htod(host, &mut view)?;
1194 Ok(())
1195 }
1196
1197 fn write_f32(&mut self, e: &Engine, name: &'static str, host: &[f32]) -> Res<()> {
1198 let buf = self
1199 .f32s
1200 .get_mut(name)
1201 .ok_or_else(|| format!("step workspace: slot {name} is not parked"))?;
1202 if buf.len() < host.len() {
1203 return Err(format!("step workspace: slot {name} is too small").into());
1204 }
1205 let mut view = buf.slice_mut(0..host.len());
1206 e.gpu.stream().memcpy_htod(host, &mut view)?;
1207 Ok(())
1208 }
1209}
1210
1211#[derive(Default)]
1222struct StepGraphs {
1223 warm: bool,
1226 a: Vec<Option<GraphEntry>>,
1227 b: Vec<Option<GraphEntry>>,
1228 exit: Option<GraphEntry>,
1229}
1230
1231type GraphEntry = (
1232 cudarc::driver::CudaGraph,
1233 Vec<Box<dyn std::any::Any + Send>>,
1234);
1235
1236impl Qwen4ExpState {
1237 pub fn position(&self) -> usize {
1238 self.pos
1239 }
1240}
1241
1242pub struct PrefillCapture {
1246 pub layer_wide: Vec<Vec<f32>>,
1248 pub exit_mixed: Vec<f32>,
1250}
1251
1252pub mod prof {
1261 use std::cell::RefCell;
1262 use std::collections::BTreeMap;
1263
1264 thread_local! {
1265 static STATE: RefCell<Option<BTreeMap<&'static str, (f64, u64)>>> =
1266 const { RefCell::new(None) };
1267 }
1268
1269 thread_local! {
1270 static PREFILL: RefCell<Option<BTreeMap<&'static str, (f64, u64)>>> =
1276 const { RefCell::new(None) };
1277 static ROUNDS_ONLY: RefCell<bool> = const { RefCell::new(false) };
1278 }
1279
1280 pub fn enable() {
1282 STATE.with(|s| *s.borrow_mut() = Some(BTreeMap::new()));
1283 PREFILL.with(|s| *s.borrow_mut() = None);
1284 }
1285
1286 pub fn set_rounds_only(on: bool) {
1289 ROUNDS_ONLY.with(|s| *s.borrow_mut() = on);
1290 }
1291
1292 pub fn rounds_only() -> bool {
1293 ROUNDS_ONLY.with(|s| *s.borrow())
1294 }
1295
1296 pub fn split_prefill() {
1299 if !on() || !rounds_only() {
1300 return;
1301 }
1302 let pre = STATE.with(|s| s.borrow_mut().replace(BTreeMap::new()));
1303 PREFILL.with(|s| *s.borrow_mut() = pre);
1304 }
1305
1306 pub fn take_prefill() -> Vec<(&'static str, f64, u64)> {
1308 PREFILL.with(|s| {
1309 s.borrow_mut()
1310 .take()
1311 .map(|map| map.into_iter().map(|(k, (t, c))| (k, t, c)).collect())
1312 .unwrap_or_default()
1313 })
1314 }
1315
1316 pub fn on() -> bool {
1317 STATE.with(|s| s.borrow().is_some())
1318 }
1319
1320 pub fn take() -> Vec<(&'static str, f64, u64)> {
1322 STATE.with(|s| {
1323 s.borrow_mut()
1324 .take()
1325 .map(|map| map.into_iter().map(|(k, (t, c))| (k, t, c)).collect())
1326 .unwrap_or_default()
1327 })
1328 }
1329
1330 pub(super) fn add(name: &'static str, seconds: f64) {
1331 STATE.with(|s| {
1332 if let Some(map) = s.borrow_mut().as_mut() {
1333 let entry = map.entry(name).or_insert((0.0, 0));
1334 entry.0 += seconds;
1335 entry.1 += 1;
1336 }
1337 });
1338 }
1339}
1340
1341static MOE_SEL_PATH: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1346
1347pub fn set_moe_sel_path(on: bool) {
1348 MOE_SEL_PATH.store(on, std::sync::atomic::Ordering::Relaxed);
1349}
1350
1351fn moe_sel_path_on() -> bool {
1352 MOE_SEL_PATH.load(std::sync::atomic::Ordering::Relaxed)
1353}
1354
1355static HC_FUSED_GATE: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1359
1360pub fn set_hc_fused_gate(on: bool) {
1361 HC_FUSED_GATE.store(on, std::sync::atomic::Ordering::Relaxed);
1362}
1363
1364fn hc_fused_gate_on() -> bool {
1365 HC_FUSED_GATE.load(std::sync::atomic::Ordering::Relaxed)
1366}
1367
1368static TRUNK_BF16: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1377
1378pub fn set_trunk_bf16(on: bool) {
1379 TRUNK_BF16.store(on, std::sync::atomic::Ordering::Relaxed);
1380}
1381
1382fn trunk_bf16_on() -> bool {
1383 TRUNK_BF16.load(std::sync::atomic::Ordering::Relaxed)
1384}
1385
1386static PREFILL_GROUPED_ALL: std::sync::atomic::AtomicBool =
1411 std::sync::atomic::AtomicBool::new(false);
1412
1413pub fn set_prefill_grouped_all(on: bool) {
1414 PREFILL_GROUPED_ALL.store(on, std::sync::atomic::Ordering::Relaxed);
1415}
1416
1417fn prefill_grouped_all_on() -> bool {
1418 PREFILL_GROUPED_ALL.load(std::sync::atomic::Ordering::Relaxed)
1419}
1420
1421static STEP_WS: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1425
1426pub fn set_step_ws(on: bool) {
1427 STEP_WS.store(on, std::sync::atomic::Ordering::Relaxed);
1428}
1429
1430fn step_ws_on() -> bool {
1431 STEP_WS.load(std::sync::atomic::Ordering::Relaxed)
1432}
1433
1434static DECODE_GRAPHS: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1442
1443pub fn set_decode_graphs(on: bool) {
1444 DECODE_GRAPHS.store(on, std::sync::atomic::Ordering::Relaxed);
1445}
1446
1447fn decode_graphs_on() -> bool {
1448 DECODE_GRAPHS.load(std::sync::atomic::Ordering::Relaxed)
1449}
1450
1451static VERIFY_GRAPHS: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
1466
1467pub fn set_verify_graphs(on: bool) {
1468 VERIFY_GRAPHS.store(on, std::sync::atomic::Ordering::Relaxed);
1469}
1470
1471fn verify_graphs_on() -> bool {
1472 VERIFY_GRAPHS.load(std::sync::atomic::Ordering::Relaxed)
1473}
1474
1475static SEL_V2: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1481
1482pub fn set_sel_v2(on: bool) {
1483 SEL_V2.store(on, std::sync::atomic::Ordering::Relaxed);
1484}
1485
1486fn sel_v2_on() -> bool {
1487 SEL_V2.load(std::sync::atomic::Ordering::Relaxed)
1488}
1489
1490pub const SEL_V3_DEFAULT: bool = true;
1498static SEL_V3: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(SEL_V3_DEFAULT);
1499
1500pub fn set_sel_v3(on: bool) {
1501 SEL_V3.store(on, std::sync::atomic::Ordering::Relaxed);
1502}
1503
1504fn sel_v3_on() -> bool {
1505 SEL_V3.load(std::sync::atomic::Ordering::Relaxed)
1506}
1507
1508const SEL_GROUP_OFF: u32 = 0;
1569const SEL_GROUP_AUTO: u32 = 1;
1570static SEL_GROUP_DN: std::sync::atomic::AtomicU32 =
1572 std::sync::atomic::AtomicU32::new(SEL_GROUP_AUTO);
1573static SEL_GROUP_GU: std::sync::atomic::AtomicU32 =
1575 std::sync::atomic::AtomicU32::new(SEL_GROUP_AUTO);
1576
1577fn sel_group_dn() -> u32 {
1578 SEL_GROUP_DN.load(std::sync::atomic::Ordering::Relaxed)
1579}
1580
1581fn sel_group_gu() -> u32 {
1582 SEL_GROUP_GU.load(std::sync::atomic::Ordering::Relaxed)
1583}
1584
1585pub fn sel_group_spec() -> String {
1589 let one = |c: u32| -> String {
1590 match c {
1591 SEL_GROUP_OFF => "off".to_string(),
1592 SEL_GROUP_AUTO => "auto".to_string(),
1593 v => format!("{}:{}", (v >> 8) & 0xff, v & 0xff),
1594 }
1595 };
1596 format!("dn:{}+gu:{}", one(sel_group_dn()), one(sel_group_gu()))
1597}
1598
1599pub fn set_sel_group(spec: &str) -> bool {
1610 let parse_one = |s: &str| -> Option<u32> {
1611 match s {
1612 "off" | "0" => Some(SEL_GROUP_OFF),
1613 "auto" | "1" | "" => Some(SEL_GROUP_AUTO),
1614 other => {
1615 let (g, rows) = other.split_once(':')?;
1616 let g: u32 = g.parse().ok()?;
1617 let rows: u32 = rows.parse().ok()?;
1618 if !matches!(g, 1 | 2 | 4 | 8 | 16 | 32) || !matches!(rows, 1 | 2 | 4) {
1619 return None;
1620 }
1621 Some((g << 8) | rows)
1622 }
1623 }
1624 };
1625 if let Some(both) = parse_one(spec) {
1626 SEL_GROUP_DN.store(both, std::sync::atomic::Ordering::Relaxed);
1627 SEL_GROUP_GU.store(both, std::sync::atomic::Ordering::Relaxed);
1628 return true;
1629 }
1630 let mut dn = None;
1631 let mut gu = None;
1632 for part in spec.split('+').filter(|p| !p.is_empty()) {
1633 let Some((family, rest)) = part.split_once(':') else {
1634 return false;
1635 };
1636 let Some(code) = parse_one(rest) else {
1637 return false;
1638 };
1639 match family {
1640 "dn" | "down" => dn = Some(code),
1641 "gu" | "gateup" => gu = Some(code),
1642 _ => return false,
1643 }
1644 }
1645 if dn.is_none() && gu.is_none() {
1646 return false;
1647 }
1648 if let Some(c) = dn {
1649 SEL_GROUP_DN.store(c, std::sync::atomic::Ordering::Relaxed);
1650 }
1651 if let Some(c) = gu {
1652 SEL_GROUP_GU.store(c, std::sync::atomic::Ordering::Relaxed);
1653 }
1654 true
1655}
1656
1657fn sel_group_resolve(code: u32, in_f: usize, out_f: usize) -> Option<(usize, usize)> {
1662 if code == SEL_GROUP_OFF || in_f % 32 != 0 {
1663 return None;
1664 }
1665 let pairs = in_f / 32;
1666 if code != SEL_GROUP_AUTO {
1667 let (g, rows) = (((code >> 8) & 0xff) as usize, (code & 0xff) as usize);
1668 if !matches!(g, 1 | 2 | 4 | 8 | 16 | 32) || !matches!(rows, 1 | 2 | 4) {
1669 return None;
1670 }
1671 if out_f % ((32 / g) * rows) != 0 {
1672 return None;
1673 }
1674 return Some((g, rows));
1675 }
1676 let mut g = 1usize;
1680 for cand in [2usize, 4, 8, 16, 32] {
1681 if pairs % cand != 0 {
1682 break;
1683 }
1684 g = cand;
1685 }
1686 let mut rows = 4usize;
1698 while rows > 1 && out_f % ((32 / g) * rows) != 0 {
1699 rows /= 2;
1700 }
1701 let rows_per_warp = (32 / g) * rows;
1702 if out_f % rows_per_warp != 0 {
1703 return None;
1704 }
1705 Some((g, rows))
1706}
1707
1708static HC_MICRO: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1716
1717pub fn set_hc_micro(on: bool) {
1718 HC_MICRO.store(on, std::sync::atomic::Ordering::Relaxed);
1719}
1720
1721fn hc_micro_on() -> bool {
1722 HC_MICRO.load(std::sync::atomic::Ordering::Relaxed)
1723}
1724
1725pub const GDN_STEP_DEFAULT: bool = true;
1735static GDN_STEP: std::sync::atomic::AtomicBool =
1736 std::sync::atomic::AtomicBool::new(GDN_STEP_DEFAULT);
1737
1738pub fn set_gdn_step(on: bool) {
1739 GDN_STEP.store(on, std::sync::atomic::Ordering::Relaxed);
1740}
1741
1742fn gdn_step_on() -> bool {
1743 GDN_STEP.load(std::sync::atomic::Ordering::Relaxed)
1744}
1745
1746pub const GDN_FUSE_DEFAULT: bool = true;
1754static GDN_FUSE: std::sync::atomic::AtomicBool =
1755 std::sync::atomic::AtomicBool::new(GDN_FUSE_DEFAULT);
1756
1757pub fn set_gdn_fuse(on: bool) {
1758 GDN_FUSE.store(on, std::sync::atomic::Ordering::Relaxed);
1759}
1760
1761fn gdn_fuse_on() -> bool {
1762 GDN_FUSE.load(std::sync::atomic::Ordering::Relaxed)
1763}
1764
1765pub const PROJ_STACK_DEFAULT: bool = true;
1776static PROJ_STACK: std::sync::atomic::AtomicBool =
1777 std::sync::atomic::AtomicBool::new(PROJ_STACK_DEFAULT);
1778
1779pub fn set_proj_stack(on: bool) {
1780 PROJ_STACK.store(on, std::sync::atomic::Ordering::Relaxed);
1781}
1782
1783fn proj_stack_on() -> bool {
1784 PROJ_STACK.load(std::sync::atomic::Ordering::Relaxed)
1785}
1786
1787pub const HC_DIET_DEFAULT: bool = true;
1800static HC_DIET: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(HC_DIET_DEFAULT);
1801
1802pub fn set_hc_diet(on: bool) {
1803 HC_DIET.store(on, std::sync::atomic::Ordering::Relaxed);
1804}
1805
1806fn hc_diet_on() -> bool {
1807 HC_DIET.load(std::sync::atomic::Ordering::Relaxed)
1808}
1809
1810pub const SEL_GUFUSE_DEFAULT: bool = true;
1823static SEL_GUFUSE: std::sync::atomic::AtomicBool =
1824 std::sync::atomic::AtomicBool::new(SEL_GUFUSE_DEFAULT);
1825
1826pub fn set_sel_gufuse(on: bool) {
1827 SEL_GUFUSE.store(on, std::sync::atomic::Ordering::Relaxed);
1828}
1829
1830fn sel_gufuse_on() -> bool {
1831 SEL_GUFUSE.load(std::sync::atomic::Ordering::Relaxed)
1832}
1833
1834pub const VERIFY_MT_DEFAULT: bool = true;
1845static VERIFY_MT: std::sync::atomic::AtomicBool =
1846 std::sync::atomic::AtomicBool::new(VERIFY_MT_DEFAULT);
1847
1848pub fn set_verify_mt(on: bool) {
1849 VERIFY_MT.store(on, std::sync::atomic::Ordering::Relaxed);
1850}
1851
1852fn verify_mt_on() -> bool {
1853 VERIFY_MT.load(std::sync::atomic::Ordering::Relaxed)
1854}
1855
1856pub const VERIFY_FUSED_DEFAULT: bool = false;
1879static VERIFY_FUSED: std::sync::atomic::AtomicBool =
1880 std::sync::atomic::AtomicBool::new(VERIFY_FUSED_DEFAULT);
1881
1882pub fn set_verify_fused(on: bool) {
1883 VERIFY_FUSED.store(on, std::sync::atomic::Ordering::Relaxed);
1884}
1885
1886pub fn verify_fused_on() -> bool {
1887 VERIFY_FUSED.load(std::sync::atomic::Ordering::Relaxed)
1888}
1889
1890pub const ROUTER_B16_DEFAULT: bool = true;
1899static ROUTER_B16: std::sync::atomic::AtomicBool =
1900 std::sync::atomic::AtomicBool::new(ROUTER_B16_DEFAULT);
1901
1902pub fn set_router_bf16(on: bool) {
1903 ROUTER_B16.store(on, std::sync::atomic::Ordering::Relaxed);
1904}
1905
1906fn router_bf16_on() -> bool {
1907 ROUTER_B16.load(std::sync::atomic::Ordering::Relaxed)
1908}
1909
1910const SDPA_MASK_TKV_BOUND: usize = 12288;
1917
1918static IDX_DEV: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1925fn idx_dev_on() -> bool {
1926 IDX_DEV.load(std::sync::atomic::Ordering::Relaxed)
1927}
1928pub fn set_idx_dev(on: bool) {
1929 IDX_DEV.store(on, std::sync::atomic::Ordering::Relaxed);
1930}
1931
1932pub const IDX_SEL_DEFAULT: bool = true;
1978static IDX_SEL: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(IDX_SEL_DEFAULT);
1979fn idx_sel_on() -> bool {
1980 IDX_SEL.load(std::sync::atomic::Ordering::Relaxed)
1981}
1982pub fn set_idx_sel(on: bool) {
1983 IDX_SEL.store(on, std::sync::atomic::Ordering::Relaxed);
1984}
1985pub const PLE_CACHE_DEFAULT: bool = true;
2050static PLE_CACHE: std::sync::atomic::AtomicBool =
2051 std::sync::atomic::AtomicBool::new(PLE_CACHE_DEFAULT);
2052fn ple_cache_on() -> bool {
2053 PLE_CACHE.load(std::sync::atomic::Ordering::Relaxed)
2054}
2055pub fn set_ple_cache(on: bool) {
2056 PLE_CACHE.store(on, std::sync::atomic::Ordering::Relaxed);
2057}
2058fn ple_cache_audit_on() -> bool {
2062 static C: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2063 *C.get_or_init(|| std::env::var("MEMRA_Q4E_PLECACHE_AUDIT").as_deref() == Ok("1"))
2064}
2065static PLE_CACHE_AUDIT_ROWS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
2066static PLE_CACHE_AUDIT_MISMATCH: std::sync::atomic::AtomicU64 =
2067 std::sync::atomic::AtomicU64::new(0);
2068static PLE_CACHE_AUDIT_MAX_FILL: std::sync::atomic::AtomicU64 =
2069 std::sync::atomic::AtomicU64::new(0);
2070pub fn ple_cache_audit_stats() -> (u64, u64, u64) {
2072 (
2073 PLE_CACHE_AUDIT_ROWS.load(std::sync::atomic::Ordering::Relaxed),
2074 PLE_CACHE_AUDIT_MISMATCH.load(std::sync::atomic::Ordering::Relaxed),
2075 PLE_CACHE_AUDIT_MAX_FILL.load(std::sync::atomic::Ordering::Relaxed),
2076 )
2077}
2078
2079fn idx_sel_audit_on() -> bool {
2086 static C: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2087 *C.get_or_init(|| std::env::var("MEMRA_Q4E_IDXSEL_AUDIT").as_deref() == Ok("1"))
2088}
2089static IDX_SEL_AUDIT_ROWS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
2090static IDX_SEL_AUDIT_MISMATCH: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
2091static IDX_SEL_AUDIT_MAX_BLOCKS: std::sync::atomic::AtomicU64 =
2092 std::sync::atomic::AtomicU64::new(0);
2093pub fn idx_sel_audit_stats() -> (u64, u64, u64) {
2095 (
2096 IDX_SEL_AUDIT_ROWS.load(std::sync::atomic::Ordering::Relaxed),
2097 IDX_SEL_AUDIT_MISMATCH.load(std::sync::atomic::Ordering::Relaxed),
2098 IDX_SEL_AUDIT_MAX_BLOCKS.load(std::sync::atomic::Ordering::Relaxed),
2099 )
2100}
2101
2102pub const ROUTER_DEV_DEFAULT: bool = true;
2122static ROUTER_DEV: std::sync::atomic::AtomicBool =
2123 std::sync::atomic::AtomicBool::new(ROUTER_DEV_DEFAULT);
2124fn router_dev_on() -> bool {
2125 ROUTER_DEV.load(std::sync::atomic::Ordering::Relaxed)
2126}
2127pub fn set_router_dev(on: bool) {
2128 ROUTER_DEV.store(on, std::sync::atomic::Ordering::Relaxed);
2129}
2130fn route_dev_geometry(experts: usize, selected: usize) -> bool {
2134 selected > 0
2137 && selected <= 32
2138 && selected <= experts
2139 && experts % 2 == 0
2140 && experts * 12 <= 48 * 1024
2141}
2142
2143fn router_audit_on() -> bool {
2150 static C: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2151 *C.get_or_init(|| std::env::var("MEMRA_Q4E_ROUTER_AUDIT").as_deref() == Ok("1"))
2152}
2153fn route_sync_diag() -> bool {
2158 static C: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2159 *C.get_or_init(|| std::env::var("MEMRA_Q4E_ROUTE_SYNC").as_deref() == Ok("1"))
2160}
2161
2162pub fn peer_kv_max_cap() -> usize {
2192 static C: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
2193 *C.get_or_init(|| {
2194 std::env::var("MEMRA_Q4E_PEER_KV_MAX_CAP")
2195 .ok()
2196 .and_then(|v| v.trim().parse::<usize>().ok())
2197 .unwrap_or(8192)
2198 })
2199}
2200
2201const ROUTE_AUDIT_ULP_BOUND: u32 = 8;
2202static ROUTE_AUDIT_ROWS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
2203static ROUTE_AUDIT_MAX_ULP: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
2204pub fn route_audit_stats() -> (u64, u32) {
2206 (
2207 ROUTE_AUDIT_ROWS.load(std::sync::atomic::Ordering::Relaxed),
2208 ROUTE_AUDIT_MAX_ULP.load(std::sync::atomic::Ordering::Relaxed),
2209 )
2210}
2211
2212pub const IDX_CACHE_DEFAULT: bool = true;
2226static IDX_CACHE: std::sync::atomic::AtomicBool =
2227 std::sync::atomic::AtomicBool::new(IDX_CACHE_DEFAULT);
2228fn idx_cache_on() -> bool {
2229 IDX_CACHE.load(std::sync::atomic::Ordering::Relaxed)
2230}
2231pub fn set_idx_cache(on: bool) {
2232 IDX_CACHE.store(on, std::sync::atomic::Ordering::Relaxed);
2233}
2234
2235pub const KV_QUANT_DEFAULT: bool = true;
2259static KV_QUANT: std::sync::atomic::AtomicBool =
2260 std::sync::atomic::AtomicBool::new(KV_QUANT_DEFAULT);
2261fn kv_quant_on() -> bool {
2262 KV_QUANT.load(std::sync::atomic::Ordering::Relaxed)
2263}
2264pub fn set_kv_quant(on: bool) {
2265 KV_QUANT.store(on, std::sync::atomic::Ordering::Relaxed);
2266}
2267
2268pub const KV_HOIST_DEFAULT: bool = false;
2295static KV_HOIST: std::sync::atomic::AtomicBool =
2296 std::sync::atomic::AtomicBool::new(KV_HOIST_DEFAULT);
2297fn kv_hoist_on() -> bool {
2298 KV_HOIST.load(std::sync::atomic::Ordering::Relaxed)
2299}
2300pub fn set_kv_hoist(on: bool) {
2301 KV_HOIST.store(on, std::sync::atomic::Ordering::Relaxed);
2302}
2303pub fn kv_hoist_is_on() -> bool {
2305 kv_hoist_on()
2306}
2307
2308pub const POOL_T_DEFAULT: bool = false;
2327static POOL_T: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(POOL_T_DEFAULT);
2328fn pool_t_on() -> bool {
2329 POOL_T.load(std::sync::atomic::Ordering::Relaxed)
2330}
2331pub fn set_pool_t(on: bool) {
2332 POOL_T.store(on, std::sync::atomic::Ordering::Relaxed);
2333}
2334pub fn pool_t_is_on() -> bool {
2336 pool_t_on()
2337}
2338
2339pub fn kv_quant_is_on() -> bool {
2344 kv_quant_on()
2345}
2346
2347#[derive(Clone, Copy, PartialEq, Eq, Debug)]
2357pub enum IdxQMode {
2358 F32,
2359 Q8,
2360 Bf16,
2361}
2362static IDXQ_MODE: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(1);
2363fn idxq_mode() -> IdxQMode {
2364 match IDXQ_MODE.load(std::sync::atomic::Ordering::Relaxed) {
2365 1 => IdxQMode::Q8,
2366 2 => IdxQMode::Bf16,
2367 _ => IdxQMode::F32,
2368 }
2369}
2370pub fn set_idxq(mode: &str) {
2371 let v = match mode {
2372 "q8" | "1" => 1,
2373 "bf16" => 2,
2374 _ => 0,
2375 };
2376 IDXQ_MODE.store(v, std::sync::atomic::Ordering::Relaxed);
2377}
2378
2379pub fn idxq_mode_name() -> &'static str {
2381 match idxq_mode() {
2382 IdxQMode::F32 => "f32",
2383 IdxQMode::Q8 => "q8",
2384 IdxQMode::Bf16 => "bf16",
2385 }
2386}
2387
2388fn idxq_audit_on() -> bool {
2394 static C: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2395 *C.get_or_init(|| std::env::var("MEMRA_Q4E_IDXQ_AUDIT").as_deref() == Ok("1"))
2396}
2397static IDXQ_AUDIT_ROWS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
2398static IDXQ_AUDIT_FLIPPED: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
2399static IDXQ_AUDIT_BLOCKS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
2400pub fn idxq_audit_stats() -> (u64, u64, u64) {
2403 (
2404 IDXQ_AUDIT_ROWS.load(std::sync::atomic::Ordering::Relaxed),
2405 IDXQ_AUDIT_FLIPPED.load(std::sync::atomic::Ordering::Relaxed),
2406 IDXQ_AUDIT_BLOCKS.load(std::sync::atomic::Ordering::Relaxed),
2407 )
2408}
2409
2410#[derive(Clone, Copy, PartialEq, Eq)]
2415enum LongAttMode {
2416 Auto,
2417 Force,
2418 Off,
2419}
2420static LONGATT_MODE: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(0);
2421fn longatt_mode() -> LongAttMode {
2422 match LONGATT_MODE.load(std::sync::atomic::Ordering::Relaxed) {
2423 1 => LongAttMode::Force,
2424 2 => LongAttMode::Off,
2425 _ => LongAttMode::Auto,
2426 }
2427}
2428pub fn set_longatt(mode: &str) {
2429 let v = match mode {
2430 "force" | "1" => 1,
2431 "off" | "0" => 2,
2432 _ => 0,
2433 };
2434 LONGATT_MODE.store(v, std::sync::atomic::Ordering::Relaxed);
2435}
2436
2437#[derive(Debug, Clone)]
2469pub struct Tp2Placement {
2470 by_layer: std::collections::BTreeMap<u32, Vec<u8>>,
2472 expert_count: usize,
2473 entry_rank: u8,
2474 strategy: String,
2475 source: String,
2476}
2477
2478#[derive(Debug, Clone)]
2482pub struct LayerPlacement {
2483 pub card1: Vec<u32>,
2488 local_of: Vec<u32>,
2490 rank_of: Vec<u8>,
2492}
2493
2494impl LayerPlacement {
2495 #[inline]
2496 pub fn rank(&self, expert: usize) -> u8 {
2497 self.rank_of[expert]
2498 }
2499 #[inline]
2500 pub fn local(&self, expert: usize) -> usize {
2501 self.local_of[expert] as usize
2502 }
2503 pub fn is_even(&self) -> bool {
2505 let half = self.rank_of.len() / 2;
2506 self.card1.len() == half
2507 && self
2508 .card1
2509 .iter()
2510 .enumerate()
2511 .all(|(i, &e)| e as usize == half + i)
2512 }
2513}
2514
2515impl Tp2Placement {
2516 pub fn even(expert_count: usize) -> Self {
2518 Self {
2519 by_layer: std::collections::BTreeMap::new(),
2520 expert_count,
2521 entry_rank: 0,
2522 strategy: "even".to_string(),
2523 source: "built-in (MEMRA_Q4E_EP_MAP unset)".to_string(),
2524 }
2525 }
2526
2527 pub fn strategy(&self) -> &str {
2528 &self.strategy
2529 }
2530 pub fn source(&self) -> &str {
2531 &self.source
2532 }
2533 pub fn entry_rank(&self) -> u8 {
2534 self.entry_rank
2535 }
2536
2537 pub fn from_env(expert_count: usize) -> Res<Option<Self>> {
2539 let Ok(path) = std::env::var("MEMRA_Q4E_EP_MAP") else {
2540 return Ok(None);
2541 };
2542 if path.is_empty() || path == "0" {
2543 return Ok(None);
2544 }
2545 Some(Self::load(std::path::Path::new(&path), expert_count)).transpose()
2546 }
2547
2548 pub fn load(path: &std::path::Path, expert_count: usize) -> Res<Self> {
2549 let text = std::fs::read_to_string(path)
2550 .map_err(|e| format!("MEMRA_Q4E_EP_MAP {}: {e}", path.display()))?;
2551 let v = memra_tokenizer::json::parse(&text)
2552 .map_err(|e| format!("MEMRA_Q4E_EP_MAP {}: {e}", path.display()))?;
2553 let want = |k: &str| -> Res<Self> {
2556 Err(format!("MEMRA_Q4E_EP_MAP {}: {k}", path.display()).into())
2557 };
2558 match v.get("format").and_then(|f| f.as_str()) {
2559 Some("memra-ep-map-v1") => {}
2560 other => {
2561 return want(&format!(
2562 "format is {other:?}, expected \"memra-ep-map-v1\" (mint it with \
2563 tools/build_expert_placement_map.py)"
2564 ));
2565 }
2566 }
2567 let ranks = v.get("ranks").and_then(|r| r.as_u64()).unwrap_or(0);
2568 if ranks != 2 {
2569 return want(&format!(
2570 "ranks={ranks}, but the TP2 route is exactly two cards"
2571 ));
2572 }
2573 let map_experts = v.get("expert_count").and_then(|r| r.as_u64()).unwrap_or(0) as usize;
2574 if map_experts != expert_count {
2575 return want(&format!(
2576 "expert_count={map_experts} but this plan has {expert_count} experts"
2577 ));
2578 }
2579 if expert_count % 2 != 0 {
2588 return want(&format!(
2589 "this plan has {expert_count} routed experts, which is ODD: the TP2 route \
2590 splits the bank into two EQUAL-size device allocations, so no two-card \
2591 placement exists for it"
2592 ));
2593 }
2594 let entry_rank = v.get("entry_rank").and_then(|r| r.as_u64()).unwrap_or(0) as u8;
2595 if entry_rank > 1 {
2596 return want(&format!("entry_rank={entry_rank} outside {{0,1}}"));
2597 }
2598 let strategy = v
2599 .get("strategy")
2600 .and_then(|s| s.as_str())
2601 .unwrap_or("unnamed")
2602 .to_string();
2603 let Some(layers) = v.get("layers").and_then(|l| l.as_arr()) else {
2604 return want("no `layers` array");
2605 };
2606 let half = expert_count / 2;
2607 let mut by_layer = std::collections::BTreeMap::new();
2608 for row in layers {
2609 let Some(index) = row.get("layer").and_then(|l| l.as_u64()) else {
2610 return want("a layer row without an integer `layer`");
2611 };
2612 let Some(assign) = row.get("assignment").and_then(|a| a.as_arr()) else {
2613 return want(&format!("layer {index}: no `assignment` array"));
2614 };
2615 if assign.len() != expert_count {
2616 return want(&format!(
2617 "layer {index}: assignment has {} entries, expected {expert_count}",
2618 assign.len()
2619 ));
2620 }
2621 let mut ranks_vec = Vec::with_capacity(expert_count);
2622 for (eid, a) in assign.iter().enumerate() {
2623 match a.as_u64() {
2624 Some(r) if r <= 1 => ranks_vec.push(r as u8),
2625 other => {
2626 return want(&format!(
2627 "layer {index} expert {eid}: rank {other:?} outside {{0,1}}"
2628 ));
2629 }
2630 }
2631 }
2632 let on1 = ranks_vec.iter().filter(|&&r| r == 1).count();
2633 if on1 != half {
2634 return want(&format!(
2635 "layer {index}: card 1 owns {on1} experts but the bank halves are \
2636 equal-size allocations, so it must own exactly {half} — rebalance \
2637 the map (build_expert_placement_map.py --balance-tolerance)"
2638 ));
2639 }
2640 by_layer.insert(index as u32, ranks_vec);
2641 }
2642 if by_layer.is_empty() {
2643 return want("`layers` is empty");
2644 }
2645 Ok(Self {
2646 by_layer,
2647 expert_count,
2648 entry_rank,
2649 strategy,
2650 source: path.display().to_string(),
2651 })
2652 }
2653
2654 pub fn layer(&self, index: u32, expert_count: usize) -> Res<LayerPlacement> {
2658 if expert_count != self.expert_count {
2659 return Err(format!(
2660 "qwen4exp_gpu tp2 placement: layer {index} has {expert_count} experts, \
2661 map is for {}",
2662 self.expert_count
2663 )
2664 .into());
2665 }
2666 if expert_count % 2 != 0 {
2680 return Err(format!(
2681 "qwen4exp_gpu tp2 placement: layer {index} has {expert_count} routed \
2682 experts, which is ODD: the TP2 route splits the bank into two EQUAL-size \
2683 device allocations, so no two-card placement exists for it"
2684 )
2685 .into());
2686 }
2687 let half = expert_count / 2;
2688 let rank_of: Vec<u8> = if self.by_layer.is_empty() {
2689 (0..expert_count).map(|e| u8::from(e >= half)).collect()
2690 } else {
2691 self.by_layer
2692 .get(&index)
2693 .ok_or_else(|| {
2694 format!(
2695 "qwen4exp_gpu tp2 placement: map {} does not cover MoE layer \
2696 {index} (fail-closed; a partly-applied map is not a placement)",
2697 self.source
2698 )
2699 })?
2700 .clone()
2701 };
2702 let card1: Vec<u32> = (0..expert_count)
2703 .filter(|&e| rank_of[e] == 1)
2704 .map(|e| e as u32)
2705 .collect();
2706 let mut local_of = vec![0u32; expert_count];
2707 for (slot, &eid) in card1.iter().enumerate() {
2708 local_of[eid as usize] = slot as u32;
2709 }
2710 for e in 0..expert_count {
2712 if rank_of[e] == 0 {
2713 local_of[e] = e as u32;
2714 }
2715 }
2716 Ok(LayerPlacement {
2717 card1,
2718 local_of,
2719 rank_of,
2720 })
2721 }
2722}
2723
2724static TP2_PEER_EXPERT_SLOTS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
2731static TP2_HOME_EXPERT_SLOTS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
2736static TP2_BOTH_TOUCH_ROWS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
2739static TP2_TOUCH_ROWS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
2740
2741pub fn tp2_expert_split_stats() -> (u64, u64, u64, u64) {
2743 use std::sync::atomic::Ordering::Relaxed;
2744 (
2745 TP2_PEER_EXPERT_SLOTS.load(Relaxed),
2746 TP2_HOME_EXPERT_SLOTS.load(Relaxed),
2747 TP2_BOTH_TOUCH_ROWS.load(Relaxed),
2748 TP2_TOUCH_ROWS.load(Relaxed),
2749 )
2750}
2751
2752fn tp2_count_split(routes0: &[Vec<(usize, f32)>], routes1: &[Vec<(usize, f32)>]) {
2753 use std::sync::atomic::Ordering::Relaxed;
2754 let (mut peer, mut home, mut both) = (0u64, 0u64, 0u64);
2755 for (r0, r1) in routes0.iter().zip(routes1.iter()) {
2756 home += r0.len() as u64;
2757 peer += r1.len() as u64;
2758 if !r0.is_empty() && !r1.is_empty() {
2759 both += 1;
2760 }
2761 }
2762 TP2_HOME_EXPERT_SLOTS.fetch_add(home, Relaxed);
2763 TP2_PEER_EXPERT_SLOTS.fetch_add(peer, Relaxed);
2764 TP2_BOTH_TOUCH_ROWS.fetch_add(both, Relaxed);
2765 TP2_TOUCH_ROWS.fetch_add(routes0.len() as u64, Relaxed);
2766}
2767
2768#[derive(Clone, Copy, PartialEq, Eq, Debug)]
2773pub enum Tp2GateRed {
2774 None,
2775 SkipPeerMoe,
2777 PeerLocalIds,
2780 ReverseePeerWeights,
2782}
2783
2784fn tp2_gate_red() -> Res<Tp2GateRed> {
2785 static C: std::sync::OnceLock<Result<Tp2GateRed, String>> = std::sync::OnceLock::new();
2786 C.get_or_init(
2787 || match std::env::var("MEMRA_Q4E_TP2_GATE_RED").as_deref() {
2788 Err(_) | Ok("") | Ok("0") | Ok("none") => Ok(Tp2GateRed::None),
2789 Ok("skip-peer-moe") => Ok(Tp2GateRed::SkipPeerMoe),
2790 Ok("peer-local-ids") => Ok(Tp2GateRed::PeerLocalIds),
2791 Ok("reverse-peer-weights") => Ok(Tp2GateRed::ReverseePeerWeights),
2792 Ok(other) => Err(format!(
2793 "MEMRA_Q4E_TP2_GATE_RED={other:?}: want skip-peer-moe|peer-local-ids|\
2794 reverse-peer-weights|none"
2795 )),
2796 },
2797 )
2798 .clone()
2799 .map_err(Into::into)
2800}
2801
2802fn trace_moe_routes(layer: u32, t: usize, routes: &[Vec<(usize, f32)>]) {
2820 use std::io::Write as _;
2821 static IDS: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
2822 static WEIGHTS: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
2823 let ids = IDS.get_or_init(|| {
2824 std::env::var("MEMRA_MOE_TRACE")
2825 .ok()
2826 .filter(|p| !p.is_empty())
2827 });
2828 let weights = WEIGHTS.get_or_init(|| {
2829 std::env::var("MEMRA_MOE_WEIGHT_TRACE")
2830 .ok()
2831 .filter(|p| !p.is_empty())
2832 });
2833 if ids.is_none() && weights.is_none() {
2834 return;
2835 }
2836 let flat: Vec<&(usize, f32)> = routes.iter().flatten().collect();
2840 let append = |path: &str, body: String| {
2841 if let Ok(mut f) = std::fs::OpenOptions::new()
2842 .create(true)
2843 .append(true)
2844 .open(path)
2845 {
2846 let _ = writeln!(f, "{layer} {t} {body}");
2847 }
2848 };
2849 if let Some(path) = ids {
2850 let body: Vec<String> = flat.iter().map(|(e, _)| e.to_string()).collect();
2851 append(path, body.join(","));
2852 }
2853 if let Some(path) = weights {
2854 let body: Vec<String> = flat.iter().map(|(e, w)| format!("{e}:{w:.9}")).collect();
2855 append(path, body.join(","));
2856 }
2857}
2858
2859pub fn set_seam(name: &str, on: bool, value: Option<&str>) -> bool {
2871 seam_dispatch(name, on, value, true)
2872}
2873
2874pub fn seam_state(name: &str) -> Option<bool> {
2883 Some(match name {
2884 "moe" => moe_sel_path_on(),
2885 "hc" => hc_fused_gate_on(),
2886 "trunk" => trunk_bf16_on(),
2887 "ws" => step_ws_on(),
2888 "graph" => decode_graphs_on(),
2889 "selv2" => sel_v2_on(),
2890 "hcmicro" => hc_micro_on(),
2891 "selv3" => sel_v3_on(),
2892 "gdnstep" => gdn_step_on(),
2893 "gdnfuse" => gdn_fuse_on(),
2894 "projstack" => proj_stack_on(),
2895 "hcdiet" => hc_diet_on(),
2896 "gufuse" => sel_gufuse_on(),
2897 "routerb16" => router_bf16_on(),
2898 "vgraph" => verify_graphs_on(),
2899 "vfuse" => verify_fused_on(),
2900 "idxdev" => idx_dev_on(),
2901 "idxsel" => idx_sel_on(),
2902 "plecache" => ple_cache_on(),
2903 "routerdev" => router_dev_on(),
2904 "idxcache" => idx_cache_on(),
2905 "kvq" => kv_quant_on(),
2906 "kvhoist" => kv_hoist_on(),
2907 "poolT" => pool_t_on(),
2908 "selgroup" => sel_group_dn() != SEL_GROUP_OFF || sel_group_gu() != SEL_GROUP_OFF,
2917 _ => return None,
2918 })
2919}
2920
2921pub fn seam_exists(name: &str) -> bool {
2926 seam_dispatch(name, false, None, false)
2927}
2928
2929pub fn seam_names() -> &'static [&'static str] {
2938 &[
2939 "moe",
2940 "hc",
2941 "trunk",
2942 "ws",
2943 "graph",
2944 "selv2",
2945 "hcmicro",
2946 "selv3",
2947 "gdnstep",
2948 "gdnfuse",
2949 "projstack",
2950 "hcdiet",
2951 "gufuse",
2952 "routerb16",
2953 "vgraph",
2954 "vfuse",
2955 "longatt",
2956 "idxdev",
2957 "idxsel",
2958 "plecache",
2959 "routerdev",
2960 "idxcache",
2961 "kvq",
2962 "idxq",
2963 "kvhoist",
2964 "poolT",
2965 "selgroup",
2966 ]
2967}
2968
2969fn seam_dispatch(name: &str, on: bool, value: Option<&str>, apply: bool) -> bool {
2972 macro_rules! seam {
2973 ($call:expr) => {{
2974 if apply {
2975 $call;
2976 }
2977 true
2978 }};
2979 }
2980 match name {
2981 "moe" => seam!(set_moe_sel_path(on)),
2982 "hc" => seam!(set_hc_fused_gate(on)),
2983 "trunk" => seam!(set_trunk_bf16(on)),
2984 "ws" => seam!(set_step_ws(on)),
2985 "graph" => seam!(set_decode_graphs(on)),
2986 "selv2" => seam!(set_sel_v2(on)),
2987 "hcmicro" => seam!(set_hc_micro(on)),
2988 "selv3" => seam!(set_sel_v3(on)),
2989 "gdnstep" => seam!(set_gdn_step(on)),
2990 "gdnfuse" => seam!(set_gdn_fuse(on)),
2991 "projstack" => seam!(set_proj_stack(on)),
2992 "hcdiet" => seam!(set_hc_diet(on)),
2993 "gufuse" => seam!(set_sel_gufuse(on)),
2994 "routerb16" => seam!(set_router_bf16(on)),
2995 "vgraph" => seam!(set_verify_graphs(on)),
2996 "vfuse" => seam!(set_verify_fused(on)),
2999 "longatt" => seam!(set_longatt(if on { "force" } else { "off" })),
3000 "idxdev" => seam!(set_idx_dev(on)),
3001 "idxsel" => seam!(set_idx_sel(on)),
3002 "plecache" => seam!(set_ple_cache(on)),
3003 "routerdev" => seam!(set_router_dev(on)),
3004 "idxcache" => seam!(set_idx_cache(on)),
3005 "kvq" => seam!(set_kv_quant(on)),
3006 "kvhoist" => seam!(set_kv_hoist(on)),
3009 "poolT" => seam!(set_pool_t(on)),
3010 "idxq" => seam!(set_idxq(value.unwrap_or("q8"))),
3013 "selgroup" => {
3018 if apply {
3019 set_sel_group(if on { value.unwrap_or("auto") } else { "off" })
3020 } else {
3021 true
3022 }
3023 }
3024 _ => {
3025 debug_assert!(
3026 !seam_names().contains(&name),
3027 "seam_names() lists {name:?} but seam_dispatch has no arm for it"
3028 );
3029 false
3030 }
3031 }
3032}
3033
3034pub fn apply_env_seams() {
3035 let Ok(spec) = std::env::var("MEMRA_Q4E_SEAMS") else {
3036 return;
3037 };
3038 for part in spec.split(',').filter(|p| !p.is_empty()) {
3039 let (name, on) = match part.split_once('=') {
3040 Some((n, v)) => (n, v != "0"),
3041 None => (part, true),
3042 };
3043 if !set_seam(name, on, part.split_once('=').map(|(_, v)| v)) {
3044 eprintln!("MEMRA_Q4E_SEAMS: unknown seam {name:?} ignored");
3045 }
3046 }
3047}
3048
3049fn micro_norm_on() -> bool {
3050 hc_micro_on()
3051}
3052
3053fn micro_inj_on() -> bool {
3054 hc_micro_on()
3055}
3056
3057fn micro_shexp_on() -> bool {
3058 hc_micro_on()
3059}
3060
3061fn prof_section<T>(e: &Engine, name: &'static str, f: impl FnOnce() -> Res<T>) -> Res<T> {
3063 if !prof::on() {
3064 return f();
3065 }
3066 e.gpu.stream().synchronize()?;
3067 let t0 = std::time::Instant::now();
3068 let out = f()?;
3069 e.gpu.stream().synchronize()?;
3070 prof::add(name, t0.elapsed().as_secs_f64());
3071 Ok(out)
3072}
3073
3074fn host_sigmoid(x: f32) -> f32 {
3077 1.0 / (1.0 + (-x).exp())
3078}
3079
3080fn host_softmax(values: &mut [f32]) {
3082 let max = values.iter().copied().fold(f32::NEG_INFINITY, f32::max);
3083 let mut sum = 0.0;
3084 for value in values.iter_mut() {
3085 *value = (*value - max).exp();
3086 sum += *value;
3087 }
3088 for value in values {
3089 *value /= sum;
3090 }
3091}
3092
3093const ROUTE_DENOM_FLOOR: f32 = 6.103_515_6e-5;
3098
3099fn host_route_softmax_topk(logits: &[f32], selected: usize) -> Vec<(usize, f32)> {
3102 let mut weights = logits.to_vec();
3103 host_softmax(&mut weights);
3104 let mut indices: Vec<usize> = (0..logits.len()).collect();
3105 indices.sort_by(|&left, &right| {
3106 weights[right]
3107 .total_cmp(&weights[left])
3108 .then(left.cmp(&right))
3109 });
3110 indices.truncate(selected);
3111 let denominator = indices
3112 .iter()
3113 .map(|&index| weights[index])
3114 .sum::<f32>()
3115 .max(ROUTE_DENOM_FLOOR);
3116 indices
3117 .into_iter()
3118 .map(|index| (index, weights[index] / denominator))
3119 .collect()
3120}
3121
3122fn host_rms_norm(x: &mut [f32], width: usize, weight: &[f32], epsilon: f32) {
3124 for row in x.chunks_exact_mut(width) {
3125 let mean_square = row.iter().map(|v| v * v).sum::<f32>() / width as f32;
3126 let inverse = 1.0 / (mean_square + epsilon).sqrt();
3127 for (value, w) in row.iter_mut().zip(weight) {
3128 *value = *value * inverse * w;
3129 }
3130 }
3131}
3132
3133fn host_rope_at(
3138 values: &mut [f32],
3139 head_dim: usize,
3140 dimensions: usize,
3141 base: f32,
3142 yarn: Option<(&[f32], f32)>,
3143 position: usize,
3144) {
3145 let dimensions = dimensions.min(head_dim) / 2 * 2;
3146 let half = dimensions / 2;
3147 for head in values.chunks_exact_mut(head_dim) {
3148 for index in 0..half {
3149 let frequency = base.powf(-2.0 * index as f32 / dimensions as f32);
3150 let frequency = match yarn {
3151 Some((ff, _)) => frequency / ff[index],
3152 None => frequency,
3153 };
3154 let angle = position as f32 * frequency;
3155 let (sin, cos) = angle.sin_cos();
3156 let (sin, cos) = match yarn {
3157 Some((_, mscale)) => (sin * mscale, cos * mscale),
3158 None => (sin, cos),
3159 };
3160 let first = head[index];
3161 let second = head[index + half];
3162 head[index] = first * cos - second * sin;
3163 head[index + half] = first * sin + second * cos;
3164 }
3165 }
3166}
3167
3168#[derive(Clone, Copy, PartialEq, Eq)]
3171pub enum HeadMode {
3172 All,
3174 LastRow,
3176 Skip,
3178}
3179
3180struct RowSel {
3184 full: bool,
3186 blocks: Vec<u32>,
3188 visible: usize,
3191}
3192
3193#[allow(clippy::too_many_arguments)]
3199fn extend_pooled_keys(
3200 pooled_keys: &mut Vec<f32>,
3201 raw_keys: &IdxRawCache,
3202 head_dim: usize,
3203 block_size: usize,
3204 idx_k_norm: &[f32],
3205 epsilon: f32,
3206 rope_dims: usize,
3207 rope_base: f32,
3208 yarn: Option<(&[f32], f32)>,
3209 pos_off: usize,
3210) {
3211 let complete_total = raw_keys.rows(head_dim) / block_size;
3212 let cached = pooled_keys.len() / head_dim;
3213 let mut block_rows: Vec<f32> = Vec::new();
3214 for block in cached..complete_total {
3215 let start = block * block_size;
3216 raw_keys.rows_f32(start, block_size, head_dim, &mut block_rows);
3219 let mut pooled = vec![0.0f32; head_dim];
3220 for offset in 0..block_size {
3221 for dim in 0..head_dim {
3222 pooled[dim] += block_rows[offset * head_dim + dim];
3223 }
3224 }
3225 for value in &mut pooled {
3226 *value /= block_size as f32;
3227 }
3228 host_rms_norm(&mut pooled, head_dim, idx_k_norm, epsilon);
3229 host_rope_at(
3230 &mut pooled,
3231 head_dim,
3232 rope_dims,
3233 rope_base,
3234 yarn,
3235 start + pos_off,
3236 );
3237 pooled_keys.extend_from_slice(&pooled);
3238 }
3239}
3240
3241#[inline]
3244fn sel_cmp(scores: &[f32], a: u32, b: u32) -> std::cmp::Ordering {
3245 scores[b as usize]
3246 .total_cmp(&scores[a as usize])
3247 .then(a.cmp(&b))
3248}
3249
3250fn top_blocks_ascending(scores: &[f32], budget: usize, threads: usize) -> Vec<u32> {
3260 fn cut(scores: &[f32], idx: &mut Vec<u32>, budget: usize) {
3261 let k = budget.min(idx.len());
3262 if k < idx.len() {
3263 idx.select_nth_unstable_by(k - 1, |&a, &b| sel_cmp(scores, a, b));
3264 idx.truncate(k);
3265 }
3266 }
3267 let complete = scores.len();
3268 debug_assert!(budget < complete);
3269 const PAR_MIN: usize = 1 << 15;
3270 let mut candidates: Vec<u32> = if threads > 1 && complete >= PAR_MIN {
3271 let ranges: Vec<(u32, u32)> = {
3272 let per = complete.div_ceil(threads);
3273 (0..threads)
3274 .map(|i| ((i * per) as u32, ((i + 1) * per).min(complete) as u32))
3275 .filter(|(a, b)| a < b)
3276 .collect()
3277 };
3278 std::thread::scope(|scope| {
3279 let handles: Vec<_> = ranges
3280 .iter()
3281 .map(|&(a, b)| {
3282 scope.spawn(move || {
3283 let mut idx: Vec<u32> = (a..b).collect();
3284 cut(scores, &mut idx, budget);
3285 idx
3286 })
3287 })
3288 .collect();
3289 handles
3290 .into_iter()
3291 .flat_map(|h| h.join().unwrap())
3292 .collect()
3293 })
3294 } else {
3295 (0..complete as u32).collect()
3296 };
3297 cut(scores, &mut candidates, budget);
3298 candidates.sort_unstable();
3299 candidates
3300}
3301
3302fn score_blocks(
3307 query: &[f32],
3308 pooled_keys: &[f32],
3309 heads: usize,
3310 head_dim: usize,
3311 complete: usize,
3312 scale: f32,
3313 threads: usize,
3314) -> Vec<f32> {
3315 let mut scores = vec![0.0f32; complete];
3316 let run = |scores: &mut [f32], block0: usize| {
3317 for (i, slot) in scores.iter_mut().enumerate() {
3318 let block = block0 + i;
3319 let pooled = &pooled_keys[block * head_dim..(block + 1) * head_dim];
3320 let mut score = 0.0f32;
3321 for head in 0..heads {
3322 let mut dot = 0.0f32;
3323 for dim in 0..head_dim {
3324 dot += query[head * head_dim + dim] * pooled[dim];
3325 }
3326 score += dot.max(0.0);
3327 }
3328 *slot = score / scale;
3329 }
3330 };
3331 const PAR_MIN: usize = 1 << 14;
3332 if threads > 1 && complete >= PAR_MIN {
3333 let per = complete.div_ceil(threads);
3334 let run = &run;
3335 std::thread::scope(|scope| {
3336 for (i, chunk) in scores.chunks_mut(per).enumerate() {
3337 scope.spawn(move || run(chunk, i * per));
3338 }
3339 });
3340 } else {
3341 run(&mut scores, 0);
3342 }
3343 scores
3344}
3345
3346#[allow(clippy::too_many_arguments)]
3354fn indexer_select_rows(
3355 overlay: &MicroBlockIndexPlan,
3356 rope_base: f32,
3357 yarn: Option<(&[f32], f32)>,
3360 epsilon: f32,
3361 idx_q_norm: &[f32],
3362 idx_k_norm: &[f32],
3363 proj_rows: &[f32], raw_keys: &IdxRawCache, pooled_keys: &mut Vec<f32>,
3366 mut dev: Option<(&Engine, &mut Option<CudaSlice<f32>>, &mut usize)>,
3372 base_pos: usize,
3373 t: usize,
3374 t_kv: usize,
3375 pos_off: usize,
3379) -> Res<Vec<RowSel>> {
3380 let heads = overlay.query_heads as usize;
3381 let head_dim = overlay.head_dim as usize;
3382 let block_size = overlay.block_size as usize;
3383 let budget_blocks = overlay.budget_blocks as usize;
3384 let rope_dims = overlay.rope_dimensions as usize;
3385 let qk_width = (heads + overlay.kv_heads as usize) * head_dim;
3386 let scale = (head_dim as f32).sqrt();
3387 debug_assert_eq!(raw_keys.rows(head_dim), t_kv);
3388 extend_pooled_keys(
3389 pooled_keys,
3390 raw_keys,
3391 head_dim,
3392 block_size,
3393 idx_k_norm,
3394 epsilon,
3395 rope_dims,
3396 rope_base,
3397 yarn,
3398 pos_off,
3399 );
3400 let threads = std::thread::available_parallelism()
3401 .map(|n| n.get())
3402 .unwrap_or(1);
3403 if let Some((e, mirror, mirrored)) = dev.as_mut() {
3407 let rows_needed: Vec<usize> = (0..t)
3408 .map(|qt| (base_pos + qt + 1) / block_size)
3409 .filter(|&c| c > budget_blocks)
3410 .collect();
3411 if let Some(&max_blocks) = rows_needed.iter().max() {
3412 let pooled_rows = pooled_keys.len() / head_dim;
3413 let want = pooled_rows.max(max_blocks);
3415 if mirror
3420 .as_ref()
3421 .is_none_or(|m| m.len() < want * head_dim * POOL_PLANES)
3422 {
3423 let cap_rows = want.next_power_of_two().max(1024);
3424 let fresh = e.zeros(cap_rows * head_dim * POOL_PLANES)?;
3425 **mirror = Some(fresh);
3432 **mirrored = 0;
3433 }
3434 let m = mirror.as_mut().expect("allocated above");
3435 if pooled_rows > **mirrored {
3436 let delta = &pooled_keys[**mirrored * head_dim..pooled_rows * head_dim];
3437 let mut view = m.slice_mut(**mirrored * head_dim..pooled_rows * head_dim);
3438 e.gpu.stream().memcpy_htod(delta, &mut view)?;
3439 let cap_rows = m.len() / (head_dim * POOL_PLANES);
3461 launch_qsa_pooled_transpose(
3462 e,
3463 m,
3464 **mirrored,
3465 pooled_rows - **mirrored,
3466 head_dim,
3467 cap_rows,
3468 )?;
3469 **mirrored = pooled_rows;
3470 }
3471 let mut sels: Vec<RowSel> = Vec::with_capacity(t);
3473 let mut queries: Vec<f32> = Vec::new();
3474 let mut scored_rows: Vec<usize> = Vec::new();
3475 for qt in 0..t {
3476 let row = base_pos + qt;
3477 let visible = row + 1;
3478 let complete = visible / block_size;
3479 if complete <= budget_blocks {
3480 sels.push(RowSel {
3481 full: true,
3482 blocks: Vec::new(),
3483 visible,
3484 });
3485 continue;
3486 }
3487 let mut query = proj_rows[qt * qk_width..qt * qk_width + heads * head_dim].to_vec();
3488 host_rms_norm(&mut query, head_dim, idx_q_norm, epsilon);
3489 host_rope_at(
3490 &mut query,
3491 head_dim,
3492 rope_dims,
3493 rope_base,
3494 yarn,
3495 row + pos_off,
3496 );
3497 queries.extend_from_slice(&query);
3498 scored_rows.push(qt);
3499 sels.push(RowSel {
3500 full: false,
3501 blocks: Vec::new(),
3502 visible,
3503 });
3504 }
3505 let score_cap_mf: usize = std::env::var("MEMRA_Q4E_IDX_SCORE_CAP_MF")
3522 .ok()
3523 .and_then(|v| v.parse::<usize>().ok())
3524 .filter(|v| *v > 0)
3525 .unwrap_or(32);
3526 let score_cap: usize = score_cap_mf << 20;
3527 #[allow(non_snake_case)]
3528 let SCORE_CAP = score_cap;
3529 let mut done = 0usize;
3530 while done < scored_rows.len() {
3531 let batch_max = scored_rows[done..]
3534 .iter()
3535 .map(|&qt| (base_pos + qt + 1) / block_size)
3536 .max()
3537 .unwrap_or(0);
3538 let per = (SCORE_CAP / batch_max.max(1)).max(1);
3539 let n = per.min(scored_rows.len() - done);
3540 let qslab = &queries[done * heads * head_dim..(done + n) * heads * head_dim];
3541 let q_dev = e.htod(qslab)?;
3542 let mut scores_dev = e.uninit(n * batch_max)?;
3543 launch_qsa_index_score(
3544 e,
3545 &q_dev,
3546 m,
3547 &mut scores_dev,
3548 heads,
3549 head_dim,
3550 batch_max,
3551 n,
3552 scale,
3553 )?;
3554 if idx_sel_on() {
3555 let counts: Vec<usize> = (0..n)
3560 .map(|i| (base_pos + scored_rows[done + i] + 1) / block_size)
3561 .collect();
3562 let picked =
3563 launch_qsa_index_topk(e, &scores_dev, &counts, batch_max, budget_blocks)?;
3564 if idx_sel_audit_on() {
3565 let host = e.dtoh(&scores_dev)?;
3566 let mut mismatched = 0u64;
3567 let mut deepest = 0u64;
3568 for i in 0..n {
3569 let complete = counts[i];
3570 let row_scores = &host[i * batch_max..i * batch_max + complete];
3571 let twin = top_blocks_ascending(row_scores, budget_blocks, threads);
3572 if twin != picked[i] {
3573 mismatched += 1;
3574 }
3575 deepest = deepest.max(complete as u64);
3576 }
3577 IDX_SEL_AUDIT_ROWS
3578 .fetch_add(n as u64, std::sync::atomic::Ordering::Relaxed);
3579 IDX_SEL_AUDIT_MISMATCH
3580 .fetch_add(mismatched, std::sync::atomic::Ordering::Relaxed);
3581 IDX_SEL_AUDIT_MAX_BLOCKS
3582 .fetch_max(deepest, std::sync::atomic::Ordering::Relaxed);
3583 if mismatched > 0 {
3584 return Err(format!(
3585 "idxsel audit: {mismatched} of {n} device selections differ \
3586 from the host twin (ids or order) at fill {t_kv}"
3587 )
3588 .into());
3589 }
3590 }
3591 for (i, blocks) in picked.into_iter().enumerate() {
3592 sels[scored_rows[done + i]].blocks = blocks;
3593 }
3594 } else {
3595 let host = e.dtoh(&scores_dev)?;
3596 for i in 0..n {
3597 let qt = scored_rows[done + i];
3598 let complete = (base_pos + qt + 1) / block_size;
3599 let row_scores = &host[i * batch_max..i * batch_max + complete];
3600 sels[qt].blocks = top_blocks_ascending(row_scores, budget_blocks, threads);
3601 }
3602 }
3603 done += n;
3604 }
3605 for sel in &sels {
3606 if sel.visible == 0
3607 || (!sel.full && sel.blocks.is_empty() && sel.visible % block_size == 0)
3608 {
3609 return Err("indexer selection left a query with no visible source".into());
3610 }
3611 }
3612 return Ok(sels);
3613 }
3614 }
3615 let pooled_ref: &[f32] = pooled_keys;
3616 let select_row = |qt: usize, threads_in_row: usize| -> RowSel {
3617 let row = base_pos + qt;
3618 let position = row + pos_off;
3619 let visible = row + 1;
3620 let complete = visible / block_size;
3621 if complete <= budget_blocks {
3628 return RowSel {
3629 full: true,
3630 blocks: Vec::new(),
3631 visible,
3632 };
3633 }
3634 let mut query = proj_rows[qt * qk_width..qt * qk_width + heads * head_dim].to_vec();
3635 host_rms_norm(&mut query, head_dim, idx_q_norm, epsilon);
3636 host_rope_at(&mut query, head_dim, rope_dims, rope_base, yarn, position);
3637 let scores = score_blocks(
3638 &query,
3639 pooled_ref,
3640 heads,
3641 head_dim,
3642 complete,
3643 scale,
3644 threads_in_row,
3645 );
3646 let blocks = top_blocks_ascending(&scores, budget_blocks, threads_in_row);
3647 RowSel {
3648 full: false,
3649 blocks,
3650 visible,
3651 }
3652 };
3653 const ROW_PAR_MIN_WORK: usize = 1 << 16;
3654 let total_scored_blocks: usize = (0..t)
3655 .map(|qt| {
3656 let complete = (base_pos + qt + 1) / block_size;
3657 if complete <= budget_blocks {
3658 0
3659 } else {
3660 complete
3661 }
3662 })
3663 .sum();
3664 let sels: Vec<RowSel> = if t > 1 && threads > 1 && total_scored_blocks >= ROW_PAR_MIN_WORK {
3665 let cursor = std::sync::atomic::AtomicUsize::new(0);
3668 let mut out: Vec<Option<RowSel>> = (0..t).map(|_| None).collect();
3669 let slots = std::sync::Mutex::new(&mut out);
3670 std::thread::scope(|scope| {
3671 for _ in 0..threads.min(t) {
3672 scope.spawn(|| {
3673 loop {
3674 let qt = cursor.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3675 if qt >= t {
3676 break;
3677 }
3678 let sel = select_row(qt, 1);
3679 slots.lock().unwrap()[qt] = Some(sel);
3680 }
3681 });
3682 }
3683 });
3684 out.into_iter().map(|s| s.unwrap()).collect()
3685 } else {
3686 (0..t).map(|qt| select_row(qt, threads)).collect()
3687 };
3688 for sel in &sels {
3689 if sel.visible == 0 || (!sel.full && sel.blocks.is_empty() && sel.visible % block_size == 0)
3690 {
3691 return Err("indexer selection left a query with no visible source".into());
3692 }
3693 }
3694 Ok(sels)
3695}
3696
3697fn rowsel_to_mask(sels: &[RowSel], block_size: usize, t_kv: usize) -> Vec<u8> {
3700 let t = sels.len();
3701 let mut mask = vec![0u8; t * t_kv];
3702 for (qt, sel) in sels.iter().enumerate() {
3703 let row = &mut mask[qt * t_kv..(qt + 1) * t_kv];
3704 if sel.full {
3705 for slot in row.iter_mut().take(sel.visible) {
3706 *slot = 1;
3707 }
3708 continue;
3709 }
3710 for &block in &sel.blocks {
3711 for offset in 0..block_size {
3712 row[block as usize * block_size + offset] = 1;
3713 }
3714 }
3715 let complete = sel.visible / block_size;
3716 for slot in row.iter_mut().take(sel.visible).skip(complete * block_size) {
3717 *slot = 1;
3718 }
3719 }
3720 mask
3721}
3722
3723fn rowsel_positions(sels: &[RowSel], block_size: usize) -> (Vec<i32>, Vec<i32>, usize) {
3728 let mut flat: Vec<i32> = Vec::new();
3729 let mut meta: Vec<i32> = Vec::with_capacity(sels.len() * 2);
3730 let mut max_count = 0usize;
3731 for sel in sels {
3732 let start = flat.len();
3733 if sel.full {
3734 flat.extend(0..sel.visible as i32);
3735 } else {
3736 for &block in &sel.blocks {
3737 let first = block as usize * block_size;
3738 flat.extend(first as i32..(first + block_size) as i32);
3739 }
3740 let complete = sel.visible / block_size;
3741 flat.extend((complete * block_size) as i32..sel.visible as i32);
3742 }
3743 let count = flat.len() - start;
3744 max_count = max_count.max(count);
3745 meta.push(start as i32);
3746 meta.push(count as i32);
3747 }
3748 (flat, meta, max_count)
3749}
3750
3751#[allow(clippy::too_many_arguments)]
3755fn launch_qsa_index_score(
3756 e: &Engine,
3757 q: &CudaSlice<f32>,
3758 pooled: &CudaSlice<f32>,
3759 out: &mut CudaSlice<f32>,
3760 heads: usize,
3761 head_dim: usize,
3762 n_blocks: usize,
3763 rows: usize,
3764 scale: f32,
3765) -> Res<()> {
3766 if rows == 0 || n_blocks == 0 {
3767 return Ok(());
3768 }
3769 if out.len() < rows * n_blocks {
3770 return Err("qsa_index_score_f32: score slab too short".into());
3771 }
3772 if rows > 65535 {
3773 return Err("qsa_index_score_f32: rows exceed grid.y (caller sub-batches)".into());
3774 }
3775 let cap_rows = pooled.len() / (head_dim * POOL_PLANES);
3780 let pool_t = pool_t_on();
3781 if pool_t && cap_rows < n_blocks {
3782 return Err("qsa_index_score_f32_t: pooled plane capacity below n_blocks".into());
3783 }
3784 let f = e.func(if pool_t {
3785 "qsa_index_score_f32_t"
3786 } else {
3787 "qsa_index_score_f32"
3788 });
3789 const TPB: usize = 128;
3790 let cfg = LaunchConfig {
3791 grid_dim: (n_blocks.div_ceil(TPB) as u32, rows as u32, 1),
3792 block_dim: (TPB as u32, 1, 1),
3793 shared_mem_bytes: 0,
3794 };
3795 let (h, hd, nb, r) = (heads as i32, head_dim as i32, n_blocks as i32, rows as i32);
3796 let pitch = cap_rows as i64;
3797 let stream = e.gpu.stream();
3798 if pool_t {
3799 let plane = pooled.slice(cap_rows * head_dim..cap_rows * head_dim * POOL_PLANES);
3802 let mut b = stream.launch_builder(&f);
3803 b.arg(q)
3804 .arg(&plane)
3805 .arg(&mut *out)
3806 .arg(&h)
3807 .arg(&hd)
3808 .arg(&nb)
3809 .arg(&r)
3810 .arg(&scale)
3811 .arg(&pitch);
3812 unsafe {
3813 b.launch(cfg)?;
3814 }
3815 return Ok(());
3816 }
3817 let mut b = stream.launch_builder(&f);
3818 b.arg(q)
3819 .arg(pooled)
3820 .arg(&mut *out)
3821 .arg(&h)
3822 .arg(&hd)
3823 .arg(&nb)
3824 .arg(&r)
3825 .arg(&scale);
3826 unsafe {
3827 b.launch(cfg)?;
3828 }
3829 Ok(())
3830}
3831
3832const POOL_PLANES: usize = 2;
3836
3837fn launch_qsa_pooled_transpose(
3840 e: &Engine,
3841 buf: &mut CudaSlice<f32>,
3842 r0: usize,
3843 rows: usize,
3844 head_dim: usize,
3845 cap_rows: usize,
3846) -> Res<()> {
3847 if rows == 0 {
3848 return Ok(());
3849 }
3850 if r0 + rows > cap_rows {
3851 return Err("qsa_pooled_transpose_f32: delta exceeds the plane capacity".into());
3852 }
3853 let f = e.func("qsa_pooled_transpose_f32");
3854 const TPB: usize = 128;
3855 let cfg = LaunchConfig {
3856 grid_dim: (rows.div_ceil(TPB) as u32, head_dim as u32, 1),
3857 block_dim: (TPB as u32, 1, 1),
3858 shared_mem_bytes: 0,
3859 };
3860 let (r, hd, r0i) = (rows as i32, head_dim as i32, r0 as i32);
3861 let cap = cap_rows as i64;
3862 let stream = e.gpu.stream();
3863 let mut b = stream.launch_builder(&f);
3864 b.arg(buf).arg(&r).arg(&hd).arg(&r0i).arg(&cap);
3865 unsafe {
3866 b.launch(cfg)?;
3867 }
3868 Ok(())
3869}
3870
3871fn launch_qsa_index_topk(
3877 e: &Engine,
3878 scores: &CudaSlice<f32>,
3879 counts: &[usize],
3880 stride: usize,
3881 budget: usize,
3882) -> Res<Vec<Vec<u32>>> {
3883 let rows = counts.len();
3884 if rows == 0 || budget == 0 {
3885 return Ok(Vec::new());
3886 }
3887 if rows > 65535 {
3888 return Err("qsa_index_topk_u32: rows exceed grid.x (caller sub-batches)".into());
3889 }
3890 if scores.len() < rows * stride {
3891 return Err("qsa_index_topk_u32: score slab too short".into());
3892 }
3893 for (r, &c) in counts.iter().enumerate() {
3894 if c <= budget || c > stride {
3895 return Err(format!(
3896 "qsa_index_topk_u32: row {r} block count {c} outside (budget {budget}, \
3897 stride {stride}] — the caller only routes scored rows here"
3898 )
3899 .into());
3900 }
3901 }
3902 let counts_i32: Vec<i32> = counts.iter().map(|&c| c as i32).collect();
3903 let counts_dev = e.htod_i32(&counts_i32)?;
3904 let mut out = e.htod_i32(&vec![-1i32; rows * budget])?;
3906 let f = e.func("qsa_index_topk_u32");
3907 let cfg = LaunchConfig {
3908 grid_dim: (rows as u32, 1, 1),
3909 block_dim: (256, 1, 1),
3910 shared_mem_bytes: 0,
3911 };
3912 let (st, bu, ro) = (stride as i32, budget as i32, rows as i32);
3913 let stream = e.gpu.stream();
3914 let mut b = stream.launch_builder(&f);
3915 b.arg(scores)
3916 .arg(&counts_dev)
3917 .arg(&mut out)
3918 .arg(&st)
3919 .arg(&bu)
3920 .arg(&ro);
3921 unsafe {
3922 b.launch(cfg)?;
3923 }
3924 let host = e.gpu.stream().clone_dtoh(&out)?;
3925 e.gpu.stream().synchronize()?;
3926 let mut out_rows: Vec<Vec<u32>> = Vec::with_capacity(rows);
3927 for r in 0..rows {
3928 let row = &host[r * budget..(r + 1) * budget];
3929 let mut blocks: Vec<u32> = Vec::with_capacity(budget);
3930 let mut prev: i64 = -1;
3931 for (j, &v) in row.iter().enumerate() {
3932 if v < 0 || (v as usize) >= counts[r] || (v as i64) <= prev {
3933 return Err(format!(
3934 "qsa_index_topk_u32: row {r} slot {j} = {v} is not a strictly ascending \
3935 in-range block id (blocks {}, budget {budget})",
3936 counts[r]
3937 )
3938 .into());
3939 }
3940 prev = v as i64;
3941 blocks.push(v as u32);
3942 }
3943 out_rows.push(blocks);
3944 }
3945 Ok(out_rows)
3946}
3947
3948#[allow(clippy::too_many_arguments)]
3952fn launch_copy_rows_col(
3953 e: &Engine,
3954 src: &CudaSlice<f32>,
3955 dst: &mut CudaSlice<f32>,
3956 rows: usize,
3957 width: usize,
3958 src_stride: usize,
3959 src_col: usize,
3960 dst_row: usize,
3961) -> Res<()> {
3962 if rows == 0 {
3963 return Ok(());
3964 }
3965 if src.len() < (rows - 1) * src_stride + src_col + width || dst.len() < (dst_row + rows) * width
3966 {
3967 return Err("copy_rows_col_f32: window out of range".into());
3968 }
3969 let f = e.func("copy_rows_col_f32");
3970 let total = rows * width;
3971 let cfg = LaunchConfig::for_num_elems(total as u32);
3972 let (r, w) = (rows as i32, width as i32);
3973 let (ss, sc, dr) = (src_stride as i64, src_col as i64, dst_row as i64);
3974 let stream = e.gpu.stream();
3975 let mut b = stream.launch_builder(&f);
3976 b.arg(src)
3977 .arg(&mut *dst)
3978 .arg(&r)
3979 .arg(&w)
3980 .arg(&ss)
3981 .arg(&sc)
3982 .arg(&dr);
3983 unsafe {
3984 b.launch(cfg)?;
3985 }
3986 Ok(())
3987}
3988
3989#[allow(clippy::too_many_arguments)]
3996fn launch_route_topk(
3997 e: &Engine,
3998 logits: &CudaSlice<f32>,
3999 sel: &mut CudaSlice<i32>,
4000 w: &mut CudaSlice<f32>,
4001 tok: Option<(&mut CudaSlice<i32>, usize)>,
4002 experts: usize,
4003 selected: usize,
4004 rows: usize,
4005) -> Res<()> {
4006 if rows == 0 {
4007 return Ok(());
4008 }
4009 if selected == 0 || selected > 32 || selected > experts {
4010 return Err("qwen4exp_route_topk_f32: selected out of range (caller guards)".into());
4011 }
4012 if experts % 2 != 0 {
4013 return Err("qwen4exp_route_topk_f32: odd expert count".into());
4016 }
4017 if logits.len() < rows * experts || sel.len() < rows * selected || w.len() < rows * selected {
4018 return Err("qwen4exp_route_topk_f32: buffer too short".into());
4019 }
4020 let smem = experts * 12; if smem > 48 * 1024 {
4022 return Err("qwen4exp_route_topk_f32: experts exceed the smem bound".into());
4023 }
4024 let stream = e.gpu.stream();
4025 let (tok_raw, tok_base) = match tok {
4026 Some((buf, base)) => {
4027 if buf.len() < rows * selected {
4028 return Err("qwen4exp_route_topk_f32: tok map too short".into());
4029 }
4030 (buf.device_ptr(&stream).0, base)
4031 }
4032 None => (0u64, 0usize),
4033 };
4034 let f = e.func("qwen4exp_route_topk_f32");
4035 let cfg = LaunchConfig {
4036 grid_dim: (rows as u32, 1, 1),
4037 block_dim: (128, 1, 1),
4038 shared_mem_bytes: smem as u32,
4039 };
4040 let (ex, se, ro, tb) = (
4041 experts as i32,
4042 selected as i32,
4043 rows as i32,
4044 tok_base as i32,
4045 );
4046 let floor = ROUTE_DENOM_FLOOR;
4047 let mut b = stream.launch_builder(&f);
4048 b.arg(logits)
4049 .arg(&mut *sel)
4050 .arg(&mut *w)
4051 .arg(&tok_raw)
4052 .arg(&ex)
4053 .arg(&se)
4054 .arg(&ro)
4055 .arg(&tb)
4056 .arg(&floor);
4057 unsafe {
4058 b.launch(cfg)?;
4059 }
4060 Ok(())
4061}
4062
4063#[allow(clippy::too_many_arguments)]
4067fn route_topk_device(
4068 e: &Engine,
4069 logits: &CudaSlice<f32>,
4070 sel: &mut CudaSlice<i32>,
4071 w: &mut CudaSlice<f32>,
4072 tok: Option<(&mut CudaSlice<i32>, usize)>,
4073 experts: usize,
4074 selected: usize,
4075 rows: usize,
4076 layer: u32,
4077) -> Res<()> {
4078 launch_route_topk(e, logits, sel, w, tok, experts, selected, rows)?;
4079 if !router_audit_on() {
4080 return Ok(());
4081 }
4082 let k = selected.min(experts);
4083 let lg = e.dtoh_view(&logits.slice(0..rows * experts))?;
4084 let sel_h = e.gpu.stream().clone_dtoh(&sel.slice(0..rows * selected))?;
4085 let w_h = e.gpu.stream().clone_dtoh(&w.slice(0..rows * selected))?;
4086 {
4100 let routes: Vec<Vec<(usize, f32)>> = (0..rows)
4101 .map(|row| {
4102 (0..selected)
4103 .map(|j| {
4104 (
4105 sel_h[row * selected + j].max(0) as usize,
4106 w_h[row * selected + j],
4107 )
4108 })
4109 .collect()
4110 })
4111 .collect();
4112 trace_moe_routes(layer, rows, &routes);
4113 }
4114 let mut worst: u32 = 0;
4115 for row in 0..rows {
4116 let twin = host_route_softmax_topk(&lg[row * experts..(row + 1) * experts], selected);
4117 if twin.len() != k {
4118 return Err("router audit: host twin emitted an unexpected selection width".into());
4119 }
4120 for (j, &(idx, wt)) in twin.iter().enumerate() {
4121 let ds = sel_h[row * selected + j];
4122 let dw = w_h[row * selected + j];
4123 if ds != idx as i32 {
4124 return Err(format!(
4125 "router audit: selection mismatch at row {row} slot {j}: \
4126 device {ds} vs host {idx} (host w {wt:e})"
4127 )
4128 .into());
4129 }
4130 let ulp = (dw.to_bits() as i64 - wt.to_bits() as i64).unsigned_abs();
4131 let ulp = u32::try_from(ulp).unwrap_or(u32::MAX);
4132 worst = worst.max(ulp);
4133 if ulp > ROUTE_AUDIT_ULP_BOUND {
4134 return Err(format!(
4135 "router audit: weight ULP {ulp} > bound {ROUTE_AUDIT_ULP_BOUND} at \
4136 row {row} slot {j}: device {dw:e} vs host {wt:e}"
4137 )
4138 .into());
4139 }
4140 }
4141 }
4142 ROUTE_AUDIT_ROWS.fetch_add(rows as u64, std::sync::atomic::Ordering::Relaxed);
4143 ROUTE_AUDIT_MAX_ULP.fetch_max(worst, std::sync::atomic::Ordering::Relaxed);
4144 Ok(())
4145}
4146
4147#[allow(dead_code)]
4152#[allow(clippy::too_many_arguments)]
4153fn indexer_mask_rows(
4154 overlay: &MicroBlockIndexPlan,
4155 rope_base: f32,
4156 yarn: Option<(&[f32], f32)>,
4157 epsilon: f32,
4158 idx_q_norm: &[f32],
4159 idx_k_norm: &[f32],
4160 proj_rows: &[f32],
4161 raw_keys: &IdxRawCache,
4162 pooled_keys: &mut Vec<f32>,
4163 base_pos: usize,
4164 t: usize,
4165 t_kv: usize,
4166 pos_off: usize,
4167) -> Res<Vec<u8>> {
4168 let sels = indexer_select_rows(
4169 overlay,
4170 rope_base,
4171 yarn,
4172 epsilon,
4173 idx_q_norm,
4174 idx_k_norm,
4175 proj_rows,
4176 raw_keys,
4177 pooled_keys,
4178 None,
4181 base_pos,
4182 t,
4183 t_kv,
4184 pos_off,
4185 )?;
4186 Ok(rowsel_to_mask(&sels, overlay.block_size as usize, t_kv))
4187}
4188
4189fn shift_right_ignore_eos(history: &[i64], shift: usize, eos: i64) -> Vec<i64> {
4191 if shift == 0 {
4192 return history.to_vec();
4193 }
4194 let mut last_eos_inclusive: i64 = -1;
4195 let mut output = Vec::with_capacity(history.len());
4196 for (position, &token) in history.iter().enumerate() {
4197 let previous_eos = last_eos_inclusive;
4198 if token == eos {
4199 last_eos_inclusive = position as i64;
4200 }
4201 let segment_start = previous_eos + 1;
4202 let position_in_segment = position as i64 - segment_start;
4203 let source = position as i64 - shift as i64;
4204 let valid = position_in_segment >= shift as i64 && source >= 0;
4205 output.push(if valid { history[source as usize] } else { eos });
4206 }
4207 output
4208}
4209
4210#[allow(clippy::too_many_arguments)]
4228fn host_ngram_ids_cached(
4229 cache_ids: &mut Vec<i64>,
4230 cache_history: &mut Vec<i64>,
4231 cache_last_eos: &mut i64,
4232 token_ids: &[u32],
4233 multipliers: &[i64],
4234 sizes: &[i64],
4235 offsets: &[i64],
4236 max_ngram: usize,
4237 heads_per_ngram: usize,
4238 eos_token_id: u32,
4239) {
4240 let context = max_ngram - 1;
4241 let eos = eos_token_id as i64;
4242 let total_heads = (max_ngram - 1) * heads_per_ngram;
4243 if cache_history.is_empty() {
4244 cache_history.extend(std::iter::repeat_n(eos, context));
4245 *cache_last_eos = context as i64 - 1; cache_ids.clear();
4247 }
4248 let cached_tokens = (cache_history.len() - context).min(cache_ids.len() / total_heads);
4249 let mut keep = cached_tokens.min(token_ids.len());
4251 for i in 0..keep {
4252 if cache_history[context + i] != token_ids[i] as i64 {
4253 keep = i;
4254 break;
4255 }
4256 }
4257 if keep < cached_tokens {
4258 cache_history.truncate(context + keep);
4260 cache_ids.truncate(keep * total_heads);
4261 *cache_last_eos = cache_history
4262 .iter()
4263 .rposition(|&v| v == eos)
4264 .map(|p| p as i64)
4265 .unwrap_or(-1);
4266 }
4267 for &token in &token_ids[keep..] {
4268 let position = cache_history.len();
4269 let value = token as i64;
4270 cache_history.push(value);
4271 let previous_eos = *cache_last_eos;
4273 if value == eos {
4274 *cache_last_eos = position as i64;
4275 }
4276 let segment_start = previous_eos + 1;
4277 let position_in_segment = position as i64 - segment_start;
4278 let shifted_at = |shift: usize| -> i64 {
4279 if shift == 0 {
4280 return cache_history[position];
4281 }
4282 let source = position as i64 - shift as i64;
4283 if position_in_segment >= shift as i64 && source >= 0 {
4284 cache_history[source as usize]
4285 } else {
4286 eos
4287 }
4288 };
4289 let mut row = vec![0i64; total_heads];
4291 for ngram in 2..=max_ngram {
4292 let head_start = (ngram - 2) * heads_per_ngram;
4293 let mut mixed = shifted_at(0).wrapping_mul(multipliers[0]);
4294 for shift in 1..ngram {
4295 mixed ^= shifted_at(shift).wrapping_mul(multipliers[shift]);
4296 }
4297 for head in 0..heads_per_ngram {
4298 let index = head_start + head;
4299 row[index] = mixed.rem_euclid(sizes[index]) + offsets[index];
4300 }
4301 }
4302 cache_ids.extend_from_slice(&row);
4303 }
4304 debug_assert_eq!(cache_ids.len(), token_ids.len() * total_heads);
4305 }
4309
4310fn host_ngram_ids(
4313 token_ids: &[u32],
4314 multipliers: &[i64],
4315 sizes: &[i64],
4316 offsets: &[i64],
4317 max_ngram: usize,
4318 heads_per_ngram: usize,
4319 eos_token_id: u32,
4320) -> Vec<i64> {
4321 let context = max_ngram - 1;
4322 let eos = eos_token_id as i64;
4323 let total_heads = (max_ngram - 1) * heads_per_ngram;
4324 let mut history = Vec::with_capacity(context + token_ids.len());
4325 history.extend(std::iter::repeat_n(eos, context));
4326 history.extend(token_ids.iter().map(|&token| token as i64));
4327 let shifted: Vec<Vec<i64>> = (0..max_ngram)
4328 .map(|shift| shift_right_ignore_eos(&history, shift, eos))
4329 .collect();
4330 let tokens = token_ids.len();
4331 let mut ids = vec![0i64; tokens * total_heads];
4332 for ngram in 2..=max_ngram {
4333 let head_start = (ngram - 2) * heads_per_ngram;
4334 for token in 0..tokens {
4335 let position = context + token;
4336 let mut mixed = shifted[0][position].wrapping_mul(multipliers[0]);
4337 for (shift, row) in shifted.iter().enumerate().take(ngram).skip(1) {
4338 mixed ^= row[position].wrapping_mul(multipliers[shift]);
4339 }
4340 for head in 0..heads_per_ngram {
4341 let index = head_start + head;
4342 ids[token * total_heads + index] = mixed.rem_euclid(sizes[index]) + offsets[index];
4343 }
4344 }
4345 }
4346 ids
4347}
4348
4349#[allow(clippy::too_many_arguments)]
4352fn launch_sdpa_mask(
4353 e: &Engine,
4354 q: &CudaSlice<f32>,
4355 k: &CudaView<'_, f32>,
4356 v: &CudaView<'_, f32>,
4357 o: &mut CudaSlice<f32>,
4358 mask: &CudaSlice<u8>,
4359 head_dim: usize,
4360 n_head: usize,
4361 n_head_kv: usize,
4362 t: usize,
4363 t_kv: usize,
4364 scale: f32,
4365) -> Res<()> {
4366 if t_kv * 4 > 48 * 1024 {
4367 return Err(
4368 "sdpa_naive_mask_f32: T_kv exceeds the smem bound; the gmem twin is perf-lane work"
4369 .into(),
4370 );
4371 }
4372 let f = e.func("sdpa_naive_mask_f32");
4373 let cfg = LaunchConfig {
4374 grid_dim: (n_head as u32, t as u32, 1),
4375 block_dim: (128, 1, 1),
4376 shared_mem_bytes: (t_kv * 4) as u32,
4377 };
4378 let (hd, nh, nkv, ti, tkvi) = (
4379 head_dim as i32,
4380 n_head as i32,
4381 n_head_kv as i32,
4382 t as i32,
4383 t_kv as i32,
4384 );
4385 let stream = e.gpu.stream();
4386 let mut b = stream.launch_builder(&f);
4387 b.arg(q)
4388 .arg(k)
4389 .arg(v)
4390 .arg(o)
4391 .arg(mask)
4392 .arg(&hd)
4393 .arg(&nh)
4394 .arg(&nkv)
4395 .arg(&ti)
4396 .arg(&tkvi)
4397 .arg(&scale);
4398 unsafe {
4399 b.launch(cfg)?;
4400 }
4401 Ok(())
4402}
4403
4404#[allow(clippy::too_many_arguments)]
4410fn launch_sdpa_blocklist(
4411 e: &Engine,
4412 q: &CudaSlice<f32>,
4413 k: &CudaView<'_, f32>,
4414 v: &CudaView<'_, f32>,
4415 o: &mut CudaSlice<f32>,
4416 pos: &CudaSlice<i32>,
4417 meta: &CudaSlice<i32>,
4418 head_dim: usize,
4419 n_head: usize,
4420 n_head_kv: usize,
4421 t: usize,
4422 max_count: usize,
4423 scale: f32,
4424) -> Res<()> {
4425 let smem = (max_count * 8) as u32;
4428 if smem > 48 * 1024 {
4429 return Err("sdpa_blocklist_f32: selection exceeds the smem budget".into());
4430 }
4431 let f = e.func("sdpa_blocklist_f32");
4432 let cfg = LaunchConfig {
4433 grid_dim: (n_head as u32, t as u32, 1),
4434 block_dim: (128, 1, 1),
4435 shared_mem_bytes: smem,
4436 };
4437 let (hd, nh, nkv, ti, mc) = (
4438 head_dim as i32,
4439 n_head as i32,
4440 n_head_kv as i32,
4441 t as i32,
4442 max_count as i32,
4443 );
4444 let stream = e.gpu.stream();
4445 let mut b = stream.launch_builder(&f);
4446 b.arg(q)
4447 .arg(k)
4448 .arg(v)
4449 .arg(o)
4450 .arg(pos)
4451 .arg(meta)
4452 .arg(&hd)
4453 .arg(&nh)
4454 .arg(&nkv)
4455 .arg(&ti)
4456 .arg(&mc)
4457 .arg(&scale);
4458 unsafe {
4459 b.launch(cfg)?;
4460 }
4461 Ok(())
4462}
4463
4464#[allow(clippy::too_many_arguments)]
4467fn launch_q4e_kv_append(
4468 e: &Engine,
4469 k_rows: &CudaSlice<f32>,
4470 v_rows: &CudaSlice<f32>,
4471 k: &mut CudaSlice<u8>,
4472 v: &mut CudaSlice<u8>,
4473 base_pos: usize,
4474 t: usize,
4475 kv_dim: usize,
4476) -> Res<()> {
4477 let f = e.func("q4e_kv_append_q8q5_rows");
4478 let blocks = kv_dim.div_ceil(32);
4479 let cfg = LaunchConfig {
4480 grid_dim: (blocks as u32, t as u32, 1),
4481 block_dim: (32, 1, 1),
4482 shared_mem_bytes: 0,
4483 };
4484 let (t0, dk, dv) = (base_pos as i32, kv_dim as i32, kv_dim as i32);
4485 let (ktb, vtb) = (q8_row_bytes(kv_dim) as i64, q5_row_bytes(kv_dim) as i64);
4486 let stream = e.gpu.stream();
4487 let mut b = stream.launch_builder(&f);
4488 b.arg(k_rows)
4489 .arg(v_rows)
4490 .arg(k)
4491 .arg(v)
4492 .arg(&t0)
4493 .arg(&dk)
4494 .arg(&dv)
4495 .arg(&ktb)
4496 .arg(&vtb);
4497 unsafe {
4498 b.launch(cfg)?;
4499 }
4500 Ok(())
4501}
4502
4503#[allow(clippy::too_many_arguments)]
4505fn launch_q4e_kv_dequant_rows(
4506 e: &Engine,
4507 k: &CudaSlice<u8>,
4508 v: &CudaSlice<u8>,
4509 k_out: &mut CudaSlice<f32>,
4510 v_out: &mut CudaSlice<f32>,
4511 r0: usize,
4512 rows: usize,
4513 kv_dim: usize,
4514) -> Res<()> {
4515 let f = e.func("q4e_kv_dequant_rows");
4516 let blocks = kv_dim.div_ceil(32);
4517 let cfg = LaunchConfig {
4518 grid_dim: (blocks as u32, rows as u32, 1),
4519 block_dim: (32, 1, 1),
4520 shared_mem_bytes: 0,
4521 };
4522 let (r0i, dk, dv) = (r0 as i32, kv_dim as i32, kv_dim as i32);
4523 let (ktb, vtb) = (q8_row_bytes(kv_dim) as i64, q5_row_bytes(kv_dim) as i64);
4524 let stream = e.gpu.stream();
4525 let mut b = stream.launch_builder(&f);
4526 b.arg(k)
4527 .arg(v)
4528 .arg(k_out)
4529 .arg(v_out)
4530 .arg(&r0i)
4531 .arg(&dk)
4532 .arg(&dv)
4533 .arg(&ktb)
4534 .arg(&vtb);
4535 unsafe {
4536 b.launch(cfg)?;
4537 }
4538 Ok(())
4539}
4540
4541#[allow(clippy::too_many_arguments)]
4544fn launch_q4e_sdpa_blocklist_q8q5(
4545 e: &Engine,
4546 q: &CudaSlice<f32>,
4547 k: &CudaSlice<u8>,
4548 v: &CudaSlice<u8>,
4549 o: &mut CudaSlice<f32>,
4550 pos: &CudaSlice<i32>,
4551 meta: &CudaSlice<i32>,
4552 head_dim: usize,
4553 n_head: usize,
4554 n_head_kv: usize,
4555 t: usize,
4556 max_count: usize,
4557 scale: f32,
4558) -> Res<()> {
4559 let smem = (max_count * 8) as u32;
4560 if smem > 48 * 1024 {
4561 return Err("q4e_sdpa_blocklist_q8q5: selection exceeds the smem budget".into());
4562 }
4563 let f = e.func(if kv_hoist_on() {
4565 "q4e_sdpa_blocklist_q8q5_hoist"
4566 } else {
4567 "q4e_sdpa_blocklist_q8q5"
4568 });
4569 let cfg = LaunchConfig {
4570 grid_dim: (n_head as u32, t as u32, 1),
4571 block_dim: (128, 1, 1),
4572 shared_mem_bytes: smem,
4573 };
4574 let kv_dim = n_head_kv * head_dim;
4575 let (hd, nh, nkv, ti, mc) = (
4576 head_dim as i32,
4577 n_head as i32,
4578 n_head_kv as i32,
4579 t as i32,
4580 max_count as i32,
4581 );
4582 let (ktb, vtb) = (q8_row_bytes(kv_dim) as i64, q5_row_bytes(kv_dim) as i64);
4583 let stream = e.gpu.stream();
4584 let mut b = stream.launch_builder(&f);
4585 b.arg(q)
4586 .arg(k)
4587 .arg(v)
4588 .arg(o)
4589 .arg(pos)
4590 .arg(meta)
4591 .arg(&hd)
4592 .arg(&nh)
4593 .arg(&nkv)
4594 .arg(&ti)
4595 .arg(&mc)
4596 .arg(&scale)
4597 .arg(&ktb)
4598 .arg(&vtb);
4599 unsafe {
4600 b.launch(cfg)?;
4601 }
4602 Ok(())
4603}
4604
4605#[allow(clippy::too_many_arguments)]
4608fn launch_q4e_idx_append_q8(
4609 e: &Engine,
4610 src: &CudaSlice<f32>,
4611 dst: &mut CudaSlice<u8>,
4612 rows: usize,
4613 width: usize,
4614 src_stride: usize,
4615 src_col: usize,
4616 dst_row: usize,
4617) -> Res<()> {
4618 let f = e.func("q4e_idx_append_q8");
4619 let cfg = LaunchConfig {
4620 grid_dim: (width.div_ceil(32) as u32, rows as u32, 1),
4621 block_dim: (32, 1, 1),
4622 shared_mem_bytes: 0,
4623 };
4624 let (r, w) = (rows as i32, width as i32);
4625 let (ss, sc, dr) = (src_stride as i64, src_col as i64, dst_row as i64);
4626 let stream = e.gpu.stream();
4627 let mut b = stream.launch_builder(&f);
4628 b.arg(src)
4629 .arg(dst)
4630 .arg(&r)
4631 .arg(&w)
4632 .arg(&ss)
4633 .arg(&sc)
4634 .arg(&dr);
4635 unsafe {
4636 b.launch(cfg)?;
4637 }
4638 Ok(())
4639}
4640
4641#[allow(clippy::too_many_arguments)]
4643fn launch_q4e_idx_append_bf16(
4644 e: &Engine,
4645 src: &CudaSlice<f32>,
4646 dst: &mut CudaSlice<u16>,
4647 rows: usize,
4648 width: usize,
4649 src_stride: usize,
4650 src_col: usize,
4651 dst_row: usize,
4652) -> Res<()> {
4653 let f = e.func("q4e_idx_append_bf16");
4654 let total = rows * width;
4655 let cfg = LaunchConfig {
4656 grid_dim: (total.div_ceil(256) as u32, 1, 1),
4657 block_dim: (256, 1, 1),
4658 shared_mem_bytes: 0,
4659 };
4660 let (r, w) = (rows as i32, width as i32);
4661 let (ss, sc, dr) = (src_stride as i64, src_col as i64, dst_row as i64);
4662 let stream = e.gpu.stream();
4663 let mut b = stream.launch_builder(&f);
4664 b.arg(src)
4665 .arg(dst)
4666 .arg(&r)
4667 .arg(&w)
4668 .arg(&ss)
4669 .arg(&sc)
4670 .arg(&dr);
4671 unsafe {
4672 b.launch(cfg)?;
4673 }
4674 Ok(())
4675}
4676
4677#[allow(clippy::too_many_arguments)]
4678fn launch_gdn_scan(
4679 e: &Engine,
4680 qkv: &CudaSlice<f32>,
4681 g_log: &CudaSlice<f32>,
4682 beta_raw: &CudaSlice<f32>,
4683 state: &mut CudaSlice<f32>,
4684 o: &mut CudaSlice<f32>,
4685 nk: usize,
4686 nv: usize,
4687 hk: usize,
4688 hv: usize,
4689 t: usize,
4690 scale: f32,
4691 eps: f32,
4692) -> Res<()> {
4693 if hk > 128 {
4694 return Err("gdn_scan_naive_f32: hk > 128".into());
4695 }
4696 let f = e.func("gdn_scan_naive_f32");
4697 let cfg = LaunchConfig {
4698 grid_dim: (nv as u32, 1, 1),
4699 block_dim: (hv as u32, 1, 1),
4700 shared_mem_bytes: ((2 * hk + 2) * 4) as u32,
4701 };
4702 let (nki, nvi, hki, hvi, ti) = (nk as i32, nv as i32, hk as i32, hv as i32, t as i32);
4703 let stream = e.gpu.stream();
4704 let mut b = stream.launch_builder(&f);
4705 b.arg(qkv)
4706 .arg(g_log)
4707 .arg(beta_raw)
4708 .arg(state)
4709 .arg(o)
4710 .arg(&nki)
4711 .arg(&nvi)
4712 .arg(&hki)
4713 .arg(&hvi)
4714 .arg(&ti)
4715 .arg(&scale)
4716 .arg(&eps);
4717 unsafe {
4718 b.launch(cfg)?;
4719 }
4720 Ok(())
4721}
4722
4723#[allow(clippy::too_many_arguments)]
4727fn launch_gdn_scan_step(
4728 e: &Engine,
4729 qkv: &CudaSlice<f32>,
4730 g_log: &CudaSlice<f32>,
4731 beta_raw: &CudaSlice<f32>,
4732 state: &mut CudaSlice<f32>,
4733 o: &mut CudaSlice<f32>,
4734 nk: usize,
4735 nv: usize,
4736 hk: usize,
4737 hv: usize,
4738 scale: f32,
4739 eps: f32,
4740) -> Res<()> {
4741 if hk % 32 != 0 || hk > 1024 {
4742 return Err("gdn_scan_step_f32: hk must be a multiple of 32 and <= 1024".into());
4743 }
4744 let f = e.func("gdn_scan_step_f32");
4745 let cfg = LaunchConfig {
4746 grid_dim: (nv as u32, hv as u32, 1),
4747 block_dim: (hk as u32, 1, 1),
4748 shared_mem_bytes: 0,
4749 };
4750 let (nki, nvi, hki, hvi) = (nk as i32, nv as i32, hk as i32, hv as i32);
4751 let stream = e.gpu.stream();
4752 let mut b = stream.launch_builder(&f);
4753 b.arg(qkv)
4754 .arg(g_log)
4755 .arg(beta_raw)
4756 .arg(state)
4757 .arg(o)
4758 .arg(&nki)
4759 .arg(&nvi)
4760 .arg(&hki)
4761 .arg(&hvi)
4762 .arg(&scale)
4763 .arg(&eps);
4764 unsafe {
4765 b.launch(cfg)?;
4766 }
4767 Ok(())
4768}
4769
4770#[allow(clippy::too_many_arguments)]
4774fn launch_gdn_scan_step_at(
4775 e: &Engine,
4776 conv_out: &CudaSlice<f32>,
4777 g_log: &CudaSlice<f32>,
4778 beta_raw: &CudaSlice<f32>,
4779 state: &mut CudaSlice<f32>,
4780 o: &mut CudaSlice<f32>,
4781 tok: usize,
4782 nk: usize,
4783 nv: usize,
4784 hk: usize,
4785 hv: usize,
4786 scale: f32,
4787 eps: f32,
4788) -> Res<()> {
4789 if hk % 32 != 0 || hk > 1024 {
4790 return Err("gdn_scan_step_f32: hk must be a multiple of 32 and <= 1024".into());
4791 }
4792 let conv_dim = 2 * nk * hk + nv * hv;
4793 let qv = conv_out.slice(tok * conv_dim..(tok + 1) * conv_dim);
4794 let gv = g_log.slice(tok * nv..(tok + 1) * nv);
4795 let bv = beta_raw.slice(tok * nv..(tok + 1) * nv);
4796 let mut ov = o.slice_mut(tok * nv * hv..(tok + 1) * nv * hv);
4797 let f = e.func("gdn_scan_step_f32");
4798 let cfg = LaunchConfig {
4799 grid_dim: (nv as u32, hv as u32, 1),
4800 block_dim: (hk as u32, 1, 1),
4801 shared_mem_bytes: 0,
4802 };
4803 let (nki, nvi, hki, hvi) = (nk as i32, nv as i32, hk as i32, hv as i32);
4804 let stream = e.gpu.stream();
4805 let mut b = stream.launch_builder(&f);
4806 b.arg(&qv)
4807 .arg(&gv)
4808 .arg(&bv)
4809 .arg(&mut *state)
4810 .arg(&mut ov)
4811 .arg(&nki)
4812 .arg(&nvi)
4813 .arg(&hki)
4814 .arg(&hvi)
4815 .arg(&scale)
4816 .arg(&eps);
4817 unsafe {
4818 b.launch(cfg)?;
4819 }
4820 Ok(())
4821}
4822
4823#[allow(clippy::too_many_arguments)]
4827fn launch_gdn_scan_at(
4828 e: &Engine,
4829 conv_out: &CudaSlice<f32>,
4830 g_log: &CudaSlice<f32>,
4831 beta_raw: &CudaSlice<f32>,
4832 state: &mut CudaSlice<f32>,
4833 o: &mut CudaSlice<f32>,
4834 tok: usize,
4835 nk: usize,
4836 nv: usize,
4837 hk: usize,
4838 hv: usize,
4839 scale: f32,
4840 eps: f32,
4841) -> Res<()> {
4842 if hk > 128 {
4843 return Err("gdn_scan_naive_f32: hk > 128".into());
4844 }
4845 let conv_dim = 2 * nk * hk + nv * hv;
4846 let qv = conv_out.slice(tok * conv_dim..(tok + 1) * conv_dim);
4847 let gv = g_log.slice(tok * nv..(tok + 1) * nv);
4848 let bv = beta_raw.slice(tok * nv..(tok + 1) * nv);
4849 let mut ov = o.slice_mut(tok * nv * hv..(tok + 1) * nv * hv);
4850 let f = e.func("gdn_scan_naive_f32");
4851 let cfg = LaunchConfig {
4852 grid_dim: (nv as u32, 1, 1),
4853 block_dim: (hv as u32, 1, 1),
4854 shared_mem_bytes: ((2 * hk + 2) * 4) as u32,
4855 };
4856 let (nki, nvi, hki, hvi, ti) = (nk as i32, nv as i32, hk as i32, hv as i32, 1i32);
4857 let stream = e.gpu.stream();
4858 let mut b = stream.launch_builder(&f);
4859 b.arg(&qv)
4860 .arg(&gv)
4861 .arg(&bv)
4862 .arg(&mut *state)
4863 .arg(&mut ov)
4864 .arg(&nki)
4865 .arg(&nvi)
4866 .arg(&hki)
4867 .arg(&hvi)
4868 .arg(&ti)
4869 .arg(&scale)
4870 .arg(&eps);
4871 unsafe {
4872 b.launch(cfg)?;
4873 }
4874 Ok(())
4875}
4876
4877#[allow(clippy::too_many_arguments)]
4881fn launch_rms_sigmul(
4882 e: &Engine,
4883 x: &CudaSlice<f32>,
4884 w: &CudaSlice<f32>,
4885 z: &CudaSlice<f32>,
4886 dst: &mut CudaSlice<f32>,
4887 ncols: usize,
4888 nrows: usize,
4889 eps: f32,
4890) -> Res<()> {
4891 let f = e.func("rms_sigmul_f32");
4892 let cfg = LaunchConfig {
4893 grid_dim: (nrows as u32, 1, 1),
4894 block_dim: (crate::rms_block(), 1, 1),
4895 shared_mem_bytes: 0,
4896 };
4897 let (nc, ep) = (ncols as i32, eps);
4898 let stream = e.gpu.stream();
4899 let mut b = stream.launch_builder(&f);
4900 b.arg(x).arg(w).arg(z).arg(dst).arg(&nc).arg(&ep);
4901 unsafe {
4902 b.launch(cfg)?;
4903 }
4904 Ok(())
4905}
4906
4907#[allow(clippy::too_many_arguments)]
4908fn launch_dwconv(
4909 e: &Engine,
4910 x: &CudaSlice<f32>,
4911 hist: &CudaSlice<f32>,
4912 w: &CudaSlice<f32>,
4913 y: &mut CudaSlice<f32>,
4914 t: usize,
4915 th: usize,
4916 c: usize,
4917 k: usize,
4918 dilation: usize,
4919 mode: i32,
4920) -> Res<()> {
4921 let f = e.func("dwconv_causal_f32");
4922 let cfg = LaunchConfig::for_num_elems((t * c) as u32);
4923 let (ti, thi, ci, ki, di) = (t as i32, th as i32, c as i32, k as i32, dilation as i32);
4924 let stream = e.gpu.stream();
4925 let mut b = stream.launch_builder(&f);
4926 b.arg(x)
4927 .arg(hist)
4928 .arg(w)
4929 .arg(y)
4930 .arg(&ti)
4931 .arg(&thi)
4932 .arg(&ci)
4933 .arg(&ki)
4934 .arg(&di)
4935 .arg(&mode);
4936 unsafe {
4937 b.launch(cfg)?;
4938 }
4939 Ok(())
4940}
4941
4942#[allow(clippy::too_many_arguments)]
4944fn run_routed_expert(
4945 e: &Engine,
4946 xg: &CudaSlice<f32>,
4947 gate: &CudaView<'_, f32>,
4948 up: &CudaView<'_, f32>,
4949 down: &CudaView<'_, f32>,
4950 m_e: usize,
4951 hidden: usize,
4952 ff: usize,
4953) -> Res<CudaSlice<f32>> {
4954 let xg_view = xg.slice(0..m_e * hidden);
4955 let mut gate_out = e.uninit(m_e * ff)?;
4956 e.linear_device_into(&xg_view, gate, &mut gate_out, m_e, hidden, ff)?;
4957 let mut up_out = e.uninit(m_e * ff)?;
4958 e.linear_device_into(&xg_view, up, &mut up_out, m_e, hidden, ff)?;
4959 let mut act = e.uninit(m_e * ff)?;
4960 e.silu_mul(&gate_out, &up_out, &mut act, m_e * ff)?;
4961 let mut down_out = e.uninit(m_e * hidden)?;
4962 e.linear_device_into(
4963 &act.slice(0..m_e * ff),
4964 down,
4965 &mut down_out,
4966 m_e,
4967 ff,
4968 hidden,
4969 )?;
4970 Ok(down_out)
4971}
4972
4973fn launch_rms_norm_into_view(
4979 e: &Engine,
4980 x: &CudaSlice<f32>,
4981 w: &CudaSlice<f32>,
4982 dst: &mut cudarc::driver::CudaViewMut<'_, f32>,
4983 ncols: usize,
4984 nrows: usize,
4985 eps: f32,
4986) -> Res<()> {
4987 let kname = if Engine::norm_ilp_on() {
4988 "rms_norm_f32_v2"
4989 } else {
4990 "rms_norm_f32"
4991 };
4992 let f = e.func(kname);
4993 let cfg = LaunchConfig {
4994 grid_dim: (nrows as u32, 1, 1),
4995 block_dim: (crate::rms_block(), 1, 1),
4996 shared_mem_bytes: 0,
4997 };
4998 let (nc, ep) = (ncols as i32, eps);
4999 let stream = e.gpu.stream();
5000 let mut b = stream.launch_builder(&f);
5001 b.arg(x).arg(w).arg(dst).arg(&nc).arg(&ep);
5002 unsafe {
5003 b.launch(cfg)?;
5004 }
5005 Ok(())
5006}
5007
5008fn launch_hc_lowrank_reduce(
5010 e: &Engine,
5011 parts: &CudaSlice<f32>,
5012 low_act: &mut CudaSlice<f32>,
5013 streams: usize,
5014 t: usize,
5015 rank: usize,
5016) -> Res<()> {
5017 let f = e.func("hc_lowrank_reduce_f32");
5018 let cfg = LaunchConfig::for_num_elems((t * rank) as u32);
5019 let (si, ti, ri) = (streams as i32, t as i32, rank as i32);
5020 let inv = 1.0f32 / streams as f32;
5021 let stream = e.gpu.stream();
5022 let mut b = stream.launch_builder(&f);
5023 b.arg(parts)
5024 .arg(low_act)
5025 .arg(&si)
5026 .arg(&ti)
5027 .arg(&ri)
5028 .arg(&inv);
5029 unsafe {
5030 b.launch(cfg)?;
5031 }
5032 Ok(())
5033}
5034
5035fn launch_hc_mix_epilogue(
5037 e: &Engine,
5038 gates: &CudaSlice<f32>,
5039 normed: &CudaSlice<f32>,
5040 mixed: &mut CudaSlice<f32>,
5041 streams: usize,
5042 t: usize,
5043 hidden: usize,
5044) -> Res<()> {
5045 let f = e.func("hc_mix_epilogue_f32");
5046 let cfg = LaunchConfig::for_num_elems((t * hidden) as u32);
5047 let (si, ti, hi) = (streams as i32, t as i32, hidden as i32);
5048 let inv = 1.0f32 / streams as f32;
5049 let stream = e.gpu.stream();
5050 let mut b = stream.launch_builder(&f);
5051 b.arg(gates)
5052 .arg(normed)
5053 .arg(mixed)
5054 .arg(&si)
5055 .arg(&ti)
5056 .arg(&hi)
5057 .arg(&inv);
5058 unsafe {
5059 b.launch(cfg)?;
5060 }
5061 Ok(())
5062}
5063
5064fn launch_hc_inject_gates(
5066 e: &Engine,
5067 normed: &CudaSlice<f32>,
5068 w: &CudaSlice<f32>,
5069 out: &mut CudaSlice<f32>,
5070 streams: usize,
5071 t: usize,
5072 hidden: usize,
5073) -> Res<()> {
5074 let f = e.func("hc_inject_gates_f32");
5075 let cfg = LaunchConfig {
5076 grid_dim: (streams as u32, t as u32, 1),
5077 block_dim: (256, 1, 1),
5078 shared_mem_bytes: 0,
5079 };
5080 let (si, ti, hi) = (streams as i32, t as i32, hidden as i32);
5081 let inv = 1.0f32 / streams as f32;
5082 let stream = e.gpu.stream();
5083 let mut b = stream.launch_builder(&f);
5084 b.arg(normed)
5085 .arg(w)
5086 .arg(out)
5087 .arg(&si)
5088 .arg(&ti)
5089 .arg(&hi)
5090 .arg(&inv);
5091 unsafe {
5092 b.launch(cfg)?;
5093 }
5094 Ok(())
5095}
5096
5097enum InjectOut {
5101 Rows(Vec<CudaSlice<f32>>),
5102 Slab(CudaSlice<f32>),
5103}
5104
5105fn put_inject(ws: &mut StepPool, inject: InjectOut) {
5108 match inject {
5109 InjectOut::Rows(rows) => {
5110 for (s, row) in rows.into_iter().enumerate() {
5111 ws.put_f32(INJECT_SLOTS[s], row);
5112 }
5113 }
5114 InjectOut::Slab(slab) => ws.put_f32("hc.inj_all", slab),
5115 }
5116}
5117
5118fn take_inject(e: &Engine, ws: &mut StepPool, streams: usize, t: usize) -> Res<InjectOut> {
5121 if micro_inj_on() && hc_fused_gate_on() {
5124 Ok(InjectOut::Slab(ws.take_f32(
5125 e,
5126 "hc.inj_all",
5127 streams * t,
5128 0,
5129 )?))
5130 } else {
5131 let mut rows = Vec::with_capacity(streams);
5132 for s in 0..streams {
5133 rows.push(ws.take_f32(e, INJECT_SLOTS[s], t, 0)?);
5134 }
5135 Ok(InjectOut::Rows(rows))
5136 }
5137}
5138
5139#[allow(clippy::too_many_arguments)]
5142fn launch_hc_norm_planes(
5143 e: &Engine,
5144 ptrs: &CudaSlice<u64>,
5145 w_stack: &CudaSlice<f32>,
5146 dst: &mut CudaSlice<f32>,
5147 hidden: usize,
5148 t: usize,
5149 streams: usize,
5150 eps: f32,
5151) -> Res<()> {
5152 let f = e.func("hc_norm_planes_f32");
5153 let cfg = LaunchConfig {
5154 grid_dim: (t as u32, streams as u32, 1),
5155 block_dim: (256, 1, 1),
5156 shared_mem_bytes: 0,
5157 };
5158 let (hi, ti) = (hidden as i32, t as i32);
5159 let stream = e.gpu.stream();
5160 let mut b = stream.launch_builder(&f);
5161 b.arg(ptrs)
5162 .arg(w_stack)
5163 .arg(dst)
5164 .arg(&hi)
5165 .arg(&ti)
5166 .arg(&eps);
5167 unsafe {
5168 b.launch(cfg)?;
5169 }
5170 Ok(())
5171}
5172
5173#[allow(clippy::too_many_arguments)]
5177fn launch_hc_inject_two_stage(
5178 e: &Engine,
5179 normed: &CudaSlice<f32>,
5180 w_f32: &CudaSlice<f32>,
5181 w_b16: Option<&CudaSlice<u8>>,
5182 partials: &mut CudaSlice<f32>,
5183 out: &mut CudaSlice<f32>,
5184 streams: usize,
5185 t: usize,
5186 hidden: usize,
5187 chunks: usize,
5188) -> Res<()> {
5189 let cfg = LaunchConfig {
5190 grid_dim: (streams as u32, t as u32, chunks as u32),
5191 block_dim: (256, 1, 1),
5192 shared_mem_bytes: 0,
5193 };
5194 let (si, ti, hi, ci) = (streams as i32, t as i32, hidden as i32, chunks as i32);
5195 let stream = e.gpu.stream();
5196 if let Some(w) = w_b16 {
5197 let f = e.func("hc_inject_partials_bf16w_f32");
5198 let mut b = stream.launch_builder(&f);
5199 b.arg(normed)
5200 .arg(w)
5201 .arg(&mut *partials)
5202 .arg(&si)
5203 .arg(&ti)
5204 .arg(&hi)
5205 .arg(&ci);
5206 unsafe {
5207 b.launch(cfg)?;
5208 }
5209 } else {
5210 let f = e.func("hc_inject_partials_f32");
5211 let mut b = stream.launch_builder(&f);
5212 b.arg(normed)
5213 .arg(w_f32)
5214 .arg(&mut *partials)
5215 .arg(&si)
5216 .arg(&ti)
5217 .arg(&hi)
5218 .arg(&ci);
5219 unsafe {
5220 b.launch(cfg)?;
5221 }
5222 }
5223 let rows = (streams * t) as i32;
5224 let inv = 1.0f32 / streams as f32;
5225 let f = e.func("hc_inject_reduce_f32");
5226 let cfg = LaunchConfig::for_num_elems((streams * t) as u32);
5227 let mut b = stream.launch_builder(&f);
5228 b.arg(&*partials).arg(out).arg(&rows).arg(&ci).arg(&inv);
5229 unsafe {
5230 b.launch(cfg)?;
5231 }
5232 Ok(())
5233}
5234
5235#[allow(clippy::too_many_arguments)]
5239fn launch_hc_diet_stage1(
5240 e: &Engine,
5241 ptrs: &CudaSlice<u64>,
5242 nw_stack: &CudaSlice<f32>,
5243 wdown_b16: &CudaSlice<u8>,
5244 winj_b16: Option<&CudaSlice<u8>>,
5245 parts: &mut CudaSlice<f32>,
5246 inj_parts: &mut CudaSlice<f32>,
5247 inv_out: &mut CudaSlice<f32>,
5248 hidden: usize,
5249 rank: usize,
5250 streams: usize,
5251 t: usize,
5252 eps: f32,
5253) -> Res<()> {
5254 if hidden % 8 != 0 {
5255 return Err("hc_diet_stage1_f32: hidden % 8 != 0".into());
5256 }
5257 let n_inj = if winj_b16.is_some() { streams } else { 0 };
5258 const ROWS_PB: usize = 4;
5259 let total_rows = rank + n_inj;
5260 if parts.len() < t * streams * rank
5261 || (n_inj > 0 && inj_parts.len() < t * n_inj * streams)
5262 || inv_out.len() < t * streams
5263 {
5264 return Err("hc_diet_stage1_f32: output buffers too short".into());
5265 }
5266 let f = e.func("hc_diet_stage1_f32");
5267 let cfg = LaunchConfig {
5268 grid_dim: (
5269 total_rows.div_ceil(ROWS_PB) as u32,
5270 t as u32,
5271 streams as u32,
5272 ),
5273 block_dim: (256, 1, 1),
5274 shared_mem_bytes: (hidden * 4) as u32,
5275 };
5276 let (hi, ri, si, nji, rpb) = (
5277 hidden as i32,
5278 rank as i32,
5279 streams as i32,
5280 n_inj as i32,
5281 ROWS_PB as i32,
5282 );
5283 let winj = winj_b16.unwrap_or(wdown_b16); let stream = e.gpu.stream();
5285 let mut b = stream.launch_builder(&f);
5286 b.arg(ptrs)
5287 .arg(nw_stack)
5288 .arg(wdown_b16)
5289 .arg(winj)
5290 .arg(&mut *parts)
5291 .arg(&mut *inj_parts)
5292 .arg(&mut *inv_out)
5293 .arg(&hi)
5294 .arg(&ri)
5295 .arg(&si)
5296 .arg(&nji)
5297 .arg(&rpb)
5298 .arg(&eps);
5299 unsafe {
5300 b.launch(cfg)?;
5301 }
5302 Ok(())
5303}
5304
5305#[allow(clippy::too_many_arguments)]
5308fn launch_hc_diet_stage2(
5309 e: &Engine,
5310 parts: &CudaSlice<f32>,
5311 inj_parts: &CudaSlice<f32>,
5312 low_act: &mut CudaSlice<f32>,
5313 inj_all: &mut CudaSlice<f32>,
5314 rank: usize,
5315 streams: usize,
5316 t: usize,
5317 with_inject: bool,
5318) -> Res<()> {
5319 let n_inj = if with_inject { streams } else { 0 };
5320 if low_act.len() < t * rank || (n_inj > 0 && inj_all.len() < n_inj * t) {
5321 return Err("hc_diet_stage2_f32: output buffers too short".into());
5322 }
5323 let f = e.func("hc_diet_stage2_f32");
5324 let cfg = LaunchConfig {
5325 grid_dim: (((rank + n_inj) as u32).div_ceil(256), t as u32, 1),
5326 block_dim: (256, 1, 1),
5327 shared_mem_bytes: 0,
5328 };
5329 let (ri, si, nji, ti) = (rank as i32, streams as i32, n_inj as i32, t as i32);
5330 let inv = 1.0f32 / streams as f32;
5331 let stream = e.gpu.stream();
5332 let mut b = stream.launch_builder(&f);
5333 b.arg(parts)
5334 .arg(inj_parts)
5335 .arg(&mut *low_act)
5336 .arg(&mut *inj_all)
5337 .arg(&ri)
5338 .arg(&si)
5339 .arg(&nji)
5340 .arg(&ti)
5341 .arg(&inv);
5342 unsafe {
5343 b.launch(cfg)?;
5344 }
5345 Ok(())
5346}
5347
5348#[allow(clippy::too_many_arguments)]
5351fn launch_hc_diet_stage3(
5352 e: &Engine,
5353 ptrs: &CudaSlice<u64>,
5354 nw_stack: &CudaSlice<f32>,
5355 inv_in: &CudaSlice<f32>,
5356 wup_b16: &CudaSlice<u8>,
5357 low_act: &CudaSlice<f32>,
5358 mixed: &mut CudaSlice<f32>,
5359 hidden: usize,
5360 rank: usize,
5361 streams: usize,
5362 t: usize,
5363) -> Res<()> {
5364 const DIMS_PB: usize = 8;
5365 if mixed.len() < t * hidden {
5366 return Err("hc_diet_stage3_f32: output buffer too short".into());
5367 }
5368 let f = e.func("hc_diet_stage3_f32");
5369 let cfg = LaunchConfig {
5370 grid_dim: (hidden.div_ceil(DIMS_PB) as u32, t as u32, 1),
5371 block_dim: (256, 1, 1),
5372 shared_mem_bytes: ((rank + DIMS_PB * streams) * 4) as u32,
5373 };
5374 let (hi, ri, si, dpb) = (hidden as i32, rank as i32, streams as i32, DIMS_PB as i32);
5375 let inv_streams = 1.0f32 / streams as f32;
5376 let stream = e.gpu.stream();
5377 let mut b = stream.launch_builder(&f);
5378 b.arg(ptrs)
5379 .arg(nw_stack)
5380 .arg(inv_in)
5381 .arg(wup_b16)
5382 .arg(low_act)
5383 .arg(&mut *mixed)
5384 .arg(&hi)
5385 .arg(&ri)
5386 .arg(&si)
5387 .arg(&dpb)
5388 .arg(&inv_streams);
5389 unsafe {
5390 b.launch(cfg)?;
5391 }
5392 Ok(())
5393}
5394
5395fn launch_hc_diet_stage0_mt(
5398 e: &Engine,
5399 ptrs: &CudaSlice<u64>,
5400 inv_out: &mut CudaSlice<f32>,
5401 hidden: usize,
5402 streams: usize,
5403 t: usize,
5404 eps: f32,
5405) -> Res<()> {
5406 if inv_out.len() < t * streams {
5407 return Err("hc_diet_stage0_mt_f32: inv buffer too short".into());
5408 }
5409 let f = e.func("hc_diet_stage0_mt_f32");
5410 let cfg = LaunchConfig {
5411 grid_dim: (t as u32, streams as u32, 1),
5412 block_dim: (256, 1, 1),
5413 shared_mem_bytes: 0,
5414 };
5415 let (hi, si, ti) = (hidden as i32, streams as i32, t as i32);
5416 let stream = e.gpu.stream();
5417 let mut b = stream.launch_builder(&f);
5418 b.arg(ptrs)
5419 .arg(&mut *inv_out)
5420 .arg(&hi)
5421 .arg(&si)
5422 .arg(&ti)
5423 .arg(&eps);
5424 unsafe {
5425 b.launch(cfg)?;
5426 }
5427 Ok(())
5428}
5429
5430#[allow(clippy::too_many_arguments)]
5433fn launch_hc_diet_stage1_mt(
5434 e: &Engine,
5435 ptrs: &CudaSlice<u64>,
5436 nw_stack: &CudaSlice<f32>,
5437 inv_in: &CudaSlice<f32>,
5438 wdown_b16: &CudaSlice<u8>,
5439 winj_b16: Option<&CudaSlice<u8>>,
5440 parts: &mut CudaSlice<f32>,
5441 inj_parts: &mut CudaSlice<f32>,
5442 hidden: usize,
5443 rank: usize,
5444 streams: usize,
5445 t: usize,
5446) -> Res<()> {
5447 if hidden % 8 != 0 || !(2..=12).contains(&t) {
5448 return Err("hc_diet_stage1_mt_f32: geometry".into());
5449 }
5450 let n_inj = if winj_b16.is_some() { streams } else { 0 };
5451 const ROWS_PB: usize = 4;
5452 let total_rows = rank + n_inj;
5453 if parts.len() < t * streams * rank || (n_inj > 0 && inj_parts.len() < t * n_inj * streams) {
5454 return Err("hc_diet_stage1_mt_f32: output buffers too short".into());
5455 }
5456 let f = e.func("hc_diet_stage1_mt_f32");
5457 let cfg = LaunchConfig {
5458 grid_dim: (total_rows.div_ceil(ROWS_PB) as u32, 1, streams as u32),
5459 block_dim: (256, 1, 1),
5460 shared_mem_bytes: 0,
5461 };
5462 let (hi, ri, si, nji, rpb, ti) = (
5463 hidden as i32,
5464 rank as i32,
5465 streams as i32,
5466 n_inj as i32,
5467 ROWS_PB as i32,
5468 t as i32,
5469 );
5470 let winj = winj_b16.unwrap_or(wdown_b16);
5471 let stream = e.gpu.stream();
5472 let mut b = stream.launch_builder(&f);
5473 b.arg(ptrs)
5474 .arg(nw_stack)
5475 .arg(inv_in)
5476 .arg(wdown_b16)
5477 .arg(winj)
5478 .arg(&mut *parts)
5479 .arg(&mut *inj_parts)
5480 .arg(&hi)
5481 .arg(&ri)
5482 .arg(&si)
5483 .arg(&nji)
5484 .arg(&rpb)
5485 .arg(&ti);
5486 unsafe {
5487 b.launch(cfg)?;
5488 }
5489 Ok(())
5490}
5491
5492#[allow(clippy::too_many_arguments)]
5494fn launch_hc_diet_stage3_mt(
5495 e: &Engine,
5496 ptrs: &CudaSlice<u64>,
5497 nw_stack: &CudaSlice<f32>,
5498 inv_in: &CudaSlice<f32>,
5499 wup_b16: &CudaSlice<u8>,
5500 low_act: &CudaSlice<f32>,
5501 mixed: &mut CudaSlice<f32>,
5502 hidden: usize,
5503 rank: usize,
5504 streams: usize,
5505 t: usize,
5506) -> Res<()> {
5507 const DIMS_PB: usize = 8;
5508 if !(2..=12).contains(&t) || mixed.len() < t * hidden {
5509 return Err("hc_diet_stage3_mt_f32: geometry".into());
5510 }
5511 let smem = ((t * rank + DIMS_PB * streams * t) * 4) as u32;
5512 if smem > 96 * 1024 {
5513 return Err("hc_diet_stage3_mt_f32: smem over budget".into());
5514 }
5515 let f = e.func("hc_diet_stage3_mt_f32");
5516 let cfg = LaunchConfig {
5517 grid_dim: (hidden.div_ceil(DIMS_PB) as u32, 1, 1),
5518 block_dim: (256, 1, 1),
5519 shared_mem_bytes: smem,
5520 };
5521 let (hi, ri, si, dpb, ti) = (
5522 hidden as i32,
5523 rank as i32,
5524 streams as i32,
5525 DIMS_PB as i32,
5526 t as i32,
5527 );
5528 let inv_streams = 1.0f32 / streams as f32;
5529 let stream = e.gpu.stream();
5530 let mut b = stream.launch_builder(&f);
5531 b.arg(ptrs)
5532 .arg(nw_stack)
5533 .arg(inv_in)
5534 .arg(wup_b16)
5535 .arg(low_act)
5536 .arg(&mut *mixed)
5537 .arg(&hi)
5538 .arg(&ri)
5539 .arg(&si)
5540 .arg(&dpb)
5541 .arg(&ti)
5542 .arg(&inv_streams);
5543 unsafe {
5544 b.launch(cfg)?;
5545 }
5546 Ok(())
5547}
5548
5549fn launch_hc_write_planes(
5552 e: &Engine,
5553 ptrs: &CudaSlice<u64>,
5554 block_out: &CudaSlice<f32>,
5555 inj: &CudaSlice<f32>,
5556 hidden: usize,
5557 t: usize,
5558 streams: usize,
5559) -> Res<()> {
5560 let f = e.func("hc_write_planes_f32");
5561 let n = (t * hidden) as u32;
5562 let cfg = LaunchConfig {
5563 grid_dim: (n.div_ceil(256), streams as u32, 1),
5564 block_dim: (256, 1, 1),
5565 shared_mem_bytes: 0,
5566 };
5567 let (hi, ti) = (hidden as i32, t as i32);
5568 let stream = e.gpu.stream();
5569 let mut b = stream.launch_builder(&f);
5570 b.arg(ptrs).arg(block_out).arg(inj).arg(&hi).arg(&ti);
5571 unsafe {
5572 b.launch(cfg)?;
5573 }
5574 Ok(())
5575}
5576
5577fn launch_hc_inject_gates_b16(
5581 e: &Engine,
5582 normed: &CudaSlice<f32>,
5583 w: &CudaSlice<u8>,
5584 out: &mut CudaSlice<f32>,
5585 streams: usize,
5586 t: usize,
5587 hidden: usize,
5588) -> Res<()> {
5589 let f = e.func("hc_inject_gates_bf16w_f32");
5590 let cfg = LaunchConfig {
5591 grid_dim: (streams as u32, t as u32, 1),
5592 block_dim: (256, 1, 1),
5593 shared_mem_bytes: 0,
5594 };
5595 let (si, ti, hi) = (streams as i32, t as i32, hidden as i32);
5596 let inv = 1.0f32 / streams as f32;
5597 let stream = e.gpu.stream();
5598 let mut b = stream.launch_builder(&f);
5599 b.arg(normed)
5600 .arg(w)
5601 .arg(out)
5602 .arg(&si)
5603 .arg(&ti)
5604 .arg(&hi)
5605 .arg(&inv);
5606 unsafe {
5607 b.launch(cfg)?;
5608 }
5609 Ok(())
5610}
5611
5612fn bf16_twin(e: &Engine, data: &[f32], in_f: usize) -> Res<Option<CudaSlice<u8>>> {
5618 if in_f % 8 != 0 {
5619 return Ok(None);
5620 }
5621 let mut bytes = Vec::with_capacity(data.len() * 2);
5622 for &v in data {
5623 let bits = v.to_bits();
5624 if bits & 0xFFFF != 0 {
5625 return Ok(None);
5626 }
5627 bytes.extend_from_slice(&((bits >> 16) as u16).to_le_bytes());
5628 }
5629 Ok(Some(e.htod_bytes(&bytes)?))
5630}
5631
5632#[allow(clippy::too_many_arguments)]
5637fn launch_qmatvec_bf16w(
5638 e: &Engine,
5639 w: &CudaSlice<u8>,
5640 x: &CudaSlice<f32>,
5641 y: &mut CudaSlice<f32>,
5642 in_f: usize,
5643 out_f: usize,
5644 t: usize,
5645 batch: usize,
5646 w_bstride: usize,
5647 x_bstride: usize,
5648 x_tstride: usize,
5649 y_bstride: usize,
5650) -> Res<()> {
5651 if in_f % 8 != 0 || x_bstride % 8 != 0 || x_tstride % 8 != 0 {
5652 return Err("qmatvec_bf16w_f32: stride breaks the uint4/float4 vector width".into());
5653 }
5654 if y.len() < (batch - 1) * y_bstride + t * out_f {
5655 return Err("qmatvec_bf16w_f32: output buffer too short".into());
5656 }
5657 let f = e.func("qmatvec_bf16w_f32");
5658 let cfg = LaunchConfig {
5659 grid_dim: (out_f as u32, t as u32, batch as u32),
5660 block_dim: (128, 1, 1),
5661 shared_mem_bytes: 0,
5662 };
5663 let (inf, outf, ti) = (in_f as i32, out_f as i32, t as i32);
5664 let (wb, xb, xt, yb) = (
5665 w_bstride as i64,
5666 x_bstride as i64,
5667 x_tstride as i64,
5668 y_bstride as i64,
5669 );
5670 let stream = e.gpu.stream();
5671 let mut b = stream.launch_builder(&f);
5672 b.arg(w)
5673 .arg(x)
5674 .arg(y)
5675 .arg(&inf)
5676 .arg(&outf)
5677 .arg(&ti)
5678 .arg(&wb)
5679 .arg(&xb)
5680 .arg(&xt)
5681 .arg(&yb);
5682 unsafe {
5683 b.launch(cfg)?;
5684 }
5685 Ok(())
5686}
5687
5688fn bf16_stack_twin(e: &Engine, parts: &[&[f32]], in_f: usize) -> Res<Option<CudaSlice<u8>>> {
5694 let mut cat: Vec<f32> = Vec::with_capacity(parts.iter().map(|p| p.len()).sum());
5695 for p in parts {
5696 cat.extend_from_slice(p);
5697 }
5698 bf16_twin(e, &cat, in_f)
5699}
5700
5701fn need_stack_twin(e: &Engine, parts: &[&[f32]], in_f: usize, what: &str) -> Res<CudaSlice<u8>> {
5703 bf16_stack_twin(e, parts, in_f)?.ok_or_else(|| {
5704 format!("qwen4exp_gpu tp2: {what} has no exact bf16 stack twin (in_f {in_f})").into()
5705 })
5706}
5707
5708#[allow(clippy::too_many_arguments)]
5712fn launch_qmatvec_bf16w_off(
5713 e: &Engine,
5714 w_stack: &CudaSlice<u8>,
5715 row_off: usize,
5716 x: &CudaSlice<f32>,
5717 y: &mut CudaSlice<f32>,
5718 in_f: usize,
5719 out_f: usize,
5720 t: usize,
5721) -> Res<()> {
5722 if in_f % 8 != 0 {
5723 return Err("qmatvec_bf16w_f32: stride breaks the uint4/float4 vector width".into());
5724 }
5725 if y.len() < t * out_f {
5726 return Err("qmatvec_bf16w_f32: output buffer too short".into());
5727 }
5728 let byte_off = row_off * in_f * 2;
5729 if w_stack.len() < byte_off + out_f * in_f * 2 {
5730 return Err("qmatvec_bf16w_f32: stacked twin shorter than the row window".into());
5731 }
5732 let wv = w_stack.slice(byte_off..w_stack.len());
5733 let f = e.func("qmatvec_bf16w_f32");
5734 let cfg = LaunchConfig {
5735 grid_dim: (out_f as u32, t as u32, 1),
5736 block_dim: (128, 1, 1),
5737 shared_mem_bytes: 0,
5738 };
5739 let (inf, outf, ti) = (in_f as i32, out_f as i32, t as i32);
5740 let (wb, xb, xt, yb) = (0i64, 0i64, in_f as i64, 0i64);
5741 let stream = e.gpu.stream();
5742 let mut b = stream.launch_builder(&f);
5743 b.arg(&wv)
5744 .arg(x)
5745 .arg(y)
5746 .arg(&inf)
5747 .arg(&outf)
5748 .arg(&ti)
5749 .arg(&wb)
5750 .arg(&xb)
5751 .arg(&xt)
5752 .arg(&yb);
5753 unsafe {
5754 b.launch(cfg)?;
5755 }
5756 Ok(())
5757}
5758
5759#[allow(clippy::too_many_arguments)]
5765fn launch_qmatvec_bf16w_off_into(
5766 e: &Engine,
5767 w_stack: &CudaSlice<u8>,
5768 w_row_off: usize,
5769 x: &CudaSlice<f32>,
5770 x_off: usize,
5771 y: &mut CudaSlice<f32>,
5772 y_off: usize,
5773 in_f: usize,
5774 out_f: usize,
5775) -> Res<()> {
5776 if in_f % 8 != 0 {
5777 return Err("qmatvec_bf16w_f32: stride breaks the uint4/float4 vector width".into());
5778 }
5779 let byte_off = w_row_off * in_f * 2;
5780 if w_stack.len() < byte_off + out_f * in_f * 2 {
5781 return Err("qmatvec_bf16w_f32: bank shorter than the expert row window".into());
5782 }
5783 if x.len() < x_off + in_f || y.len() < y_off + out_f {
5784 return Err("qmatvec_bf16w_f32: operand views out of range".into());
5785 }
5786 let wv = w_stack.slice(byte_off..w_stack.len());
5787 let xv = x.slice(x_off..x_off + in_f);
5788 let mut yv = y.slice_mut(y_off..y_off + out_f);
5789 let f = e.func("qmatvec_bf16w_f32");
5790 let cfg = LaunchConfig {
5791 grid_dim: (out_f as u32, 1, 1),
5792 block_dim: (128, 1, 1),
5793 shared_mem_bytes: 0,
5794 };
5795 let (inf, outf, ti) = (in_f as i32, out_f as i32, 1i32);
5796 let (wb, xb, xt, yb) = (0i64, 0i64, in_f as i64, 0i64);
5797 let stream = e.gpu.stream();
5798 let mut b = stream.launch_builder(&f);
5799 b.arg(&wv)
5800 .arg(&xv)
5801 .arg(&mut yv)
5802 .arg(&inf)
5803 .arg(&outf)
5804 .arg(&ti)
5805 .arg(&wb)
5806 .arg(&xb)
5807 .arg(&xt)
5808 .arg(&yb);
5809 unsafe {
5810 b.launch(cfg)?;
5811 }
5812 Ok(())
5813}
5814
5815#[allow(clippy::too_many_arguments)]
5821fn launch_qmatvec_bf16w_sel(
5822 e: &Engine,
5823 bank: &CudaSlice<u8>,
5824 sel: &CudaSlice<i32>,
5825 sel_off: usize,
5826 x: &CudaSlice<f32>,
5827 x_off: usize,
5828 x_sstride: usize,
5831 y: &mut CudaSlice<f32>,
5832 n_sel: usize,
5833 in_f: usize,
5834 out_f: usize,
5835) -> Res<()> {
5836 if in_f % 8 != 0 {
5837 return Err("qmatvec_bf16w_sel_f32: stride breaks the uint4/float4 vector width".into());
5838 }
5839 if sel.len() < sel_off + n_sel
5840 || x.len() < x_off + (n_sel - 1) * x_sstride + in_f
5841 || y.len() < n_sel * out_f
5842 || n_sel == 0
5843 {
5844 return Err("qmatvec_bf16w_sel_f32: operand views out of range".into());
5845 }
5846 let sv = sel.slice(sel_off..sel_off + n_sel);
5847 let xv = x.slice(x_off..x.len());
5848 let f = e.func("qmatvec_bf16w_sel_f32");
5849 let cfg = LaunchConfig {
5850 grid_dim: (out_f as u32, 1, n_sel as u32),
5851 block_dim: (128, 1, 1),
5852 shared_mem_bytes: 0,
5853 };
5854 let (inf, outf, ns) = (in_f as i32, out_f as i32, n_sel as i32);
5855 let xs = x_sstride as i64;
5856 let stream = e.gpu.stream();
5857 let mut b = stream.launch_builder(&f);
5858 b.arg(bank)
5859 .arg(&sv)
5860 .arg(&xv)
5861 .arg(&mut *y)
5862 .arg(&inf)
5863 .arg(&outf)
5864 .arg(&ns)
5865 .arg(&xs);
5866 unsafe {
5867 b.launch(cfg)?;
5868 }
5869 Ok(())
5870}
5871
5872#[allow(clippy::too_many_arguments)]
5877fn launch_qmatvec_bf16w_mt(
5878 e: &Engine,
5879 w_stack: &CudaSlice<u8>,
5880 w_row_off: usize,
5881 x: &CudaSlice<f32>,
5882 y: &mut CudaSlice<f32>,
5883 in_f: usize,
5884 out_f: usize,
5885 t: usize,
5886) -> Res<()> {
5887 if in_f % 8 != 0 {
5888 return Err("qmatvec_bf16w_mt_f32: in_f % 8 != 0".into());
5889 }
5890 if !(2..=12).contains(&t) {
5891 return Err("qmatvec_bf16w_mt_f32: t out of range (2..=12)".into());
5892 }
5893 let byte_off = w_row_off * in_f * 2;
5894 if w_stack.len() < byte_off + out_f * in_f * 2 || y.len() < t * out_f || x.len() < t * in_f {
5895 return Err("qmatvec_bf16w_mt_f32: operands out of range".into());
5896 }
5897 let wv = w_stack.slice(byte_off..w_stack.len());
5898 let f = e.func("qmatvec_bf16w_mt_f32");
5899 let cfg = LaunchConfig {
5900 grid_dim: (out_f as u32, 1, 1),
5901 block_dim: (128, 1, 1),
5902 shared_mem_bytes: 0,
5903 };
5904 let (inf, outf, ti) = (in_f as i32, out_f as i32, t as i32);
5905 let (wb, xb, xt, yb) = (0i64, 0i64, in_f as i64, 0i64);
5906 let stream = e.gpu.stream();
5907 let mut b = stream.launch_builder(&f);
5908 b.arg(&wv)
5909 .arg(x)
5910 .arg(y)
5911 .arg(&inf)
5912 .arg(&outf)
5913 .arg(&ti)
5914 .arg(&wb)
5915 .arg(&xb)
5916 .arg(&xt)
5917 .arg(&yb);
5918 unsafe {
5919 b.launch(cfg)?;
5920 }
5921 Ok(())
5922}
5923
5924#[allow(clippy::too_many_arguments)]
5928fn linear_trunk_stacked_into(
5929 e: &Engine,
5930 w_f32: &CudaSlice<f32>,
5931 stack_b16: &Option<CudaSlice<u8>>,
5932 row_off: usize,
5933 x: &CudaSlice<f32>,
5934 y: &mut CudaSlice<f32>,
5935 t: usize,
5936 in_f: usize,
5937 out_f: usize,
5938) -> Res<()> {
5939 if trunk_bf16_on() {
5940 if let Some(w) = stack_b16 {
5941 if (2..=12).contains(&t) && verify_mt_on() {
5942 return launch_qmatvec_bf16w_mt(e, w, row_off, x, y, in_f, out_f, t);
5943 }
5944 return launch_qmatvec_bf16w_off(e, w, row_off, x, y, in_f, out_f, t);
5945 }
5946 }
5947 if w_f32.len() < in_f * out_f {
5948 return Err(
5949 "qwen4exp_gpu: trunk f32 original dropped (trunk_f32_diet) — the bf16 \
5950 twin path is required (keep trunk seams ON)"
5951 .into(),
5952 );
5953 }
5954 e.linear_device_into(x, w_f32, y, t, in_f, out_f)
5955}
5956
5957fn launch_qmatvec_bf16w_multi4(
5962 e: &Engine,
5963 w_stack: &CudaSlice<u8>,
5964 x: &CudaSlice<f32>,
5965 parts: &[(&CudaSlice<f32>, usize)],
5966 in_f: usize,
5967) -> Res<()> {
5968 if in_f % 8 != 0 {
5969 return Err("qmatvec_bf16w_multi4_f32: in_f % 8 != 0".into());
5970 }
5971 if parts.is_empty() || parts.len() > 4 {
5972 return Err("qmatvec_bf16w_multi4_f32: 1..=4 parts".into());
5973 }
5974 let total: usize = parts.iter().map(|&(_, r)| r).sum();
5975 if w_stack.len() < total * in_f * 2 {
5976 return Err("qmatvec_bf16w_multi4_f32: stacked twin shorter than the row plan".into());
5977 }
5978 let stream = e.gpu.stream();
5979 let mut ptrs = [0u64; 4];
5980 let mut rows = [0i32; 4];
5981 for (i, &(buf, r)) in parts.iter().enumerate() {
5982 if buf.len() < r {
5983 return Err("qmatvec_bf16w_multi4_f32: destination shorter than its rows".into());
5984 }
5985 ptrs[i] = buf.device_ptr(&stream).0;
5986 rows[i] = r as i32;
5987 }
5988 let f = e.func("qmatvec_bf16w_multi4_f32");
5989 let cfg = LaunchConfig {
5990 grid_dim: (total as u32, 1, 1),
5991 block_dim: (128, 1, 1),
5992 shared_mem_bytes: 0,
5993 };
5994 let inf = in_f as i32;
5995 let mut b = stream.launch_builder(&f);
5996 b.arg(w_stack)
5997 .arg(x)
5998 .arg(&ptrs[0])
5999 .arg(&rows[0])
6000 .arg(&ptrs[1])
6001 .arg(&rows[1])
6002 .arg(&ptrs[2])
6003 .arg(&rows[2])
6004 .arg(&ptrs[3])
6005 .arg(&rows[3])
6006 .arg(&inf);
6007 unsafe {
6008 b.launch(cfg)?;
6009 }
6010 Ok(())
6011}
6012
6013#[allow(clippy::too_many_arguments)]
6017fn linear_trunk_into(
6018 e: &Engine,
6019 w_f32: &CudaSlice<f32>,
6020 w_b16: &Option<CudaSlice<u8>>,
6021 x: &CudaSlice<f32>,
6022 y: &mut CudaSlice<f32>,
6023 t: usize,
6024 in_f: usize,
6025 out_f: usize,
6026) -> Res<()> {
6027 if trunk_bf16_on() {
6028 if let Some(w) = w_b16 {
6029 if (2..=12).contains(&t) && verify_mt_on() {
6030 return launch_qmatvec_bf16w_mt(e, w, 0, x, y, in_f, out_f, t);
6031 }
6032 return launch_qmatvec_bf16w(e, w, x, y, in_f, out_f, t, 1, 0, 0, in_f, 0);
6033 }
6034 }
6035 if w_f32.len() < in_f * out_f {
6036 return Err(
6037 "qwen4exp_gpu: trunk f32 original dropped (trunk_f32_diet) — the bf16 \
6038 twin path is required (keep trunk seams ON)"
6039 .into(),
6040 );
6041 }
6042 e.linear_device_into(x, w_f32, y, t, in_f, out_f)
6043}
6044
6045#[allow(clippy::too_many_arguments)]
6053fn launch_nvfp4_sel_matvec(
6054 e: &Engine,
6055 codes: &CudaSlice<u8>,
6056 scales: &CudaSlice<u8>,
6057 macros_dev: &CudaSlice<f32>,
6058 sel: &CudaSlice<i32>,
6059 x: &CudaSlice<f32>,
6060 y: &mut CudaSlice<f32>,
6061 n_sel: usize,
6062 in_f: usize,
6063 out_f: usize,
6064 x_stride: usize,
6065) -> Res<()> {
6066 if in_f % 16 != 0 {
6067 return Err("qmatvec_nvfp4_modelopt_sel_f32: in_f % 16 != 0".into());
6068 }
6069 if y.len() < n_sel * out_f {
6070 return Err("qmatvec_nvfp4_modelopt_sel_f32: output shorter than n_sel*out_f".into());
6071 }
6072 let grp = sel_group_resolve(sel_group_dn(), in_f, out_f);
6075 let v3 = grp.is_none() && sel_v3_on() && in_f % 32 == 0 && out_f % 4 == 0;
6076 let v2 = grp.is_none() && !v3 && sel_v2_on() && in_f % 32 == 0 && out_f % 2 == 0;
6077 let f = e.func(if grp.is_some() {
6078 "qmatvec_nvfp4_modelopt_sel_g_f32"
6079 } else if v3 {
6080 "qmatvec_nvfp4_modelopt_sel_f32_v3"
6081 } else if v2 {
6082 "qmatvec_nvfp4_modelopt_sel_f32_v2"
6083 } else {
6084 "qmatvec_nvfp4_modelopt_sel_f32"
6085 });
6086 let grid_x = match grp {
6094 Some((g, rows)) => out_f / ((32 / g) * rows),
6095 None if v3 => out_f / 4,
6096 None if v2 => out_f / 2,
6097 None => out_f,
6098 };
6099 let cfg = LaunchConfig {
6100 grid_dim: (grid_x as u32, n_sel as u32, 1),
6101 block_dim: (32, 1, 1),
6102 shared_mem_bytes: 0,
6103 };
6104 let (inf, outf) = (in_f as i32, out_f as i32);
6105 let xs = x_stride as i64;
6106 let (gi, ri) = grp.map_or((0i32, 0i32), |(g, rows)| (g as i32, rows as i32));
6107 let stream = e.gpu.stream();
6108 let mut b = stream.launch_builder(&f);
6109 b.arg(codes)
6110 .arg(scales)
6111 .arg(macros_dev)
6112 .arg(sel)
6113 .arg(x)
6114 .arg(y)
6115 .arg(&inf)
6116 .arg(&outf)
6117 .arg(&xs);
6118 if grp.is_some() {
6119 b.arg(&gi).arg(&ri);
6120 }
6121 unsafe {
6122 b.launch(cfg)?;
6123 }
6124 Ok(())
6125}
6126
6127#[allow(clippy::too_many_arguments)]
6133fn launch_nvfp4_sel_gu_silu(
6134 e: &Engine,
6135 gate: (&CudaSlice<u8>, &CudaSlice<u8>, &CudaSlice<f32>),
6136 up: (&CudaSlice<u8>, &CudaSlice<u8>, &CudaSlice<f32>),
6137 sel: Option<&CudaSlice<i32>>,
6138 pack_raw: u64,
6139 n_sel: usize,
6140 x: &CudaSlice<f32>,
6141 act: &mut CudaSlice<f32>,
6142 in_f: usize,
6143 ff: usize,
6144 tok: Option<(&CudaSlice<i32>, usize)>,
6147) -> Res<()> {
6148 if in_f % 32 != 0 || ff % 4 != 0 {
6149 return Err("qmatvec_nvfp4_modelopt_sel_gu_silu_f32: geometry".into());
6150 }
6151 if act.len() < n_sel * ff {
6152 return Err("qmatvec_nvfp4_modelopt_sel_gu_silu_f32: act buffer too short".into());
6153 }
6154 if sel.is_none() == (pack_raw == 0) {
6155 return Err("qmatvec_nvfp4_modelopt_sel_gu_silu_f32: exactly one of sel/pack".into());
6156 }
6157 let grp = sel_group_resolve(sel_group_gu(), in_f, ff);
6160 let f = e.func(if grp.is_some() {
6161 "qmatvec_nvfp4_modelopt_sel_gu_silu_g_f32"
6162 } else {
6163 "qmatvec_nvfp4_modelopt_sel_gu_silu_f32"
6164 });
6165 let grid_x = match grp {
6167 Some((g, rows)) => ff / ((32 / g) * rows),
6168 None => ff / 4,
6169 };
6170 let cfg = LaunchConfig {
6171 grid_dim: (grid_x as u32, n_sel as u32, 1),
6172 block_dim: (32, 1, 1),
6173 shared_mem_bytes: 0,
6174 };
6175 let (inf, ffi, ms) = (in_f as i32, ff as i32, n_sel as i32);
6176 let (gi, ri) = grp.map_or((0i32, 0i32), |(g, rows)| (g as i32, rows as i32));
6177 let stream = e.gpu.stream();
6178 let mut b = stream.launch_builder(&f);
6179 b.arg(gate.0)
6180 .arg(gate.1)
6181 .arg(gate.2)
6182 .arg(up.0)
6183 .arg(up.1)
6184 .arg(up.2);
6185 match sel {
6186 Some(s) => {
6187 b.arg(s);
6188 }
6189 None => {
6190 b.arg(gate.2);
6192 }
6193 }
6194 let stream2 = e.gpu.stream();
6195 let (tok_raw, x_tstride) = match tok {
6196 Some((tm, stride)) => (tm.device_ptr(&stream2).0, stride as i64),
6197 None => (0u64, 0i64),
6198 };
6199 b.arg(&pack_raw)
6200 .arg(&ms)
6201 .arg(x)
6202 .arg(&mut *act)
6203 .arg(&inf)
6204 .arg(&ffi)
6205 .arg(&tok_raw)
6206 .arg(&x_tstride);
6207 if grp.is_some() {
6208 b.arg(&gi).arg(&ri);
6209 }
6210 unsafe {
6211 b.launch(cfg)?;
6212 }
6213 Ok(())
6214}
6215
6216#[derive(Debug, Clone, Copy)]
6218pub struct MoeUnionRow {
6219 pub t: usize,
6221 pub slots: usize,
6223 pub union_size: usize,
6225 pub gu_us: f64,
6227 pub down_us: f64,
6229 pub gu_spread_rel: f64,
6234 pub down_spread_rel: f64,
6235}
6236
6237pub fn moe_union_cost_probe(
6274 e: &Engine,
6275 experts: usize,
6276 hidden: usize,
6277 ff: usize,
6278 selected: usize,
6279 t: usize,
6280 reps: usize,
6281) -> Res<Vec<MoeUnionRow>> {
6282 if hidden % 32 != 0 || ff % 4 != 0 {
6283 return Err("moe_union_cost_probe: needs the gufuse geometry (hidden%32, ff%4)".into());
6284 }
6285 if selected == 0 || t == 0 || reps == 0 {
6286 return Err("moe_union_cost_probe: selected/t/reps must be non-zero".into());
6287 }
6288 let code_byte = |i: usize| -> u8 { (i.wrapping_mul(2_654_435_761) >> 13) as u8 };
6291 let scale_byte = |i: usize| -> u8 { 0x38 | ((i.wrapping_mul(40_503) >> 7) & 0x07) as u8 };
6292 let mk = |n: usize, f: &dyn Fn(usize) -> u8| -> Res<CudaSlice<u8>> {
6293 let host: Vec<u8> = (0..n).map(f).collect();
6294 let d = e.htod_bytes(&host)?;
6295 drop(host);
6296 Ok(d)
6297 };
6298 let gu_codes_n = experts * ff * (hidden / 2);
6302 let gu_scales_n = experts * ff * (hidden / 16);
6303 let dn_codes_n = experts * hidden * (ff / 2);
6304 let dn_scales_n = experts * hidden * (ff / 16);
6305 let gc = mk(gu_codes_n, &code_byte)?;
6306 let gs = mk(gu_scales_n, &scale_byte)?;
6307 let uc = mk(gu_codes_n, &|i| code_byte(i ^ 0x5A5A_5A5A))?;
6308 let us = mk(gu_scales_n, &|i| scale_byte(i ^ 0x3C3C_3C3C))?;
6309 let dc = mk(dn_codes_n, &|i| code_byte(i ^ 0x0F0F_0F0F))?;
6310 let ds = mk(dn_scales_n, &|i| scale_byte(i ^ 0x1111_1111))?;
6311 let gm = e.htod(&vec![1.0f32; experts])?;
6312 let um = e.htod(&vec![1.0f32; experts])?;
6313 let dm = e.htod(&vec![1.0f32; experts])?;
6314 let mixed_h: Vec<f32> = (0..t * hidden)
6316 .map(|i| ((i.wrapping_mul(40_503) % 1000) as f32) / 4000.0 - 0.125)
6317 .collect();
6318 let mixed = e.htod(&mixed_h)?;
6319
6320 let pool: Vec<i32> = {
6322 let stride = (experts / (t * selected).max(1)).max(1);
6323 (0..t * selected)
6324 .map(|i| ((i * stride) % experts) as i32)
6325 .collect()
6326 };
6327
6328 let mut rows: Vec<MoeUnionRow> = Vec::new();
6329 let mut cells: Vec<(usize, usize)> = vec![(1, selected)];
6331 for new in 0..=selected {
6332 cells.push((t, selected + (t - 1) * new));
6333 }
6334 struct Cell {
6343 t: usize,
6344 slots: usize,
6345 union_size: usize,
6346 sel: CudaSlice<i32>,
6347 tokm: CudaSlice<i32>,
6348 act: CudaSlice<f32>,
6349 partial: CudaSlice<f32>,
6350 gu: Vec<f64>,
6351 dn: Vec<f64>,
6352 }
6353 let mut built: Vec<Cell> = Vec::with_capacity(cells.len());
6354 for (cells_t, want_union) in cells {
6355 let slots = cells_t * selected;
6356 let new = if cells_t > 1 {
6360 (want_union - selected) / (cells_t - 1)
6361 } else {
6362 0
6363 };
6364 let shared = selected - new;
6365 let mut sel_h: Vec<i32> = Vec::with_capacity(slots);
6366 let mut tok_h: Vec<i32> = Vec::with_capacity(slots);
6367 let mut fresh = selected;
6368 for col in 0..cells_t {
6369 if col == 0 {
6370 sel_h.extend_from_slice(&pool[0..selected]);
6371 } else {
6372 sel_h.extend_from_slice(&pool[0..shared]);
6373 for _ in 0..new {
6374 sel_h.push(pool[fresh % pool.len()]);
6375 fresh += 1;
6376 }
6377 }
6378 for _ in 0..selected {
6379 tok_h.push(col as i32);
6380 }
6381 }
6382 let union_size = {
6383 let mut u: Vec<i32> = sel_h.clone();
6384 u.sort_unstable();
6385 u.dedup();
6386 u.len()
6387 };
6388 built.push(Cell {
6389 t: cells_t,
6390 slots,
6391 union_size,
6392 sel: e.htod_i32(&sel_h)?,
6393 tokm: e.htod_i32(&tok_h)?,
6394 act: e.zeros(slots * ff)?,
6395 partial: e.zeros(slots * hidden)?,
6396 gu: Vec::with_capacity(reps),
6397 dn: Vec::with_capacity(reps),
6398 });
6399 }
6400 for rep in 0..(reps + 1) {
6403 for c in built.iter_mut() {
6404 let tok_arg = if c.t > 1 {
6405 Some((&c.tokm, hidden))
6406 } else {
6407 None
6408 };
6409 e.stream().synchronize()?;
6410 let t0 = std::time::Instant::now();
6411 launch_nvfp4_sel_gu_silu(
6412 e,
6413 (&gc, &gs, &gm),
6414 (&uc, &us, &um),
6415 Some(&c.sel),
6416 0,
6417 c.slots,
6418 &mixed,
6419 &mut c.act,
6420 hidden,
6421 ff,
6422 tok_arg,
6423 )?;
6424 e.stream().synchronize()?;
6425 let t1 = std::time::Instant::now();
6426 launch_nvfp4_sel_matvec(
6427 e,
6428 &dc,
6429 &ds,
6430 &dm,
6431 &c.sel,
6432 &c.act,
6433 &mut c.partial,
6434 c.slots,
6435 ff,
6436 hidden,
6437 ff,
6438 )?;
6439 e.stream().synchronize()?;
6440 let t2 = std::time::Instant::now();
6441 if rep > 0 {
6442 c.gu.push(t1.duration_since(t0).as_secs_f64() * 1e6);
6443 c.dn.push(t2.duration_since(t1).as_secs_f64() * 1e6);
6444 }
6445 }
6446 }
6447 let stat = |v: &[f64]| -> (f64, f64) {
6448 let mut s = v.to_vec();
6449 s.sort_by(|a, b| a.partial_cmp(b).unwrap());
6450 let med = s[s.len() / 2];
6451 let spread = if med > 0.0 {
6454 (s[s.len() - 1] - s[0]) / med
6455 } else {
6456 0.0
6457 };
6458 (med, spread)
6459 };
6460 for c in &built {
6461 let (gu_us, gu_spread) = stat(&c.gu);
6462 let (down_us, down_spread) = stat(&c.dn);
6463 rows.push(MoeUnionRow {
6464 t: c.t,
6465 slots: c.slots,
6466 union_size: c.union_size,
6467 gu_us,
6468 down_us,
6469 gu_spread_rel: gu_spread,
6470 down_spread_rel: down_spread,
6471 });
6472 }
6473 Ok(rows)
6474}
6475
6476#[derive(Debug, Clone)]
6478pub struct SelShapeRow {
6479 pub t: usize,
6481 pub slots: usize,
6483 pub arm: String,
6485 pub gu_shape: String,
6489 pub dn_shape: String,
6490 pub gu_grid_x: usize,
6491 pub dn_grid_x: usize,
6492 pub gu_us: f64,
6493 pub down_us: f64,
6494 pub gu_spread_rel: f64,
6495 pub down_spread_rel: f64,
6496}
6497
6498#[allow(clippy::too_many_arguments)]
6529pub fn sel_shape_cost_probe(
6530 e: &Engine,
6531 experts: usize,
6532 hidden: usize,
6533 ff: usize,
6534 selected: usize,
6535 t: usize,
6536 reps: usize,
6537 arms: &[String],
6538) -> Res<Vec<SelShapeRow>> {
6539 if hidden % 32 != 0 || ff % 4 != 0 {
6540 return Err("sel_shape_cost_probe: needs the gufuse geometry (hidden%32, ff%4)".into());
6541 }
6542 if selected == 0 || t == 0 || reps == 0 || arms.is_empty() {
6543 return Err("sel_shape_cost_probe: selected/t/reps/arms must be non-empty".into());
6544 }
6545 let saved = sel_group_spec();
6546 let out = sel_shape_cost_probe_inner(e, experts, hidden, ff, selected, t, reps, arms);
6547 set_sel_group(&saved);
6548 out
6549}
6550
6551#[allow(clippy::too_many_arguments)]
6552fn sel_shape_cost_probe_inner(
6553 e: &Engine,
6554 experts: usize,
6555 hidden: usize,
6556 ff: usize,
6557 selected: usize,
6558 t: usize,
6559 reps: usize,
6560 arms: &[String],
6561) -> Res<Vec<SelShapeRow>> {
6562 let code_byte = |i: usize| -> u8 { (i.wrapping_mul(2_654_435_761) >> 13) as u8 };
6568 let scale_byte = |i: usize| -> u8 { 0x38 | ((i.wrapping_mul(40_503) >> 7) & 0x07) as u8 };
6569 let mk = |n: usize, f: &dyn Fn(usize) -> u8| -> Res<CudaSlice<u8>> {
6570 let host: Vec<u8> = (0..n).map(f).collect();
6571 let d = e.htod_bytes(&host)?;
6572 drop(host);
6573 Ok(d)
6574 };
6575 let gc = mk(experts * ff * (hidden / 2), &code_byte)?;
6576 let gs = mk(experts * ff * (hidden / 16), &scale_byte)?;
6577 let uc = mk(experts * ff * (hidden / 2), &|i| code_byte(i ^ 0x5A5A_5A5A))?;
6578 let us = mk(experts * ff * (hidden / 16), &|i| {
6579 scale_byte(i ^ 0x3C3C_3C3C)
6580 })?;
6581 let dc = mk(experts * hidden * (ff / 2), &|i| code_byte(i ^ 0x0F0F_0F0F))?;
6582 let ds = mk(experts * hidden * (ff / 16), &|i| {
6583 scale_byte(i ^ 0x1111_1111)
6584 })?;
6585 let gm = e.htod(&vec![1.0f32; experts])?;
6586 let um = e.htod(&vec![1.0f32; experts])?;
6587 let dm = e.htod(&vec![1.0f32; experts])?;
6588 let mixed_h: Vec<f32> = (0..t * hidden)
6589 .map(|i| ((i.wrapping_mul(40_503) % 1000) as f32) / 4000.0 - 0.125)
6590 .collect();
6591 let mixed = e.htod(&mixed_h)?;
6592
6593 let slots = t * selected;
6597 let stride = (experts / slots.max(1)).max(1);
6598 let sel_h: Vec<i32> = (0..slots)
6599 .map(|i| ((i * stride) % experts) as i32)
6600 .collect();
6601 let tok_h: Vec<i32> = (0..slots).map(|i| (i / selected) as i32).collect();
6602 let sel = e.htod_i32(&sel_h)?;
6603 let tokm = e.htod_i32(&tok_h)?;
6604 let mut act = e.zeros(slots * ff)?;
6605 let mut partial = e.zeros(slots * hidden)?;
6606
6607 struct Arm {
6608 spec: String,
6609 gu_shape: String,
6610 dn_shape: String,
6611 gu_grid_x: usize,
6612 dn_grid_x: usize,
6613 gu: Vec<f64>,
6614 dn: Vec<f64>,
6615 }
6616 let describe = |code: u32, in_f: usize, out_f: usize| -> (String, usize) {
6617 match sel_group_resolve(code, in_f, out_f) {
6618 Some((g, rows)) => {
6619 let rpw = (32 / g) * rows;
6620 (format!("g{g}r{rows}/rpw{rpw}"), out_f / rpw)
6621 }
6622 None => ("shipped".to_string(), out_f / 4),
6623 }
6624 };
6625 let mut built: Vec<Arm> = Vec::with_capacity(arms.len());
6626 for spec in arms {
6627 if !set_sel_group(spec) {
6628 return Err(format!("sel_shape_cost_probe: bad arm spec {spec:?}").into());
6629 }
6630 let (gu_shape, gu_grid_x) = describe(sel_group_gu(), hidden, ff);
6631 let (dn_shape, dn_grid_x) = describe(sel_group_dn(), ff, hidden);
6632 built.push(Arm {
6633 spec: spec.clone(),
6634 gu_shape,
6635 dn_shape,
6636 gu_grid_x,
6637 dn_grid_x,
6638 gu: Vec::with_capacity(reps),
6639 dn: Vec::with_capacity(reps),
6640 });
6641 }
6642 let tok_arg = if t > 1 { Some((&tokm, hidden)) } else { None };
6643 for rep in 0..(reps + 1) {
6644 for a in built.iter_mut() {
6645 set_sel_group(&a.spec);
6649 e.stream().synchronize()?;
6650 let t0 = std::time::Instant::now();
6651 launch_nvfp4_sel_gu_silu(
6652 e,
6653 (&gc, &gs, &gm),
6654 (&uc, &us, &um),
6655 Some(&sel),
6656 0,
6657 slots,
6658 &mixed,
6659 &mut act,
6660 hidden,
6661 ff,
6662 tok_arg,
6663 )?;
6664 e.stream().synchronize()?;
6665 let t1 = std::time::Instant::now();
6666 launch_nvfp4_sel_matvec(
6667 e,
6668 &dc,
6669 &ds,
6670 &dm,
6671 &sel,
6672 &act,
6673 &mut partial,
6674 slots,
6675 ff,
6676 hidden,
6677 ff,
6678 )?;
6679 e.stream().synchronize()?;
6680 let t2 = std::time::Instant::now();
6681 if rep > 0 {
6682 a.gu.push(t1.duration_since(t0).as_secs_f64() * 1e6);
6683 a.dn.push(t2.duration_since(t1).as_secs_f64() * 1e6);
6684 }
6685 }
6686 }
6687 let stat = |v: &[f64]| -> (f64, f64) {
6688 let mut s = v.to_vec();
6689 s.sort_by(|a, b| a.partial_cmp(b).unwrap());
6690 let med = s[s.len() / 2];
6691 let spread = if med > 0.0 {
6692 (s[s.len() - 1] - s[0]) / med
6693 } else {
6694 0.0
6695 };
6696 (med, spread)
6697 };
6698 Ok(built
6699 .iter()
6700 .map(|a| {
6701 let (gu_us, gu_spread_rel) = stat(&a.gu);
6702 let (down_us, down_spread_rel) = stat(&a.dn);
6703 SelShapeRow {
6704 t,
6705 slots,
6706 arm: a.spec.clone(),
6707 gu_shape: a.gu_shape.clone(),
6708 dn_shape: a.dn_shape.clone(),
6709 gu_grid_x: a.gu_grid_x,
6710 dn_grid_x: a.dn_grid_x,
6711 gu_us,
6712 down_us,
6713 gu_spread_rel,
6714 down_spread_rel,
6715 }
6716 })
6717 .collect())
6718}
6719
6720#[allow(clippy::too_many_arguments)]
6725fn launch_axpy_rows_seq_at(
6726 e: &Engine,
6727 x: &CudaSlice<f32>,
6728 x_row0: usize,
6729 w: &CudaSlice<f32>,
6730 w_off: usize,
6731 y: &mut CudaSlice<f32>,
6732 y_row: usize,
6733 width: usize,
6734 n_rows: usize,
6735) -> Res<()> {
6736 if x.len() < (x_row0 + n_rows) * width
6737 || w.len() < w_off + n_rows
6738 || y.len() < (y_row + 1) * width
6739 {
6740 return Err("axpy_rows_seq_f32: window out of range".into());
6741 }
6742 let xv = x.slice(x_row0 * width..(x_row0 + n_rows) * width);
6743 let wv = w.slice(w_off..w_off + n_rows);
6744 let mut yv = y.slice_mut(y_row * width..(y_row + 1) * width);
6745 let f = e.func("axpy_rows_seq_f32");
6746 let cfg = LaunchConfig::for_num_elems(width as u32);
6747 let (wi, nr) = (width as i32, n_rows as i32);
6748 let stream = e.gpu.stream();
6749 let mut b = stream.launch_builder(&f);
6750 b.arg(&xv).arg(&wv).arg(&mut yv).arg(&wi).arg(&nr);
6751 unsafe {
6752 b.launch(cfg)?;
6753 }
6754 Ok(())
6755}
6756
6757pub fn gate_nvfp4_sel_matvec(e: &Engine) -> Res<String> {
6766 let mut lcg = 0x2545_f491_u64;
6767 let mut next_u32 = move || -> u32 {
6768 lcg = lcg
6769 .wrapping_mul(6364136223846793005)
6770 .wrapping_add(1442695040888963407);
6771 (lcg >> 33) as u32
6772 };
6773 let macros = [
6774 1.0f32,
6775 0.5,
6776 5.9945243e-5, 2.0,
6778 0.25,
6779 3.7e-3,
6780 1.0,
6781 8.0,
6782 ];
6783 let sel_host: Vec<i32> = vec![3, 5, 3, 0]; let n_sel = sel_host.len();
6785 let mut worst = (0.0f32, 0.0f32); for (mode, out_f, in_f) in [
6792 ("gate_up_v1", 16usize, 48usize),
6793 ("down_v1", 32, 16),
6794 ("gate_up_v1_oddrows", 7, 64),
6795 ("gate_up_v2", 16, 64),
6796 ("down_v2", 32, 32),
6797 ("gate_up_v3", 16, 64),
6798 ("down_v3", 32, 32),
6799 ("gate_up_v3_v2rows", 6, 64), ] {
6801 set_sel_v3(mode.contains("v3"));
6802 let n_expert = macros.len();
6803 let mut codes = vec![0u8; n_expert * out_f * in_f / 2];
6804 for byte in &mut codes {
6805 *byte = next_u32() as u8;
6806 }
6807 let mut scales = vec![0u8; n_expert * out_f * in_f / 16];
6808 for byte in &mut scales {
6809 *byte = (next_u32() as u8) & 0xBF; }
6811 scales[0] = 0x7F; scales[3] = 0xFF; let x_stride = if mode.starts_with("down") { in_f } else { 0 };
6814 let x_rows = if x_stride == 0 { 1 } else { n_sel };
6815 let x_host: Vec<f32> = (0..x_rows * in_f)
6816 .map(|_| (next_u32() % 2000) as f32 / 1000.0 - 1.0)
6817 .collect();
6818 let codes_dev = e.htod_bytes(&codes)?;
6819 let scales_dev = e.htod_bytes(&scales)?;
6820 let macros_dev = e.htod(¯os)?;
6821 let sel_dev = e.htod_i32(&sel_host)?;
6822 let x_dev = e.htod(&x_host)?;
6823 let mut y_dev = e.uninit(n_sel * out_f)?;
6824 launch_nvfp4_sel_matvec(
6825 e,
6826 &codes_dev,
6827 &scales_dev,
6828 ¯os_dev,
6829 &sel_dev,
6830 &x_dev,
6831 &mut y_dev,
6832 n_sel,
6833 in_f,
6834 out_f,
6835 x_stride,
6836 )?;
6837 let y = e.dtoh(&y_dev)?;
6838 let wbytes = out_f * in_f / 2;
6839 let sbytes = out_f * in_f / 16;
6840 for (slot, &expert) in sel_host.iter().enumerate() {
6841 let expert = expert as usize;
6842 let w = memra_gguf::dsv4::dequant_nvfp4_expert(
6843 &codes[expert * wbytes..(expert + 1) * wbytes],
6844 &scales[expert * sbytes..(expert + 1) * sbytes],
6845 macros[expert],
6846 out_f,
6847 in_f,
6848 );
6849 let xrow = &x_host[slot * x_stride..slot * x_stride + in_f];
6850 for o in 0..out_f {
6851 let mut want = 0.0f32;
6852 for i in 0..in_f {
6853 want += w[o * in_f + i] * xrow[i];
6854 }
6855 let got = y[slot * out_f + o];
6856 let abs = (want - got).abs();
6857 let rel = abs / want.abs().max(1.0);
6858 if abs > worst.0 {
6859 worst.0 = abs;
6860 }
6861 if rel > worst.1 {
6862 worst.1 = rel;
6863 }
6864 if rel > 1e-5 {
6865 return Err(format!(
6866 "nvfp4-sel-matvec oracle: {mode} slot {slot} row {o}: want {want} \
6867 got {got} (rel {rel:.3e})"
6868 )
6869 .into());
6870 }
6871 }
6872 }
6873 }
6874 set_sel_v3(SEL_V3_DEFAULT);
6875
6876 {
6881 set_sel_v3(true);
6882 let (ff, in_f) = (16usize, 64usize);
6883 let n_expert = macros.len();
6884 let mut mk = |seed: u8| -> (Vec<u8>, Vec<u8>) {
6885 let mut codes = vec![0u8; n_expert * ff * in_f / 2];
6886 for byte in &mut codes {
6887 *byte = (next_u32() as u8) ^ seed;
6888 }
6889 let mut scales = vec![0u8; n_expert * ff * in_f / 16];
6890 for byte in &mut scales {
6891 *byte = (next_u32() as u8) & 0xBF;
6892 }
6893 scales[1] = 0x7F; (codes, scales)
6895 };
6896 let (g_codes, g_scales) = mk(0x00);
6897 let (u_codes, u_scales) = mk(0x5A);
6898 let gmac: Vec<f32> = macros.to_vec();
6899 let umac: Vec<f32> = macros.iter().map(|m| m * 0.5).collect();
6900 let x_host: Vec<f32> = (0..in_f)
6901 .map(|_| (next_u32() % 2000) as f32 / 1000.0 - 1.0)
6902 .collect();
6903 let gc = e.htod_bytes(&g_codes)?;
6904 let gs = e.htod_bytes(&g_scales)?;
6905 let gm = e.htod(&gmac)?;
6906 let uc = e.htod_bytes(&u_codes)?;
6907 let us = e.htod_bytes(&u_scales)?;
6908 let um = e.htod(&umac)?;
6909 let sel_dev = e.htod_i32(&sel_host)?;
6910 let x_dev = e.htod(&x_host)?;
6911 let mut yg = e.uninit(n_sel * ff)?;
6913 let mut yu = e.uninit(n_sel * ff)?;
6914 launch_nvfp4_sel_matvec(
6915 e, &gc, &gs, &gm, &sel_dev, &x_dev, &mut yg, n_sel, in_f, ff, 0,
6916 )?;
6917 launch_nvfp4_sel_matvec(
6918 e, &uc, &us, &um, &sel_dev, &x_dev, &mut yu, n_sel, in_f, ff, 0,
6919 )?;
6920 let mut act_chain = e.zeros(n_sel * ff)?;
6921 e.silu_mul(&yg, &yu, &mut act_chain, n_sel * ff)?;
6922 let mut act_fused = e.zeros(n_sel * ff)?;
6924 launch_nvfp4_sel_gu_silu(
6925 e,
6926 (&gc, &gs, &gm),
6927 (&uc, &us, &um),
6928 Some(&sel_dev),
6929 0,
6930 n_sel,
6931 &x_dev,
6932 &mut act_fused,
6933 in_f,
6934 ff,
6935 None,
6936 )?;
6937 let a = e.dtoh(&act_chain)?;
6938 let b = e.dtoh(&act_fused)?;
6939 for (i, (&x1, &x2)) in a.iter().zip(&b).enumerate() {
6940 if x1.to_bits() != x2.to_bits() {
6941 return Err(format!(
6942 "nvfp4-sel-matvec oracle: gufuse idx {i} not bit-identical \
6943 (chain {x1} fused {x2})"
6944 )
6945 .into());
6946 }
6947 }
6948 let pack_bytes = tp2_pack_bytes(&sel_host[..2], &[0.5, 0.25], n_sel);
6951 let pack = e.htod_bytes(&pack_bytes)?;
6952 let pack_raw = {
6953 let stream = e.gpu.stream();
6954 pack.device_ptr(&stream).0
6955 };
6956 let sentinel = vec![-777.0f32; n_sel * ff];
6957 let mut act_pack = e.htod(&sentinel)?;
6958 launch_nvfp4_sel_gu_silu(
6959 e,
6960 (&gc, &gs, &gm),
6961 (&uc, &us, &um),
6962 None,
6963 pack_raw,
6964 n_sel,
6965 &x_dev,
6966 &mut act_pack,
6967 in_f,
6968 ff,
6969 None,
6970 )?;
6971 let c = e.dtoh(&act_pack)?;
6972 for slot in 0..n_sel {
6973 for o in 0..ff {
6974 let got = c[slot * ff + o];
6975 if slot < 2 {
6976 if got.to_bits() != a[slot * ff + o].to_bits() {
6977 return Err(format!(
6978 "nvfp4-sel-matvec oracle: gufuse pack slot {slot} o {o} \
6979 not bit-identical"
6980 )
6981 .into());
6982 }
6983 } else if got != -777.0 {
6984 return Err(format!(
6985 "nvfp4-sel-matvec oracle: gufuse pack dead slot {slot} written"
6986 )
6987 .into());
6988 }
6989 }
6990 }
6991 {
6994 let t2 = 2usize;
6995 let x2_host: Vec<f32> = (0..t2 * in_f)
6996 .map(|_| (next_u32() % 2000) as f32 / 1000.0 - 1.0)
6997 .collect();
6998 let x2 = e.htod(&x2_host)?;
6999 let tok_host: Vec<i32> = (0..n_sel).map(|s| (s % t2) as i32).collect();
7000 let tokm = e.htod_i32(&tok_host)?;
7001 let mut act_map = e.zeros(n_sel * ff)?;
7002 launch_nvfp4_sel_gu_silu(
7003 e,
7004 (&gc, &gs, &gm),
7005 (&uc, &us, &um),
7006 Some(&sel_dev),
7007 0,
7008 n_sel,
7009 &x2,
7010 &mut act_map,
7011 in_f,
7012 ff,
7013 Some((&tokm, in_f)),
7014 )?;
7015 let got = e.dtoh(&act_map)?;
7016 for tok in 0..t2 {
7017 let slots: Vec<usize> = (0..n_sel).filter(|s| s % t2 == tok).collect();
7018 let sel_tok: Vec<i32> = slots.iter().map(|&s| sel_host[s]).collect();
7019 let sel_tok_dev = e.htod_i32(&sel_tok)?;
7020 let xrow = e.htod(&x2_host[tok * in_f..(tok + 1) * in_f])?;
7021 let mut act_tok = e.zeros(sel_tok.len() * ff)?;
7022 launch_nvfp4_sel_gu_silu(
7023 e,
7024 (&gc, &gs, &gm),
7025 (&uc, &us, &um),
7026 Some(&sel_tok_dev),
7027 0,
7028 sel_tok.len(),
7029 &xrow,
7030 &mut act_tok,
7031 in_f,
7032 ff,
7033 None,
7034 )?;
7035 let want = e.dtoh(&act_tok)?;
7036 for (local, &slot) in slots.iter().enumerate() {
7037 for o in 0..ff {
7038 let a = got[slot * ff + o];
7039 let b = want[local * ff + o];
7040 if a.to_bits() != b.to_bits() {
7041 return Err(format!(
7042 "nvfp4-sel-matvec oracle: gufuse tok_map slot {slot} o {o} \
7043 not bit-identical (map {a} per-token {b})"
7044 )
7045 .into());
7046 }
7047 }
7048 }
7049 }
7050 }
7051 set_sel_v3(SEL_V3_DEFAULT);
7052 }
7053 Ok(format!(
7054 "nvfp4-sel-matvec kernel oracle: worst abs {:.3e} rel {:.3e} over gate_up+down \
7055 v1/v2/v3 modes, NaN scales + non-pow2 macros + duplicate slots; gufuse \
7056 BIT-IDENTICAL to the v3+silu chain incl. the count-gated pack twin + the \
7057 tok_map verify merge",
7058 worst.0, worst.1
7059 ))
7060}
7061
7062pub fn gate_nvfp4_sel_group(e: &Engine) -> Res<String> {
7089 let saved = sel_group_spec();
7090 let out = gate_nvfp4_sel_group_inner(e);
7091 set_sel_group(&saved);
7094 set_sel_v3(SEL_V3_DEFAULT);
7095 out
7096}
7097
7098fn gate_nvfp4_sel_group_inner(e: &Engine) -> Res<String> {
7099 let mut lcg = 0x2545_f491_u64; let mut next_u32 = move || -> u32 {
7101 lcg = lcg
7102 .wrapping_mul(6364136223846793005)
7103 .wrapping_add(1442695040888963407);
7104 (lcg >> 33) as u32
7105 };
7106 let macros = [
7107 1.0f32,
7108 0.5,
7109 5.9945243e-5, 2.0,
7111 0.25,
7112 3.7e-3,
7113 1.0,
7114 8.0,
7115 ];
7116 let n_expert = macros.len();
7117 let sel_host: Vec<i32> = vec![3, 5, 3, 0]; let n_sel = sel_host.len();
7119 let mut worst = (0.0f32, 0.0f32);
7120 let mut shapes_checked = 0usize;
7121 let mut bits_checked = 0usize;
7122 let mut calib: Vec<String> = Vec::new();
7123
7124 for (geom, out_f, in_f, per_slot_x) in [
7130 ("down_real", 2560usize, 640usize, true),
7131 ("gateup_real", 640, 2560, false),
7132 ("down_tiny", 32, 32, true),
7133 ("gateup_tiny", 16, 64, false),
7134 ] {
7135 let mut codes = vec![0u8; n_expert * out_f * in_f / 2];
7136 for byte in &mut codes {
7137 *byte = next_u32() as u8;
7138 }
7139 let mut scales = vec![0u8; n_expert * out_f * in_f / 16];
7140 for byte in &mut scales {
7141 *byte = (next_u32() as u8) & 0xBF; }
7143 scales[0] = 0x7F; scales[3] = 0xFF; let x_stride = if per_slot_x { in_f } else { 0 };
7146 let x_rows = if per_slot_x { n_sel } else { 1 };
7147 let x_host: Vec<f32> = (0..x_rows * in_f)
7148 .map(|_| (next_u32() % 2000) as f32 / 1000.0 - 1.0)
7149 .collect();
7150 let codes_dev = e.htod_bytes(&codes)?;
7151 let scales_dev = e.htod_bytes(&scales)?;
7152 let macros_dev = e.htod(¯os)?;
7153 let sel_dev = e.htod_i32(&sel_host)?;
7154 let x_dev = e.htod(&x_host)?;
7155 let run = |spec: &str| -> Res<Vec<f32>> {
7156 set_sel_group(spec);
7157 let mut y = e.uninit(n_sel * out_f)?;
7158 launch_nvfp4_sel_matvec(
7159 e,
7160 &codes_dev,
7161 &scales_dev,
7162 ¯os_dev,
7163 &sel_dev,
7164 &x_dev,
7165 &mut y,
7166 n_sel,
7167 in_f,
7168 out_f,
7169 x_stride,
7170 )?;
7171 e.dtoh(&y)
7172 };
7173 set_sel_v3(true);
7181 let shipped = run("off")?;
7182 let wbytes = out_f * in_f / 2;
7183 let sbytes = out_f * in_f / 16;
7184 let mut want = vec![0.0f32; n_sel * out_f];
7185 for (slot, &expert) in sel_host.iter().enumerate() {
7186 let expert = expert as usize;
7187 let w = memra_gguf::dsv4::dequant_nvfp4_expert(
7188 &codes[expert * wbytes..(expert + 1) * wbytes],
7189 &scales[expert * sbytes..(expert + 1) * sbytes],
7190 macros[expert],
7191 out_f,
7192 in_f,
7193 );
7194 let xrow = &x_host[slot * x_stride..slot * x_stride + in_f];
7195 for o in 0..out_f {
7196 let mut acc = 0.0f32;
7197 for i in 0..in_f {
7198 acc += w[o * in_f + i] * xrow[i];
7199 }
7200 want[slot * out_f + o] = acc;
7201 }
7202 }
7203 let ship_vs_host = want
7212 .iter()
7213 .zip(&shipped)
7214 .map(|(&w, &s)| (w - s).abs() / w.abs().max(1.0))
7215 .fold(0.0f32, f32::max);
7216 let class_tol = (4.0 * ship_vs_host).max(1e-5);
7220 calib.push(format!(
7221 "{geom} ship_vs_host={ship_vs_host:.3e} tol={class_tol:.3e}"
7222 ));
7223 for spec in [
7228 "dn:32:4", "dn:auto", "dn:16:4", "dn:16:2", "dn:8:4", "dn:8:2", "dn:8:1", "dn:4:4",
7229 "dn:4:2", "dn:4:1", "dn:2:4", "dn:2:2", "dn:2:1", "dn:1:1",
7230 ] {
7231 let Some((g, rows)) = sel_group_resolve(
7232 match spec {
7233 "dn:auto" => SEL_GROUP_AUTO,
7234 _ => {
7235 let (gs, rs) = spec.trim_start_matches("dn:").split_once(':').unwrap();
7236 (gs.parse::<u32>().unwrap() << 8) | rs.parse::<u32>().unwrap()
7237 }
7238 },
7239 in_f,
7240 out_f,
7241 ) else {
7242 continue; };
7244 let got = run(spec)?;
7245 shapes_checked += 1;
7246 if (g, rows) == (32, 4) {
7247 for (i, (&a, &b)) in shipped.iter().zip(&got).enumerate() {
7249 if a.to_bits() != b.to_bits() {
7250 return Err(format!(
7251 "sel-group oracle: {geom} g=32 rows=4 idx {i} NOT bit-identical to \
7252 the shipped v3 kernel (v3 {a} group {b}) — the sub-warp form must \
7253 degenerate to v3 exactly"
7254 )
7255 .into());
7256 }
7257 }
7258 bits_checked += shipped.len();
7259 }
7260 for (i, (&w, &got)) in want.iter().zip(&got).enumerate() {
7265 let abs = (w - got).abs();
7266 let rel = abs / w.abs().max(1.0);
7267 worst.0 = worst.0.max(abs);
7268 worst.1 = worst.1.max(rel);
7269 if rel > class_tol {
7270 return Err(format!(
7271 "sel-group oracle: {geom} {spec} (g={g} rows={rows}) idx {i} vs HOST \
7272 chain: want {w} got {got} (rel {rel:.3e} > tol {class_tol:.3e}, \
7273 shipped v3 itself is {ship_vs_host:.3e})"
7274 )
7275 .into());
7276 }
7277 }
7278 for (i, (&s, &got)) in shipped.iter().zip(&got).enumerate() {
7279 let rel = (s - got).abs() / s.abs().max(1.0);
7280 if rel > class_tol {
7281 return Err(format!(
7282 "sel-group oracle: {geom} {spec} (g={g} rows={rows}) idx {i} vs SHIPPED \
7283 v3: v3 {s} group {got} (rel {rel:.3e} > tol {class_tol:.3e})"
7284 )
7285 .into());
7286 }
7287 }
7288 }
7289 set_sel_group("off");
7290 }
7291
7292 for (geom, ff, in_f) in [("gu_real", 640usize, 2560usize), ("gu_tiny", 16, 64)] {
7296 let mut mk = |seed: u8| -> (Vec<u8>, Vec<u8>) {
7297 let mut codes = vec![0u8; n_expert * ff * in_f / 2];
7298 for byte in &mut codes {
7299 *byte = (next_u32() as u8) ^ seed;
7300 }
7301 let mut scales = vec![0u8; n_expert * ff * in_f / 16];
7302 for byte in &mut scales {
7303 *byte = (next_u32() as u8) & 0xBF;
7304 }
7305 scales[1] = 0x7F; (codes, scales)
7307 };
7308 let (g_codes, g_scales) = mk(0x00);
7309 let (u_codes, u_scales) = mk(0x5A);
7310 let gmac: Vec<f32> = macros.to_vec();
7311 let umac: Vec<f32> = macros.iter().map(|m| m * 0.5).collect();
7312 let x_host: Vec<f32> = (0..in_f)
7313 .map(|_| (next_u32() % 2000) as f32 / 1000.0 - 1.0)
7314 .collect();
7315 let gc = e.htod_bytes(&g_codes)?;
7316 let gs = e.htod_bytes(&g_scales)?;
7317 let gm = e.htod(&gmac)?;
7318 let uc = e.htod_bytes(&u_codes)?;
7319 let us = e.htod_bytes(&u_scales)?;
7320 let um = e.htod(&umac)?;
7321 let sel_dev = e.htod_i32(&sel_host)?;
7322 let x_dev = e.htod(&x_host)?;
7323 set_sel_group("off");
7324 set_sel_v3(true);
7325 let shipped_fused = {
7326 let mut act = e.zeros(n_sel * ff)?;
7327 launch_nvfp4_sel_gu_silu(
7328 e,
7329 (&gc, &gs, &gm),
7330 (&uc, &us, &um),
7331 Some(&sel_dev),
7332 0,
7333 n_sel,
7334 &x_dev,
7335 &mut act,
7336 in_f,
7337 ff,
7338 None,
7339 )?;
7340 e.dtoh(&act)?
7341 };
7342 for spec in ["32:4", "auto", "16:4", "16:2", "8:4", "8:1", "4:4"] {
7343 let Some((g, rows)) = sel_group_resolve(
7344 match spec {
7345 "auto" => SEL_GROUP_AUTO,
7346 _ => {
7347 let (gs, rs) = spec.split_once(':').unwrap();
7348 (gs.parse::<u32>().unwrap() << 8) | rs.parse::<u32>().unwrap()
7349 }
7350 },
7351 in_f,
7352 ff,
7353 ) else {
7354 continue;
7355 };
7356 set_sel_group(&format!("dn:{spec}+gu:off"));
7358 let mut yg = e.uninit(n_sel * ff)?;
7359 let mut yu = e.uninit(n_sel * ff)?;
7360 launch_nvfp4_sel_matvec(
7361 e, &gc, &gs, &gm, &sel_dev, &x_dev, &mut yg, n_sel, in_f, ff, 0,
7362 )?;
7363 launch_nvfp4_sel_matvec(
7364 e, &uc, &us, &um, &sel_dev, &x_dev, &mut yu, n_sel, in_f, ff, 0,
7365 )?;
7366 let mut act_chain = e.zeros(n_sel * ff)?;
7367 e.silu_mul(&yg, &yu, &mut act_chain, n_sel * ff)?;
7368 let chain = e.dtoh(&act_chain)?;
7369 set_sel_group(&format!("dn:off+gu:{spec}"));
7371 let mut act_fused = e.zeros(n_sel * ff)?;
7372 launch_nvfp4_sel_gu_silu(
7373 e,
7374 (&gc, &gs, &gm),
7375 (&uc, &us, &um),
7376 Some(&sel_dev),
7377 0,
7378 n_sel,
7379 &x_dev,
7380 &mut act_fused,
7381 in_f,
7382 ff,
7383 None,
7384 )?;
7385 let fused = e.dtoh(&act_fused)?;
7386 for (i, (&a, &b)) in chain.iter().zip(&fused).enumerate() {
7387 if a.to_bits() != b.to_bits() {
7388 return Err(format!(
7389 "sel-group oracle: {geom} gu {spec} (g={g} rows={rows}) idx {i} fused \
7390 NOT bit-identical to the same-shape chain (chain {a} fused {b})"
7391 )
7392 .into());
7393 }
7394 }
7395 bits_checked += chain.len();
7396 shapes_checked += 1;
7397 if (g, rows) == (32, 4) {
7398 for (i, (&a, &b)) in shipped_fused.iter().zip(&fused).enumerate() {
7399 if a.to_bits() != b.to_bits() {
7400 return Err(format!(
7401 "sel-group oracle: {geom} gu g=32 rows=4 idx {i} NOT bit-identical \
7402 to the shipped gufuse kernel (gufuse {a} group {b})"
7403 )
7404 .into());
7405 }
7406 }
7407 bits_checked += shipped_fused.len();
7408 }
7409 }
7410 set_sel_group("dn:off+gu:auto");
7413 if sel_group_resolve(SEL_GROUP_AUTO, in_f, ff).is_some() {
7414 let auto_plain = {
7415 let mut act = e.zeros(n_sel * ff)?;
7416 launch_nvfp4_sel_gu_silu(
7417 e,
7418 (&gc, &gs, &gm),
7419 (&uc, &us, &um),
7420 Some(&sel_dev),
7421 0,
7422 n_sel,
7423 &x_dev,
7424 &mut act,
7425 in_f,
7426 ff,
7427 None,
7428 )?;
7429 e.dtoh(&act)?
7430 };
7431 let pack_bytes = tp2_pack_bytes(&sel_host[..2], &[0.5, 0.25], n_sel);
7432 let pack = e.htod_bytes(&pack_bytes)?;
7433 let pack_raw = {
7434 let stream = e.gpu.stream();
7435 pack.device_ptr(&stream).0
7436 };
7437 let sentinel = vec![-777.0f32; n_sel * ff];
7438 let mut act_pack = e.htod(&sentinel)?;
7439 launch_nvfp4_sel_gu_silu(
7440 e,
7441 (&gc, &gs, &gm),
7442 (&uc, &us, &um),
7443 None,
7444 pack_raw,
7445 n_sel,
7446 &x_dev,
7447 &mut act_pack,
7448 in_f,
7449 ff,
7450 None,
7451 )?;
7452 let packed = e.dtoh(&act_pack)?;
7453 for slot in 0..n_sel {
7454 for o in 0..ff {
7455 let got = packed[slot * ff + o];
7456 if slot < 2 {
7457 if got.to_bits() != auto_plain[slot * ff + o].to_bits() {
7458 return Err(format!(
7459 "sel-group oracle: {geom} gu auto pack slot {slot} o {o} not \
7460 bit-identical to the sel-array arm"
7461 )
7462 .into());
7463 }
7464 } else if got != -777.0 {
7465 return Err(format!(
7466 "sel-group oracle: {geom} gu auto pack dead slot {slot} written"
7467 )
7468 .into());
7469 }
7470 }
7471 }
7472 let t2 = 2usize;
7474 let x2_host: Vec<f32> = (0..t2 * in_f)
7475 .map(|_| (next_u32() % 2000) as f32 / 1000.0 - 1.0)
7476 .collect();
7477 let x2 = e.htod(&x2_host)?;
7478 let tok_host: Vec<i32> = (0..n_sel).map(|s| (s % t2) as i32).collect();
7479 let tokm = e.htod_i32(&tok_host)?;
7480 let mut act_map = e.zeros(n_sel * ff)?;
7481 launch_nvfp4_sel_gu_silu(
7482 e,
7483 (&gc, &gs, &gm),
7484 (&uc, &us, &um),
7485 Some(&sel_dev),
7486 0,
7487 n_sel,
7488 &x2,
7489 &mut act_map,
7490 in_f,
7491 ff,
7492 Some((&tokm, in_f)),
7493 )?;
7494 let mapped = e.dtoh(&act_map)?;
7495 for tok in 0..t2 {
7496 let slots: Vec<usize> = (0..n_sel).filter(|s| s % t2 == tok).collect();
7497 let sel_tok: Vec<i32> = slots.iter().map(|&s| sel_host[s]).collect();
7498 let sel_tok_dev = e.htod_i32(&sel_tok)?;
7499 let xrow = e.htod(&x2_host[tok * in_f..(tok + 1) * in_f])?;
7500 let mut act_tok = e.zeros(sel_tok.len() * ff)?;
7501 launch_nvfp4_sel_gu_silu(
7502 e,
7503 (&gc, &gs, &gm),
7504 (&uc, &us, &um),
7505 Some(&sel_tok_dev),
7506 0,
7507 sel_tok.len(),
7508 &xrow,
7509 &mut act_tok,
7510 in_f,
7511 ff,
7512 None,
7513 )?;
7514 let want = e.dtoh(&act_tok)?;
7515 for (local, &slot) in slots.iter().enumerate() {
7516 for o in 0..ff {
7517 let a = mapped[slot * ff + o];
7518 let b = want[local * ff + o];
7519 if a.to_bits() != b.to_bits() {
7520 return Err(format!(
7521 "sel-group oracle: {geom} gu auto tok_map slot {slot} o {o} not \
7522 bit-identical (map {a} per-token {b})"
7523 )
7524 .into());
7525 }
7526 }
7527 }
7528 }
7529 bits_checked += auto_plain.len() + mapped.len();
7530 }
7531 set_sel_group("off");
7532 }
7533
7534 Ok(format!(
7535 "nvfp4-sel-GROUP kernel oracle: {shapes_checked} (geometry, shape) cells over REAL \
7536 MoE geometry (down 2560x640 pairs=20, gate_up 640x2560 pairs=80) + tiny, worst abs \
7537 {:.3e} rel {:.3e} vs the host decoder chain; (g=32,rows=4) BIT-IDENTICAL to the \
7538 shipped v3 and gufuse kernels and every shape's fused arm BIT-IDENTICAL to its \
7539 same-shape chain ({bits_checked} f32 byte-compared), incl. the count-gated pack \
7540 twin + the tok_map verify merge; NaN scales + non-pow2 macros + duplicate slots; \
7541 per-geometry class calibration [{}]",
7542 worst.0,
7543 worst.1,
7544 calib.join("; ")
7545 ))
7546}
7547
7548pub fn gate_hc_diet_kernels(e: &Engine) -> Res<String> {
7556 let (streams, hidden, rank, t) = (4usize, 2560usize, 320usize, 1usize);
7557 let wide = streams * hidden;
7558 let mut lcg = 0x8badf00d_u64;
7559 let mut next_f32 = move || -> f32 {
7560 lcg = lcg
7561 .wrapping_mul(6364136223846793005)
7562 .wrapping_add(1442695040888963407);
7563 (((lcg >> 33) as u32) % 2000) as f32 / 1000.0 - 1.0
7564 };
7565 let mut rand_vec = |n: usize| -> Vec<f32> { (0..n).map(|_| next_f32()).collect() };
7566 let to_b16_vals = |v: Vec<f32>| -> Vec<f32> {
7568 v.into_iter()
7569 .map(|x| f32::from_bits(x.to_bits() & 0xFFFF_0000))
7570 .collect()
7571 };
7572 let planes_host: Vec<Vec<f32>> = (0..streams).map(|_| rand_vec(t * hidden)).collect();
7573 let planes: Vec<CudaSlice<f32>> = planes_host
7574 .iter()
7575 .map(|v| e.htod(v))
7576 .collect::<Result<_, _>>()?;
7577 let ptr_vals: Vec<u64> = {
7578 let stream = e.gpu.stream();
7579 planes.iter().map(|p| p.device_ptr(&stream).0).collect()
7580 };
7581 let ptrs = e.htod_u64(&ptr_vals)?;
7582 let norm_stack_host = rand_vec(wide);
7583 let norm_stack = e.htod(&norm_stack_host)?;
7584 let down_host = to_b16_vals(rand_vec(streams * rank * hidden));
7585 let up_host = to_b16_vals(rand_vec(streams * hidden * rank));
7586 let inj_host = to_b16_vals(rand_vec(streams * wide));
7587 let down_b16 = bf16_twin(e, &down_host, hidden)?.ok_or("hc-diet oracle: down twin")?;
7588 let up_b16 = bf16_twin(e, &up_host, rank)?.ok_or("hc-diet oracle: up twin")?;
7589 let inj_b16 = bf16_twin(e, &inj_host, hidden)?.ok_or("hc-diet oracle: inject twin")?;
7590 let inj_f32 = e.htod(&inj_host)?;
7591 let eps = 1e-6f32;
7592
7593 let mut normed = e.zeros(streams * t * hidden)?;
7595 launch_hc_norm_planes(e, &ptrs, &norm_stack, &mut normed, hidden, t, streams, eps)?;
7596 let mut parts_c = e.zeros(streams * t * rank)?;
7597 launch_qmatvec_bf16w(
7598 e,
7599 &down_b16,
7600 &normed,
7601 &mut parts_c,
7602 hidden,
7603 rank,
7604 t,
7605 streams,
7606 rank * hidden,
7607 t * hidden,
7608 hidden,
7609 t * rank,
7610 )?;
7611 let mut low_c = e.zeros(t * rank)?;
7612 launch_hc_lowrank_reduce(e, &parts_c, &mut low_c, streams, t, rank)?;
7613 let mut gates_c = e.zeros(streams * t * hidden)?;
7614 launch_qmatvec_bf16w(
7615 e,
7616 &up_b16,
7617 &low_c,
7618 &mut gates_c,
7619 rank,
7620 hidden,
7621 t,
7622 streams,
7623 hidden * rank,
7624 0,
7625 rank,
7626 t * hidden,
7627 )?;
7628 let mut mixed_c = e.zeros(t * hidden)?;
7629 launch_hc_mix_epilogue(e, &gates_c, &normed, &mut mixed_c, streams, t, hidden)?;
7630 let mut partials_c = e.zeros(streams * t * 16)?;
7631 let mut all_c = e.zeros(streams * t)?;
7632 launch_hc_inject_two_stage(
7633 e,
7634 &normed,
7635 &inj_f32,
7636 Some(&inj_b16),
7637 &mut partials_c,
7638 &mut all_c,
7639 streams,
7640 t,
7641 hidden,
7642 16,
7643 )?;
7644
7645 let mut parts_d = e.zeros(streams * rank)?;
7647 let mut injp_d = e.zeros(streams * streams)?;
7648 let mut inv_d = e.zeros(streams)?;
7649 launch_hc_diet_stage1(
7650 e,
7651 &ptrs,
7652 &norm_stack,
7653 &down_b16,
7654 Some(&inj_b16),
7655 &mut parts_d,
7656 &mut injp_d,
7657 &mut inv_d,
7658 hidden,
7659 rank,
7660 streams,
7661 1,
7662 eps,
7663 )?;
7664 let mut low_d = e.zeros(rank)?;
7665 let mut all_d = e.zeros(streams)?;
7666 launch_hc_diet_stage2(
7667 e, &parts_d, &injp_d, &mut low_d, &mut all_d, rank, streams, 1, true,
7668 )?;
7669 let mut mixed_d = e.zeros(hidden)?;
7670 launch_hc_diet_stage3(
7671 e,
7672 &ptrs,
7673 &norm_stack,
7674 &inv_d,
7675 &up_b16,
7676 &low_d,
7677 &mut mixed_d,
7678 hidden,
7679 rank,
7680 streams,
7681 1,
7682 )?;
7683
7684 let mut worst = 0.0f32;
7685 let check = |name: &str, a: &[f32], b: &[f32], worst: &mut f32| -> Res<()> {
7686 for (i, (&x, &y)) in a.iter().zip(b).enumerate() {
7687 let rel = (x - y).abs() / y.abs().max(1.0);
7688 if rel > *worst {
7689 *worst = rel;
7690 }
7691 if rel > 1e-4 {
7692 return Err(format!(
7693 "hc-diet oracle: {name} idx {i}: diet {x} classic {y} (rel {rel:.3e})"
7694 )
7695 .into());
7696 }
7697 }
7698 Ok(())
7699 };
7700 check("low_act", &e.dtoh(&low_d)?, &e.dtoh(&low_c)?, &mut worst)?;
7701 check("inject", &e.dtoh(&all_d)?, &e.dtoh(&all_c)?, &mut worst)?;
7702 check("mixed", &e.dtoh(&mixed_d)?, &e.dtoh(&mixed_c)?, &mut worst)?;
7703
7704 {
7708 let t3 = 3usize;
7709 let planes3_host: Vec<Vec<f32>> = (0..streams).map(|_| rand_vec(t3 * hidden)).collect();
7710 let planes3: Vec<CudaSlice<f32>> = planes3_host
7711 .iter()
7712 .map(|v| e.htod(v))
7713 .collect::<Result<_, _>>()?;
7714 let ptr_vals3: Vec<u64> = {
7715 let stream = e.gpu.stream();
7716 planes3.iter().map(|p| p.device_ptr(&stream).0).collect()
7717 };
7718 let ptrs3 = e.htod_u64(&ptr_vals3)?;
7719 let mut parts3 = e.zeros(t3 * streams * rank)?;
7720 let mut injp3 = e.zeros(t3 * streams * streams)?;
7721 let mut inv3 = e.zeros(t3 * streams)?;
7722 launch_hc_diet_stage1(
7723 e,
7724 &ptrs3,
7725 &norm_stack,
7726 &down_b16,
7727 Some(&inj_b16),
7728 &mut parts3,
7729 &mut injp3,
7730 &mut inv3,
7731 hidden,
7732 rank,
7733 streams,
7734 t3,
7735 eps,
7736 )?;
7737 let mut low3 = e.zeros(t3 * rank)?;
7738 let mut all3 = e.zeros(streams * t3)?;
7739 launch_hc_diet_stage2(
7740 e, &parts3, &injp3, &mut low3, &mut all3, rank, streams, t3, true,
7741 )?;
7742 let mut mixed3 = e.zeros(t3 * hidden)?;
7743 launch_hc_diet_stage3(
7744 e,
7745 &ptrs3,
7746 &norm_stack,
7747 &inv3,
7748 &up_b16,
7749 &low3,
7750 &mut mixed3,
7751 hidden,
7752 rank,
7753 streams,
7754 t3,
7755 )?;
7756 let low3_h = e.dtoh(&low3)?;
7757 let all3_h = e.dtoh(&all3)?;
7758 let mixed3_h = e.dtoh(&mixed3)?;
7759 {
7762 let mut inv_mt = e.zeros(t3 * streams)?;
7763 launch_hc_diet_stage0_mt(e, &ptrs3, &mut inv_mt, hidden, streams, t3, eps)?;
7764 let mut parts_mt = e.zeros(t3 * streams * rank)?;
7765 let mut injp_mt = e.zeros(t3 * streams * streams)?;
7766 launch_hc_diet_stage1_mt(
7767 e,
7768 &ptrs3,
7769 &norm_stack,
7770 &inv_mt,
7771 &down_b16,
7772 Some(&inj_b16),
7773 &mut parts_mt,
7774 &mut injp_mt,
7775 hidden,
7776 rank,
7777 streams,
7778 t3,
7779 )?;
7780 let mut low_mt = e.zeros(t3 * rank)?;
7781 let mut all_mt = e.zeros(streams * t3)?;
7782 launch_hc_diet_stage2(
7783 e,
7784 &parts_mt,
7785 &injp_mt,
7786 &mut low_mt,
7787 &mut all_mt,
7788 rank,
7789 streams,
7790 t3,
7791 true,
7792 )?;
7793 let mut mixed_mt = e.zeros(t3 * hidden)?;
7794 launch_hc_diet_stage3_mt(
7795 e,
7796 &ptrs3,
7797 &norm_stack,
7798 &inv_mt,
7799 &up_b16,
7800 &low_mt,
7801 &mut mixed_mt,
7802 hidden,
7803 rank,
7804 streams,
7805 t3,
7806 )?;
7807 let bit_check_mt = |name: &str, a: &[f32], b: &[f32]| -> Res<()> {
7808 for (i, (&x, &y)) in a.iter().zip(b).enumerate() {
7809 if x.to_bits() != y.to_bits() {
7810 return Err(format!(
7811 "hc-diet mt oracle: {name} idx {i}: mt {x} vs grid {y} NOT \
7812 bit-identical"
7813 )
7814 .into());
7815 }
7816 }
7817 Ok(())
7818 };
7819 bit_check_mt("inv", &e.dtoh(&inv_mt)?, &e.dtoh(&inv3)?)?;
7820 bit_check_mt("low_act", &e.dtoh(&low_mt)?, &low3_h)?;
7821 bit_check_mt("inject", &e.dtoh(&all_mt)?, &all3_h)?;
7822 bit_check_mt("mixed", &e.dtoh(&mixed_mt)?, &mixed3_h)?;
7823 }
7824 let bit_check = |name: &str, a: &[f32], b: &[f32]| -> Res<()> {
7825 for (i, (&x, &y)) in a.iter().zip(b).enumerate() {
7826 if x.to_bits() != y.to_bits() {
7827 return Err(format!(
7828 "hc-diet t-ext oracle: {name} idx {i}: t3 {x} vs t1 {y} NOT bit-identical"
7829 )
7830 .into());
7831 }
7832 }
7833 Ok(())
7834 };
7835 for tok in 0..t3 {
7836 let ptr_tok: Vec<u64> = ptr_vals3
7837 .iter()
7838 .map(|&base| base + (tok * hidden * 4) as u64)
7839 .collect();
7840 let ptrs_tok = e.htod_u64(&ptr_tok)?;
7841 let mut parts1 = e.zeros(streams * rank)?;
7842 let mut injp1 = e.zeros(streams * streams)?;
7843 let mut inv1 = e.zeros(streams)?;
7844 launch_hc_diet_stage1(
7845 e,
7846 &ptrs_tok,
7847 &norm_stack,
7848 &down_b16,
7849 Some(&inj_b16),
7850 &mut parts1,
7851 &mut injp1,
7852 &mut inv1,
7853 hidden,
7854 rank,
7855 streams,
7856 1,
7857 eps,
7858 )?;
7859 let mut low1 = e.zeros(rank)?;
7860 let mut all1 = e.zeros(streams)?;
7861 launch_hc_diet_stage2(
7862 e, &parts1, &injp1, &mut low1, &mut all1, rank, streams, 1, true,
7863 )?;
7864 let mut mixed1 = e.zeros(hidden)?;
7865 launch_hc_diet_stage3(
7866 e,
7867 &ptrs_tok,
7868 &norm_stack,
7869 &inv1,
7870 &up_b16,
7871 &low1,
7872 &mut mixed1,
7873 hidden,
7874 rank,
7875 streams,
7876 1,
7877 )?;
7878 bit_check(
7879 "low_act",
7880 &low3_h[tok * rank..(tok + 1) * rank],
7881 &e.dtoh(&low1)?,
7882 )?;
7883 let all1_h = e.dtoh(&all1)?;
7884 let col: Vec<f32> = (0..streams).map(|s| all3_h[s * t3 + tok]).collect();
7885 bit_check("inject", &col, &all1_h)?;
7886 bit_check(
7887 "mixed",
7888 &mixed3_h[tok * hidden..(tok + 1) * hidden],
7889 &e.dtoh(&mixed1)?,
7890 )?;
7891 }
7892 }
7893 Ok(format!(
7894 "hc-diet real-geometry oracle: streams 4 hidden 2560 rank 320, worst rel \
7895 {worst:.3e} vs the classic fused chain at t 1; t 3 token-dim AND the mt \
7896 weight-shared stages BIT-IDENTICAL to per-token t 1 launches"
7897 ))
7898}
7899
7900pub fn gate_hc_micro_kernels(e: &Engine) -> Res<String> {
7915 let (streams, hidden, t) = (4usize, 2560usize, 10usize);
7916 let wide = streams * hidden;
7917 let mut lcg = 0x1357_9bdf_u64;
7918 let mut next_f32 = move || -> f32 {
7919 lcg = lcg
7920 .wrapping_mul(6364136223846793005)
7921 .wrapping_add(1442695040888963407);
7922 (((lcg >> 33) as u32) % 2000) as f32 / 1000.0 - 1.0
7923 };
7924 let mut rand_vec = |n: usize| -> Vec<f32> { (0..n).map(|_| next_f32()).collect() };
7925 let planes_host: Vec<Vec<f32>> = (0..streams).map(|_| rand_vec(t * hidden)).collect();
7926 let planes: Vec<CudaSlice<f32>> = planes_host
7927 .iter()
7928 .map(|v| e.htod(v))
7929 .collect::<Result<_, _>>()?;
7930 let ptr_vals: Vec<u64> = {
7931 let stream = e.gpu.stream();
7932 planes.iter().map(|p| p.device_ptr(&stream).0).collect()
7933 };
7934 let ptrs = e.htod_u64(&ptr_vals)?;
7935 let mut worst = 0.0f32;
7936 let check = |name: &str, a: &[f32], b: &[f32], worst: &mut f32| -> Res<()> {
7937 for (i, (&x, &y)) in a.iter().zip(b).enumerate() {
7938 let rel = (x - y).abs() / y.abs().max(1.0);
7939 if rel > *worst {
7940 *worst = rel;
7941 }
7942 if rel > 1e-4 {
7943 return Err(format!(
7944 "hc-micro oracle: {name} idx {i}: micro {x} classic {y} (rel {rel:.3e})"
7945 )
7946 .into());
7947 }
7948 }
7949 Ok(())
7950 };
7951
7952 let norm_stack_host = rand_vec(wide);
7954 let norm_stack = e.htod(&norm_stack_host)?;
7955 let eps = 1e-6f32;
7956 let mut normed_a = e.zeros(streams * t * hidden)?;
7957 launch_hc_norm_planes(
7958 e,
7959 &ptrs,
7960 &norm_stack,
7961 &mut normed_a,
7962 hidden,
7963 t,
7964 streams,
7965 eps,
7966 )?;
7967 let mut normed_b = e.zeros(streams * t * hidden)?;
7968 for s in 0..streams {
7969 let w = e.htod(&norm_stack_host[s * hidden..(s + 1) * hidden])?;
7970 let mut dst = normed_b.slice_mut(s * t * hidden..(s + 1) * t * hidden);
7971 launch_rms_norm_into_view(e, &planes[s], &w, &mut dst, hidden, t, eps)?;
7972 }
7973 check("norm", &e.dtoh(&normed_a)?, &e.dtoh(&normed_b)?, &mut worst)?;
7974
7975 let inj_w_host = rand_vec(streams * wide);
7977 let inj_w = e.htod(&inj_w_host)?;
7978 let mut all_a = e.zeros(streams * t)?;
7979 let mut partials = e.zeros(streams * t * 16)?;
7980 launch_hc_inject_two_stage(
7981 e,
7982 &normed_b,
7983 &inj_w,
7984 None,
7985 &mut partials,
7986 &mut all_a,
7987 streams,
7988 t,
7989 hidden,
7990 16,
7991 )?;
7992 let mut all_b = e.zeros(streams * t)?;
7993 launch_hc_inject_gates(e, &normed_b, &inj_w, &mut all_b, streams, t, hidden)?;
7994 check("inject", &e.dtoh(&all_a)?, &e.dtoh(&all_b)?, &mut worst)?;
7995
7996 let block_out = e.htod(&rand_vec(t * hidden))?;
7998 launch_hc_write_planes(e, &ptrs, &block_out, &all_b, hidden, t, streams)?;
7999 let mut expect: Vec<Vec<f32>> = Vec::with_capacity(streams);
8000 let all_host = e.dtoh(&all_b)?;
8001 let bo_host = e.dtoh(&block_out)?;
8002 for (s, base) in planes_host.iter().enumerate() {
8003 let mut rows = base.clone();
8004 for tok in 0..t {
8005 let g = all_host[s * t + tok];
8006 for d in 0..hidden {
8007 rows[tok * hidden + d] += bo_host[tok * hidden + d] * g;
8008 }
8009 }
8010 expect.push(rows);
8011 }
8012 for (s, plane) in planes.iter().enumerate() {
8013 check(
8014 &format!("write plane {s}"),
8015 &e.dtoh(plane)?,
8016 &expect[s],
8017 &mut worst,
8018 )?;
8019 }
8020 Ok(format!(
8021 "hc-micro real-geometry oracle: streams 4 hidden 2560 t 10, worst rel {worst:.3e} \
8022 over norm/inject/write vs the classic composition"
8023 ))
8024}
8025
8026pub fn gate_sdpa_blocklist(e: &Engine) -> Res<String> {
8044 let mut lcg = 0x51ee_7bad_u64;
8045 let mut next_f32 = move || -> f32 {
8046 lcg = lcg
8047 .wrapping_mul(6364136223846793005)
8048 .wrapping_add(1442695040888963407);
8049 (((lcg >> 33) as u32) % 2000) as f32 / 1000.0 - 1.0
8050 };
8051 let (hd, nh, nkv, t) = (256usize, 24usize, 2usize, 3usize);
8052 let block_size = 4usize;
8053 let scale = 1.0 / (hd as f32).sqrt();
8054 let mut bit_rows = 0usize;
8055 let mut worst_rel = 0.0f32;
8056 for (t_kv, vs_masked) in [(4096usize, true), (16384usize, false)] {
8057 let q_host: Vec<f32> = (0..t * nh * hd).map(|_| next_f32()).collect();
8058 let k_host: Vec<f32> = (0..t_kv * nkv * hd).map(|_| next_f32()).collect();
8059 let v_host: Vec<f32> = (0..t_kv * nkv * hd).map(|_| next_f32()).collect();
8060 let sels: Vec<RowSel> = (0..t)
8063 .map(|qt| {
8064 let visible = t_kv - t + qt + 1;
8065 let complete = visible / block_size;
8066 if qt == 0 && vs_masked {
8071 return RowSel {
8072 full: true,
8073 blocks: Vec::new(),
8074 visible,
8075 };
8076 }
8077 let stride = if qt == 1 { 3 } else { 7 };
8078 let blocks: Vec<u32> = (0..complete as u32)
8079 .rev()
8080 .step_by(stride)
8081 .take(512)
8082 .collect::<Vec<_>>()
8083 .into_iter()
8084 .rev()
8085 .collect();
8086 RowSel {
8087 full: false,
8088 blocks,
8089 visible,
8090 }
8091 })
8092 .collect();
8093 let (pos_flat, meta, max_count) = rowsel_positions(&sels, block_size);
8094 let q = e.htod(&q_host)?;
8095 let k = e.htod(&k_host)?;
8096 let v = e.htod(&v_host)?;
8097 let pos = e.htod_i32(&pos_flat)?;
8098 let meta_dev = e.htod_i32(&meta)?;
8099 let mut o_list = e.zeros(t * nh * hd)?;
8100 launch_sdpa_blocklist(
8101 e,
8102 &q,
8103 &k.slice(0..t_kv * nkv * hd),
8104 &v.slice(0..t_kv * nkv * hd),
8105 &mut o_list,
8106 &pos,
8107 &meta_dev,
8108 hd,
8109 nh,
8110 nkv,
8111 t,
8112 max_count,
8113 scale,
8114 )?;
8115 let ours = e.dtoh(&o_list)?;
8116 if vs_masked {
8117 let mask = rowsel_to_mask(&sels, block_size, t_kv);
8118 let mask_dev = e.htod_bytes(&mask)?;
8119 let mut o_mask = e.zeros(t * nh * hd)?;
8120 launch_sdpa_mask(
8121 e,
8122 &q,
8123 &k.slice(0..t_kv * nkv * hd),
8124 &v.slice(0..t_kv * nkv * hd),
8125 &mut o_mask,
8126 &mask_dev,
8127 hd,
8128 nh,
8129 nkv,
8130 t,
8131 t_kv,
8132 scale,
8133 )?;
8134 let masked = e.dtoh(&o_mask)?;
8135 for (i, (a, b)) in masked.iter().zip(ours.iter()).enumerate() {
8136 if a.to_bits() != b.to_bits() {
8137 return Err(format!(
8138 "sdpa_blocklist vs masked: bit mismatch at {i}: {a} vs {b} (t_kv {t_kv})"
8139 )
8140 .into());
8141 }
8142 }
8143 bit_rows = t * nh * hd;
8144 } else {
8145 for qt in 0..t {
8148 let off = meta[2 * qt] as usize;
8149 let count = meta[2 * qt + 1] as usize;
8150 for head in 0..nh {
8151 let kvh = head / (nh / nkv);
8152 let qrow = &q_host[(qt * nh + head) * hd..(qt * nh + head + 1) * hd];
8153 let mut scores: Vec<f32> = (0..count)
8154 .map(|i| {
8155 let p = pos_flat[off + i] as usize;
8156 let krow = &k_host[(p * nkv + kvh) * hd..(p * nkv + kvh + 1) * hd];
8157 let mut acc = 0.0f32;
8158 for d in 0..hd {
8159 acc += qrow[d] * krow[d];
8160 }
8161 acc * scale
8162 })
8163 .collect();
8164 let mx = scores.iter().copied().fold(-1e30f32, f32::max);
8165 let mut sum = 0.0f32;
8166 for s in scores.iter_mut() {
8167 *s = (*s - mx).exp();
8168 sum += *s;
8169 }
8170 let inv = 1.0 / sum;
8171 for s in scores.iter_mut() {
8172 *s *= inv;
8173 }
8174 for d in 0..hd {
8175 let mut acc = 0.0f32;
8176 for (i, s) in scores.iter().enumerate() {
8177 let p = pos_flat[off + i] as usize;
8178 acc += s * v_host[(p * nkv + kvh) * hd + d];
8179 }
8180 let got = ours[(qt * nh + head) * hd + d];
8181 let rel = (got - acc).abs() / acc.abs().max(1e-3);
8182 worst_rel = worst_rel.max(rel);
8183 if rel > 1e-4 {
8184 return Err(format!(
8185 "sdpa_blocklist vs host twin: rel {rel} at row {qt} head {head} \
8186 dim {d} (t_kv {t_kv})"
8187 )
8188 .into());
8189 }
8190 }
8191 }
8192 }
8193 }
8194 }
8195 Ok(format!(
8196 "sdpa-blocklist oracle: BIT-IDENTICAL to the masked kernel over {bit_rows} values \
8197 (t_kv 4096, full+stride selections); past the mask bound (t_kv 16384) worst rel \
8198 {worst_rel:.3e} vs the host twin"
8199 ))
8200}
8201
8202pub fn gate_kvq_kernels(e: &Engine) -> Res<String> {
8216 let mut lcg = 0x6b_7671_5eed_u64; let mut next_f32 = move || -> f32 {
8218 lcg = lcg
8219 .wrapping_mul(6364136223846793005)
8220 .wrapping_add(1442695040888963407);
8221 (((lcg >> 33) as u32) % 2000) as f32 / 1000.0 - 1.0
8222 };
8223 let mut report = Vec::new();
8224
8225 for &dim in &[512usize, 40usize] {
8227 let rows = 9usize;
8228 let mut host_rows_f: Vec<f32> = (0..rows * dim).map(|_| next_f32()).collect();
8229 for v in host_rows_f[0..dim].iter_mut() {
8232 *v = 0.0;
8233 }
8234 for v in host_rows_f[dim..2 * dim].iter_mut() {
8235 *v = 0.75;
8236 }
8237 for (i, v) in host_rows_f[2 * dim..3 * dim].iter_mut().enumerate() {
8238 *v = if i == 0 {
8240 1.0
8241 } else {
8242 (i as f32) * 0.5 / 127.0
8243 };
8244 }
8245 for v in host_rows_f[3 * dim..4 * dim].iter_mut() {
8247 *v *= 1e-40;
8248 }
8249 let dev_rows = e.htod(&host_rows_f)?;
8250 let mut kq = e.alloc_u8(rows * q8_row_bytes(dim))?;
8251 let mut vq = e.alloc_u8(rows * q5_row_bytes(dim))?;
8252 launch_q4e_kv_append(e, &dev_rows, &dev_rows, &mut kq, &mut vq, 0, rows, dim)?;
8253 let kq_host = e.dtoh_u8(&kq)?;
8254 let vq_host = e.dtoh_u8(&vq)?;
8255 let mut k_twin = Vec::new();
8256 let mut v_twin = Vec::new();
8257 for r in 0..rows {
8258 host_quant_q8_row(&host_rows_f[r * dim..(r + 1) * dim], dim, &mut k_twin);
8259 host_quant_q5_row(&host_rows_f[r * dim..(r + 1) * dim], dim, &mut v_twin);
8260 }
8261 if kq_host != k_twin {
8262 let i = kq_host.iter().zip(&k_twin).position(|(a, b)| a != b);
8263 return Err(format!("kvq q8 quantize twin: byte mismatch at {i:?} (dim {dim})").into());
8264 }
8265 if vq_host != v_twin {
8266 let i = vq_host.iter().zip(&v_twin).position(|(a, b)| a != b);
8267 return Err(format!("kvq q5 quantize twin: byte mismatch at {i:?} (dim {dim})").into());
8268 }
8269 let mut kf = e.zeros(rows * dim)?;
8271 let mut vf = e.zeros(rows * dim)?;
8272 launch_q4e_kv_dequant_rows(e, &kq, &vq, &mut kf, &mut vf, 0, rows, dim)?;
8273 let kf_host = e.dtoh(&kf)?;
8274 let vf_host = e.dtoh(&vf)?;
8275 let mut kf_twin = Vec::new();
8276 let mut vf_twin = Vec::new();
8277 host_deq_q8_rows(&kq_host, 0, rows, dim, &mut kf_twin);
8278 host_deq_q5_rows(&vq_host, 0, rows, dim, &mut vf_twin);
8279 for (i, (a, b)) in kf_host.iter().zip(&kf_twin).enumerate() {
8280 if a.to_bits() != b.to_bits() {
8281 return Err(format!("kvq q8 dequant twin: bit mismatch at {i} (dim {dim})").into());
8282 }
8283 }
8284 for (i, (a, b)) in vf_host.iter().zip(&vf_twin).enumerate() {
8285 if a.to_bits() != b.to_bits() {
8286 return Err(format!("kvq q5 dequant twin: bit mismatch at {i} (dim {dim})").into());
8287 }
8288 }
8289 report.push(format!("quant+dequant twins dim {dim}: BYTE/BIT-IDENTICAL"));
8290 }
8291
8292 {
8294 let (hd, nh, nkv, t) = (256usize, 24usize, 2usize, 3usize);
8295 let kv_dim = nkv * hd;
8296 let block_size = 4usize;
8297 let scale = 1.0 / (hd as f32).sqrt();
8298 let t_kv = 4096usize;
8299 let q_host: Vec<f32> = (0..t * nh * hd).map(|_| next_f32()).collect();
8300 let k_host: Vec<f32> = (0..t_kv * kv_dim).map(|_| next_f32()).collect();
8301 let v_host: Vec<f32> = (0..t_kv * kv_dim).map(|_| next_f32()).collect();
8302 let k_rows = e.htod(&k_host)?;
8303 let v_rows = e.htod(&v_host)?;
8304 let mut kq = e.alloc_u8(t_kv * q8_row_bytes(kv_dim))?;
8305 let mut vq = e.alloc_u8(t_kv * q5_row_bytes(kv_dim))?;
8306 launch_q4e_kv_append(e, &k_rows, &v_rows, &mut kq, &mut vq, 0, t_kv, kv_dim)?;
8307 let sels: Vec<RowSel> = (0..t)
8310 .map(|qt| {
8311 let visible = (t_kv - t + qt + 1).min(2052);
8312 if qt == 0 {
8313 return RowSel {
8314 full: true,
8315 blocks: Vec::new(),
8316 visible,
8317 };
8318 }
8319 let complete = (t_kv - t + qt + 1) / block_size;
8320 let stride = if qt == 1 { 3 } else { 7 };
8321 let blocks: Vec<u32> = (0..complete as u32)
8322 .rev()
8323 .step_by(stride)
8324 .take(512)
8325 .collect::<Vec<_>>()
8326 .into_iter()
8327 .rev()
8328 .collect();
8329 RowSel {
8330 full: false,
8331 blocks,
8332 visible: t_kv - t + qt + 1,
8333 }
8334 })
8335 .collect();
8336 let (pos_flat, meta, max_count) = rowsel_positions(&sels, block_size);
8337 let q = e.htod(&q_host)?;
8338 let pos = e.htod_i32(&pos_flat)?;
8339 let meta_dev = e.htod_i32(&meta)?;
8340 let mut o_fused = e.zeros(t * nh * hd)?;
8341 launch_q4e_sdpa_blocklist_q8q5(
8342 e,
8343 &q,
8344 &kq,
8345 &vq,
8346 &mut o_fused,
8347 &pos,
8348 &meta_dev,
8349 hd,
8350 nh,
8351 nkv,
8352 t,
8353 max_count,
8354 scale,
8355 )?;
8356 let mut k_deq = e.zeros(t_kv * kv_dim)?;
8357 let mut v_deq = e.zeros(t_kv * kv_dim)?;
8358 launch_q4e_kv_dequant_rows(e, &kq, &vq, &mut k_deq, &mut v_deq, 0, t_kv, kv_dim)?;
8359 let mut o_comp = e.zeros(t * nh * hd)?;
8360 launch_sdpa_blocklist(
8361 e,
8362 &q,
8363 &k_deq.slice(0..t_kv * kv_dim),
8364 &v_deq.slice(0..t_kv * kv_dim),
8365 &mut o_comp,
8366 &pos,
8367 &meta_dev,
8368 hd,
8369 nh,
8370 nkv,
8371 t,
8372 max_count,
8373 scale,
8374 )?;
8375 let fused = e.dtoh(&o_fused)?;
8376 let comp = e.dtoh(&o_comp)?;
8377 for (i, (a, b)) in fused.iter().zip(&comp).enumerate() {
8378 if a.to_bits() != b.to_bits() {
8379 return Err(format!(
8380 "kvq fused attention vs dequant composition: bit mismatch at {i}: {a} vs {b}"
8381 )
8382 .into());
8383 }
8384 }
8385 {
8397 let was = kv_hoist_on();
8398 set_kv_hoist(true);
8399 let mut o_hoist = e.zeros(t * nh * hd)?;
8400 let launched = launch_q4e_sdpa_blocklist_q8q5(
8401 e,
8402 &q,
8403 &kq,
8404 &vq,
8405 &mut o_hoist,
8406 &pos,
8407 &meta_dev,
8408 hd,
8409 nh,
8410 nkv,
8411 t,
8412 max_count,
8413 scale,
8414 );
8415 set_kv_hoist(was);
8416 launched?;
8417 let hoist = e.dtoh(&o_hoist)?;
8418 let mut worst: Option<(usize, f32, f32)> = None;
8419 for (i, (a, b)) in hoist.iter().zip(&fused).enumerate() {
8420 if a.to_bits() != b.to_bits() && worst.is_none() {
8421 worst = Some((i, *a, *b));
8422 }
8423 }
8424 if let Some((i, a, b)) = worst {
8425 return Err(format!(
8426 "kvhoist vs un-hoisted q8q5 blocklist: bit mismatch at {i}: {a} vs {b} \
8427 (hd={hd} nh={nh} nkv={nkv} t={t} t_kv={t_kv} max_count={max_count})"
8428 )
8429 .into());
8430 }
8431 report.push(format!(
8437 "kvhoist vs un-hoisted q8q5 blocklist: BIT-IDENTICAL over {} values \
8438 (real geometry hd={hd} nh={nh} nkv={nkv}, {} blocks/head slice, max_count={max_count})",
8439 t * nh * hd,
8440 hd / 32
8441 ));
8442 }
8443 report.push(format!(
8444 "fused q8q5 blocklist vs dequant+f32 composition: BIT-IDENTICAL over {} values",
8445 t * nh * hd
8446 ));
8447 }
8448
8449 {
8451 let idx_dim = 128usize;
8452 let qk_width = 5 * idx_dim; let rows = 7usize;
8454 let src_host: Vec<f32> = (0..rows * qk_width).map(|_| next_f32()).collect();
8455 let src = e.htod(&src_host)?;
8456 let q_off = 4 * idx_dim;
8457 let mut dst_q8 = e.alloc_u8((rows + 2) * q8_row_bytes(idx_dim))?;
8459 launch_q4e_idx_append_q8(e, &src, &mut dst_q8, rows, idx_dim, qk_width, q_off, 2)?;
8460 let got = e.dtoh_u8(&dst_q8)?;
8461 let mut twin = vec![0u8; 2 * q8_row_bytes(idx_dim)];
8462 for r in 0..rows {
8463 host_quant_q8_row(
8464 &src_host[r * qk_width + q_off..(r + 1) * qk_width],
8465 idx_dim,
8466 &mut twin,
8467 );
8468 }
8469 if got[2 * q8_row_bytes(idx_dim)..] != twin[2 * q8_row_bytes(idx_dim)..] {
8470 return Err("idxq q8 append twin: byte mismatch".into());
8471 }
8472 let mut dst_bf = unsafe { e.gpu.stream().alloc::<u16>((rows + 2) * idx_dim)? };
8474 e.gpu.stream().memset_zeros(&mut dst_bf)?;
8475 launch_q4e_idx_append_bf16(e, &src, &mut dst_bf, rows, idx_dim, qk_width, q_off, 2)?;
8476 let got_bf: Vec<u16> = {
8477 let v = e
8478 .gpu
8479 .stream()
8480 .clone_dtoh(&dst_bf.slice(0..(rows + 2) * idx_dim))?;
8481 e.gpu.stream().synchronize()?;
8482 v
8483 };
8484 for r in 0..rows {
8485 for c in 0..idx_dim {
8486 let want = f32_to_bf16_rne(src_host[r * qk_width + q_off + c]);
8487 if got_bf[(2 + r) * idx_dim + c] != want {
8488 return Err(format!("idxq bf16 append twin: mismatch row {r} col {c}").into());
8489 }
8490 }
8491 }
8492 report.push("idx q8/bf16 appenders vs host twins: BYTE-IDENTICAL".to_string());
8493 }
8494
8495 Ok(format!("kvq kernel oracles: {}", report.join("; ")))
8496}
8497
8498pub fn gate_qsa_index_score(e: &Engine) -> Res<String> {
8503 let mut lcg = 0xfeed_1234_u64;
8504 let mut next_f32 = move || -> f32 {
8505 lcg = lcg
8506 .wrapping_mul(6364136223846793005)
8507 .wrapping_add(1442695040888963407);
8508 (((lcg >> 33) as u32) % 4000) as f32 / 2000.0 - 1.0
8509 };
8510 let (heads, head_dim) = (4usize, 128usize);
8511 let scale = (head_dim as f32).sqrt();
8512 let budget = 512usize;
8513 let mut worst_rows = 0usize;
8514 for (rows, n_blocks) in [(1usize, 4096usize), (7, 1031)] {
8515 let q_host: Vec<f32> = (0..rows * heads * head_dim).map(|_| next_f32()).collect();
8516 let pooled_host: Vec<f32> = (0..n_blocks * head_dim).map(|_| next_f32()).collect();
8517 let q = e.htod(&q_host)?;
8518 let pooled = e.htod(&pooled_host)?;
8519 let mut scores_dev = e.uninit(rows * n_blocks)?;
8520 launch_qsa_index_score(
8521 e,
8522 &q,
8523 &pooled,
8524 &mut scores_dev,
8525 heads,
8526 head_dim,
8527 n_blocks,
8528 rows,
8529 scale,
8530 )?;
8531 let got = e.dtoh(&scores_dev)?;
8532 for row in 0..rows {
8533 let qr = &q_host[row * heads * head_dim..(row + 1) * heads * head_dim];
8534 let host = score_blocks(qr, &pooled_host, heads, head_dim, n_blocks, scale, 1);
8535 for (b, want) in host.iter().enumerate() {
8536 let g = got[row * n_blocks + b];
8537 if g.to_bits() != want.to_bits() {
8538 return Err(format!(
8539 "qsa_index_score: bit mismatch row {row} block {b}: {g} vs host {want}"
8540 )
8541 .into());
8542 }
8543 }
8544 let a = top_blocks_ascending(&host, budget, 1);
8545 let b = top_blocks_ascending(&got[row * n_blocks..(row + 1) * n_blocks], budget, 1);
8546 if a != b {
8547 return Err(format!("qsa_index_score: top-k set differs at row {row}").into());
8548 }
8549 worst_rows += 1;
8550 }
8551 }
8552 let mut pool_t_rows = 0usize;
8567 for (rows, n_blocks, cap_rows) in [(1usize, 1031usize, 4096usize), (5, 2048, 2048)] {
8568 let q_host: Vec<f32> = (0..rows * heads * head_dim).map(|_| next_f32()).collect();
8569 let pooled_host: Vec<f32> = (0..n_blocks * head_dim).map(|_| next_f32()).collect();
8570 let q = e.htod(&q_host)?;
8571 let mut mirror = e.zeros(cap_rows * head_dim * POOL_PLANES)?;
8574 {
8575 let mut view = mirror.slice_mut(0..n_blocks * head_dim);
8576 e.gpu.stream().memcpy_htod(&pooled_host, &mut view)?;
8577 }
8578 launch_qsa_pooled_transpose(e, &mut mirror, 0, n_blocks, head_dim, cap_rows)?;
8579 let was = pool_t_on();
8580 set_pool_t(true);
8581 let mut scores_dev = e.uninit(rows * n_blocks)?;
8582 let launched = launch_qsa_index_score(
8583 e,
8584 &q,
8585 &mirror,
8586 &mut scores_dev,
8587 heads,
8588 head_dim,
8589 n_blocks,
8590 rows,
8591 scale,
8592 );
8593 set_pool_t(was);
8594 launched?;
8595 let got = e.dtoh(&scores_dev)?;
8596 for row in 0..rows {
8597 let qr = &q_host[row * heads * head_dim..(row + 1) * heads * head_dim];
8598 let host = score_blocks(qr, &pooled_host, heads, head_dim, n_blocks, scale, 1);
8599 for (b, want) in host.iter().enumerate() {
8600 let g = got[row * n_blocks + b];
8601 if g.to_bits() != want.to_bits() {
8602 return Err(format!(
8603 "poolT qsa_index_score_f32_t: bit mismatch row {row} block {b}: \
8604 {g} vs host {want} (n_blocks={n_blocks} cap_rows={cap_rows})"
8605 )
8606 .into());
8607 }
8608 }
8609 if top_blocks_ascending(&host, budget, 1)
8610 != top_blocks_ascending(&got[row * n_blocks..(row + 1) * n_blocks], budget, 1)
8611 {
8612 return Err(format!(
8613 "poolT qsa_index_score_f32_t: top-{budget} set differs at row {row} \
8614 (n_blocks={n_blocks} cap_rows={cap_rows})"
8615 )
8616 .into());
8617 }
8618 pool_t_rows += 1;
8619 }
8620 }
8621 Ok(format!(
8622 "qsa-index-score oracle: device scores BIT-IDENTICAL to the host twin over \
8623 {worst_rows} rows (4096 + 1031 blocks, real 4x128 geometry) and top-512 sets equal; \
8624 poolT dim-major plane (transpose + transposed kernel) BIT-IDENTICAL to the SAME host \
8625 twin over {pool_t_rows} rows, incl. the pitch-trap case cap_rows=4096 != n_blocks=1031"
8626 ))
8627}
8628
8629pub fn gate_ple_ngram_cache() -> Res<String> {
8648 let max_ngram = 3usize;
8650 let heads_per_ngram = 8usize;
8651 let total_heads = (max_ngram - 1) * heads_per_ngram;
8652 let multipliers: Vec<i64> = vec![
8653 0x2545_F491_4F6C_DD1D,
8654 0x9E37_79B9_7F4A_7C15u64 as i64,
8655 0x1234_5678_9ABC_DEF1,
8656 ];
8657 let sizes: Vec<i64> = (0..total_heads)
8658 .map(|i| 2_500_012_160 - (i as i64) * 7)
8659 .collect();
8660 let offsets: Vec<i64> = (0..total_heads)
8661 .map(|i| (i as i64) * 2_500_012_160)
8662 .collect();
8663 let eos = 248_046u32;
8664 let full = |ids: &[u32]| -> Vec<i64> {
8665 host_ngram_ids(
8666 ids,
8667 &multipliers,
8668 &sizes,
8669 &offsets,
8670 max_ngram,
8671 heads_per_ngram,
8672 eos,
8673 )
8674 };
8675 let mut lcg = 0x0be1_10ca_u64;
8676 let mut next_tok = move || -> u32 {
8677 lcg = lcg
8678 .wrapping_mul(6364136223846793005)
8679 .wrapping_add(1442695040888963407);
8680 ((lcg >> 33) as u32) % 250_000
8681 };
8682 let mut checks = 0usize;
8683 let run = |label: &str, steps: Vec<Vec<u32>>| -> Res<usize> {
8684 let (mut ci, mut ch, mut ce) = (Vec::new(), Vec::new(), -1i64);
8686 let mut n = 0usize;
8687 for seq in &steps {
8688 host_ngram_ids_cached(
8689 &mut ci,
8690 &mut ch,
8691 &mut ce,
8692 seq,
8693 &multipliers,
8694 &sizes,
8695 &offsets,
8696 max_ngram,
8697 heads_per_ngram,
8698 eos,
8699 );
8700 let want = full(seq);
8701 if ci.len() != want.len() {
8702 return Err(format!(
8703 "plecache oracle {label}: cache has {} ids, twin {} at len {}",
8704 ci.len(),
8705 want.len(),
8706 seq.len()
8707 )
8708 .into());
8709 }
8710 if let Some(i) = ci.iter().zip(&want).position(|(a, b)| a != b) {
8711 return Err(format!(
8712 "plecache oracle {label}: id {i} differs at len {} (token {}, head {}): \
8713 cache {} vs twin {}",
8714 seq.len(),
8715 i / total_heads,
8716 i % total_heads,
8717 ci[i],
8718 want[i]
8719 )
8720 .into());
8721 }
8722 n += seq.len();
8723 }
8724 Ok(n)
8725 };
8726 {
8728 let base: Vec<u32> = (0..200).map(|_| next_tok()).collect();
8729 let steps: Vec<Vec<u32>> = (1..=base.len()).map(|n| base[..n].to_vec()).collect();
8730 checks += run("decode-growth", steps)?;
8731 }
8732 {
8734 let base: Vec<u32> = (0..600).map(|_| next_tok()).collect();
8735 let mut steps = Vec::new();
8736 let mut n = 0usize;
8737 for step in [7usize, 1, 64, 3, 128, 2, 200, 195] {
8738 n = (n + step).min(base.len());
8739 steps.push(base[..n].to_vec());
8740 }
8741 checks += run("prefill-chunks", steps)?;
8742 }
8743 {
8745 let mut base: Vec<u32> = (0..300).map(|_| next_tok()).collect();
8746 for p in [0usize, 1, 2, 37, 38, 100, 101, 102, 299] {
8747 base[p] = eos;
8748 }
8749 let steps: Vec<Vec<u32>> = (1..=base.len()).map(|n| base[..n].to_vec()).collect();
8750 checks += run("eos-segments", steps)?;
8751 }
8752 {
8754 let base: Vec<u32> = vec![eos; 40];
8755 let steps: Vec<Vec<u32>> = (1..=base.len()).map(|n| base[..n].to_vec()).collect();
8756 checks += run("all-eos", steps)?;
8757 }
8758 {
8760 let a: Vec<u32> = (0..300).map(|_| next_tok()).collect();
8761 let mut b = a.clone();
8762 b[150] = a[150].wrapping_add(1) % 250_000;
8763 let mut c = b.clone();
8764 c[7] = b[7].wrapping_add(3) % 250_000;
8765 let mut d = c.clone();
8766 d.truncate(9);
8767 d.extend((0..100).map(|_| next_tok()));
8768 checks += run(
8769 "rewind-divergent",
8770 vec![
8771 a.clone(),
8772 a[..151].to_vec(),
8773 b.clone(),
8774 b[..8].to_vec(),
8775 c.clone(),
8776 d.clone(),
8777 a.clone(),
8778 ],
8779 )?;
8780 }
8781 {
8783 let a: Vec<u32> = (0..250).map(|_| next_tok()).collect();
8784 let mut s: Vec<u32> = (0..11).map(|_| next_tok()).collect();
8785 s[0] = eos;
8786 checks += run("state-reuse", vec![a.clone(), s.clone(), a.clone(), s])?;
8787 }
8788 Ok(format!(
8789 "plecache oracle: incremental n-gram ids EXACT vs the full host_ngram_ids twin over \
8790 {checks} cumulative-sequence comparisons across 6 case families (decode one-at-a-time \
8791 growth, ragged prefill chunks, eos segment resets incl. adjacent + leading + trailing \
8792 eos, all-eos, repeated rewinds to DIVERGING prefixes, and shorter-unrelated-sequence \
8793 state reuse)"
8794 ))
8795}
8796
8797pub fn gate_seam_table() -> Res<String> {
8813 let all: &[&str] = seam_names();
8818 let boolean: Vec<&str> = all
8819 .iter()
8820 .copied()
8821 .filter(|n| seam_state(n).is_some())
8822 .collect();
8823 if boolean.len() < 20 || all.len() < boolean.len() + 2 {
8828 return Err(format!(
8829 "seam-table oracle: refusing to report on {} boolean names out of {} total — the \
8830 seam list collapsed, so every assertion below would be vacuous",
8831 boolean.len(),
8832 all.len()
8833 )
8834 .into());
8835 }
8836 let names: &[&str] = &boolean;
8837 let snapshot = || -> Res<Vec<bool>> {
8838 names
8839 .iter()
8840 .map(|n| {
8841 seam_state(n).ok_or_else(|| {
8842 Box::<dyn std::error::Error>::from(format!(
8843 "seam-table oracle: seam_state({n:?}) is None — the name is in set_seam \
8844 but not in seam_state, so save/restore around a measurement would \
8845 silently not restore it"
8846 ))
8847 })
8848 })
8849 .collect()
8850 };
8851 let restore = |v: &[bool]| {
8852 for (n, &b) in names.iter().zip(v) {
8853 set_seam(n, b, None);
8854 }
8855 };
8856 let entry = snapshot()?;
8857 let mut checks = 0usize;
8858 for (i, name) in names.iter().enumerate() {
8859 for &want in &[true, false, true] {
8860 let before = snapshot()?;
8861 if !set_seam(name, want, None) {
8862 restore(&entry);
8863 return Err(format!("seam-table oracle: set_seam({name:?}) refused").into());
8864 }
8865 let after = snapshot()?;
8866 if after[i] != want {
8867 restore(&entry);
8868 return Err(format!(
8869 "seam-table oracle: set_seam({name:?}, {want}) then seam_state read {} — the \
8870 two tables disagree on this name",
8871 after[i]
8872 )
8873 .into());
8874 }
8875 for (j, other) in names.iter().enumerate() {
8879 if j != i && after[j] != before[j] {
8880 restore(&entry);
8881 return Err(format!(
8882 "seam-table oracle: arming {name:?} also changed {other:?} ({} -> {}) — \
8883 two names share one switch",
8884 before[j], after[j]
8885 )
8886 .into());
8887 }
8888 }
8889 checks += 1;
8890 }
8891 }
8892 for name in all {
8896 if !seam_exists(name) {
8897 restore(&entry);
8898 return Err(format!(
8899 "seam-table oracle: seam_names() lists {name:?} but seam_exists refuses it"
8900 )
8901 .into());
8902 }
8903 if !set_seam(name, seam_state(name).unwrap_or(false), None) {
8904 restore(&entry);
8905 return Err(format!(
8906 "seam-table oracle: seam_names() lists {name:?} but set_seam refuses it"
8907 )
8908 .into());
8909 }
8910 }
8911 if seam_exists("definitely-not-a-seam") || set_seam("definitely-not-a-seam", true, None) {
8913 restore(&entry);
8914 return Err("seam-table oracle: an unknown seam name was accepted".into());
8915 }
8916 let before = snapshot()?;
8919 for name in all {
8920 let _ = seam_exists(name);
8921 }
8922 if snapshot()? != before {
8923 restore(&entry);
8924 return Err("seam-table oracle: seam_exists mutated a seam (it must be name-only)".into());
8925 }
8926 restore(&entry);
8927 if snapshot()? != entry {
8928 return Err("seam-table oracle: the gate did not restore the entry state".into());
8929 }
8930 Ok(format!(
8931 "seam-table oracle: {} boolean seam names of {} total, {checks} set/read cycles, each \
8932 verified to change its OWN state and NO other (the cross-check that catches an arm \
8933 wired to a neighbour's switch), every listed name accepted by both entry points, \
8934 unknown names refused by both, seam_exists proven side-effect-free, entry state restored",
8935 names.len(),
8936 all.len()
8937 ))
8938}
8939
8940pub fn gate_qsa_index_topk(e: &Engine) -> Res<String> {
8963 let budget = 512usize;
8964 let mut lcg = 0x1d5e_10ca_u64;
8965 let mut rows_checked = 0usize;
8966 let mut deepest = 0usize;
8967 let mut cases: Vec<(String, Vec<usize>, usize, Vec<f32>)> = Vec::new();
8969 let mut next_f32 = move || -> f32 {
8970 lcg = lcg
8971 .wrapping_mul(6364136223846793005)
8972 .wrapping_add(1442695040888963407);
8973 let r = ((lcg >> 33) as u32) % 1000;
8975 if r < 250 { 0.0 } else { (r as f32) / 250.0 }
8976 };
8977 for (label, counts) in [
8978 ("real-262k-depth", vec![65_536usize]),
8979 ("real-131k-depth", vec![32_768usize, 32_768]),
8980 ("shallow", vec![513usize, 1_031, 4_096]),
8981 ("ragged-batch", vec![2_049usize, 8_191, 65_536, 4_097]),
8982 ] {
8983 let stride = *counts.iter().max().unwrap();
8984 let slab: Vec<f32> = (0..counts.len() * stride).map(|_| next_f32()).collect();
8985 cases.push((label.to_string(), counts, stride, slab));
8986 }
8987 cases.push((
8989 "all-zero".into(),
8990 vec![65_536usize],
8991 65_536,
8992 vec![0.0f32; 65_536],
8993 ));
8994 {
8996 let n = 4_096usize;
8997 let mut v = vec![0.0f32; n];
8998 for (i, slot) in v.iter_mut().enumerate() {
8999 *slot = if i < 12 {
9000 100.0 - i as f32
9001 } else if i % 7 == 0 {
9002 2.5 } else {
9004 (i % 3) as f32 * 0.25
9005 };
9006 }
9007 cases.push(("dup-straddle".into(), vec![n], n, v));
9008 }
9009 {
9012 let n = 2_048usize;
9013 let mut v = vec![0.0f32; n];
9014 for (i, slot) in v.iter_mut().enumerate() {
9015 *slot = match i % 8 {
9016 0 => 0.0,
9017 1 => -0.0,
9018 2 => f32::from_bits(1), 3 => -f32::from_bits(1), 4 => -(i as f32) * 0.5,
9021 5 => f32::NAN,
9022 6 => -f32::NAN,
9023 _ => (i % 5) as f32,
9024 };
9025 }
9026 cases.push(("total-cmp-domain".into(), vec![n], n, v));
9027 }
9028 for (label, counts, stride, slab) in &cases {
9029 let scores = e.htod(slab)?;
9030 let picked = launch_qsa_index_topk(e, &scores, counts, *stride, budget)?;
9031 if picked.len() != counts.len() {
9032 return Err(format!("idxsel oracle {label}: {} rows back", picked.len()).into());
9033 }
9034 for (r, &complete) in counts.iter().enumerate() {
9035 let row = &slab[r * *stride..r * *stride + complete];
9036 let twin = top_blocks_ascending(row, budget, 1);
9037 if twin != picked[r] {
9038 let first = twin
9039 .iter()
9040 .zip(picked[r].iter())
9041 .position(|(a, b)| a != b)
9042 .unwrap_or(twin.len().min(picked[r].len()));
9043 return Err(format!(
9044 "idxsel oracle {label}: selection differs at row {r} (blocks {complete}), \
9045 first differing slot {first}: host {:?} vs device {:?}",
9046 twin.get(first),
9047 picked[r].get(first)
9048 )
9049 .into());
9050 }
9051 rows_checked += 1;
9052 deepest = deepest.max(complete);
9053 }
9054 }
9055 Ok(format!(
9056 "qsa-index-topk oracle: device selection ids + ASCENDING order EXACT vs \
9057 top_blocks_ascending over {rows_checked} rows / {} cases at budget {budget}, \
9058 deepest {deepest} blocks (= the 262,144-token window), incl. the all-zero, \
9059 boundary-straddling-duplicate and total_cmp-domain (signed zero / subnormal / \
9060 negative / NaN) tie classes",
9061 cases.len()
9062 ))
9063}
9064
9065pub fn gate_route_kernel(e: &Engine) -> Res<String> {
9076 let mut lcg = 0x00de_7710_u64;
9077 let mut next_f32 = move || -> f32 {
9078 lcg = lcg
9079 .wrapping_mul(6364136223846793005)
9080 .wrapping_add(1442695040888963407);
9081 (((lcg >> 33) as u32) % 8000) as f32 / 200.0 - 20.0 };
9083 const ULP_BOUND: u32 = 2;
9084 let mut worst_ulp: u32 = 0;
9085 let mut rows_checked = 0usize;
9086 let run = |e: &Engine,
9087 label: &str,
9088 logits_host: &[f32],
9089 experts: usize,
9090 selected: usize,
9091 rows: usize,
9092 worst_ulp: &mut u32|
9093 -> Res<()> {
9094 let logits = e.htod(logits_host)?;
9095 let mut sel = e.alloc_uninit::<i32>(rows * selected)?;
9096 let mut w = e.uninit(rows * selected)?;
9097 let mut tok = e.alloc_uninit::<i32>(rows * selected)?;
9098 launch_route_topk(
9099 e,
9100 &logits,
9101 &mut sel,
9102 &mut w,
9103 Some((&mut tok, 3)),
9104 experts,
9105 selected,
9106 rows,
9107 )?;
9108 let sel_h = e.gpu.stream().clone_dtoh(&sel.slice(0..rows * selected))?;
9109 let w_h = e.dtoh(&w)?;
9110 let tok_h = e.gpu.stream().clone_dtoh(&tok.slice(0..rows * selected))?;
9111 let k = selected.min(experts);
9112 for row in 0..rows {
9113 let twin =
9114 host_route_softmax_topk(&logits_host[row * experts..(row + 1) * experts], selected);
9115 if twin.len() != k {
9116 return Err(format!("route oracle {label}: host twin width {}", twin.len()).into());
9117 }
9118 for (j, &(idx, wt)) in twin.iter().enumerate() {
9119 let ds = sel_h[row * selected + j];
9120 let dw = w_h[row * selected + j];
9121 if ds != idx as i32 {
9122 return Err(format!(
9123 "route oracle {label}: selection mismatch row {row} slot {j}: \
9124 device {ds} vs host {idx}"
9125 )
9126 .into());
9127 }
9128 let ulp = (dw.to_bits() as i64 - wt.to_bits() as i64).unsigned_abs();
9129 let ulp = u32::try_from(ulp).unwrap_or(u32::MAX);
9130 if ulp > ULP_BOUND {
9131 return Err(format!(
9132 "route oracle {label}: weight ULP {ulp} > {ULP_BOUND} at row {row} \
9133 slot {j}: device {dw:e} vs host {wt:e}"
9134 )
9135 .into());
9136 }
9137 *worst_ulp = (*worst_ulp).max(ulp);
9138 if tok_h[row * selected + j] != (3 + row) as i32 {
9139 return Err(format!(
9140 "route oracle {label}: tok map wrong at row {row} slot {j}"
9141 )
9142 .into());
9143 }
9144 }
9145 }
9146 Ok(())
9147 };
9148 let (experts, selected) = (512usize, 10usize);
9150 for rows in [1usize, 6, 16] {
9151 let logits: Vec<f32> = (0..rows * experts).map(|_| next_f32()).collect();
9152 run(e, "real", &logits, experts, selected, rows, &mut worst_ulp)?;
9153 rows_checked += rows;
9154 }
9155 let mut tie_rows: Vec<(String, Vec<f32>)> = Vec::new();
9157 {
9158 let mut v: Vec<f32> = (0..experts).map(|i| -30.0 - (i as f32) * 0.01).collect();
9161 for (rank, slot) in [40usize, 7, 300, 11].iter().enumerate() {
9162 v[*slot] = 10.0 - rank as f32;
9163 }
9164 for slot in [500usize, 3, 77, 210, 8, 401, 129, 64, 255, 380, 17, 450] {
9165 v[slot] = 2.5;
9166 }
9167 tie_rows.push(("dup-straddle".into(), v));
9168 tie_rows.push(("all-equal".into(), vec![0.125f32; experts]));
9170 let mut v = vec![-200.0f32; experts];
9173 v[100] = 5.0;
9174 for (i, slot) in [479usize, 2, 33].iter().enumerate() {
9175 v[*slot] = -80.0 - i as f32; }
9177 tie_rows.push(("underflow".into(), v));
9178 }
9179 for (label, v) in &tie_rows {
9180 run(e, label, v, experts, selected, 1, &mut worst_ulp)?;
9181 rows_checked += 1;
9182 }
9183 for (ex, se) in [(64usize, 4usize), (16, 16), (128, 32)] {
9185 let logits: Vec<f32> = (0..3 * ex).map(|_| next_f32()).collect();
9186 run(e, "geom", &logits, ex, se, 3, &mut worst_ulp)?;
9187 rows_checked += 3;
9188 }
9189 Ok(format!(
9190 "route oracle: device selection ids+order EXACT vs host twin over {rows_checked} rows \
9191 (real 512/10 + tie straddle/all-equal/underflow + geometry edges), worst weight \
9192 ULP {worst_ulp} (bound {ULP_BOUND}), tok map exact"
9193 ))
9194}
9195
9196pub fn gate_gdn_step_kernels(e: &Engine) -> Res<String> {
9197 let mut lcg = 0x0bad_cafe_u64;
9198 let mut next_f32 = move || -> f32 {
9199 lcg = lcg
9200 .wrapping_mul(6364136223846793005)
9201 .wrapping_add(1442695040888963407);
9202 (((lcg >> 33) as u32) % 2000) as f32 / 1000.0 - 1.0
9203 };
9204 let mut worst = 0.0f32;
9205 for (nk, nv, hk, hv) in [(16usize, 48usize, 128usize, 128usize), (2, 4, 32, 8)] {
9206 let conv_dim = 2 * nk * hk + nv * hv;
9207 let qkv_host: Vec<f32> = (0..conv_dim).map(|_| next_f32()).collect();
9208 let g_log_host: Vec<f32> = (0..nv).map(|_| next_f32().abs() * -2.0).collect();
9209 let beta_host: Vec<f32> = (0..nv).map(|_| next_f32()).collect();
9210 let state_host: Vec<f32> = (0..nv * hv * hk).map(|_| next_f32()).collect();
9211 let qkv = e.htod(&qkv_host)?;
9212 let g_log = e.htod(&g_log_host)?;
9213 let beta = e.htod(&beta_host)?;
9214 let scale = 1.0 / (hk as f32).sqrt();
9215 let eps = 1e-6f32;
9216 let mut state_a = e.htod(&state_host)?;
9217 let mut o_a = e.zeros(nv * hv)?;
9218 launch_gdn_scan(
9219 e,
9220 &qkv,
9221 &g_log,
9222 &beta,
9223 &mut state_a,
9224 &mut o_a,
9225 nk,
9226 nv,
9227 hk,
9228 hv,
9229 1,
9230 scale,
9231 eps,
9232 )?;
9233 let mut state_b = e.htod(&state_host)?;
9234 let mut o_b = e.zeros(nv * hv)?;
9235 launch_gdn_scan_step(
9236 e,
9237 &qkv,
9238 &g_log,
9239 &beta,
9240 &mut state_b,
9241 &mut o_b,
9242 nk,
9243 nv,
9244 hk,
9245 hv,
9246 scale,
9247 eps,
9248 )?;
9249 for (name, reference, candidate) in [
9250 ("o", e.dtoh(&o_a)?, e.dtoh(&o_b)?),
9251 ("state", e.dtoh(&state_a)?, e.dtoh(&state_b)?),
9252 ] {
9253 for (i, (&r, &c)) in reference.iter().zip(&candidate).enumerate() {
9254 let rel = (r - c).abs() / r.abs().max(1.0);
9255 if rel > worst {
9256 worst = rel;
9257 }
9258 if rel > 1e-4 {
9259 return Err(format!(
9260 "gdn-step oracle: nk{nk}/nv{nv}/hk{hk}/hv{hv} {name} idx {i}: \
9261 naive {r} step {c} (rel {rel:.3e})"
9262 )
9263 .into());
9264 }
9265 }
9266 }
9267 }
9268 let (rows, cols) = (48usize, 128usize);
9270 let x = e.htod(&(0..rows * cols).map(|_| next_f32()).collect::<Vec<_>>())?;
9271 let w = e.htod(&(0..cols).map(|_| next_f32()).collect::<Vec<_>>())?;
9272 let z = e.htod(&(0..rows * cols).map(|_| next_f32()).collect::<Vec<_>>())?;
9273 let eps = 1e-6f32;
9274 let mut normed = e.zeros(rows * cols)?;
9275 e.rms_norm(&x, &w, &mut normed, cols, rows, eps)?;
9276 let mut sg = e.zeros(rows * cols)?;
9277 e.sigmoid(&z, &mut sg, rows * cols)?;
9278 let mut chain = e.zeros(rows * cols)?;
9279 e.mul(&normed, &sg, &mut chain, rows * cols)?;
9280 let mut fused = e.zeros(rows * cols)?;
9281 launch_rms_sigmul(e, &x, &w, &z, &mut fused, cols, rows, eps)?;
9282 let (chain_h, fused_h) = (e.dtoh(&chain)?, e.dtoh(&fused)?);
9283 for (i, (&a, &b)) in chain_h.iter().zip(&fused_h).enumerate() {
9284 if a.to_bits() != b.to_bits() {
9285 return Err(format!(
9286 "rms_sigmul oracle: idx {i} not bit-identical: chain {a:?} fused {b:?}"
9287 )
9288 .into());
9289 }
9290 }
9291 Ok(format!(
9292 "gdn-step kernel oracle: scan step twin worst rel {worst:.3e} over artifact + \
9293 hk32 geometries; rms_sigmul bit-identical to the norm/sigmoid/mul chain ({rows}x{cols})"
9294 ))
9295}
9296
9297pub fn gate_qmatvec_bf16(e: &Engine) -> Res<String> {
9298 let mut lcg = 0x9e37_79b9_u64;
9299 let mut next_u32 = move || -> u32 {
9300 lcg = lcg
9301 .wrapping_mul(6364136223846793005)
9302 .wrapping_add(1442695040888963407);
9303 (lcg >> 33) as u32
9304 };
9305 let mut worst = (0.0f32, 0.0f32);
9306 for (mode, batch, t, out_f, in_f, x_bstride) in [
9307 ("per_batch_x", 3usize, 2usize, 5usize, 48usize, 2 * 48usize),
9308 ("shared_x", 4, 3, 7, 16, 0usize),
9309 ] {
9310 let w_elems = batch * out_f * in_f;
9313 let mut w_bytes = Vec::with_capacity(w_elems * 2);
9314 let mut w_host = Vec::with_capacity(w_elems);
9315 for _ in 0..w_elems {
9316 let h = ((next_u32() % 0x4000) as u16) | (((next_u32() & 1) as u16) << 15);
9319 w_bytes.extend_from_slice(&h.to_le_bytes());
9320 w_host.push(f32::from_bits(u32::from(h) << 16));
9321 }
9322 let x_rows = if x_bstride == 0 { t } else { batch * t };
9323 let x_host: Vec<f32> = (0..x_rows * in_f)
9324 .map(|_| (next_u32() % 2000) as f32 / 1000.0 - 1.0)
9325 .collect();
9326 let w_dev = e.htod_bytes(&w_bytes)?;
9327 let x_dev = e.htod(&x_host)?;
9328 let mut y_dev = e.uninit(batch * t * out_f)?;
9329 launch_qmatvec_bf16w(
9330 e,
9331 &w_dev,
9332 &x_dev,
9333 &mut y_dev,
9334 in_f,
9335 out_f,
9336 t,
9337 batch,
9338 out_f * in_f,
9339 x_bstride,
9340 in_f,
9341 t * out_f,
9342 )?;
9343 let y = e.dtoh(&y_dev)?;
9344 for b in 0..batch {
9345 for tok in 0..t {
9346 let xrow = &x_host[b * x_bstride + tok * in_f..][..in_f];
9347 for o in 0..out_f {
9348 let wrow = &w_host[(b * out_f + o) * in_f..][..in_f];
9349 let mut want = 0.0f32;
9350 for i in 0..in_f {
9351 want += wrow[i] * xrow[i];
9352 }
9353 let got = y[(b * t + tok) * out_f + o];
9354 let abs = (want - got).abs();
9355 let rel = abs / want.abs().max(1.0);
9356 worst.0 = worst.0.max(abs);
9357 worst.1 = worst.1.max(rel);
9358 if rel > 1e-5 {
9359 return Err(format!(
9360 "bf16-matvec oracle: {mode} b {b} tok {tok} row {o}: want {want} \
9361 got {got} (rel {rel:.3e})"
9362 )
9363 .into());
9364 }
9365 }
9366 }
9367 }
9368 }
9369 {
9373 let (out_f, in_f, t) = (33usize, 64usize, 5usize);
9374 let w_elems = out_f * in_f;
9375 let mut w_bytes = Vec::with_capacity(w_elems * 2);
9376 for _ in 0..w_elems {
9377 let h = ((next_u32() % 0x4000) as u16) | (((next_u32() & 1) as u16) << 15);
9378 w_bytes.extend_from_slice(&h.to_le_bytes());
9379 }
9380 let x_host: Vec<f32> = (0..t * in_f)
9381 .map(|_| (next_u32() % 2000) as f32 / 1000.0 - 1.0)
9382 .collect();
9383 let w_dev = e.htod_bytes(&w_bytes)?;
9384 let x_dev = e.htod(&x_host)?;
9385 let mut y_grid = e.uninit(t * out_f)?;
9386 launch_qmatvec_bf16w(
9387 e,
9388 &w_dev,
9389 &x_dev,
9390 &mut y_grid,
9391 in_f,
9392 out_f,
9393 t,
9394 1,
9395 0,
9396 0,
9397 in_f,
9398 0,
9399 )?;
9400 let mut y_mt = e.uninit(t * out_f)?;
9401 launch_qmatvec_bf16w_mt(e, &w_dev, 0, &x_dev, &mut y_mt, in_f, out_f, t)?;
9402 let (a, b) = (e.dtoh(&y_grid)?, e.dtoh(&y_mt)?);
9403 for (i, (&x1, &x2)) in a.iter().zip(&b).enumerate() {
9404 if x1.to_bits() != x2.to_bits() {
9405 return Err(format!(
9406 "bf16-matvec mt oracle: idx {i}: grid {x1} vs mt {x2} NOT bit-identical"
9407 )
9408 .into());
9409 }
9410 }
9411 }
9412 {
9417 let (experts, out_f, in_f, n_sel) = (16usize, 24usize, 32usize, 6usize);
9418 let w_elems = experts * out_f * in_f;
9419 let mut w_bytes = Vec::with_capacity(w_elems * 2);
9420 for _ in 0..w_elems {
9421 let h = ((next_u32() % 0x4000) as u16) | (((next_u32() & 1) as u16) << 15);
9422 w_bytes.extend_from_slice(&h.to_le_bytes());
9423 }
9424 let sel_host: Vec<i32> = vec![7, 0, 15, 7, 3, 9]; let bank = e.htod_bytes(&w_bytes)?;
9426 let sel = e.htod_i32(&sel_host)?;
9427 for (label, x_rows, x_sstride) in [("shared-x", 1usize, 0usize), ("slot-x", n_sel, in_f)] {
9428 let x_host: Vec<f32> = (0..x_rows * in_f)
9429 .map(|_| (next_u32() % 2000) as f32 / 1000.0 - 1.0)
9430 .collect();
9431 let x_dev = e.htod(&x_host)?;
9432 let mut y_sel = e.uninit(n_sel * out_f)?;
9433 launch_qmatvec_bf16w_sel(
9434 e, &bank, &sel, 0, &x_dev, 0, x_sstride, &mut y_sel, n_sel, in_f, out_f,
9435 )?;
9436 let mut y_ref = e.uninit(n_sel * out_f)?;
9437 for (slot, &eid) in sel_host.iter().enumerate() {
9438 launch_qmatvec_bf16w_off_into(
9439 e,
9440 &bank,
9441 eid as usize * out_f,
9442 &x_dev,
9443 slot * x_sstride,
9444 &mut y_ref,
9445 slot * out_f,
9446 in_f,
9447 out_f,
9448 )?;
9449 }
9450 let (a, b) = (e.dtoh(&y_sel)?, e.dtoh(&y_ref)?);
9451 for (i, (&x1, &x2)) in a.iter().zip(&b).enumerate() {
9452 if x1.to_bits() != x2.to_bits() {
9453 return Err(format!(
9454 "bf16-matvec sel oracle ({label}): idx {i}: sel {x1} vs off_into {x2} \
9455 NOT bit-identical"
9456 )
9457 .into());
9458 }
9459 }
9460 }
9461 }
9462 Ok(format!(
9463 "bf16-matvec kernel oracle: worst abs {:.3e} rel {:.3e} over per-batch + shared-x \
9464 modes, batch>1, t>1, signed/denormal bf16; mt weight-shared twin BIT-IDENTICAL \
9465 at t 5; sel grouped twin BIT-IDENTICAL to the off_into chain (shared-x + slot-x, \
9466 duplicate slots)",
9467 worst.0, worst.1
9468 ))
9469}
9470
9471fn dequant_nvfp4_expert_f32(
9481 e: &Engine,
9482 codes: &CudaSlice<u8>,
9483 scales: &CudaSlice<u8>,
9484 macro_scale: f32,
9485 expert: usize,
9486 rows: usize,
9487 cols: usize,
9488) -> Res<CudaSlice<f32>> {
9489 let wbytes = rows * cols / 2;
9490 let sbytes = rows * cols / 16;
9491 let bf = e.alloc_u8(rows * cols * 2)?;
9492 let stream = e.gpu.stream();
9493 let wp = (codes.device_ptr(&stream).0 as usize + expert * wbytes) as *const c_void;
9494 let scp = (scales.device_ptr(&stream).0 as usize + expert * sbytes) as *const c_void;
9495 let dst = bf.device_ptr(&stream).0 as usize as *mut c_void;
9496 let rc = unsafe {
9497 crate::dsv4_ffi::memra_dsv4_nvfp4_deq_bf16(
9498 wp,
9499 scp,
9500 1.0, rows as i32,
9502 cols as i32,
9503 dst,
9504 stream.cu_stream() as *mut c_void,
9505 )
9506 };
9507 if rc != 0 {
9508 return Err(format!("memra_dsv4_nvfp4_deq_bf16 rc={rc}").into());
9509 }
9510 let mut out = e.bf16_to_f32(&bf.slice(0..rows * cols * 2), rows * cols)?;
9511 if macro_scale != 1.0 {
9512 e.scale_inplace(&mut out, macro_scale, rows * cols)?;
9513 }
9514 Ok(out)
9515}
9516
9517fn expect(weights: &ReferenceWeights, id: &TensorId) -> Res<ReferenceTensor> {
9520 weights
9521 .get(id)
9522 .cloned()
9523 .ok_or_else(|| format!("qwen4exp_gpu: missing weight {id:?}").into())
9524}
9525
9526fn family_id(key: String) -> TensorId {
9527 TensorId::Family {
9528 family: "qwen4_exp",
9529 key,
9530 }
9531}
9532
9533fn layer_id(index: u32, tensor: LayerTensor) -> TensorId {
9534 TensorId::Layer { index, tensor }
9535}
9536
9537fn upload(e: &Engine, tensor: &ReferenceTensor) -> Res<CudaSlice<f32>> {
9538 e.htod(&tensor.data)
9539}
9540
9541fn split_columns(data: &[f32], rows: usize, streams: usize, hidden: usize) -> Vec<Vec<f32>> {
9543 let wide = streams * hidden;
9544 (0..streams)
9545 .map(|s| {
9546 let mut out = Vec::with_capacity(rows * hidden);
9547 for row in 0..rows {
9548 out.extend_from_slice(
9549 &data[row * wide + s * hidden..row * wide + (s + 1) * hidden],
9550 );
9551 }
9552 out
9553 })
9554 .collect()
9555}
9556
9557fn split_rows(data: &[f32], streams: usize, hidden: usize, cols: usize) -> Vec<Vec<f32>> {
9559 (0..streams)
9560 .map(|s| data[s * hidden * cols..(s + 1) * hidden * cols].to_vec())
9561 .collect()
9562}
9563
9564fn load_gate(
9565 e: &Engine,
9566 weights: &ReferenceWeights,
9567 prefix: &str,
9568 sublayer: &str,
9569 streams: usize,
9570 hidden: usize,
9571 rank: usize,
9572 with_inject: bool,
9573) -> Res<GateW> {
9574 let wide = streams * hidden;
9575 let norm = expect(
9576 weights,
9577 &family_id(format!("{prefix}{sublayer}hc_norm.weight")),
9578 )?;
9579 let down = expect(
9580 weights,
9581 &family_id(format!("{prefix}{sublayer}input_mix_weight_down.weight")),
9582 )?;
9583 let up = expect(
9584 weights,
9585 &family_id(format!("{prefix}{sublayer}input_mix_weight_up.weight")),
9586 )?;
9587 if norm.data.len() != wide || down.data.len() != rank * wide || up.data.len() != wide * rank {
9588 return Err(format!("qwen4exp_gpu: gate {prefix}{sublayer} shape mismatch").into());
9589 }
9590 let norm_slices = split_rows(&norm.data, streams, hidden, 1);
9591 let down_slices = split_columns(&down.data, rank, streams, hidden);
9592 let up_slices = split_rows(&up.data, streams, hidden, rank);
9593 let stack = |slices: &[Vec<f32>]| -> Vec<f32> {
9596 let mut out = Vec::with_capacity(slices.len() * slices[0].len());
9597 for s in slices {
9598 out.extend_from_slice(s);
9599 }
9600 out
9601 };
9602 let down_b16 = bf16_twin(e, &stack(&down_slices), hidden)?;
9603 let up_b16 = bf16_twin(e, &stack(&up_slices), rank)?;
9604 let (inject, inject_b16) = if with_inject {
9605 let inject = expect(
9606 weights,
9607 &family_id(format!("{prefix}{sublayer}block_inject_weight.weight")),
9608 )?;
9609 if inject.data.len() != streams * wide {
9610 return Err(format!("qwen4exp_gpu: inject {prefix}{sublayer} shape mismatch").into());
9611 }
9612 (
9615 Some(e.htod(&inject.data)?),
9616 bf16_twin(e, &inject.data, hidden)?,
9617 )
9618 } else {
9619 (None, None)
9620 };
9621 Ok(GateW {
9622 norm_stack: e.htod(&stack(&norm_slices))?,
9623 norm: norm_slices
9624 .into_iter()
9625 .map(|v| e.htod(&v))
9626 .collect::<Result<_, _>>()?,
9627 down: down_slices
9628 .into_iter()
9629 .map(|v| e.htod(&v))
9630 .collect::<Result<_, _>>()?,
9631 up: up_slices
9632 .into_iter()
9633 .map(|v| e.htod(&v))
9634 .collect::<Result<_, _>>()?,
9635 inject,
9636 down_b16,
9637 up_b16,
9638 inject_b16,
9639 })
9640}
9641
9642#[derive(Default)]
9646pub struct ExternalParts {
9647 expert_banks: std::collections::BTreeMap<u32, ExpertBank>,
9648 ngram_tables: std::collections::BTreeMap<u32, NgramTable>,
9649}
9650
9651#[allow(clippy::too_many_arguments)]
9655fn build_layer_w(
9656 e: &Engine,
9657 weights: &ReferenceWeights,
9658 layer: &memra_gguf::model_plan::LayerPlan,
9659 prefix: &str,
9660 streams: usize,
9661 hidden: usize,
9662 rank: usize,
9663 bank_override: Option<ExpertBank>,
9664 table_override: Option<NgramTable>,
9665) -> Res<LayerW> {
9666 let ResidualTopology::GatedResidual { .. } = layer.residual else {
9667 return Err(format!("qwen4exp_gpu: layer {} is not gated-residual", layer.index).into());
9668 };
9669 let attn_gate = load_gate(
9670 e,
9671 weights,
9672 prefix,
9673 "attn_hyper_connection.",
9674 streams,
9675 hidden,
9676 rank,
9677 true,
9678 )?;
9679 let mlp_gate = load_gate(
9680 e,
9681 weights,
9682 prefix,
9683 "mlp_hyper_connection.",
9684 streams,
9685 hidden,
9686 rank,
9687 true,
9688 )?;
9689 let mixer = match &layer.attention {
9690 AttentionPlan::Full(attn) => {
9691 let overlay = layer.sparse_overlay.ok_or_else(|| {
9692 format!(
9693 "qwen4exp_gpu: QSA layer {} has no indexer overlay",
9694 layer.index
9695 )
9696 })?;
9697 let yarn = build_yarn(e, &attn.rope, Some(&overlay), layer.index)?;
9700 if attn.key_head_dim != attn.value_head_dim {
9704 return Err(format!(
9705 "qwen4exp_gpu: layer {} key_head_dim {} != value_head_dim {}",
9706 layer.index, attn.key_head_dim, attn.value_head_dim
9707 )
9708 .into());
9709 }
9710 let load_opt_norm = |tensor: LayerTensor| -> Res<Option<CudaSlice<f32>>> {
9711 match weights.get(&layer_id(layer.index, tensor)) {
9712 Some(t) => Ok(Some(e.htod(&t.data)?)),
9713 None if attn.qk_norm == TensorPresence::Required => {
9714 Err(format!("qwen4exp_gpu: layer {} missing qk norm", layer.index).into())
9715 }
9716 None => Ok(None),
9717 }
9718 };
9719 let wq_t = expect(weights, &layer_id(layer.index, LayerTensor::Query))?;
9720 let wk_t = expect(weights, &layer_id(layer.index, LayerTensor::Key))?;
9721 let wv_t = expect(weights, &layer_id(layer.index, LayerTensor::Value))?;
9722 let wo_t = expect(
9723 weights,
9724 &layer_id(layer.index, LayerTensor::AttentionOutput),
9725 )?;
9726 let o_in = (attn.query_heads * attn.key_head_dim) as usize;
9727 MixerW::Qsa(QsaW {
9728 attn: attn.clone(),
9729 overlay,
9730 yarn,
9731 proj_b16: bf16_stack_twin(e, &[&wq_t.data, &wk_t.data, &wv_t.data], hidden)?,
9732 wo_b16: bf16_twin(e, &wo_t.data, o_in)?,
9733 wq: upload(e, &wq_t)?,
9734 wk: upload(e, &wk_t)?,
9735 wv: upload(e, &wv_t)?,
9736 wo: upload(e, &wo_t)?,
9737 q_norm: load_opt_norm(LayerTensor::QueryNorm)?,
9738 k_norm: load_opt_norm(LayerTensor::KeyNorm)?,
9739 idx_proj: upload(
9740 e,
9741 &expect(
9742 weights,
9743 &family_id(format!("{prefix}self_attn.indexer.index_qk_proj.weight")),
9744 )?,
9745 )?,
9746 idx_q_norm: expect(
9747 weights,
9748 &family_id(format!("{prefix}self_attn.indexer.q_layernorm.weight")),
9749 )?
9750 .data,
9751 idx_k_norm: expect(
9752 weights,
9753 &family_id(format!("{prefix}self_attn.indexer.k_layernorm.weight")),
9754 )?
9755 .data,
9756 })
9757 }
9758 AttentionPlan::GatedDeltaNet(gdn) => {
9759 let qkv_t = expect(weights, &layer_id(layer.index, LayerTensor::GdnQkv))?;
9760 let z_t = expect(weights, &layer_id(layer.index, LayerTensor::GdnGate))?;
9761 let beta_t = expect(weights, &layer_id(layer.index, LayerTensor::GdnBeta))?;
9762 let alpha_t = expect(weights, &layer_id(layer.index, LayerTensor::GdnAlpha))?;
9763 let out_t = expect(weights, &layer_id(layer.index, LayerTensor::GdnOutput))?;
9764 let o_in = (gdn.value_heads * gdn.value_head_dim) as usize;
9765 MixerW::Gdn(GdnW {
9766 plan: *gdn,
9767 proj_b16: bf16_stack_twin(
9768 e,
9769 &[&qkv_t.data, &z_t.data, &beta_t.data, &alpha_t.data],
9770 hidden,
9771 )?,
9772 out_b16: bf16_twin(e, &out_t.data, o_in)?,
9773 qkv: upload(e, &qkv_t)?,
9774 z: upload(e, &z_t)?,
9775 beta: upload(e, &beta_t)?,
9776 alpha: upload(e, &alpha_t)?,
9777 conv_w: upload(
9778 e,
9779 &expect(weights, &layer_id(layer.index, LayerTensor::GdnConv1d))?,
9780 )?,
9781 a: upload(
9782 e,
9783 &expect(weights, &layer_id(layer.index, LayerTensor::GdnA))?,
9784 )?,
9785 dt: upload(
9786 e,
9787 &expect(weights, &layer_id(layer.index, LayerTensor::GdnDtBias))?,
9788 )?,
9789 norm: upload(
9790 e,
9791 &expect(weights, &layer_id(layer.index, LayerTensor::GdnNorm))?,
9792 )?,
9793 out: upload(e, &out_t)?,
9794 })
9795 }
9796 other => {
9797 return Err(format!(
9798 "qwen4exp_gpu: unsupported mixer {other:?} at layer {}",
9799 layer.index
9800 )
9801 .into());
9802 }
9803 };
9804 let MlpPlan::Moe(moe_plan) = &layer.mlp else {
9805 return Err(format!("qwen4exp_gpu: layer {} is not MoE", layer.index).into());
9806 };
9807 if !matches!(moe_plan.router, RouterPlan::Softmax) {
9808 return Err("qwen4exp_gpu: only the softmax router arm is implemented".into());
9809 }
9810 let shared = moe_plan
9811 .shared
9812 .as_ref()
9813 .ok_or("qwen4exp_gpu: missing shared expert plan")?;
9814 let bank = match bank_override {
9815 Some(bank) => bank,
9816 None => {
9817 let gate = expect(
9818 weights,
9819 &layer_id(layer.index, LayerTensor::MoeExpertGateBank),
9820 )?;
9821 let up = expect(
9822 weights,
9823 &layer_id(layer.index, LayerTensor::MoeExpertUpBank),
9824 )?;
9825 let down = expect(
9826 weights,
9827 &layer_id(layer.index, LayerTensor::MoeExpertDownBank),
9828 )?;
9829 let experts = moe_plan.expert_count as usize;
9830 let ff = moe_plan.expert_intermediate_size as usize;
9831 if gate.data.len() != experts * ff * hidden
9832 || up.data.len() != experts * ff * hidden
9833 || down.data.len() != experts * hidden * ff
9834 {
9835 return Err(format!(
9836 "qwen4exp_gpu: layer {} expert bank shape mismatch",
9837 layer.index
9838 )
9839 .into());
9840 }
9841 ExpertBank {
9842 gate: BankHalf::F32(e.htod(&gate.data)?),
9843 up: BankHalf::F32(e.htod(&up.data)?),
9844 down: BankHalf::F32(e.htod(&down.data)?),
9845 }
9846 }
9847 };
9848 let sh_gate_t = expect(weights, &layer_id(layer.index, LayerTensor::SharedMlpGate))?;
9849 let sh_up_t = expect(weights, &layer_id(layer.index, LayerTensor::SharedMlpUp))?;
9850 let sh_down_t = expect(weights, &layer_id(layer.index, LayerTensor::SharedMlpDown))?;
9851 let sff = shared.intermediate_size as usize;
9852 let router_t = expect(weights, &layer_id(layer.index, LayerTensor::MoeRouter))?;
9853 let moe = MoeW {
9854 plan: moe_plan.clone(),
9855 router_b16: bf16_twin(e, &router_t.data, hidden)?,
9856 router: upload(e, &router_t)?,
9857 bank,
9858 shared_gu_b16: bf16_stack_twin(e, &[&sh_gate_t.data, &sh_up_t.data], hidden)?,
9859 shared_down_b16: bf16_twin(e, &sh_down_t.data, sff)?,
9860 shared_gate: upload(e, &sh_gate_t)?,
9861 shared_up: upload(e, &sh_up_t)?,
9862 shared_down: upload(e, &sh_down_t)?,
9863 shared_input_gate: if shared.gated {
9864 Some(upload(
9865 e,
9866 &expect(
9867 weights,
9868 &layer_id(layer.index, LayerTensor::SharedMlpInputGate),
9869 )?,
9870 )?)
9871 } else {
9872 None
9873 },
9874 };
9875 let ple = match layer.ple.as_ref() {
9876 None => None,
9877 Some(ple_plan) => {
9878 let embed_dim = ple_plan.embed_dim as usize;
9879 let head_dim = ple_plan.head_embed_dim as usize;
9880 let wide = streams * hidden;
9881 let key_proj = expect(weights, &family_id(format!("{prefix}ple.key_proj.weight")))?;
9882 let conv_w = expect(weights, &family_id(format!("{prefix}ple.conv1d.weight")))?;
9883 if key_proj.data.len() != wide * embed_dim {
9884 return Err("qwen4exp_gpu: ple key_proj shape mismatch".into());
9885 }
9886 let norm_slices = |name: &str| -> Res<Vec<CudaSlice<f32>>> {
9887 let t = expect(weights, &family_id(format!("{prefix}ple.{name}.weight")))?;
9888 split_rows(&t.data, streams, hidden, 1)
9889 .into_iter()
9890 .map(|v| e.htod(&v))
9891 .collect::<Result<_, _>>()
9892 };
9893 let ints = |name: &str| -> Res<Vec<i64>> {
9894 let t = expect(
9895 weights,
9896 &family_id(format!("{prefix}ple.ple_embedding.{name}")),
9897 )?;
9898 t.ints
9899 .clone()
9900 .ok_or_else(|| "qwen4exp_gpu: n-gram buffer must be I64".into())
9901 };
9902 let table = match table_override {
9903 Some(table) => table,
9904 None => {
9905 let t = expect(
9906 weights,
9907 &family_id(format!("{prefix}ple.ple_embedding.ngram_embedding")),
9908 )?;
9909 if t.shape.len() != 2 || t.shape[1] != head_dim {
9910 return Err("qwen4exp_gpu: n-gram table shape mismatch".into());
9911 }
9912 NgramTable::F32(t.data)
9913 }
9914 };
9915 Some(PleW {
9916 plan: *ple_plan,
9917 key_proj: split_rows(&key_proj.data, streams, hidden, embed_dim)
9918 .into_iter()
9919 .map(|v| e.htod(&v))
9920 .collect::<Result<_, _>>()?,
9921 value_proj: upload(
9922 e,
9923 &expect(
9924 weights,
9925 &family_id(format!("{prefix}ple.value_proj.weight")),
9926 )?,
9927 )?,
9928 norm_key: norm_slices("norm_key")?,
9929 norm_query: norm_slices("norm_query")?,
9930 norm_conv: norm_slices("norm_conv")?,
9931 conv_w: split_rows(&conv_w.data, streams, hidden, ple_plan.conv_kernel as usize)
9932 .into_iter()
9933 .map(|v| e.htod(&v))
9934 .collect::<Result<_, _>>()?,
9935 multipliers: ints("layer_multipliers")?,
9936 sizes: ints("ngram_heads_vocab_sizes")?,
9937 offsets: ints("ngram_heads_offsets")?,
9938 table,
9939 })
9940 }
9941 };
9942 Ok(LayerW {
9943 index: layer.index,
9944 eps_attn: layer.pre_attention_norm.epsilon,
9945 eps_mlp: layer.pre_mlp_norm.epsilon,
9946 attn_gate,
9947 mlp_gate,
9948 mixer,
9949 moe,
9950 ple,
9951 })
9952}
9953
9954#[allow(clippy::too_many_arguments)]
9957fn build_mtp_w(
9958 e: &Engine,
9959 weights: &ReferenceWeights,
9960 block: &memra_gguf::model_plan::MtpBlockPlan,
9961 streams: usize,
9962 hidden: usize,
9963 rank: usize,
9964 bank_override: Option<ExpertBank>,
9965) -> Res<MtpW> {
9966 use memra_gguf::tensor_contract::MtpTensor;
9967 if block.input.fusion != memra_gguf::model_plan::MtpFusionPlan::SeparateProjections {
9968 return Err("qwen4exp_gpu: MTP block is not the separate-projections family".into());
9969 }
9970 let wide = streams * hidden;
9971 let depth = block.depth;
9972 let mtp_id = |tensor: MtpTensor| TensorId::Mtp { depth, tensor };
9973 let pre_e = expect(weights, &mtp_id(MtpTensor::EmbeddingNorm))?;
9974 let pre_h = expect(weights, &mtp_id(MtpTensor::HiddenNorm))?;
9975 let fc_e = expect(weights, &mtp_id(MtpTensor::EmbeddingProjection))?;
9976 let fc_h = expect(weights, &mtp_id(MtpTensor::HiddenProjection))?;
9977 if pre_e.data.len() != hidden
9978 || pre_h.data.len() != wide
9979 || fc_e.data.len() != hidden * hidden
9980 || fc_h.data.len() != hidden * hidden
9981 {
9982 return Err("qwen4exp_gpu: MTP fusion tensor shape mismatch".into());
9983 }
9984 let prefix = format!("mtp.layers.{depth}.");
9985 let layer = build_layer_w(
9986 e,
9987 weights,
9988 &block.layer,
9989 &prefix,
9990 streams,
9991 hidden,
9992 rank,
9993 bank_override,
9994 None,
9995 )?;
9996 let mixer = load_gate(
9997 e,
9998 weights,
9999 "mtp.hyper_connection_mixer.",
10000 "",
10001 streams,
10002 hidden,
10003 rank,
10004 false,
10005 )?;
10006 Ok(MtpW {
10007 eps_embed: block.input.embedding_norm.epsilon,
10008 eps_hidden: block.input.hidden_norm.epsilon,
10009 fc_embed_b16: bf16_twin(e, &fc_e.data, hidden)?,
10010 fc_hidden_b16: bf16_twin(e, &fc_h.data, hidden)?,
10011 pre_norm_embed: upload(e, &pre_e)?,
10012 pre_norm_hidden: upload(e, &pre_h)?,
10013 fc_embed: upload(e, &fc_e)?,
10014 fc_hidden: upload(e, &fc_h)?,
10015 layer,
10016 mixer,
10017 })
10018}
10019
10020impl Qwen4ExpGpu {
10021 pub fn from_reference_weights(
10025 e: &Engine,
10026 plan: &ModelPlan,
10027 weights: &ReferenceWeights,
10028 ) -> Res<Self> {
10029 Self::from_reference_weights_with(e, None, plan, weights, ExternalParts::default())
10030 }
10031
10032 fn from_reference_weights_with(
10033 e: &Engine,
10034 draft_e: Option<&Engine>,
10037 plan: &ModelPlan,
10038 weights: &ReferenceWeights,
10039 mut parts: ExternalParts,
10040 ) -> Res<Self> {
10041 let hidden = plan.hidden_size as usize;
10042 let vocab = plan.vocab_size as usize;
10043 let Some(mixer_plan) = plan.exit_mixer else {
10044 return Err("qwen4exp_gpu requires the gated-residual exit mixer".into());
10045 };
10046 let streams = mixer_plan.streams as usize;
10047 if streams > PLANE_SLOTS.len() {
10048 return Err("qwen4exp_gpu: hc_count exceeds the step-workspace slot table".into());
10049 }
10050 let rank = mixer_plan.bottleneck_rank as usize;
10051 if !plan.logits.is_empty() {
10052 return Err("qwen4exp_gpu: logits transforms are not part of this family".into());
10053 }
10054
10055 let embed = expect(weights, &TensorId::TokenEmbedding)?;
10056 if embed.data.len() != vocab * hidden {
10057 return Err("qwen4exp_gpu: embedding shape mismatch".into());
10058 }
10059 let (output, output_b16) = match weights.get(&TensorId::OutputProjection) {
10060 Some(tensor) => (e.htod(&tensor.data)?, bf16_twin(e, &tensor.data, hidden)?),
10061 None => (e.htod(&embed.data)?, bf16_twin(e, &embed.data, hidden)?),
10062 };
10063
10064 let mut layers = Vec::with_capacity(plan.layers.len());
10065 for layer in &plan.layers {
10066 let prefix = format!("trunk.layers.{}.", layer.index);
10067 layers.push(build_layer_w(
10068 e,
10069 weights,
10070 layer,
10071 &prefix,
10072 streams,
10073 hidden,
10074 rank,
10075 parts.expert_banks.remove(&layer.index),
10076 parts.ngram_tables.remove(&layer.index),
10077 )?);
10078 }
10079 let mtp = match plan.mtp_blocks.first() {
10083 Some(block)
10084 if weights
10085 .get(&TensorId::Mtp {
10086 depth: block.depth,
10087 tensor: memra_gguf::tensor_contract::MtpTensor::EmbeddingProjection,
10088 })
10089 .is_some() =>
10090 {
10091 Some(build_mtp_w(
10092 draft_e.unwrap_or(e),
10093 weights,
10094 block,
10095 streams,
10096 hidden,
10097 rank,
10098 parts.expert_banks.remove(&block.layer.index),
10099 )?)
10100 }
10101 _ => None,
10102 };
10103 let mtp_dev1 = match (draft_e, mtp.as_ref()) {
10106 (Some(de), Some(_)) => {
10107 let head_data: &[f32] = match weights.get(&TensorId::OutputProjection) {
10108 Some(tensor) => &tensor.data,
10109 None => &embed.data,
10110 };
10111 Some(MtpDev1 {
10112 dev: de.ctx().ordinal(),
10113 output: de.htod(head_data)?,
10114 output_b16: bf16_twin(de, head_data, hidden)?,
10115 })
10116 }
10117 (Some(_), None) => {
10118 return Err(
10119 "qwen4exp_gpu: a draft engine was given but no mtp.* rows were \
10120 materialized (LoadOptions::load_mtp)"
10121 .into(),
10122 );
10123 }
10124 _ => None,
10125 };
10126 let exit_mixer = load_gate(
10127 e,
10128 weights,
10129 "trunk.hyper_connection_mixer.",
10130 "",
10131 streams,
10132 hidden,
10133 rank,
10134 false,
10135 )?;
10136 Ok(Self {
10137 plan: plan.clone(),
10138 hidden,
10139 streams,
10140 vocab,
10141 embed_host: embed.data,
10142 output,
10143 output_b16,
10144 layers,
10145 exit_mixer,
10146 exit_eps: plan.output_norm.epsilon,
10147 mtp,
10148 mtp_dev1,
10149 draft_trim: None,
10150 draft_trim_parked: None,
10151 chain_embed: None,
10152 })
10153 }
10154
10155 pub fn build_draft_trim(&mut self, e: &Engine, ids: &[u32]) -> Res<()> {
10163 self.check_draft_engine(e)?;
10166 let n = ids.len();
10167 if n == 0 || n > self.vocab {
10168 return Err(format!("qwen4exp_gpu: draft trim wants 1..={} ids", self.vocab).into());
10169 }
10170 let mut seen = vec![false; self.vocab];
10171 for &id in ids {
10172 let id = id as usize;
10173 if id >= self.vocab {
10174 return Err(format!("qwen4exp_gpu: draft trim id {id} out of vocab").into());
10175 }
10176 if std::mem::replace(&mut seen[id], true) {
10177 return Err(format!("qwen4exp_gpu: draft trim id {id} repeats").into());
10178 }
10179 }
10180 let hidden = self.hidden;
10181 let (src_f32, src_b16) = match self.mtp_dev1.as_ref() {
10182 Some(d) => (&d.output, d.output_b16.as_ref()),
10183 None => (&self.output, self.output_b16.as_ref()),
10184 };
10185 let (head_b16, head) = match src_b16 {
10189 Some(full) => {
10190 let mut trim = e.alloc_u8_uninit(n * hidden * 2)?;
10191 for (row, &id) in ids.iter().enumerate() {
10192 e.copy_u8_range_into(
10193 &mut trim,
10194 row * hidden * 2,
10195 full,
10196 id as usize * hidden * 2,
10197 hidden * 2,
10198 )?;
10199 }
10200 (Some(trim), None)
10201 }
10202 None => {
10203 let mut head = e.uninit(n * hidden)?;
10204 for (row, &id) in ids.iter().enumerate() {
10205 e.copy_range_into(
10206 &mut head,
10207 row * hidden,
10208 src_f32,
10209 id as usize * hidden,
10210 hidden,
10211 )?;
10212 }
10213 (None, Some(head))
10214 }
10215 };
10216 self.draft_trim = Some(DraftTrim {
10217 n,
10218 d2t: ids.to_vec(),
10219 head,
10220 head_b16,
10221 });
10222 self.draft_trim_parked = None;
10223 Ok(())
10224 }
10225
10226 pub fn set_draft_trim(&mut self, on: bool) {
10229 if on {
10230 if let Some(t) = self.draft_trim_parked.take() {
10231 self.draft_trim = Some(t);
10232 }
10233 } else if let Some(t) = self.draft_trim.take() {
10234 self.draft_trim_parked = Some(t);
10235 }
10236 }
10237
10238 pub fn clear_draft_trim(&mut self) {
10240 self.draft_trim = None;
10241 self.draft_trim_parked = None;
10242 }
10243
10244 pub fn arm_spec_devchain(&mut self, de: &Engine) -> Res<()> {
10251 self.check_draft_engine(de)?;
10252 let hidden = self.hidden;
10253 let (rows, for_trim) = match self.draft_trim.as_ref() {
10254 Some(tr) => (tr.n, true),
10255 None => (self.vocab, false),
10256 };
10257 let src_row = |r: usize| -> &[f32] {
10258 let id = match self.draft_trim.as_ref() {
10259 Some(tr) => tr.d2t[r] as usize,
10260 None => r,
10261 };
10262 &self.embed_host[id * hidden..(id + 1) * hidden]
10263 };
10264 let clean = (0..rows).all(|r| src_row(r).iter().all(|x| x.to_bits() & 0xFFFF == 0));
10267 let (bytes, qt, row_bytes) = if clean {
10268 let mut b = vec![0u8; rows * hidden * 2];
10269 for r in 0..rows {
10270 for (j, &x) in src_row(r).iter().enumerate() {
10271 let h = (x.to_bits() >> 16) as u16;
10272 b[(r * hidden + j) * 2..(r * hidden + j) * 2 + 2]
10273 .copy_from_slice(&h.to_le_bytes());
10274 }
10275 }
10276 (b, crate::QT_BF16, hidden * 2)
10277 } else {
10278 let mut b = vec![0u8; rows * hidden * 4];
10279 for r in 0..rows {
10280 for (j, &x) in src_row(r).iter().enumerate() {
10281 b[(r * hidden + j) * 4..(r * hidden + j) * 4 + 4]
10282 .copy_from_slice(&x.to_le_bytes());
10283 }
10284 }
10285 (b, crate::QT_F32, hidden * 4)
10286 };
10287 let table = de.upload_u8(&bytes)?;
10288 println!(
10289 "[qwen4exp-spec] deferred-chain embed table armed: {} rows x {hidden} ({}, {:.1} MiB, dev {}{})",
10290 rows,
10291 if clean {
10292 "bf16 bit-clean"
10293 } else {
10294 "f32 fallback"
10295 },
10296 (rows * row_bytes) as f64 / (1024.0 * 1024.0),
10297 de.ctx().ordinal(),
10298 if for_trim { ", trim-rank order" } else { "" },
10299 );
10300 self.chain_embed = Some(ChainEmbed {
10301 table,
10302 qt,
10303 row_bytes,
10304 rows,
10305 for_trim,
10306 dev: de.ctx().ordinal(),
10307 });
10308 Ok(())
10309 }
10310
10311 pub fn clear_spec_devchain(&mut self) {
10313 self.chain_embed = None;
10314 }
10315
10316 pub fn draft_logits_width(&self) -> usize {
10319 match self.draft_trim.as_ref() {
10320 Some(t) => t.n,
10321 None => self.vocab,
10322 }
10323 }
10324
10325 fn draft_token(&self, row: u32) -> Res<u32> {
10328 match self.draft_trim.as_ref() {
10329 Some(t) => t
10330 .d2t
10331 .get(row as usize)
10332 .copied()
10333 .ok_or_else(|| format!("qwen4exp_gpu: draft row {row} outside the trim").into()),
10334 None => Ok(row),
10335 }
10336 }
10337
10338 pub fn trunk_f32_diet(&mut self, e: &Engine) -> Res<usize> {
10347 if !trunk_bf16_on() || !hc_fused_gate_on() {
10348 return Err(
10349 "qwen4exp_gpu: trunk_f32_diet requires the trunk-bf16 + fused-gate seams ON \
10350 (the bf16 paths must be the ones serving)"
10351 .into(),
10352 );
10353 }
10354 let mut freed = 0usize;
10355 fn stub(e: &Engine, s: &mut CudaSlice<f32>, freed: &mut usize) -> Res<()> {
10356 if s.len() > 1 {
10357 *freed += s.len() * 4;
10358 *s = e.zeros(1)?;
10359 }
10360 Ok(())
10361 }
10362 fn diet_gate(e: &Engine, g: &mut GateW, freed: &mut usize) -> Res<()> {
10363 if g.down_b16.is_none()
10364 || g.up_b16.is_none()
10365 || (g.inject.is_some() && g.inject_b16.is_none())
10366 {
10367 return Ok(()); }
10369 for s in g.down.iter_mut() {
10370 stub(e, s, freed)?;
10371 }
10372 for s in g.up.iter_mut() {
10373 stub(e, s, freed)?;
10374 }
10375 if let Some(inj) = g.inject.as_mut() {
10376 stub(e, inj, freed)?;
10377 }
10378 Ok(())
10379 }
10380 for layer in self.layers.iter_mut() {
10381 diet_gate(e, &mut layer.attn_gate, &mut freed)?;
10382 diet_gate(e, &mut layer.mlp_gate, &mut freed)?;
10383 match &mut layer.mixer {
10384 MixerW::Qsa(q) => {
10385 if q.proj_b16.is_some() {
10386 stub(e, &mut q.wq, &mut freed)?;
10387 stub(e, &mut q.wk, &mut freed)?;
10388 stub(e, &mut q.wv, &mut freed)?;
10389 }
10390 if q.wo_b16.is_some() {
10391 stub(e, &mut q.wo, &mut freed)?;
10392 }
10393 }
10394 MixerW::Gdn(g) => {
10395 if g.proj_b16.is_some() {
10396 stub(e, &mut g.qkv, &mut freed)?;
10397 stub(e, &mut g.z, &mut freed)?;
10398 stub(e, &mut g.beta, &mut freed)?;
10399 stub(e, &mut g.alpha, &mut freed)?;
10400 }
10401 if g.out_b16.is_some() {
10402 stub(e, &mut g.out, &mut freed)?;
10403 }
10404 }
10405 }
10406 let moe = &mut layer.moe;
10407 if moe.router_b16.is_some() {
10408 stub(e, &mut moe.router, &mut freed)?;
10409 }
10410 if moe.shared_gu_b16.is_some() {
10411 stub(e, &mut moe.shared_gate, &mut freed)?;
10412 stub(e, &mut moe.shared_up, &mut freed)?;
10413 }
10414 if moe.shared_down_b16.is_some() {
10415 stub(e, &mut moe.shared_down, &mut freed)?;
10416 }
10417 }
10418 diet_gate(e, &mut self.exit_mixer, &mut freed)?;
10419 if self.output_b16.is_some() {
10420 stub(e, &mut self.output, &mut freed)?;
10421 }
10422 Ok(freed)
10423 }
10424
10425 pub fn alloc_state(&self, e: &Engine, capacity: usize) -> Res<Qwen4ExpState> {
10426 self.alloc_state_reserve(e, capacity, capacity, None)
10427 }
10428
10429 pub fn alloc_state_reserve(
10434 &self,
10435 e: &Engine,
10436 capacity: usize,
10437 reserve: usize,
10438 kv_engine: Option<&Engine>,
10439 ) -> Res<Qwen4ExpState> {
10440 let kv_e = kv_engine.unwrap_or(e);
10441 if kv_e.ctx().ordinal() != e.ctx().ordinal() {
10447 let limit = peer_kv_max_cap();
10448 if capacity > limit {
10449 return Err(format!(
10450 "qwen4exp_gpu: peer-resident QSA KV refused — capacity {capacity} rows on \
10451 device {} while the attention runs on device {} (ceiling {limit} rows, \
10452 MEMRA_Q4E_PEER_KV_MAX_CAP). The block-list form is the only read path for a \
10453 quantized cache and it is a scatter reader (q4e_sdpa_blocklist_q8q5 phase 1 \
10454 is thread-per-position: 32 lanes on 32 rows, 32 sectors per load \
10455 instruction). Peer memory is not cached in the reading card's L2, so at this \
10456 depth ONE 2,048-token prefill chunk asks ~523 GB across the link and the run \
10457 never finishes — it does not deadlock, it just never arrives. Keep the QSA KV \
10458 on the compute card: it is 10,368 B/row across the 12 QSA layers (2.7 GiB at \
10459 262,144), while the allocation that forces a second card is the MTP draft \
10460 state (~17.6 GiB), which --mtp-dev1 / load_from_dir_dev1 already places \
10461 there.",
10462 kv_e.ctx().ordinal(),
10463 e.ctx().ordinal(),
10464 )
10465 .into());
10466 }
10467 }
10468 let mut layers = Vec::with_capacity(self.layers.len());
10469 for layer in &self.layers {
10470 let mixer = match &layer.mixer {
10471 MixerW::Qsa(qsa) => {
10472 let kv_width = qsa.attn.kv_heads as usize * qsa.attn.key_head_dim as usize;
10473 let v_width = qsa.attn.kv_heads as usize * qsa.attn.value_head_dim as usize;
10474 let kv = if kv_quant_on() {
10477 QsaKvStore::Q8Q5 {
10478 k: kv_e.alloc_u8(capacity * q8_row_bytes(kv_width))?,
10479 v: kv_e.alloc_u8(capacity * q5_row_bytes(v_width))?,
10480 }
10481 } else {
10482 QsaKvStore::F32 {
10483 k: kv_e.zeros(capacity * kv_width)?,
10484 v: kv_e.zeros(capacity * v_width)?,
10485 }
10486 };
10487 MixerState::Qsa {
10488 kv,
10489 raw_keys: IdxRawCache::new(idxq_mode()),
10490 pooled_keys: Vec::new(),
10491 pooled_dev: None,
10492 pooled_dev_rows: 0,
10493 raw_dev: None,
10494 raw_dev_rows: 0,
10495 idx_audit: (idxq_mode() != IdxQMode::F32 && idxq_audit_on()).then(|| {
10496 Box::new(IdxAudit {
10497 raw_f32: IdxRawCache::F32(Vec::new()),
10498 pooled_f32: Vec::new(),
10499 })
10500 }),
10501 }
10502 }
10503 MixerW::Gdn(gdn) => {
10504 let p = &gdn.plan;
10505 let conv_dim = 2 * (p.key_heads * p.key_head_dim) as usize
10506 + (p.value_heads * p.value_head_dim) as usize;
10507 let pad = p.conv_kernel as usize - 1;
10508 MixerState::Gdn {
10509 conv: e.zeros(pad * conv_dim)?,
10510 state: e
10511 .zeros((p.value_heads * p.value_head_dim * p.key_head_dim) as usize)?,
10512 }
10513 }
10514 };
10515 let ple = match layer.ple.as_ref() {
10516 None => None,
10517 Some(ple) => {
10518 let pad = (ple.plan.conv_kernel as usize - 1) * ple.plan.max_ngram as usize;
10519 let mut conv_hist = Vec::with_capacity(self.streams);
10520 for _ in 0..self.streams {
10521 conv_hist.push(e.zeros(pad * self.hidden)?);
10522 }
10523 Some(PleState {
10524 conv_hist,
10525 ngram_ids: Vec::new(),
10526 ngram_history: Vec::new(),
10527 ngram_last_eos: -1,
10528 })
10529 }
10530 };
10531 layers.push(LayerState { mixer, ple });
10532 }
10533 Ok(Qwen4ExpState {
10534 pos: 0,
10535 capacity,
10536 reserve,
10537 tokens: Vec::new(),
10538 layers,
10539 ws: StepPool::default(),
10540 graphs: StepGraphs::default(),
10541 tp2: None,
10542 verify: None,
10543 })
10544 }
10545
10546 pub fn prefill(&self, e: &Engine, ids: &[u32], state: &mut Qwen4ExpState) -> Res<Vec<f32>> {
10548 self.forward(e, ids, state, None)
10549 }
10550
10551 pub fn prefill_extend(
10558 &self,
10559 e: &Engine,
10560 ids: &[u32],
10561 state: &mut Qwen4ExpState,
10562 chunk: usize,
10563 ) -> Res<Vec<f32>> {
10564 if ids.is_empty() || chunk == 0 {
10565 return Err("qwen4exp_gpu: prefill_extend needs ids and a chunk size".into());
10566 }
10567 let mut last = Vec::new();
10568 for piece in ids.chunks(chunk) {
10569 let is_last =
10570 piece.as_ptr() as usize + piece.len() * 4 == ids.as_ptr() as usize + ids.len() * 4;
10571 let head = if is_last {
10572 HeadMode::LastRow
10573 } else {
10574 HeadMode::Skip
10575 };
10576 last = self.forward_with(e, piece, state, None, head)?;
10577 }
10578 Ok(last)
10579 }
10580
10581 pub fn decode_step(&self, e: &Engine, token: u32, state: &mut Qwen4ExpState) -> Res<Vec<f32>> {
10583 self.forward(e, &[token], state, None)
10584 }
10585
10586 pub fn prefill_captured(
10589 &self,
10590 e: &Engine,
10591 ids: &[u32],
10592 state: &mut Qwen4ExpState,
10593 ) -> Res<(Vec<f32>, PrefillCapture)> {
10594 let mut capture = PrefillCapture {
10595 layer_wide: Vec::with_capacity(self.layers.len()),
10596 exit_mixed: Vec::new(),
10597 };
10598 let logits = self.forward(e, ids, state, Some(&mut capture))?;
10599 Ok((logits, capture))
10600 }
10601
10602 fn planes_to_wide(&self, e: &Engine, planes: &[CudaSlice<f32>], t: usize) -> Res<Vec<f32>> {
10605 let hidden = self.hidden;
10606 let wide = self.streams * hidden;
10607 let mut out = vec![0.0f32; t * wide];
10608 for (s, plane) in planes.iter().enumerate() {
10609 let host = e.dtoh_view(&plane.slice(0..t * hidden))?;
10611 for row in 0..t {
10612 out[row * wide + s * hidden..row * wide + (s + 1) * hidden]
10613 .copy_from_slice(&host[row * hidden..(row + 1) * hidden]);
10614 }
10615 }
10616 Ok(out)
10617 }
10618
10619 fn forward(
10620 &self,
10621 e: &Engine,
10622 ids: &[u32],
10623 state: &mut Qwen4ExpState,
10624 capture: Option<&mut PrefillCapture>,
10625 ) -> Res<Vec<f32>> {
10626 self.forward_with(e, ids, state, capture, HeadMode::All)
10627 }
10628
10629 fn forward_with(
10630 &self,
10631 e: &Engine,
10632 ids: &[u32],
10633 state: &mut Qwen4ExpState,
10634 mut capture: Option<&mut PrefillCapture>,
10635 head: HeadMode,
10636 ) -> Res<Vec<f32>> {
10637 let t = ids.len();
10638 let hidden = self.hidden;
10639 if t == 0 {
10640 return Err("qwen4exp_gpu: empty input".into());
10641 }
10642 if head != HeadMode::All {
10643 if capture.is_some() {
10649 return Err("qwen4exp_gpu: prefill capture wants every logits row".into());
10650 }
10651 if let Some(v) = state.verify.as_ref()
10652 && (t == 1 || t <= v.k_cap)
10653 {
10654 return Err(
10655 "qwen4exp_gpu: head-skipping forward on a verify-exact chunk shape".into(),
10656 );
10657 }
10658 }
10659 if state.pos + t > state.capacity {
10660 return Err("qwen4exp_gpu: state capacity exceeded".into());
10661 }
10662 if state.tp2.is_some() {
10663 return Err(
10664 "qwen4exp_gpu: state already decoded in TP2 mode; single-card forward \
10665 requires a fresh state (the half-state migration is one-way)"
10666 .into(),
10667 );
10668 }
10669 let base_pos = state.pos;
10670 state.tokens.extend_from_slice(ids);
10671 if t > 1 {
10674 state.graphs = StepGraphs::default();
10675 }
10676 let graphs_mode = t == 1
10683 && decode_graphs_on()
10684 && step_ws_on()
10685 && hc_fused_gate_on()
10686 && !prof::on()
10687 && capture.is_none()
10688 && state.verify.is_none();
10689 let tokens = &state.tokens;
10690 let ws = &mut state.ws;
10691 let verify = state.verify.as_mut();
10695 let (exact, vfused, stash_gdn, stash_ple, stash_wide, argmax_sink, last_row_only) =
10696 match verify {
10697 Some(v) => {
10698 let vchunk = base_pos > 0 && t > 1 && t <= v.k_cap;
10711 let vfused = vchunk && verify_fused_on();
10712 let exact = vchunk && !vfused;
10713 let amx_t1 = t == 1 && v.want_argmax_t1;
10717 if exact {
10718 v.chunk = Some((base_pos, t));
10719 v.argmax.clear();
10720 } else if (vfused || amx_t1) && v.want_argmax {
10721 v.argmax.clear();
10722 }
10723 if vfused {
10724 v.fused_chunk = Some((base_pos, t));
10728 }
10729 (
10730 exact,
10731 vfused,
10732 Some(&mut v.gdn),
10733 Some(&mut v.ple),
10734 Some((&mut v.wide, v.ring_rows)),
10735 if (exact || vfused || amx_t1) && v.want_argmax {
10739 Some((&mut v.argmax, &mut v.toks))
10740 } else {
10741 None
10742 },
10743 v.last_row_only && t > 1 && !exact && !vfused,
10744 )
10745 }
10746 None => (false, false, None, None, None, None, false),
10747 };
10748 let mut stash_gdn = stash_gdn;
10749 let mut stash_ple = stash_ple;
10750 let cap = state.reserve.max(t);
10756
10757 let mut planes = prof_section(e, "entry.embed", || {
10760 let mut embedded = vec![0.0f32; t * hidden];
10761 for (row, &token) in ids.iter().enumerate() {
10762 let token = token as usize;
10763 if token >= self.vocab {
10764 return Err(format!("qwen4exp_gpu: token {token} out of range").into());
10765 }
10766 embedded[row * hidden..(row + 1) * hidden]
10767 .copy_from_slice(&self.embed_host[token * hidden..(token + 1) * hidden]);
10768 }
10769 let embedded_dev = ws.take_f32_h2d(e, "entry.embed", &embedded, cap * hidden)?;
10770 let mut planes: Vec<CudaSlice<f32>> = Vec::with_capacity(self.streams);
10771 for s in 0..self.streams {
10772 let mut plane = ws.take_f32(e, PLANE_SLOTS[s], t * hidden, cap * hidden)?;
10773 e.copy_into(&mut plane, 0, &embedded_dev, t * hidden)?;
10774 planes.push(plane);
10775 }
10776 ws.put_f32("entry.embed", embedded_dev);
10777 Ok(planes)
10778 })?;
10779
10780 let ptr_vals: Vec<u64> = {
10784 let stream = e.gpu.stream();
10785 planes.iter().map(|p| p.device_ptr(&stream).0).collect()
10786 };
10787 let ptrs = ws.take_u64_h2d(e, "hc.ptrs", &ptr_vals, 0)?;
10788
10789 if graphs_mode {
10790 if state.graphs.warm {
10791 return self.forward_graphs_tail(e, state, planes, ptrs, base_pos);
10792 }
10793 state.graphs.warm = true;
10796 }
10797
10798 for (li, (layer, lstate)) in self.layers.iter().zip(state.layers.iter_mut()).enumerate() {
10799 if let (Some(ple), Some(ple_state)) = (layer.ple.as_ref(), lstate.ple.as_mut()) {
10800 let ps = if exact {
10801 stash_ple
10802 .as_mut()
10803 .and_then(|v| v.get_mut(li))
10804 .and_then(|s| s.as_mut())
10805 } else {
10806 None
10807 };
10808 self.ple_block(
10809 e,
10810 layer,
10811 ple,
10812 &ple.table,
10813 ple_state,
10814 &mut planes,
10815 tokens,
10816 t,
10817 exact,
10818 ps,
10819 )?;
10820 }
10821 let (mixed, inject) = prof_section(e, "hyper.read", || {
10822 self.gate_read(
10823 e,
10824 ws,
10825 &ptrs,
10826 &layer.attn_gate,
10827 &planes,
10828 t,
10829 layer.eps_attn,
10830 exact,
10831 )
10832 })?;
10833 let block_out = match &layer.mixer {
10834 MixerW::Qsa(qsa) => self.qsa_forward(
10835 e,
10836 ws,
10837 layer,
10838 qsa,
10839 &mixed,
10840 &mut lstate.mixer,
10841 base_pos,
10842 t,
10843 0,
10844 exact,
10845 )?,
10846 MixerW::Gdn(gdn) => {
10847 let gs = if exact {
10848 stash_gdn
10849 .as_mut()
10850 .and_then(|v| v.get_mut(li))
10851 .and_then(|s| s.as_mut())
10852 } else {
10853 None
10854 };
10855 self.gdn_forward(e, ws, layer, gdn, &mixed, &mut lstate.mixer, t, gs)?
10856 }
10857 };
10858 ws.put_f32("hc.mixed", mixed);
10859 prof_section(e, "hyper.write", || {
10860 self.gate_write(e, &mut planes, &ptrs, &block_out, &inject, t)
10861 })?;
10862 ws.put_f32("mixer.out", block_out);
10863 put_inject(ws, inject);
10864 let (mixed, inject) = prof_section(e, "hyper.read", || {
10865 self.gate_read(
10866 e,
10867 ws,
10868 &ptrs,
10869 &layer.mlp_gate,
10870 &planes,
10871 t,
10872 layer.eps_mlp,
10873 exact,
10874 )
10875 })?;
10876 let grouped = exact || vfused || head != HeadMode::All || prefill_grouped_all_on();
10894 let mlp = self.moe_forward(e, ws, &layer.moe, &mixed, t, grouped, layer.index)?;
10895 ws.put_f32("hc.mixed", mixed);
10896 prof_section(e, "hyper.write", || {
10897 self.gate_write(e, &mut planes, &ptrs, &mlp, &inject, t)
10898 })?;
10899 ws.put_f32("moe.out", mlp);
10900 put_inject(ws, inject);
10901 if let Some(capture) = capture.as_deref_mut() {
10902 capture.layer_wide.push(self.planes_to_wide(e, &planes, t)?);
10903 }
10904 }
10905
10906 if let Some((wide_buf, ring_rows)) = stash_wide {
10910 let wide = self.streams * hidden;
10911 for (s, plane) in planes.iter().enumerate() {
10912 for tok in 0..t {
10913 e.copy_range_into(
10914 wide_buf,
10915 ((base_pos + tok) % ring_rows) * wide + s * hidden,
10916 plane,
10917 tok * hidden,
10918 hidden,
10919 )?;
10920 }
10921 }
10922 }
10923
10924 if head == HeadMode::Skip {
10927 ws.put_u64("hc.ptrs", ptrs);
10928 state.pos += t;
10929 for (s, plane) in planes.into_iter().enumerate() {
10930 ws.put_f32(PLANE_SLOTS[s], plane);
10931 }
10932 return Ok(Vec::new());
10933 }
10934
10935 let x = prof_section(e, "exit.mixer", || {
10937 Ok(self
10938 .gate_read_inner(
10939 e,
10940 ws,
10941 &ptrs,
10942 &self.exit_mixer,
10943 &planes,
10944 t,
10945 self.exit_eps,
10946 false,
10947 exact,
10948 )?
10949 .0)
10950 })?;
10951 ws.put_u64("hc.ptrs", ptrs);
10952 if let Some(capture) = capture.as_deref_mut() {
10953 capture.exit_mixed = e.dtoh(&x)?;
10954 }
10955 let head_rows = if head == HeadMode::LastRow { 1 } else { t };
10958 let logits = prof_section(e, "lm_head", || {
10959 let mut logits =
10960 ws.take_f32(e, "logits", head_rows * self.vocab, head_rows * self.vocab)?;
10961 let x_head = if head == HeadMode::LastRow {
10962 let mut last = ws.take_f32(e, "exit.last", hidden, hidden)?;
10963 e.copy_range_into(&mut last, 0, &x, (t - 1) * hidden, hidden)?;
10964 last
10965 } else {
10966 x
10967 };
10968 linear_trunk_into(
10969 e,
10970 &self.output,
10971 &self.output_b16,
10972 &x_head,
10973 &mut logits,
10974 head_rows,
10975 hidden,
10976 self.vocab,
10977 )?;
10978 ws.put_f32(
10979 if head == HeadMode::LastRow {
10980 "exit.last"
10981 } else {
10982 "hc.mixed"
10983 },
10984 x_head,
10985 );
10986 Ok(logits)
10987 })?;
10988 state.pos += t;
10989 if head == HeadMode::LastRow {
10990 let out = prof_section(e, "logits.dtoh", || {
10991 Ok(e.dtoh_view(&logits.slice(0..self.vocab))?)
10992 })?;
10993 ws.put_f32("logits", logits);
10994 for (s, plane) in planes.into_iter().enumerate() {
10995 ws.put_f32(PLANE_SLOTS[s], plane);
10996 }
10997 return Ok(out);
10998 }
10999 let out = if let Some((argmax_rows, toks)) = argmax_sink {
11002 prof_section(e, "logits.argmax", || {
11003 for row in 0..t {
11004 e.argmax_token_device_col(&logits, row, self.vocab, toks, row)?;
11005 }
11006 let host = e.gpu.stream().clone_dtoh(&toks.slice(0..t))?;
11007 argmax_rows.extend_from_slice(&host);
11008 Ok(Vec::new())
11009 })?
11010 } else if last_row_only {
11011 prof_section(e, "logits.dtoh", || {
11014 Ok(e.dtoh_view(&logits.slice((t - 1) * self.vocab..t * self.vocab))?)
11015 })?
11016 } else {
11017 prof_section(e, "logits.dtoh", || {
11018 Ok(e.dtoh_view(&logits.slice(0..t * self.vocab))?)
11019 })?
11020 };
11021 ws.put_f32("logits", logits);
11022 for (s, plane) in planes.into_iter().enumerate() {
11023 ws.put_f32(PLANE_SLOTS[s], plane);
11024 }
11025 Ok(out)
11026 }
11027
11028 #[allow(clippy::too_many_arguments)]
11032 fn gate_read(
11033 &self,
11034 e: &Engine,
11035 ws: &mut StepPool,
11036 ptrs: &CudaSlice<u64>,
11037 gate: &GateW,
11038 planes: &[CudaSlice<f32>],
11039 t: usize,
11040 eps: f32,
11041 exact: bool,
11042 ) -> Res<(CudaSlice<f32>, InjectOut)> {
11043 self.gate_read_inner(e, ws, ptrs, gate, planes, t, eps, true, exact)
11044 }
11045
11046 #[allow(clippy::too_many_arguments)]
11047 fn gate_read_inner(
11048 &self,
11049 e: &Engine,
11050 ws: &mut StepPool,
11051 ptrs: &CudaSlice<u64>,
11052 gate: &GateW,
11053 planes: &[CudaSlice<f32>],
11054 t: usize,
11055 eps: f32,
11056 with_inject: bool,
11057 exact: bool,
11062 ) -> Res<(CudaSlice<f32>, InjectOut)> {
11063 if !hc_fused_gate_on() {
11064 return self.gate_read_legacy(e, ws, gate, planes, t, eps, with_inject);
11065 }
11066 let hidden = self.hidden;
11067 let streams = self.streams;
11068 let rank = gate_rank(gate, hidden, streams)?;
11069 let micro_norm = micro_norm_on();
11070 let micro_inj = micro_inj_on();
11071 if hc_diet_on()
11075 && (t == 1 || exact)
11076 && trunk_bf16_on()
11077 && micro_inj
11078 && hidden % 8 == 0
11079 && rank % 8 == 0
11080 && gate.down_b16.is_some()
11081 && gate.up_b16.is_some()
11082 && (!with_inject || gate.inject_b16.is_some())
11083 {
11084 let mut parts = ws.take_f32(e, "hc.parts", t * streams * rank, 0)?;
11085 let mut injp = ws.take_f32(e, "hc.injp", t * streams * streams, 0)?;
11086 let mut inv = ws.take_f32(e, "hc.inv", t * streams, 0)?;
11087 let winj = if with_inject {
11088 gate.inject_b16.as_ref()
11089 } else {
11090 None
11091 };
11092 let mt = t > 1 && verify_mt_on() && (2..=12).contains(&t);
11096 if mt {
11097 launch_hc_diet_stage0_mt(e, ptrs, &mut inv, hidden, streams, t, eps)?;
11098 launch_hc_diet_stage1_mt(
11099 e,
11100 ptrs,
11101 &gate.norm_stack,
11102 &inv,
11103 gate.down_b16.as_ref().expect("guarded above"),
11104 winj,
11105 &mut parts,
11106 &mut injp,
11107 hidden,
11108 rank,
11109 streams,
11110 t,
11111 )?;
11112 } else {
11113 launch_hc_diet_stage1(
11114 e,
11115 ptrs,
11116 &gate.norm_stack,
11117 gate.down_b16.as_ref().expect("guarded above"),
11118 winj,
11119 &mut parts,
11120 &mut injp,
11121 &mut inv,
11122 hidden,
11123 rank,
11124 streams,
11125 t,
11126 eps,
11127 )?;
11128 }
11129 let mut low_act = ws.take_f32(e, "hc.low_act", t * rank, 0)?;
11130 let mut all = ws.take_f32(e, "hc.inj_all", streams * t, 0)?;
11131 launch_hc_diet_stage2(
11132 e,
11133 &parts,
11134 &injp,
11135 &mut low_act,
11136 &mut all,
11137 rank,
11138 streams,
11139 t,
11140 with_inject,
11141 )?;
11142 let mut mixed = ws.take_f32(e, "hc.mixed", t * hidden, 0)?;
11143 if mt && (t * rank + 8 * streams * t) * 4 <= 96 * 1024 {
11144 launch_hc_diet_stage3_mt(
11145 e,
11146 ptrs,
11147 &gate.norm_stack,
11148 &inv,
11149 gate.up_b16.as_ref().expect("guarded above"),
11150 &low_act,
11151 &mut mixed,
11152 hidden,
11153 rank,
11154 streams,
11155 t,
11156 )?;
11157 } else {
11158 launch_hc_diet_stage3(
11159 e,
11160 ptrs,
11161 &gate.norm_stack,
11162 &inv,
11163 gate.up_b16.as_ref().expect("guarded above"),
11164 &low_act,
11165 &mut mixed,
11166 hidden,
11167 rank,
11168 streams,
11169 t,
11170 )?;
11171 }
11172 ws.put_f32("hc.parts", parts);
11173 ws.put_f32("hc.injp", injp);
11174 ws.put_f32("hc.inv", inv);
11175 ws.put_f32("hc.low_act", low_act);
11176 let inject_out = if with_inject {
11177 InjectOut::Slab(all)
11178 } else {
11179 ws.put_f32("hc.inj_all", all);
11180 InjectOut::Rows(Vec::new())
11181 };
11182 return Ok((mixed, inject_out));
11183 }
11184
11185 let mut normed = ws.take_f32(e, "hc.normed", streams * t * hidden, 0)?;
11191 if micro_norm {
11192 launch_hc_norm_planes(
11194 e,
11195 ptrs,
11196 &gate.norm_stack,
11197 &mut normed,
11198 hidden,
11199 t,
11200 streams,
11201 eps,
11202 )?;
11203 } else {
11204 for s in 0..streams {
11205 let mut dst = normed.slice_mut(s * t * hidden..(s + 1) * t * hidden);
11206 launch_rms_norm_into_view(e, &planes[s], &gate.norm[s], &mut dst, hidden, t, eps)?;
11207 }
11208 }
11209 let trunk_b16 = trunk_bf16_on();
11213 let mut parts = ws.take_f32(e, "hc.parts", streams * t * rank, 0)?;
11214 if let (true, Some(w)) = (trunk_b16, gate.down_b16.as_ref()) {
11215 launch_qmatvec_bf16w(
11216 e,
11217 w,
11218 &normed,
11219 &mut parts,
11220 hidden,
11221 rank,
11222 t,
11223 streams,
11224 rank * hidden,
11225 t * hidden,
11226 hidden,
11227 t * rank,
11228 )?;
11229 } else {
11230 if gate.down[0].len() < rank * hidden {
11231 return Err(
11232 "qwen4exp_gpu: gate down f32 dropped (trunk_f32_diet) — keep the \
11233 trunk-bf16 seam ON"
11234 .into(),
11235 );
11236 }
11237 for s in 0..streams {
11238 let x = normed.slice(s * t * hidden..(s + 1) * t * hidden);
11239 let w = gate.down[s].slice(0..rank * hidden);
11240 let mut out = parts.slice_mut(s * t * rank..(s + 1) * t * rank);
11241 e.linear_device_into(&x, &w, &mut out, t, hidden, rank)?;
11242 }
11243 }
11244 let mut low_act = ws.take_f32(e, "hc.low_act", t * rank, 0)?;
11245 launch_hc_lowrank_reduce(e, &parts, &mut low_act, streams, t, rank)?;
11246 ws.put_f32("hc.parts", parts);
11247
11248 let mut gates = ws.take_f32(e, "hc.gates", streams * t * hidden, 0)?;
11251 if let (true, Some(w)) = (trunk_b16, gate.up_b16.as_ref()) {
11252 launch_qmatvec_bf16w(
11253 e,
11254 w,
11255 &low_act,
11256 &mut gates,
11257 rank,
11258 hidden,
11259 t,
11260 streams,
11261 hidden * rank,
11262 0,
11263 rank,
11264 t * hidden,
11265 )?;
11266 } else {
11267 if gate.up[0].len() < hidden * rank {
11268 return Err(
11269 "qwen4exp_gpu: gate up f32 dropped (trunk_f32_diet) — keep the \
11270 trunk-bf16 seam ON"
11271 .into(),
11272 );
11273 }
11274 for s in 0..streams {
11275 let x = low_act.slice(0..t * rank);
11276 let w = gate.up[s].slice(0..hidden * rank);
11277 let mut out = gates.slice_mut(s * t * hidden..(s + 1) * t * hidden);
11278 e.linear_device_into(&x, &w, &mut out, t, rank, hidden)?;
11279 }
11280 }
11281 let mut mixed = ws.take_f32(e, "hc.mixed", t * hidden, 0)?;
11282 launch_hc_mix_epilogue(e, &gates, &normed, &mut mixed, streams, t, hidden)?;
11283 ws.put_f32("hc.gates", gates);
11284 ws.put_f32("hc.low_act", low_act);
11285
11286 let mut inject_out = InjectOut::Rows(Vec::new());
11287 if with_inject {
11288 let inject = gate
11289 .inject
11290 .as_ref()
11291 .ok_or("qwen4exp_gpu: read gate missing inject weights")?;
11292 let inject_dropped = inject.len() < streams * streams * hidden;
11295 let inject_guard = || -> Res<()> {
11296 if inject_dropped {
11297 return Err("qwen4exp_gpu: inject f32 dropped (trunk_f32_diet) — keep \
11298 the trunk-bf16 seam ON"
11299 .into());
11300 }
11301 Ok(())
11302 };
11303 let mut all = ws.take_f32(e, "hc.inj_all", streams * t, 0)?;
11304 if micro_inj {
11305 const CHUNKS: usize = 16;
11308 let mut partials = ws.take_f32(e, "hc.inj_part", streams * t * CHUNKS, 0)?;
11309 let w_b16 = if trunk_b16 {
11310 gate.inject_b16.as_ref()
11311 } else {
11312 None
11313 };
11314 if w_b16.is_none() {
11315 inject_guard()?;
11316 }
11317 launch_hc_inject_two_stage(
11318 e,
11319 &normed,
11320 inject,
11321 w_b16,
11322 &mut partials,
11323 &mut all,
11324 streams,
11325 t,
11326 hidden,
11327 CHUNKS,
11328 )?;
11329 ws.put_f32("hc.inj_part", partials);
11330 inject_out = InjectOut::Slab(all);
11331 } else {
11332 if let (true, Some(w)) = (trunk_b16, gate.inject_b16.as_ref()) {
11335 launch_hc_inject_gates_b16(e, &normed, w, &mut all, streams, t, hidden)?;
11336 } else {
11337 inject_guard()?;
11338 launch_hc_inject_gates(e, &normed, inject, &mut all, streams, t, hidden)?;
11339 }
11340 let mut rows = Vec::with_capacity(streams);
11341 for s in 0..streams {
11342 let mut row = ws.take_f32(e, INJECT_SLOTS[s], t, 0)?;
11343 e.copy_range_into(&mut row, 0, &all, s * t, t)?;
11344 rows.push(row);
11345 }
11346 ws.put_f32("hc.inj_all", all);
11347 inject_out = InjectOut::Rows(rows);
11348 }
11349 }
11350 ws.put_f32("hc.normed", normed);
11351 Ok((mixed, inject_out))
11352 }
11353
11354 #[allow(clippy::too_many_arguments)]
11359 fn gate_read_legacy(
11360 &self,
11361 e: &Engine,
11362 _ws: &mut StepPool,
11363 gate: &GateW,
11364 planes: &[CudaSlice<f32>],
11365 t: usize,
11366 eps: f32,
11367 with_inject: bool,
11368 ) -> Res<(CudaSlice<f32>, InjectOut)> {
11369 let hidden = self.hidden;
11370 let streams = self.streams;
11371 let rank = gate_rank(gate, hidden, streams)?;
11372 if gate.down[0].len() < rank * hidden {
11373 return Err(
11374 "qwen4exp_gpu: gate f32 originals dropped (trunk_f32_diet) — the \
11375 legacy gate path needs them (keep hc seams ON)"
11376 .into(),
11377 );
11378 }
11379 let inv_streams = 1.0 / streams as f32; let mut normed = Vec::with_capacity(streams);
11382 for s in 0..streams {
11383 let mut dst = e.uninit(t * hidden)?;
11384 e.rms_norm(&planes[s], &gate.norm[s], &mut dst, hidden, t, eps)?;
11385 normed.push(dst);
11386 }
11387 let mut low = e.linear(&normed[0], &gate.down[0], t, hidden, rank)?;
11389 for s in 1..streams {
11390 let part = e.linear(&normed[s], &gate.down[s], t, hidden, rank)?;
11391 let mut view = low.slice_mut(0..t * rank);
11392 e.axpy_into(&part, 1.0, &mut view, t * rank)?;
11393 }
11394 e.scale_inplace(&mut low, inv_streams, t * rank)?;
11395 let ones = e.htod(&vec![1.0f32; t * rank.max(1)])?;
11396 let mut low_act = e.uninit(t * rank)?;
11397 e.silu_mul(&low, &ones, &mut low_act, t * rank)?;
11398
11399 let mut mixed = e.zeros(t * hidden)?;
11401 let mut gate_buf = e.uninit(t * hidden)?;
11402 let mut prod = e.uninit(t * hidden)?;
11403 for s in 0..streams {
11404 let g = e.linear(&low_act, &gate.up[s], t, rank, hidden)?;
11405 e.sigmoid(&g, &mut gate_buf, t * hidden)?;
11406 e.mul(&gate_buf, &normed[s], &mut prod, t * hidden)?;
11407 let mut view = mixed.slice_mut(0..t * hidden);
11408 e.axpy_into(&prod, 1.0, &mut view, t * hidden)?;
11409 }
11410 e.scale_inplace(&mut mixed, inv_streams, t * hidden)?;
11411
11412 let mut inject_out = Vec::new();
11413 if with_inject {
11414 let inject = gate
11415 .inject
11416 .as_ref()
11417 .ok_or("qwen4exp_gpu: read gate missing inject weights")?;
11418 let wide = streams * hidden;
11419 for s in 0..streams {
11420 let mut acc = {
11422 let w = inject.slice(s * wide..s * wide + hidden);
11423 let x = normed[0].slice(0..t * hidden);
11424 let mut out = e.uninit(t)?;
11425 e.linear_device_into(&x, &w, &mut out, t, hidden, 1)?;
11426 out
11427 };
11428 for s2 in 1..streams {
11429 let w = inject.slice(s * wide + s2 * hidden..s * wide + (s2 + 1) * hidden);
11430 let x = normed[s2].slice(0..t * hidden);
11431 let mut part = e.uninit(t)?;
11432 e.linear_device_into(&x, &w, &mut part, t, hidden, 1)?;
11433 let mut view = acc.slice_mut(0..t);
11434 e.axpy_into(&part, 1.0, &mut view, t)?;
11435 }
11436 e.scale_inplace(&mut acc, inv_streams, t)?;
11437 let mut sg = e.uninit(t)?;
11438 e.sigmoid(&acc, &mut sg, t)?;
11439 e.scale_inplace(&mut sg, 2.0, t)?;
11440 inject_out.push(sg);
11441 }
11442 }
11443 Ok((mixed, InjectOut::Rows(inject_out)))
11444 }
11445
11446 fn gate_write(
11450 &self,
11451 e: &Engine,
11452 planes: &mut [CudaSlice<f32>],
11453 ptrs: &CudaSlice<u64>,
11454 block_out: &CudaSlice<f32>,
11455 inject: &InjectOut,
11456 t: usize,
11457 ) -> Res<()> {
11458 match inject {
11459 InjectOut::Rows(rows) => {
11460 for (plane, inj) in planes.iter_mut().zip(rows) {
11461 e.add_scaled_rows(block_out, inj, plane, self.hidden, t)?;
11462 }
11463 Ok(())
11464 }
11465 InjectOut::Slab(slab) => {
11466 launch_hc_write_planes(e, ptrs, block_out, slab, self.hidden, t, self.streams)
11467 }
11468 }
11469 }
11470
11471 #[allow(clippy::too_many_arguments)]
11477 fn layer_interior(
11478 &self,
11479 e: &Engine,
11480 ws: &mut StepPool,
11481 ptrs: &CudaSlice<u64>,
11482 layer: &LayerW,
11483 lstate: &mut LayerState,
11484 planes: &mut [CudaSlice<f32>],
11485 tokens: &[u32],
11486 base_pos: usize,
11487 ) -> Res<()> {
11488 if let (Some(ple), Some(ple_state)) = (layer.ple.as_ref(), lstate.ple.as_mut()) {
11489 self.ple_block(
11490 e, layer, ple, &ple.table, ple_state, planes, tokens, 1, false, None,
11491 )?;
11492 }
11493 let (mixed, inject) = self.gate_read(
11494 e,
11495 ws,
11496 ptrs,
11497 &layer.attn_gate,
11498 planes,
11499 1,
11500 layer.eps_attn,
11501 false,
11502 )?;
11503 let block_out = match &layer.mixer {
11504 MixerW::Qsa(qsa) => self.qsa_forward(
11505 e,
11506 ws,
11507 layer,
11508 qsa,
11509 &mixed,
11510 &mut lstate.mixer,
11511 base_pos,
11512 1,
11513 0,
11514 false,
11515 )?,
11516 MixerW::Gdn(gdn) => {
11517 self.gdn_forward(e, ws, layer, gdn, &mixed, &mut lstate.mixer, 1, None)?
11518 }
11519 };
11520 ws.put_f32("hc.mixed", mixed);
11521 self.gate_write(e, planes, ptrs, &block_out, &inject, 1)?;
11522 ws.put_f32("mixer.out", block_out);
11523 put_inject(ws, inject);
11524 let (mixed, inject) = self.gate_read(
11525 e,
11526 ws,
11527 ptrs,
11528 &layer.mlp_gate,
11529 planes,
11530 1,
11531 layer.eps_mlp,
11532 false,
11533 )?;
11534 ws.put_f32("hc.mixed", mixed);
11535 put_inject(ws, inject);
11536 Ok(())
11537 }
11538
11539 fn moe_route_slots(&self, e: &Engine, ws: &mut StepPool, moe: &MoeW, layer: u32) -> Res<()> {
11545 let hidden = self.hidden;
11546 let experts = moe.plan.expert_count as usize;
11547 let selected = moe.plan.experts_per_token as usize;
11548 let mixed = ws.take_f32(e, "hc.mixed", hidden, 0)?;
11549 let mut router_out = ws.take_f32(e, "moe.router", experts, 0)?;
11550 let none: Option<CudaSlice<u8>> = None;
11551 let rb = if router_bf16_on() {
11552 &moe.router_b16
11553 } else {
11554 &none
11555 };
11556 linear_trunk_into(
11557 e,
11558 &moe.router,
11559 rb,
11560 &mixed,
11561 &mut router_out,
11562 1,
11563 hidden,
11564 experts,
11565 )?;
11566 if router_dev_on() && route_dev_geometry(experts, selected) {
11570 let mut sel = ws.take_i32_slot(e, "moe.sel", selected, 0)?;
11571 let mut w = ws.take_f32(e, "moe.w", selected, 0)?;
11572 route_topk_device(
11573 e,
11574 &router_out,
11575 &mut sel,
11576 &mut w,
11577 None,
11578 experts,
11579 selected,
11580 1,
11581 layer,
11582 )?;
11583 if route_sync_diag() {
11589 e.gpu.stream().synchronize()?;
11590 }
11591 ws.put_i32("moe.sel", sel);
11592 ws.put_f32("moe.w", w);
11593 ws.put_f32("moe.router", router_out);
11594 ws.put_f32("hc.mixed", mixed);
11595 return Ok(());
11596 }
11597 let logits = e.dtoh_view(&router_out.slice(0..experts))?;
11598 ws.put_f32("moe.router", router_out);
11599 ws.put_f32("hc.mixed", mixed);
11600 let route = host_route_softmax_topk(&logits, selected);
11601 let sel_host: Vec<i32> = route.iter().map(|&(x, _)| x as i32).collect();
11602 let w_host: Vec<f32> = route.iter().map(|&(_, w)| w).collect();
11603 ws.write_i32(e, "moe.sel", &sel_host)?;
11604 ws.write_f32(e, "moe.w", &w_host)?;
11605 Ok(())
11606 }
11607
11608 fn moe_grouped_tail_slots(
11613 &self,
11614 e: &Engine,
11615 ws: &mut StepPool,
11616 ptrs: &CudaSlice<u64>,
11617 moe: &MoeW,
11618 planes: &mut [CudaSlice<f32>],
11619 ) -> Res<()> {
11620 let hidden = self.hidden;
11621 let ff = moe.plan.expert_intermediate_size as usize;
11622 let n_sel = moe.plan.experts_per_token as usize;
11623 let (
11624 BankHalf::Nvfp4 {
11625 codes: gc,
11626 scales: gs,
11627 macros_dev: gm,
11628 ..
11629 },
11630 BankHalf::Nvfp4 {
11631 codes: uc,
11632 scales: us,
11633 macros_dev: um,
11634 ..
11635 },
11636 BankHalf::Nvfp4 {
11637 codes: dc,
11638 scales: ds,
11639 macros_dev: dm,
11640 ..
11641 },
11642 ) = (&moe.bank.gate, &moe.bank.up, &moe.bank.down)
11643 else {
11644 return Err("qwen4exp_gpu: grouped tail on a non-NVFP4 bank".into());
11645 };
11646 let mixed = ws.take_f32(e, "hc.mixed", hidden, 0)?;
11647 let sel = ws
11648 .i32s
11649 .remove("moe.sel")
11650 .ok_or("step workspace: moe.sel is not parked")?;
11651 let w_dev = ws.take_f32(e, "moe.w", n_sel, 0)?;
11652 let mut act = ws.take_f32(e, "moe.act", n_sel * ff, 0)?;
11653 if sel_gufuse_on() && hidden % 32 == 0 && ff % 4 == 0 {
11656 launch_nvfp4_sel_gu_silu(
11657 e,
11658 (gc, gs, gm),
11659 (uc, us, um),
11660 Some(&sel),
11661 0,
11662 n_sel,
11663 &mixed,
11664 &mut act,
11665 hidden,
11666 ff,
11667 None,
11668 )?;
11669 } else {
11670 let mut yg = ws.take_f32(e, "moe.yg", n_sel * ff, 0)?;
11671 let mut yu = ws.take_f32(e, "moe.yu", n_sel * ff, 0)?;
11672 launch_nvfp4_sel_matvec(e, gc, gs, gm, &sel, &mixed, &mut yg, n_sel, hidden, ff, 0)?;
11673 launch_nvfp4_sel_matvec(e, uc, us, um, &sel, &mixed, &mut yu, n_sel, hidden, ff, 0)?;
11674 e.silu_mul(&yg, &yu, &mut act, n_sel * ff)?;
11675 ws.put_f32("moe.yg", yg);
11676 ws.put_f32("moe.yu", yu);
11677 }
11678 let mut partial = ws.take_f32(e, "moe.partial", n_sel * hidden, 0)?;
11679 launch_nvfp4_sel_matvec(
11680 e,
11681 dc,
11682 ds,
11683 dm,
11684 &sel,
11685 &act,
11686 &mut partial,
11687 n_sel,
11688 ff,
11689 hidden,
11690 ff,
11691 )?;
11692 let mut out = ws.take_f32(e, "moe.out", hidden, 0)?;
11693 e.axpy_rows_seq_into(&partial, &w_dev, &mut out, hidden, n_sel)?;
11694 ws.put_i32("moe.sel", sel);
11695 ws.put_f32("moe.w", w_dev);
11696 ws.put_f32("moe.act", act);
11697 ws.put_f32("moe.partial", partial);
11698 let out = self.moe_shared_tail(e, ws, moe, &mixed, out, 1)?;
11699 ws.put_f32("hc.mixed", mixed);
11700 let inject = take_inject(e, ws, self.streams, 1)?;
11701 self.gate_write(e, planes, ptrs, &out, &inject, 1)?;
11702 ws.put_f32("moe.out", out);
11703 put_inject(ws, inject);
11704 Ok(())
11705 }
11706
11707 fn forward_graphs_tail(
11713 &self,
11714 e: &Engine,
11715 state: &mut Qwen4ExpState,
11716 mut planes: Vec<CudaSlice<f32>>,
11717 ptrs: CudaSlice<u64>,
11718 base_pos: usize,
11719 ) -> Res<Vec<f32>> {
11720 let mut graphs = std::mem::take(&mut state.graphs);
11721 if graphs.a.len() != self.layers.len() {
11722 graphs.a = (0..self.layers.len()).map(|_| None).collect();
11723 graphs.b = (0..self.layers.len()).map(|_| None).collect();
11724 }
11725 let ws = &mut state.ws;
11726 let tokens = &state.tokens;
11727 for (li, (layer, lstate)) in self.layers.iter().zip(state.layers.iter_mut()).enumerate() {
11728 let a_ok = matches!(layer.mixer, MixerW::Gdn(_)) && layer.ple.is_none();
11729 if a_ok {
11730 if graphs.a[li].is_none() {
11731 graphs.a[li] = Some(e.capture_graph_retained_nowarm(|eng| {
11732 self.layer_interior(
11733 eng,
11734 ws,
11735 &ptrs,
11736 layer,
11737 lstate,
11738 &mut planes,
11739 tokens,
11740 base_pos,
11741 )
11742 })?);
11743 }
11744 graphs.a[li].as_ref().unwrap().0.launch()?;
11745 } else {
11746 self.layer_interior(e, ws, &ptrs, layer, lstate, &mut planes, tokens, base_pos)?;
11747 }
11748 let b_ok = moe_sel_path_on()
11749 && matches!(
11750 (
11751 &layer.moe.bank.gate,
11752 &layer.moe.bank.up,
11753 &layer.moe.bank.down
11754 ),
11755 (
11756 BankHalf::Nvfp4 { .. },
11757 BankHalf::Nvfp4 { .. },
11758 BankHalf::Nvfp4 { .. }
11759 )
11760 );
11761 if b_ok {
11762 self.moe_route_slots(e, ws, &layer.moe, layer.index)?;
11763 if graphs.b[li].is_none() {
11764 graphs.b[li] = Some(e.capture_graph_retained_nowarm(|eng| {
11765 self.moe_grouped_tail_slots(eng, ws, &ptrs, &layer.moe, &mut planes)
11766 })?);
11767 }
11768 graphs.b[li].as_ref().unwrap().0.launch()?;
11769 } else {
11770 let mixed = ws.take_f32(e, "hc.mixed", self.hidden, 0)?;
11772 let mlp = self.moe_forward(e, ws, &layer.moe, &mixed, 1, false, layer.index)?;
11773 ws.put_f32("hc.mixed", mixed);
11774 let inject = take_inject(e, ws, self.streams, 1)?;
11775 self.gate_write(e, &mut planes, &ptrs, &mlp, &inject, 1)?;
11776 ws.put_f32("moe.out", mlp);
11777 put_inject(ws, inject);
11778 }
11779 }
11780 if graphs.exit.is_none() {
11781 graphs.exit = Some(e.capture_graph_retained_nowarm(|eng| {
11782 let x = self
11783 .gate_read_inner(
11784 eng,
11785 ws,
11786 &ptrs,
11787 &self.exit_mixer,
11788 &planes,
11789 1,
11790 self.exit_eps,
11791 false,
11792 false,
11793 )?
11794 .0;
11795 let mut logits = ws.take_f32(eng, "logits", self.vocab, 0)?;
11796 linear_trunk_into(
11797 eng,
11798 &self.output,
11799 &self.output_b16,
11800 &x,
11801 &mut logits,
11802 1,
11803 self.hidden,
11804 self.vocab,
11805 )?;
11806 ws.put_f32("hc.mixed", x);
11807 ws.put_f32("logits", logits);
11808 Ok(())
11809 })?);
11810 }
11811 graphs.exit.as_ref().unwrap().0.launch()?;
11812 let out = {
11813 let logits = ws.peek_f32("logits")?;
11814 e.dtoh_view(&logits.slice(0..self.vocab))?
11815 };
11816 for (s, plane) in planes.into_iter().enumerate() {
11817 ws.put_f32(PLANE_SLOTS[s], plane);
11818 }
11819 ws.put_u64("hc.ptrs", ptrs);
11820 state.pos += 1;
11821 state.graphs = graphs;
11822 Ok(out)
11823 }
11824
11825 #[allow(clippy::too_many_arguments)]
11833 fn qsa_update_select(
11834 &self,
11835 e: &Engine,
11836 ws: &mut StepPool,
11837 qsa: &QsaW,
11838 eps: f32,
11839 mixed: &CudaSlice<f32>,
11840 raw_keys: &mut IdxRawCache,
11841 pooled_keys: &mut Vec<f32>,
11842 pooled_dev: &mut Option<CudaSlice<f32>>,
11843 pooled_dev_rows: &mut usize,
11844 raw_dev: &mut Option<IdxRawDev>,
11845 raw_dev_rows: &mut usize,
11846 mut idx_audit: Option<&mut Box<IdxAudit>>,
11847 base_pos: usize,
11848 t: usize,
11849 pos_off: usize,
11850 exact: bool,
11851 ) -> Res<Vec<RowSel>> {
11852 let hidden = self.hidden;
11853 let base = qsa.attn.rope.base;
11854 let t_kv = base_pos + t;
11855 let overlay = &qsa.overlay;
11857 let idx_dim = overlay.head_dim as usize;
11858 let qk_width = (overlay.query_heads as usize + overlay.kv_heads as usize) * idx_dim;
11859 if overlay.kv_heads != 1 {
11860 return Err("qwen4exp_gpu: indexer with more than one key head".into());
11861 }
11862 let idx_proj = prof_section(e, "qsa.idx_proj", || {
11863 let mut idx_proj = ws.take_f32(e, "qsa.idxp", t * qk_width, 0)?;
11864 if exact && t > 1 {
11865 let wv = qsa.idx_proj.slice(0..qsa.idx_proj.len());
11866 for tok in 0..t {
11867 let xv = mixed.slice(tok * hidden..(tok + 1) * hidden);
11868 let mut yv = idx_proj.slice_mut(tok * qk_width..(tok + 1) * qk_width);
11869 e.linear_device_into(&xv, &wv, &mut yv, 1, hidden, qk_width)?;
11870 }
11871 } else {
11872 e.linear_device_into(mixed, &qsa.idx_proj, &mut idx_proj, t, hidden, qk_width)?;
11873 }
11874 Ok(idx_proj)
11875 })?;
11876 let dev_cache = idx_cache_on();
11886 let block_size = overlay.block_size as usize;
11887 let all_full = (base_pos + t) / block_size <= overlay.budget_blocks as usize;
11888 let host_rows = raw_keys.rows(idx_dim);
11889 if *raw_dev_rows > host_rows && !(dev_cache && all_full) {
11890 idx_materialize_host(e, raw_keys, raw_dev, *raw_dev_rows, idx_dim)?;
11894 }
11895 if dev_cache {
11896 let host_rows = raw_keys.rows(idx_dim);
11897 let base_rows = (*raw_dev_rows).max(host_rows);
11898 let cap_rows = (base_rows + t).next_power_of_two().max(64);
11899 let q_off = overlay.query_heads as usize * idx_dim;
11900 match &mut *raw_keys {
11901 IdxRawCache::F32(h) => {
11902 let want = (base_rows + t) * idx_dim;
11903 let grow = match raw_dev.as_ref() {
11904 Some(IdxRawDev::F32(m)) => m.len() < want,
11905 Some(_) => return Err("idxcache: device format lag on f32".into()),
11906 None => true,
11907 };
11908 if grow {
11909 let mut fresh = e.uninit(cap_rows * idx_dim)?;
11910 if let (Some(IdxRawDev::F32(old)), rows) = (raw_dev.as_ref(), *raw_dev_rows)
11911 {
11912 if rows > 0 {
11913 e.copy_range_into(&mut fresh, 0, old, 0, rows * idx_dim)?;
11914 }
11915 }
11916 *raw_dev = Some(IdxRawDev::F32(fresh));
11917 }
11918 let Some(IdxRawDev::F32(m)) = raw_dev.as_mut() else {
11919 unreachable!("allocated above");
11920 };
11921 if host_rows > *raw_dev_rows {
11922 let mut view = m.slice_mut(*raw_dev_rows * idx_dim..host_rows * idx_dim);
11924 e.gpu
11925 .stream()
11926 .memcpy_htod(&h[*raw_dev_rows * idx_dim..], &mut view)?;
11927 *raw_dev_rows = host_rows;
11928 }
11929 launch_copy_rows_col(
11930 e,
11931 &idx_proj,
11932 m,
11933 t,
11934 idx_dim,
11935 qk_width,
11936 q_off,
11937 *raw_dev_rows,
11938 )?;
11939 }
11940 IdxRawCache::Q8(h) => {
11941 let rb = q8_row_bytes(idx_dim);
11942 let want = (base_rows + t) * rb;
11943 let grow = match raw_dev.as_ref() {
11944 Some(IdxRawDev::Q8(m)) => m.len() < want,
11945 Some(_) => return Err("idxcache: device format lag on q8".into()),
11946 None => true,
11947 };
11948 if grow {
11949 let mut fresh = e.alloc_u8_uninit(cap_rows * rb)?;
11950 if let (Some(IdxRawDev::Q8(old)), rows) = (raw_dev.as_ref(), *raw_dev_rows)
11951 {
11952 if rows > 0 {
11953 let mut dst = fresh.slice_mut(0..rows * rb);
11954 e.gpu
11955 .stream()
11956 .memcpy_dtod(&old.slice(0..rows * rb), &mut dst)?;
11957 }
11958 }
11959 *raw_dev = Some(IdxRawDev::Q8(fresh));
11960 }
11961 let Some(IdxRawDev::Q8(m)) = raw_dev.as_mut() else {
11962 unreachable!("allocated above");
11963 };
11964 if host_rows > *raw_dev_rows {
11965 let mut view = m.slice_mut(*raw_dev_rows * rb..host_rows * rb);
11966 e.gpu
11967 .stream()
11968 .memcpy_htod(&h[*raw_dev_rows * rb..host_rows * rb], &mut view)?;
11969 *raw_dev_rows = host_rows;
11970 }
11971 launch_q4e_idx_append_q8(
11972 e,
11973 &idx_proj,
11974 m,
11975 t,
11976 idx_dim,
11977 qk_width,
11978 q_off,
11979 *raw_dev_rows,
11980 )?;
11981 }
11982 IdxRawCache::Bf16(h) => {
11983 let want = (base_rows + t) * idx_dim;
11984 let grow = match raw_dev.as_ref() {
11985 Some(IdxRawDev::Bf16(m)) => m.len() < want,
11986 Some(_) => return Err("idxcache: device format lag on bf16".into()),
11987 None => true,
11988 };
11989 if grow {
11990 let mut fresh = unsafe { e.gpu.stream().alloc::<u16>(cap_rows * idx_dim)? };
11991 if let (Some(IdxRawDev::Bf16(old)), rows) =
11992 (raw_dev.as_ref(), *raw_dev_rows)
11993 {
11994 if rows > 0 {
11995 let mut dst = fresh.slice_mut(0..rows * idx_dim);
11996 e.gpu
11997 .stream()
11998 .memcpy_dtod(&old.slice(0..rows * idx_dim), &mut dst)?;
11999 }
12000 }
12001 *raw_dev = Some(IdxRawDev::Bf16(fresh));
12002 }
12003 let Some(IdxRawDev::Bf16(m)) = raw_dev.as_mut() else {
12004 unreachable!("allocated above");
12005 };
12006 if host_rows > *raw_dev_rows {
12007 let mut view = m.slice_mut(*raw_dev_rows * idx_dim..host_rows * idx_dim);
12008 e.gpu.stream().memcpy_htod(
12009 &h[*raw_dev_rows * idx_dim..host_rows * idx_dim],
12010 &mut view,
12011 )?;
12012 *raw_dev_rows = host_rows;
12013 }
12014 launch_q4e_idx_append_bf16(
12015 e,
12016 &idx_proj,
12017 m,
12018 t,
12019 idx_dim,
12020 qk_width,
12021 q_off,
12022 *raw_dev_rows,
12023 )?;
12024 }
12025 }
12026 *raw_dev_rows += t;
12027 }
12028 if let Some(audit) = idx_audit.as_deref_mut() {
12032 let q_off = overlay.query_heads as usize * idx_dim;
12033 let rows_f = e.dtoh_view(&idx_proj.slice(0..t * qk_width))?;
12034 let IdxRawCache::F32(twin) = &mut audit.raw_f32 else {
12035 return Err("idxq audit: twin cache is not f32".into());
12036 };
12037 for row in 0..t {
12038 twin.extend_from_slice(&rows_f[row * qk_width + q_off..(row + 1) * qk_width]);
12039 }
12040 }
12041 let sels: Vec<RowSel> = if dev_cache && all_full {
12042 ws.put_f32("qsa.idxp", idx_proj);
12043 (0..t)
12044 .map(|qt| RowSel {
12045 full: true,
12046 blocks: Vec::new(),
12047 visible: base_pos + qt + 1,
12048 })
12049 .collect()
12050 } else {
12051 let idx_rows = e.dtoh_view(&idx_proj.slice(0..t * qk_width))?;
12052 ws.put_f32("qsa.idxp", idx_proj);
12053 let q_off = overlay.query_heads as usize * idx_dim;
12054 for row in 0..t {
12055 raw_keys.append_rows_f32(
12056 &idx_rows[row * qk_width + q_off..(row + 1) * qk_width],
12057 1,
12058 idx_dim,
12059 );
12060 }
12061 let dev_scorer = idx_dev_on();
12068 let sels = prof_section(e, "qsa.idx_host", || {
12069 indexer_select_rows(
12070 overlay,
12071 base,
12072 qsa.yarn.as_ref().map(|y| (y.ff_host.as_slice(), y.mscale)),
12073 eps,
12074 &qsa.idx_q_norm,
12075 &qsa.idx_k_norm,
12076 &idx_rows,
12077 raw_keys,
12078 pooled_keys,
12079 if dev_scorer {
12080 Some((e, pooled_dev, pooled_dev_rows))
12081 } else {
12082 None
12083 },
12084 base_pos,
12085 t,
12086 t_kv,
12087 pos_off,
12088 )
12089 })?;
12090 if let Some(audit) = idx_audit.as_deref_mut() {
12100 if t <= 8 && sels.iter().any(|s| !s.full) {
12101 let twin_sels = indexer_select_rows(
12102 overlay,
12103 base,
12104 qsa.yarn.as_ref().map(|y| (y.ff_host.as_slice(), y.mscale)),
12105 eps,
12106 &qsa.idx_q_norm,
12107 &qsa.idx_k_norm,
12108 &idx_rows,
12109 &audit.raw_f32,
12110 &mut audit.pooled_f32,
12111 None,
12112 base_pos,
12113 t,
12114 t_kv,
12115 pos_off,
12116 )?;
12117 use std::sync::atomic::Ordering::Relaxed;
12118 for (a, b) in sels.iter().zip(&twin_sels) {
12119 if a.full && b.full {
12120 continue;
12121 }
12122 IDXQ_AUDIT_ROWS.fetch_add(1, Relaxed);
12123 if a.full != b.full || a.blocks != b.blocks {
12124 IDXQ_AUDIT_FLIPPED.fetch_add(1, Relaxed);
12125 let mut diff = 0u64;
12126 let (sa, sb) = (&a.blocks, &b.blocks);
12127 let seta: std::collections::BTreeSet<_> = sa.iter().collect();
12128 let setb: std::collections::BTreeSet<_> = sb.iter().collect();
12129 diff += seta.symmetric_difference(&setb).count() as u64;
12130 IDXQ_AUDIT_BLOCKS.fetch_add(diff, Relaxed);
12131 }
12132 }
12133 }
12134 }
12135 sels
12136 };
12137 Ok(sels)
12138 }
12139
12140 fn qsa_forward(
12141 &self,
12142 e: &Engine,
12143 ws: &mut StepPool,
12144 layer: &LayerW,
12145 qsa: &QsaW,
12146 mixed: &CudaSlice<f32>,
12147 mstate: &mut MixerState,
12148 base_pos: usize,
12149 t: usize,
12150 pos_off: usize,
12153 exact: bool,
12157 ) -> Res<CudaSlice<f32>> {
12158 let MixerState::Qsa {
12159 kv,
12160 raw_keys,
12161 pooled_keys,
12162 pooled_dev,
12163 pooled_dev_rows,
12164 raw_dev,
12165 raw_dev_rows,
12166 idx_audit,
12167 } = mstate
12168 else {
12169 return Err(format!(
12170 "qwen4exp_gpu: QSA layer {} bound to non-QSA state",
12171 layer.index
12172 )
12173 .into());
12174 };
12175 let hidden = self.hidden;
12176 let nh = qsa.attn.query_heads as usize;
12177 let nkv = qsa.attn.kv_heads as usize;
12178 let hd = qsa.attn.key_head_dim as usize;
12179 let eps = layer.eps_attn;
12180 let cap = kv.capacity_rows(nkv * hd);
12182
12183 let n_rot = qsa.attn.rope.dimensions as usize;
12184 let base = qsa.attn.rope.base;
12185 let (q, gate) = prof_section(e, "qsa.proj", || {
12186 let mut q_fused = ws.take_f32(e, "qsa.qf", t * 2 * nh * hd, 0)?;
12187 let mut k_new = ws.take_f32(e, "qsa.k", t * nkv * hd, 0)?;
12188 let mut v_new = ws.take_f32(e, "qsa.v", t * nkv * hd, 0)?;
12189 if let (true, Some(stack)) = (
12193 t == 1 && proj_stack_on() && trunk_bf16_on(),
12194 qsa.proj_b16.as_ref(),
12195 ) {
12196 launch_qmatvec_bf16w_multi4(
12197 e,
12198 stack,
12199 mixed,
12200 &[
12201 (&q_fused, 2 * nh * hd),
12202 (&k_new, nkv * hd),
12203 (&v_new, nkv * hd),
12204 ],
12205 hidden,
12206 )?;
12207 } else {
12208 linear_trunk_stacked_into(
12209 e,
12210 &qsa.wq,
12211 &qsa.proj_b16,
12212 0,
12213 mixed,
12214 &mut q_fused,
12215 t,
12216 hidden,
12217 2 * nh * hd,
12218 )?;
12219 linear_trunk_stacked_into(
12220 e,
12221 &qsa.wk,
12222 &qsa.proj_b16,
12223 2 * nh * hd,
12224 mixed,
12225 &mut k_new,
12226 t,
12227 hidden,
12228 nkv * hd,
12229 )?;
12230 linear_trunk_stacked_into(
12231 e,
12232 &qsa.wv,
12233 &qsa.proj_b16,
12234 2 * nh * hd + nkv * hd,
12235 mixed,
12236 &mut v_new,
12237 t,
12238 hidden,
12239 nkv * hd,
12240 )?;
12241 }
12242 let mut q = ws.take_f32(e, "qsa.q", t * nh * hd, 0)?;
12243 let mut gate = ws.take_f32(e, "qsa.gate", t * nh * hd, 0)?;
12244 e.q_gate_split(&q_fused, &mut q, &mut gate, hd, nh, t)?;
12245 ws.put_f32("qsa.qf", q_fused);
12246 let mut q = if let Some(norm) = qsa.q_norm.as_ref() {
12247 let mut dst = ws.take_f32(e, "qsa.qn", t * nh * hd, 0)?;
12248 e.rms_norm(&q, norm, &mut dst, hd, t * nh, eps)?;
12249 ws.put_f32("qsa.q", q);
12250 dst
12251 } else {
12252 q
12253 };
12254 let mut k_new = if let Some(norm) = qsa.k_norm.as_ref() {
12255 let mut dst = ws.take_f32(e, "qsa.kn", t * nkv * hd, 0)?;
12256 e.rms_norm(&k_new, norm, &mut dst, hd, t * nkv, eps)?;
12257 ws.put_f32("qsa.k", k_new);
12258 dst
12259 } else {
12260 k_new
12261 };
12262 let positions: Vec<i32> = (0..t).map(|i| (base_pos + i + pos_off) as i32).collect();
12263 let pos_dev = ws.take_i32(e, "qsa.pos", &positions, 0)?;
12264 if let Some(yarn) = qsa.yarn.as_ref() {
12265 e.rope_neox_ffm(
12266 &mut q,
12267 &pos_dev,
12268 hd,
12269 n_rot,
12270 nh,
12271 t,
12272 base,
12273 1.0,
12274 &yarn.ff,
12275 yarn.mscale,
12276 )?;
12277 e.rope_neox_ffm(
12278 &mut k_new,
12279 &pos_dev,
12280 hd,
12281 n_rot,
12282 nkv,
12283 t,
12284 base,
12285 1.0,
12286 &yarn.ff,
12287 yarn.mscale,
12288 )?;
12289 } else {
12290 e.rope_neox(&mut q, &pos_dev, hd, n_rot, nh, t, base, 1.0)?;
12291 e.rope_neox(&mut k_new, &pos_dev, hd, n_rot, nkv, t, base, 1.0)?;
12292 }
12293 ws.put_i32("qsa.pos", pos_dev);
12294 match kv {
12296 QsaKvStore::F32 { k, v } => {
12297 e.copy_range_into(k, base_pos * nkv * hd, &k_new, 0, t * nkv * hd)?;
12298 e.copy_range_into(v, base_pos * nkv * hd, &v_new, 0, t * nkv * hd)?;
12299 }
12300 QsaKvStore::Q8Q5 { k, v } => {
12303 launch_q4e_kv_append(e, &k_new, &v_new, k, v, base_pos, t, nkv * hd)?;
12304 }
12305 }
12306 ws.put_f32(
12307 if qsa.k_norm.is_some() {
12308 "qsa.kn"
12309 } else {
12310 "qsa.k"
12311 },
12312 k_new,
12313 );
12314 ws.put_f32("qsa.v", v_new);
12315 Ok((q, gate))
12316 })?;
12317 let t_kv = base_pos + t;
12318 let sels = self.qsa_update_select(
12319 e,
12320 ws,
12321 qsa,
12322 eps,
12323 mixed,
12324 raw_keys,
12325 pooled_keys,
12326 pooled_dev,
12327 pooled_dev_rows,
12328 raw_dev,
12329 raw_dev_rows,
12330 idx_audit.as_mut(),
12331 base_pos,
12332 t,
12333 pos_off,
12334 exact,
12335 )?;
12336 let overlay = &qsa.overlay;
12337
12338 let scale = match qsa.attn.scale {
12339 memra_gguf::model_plan::AttentionScale::InverseSqrtKeyDim => 1.0 / (hd as f32).sqrt(),
12340 memra_gguf::model_plan::AttentionScale::Fixed(scale) => scale,
12341 };
12342 let long_att = if kv.is_quant() {
12366 if longatt_mode() == LongAttMode::Off {
12367 return Err(
12368 "qwen4exp_gpu: kvq requires the block-list attention form (longatt=off)".into(),
12369 );
12370 }
12371 true
12372 } else {
12373 match longatt_mode() {
12374 LongAttMode::Force => true,
12375 LongAttMode::Auto => t_kv > SDPA_MASK_TKV_BOUND || sels.iter().any(|s| !s.full),
12376 LongAttMode::Off => false,
12377 }
12378 };
12379 let block_size = overlay.block_size as usize;
12380 let attended = if long_att {
12381 let (pos_flat, meta, max_count) = rowsel_positions(&sels, block_size);
12382 let pos_dev = prof_section(e, "qsa.mask_h2d", || {
12383 ws.take_i32(e, "qsa.selpos", &pos_flat, 0)
12384 })?;
12385 let meta_dev = ws.take_i32(e, "qsa.selmeta", &meta, 0)?;
12386 let attended = prof_section(e, "qsa.sdpa", || {
12387 let mut attended = ws.take_f32(e, "qsa.att", t * nh * hd, 0)?;
12388 match kv {
12389 QsaKvStore::F32 { k, v } => {
12390 let k_view = k.slice(0..t_kv * nkv * hd);
12391 let v_view = v.slice(0..t_kv * nkv * hd);
12392 launch_sdpa_blocklist(
12393 e,
12394 &q,
12395 &k_view,
12396 &v_view,
12397 &mut attended,
12398 &pos_dev,
12399 &meta_dev,
12400 hd,
12401 nh,
12402 nkv,
12403 t,
12404 max_count,
12405 scale,
12406 )?;
12407 }
12408 QsaKvStore::Q8Q5 { k, v } => {
12409 launch_q4e_sdpa_blocklist_q8q5(
12410 e,
12411 &q,
12412 k,
12413 v,
12414 &mut attended,
12415 &pos_dev,
12416 &meta_dev,
12417 hd,
12418 nh,
12419 nkv,
12420 t,
12421 max_count,
12422 scale,
12423 )?;
12424 }
12425 }
12426 Ok(attended)
12427 })?;
12428 ws.put_i32("qsa.selpos", pos_dev);
12429 ws.put_i32("qsa.selmeta", meta_dev);
12430 attended
12431 } else {
12432 let QsaKvStore::F32 { k, v } = &*kv else {
12433 return Err("qwen4exp_gpu: masked SDPA reached with a quantized cache".into());
12434 };
12435 let mask = rowsel_to_mask(&sels, block_size, t_kv);
12436 let mask_dev = prof_section(e, "qsa.mask_h2d", || {
12437 ws.take_u8_h2d(e, "qsa.mask", &mask, t * cap.min(SDPA_MASK_TKV_BOUND))
12440 })?;
12441 let attended = prof_section(e, "qsa.sdpa", || {
12442 let mut attended = ws.take_f32(e, "qsa.att", t * nh * hd, 0)?;
12443 let k_view = k.slice(0..t_kv * nkv * hd);
12444 let v_view = v.slice(0..t_kv * nkv * hd);
12445 launch_sdpa_mask(
12446 e,
12447 &q,
12448 &k_view,
12449 &v_view,
12450 &mut attended,
12451 &mask_dev,
12452 hd,
12453 nh,
12454 nkv,
12455 t,
12456 t_kv,
12457 scale,
12458 )?;
12459 Ok(attended)
12460 })?;
12461 ws.put_u8("qsa.mask", mask_dev);
12462 attended
12463 };
12464 ws.put_f32(
12465 if qsa.q_norm.is_some() {
12466 "qsa.qn"
12467 } else {
12468 "qsa.q"
12469 },
12470 q,
12471 );
12472 let out = prof_section(e, "qsa.gate_wo", || {
12473 let mut sg = ws.take_f32(e, "qsa.sg", t * nh * hd, 0)?;
12475 e.sigmoid(&gate, &mut sg, t * nh * hd)?;
12476 let mut gated = ws.take_f32(e, "qsa.gated", t * nh * hd, 0)?;
12477 e.mul(&attended, &sg, &mut gated, t * nh * hd)?;
12478 let mut out = ws.take_f32(e, "mixer.out", t * hidden, 0)?;
12479 linear_trunk_into(
12480 e,
12481 &qsa.wo,
12482 &qsa.wo_b16,
12483 &gated,
12484 &mut out,
12485 t,
12486 nh * hd,
12487 hidden,
12488 )?;
12489 ws.put_f32("qsa.sg", sg);
12490 ws.put_f32("qsa.gated", gated);
12491 Ok(out)
12492 })?;
12493 ws.put_f32("qsa.att", attended);
12494 ws.put_f32("qsa.gate", gate);
12495 Ok(out)
12496 }
12497
12498 #[allow(clippy::too_many_arguments)]
12502 fn gdn_forward(
12503 &self,
12504 e: &Engine,
12505 ws: &mut StepPool,
12506 layer: &LayerW,
12507 gdn: &GdnW,
12508 mixed: &CudaSlice<f32>,
12509 mstate: &mut MixerState,
12510 t: usize,
12511 mut stash: Option<&mut GdnStash>,
12515 ) -> Res<CudaSlice<f32>> {
12516 let MixerState::Gdn { conv, state } = mstate else {
12517 return Err(format!(
12518 "qwen4exp_gpu: GDN layer {} bound to non-GDN state",
12519 layer.index
12520 )
12521 .into());
12522 };
12523 let hidden = self.hidden;
12524 let p = &gdn.plan;
12525 let (nk, nv) = (p.key_heads as usize, p.value_heads as usize);
12526 let (hk, hv) = (p.key_head_dim as usize, p.value_head_dim as usize);
12527 let kernel = p.conv_kernel as usize;
12528 let pad = kernel - 1;
12529 let conv_dim = 2 * nk * hk + nv * hv;
12530 let eps = layer.eps_attn;
12531
12532 let (qkv, z, beta_raw, g_log) = prof_section(e, "gdn.proj", || {
12533 let mut qkv = ws.take_f32(e, "gdn.qkv", t * conv_dim, 0)?;
12534 let mut z = ws.take_f32(e, "gdn.z", t * nv * hv, 0)?;
12535 let mut beta_raw = ws.take_f32(e, "gdn.beta", t * nv, 0)?;
12536 let mut alpha = ws.take_f32(e, "gdn.alpha", t * nv, 0)?;
12537 if let (true, Some(stack)) = (
12542 t == 1 && proj_stack_on() && trunk_bf16_on(),
12543 gdn.proj_b16.as_ref(),
12544 ) {
12545 launch_qmatvec_bf16w_multi4(
12546 e,
12547 stack,
12548 mixed,
12549 &[
12550 (&qkv, conv_dim),
12551 (&z, nv * hv),
12552 (&beta_raw, nv),
12553 (&alpha, nv),
12554 ],
12555 hidden,
12556 )?;
12557 } else {
12558 linear_trunk_stacked_into(
12559 e,
12560 &gdn.qkv,
12561 &gdn.proj_b16,
12562 0,
12563 mixed,
12564 &mut qkv,
12565 t,
12566 hidden,
12567 conv_dim,
12568 )?;
12569 linear_trunk_stacked_into(
12570 e,
12571 &gdn.z,
12572 &gdn.proj_b16,
12573 conv_dim,
12574 mixed,
12575 &mut z,
12576 t,
12577 hidden,
12578 nv * hv,
12579 )?;
12580 linear_trunk_stacked_into(
12581 e,
12582 &gdn.beta,
12583 &gdn.proj_b16,
12584 conv_dim + nv * hv,
12585 mixed,
12586 &mut beta_raw,
12587 t,
12588 hidden,
12589 nv,
12590 )?;
12591 linear_trunk_stacked_into(
12592 e,
12593 &gdn.alpha,
12594 &gdn.proj_b16,
12595 conv_dim + nv * hv + nv,
12596 mixed,
12597 &mut alpha,
12598 t,
12599 hidden,
12600 nv,
12601 )?;
12602 }
12603 let mut g_log = ws.take_f32(e, "gdn.glog", t * nv, 0)?;
12604 e.gdn_glog_v(&alpha.slice(0..t * nv), &gdn.dt, &gdn.a, &mut g_log, nv, t)?;
12605 ws.put_f32("gdn.alpha", alpha);
12606 Ok((qkv, z, beta_raw, g_log))
12607 })?;
12608
12609 let o = prof_section(e, "gdn.conv_scan", || {
12610 if let Some(st) = stash.as_deref_mut() {
12614 e.copy_range_into(&mut st.conv_pre, 0, conv, 0, pad * conv_dim)?;
12615 e.copy_range_into(&mut st.qkv_rows, 0, &qkv, 0, t * conv_dim)?;
12616 }
12617 let mut conv_out = ws.take_f32(e, "gdn.conv_out", t * conv_dim, 0)?;
12620 let mut o = ws.take_f32(e, "gdn.o", t * nv * hv, 0)?;
12621 let mut tmp = if t >= pad {
12622 None
12623 } else {
12624 Some(ws.take_f32(e, "gdn.tmp", (pad - t) * conv_dim, 0)?)
12625 };
12626 let scale = 1.0 / (hk as f32).sqrt();
12627 let step_ok = gdn_step_on() && hk % 32 == 0 && hk <= 1024;
12628 let chain = |eng: &Engine,
12639 conv: &mut CudaSlice<f32>,
12640 state: &mut CudaSlice<f32>,
12641 states_snap: Option<&mut CudaSlice<f32>>,
12642 conv_out: &mut CudaSlice<f32>,
12643 o: &mut CudaSlice<f32>,
12644 tmp: Option<&mut CudaSlice<f32>>|
12645 -> Res<()> {
12646 launch_dwconv(
12647 eng,
12648 &qkv,
12649 conv,
12650 &gdn.conv_w,
12651 conv_out,
12652 t,
12653 pad,
12654 conv_dim,
12655 kernel,
12656 1,
12657 1,
12658 )?;
12659 match states_snap {
12660 Some(states) => {
12661 let state_len = nv * hv * hk;
12662 for tok in 0..t {
12663 if step_ok {
12664 launch_gdn_scan_step_at(
12665 eng, conv_out, &g_log, &beta_raw, state, o, tok, nk, nv, hk,
12666 hv, scale, eps,
12667 )?;
12668 } else {
12669 launch_gdn_scan_at(
12670 eng, conv_out, &g_log, &beta_raw, state, o, tok, nk, nv, hk,
12671 hv, scale, eps,
12672 )?;
12673 }
12674 eng.copy_range_into(states, tok * state_len, state, 0, state_len)?;
12675 }
12676 }
12677 None if t == 1 && step_ok => {
12678 launch_gdn_scan_step(
12679 eng, conv_out, &g_log, &beta_raw, state, o, nk, nv, hk, hv, scale, eps,
12680 )?;
12681 }
12682 None => {
12683 launch_gdn_scan(
12684 eng, conv_out, &g_log, &beta_raw, state, o, nk, nv, hk, hv, t, scale,
12685 eps,
12686 )?;
12687 }
12688 }
12689 if t >= pad {
12692 eng.copy_range_into(conv, 0, &qkv, (t - pad) * conv_dim, pad * conv_dim)?;
12693 } else {
12694 let keep = pad - t;
12695 let tmp = tmp.ok_or("qwen4exp_gpu: gdn conv roll needs the tmp slot")?;
12696 eng.copy_range_into(tmp, 0, conv, t * conv_dim, keep * conv_dim)?;
12697 eng.copy_range_into(conv, 0, tmp, 0, keep * conv_dim)?;
12698 eng.copy_range_into(conv, keep * conv_dim, &qkv, 0, t * conv_dim)?;
12699 }
12700 Ok(())
12701 };
12702 let graphable = stash.is_some() && verify_graphs_on() && step_ws_on() && !prof::on();
12705 match stash.as_deref_mut() {
12706 Some(st) if graphable => {
12707 let entry = match st.scan_graph.take() {
12710 Some((gt, g)) if gt == t => Some(g),
12711 _ => None,
12712 };
12713 let warm = st.scan_warm == Some(t);
12714 st.scan_warm = Some(t);
12715 let entry = match (warm, entry) {
12717 (false, _) => {
12720 chain(
12721 e,
12722 conv,
12723 state,
12724 Some(&mut st.states),
12725 &mut conv_out,
12726 &mut o,
12727 tmp.as_mut(),
12728 )?;
12729 None
12730 }
12731 (true, Some(g)) => {
12733 g.0.launch()?;
12734 Some(g)
12735 }
12736 (true, None) => {
12741 let states = &mut st.states;
12742 let mut tmp_ref = tmp.as_mut();
12743 let g = e.capture_graph_retained_nowarm(|eng| {
12744 chain(
12745 eng,
12746 conv,
12747 state,
12748 Some(states),
12749 &mut conv_out,
12750 &mut o,
12751 tmp_ref.as_deref_mut(),
12752 )
12753 })?;
12754 g.0.launch()?;
12755 Some(g)
12756 }
12757 };
12758 if let Some(g) = entry {
12759 st.scan_graph = Some((t, g));
12760 }
12761 }
12762 Some(st) => chain(
12763 e,
12764 conv,
12765 state,
12766 Some(&mut st.states),
12767 &mut conv_out,
12768 &mut o,
12769 tmp.as_mut(),
12770 )?,
12771 None => chain(e, conv, state, None, &mut conv_out, &mut o, tmp.as_mut())?,
12772 }
12773 ws.put_f32("gdn.conv_out", conv_out);
12774 if let Some(tmp) = tmp {
12775 ws.put_f32("gdn.tmp", tmp);
12776 }
12777 Ok(o)
12778 })?;
12779 ws.put_f32("gdn.qkv", qkv);
12780 ws.put_f32("gdn.beta", beta_raw);
12781 ws.put_f32("gdn.glog", g_log);
12782
12783 let out = prof_section(e, "gdn.norm_gate_out", || {
12784 let mut gated = ws.take_f32(e, "gdn.gated", t * nv * hv, 0)?;
12785 match p.gate_activation {
12786 GdnGateActivation::Sigmoid if gdn_fuse_on() => {
12789 launch_rms_sigmul(e, &o, &gdn.norm, &z, &mut gated, hv, t * nv, eps)?;
12790 }
12791 GdnGateActivation::Sigmoid => {
12792 let mut normed = ws.take_f32(e, "gdn.normed", t * nv * hv, 0)?;
12793 e.rms_norm(&o, &gdn.norm, &mut normed, hv, t * nv, eps)?;
12794 let mut sg = ws.take_f32(e, "gdn.sg", t * nv * hv, 0)?;
12795 e.sigmoid(&z, &mut sg, t * nv * hv)?;
12796 e.mul(&normed, &sg, &mut gated, t * nv * hv)?;
12797 ws.put_f32("gdn.sg", sg);
12798 ws.put_f32("gdn.normed", normed);
12799 }
12800 GdnGateActivation::Silu => {
12801 let mut normed = ws.take_f32(e, "gdn.normed", t * nv * hv, 0)?;
12802 e.rms_norm(&o, &gdn.norm, &mut normed, hv, t * nv, eps)?;
12803 e.silu_mul(&z, &normed, &mut gated, t * nv * hv)?;
12804 ws.put_f32("gdn.normed", normed);
12805 }
12806 }
12807 let mut out = ws.take_f32(e, "mixer.out", t * hidden, 0)?;
12808 linear_trunk_into(
12809 e,
12810 &gdn.out,
12811 &gdn.out_b16,
12812 &gated,
12813 &mut out,
12814 t,
12815 nv * hv,
12816 hidden,
12817 )?;
12818 ws.put_f32("gdn.gated", gated);
12819 Ok(out)
12820 })?;
12821 ws.put_f32("gdn.z", z);
12822 ws.put_f32("gdn.o", o);
12823 Ok(out)
12824 }
12825
12826 fn moe_forward(
12830 &self,
12831 e: &Engine,
12832 ws: &mut StepPool,
12833 moe: &MoeW,
12834 mixed: &CudaSlice<f32>,
12835 t: usize,
12836 rows_grouped: bool,
12840 layer: u32,
12844 ) -> Res<CudaSlice<f32>> {
12845 let hidden = self.hidden;
12846 let experts = moe.plan.expert_count as usize;
12847 let selected = moe.plan.experts_per_token as usize;
12848 let ff = moe.plan.expert_intermediate_size as usize;
12849
12850 let nvfp4_bank = matches!(
12859 (&moe.bank.gate, &moe.bank.up, &moe.bank.down),
12860 (
12861 BankHalf::Nvfp4 { .. },
12862 BankHalf::Nvfp4 { .. },
12863 BankHalf::Nvfp4 { .. }
12864 )
12865 );
12866 let devbf16_bank = matches!(
12867 (&moe.bank.gate, &moe.bank.up, &moe.bank.down),
12868 (
12869 BankHalf::DeviceBf16(_),
12870 BankHalf::DeviceBf16(_),
12871 BankHalf::DeviceBf16(_)
12872 )
12873 );
12874 let use_dev_router = router_dev_on()
12875 && moe_sel_path_on()
12876 && route_dev_geometry(experts, selected)
12877 && ((nvfp4_bank
12878 && hidden % 32 == 0
12879 && ff % 4 == 0
12880 && (t == 1
12881 || (rows_grouped
12882 && verify_mt_on()
12883 && sel_gufuse_on()
12884 && t * selected <= 8192)))
12885 || (devbf16_bank && hidden % 8 == 0 && ff % 8 == 0 && (t == 1 || rows_grouped)));
12886 type DevRoute = (CudaSlice<i32>, CudaSlice<f32>, Option<CudaSlice<i32>>);
12889 let (routes, mut dev_route): (Vec<Vec<(usize, f32)>>, Option<DevRoute>) =
12890 prof_section(e, "moe.router", || {
12891 let mut router_out = ws.take_f32(e, "moe.router", t * experts, 0)?;
12892 let none: Option<CudaSlice<u8>> = None;
12893 let rb = if router_bf16_on() {
12894 &moe.router_b16
12895 } else {
12896 &none
12897 };
12898 linear_trunk_into(
12899 e,
12900 &moe.router,
12901 rb,
12902 mixed,
12903 &mut router_out,
12904 t,
12905 hidden,
12906 experts,
12907 )?;
12908 if use_dev_router {
12909 let mut sel = ws.take_i32_slot(e, "moe.sel", t * selected, 0)?;
12910 let mut w = ws.take_f32(e, "moe.w", t * selected, 0)?;
12911 let mut tokm = if t > 1 {
12912 Some(ws.take_i32_slot(e, "moe.tok", t * selected, 0)?)
12913 } else {
12914 None
12915 };
12916 route_topk_device(
12917 e,
12918 &router_out,
12919 &mut sel,
12920 &mut w,
12921 tokm.as_mut().map(|m| (m, 0)),
12922 experts,
12923 selected,
12924 t,
12925 layer,
12926 )?;
12927 ws.put_f32("moe.router", router_out);
12928 return Ok((Vec::new(), Some((sel, w, tokm))));
12929 }
12930 let logits = e.dtoh_view(&router_out.slice(0..t * experts))?;
12931 ws.put_f32("moe.router", router_out);
12932 let mut routes: Vec<Vec<(usize, f32)>> = Vec::with_capacity(t);
12933 for token in 0..t {
12934 routes.push(host_route_softmax_topk(
12935 &logits[token * experts..(token + 1) * experts],
12936 selected,
12937 ));
12938 }
12939 Ok((routes, None))
12940 })?;
12941 if (t == 1 || rows_grouped) && moe_sel_path_on() {
12949 if let (
12950 BankHalf::Nvfp4 {
12951 codes: gc,
12952 scales: gs,
12953 macros_dev: gm,
12954 ..
12955 },
12956 BankHalf::Nvfp4 {
12957 codes: uc,
12958 scales: us,
12959 macros_dev: um,
12960 ..
12961 },
12962 BankHalf::Nvfp4 {
12963 codes: dc,
12964 scales: ds,
12965 macros_dev: dm,
12966 ..
12967 },
12968 ) = (&moe.bank.gate, &moe.bank.up, &moe.bank.down)
12969 {
12970 if t > 1 && verify_mt_on() && sel_gufuse_on() && hidden % 32 == 0 && ff % 4 == 0 {
12976 if let Some((sel, w_dev, tokm)) = dev_route.take() {
12982 let tokm =
12983 tokm.ok_or("moe_forward: device route at t > 1 without a tok map")?;
12984 let out = prof_section(e, "moe.sel_grouped", || {
12985 let mut out = ws.take_f32(e, "moe.out", t * hidden, 0)?;
12986 let s_total = t * selected;
12987 let mut act = ws.take_f32(e, "moe.act", s_total * ff, 0)?;
12988 launch_nvfp4_sel_gu_silu(
12989 e,
12990 (gc, gs, gm),
12991 (uc, us, um),
12992 Some(&sel),
12993 0,
12994 s_total,
12995 mixed,
12996 &mut act,
12997 hidden,
12998 ff,
12999 Some((&tokm, hidden)),
13000 )?;
13001 let mut partial = ws.take_f32(e, "moe.partial", s_total * hidden, 0)?;
13002 launch_nvfp4_sel_matvec(
13003 e,
13004 dc,
13005 ds,
13006 dm,
13007 &sel,
13008 &act,
13009 &mut partial,
13010 s_total,
13011 ff,
13012 hidden,
13013 ff,
13014 )?;
13015 for tok in 0..t {
13016 launch_axpy_rows_seq_at(
13017 e,
13018 &partial,
13019 tok * selected,
13020 &w_dev,
13021 tok * selected,
13022 &mut out,
13023 tok,
13024 hidden,
13025 selected,
13026 )?;
13027 }
13028 ws.put_i32("moe.sel", sel);
13029 ws.put_i32("moe.tok", tokm);
13030 ws.put_f32("moe.w", w_dev);
13031 ws.put_f32("moe.act", act);
13032 ws.put_f32("moe.partial", partial);
13033 Ok(out)
13034 })?;
13035 return self.moe_shared_tail(e, ws, moe, mixed, out, t);
13036 }
13037 let out = prof_section(e, "moe.sel_grouped", || {
13038 let mut out = ws.take_f32(e, "moe.out", t * hidden, 0)?;
13039 const SLOT_CAP: usize = 8192;
13049 let tok_step = (SLOT_CAP / selected.max(1)).max(1);
13050 let mut tok0 = 0usize;
13051 while tok0 < t {
13052 let tok_n = tok_step.min(t - tok0);
13053 let batch = &routes[tok0..tok0 + tok_n];
13054 let mut sel_all: Vec<i32> = Vec::with_capacity(tok_n * selected);
13055 let mut w_all: Vec<f32> = Vec::with_capacity(tok_n * selected);
13056 let mut tok_all: Vec<i32> = Vec::with_capacity(tok_n * selected);
13057 let mut ranges: Vec<(usize, usize)> = Vec::with_capacity(tok_n);
13058 for (i, route) in batch.iter().enumerate() {
13059 ranges.push((sel_all.len(), route.len()));
13060 for &(eid, wgt) in route {
13061 sel_all.push(eid as i32);
13062 w_all.push(wgt);
13063 tok_all.push((tok0 + i) as i32);
13068 }
13069 }
13070 let s_total = sel_all.len();
13071 let sel = ws.take_i32(e, "moe.sel", &sel_all, 0)?;
13072 let w_dev = ws.take_f32_h2d(e, "moe.w", &w_all, 0)?;
13073 let tokm = ws.take_i32(e, "moe.tok", &tok_all, 0)?;
13074 let mut act = ws.take_f32(e, "moe.act", s_total * ff, 0)?;
13075 launch_nvfp4_sel_gu_silu(
13076 e,
13077 (gc, gs, gm),
13078 (uc, us, um),
13079 Some(&sel),
13080 0,
13081 s_total,
13082 mixed,
13083 &mut act,
13084 hidden,
13085 ff,
13086 Some((&tokm, hidden)),
13087 )?;
13088 let mut partial = ws.take_f32(e, "moe.partial", s_total * hidden, 0)?;
13089 launch_nvfp4_sel_matvec(
13090 e,
13091 dc,
13092 ds,
13093 dm,
13094 &sel,
13095 &act,
13096 &mut partial,
13097 s_total,
13098 ff,
13099 hidden,
13100 ff,
13101 )?;
13102 for (i, &(start, len)) in ranges.iter().enumerate() {
13103 launch_axpy_rows_seq_at(
13104 e,
13105 &partial,
13106 start,
13107 &w_dev,
13108 start,
13109 &mut out,
13110 tok0 + i,
13111 hidden,
13112 len,
13113 )?;
13114 }
13115 ws.put_i32("moe.sel", sel);
13116 ws.put_i32("moe.tok", tokm);
13117 ws.put_f32("moe.w", w_dev);
13118 ws.put_f32("moe.act", act);
13119 ws.put_f32("moe.partial", partial);
13120 tok0 += tok_n;
13121 }
13122 Ok(out)
13123 })?;
13124 return self.moe_shared_tail(e, ws, moe, mixed, out, t);
13125 }
13126 if let Some((sel, w_dev, _)) = dev_route.take() {
13130 let out = prof_section(e, "moe.sel_grouped", || {
13131 let mut out = ws.take_f32(e, "moe.out", hidden, 0)?;
13132 let mut act = ws.take_f32(e, "moe.act", selected * ff, 0)?;
13133 if sel_gufuse_on() && hidden % 32 == 0 && ff % 4 == 0 {
13134 launch_nvfp4_sel_gu_silu(
13135 e,
13136 (gc, gs, gm),
13137 (uc, us, um),
13138 Some(&sel),
13139 0,
13140 selected,
13141 mixed,
13142 &mut act,
13143 hidden,
13144 ff,
13145 None,
13146 )?;
13147 } else {
13148 let mut yg = ws.take_f32(e, "moe.yg", selected * ff, 0)?;
13149 let mut yu = ws.take_f32(e, "moe.yu", selected * ff, 0)?;
13150 launch_nvfp4_sel_matvec(
13151 e, gc, gs, gm, &sel, mixed, &mut yg, selected, hidden, ff, 0,
13152 )?;
13153 launch_nvfp4_sel_matvec(
13154 e, uc, us, um, &sel, mixed, &mut yu, selected, hidden, ff, 0,
13155 )?;
13156 e.silu_mul(&yg, &yu, &mut act, selected * ff)?;
13157 ws.put_f32("moe.yg", yg);
13158 ws.put_f32("moe.yu", yu);
13159 }
13160 let mut partial = ws.take_f32(e, "moe.partial", selected * hidden, 0)?;
13161 launch_nvfp4_sel_matvec(
13162 e,
13163 dc,
13164 ds,
13165 dm,
13166 &sel,
13167 &act,
13168 &mut partial,
13169 selected,
13170 ff,
13171 hidden,
13172 ff,
13173 )?;
13174 e.axpy_rows_seq_into(&partial, &w_dev, &mut out, hidden, selected)?;
13175 ws.put_i32("moe.sel", sel);
13176 ws.put_f32("moe.w", w_dev);
13177 ws.put_f32("moe.act", act);
13178 ws.put_f32("moe.partial", partial);
13179 Ok(out)
13180 })?;
13181 return self.moe_shared_tail(e, ws, moe, mixed, out, t);
13182 }
13183 let out = prof_section(e, "moe.sel_grouped", || {
13184 let mut out = ws.take_f32(e, "moe.out", t * hidden, 0)?;
13185 for (tok, route) in routes.iter().enumerate() {
13186 let n_sel = route.len();
13187 let sel_host: Vec<i32> = route.iter().map(|&(x, _)| x as i32).collect();
13188 let w_host: Vec<f32> = route.iter().map(|&(_, w)| w).collect();
13189 let sel = ws.take_i32(e, "moe.sel", &sel_host, 0)?;
13190 let w_dev = ws.take_f32_h2d(e, "moe.w", &w_host, 0)?;
13191 let x_tok = if t == 1 {
13196 None
13197 } else {
13198 let mut x = ws.take_f32(e, "moe.x", hidden, 0)?;
13199 e.copy_range_into(&mut x, 0, mixed, tok * hidden, hidden)?;
13200 Some(x)
13201 };
13202 let x_ref = x_tok.as_ref().unwrap_or(mixed);
13203 let mut act = ws.take_f32(e, "moe.act", n_sel * ff, 0)?;
13204 if sel_gufuse_on() && hidden % 32 == 0 && ff % 4 == 0 {
13207 launch_nvfp4_sel_gu_silu(
13208 e,
13209 (gc, gs, gm),
13210 (uc, us, um),
13211 Some(&sel),
13212 0,
13213 n_sel,
13214 x_ref,
13215 &mut act,
13216 hidden,
13217 ff,
13218 None,
13219 )?;
13220 } else {
13221 let mut yg = ws.take_f32(e, "moe.yg", n_sel * ff, 0)?;
13222 let mut yu = ws.take_f32(e, "moe.yu", n_sel * ff, 0)?;
13223 launch_nvfp4_sel_matvec(
13224 e, gc, gs, gm, &sel, x_ref, &mut yg, n_sel, hidden, ff, 0,
13225 )?;
13226 launch_nvfp4_sel_matvec(
13227 e, uc, us, um, &sel, x_ref, &mut yu, n_sel, hidden, ff, 0,
13228 )?;
13229 e.silu_mul(&yg, &yu, &mut act, n_sel * ff)?;
13230 ws.put_f32("moe.yg", yg);
13231 ws.put_f32("moe.yu", yu);
13232 }
13233 let mut partial = ws.take_f32(e, "moe.partial", n_sel * hidden, 0)?;
13234 launch_nvfp4_sel_matvec(
13235 e,
13236 dc,
13237 ds,
13238 dm,
13239 &sel,
13240 &act,
13241 &mut partial,
13242 n_sel,
13243 ff,
13244 hidden,
13245 ff,
13246 )?;
13247 if t == 1 {
13250 e.axpy_rows_seq_into(&partial, &w_dev, &mut out, hidden, n_sel)?;
13251 } else {
13252 let mut row = ws.take_f32(e, "moe.row", hidden, 0)?;
13253 e.axpy_rows_seq_into(&partial, &w_dev, &mut row, hidden, n_sel)?;
13254 e.copy_range_into(&mut out, tok * hidden, &row, 0, hidden)?;
13255 ws.put_f32("moe.row", row);
13256 }
13257 ws.put_i32("moe.sel", sel);
13258 ws.put_f32("moe.w", w_dev);
13259 ws.put_f32("moe.act", act);
13260 ws.put_f32("moe.partial", partial);
13261 if let Some(x) = x_tok {
13262 ws.put_f32("moe.x", x);
13263 }
13264 }
13265 Ok(out)
13266 })?;
13267 return self.moe_shared_tail(e, ws, moe, mixed, out, t);
13268 }
13269 if let (BankHalf::DeviceBf16(gb), BankHalf::DeviceBf16(ub), BankHalf::DeviceBf16(db)) =
13274 (&moe.bank.gate, &moe.bank.up, &moe.bank.down)
13275 {
13276 if let Some((sel, w_dev, _)) = dev_route.take() {
13283 let out = prof_section(e, "moe.sel_bf16", || {
13284 let mut out = ws.take_f32(e, "moe.out", t * hidden, 0)?;
13285 for tok in 0..t {
13286 let mut yg = ws.take_f32(e, "moe.yg", selected * ff, 0)?;
13287 let mut yu = ws.take_f32(e, "moe.yu", selected * ff, 0)?;
13288 launch_qmatvec_bf16w_sel(
13289 e,
13290 gb,
13291 &sel,
13292 tok * selected,
13293 mixed,
13294 tok * hidden,
13295 0,
13296 &mut yg,
13297 selected,
13298 hidden,
13299 ff,
13300 )?;
13301 launch_qmatvec_bf16w_sel(
13302 e,
13303 ub,
13304 &sel,
13305 tok * selected,
13306 mixed,
13307 tok * hidden,
13308 0,
13309 &mut yu,
13310 selected,
13311 hidden,
13312 ff,
13313 )?;
13314 let mut act = ws.take_f32(e, "moe.act", selected * ff, 0)?;
13315 e.silu_mul(&yg, &yu, &mut act, selected * ff)?;
13316 let mut partial =
13317 ws.take_f32(e, "moe.partial", selected * hidden, 0)?;
13318 launch_qmatvec_bf16w_sel(
13319 e,
13320 db,
13321 &sel,
13322 tok * selected,
13323 &act,
13324 0,
13325 ff,
13326 &mut partial,
13327 selected,
13328 ff,
13329 hidden,
13330 )?;
13331 launch_axpy_rows_seq_at(
13332 e,
13333 &partial,
13334 0,
13335 &w_dev,
13336 tok * selected,
13337 &mut out,
13338 tok,
13339 hidden,
13340 selected,
13341 )?;
13342 ws.put_f32("moe.yg", yg);
13343 ws.put_f32("moe.yu", yu);
13344 ws.put_f32("moe.act", act);
13345 ws.put_f32("moe.partial", partial);
13346 }
13347 ws.put_i32("moe.sel", sel);
13348 ws.put_f32("moe.w", w_dev);
13349 Ok(out)
13350 })?;
13351 return self.moe_shared_tail(e, ws, moe, mixed, out, t);
13352 }
13353 let out = prof_section(e, "moe.sel_bf16", || {
13354 let mut out = ws.take_f32(e, "moe.out", t * hidden, 0)?;
13355 for (tok, route) in routes.iter().enumerate() {
13356 let n_sel = route.len();
13357 let w_host: Vec<f32> = route.iter().map(|&(_, w)| w).collect();
13358 let w_dev = ws.take_f32_h2d(e, "moe.w", &w_host, 0)?;
13359 let mut yg = ws.take_f32(e, "moe.yg", n_sel * ff, 0)?;
13360 let mut yu = ws.take_f32(e, "moe.yu", n_sel * ff, 0)?;
13361 for (slot, &(eid, _)) in route.iter().enumerate() {
13362 launch_qmatvec_bf16w_off_into(
13363 e,
13364 gb,
13365 eid * ff,
13366 mixed,
13367 tok * hidden,
13368 &mut yg,
13369 slot * ff,
13370 hidden,
13371 ff,
13372 )?;
13373 launch_qmatvec_bf16w_off_into(
13374 e,
13375 ub,
13376 eid * ff,
13377 mixed,
13378 tok * hidden,
13379 &mut yu,
13380 slot * ff,
13381 hidden,
13382 ff,
13383 )?;
13384 }
13385 let mut act = ws.take_f32(e, "moe.act", n_sel * ff, 0)?;
13386 e.silu_mul(&yg, &yu, &mut act, n_sel * ff)?;
13387 let mut partial = ws.take_f32(e, "moe.partial", n_sel * hidden, 0)?;
13388 for (slot, &(eid, _)) in route.iter().enumerate() {
13389 launch_qmatvec_bf16w_off_into(
13390 e,
13391 db,
13392 eid * hidden,
13393 &act,
13394 slot * ff,
13395 &mut partial,
13396 slot * hidden,
13397 ff,
13398 hidden,
13399 )?;
13400 }
13401 let mut row = ws.take_f32(e, "moe.row", hidden, 0)?;
13402 e.axpy_rows_seq_into(&partial, &w_dev, &mut row, hidden, n_sel)?;
13403 e.copy_range_into(&mut out, tok * hidden, &row, 0, hidden)?;
13404 ws.put_f32("moe.row", row);
13405 ws.put_f32("moe.w", w_dev);
13406 ws.put_f32("moe.yg", yg);
13407 ws.put_f32("moe.yu", yu);
13408 ws.put_f32("moe.act", act);
13409 ws.put_f32("moe.partial", partial);
13410 }
13411 Ok(out)
13412 })?;
13413 return self.moe_shared_tail(e, ws, moe, mixed, out, t);
13414 }
13415 }
13416
13417 if dev_route.is_some() {
13421 return Err(
13422 "moe_forward: device route left unconsumed (engage guard drifted from the \
13423 dispatch arms)"
13424 .into(),
13425 );
13426 }
13427 let mut by_expert: Vec<Vec<(i32, i32, f32)>> = vec![Vec::new(); experts];
13429 for (token, token_routes) in routes.iter().enumerate() {
13430 for (slot, &(expert, weight)) in token_routes.iter().enumerate() {
13431 by_expert[expert].push((token as i32, slot as i32, weight));
13432 }
13433 }
13434 let mut slots = e.zeros(t * selected * hidden)?;
13435 let mut wbuf = e.zeros(t * selected)?;
13436 for (expert, entries) in by_expert.iter().enumerate() {
13437 if entries.is_empty() {
13438 continue;
13439 }
13440 let m_e = entries.len();
13441 let (tok_dev, slot_dev, w_dev, xg) = prof_section(e, "moe.idx_gather", || {
13442 let tok_idx: Vec<i32> = entries.iter().map(|&(tok, _, _)| tok).collect();
13443 let slot_idx: Vec<i32> = entries.iter().map(|&(_, slot, _)| slot).collect();
13444 let weights: Vec<f32> = entries.iter().map(|&(_, _, w)| w).collect();
13445 let tok_dev = e.htod_i32(&tok_idx)?;
13446 let slot_dev = e.htod_i32(&slot_idx)?;
13447 let w_dev = e.htod(&weights)?;
13448 let mut xg = e.uninit(m_e * hidden)?;
13449 e.gather_rows(mixed, &tok_dev, &mut xg, hidden, m_e)?;
13450 Ok((tok_dev, slot_dev, w_dev, xg))
13451 })?;
13452 let resolve = |half: &BankHalf,
13455 out_f: usize,
13456 in_f: usize|
13457 -> Res<(Option<CudaSlice<f32>>, usize)> {
13458 match half {
13459 BankHalf::F32(_) => Ok((None, expert * out_f * in_f)),
13460 BankHalf::Nvfp4 {
13461 codes,
13462 scales,
13463 macros,
13464 ..
13465 } => Ok((
13466 Some(dequant_nvfp4_expert_f32(
13467 e,
13468 codes,
13469 scales,
13470 macros[expert],
13471 expert,
13472 out_f,
13473 in_f,
13474 )?),
13475 0,
13476 )),
13477 BankHalf::HostBf16(bytes) => {
13480 let row_bytes = out_f * in_f * 2;
13481 let dev =
13482 e.htod_bytes(&bytes[expert * row_bytes..(expert + 1) * row_bytes])?;
13483 Ok((
13484 Some(e.bf16_to_f32(&dev.slice(0..row_bytes), out_f * in_f)?),
13485 0,
13486 ))
13487 }
13488 BankHalf::DeviceBf16(bytes) => {
13492 let row_bytes = out_f * in_f * 2;
13493 let view = bytes.slice(expert * row_bytes..(expert + 1) * row_bytes);
13494 Ok((Some(e.bf16_to_f32(&view, out_f * in_f)?), 0))
13495 }
13496 }
13497 };
13498 let ((gate_owned, gate_base), (up_owned, up_base), (down_owned, down_base)) =
13499 prof_section(e, "moe.dequant", || {
13500 Ok((
13501 resolve(&moe.bank.gate, ff, hidden)?,
13502 resolve(&moe.bank.up, ff, hidden)?,
13503 resolve(&moe.bank.down, hidden, ff)?,
13504 ))
13505 })?;
13506 let gate_view = match (&moe.bank.gate, &gate_owned) {
13507 (_, Some(owned)) => owned.slice(0..ff * hidden),
13508 (BankHalf::F32(bank), None) => bank.slice(gate_base..gate_base + ff * hidden),
13509 (
13510 BankHalf::Nvfp4 { .. } | BankHalf::HostBf16(_) | BankHalf::DeviceBf16(_),
13511 None,
13512 ) => {
13513 unreachable!("quantized/host/device-bf16 halves always resolve owned")
13514 }
13515 };
13516 let up_view = match (&moe.bank.up, &up_owned) {
13517 (_, Some(owned)) => owned.slice(0..ff * hidden),
13518 (BankHalf::F32(bank), None) => bank.slice(up_base..up_base + ff * hidden),
13519 (
13520 BankHalf::Nvfp4 { .. } | BankHalf::HostBf16(_) | BankHalf::DeviceBf16(_),
13521 None,
13522 ) => {
13523 unreachable!("quantized/host/device-bf16 halves always resolve owned")
13524 }
13525 };
13526 let down_view = match (&moe.bank.down, &down_owned) {
13527 (_, Some(owned)) => owned.slice(0..hidden * ff),
13528 (BankHalf::F32(bank), None) => bank.slice(down_base..down_base + hidden * ff),
13529 (
13530 BankHalf::Nvfp4 { .. } | BankHalf::HostBf16(_) | BankHalf::DeviceBf16(_),
13531 None,
13532 ) => {
13533 unreachable!("quantized/host/device-bf16 halves always resolve owned")
13534 }
13535 };
13536 prof_section(e, "moe.expert_gemms", || {
13537 let down_out =
13538 run_routed_expert(e, &xg, &gate_view, &up_view, &down_view, m_e, hidden, ff)?;
13539 e.scatter_slot(
13540 &down_out, &tok_dev, &slot_dev, &w_dev, &mut slots, &mut wbuf, hidden,
13541 selected, m_e,
13542 )
13543 })?;
13544 }
13545 let out = prof_section(e, "moe.reduce", || {
13546 let mut out = e.zeros(t * hidden)?;
13547 e.reduce_slots(&slots, &wbuf, &mut out, hidden, selected, t)?;
13548 Ok(out)
13549 })?;
13550 self.moe_shared_tail(e, ws, moe, mixed, out, t)
13551 }
13552
13553 fn moe_shared_tail(
13556 &self,
13557 e: &Engine,
13558 ws: &mut StepPool,
13559 moe: &MoeW,
13560 mixed: &CudaSlice<f32>,
13561 mut out: CudaSlice<f32>,
13562 t: usize,
13563 ) -> Res<CudaSlice<f32>> {
13564 let hidden = self.hidden;
13565 let sff = moe
13566 .plan
13567 .shared
13568 .as_ref()
13569 .map(|s| s.intermediate_size as usize)
13570 .unwrap_or(0);
13571 if sff > 0 {
13572 prof_section(e, "moe.shared", || {
13573 let none: Option<CudaSlice<u8>> = None;
13576 let (gu, db) = if micro_shexp_on() {
13577 (&moe.shared_gu_b16, &moe.shared_down_b16)
13578 } else {
13579 (&none, &none)
13580 };
13581 let mut gate = ws.take_f32(e, "moe.sh_gate", t * sff, 0)?;
13582 let mut up = ws.take_f32(e, "moe.sh_up", t * sff, 0)?;
13583 if let (true, Some(stack)) =
13586 (t == 1 && proj_stack_on() && trunk_bf16_on(), gu.as_ref())
13587 {
13588 launch_qmatvec_bf16w_multi4(
13589 e,
13590 stack,
13591 mixed,
13592 &[(&gate, sff), (&up, sff)],
13593 hidden,
13594 )?;
13595 } else {
13596 linear_trunk_stacked_into(
13597 e,
13598 &moe.shared_gate,
13599 gu,
13600 0,
13601 mixed,
13602 &mut gate,
13603 t,
13604 hidden,
13605 sff,
13606 )?;
13607 linear_trunk_stacked_into(
13608 e,
13609 &moe.shared_up,
13610 gu,
13611 sff,
13612 mixed,
13613 &mut up,
13614 t,
13615 hidden,
13616 sff,
13617 )?;
13618 }
13619 let mut act = ws.take_f32(e, "moe.sh_act", t * sff, 0)?;
13620 e.silu_mul(&gate, &up, &mut act, t * sff)?;
13621 let mut shared = ws.take_f32(e, "moe.sh_down", t * hidden, 0)?;
13622 linear_trunk_into(e, &moe.shared_down, db, &act, &mut shared, t, sff, hidden)?;
13623 if let Some(input_gate) = moe.shared_input_gate.as_ref() {
13624 let mut g = ws.take_f32(e, "moe.g", t, 0)?;
13627 e.sigmoid_dot_rows_into(mixed, input_gate, &mut g, hidden, t)?;
13628 e.add_scaled_rows(&shared, &g, &mut out, hidden, t)?;
13629 ws.put_f32("moe.g", g);
13630 } else {
13631 let mut view = out.slice_mut(0..t * hidden);
13632 e.axpy_into(&shared, 1.0, &mut view, t * hidden)?;
13633 }
13634 ws.put_f32("moe.sh_gate", gate);
13635 ws.put_f32("moe.sh_up", up);
13636 ws.put_f32("moe.sh_act", act);
13637 ws.put_f32("moe.sh_down", shared);
13638 Ok(())
13639 })?;
13640 }
13641 Ok(out)
13642 }
13643
13644 #[allow(clippy::too_many_arguments)]
13648 fn ple_block(
13649 &self,
13650 e: &Engine,
13651 layer: &LayerW,
13652 ple: &PleW,
13653 table: &NgramTable,
13654 ple_state: &mut PleState,
13655 planes: &mut [CudaSlice<f32>],
13656 tokens: &[u32],
13657 t: usize,
13658 exact: bool,
13662 mut stash: Option<&mut PleStash>,
13663 ) -> Res<()> {
13664 let hidden = self.hidden;
13665 let streams = self.streams;
13666 let plan = &ple.plan;
13667 let heads = plan.ngram_heads as usize;
13668 let head_dim = plan.head_embed_dim as usize;
13669 let embed_dim = plan.embed_dim as usize;
13670 let kernel = plan.conv_kernel as usize;
13671 let max_ngram = plan.max_ngram as usize;
13672 let dilation = max_ngram;
13673 let pad = (kernel - 1) * dilation;
13674 let eps = layer.eps_attn;
13675
13676 let gathered = prof_section(e, "ple.host_ngram_gather", || {
13678 let total_heads = heads;
13679 let mut ids: Vec<i64> = Vec::new();
13683 if ple_cache_on() {
13684 host_ngram_ids_cached(
13685 &mut ple_state.ngram_ids,
13686 &mut ple_state.ngram_history,
13687 &mut ple_state.ngram_last_eos,
13688 tokens,
13689 &ple.multipliers,
13690 &ple.sizes,
13691 &ple.offsets,
13692 max_ngram,
13693 heads / (max_ngram - 1),
13694 plan.eos_token_id,
13695 );
13696 if ple_cache_audit_on() {
13697 let twin = host_ngram_ids(
13698 tokens,
13699 &ple.multipliers,
13700 &ple.sizes,
13701 &ple.offsets,
13702 max_ngram,
13703 heads / (max_ngram - 1),
13704 plan.eos_token_id,
13705 );
13706 let from = (tokens.len() - t) * total_heads;
13707 let mism = twin[from..]
13708 .iter()
13709 .zip(&ple_state.ngram_ids[from..])
13710 .filter(|(a, b)| a != b)
13711 .count() as u64;
13712 PLE_CACHE_AUDIT_ROWS.fetch_add(t as u64, std::sync::atomic::Ordering::Relaxed);
13713 PLE_CACHE_AUDIT_MISMATCH.fetch_add(mism, std::sync::atomic::Ordering::Relaxed);
13714 PLE_CACHE_AUDIT_MAX_FILL
13715 .fetch_max(tokens.len() as u64, std::sync::atomic::Ordering::Relaxed);
13716 if mism > 0 {
13717 return Err(format!(
13718 "plecache audit: {mism} cached n-gram ids differ from the full twin \
13719 at history {} (t={t})",
13720 tokens.len()
13721 )
13722 .into());
13723 }
13724 }
13725 } else {
13726 ids = host_ngram_ids(
13727 tokens,
13728 &ple.multipliers,
13729 &ple.sizes,
13730 &ple.offsets,
13731 max_ngram,
13732 heads / (max_ngram - 1),
13733 plan.eos_token_id,
13734 );
13735 }
13736 let all_ids: &[i64] = if ple_cache_on() {
13737 &ple_state.ngram_ids
13738 } else {
13739 &ids
13740 };
13741 let chunk_ids = &all_ids[(tokens.len() - t) * total_heads..];
13742 let table_rows = table.rows(head_dim);
13743 let mut gathered = vec![0.0f32; t * embed_dim];
13744 for token in 0..t {
13745 for head in 0..heads {
13746 let id = chunk_ids[token * total_heads + head];
13747 if id < 0 || id as usize >= table_rows {
13748 return Err("qwen4exp_gpu: n-gram id outside the embedding table".into());
13749 }
13750 table.gather_into(
13751 id as usize,
13752 head_dim,
13753 &mut gathered[token * embed_dim + head * head_dim
13754 ..token * embed_dim + (head + 1) * head_dim],
13755 );
13756 }
13757 }
13758 Ok(gathered)
13759 })?;
13760 let emb = prof_section(e, "ple.h2d", || e.htod(&gathered))?;
13761
13762 let lin_rows = |x: &CudaSlice<f32>,
13765 w: &CudaSlice<f32>,
13766 in_f: usize,
13767 out_f: usize|
13768 -> Res<CudaSlice<f32>> {
13769 let mut out = e.uninit(t * out_f)?;
13770 if exact && t > 1 {
13771 let wv = w.slice(0..w.len());
13772 for tok in 0..t {
13773 let xv = x.slice(tok * in_f..(tok + 1) * in_f);
13774 let mut yv = out.slice_mut(tok * out_f..(tok + 1) * out_f);
13775 e.linear_device_into(&xv, &wv, &mut yv, 1, in_f, out_f)?;
13776 }
13777 } else {
13778 e.linear_device_into(x, w, &mut out, t, in_f, out_f)?;
13779 }
13780 Ok(out)
13781 };
13782 let (value, mut dots_host) = prof_section(e, "ple.key_gate", || {
13783 let value = lin_rows(&emb, &ple.value_proj, embed_dim, hidden)?;
13784 let ones = e.htod(&vec![1.0f32; hidden])?;
13785 let mut dots_host = vec![0.0f32; streams * t];
13786 for s in 0..streams {
13787 let key = lin_rows(&emb, &ple.key_proj[s], embed_dim, hidden)?;
13788 let mut key_normed = e.uninit(t * hidden)?;
13789 e.rms_norm(&key, &ple.norm_key[s], &mut key_normed, hidden, t, eps)?;
13790 let mut query = e.uninit(t * hidden)?;
13791 e.rms_norm(&planes[s], &ple.norm_query[s], &mut query, hidden, t, eps)?;
13792 let mut prod = e.uninit(t * hidden)?;
13793 e.mul(&key_normed, &query, &mut prod, t * hidden)?;
13794 let dots = lin_rows(&prod, &ones, hidden, 1)?;
13795 dots_host[s * t..(s + 1) * t].copy_from_slice(&e.dtoh(&dots)?);
13796 }
13797 Ok((value, dots_host))
13798 })?;
13799 for dot in dots_host.iter_mut() {
13801 let gate = *dot / (hidden as f32).sqrt();
13802 let magnitude = gate.abs().max(1e-6).sqrt();
13803 let signed = if gate > 0.0 {
13804 magnitude
13805 } else if gate < 0.0 {
13806 -magnitude
13807 } else {
13808 0.0
13809 };
13810 *dot = host_sigmoid(signed);
13811 }
13812
13813 prof_section(e, "ple.conv_write", || {
13814 for s in 0..streams {
13815 let g = e.htod(&dots_host[s * t..(s + 1) * t])?;
13816 let mut gated = e.zeros(t * hidden)?;
13817 e.add_scaled_rows(&value, &g, &mut gated, hidden, t)?;
13818 let mut normed = e.uninit(t * hidden)?;
13819 e.rms_norm(&gated, &ple.norm_conv[s], &mut normed, hidden, t, eps)?;
13820 if let Some(st) = stash.as_deref_mut() {
13823 e.copy_range_into(
13824 &mut st.hist_pre[s],
13825 0,
13826 &ple_state.conv_hist[s],
13827 0,
13828 pad * hidden,
13829 )?;
13830 e.copy_range_into(&mut st.normed_rows[s], 0, &normed, 0, t * hidden)?;
13831 }
13832 launch_dwconv(
13834 e,
13835 &normed,
13836 &ple_state.conv_hist[s],
13837 &ple.conv_w[s],
13838 &mut gated,
13839 t,
13840 pad,
13841 hidden,
13842 kernel,
13843 dilation,
13844 2,
13845 )?;
13846 let hist = &mut ple_state.conv_hist[s];
13848 if t >= pad {
13849 e.copy_range_into(hist, 0, &normed, (t - pad) * hidden, pad * hidden)?;
13850 } else {
13851 let keep = pad - t;
13852 let mut tmp = e.uninit(keep * hidden)?;
13853 e.copy_range_into(&mut tmp, 0, hist, t * hidden, keep * hidden)?;
13854 e.copy_range_into(hist, 0, &tmp, 0, keep * hidden)?;
13855 e.copy_range_into(hist, keep * hidden, &normed, 0, t * hidden)?;
13856 }
13857 let mut view = planes[s].slice_mut(0..t * hidden);
13859 e.axpy_into(&gated, 1.0, &mut view, t * hidden)?;
13860 }
13861 Ok(())
13862 })
13863 }
13864}
13865
13866pub struct MtpDraftState {
13874 mixer: MixerState,
13875 rows: usize,
13877 pub committed: usize,
13880 capacity: usize,
13881 ws: StepPool,
13882}
13883
13884impl MtpDraftState {
13885 pub fn rows(&self) -> usize {
13886 self.rows
13887 }
13888}
13889
13890#[derive(Clone, Copy)]
13896enum DraftTokSrc<'a> {
13897 Host(&'a [u32]),
13898 HostDev(&'a [u32]),
13899 DevSlot(&'a CudaSlice<u32>, usize),
13900}
13901
13902impl Qwen4ExpGpu {
13903 pub fn has_mtp(&self) -> bool {
13904 self.mtp.is_some()
13905 }
13906
13907 pub fn mtp_on_dev1(&self) -> bool {
13910 self.mtp_dev1.is_some()
13911 }
13912
13913 fn check_draft_engine(&self, e: &Engine) -> Res<()> {
13917 if let Some(d) = self.mtp_dev1.as_ref() {
13918 if e.ctx().ordinal() != d.dev {
13919 return Err(format!(
13920 "qwen4exp_gpu: the draft lives on device {} (card-1 placement); \
13921 this call presented device {}",
13922 d.dev,
13923 e.ctx().ordinal()
13924 )
13925 .into());
13926 }
13927 }
13928 Ok(())
13929 }
13930
13931 pub fn mtp_state(&self, e: &Engine, capacity: usize) -> Res<MtpDraftState> {
13934 self.check_draft_engine(e)?;
13935 let mtp = self
13936 .mtp
13937 .as_ref()
13938 .ok_or("qwen4exp_gpu: no MTP block loaded (LoadOptions::load_mtp)")?;
13939 let MixerW::Qsa(qsa) = &mtp.layer.mixer else {
13940 return Err("qwen4exp_gpu: MTP mixer is not QSA".into());
13941 };
13942 let kv_width = qsa.attn.kv_heads as usize * qsa.attn.key_head_dim as usize;
13943 let v_width = qsa.attn.kv_heads as usize * qsa.attn.value_head_dim as usize;
13944 let kv = if kv_quant_on() {
13947 QsaKvStore::Q8Q5 {
13948 k: e.alloc_u8(capacity * q8_row_bytes(kv_width))?,
13949 v: e.alloc_u8(capacity * q5_row_bytes(v_width))?,
13950 }
13951 } else {
13952 QsaKvStore::F32 {
13953 k: e.zeros(capacity * kv_width)?,
13954 v: e.zeros(capacity * v_width)?,
13955 }
13956 };
13957 Ok(MtpDraftState {
13958 mixer: MixerState::Qsa {
13959 kv,
13960 raw_keys: IdxRawCache::new(idxq_mode()),
13961 pooled_keys: Vec::new(),
13962 pooled_dev: None,
13963 pooled_dev_rows: 0,
13964 raw_dev: None,
13965 raw_dev_rows: 0,
13966 idx_audit: None,
13967 },
13968 rows: 0,
13969 committed: 0,
13970 capacity,
13971 ws: StepPool::default(),
13972 })
13973 }
13974
13975 pub fn mtp_rewind(&self, dstate: &mut MtpDraftState, rows: usize) -> Res<()> {
13978 if rows > dstate.rows {
13979 return Err("qwen4exp_gpu: mtp_rewind past the cache".into());
13980 }
13981 let mtp = self.mtp.as_ref().ok_or("qwen4exp_gpu: no MTP block")?;
13982 let MixerW::Qsa(qsa) = &mtp.layer.mixer else {
13983 return Err("qwen4exp_gpu: MTP mixer is not QSA".into());
13984 };
13985 let MixerState::Qsa {
13986 raw_keys,
13987 pooled_keys,
13988 pooled_dev_rows,
13989 raw_dev_rows,
13990 ..
13991 } = &mut dstate.mixer
13992 else {
13993 return Err("qwen4exp_gpu: MTP state is not QSA".into());
13994 };
13995 let idx_dim = qsa.overlay.head_dim as usize;
13996 raw_keys.truncate_rows(rows, idx_dim);
13997 let block = qsa.overlay.block_size as usize;
13998 pooled_keys.truncate((rows / block) * idx_dim);
13999 *pooled_dev_rows = (*pooled_dev_rows).min(pooled_keys.len() / idx_dim);
14003 *raw_dev_rows = (*raw_dev_rows).min(rows);
14006 dstate.rows = rows;
14007 dstate.committed = dstate.committed.min(rows);
14008 Ok(())
14009 }
14010
14011 #[allow(clippy::too_many_arguments)]
14022 pub fn mtp_draft_forward(
14023 &self,
14024 e: &Engine,
14025 tokens: &[u32],
14026 hidden_wide: &CudaSlice<f32>,
14027 wide_off: usize,
14028 dstate: &mut MtpDraftState,
14029 pos_off: usize,
14030 logits_all: bool,
14034 ) -> Res<(CudaSlice<f32>, CudaSlice<f32>)> {
14035 self.mtp_draft_forward_impl(
14036 e,
14037 DraftTokSrc::Host(tokens),
14038 hidden_wide,
14039 wide_off,
14040 dstate,
14041 pos_off,
14042 logits_all,
14043 )
14044 }
14045
14046 fn mtp_draft_forward_devslot(
14050 &self,
14051 e: &Engine,
14052 toks: &CudaSlice<u32>,
14053 slot: usize,
14054 hidden_wide: &CudaSlice<f32>,
14055 wide_off: usize,
14056 dstate: &mut MtpDraftState,
14057 ) -> Res<(CudaSlice<f32>, CudaSlice<f32>)> {
14058 self.mtp_draft_forward_impl(
14059 e,
14060 DraftTokSrc::DevSlot(toks, slot),
14061 hidden_wide,
14062 wide_off,
14063 dstate,
14064 1,
14065 false,
14066 )
14067 }
14068
14069 fn mtp_draft_forward_spec(
14073 &self,
14074 e: &Engine,
14075 tokens: &[u32],
14076 dev_embed: bool,
14077 hidden_wide: &CudaSlice<f32>,
14078 wide_off: usize,
14079 dstate: &mut MtpDraftState,
14080 ) -> Res<(CudaSlice<f32>, CudaSlice<f32>)> {
14081 let src = if dev_embed {
14082 DraftTokSrc::HostDev(tokens)
14083 } else {
14084 DraftTokSrc::Host(tokens)
14085 };
14086 self.mtp_draft_forward_impl(e, src, hidden_wide, wide_off, dstate, 1, false)
14087 }
14088
14089 fn draft_consume_ring(
14096 &self,
14097 de: &Engine,
14098 tokens: &[u32],
14099 dev_embed: bool,
14100 seed: &CudaSlice<f32>,
14101 ring: usize,
14102 first_row: usize,
14103 dstate: &mut MtpDraftState,
14104 ) -> Res<(CudaSlice<f32>, CudaSlice<f32>, usize)> {
14105 let mut out: Option<(CudaSlice<f32>, CudaSlice<f32>, usize)> = None;
14106 let mut done = 0usize;
14107 while done < tokens.len() {
14108 let slot = (first_row + done) % ring;
14109 let len = (tokens.len() - done).min(ring - slot);
14110 let (l, c) = self.mtp_draft_forward_spec(
14111 de,
14112 &tokens[done..done + len],
14113 dev_embed,
14114 seed,
14115 slot,
14116 dstate,
14117 )?;
14118 if let Some((pl, pc, _)) = out.take() {
14119 self.mtp_recycle(dstate, pl, pc);
14120 }
14121 out = Some((l, c, len));
14122 done += len;
14123 }
14124 out.ok_or("qwen4exp_gpu: empty draft consume".into())
14125 }
14126
14127 #[allow(clippy::too_many_arguments)]
14128 fn mtp_draft_forward_impl(
14129 &self,
14130 e: &Engine,
14131 tok_src: DraftTokSrc<'_>,
14132 hidden_wide: &CudaSlice<f32>,
14133 wide_off: usize,
14134 dstate: &mut MtpDraftState,
14135 pos_off: usize,
14136 logits_all: bool,
14137 ) -> Res<(CudaSlice<f32>, CudaSlice<f32>)> {
14138 self.check_draft_engine(e)?;
14139 let mtp = self
14140 .mtp
14141 .as_ref()
14142 .ok_or("qwen4exp_gpu: no MTP block loaded (LoadOptions::load_mtp)")?;
14143 let t = match tok_src {
14144 DraftTokSrc::Host(tokens) | DraftTokSrc::HostDev(tokens) => tokens.len(),
14145 DraftTokSrc::DevSlot(..) => 1,
14146 };
14147 let hidden = self.hidden;
14148 let streams = self.streams;
14149 let wide = streams * hidden;
14150 if t == 0 {
14151 return Err("qwen4exp_gpu: empty draft input".into());
14152 }
14153 if dstate.rows + t > dstate.capacity {
14154 return Err("qwen4exp_gpu: draft state capacity exceeded".into());
14155 }
14156 if hidden_wide.len() < (wide_off + t) * wide {
14157 return Err("qwen4exp_gpu: draft hidden seed rows out of range".into());
14158 }
14159 let base = dstate.rows;
14160 let ws = &mut dstate.ws;
14161 let cap = dstate.capacity;
14162
14163 let mut planes = prof_section(e, "mtp.fuse", || {
14165 let emb = match tok_src {
14166 DraftTokSrc::Host(tokens) => {
14167 let mut embedded = vec![0.0f32; t * hidden];
14168 for (row, &token) in tokens.iter().enumerate() {
14169 let token = token as usize;
14170 if token >= self.vocab {
14171 return Err(
14172 format!("qwen4exp_gpu: draft token {token} out of range").into()
14173 );
14174 }
14175 embedded[row * hidden..(row + 1) * hidden].copy_from_slice(
14176 &self.embed_host[token * hidden..(token + 1) * hidden],
14177 );
14178 }
14179 ws.take_f32_h2d(e, "mtp.emb", &embedded, cap * hidden)?
14180 }
14181 DraftTokSrc::HostDev(tokens) => {
14182 let ce = self
14188 .chain_embed
14189 .as_ref()
14190 .filter(|ce| !ce.for_trim && ce.rows == self.vocab)
14191 .ok_or("qwen4exp_gpu: HostDev embed needs the full-vocab chain table")?;
14192 for &token in tokens {
14193 if token as usize >= self.vocab {
14194 return Err(
14195 format!("qwen4exp_gpu: draft token {token} out of range").into()
14196 );
14197 }
14198 }
14199 let tok_d = e.gpu.stream().clone_htod(tokens)?;
14200 let mut emb = ws.take_f32(e, "mtp.emb", t * hidden, cap * hidden)?;
14201 let tv = tok_d.slice(0..t);
14202 embed_gather_rows_into(
14203 e,
14204 &ce.table,
14205 &tv,
14206 &mut emb,
14207 t,
14208 hidden,
14209 ce.qt,
14210 ce.row_bytes,
14211 )?;
14212 emb
14213 }
14214 DraftTokSrc::DevSlot(toks, slot) => {
14215 let ce = self
14221 .chain_embed
14222 .as_ref()
14223 .ok_or("qwen4exp_gpu: deferred draft step without arm_spec_devchain")?;
14224 let mut emb = ws.take_f32(e, "mtp.emb", hidden, cap * hidden)?;
14225 let tv = toks.slice(slot..slot + 1);
14226 embed_gather_rows_into(
14227 e,
14228 &ce.table,
14229 &tv,
14230 &mut emb,
14231 1,
14232 hidden,
14233 ce.qt,
14234 ce.row_bytes,
14235 )?;
14236 emb
14237 }
14238 };
14239 let mut enorm = ws.take_f32(e, "mtp.enorm", t * hidden, 0)?;
14240 e.rms_norm(
14241 &emb,
14242 &mtp.pre_norm_embed,
14243 &mut enorm,
14244 hidden,
14245 t,
14246 mtp.eps_embed,
14247 )?;
14248 let mut evec = ws.take_f32(e, "mtp.evec", t * hidden, 0)?;
14249 linear_trunk_into(
14250 e,
14251 &mtp.fc_embed,
14252 &mtp.fc_embed_b16,
14253 &enorm,
14254 &mut evec,
14255 t,
14256 hidden,
14257 hidden,
14258 )?;
14259 let mut hin = ws.take_f32(e, "mtp.hin", t * wide, 0)?;
14262 e.copy_range_into(&mut hin, 0, hidden_wide, wide_off * wide, t * wide)?;
14263 let mut hnorm = ws.take_f32(e, "mtp.hnorm", t * wide, 0)?;
14264 e.rms_norm(
14265 &hin,
14266 &mtp.pre_norm_hidden,
14267 &mut hnorm,
14268 wide,
14269 t,
14270 mtp.eps_hidden,
14271 )?;
14272 let mut fused = ws.take_f32(e, "mtp.fused", t * wide, 0)?;
14275 linear_trunk_into(
14276 e,
14277 &mtp.fc_hidden,
14278 &mtp.fc_hidden_b16,
14279 &hnorm,
14280 &mut fused,
14281 t * streams,
14282 hidden,
14283 hidden,
14284 )?;
14285 let mut planes: Vec<CudaSlice<f32>> = Vec::with_capacity(streams);
14286 for s in 0..streams {
14287 let mut plane = ws.take_f32(e, PLANE_SLOTS[s], t * hidden, cap * hidden)?;
14288 for tok in 0..t {
14289 e.copy_range_into(
14290 &mut plane,
14291 tok * hidden,
14292 &fused,
14293 (tok * streams + s) * hidden,
14294 hidden,
14295 )?;
14296 }
14297 let mut view = plane.slice_mut(0..t * hidden);
14298 e.axpy_into(&evec, 1.0, &mut view, t * hidden)?;
14299 planes.push(plane);
14300 }
14301 ws.put_f32("mtp.emb", emb);
14302 ws.put_f32("mtp.enorm", enorm);
14303 ws.put_f32("mtp.evec", evec);
14304 ws.put_f32("mtp.hin", hin);
14305 ws.put_f32("mtp.hnorm", hnorm);
14306 ws.put_f32("mtp.fused", fused);
14307 Ok(planes)
14308 })?;
14309
14310 let ptr_vals: Vec<u64> = {
14311 let stream = e.gpu.stream();
14312 planes.iter().map(|p| p.device_ptr(&stream).0).collect()
14313 };
14314 let ptrs = ws.take_u64_h2d(e, "hc.ptrs", &ptr_vals, 0)?;
14315
14316 let layer = &mtp.layer;
14318 let (mixed, inject) = prof_section(e, "mtp.hyper.read", || {
14319 self.gate_read(
14320 e,
14321 ws,
14322 &ptrs,
14323 &layer.attn_gate,
14324 &planes,
14325 t,
14326 layer.eps_attn,
14327 false,
14328 )
14329 })?;
14330 let MixerW::Qsa(qsa) = &layer.mixer else {
14331 return Err("qwen4exp_gpu: MTP mixer is not QSA".into());
14332 };
14333 let block_out = prof_section(e, "mtp.qsa", || {
14334 self.qsa_forward(
14335 e,
14336 ws,
14337 layer,
14338 qsa,
14339 &mixed,
14340 &mut dstate.mixer,
14341 base,
14342 t,
14343 pos_off,
14344 false,
14345 )
14346 })?;
14347 ws.put_f32("hc.mixed", mixed);
14348 prof_section(e, "mtp.hyper.write", || {
14349 self.gate_write(e, &mut planes, &ptrs, &block_out, &inject, t)
14350 })?;
14351 ws.put_f32("mixer.out", block_out);
14352 put_inject(ws, inject);
14353 let (mixed, inject) = prof_section(e, "mtp.hyper.read", || {
14354 self.gate_read(
14355 e,
14356 ws,
14357 &ptrs,
14358 &layer.mlp_gate,
14359 &planes,
14360 t,
14361 layer.eps_mlp,
14362 false,
14363 )
14364 })?;
14365 let mlp = prof_section(e, "mtp.moe", || {
14366 self.moe_forward(e, ws, &layer.moe, &mixed, t, t <= 32, layer.index)
14369 })?;
14370 ws.put_f32("hc.mixed", mixed);
14371 prof_section(e, "mtp.hyper.write", || {
14372 self.gate_write(e, &mut planes, &ptrs, &mlp, &inject, t)
14373 })?;
14374 ws.put_f32("moe.out", mlp);
14375 put_inject(ws, inject);
14376
14377 let mut carrier = ws.take_f32(e, "mtp.carrier", t * wide, 0)?;
14379 for (s, plane) in planes.iter().enumerate() {
14380 for tok in 0..t {
14381 e.copy_range_into(
14382 &mut carrier,
14383 tok * wide + s * hidden,
14384 plane,
14385 tok * hidden,
14386 hidden,
14387 )?;
14388 }
14389 }
14390
14391 let x = prof_section(e, "mtp.exit", || {
14397 Ok(self
14398 .gate_read_inner(
14399 e,
14400 ws,
14401 &ptrs,
14402 &mtp.mixer,
14403 &planes,
14404 t,
14405 self.exit_eps,
14406 false,
14407 false,
14408 )?
14409 .0)
14410 })?;
14411 ws.put_u64("hc.ptrs", ptrs);
14412 let trim = self.draft_trim.as_ref();
14417 let out_f = trim.map_or(self.vocab, |t| t.n);
14418 let (head_w, head_b16) = match self.mtp_dev1.as_ref() {
14421 Some(d) => (&d.output, &d.output_b16),
14422 None => (&self.output, &self.output_b16),
14423 };
14424 let head_into =
14425 |e: &Engine, x: &CudaSlice<f32>, y: &mut CudaSlice<f32>, rows: usize| -> Res<()> {
14426 match trim {
14427 Some(trim) => linear_trim_into(e, trim, x, y, rows, hidden),
14428 None => linear_trunk_into(e, head_w, head_b16, x, y, rows, hidden, self.vocab),
14429 }
14430 };
14431 let logits = prof_section(e, "mtp.lm_head", || {
14432 if logits_all {
14433 let mut logits = ws.take_f32(e, "mtp.logits", t * out_f, 0)?;
14434 head_into(e, &x, &mut logits, t)?;
14435 return Ok(logits);
14436 }
14437 let mut logits = ws.take_f32(e, "mtp.logits", out_f, 0)?;
14438 let mut x_last = ws.take_f32(e, "mtp.xlast", hidden, 0)?;
14439 e.copy_range_into(&mut x_last, 0, &x, (t - 1) * hidden, hidden)?;
14440 head_into(e, &x_last, &mut logits, 1)?;
14441 ws.put_f32("mtp.xlast", x_last);
14442 Ok(logits)
14443 })?;
14444 ws.put_f32("hc.mixed", x);
14445 for (s, plane) in planes.into_iter().enumerate() {
14446 ws.put_f32(PLANE_SLOTS[s], plane);
14447 }
14448 dstate.rows += t;
14449 Ok((logits, carrier))
14450 }
14451
14452 pub fn mtp_recycle(
14455 &self,
14456 dstate: &mut MtpDraftState,
14457 logits: CudaSlice<f32>,
14458 carrier: CudaSlice<f32>,
14459 ) {
14460 dstate.ws.put_f32("mtp.logits", logits);
14461 dstate.ws.put_f32("mtp.carrier", carrier);
14462 }
14463}
14464
14465#[derive(Clone, Copy)]
14471pub struct SpecSamplerCfg {
14472 pub temperature: f32,
14473 pub top_p: f32,
14474 pub top_k: usize,
14475 pub seed: u64,
14476}
14477
14478struct SpecRng(u64);
14480
14481impl SpecRng {
14482 fn next_f32(&mut self) -> f32 {
14483 let mut x = self.0;
14484 x ^= x >> 12;
14485 x ^= x << 25;
14486 x ^= x >> 27;
14487 self.0 = x;
14488 let bits = (x.wrapping_mul(0x2545_F491_4F6C_DD1D) >> 40) as u32;
14489 bits as f32 / (1u64 << 24) as f32
14490 }
14491}
14492
14493fn sample_row(cfg: &SpecSamplerCfg, rng: &mut SpecRng, row: &[f32]) -> u32 {
14495 let k = cfg.top_k.max(1).min(row.len());
14496 let mut idx: Vec<u32> = (0..row.len() as u32).collect();
14497 idx.select_nth_unstable_by(k - 1, |&a, &b| row[b as usize].total_cmp(&row[a as usize]));
14498 let mut top: Vec<(u32, f32)> = idx[..k].iter().map(|&i| (i, row[i as usize])).collect();
14499 top.sort_by(|a, b| b.1.total_cmp(&a.1));
14500 let temp = cfg.temperature.max(1e-6);
14501 let mx = top[0].1;
14502 let mut probs: Vec<f32> = top.iter().map(|&(_, v)| ((v - mx) / temp).exp()).collect();
14503 let sum: f32 = probs.iter().sum();
14504 for p in &mut probs {
14505 *p /= sum;
14506 }
14507 let mut cut = probs.len();
14509 let mut acc = 0.0f32;
14510 for (i, &p) in probs.iter().enumerate() {
14511 acc += p;
14512 if acc >= cfg.top_p {
14513 cut = i + 1;
14514 break;
14515 }
14516 }
14517 let renorm: f32 = probs[..cut].iter().sum();
14518 let draw = rng.next_f32() * renorm;
14519 let mut acc = 0.0f32;
14520 for (i, &p) in probs[..cut].iter().enumerate() {
14521 acc += p;
14522 if draw < acc {
14523 return top[i].0;
14524 }
14525 }
14526 top[cut - 1].0
14527}
14528
14529fn host_argmax(row: &[f32]) -> usize {
14533 let mut best = 0usize;
14534 for (i, &v) in row.iter().enumerate() {
14535 if v > row[best] {
14536 best = i;
14537 }
14538 }
14539 best
14540}
14541
14542fn ring_pieces(ring: usize, off: usize, t: usize) -> Vec<(usize, usize)> {
14552 debug_assert!(t <= ring, "wide-ring consumer wider than the ring");
14553 let slot = off % ring;
14554 if slot + t <= ring {
14555 vec![(slot, t)]
14556 } else {
14557 vec![(slot, ring - slot), (0, t - (ring - slot))]
14558 }
14559}
14560
14561fn cross_wide_rows(
14562 e: &Engine,
14563 de: &Engine,
14564 src: &CudaSlice<f32>,
14565 dst: &mut CudaSlice<f32>,
14566 off: usize,
14567 t: usize,
14568 wide: usize,
14569) -> Res<f64> {
14570 let t0 = std::time::Instant::now();
14571 let stream = de.gpu.stream();
14572 let bytes = t * wide * 4;
14573 let byte_off = (off * wide * 4) as u64;
14574 let (sp, _g0) = src.device_ptr(&stream);
14575 let (dp, _g1) = dst.device_ptr_mut(&stream);
14576 unsafe {
14577 cudarc::driver::result::memcpy_peer_async(
14578 de.ctx().cu_ctx(),
14579 dp + byte_off,
14580 e.ctx().cu_ctx(),
14581 sp + byte_off,
14582 bytes,
14583 stream.cu_stream(),
14584 )?;
14585 }
14586 stream.synchronize()?;
14587 Ok(t0.elapsed().as_secs_f64() * 1e3)
14588}
14589
14590fn embed_gather_rows_into(
14595 e: &Engine,
14596 table: &CudaSlice<u8>,
14597 tok_v: &CudaView<u32>,
14598 x_out: &mut CudaSlice<f32>,
14599 t: usize,
14600 n_embd: usize,
14601 qtype: i32,
14602 row_bytes: usize,
14603) -> Res<()> {
14604 let f = e.func("embed_gather_u32_t");
14605 let cfg = LaunchConfig {
14606 grid_dim: (((n_embd as u32).div_ceil(256)).max(1), t as u32, 1),
14607 block_dim: (256, 1, 1),
14608 shared_mem_bytes: 0,
14609 };
14610 let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
14611 let stream = e.gpu.stream();
14612 let mut b = stream.launch_builder(&f);
14613 b.arg(table)
14614 .arg(tok_v)
14615 .arg(x_out)
14616 .arg(&ne)
14617 .arg(&qt)
14618 .arg(&rb)
14619 .arg(&ti);
14620 unsafe {
14621 b.launch(cfg)?;
14622 }
14623 Ok(())
14624}
14625
14626#[derive(Debug, Default, Clone)]
14628pub struct SpecReport {
14629 pub tokens: Vec<u32>,
14630 pub rounds: usize,
14631 pub drafted: u64,
14632 pub accepted: u64,
14633 pub accept_hist: Vec<u64>,
14635 pub draft_ms: f64,
14636 pub verify_ms: f64,
14637 pub prefill_ms: f64,
14638 pub total_ms: f64,
14639 pub chain_ms: f64,
14642 pub replay_ms: f64,
14643 pub draft_prefill_ms: f64,
14644 pub cross_ms: f64,
14647 pub cross_bytes: u64,
14648 pub k_decays: Vec<(usize, usize)>,
14650 pub spec_off_at: Option<usize>,
14653 pub plain_steps: usize,
14654 pub round_wall: Vec<(usize, f64)>,
14659 pub zero_draft_rounds: usize,
14662 pub guard_stops: usize,
14663}
14664
14665#[derive(Clone, Copy, Debug)]
14675pub struct DynKCfg {
14676 pub window: usize,
14677 pub thr: f64,
14678 pub k_floor: usize,
14679}
14680
14681#[derive(Clone, Copy, Debug, Default)]
14685pub struct SpecOpts {
14686 pub dynk: Option<DynKCfg>,
14688 pub adapt_k_lo: Option<usize>,
14691 pub pmin: f32,
14698 pub defer: bool,
14708 pub defer_guard_sync: bool,
14715 pub prefill_chunk: Option<usize>,
14721 pub wide_ring: Option<usize>,
14726}
14727
14728pub fn spec_guard_trunc(probs: &[f32], pmin: f32) -> usize {
14737 probs.iter().position(|&p| p < pmin).unwrap_or(probs.len())
14738}
14739
14740#[derive(Debug, Default, Clone)]
14746pub struct SpecTraceRound {
14747 pub round: usize,
14748 pub gen_pos: usize,
14750 pub base: usize,
14752 pub k: usize,
14753 pub a: usize,
14754 pub drafts: Vec<u32>,
14755 pub targets: Vec<u32>,
14757 pub draft_top1: f32,
14762 pub draft_top2: f32,
14763 pub draft_tgt_logit: f32,
14764 pub draft_tgt_rank: usize,
14765 pub target_top1: f32,
14766 pub target_top2: f32,
14767 pub target_draft_logit: f32,
14768 pub target_entropy: f64,
14769 pub carrier_rel_l2: Vec<f32>,
14773 pub carrier_cos: Vec<f32>,
14774}
14775
14776impl SpecReport {
14777 pub fn accept_rate(&self) -> f64 {
14778 if self.drafted == 0 {
14779 0.0
14780 } else {
14781 self.accepted as f64 / self.drafted as f64
14782 }
14783 }
14784 pub fn mean_accept_len(&self) -> f64 {
14786 if self.rounds == 0 {
14787 0.0
14788 } else {
14789 self.tokens.len() as f64 / self.rounds as f64
14790 }
14791 }
14792}
14793
14794impl Qwen4ExpGpu {
14795 pub fn spec_arm(&self, e: &Engine, state: &mut Qwen4ExpState, k_cap: usize) -> Res<()> {
14799 self.spec_arm_ring(e, state, k_cap, state.capacity)
14800 }
14801
14802 pub fn spec_arm_ring(
14809 &self,
14810 e: &Engine,
14811 state: &mut Qwen4ExpState,
14812 k_cap: usize,
14813 ring_rows: usize,
14814 ) -> Res<()> {
14815 let ring_rows = ring_rows.min(state.capacity).max(k_cap + 2);
14816 if let Some(v) = state.verify.as_ref() {
14817 if v.k_cap == k_cap && v.ring_rows == ring_rows {
14818 return Ok(());
14819 }
14820 }
14821 let wide = self.streams * self.hidden;
14822 let mut gdn = Vec::with_capacity(self.layers.len());
14823 let mut ple = Vec::with_capacity(self.layers.len());
14824 for layer in &self.layers {
14825 gdn.push(match &layer.mixer {
14826 MixerW::Gdn(g) => {
14827 let p = &g.plan;
14828 let (nk, nv) = (p.key_heads as usize, p.value_heads as usize);
14829 let (hk, hv) = (p.key_head_dim as usize, p.value_head_dim as usize);
14830 let conv_dim = 2 * nk * hk + nv * hv;
14831 let pad = p.conv_kernel as usize - 1;
14832 Some(GdnStash {
14833 states: e.zeros(k_cap * nv * hv * hk)?,
14834 conv_pre: e.zeros(pad * conv_dim)?,
14835 qkv_rows: e.zeros(k_cap * conv_dim)?,
14836 scan_graph: None,
14837 scan_warm: None,
14838 })
14839 }
14840 MixerW::Qsa(_) => None,
14841 });
14842 ple.push(match layer.ple.as_ref() {
14843 Some(pw) => {
14844 let pad = (pw.plan.conv_kernel as usize - 1) * pw.plan.max_ngram as usize;
14845 let mut hist_pre = Vec::with_capacity(self.streams);
14846 let mut normed_rows = Vec::with_capacity(self.streams);
14847 for _ in 0..self.streams {
14848 hist_pre.push(e.zeros(pad * self.hidden)?);
14849 normed_rows.push(e.zeros(k_cap * self.hidden)?);
14850 }
14851 Some(PleStash {
14852 hist_pre,
14853 normed_rows,
14854 })
14855 }
14856 None => None,
14857 });
14858 }
14859 state.verify = Some(VerifyStash {
14860 k_cap,
14861 chunk: None,
14862 fused_chunk: None,
14863 gdn,
14864 ple,
14865 wide: e.zeros(ring_rows * wide)?,
14866 ring_rows,
14867 wide_dev1: None,
14868 argmax: Vec::new(),
14869 toks: unsafe { e.gpu.stream().alloc::<u32>(k_cap)? },
14870 want_argmax: false,
14871 want_argmax_t1: false,
14872 last_row_only: false,
14873 });
14874 Ok(())
14875 }
14876
14877 pub fn spec_disarm(&self, state: &mut Qwen4ExpState) {
14878 state.verify = None;
14879 }
14880
14881 pub fn set_verify_want_argmax(&self, state: &mut Qwen4ExpState, on: bool) -> Res<()> {
14882 state
14883 .verify
14884 .as_mut()
14885 .ok_or("qwen4exp_gpu: verify not armed")?
14886 .want_argmax = on;
14887 Ok(())
14888 }
14889
14890 pub fn verify_argmax_rows<'s>(&self, state: &'s Qwen4ExpState) -> Res<&'s [u32]> {
14892 Ok(&state
14893 .verify
14894 .as_ref()
14895 .ok_or("qwen4exp_gpu: verify not armed")?
14896 .argmax)
14897 }
14898
14899 pub fn verify_rewind(&self, e: &Engine, state: &mut Qwen4ExpState, keep: usize) -> Res<()> {
14904 let Some(v) = state.verify.as_mut() else {
14905 return Err("qwen4exp_gpu: verify not armed".into());
14906 };
14907 let Some((base, t)) = v.chunk.take() else {
14908 if let Some((fb, ft)) = v.fused_chunk.take() {
14909 return Err(format!(
14910 "qwen4exp_gpu: verify chunk (base {fb}, t {ft}) ran the FUSED program \
14911 (`vfuse` cost instrument) — no per-column GDN/PLE stash exists, so it \
14912 cannot be rewound. vfuse is a timing probe on a throwaway state; drop \
14913 the seam to run a spec loop."
14914 )
14915 .into());
14916 }
14917 return Err("qwen4exp_gpu: no live verify chunk to rewind".into());
14918 };
14919 if keep == 0 || keep > t {
14920 return Err("qwen4exp_gpu: rewind keep out of range".into());
14921 }
14922 if keep == t {
14923 return Ok(());
14924 }
14925 state.pos = base + keep;
14926 state.tokens.truncate(base + keep);
14927 for (li, (layer, lstate)) in self.layers.iter().zip(state.layers.iter_mut()).enumerate() {
14928 match (&layer.mixer, &mut lstate.mixer) {
14929 (
14930 MixerW::Qsa(qsa),
14931 MixerState::Qsa {
14932 raw_keys,
14933 pooled_keys,
14934 pooled_dev_rows,
14935 raw_dev_rows,
14936 idx_audit,
14937 ..
14938 },
14939 ) => {
14940 let idx_dim = qsa.overlay.head_dim as usize;
14941 raw_keys.truncate_rows(base + keep, idx_dim);
14942 let block = qsa.overlay.block_size as usize;
14943 pooled_keys.truncate(((base + keep) / block) * idx_dim);
14944 *pooled_dev_rows = (*pooled_dev_rows).min(pooled_keys.len() / idx_dim);
14946 *raw_dev_rows = (*raw_dev_rows).min(base + keep);
14949 if let Some(audit) = idx_audit.as_deref_mut() {
14951 audit.raw_f32.truncate_rows(base + keep, idx_dim);
14952 audit.pooled_f32.truncate(((base + keep) / block) * idx_dim);
14953 }
14954 }
14955 (MixerW::Gdn(g), MixerState::Gdn { conv, state: rec }) => {
14956 let st = v.gdn[li]
14957 .as_mut()
14958 .ok_or("qwen4exp_gpu: GDN layer without a verify stash")?;
14959 let p = &g.plan;
14960 let (nk, nv) = (p.key_heads as usize, p.value_heads as usize);
14961 let (hk, hv) = (p.key_head_dim as usize, p.value_head_dim as usize);
14962 let conv_dim = 2 * nk * hk + nv * hv;
14963 let pad = p.conv_kernel as usize - 1;
14964 let state_len = nv * hv * hk;
14965 e.copy_range_into(rec, 0, &st.states, (keep - 1) * state_len, state_len)?;
14966 if keep >= pad {
14967 e.copy_range_into(
14968 conv,
14969 0,
14970 &st.qkv_rows,
14971 (keep - pad) * conv_dim,
14972 pad * conv_dim,
14973 )?;
14974 } else {
14975 let keep_hist = pad - keep;
14976 e.copy_range_into(
14977 conv,
14978 0,
14979 &st.conv_pre,
14980 keep * conv_dim,
14981 keep_hist * conv_dim,
14982 )?;
14983 e.copy_range_into(
14984 conv,
14985 keep_hist * conv_dim,
14986 &st.qkv_rows,
14987 0,
14988 keep * conv_dim,
14989 )?;
14990 }
14991 }
14992 _ => return Err("qwen4exp_gpu: mixer/state mismatch in rewind".into()),
14993 }
14994 if let (Some(pw), Some(ps)) = (layer.ple.as_ref(), lstate.ple.as_mut()) {
14995 let st = v.ple[li]
14996 .as_mut()
14997 .ok_or("qwen4exp_gpu: PLE layer without a verify stash")?;
14998 let pad = (pw.plan.conv_kernel as usize - 1) * pw.plan.max_ngram as usize;
14999 let hidden = self.hidden;
15000 for s in 0..self.streams {
15001 let hist = &mut ps.conv_hist[s];
15002 if keep >= pad {
15003 e.copy_range_into(
15004 hist,
15005 0,
15006 &st.normed_rows[s],
15007 (keep - pad) * hidden,
15008 pad * hidden,
15009 )?;
15010 } else {
15011 let keep_hist = pad - keep;
15012 e.copy_range_into(
15013 hist,
15014 0,
15015 &st.hist_pre[s],
15016 keep * hidden,
15017 keep_hist * hidden,
15018 )?;
15019 e.copy_range_into(
15020 hist,
15021 keep_hist * hidden,
15022 &st.normed_rows[s],
15023 0,
15024 keep * hidden,
15025 )?;
15026 }
15027 }
15028 }
15029 }
15030 Ok(())
15031 }
15032
15033 fn draft_row_argmax(
15042 &self,
15043 e: &Engine,
15044 logits: &CudaSlice<f32>,
15045 row: usize,
15046 conf: bool,
15047 ) -> Res<(u32, f32)> {
15048 let width = self.draft_logits_width();
15049 let mut tok = unsafe { e.gpu.stream().alloc::<u32>(1)? };
15050 e.argmax_token_device_col(logits, row, width, &mut tok, 0)?;
15051 let p = if conf {
15052 if row != 0 {
15053 return Err("qwen4exp_gpu: draft confidence reads row 0 (the chain shape)".into());
15055 }
15056 let pd = e.prob_of_token_device(logits, &tok, width)?;
15057 e.gpu.stream().clone_dtoh(&pd)?[0]
15058 } else {
15059 1.0
15060 };
15061 Ok((self.draft_token(e.gpu.stream().clone_dtoh(&tok)?[0])?, p))
15062 }
15063
15064 #[allow(clippy::too_many_arguments)]
15077 pub fn spec_generate(
15078 &self,
15079 e: &Engine,
15080 prompt: &[u32],
15081 max_new: usize,
15082 k: usize,
15083 state: &mut Qwen4ExpState,
15084 dstate: &mut MtpDraftState,
15085 sampler: Option<SpecSamplerCfg>,
15086 ) -> Res<SpecReport> {
15087 self.spec_generate_ext(
15088 e,
15089 e,
15090 prompt,
15091 max_new,
15092 k,
15093 state,
15094 dstate,
15095 sampler,
15096 SpecOpts::default(),
15097 None,
15098 )
15099 }
15100
15101 #[allow(clippy::too_many_arguments)]
15112 pub fn spec_generate_ext(
15113 &self,
15114 e: &Engine,
15115 de: &Engine,
15116 prompt: &[u32],
15117 max_new: usize,
15118 k: usize,
15119 state: &mut Qwen4ExpState,
15120 dstate: &mut MtpDraftState,
15121 sampler: Option<SpecSamplerCfg>,
15122 opts: SpecOpts,
15123 mut trace: Option<&mut Vec<SpecTraceRound>>,
15124 ) -> Res<SpecReport> {
15125 use std::time::Instant;
15126 if k == 0 {
15127 return Err("qwen4exp_gpu: spec needs k >= 1".into());
15128 }
15129 let n = prompt.len();
15130 if n < 2 {
15131 return Err("qwen4exp_gpu: spec needs a >= 2 token prompt".into());
15132 }
15133 if state.pos != 0 || dstate.rows != 0 {
15134 return Err("qwen4exp_gpu: spec_generate wants FRESH trunk + draft states".into());
15135 }
15136 if state.capacity < n + max_new + k + 2 || dstate.capacity < n + max_new + k + 2 {
15137 return Err("qwen4exp_gpu: state capacity too small for prompt + max_new + k".into());
15138 }
15139 self.check_draft_engine(de)?;
15140 let dev1 = self.mtp_dev1.is_some();
15141 if !dev1 && de.ctx().ordinal() != e.ctx().ordinal() {
15142 return Err(
15143 "qwen4exp_gpu: draft engine on another card, but the draft was not \
15144 built there (load_from_dir_dev1)"
15145 .into(),
15146 );
15147 }
15148 let vocab = self.vocab;
15149 let wide_w = self.streams * self.hidden;
15150 let greedy = sampler.is_none();
15151 let tracing = trace.is_some();
15152 let guard = opts.pmin > 0.0;
15153 let deferred = opts.defer;
15154 if deferred && tracing {
15155 return Err(
15156 "qwen4exp_gpu: spec defer + trace are mutually exclusive (the trace \
15157 instrument reads per-step host rows); run the trace on the host-chain arm"
15158 .into(),
15159 );
15160 }
15161 if deferred {
15162 let ce = self.chain_embed.as_ref().ok_or(
15163 "qwen4exp_gpu: SpecOpts::defer needs arm_spec_devchain on the draft engine",
15164 )?;
15165 if ce.dev != de.ctx().ordinal() {
15166 return Err(format!(
15167 "qwen4exp_gpu: the chain-embed table lives on device {} but the \
15168 draft engine is device {} — re-arm arm_spec_devchain",
15169 ce.dev,
15170 de.ctx().ordinal()
15171 )
15172 .into());
15173 }
15174 if ce.for_trim != self.draft_trim.is_some() || ce.rows != self.draft_logits_width() {
15175 return Err(
15176 "qwen4exp_gpu: the chain-embed table was armed for a different trim \
15177 state — re-arm arm_spec_devchain after trim changes"
15178 .into(),
15179 );
15180 }
15181 }
15182 let (mut chain_toks_d, mut chain_probs_d) = if deferred {
15185 (
15186 Some(unsafe { de.gpu.stream().alloc::<u32>(k)? }),
15187 Some(de.zeros(k)?),
15188 )
15189 } else {
15190 (None, None)
15191 };
15192 let mut rng = sampler
15193 .as_ref()
15194 .map(|cfg| SpecRng(cfg.seed | 1))
15195 .unwrap_or(SpecRng(1));
15196 let t_total = Instant::now();
15197 let mut report = SpecReport {
15198 accept_hist: vec![0; k + 1],
15199 ..Default::default()
15200 };
15201
15202 match opts.wide_ring {
15203 Some(ring) => {
15204 let chunk = opts
15205 .prefill_chunk
15206 .ok_or("qwen4exp_gpu: SpecOpts::wide_ring needs prefill_chunk")?;
15207 if ring < 2 * chunk || ring < 2 * (k + 2) {
15208 return Err("qwen4exp_gpu: wide_ring must cover 2 prefill chunks".into());
15209 }
15210 self.spec_arm_ring(e, state, k + 1, ring)?;
15211 }
15212 None => self.spec_arm(e, state, k + 1)?,
15213 }
15214 self.set_verify_want_argmax(state, false)?;
15215 if let Some(v) = state.verify.as_mut() {
15216 v.want_argmax_t1 = deferred && greedy && !tracing;
15219 v.last_row_only = deferred;
15220 }
15221 let ring = state.verify.as_ref().expect("armed above").ring_rows;
15224 if dev1 {
15225 let v = state.verify.as_mut().expect("armed above");
15226 if v.wide_dev1.as_ref().is_none_or(|m| m.len() < ring * wide_w) {
15227 v.wide_dev1 = Some(de.zeros(ring * wide_w)?);
15228 }
15229 }
15230 let dev_embed = deferred
15235 && self
15236 .chain_embed
15237 .as_ref()
15238 .is_some_and(|ce| !ce.for_trim && ce.rows == self.vocab);
15239 let t_prefill = Instant::now();
15240 let mut draft_prefill_ms = 0f64;
15241 let x0: u32 = match opts.prefill_chunk {
15242 Some(chunk) if n > chunk => {
15249 let mut b = 0usize;
15250 let mut last = Vec::new();
15251 let mut chunks = 0usize;
15258 while b < n {
15259 let mut t = chunk.min(n - b);
15260 if n - (b + t) > 0 && n - (b + t) <= k + 1 {
15262 t = n - b;
15263 }
15264 let is_last = b + t == n;
15265 let head = if is_last {
15266 HeadMode::LastRow
15267 } else {
15268 HeadMode::Skip
15269 };
15270 let piece = self.forward_with(e, &prompt[b..b + t], state, None, head)?;
15271 let t_draft = Instant::now();
15272 if dev1 {
15273 let v = state.verify.as_mut().expect("armed above");
15274 let VerifyStash {
15275 wide, wide_dev1, ..
15276 } = v;
15277 let mirror = wide_dev1.as_mut().expect("allocated above");
15278 for (slot, len) in ring_pieces(ring, b, t) {
15279 report.cross_ms +=
15280 cross_wide_rows(e, de, wide, mirror, slot, len, wide_w)?;
15281 }
15282 report.cross_bytes += (t * wide_w * 4) as u64;
15283 }
15284 let p0 = b.max(1);
15287 if b + t > p0 {
15288 let v = state.verify.as_ref().expect("armed above");
15289 let seed: &CudaSlice<f32> = v.wide_dev1.as_ref().unwrap_or(&v.wide);
15290 let (ld, cd, _) = self.draft_consume_ring(
15291 de,
15292 &prompt[p0..b + t],
15293 dev_embed,
15294 seed,
15295 ring,
15296 p0 - 1,
15297 dstate,
15298 )?;
15299 self.mtp_recycle(dstate, ld, cd);
15300 }
15301 draft_prefill_ms += t_draft.elapsed().as_secs_f64() * 1e3;
15302 b += t;
15303 chunks += 1;
15304 if chunks % 8 == 0 || is_last {
15305 println!(
15306 "# spec-prefill-progress\tfill={b}/{n}\tchunks={chunks}\t\
15307 elapsed_s={:.1}\tdraft_s={:.1}",
15308 t_prefill.elapsed().as_secs_f64(),
15309 draft_prefill_ms / 1e3,
15310 );
15311 }
15312 if is_last {
15313 last = piece;
15314 }
15315 }
15316 dstate.committed = n - 1;
15317 let shed_bytes = state.ws.shed();
15324 state.graphs = StepGraphs::default();
15325 println!(
15326 "# spec-prefill-shed\tworkspace_mib={:.1}\tchunks={chunks}",
15327 shed_bytes as f64 / (1024.0 * 1024.0),
15328 );
15329 debug_assert_eq!(last.len(), vocab);
15330 match sampler.as_ref() {
15331 None => host_argmax(&last) as u32,
15332 Some(cfg) => sample_row(cfg, &mut rng, &last),
15333 }
15334 }
15335 _ => {
15337 let prefill = self.forward(e, prompt, state, None)?;
15338 let last = &prefill[prefill.len() - vocab..];
15343 let x0 = match sampler.as_ref() {
15344 None => host_argmax(last) as u32,
15345 Some(cfg) => sample_row(cfg, &mut rng, last),
15346 };
15347 let t_draft0 = Instant::now();
15348 if dev1 {
15349 let v = state.verify.as_mut().expect("armed above");
15350 let VerifyStash {
15351 wide, wide_dev1, ..
15352 } = v;
15353 let mirror = wide_dev1.as_mut().expect("allocated above");
15354 for (slot, len) in ring_pieces(ring, 0, n) {
15355 report.cross_ms += cross_wide_rows(e, de, wide, mirror, slot, len, wide_w)?;
15356 }
15357 report.cross_bytes += (n * wide_w * 4) as u64;
15358 }
15359 {
15360 let v = state.verify.as_ref().expect("armed above");
15361 let seed: &CudaSlice<f32> = v.wide_dev1.as_ref().unwrap_or(&v.wide);
15362 if n >= 2 {
15363 let (ld, cd, _) = self.draft_consume_ring(
15364 de,
15365 &prompt[1..],
15366 dev_embed,
15367 seed,
15368 ring,
15369 0,
15370 dstate,
15371 )?;
15372 self.mtp_recycle(dstate, ld, cd);
15373 }
15374 dstate.committed = n - 1;
15375 }
15376 draft_prefill_ms += t_draft0.elapsed().as_secs_f64() * 1e3;
15377 x0
15378 }
15379 };
15380 report.prefill_ms = t_prefill.elapsed().as_secs_f64() * 1e3 - draft_prefill_ms;
15381 self.set_verify_want_argmax(state, greedy && !tracing)?;
15384 report.tokens.push(x0);
15386
15387 let t_boot = Instant::now();
15389 let (mut tip_logits, mut tip_carrier) = {
15390 let v = state.verify.as_ref().expect("armed above");
15391 let seed: &CudaSlice<f32> = v.wide_dev1.as_ref().unwrap_or(&v.wide);
15392 self.mtp_draft_forward_spec(de, &[x0], dev_embed, seed, (n - 1) % ring, dstate)?
15393 };
15394 let mut tip_rows = 1usize;
15395 dstate.committed = dstate.rows;
15396 report.draft_prefill_ms = draft_prefill_ms + t_boot.elapsed().as_secs_f64() * 1e3;
15397 report.draft_ms += report.draft_prefill_ms;
15398 prof::split_prefill();
15400
15401 let mut m = n; let mut tip = x0;
15403 let mut k_cur = k;
15406 let mut k_next = k;
15407 let mut window: Vec<usize> = Vec::new();
15408 let mut round_idx = 0usize;
15409 while report.tokens.len() < max_new {
15410 if k_cur == 0 {
15411 let row = self.forward(e, &[tip], state, None)?;
15415 let next: u32 = match sampler.as_ref() {
15416 None if deferred => self.verify_argmax_rows(state)?[0],
15419 None => host_argmax(&row) as u32,
15420 Some(cfg) => sample_row(cfg, &mut rng, &row),
15421 };
15422 report.tokens.push(next);
15423 report.plain_steps += 1;
15424 report
15425 .round_wall
15426 .push((report.tokens.len(), t_total.elapsed().as_secs_f64() * 1e3));
15427 m += 1;
15428 tip = next;
15429 continue;
15430 }
15431 let k_round = k_next.min(k_cur).max(1);
15432 let t_draft = Instant::now();
15437 let mut drafts: Vec<u32> = Vec::with_capacity(k_round);
15438 let mut chain_rows_h: Vec<Vec<f32>> = Vec::new(); let mut seeds_h: Vec<Vec<f32>> = Vec::new(); if let (Some(toks), Some(probs)) = (chain_toks_d.as_mut(), chain_probs_d.as_mut()) {
15441 let width = self.draft_logits_width();
15446 de.argmax_token_device_col(&tip_logits, 0, width, toks, 0)?;
15447 if guard {
15448 de.prob_of_token_device_col(&tip_logits, toks, 0, probs, 0, width)?;
15449 }
15450 let mut prev_logits = tip_logits;
15451 let mut prev_carrier = tip_carrier;
15452 let mut prev_rows = tip_rows;
15453 let mut drafted = 1usize;
15455 let mut stopped = false;
15456 if guard && opts.defer_guard_sync {
15457 let p = de.gpu.stream().clone_dtoh(&probs.slice(0..1))?[0];
15461 if p < opts.pmin {
15462 drafted = 0;
15463 stopped = true;
15464 report.guard_stops += 1;
15465 }
15466 }
15467 while !stopped && drafted < k_round {
15468 let (l2, c2) = self.mtp_draft_forward_devslot(
15469 de,
15470 toks,
15471 drafted - 1,
15472 &prev_carrier,
15473 prev_rows - 1,
15474 dstate,
15475 )?;
15476 self.mtp_recycle(dstate, prev_logits, prev_carrier);
15477 prev_logits = l2;
15478 prev_carrier = c2;
15479 prev_rows = 1;
15480 de.argmax_token_device_col(&prev_logits, 0, width, toks, drafted)?;
15481 if guard {
15482 de.prob_of_token_device_col(
15483 &prev_logits,
15484 toks,
15485 drafted,
15486 probs,
15487 drafted,
15488 width,
15489 )?;
15490 if opts.defer_guard_sync {
15491 let p = de
15492 .gpu
15493 .stream()
15494 .clone_dtoh(&probs.slice(drafted..drafted + 1))?[0];
15495 if p < opts.pmin {
15496 report.guard_stops += 1;
15497 break;
15498 }
15499 }
15500 }
15501 drafted += 1;
15502 }
15503 self.mtp_recycle(dstate, prev_logits, prev_carrier);
15504 if drafted > 0 {
15509 let raw = de.gpu.stream().clone_dtoh(&toks.slice(0..drafted))?;
15510 let trunc = if guard && !opts.defer_guard_sync {
15511 let pw = de.gpu.stream().clone_dtoh(&probs.slice(0..drafted))?;
15512 let trunc = spec_guard_trunc(&pw, opts.pmin);
15513 if trunc < drafted {
15514 report.guard_stops += 1;
15515 }
15516 trunc
15517 } else {
15518 drafted
15519 };
15520 for &r in raw.iter().take(trunc) {
15521 drafts.push(self.draft_token(r)?);
15522 }
15523 }
15524 } else {
15525 let (d1, c1) = self.draft_row_argmax(de, &tip_logits, 0, guard)?;
15526 if !(guard && c1 < opts.pmin) {
15527 drafts.push(d1);
15528 if tracing {
15529 chain_rows_h
15530 .push(de.dtoh_view(&tip_logits.slice(0..self.draft_logits_width()))?);
15531 }
15532 } else {
15533 report.guard_stops += 1;
15534 }
15535 let mut prev_logits = tip_logits;
15536 let mut prev_carrier = tip_carrier;
15537 let mut prev_rows = tip_rows;
15538 while !drafts.is_empty() && drafts.len() < k_round {
15539 if tracing {
15540 seeds_h.push(de.dtoh_view(
15541 &prev_carrier.slice((prev_rows - 1) * wide_w..prev_rows * wide_w),
15542 )?);
15543 }
15544 let lastd = *drafts.last().expect("non-empty");
15545 let (l2, c2) = self.mtp_draft_forward(
15546 de,
15547 &[lastd],
15548 &prev_carrier,
15549 prev_rows - 1,
15550 dstate,
15551 1,
15552 false,
15553 )?;
15554 self.mtp_recycle(dstate, prev_logits, prev_carrier);
15555 prev_logits = l2;
15556 prev_carrier = c2;
15557 prev_rows = 1;
15558 let (dn, cn) = self.draft_row_argmax(de, &prev_logits, 0, guard)?;
15559 if guard && cn < opts.pmin {
15560 report.guard_stops += 1;
15561 break;
15562 }
15563 drafts.push(dn);
15564 if tracing {
15565 chain_rows_h
15566 .push(de.dtoh_view(&prev_logits.slice(0..self.draft_logits_width()))?);
15567 }
15568 }
15569 self.mtp_recycle(dstate, prev_logits, prev_carrier);
15570 }
15571 let chain_ms = t_draft.elapsed().as_secs_f64() * 1e3;
15572 report.chain_ms += chain_ms;
15573 report.draft_ms += chain_ms;
15574
15575 let t_ver = Instant::now();
15578 let mut chunk = Vec::with_capacity(drafts.len() + 1);
15579 chunk.push(tip);
15580 chunk.extend_from_slice(&drafts);
15581 let tlen = chunk.len();
15582 let host_logits = self.forward(e, &chunk, state, None)?;
15583 let targets: Vec<u32> = if greedy && !tracing && (tlen > 1 || deferred) {
15586 self.verify_argmax_rows(state)?.to_vec()
15587 } else if greedy {
15588 (0..tlen)
15589 .map(|row| host_argmax(&host_logits[row * vocab..(row + 1) * vocab]) as u32)
15590 .collect()
15591 } else {
15592 let cfg = sampler.as_ref().expect("sampled mode");
15593 (0..tlen)
15594 .map(|row| {
15595 sample_row(cfg, &mut rng, &host_logits[row * vocab..(row + 1) * vocab])
15596 })
15597 .collect()
15598 };
15599 report.verify_ms += t_ver.elapsed().as_secs_f64() * 1e3;
15600 if targets.len() != tlen {
15601 return Err("qwen4exp_gpu: verify produced the wrong row count".into());
15602 }
15603
15604 let mut a = 0usize;
15606 while a < drafts.len() && drafts[a] == targets[a] {
15607 a += 1;
15608 }
15609 report.rounds += 1;
15610 report.drafted += drafts.len() as u64;
15611 report.accepted += a as u64;
15612 report.accept_hist[a] += 1;
15613 if drafts.is_empty() {
15614 report.zero_draft_rounds += 1;
15615 }
15616 report.tokens.extend_from_slice(&targets[0..=a]);
15617
15618 if let Some(tr) = trace.as_deref_mut() {
15621 let mut rec = SpecTraceRound {
15622 round: round_idx,
15623 gen_pos: report.tokens.len() - (a + 1),
15624 base: m,
15625 k: drafts.len(),
15626 a,
15627 drafts: drafts.clone(),
15628 targets: targets.clone(),
15629 draft_top1: f32::NAN,
15630 draft_top2: f32::NAN,
15631 draft_tgt_logit: f32::NAN,
15632 draft_tgt_rank: 0,
15633 target_top1: f32::NAN,
15634 target_top2: f32::NAN,
15635 target_draft_logit: f32::NAN,
15636 target_entropy: 0.0,
15637 carrier_rel_l2: Vec::new(),
15638 carrier_cos: Vec::new(),
15639 };
15640 if a < drafts.len() {
15641 let drow = &chain_rows_h[a];
15642 let trow = &host_logits[a * vocab..(a + 1) * vocab];
15643 let tgt = targets[a] as usize;
15644 let dtok = drafts[a] as usize;
15645 let (mut d1v, mut d2v) = (f32::NEG_INFINITY, f32::NEG_INFINITY);
15646 let mut rank = 0usize;
15647 let dt = drow[tgt];
15648 for &v in drow.iter() {
15649 if v > d1v {
15650 d2v = d1v;
15651 d1v = v;
15652 } else if v > d2v {
15653 d2v = v;
15654 }
15655 if v > dt {
15656 rank += 1;
15657 }
15658 }
15659 let (mut t1v, mut t2v) = (f32::NEG_INFINITY, f32::NEG_INFINITY);
15660 for &v in trow.iter() {
15661 if v > t1v {
15662 t2v = t1v;
15663 t1v = v;
15664 } else if v > t2v {
15665 t2v = v;
15666 }
15667 }
15668 let mx = t1v as f64;
15670 let mut z = 0.0f64;
15671 let mut sxl = 0.0f64;
15672 for &v in trow.iter() {
15673 let ev = ((v as f64) - mx).exp();
15674 z += ev;
15675 sxl += ev * ((v as f64) - mx);
15676 }
15677 rec.draft_top1 = d1v;
15678 rec.draft_top2 = d2v;
15679 rec.draft_tgt_logit = dt;
15680 rec.draft_tgt_rank = rank;
15681 rec.target_top1 = t1v;
15682 rec.target_top2 = t2v;
15683 rec.target_draft_logit = trow[dtok];
15684 rec.target_entropy = z.ln() - sxl / z;
15685 }
15686 let v = state.verify.as_ref().expect("armed above");
15687 for (j, seed) in seeds_h.iter().enumerate() {
15688 let slot = (m + j) % ring;
15689 let truth = e.dtoh_view(&v.wide.slice(slot * wide_w..(slot + 1) * wide_w))?;
15690 let mut dd = 0.0f64;
15691 let mut tt = 0.0f64;
15692 let mut st = 0.0f64;
15693 let mut ss = 0.0f64;
15694 for (&s, &t) in seed.iter().zip(truth.iter()) {
15695 let (s, t) = (s as f64, t as f64);
15696 dd += (s - t) * (s - t);
15697 tt += t * t;
15698 st += s * t;
15699 ss += s * s;
15700 }
15701 rec.carrier_rel_l2
15702 .push((dd.sqrt() / tt.sqrt().max(1e-30)) as f32);
15703 rec.carrier_cos
15704 .push((st / (ss.sqrt() * tt.sqrt()).max(1e-30)) as f32);
15705 }
15706 tr.push(rec);
15707 }
15708
15709 if tlen > 1 {
15711 self.verify_rewind(e, state, a + 1)?;
15712 }
15713 self.mtp_rewind(dstate, m)?;
15714 let t_draft2 = Instant::now();
15715 let x_next = targets[a];
15716 let mut replay: Vec<u32> = drafts[0..a].to_vec();
15717 replay.push(x_next);
15718 if dev1 {
15719 let v = state.verify.as_mut().expect("armed above");
15720 let VerifyStash {
15721 wide, wide_dev1, ..
15722 } = v;
15723 let mirror = wide_dev1.as_mut().expect("allocated above");
15724 for (slot, len) in ring_pieces(ring, m, replay.len()) {
15725 report.cross_ms += cross_wide_rows(e, de, wide, mirror, slot, len, wide_w)?;
15726 }
15727 report.cross_bytes += (replay.len() * wide_w * 4) as u64;
15728 }
15729 let (l, c, last_len) = {
15730 let v = state.verify.as_ref().expect("armed above");
15731 let seed: &CudaSlice<f32> = v.wide_dev1.as_ref().unwrap_or(&v.wide);
15732 self.draft_consume_ring(de, &replay, dev_embed, seed, ring, m, dstate)?
15733 };
15734 tip_logits = l;
15735 tip_carrier = c;
15736 tip_rows = last_len;
15737 dstate.committed = dstate.rows;
15738 let replay_ms = t_draft2.elapsed().as_secs_f64() * 1e3;
15739 report.replay_ms += replay_ms;
15740 report.draft_ms += replay_ms;
15741 m += a + 1;
15742 tip = x_next;
15743 report
15744 .round_wall
15745 .push((report.tokens.len(), t_total.elapsed().as_secs_f64() * 1e3));
15746
15747 if let Some(lo) = opts.adapt_k_lo {
15749 k_next = (a + 1).clamp(lo.max(1), k);
15750 }
15751 if let Some(cfg) = opts.dynk {
15752 window.push(a);
15753 if window.len() >= cfg.window.max(1) {
15754 let mean = window.iter().sum::<usize>() as f64 / window.len() as f64;
15755 if mean < cfg.thr {
15756 let new_k = k_cur.saturating_sub(1).max(cfg.k_floor);
15757 if new_k < k_cur {
15758 k_cur = new_k;
15759 report.k_decays.push((round_idx, k_cur));
15760 if k_cur == 0 {
15761 report.spec_off_at = Some(report.tokens.len());
15762 }
15763 }
15764 }
15765 window.clear();
15766 }
15767 }
15768 round_idx += 1;
15769 }
15770 report.tokens.truncate(max_new);
15771 report.total_ms = t_total.elapsed().as_secs_f64() * 1e3;
15772 Ok(report)
15773 }
15774}
15775
15776enum BankTensorSrc {
15794 F32(Vec<f32>),
15796 Nvfp4 {
15804 codes: Vec<u8>,
15805 scales: Vec<u8>,
15806 macros: Vec<f32>,
15807 act_scale: Option<f32>,
15808 },
15809 Bf16(Vec<u8>),
15812}
15813
15814struct BankSrc {
15815 gate: BankTensorSrc, up: BankTensorSrc, down: BankTensorSrc, n_expert: usize,
15819 ff: usize,
15820 hidden: usize,
15821}
15822
15823struct FusedTensorPlan {
15826 name: String,
15827 shape: [usize; 3],
15828}
15829
15830struct PerExpertPlan {
15835 names: Vec<String>, out_f: usize,
15837 in_f: usize,
15838 quant: memra_gguf::tensor_contract::QuantConstraint,
15839}
15840
15841enum BankPlanSrc {
15842 Fused {
15844 gate_up: FusedTensorPlan,
15845 down: FusedTensorPlan,
15846 keep_bf16: bool,
15851 },
15852 PerExpert {
15854 gate: PerExpertPlan,
15855 up: PerExpertPlan,
15856 down: PerExpertPlan,
15857 },
15858}
15859
15860struct BankPlan {
15874 n_expert: usize,
15875 ff: usize,
15876 hidden: usize,
15877 src: BankPlanSrc,
15878}
15879
15880impl BankPlan {
15881 fn read(&self, model: &memra_gguf::safetensors::StModel) -> Res<BankSrc> {
15884 let (gate, up, down) = match &self.src {
15885 BankPlanSrc::Fused {
15886 gate_up,
15887 down,
15888 keep_bf16,
15889 } => {
15890 let fused = read_bank_tensor(
15891 model,
15892 &gate_up.name,
15893 gate_up.shape[0],
15894 gate_up.shape[1],
15895 gate_up.shape[2],
15896 *keep_bf16,
15897 )?;
15898 let (gate, up) = split_fused_gate_up(fused, self.n_expert, self.ff, self.hidden)?;
15899 let down = read_bank_tensor(
15900 model,
15901 &down.name,
15902 down.shape[0],
15903 down.shape[1],
15904 down.shape[2],
15905 *keep_bf16,
15906 )?;
15907 (gate, up, down)
15908 }
15909 BankPlanSrc::PerExpert { gate, up, down } => (
15910 read_per_expert_bank(model, gate)?,
15911 read_per_expert_bank(model, up)?,
15912 read_per_expert_bank(model, down)?,
15913 ),
15914 };
15915 Ok(BankSrc {
15916 gate,
15917 up,
15918 down,
15919 n_expert: self.n_expert,
15920 ff: self.ff,
15921 hidden: self.hidden,
15922 })
15923 }
15924}
15925
15926fn check_bank_header(model: &memra_gguf::safetensors::StModel, name: &str) -> Res<()> {
15936 let info = model
15937 .info(name)
15938 .ok_or_else(|| format!("qwen4exp_gpu: checkpoint is missing {name}"))?;
15939 match info.dtype.as_str() {
15940 "BF16" | "F32" | "U8" => Ok(()),
15941 other => Err(format!("qwen4exp_gpu: {name} bank dtype {other} unsupported").into()),
15942 }
15943}
15944
15945fn read_per_expert_bank(
15948 model: &memra_gguf::safetensors::StModel,
15949 plan: &PerExpertPlan,
15950) -> Res<BankTensorSrc> {
15951 let mut experts = Vec::with_capacity(plan.names.len());
15952 for name in &plan.names {
15953 experts.push(read_per_expert(
15954 model, name, plan.out_f, plan.in_f, plan.quant,
15955 )?);
15956 }
15957 assemble_per_expert_bank(experts)
15958}
15959
15960pub struct LoadedCheckpoint {
15969 pub plan: ModelPlan,
15970 pub weights: ReferenceWeights,
15971 model: memra_gguf::safetensors::StModel,
15972 bank_plans: std::collections::BTreeMap<u32, BankPlan>,
15973 tables: std::collections::BTreeMap<u32, Vec<u8>>, }
15975
15976fn norm_fold_add_one(name: &str) -> bool {
15982 name.contains("norm") && name.ends_with(".weight") && !name.ends_with("linear_attn.norm.weight")
15983}
15984
15985fn indexer_layernorm(name: &str) -> bool {
15989 name.contains(".indexer.")
15990 && (name.ends_with("q_layernorm.weight") || name.ends_with("k_layernorm.weight"))
15991}
15992
15993#[derive(Default, Clone, Copy)]
15995pub struct LoadOptions {
15996 pub host_bf16_banks: bool,
16001 pub indexer_norm_raw: bool,
16004 pub load_mtp: bool,
16009}
16010
16011fn bridge_transform(
16012 transform: memra_gguf::tensor_contract::TensorTransform,
16013) -> Res<memra_gguf::hf_mapping::TransformKind> {
16014 use memra_gguf::hf_mapping::TransformKind as K;
16015 use memra_gguf::tensor_contract::TensorTransform as T;
16016 Ok(match transform {
16017 T::Identity => K::Identity,
16018 T::NormAddOne => K::NormPlusOne,
16019 T::QkvVReorderRows => K::QkvVReorderRows,
16020 T::ZReorderRows => K::ZReorderRows,
16021 T::AbReorderRows => K::AbReorderRows,
16022 T::NegExpReorderHeads => K::NegExpReorderHeads,
16023 T::ReorderHeads => K::ReorderHeads,
16024 T::Conv1dSqueezeReorder => K::Conv1dSqueezeReorder,
16025 T::OutReorderColumns => K::OutReorderCols,
16026 other => return Err(format!("qwen4exp_gpu: unsupported transform {other:?}").into()),
16027 })
16028}
16029
16030fn dequant_float(
16031 name: &str,
16032 info: &memra_gguf::safetensors::StInfo,
16033 bytes: &[u8],
16034) -> Res<Vec<f32>> {
16035 let elements: usize = info.shape.iter().map(|&d| d as usize).product();
16036 match info.dtype.as_str() {
16037 "BF16" | "F32" => Ok(memra_gguf::dequant::dequantize(
16038 info.ggml_type()
16039 .map_err(|error| format!("qwen4exp_gpu: {name}: {error}"))?,
16040 bytes,
16041 elements,
16042 )),
16043 other => Err(format!("qwen4exp_gpu: {name} has unsupported float dtype {other}").into()),
16044 }
16045}
16046
16047fn read_i64(name: &str, info: &memra_gguf::safetensors::StInfo, bytes: &[u8]) -> Res<Vec<i64>> {
16048 if info.dtype != "I64" {
16049 return Err(format!("qwen4exp_gpu: {name} must be I64, got {}", info.dtype).into());
16050 }
16051 Ok(bytes
16052 .chunks_exact(8)
16053 .map(|chunk| i64::from_le_bytes(chunk.try_into().unwrap()))
16054 .collect())
16055}
16056
16057fn validate_macro(stem: &str, value: f32) -> Res<()> {
16065 if !(value.is_finite() && value > 0.0) {
16066 return Err(format!(
16067 "qwen4exp_gpu: {stem}.weight_scale_2 carries a non-finite/non-positive \
16068 macro {value}"
16069 )
16070 .into());
16071 }
16072 Ok(())
16073}
16074
16075fn read_bank_tensor(
16080 model: &memra_gguf::safetensors::StModel,
16081 name: &str,
16082 n_expert: usize,
16083 out_f: usize,
16084 in_f: usize,
16085 host_bf16: bool,
16086) -> Res<BankTensorSrc> {
16087 let (info, bytes) = model
16088 .raw(name)
16089 .ok_or_else(|| format!("qwen4exp_gpu: checkpoint is missing {name}"))?;
16090 match info.dtype.as_str() {
16091 "BF16" | "F32" => {
16092 if info.shape != [n_expert as u64, out_f as u64, in_f as u64] {
16093 return Err(format!("qwen4exp_gpu: {name} bank shape mismatch").into());
16094 }
16095 if host_bf16 && info.dtype == "BF16" {
16096 if bytes.len() != n_expert * out_f * in_f * 2 {
16097 return Err(format!("qwen4exp_gpu: {name} bank byte-length mismatch").into());
16098 }
16099 return Ok(BankTensorSrc::Bf16(bytes.to_vec()));
16100 }
16101 Ok(BankTensorSrc::F32(dequant_float(name, info, bytes)?))
16102 }
16103 "U8" => {
16104 if in_f % 16 != 0
16105 || info.shape != [n_expert as u64, out_f as u64, (in_f / 2) as u64]
16106 || bytes.len() != n_expert * out_f * in_f / 2
16107 {
16108 return Err(format!("qwen4exp_gpu: {name} NVFP4 code shape mismatch").into());
16109 }
16110 let stem = name.strip_suffix(".weight").unwrap_or(name);
16111 let scale_name = format!("{stem}.weight_scale");
16112 let (scale_info, scale_bytes) = model
16113 .raw(&scale_name)
16114 .ok_or_else(|| format!("qwen4exp_gpu: missing {scale_name}"))?;
16115 if scale_info.dtype != "F8_E4M3"
16116 || scale_info.shape != [n_expert as u64, out_f as u64, (in_f / 16) as u64]
16117 || scale_bytes.len() != n_expert * out_f * in_f / 16
16118 {
16119 return Err(format!("qwen4exp_gpu: {scale_name} shape mismatch").into());
16120 }
16121 let macros = match model.raw(&format!("{stem}.weight_scale_2")) {
16122 Some((macro_info, macro_bytes))
16123 if macro_info.dtype == "F32" && macro_bytes.len() == n_expert * 4 =>
16124 {
16125 macro_bytes
16126 .chunks_exact(4)
16127 .map(|chunk| f32::from_le_bytes(chunk.try_into().unwrap()))
16128 .collect()
16129 }
16130 None => vec![1.0; n_expert],
16131 _ => return Err(format!("qwen4exp_gpu: {stem}.weight_scale_2 malformed").into()),
16132 };
16133 for &m in ¯os {
16134 validate_macro(stem, m)?;
16135 }
16136 let act_scale = match model.raw(&format!("{stem}.input_scale")) {
16140 Some((is_info, is_bytes))
16141 if is_info.dtype == "F32" && is_bytes.len() == n_expert * 4 =>
16142 {
16143 let mut mx = 0.0f32;
16144 for chunk in is_bytes.chunks_exact(4) {
16145 let v = f32::from_le_bytes(chunk.try_into().unwrap());
16146 if !(v.is_finite() && v > 0.0) {
16147 return Err(format!(
16148 "qwen4exp_gpu: {stem}.input_scale carries a non-finite/\
16149 non-positive value {v}"
16150 )
16151 .into());
16152 }
16153 mx = mx.max(v);
16154 }
16155 Some(mx)
16156 }
16157 Some(_) => {
16158 return Err(format!("qwen4exp_gpu: {stem}.input_scale malformed").into());
16159 }
16160 None => None,
16161 };
16162 Ok(BankTensorSrc::Nvfp4 {
16163 codes: bytes.to_vec(),
16164 scales: scale_bytes.to_vec(),
16165 macros,
16166 act_scale,
16167 })
16168 }
16169 other => Err(format!("qwen4exp_gpu: {name} bank dtype {other} unsupported").into()),
16170 }
16171}
16172
16173fn split_fused_gate_up(
16177 fused: BankTensorSrc,
16178 n_expert: usize,
16179 ff: usize,
16180 hidden: usize,
16181) -> Res<(BankTensorSrc, BankTensorSrc)> {
16182 match fused {
16183 BankTensorSrc::F32(data) => {
16184 if data.len() != n_expert * 2 * ff * hidden {
16185 return Err("qwen4exp_gpu: fused gate_up bank size mismatch".into());
16186 }
16187 let mut gate = Vec::with_capacity(n_expert * ff * hidden);
16188 let mut up = Vec::with_capacity(n_expert * ff * hidden);
16189 for expert in 0..n_expert {
16190 let base = expert * 2 * ff * hidden;
16191 gate.extend_from_slice(&data[base..base + ff * hidden]);
16192 up.extend_from_slice(&data[base + ff * hidden..base + 2 * ff * hidden]);
16193 }
16194 Ok((BankTensorSrc::F32(gate), BankTensorSrc::F32(up)))
16195 }
16196 BankTensorSrc::Bf16(bytes) => {
16197 let row = hidden * 2; if bytes.len() != n_expert * 2 * ff * row {
16199 return Err("qwen4exp_gpu: fused bf16 gate_up bank size mismatch".into());
16200 }
16201 let mut gate = Vec::with_capacity(n_expert * ff * row);
16202 let mut up = Vec::with_capacity(n_expert * ff * row);
16203 for expert in 0..n_expert {
16204 let base = expert * 2 * ff * row;
16205 gate.extend_from_slice(&bytes[base..base + ff * row]);
16206 up.extend_from_slice(&bytes[base + ff * row..base + 2 * ff * row]);
16207 }
16208 Ok((BankTensorSrc::Bf16(gate), BankTensorSrc::Bf16(up)))
16209 }
16210 BankTensorSrc::Nvfp4 {
16211 codes,
16212 scales,
16213 macros,
16214 act_scale,
16215 } => {
16216 let code_row = hidden / 2;
16217 let scale_row = hidden / 16;
16218 let mut gate_codes = Vec::with_capacity(n_expert * ff * code_row);
16219 let mut up_codes = Vec::with_capacity(n_expert * ff * code_row);
16220 let mut gate_scales = Vec::with_capacity(n_expert * ff * scale_row);
16221 let mut up_scales = Vec::with_capacity(n_expert * ff * scale_row);
16222 for expert in 0..n_expert {
16223 let cbase = expert * 2 * ff * code_row;
16224 gate_codes.extend_from_slice(&codes[cbase..cbase + ff * code_row]);
16225 up_codes
16226 .extend_from_slice(&codes[cbase + ff * code_row..cbase + 2 * ff * code_row]);
16227 let sbase = expert * 2 * ff * scale_row;
16228 gate_scales.extend_from_slice(&scales[sbase..sbase + ff * scale_row]);
16229 up_scales
16230 .extend_from_slice(&scales[sbase + ff * scale_row..sbase + 2 * ff * scale_row]);
16231 }
16232 Ok((
16233 BankTensorSrc::Nvfp4 {
16234 codes: gate_codes,
16235 scales: gate_scales,
16236 macros: macros.clone(),
16237 act_scale,
16238 },
16239 BankTensorSrc::Nvfp4 {
16240 codes: up_codes,
16241 scales: up_scales,
16242 macros,
16243 act_scale,
16244 },
16245 ))
16246 }
16247 }
16248}
16249
16250enum PerExpertSrc {
16255 F32(Vec<f32>),
16256 Nvfp4 {
16257 codes: Vec<u8>,
16258 scales: Vec<u8>,
16259 macro_scale: f32,
16260 input_scale: Option<f32>,
16261 },
16262}
16263
16264fn read_per_expert(
16265 model: &memra_gguf::safetensors::StModel,
16266 name: &str,
16267 out_f: usize,
16268 in_f: usize,
16269 quant: memra_gguf::tensor_contract::QuantConstraint,
16270) -> Res<PerExpertSrc> {
16271 use memra_gguf::tensor_contract::QuantConstraint;
16272 let (info, bytes) = model
16273 .raw(name)
16274 .ok_or_else(|| format!("qwen4exp_gpu: checkpoint is missing {name}"))?;
16275 match quant {
16276 QuantConstraint::ExactFloat(_) => {
16277 if info.shape != [out_f as u64, in_f as u64] {
16278 return Err(format!("qwen4exp_gpu: {name} shape mismatch").into());
16279 }
16280 Ok(PerExpertSrc::F32(dequant_float(name, info, bytes)?))
16281 }
16282 QuantConstraint::Nvfp4 => {
16283 if info.dtype != "U8"
16284 || in_f % 16 != 0
16285 || info.shape != [out_f as u64, (in_f / 2) as u64]
16286 || bytes.len() != out_f * in_f / 2
16287 {
16288 return Err(format!("qwen4exp_gpu: {name} NVFP4 code shape mismatch").into());
16289 }
16290 let stem = name.strip_suffix(".weight").unwrap_or(name);
16291 let (scale_info, scale_bytes) = model
16292 .raw(&format!("{stem}.weight_scale"))
16293 .ok_or_else(|| format!("qwen4exp_gpu: missing {stem}.weight_scale"))?;
16294 if scale_info.dtype != "F8_E4M3"
16295 || scale_info.shape != [out_f as u64, (in_f / 16) as u64]
16296 || scale_bytes.len() != out_f * in_f / 16
16297 {
16298 return Err(format!("qwen4exp_gpu: {stem}.weight_scale shape mismatch").into());
16299 }
16300 let macro_scale = match model.raw(&format!("{stem}.weight_scale_2")) {
16301 Some((macro_info, macro_bytes))
16302 if macro_info.dtype == "F32" && macro_bytes.len() == 4 =>
16303 {
16304 f32::from_le_bytes(macro_bytes.try_into().unwrap())
16305 }
16306 None => 1.0,
16307 _ => return Err(format!("qwen4exp_gpu: {stem}.weight_scale_2 malformed").into()),
16308 };
16309 validate_macro(stem, macro_scale)?;
16310 let input_scale = match model.raw(&format!("{stem}.input_scale")) {
16315 Some((input_info, input_bytes)) => {
16316 if input_info.dtype != "F32" || input_bytes.len() != 4 {
16317 return Err(format!("qwen4exp_gpu: {stem}.input_scale malformed").into());
16318 }
16319 let v = f32::from_le_bytes(input_bytes.try_into().unwrap());
16320 if !(v.is_finite() && v > 0.0) {
16321 return Err(format!(
16322 "qwen4exp_gpu: {stem}.input_scale carries a non-finite/non-positive \
16323 value {v}"
16324 )
16325 .into());
16326 }
16327 Some(v)
16328 }
16329 None => None,
16330 };
16331 Ok(PerExpertSrc::Nvfp4 {
16332 codes: bytes.to_vec(),
16333 scales: scale_bytes.to_vec(),
16334 macro_scale,
16335 input_scale,
16336 })
16337 }
16338 other => Err(format!("qwen4exp_gpu: per-expert quant {other:?} unsupported").into()),
16339 }
16340}
16341
16342fn assemble_per_expert_bank(experts: Vec<PerExpertSrc>) -> Res<BankTensorSrc> {
16345 let mut f32_data: Vec<f32> = Vec::new();
16346 let mut codes: Vec<u8> = Vec::new();
16347 let mut scales: Vec<u8> = Vec::new();
16348 let mut macros: Vec<f32> = Vec::new();
16349 let mut act_scale: Option<f32> = None;
16350 let mut act_scale_complete = true;
16351 let mut kinds = (false, false);
16352 for expert in experts {
16353 match expert {
16354 PerExpertSrc::F32(data) => {
16355 kinds.0 = true;
16356 f32_data.extend_from_slice(&data);
16357 }
16358 PerExpertSrc::Nvfp4 {
16359 codes: c,
16360 scales: s,
16361 macro_scale,
16362 input_scale,
16363 } => {
16364 kinds.1 = true;
16365 codes.extend_from_slice(&c);
16366 scales.extend_from_slice(&s);
16367 macros.push(macro_scale);
16368 match input_scale {
16369 Some(v) => act_scale = Some(act_scale.map_or(v, |a: f32| a.max(v))),
16370 None => act_scale_complete = false,
16371 }
16372 }
16373 }
16374 }
16375 match kinds {
16376 (true, false) => Ok(BankTensorSrc::F32(f32_data)),
16377 (false, true) => Ok(BankTensorSrc::Nvfp4 {
16378 codes,
16379 scales,
16380 macros,
16381 act_scale: if act_scale_complete { act_scale } else { None },
16382 }),
16383 _ => Err("qwen4exp_gpu: mixed per-expert kinds within one projection".into()),
16384 }
16385}
16386
16387fn family_layer_index(key: &str) -> Option<u32> {
16389 key.strip_prefix("trunk.layers.")?
16390 .split('.')
16391 .next()?
16392 .parse()
16393 .ok()
16394}
16395
16396fn plan_layer_at(plan: &ModelPlan, index: u32) -> Option<&memra_gguf::model_plan::LayerPlan> {
16404 let n_trunk = plan.layers.len() as u32;
16405 if index < n_trunk {
16406 plan.layers.get(index as usize)
16407 } else {
16408 plan.mtp_blocks
16409 .iter()
16410 .find(|block| block.layer.index == index)
16411 .map(|block| &block.layer)
16412 }
16413}
16414
16415pub fn read_checkpoint(dir: &std::path::Path) -> Res<LoadedCheckpoint> {
16416 read_checkpoint_with(dir, LoadOptions::default())
16417}
16418
16419pub fn read_checkpoint_with(dir: &std::path::Path, opts: LoadOptions) -> Res<LoadedCheckpoint> {
16421 use memra_gguf::model_packs::qwen4_exp::{ExpertDialect, tensor_contract_for};
16422 use memra_gguf::tensor_contract::{TensorMatch, TensorOwner};
16423 let config = std::fs::read_to_string(dir.join("config.json"))?;
16424 let cfg =
16425 memra_gguf::config::ModelConfig::from_hf(&memra_gguf::config::HfConfig::parse(&config));
16426 let pack = memra_gguf::model_packs::for_config(&cfg)
16427 .ok_or("qwen4exp_gpu: no model pack matches this config")?;
16428 if pack.family != "qwen4_exp" {
16429 return Err(format!("qwen4exp_gpu: config resolves to pack {}", pack.family).into());
16430 }
16431 let plan = pack.compile_plan(&cfg)?;
16432 let model = memra_gguf::safetensors::StModel::open(dir)?;
16433 let dialect = if model
16435 .raw("model.language_model.layers.0.mlp.experts.0.gate_proj.weight")
16436 .is_some()
16437 {
16438 ExpertDialect::PerExpertModelopt
16439 } else {
16440 ExpertDialect::FusedBanks
16441 };
16442 let contract = tensor_contract_for(&cfg, &plan, dialect)?;
16443
16444 let mut weights = ReferenceWeights::new();
16445 let mut gate_up_banks: std::collections::BTreeMap<u32, FusedTensorPlan> = Default::default();
16446 let mut per_expert: std::collections::BTreeMap<
16450 (u32, u8),
16451 std::collections::BTreeMap<
16452 u32,
16453 (
16454 String,
16455 usize,
16456 usize,
16457 memra_gguf::tensor_contract::QuantConstraint,
16458 ),
16459 >,
16460 > = Default::default();
16461 let mut down_banks: std::collections::BTreeMap<u32, FusedTensorPlan> = Default::default();
16462 let mut tables: std::collections::BTreeMap<u32, Vec<u8>> = Default::default();
16463 let n_trunk = plan.layers.len() as u32;
16464
16465 for requirement in &contract.requirements {
16466 match requirement.owner {
16467 TensorOwner::Mtp(_) if !opts.load_mtp => continue,
16471 TensorOwner::Vision(_) => continue,
16472 TensorOwner::Global | TensorOwner::Layer(_) | TensorOwner::Mtp(_) => {}
16473 }
16474 if requirement.match_mode == TensorMatch::All {
16476 let TensorId::Family { key, .. } = &requirement.id else {
16477 return Err("qwen4exp_gpu: unexpected All-mode requirement".into());
16478 };
16479 let layer =
16480 family_layer_index(key).ok_or("qwen4exp_gpu: n-gram bank outside a trunk layer")?;
16481 let mut bytes = Vec::new();
16482 for name in &requirement.names {
16483 let (info, shard) = model
16484 .raw(name)
16485 .ok_or_else(|| format!("qwen4exp_gpu: checkpoint is missing {name}"))?;
16486 if info.dtype != "BF16" || info.shape != requirement.shape {
16487 return Err(format!("qwen4exp_gpu: {name} shard shape/dtype mismatch").into());
16488 }
16489 bytes.extend_from_slice(shard);
16490 }
16491 tables.insert(layer, bytes);
16492 continue;
16493 }
16494 let name = &requirement.names[0];
16495 if let TensorId::Family { key, .. } = &requirement.id {
16498 if key.ends_with(".ple_embedding.ngram_embedding") {
16499 let layer = family_layer_index(key)
16500 .ok_or("qwen4exp_gpu: n-gram table outside a trunk layer")?;
16501 let (info, bytes) = model
16502 .raw(name)
16503 .ok_or_else(|| format!("qwen4exp_gpu: checkpoint is missing {name}"))?;
16504 if info.dtype != "BF16" || info.shape != requirement.shape {
16505 return Err(format!("qwen4exp_gpu: {name} table shape/dtype mismatch").into());
16506 }
16507 tables.insert(layer, bytes.to_vec());
16508 continue;
16509 }
16510 }
16511 if let TensorId::Expert {
16513 layer,
16514 expert,
16515 tensor,
16516 } = requirement.id
16517 {
16518 let (out_f, in_f) = (requirement.shape[0] as usize, requirement.shape[1] as usize);
16519 check_bank_header(&model, name)?;
16520 let proj = match tensor {
16521 memra_gguf::tensor_contract::ExpertTensor::Gate => 0u8,
16522 memra_gguf::tensor_contract::ExpertTensor::Up => 1,
16523 memra_gguf::tensor_contract::ExpertTensor::Down => 2,
16524 };
16525 if per_expert
16526 .entry((layer, proj))
16527 .or_default()
16528 .insert(expert, (name.clone(), out_f, in_f, requirement.quant))
16529 .is_some()
16530 {
16531 return Err(format!(
16532 "qwen4exp_gpu: duplicate per-expert row layer {layer} expert {expert}"
16533 )
16534 .into());
16535 }
16536 continue;
16537 }
16538 if let TensorId::Layer { index, tensor } = requirement.id {
16540 if matches!(
16541 tensor,
16542 LayerTensor::MoeExpertGateUpBank | LayerTensor::MoeExpertDownBank
16543 ) {
16544 let shape = [
16545 requirement.shape[0] as usize,
16546 requirement.shape[1] as usize,
16547 requirement.shape[2] as usize,
16548 ];
16549 check_bank_header(&model, name)?;
16550 let address = FusedTensorPlan {
16551 name: name.clone(),
16552 shape,
16553 };
16554 if tensor == LayerTensor::MoeExpertGateUpBank {
16555 gate_up_banks.insert(index, address);
16556 } else {
16557 down_banks.insert(index, address);
16558 }
16559 continue;
16560 }
16561 }
16562 let (info, bytes) = model
16563 .raw(name)
16564 .ok_or_else(|| format!("qwen4exp_gpu: checkpoint is missing {name}"))?;
16565 if info.shape != requirement.shape {
16566 return Err(format!(
16567 "qwen4exp_gpu: {name} shape {:?} != contract {:?}",
16568 info.shape, requirement.shape
16569 )
16570 .into());
16571 }
16572 if info.dtype == "I64" {
16573 let ints = read_i64(name, info, bytes)?;
16574 let shape: Vec<usize> = info.shape.iter().map(|&d| d as usize).collect();
16575 weights.insert(
16576 requirement.id.clone(),
16577 ReferenceTensor::new_i64(shape, ints)?,
16578 );
16579 continue;
16580 }
16581 let mut data = dequant_float(name, info, bytes)?;
16582 if norm_fold_add_one(name) && !(opts.indexer_norm_raw && indexer_layernorm(name)) {
16583 for value in &mut data {
16584 *value += 1.0;
16585 }
16586 }
16587 let kind = bridge_transform(requirement.transform)?;
16588 let (ne_out, out_bytes) = kind.apply(&mut data, info.ne(), &cfg);
16589 let data: Vec<f32> = out_bytes
16590 .chunks_exact(4)
16591 .map(|chunk| f32::from_le_bytes(chunk.try_into().unwrap()))
16592 .collect();
16593 let mut shape: Vec<usize> = ne_out.iter().rev().map(|&d| d as usize).collect();
16594 if name.ends_with("ple.conv1d.weight") && shape.len() == 3 && shape[1] == 1 {
16597 shape = vec![shape[0], shape[2]];
16598 }
16599 if name.ends_with("mlp.shared_expert_gate.weight") && shape.len() == 2 && shape[0] == 1 {
16601 shape = vec![shape[1]];
16602 }
16603 weights.insert(requirement.id.clone(), ReferenceTensor::new(shape, data)?);
16604 }
16605
16606 let mut bank_plans = std::collections::BTreeMap::new();
16607 for (index, gate_up) in gate_up_banks {
16611 let down = down_banks
16612 .remove(&index)
16613 .ok_or_else(|| format!("qwen4exp_gpu: layer {index} has gate_up but no down bank"))?;
16614 let layer_plan = plan_layer_at(&plan, index)
16615 .ok_or_else(|| format!("qwen4exp_gpu: bank at unknown layer index {index}"))?;
16616 let MlpPlan::Moe(moe) = &layer_plan.mlp else {
16617 return Err(format!("qwen4exp_gpu: bank on non-MoE layer {index}").into());
16618 };
16619 bank_plans.insert(
16620 index,
16621 BankPlan {
16622 n_expert: moe.expert_count as usize,
16623 ff: moe.expert_intermediate_size as usize,
16624 hidden: plan.hidden_size as usize,
16625 src: BankPlanSrc::Fused {
16626 gate_up,
16627 down,
16628 keep_bf16: opts.host_bf16_banks || index >= n_trunk,
16631 },
16632 },
16633 );
16634 }
16635 if !down_banks.is_empty() {
16636 return Err("qwen4exp_gpu: down bank without a gate_up twin".into());
16637 }
16638 let mut per_layer: std::collections::BTreeMap<u32, [Option<PerExpertPlan>; 3]> =
16641 Default::default();
16642 for ((layer, proj), experts) in per_expert {
16643 let layer_plan = plan_layer_at(&plan, layer)
16644 .ok_or_else(|| format!("qwen4exp_gpu: per-expert rows at unknown layer {layer}"))?;
16645 let MlpPlan::Moe(moe) = &layer_plan.mlp else {
16646 return Err(format!("qwen4exp_gpu: per-expert rows on non-MoE layer {layer}").into());
16647 };
16648 let count = moe.expert_count as usize;
16649 if experts.len() != count || experts.keys().last().copied() != Some(count as u32 - 1) {
16652 return Err(format!(
16653 "qwen4exp_gpu: layer {layer} proj {proj} has {} experts, plan says {count}",
16654 experts.len()
16655 )
16656 .into());
16657 }
16658 let mut names = Vec::with_capacity(count);
16662 let mut geometry: Option<(usize, usize, memra_gguf::tensor_contract::QuantConstraint)> =
16663 None;
16664 for (name, out_f, in_f, quant) in experts.into_values() {
16665 match geometry {
16666 None => geometry = Some((out_f, in_f, quant)),
16667 Some((o, i, q)) if (o, i) == (out_f, in_f) && q == quant => {}
16668 Some((o, i, _)) => {
16669 return Err(format!(
16670 "qwen4exp_gpu: layer {layer} proj {proj} mixes expert geometry \
16671 ({out_f}, {in_f}) vs ({o}, {i}) or quant classes"
16672 )
16673 .into());
16674 }
16675 }
16676 names.push(name);
16677 }
16678 let (out_f, in_f, quant) = geometry
16679 .ok_or_else(|| format!("qwen4exp_gpu: layer {layer} proj {proj} has no expert rows"))?;
16680 per_layer.entry(layer).or_default()[proj as usize] = Some(PerExpertPlan {
16681 names,
16682 out_f,
16683 in_f,
16684 quant,
16685 });
16686 }
16687 for (layer, mut projections) in per_layer {
16688 let MlpPlan::Moe(moe) = &plan_layer_at(&plan, layer).expect("checked above").mlp else {
16689 unreachable!("checked above");
16690 };
16691 let take = |slot: &mut Option<PerExpertPlan>, what: &str| -> Res<PerExpertPlan> {
16692 slot.take()
16693 .ok_or_else(|| format!("qwen4exp_gpu: layer {layer} missing {what} experts").into())
16694 };
16695 bank_plans.insert(
16696 layer,
16697 BankPlan {
16698 n_expert: moe.expert_count as usize,
16699 ff: moe.expert_intermediate_size as usize,
16700 hidden: plan.hidden_size as usize,
16701 src: BankPlanSrc::PerExpert {
16702 gate: take(&mut projections[0], "gate")?,
16703 up: take(&mut projections[1], "up")?,
16704 down: take(&mut projections[2], "down")?,
16705 },
16706 },
16707 );
16708 }
16709 Ok(LoadedCheckpoint {
16710 plan,
16711 weights,
16712 model,
16713 bank_plans,
16714 tables,
16715 })
16716}
16717
16718pub struct BankFingerprint {
16726 pub layer: u32,
16727 pub projection: &'static str,
16729 pub kind: &'static str,
16731 pub bytes: usize,
16732 pub digest: String,
16734}
16735
16736impl LoadedCheckpoint {
16737 fn read_bank(&self, index: u32) -> Res<BankSrc> {
16740 self.bank_plans
16741 .get(&index)
16742 .ok_or_else(|| format!("qwen4exp_gpu: no bank source for layer {index}"))?
16743 .read(&self.model)
16744 }
16745
16746 pub fn bank_fingerprints(&self) -> Res<Vec<BankFingerprint>> {
16750 use sha2::{Digest, Sha256};
16751 let mut out = Vec::new();
16752 for (&layer, plan) in &self.bank_plans {
16753 let bank = plan.read(&self.model)?;
16754 for (projection, src) in [("gate", &bank.gate), ("up", &bank.up), ("down", &bank.down)]
16755 {
16756 let mut hasher = Sha256::new();
16757 let (kind, bytes) = match src {
16758 BankTensorSrc::F32(data) => {
16760 for value in data {
16761 hasher.update(value.to_le_bytes());
16762 }
16763 ("f32", data.len() * 4)
16764 }
16765 BankTensorSrc::Bf16(raw) => {
16766 hasher.update(raw);
16767 ("bf16", raw.len())
16768 }
16769 BankTensorSrc::Nvfp4 {
16770 codes,
16771 scales,
16772 macros,
16773 ..
16774 } => {
16775 hasher.update(codes);
16776 hasher.update(scales);
16777 for m in macros {
16778 hasher.update(m.to_le_bytes());
16779 }
16780 ("nvfp4", codes.len() + scales.len() + macros.len() * 4)
16781 }
16782 };
16783 out.push(BankFingerprint {
16784 layer,
16785 projection,
16786 kind,
16787 bytes,
16788 digest: hasher
16789 .finalize()
16790 .iter()
16791 .map(|b| format!("{b:02x}"))
16792 .collect(),
16793 });
16794 }
16795 }
16796 Ok(out)
16797 }
16798
16799 pub fn into_reference_weights(mut self) -> Res<ReferenceWeights> {
16803 let bank_plans = std::mem::take(&mut self.bank_plans);
16804 for (index, plan) in bank_plans {
16805 let bank = plan.read(&self.model)?;
16806 let gate = bank_to_f32(&bank.gate, bank.n_expert, bank.ff, bank.hidden)?;
16807 let up = bank_to_f32(&bank.up, bank.n_expert, bank.ff, bank.hidden)?;
16808 let down = bank_to_f32(&bank.down, bank.n_expert, bank.hidden, bank.ff)?;
16809 self.weights.insert(
16810 layer_id(index, LayerTensor::MoeExpertGateBank),
16811 ReferenceTensor::new(vec![bank.n_expert, bank.ff, bank.hidden], gate)?,
16812 );
16813 self.weights.insert(
16814 layer_id(index, LayerTensor::MoeExpertUpBank),
16815 ReferenceTensor::new(vec![bank.n_expert, bank.ff, bank.hidden], up)?,
16816 );
16817 self.weights.insert(
16818 layer_id(index, LayerTensor::MoeExpertDownBank),
16819 ReferenceTensor::new(vec![bank.n_expert, bank.hidden, bank.ff], down)?,
16820 );
16821 }
16822 for (index, bytes) in self.tables {
16823 let ple = self.plan.layers[index as usize]
16824 .ple
16825 .as_ref()
16826 .ok_or("qwen4exp_gpu: table on a non-PLE layer")?;
16827 let head_dim = ple.head_embed_dim as usize;
16828 let table = NgramTable::Bf16(bytes);
16829 let rows = table.rows(head_dim);
16830 let mut data = vec![0.0f32; rows * head_dim];
16831 for row in 0..rows {
16832 table.gather_into(
16833 row,
16834 head_dim,
16835 &mut data[row * head_dim..(row + 1) * head_dim],
16836 );
16837 }
16838 self.weights.insert(
16839 family_id(format!(
16840 "trunk.layers.{index}.ple.ple_embedding.ngram_embedding"
16841 )),
16842 ReferenceTensor::new(vec![rows, head_dim], data)?,
16843 );
16844 }
16845 Ok(self.weights)
16846 }
16847}
16848
16849impl LoadedCheckpoint {
16850 pub fn mtp_reference_weights(&self) -> Res<ReferenceWeights> {
16856 let mut weights = self.weights.clone();
16857 let n_trunk = self.plan.layers.len() as u32;
16858 for (index, plan) in &self.bank_plans {
16859 if *index < n_trunk {
16860 continue;
16861 }
16862 let bank = plan.read(&self.model)?;
16863 let gate = bank_to_f32(&bank.gate, bank.n_expert, bank.ff, bank.hidden)?;
16864 let up = bank_to_f32(&bank.up, bank.n_expert, bank.ff, bank.hidden)?;
16865 let down = bank_to_f32(&bank.down, bank.n_expert, bank.hidden, bank.ff)?;
16866 weights.insert(
16867 layer_id(*index, LayerTensor::MoeExpertGateBank),
16868 ReferenceTensor::new(vec![bank.n_expert, bank.ff, bank.hidden], gate)?,
16869 );
16870 weights.insert(
16871 layer_id(*index, LayerTensor::MoeExpertUpBank),
16872 ReferenceTensor::new(vec![bank.n_expert, bank.ff, bank.hidden], up)?,
16873 );
16874 weights.insert(
16875 layer_id(*index, LayerTensor::MoeExpertDownBank),
16876 ReferenceTensor::new(vec![bank.n_expert, bank.hidden, bank.ff], down)?,
16877 );
16878 }
16879 Ok(weights)
16880 }
16881}
16882
16883fn bank_to_f32(bank: &BankTensorSrc, n_expert: usize, out_f: usize, in_f: usize) -> Res<Vec<f32>> {
16886 match bank {
16887 BankTensorSrc::F32(data) => Ok(data.clone()),
16888 BankTensorSrc::Bf16(bytes) => Ok(bytes
16889 .chunks_exact(2)
16890 .map(|b| f32::from_bits(u32::from(u16::from_le_bytes([b[0], b[1]])) << 16))
16891 .collect()),
16892 BankTensorSrc::Nvfp4 {
16893 codes,
16894 scales,
16895 macros,
16896 ..
16897 } => {
16898 let mut out = Vec::with_capacity(n_expert * out_f * in_f);
16899 let wbytes = out_f * in_f / 2;
16900 let sbytes = out_f * in_f / 16;
16901 for expert in 0..n_expert {
16902 out.extend(memra_gguf::dsv4::dequant_nvfp4_expert(
16903 &codes[expert * wbytes..(expert + 1) * wbytes],
16904 &scales[expert * sbytes..(expert + 1) * sbytes],
16905 macros[expert],
16906 out_f,
16907 in_f,
16908 ));
16909 }
16910 Ok(out)
16911 }
16912 }
16913}
16914
16915impl Qwen4ExpGpu {
16916 pub fn load_from_dir(e: &Engine, dir: &std::path::Path) -> Res<Self> {
16921 Self::from_loaded_checkpoint(e, read_checkpoint(dir)?)
16922 }
16923
16924 pub fn load_from_dir_with(e: &Engine, dir: &std::path::Path, opts: LoadOptions) -> Res<Self> {
16926 Self::from_loaded_checkpoint(e, read_checkpoint_with(dir, opts)?)
16927 }
16928
16929 pub fn load_from_dir_dev1(
16934 e: &Engine,
16935 draft_e: &Engine,
16936 dir: &std::path::Path,
16937 opts: LoadOptions,
16938 ) -> Res<Self> {
16939 Self::from_loaded_checkpoint_dual(e, Some(draft_e), read_checkpoint_with(dir, opts)?)
16940 }
16941
16942 pub fn from_loaded_checkpoint(e: &Engine, checkpoint: LoadedCheckpoint) -> Res<Self> {
16945 Self::from_loaded_checkpoint_dual(e, None, checkpoint)
16946 }
16947
16948 pub fn from_loaded_checkpoint_dual(
16952 e: &Engine,
16953 draft_e: Option<&Engine>,
16954 checkpoint: LoadedCheckpoint,
16955 ) -> Res<Self> {
16956 let LoadedCheckpoint {
16957 plan,
16958 weights,
16959 model,
16960 bank_plans,
16961 tables,
16962 } = checkpoint;
16963 let mut parts = ExternalParts::default();
16964 let n_trunk = plan.layers.len() as u32;
16965 let upload_half = |e: &Engine, src: BankTensorSrc, device_bf16: bool| -> Res<BankHalf> {
16966 Ok(match src {
16967 BankTensorSrc::F32(data) => BankHalf::F32(e.htod(&data)?),
16968 BankTensorSrc::Nvfp4 {
16969 codes,
16970 scales,
16971 macros,
16972 ..
16973 } => BankHalf::Nvfp4 {
16974 codes: e.htod_bytes(&codes)?,
16975 scales: e.htod_bytes(&scales)?,
16976 macros_dev: e.htod(¯os)?,
16977 macros,
16978 },
16979 BankTensorSrc::Bf16(bytes) if device_bf16 => {
16983 BankHalf::DeviceBf16(e.htod_bytes(&bytes)?)
16984 }
16985 BankTensorSrc::Bf16(bytes) => BankHalf::HostBf16(bytes),
16986 })
16987 };
16988 for (index, bank_plan) in bank_plans {
16995 let bank = bank_plan.read(&model)?;
16996 let device_bf16 = index >= n_trunk;
16997 let bank_e = if device_bf16 { draft_e.unwrap_or(e) } else { e };
16999 parts.expert_banks.insert(
17000 index,
17001 ExpertBank {
17002 gate: upload_half(bank_e, bank.gate, device_bf16)?,
17003 up: upload_half(bank_e, bank.up, device_bf16)?,
17004 down: upload_half(bank_e, bank.down, device_bf16)?,
17005 },
17006 );
17007 }
17008 drop(model);
17011 for (index, bytes) in tables {
17012 parts.ngram_tables.insert(index, NgramTable::Bf16(bytes));
17013 }
17014 Self::from_reference_weights_with(e, draft_e, &plan, &weights, parts)
17015 }
17016}
17017
17018struct GdnHalfW {
17056 nk_h: usize,
17057 nv_h: usize,
17058 hk: usize,
17059 hv: usize,
17060 kernel: usize,
17061 gate_activation: GdnGateActivation,
17062 proj_b16: CudaSlice<u8>,
17065 out_b16: CudaSlice<u8>, conv_w: CudaSlice<f32>, a: CudaSlice<f32>, dt: CudaSlice<f32>, norm: CudaSlice<f32>, }
17071
17072struct QsaHalfW {
17074 nh_h: usize,
17075 nkv_h: usize,
17076 hd: usize,
17077 n_rot: usize,
17078 rope_base: f32,
17079 scale: f32,
17080 proj_b16: CudaSlice<u8>,
17083 wo_b16: CudaSlice<u8>, q_norm: Option<CudaSlice<f32>>,
17085 k_norm: Option<CudaSlice<f32>>,
17086 yarn: Option<YarnRopeW>,
17088}
17089
17090enum MixerHalfW {
17091 Gdn(GdnHalfW),
17092 Qsa(QsaHalfW),
17093}
17094
17095struct Nvfp4Half {
17097 codes: CudaSlice<u8>,
17098 scales: CudaSlice<u8>,
17099 macros_dev: CudaSlice<f32>,
17100}
17101
17102struct MoeHalfW {
17103 gate1: Nvfp4Half,
17105 up1: Nvfp4Half,
17106 down1: Nvfp4Half,
17107 shared_down0: CudaSlice<u8>, shared_down1: CudaSlice<u8>, shared_input_gate1: Option<CudaSlice<f32>>, shared_gu0_b16: CudaSlice<u8>,
17116 shared_gu1_b16: CudaSlice<u8>,
17117}
17118
17119struct Tp2LayerW {
17120 attn_gate1: GateW,
17121 mlp_gate1: GateW,
17122 mixer0: MixerHalfW,
17123 mixer1: MixerHalfW,
17124 moe: MoeHalfW,
17125 ple1: Option<PleW>,
17126 place: LayerPlacement,
17131}
17132
17133pub struct Tp2Shard {
17135 layers: Vec<Tp2LayerW>,
17136 exit_gate1: GateW,
17137 lm_head1: CudaSlice<u8>, vsplit: usize,
17139 stage0: [CudaSlice<f32>; 2], stage1: [CudaSlice<f32>; 2], stage0_raw: [u64; 2],
17146 stage1_raw: [u64; 2],
17147 ev0: [cudarc::driver::CudaEvent; 2], ev1: [cudarc::driver::CudaEvent; 2], }
17150
17151enum MixerHalfState {
17152 Gdn {
17153 conv: CudaSlice<f32>, state: CudaSlice<f32>, },
17156 Qsa {
17157 kv: QsaKvStore,
17161 },
17162}
17163
17164struct Tp2LayerState {
17165 m0: MixerHalfState,
17166 m1: MixerHalfState,
17167 ple1: Option<PleState>,
17168}
17169
17170struct Tp2State {
17171 ws1: StepPool,
17172 layers: Vec<Tp2LayerState>,
17173 graphs: Tp2Graphs,
17174 pf_stage0: Option<[CudaSlice<f32>; 2]>, pf_stage1: Option<[CudaSlice<f32>; 2]>,
17180 pf_stage0_raw: [u64; 2],
17181 pf_stage1_raw: [u64; 2],
17182 pf_rows: usize,
17183}
17184
17185#[derive(Default)]
17194struct Tp2Graphs {
17195 warm: bool,
17196 a: [Vec<Option<GraphEntry>>; 2],
17197 b: [Vec<Option<GraphEntry>>; 2],
17198 c: [Vec<Option<GraphEntry>>; 2],
17201 d: [Vec<Option<GraphEntry>>; 2],
17203 exit: [Option<GraphEntry>; 2],
17204}
17205
17206fn tp2_gdn_head_map(d: usize, nk: usize, nv: usize) -> Vec<usize> {
17210 let nk_h = nk / 2;
17211 let nv_h = nv / 2;
17212 (0..nv_h)
17213 .map(|j| (j / nk_h) * nk + (j % nk_h) + d * nk_h)
17214 .collect()
17215}
17216
17217fn gather_rows_host(src: &[f32], in_f: usize, rows: &[usize]) -> Vec<f32> {
17219 let mut out = Vec::with_capacity(rows.len() * in_f);
17220 for &r in rows {
17221 out.extend_from_slice(&src[r * in_f..(r + 1) * in_f]);
17222 }
17223 out
17224}
17225
17226fn gather_cols_host(
17228 src: &[f32],
17229 nrows: usize,
17230 ncols: usize,
17231 blocks: &[(usize, usize)],
17232) -> Vec<f32> {
17233 let width: usize = blocks.iter().map(|&(_, l)| l).sum();
17234 let mut out = Vec::with_capacity(nrows * width);
17235 for r in 0..nrows {
17236 for &(start, len) in blocks {
17237 out.extend_from_slice(&src[r * ncols + start..r * ncols + start + len]);
17238 }
17239 }
17240 out
17241}
17242
17243fn need_twin(e: &Engine, data: &[f32], in_f: usize, what: &str) -> Res<CudaSlice<u8>> {
17244 bf16_twin(e, data, in_f)?.ok_or_else(|| {
17245 format!("qwen4exp_gpu tp2: {what} has no exact bf16 twin (in_f {in_f})").into()
17246 })
17247}
17248
17249fn launch_push(e: &Engine, src: &CudaSlice<f32>, dst_raw: u64, n: usize) -> Res<()> {
17252 let f = e.func("q4e_push_f32");
17253 let cfg = LaunchConfig::for_num_elems(n as u32);
17254 let nl = n as i64;
17255 let stream = e.gpu.stream();
17256 let mut b = stream.launch_builder(&f);
17257 b.arg(src).arg(&dst_raw).arg(&nl);
17258 unsafe {
17259 b.launch(cfg)?;
17260 }
17261 Ok(())
17262}
17263
17264pub fn tp2_enable_p2p(e0: &Engine, e1: &Engine) -> Res<()> {
17268 use cudarc::driver::sys;
17269 for (src, dst) in [(e0, e1), (e1, e0)] {
17270 let mut can = 0i32;
17271 unsafe {
17272 sys::cuDeviceCanAccessPeer(&mut can, src.ctx().cu_device(), dst.ctx().cu_device())
17273 .result()?;
17274 }
17275 if can == 0 {
17276 return Err(format!(
17277 "qwen4exp_gpu tp2: dev{} cannot access dev{} over P2P",
17278 src.ctx().ordinal(),
17279 dst.ctx().ordinal()
17280 )
17281 .into());
17282 }
17283 src.ctx().bind_to_thread()?;
17284 let rc = unsafe { sys::cuCtxEnablePeerAccess(dst.ctx().cu_ctx(), 0) };
17285 use cudarc::driver::sys::cudaError_enum as E;
17286 if rc != E::CUDA_SUCCESS && rc != E::CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED {
17287 return Err(format!("qwen4exp_gpu tp2: cuCtxEnablePeerAccess failed: {rc:?}").into());
17288 }
17289 }
17290 for (owner, accessor) in [(e0, e1), (e1, e0)] {
17291 let device = cudarc::driver::result::device::get(owner.ctx().ordinal() as i32)?;
17292 let mut pool: sys::CUmemoryPool = std::ptr::null_mut();
17293 unsafe {
17294 sys::cuDeviceGetDefaultMemPool(&mut pool, device).result()?;
17295 }
17296 let desc = sys::CUmemAccessDesc {
17297 location: sys::CUmemLocation {
17298 type_: sys::CUmemLocationType::CU_MEM_LOCATION_TYPE_DEVICE,
17299 id: accessor.ctx().ordinal() as i32,
17300 },
17301 flags: sys::CUmemAccess_flags::CU_MEM_ACCESS_FLAGS_PROT_READWRITE,
17302 };
17303 let rc = unsafe { sys::cuMemPoolSetAccess(pool, &desc, 1) };
17304 if rc != sys::cudaError_enum::CUDA_SUCCESS {
17305 return Err(format!("qwen4exp_gpu tp2: cuMemPoolSetAccess failed: {rc:?}").into());
17306 }
17307 }
17308 Ok(())
17309}
17310
17311fn build_ple_replica(
17315 e: &Engine,
17316 weights: &ReferenceWeights,
17317 prefix: &str,
17318 ple_plan: &PleEmbeddingPlan,
17319 streams: usize,
17320 hidden: usize,
17321) -> Res<PleW> {
17322 let embed_dim = ple_plan.embed_dim as usize;
17323 let key_proj = expect(weights, &family_id(format!("{prefix}ple.key_proj.weight")))?;
17324 let conv_w = expect(weights, &family_id(format!("{prefix}ple.conv1d.weight")))?;
17325 let norm_slices = |name: &str| -> Res<Vec<CudaSlice<f32>>> {
17326 let t = expect(weights, &family_id(format!("{prefix}ple.{name}.weight")))?;
17327 split_rows(&t.data, streams, hidden, 1)
17328 .into_iter()
17329 .map(|v| e.htod(&v))
17330 .collect::<Result<_, _>>()
17331 };
17332 let ints = |name: &str| -> Res<Vec<i64>> {
17333 let t = expect(
17334 weights,
17335 &family_id(format!("{prefix}ple.ple_embedding.{name}")),
17336 )?;
17337 t.ints
17338 .clone()
17339 .ok_or_else(|| "qwen4exp_gpu: n-gram buffer must be I64".into())
17340 };
17341 Ok(PleW {
17342 plan: *ple_plan,
17343 key_proj: split_rows(&key_proj.data, streams, hidden, embed_dim)
17344 .into_iter()
17345 .map(|v| e.htod(&v))
17346 .collect::<Result<_, _>>()?,
17347 value_proj: upload(
17348 e,
17349 &expect(
17350 weights,
17351 &family_id(format!("{prefix}ple.value_proj.weight")),
17352 )?,
17353 )?,
17354 norm_key: norm_slices("norm_key")?,
17355 norm_query: norm_slices("norm_query")?,
17356 norm_conv: norm_slices("norm_conv")?,
17357 conv_w: split_rows(&conv_w.data, streams, hidden, ple_plan.conv_kernel as usize)
17358 .into_iter()
17359 .map(|v| e.htod(&v))
17360 .collect::<Result<_, _>>()?,
17361 multipliers: ints("layer_multipliers")?,
17362 sizes: ints("ngram_heads_vocab_sizes")?,
17363 offsets: ints("ngram_heads_offsets")?,
17364 table: NgramTable::F32(Vec::new()), })
17366}
17367
17368#[allow(clippy::too_many_arguments)]
17370fn build_gdn_half(
17371 e: &Engine,
17372 weights: &ReferenceWeights,
17373 index: u32,
17374 gdn: &GatedDeltaNetPlan,
17375 hidden: usize,
17376 d: usize,
17377) -> Res<GdnHalfW> {
17378 let (nk, nv) = (gdn.key_heads as usize, gdn.value_heads as usize);
17379 let (hk, hv) = (gdn.key_head_dim as usize, gdn.value_head_dim as usize);
17380 if nk % 2 != 0 || nv % nk != 0 {
17381 return Err(format!(
17382 "qwen4exp_gpu tp2: GDN layer {index} nk {nk} / nv {nv} does not split by key-head halves"
17383 )
17384 .into());
17385 }
17386 let (nk_h, nv_h) = (nk / 2, nv / 2);
17387 let head_map = tp2_gdn_head_map(d, nk, nv);
17388 let qkv = expect(weights, &layer_id(index, LayerTensor::GdnQkv))?;
17389 let z = expect(weights, &layer_id(index, LayerTensor::GdnGate))?;
17390 let beta = expect(weights, &layer_id(index, LayerTensor::GdnBeta))?;
17391 let alpha = expect(weights, &layer_id(index, LayerTensor::GdnAlpha))?;
17392 let out = expect(weights, &layer_id(index, LayerTensor::GdnOutput))?;
17393 let conv_w = expect(weights, &layer_id(index, LayerTensor::GdnConv1d))?;
17394 let a = expect(weights, &layer_id(index, LayerTensor::GdnA))?;
17395 let dt = expect(weights, &layer_id(index, LayerTensor::GdnDtBias))?;
17396 let norm = expect(weights, &layer_id(index, LayerTensor::GdnNorm))?;
17397 let kernel = gdn.conv_kernel as usize;
17398 let mut qkv_rows: Vec<usize> = Vec::with_capacity(2 * nk_h * hk + nv_h * hv);
17400 qkv_rows.extend(d * nk_h * hk..(d + 1) * nk_h * hk);
17401 qkv_rows.extend(nk * hk + d * nk_h * hk..nk * hk + (d + 1) * nk_h * hk);
17402 for &hm in &head_map {
17403 qkv_rows.extend(2 * nk * hk + hm * hv..2 * nk * hk + (hm + 1) * hv);
17404 }
17405 let mut z_rows: Vec<usize> = Vec::with_capacity(nv_h * hv);
17406 for &hm in &head_map {
17407 z_rows.extend(hm * hv..(hm + 1) * hv);
17408 }
17409 let out_blocks: Vec<(usize, usize)> = head_map.iter().map(|&hm| (hm * hv, hv)).collect();
17410 let qkv_c = gather_rows_host(&qkv.data, hidden, &qkv_rows);
17411 let z_c = gather_rows_host(&z.data, hidden, &z_rows);
17412 let beta_c = gather_rows_host(&beta.data, hidden, &head_map);
17413 let alpha_c = gather_rows_host(&alpha.data, hidden, &head_map);
17414 let out_c = gather_cols_host(&out.data, hidden, nv * hv, &out_blocks);
17415 let conv_c = gather_rows_host(&conv_w.data, kernel, &qkv_rows);
17416 let a_c: Vec<f32> = head_map.iter().map(|&hm| a.data[hm]).collect();
17417 let dt_c: Vec<f32> = head_map.iter().map(|&hm| dt.data[hm]).collect();
17418 Ok(GdnHalfW {
17419 nk_h,
17420 nv_h,
17421 hk,
17422 hv,
17423 kernel,
17424 gate_activation: gdn.gate_activation,
17425 proj_b16: need_stack_twin(
17426 e,
17427 &[&qkv_c, &z_c, &beta_c, &alpha_c],
17428 hidden,
17429 "tp2 gdn proj half",
17430 )?,
17431 out_b16: need_twin(e, &out_c, nv_h * hv, "tp2 gdn out half")?,
17432 conv_w: e.htod(&conv_c)?,
17433 a: e.htod(&a_c)?,
17434 dt: e.htod(&dt_c)?,
17435 norm: e.htod(&norm.data)?,
17436 })
17437}
17438
17439#[allow(clippy::too_many_arguments)]
17441fn build_qsa_half(
17442 e: &Engine,
17443 weights: &ReferenceWeights,
17444 index: u32,
17445 attn: &FullAttentionPlan,
17446 hidden: usize,
17447 d: usize,
17448) -> Res<QsaHalfW> {
17449 let nh = attn.query_heads as usize;
17450 let nkv = attn.kv_heads as usize;
17451 let hd = attn.key_head_dim as usize;
17452 if nh % 2 != 0 || nkv % 2 != 0 || nh % nkv != 0 {
17453 return Err(format!(
17454 "qwen4exp_gpu tp2: QSA layer {index} heads {nh}/{nkv} do not split in halves"
17455 )
17456 .into());
17457 }
17458 let (nh_h, nkv_h) = (nh / 2, nkv / 2);
17459 let wq = expect(weights, &layer_id(index, LayerTensor::Query))?;
17460 let wk = expect(weights, &layer_id(index, LayerTensor::Key))?;
17461 let wv = expect(weights, &layer_id(index, LayerTensor::Value))?;
17462 let wo = expect(weights, &layer_id(index, LayerTensor::AttentionOutput))?;
17463 let q_rows: Vec<usize> = (d * nh_h * 2 * hd..(d + 1) * nh_h * 2 * hd).collect();
17465 let kv_rows: Vec<usize> = (d * nkv_h * hd..(d + 1) * nkv_h * hd).collect();
17466 let wq_c = gather_rows_host(&wq.data, hidden, &q_rows);
17467 let wk_c = gather_rows_host(&wk.data, hidden, &kv_rows);
17468 let wv_c = gather_rows_host(&wv.data, hidden, &kv_rows);
17469 let wo_c = gather_cols_host(&wo.data, hidden, nh * hd, &[(d * nh_h * hd, nh_h * hd)]);
17470 let opt_norm = |tensor: LayerTensor| -> Res<Option<CudaSlice<f32>>> {
17471 match weights.get(&layer_id(index, tensor)) {
17472 Some(t) => Ok(Some(e.htod(&t.data)?)),
17473 None => Ok(None),
17474 }
17475 };
17476 let scale = match attn.scale {
17477 memra_gguf::model_plan::AttentionScale::InverseSqrtKeyDim => 1.0 / (hd as f32).sqrt(),
17478 memra_gguf::model_plan::AttentionScale::Fixed(scale) => scale,
17479 };
17480 Ok(QsaHalfW {
17481 nh_h,
17482 nkv_h,
17483 hd,
17484 n_rot: attn.rope.dimensions as usize,
17485 rope_base: attn.rope.base,
17486 scale,
17487 proj_b16: need_stack_twin(e, &[&wq_c, &wk_c, &wv_c], hidden, "tp2 qsa proj half")?,
17488 wo_b16: need_twin(e, &wo_c, nh_h * hd, "tp2 qsa o half")?,
17489 q_norm: opt_norm(LayerTensor::QueryNorm)?,
17490 k_norm: opt_norm(LayerTensor::KeyNorm)?,
17491 yarn: build_yarn(e, &attn.rope, None, index)?,
17493 })
17494}
17495
17496pub fn build_tp2_shard(e0: &Engine, e1: &Engine, ckpt: &LoadedCheckpoint) -> Res<Tp2Shard> {
17500 let plan = &ckpt.plan;
17501 let weights = &ckpt.weights;
17502 let hidden = plan.hidden_size as usize;
17503 let vocab = plan.vocab_size as usize;
17504 if vocab % 2 != 0 {
17505 return Err("qwen4exp_gpu tp2: odd vocab".into());
17506 }
17507 let mixer_plan = plan
17508 .exit_mixer
17509 .ok_or("qwen4exp_gpu tp2: missing exit mixer")?;
17510 let streams = mixer_plan.streams as usize;
17511 let rank = mixer_plan.bottleneck_rank as usize;
17512 let plan_experts = plan
17515 .layers
17516 .iter()
17517 .find_map(|l| match &l.mlp {
17518 MlpPlan::Moe(m) => Some(m.expert_count as usize),
17519 _ => None,
17520 })
17521 .ok_or("qwen4exp_gpu tp2: no MoE layer in the plan")?;
17522 let placement = match Tp2Placement::from_env(plan_experts)? {
17523 Some(p) => p,
17524 None => Tp2Placement::even(plan_experts),
17525 };
17526 println!(
17527 "# tp2-placement\tstrategy={}\tentry_rank={}\texperts={plan_experts}\tsource={}",
17528 placement.strategy(),
17529 placement.entry_rank(),
17530 placement.source()
17531 );
17532 let mut layers = Vec::with_capacity(plan.layers.len());
17533 for layer in &plan.layers {
17534 let prefix = format!("trunk.layers.{}.", layer.index);
17535 let _g1 = e1.gpu.enter_main()?;
17536 let attn_gate1 = load_gate(
17537 e1,
17538 weights,
17539 &prefix,
17540 "attn_hyper_connection.",
17541 streams,
17542 hidden,
17543 rank,
17544 true,
17545 )?;
17546 let mlp_gate1 = load_gate(
17547 e1,
17548 weights,
17549 &prefix,
17550 "mlp_hyper_connection.",
17551 streams,
17552 hidden,
17553 rank,
17554 true,
17555 )?;
17556 let ple1 = match layer.ple.as_ref() {
17557 None => None,
17558 Some(ple_plan) => Some(build_ple_replica(
17559 e1, weights, &prefix, ple_plan, streams, hidden,
17560 )?),
17561 };
17562 drop(_g1);
17563 let (mixer0, mixer1) = match &layer.attention {
17564 AttentionPlan::GatedDeltaNet(gdn) => {
17565 let _g0 = e0.gpu.enter_main()?;
17566 let m0 = MixerHalfW::Gdn(build_gdn_half(e0, weights, layer.index, gdn, hidden, 0)?);
17567 drop(_g0);
17568 let _g1 = e1.gpu.enter_main()?;
17569 let m1 = MixerHalfW::Gdn(build_gdn_half(e1, weights, layer.index, gdn, hidden, 1)?);
17570 (m0, m1)
17571 }
17572 AttentionPlan::Full(attn) => {
17573 let _g0 = e0.gpu.enter_main()?;
17574 let m0 =
17575 MixerHalfW::Qsa(build_qsa_half(e0, weights, layer.index, attn, hidden, 0)?);
17576 drop(_g0);
17577 let _g1 = e1.gpu.enter_main()?;
17578 let m1 =
17579 MixerHalfW::Qsa(build_qsa_half(e1, weights, layer.index, attn, hidden, 1)?);
17580 (m0, m1)
17581 }
17582 other => {
17583 return Err(format!("qwen4exp_gpu tp2: unsupported mixer {other:?}").into());
17584 }
17585 };
17586 let MlpPlan::Moe(moe_plan) = &layer.mlp else {
17588 return Err("qwen4exp_gpu tp2: non-MoE layer".into());
17589 };
17590 let experts = moe_plan.expert_count as usize;
17591 let ff = moe_plan.expert_intermediate_size as usize;
17592 if experts % 2 != 0 {
17593 return Err("qwen4exp_gpu tp2: odd expert count".into());
17594 }
17595 let bank = ckpt.read_bank(layer.index)?;
17602 let bank = &bank;
17603 let place = placement.layer(layer.index, experts)?;
17606 let upper =
17612 |src: &BankTensorSrc, out_f: usize, in_f: usize, what: &str| -> Res<Nvfp4Half> {
17613 let BankTensorSrc::Nvfp4 {
17614 codes,
17615 scales,
17616 macros,
17617 ..
17618 } = src
17619 else {
17620 return Err(format!("qwen4exp_gpu tp2: {what} bank is not NVFP4").into());
17621 };
17622 let wbytes = out_f * in_f / 2;
17623 let sbytes = out_f * in_f / 16;
17624 let need_codes = place.card1.len() * wbytes;
17625 let need_scales = place.card1.len() * sbytes;
17626 if codes.len() < experts * wbytes || scales.len() < experts * sbytes {
17627 return Err(format!(
17628 "qwen4exp_gpu tp2: {what} bank is {} code / {} scale bytes, too \
17629 small for {experts} experts x ({wbytes}, {sbytes})",
17630 codes.len(),
17631 scales.len()
17632 )
17633 .into());
17634 }
17635 let mut gcodes = Vec::with_capacity(need_codes);
17636 let mut gscales = Vec::with_capacity(need_scales);
17637 let mut gmacros = Vec::with_capacity(place.card1.len());
17638 for &eid in &place.card1 {
17639 let e = eid as usize;
17640 gcodes.extend_from_slice(&codes[e * wbytes..(e + 1) * wbytes]);
17641 gscales.extend_from_slice(&scales[e * sbytes..(e + 1) * sbytes]);
17642 gmacros.push(macros[e]);
17643 }
17644 Ok(Nvfp4Half {
17645 codes: e1.htod_bytes(&gcodes)?,
17646 scales: e1.htod_bytes(&gscales)?,
17647 macros_dev: e1.htod(&gmacros)?,
17648 })
17649 };
17650 let shared = moe_plan
17651 .shared
17652 .as_ref()
17653 .ok_or("qwen4exp_gpu tp2: missing shared expert")?;
17654 let sff = shared.intermediate_size as usize;
17655 if sff % 2 != 0 {
17656 return Err("qwen4exp_gpu tp2: odd shared ff".into());
17657 }
17658 let sffh = sff / 2;
17659 let sh_gate = expect(weights, &layer_id(layer.index, LayerTensor::SharedMlpGate))?;
17660 let sh_up = expect(weights, &layer_id(layer.index, LayerTensor::SharedMlpUp))?;
17661 let sh_down = expect(weights, &layer_id(layer.index, LayerTensor::SharedMlpDown))?;
17662 let sh_ig = if shared.gated {
17663 Some(expect(
17664 weights,
17665 &layer_id(layer.index, LayerTensor::SharedMlpInputGate),
17666 )?)
17667 } else {
17668 None
17669 };
17670 let moe = {
17671 let _g1 = e1.gpu.enter_main()?;
17672 let gate1 = upper(&bank.gate, ff, hidden, "gate")?;
17673 let up1 = upper(&bank.up, ff, hidden, "up")?;
17674 let down1 = upper(&bank.down, hidden, ff, "down")?;
17675 let shared_gu1_b16 = need_stack_twin(
17676 e1,
17677 &[&sh_gate.data[sffh * hidden..], &sh_up.data[sffh * hidden..]],
17678 hidden,
17679 "tp2 shared gate/up (card1)",
17680 )?;
17681 let down1_c = gather_cols_host(&sh_down.data, hidden, sff, &[(sffh, sffh)]);
17682 let shared_down1 = need_twin(e1, &down1_c, sffh, "tp2 shared down (card1)")?;
17683 let shared_input_gate1 = match sh_ig.as_ref() {
17684 Some(t) => Some(e1.htod(&t.data)?),
17685 None => None,
17686 };
17687 drop(_g1);
17688 let _g0 = e0.gpu.enter_main()?;
17689 let down0_c = gather_cols_host(&sh_down.data, hidden, sff, &[(0, sffh)]);
17690 let shared_down0 = need_twin(e0, &down0_c, sffh, "tp2 shared down (card0)")?;
17691 let shared_gu0_b16 = need_stack_twin(
17692 e0,
17693 &[&sh_gate.data[..sffh * hidden], &sh_up.data[..sffh * hidden]],
17694 hidden,
17695 "tp2 shared gate/up (card0)",
17696 )?;
17697 MoeHalfW {
17698 gate1,
17699 up1,
17700 down1,
17701 shared_down0,
17702 shared_down1,
17703 shared_input_gate1,
17704 shared_gu0_b16,
17705 shared_gu1_b16,
17706 }
17707 };
17708 layers.push(Tp2LayerW {
17709 attn_gate1,
17710 mlp_gate1,
17711 mixer0,
17712 mixer1,
17713 moe,
17714 ple1,
17715 place,
17716 });
17717 }
17718 let _g1 = e1.gpu.enter_main()?;
17719 let exit_gate1 = load_gate(
17720 e1,
17721 weights,
17722 "trunk.hyper_connection_mixer.",
17723 "",
17724 streams,
17725 hidden,
17726 rank,
17727 false,
17728 )?;
17729 let vsplit = vocab / 2;
17730 let head = match weights.get(&TensorId::OutputProjection) {
17731 Some(t) => &t.data,
17732 None => &expect(weights, &TensorId::TokenEmbedding)?.data.clone(),
17733 };
17734 let lm_head1 = need_twin(
17735 e1,
17736 &head[vsplit * hidden..],
17737 hidden,
17738 "tp2 lm_head upper half",
17739 )?;
17740 let stage1 = [e1.zeros(hidden)?, e1.zeros(hidden)?];
17741 let ev1 = [e1.ctx().new_event(None)?, e1.ctx().new_event(None)?];
17742 let stage1_raw = {
17743 let s = e1.gpu.stream();
17744 [stage1[0].device_ptr(&s).0, stage1[1].device_ptr(&s).0]
17745 };
17746 drop(_g1);
17747 let _g0 = e0.gpu.enter_main()?;
17748 let stage0 = [e0.zeros(hidden)?, e0.zeros(hidden)?];
17749 let ev0 = [e0.ctx().new_event(None)?, e0.ctx().new_event(None)?];
17750 let stage0_raw = {
17751 let s = e0.gpu.stream();
17752 [stage0[0].device_ptr(&s).0, stage0[1].device_ptr(&s).0]
17753 };
17754 Ok(Tp2Shard {
17755 layers,
17756 exit_gate1,
17757 lm_head1,
17758 vsplit,
17759 stage0,
17760 stage1,
17761 stage0_raw,
17762 stage1_raw,
17763 ev0,
17764 ev1,
17765 })
17766}
17767
17768impl Qwen4ExpGpu {
17769 pub fn load_from_dir_tp2(
17773 e0: &Engine,
17774 e1: &Engine,
17775 dir: &std::path::Path,
17776 opts: LoadOptions,
17777 ) -> Res<(Self, Tp2Shard)> {
17778 tp2_enable_p2p(e0, e1)?;
17779 let checkpoint = read_checkpoint_with(dir, opts)?;
17780 let shard = build_tp2_shard(e0, e1, &checkpoint)?;
17781 let model = Self::from_loaded_checkpoint(e0, checkpoint)?;
17782 Ok((model, shard))
17783 }
17784
17785 fn tp2_migrate(
17789 &self,
17790 e0: &Engine,
17791 e1: &Engine,
17792 _shard: &Tp2Shard,
17793 state: &mut Qwen4ExpState,
17794 ) -> Res<()> {
17795 let cap = state.capacity;
17796 let pos = state.pos;
17797 let mut tlayers = Vec::with_capacity(self.layers.len());
17798 for (layer, lstate) in self.layers.iter().zip(state.layers.iter_mut()) {
17799 let (m0, m1) = match (&layer.mixer, &mut lstate.mixer) {
17800 (
17801 MixerW::Gdn(gdn),
17802 MixerState::Gdn {
17803 conv,
17804 state: gstate,
17805 },
17806 ) => {
17807 let p = &gdn.plan;
17808 let (nk, nv) = (p.key_heads as usize, p.value_heads as usize);
17809 let (hk, hv) = (p.key_head_dim as usize, p.value_head_dim as usize);
17810 let (nk_h, nv_h) = (nk / 2, nv / 2);
17811 let pad = p.conv_kernel as usize - 1;
17812 let conv_dim = 2 * nk * hk + nv * hv;
17813 let conv_dim_h = 2 * nk_h * hk + nv_h * hv;
17814 let state_host = {
17815 let _g = e0.gpu.enter_main()?;
17816 e0.dtoh(gstate)?
17817 };
17818 let conv_host = {
17819 let _g = e0.gpu.enter_main()?;
17820 e0.dtoh(conv)?
17821 };
17822 let mut halves = Vec::with_capacity(2);
17823 for d in 0..2 {
17824 let head_map = tp2_gdn_head_map(d, nk, nv);
17825 let state_c = gather_rows_host(&state_host, hv * hk, &head_map);
17826 let mut blocks: Vec<(usize, usize)> = vec![
17827 (d * nk_h * hk, nk_h * hk),
17828 (nk * hk + d * nk_h * hk, nk_h * hk),
17829 ];
17830 blocks.extend(head_map.iter().map(|&hm| (2 * nk * hk + hm * hv, hv)));
17831 let conv_c = gather_cols_host(&conv_host, pad, conv_dim, &blocks);
17832 let e = if d == 0 { e0 } else { e1 };
17833 let _g = e.gpu.enter_main()?;
17834 let state_dev = e.htod(&state_c)?;
17835 let conv_dev = e.htod(&conv_c)?;
17836 debug_assert_eq!(conv_c.len(), pad * conv_dim_h);
17837 halves.push(MixerHalfState::Gdn {
17838 conv: conv_dev,
17839 state: state_dev,
17840 });
17841 }
17842 let m1 = halves.pop().expect("two halves");
17843 let m0 = halves.pop().expect("two halves");
17844 (m0, m1)
17845 }
17846 (MixerW::Qsa(qsa), MixerState::Qsa { kv, .. }) => {
17847 let nkv = qsa.attn.kv_heads as usize;
17848 let hd = qsa.attn.key_head_dim as usize;
17849 let nkv_h = nkv / 2;
17850 let mut halves = Vec::with_capacity(2);
17851 match &*kv {
17852 QsaKvStore::F32 { k, v } => {
17853 let (k_host, v_host) = {
17854 let _g = e0.gpu.enter_main()?;
17855 (
17856 e0.dtoh_view(&k.slice(0..pos * nkv * hd))?,
17857 e0.dtoh_view(&v.slice(0..pos * nkv * hd))?,
17858 )
17859 };
17860 for d in 0..2 {
17861 let block = [(d * nkv_h * hd, nkv_h * hd)];
17862 let k_c = gather_cols_host(&k_host, pos, nkv * hd, &block);
17863 let v_c = gather_cols_host(&v_host, pos, nkv * hd, &block);
17864 let e = if d == 0 { e0 } else { e1 };
17865 let _g = e.gpu.enter_main()?;
17866 let mut k_dev = e.zeros(cap * nkv_h * hd)?;
17867 let mut v_dev = e.zeros(cap * nkv_h * hd)?;
17868 if pos > 0 {
17869 let mut kv_view = k_dev.slice_mut(0..pos * nkv_h * hd);
17870 e.gpu.stream().memcpy_htod(&k_c, &mut kv_view)?;
17871 let mut vv_view = v_dev.slice_mut(0..pos * nkv_h * hd);
17872 e.gpu.stream().memcpy_htod(&v_c, &mut vv_view)?;
17873 }
17874 halves.push(MixerHalfState::Qsa {
17875 kv: QsaKvStore::F32 { k: k_dev, v: v_dev },
17876 });
17877 }
17878 }
17879 QsaKvStore::Q8Q5 { k, v } => {
17880 if hd % 32 != 0 {
17885 return Err("qwen4exp_gpu tp2: quantized halves need \
17886 hd % 32 == 0 (byte-aligned head blocks)"
17887 .into());
17888 }
17889 let (krb, vrb) = (q8_row_bytes(nkv * hd), q5_row_bytes(nkv * hd));
17890 let (krb_h, vrb_h) =
17891 (q8_row_bytes(nkv_h * hd), q5_row_bytes(nkv_h * hd));
17892 let (k_host, v_host) = {
17893 let _g = e0.gpu.enter_main()?;
17894 (
17895 e0.dtoh_u8_view(&k.slice(0..pos * krb))?,
17896 e0.dtoh_u8_view(&v.slice(0..pos * vrb))?,
17897 )
17898 };
17899 for d in 0..2 {
17900 let mut k_c = Vec::with_capacity(pos * krb_h);
17901 let mut v_c = Vec::with_capacity(pos * vrb_h);
17902 for r in 0..pos {
17903 let ko = r * krb + d * krb_h;
17904 k_c.extend_from_slice(&k_host[ko..ko + krb_h]);
17905 let vo = r * vrb + d * vrb_h;
17906 v_c.extend_from_slice(&v_host[vo..vo + vrb_h]);
17907 }
17908 let e = if d == 0 { e0 } else { e1 };
17909 let _g = e.gpu.enter_main()?;
17910 let mut k_dev = e.alloc_u8(cap * krb_h)?;
17911 let mut v_dev = e.alloc_u8(cap * vrb_h)?;
17912 if pos > 0 {
17913 let mut kv_view = k_dev.slice_mut(0..pos * krb_h);
17914 e.gpu.stream().memcpy_htod(&k_c, &mut kv_view)?;
17915 let mut vv_view = v_dev.slice_mut(0..pos * vrb_h);
17916 e.gpu.stream().memcpy_htod(&v_c, &mut vv_view)?;
17917 }
17918 halves.push(MixerHalfState::Qsa {
17919 kv: QsaKvStore::Q8Q5 { k: k_dev, v: v_dev },
17920 });
17921 }
17922 }
17923 }
17924 {
17928 let _g = e0.gpu.enter_main()?;
17929 *kv = match &*kv {
17930 QsaKvStore::F32 { .. } => QsaKvStore::F32 {
17931 k: e0.zeros(1)?,
17932 v: e0.zeros(1)?,
17933 },
17934 QsaKvStore::Q8Q5 { .. } => QsaKvStore::Q8Q5 {
17935 k: e0.alloc_u8(34)?,
17936 v: e0.alloc_u8(24)?,
17937 },
17938 };
17939 }
17940 let m1 = halves.pop().expect("two halves");
17941 let m0 = halves.pop().expect("two halves");
17942 (m0, m1)
17943 }
17944 _ => return Err("qwen4exp_gpu tp2: layer/state mixer mismatch".into()),
17945 };
17946 let ple1 = match lstate.ple.as_ref() {
17948 None => None,
17949 Some(ps) => {
17950 let mut conv_hist = Vec::with_capacity(ps.conv_hist.len());
17951 for h in &ps.conv_hist {
17952 let host = {
17953 let _g = e0.gpu.enter_main()?;
17954 e0.dtoh(h)?
17955 };
17956 let _g = e1.gpu.enter_main()?;
17957 conv_hist.push(e1.htod(&host)?);
17958 }
17959 Some(PleState {
17960 conv_hist,
17961 ngram_ids: Vec::new(),
17962 ngram_history: Vec::new(),
17963 ngram_last_eos: -1,
17964 })
17965 }
17966 };
17967 tlayers.push(Tp2LayerState { m0, m1, ple1 });
17968 }
17969 state.tp2 = Some(Tp2State {
17970 ws1: StepPool::default(),
17971 layers: tlayers,
17972 graphs: Tp2Graphs::default(),
17973 pf_stage0: None,
17974 pf_stage1: None,
17975 pf_stage0_raw: [0; 2],
17976 pf_stage1_raw: [0; 2],
17977 pf_rows: 0,
17978 });
17979 Ok(())
17980 }
17981
17982 #[allow(clippy::too_many_arguments)]
17986 fn gdn_forward_half(
17987 &self,
17988 e: &Engine,
17989 ws: &mut StepPool,
17990 eps: f32,
17991 h: &GdnHalfW,
17992 mixed: &CudaSlice<f32>,
17993 hstate: &mut MixerHalfState,
17994 t: usize,
17995 ) -> Res<CudaSlice<f32>> {
17996 let MixerHalfState::Gdn { conv, state } = hstate else {
17997 return Err("qwen4exp_gpu tp2: GDN half bound to non-GDN state".into());
17998 };
17999 let hidden = self.hidden;
18000 let (nk, nv, hk, hv) = (h.nk_h, h.nv_h, h.hk, h.hv);
18001 let kernel = h.kernel;
18002 let pad = kernel - 1;
18003 let conv_dim = 2 * nk * hk + nv * hv;
18004 let mut qkv = ws.take_f32(e, "gdn.qkv", t * conv_dim, 0)?;
18005 let mut z = ws.take_f32(e, "gdn.z", t * nv * hv, 0)?;
18006 let mut beta_raw = ws.take_f32(e, "gdn.beta", t * nv, 0)?;
18007 let mut alpha = ws.take_f32(e, "gdn.alpha", t * nv, 0)?;
18008 if t == 1 && proj_stack_on() {
18012 launch_qmatvec_bf16w_multi4(
18013 e,
18014 &h.proj_b16,
18015 mixed,
18016 &[
18017 (&qkv, conv_dim),
18018 (&z, nv * hv),
18019 (&beta_raw, nv),
18020 (&alpha, nv),
18021 ],
18022 hidden,
18023 )?;
18024 } else {
18025 launch_qmatvec_bf16w_off(e, &h.proj_b16, 0, mixed, &mut qkv, hidden, conv_dim, t)?;
18026 launch_qmatvec_bf16w_off(e, &h.proj_b16, conv_dim, mixed, &mut z, hidden, nv * hv, t)?;
18027 launch_qmatvec_bf16w_off(
18028 e,
18029 &h.proj_b16,
18030 conv_dim + nv * hv,
18031 mixed,
18032 &mut beta_raw,
18033 hidden,
18034 nv,
18035 t,
18036 )?;
18037 launch_qmatvec_bf16w_off(
18038 e,
18039 &h.proj_b16,
18040 conv_dim + nv * hv + nv,
18041 mixed,
18042 &mut alpha,
18043 hidden,
18044 nv,
18045 t,
18046 )?;
18047 }
18048 let mut g_log = ws.take_f32(e, "gdn.glog", t * nv, 0)?;
18049 e.gdn_glog_v(&alpha.slice(0..t * nv), &h.dt, &h.a, &mut g_log, nv, t)?;
18050 ws.put_f32("gdn.alpha", alpha);
18051 let mut conv_out = ws.take_f32(e, "gdn.conv_out", t * conv_dim, 0)?;
18052 launch_dwconv(
18053 e,
18054 &qkv,
18055 conv,
18056 &h.conv_w,
18057 &mut conv_out,
18058 t,
18059 pad,
18060 conv_dim,
18061 kernel,
18062 1,
18063 1,
18064 )?;
18065 let mut o = ws.take_f32(e, "gdn.o", t * nv * hv, 0)?;
18066 let scale = 1.0 / (hk as f32).sqrt();
18067 if t == 1 && gdn_step_on() && hk % 32 == 0 && hk <= 1024 {
18068 launch_gdn_scan_step(
18069 e, &conv_out, &g_log, &beta_raw, state, &mut o, nk, nv, hk, hv, scale, eps,
18070 )?;
18071 } else {
18072 launch_gdn_scan(
18073 e, &conv_out, &g_log, &beta_raw, state, &mut o, nk, nv, hk, hv, t, scale, eps,
18074 )?;
18075 }
18076 ws.put_f32("gdn.conv_out", conv_out);
18077 if t >= pad {
18079 e.copy_range_into(conv, 0, &qkv, (t - pad) * conv_dim, pad * conv_dim)?;
18080 } else {
18081 let keep = pad - t;
18082 let mut tmp = ws.take_f32(e, "gdn.tmp", keep * conv_dim, 0)?;
18083 e.copy_range_into(&mut tmp, 0, conv, t * conv_dim, keep * conv_dim)?;
18084 e.copy_range_into(conv, 0, &tmp, 0, keep * conv_dim)?;
18085 e.copy_range_into(conv, keep * conv_dim, &qkv, 0, t * conv_dim)?;
18086 ws.put_f32("gdn.tmp", tmp);
18087 }
18088 ws.put_f32("gdn.qkv", qkv);
18089 ws.put_f32("gdn.beta", beta_raw);
18090 ws.put_f32("gdn.glog", g_log);
18091 let mut gated = ws.take_f32(e, "gdn.gated", t * nv * hv, 0)?;
18092 match h.gate_activation {
18093 GdnGateActivation::Sigmoid if gdn_fuse_on() => {
18094 launch_rms_sigmul(e, &o, &h.norm, &z, &mut gated, hv, t * nv, eps)?;
18095 }
18096 GdnGateActivation::Sigmoid => {
18097 let mut normed = ws.take_f32(e, "gdn.normed", t * nv * hv, 0)?;
18098 e.rms_norm(&o, &h.norm, &mut normed, hv, t * nv, eps)?;
18099 let mut sg = ws.take_f32(e, "gdn.sg", t * nv * hv, 0)?;
18100 e.sigmoid(&z, &mut sg, t * nv * hv)?;
18101 e.mul(&normed, &sg, &mut gated, t * nv * hv)?;
18102 ws.put_f32("gdn.sg", sg);
18103 ws.put_f32("gdn.normed", normed);
18104 }
18105 GdnGateActivation::Silu => {
18106 let mut normed = ws.take_f32(e, "gdn.normed", t * nv * hv, 0)?;
18107 e.rms_norm(&o, &h.norm, &mut normed, hv, t * nv, eps)?;
18108 e.silu_mul(&z, &normed, &mut gated, t * nv * hv)?;
18109 ws.put_f32("gdn.normed", normed);
18110 }
18111 }
18112 let mut partial = ws.take_f32(e, "mixer.out", t * hidden, 0)?;
18113 launch_qmatvec_bf16w(
18114 e,
18115 &h.out_b16,
18116 &gated,
18117 &mut partial,
18118 nv * hv,
18119 hidden,
18120 t,
18121 1,
18122 0,
18123 0,
18124 nv * hv,
18125 0,
18126 )?;
18127 ws.put_f32("gdn.gated", gated);
18128 ws.put_f32("gdn.z", z);
18129 ws.put_f32("gdn.o", o);
18130 Ok(partial)
18131 }
18132
18133 #[allow(clippy::too_many_arguments)]
18137 fn qsa_half_proj(
18138 &self,
18139 e: &Engine,
18140 ws: &mut StepPool,
18141 eps: f32,
18142 h: &QsaHalfW,
18143 mixed: &CudaSlice<f32>,
18144 hstate: &mut MixerHalfState,
18145 base_pos: usize,
18146 t: usize,
18147 ) -> Res<(CudaSlice<f32>, CudaSlice<f32>)> {
18148 let MixerHalfState::Qsa { kv } = hstate else {
18149 return Err("qwen4exp_gpu tp2: QSA half bound to non-QSA state".into());
18150 };
18151 let hidden = self.hidden;
18152 let (nh, nkv, hd) = (h.nh_h, h.nkv_h, h.hd);
18153 let mut q_fused = ws.take_f32(e, "qsa.qf", t * 2 * nh * hd, 0)?;
18154 let mut k_new = ws.take_f32(e, "qsa.k", t * nkv * hd, 0)?;
18155 let mut v_new = ws.take_f32(e, "qsa.v", t * nkv * hd, 0)?;
18156 if t == 1 && proj_stack_on() {
18160 launch_qmatvec_bf16w_multi4(
18161 e,
18162 &h.proj_b16,
18163 mixed,
18164 &[
18165 (&q_fused, 2 * nh * hd),
18166 (&k_new, nkv * hd),
18167 (&v_new, nkv * hd),
18168 ],
18169 hidden,
18170 )?;
18171 } else {
18172 launch_qmatvec_bf16w_off(
18173 e,
18174 &h.proj_b16,
18175 0,
18176 mixed,
18177 &mut q_fused,
18178 hidden,
18179 2 * nh * hd,
18180 t,
18181 )?;
18182 launch_qmatvec_bf16w_off(
18183 e,
18184 &h.proj_b16,
18185 2 * nh * hd,
18186 mixed,
18187 &mut k_new,
18188 hidden,
18189 nkv * hd,
18190 t,
18191 )?;
18192 launch_qmatvec_bf16w_off(
18193 e,
18194 &h.proj_b16,
18195 2 * nh * hd + nkv * hd,
18196 mixed,
18197 &mut v_new,
18198 hidden,
18199 nkv * hd,
18200 t,
18201 )?;
18202 }
18203 let mut q = ws.take_f32(e, "qsa.q", t * nh * hd, 0)?;
18204 let mut gate = ws.take_f32(e, "qsa.gate", t * nh * hd, 0)?;
18205 e.q_gate_split(&q_fused, &mut q, &mut gate, hd, nh, t)?;
18206 ws.put_f32("qsa.qf", q_fused);
18207 let mut q = if let Some(norm) = h.q_norm.as_ref() {
18208 let mut dst = ws.take_f32(e, "qsa.qn", t * nh * hd, 0)?;
18209 e.rms_norm(&q, norm, &mut dst, hd, t * nh, eps)?;
18210 ws.put_f32("qsa.q", q);
18211 dst
18212 } else {
18213 q
18214 };
18215 let mut k_new = if let Some(norm) = h.k_norm.as_ref() {
18216 let mut dst = ws.take_f32(e, "qsa.kn", t * nkv * hd, 0)?;
18217 e.rms_norm(&k_new, norm, &mut dst, hd, t * nkv, eps)?;
18218 ws.put_f32("qsa.k", k_new);
18219 dst
18220 } else {
18221 k_new
18222 };
18223 let positions: Vec<i32> = (0..t).map(|i| (base_pos + i) as i32).collect();
18224 let pos_dev = ws.take_i32(e, "qsa.pos", &positions, 0)?;
18225 if let Some(yarn) = h.yarn.as_ref() {
18226 e.rope_neox_ffm(
18227 &mut q,
18228 &pos_dev,
18229 hd,
18230 h.n_rot,
18231 nh,
18232 t,
18233 h.rope_base,
18234 1.0,
18235 &yarn.ff,
18236 yarn.mscale,
18237 )?;
18238 e.rope_neox_ffm(
18239 &mut k_new,
18240 &pos_dev,
18241 hd,
18242 h.n_rot,
18243 nkv,
18244 t,
18245 h.rope_base,
18246 1.0,
18247 &yarn.ff,
18248 yarn.mscale,
18249 )?;
18250 } else {
18251 e.rope_neox(&mut q, &pos_dev, hd, h.n_rot, nh, t, h.rope_base, 1.0)?;
18252 e.rope_neox(&mut k_new, &pos_dev, hd, h.n_rot, nkv, t, h.rope_base, 1.0)?;
18253 }
18254 ws.put_i32("qsa.pos", pos_dev);
18255 match kv {
18256 QsaKvStore::F32 { k, v } => {
18257 e.copy_range_into(k, base_pos * nkv * hd, &k_new, 0, t * nkv * hd)?;
18258 e.copy_range_into(v, base_pos * nkv * hd, &v_new, 0, t * nkv * hd)?;
18259 }
18260 QsaKvStore::Q8Q5 { k, v } => {
18261 launch_q4e_kv_append(e, &k_new, &v_new, k, v, base_pos, t, nkv * hd)?;
18262 }
18263 }
18264 ws.put_f32(
18265 if h.k_norm.is_some() {
18266 "qsa.kn"
18267 } else {
18268 "qsa.k"
18269 },
18270 k_new,
18271 );
18272 ws.put_f32("qsa.v", v_new);
18273 Ok((q, gate))
18274 }
18275
18276 #[allow(clippy::too_many_arguments)]
18281 fn qsa_half_attend(
18282 &self,
18283 e: &Engine,
18284 ws: &mut StepPool,
18285 h: &QsaHalfW,
18286 hstate: &MixerHalfState,
18287 q: CudaSlice<f32>,
18288 gate: CudaSlice<f32>,
18289 pos_dev: &CudaSlice<i32>,
18290 meta_dev: &CudaSlice<i32>,
18291 max_count: usize,
18292 t: usize,
18293 t_kv: usize,
18294 ) -> Res<CudaSlice<f32>> {
18295 let MixerHalfState::Qsa { kv } = hstate else {
18296 return Err("qwen4exp_gpu tp2: QSA half bound to non-QSA state".into());
18297 };
18298 let hidden = self.hidden;
18299 let (nh, nkv, hd) = (h.nh_h, h.nkv_h, h.hd);
18300 let mut attended = ws.take_f32(e, "qsa.att", t * nh * hd, 0)?;
18301 match kv {
18302 QsaKvStore::F32 { k, v } => {
18303 let k_view = k.slice(0..t_kv * nkv * hd);
18304 let v_view = v.slice(0..t_kv * nkv * hd);
18305 launch_sdpa_blocklist(
18306 e,
18307 &q,
18308 &k_view,
18309 &v_view,
18310 &mut attended,
18311 pos_dev,
18312 meta_dev,
18313 hd,
18314 nh,
18315 nkv,
18316 t,
18317 max_count,
18318 h.scale,
18319 )?;
18320 }
18321 QsaKvStore::Q8Q5 { k, v } => {
18322 launch_q4e_sdpa_blocklist_q8q5(
18323 e,
18324 &q,
18325 k,
18326 v,
18327 &mut attended,
18328 pos_dev,
18329 meta_dev,
18330 hd,
18331 nh,
18332 nkv,
18333 t,
18334 max_count,
18335 h.scale,
18336 )?;
18337 }
18338 }
18339 ws.put_f32(
18340 if h.q_norm.is_some() {
18341 "qsa.qn"
18342 } else {
18343 "qsa.q"
18344 },
18345 q,
18346 );
18347 let mut sg = ws.take_f32(e, "qsa.sg", t * nh * hd, 0)?;
18348 e.sigmoid(&gate, &mut sg, t * nh * hd)?;
18349 let mut gated = ws.take_f32(e, "qsa.gated", t * nh * hd, 0)?;
18350 e.mul(&attended, &sg, &mut gated, t * nh * hd)?;
18351 let mut partial = ws.take_f32(e, "mixer.out", t * hidden, 0)?;
18352 launch_qmatvec_bf16w(
18353 e,
18354 &h.wo_b16,
18355 &gated,
18356 &mut partial,
18357 nh * hd,
18358 hidden,
18359 t,
18360 1,
18361 0,
18362 0,
18363 nh * hd,
18364 0,
18365 )?;
18366 ws.put_f32("qsa.sg", sg);
18367 ws.put_f32("qsa.gated", gated);
18368 ws.put_f32("qsa.att", attended);
18369 ws.put_f32("qsa.gate", gate);
18370 Ok(partial)
18371 }
18372
18373 #[allow(dead_code)]
18378 #[allow(clippy::too_many_arguments)]
18379 fn qsa_indexer_mask(
18380 &self,
18381 e: &Engine,
18382 ws: &mut StepPool,
18383 qsa: &QsaW,
18384 eps: f32,
18385 mixed: &CudaSlice<f32>,
18386 raw_keys: &mut IdxRawCache,
18387 pooled_keys: &mut Vec<f32>,
18388 base_pos: usize,
18389 ) -> Res<Vec<u8>> {
18390 let overlay = &qsa.overlay;
18391 let idx_dim = overlay.head_dim as usize;
18392 let qk_width = (overlay.query_heads as usize + overlay.kv_heads as usize) * idx_dim;
18393 let hidden = self.hidden;
18394 let mut idx_proj = ws.take_f32(e, "qsa.idxp", qk_width, 0)?;
18395 e.linear_device_into(mixed, &qsa.idx_proj, &mut idx_proj, 1, hidden, qk_width)?;
18396 let rows = e.dtoh_view(&idx_proj.slice(0..qk_width))?;
18397 ws.put_f32("qsa.idxp", idx_proj);
18398 raw_keys.append_rows_f32(
18399 &rows[overlay.query_heads as usize * idx_dim..qk_width],
18400 1,
18401 idx_dim,
18402 );
18403 indexer_mask_rows(
18404 overlay,
18405 qsa.attn.rope.base,
18406 qsa.yarn.as_ref().map(|y| (y.ff_host.as_slice(), y.mscale)),
18407 eps,
18408 &qsa.idx_q_norm,
18409 &qsa.idx_k_norm,
18410 &rows,
18411 raw_keys,
18412 pooled_keys,
18413 base_pos,
18414 1,
18415 base_pos + 1,
18416 0,
18417 )
18418 }
18419
18420 #[allow(clippy::too_many_arguments)]
18424 fn tp2_shared_half(
18425 &self,
18426 e: &Engine,
18427 ws: &mut StepPool,
18428 gu_b16: &CudaSlice<u8>,
18429 down_b16: &CudaSlice<u8>,
18430 input_gate: Option<&CudaSlice<f32>>,
18431 mixed: &CudaSlice<f32>,
18432 sffh: usize,
18433 t: usize,
18434 ) -> Res<(CudaSlice<f32>, Option<CudaSlice<f32>>)> {
18435 let hidden = self.hidden;
18436 let mut sh_gate = ws.take_f32(e, "moe.sh_gate", t * sffh, 0)?;
18437 let mut sh_up = ws.take_f32(e, "moe.sh_up", t * sffh, 0)?;
18438 if t == 1 && proj_stack_on() {
18441 launch_qmatvec_bf16w_multi4(
18442 e,
18443 gu_b16,
18444 mixed,
18445 &[(&sh_gate, sffh), (&sh_up, sffh)],
18446 hidden,
18447 )?;
18448 } else {
18449 launch_qmatvec_bf16w_off(e, gu_b16, 0, mixed, &mut sh_gate, hidden, sffh, t)?;
18450 launch_qmatvec_bf16w_off(e, gu_b16, sffh, mixed, &mut sh_up, hidden, sffh, t)?;
18451 }
18452 let mut act = ws.take_f32(e, "moe.sh_act", t * sffh, 0)?;
18453 e.silu_mul(&sh_gate, &sh_up, &mut act, t * sffh)?;
18454 let mut shared = ws.take_f32(e, "moe.sh_down", t * hidden, 0)?;
18455 launch_qmatvec_bf16w(
18456 e,
18457 down_b16,
18458 &act,
18459 &mut shared,
18460 sffh,
18461 hidden,
18462 t,
18463 1,
18464 0,
18465 0,
18466 sffh,
18467 0,
18468 )?;
18469 let g = match input_gate {
18470 Some(w) => {
18471 let mut g = ws.take_f32(e, "moe.g", t, 0)?;
18472 e.sigmoid_dot_rows_into(mixed, w, &mut g, hidden, t)?;
18473 Some(g)
18474 }
18475 None => None,
18476 };
18477 ws.put_f32("moe.sh_gate", sh_gate);
18478 ws.put_f32("moe.sh_up", sh_up);
18479 ws.put_f32("moe.sh_act", act);
18480 Ok((shared, g))
18481 }
18482}
18483
18484impl Qwen4ExpGpu {
18485 pub fn decode_step_tp2(
18490 &self,
18491 e0: &Engine,
18492 e1: &Engine,
18493 shard: &Tp2Shard,
18494 token: u32,
18495 state: &mut Qwen4ExpState,
18496 ) -> Res<Vec<f32>> {
18497 if !trunk_bf16_on() || !hc_fused_gate_on() {
18498 return Err(
18499 "qwen4exp_gpu tp2: requires set_trunk_bf16(true) and set_hc_fused_gate(true) \
18500 (replicated compute must run deterministic kernels)"
18501 .into(),
18502 );
18503 }
18504 if state.pos + 1 > state.capacity {
18505 return Err("qwen4exp_gpu: state capacity exceeded".into());
18506 }
18507 if state.tp2.is_none() {
18508 self.tp2_migrate(e0, e1, shard, state)?;
18509 state.graphs = StepGraphs::default();
18510 }
18511 let hidden = self.hidden;
18512 let vocab = self.vocab;
18513 let vsplit = shard.vsplit;
18514 let base_pos = state.pos;
18515 let reserve = state.reserve;
18516 state.tokens.push(token);
18517 let Qwen4ExpState {
18518 ref tokens,
18519 ws: ref mut ws0,
18520 ref mut tp2,
18521 layers: ref mut lstates,
18522 ..
18523 } = *state;
18524 let Tp2State {
18525 ws1,
18526 layers: tlayers,
18527 graphs: tgraphs,
18528 ..
18529 } = tp2.as_mut().expect("migrated above");
18530 let cap = reserve.max(1);
18533
18534 let token_us = token as usize;
18536 if token_us >= vocab {
18537 return Err(format!("qwen4exp_gpu: token {token_us} out of range").into());
18538 }
18539 let embedded = &self.embed_host[token_us * hidden..(token_us + 1) * hidden];
18540 let mut planes1: Vec<CudaSlice<f32>> = Vec::with_capacity(self.streams);
18541 let ptrs1 = {
18542 let _g = e1.gpu.enter_main()?;
18543 let embedded_dev = ws1.take_f32_h2d(e1, "entry.embed", embedded, cap * hidden)?;
18544 for s in 0..self.streams {
18545 let mut plane = ws1.take_f32(e1, PLANE_SLOTS[s], hidden, cap * hidden)?;
18546 e1.copy_into(&mut plane, 0, &embedded_dev, hidden)?;
18547 planes1.push(plane);
18548 }
18549 ws1.put_f32("entry.embed", embedded_dev);
18550 let ptr_vals: Vec<u64> = {
18551 let stream = e1.gpu.stream();
18552 planes1.iter().map(|p| p.device_ptr(&stream).0).collect()
18553 };
18554 ws1.take_u64_h2d(e1, "hc.ptrs", &ptr_vals, 0)?
18555 };
18556 let mut planes0: Vec<CudaSlice<f32>> = Vec::with_capacity(self.streams);
18557 let ptrs0 = {
18558 let _g = e0.gpu.enter_main()?;
18559 let embedded_dev = ws0.take_f32_h2d(e0, "entry.embed", embedded, cap * hidden)?;
18560 for s in 0..self.streams {
18561 let mut plane = ws0.take_f32(e0, PLANE_SLOTS[s], hidden, cap * hidden)?;
18562 e0.copy_into(&mut plane, 0, &embedded_dev, hidden)?;
18563 planes0.push(plane);
18564 }
18565 ws0.put_f32("entry.embed", embedded_dev);
18566 let ptr_vals: Vec<u64> = {
18567 let stream = e0.gpu.stream();
18568 planes0.iter().map(|p| p.device_ptr(&stream).0).collect()
18569 };
18570 ws0.take_u64_h2d(e0, "hc.ptrs", &ptr_vals, 0)?
18571 };
18572
18573 let use_graphs = decode_graphs_on() && step_ws_on();
18576 let graphs_live = use_graphs && tgraphs.warm;
18577 if use_graphs && !tgraphs.warm {
18578 tgraphs.warm = true;
18579 }
18580 if graphs_live && tgraphs.a[0].len() != self.layers.len() {
18581 for d in 0..2 {
18582 tgraphs.a[d] = (0..self.layers.len()).map(|_| None).collect();
18583 tgraphs.b[d] = (0..self.layers.len()).map(|_| None).collect();
18584 tgraphs.c[d] = (0..self.layers.len()).map(|_| None).collect();
18585 tgraphs.d[d] = (0..self.layers.len()).map(|_| None).collect();
18586 }
18587 }
18588 for (li, layer) in self.layers.iter().enumerate() {
18589 let lstate = &mut lstates[li];
18590 let tw = &shard.layers[li];
18591 let ts = &mut tlayers[li];
18592 let eps_a = layer.eps_attn;
18593 let eps_m = layer.eps_mlp;
18594 let moe = &layer.moe;
18595 let ff = moe.plan.expert_intermediate_size as usize;
18596 let experts = moe.plan.expert_count as usize;
18597 let selected = moe.plan.experts_per_token as usize;
18598 let sff = moe
18599 .plan
18600 .shared
18601 .as_ref()
18602 .map(|s| s.intermediate_size as usize)
18603 .unwrap_or(0);
18604 let sffh = sff / 2;
18605
18606 match (&layer.mixer, &tw.mixer0, &tw.mixer1) {
18608 (MixerW::Gdn(_), MixerHalfW::Gdn(h0), MixerHalfW::Gdn(h1)) => {
18609 {
18610 let _g = e1.gpu.enter_main()?;
18611 if let (Some(ple1), Some(ps1)) = (tw.ple1.as_ref(), ts.ple1.as_mut()) {
18612 let table = &layer.ple.as_ref().expect("ple plan").table;
18613 self.ple_block(
18614 e1,
18615 layer,
18616 ple1,
18617 table,
18618 ps1,
18619 &mut planes1,
18620 tokens,
18621 1,
18622 false,
18623 None,
18624 )?;
18625 }
18626 if graphs_live && layer.ple.is_none() {
18627 if tgraphs.a[1][li].is_none() {
18628 tgraphs.a[1][li] =
18629 Some(e1.capture_graph_retained_nowarm(|eng| {
18630 self.tp2_gdn_seg_a(
18631 eng,
18632 ws1,
18633 &ptrs1,
18634 &tw.attn_gate1,
18635 h1,
18636 &mut ts.m1,
18637 &planes1,
18638 eps_a,
18639 shard.stage0_raw[0],
18640 )
18641 })?);
18642 }
18643 tgraphs.a[1][li].as_ref().unwrap().0.launch()?;
18644 } else {
18645 self.tp2_gdn_seg_a(
18646 e1,
18647 ws1,
18648 &ptrs1,
18649 &tw.attn_gate1,
18650 h1,
18651 &mut ts.m1,
18652 &planes1,
18653 eps_a,
18654 shard.stage0_raw[0],
18655 )?;
18656 }
18657 shard.ev1[0].record(&e1.gpu.stream())?;
18658 }
18659 {
18660 let _g = e0.gpu.enter_main()?;
18661 if let (Some(ple), Some(ps)) = (layer.ple.as_ref(), lstate.ple.as_mut()) {
18662 self.ple_block(
18663 e0,
18664 layer,
18665 ple,
18666 &ple.table,
18667 ps,
18668 &mut planes0,
18669 tokens,
18670 1,
18671 false,
18672 None,
18673 )?;
18674 }
18675 if graphs_live && layer.ple.is_none() {
18676 if tgraphs.a[0][li].is_none() {
18677 tgraphs.a[0][li] =
18678 Some(e0.capture_graph_retained_nowarm(|eng| {
18679 self.tp2_gdn_seg_a(
18680 eng,
18681 ws0,
18682 &ptrs0,
18683 &layer.attn_gate,
18684 h0,
18685 &mut ts.m0,
18686 &planes0,
18687 eps_a,
18688 shard.stage1_raw[0],
18689 )
18690 })?);
18691 }
18692 tgraphs.a[0][li].as_ref().unwrap().0.launch()?;
18693 } else {
18694 self.tp2_gdn_seg_a(
18695 e0,
18696 ws0,
18697 &ptrs0,
18698 &layer.attn_gate,
18699 h0,
18700 &mut ts.m0,
18701 &planes0,
18702 eps_a,
18703 shard.stage1_raw[0],
18704 )?;
18705 }
18706 shard.ev0[0].record(&e0.gpu.stream())?;
18707 }
18708 }
18709 (MixerW::Qsa(qsa), MixerHalfW::Qsa(h0), MixerHalfW::Qsa(h1)) => {
18710 let (q1, g1, inj1) = {
18713 let _g = e1.gpu.enter_main()?;
18714 let (mixed1, inj1) = self.gate_read(
18715 e1,
18716 ws1,
18717 &ptrs1,
18718 &tw.attn_gate1,
18719 &planes1,
18720 1,
18721 eps_a,
18722 false,
18723 )?;
18724 let (q1, g1) = self
18725 .qsa_half_proj(e1, ws1, eps_a, h1, &mixed1, &mut ts.m1, base_pos, 1)?;
18726 ws1.put_f32("hc.mixed", mixed1);
18727 (q1, g1, inj1)
18728 };
18729 let (sels, q0, g0, inj0) = {
18734 let _g = e0.gpu.enter_main()?;
18735 let (mixed0, inj0) = self.gate_read(
18736 e0,
18737 ws0,
18738 &ptrs0,
18739 &layer.attn_gate,
18740 &planes0,
18741 1,
18742 eps_a,
18743 false,
18744 )?;
18745 let (q0, g0) = self
18746 .qsa_half_proj(e0, ws0, eps_a, h0, &mixed0, &mut ts.m0, base_pos, 1)?;
18747 let MixerState::Qsa {
18748 raw_keys,
18749 pooled_keys,
18750 pooled_dev,
18751 pooled_dev_rows,
18752 raw_dev,
18753 raw_dev_rows,
18754 idx_audit,
18755 ..
18756 } = &mut lstate.mixer
18757 else {
18758 return Err("qwen4exp_gpu tp2: QSA layer without raw-key cache".into());
18759 };
18760 let sels = self.qsa_update_select(
18761 e0,
18762 ws0,
18763 qsa,
18764 eps_a,
18765 &mixed0,
18766 raw_keys,
18767 pooled_keys,
18768 pooled_dev,
18769 pooled_dev_rows,
18770 raw_dev,
18771 raw_dev_rows,
18772 idx_audit.as_mut(),
18773 base_pos,
18774 1,
18775 0,
18776 false,
18777 )?;
18778 ws0.put_f32("hc.mixed", mixed0);
18779 (sels, q0, g0, inj0)
18780 };
18781 let t_kv = base_pos + 1;
18782 let block_size = qsa.overlay.block_size as usize;
18783 let (pos_flat, meta, max_count) = rowsel_positions(&sels, block_size);
18784 {
18785 let _g = e1.gpu.enter_main()?;
18786 let pos_dev = ws1.take_i32(e1, "qsa.selpos", &pos_flat, 0)?;
18787 let meta_dev = ws1.take_i32(e1, "qsa.selmeta", &meta, 0)?;
18788 let p1 = self.qsa_half_attend(
18789 e1, ws1, h1, &ts.m1, q1, g1, &pos_dev, &meta_dev, max_count, 1, t_kv,
18790 )?;
18791 ws1.put_i32("qsa.selpos", pos_dev);
18792 ws1.put_i32("qsa.selmeta", meta_dev);
18793 launch_push(e1, &p1, shard.stage0_raw[0], hidden)?;
18794 ws1.put_f32("mixer.out", p1);
18795 put_inject(ws1, inj1);
18796 shard.ev1[0].record(&e1.gpu.stream())?;
18797 }
18798 {
18799 let _g = e0.gpu.enter_main()?;
18800 let pos_dev = ws0.take_i32(e0, "qsa.selpos", &pos_flat, 0)?;
18801 let meta_dev = ws0.take_i32(e0, "qsa.selmeta", &meta, 0)?;
18802 let p0 = self.qsa_half_attend(
18803 e0, ws0, h0, &ts.m0, q0, g0, &pos_dev, &meta_dev, max_count, 1, t_kv,
18804 )?;
18805 ws0.put_i32("qsa.selpos", pos_dev);
18806 ws0.put_i32("qsa.selmeta", meta_dev);
18807 launch_push(e0, &p0, shard.stage1_raw[0], hidden)?;
18808 ws0.put_f32("mixer.out", p0);
18809 put_inject(ws0, inj0);
18810 shard.ev0[0].record(&e0.gpu.stream())?;
18811 }
18812 }
18813 _ => return Err("qwen4exp_gpu tp2: mixer/shard shape mismatch".into()),
18814 }
18815 {
18816 let _g = e0.gpu.enter_main()?;
18817 e0.gpu.stream().wait(&shard.ev1[0])?;
18818 }
18819 {
18820 let _g = e1.gpu.enter_main()?;
18821 e1.gpu.stream().wait(&shard.ev0[0])?;
18822 }
18823
18824 {
18826 let _g = e1.gpu.enter_main()?;
18827 if graphs_live {
18828 if tgraphs.b[1][li].is_none() {
18829 tgraphs.b[1][li] = Some(e1.capture_graph_retained_nowarm(|eng| {
18830 self.tp2_seg_b(
18831 eng,
18832 ws1,
18833 &ptrs1,
18834 &tw.mlp_gate1,
18835 &mut planes1,
18836 &shard.stage1[0],
18837 false,
18838 eps_m,
18839 Some((
18840 &tw.moe.shared_gu1_b16,
18841 &tw.moe.shared_down1,
18842 tw.moe.shared_input_gate1.as_ref(),
18843 sffh,
18844 )),
18845 )
18846 })?);
18847 }
18848 tgraphs.b[1][li].as_ref().unwrap().0.launch()?;
18849 } else {
18850 self.tp2_seg_b(
18851 e1,
18852 ws1,
18853 &ptrs1,
18854 &tw.mlp_gate1,
18855 &mut planes1,
18856 &shard.stage1[0],
18857 false,
18858 eps_m,
18859 Some((
18860 &tw.moe.shared_gu1_b16,
18861 &tw.moe.shared_down1,
18862 tw.moe.shared_input_gate1.as_ref(),
18863 sffh,
18864 )),
18865 )?;
18866 }
18867 }
18868 {
18869 let _g = e0.gpu.enter_main()?;
18870 if graphs_live {
18871 if tgraphs.b[0][li].is_none() {
18872 tgraphs.b[0][li] = Some(e0.capture_graph_retained_nowarm(|eng| {
18873 self.tp2_seg_b(
18874 eng,
18875 ws0,
18876 &ptrs0,
18877 &layer.mlp_gate,
18878 &mut planes0,
18879 &shard.stage0[0],
18880 true,
18881 eps_m,
18882 None,
18883 )
18884 })?);
18885 }
18886 tgraphs.b[0][li].as_ref().unwrap().0.launch()?;
18887 } else {
18888 self.tp2_seg_b(
18889 e0,
18890 ws0,
18891 &ptrs0,
18892 &layer.mlp_gate,
18893 &mut planes0,
18894 &shard.stage0[0],
18895 true,
18896 eps_m,
18897 None,
18898 )?;
18899 }
18900 }
18901
18902 let route = {
18905 let _g = e0.gpu.enter_main()?;
18906 let mixed0 = ws0.take_f32(e0, "hc.mixed", hidden, 0)?;
18907 let mut router_out = ws0.take_f32(e0, "moe.router", experts, 0)?;
18908 let none: Option<CudaSlice<u8>> = None;
18909 let rb = if router_bf16_on() {
18910 &moe.router_b16
18911 } else {
18912 &none
18913 };
18914 linear_trunk_into(
18915 e0,
18916 &moe.router,
18917 rb,
18918 &mixed0,
18919 &mut router_out,
18920 1,
18921 hidden,
18922 experts,
18923 )?;
18924 let logits = e0.dtoh_view(&router_out.slice(0..experts))?;
18925 ws0.put_f32("moe.router", router_out);
18926 ws0.put_f32("hc.mixed", mixed0);
18927 host_route_softmax_topk(&logits, selected)
18928 };
18929 let place = &tw.place;
18933 let mut sel0: Vec<i32> = Vec::with_capacity(selected);
18934 let mut w0: Vec<f32> = Vec::with_capacity(selected);
18935 let mut sel1: Vec<i32> = Vec::with_capacity(selected);
18936 let mut w1: Vec<f32> = Vec::with_capacity(selected);
18937 for &(expert, weight) in &route {
18938 if place.rank(expert) == 0 {
18939 sel0.push(place.local(expert) as i32);
18940 w0.push(weight);
18941 } else {
18942 sel1.push(place.local(expert) as i32);
18943 w1.push(weight);
18944 }
18945 }
18946 {
18947 let r0: Vec<Vec<(usize, f32)>> = vec![
18950 sel0.iter()
18951 .zip(&w0)
18952 .map(|(&s, &w)| (s as usize, w))
18953 .collect(),
18954 ];
18955 let r1: Vec<Vec<(usize, f32)>> = vec![
18956 sel1.iter()
18957 .zip(&w1)
18958 .map(|(&s, &w)| (s as usize, w))
18959 .collect(),
18960 ];
18961 tp2_count_split(&r0, &r1);
18962 trace_moe_routes(layer.index, 1, std::slice::from_ref(&route));
18963 }
18964 match tp2_gate_red()? {
18965 Tp2GateRed::None => {}
18966 Tp2GateRed::SkipPeerMoe => {
18968 sel1.clear();
18969 w1.clear();
18970 }
18971 Tp2GateRed::PeerLocalIds => {
18974 sel0.extend(sel1.drain(..));
18975 w0.extend(w1.drain(..));
18976 }
18977 Tp2GateRed::ReverseePeerWeights => w1.reverse(),
18978 }
18979 let max_sel = selected;
18980 {
18981 let _g = e1.gpu.enter_main()?;
18982 ws1.upsert_u8(e1, "moe.pack", &tp2_pack_bytes(&sel1, &w1, max_sel), 0)?;
18983 if graphs_live {
18984 if tgraphs.c[1][li].is_none() {
18985 tgraphs.c[1][li] = Some(e1.capture_graph_retained_nowarm(|eng| {
18986 self.tp2_seg_c(
18987 eng,
18988 ws1,
18989 (
18990 &tw.moe.gate1.codes,
18991 &tw.moe.gate1.scales,
18992 &tw.moe.gate1.macros_dev,
18993 ),
18994 (
18995 &tw.moe.up1.codes,
18996 &tw.moe.up1.scales,
18997 &tw.moe.up1.macros_dev,
18998 ),
18999 (
19000 &tw.moe.down1.codes,
19001 &tw.moe.down1.scales,
19002 &tw.moe.down1.macros_dev,
19003 ),
19004 ff,
19005 max_sel,
19006 None,
19007 tw.moe.shared_input_gate1.is_some(),
19008 shard.stage0_raw[1],
19009 )
19010 })?);
19011 }
19012 tgraphs.c[1][li].as_ref().unwrap().0.launch()?;
19013 } else {
19014 self.tp2_seg_c(
19015 e1,
19016 ws1,
19017 (
19018 &tw.moe.gate1.codes,
19019 &tw.moe.gate1.scales,
19020 &tw.moe.gate1.macros_dev,
19021 ),
19022 (
19023 &tw.moe.up1.codes,
19024 &tw.moe.up1.scales,
19025 &tw.moe.up1.macros_dev,
19026 ),
19027 (
19028 &tw.moe.down1.codes,
19029 &tw.moe.down1.scales,
19030 &tw.moe.down1.macros_dev,
19031 ),
19032 ff,
19033 max_sel,
19034 None,
19035 tw.moe.shared_input_gate1.is_some(),
19036 shard.stage0_raw[1],
19037 )?;
19038 }
19039 shard.ev1[1].record(&e1.gpu.stream())?;
19040 }
19041 {
19042 let _g = e0.gpu.enter_main()?;
19043 let (
19044 BankHalf::Nvfp4 {
19045 codes: gc,
19046 scales: gs,
19047 macros_dev: gm,
19048 ..
19049 },
19050 BankHalf::Nvfp4 {
19051 codes: uc,
19052 scales: us,
19053 macros_dev: um,
19054 ..
19055 },
19056 BankHalf::Nvfp4 {
19057 codes: dc,
19058 scales: ds,
19059 macros_dev: dm,
19060 ..
19061 },
19062 ) = (&moe.bank.gate, &moe.bank.up, &moe.bank.down)
19063 else {
19064 return Err("qwen4exp_gpu tp2: card0 bank is not NVFP4".into());
19065 };
19066 ws0.upsert_u8(e0, "moe.pack", &tp2_pack_bytes(&sel0, &w0, max_sel), 0)?;
19067 if graphs_live {
19068 if tgraphs.c[0][li].is_none() {
19069 tgraphs.c[0][li] = Some(e0.capture_graph_retained_nowarm(|eng| {
19070 self.tp2_seg_c(
19071 eng,
19072 ws0,
19073 (gc, gs, gm),
19074 (uc, us, um),
19075 (dc, ds, dm),
19076 ff,
19077 max_sel,
19078 Some((
19079 &tw.moe.shared_gu0_b16,
19080 &tw.moe.shared_down0,
19081 moe.shared_input_gate.as_ref(),
19082 sffh,
19083 )),
19084 false,
19085 shard.stage1_raw[1],
19086 )
19087 })?);
19088 }
19089 tgraphs.c[0][li].as_ref().unwrap().0.launch()?;
19090 } else {
19091 self.tp2_seg_c(
19092 e0,
19093 ws0,
19094 (gc, gs, gm),
19095 (uc, us, um),
19096 (dc, ds, dm),
19097 ff,
19098 max_sel,
19099 Some((
19100 &tw.moe.shared_gu0_b16,
19101 &tw.moe.shared_down0,
19102 moe.shared_input_gate.as_ref(),
19103 sffh,
19104 )),
19105 false,
19106 shard.stage1_raw[1],
19107 )?;
19108 }
19109 shard.ev0[1].record(&e0.gpu.stream())?;
19110 }
19111 {
19112 let _g = e1.gpu.enter_main()?;
19113 e1.gpu.stream().wait(&shard.ev0[1])?;
19114 if graphs_live {
19115 if tgraphs.d[1][li].is_none() {
19116 tgraphs.d[1][li] = Some(e1.capture_graph_retained_nowarm(|eng| {
19117 self.tp2_seg_d(eng, ws1, &ptrs1, &mut planes1, &shard.stage1[1], false)
19118 })?);
19119 }
19120 tgraphs.d[1][li].as_ref().unwrap().0.launch()?;
19121 } else {
19122 self.tp2_seg_d(e1, ws1, &ptrs1, &mut planes1, &shard.stage1[1], false)?;
19123 }
19124 }
19125 {
19126 let _g = e0.gpu.enter_main()?;
19127 e0.gpu.stream().wait(&shard.ev1[1])?;
19128 if graphs_live {
19129 if tgraphs.d[0][li].is_none() {
19130 tgraphs.d[0][li] = Some(e0.capture_graph_retained_nowarm(|eng| {
19131 self.tp2_seg_d(eng, ws0, &ptrs0, &mut planes0, &shard.stage0[1], true)
19132 })?);
19133 }
19134 tgraphs.d[0][li].as_ref().unwrap().0.launch()?;
19135 } else {
19136 self.tp2_seg_d(e0, ws0, &ptrs0, &mut planes0, &shard.stage0[1], true)?;
19137 }
19138 }
19139 }
19140
19141 {
19143 let _g = e1.gpu.enter_main()?;
19144 if graphs_live {
19145 if tgraphs.exit[1].is_none() {
19146 tgraphs.exit[1] = Some(e1.capture_graph_retained_nowarm(|eng| {
19147 self.tp2_seg_exit(
19148 eng,
19149 ws1,
19150 &ptrs1,
19151 &shard.exit_gate1,
19152 &planes1,
19153 &shard.lm_head1,
19154 vocab - vsplit,
19155 1, )
19157 })?);
19158 }
19159 tgraphs.exit[1].as_ref().unwrap().0.launch()?;
19160 } else {
19161 self.tp2_seg_exit(
19162 e1,
19163 ws1,
19164 &ptrs1,
19165 &shard.exit_gate1,
19166 &planes1,
19167 &shard.lm_head1,
19168 vocab - vsplit,
19169 1, )?;
19171 }
19172 }
19173 {
19174 let _g = e0.gpu.enter_main()?;
19175 let head0 = self
19176 .output_b16
19177 .as_ref()
19178 .ok_or("qwen4exp_gpu tp2: lm_head has no bf16 twin")?;
19179 if graphs_live {
19180 if tgraphs.exit[0].is_none() {
19181 tgraphs.exit[0] = Some(e0.capture_graph_retained_nowarm(|eng| {
19182 self.tp2_seg_exit(
19183 eng,
19184 ws0,
19185 &ptrs0,
19186 &self.exit_mixer,
19187 &planes0,
19188 head0,
19189 vsplit,
19190 1, )
19192 })?);
19193 }
19194 tgraphs.exit[0].as_ref().unwrap().0.launch()?;
19195 } else {
19196 self.tp2_seg_exit(
19197 e0,
19198 ws0,
19199 &ptrs0,
19200 &self.exit_mixer,
19201 &planes0,
19202 head0,
19203 vsplit,
19204 1, )?;
19206 }
19207 }
19208 let mut out = vec![0.0f32; vocab];
19209 {
19210 let _g = e0.gpu.enter_main()?;
19211 let logits0 = ws0.peek_f32("logits")?;
19212 let host0 = e0.dtoh_view(&logits0.slice(0..vsplit))?;
19213 out[..vsplit].copy_from_slice(&host0);
19214 }
19215 {
19216 let _g = e1.gpu.enter_main()?;
19217 let logits1 = ws1.peek_f32("logits")?;
19218 let host1 = e1.dtoh_view(&logits1.slice(0..vocab - vsplit))?;
19219 out[vsplit..].copy_from_slice(&host1);
19220 }
19221 for (s, plane) in planes0.into_iter().enumerate() {
19222 ws0.put_f32(PLANE_SLOTS[s], plane);
19223 }
19224 for (s, plane) in planes1.into_iter().enumerate() {
19225 ws1.put_f32(PLANE_SLOTS[s], plane);
19226 }
19227 ws0.put_u64("hc.ptrs", ptrs0);
19228 ws1.put_u64("hc.ptrs", ptrs1);
19229 state.pos += 1;
19230 Ok(out)
19231 }
19232}
19233
19234impl Qwen4ExpGpu {
19235 pub fn alloc_state_tp2(
19241 &self,
19242 e0: &Engine,
19243 e1: &Engine,
19244 shard: &Tp2Shard,
19245 capacity: usize,
19246 reserve: usize,
19247 ) -> Res<Qwen4ExpState> {
19248 let mut state = {
19252 let mut st = self.alloc_state_reserve(e0, 1, 1, None)?;
19259 st.capacity = capacity;
19260 st.reserve = reserve;
19261 st
19262 };
19263 let mut tlayers = Vec::with_capacity(self.layers.len());
19264 for (layer, tw) in self.layers.iter().zip(shard.layers.iter()) {
19265 let mk_half = |e: &Engine, hw: &MixerHalfW| -> Res<MixerHalfState> {
19266 let _g = e.gpu.enter_main()?;
19267 match hw {
19268 MixerHalfW::Gdn(h) => {
19269 let conv_dim = 2 * h.nk_h * h.hk + h.nv_h * h.hv;
19270 let pad = h.kernel - 1;
19271 Ok(MixerHalfState::Gdn {
19272 conv: e.zeros(pad * conv_dim)?,
19273 state: e.zeros(h.nv_h * h.hv * h.hk)?,
19274 })
19275 }
19276 MixerHalfW::Qsa(h) => {
19277 let kv_dim = h.nkv_h * h.hd;
19278 let kv = if kv_quant_on() {
19279 QsaKvStore::Q8Q5 {
19280 k: e.alloc_u8(capacity * q8_row_bytes(kv_dim))?,
19281 v: e.alloc_u8(capacity * q5_row_bytes(kv_dim))?,
19282 }
19283 } else {
19284 QsaKvStore::F32 {
19285 k: e.zeros(capacity * kv_dim)?,
19286 v: e.zeros(capacity * kv_dim)?,
19287 }
19288 };
19289 Ok(MixerHalfState::Qsa { kv })
19290 }
19291 }
19292 };
19293 let m0 = mk_half(e0, &tw.mixer0)?;
19294 let m1 = mk_half(e1, &tw.mixer1)?;
19295 let ple1 = match layer.ple.as_ref() {
19296 None => None,
19297 Some(ple) => {
19298 let pad = (ple.plan.conv_kernel as usize - 1) * ple.plan.max_ngram as usize;
19299 let _g = e1.gpu.enter_main()?;
19300 let mut conv_hist = Vec::with_capacity(self.streams);
19301 for _ in 0..self.streams {
19302 conv_hist.push(e1.zeros(pad * self.hidden)?);
19303 }
19304 Some(PleState {
19305 conv_hist,
19306 ngram_ids: Vec::new(),
19307 ngram_history: Vec::new(),
19308 ngram_last_eos: -1,
19309 })
19310 }
19311 };
19312 tlayers.push(Tp2LayerState { m0, m1, ple1 });
19313 }
19314 state.tp2 = Some(Tp2State {
19315 ws1: StepPool::default(),
19316 layers: tlayers,
19317 graphs: Tp2Graphs::default(),
19318 pf_stage0: None,
19319 pf_stage1: None,
19320 pf_stage0_raw: [0; 2],
19321 pf_stage1_raw: [0; 2],
19322 pf_rows: 0,
19323 });
19324 Ok(state)
19325 }
19326
19327 pub fn prefill_extend_tp2(
19332 &self,
19333 e0: &Engine,
19334 e1: &Engine,
19335 shard: &Tp2Shard,
19336 ids: &[u32],
19337 state: &mut Qwen4ExpState,
19338 chunk: usize,
19339 ) -> Res<Vec<f32>> {
19340 if ids.is_empty() || chunk == 0 {
19341 return Err("qwen4exp_gpu: prefill_extend_tp2 needs ids and a chunk size".into());
19342 }
19343 let mut last = Vec::new();
19344 for piece in ids.chunks(chunk) {
19345 let is_last =
19346 piece.as_ptr() as usize + piece.len() * 4 == ids.as_ptr() as usize + ids.len() * 4;
19347 let head = if is_last {
19348 HeadMode::LastRow
19349 } else {
19350 HeadMode::Skip
19351 };
19352 last = self.forward_tp2(e0, e1, shard, piece, state, head)?;
19353 }
19354 Ok(last)
19355 }
19356
19357 #[allow(clippy::too_many_arguments)]
19363 pub fn forward_tp2(
19364 &self,
19365 e0: &Engine,
19366 e1: &Engine,
19367 shard: &Tp2Shard,
19368 ids: &[u32],
19369 state: &mut Qwen4ExpState,
19370 head: HeadMode,
19371 ) -> Res<Vec<f32>> {
19372 if !trunk_bf16_on() || !hc_fused_gate_on() {
19373 return Err(
19374 "qwen4exp_gpu tp2: requires set_trunk_bf16(true) and set_hc_fused_gate(true)"
19375 .into(),
19376 );
19377 }
19378 let t = ids.len();
19379 if t == 0 {
19380 return Err("qwen4exp_gpu tp2: empty chunk".into());
19381 }
19382 if state.pos + t > state.capacity {
19383 return Err("qwen4exp_gpu: state capacity exceeded".into());
19384 }
19385 if state.tp2.is_none() {
19386 self.tp2_migrate(e0, e1, shard, state)?;
19387 state.graphs = StepGraphs::default();
19388 }
19389 let hidden = self.hidden;
19390 let vocab = self.vocab;
19391 let vsplit = shard.vsplit;
19392 let base_pos = state.pos;
19393 let reserve = state.reserve;
19394 state.tokens.extend_from_slice(ids);
19395 let Qwen4ExpState {
19396 ref tokens,
19397 ws: ref mut ws0,
19398 ref mut tp2,
19399 layers: ref mut lstates,
19400 ..
19401 } = *state;
19402 let tp2s = tp2.as_mut().expect("alloc'd or migrated above");
19403 if tp2s.pf_rows < t {
19406 {
19407 let _g = e1.gpu.enter_main()?;
19408 let s1 = [e1.zeros(t * hidden)?, e1.zeros(t * hidden)?];
19409 let s = e1.gpu.stream();
19410 tp2s.pf_stage1_raw = [s1[0].device_ptr(&s).0, s1[1].device_ptr(&s).0];
19411 tp2s.pf_stage1 = Some(s1);
19412 }
19413 {
19414 let _g = e0.gpu.enter_main()?;
19415 let s0 = [e0.zeros(t * hidden)?, e0.zeros(t * hidden)?];
19416 let s = e0.gpu.stream();
19417 tp2s.pf_stage0_raw = [s0[0].device_ptr(&s).0, s0[1].device_ptr(&s).0];
19418 tp2s.pf_stage0 = Some(s0);
19419 }
19420 tp2s.pf_rows = t;
19421 }
19422 let Tp2State {
19423 ws1,
19424 layers: tlayers,
19425 pf_stage0,
19426 pf_stage1,
19427 pf_stage0_raw,
19428 pf_stage1_raw,
19429 ..
19430 } = tp2s;
19431 let pf_stage0 = pf_stage0.as_ref().expect("sized above");
19432 let pf_stage1 = pf_stage1.as_ref().expect("sized above");
19433 let resv = reserve.max(t);
19434
19435 let mut embedded = vec![0.0f32; t * hidden];
19437 for (row, &token) in ids.iter().enumerate() {
19438 let token = token as usize;
19439 if token >= vocab {
19440 return Err(format!("qwen4exp_gpu: token {token} out of range").into());
19441 }
19442 embedded[row * hidden..(row + 1) * hidden]
19443 .copy_from_slice(&self.embed_host[token * hidden..(token + 1) * hidden]);
19444 }
19445 let mut planes1: Vec<CudaSlice<f32>> = Vec::with_capacity(self.streams);
19446 let ptrs1 = {
19447 let _g = e1.gpu.enter_main()?;
19448 let embedded_dev = ws1.take_f32_h2d(e1, "entry.embed", &embedded, resv * hidden)?;
19449 for s in 0..self.streams {
19450 let mut plane = ws1.take_f32(e1, PLANE_SLOTS[s], t * hidden, resv * hidden)?;
19451 e1.copy_into(&mut plane, 0, &embedded_dev, t * hidden)?;
19452 planes1.push(plane);
19453 }
19454 ws1.put_f32("entry.embed", embedded_dev);
19455 let ptr_vals: Vec<u64> = {
19456 let stream = e1.gpu.stream();
19457 planes1.iter().map(|p| p.device_ptr(&stream).0).collect()
19458 };
19459 ws1.take_u64_h2d(e1, "hc.ptrs", &ptr_vals, 0)?
19460 };
19461 let mut planes0: Vec<CudaSlice<f32>> = Vec::with_capacity(self.streams);
19462 let ptrs0 = {
19463 let _g = e0.gpu.enter_main()?;
19464 let embedded_dev = ws0.take_f32_h2d(e0, "entry.embed", &embedded, resv * hidden)?;
19465 for s in 0..self.streams {
19466 let mut plane = ws0.take_f32(e0, PLANE_SLOTS[s], t * hidden, resv * hidden)?;
19467 e0.copy_into(&mut plane, 0, &embedded_dev, t * hidden)?;
19468 planes0.push(plane);
19469 }
19470 ws0.put_f32("entry.embed", embedded_dev);
19471 let ptr_vals: Vec<u64> = {
19472 let stream = e0.gpu.stream();
19473 planes0.iter().map(|p| p.device_ptr(&stream).0).collect()
19474 };
19475 ws0.take_u64_h2d(e0, "hc.ptrs", &ptr_vals, 0)?
19476 };
19477
19478 for (li, layer) in self.layers.iter().enumerate() {
19479 let lstate = &mut lstates[li];
19480 let tw = &shard.layers[li];
19481 let ts = &mut tlayers[li];
19482 let eps_a = layer.eps_attn;
19483 let eps_m = layer.eps_mlp;
19484 let moe = &layer.moe;
19485 let ff = moe.plan.expert_intermediate_size as usize;
19486 let experts = moe.plan.expert_count as usize;
19487 let selected = moe.plan.experts_per_token as usize;
19488 let sff = moe
19489 .plan
19490 .shared
19491 .as_ref()
19492 .map(|s| s.intermediate_size as usize)
19493 .unwrap_or(0);
19494 let sffh = sff / 2;
19495
19496 if let (Some(ple), Some(ps)) = (layer.ple.as_ref(), lstate.ple.as_mut()) {
19498 let _g = e0.gpu.enter_main()?;
19499 self.ple_block(
19500 e0,
19501 layer,
19502 ple,
19503 &ple.table,
19504 ps,
19505 &mut planes0,
19506 tokens,
19507 t,
19508 false,
19509 None,
19510 )?;
19511 }
19512 if let (Some(ple1), Some(ps1)) = (tw.ple1.as_ref(), ts.ple1.as_mut()) {
19513 let table = &layer.ple.as_ref().expect("ple plan").table;
19514 let _g = e1.gpu.enter_main()?;
19515 self.ple_block(
19516 e1,
19517 layer,
19518 ple1,
19519 table,
19520 ps1,
19521 &mut planes1,
19522 tokens,
19523 t,
19524 false,
19525 None,
19526 )?;
19527 }
19528
19529 match (&layer.mixer, &tw.mixer0, &tw.mixer1) {
19531 (MixerW::Gdn(_), MixerHalfW::Gdn(h0), MixerHalfW::Gdn(h1)) => {
19532 {
19533 let _g = e1.gpu.enter_main()?;
19534 let (mixed1, inj1) = self.gate_read(
19535 e1,
19536 ws1,
19537 &ptrs1,
19538 &tw.attn_gate1,
19539 &planes1,
19540 t,
19541 eps_a,
19542 false,
19543 )?;
19544 let p1 =
19545 self.gdn_forward_half(e1, ws1, eps_a, h1, &mixed1, &mut ts.m1, t)?;
19546 ws1.put_f32("hc.mixed", mixed1);
19547 launch_push(e1, &p1, pf_stage0_raw[0], t * hidden)?;
19548 ws1.put_f32("mixer.out", p1);
19549 put_inject(ws1, inj1);
19550 shard.ev1[0].record(&e1.gpu.stream())?;
19551 }
19552 {
19553 let _g = e0.gpu.enter_main()?;
19554 let (mixed0, inj0) = self.gate_read(
19555 e0,
19556 ws0,
19557 &ptrs0,
19558 &layer.attn_gate,
19559 &planes0,
19560 t,
19561 eps_a,
19562 false,
19563 )?;
19564 let p0 =
19565 self.gdn_forward_half(e0, ws0, eps_a, h0, &mixed0, &mut ts.m0, t)?;
19566 ws0.put_f32("hc.mixed", mixed0);
19567 launch_push(e0, &p0, pf_stage1_raw[0], t * hidden)?;
19568 ws0.put_f32("mixer.out", p0);
19569 put_inject(ws0, inj0);
19570 shard.ev0[0].record(&e0.gpu.stream())?;
19571 }
19572 }
19573 (MixerW::Qsa(qsa), MixerHalfW::Qsa(h0), MixerHalfW::Qsa(h1)) => {
19574 let (q1, g1, inj1) = {
19575 let _g = e1.gpu.enter_main()?;
19576 let (mixed1, inj1) = self.gate_read(
19577 e1,
19578 ws1,
19579 &ptrs1,
19580 &tw.attn_gate1,
19581 &planes1,
19582 t,
19583 eps_a,
19584 false,
19585 )?;
19586 let (q1, g1) = self
19587 .qsa_half_proj(e1, ws1, eps_a, h1, &mixed1, &mut ts.m1, base_pos, t)?;
19588 ws1.put_f32("hc.mixed", mixed1);
19589 (q1, g1, inj1)
19590 };
19591 let (sels, q0, g0, inj0) = {
19592 let _g = e0.gpu.enter_main()?;
19593 let (mixed0, inj0) = self.gate_read(
19594 e0,
19595 ws0,
19596 &ptrs0,
19597 &layer.attn_gate,
19598 &planes0,
19599 t,
19600 eps_a,
19601 false,
19602 )?;
19603 let (q0, g0) = self
19604 .qsa_half_proj(e0, ws0, eps_a, h0, &mixed0, &mut ts.m0, base_pos, t)?;
19605 let MixerState::Qsa {
19606 raw_keys,
19607 pooled_keys,
19608 pooled_dev,
19609 pooled_dev_rows,
19610 raw_dev,
19611 raw_dev_rows,
19612 idx_audit,
19613 ..
19614 } = &mut lstate.mixer
19615 else {
19616 return Err("qwen4exp_gpu tp2: QSA layer without raw-key cache".into());
19617 };
19618 let sels = self.qsa_update_select(
19619 e0,
19620 ws0,
19621 qsa,
19622 eps_a,
19623 &mixed0,
19624 raw_keys,
19625 pooled_keys,
19626 pooled_dev,
19627 pooled_dev_rows,
19628 raw_dev,
19629 raw_dev_rows,
19630 idx_audit.as_mut(),
19631 base_pos,
19632 t,
19633 0,
19634 false,
19635 )?;
19636 ws0.put_f32("hc.mixed", mixed0);
19637 (sels, q0, g0, inj0)
19638 };
19639 let t_kv = base_pos + t;
19640 let block_size = qsa.overlay.block_size as usize;
19641 let (pos_flat, meta, max_count) = rowsel_positions(&sels, block_size);
19642 {
19643 let _g = e1.gpu.enter_main()?;
19644 let pos_dev = ws1.take_i32(e1, "qsa.selpos", &pos_flat, 0)?;
19645 let meta_dev = ws1.take_i32(e1, "qsa.selmeta", &meta, 0)?;
19646 let p1 = self.qsa_half_attend(
19647 e1, ws1, h1, &ts.m1, q1, g1, &pos_dev, &meta_dev, max_count, t, t_kv,
19648 )?;
19649 ws1.put_i32("qsa.selpos", pos_dev);
19650 ws1.put_i32("qsa.selmeta", meta_dev);
19651 launch_push(e1, &p1, pf_stage0_raw[0], t * hidden)?;
19652 ws1.put_f32("mixer.out", p1);
19653 put_inject(ws1, inj1);
19654 shard.ev1[0].record(&e1.gpu.stream())?;
19655 }
19656 {
19657 let _g = e0.gpu.enter_main()?;
19658 let pos_dev = ws0.take_i32(e0, "qsa.selpos", &pos_flat, 0)?;
19659 let meta_dev = ws0.take_i32(e0, "qsa.selmeta", &meta, 0)?;
19660 let p0 = self.qsa_half_attend(
19661 e0, ws0, h0, &ts.m0, q0, g0, &pos_dev, &meta_dev, max_count, t, t_kv,
19662 )?;
19663 ws0.put_i32("qsa.selpos", pos_dev);
19664 ws0.put_i32("qsa.selmeta", meta_dev);
19665 launch_push(e0, &p0, pf_stage1_raw[0], t * hidden)?;
19666 ws0.put_f32("mixer.out", p0);
19667 put_inject(ws0, inj0);
19668 shard.ev0[0].record(&e0.gpu.stream())?;
19669 }
19670 }
19671 _ => return Err("qwen4exp_gpu tp2: mixer/shard shape mismatch".into()),
19672 }
19673 {
19674 let _g = e0.gpu.enter_main()?;
19675 e0.gpu.stream().wait(&shard.ev1[0])?;
19676 }
19677 {
19678 let _g = e1.gpu.enter_main()?;
19679 e1.gpu.stream().wait(&shard.ev0[0])?;
19680 }
19681
19682 let join_write = |e: &Engine,
19684 ws: &mut StepPool,
19685 ptrs: &CudaSlice<u64>,
19686 planes: &mut [CudaSlice<f32>],
19687 stage: &CudaSlice<f32>,
19688 rank0: bool|
19689 -> Res<()> {
19690 let p = ws.take_f32(e, "mixer.out", t * hidden, 0)?;
19691 let mut out = ws.take_f32(e, "join.out", t * hidden, 0)?;
19692 if rank0 {
19693 e.add(&p, stage, &mut out, t * hidden)?;
19694 } else {
19695 e.add(stage, &p, &mut out, t * hidden)?;
19696 }
19697 let inj = take_inject(e, ws, self.streams, t)?;
19698 self.gate_write(e, planes, ptrs, &out, &inj, t)?;
19699 ws.put_f32("mixer.out", p);
19700 ws.put_f32("join.out", out);
19701 put_inject(ws, inj);
19702 Ok(())
19703 };
19704 {
19705 let _g = e1.gpu.enter_main()?;
19706 join_write(e1, ws1, &ptrs1, &mut planes1, &pf_stage1[0], false)?;
19707 }
19708 {
19709 let _g = e0.gpu.enter_main()?;
19710 join_write(e0, ws0, &ptrs0, &mut planes0, &pf_stage0[0], true)?;
19711 }
19712
19713 let mixed1 = {
19715 let _g = e1.gpu.enter_main()?;
19716 let (mixed1, injm1) =
19717 self.gate_read(e1, ws1, &ptrs1, &tw.mlp_gate1, &planes1, t, eps_m, false)?;
19718 put_inject(ws1, injm1);
19719 mixed1
19720 };
19721 let mixed0 = {
19722 let _g = e0.gpu.enter_main()?;
19723 let (mixed0, injm0) =
19724 self.gate_read(e0, ws0, &ptrs0, &layer.mlp_gate, &planes0, t, eps_m, false)?;
19725 put_inject(ws0, injm0);
19726 mixed0
19727 };
19728 let routes: Vec<Vec<(usize, f32)>> = {
19731 let _g = e0.gpu.enter_main()?;
19732 let mut router_out = ws0.take_f32(e0, "moe.router", t * experts, 0)?;
19733 let none: Option<CudaSlice<u8>> = None;
19734 let rb = if router_bf16_on() {
19735 &moe.router_b16
19736 } else {
19737 &none
19738 };
19739 linear_trunk_into(
19740 e0,
19741 &moe.router,
19742 rb,
19743 &mixed0,
19744 &mut router_out,
19745 t,
19746 hidden,
19747 experts,
19748 )?;
19749 let logits = e0.dtoh_view(&router_out.slice(0..t * experts))?;
19750 ws0.put_f32("moe.router", router_out);
19751 let mut routes = Vec::with_capacity(t);
19752 for token in 0..t {
19753 routes.push(host_route_softmax_topk(
19754 &logits[token * experts..(token + 1) * experts],
19755 selected,
19756 ));
19757 }
19758 routes
19759 };
19760 let place = &tw.place;
19764 let split_half = |home: bool| -> Vec<Vec<(usize, f32)>> {
19765 routes
19766 .iter()
19767 .map(|r| {
19768 r.iter()
19769 .filter(|&&(eid, _)| (place.rank(eid) == 0) == home)
19770 .map(|&(eid, w)| (place.local(eid), w))
19771 .collect()
19772 })
19773 .collect()
19774 };
19775 let mut routes0 = split_half(true);
19776 let mut routes1 = split_half(false);
19777 tp2_count_split(&routes0, &routes1);
19780 trace_moe_routes(layer.index, t, &routes);
19781 match tp2_gate_red()? {
19782 Tp2GateRed::None => {}
19783 Tp2GateRed::SkipPeerMoe => routes1.iter_mut().for_each(|r| r.clear()),
19784 Tp2GateRed::PeerLocalIds => {
19785 for (r0, r1) in routes0.iter_mut().zip(routes1.iter_mut()) {
19786 r0.append(r1);
19787 }
19788 }
19789 Tp2GateRed::ReverseePeerWeights => {
19790 for r in routes1.iter_mut() {
19791 let n = r.len();
19792 for i in 0..n / 2 {
19793 let (a, b) = (r[i].1, r[n - 1 - i].1);
19794 r[i].1 = b;
19795 r[n - 1 - i].1 = a;
19796 }
19797 }
19798 }
19799 }
19800 let (routes0, routes1) = (routes0, routes1);
19801 {
19803 let _g = e1.gpu.enter_main()?;
19804 let mut out1 = self.tp2_moe_rows(
19805 e1,
19806 ws1,
19807 (
19808 &tw.moe.gate1.codes,
19809 &tw.moe.gate1.scales,
19810 &tw.moe.gate1.macros_dev,
19811 ),
19812 (
19813 &tw.moe.up1.codes,
19814 &tw.moe.up1.scales,
19815 &tw.moe.up1.macros_dev,
19816 ),
19817 (
19818 &tw.moe.down1.codes,
19819 &tw.moe.down1.scales,
19820 &tw.moe.down1.macros_dev,
19821 ),
19822 &routes1,
19823 &mixed1,
19824 t,
19825 ff,
19826 )?;
19827 let (sh, g) = self.tp2_shared_half(
19828 e1,
19829 ws1,
19830 &tw.moe.shared_gu1_b16,
19831 &tw.moe.shared_down1,
19832 tw.moe.shared_input_gate1.as_ref(),
19833 &mixed1,
19834 sffh,
19835 t,
19836 )?;
19837 match g.as_ref() {
19838 Some(g) => e1.add_scaled_rows(&sh, g, &mut out1, hidden, t)?,
19839 None => {
19840 let mut summed = ws1.take_f32(e1, "moe.sum", t * hidden, 0)?;
19841 e1.add(&out1, &sh, &mut summed, t * hidden)?;
19842 ws1.put_f32("moe.out", out1);
19843 out1 = summed;
19844 }
19845 }
19846 ws1.put_f32("moe.sh_down", sh);
19847 if let Some(g) = g {
19848 ws1.put_f32("moe.g", g);
19849 }
19850 launch_push(e1, &out1, pf_stage0_raw[1], t * hidden)?;
19851 ws1.put_f32("moe.out", out1);
19852 ws1.put_f32("hc.mixed", mixed1);
19853 shard.ev1[1].record(&e1.gpu.stream())?;
19854 }
19855 {
19858 let _g = e0.gpu.enter_main()?;
19859 let (
19860 BankHalf::Nvfp4 {
19861 codes: gc,
19862 scales: gs,
19863 macros_dev: gm,
19864 ..
19865 },
19866 BankHalf::Nvfp4 {
19867 codes: uc,
19868 scales: us,
19869 macros_dev: um,
19870 ..
19871 },
19872 BankHalf::Nvfp4 {
19873 codes: dc,
19874 scales: ds,
19875 macros_dev: dm,
19876 ..
19877 },
19878 ) = (&moe.bank.gate, &moe.bank.up, &moe.bank.down)
19879 else {
19880 return Err("qwen4exp_gpu tp2: card0 bank is not NVFP4".into());
19881 };
19882 let mut out0 = self.tp2_moe_rows(
19883 e0,
19884 ws0,
19885 (gc, gs, gm),
19886 (uc, us, um),
19887 (dc, ds, dm),
19888 &routes0,
19889 &mixed0,
19890 t,
19891 ff,
19892 )?;
19893 let (sh, g) = self.tp2_shared_half(
19894 e0,
19895 ws0,
19896 &tw.moe.shared_gu0_b16,
19897 &tw.moe.shared_down0,
19898 moe.shared_input_gate.as_ref(),
19899 &mixed0,
19900 sffh,
19901 t,
19902 )?;
19903 match g.as_ref() {
19904 Some(g) => e0.add_scaled_rows(&sh, g, &mut out0, hidden, t)?,
19905 None => {
19906 let mut summed = ws0.take_f32(e0, "moe.sum", t * hidden, 0)?;
19907 e0.add(&out0, &sh, &mut summed, t * hidden)?;
19908 ws0.put_f32("moe.out", out0);
19909 out0 = summed;
19910 }
19911 }
19912 ws0.put_f32("moe.sh_down", sh);
19913 if let Some(g) = g {
19914 ws0.put_f32("moe.g", g);
19915 }
19916 launch_push(e0, &out0, pf_stage1_raw[1], t * hidden)?;
19917 ws0.put_f32("moe.out", out0);
19918 ws0.put_f32("hc.mixed", mixed0);
19919 shard.ev0[1].record(&e0.gpu.stream())?;
19920 }
19921 {
19922 let _g = e1.gpu.enter_main()?;
19923 e1.gpu.stream().wait(&shard.ev0[1])?;
19924 let p = ws1.take_f32(e1, "moe.out", t * hidden, 0)?;
19925 let mut out = ws1.take_f32(e1, "join.out", t * hidden, 0)?;
19926 e1.add(&pf_stage1[1], &p, &mut out, t * hidden)?;
19927 let inj = take_inject(e1, ws1, self.streams, t)?;
19928 self.gate_write(e1, &mut planes1, &ptrs1, &out, &inj, t)?;
19929 ws1.put_f32("moe.out", p);
19930 ws1.put_f32("join.out", out);
19931 put_inject(ws1, inj);
19932 }
19933 {
19934 let _g = e0.gpu.enter_main()?;
19935 e0.gpu.stream().wait(&shard.ev1[1])?;
19936 let p = ws0.take_f32(e0, "moe.out", t * hidden, 0)?;
19937 let mut out = ws0.take_f32(e0, "join.out", t * hidden, 0)?;
19938 e0.add(&p, &pf_stage0[1], &mut out, t * hidden)?;
19939 let inj = take_inject(e0, ws0, self.streams, t)?;
19940 self.gate_write(e0, &mut planes0, &ptrs0, &out, &inj, t)?;
19941 ws0.put_f32("moe.out", p);
19942 ws0.put_f32("join.out", out);
19943 put_inject(ws0, inj);
19944 }
19945 }
19946
19947 let head_rows = match head {
19964 HeadMode::All => t,
19965 _ => 1,
19966 };
19967 let mut out = vec![
19968 0.0f32;
19969 if head == HeadMode::Skip {
19970 0
19971 } else {
19972 head_rows * vocab
19973 }
19974 ];
19975 if head != HeadMode::Skip {
19976 {
19977 let _g = e1.gpu.enter_main()?;
19978 let mut exit_planes: Vec<CudaSlice<f32>> = Vec::with_capacity(self.streams);
19982 if head != HeadMode::All {
19983 for (s, plane) in planes1.iter().enumerate() {
19984 let mut row = ws1.take_f32(e1, EXIT_PLANE_SLOTS[s], hidden, hidden)?;
19985 e1.copy_range_into(&mut row, 0, plane, (t - 1) * hidden, hidden)?;
19986 exit_planes.push(row);
19987 }
19988 }
19989 let use_planes: &[CudaSlice<f32>] = if head == HeadMode::All {
19990 &planes1
19991 } else {
19992 &exit_planes
19993 };
19994 let ptr_vals: Vec<u64> = {
19995 let stream = e1.gpu.stream();
19996 use_planes.iter().map(|p| p.device_ptr(&stream).0).collect()
19997 };
19998 let eptrs = ws1.take_u64_h2d(e1, "exit.ptrs", &ptr_vals, 0)?;
19999 self.tp2_seg_exit(
20000 e1,
20001 ws1,
20002 &eptrs,
20003 &shard.exit_gate1,
20004 use_planes,
20005 &shard.lm_head1,
20006 vocab - vsplit,
20007 head_rows,
20008 )?;
20009 ws1.put_u64("exit.ptrs", eptrs);
20010 for (s, p) in exit_planes.into_iter().enumerate() {
20011 ws1.put_f32(EXIT_PLANE_SLOTS[s], p);
20012 }
20013 let logits1 = ws1.peek_f32("logits")?;
20014 let half1 = vocab - vsplit;
20015 let host1 = e1.dtoh_view(&logits1.slice(0..head_rows * half1))?;
20016 for r in 0..head_rows {
20019 out[r * vocab + vsplit..(r + 1) * vocab]
20020 .copy_from_slice(&host1[r * half1..(r + 1) * half1]);
20021 }
20022 }
20023 {
20024 let _g = e0.gpu.enter_main()?;
20025 let head0 = self
20026 .output_b16
20027 .as_ref()
20028 .ok_or("qwen4exp_gpu tp2: lm_head has no bf16 twin")?;
20029 let mut exit_planes: Vec<CudaSlice<f32>> = Vec::with_capacity(self.streams);
20030 if head != HeadMode::All {
20031 for (s, plane) in planes0.iter().enumerate() {
20032 let mut row = ws0.take_f32(e0, EXIT_PLANE_SLOTS[s], hidden, hidden)?;
20033 e0.copy_range_into(&mut row, 0, plane, (t - 1) * hidden, hidden)?;
20034 exit_planes.push(row);
20035 }
20036 }
20037 let use_planes: &[CudaSlice<f32>] = if head == HeadMode::All {
20038 &planes0
20039 } else {
20040 &exit_planes
20041 };
20042 let ptr_vals: Vec<u64> = {
20043 let stream = e0.gpu.stream();
20044 use_planes.iter().map(|p| p.device_ptr(&stream).0).collect()
20045 };
20046 let eptrs = ws0.take_u64_h2d(e0, "exit.ptrs", &ptr_vals, 0)?;
20047 self.tp2_seg_exit(
20048 e0,
20049 ws0,
20050 &eptrs,
20051 &self.exit_mixer,
20052 use_planes,
20053 head0,
20054 vsplit,
20055 head_rows,
20056 )?;
20057 ws0.put_u64("exit.ptrs", eptrs);
20058 for (s, p) in exit_planes.into_iter().enumerate() {
20059 ws0.put_f32(EXIT_PLANE_SLOTS[s], p);
20060 }
20061 let logits0 = ws0.peek_f32("logits")?;
20062 let host0 = e0.dtoh_view(&logits0.slice(0..head_rows * vsplit))?;
20063 for r in 0..head_rows {
20065 out[r * vocab..r * vocab + vsplit]
20066 .copy_from_slice(&host0[r * vsplit..(r + 1) * vsplit]);
20067 }
20068 }
20069 } else {
20070 {
20073 let _g = e0.gpu.enter_main()?;
20074 e0.gpu.stream().synchronize()?;
20075 }
20076 {
20077 let _g = e1.gpu.enter_main()?;
20078 e1.gpu.stream().synchronize()?;
20079 }
20080 }
20081 for (s, plane) in planes0.into_iter().enumerate() {
20082 ws0.put_f32(PLANE_SLOTS[s], plane);
20083 }
20084 for (s, plane) in planes1.into_iter().enumerate() {
20085 ws1.put_f32(PLANE_SLOTS[s], plane);
20086 }
20087 ws0.put_u64("hc.ptrs", ptrs0);
20088 ws1.put_u64("hc.ptrs", ptrs1);
20089 state.pos += t;
20090 Ok(out)
20091 }
20092
20093 #[allow(clippy::too_many_arguments)]
20099 fn tp2_moe_rows(
20100 &self,
20101 e: &Engine,
20102 ws: &mut StepPool,
20103 gate: (&CudaSlice<u8>, &CudaSlice<u8>, &CudaSlice<f32>),
20104 up: (&CudaSlice<u8>, &CudaSlice<u8>, &CudaSlice<f32>),
20105 down: (&CudaSlice<u8>, &CudaSlice<u8>, &CudaSlice<f32>),
20106 routes: &[Vec<(usize, f32)>],
20107 mixed: &CudaSlice<f32>,
20108 t: usize,
20109 ff: usize,
20110 ) -> Res<CudaSlice<f32>> {
20111 let hidden = self.hidden;
20112 if !(sel_gufuse_on() && hidden % 32 == 0 && ff % 4 == 0) {
20113 return Err(
20114 "qwen4exp_gpu tp2: prefill MoE needs the gufuse geometry (hidden%32, ff%4)".into(),
20115 );
20116 }
20117 let mut out = ws.take_f32(e, "moe.out", t * hidden, 0)?;
20118 {
20119 let mut view = out.slice_mut(0..t * hidden);
20120 e.memset_zeros_view(&mut view)?;
20121 }
20122 const SLOT_CAP: usize = 8192;
20123 let mut tok0 = 0usize;
20124 while tok0 < t {
20125 let mut tok_n = 0usize;
20127 let mut slots = 0usize;
20128 while tok0 + tok_n < t {
20129 let n = routes[tok0 + tok_n].len();
20130 if tok_n > 0 && slots + n > SLOT_CAP {
20131 break;
20132 }
20133 slots += n;
20134 tok_n += 1;
20135 }
20136 let batch = &routes[tok0..tok0 + tok_n];
20137 let mut sel_all: Vec<i32> = Vec::with_capacity(slots);
20138 let mut w_all: Vec<f32> = Vec::with_capacity(slots);
20139 let mut tok_all: Vec<i32> = Vec::with_capacity(slots);
20140 let mut ranges: Vec<(usize, usize)> = Vec::with_capacity(tok_n);
20141 for (i, route) in batch.iter().enumerate() {
20142 ranges.push((sel_all.len(), route.len()));
20143 for &(eid, wgt) in route {
20144 sel_all.push(eid as i32);
20145 w_all.push(wgt);
20146 tok_all.push((tok0 + i) as i32);
20147 }
20148 }
20149 let s_total = sel_all.len();
20150 if s_total > 0 {
20151 let sel = ws.take_i32(e, "moe.sel", &sel_all, 0)?;
20152 let w_dev = ws.take_f32_h2d(e, "moe.w", &w_all, 0)?;
20153 let tokm = ws.take_i32(e, "moe.tok", &tok_all, 0)?;
20154 let mut act = ws.take_f32(e, "moe.act", s_total * ff, 0)?;
20155 launch_nvfp4_sel_gu_silu(
20156 e,
20157 gate,
20158 up,
20159 Some(&sel),
20160 0,
20161 s_total,
20162 mixed,
20163 &mut act,
20164 hidden,
20165 ff,
20166 Some((&tokm, hidden)),
20167 )?;
20168 let mut partial = ws.take_f32(e, "moe.partial", s_total * hidden, 0)?;
20169 launch_nvfp4_sel_matvec(
20170 e,
20171 down.0,
20172 down.1,
20173 down.2,
20174 &sel,
20175 &act,
20176 &mut partial,
20177 s_total,
20178 ff,
20179 hidden,
20180 ff,
20181 )?;
20182 for (i, &(start, len)) in ranges.iter().enumerate() {
20183 if len > 0 {
20184 launch_axpy_rows_seq_at(
20185 e,
20186 &partial,
20187 start,
20188 &w_dev,
20189 start,
20190 &mut out,
20191 tok0 + i,
20192 hidden,
20193 len,
20194 )?;
20195 }
20196 }
20197 ws.put_i32("moe.sel", sel);
20198 ws.put_i32("moe.tok", tokm);
20199 ws.put_f32("moe.w", w_dev);
20200 ws.put_f32("moe.act", act);
20201 ws.put_f32("moe.partial", partial);
20202 }
20203 tok0 += tok_n;
20204 }
20205 Ok(out)
20206 }
20207
20208 #[allow(clippy::too_many_arguments)]
20211 fn tp2_gdn_seg_a(
20212 &self,
20213 e: &Engine,
20214 ws: &mut StepPool,
20215 ptrs: &CudaSlice<u64>,
20216 attn_gate: &GateW,
20217 h: &GdnHalfW,
20218 hstate: &mut MixerHalfState,
20219 planes: &[CudaSlice<f32>],
20220 eps: f32,
20221 push_raw: u64,
20222 ) -> Res<()> {
20223 let (mixed, inj) = self.gate_read(e, ws, ptrs, attn_gate, planes, 1, eps, false)?;
20224 let p = self.gdn_forward_half(e, ws, eps, h, &mixed, hstate, 1)?;
20225 ws.put_f32("hc.mixed", mixed);
20226 launch_push(e, &p, push_raw, self.hidden)?;
20227 ws.put_f32("mixer.out", p);
20228 put_inject(ws, inj);
20229 Ok(())
20230 }
20231
20232 #[allow(clippy::too_many_arguments)]
20237 fn tp2_seg_b(
20238 &self,
20239 e: &Engine,
20240 ws: &mut StepPool,
20241 ptrs: &CudaSlice<u64>,
20242 mlp_gate: &GateW,
20243 planes: &mut [CudaSlice<f32>],
20244 stage_recv: &CudaSlice<f32>,
20245 rank0: bool,
20246 eps_m: f32,
20247 shared: Option<(
20248 &CudaSlice<u8>,
20249 &CudaSlice<u8>,
20250 Option<&CudaSlice<f32>>,
20251 usize,
20252 )>,
20253 ) -> Res<()> {
20254 let hidden = self.hidden;
20255 let p = ws.take_f32(e, "mixer.out", hidden, 0)?;
20256 let mut out = ws.take_f32(e, "join.out", hidden, 0)?;
20257 if rank0 {
20258 e.add(&p, stage_recv, &mut out, hidden)?;
20259 } else {
20260 e.add(stage_recv, &p, &mut out, hidden)?;
20261 }
20262 let inj = take_inject(e, ws, self.streams, 1)?;
20263 self.gate_write(e, planes, ptrs, &out, &inj, 1)?;
20264 ws.put_f32("mixer.out", p);
20265 ws.put_f32("join.out", out);
20266 put_inject(ws, inj);
20267 let (mixed, injm) = self.gate_read(e, ws, ptrs, mlp_gate, planes, 1, eps_m, false)?;
20268 if let Some((gu_b16, d_b16, ig, sffh)) = shared {
20269 let (sh, gg) = self.tp2_shared_half(e, ws, gu_b16, d_b16, ig, &mixed, sffh, 1)?;
20270 ws.put_f32("moe.sh_down", sh);
20274 if let Some(gg) = gg {
20275 ws.put_f32("moe.g", gg);
20276 }
20277 }
20278 ws.put_f32("hc.mixed", mixed);
20279 put_inject(ws, injm);
20280 Ok(())
20281 }
20282
20283 #[allow(clippy::too_many_arguments)]
20286 fn tp2_seg_exit(
20296 &self,
20297 e: &Engine,
20298 ws: &mut StepPool,
20299 ptrs: &CudaSlice<u64>,
20300 gate: &GateW,
20301 planes: &[CudaSlice<f32>],
20302 head_b16: &CudaSlice<u8>,
20303 out_f: usize,
20304 rows: usize,
20305 ) -> Res<()> {
20306 let x = self
20307 .gate_read_inner(e, ws, ptrs, gate, planes, rows, self.exit_eps, false, false)?
20308 .0;
20309 let mut logits = ws.take_f32(e, "logits", rows * out_f, rows * out_f)?;
20310 launch_qmatvec_bf16w(
20311 e,
20312 head_b16,
20313 &x,
20314 &mut logits,
20315 self.hidden,
20316 out_f,
20317 rows,
20318 1,
20319 0,
20320 0,
20321 self.hidden,
20322 0,
20323 )?;
20324 ws.put_f32("hc.mixed", x);
20325 ws.put_f32("logits", logits);
20326 Ok(())
20327 }
20328}
20329
20330#[allow(clippy::too_many_arguments)]
20334fn launch_nvfp4_sel_matvec_pack(
20335 e: &Engine,
20336 codes: &CudaSlice<u8>,
20337 scales: &CudaSlice<u8>,
20338 macros_dev: &CudaSlice<f32>,
20339 pack_raw: u64,
20340 max_sel: usize,
20341 x: &CudaSlice<f32>,
20342 y: &mut CudaSlice<f32>,
20343 in_f: usize,
20344 out_f: usize,
20345 x_stride: usize,
20346) -> Res<()> {
20347 if in_f % 32 != 0 || out_f % 4 != 0 {
20348 return Err(
20349 "qmatvec_nvfp4_modelopt_sel_f32_v3c: geometry needs in_f%32==0 && out_f%4==0".into(),
20350 );
20351 }
20352 let f = e.func("qmatvec_nvfp4_modelopt_sel_f32_v3c");
20353 let cfg = LaunchConfig {
20354 grid_dim: ((out_f / 4) as u32, max_sel as u32, 1),
20355 block_dim: (32, 1, 1),
20356 shared_mem_bytes: 0,
20357 };
20358 let (inf, outf, ms) = (in_f as i32, out_f as i32, max_sel as i32);
20359 let xs = x_stride as i64;
20360 let stream = e.gpu.stream();
20361 let mut b = stream.launch_builder(&f);
20362 b.arg(codes)
20363 .arg(scales)
20364 .arg(macros_dev)
20365 .arg(&pack_raw)
20366 .arg(&ms)
20367 .arg(x)
20368 .arg(y)
20369 .arg(&inf)
20370 .arg(&outf)
20371 .arg(&xs);
20372 unsafe {
20373 b.launch(cfg)?;
20374 }
20375 Ok(())
20376}
20377
20378fn launch_axpy_rows_seq_pack(
20379 e: &Engine,
20380 x: &CudaSlice<f32>,
20381 pack_raw: u64,
20382 max_sel: usize,
20383 y: &mut CudaSlice<f32>,
20384 width: usize,
20385) -> Res<()> {
20386 let f = e.func("axpy_rows_seq_pack_f32");
20387 let cfg = LaunchConfig::for_num_elems(width as u32);
20388 let (ms, wi) = (max_sel as i32, width as i32);
20389 let stream = e.gpu.stream();
20390 let mut b = stream.launch_builder(&f);
20391 b.arg(x).arg(&pack_raw).arg(&ms).arg(y).arg(&wi);
20392 unsafe {
20393 b.launch(cfg)?;
20394 }
20395 Ok(())
20396}
20397
20398fn tp2_pack_bytes(sel: &[i32], w: &[f32], max_sel: usize) -> Vec<u8> {
20400 let mut out = Vec::with_capacity((2 * max_sel + 1) * 4);
20401 for i in 0..max_sel {
20402 out.extend_from_slice(&sel.get(i).copied().unwrap_or(0).to_le_bytes());
20403 }
20404 for i in 0..max_sel {
20405 out.extend_from_slice(&w.get(i).copied().unwrap_or(0.0).to_le_bytes());
20406 }
20407 out.extend_from_slice(&(sel.len() as i32).to_le_bytes());
20408 out
20409}
20410
20411impl Qwen4ExpGpu {
20412 #[allow(clippy::too_many_arguments)]
20417 fn tp2_seg_c(
20418 &self,
20419 e: &Engine,
20420 ws: &mut StepPool,
20421 gate: (&CudaSlice<u8>, &CudaSlice<u8>, &CudaSlice<f32>),
20422 up: (&CudaSlice<u8>, &CudaSlice<u8>, &CudaSlice<f32>),
20423 down: (&CudaSlice<u8>, &CudaSlice<u8>, &CudaSlice<f32>),
20424 ff: usize,
20425 max_sel: usize,
20426 shared_compute: Option<(
20427 &CudaSlice<u8>,
20428 &CudaSlice<u8>,
20429 Option<&CudaSlice<f32>>,
20430 usize,
20431 )>,
20432 shared_gated: bool,
20433 push_raw: u64,
20434 ) -> Res<()> {
20435 let hidden = self.hidden;
20436 let pack_raw = {
20437 let pack = ws.peek_u8("moe.pack")?;
20438 let stream = e.gpu.stream();
20439 pack.device_ptr(&stream).0
20440 };
20441 let mixed = ws.take_f32(e, "hc.mixed", hidden, 0)?;
20442 let mut act = ws.take_f32(e, "moe.act", max_sel * ff, 0)?;
20443 if sel_gufuse_on() && hidden % 32 == 0 && ff % 4 == 0 {
20447 launch_nvfp4_sel_gu_silu(
20448 e, gate, up, None, pack_raw, max_sel, &mixed, &mut act, hidden, ff, None,
20449 )?;
20450 } else {
20451 let mut yg = ws.take_f32(e, "moe.yg", max_sel * ff, 0)?;
20452 let mut yu = ws.take_f32(e, "moe.yu", max_sel * ff, 0)?;
20453 launch_nvfp4_sel_matvec_pack(
20454 e, gate.0, gate.1, gate.2, pack_raw, max_sel, &mixed, &mut yg, hidden, ff, 0,
20455 )?;
20456 launch_nvfp4_sel_matvec_pack(
20457 e, up.0, up.1, up.2, pack_raw, max_sel, &mixed, &mut yu, hidden, ff, 0,
20458 )?;
20459 e.silu_mul(&yg, &yu, &mut act, max_sel * ff)?;
20460 ws.put_f32("moe.yg", yg);
20461 ws.put_f32("moe.yu", yu);
20462 }
20463 let mut partial = ws.take_f32(e, "moe.partial", max_sel * hidden, 0)?;
20464 launch_nvfp4_sel_matvec_pack(
20465 e,
20466 down.0,
20467 down.1,
20468 down.2,
20469 pack_raw,
20470 max_sel,
20471 &act,
20472 &mut partial,
20473 ff,
20474 hidden,
20475 ff,
20476 )?;
20477 let mut r = ws.take_f32(e, "moe.out", hidden, 0)?;
20478 launch_axpy_rows_seq_pack(e, &partial, pack_raw, max_sel, &mut r, hidden)?;
20479 ws.put_f32("moe.act", act);
20480 ws.put_f32("moe.partial", partial);
20481 let (sh, g) = match shared_compute {
20482 Some((gu_b16, d_b16, ig, sffh)) => {
20483 self.tp2_shared_half(e, ws, gu_b16, d_b16, ig, &mixed, sffh, 1)?
20484 }
20485 None => {
20486 let sh = ws.take_f32(e, "moe.sh_down", hidden, 0)?;
20487 let g = if shared_gated {
20488 Some(ws.take_f32(e, "moe.g", 1, 0)?)
20489 } else {
20490 None
20491 };
20492 (sh, g)
20493 }
20494 };
20495 match g.as_ref() {
20496 Some(g) => e.add_scaled_rows(&sh, g, &mut r, hidden, 1)?,
20497 None => {
20498 let mut view = r.slice_mut(0..hidden);
20499 e.axpy_into(&sh, 1.0, &mut view, hidden)?;
20500 }
20501 }
20502 ws.put_f32("moe.sh_down", sh);
20503 if let Some(g) = g {
20504 ws.put_f32("moe.g", g);
20505 }
20506 launch_push(e, &r, push_raw, hidden)?;
20507 ws.put_f32("moe.out", r);
20508 ws.put_f32("hc.mixed", mixed);
20509 Ok(())
20510 }
20511
20512 #[allow(clippy::too_many_arguments)]
20514 fn tp2_seg_d(
20515 &self,
20516 e: &Engine,
20517 ws: &mut StepPool,
20518 ptrs: &CudaSlice<u64>,
20519 planes: &mut [CudaSlice<f32>],
20520 stage_recv: &CudaSlice<f32>,
20521 rank0: bool,
20522 ) -> Res<()> {
20523 let hidden = self.hidden;
20524 let mp = ws.take_f32(e, "moe.out", hidden, 0)?;
20525 let mut mo = ws.take_f32(e, "join.out", hidden, 0)?;
20526 if rank0 {
20527 e.add(&mp, stage_recv, &mut mo, hidden)?;
20528 } else {
20529 e.add(stage_recv, &mp, &mut mo, hidden)?;
20530 }
20531 let injm = take_inject(e, ws, self.streams, 1)?;
20532 self.gate_write(e, planes, ptrs, &mo, &injm, 1)?;
20533 ws.put_f32("moe.out", mp);
20534 ws.put_f32("join.out", mo);
20535 put_inject(ws, injm);
20536 Ok(())
20537 }
20538}
20539
20540#[cfg(test)]
20541mod sel_group_tests {
20542 use super::*;
20543
20544 static SEAM: std::sync::Mutex<()> = std::sync::Mutex::new(());
20548
20549 #[test]
20554 fn auto_resolves_the_measured_serving_shapes() {
20555 assert_eq!(sel_group_resolve(SEL_GROUP_AUTO, 640, 2560), Some((4, 4)));
20558 assert_eq!(sel_group_resolve(SEL_GROUP_AUTO, 2560, 640), Some((16, 4)));
20561 }
20562
20563 #[test]
20564 fn off_and_odd_in_f_take_the_shipped_kernel() {
20565 assert_eq!(sel_group_resolve(SEL_GROUP_OFF, 640, 2560), None);
20566 assert_eq!(sel_group_resolve(SEL_GROUP_AUTO, 48, 2560), None);
20568 }
20569
20570 #[test]
20574 fn auto_backs_off_rows_then_refuses_rather_than_tiling_raggedly() {
20575 assert_eq!(sel_group_resolve(SEL_GROUP_AUTO, 64, 32), Some((2, 2)));
20578 assert_eq!(sel_group_resolve(SEL_GROUP_AUTO, 64, 24), None);
20580 for &(in_f, out_f) in &[(640usize, 2560usize), (2560, 640), (32, 32), (64, 16)] {
20581 let (g, rows) = sel_group_resolve(SEL_GROUP_AUTO, in_f, out_f)
20582 .unwrap_or_else(|| panic!("auto refused {in_f}x{out_f}"));
20583 assert_eq!(
20584 out_f % ((32 / g) * rows),
20585 0,
20586 "{in_f}x{out_f} -> g{g} rows{rows}"
20587 );
20588 }
20589 }
20590
20591 #[test]
20595 fn explicit_pins_are_verbatim_and_still_tile_checked() {
20596 let _g = SEAM.lock().unwrap();
20597 assert!(set_sel_group("dn:8:1+gu:16:4"));
20598 assert_eq!(sel_group_resolve(sel_group_dn(), 640, 2560), Some((8, 1)));
20599 assert_eq!(sel_group_resolve(sel_group_gu(), 2560, 640), Some((16, 4)));
20600 assert!(set_sel_group("dn:1:4"));
20603 assert_eq!(sel_group_resolve(sel_group_dn(), 2560, 640), Some((1, 4)));
20604 assert_eq!(sel_group_resolve(sel_group_dn(), 2560, 96), None);
20605 set_sel_group("off");
20606 }
20607
20608 #[test]
20612 fn malformed_specs_apply_nothing_and_refuse() {
20613 let _g = SEAM.lock().unwrap();
20614 assert!(set_sel_group("dn:4:4+gu:16:4"));
20615 let before = sel_group_spec();
20616 for bad in [
20617 "dn:3:4", "dn:4:3", "dn:64:4", "dn:4", "xx:4:4", "dn:4:4+xx:1", "+",
20624 ] {
20625 assert!(!set_sel_group(bad), "{bad:?} was accepted");
20626 assert_eq!(
20627 sel_group_spec(),
20628 before,
20629 "{bad:?} mutated state while refusing"
20630 );
20631 }
20632 set_sel_group("off");
20633 }
20634
20635 #[test]
20639 fn seam_round_trips_through_the_boolean_harness() {
20640 let _g = SEAM.lock().unwrap();
20641 set_sel_group("off");
20642 assert_eq!(seam_state("selgroup"), Some(false));
20643 assert!(set_seam("selgroup", true, None));
20644 assert_eq!(seam_state("selgroup"), Some(true));
20645 assert_eq!(sel_group_spec(), "dn:auto+gu:auto");
20646 assert!(set_seam("selgroup", false, None));
20647 assert_eq!(seam_state("selgroup"), Some(false));
20648 assert_eq!(sel_group_spec(), "dn:off+gu:off");
20649 assert!(set_sel_group("dn:4:4+gu:off"));
20652 assert_eq!(seam_state("selgroup"), Some(true));
20653 set_sel_group("off");
20654 assert!(seam_names().contains(&"selgroup"));
20656 assert!(seam_exists("selgroup"));
20657 }
20658}
20659
20660#[cfg(test)]
20672mod tp2_placement_tests {
20673 use super::{LayerPlacement, Tp2Placement};
20674
20675 fn write_map(name: &str, body: &str) -> std::path::PathBuf {
20679 let path = std::env::temp_dir().join(format!(
20680 "memra-q4e-ep-map-{name}-{}.json",
20681 std::process::id()
20682 ));
20683 std::fs::write(&path, body).expect("write map fixture");
20684 path
20685 }
20686
20687 fn load(name: &str, body: &str, expert_count: usize) -> Result<Tp2Placement, String> {
20688 let path = write_map(name, body);
20689 let out = Tp2Placement::load(&path, expert_count).map_err(|e| e.to_string());
20690 let _ = std::fs::remove_file(&path);
20691 out
20692 }
20693
20694 fn doc(body: &str) -> String {
20696 format!(
20697 "{{\"format\": \"memra-ep-map-v1\", \"strategy\": \"coactivation\", \
20698 \"ranks\": 2, \"entry_rank\": 0, \"expert_count\": 4, {body}}}"
20699 )
20700 }
20701
20702 fn assert_refuses(name: &str, body: &str, expert_count: usize, clause: &str) {
20703 match load(name, body, expert_count) {
20704 Ok(_) => panic!("{name}: expected a refusal naming {clause:?}, but the map loaded"),
20705 Err(msg) => {
20706 assert!(
20707 msg.contains(clause),
20708 "{name}: refusal must name the broken clause {clause:?}, got: {msg}"
20709 );
20710 assert!(
20713 msg.contains("MEMRA_Q4E_EP_MAP"),
20714 "{name}: refusal must name the flag/file, got: {msg}"
20715 );
20716 }
20717 }
20718 }
20719
20720 #[test]
20728 fn even_split_is_the_contiguous_suffix_control_arm() {
20729 let p = Tp2Placement::even(512);
20730 assert_eq!(p.strategy(), "even");
20731 assert_eq!(p.entry_rank(), 0);
20732 assert!(p.source().contains("MEMRA_Q4E_EP_MAP unset"));
20733
20734 let l = p.layer(0, 512).expect("even split resolves every layer");
20735 assert!(l.is_even(), "the built-in even split must report as even");
20736 assert_eq!(l.card1.len(), 256);
20737 assert_eq!(l.card1, (256u32..512).collect::<Vec<_>>());
20739 for e in 0..256 {
20740 assert_eq!(l.rank(e), 0, "expert {e} belongs to card 0");
20741 assert_eq!(l.local(e), e, "card-0 local slot IS the global id");
20742 }
20743 for (slot, e) in (256..512).enumerate() {
20744 assert_eq!(l.rank(e), 1, "expert {e} belongs to card 1");
20745 assert_eq!(l.local(e), slot, "card-1 local slot is the gather position");
20746 }
20747 let l47 = p.layer(47, 512).expect("layer 47");
20749 assert_eq!(l47.card1, l.card1);
20750 }
20751
20752 #[test]
20756 fn measured_map_resolves_ascending_gather_and_local_slots() {
20757 let body = "\"layers\": [{\"layer\": 0, \"assignment\": [1, 0, 0, 1]}]";
20759 let p = load("measured", &doc(body), 4).expect("balanced map loads");
20760 assert_eq!(p.strategy(), "coactivation");
20761 let l = p.layer(0, 4).expect("layer 0");
20762
20763 assert!(
20764 !l.is_even(),
20765 "a non-suffix placement is not the control arm"
20766 );
20767 assert_eq!(l.card1, vec![0u32, 3]);
20770 assert_eq!((l.rank(0), l.rank(1), l.rank(2), l.rank(3)), (1, 0, 0, 1));
20771 assert_eq!(l.local(0), 0);
20773 assert_eq!(l.local(3), 1);
20774 assert_eq!(l.local(1), 1);
20776 assert_eq!(l.local(2), 2);
20777 }
20778
20779 #[test]
20783 fn is_even_rejects_a_balanced_permutation() {
20784 let body = "\"layers\": [{\"layer\": 0, \"assignment\": [0, 1, 1, 0]}]";
20785 let p = load("perm", &doc(body), 4).expect("balanced map loads");
20786 let l = p.layer(0, 4).expect("layer 0");
20787 assert_eq!(l.card1, vec![1u32, 2]);
20788 assert!(!l.is_even());
20789 }
20790
20791 #[test]
20794 fn an_explicit_even_map_matches_the_builtin_even_split() {
20795 let body = "\"layers\": [{\"layer\": 0, \"assignment\": [0, 0, 1, 1]}]";
20796 let p = load("explicit-even", &doc(body), 4).expect("even map loads");
20797 let l = p.layer(0, 4).expect("layer 0");
20798 let builtin = Tp2Placement::even(4).layer(0, 4).expect("builtin");
20799 assert!(l.is_even());
20800 assert_eq!(l.card1, builtin.card1);
20801 for e in 0..4 {
20802 assert_eq!(l.rank(e), builtin.rank(e), "rank of expert {e}");
20803 assert_eq!(l.local(e), builtin.local(e), "local slot of expert {e}");
20804 }
20805 }
20806
20807 #[test]
20814 fn refuses_a_foreign_format() {
20815 let body = "\"layers\": [{\"layer\": 0, \"assignment\": [0, 0, 1, 1]}]";
20816 let text = format!(
20817 "{{\"format\": \"memra-ep-map-v2\", \"ranks\": 2, \"expert_count\": 4, {body}}}"
20818 );
20819 assert_refuses("format", &text, 4, "memra-ep-map-v1");
20820 }
20821
20822 #[test]
20823 fn refuses_a_rank_count_that_is_not_two() {
20824 let text = "{\"format\": \"memra-ep-map-v1\", \"ranks\": 4, \"expert_count\": 4, \
20825 \"layers\": [{\"layer\": 0, \"assignment\": [0, 0, 1, 1]}]}";
20826 assert_refuses("ranks", text, 4, "exactly two cards");
20827 }
20828
20829 #[test]
20830 fn refuses_an_expert_count_that_is_not_the_plans() {
20831 let body = "\"layers\": [{\"layer\": 0, \"assignment\": [0, 0, 1, 1]}]";
20832 assert_refuses("experts", &doc(body), 8, "expert_count=4");
20833 }
20834
20835 #[test]
20836 fn refuses_an_entry_rank_outside_the_two_cards() {
20837 let text = "{\"format\": \"memra-ep-map-v1\", \"ranks\": 2, \"entry_rank\": 2, \
20838 \"expert_count\": 4, \
20839 \"layers\": [{\"layer\": 0, \"assignment\": [0, 0, 1, 1]}]}";
20840 assert_refuses("entry", text, 4, "entry_rank=2");
20841 }
20842
20843 #[test]
20844 fn refuses_a_document_with_no_layers_array() {
20845 assert_refuses("nolayers", &doc("\"strategy2\": 0"), 4, "no `layers` array");
20846 }
20847
20848 #[test]
20849 fn refuses_an_empty_layers_array() {
20850 assert_refuses(
20851 "emptylayers",
20852 &doc("\"layers\": []"),
20853 4,
20854 "`layers` is empty",
20855 );
20856 }
20857
20858 #[test]
20859 fn refuses_an_assignment_of_the_wrong_length() {
20860 let body = "\"layers\": [{\"layer\": 0, \"assignment\": [0, 1]}]";
20861 assert_refuses("shortassign", &doc(body), 4, "expected 4");
20862 }
20863
20864 #[test]
20865 fn refuses_a_rank_id_outside_the_two_cards() {
20866 let body = "\"layers\": [{\"layer\": 0, \"assignment\": [0, 0, 1, 7]}]";
20867 assert_refuses("badrank", &doc(body), 4, "expert 3");
20868 }
20869
20870 #[test]
20874 fn refuses_an_unbalanced_layer_and_names_the_rebalance_knob() {
20875 let body = "\"layers\": [{\"layer\": 0, \"assignment\": [0, 1, 1, 1]}]";
20876 assert_refuses("unbalanced", &doc(body), 4, "card 1 owns 3 experts");
20877 let body = "\"layers\": [{\"layer\": 0, \"assignment\": [0, 1, 1, 1]}]";
20878 assert_refuses("unbalanced2", &doc(body), 4, "--balance-tolerance");
20879 }
20880
20881 #[test]
20884 fn refuses_a_layer_the_map_does_not_cover() {
20885 let body = "\"layers\": [{\"layer\": 0, \"assignment\": [0, 0, 1, 1]}]";
20886 let p = load("partial", &doc(body), 4).expect("map loads");
20887 assert!(p.layer(0, 4).is_ok(), "the covered layer resolves");
20888 let msg = p
20889 .layer(1, 4)
20890 .expect_err("an uncovered MoE layer must refuse")
20891 .to_string();
20892 assert!(msg.contains("does not cover MoE layer 1"), "got: {msg}");
20893 assert!(
20894 msg.contains("partly-applied map is not a placement"),
20895 "the refusal must say WHY it is fail-closed, got: {msg}"
20896 );
20897 }
20898
20899 #[test]
20900 fn refuses_a_layer_whose_expert_count_disagrees_with_the_map() {
20901 let p = Tp2Placement::even(512);
20902 let msg = p
20903 .layer(0, 256)
20904 .expect_err("a layer geometry the map is not for must refuse")
20905 .to_string();
20906 assert!(msg.contains("map is for 512"), "got: {msg}");
20907 }
20908
20909 #[test]
20910 fn refuses_an_unreadable_map_path() {
20911 let missing = std::env::temp_dir().join(format!(
20912 "memra-q4e-ep-map-absent-{}.json",
20913 std::process::id()
20914 ));
20915 let _ = std::fs::remove_file(&missing);
20916 let msg = Tp2Placement::load(&missing, 4)
20917 .expect_err("an unreadable map must refuse at the load preflight")
20918 .to_string();
20919 assert!(msg.contains("MEMRA_Q4E_EP_MAP"), "got: {msg}");
20920 }
20921
20922 #[test]
20935 fn refuses_an_odd_routed_bank_on_both_paths() {
20936 let msg = Tp2Placement::even(5)
20938 .layer(0, 5)
20939 .expect_err("an odd bank has no two-card placement")
20940 .to_string();
20941 assert!(msg.contains("ODD"), "got: {msg}");
20942 assert!(msg.contains("EQUAL-size"), "got: {msg}");
20943 let balanced_odd = "{\"format\": \"memra-ep-map-v1\", \"ranks\": 2, \
20946 \"expert_count\": 5, \
20947 \"layers\": [{\"layer\": 0, \"assignment\": [0, 0, 0, 1, 1]}]}";
20948 assert_refuses("odd-balanced", balanced_odd, 5, "ODD");
20949 let unbalanced_odd = "{\"format\": \"memra-ep-map-v1\", \"ranks\": 2, \
20952 \"expert_count\": 5, \
20953 \"layers\": [{\"layer\": 0, \"assignment\": [0, 0, 1, 1, 1]}]}";
20954 assert_refuses("odd-unbalanced", unbalanced_odd, 5, "ODD");
20955 }
20956
20957 #[test]
20964 fn local_slots_are_a_bijection_at_the_serving_geometry() {
20965 let experts = 512usize;
20966 let half = experts / 2;
20967 let assignment: Vec<String> = (0..experts).map(|e| (e % 2).to_string()).collect();
20969 let body = format!(
20970 "\"layers\": [{{\"layer\": 0, \"assignment\": [{}]}}]",
20971 assignment.join(", ")
20972 );
20973 let text = format!(
20974 "{{\"format\": \"memra-ep-map-v1\", \"ranks\": 2, \"expert_count\": {experts}, \
20975 {body}}}"
20976 );
20977 let p = load("bijection", &text, experts).expect("balanced alternating map loads");
20978 let l: LayerPlacement = p.layer(0, experts).expect("layer 0");
20979
20980 assert_eq!(l.card1.len(), half, "card 1 must own exactly half the bank");
20981 assert!(
20982 l.card1.windows(2).all(|w| w[0] < w[1]),
20983 "the gather order must be strictly ascending"
20984 );
20985 let mut seen = vec![false; half];
20986 for e in 0..experts {
20987 match l.rank(e) {
20988 0 => assert_eq!(l.local(e), e, "card-0 slot is the global id"),
20989 1 => {
20990 let slot = l.local(e);
20991 assert!(slot < half, "card-1 slot {slot} outside its half-bank");
20992 assert!(!seen[slot], "card-1 slot {slot} claimed twice");
20993 seen[slot] = true;
20994 }
20995 r => panic!("expert {e} has rank {r}"),
20996 }
20997 }
20998 assert!(seen.into_iter().all(|s| s), "card-1 slots must be dense");
20999 assert!(!l.is_even());
21000 }
21001}