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_env_on(name: &'static str, cell: &'static std::sync::OnceLock<bool>) -> bool {
3053 *cell.get_or_init(|| std::env::var(name).as_deref() != Ok("0"))
3054}
3055
3056fn micro_norm_on() -> bool {
3057 static C: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3058 hc_micro_on() && micro_env_on("MEMRA_Q4E_MICRO_NORM", &C)
3059}
3060
3061fn micro_inj_on() -> bool {
3062 static C: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3063 hc_micro_on() && micro_env_on("MEMRA_Q4E_MICRO_INJ", &C)
3064}
3065
3066fn micro_shexp_on() -> bool {
3067 static C: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3068 hc_micro_on() && micro_env_on("MEMRA_Q4E_MICRO_SHEXP", &C)
3069}
3070
3071fn prof_section<T>(e: &Engine, name: &'static str, f: impl FnOnce() -> Res<T>) -> Res<T> {
3073 if !prof::on() {
3074 return f();
3075 }
3076 e.gpu.stream().synchronize()?;
3077 let t0 = std::time::Instant::now();
3078 let out = f()?;
3079 e.gpu.stream().synchronize()?;
3080 prof::add(name, t0.elapsed().as_secs_f64());
3081 Ok(out)
3082}
3083
3084fn host_sigmoid(x: f32) -> f32 {
3087 1.0 / (1.0 + (-x).exp())
3088}
3089
3090fn host_softmax(values: &mut [f32]) {
3092 let max = values.iter().copied().fold(f32::NEG_INFINITY, f32::max);
3093 let mut sum = 0.0;
3094 for value in values.iter_mut() {
3095 *value = (*value - max).exp();
3096 sum += *value;
3097 }
3098 for value in values {
3099 *value /= sum;
3100 }
3101}
3102
3103const ROUTE_DENOM_FLOOR: f32 = 6.103_515_6e-5;
3108
3109fn host_route_softmax_topk(logits: &[f32], selected: usize) -> Vec<(usize, f32)> {
3112 let mut weights = logits.to_vec();
3113 host_softmax(&mut weights);
3114 let mut indices: Vec<usize> = (0..logits.len()).collect();
3115 indices.sort_by(|&left, &right| {
3116 weights[right]
3117 .total_cmp(&weights[left])
3118 .then(left.cmp(&right))
3119 });
3120 indices.truncate(selected);
3121 let denominator = indices
3122 .iter()
3123 .map(|&index| weights[index])
3124 .sum::<f32>()
3125 .max(ROUTE_DENOM_FLOOR);
3126 indices
3127 .into_iter()
3128 .map(|index| (index, weights[index] / denominator))
3129 .collect()
3130}
3131
3132fn host_rms_norm(x: &mut [f32], width: usize, weight: &[f32], epsilon: f32) {
3134 for row in x.chunks_exact_mut(width) {
3135 let mean_square = row.iter().map(|v| v * v).sum::<f32>() / width as f32;
3136 let inverse = 1.0 / (mean_square + epsilon).sqrt();
3137 for (value, w) in row.iter_mut().zip(weight) {
3138 *value = *value * inverse * w;
3139 }
3140 }
3141}
3142
3143fn host_rope_at(
3148 values: &mut [f32],
3149 head_dim: usize,
3150 dimensions: usize,
3151 base: f32,
3152 yarn: Option<(&[f32], f32)>,
3153 position: usize,
3154) {
3155 let dimensions = dimensions.min(head_dim) / 2 * 2;
3156 let half = dimensions / 2;
3157 for head in values.chunks_exact_mut(head_dim) {
3158 for index in 0..half {
3159 let frequency = base.powf(-2.0 * index as f32 / dimensions as f32);
3160 let frequency = match yarn {
3161 Some((ff, _)) => frequency / ff[index],
3162 None => frequency,
3163 };
3164 let angle = position as f32 * frequency;
3165 let (sin, cos) = angle.sin_cos();
3166 let (sin, cos) = match yarn {
3167 Some((_, mscale)) => (sin * mscale, cos * mscale),
3168 None => (sin, cos),
3169 };
3170 let first = head[index];
3171 let second = head[index + half];
3172 head[index] = first * cos - second * sin;
3173 head[index + half] = first * sin + second * cos;
3174 }
3175 }
3176}
3177
3178#[derive(Clone, Copy, PartialEq, Eq)]
3181pub enum HeadMode {
3182 All,
3184 LastRow,
3186 Skip,
3188}
3189
3190struct RowSel {
3194 full: bool,
3196 blocks: Vec<u32>,
3198 visible: usize,
3201}
3202
3203#[allow(clippy::too_many_arguments)]
3209fn extend_pooled_keys(
3210 pooled_keys: &mut Vec<f32>,
3211 raw_keys: &IdxRawCache,
3212 head_dim: usize,
3213 block_size: usize,
3214 idx_k_norm: &[f32],
3215 epsilon: f32,
3216 rope_dims: usize,
3217 rope_base: f32,
3218 yarn: Option<(&[f32], f32)>,
3219 pos_off: usize,
3220) {
3221 let complete_total = raw_keys.rows(head_dim) / block_size;
3222 let cached = pooled_keys.len() / head_dim;
3223 let mut block_rows: Vec<f32> = Vec::new();
3224 for block in cached..complete_total {
3225 let start = block * block_size;
3226 raw_keys.rows_f32(start, block_size, head_dim, &mut block_rows);
3229 let mut pooled = vec![0.0f32; head_dim];
3230 for offset in 0..block_size {
3231 for dim in 0..head_dim {
3232 pooled[dim] += block_rows[offset * head_dim + dim];
3233 }
3234 }
3235 for value in &mut pooled {
3236 *value /= block_size as f32;
3237 }
3238 host_rms_norm(&mut pooled, head_dim, idx_k_norm, epsilon);
3239 host_rope_at(
3240 &mut pooled,
3241 head_dim,
3242 rope_dims,
3243 rope_base,
3244 yarn,
3245 start + pos_off,
3246 );
3247 pooled_keys.extend_from_slice(&pooled);
3248 }
3249}
3250
3251#[inline]
3254fn sel_cmp(scores: &[f32], a: u32, b: u32) -> std::cmp::Ordering {
3255 scores[b as usize]
3256 .total_cmp(&scores[a as usize])
3257 .then(a.cmp(&b))
3258}
3259
3260fn top_blocks_ascending(scores: &[f32], budget: usize, threads: usize) -> Vec<u32> {
3270 fn cut(scores: &[f32], idx: &mut Vec<u32>, budget: usize) {
3271 let k = budget.min(idx.len());
3272 if k < idx.len() {
3273 idx.select_nth_unstable_by(k - 1, |&a, &b| sel_cmp(scores, a, b));
3274 idx.truncate(k);
3275 }
3276 }
3277 let complete = scores.len();
3278 debug_assert!(budget < complete);
3279 const PAR_MIN: usize = 1 << 15;
3280 let mut candidates: Vec<u32> = if threads > 1 && complete >= PAR_MIN {
3281 let ranges: Vec<(u32, u32)> = {
3282 let per = complete.div_ceil(threads);
3283 (0..threads)
3284 .map(|i| ((i * per) as u32, ((i + 1) * per).min(complete) as u32))
3285 .filter(|(a, b)| a < b)
3286 .collect()
3287 };
3288 std::thread::scope(|scope| {
3289 let handles: Vec<_> = ranges
3290 .iter()
3291 .map(|&(a, b)| {
3292 scope.spawn(move || {
3293 let mut idx: Vec<u32> = (a..b).collect();
3294 cut(scores, &mut idx, budget);
3295 idx
3296 })
3297 })
3298 .collect();
3299 handles
3300 .into_iter()
3301 .flat_map(|h| h.join().unwrap())
3302 .collect()
3303 })
3304 } else {
3305 (0..complete as u32).collect()
3306 };
3307 cut(scores, &mut candidates, budget);
3308 candidates.sort_unstable();
3309 candidates
3310}
3311
3312fn score_blocks(
3317 query: &[f32],
3318 pooled_keys: &[f32],
3319 heads: usize,
3320 head_dim: usize,
3321 complete: usize,
3322 scale: f32,
3323 threads: usize,
3324) -> Vec<f32> {
3325 let mut scores = vec![0.0f32; complete];
3326 let run = |scores: &mut [f32], block0: usize| {
3327 for (i, slot) in scores.iter_mut().enumerate() {
3328 let block = block0 + i;
3329 let pooled = &pooled_keys[block * head_dim..(block + 1) * head_dim];
3330 let mut score = 0.0f32;
3331 for head in 0..heads {
3332 let mut dot = 0.0f32;
3333 for dim in 0..head_dim {
3334 dot += query[head * head_dim + dim] * pooled[dim];
3335 }
3336 score += dot.max(0.0);
3337 }
3338 *slot = score / scale;
3339 }
3340 };
3341 const PAR_MIN: usize = 1 << 14;
3342 if threads > 1 && complete >= PAR_MIN {
3343 let per = complete.div_ceil(threads);
3344 let run = &run;
3345 std::thread::scope(|scope| {
3346 for (i, chunk) in scores.chunks_mut(per).enumerate() {
3347 scope.spawn(move || run(chunk, i * per));
3348 }
3349 });
3350 } else {
3351 run(&mut scores, 0);
3352 }
3353 scores
3354}
3355
3356#[allow(clippy::too_many_arguments)]
3364fn indexer_select_rows(
3365 overlay: &MicroBlockIndexPlan,
3366 rope_base: f32,
3367 yarn: Option<(&[f32], f32)>,
3370 epsilon: f32,
3371 idx_q_norm: &[f32],
3372 idx_k_norm: &[f32],
3373 proj_rows: &[f32], raw_keys: &IdxRawCache, pooled_keys: &mut Vec<f32>,
3376 mut dev: Option<(&Engine, &mut Option<CudaSlice<f32>>, &mut usize)>,
3382 base_pos: usize,
3383 t: usize,
3384 t_kv: usize,
3385 pos_off: usize,
3389) -> Res<Vec<RowSel>> {
3390 let heads = overlay.query_heads as usize;
3391 let head_dim = overlay.head_dim as usize;
3392 let block_size = overlay.block_size as usize;
3393 let budget_blocks = overlay.budget_blocks as usize;
3394 let rope_dims = overlay.rope_dimensions as usize;
3395 let qk_width = (heads + overlay.kv_heads as usize) * head_dim;
3396 let scale = (head_dim as f32).sqrt();
3397 debug_assert_eq!(raw_keys.rows(head_dim), t_kv);
3398 extend_pooled_keys(
3399 pooled_keys,
3400 raw_keys,
3401 head_dim,
3402 block_size,
3403 idx_k_norm,
3404 epsilon,
3405 rope_dims,
3406 rope_base,
3407 yarn,
3408 pos_off,
3409 );
3410 let threads = std::thread::available_parallelism()
3411 .map(|n| n.get())
3412 .unwrap_or(1);
3413 if let Some((e, mirror, mirrored)) = dev.as_mut() {
3417 let rows_needed: Vec<usize> = (0..t)
3418 .map(|qt| (base_pos + qt + 1) / block_size)
3419 .filter(|&c| c > budget_blocks)
3420 .collect();
3421 if let Some(&max_blocks) = rows_needed.iter().max() {
3422 let pooled_rows = pooled_keys.len() / head_dim;
3423 let want = pooled_rows.max(max_blocks);
3425 if mirror
3430 .as_ref()
3431 .is_none_or(|m| m.len() < want * head_dim * POOL_PLANES)
3432 {
3433 let cap_rows = want.next_power_of_two().max(1024);
3434 let fresh = e.zeros(cap_rows * head_dim * POOL_PLANES)?;
3435 **mirror = Some(fresh);
3442 **mirrored = 0;
3443 }
3444 let m = mirror.as_mut().expect("allocated above");
3445 if pooled_rows > **mirrored {
3446 let delta = &pooled_keys[**mirrored * head_dim..pooled_rows * head_dim];
3447 let mut view = m.slice_mut(**mirrored * head_dim..pooled_rows * head_dim);
3448 e.gpu.stream().memcpy_htod(delta, &mut view)?;
3449 let cap_rows = m.len() / (head_dim * POOL_PLANES);
3471 launch_qsa_pooled_transpose(
3472 e,
3473 m,
3474 **mirrored,
3475 pooled_rows - **mirrored,
3476 head_dim,
3477 cap_rows,
3478 )?;
3479 **mirrored = pooled_rows;
3480 }
3481 let mut sels: Vec<RowSel> = Vec::with_capacity(t);
3483 let mut queries: Vec<f32> = Vec::new();
3484 let mut scored_rows: Vec<usize> = Vec::new();
3485 for qt in 0..t {
3486 let row = base_pos + qt;
3487 let visible = row + 1;
3488 let complete = visible / block_size;
3489 if complete <= budget_blocks {
3490 sels.push(RowSel {
3491 full: true,
3492 blocks: Vec::new(),
3493 visible,
3494 });
3495 continue;
3496 }
3497 let mut query = proj_rows[qt * qk_width..qt * qk_width + heads * head_dim].to_vec();
3498 host_rms_norm(&mut query, head_dim, idx_q_norm, epsilon);
3499 host_rope_at(
3500 &mut query,
3501 head_dim,
3502 rope_dims,
3503 rope_base,
3504 yarn,
3505 row + pos_off,
3506 );
3507 queries.extend_from_slice(&query);
3508 scored_rows.push(qt);
3509 sels.push(RowSel {
3510 full: false,
3511 blocks: Vec::new(),
3512 visible,
3513 });
3514 }
3515 let score_cap_mf: usize = std::env::var("MEMRA_Q4E_IDX_SCORE_CAP_MF")
3532 .ok()
3533 .and_then(|v| v.parse::<usize>().ok())
3534 .filter(|v| *v > 0)
3535 .unwrap_or(32);
3536 let score_cap: usize = score_cap_mf << 20;
3537 #[allow(non_snake_case)]
3538 let SCORE_CAP = score_cap;
3539 let mut done = 0usize;
3540 while done < scored_rows.len() {
3541 let batch_max = scored_rows[done..]
3544 .iter()
3545 .map(|&qt| (base_pos + qt + 1) / block_size)
3546 .max()
3547 .unwrap_or(0);
3548 let per = (SCORE_CAP / batch_max.max(1)).max(1);
3549 let n = per.min(scored_rows.len() - done);
3550 let qslab = &queries[done * heads * head_dim..(done + n) * heads * head_dim];
3551 let q_dev = e.htod(qslab)?;
3552 let mut scores_dev = e.uninit(n * batch_max)?;
3553 launch_qsa_index_score(
3554 e,
3555 &q_dev,
3556 m,
3557 &mut scores_dev,
3558 heads,
3559 head_dim,
3560 batch_max,
3561 n,
3562 scale,
3563 )?;
3564 if idx_sel_on() {
3565 let counts: Vec<usize> = (0..n)
3570 .map(|i| (base_pos + scored_rows[done + i] + 1) / block_size)
3571 .collect();
3572 let picked =
3573 launch_qsa_index_topk(e, &scores_dev, &counts, batch_max, budget_blocks)?;
3574 if idx_sel_audit_on() {
3575 let host = e.dtoh(&scores_dev)?;
3576 let mut mismatched = 0u64;
3577 let mut deepest = 0u64;
3578 for i in 0..n {
3579 let complete = counts[i];
3580 let row_scores = &host[i * batch_max..i * batch_max + complete];
3581 let twin = top_blocks_ascending(row_scores, budget_blocks, threads);
3582 if twin != picked[i] {
3583 mismatched += 1;
3584 }
3585 deepest = deepest.max(complete as u64);
3586 }
3587 IDX_SEL_AUDIT_ROWS
3588 .fetch_add(n as u64, std::sync::atomic::Ordering::Relaxed);
3589 IDX_SEL_AUDIT_MISMATCH
3590 .fetch_add(mismatched, std::sync::atomic::Ordering::Relaxed);
3591 IDX_SEL_AUDIT_MAX_BLOCKS
3592 .fetch_max(deepest, std::sync::atomic::Ordering::Relaxed);
3593 if mismatched > 0 {
3594 return Err(format!(
3595 "idxsel audit: {mismatched} of {n} device selections differ \
3596 from the host twin (ids or order) at fill {t_kv}"
3597 )
3598 .into());
3599 }
3600 }
3601 for (i, blocks) in picked.into_iter().enumerate() {
3602 sels[scored_rows[done + i]].blocks = blocks;
3603 }
3604 } else {
3605 let host = e.dtoh(&scores_dev)?;
3606 for i in 0..n {
3607 let qt = scored_rows[done + i];
3608 let complete = (base_pos + qt + 1) / block_size;
3609 let row_scores = &host[i * batch_max..i * batch_max + complete];
3610 sels[qt].blocks = top_blocks_ascending(row_scores, budget_blocks, threads);
3611 }
3612 }
3613 done += n;
3614 }
3615 for sel in &sels {
3616 if sel.visible == 0
3617 || (!sel.full && sel.blocks.is_empty() && sel.visible % block_size == 0)
3618 {
3619 return Err("indexer selection left a query with no visible source".into());
3620 }
3621 }
3622 return Ok(sels);
3623 }
3624 }
3625 let pooled_ref: &[f32] = pooled_keys;
3626 let select_row = |qt: usize, threads_in_row: usize| -> RowSel {
3627 let row = base_pos + qt;
3628 let position = row + pos_off;
3629 let visible = row + 1;
3630 let complete = visible / block_size;
3631 if complete <= budget_blocks {
3638 return RowSel {
3639 full: true,
3640 blocks: Vec::new(),
3641 visible,
3642 };
3643 }
3644 let mut query = proj_rows[qt * qk_width..qt * qk_width + heads * head_dim].to_vec();
3645 host_rms_norm(&mut query, head_dim, idx_q_norm, epsilon);
3646 host_rope_at(&mut query, head_dim, rope_dims, rope_base, yarn, position);
3647 let scores = score_blocks(
3648 &query,
3649 pooled_ref,
3650 heads,
3651 head_dim,
3652 complete,
3653 scale,
3654 threads_in_row,
3655 );
3656 let blocks = top_blocks_ascending(&scores, budget_blocks, threads_in_row);
3657 RowSel {
3658 full: false,
3659 blocks,
3660 visible,
3661 }
3662 };
3663 const ROW_PAR_MIN_WORK: usize = 1 << 16;
3664 let total_scored_blocks: usize = (0..t)
3665 .map(|qt| {
3666 let complete = (base_pos + qt + 1) / block_size;
3667 if complete <= budget_blocks {
3668 0
3669 } else {
3670 complete
3671 }
3672 })
3673 .sum();
3674 let sels: Vec<RowSel> = if t > 1 && threads > 1 && total_scored_blocks >= ROW_PAR_MIN_WORK {
3675 let cursor = std::sync::atomic::AtomicUsize::new(0);
3678 let mut out: Vec<Option<RowSel>> = (0..t).map(|_| None).collect();
3679 let slots = std::sync::Mutex::new(&mut out);
3680 std::thread::scope(|scope| {
3681 for _ in 0..threads.min(t) {
3682 scope.spawn(|| {
3683 loop {
3684 let qt = cursor.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3685 if qt >= t {
3686 break;
3687 }
3688 let sel = select_row(qt, 1);
3689 slots.lock().unwrap()[qt] = Some(sel);
3690 }
3691 });
3692 }
3693 });
3694 out.into_iter().map(|s| s.unwrap()).collect()
3695 } else {
3696 (0..t).map(|qt| select_row(qt, threads)).collect()
3697 };
3698 for sel in &sels {
3699 if sel.visible == 0 || (!sel.full && sel.blocks.is_empty() && sel.visible % block_size == 0)
3700 {
3701 return Err("indexer selection left a query with no visible source".into());
3702 }
3703 }
3704 Ok(sels)
3705}
3706
3707fn rowsel_to_mask(sels: &[RowSel], block_size: usize, t_kv: usize) -> Vec<u8> {
3710 let t = sels.len();
3711 let mut mask = vec![0u8; t * t_kv];
3712 for (qt, sel) in sels.iter().enumerate() {
3713 let row = &mut mask[qt * t_kv..(qt + 1) * t_kv];
3714 if sel.full {
3715 for slot in row.iter_mut().take(sel.visible) {
3716 *slot = 1;
3717 }
3718 continue;
3719 }
3720 for &block in &sel.blocks {
3721 for offset in 0..block_size {
3722 row[block as usize * block_size + offset] = 1;
3723 }
3724 }
3725 let complete = sel.visible / block_size;
3726 for slot in row.iter_mut().take(sel.visible).skip(complete * block_size) {
3727 *slot = 1;
3728 }
3729 }
3730 mask
3731}
3732
3733fn rowsel_positions(sels: &[RowSel], block_size: usize) -> (Vec<i32>, Vec<i32>, usize) {
3738 let mut flat: Vec<i32> = Vec::new();
3739 let mut meta: Vec<i32> = Vec::with_capacity(sels.len() * 2);
3740 let mut max_count = 0usize;
3741 for sel in sels {
3742 let start = flat.len();
3743 if sel.full {
3744 flat.extend(0..sel.visible as i32);
3745 } else {
3746 for &block in &sel.blocks {
3747 let first = block as usize * block_size;
3748 flat.extend(first as i32..(first + block_size) as i32);
3749 }
3750 let complete = sel.visible / block_size;
3751 flat.extend((complete * block_size) as i32..sel.visible as i32);
3752 }
3753 let count = flat.len() - start;
3754 max_count = max_count.max(count);
3755 meta.push(start as i32);
3756 meta.push(count as i32);
3757 }
3758 (flat, meta, max_count)
3759}
3760
3761#[allow(clippy::too_many_arguments)]
3765fn launch_qsa_index_score(
3766 e: &Engine,
3767 q: &CudaSlice<f32>,
3768 pooled: &CudaSlice<f32>,
3769 out: &mut CudaSlice<f32>,
3770 heads: usize,
3771 head_dim: usize,
3772 n_blocks: usize,
3773 rows: usize,
3774 scale: f32,
3775) -> Res<()> {
3776 if rows == 0 || n_blocks == 0 {
3777 return Ok(());
3778 }
3779 if out.len() < rows * n_blocks {
3780 return Err("qsa_index_score_f32: score slab too short".into());
3781 }
3782 if rows > 65535 {
3783 return Err("qsa_index_score_f32: rows exceed grid.y (caller sub-batches)".into());
3784 }
3785 let cap_rows = pooled.len() / (head_dim * POOL_PLANES);
3790 let pool_t = pool_t_on();
3791 if pool_t && cap_rows < n_blocks {
3792 return Err("qsa_index_score_f32_t: pooled plane capacity below n_blocks".into());
3793 }
3794 let f = e.func(if pool_t {
3795 "qsa_index_score_f32_t"
3796 } else {
3797 "qsa_index_score_f32"
3798 });
3799 const TPB: usize = 128;
3800 let cfg = LaunchConfig {
3801 grid_dim: (n_blocks.div_ceil(TPB) as u32, rows as u32, 1),
3802 block_dim: (TPB as u32, 1, 1),
3803 shared_mem_bytes: 0,
3804 };
3805 let (h, hd, nb, r) = (heads as i32, head_dim as i32, n_blocks as i32, rows as i32);
3806 let pitch = cap_rows as i64;
3807 let stream = e.gpu.stream();
3808 if pool_t {
3809 let plane = pooled.slice(cap_rows * head_dim..cap_rows * head_dim * POOL_PLANES);
3812 let mut b = stream.launch_builder(&f);
3813 b.arg(q)
3814 .arg(&plane)
3815 .arg(&mut *out)
3816 .arg(&h)
3817 .arg(&hd)
3818 .arg(&nb)
3819 .arg(&r)
3820 .arg(&scale)
3821 .arg(&pitch);
3822 unsafe {
3823 b.launch(cfg)?;
3824 }
3825 return Ok(());
3826 }
3827 let mut b = stream.launch_builder(&f);
3828 b.arg(q)
3829 .arg(pooled)
3830 .arg(&mut *out)
3831 .arg(&h)
3832 .arg(&hd)
3833 .arg(&nb)
3834 .arg(&r)
3835 .arg(&scale);
3836 unsafe {
3837 b.launch(cfg)?;
3838 }
3839 Ok(())
3840}
3841
3842const POOL_PLANES: usize = 2;
3846
3847fn launch_qsa_pooled_transpose(
3850 e: &Engine,
3851 buf: &mut CudaSlice<f32>,
3852 r0: usize,
3853 rows: usize,
3854 head_dim: usize,
3855 cap_rows: usize,
3856) -> Res<()> {
3857 if rows == 0 {
3858 return Ok(());
3859 }
3860 if r0 + rows > cap_rows {
3861 return Err("qsa_pooled_transpose_f32: delta exceeds the plane capacity".into());
3862 }
3863 let f = e.func("qsa_pooled_transpose_f32");
3864 const TPB: usize = 128;
3865 let cfg = LaunchConfig {
3866 grid_dim: (rows.div_ceil(TPB) as u32, head_dim as u32, 1),
3867 block_dim: (TPB as u32, 1, 1),
3868 shared_mem_bytes: 0,
3869 };
3870 let (r, hd, r0i) = (rows as i32, head_dim as i32, r0 as i32);
3871 let cap = cap_rows as i64;
3872 let stream = e.gpu.stream();
3873 let mut b = stream.launch_builder(&f);
3874 b.arg(buf).arg(&r).arg(&hd).arg(&r0i).arg(&cap);
3875 unsafe {
3876 b.launch(cfg)?;
3877 }
3878 Ok(())
3879}
3880
3881fn launch_qsa_index_topk(
3887 e: &Engine,
3888 scores: &CudaSlice<f32>,
3889 counts: &[usize],
3890 stride: usize,
3891 budget: usize,
3892) -> Res<Vec<Vec<u32>>> {
3893 let rows = counts.len();
3894 if rows == 0 || budget == 0 {
3895 return Ok(Vec::new());
3896 }
3897 if rows > 65535 {
3898 return Err("qsa_index_topk_u32: rows exceed grid.x (caller sub-batches)".into());
3899 }
3900 if scores.len() < rows * stride {
3901 return Err("qsa_index_topk_u32: score slab too short".into());
3902 }
3903 for (r, &c) in counts.iter().enumerate() {
3904 if c <= budget || c > stride {
3905 return Err(format!(
3906 "qsa_index_topk_u32: row {r} block count {c} outside (budget {budget}, \
3907 stride {stride}] — the caller only routes scored rows here"
3908 )
3909 .into());
3910 }
3911 }
3912 let counts_i32: Vec<i32> = counts.iter().map(|&c| c as i32).collect();
3913 let counts_dev = e.htod_i32(&counts_i32)?;
3914 let mut out = e.htod_i32(&vec![-1i32; rows * budget])?;
3916 let f = e.func("qsa_index_topk_u32");
3917 let cfg = LaunchConfig {
3918 grid_dim: (rows as u32, 1, 1),
3919 block_dim: (256, 1, 1),
3920 shared_mem_bytes: 0,
3921 };
3922 let (st, bu, ro) = (stride as i32, budget as i32, rows as i32);
3923 let stream = e.gpu.stream();
3924 let mut b = stream.launch_builder(&f);
3925 b.arg(scores)
3926 .arg(&counts_dev)
3927 .arg(&mut out)
3928 .arg(&st)
3929 .arg(&bu)
3930 .arg(&ro);
3931 unsafe {
3932 b.launch(cfg)?;
3933 }
3934 let host = e.gpu.stream().clone_dtoh(&out)?;
3935 e.gpu.stream().synchronize()?;
3936 let mut out_rows: Vec<Vec<u32>> = Vec::with_capacity(rows);
3937 for r in 0..rows {
3938 let row = &host[r * budget..(r + 1) * budget];
3939 let mut blocks: Vec<u32> = Vec::with_capacity(budget);
3940 let mut prev: i64 = -1;
3941 for (j, &v) in row.iter().enumerate() {
3942 if v < 0 || (v as usize) >= counts[r] || (v as i64) <= prev {
3943 return Err(format!(
3944 "qsa_index_topk_u32: row {r} slot {j} = {v} is not a strictly ascending \
3945 in-range block id (blocks {}, budget {budget})",
3946 counts[r]
3947 )
3948 .into());
3949 }
3950 prev = v as i64;
3951 blocks.push(v as u32);
3952 }
3953 out_rows.push(blocks);
3954 }
3955 Ok(out_rows)
3956}
3957
3958#[allow(clippy::too_many_arguments)]
3962fn launch_copy_rows_col(
3963 e: &Engine,
3964 src: &CudaSlice<f32>,
3965 dst: &mut CudaSlice<f32>,
3966 rows: usize,
3967 width: usize,
3968 src_stride: usize,
3969 src_col: usize,
3970 dst_row: usize,
3971) -> Res<()> {
3972 if rows == 0 {
3973 return Ok(());
3974 }
3975 if src.len() < (rows - 1) * src_stride + src_col + width || dst.len() < (dst_row + rows) * width
3976 {
3977 return Err("copy_rows_col_f32: window out of range".into());
3978 }
3979 let f = e.func("copy_rows_col_f32");
3980 let total = rows * width;
3981 let cfg = LaunchConfig::for_num_elems(total as u32);
3982 let (r, w) = (rows as i32, width as i32);
3983 let (ss, sc, dr) = (src_stride as i64, src_col as i64, dst_row as i64);
3984 let stream = e.gpu.stream();
3985 let mut b = stream.launch_builder(&f);
3986 b.arg(src)
3987 .arg(&mut *dst)
3988 .arg(&r)
3989 .arg(&w)
3990 .arg(&ss)
3991 .arg(&sc)
3992 .arg(&dr);
3993 unsafe {
3994 b.launch(cfg)?;
3995 }
3996 Ok(())
3997}
3998
3999#[allow(clippy::too_many_arguments)]
4006fn launch_route_topk(
4007 e: &Engine,
4008 logits: &CudaSlice<f32>,
4009 sel: &mut CudaSlice<i32>,
4010 w: &mut CudaSlice<f32>,
4011 tok: Option<(&mut CudaSlice<i32>, usize)>,
4012 experts: usize,
4013 selected: usize,
4014 rows: usize,
4015) -> Res<()> {
4016 if rows == 0 {
4017 return Ok(());
4018 }
4019 if selected == 0 || selected > 32 || selected > experts {
4020 return Err("qwen4exp_route_topk_f32: selected out of range (caller guards)".into());
4021 }
4022 if experts % 2 != 0 {
4023 return Err("qwen4exp_route_topk_f32: odd expert count".into());
4026 }
4027 if logits.len() < rows * experts || sel.len() < rows * selected || w.len() < rows * selected {
4028 return Err("qwen4exp_route_topk_f32: buffer too short".into());
4029 }
4030 let smem = experts * 12; if smem > 48 * 1024 {
4032 return Err("qwen4exp_route_topk_f32: experts exceed the smem bound".into());
4033 }
4034 let stream = e.gpu.stream();
4035 let (tok_raw, tok_base) = match tok {
4036 Some((buf, base)) => {
4037 if buf.len() < rows * selected {
4038 return Err("qwen4exp_route_topk_f32: tok map too short".into());
4039 }
4040 (buf.device_ptr(&stream).0, base)
4041 }
4042 None => (0u64, 0usize),
4043 };
4044 let f = e.func("qwen4exp_route_topk_f32");
4045 let cfg = LaunchConfig {
4046 grid_dim: (rows as u32, 1, 1),
4047 block_dim: (128, 1, 1),
4048 shared_mem_bytes: smem as u32,
4049 };
4050 let (ex, se, ro, tb) = (
4051 experts as i32,
4052 selected as i32,
4053 rows as i32,
4054 tok_base as i32,
4055 );
4056 let floor = ROUTE_DENOM_FLOOR;
4057 let mut b = stream.launch_builder(&f);
4058 b.arg(logits)
4059 .arg(&mut *sel)
4060 .arg(&mut *w)
4061 .arg(&tok_raw)
4062 .arg(&ex)
4063 .arg(&se)
4064 .arg(&ro)
4065 .arg(&tb)
4066 .arg(&floor);
4067 unsafe {
4068 b.launch(cfg)?;
4069 }
4070 Ok(())
4071}
4072
4073#[allow(clippy::too_many_arguments)]
4077fn route_topk_device(
4078 e: &Engine,
4079 logits: &CudaSlice<f32>,
4080 sel: &mut CudaSlice<i32>,
4081 w: &mut CudaSlice<f32>,
4082 tok: Option<(&mut CudaSlice<i32>, usize)>,
4083 experts: usize,
4084 selected: usize,
4085 rows: usize,
4086 layer: u32,
4087) -> Res<()> {
4088 launch_route_topk(e, logits, sel, w, tok, experts, selected, rows)?;
4089 if !router_audit_on() {
4090 return Ok(());
4091 }
4092 let k = selected.min(experts);
4093 let lg = e.dtoh_view(&logits.slice(0..rows * experts))?;
4094 let sel_h = e.gpu.stream().clone_dtoh(&sel.slice(0..rows * selected))?;
4095 let w_h = e.gpu.stream().clone_dtoh(&w.slice(0..rows * selected))?;
4096 {
4110 let routes: Vec<Vec<(usize, f32)>> = (0..rows)
4111 .map(|row| {
4112 (0..selected)
4113 .map(|j| {
4114 (
4115 sel_h[row * selected + j].max(0) as usize,
4116 w_h[row * selected + j],
4117 )
4118 })
4119 .collect()
4120 })
4121 .collect();
4122 trace_moe_routes(layer, rows, &routes);
4123 }
4124 let mut worst: u32 = 0;
4125 for row in 0..rows {
4126 let twin = host_route_softmax_topk(&lg[row * experts..(row + 1) * experts], selected);
4127 if twin.len() != k {
4128 return Err("router audit: host twin emitted an unexpected selection width".into());
4129 }
4130 for (j, &(idx, wt)) in twin.iter().enumerate() {
4131 let ds = sel_h[row * selected + j];
4132 let dw = w_h[row * selected + j];
4133 if ds != idx as i32 {
4134 return Err(format!(
4135 "router audit: selection mismatch at row {row} slot {j}: \
4136 device {ds} vs host {idx} (host w {wt:e})"
4137 )
4138 .into());
4139 }
4140 let ulp = (dw.to_bits() as i64 - wt.to_bits() as i64).unsigned_abs();
4141 let ulp = u32::try_from(ulp).unwrap_or(u32::MAX);
4142 worst = worst.max(ulp);
4143 if ulp > ROUTE_AUDIT_ULP_BOUND {
4144 return Err(format!(
4145 "router audit: weight ULP {ulp} > bound {ROUTE_AUDIT_ULP_BOUND} at \
4146 row {row} slot {j}: device {dw:e} vs host {wt:e}"
4147 )
4148 .into());
4149 }
4150 }
4151 }
4152 ROUTE_AUDIT_ROWS.fetch_add(rows as u64, std::sync::atomic::Ordering::Relaxed);
4153 ROUTE_AUDIT_MAX_ULP.fetch_max(worst, std::sync::atomic::Ordering::Relaxed);
4154 Ok(())
4155}
4156
4157#[allow(dead_code)]
4162#[allow(clippy::too_many_arguments)]
4163fn indexer_mask_rows(
4164 overlay: &MicroBlockIndexPlan,
4165 rope_base: f32,
4166 yarn: Option<(&[f32], f32)>,
4167 epsilon: f32,
4168 idx_q_norm: &[f32],
4169 idx_k_norm: &[f32],
4170 proj_rows: &[f32],
4171 raw_keys: &IdxRawCache,
4172 pooled_keys: &mut Vec<f32>,
4173 base_pos: usize,
4174 t: usize,
4175 t_kv: usize,
4176 pos_off: usize,
4177) -> Res<Vec<u8>> {
4178 let sels = indexer_select_rows(
4179 overlay,
4180 rope_base,
4181 yarn,
4182 epsilon,
4183 idx_q_norm,
4184 idx_k_norm,
4185 proj_rows,
4186 raw_keys,
4187 pooled_keys,
4188 None,
4191 base_pos,
4192 t,
4193 t_kv,
4194 pos_off,
4195 )?;
4196 Ok(rowsel_to_mask(&sels, overlay.block_size as usize, t_kv))
4197}
4198
4199fn shift_right_ignore_eos(history: &[i64], shift: usize, eos: i64) -> Vec<i64> {
4201 if shift == 0 {
4202 return history.to_vec();
4203 }
4204 let mut last_eos_inclusive: i64 = -1;
4205 let mut output = Vec::with_capacity(history.len());
4206 for (position, &token) in history.iter().enumerate() {
4207 let previous_eos = last_eos_inclusive;
4208 if token == eos {
4209 last_eos_inclusive = position as i64;
4210 }
4211 let segment_start = previous_eos + 1;
4212 let position_in_segment = position as i64 - segment_start;
4213 let source = position as i64 - shift as i64;
4214 let valid = position_in_segment >= shift as i64 && source >= 0;
4215 output.push(if valid { history[source as usize] } else { eos });
4216 }
4217 output
4218}
4219
4220#[allow(clippy::too_many_arguments)]
4238fn host_ngram_ids_cached(
4239 cache_ids: &mut Vec<i64>,
4240 cache_history: &mut Vec<i64>,
4241 cache_last_eos: &mut i64,
4242 token_ids: &[u32],
4243 multipliers: &[i64],
4244 sizes: &[i64],
4245 offsets: &[i64],
4246 max_ngram: usize,
4247 heads_per_ngram: usize,
4248 eos_token_id: u32,
4249) {
4250 let context = max_ngram - 1;
4251 let eos = eos_token_id as i64;
4252 let total_heads = (max_ngram - 1) * heads_per_ngram;
4253 if cache_history.is_empty() {
4254 cache_history.extend(std::iter::repeat_n(eos, context));
4255 *cache_last_eos = context as i64 - 1; cache_ids.clear();
4257 }
4258 let cached_tokens = (cache_history.len() - context).min(cache_ids.len() / total_heads);
4259 let mut keep = cached_tokens.min(token_ids.len());
4261 for i in 0..keep {
4262 if cache_history[context + i] != token_ids[i] as i64 {
4263 keep = i;
4264 break;
4265 }
4266 }
4267 if keep < cached_tokens {
4268 cache_history.truncate(context + keep);
4270 cache_ids.truncate(keep * total_heads);
4271 *cache_last_eos = cache_history
4272 .iter()
4273 .rposition(|&v| v == eos)
4274 .map(|p| p as i64)
4275 .unwrap_or(-1);
4276 }
4277 for &token in &token_ids[keep..] {
4278 let position = cache_history.len();
4279 let value = token as i64;
4280 cache_history.push(value);
4281 let previous_eos = *cache_last_eos;
4283 if value == eos {
4284 *cache_last_eos = position as i64;
4285 }
4286 let segment_start = previous_eos + 1;
4287 let position_in_segment = position as i64 - segment_start;
4288 let shifted_at = |shift: usize| -> i64 {
4289 if shift == 0 {
4290 return cache_history[position];
4291 }
4292 let source = position as i64 - shift as i64;
4293 if position_in_segment >= shift as i64 && source >= 0 {
4294 cache_history[source as usize]
4295 } else {
4296 eos
4297 }
4298 };
4299 let mut row = vec![0i64; total_heads];
4301 for ngram in 2..=max_ngram {
4302 let head_start = (ngram - 2) * heads_per_ngram;
4303 let mut mixed = shifted_at(0).wrapping_mul(multipliers[0]);
4304 for shift in 1..ngram {
4305 mixed ^= shifted_at(shift).wrapping_mul(multipliers[shift]);
4306 }
4307 for head in 0..heads_per_ngram {
4308 let index = head_start + head;
4309 row[index] = mixed.rem_euclid(sizes[index]) + offsets[index];
4310 }
4311 }
4312 cache_ids.extend_from_slice(&row);
4313 }
4314 debug_assert_eq!(cache_ids.len(), token_ids.len() * total_heads);
4315 }
4319
4320fn host_ngram_ids(
4323 token_ids: &[u32],
4324 multipliers: &[i64],
4325 sizes: &[i64],
4326 offsets: &[i64],
4327 max_ngram: usize,
4328 heads_per_ngram: usize,
4329 eos_token_id: u32,
4330) -> Vec<i64> {
4331 let context = max_ngram - 1;
4332 let eos = eos_token_id as i64;
4333 let total_heads = (max_ngram - 1) * heads_per_ngram;
4334 let mut history = Vec::with_capacity(context + token_ids.len());
4335 history.extend(std::iter::repeat_n(eos, context));
4336 history.extend(token_ids.iter().map(|&token| token as i64));
4337 let shifted: Vec<Vec<i64>> = (0..max_ngram)
4338 .map(|shift| shift_right_ignore_eos(&history, shift, eos))
4339 .collect();
4340 let tokens = token_ids.len();
4341 let mut ids = vec![0i64; tokens * total_heads];
4342 for ngram in 2..=max_ngram {
4343 let head_start = (ngram - 2) * heads_per_ngram;
4344 for token in 0..tokens {
4345 let position = context + token;
4346 let mut mixed = shifted[0][position].wrapping_mul(multipliers[0]);
4347 for (shift, row) in shifted.iter().enumerate().take(ngram).skip(1) {
4348 mixed ^= row[position].wrapping_mul(multipliers[shift]);
4349 }
4350 for head in 0..heads_per_ngram {
4351 let index = head_start + head;
4352 ids[token * total_heads + index] = mixed.rem_euclid(sizes[index]) + offsets[index];
4353 }
4354 }
4355 }
4356 ids
4357}
4358
4359#[allow(clippy::too_many_arguments)]
4362fn launch_sdpa_mask(
4363 e: &Engine,
4364 q: &CudaSlice<f32>,
4365 k: &CudaView<'_, f32>,
4366 v: &CudaView<'_, f32>,
4367 o: &mut CudaSlice<f32>,
4368 mask: &CudaSlice<u8>,
4369 head_dim: usize,
4370 n_head: usize,
4371 n_head_kv: usize,
4372 t: usize,
4373 t_kv: usize,
4374 scale: f32,
4375) -> Res<()> {
4376 if t_kv * 4 > 48 * 1024 {
4377 return Err(
4378 "sdpa_naive_mask_f32: T_kv exceeds the smem bound; the gmem twin is perf-lane work"
4379 .into(),
4380 );
4381 }
4382 let f = e.func("sdpa_naive_mask_f32");
4383 let cfg = LaunchConfig {
4384 grid_dim: (n_head as u32, t as u32, 1),
4385 block_dim: (128, 1, 1),
4386 shared_mem_bytes: (t_kv * 4) as u32,
4387 };
4388 let (hd, nh, nkv, ti, tkvi) = (
4389 head_dim as i32,
4390 n_head as i32,
4391 n_head_kv as i32,
4392 t as i32,
4393 t_kv as i32,
4394 );
4395 let stream = e.gpu.stream();
4396 let mut b = stream.launch_builder(&f);
4397 b.arg(q)
4398 .arg(k)
4399 .arg(v)
4400 .arg(o)
4401 .arg(mask)
4402 .arg(&hd)
4403 .arg(&nh)
4404 .arg(&nkv)
4405 .arg(&ti)
4406 .arg(&tkvi)
4407 .arg(&scale);
4408 unsafe {
4409 b.launch(cfg)?;
4410 }
4411 Ok(())
4412}
4413
4414#[allow(clippy::too_many_arguments)]
4420fn launch_sdpa_blocklist(
4421 e: &Engine,
4422 q: &CudaSlice<f32>,
4423 k: &CudaView<'_, f32>,
4424 v: &CudaView<'_, f32>,
4425 o: &mut CudaSlice<f32>,
4426 pos: &CudaSlice<i32>,
4427 meta: &CudaSlice<i32>,
4428 head_dim: usize,
4429 n_head: usize,
4430 n_head_kv: usize,
4431 t: usize,
4432 max_count: usize,
4433 scale: f32,
4434) -> Res<()> {
4435 let smem = (max_count * 8) as u32;
4438 if smem > 48 * 1024 {
4439 return Err("sdpa_blocklist_f32: selection exceeds the smem budget".into());
4440 }
4441 let f = e.func("sdpa_blocklist_f32");
4442 let cfg = LaunchConfig {
4443 grid_dim: (n_head as u32, t as u32, 1),
4444 block_dim: (128, 1, 1),
4445 shared_mem_bytes: smem,
4446 };
4447 let (hd, nh, nkv, ti, mc) = (
4448 head_dim as i32,
4449 n_head as i32,
4450 n_head_kv as i32,
4451 t as i32,
4452 max_count as i32,
4453 );
4454 let stream = e.gpu.stream();
4455 let mut b = stream.launch_builder(&f);
4456 b.arg(q)
4457 .arg(k)
4458 .arg(v)
4459 .arg(o)
4460 .arg(pos)
4461 .arg(meta)
4462 .arg(&hd)
4463 .arg(&nh)
4464 .arg(&nkv)
4465 .arg(&ti)
4466 .arg(&mc)
4467 .arg(&scale);
4468 unsafe {
4469 b.launch(cfg)?;
4470 }
4471 Ok(())
4472}
4473
4474#[allow(clippy::too_many_arguments)]
4477fn launch_q4e_kv_append(
4478 e: &Engine,
4479 k_rows: &CudaSlice<f32>,
4480 v_rows: &CudaSlice<f32>,
4481 k: &mut CudaSlice<u8>,
4482 v: &mut CudaSlice<u8>,
4483 base_pos: usize,
4484 t: usize,
4485 kv_dim: usize,
4486) -> Res<()> {
4487 let f = e.func("q4e_kv_append_q8q5_rows");
4488 let blocks = kv_dim.div_ceil(32);
4489 let cfg = LaunchConfig {
4490 grid_dim: (blocks as u32, t as u32, 1),
4491 block_dim: (32, 1, 1),
4492 shared_mem_bytes: 0,
4493 };
4494 let (t0, dk, dv) = (base_pos as i32, kv_dim as i32, kv_dim as i32);
4495 let (ktb, vtb) = (q8_row_bytes(kv_dim) as i64, q5_row_bytes(kv_dim) as i64);
4496 let stream = e.gpu.stream();
4497 let mut b = stream.launch_builder(&f);
4498 b.arg(k_rows)
4499 .arg(v_rows)
4500 .arg(k)
4501 .arg(v)
4502 .arg(&t0)
4503 .arg(&dk)
4504 .arg(&dv)
4505 .arg(&ktb)
4506 .arg(&vtb);
4507 unsafe {
4508 b.launch(cfg)?;
4509 }
4510 Ok(())
4511}
4512
4513#[allow(clippy::too_many_arguments)]
4515fn launch_q4e_kv_dequant_rows(
4516 e: &Engine,
4517 k: &CudaSlice<u8>,
4518 v: &CudaSlice<u8>,
4519 k_out: &mut CudaSlice<f32>,
4520 v_out: &mut CudaSlice<f32>,
4521 r0: usize,
4522 rows: usize,
4523 kv_dim: usize,
4524) -> Res<()> {
4525 let f = e.func("q4e_kv_dequant_rows");
4526 let blocks = kv_dim.div_ceil(32);
4527 let cfg = LaunchConfig {
4528 grid_dim: (blocks as u32, rows as u32, 1),
4529 block_dim: (32, 1, 1),
4530 shared_mem_bytes: 0,
4531 };
4532 let (r0i, dk, dv) = (r0 as i32, kv_dim as i32, kv_dim as i32);
4533 let (ktb, vtb) = (q8_row_bytes(kv_dim) as i64, q5_row_bytes(kv_dim) as i64);
4534 let stream = e.gpu.stream();
4535 let mut b = stream.launch_builder(&f);
4536 b.arg(k)
4537 .arg(v)
4538 .arg(k_out)
4539 .arg(v_out)
4540 .arg(&r0i)
4541 .arg(&dk)
4542 .arg(&dv)
4543 .arg(&ktb)
4544 .arg(&vtb);
4545 unsafe {
4546 b.launch(cfg)?;
4547 }
4548 Ok(())
4549}
4550
4551#[allow(clippy::too_many_arguments)]
4554fn launch_q4e_sdpa_blocklist_q8q5(
4555 e: &Engine,
4556 q: &CudaSlice<f32>,
4557 k: &CudaSlice<u8>,
4558 v: &CudaSlice<u8>,
4559 o: &mut CudaSlice<f32>,
4560 pos: &CudaSlice<i32>,
4561 meta: &CudaSlice<i32>,
4562 head_dim: usize,
4563 n_head: usize,
4564 n_head_kv: usize,
4565 t: usize,
4566 max_count: usize,
4567 scale: f32,
4568) -> Res<()> {
4569 let smem = (max_count * 8) as u32;
4570 if smem > 48 * 1024 {
4571 return Err("q4e_sdpa_blocklist_q8q5: selection exceeds the smem budget".into());
4572 }
4573 let f = e.func(if kv_hoist_on() {
4575 "q4e_sdpa_blocklist_q8q5_hoist"
4576 } else {
4577 "q4e_sdpa_blocklist_q8q5"
4578 });
4579 let cfg = LaunchConfig {
4580 grid_dim: (n_head as u32, t as u32, 1),
4581 block_dim: (128, 1, 1),
4582 shared_mem_bytes: smem,
4583 };
4584 let kv_dim = n_head_kv * head_dim;
4585 let (hd, nh, nkv, ti, mc) = (
4586 head_dim as i32,
4587 n_head as i32,
4588 n_head_kv as i32,
4589 t as i32,
4590 max_count as i32,
4591 );
4592 let (ktb, vtb) = (q8_row_bytes(kv_dim) as i64, q5_row_bytes(kv_dim) as i64);
4593 let stream = e.gpu.stream();
4594 let mut b = stream.launch_builder(&f);
4595 b.arg(q)
4596 .arg(k)
4597 .arg(v)
4598 .arg(o)
4599 .arg(pos)
4600 .arg(meta)
4601 .arg(&hd)
4602 .arg(&nh)
4603 .arg(&nkv)
4604 .arg(&ti)
4605 .arg(&mc)
4606 .arg(&scale)
4607 .arg(&ktb)
4608 .arg(&vtb);
4609 unsafe {
4610 b.launch(cfg)?;
4611 }
4612 Ok(())
4613}
4614
4615#[allow(clippy::too_many_arguments)]
4618fn launch_q4e_idx_append_q8(
4619 e: &Engine,
4620 src: &CudaSlice<f32>,
4621 dst: &mut CudaSlice<u8>,
4622 rows: usize,
4623 width: usize,
4624 src_stride: usize,
4625 src_col: usize,
4626 dst_row: usize,
4627) -> Res<()> {
4628 let f = e.func("q4e_idx_append_q8");
4629 let cfg = LaunchConfig {
4630 grid_dim: (width.div_ceil(32) as u32, rows as u32, 1),
4631 block_dim: (32, 1, 1),
4632 shared_mem_bytes: 0,
4633 };
4634 let (r, w) = (rows as i32, width as i32);
4635 let (ss, sc, dr) = (src_stride as i64, src_col as i64, dst_row as i64);
4636 let stream = e.gpu.stream();
4637 let mut b = stream.launch_builder(&f);
4638 b.arg(src)
4639 .arg(dst)
4640 .arg(&r)
4641 .arg(&w)
4642 .arg(&ss)
4643 .arg(&sc)
4644 .arg(&dr);
4645 unsafe {
4646 b.launch(cfg)?;
4647 }
4648 Ok(())
4649}
4650
4651#[allow(clippy::too_many_arguments)]
4653fn launch_q4e_idx_append_bf16(
4654 e: &Engine,
4655 src: &CudaSlice<f32>,
4656 dst: &mut CudaSlice<u16>,
4657 rows: usize,
4658 width: usize,
4659 src_stride: usize,
4660 src_col: usize,
4661 dst_row: usize,
4662) -> Res<()> {
4663 let f = e.func("q4e_idx_append_bf16");
4664 let total = rows * width;
4665 let cfg = LaunchConfig {
4666 grid_dim: (total.div_ceil(256) as u32, 1, 1),
4667 block_dim: (256, 1, 1),
4668 shared_mem_bytes: 0,
4669 };
4670 let (r, w) = (rows as i32, width as i32);
4671 let (ss, sc, dr) = (src_stride as i64, src_col as i64, dst_row as i64);
4672 let stream = e.gpu.stream();
4673 let mut b = stream.launch_builder(&f);
4674 b.arg(src)
4675 .arg(dst)
4676 .arg(&r)
4677 .arg(&w)
4678 .arg(&ss)
4679 .arg(&sc)
4680 .arg(&dr);
4681 unsafe {
4682 b.launch(cfg)?;
4683 }
4684 Ok(())
4685}
4686
4687#[allow(clippy::too_many_arguments)]
4688fn launch_gdn_scan(
4689 e: &Engine,
4690 qkv: &CudaSlice<f32>,
4691 g_log: &CudaSlice<f32>,
4692 beta_raw: &CudaSlice<f32>,
4693 state: &mut CudaSlice<f32>,
4694 o: &mut CudaSlice<f32>,
4695 nk: usize,
4696 nv: usize,
4697 hk: usize,
4698 hv: usize,
4699 t: usize,
4700 scale: f32,
4701 eps: f32,
4702) -> Res<()> {
4703 if hk > 128 {
4704 return Err("gdn_scan_naive_f32: hk > 128".into());
4705 }
4706 let f = e.func("gdn_scan_naive_f32");
4707 let cfg = LaunchConfig {
4708 grid_dim: (nv as u32, 1, 1),
4709 block_dim: (hv as u32, 1, 1),
4710 shared_mem_bytes: ((2 * hk + 2) * 4) as u32,
4711 };
4712 let (nki, nvi, hki, hvi, ti) = (nk as i32, nv as i32, hk as i32, hv as i32, t as i32);
4713 let stream = e.gpu.stream();
4714 let mut b = stream.launch_builder(&f);
4715 b.arg(qkv)
4716 .arg(g_log)
4717 .arg(beta_raw)
4718 .arg(state)
4719 .arg(o)
4720 .arg(&nki)
4721 .arg(&nvi)
4722 .arg(&hki)
4723 .arg(&hvi)
4724 .arg(&ti)
4725 .arg(&scale)
4726 .arg(&eps);
4727 unsafe {
4728 b.launch(cfg)?;
4729 }
4730 Ok(())
4731}
4732
4733#[allow(clippy::too_many_arguments)]
4737fn launch_gdn_scan_step(
4738 e: &Engine,
4739 qkv: &CudaSlice<f32>,
4740 g_log: &CudaSlice<f32>,
4741 beta_raw: &CudaSlice<f32>,
4742 state: &mut CudaSlice<f32>,
4743 o: &mut CudaSlice<f32>,
4744 nk: usize,
4745 nv: usize,
4746 hk: usize,
4747 hv: usize,
4748 scale: f32,
4749 eps: f32,
4750) -> Res<()> {
4751 if hk % 32 != 0 || hk > 1024 {
4752 return Err("gdn_scan_step_f32: hk must be a multiple of 32 and <= 1024".into());
4753 }
4754 let f = e.func("gdn_scan_step_f32");
4755 let cfg = LaunchConfig {
4756 grid_dim: (nv as u32, hv as u32, 1),
4757 block_dim: (hk as u32, 1, 1),
4758 shared_mem_bytes: 0,
4759 };
4760 let (nki, nvi, hki, hvi) = (nk as i32, nv as i32, hk as i32, hv as i32);
4761 let stream = e.gpu.stream();
4762 let mut b = stream.launch_builder(&f);
4763 b.arg(qkv)
4764 .arg(g_log)
4765 .arg(beta_raw)
4766 .arg(state)
4767 .arg(o)
4768 .arg(&nki)
4769 .arg(&nvi)
4770 .arg(&hki)
4771 .arg(&hvi)
4772 .arg(&scale)
4773 .arg(&eps);
4774 unsafe {
4775 b.launch(cfg)?;
4776 }
4777 Ok(())
4778}
4779
4780#[allow(clippy::too_many_arguments)]
4784fn launch_gdn_scan_step_at(
4785 e: &Engine,
4786 conv_out: &CudaSlice<f32>,
4787 g_log: &CudaSlice<f32>,
4788 beta_raw: &CudaSlice<f32>,
4789 state: &mut CudaSlice<f32>,
4790 o: &mut CudaSlice<f32>,
4791 tok: usize,
4792 nk: usize,
4793 nv: usize,
4794 hk: usize,
4795 hv: usize,
4796 scale: f32,
4797 eps: f32,
4798) -> Res<()> {
4799 if hk % 32 != 0 || hk > 1024 {
4800 return Err("gdn_scan_step_f32: hk must be a multiple of 32 and <= 1024".into());
4801 }
4802 let conv_dim = 2 * nk * hk + nv * hv;
4803 let qv = conv_out.slice(tok * conv_dim..(tok + 1) * conv_dim);
4804 let gv = g_log.slice(tok * nv..(tok + 1) * nv);
4805 let bv = beta_raw.slice(tok * nv..(tok + 1) * nv);
4806 let mut ov = o.slice_mut(tok * nv * hv..(tok + 1) * nv * hv);
4807 let f = e.func("gdn_scan_step_f32");
4808 let cfg = LaunchConfig {
4809 grid_dim: (nv as u32, hv as u32, 1),
4810 block_dim: (hk as u32, 1, 1),
4811 shared_mem_bytes: 0,
4812 };
4813 let (nki, nvi, hki, hvi) = (nk as i32, nv as i32, hk as i32, hv as i32);
4814 let stream = e.gpu.stream();
4815 let mut b = stream.launch_builder(&f);
4816 b.arg(&qv)
4817 .arg(&gv)
4818 .arg(&bv)
4819 .arg(&mut *state)
4820 .arg(&mut ov)
4821 .arg(&nki)
4822 .arg(&nvi)
4823 .arg(&hki)
4824 .arg(&hvi)
4825 .arg(&scale)
4826 .arg(&eps);
4827 unsafe {
4828 b.launch(cfg)?;
4829 }
4830 Ok(())
4831}
4832
4833#[allow(clippy::too_many_arguments)]
4837fn launch_gdn_scan_at(
4838 e: &Engine,
4839 conv_out: &CudaSlice<f32>,
4840 g_log: &CudaSlice<f32>,
4841 beta_raw: &CudaSlice<f32>,
4842 state: &mut CudaSlice<f32>,
4843 o: &mut CudaSlice<f32>,
4844 tok: usize,
4845 nk: usize,
4846 nv: usize,
4847 hk: usize,
4848 hv: usize,
4849 scale: f32,
4850 eps: f32,
4851) -> Res<()> {
4852 if hk > 128 {
4853 return Err("gdn_scan_naive_f32: hk > 128".into());
4854 }
4855 let conv_dim = 2 * nk * hk + nv * hv;
4856 let qv = conv_out.slice(tok * conv_dim..(tok + 1) * conv_dim);
4857 let gv = g_log.slice(tok * nv..(tok + 1) * nv);
4858 let bv = beta_raw.slice(tok * nv..(tok + 1) * nv);
4859 let mut ov = o.slice_mut(tok * nv * hv..(tok + 1) * nv * hv);
4860 let f = e.func("gdn_scan_naive_f32");
4861 let cfg = LaunchConfig {
4862 grid_dim: (nv as u32, 1, 1),
4863 block_dim: (hv as u32, 1, 1),
4864 shared_mem_bytes: ((2 * hk + 2) * 4) as u32,
4865 };
4866 let (nki, nvi, hki, hvi, ti) = (nk as i32, nv as i32, hk as i32, hv as i32, 1i32);
4867 let stream = e.gpu.stream();
4868 let mut b = stream.launch_builder(&f);
4869 b.arg(&qv)
4870 .arg(&gv)
4871 .arg(&bv)
4872 .arg(&mut *state)
4873 .arg(&mut ov)
4874 .arg(&nki)
4875 .arg(&nvi)
4876 .arg(&hki)
4877 .arg(&hvi)
4878 .arg(&ti)
4879 .arg(&scale)
4880 .arg(&eps);
4881 unsafe {
4882 b.launch(cfg)?;
4883 }
4884 Ok(())
4885}
4886
4887#[allow(clippy::too_many_arguments)]
4891fn launch_rms_sigmul(
4892 e: &Engine,
4893 x: &CudaSlice<f32>,
4894 w: &CudaSlice<f32>,
4895 z: &CudaSlice<f32>,
4896 dst: &mut CudaSlice<f32>,
4897 ncols: usize,
4898 nrows: usize,
4899 eps: f32,
4900) -> Res<()> {
4901 let f = e.func("rms_sigmul_f32");
4902 let cfg = LaunchConfig {
4903 grid_dim: (nrows as u32, 1, 1),
4904 block_dim: (crate::rms_block(), 1, 1),
4905 shared_mem_bytes: 0,
4906 };
4907 let (nc, ep) = (ncols as i32, eps);
4908 let stream = e.gpu.stream();
4909 let mut b = stream.launch_builder(&f);
4910 b.arg(x).arg(w).arg(z).arg(dst).arg(&nc).arg(&ep);
4911 unsafe {
4912 b.launch(cfg)?;
4913 }
4914 Ok(())
4915}
4916
4917#[allow(clippy::too_many_arguments)]
4918fn launch_dwconv(
4919 e: &Engine,
4920 x: &CudaSlice<f32>,
4921 hist: &CudaSlice<f32>,
4922 w: &CudaSlice<f32>,
4923 y: &mut CudaSlice<f32>,
4924 t: usize,
4925 th: usize,
4926 c: usize,
4927 k: usize,
4928 dilation: usize,
4929 mode: i32,
4930) -> Res<()> {
4931 let f = e.func("dwconv_causal_f32");
4932 let cfg = LaunchConfig::for_num_elems((t * c) as u32);
4933 let (ti, thi, ci, ki, di) = (t as i32, th as i32, c as i32, k as i32, dilation as i32);
4934 let stream = e.gpu.stream();
4935 let mut b = stream.launch_builder(&f);
4936 b.arg(x)
4937 .arg(hist)
4938 .arg(w)
4939 .arg(y)
4940 .arg(&ti)
4941 .arg(&thi)
4942 .arg(&ci)
4943 .arg(&ki)
4944 .arg(&di)
4945 .arg(&mode);
4946 unsafe {
4947 b.launch(cfg)?;
4948 }
4949 Ok(())
4950}
4951
4952#[allow(clippy::too_many_arguments)]
4954fn run_routed_expert(
4955 e: &Engine,
4956 xg: &CudaSlice<f32>,
4957 gate: &CudaView<'_, f32>,
4958 up: &CudaView<'_, f32>,
4959 down: &CudaView<'_, f32>,
4960 m_e: usize,
4961 hidden: usize,
4962 ff: usize,
4963) -> Res<CudaSlice<f32>> {
4964 let xg_view = xg.slice(0..m_e * hidden);
4965 let mut gate_out = e.uninit(m_e * ff)?;
4966 e.linear_device_into(&xg_view, gate, &mut gate_out, m_e, hidden, ff)?;
4967 let mut up_out = e.uninit(m_e * ff)?;
4968 e.linear_device_into(&xg_view, up, &mut up_out, m_e, hidden, ff)?;
4969 let mut act = e.uninit(m_e * ff)?;
4970 e.silu_mul(&gate_out, &up_out, &mut act, m_e * ff)?;
4971 let mut down_out = e.uninit(m_e * hidden)?;
4972 e.linear_device_into(
4973 &act.slice(0..m_e * ff),
4974 down,
4975 &mut down_out,
4976 m_e,
4977 ff,
4978 hidden,
4979 )?;
4980 Ok(down_out)
4981}
4982
4983fn launch_rms_norm_into_view(
4989 e: &Engine,
4990 x: &CudaSlice<f32>,
4991 w: &CudaSlice<f32>,
4992 dst: &mut cudarc::driver::CudaViewMut<'_, f32>,
4993 ncols: usize,
4994 nrows: usize,
4995 eps: f32,
4996) -> Res<()> {
4997 let kname = if Engine::norm_ilp_on() {
4998 "rms_norm_f32_v2"
4999 } else {
5000 "rms_norm_f32"
5001 };
5002 let f = e.func(kname);
5003 let cfg = LaunchConfig {
5004 grid_dim: (nrows as u32, 1, 1),
5005 block_dim: (crate::rms_block(), 1, 1),
5006 shared_mem_bytes: 0,
5007 };
5008 let (nc, ep) = (ncols as i32, eps);
5009 let stream = e.gpu.stream();
5010 let mut b = stream.launch_builder(&f);
5011 b.arg(x).arg(w).arg(dst).arg(&nc).arg(&ep);
5012 unsafe {
5013 b.launch(cfg)?;
5014 }
5015 Ok(())
5016}
5017
5018fn launch_hc_lowrank_reduce(
5020 e: &Engine,
5021 parts: &CudaSlice<f32>,
5022 low_act: &mut CudaSlice<f32>,
5023 streams: usize,
5024 t: usize,
5025 rank: usize,
5026) -> Res<()> {
5027 let f = e.func("hc_lowrank_reduce_f32");
5028 let cfg = LaunchConfig::for_num_elems((t * rank) as u32);
5029 let (si, ti, ri) = (streams as i32, t as i32, rank as i32);
5030 let inv = 1.0f32 / streams as f32;
5031 let stream = e.gpu.stream();
5032 let mut b = stream.launch_builder(&f);
5033 b.arg(parts)
5034 .arg(low_act)
5035 .arg(&si)
5036 .arg(&ti)
5037 .arg(&ri)
5038 .arg(&inv);
5039 unsafe {
5040 b.launch(cfg)?;
5041 }
5042 Ok(())
5043}
5044
5045fn launch_hc_mix_epilogue(
5047 e: &Engine,
5048 gates: &CudaSlice<f32>,
5049 normed: &CudaSlice<f32>,
5050 mixed: &mut CudaSlice<f32>,
5051 streams: usize,
5052 t: usize,
5053 hidden: usize,
5054) -> Res<()> {
5055 let f = e.func("hc_mix_epilogue_f32");
5056 let cfg = LaunchConfig::for_num_elems((t * hidden) as u32);
5057 let (si, ti, hi) = (streams as i32, t as i32, hidden as i32);
5058 let inv = 1.0f32 / streams as f32;
5059 let stream = e.gpu.stream();
5060 let mut b = stream.launch_builder(&f);
5061 b.arg(gates)
5062 .arg(normed)
5063 .arg(mixed)
5064 .arg(&si)
5065 .arg(&ti)
5066 .arg(&hi)
5067 .arg(&inv);
5068 unsafe {
5069 b.launch(cfg)?;
5070 }
5071 Ok(())
5072}
5073
5074fn launch_hc_inject_gates(
5076 e: &Engine,
5077 normed: &CudaSlice<f32>,
5078 w: &CudaSlice<f32>,
5079 out: &mut CudaSlice<f32>,
5080 streams: usize,
5081 t: usize,
5082 hidden: usize,
5083) -> Res<()> {
5084 let f = e.func("hc_inject_gates_f32");
5085 let cfg = LaunchConfig {
5086 grid_dim: (streams as u32, t as u32, 1),
5087 block_dim: (256, 1, 1),
5088 shared_mem_bytes: 0,
5089 };
5090 let (si, ti, hi) = (streams as i32, t as i32, hidden as i32);
5091 let inv = 1.0f32 / streams as f32;
5092 let stream = e.gpu.stream();
5093 let mut b = stream.launch_builder(&f);
5094 b.arg(normed)
5095 .arg(w)
5096 .arg(out)
5097 .arg(&si)
5098 .arg(&ti)
5099 .arg(&hi)
5100 .arg(&inv);
5101 unsafe {
5102 b.launch(cfg)?;
5103 }
5104 Ok(())
5105}
5106
5107enum InjectOut {
5111 Rows(Vec<CudaSlice<f32>>),
5112 Slab(CudaSlice<f32>),
5113}
5114
5115fn put_inject(ws: &mut StepPool, inject: InjectOut) {
5118 match inject {
5119 InjectOut::Rows(rows) => {
5120 for (s, row) in rows.into_iter().enumerate() {
5121 ws.put_f32(INJECT_SLOTS[s], row);
5122 }
5123 }
5124 InjectOut::Slab(slab) => ws.put_f32("hc.inj_all", slab),
5125 }
5126}
5127
5128fn take_inject(e: &Engine, ws: &mut StepPool, streams: usize, t: usize) -> Res<InjectOut> {
5131 if micro_inj_on() && hc_fused_gate_on() {
5134 Ok(InjectOut::Slab(ws.take_f32(
5135 e,
5136 "hc.inj_all",
5137 streams * t,
5138 0,
5139 )?))
5140 } else {
5141 let mut rows = Vec::with_capacity(streams);
5142 for s in 0..streams {
5143 rows.push(ws.take_f32(e, INJECT_SLOTS[s], t, 0)?);
5144 }
5145 Ok(InjectOut::Rows(rows))
5146 }
5147}
5148
5149#[allow(clippy::too_many_arguments)]
5152fn launch_hc_norm_planes(
5153 e: &Engine,
5154 ptrs: &CudaSlice<u64>,
5155 w_stack: &CudaSlice<f32>,
5156 dst: &mut CudaSlice<f32>,
5157 hidden: usize,
5158 t: usize,
5159 streams: usize,
5160 eps: f32,
5161) -> Res<()> {
5162 let f = e.func("hc_norm_planes_f32");
5163 let cfg = LaunchConfig {
5164 grid_dim: (t as u32, streams as u32, 1),
5165 block_dim: (256, 1, 1),
5166 shared_mem_bytes: 0,
5167 };
5168 let (hi, ti) = (hidden as i32, t as i32);
5169 let stream = e.gpu.stream();
5170 let mut b = stream.launch_builder(&f);
5171 b.arg(ptrs)
5172 .arg(w_stack)
5173 .arg(dst)
5174 .arg(&hi)
5175 .arg(&ti)
5176 .arg(&eps);
5177 unsafe {
5178 b.launch(cfg)?;
5179 }
5180 Ok(())
5181}
5182
5183#[allow(clippy::too_many_arguments)]
5187fn launch_hc_inject_two_stage(
5188 e: &Engine,
5189 normed: &CudaSlice<f32>,
5190 w_f32: &CudaSlice<f32>,
5191 w_b16: Option<&CudaSlice<u8>>,
5192 partials: &mut CudaSlice<f32>,
5193 out: &mut CudaSlice<f32>,
5194 streams: usize,
5195 t: usize,
5196 hidden: usize,
5197 chunks: usize,
5198) -> Res<()> {
5199 let cfg = LaunchConfig {
5200 grid_dim: (streams as u32, t as u32, chunks as u32),
5201 block_dim: (256, 1, 1),
5202 shared_mem_bytes: 0,
5203 };
5204 let (si, ti, hi, ci) = (streams as i32, t as i32, hidden as i32, chunks as i32);
5205 let stream = e.gpu.stream();
5206 if let Some(w) = w_b16 {
5207 let f = e.func("hc_inject_partials_bf16w_f32");
5208 let mut b = stream.launch_builder(&f);
5209 b.arg(normed)
5210 .arg(w)
5211 .arg(&mut *partials)
5212 .arg(&si)
5213 .arg(&ti)
5214 .arg(&hi)
5215 .arg(&ci);
5216 unsafe {
5217 b.launch(cfg)?;
5218 }
5219 } else {
5220 let f = e.func("hc_inject_partials_f32");
5221 let mut b = stream.launch_builder(&f);
5222 b.arg(normed)
5223 .arg(w_f32)
5224 .arg(&mut *partials)
5225 .arg(&si)
5226 .arg(&ti)
5227 .arg(&hi)
5228 .arg(&ci);
5229 unsafe {
5230 b.launch(cfg)?;
5231 }
5232 }
5233 let rows = (streams * t) as i32;
5234 let inv = 1.0f32 / streams as f32;
5235 let f = e.func("hc_inject_reduce_f32");
5236 let cfg = LaunchConfig::for_num_elems((streams * t) as u32);
5237 let mut b = stream.launch_builder(&f);
5238 b.arg(&*partials).arg(out).arg(&rows).arg(&ci).arg(&inv);
5239 unsafe {
5240 b.launch(cfg)?;
5241 }
5242 Ok(())
5243}
5244
5245#[allow(clippy::too_many_arguments)]
5249fn launch_hc_diet_stage1(
5250 e: &Engine,
5251 ptrs: &CudaSlice<u64>,
5252 nw_stack: &CudaSlice<f32>,
5253 wdown_b16: &CudaSlice<u8>,
5254 winj_b16: Option<&CudaSlice<u8>>,
5255 parts: &mut CudaSlice<f32>,
5256 inj_parts: &mut CudaSlice<f32>,
5257 inv_out: &mut CudaSlice<f32>,
5258 hidden: usize,
5259 rank: usize,
5260 streams: usize,
5261 t: usize,
5262 eps: f32,
5263) -> Res<()> {
5264 if hidden % 8 != 0 {
5265 return Err("hc_diet_stage1_f32: hidden % 8 != 0".into());
5266 }
5267 let n_inj = if winj_b16.is_some() { streams } else { 0 };
5268 const ROWS_PB: usize = 4;
5269 let total_rows = rank + n_inj;
5270 if parts.len() < t * streams * rank
5271 || (n_inj > 0 && inj_parts.len() < t * n_inj * streams)
5272 || inv_out.len() < t * streams
5273 {
5274 return Err("hc_diet_stage1_f32: output buffers too short".into());
5275 }
5276 let f = e.func("hc_diet_stage1_f32");
5277 let cfg = LaunchConfig {
5278 grid_dim: (
5279 total_rows.div_ceil(ROWS_PB) as u32,
5280 t as u32,
5281 streams as u32,
5282 ),
5283 block_dim: (256, 1, 1),
5284 shared_mem_bytes: (hidden * 4) as u32,
5285 };
5286 let (hi, ri, si, nji, rpb) = (
5287 hidden as i32,
5288 rank as i32,
5289 streams as i32,
5290 n_inj as i32,
5291 ROWS_PB as i32,
5292 );
5293 let winj = winj_b16.unwrap_or(wdown_b16); let stream = e.gpu.stream();
5295 let mut b = stream.launch_builder(&f);
5296 b.arg(ptrs)
5297 .arg(nw_stack)
5298 .arg(wdown_b16)
5299 .arg(winj)
5300 .arg(&mut *parts)
5301 .arg(&mut *inj_parts)
5302 .arg(&mut *inv_out)
5303 .arg(&hi)
5304 .arg(&ri)
5305 .arg(&si)
5306 .arg(&nji)
5307 .arg(&rpb)
5308 .arg(&eps);
5309 unsafe {
5310 b.launch(cfg)?;
5311 }
5312 Ok(())
5313}
5314
5315#[allow(clippy::too_many_arguments)]
5318fn launch_hc_diet_stage2(
5319 e: &Engine,
5320 parts: &CudaSlice<f32>,
5321 inj_parts: &CudaSlice<f32>,
5322 low_act: &mut CudaSlice<f32>,
5323 inj_all: &mut CudaSlice<f32>,
5324 rank: usize,
5325 streams: usize,
5326 t: usize,
5327 with_inject: bool,
5328) -> Res<()> {
5329 let n_inj = if with_inject { streams } else { 0 };
5330 if low_act.len() < t * rank || (n_inj > 0 && inj_all.len() < n_inj * t) {
5331 return Err("hc_diet_stage2_f32: output buffers too short".into());
5332 }
5333 let f = e.func("hc_diet_stage2_f32");
5334 let cfg = LaunchConfig {
5335 grid_dim: (((rank + n_inj) as u32).div_ceil(256), t as u32, 1),
5336 block_dim: (256, 1, 1),
5337 shared_mem_bytes: 0,
5338 };
5339 let (ri, si, nji, ti) = (rank as i32, streams as i32, n_inj as i32, t as i32);
5340 let inv = 1.0f32 / streams as f32;
5341 let stream = e.gpu.stream();
5342 let mut b = stream.launch_builder(&f);
5343 b.arg(parts)
5344 .arg(inj_parts)
5345 .arg(&mut *low_act)
5346 .arg(&mut *inj_all)
5347 .arg(&ri)
5348 .arg(&si)
5349 .arg(&nji)
5350 .arg(&ti)
5351 .arg(&inv);
5352 unsafe {
5353 b.launch(cfg)?;
5354 }
5355 Ok(())
5356}
5357
5358#[allow(clippy::too_many_arguments)]
5361fn launch_hc_diet_stage3(
5362 e: &Engine,
5363 ptrs: &CudaSlice<u64>,
5364 nw_stack: &CudaSlice<f32>,
5365 inv_in: &CudaSlice<f32>,
5366 wup_b16: &CudaSlice<u8>,
5367 low_act: &CudaSlice<f32>,
5368 mixed: &mut CudaSlice<f32>,
5369 hidden: usize,
5370 rank: usize,
5371 streams: usize,
5372 t: usize,
5373) -> Res<()> {
5374 const DIMS_PB: usize = 8;
5375 if mixed.len() < t * hidden {
5376 return Err("hc_diet_stage3_f32: output buffer too short".into());
5377 }
5378 let f = e.func("hc_diet_stage3_f32");
5379 let cfg = LaunchConfig {
5380 grid_dim: (hidden.div_ceil(DIMS_PB) as u32, t as u32, 1),
5381 block_dim: (256, 1, 1),
5382 shared_mem_bytes: ((rank + DIMS_PB * streams) * 4) as u32,
5383 };
5384 let (hi, ri, si, dpb) = (hidden as i32, rank as i32, streams as i32, DIMS_PB as i32);
5385 let inv_streams = 1.0f32 / streams as f32;
5386 let stream = e.gpu.stream();
5387 let mut b = stream.launch_builder(&f);
5388 b.arg(ptrs)
5389 .arg(nw_stack)
5390 .arg(inv_in)
5391 .arg(wup_b16)
5392 .arg(low_act)
5393 .arg(&mut *mixed)
5394 .arg(&hi)
5395 .arg(&ri)
5396 .arg(&si)
5397 .arg(&dpb)
5398 .arg(&inv_streams);
5399 unsafe {
5400 b.launch(cfg)?;
5401 }
5402 Ok(())
5403}
5404
5405fn launch_hc_diet_stage0_mt(
5408 e: &Engine,
5409 ptrs: &CudaSlice<u64>,
5410 inv_out: &mut CudaSlice<f32>,
5411 hidden: usize,
5412 streams: usize,
5413 t: usize,
5414 eps: f32,
5415) -> Res<()> {
5416 if inv_out.len() < t * streams {
5417 return Err("hc_diet_stage0_mt_f32: inv buffer too short".into());
5418 }
5419 let f = e.func("hc_diet_stage0_mt_f32");
5420 let cfg = LaunchConfig {
5421 grid_dim: (t as u32, streams as u32, 1),
5422 block_dim: (256, 1, 1),
5423 shared_mem_bytes: 0,
5424 };
5425 let (hi, si, ti) = (hidden as i32, streams as i32, t as i32);
5426 let stream = e.gpu.stream();
5427 let mut b = stream.launch_builder(&f);
5428 b.arg(ptrs)
5429 .arg(&mut *inv_out)
5430 .arg(&hi)
5431 .arg(&si)
5432 .arg(&ti)
5433 .arg(&eps);
5434 unsafe {
5435 b.launch(cfg)?;
5436 }
5437 Ok(())
5438}
5439
5440#[allow(clippy::too_many_arguments)]
5443fn launch_hc_diet_stage1_mt(
5444 e: &Engine,
5445 ptrs: &CudaSlice<u64>,
5446 nw_stack: &CudaSlice<f32>,
5447 inv_in: &CudaSlice<f32>,
5448 wdown_b16: &CudaSlice<u8>,
5449 winj_b16: Option<&CudaSlice<u8>>,
5450 parts: &mut CudaSlice<f32>,
5451 inj_parts: &mut CudaSlice<f32>,
5452 hidden: usize,
5453 rank: usize,
5454 streams: usize,
5455 t: usize,
5456) -> Res<()> {
5457 if hidden % 8 != 0 || !(2..=12).contains(&t) {
5458 return Err("hc_diet_stage1_mt_f32: geometry".into());
5459 }
5460 let n_inj = if winj_b16.is_some() { streams } else { 0 };
5461 const ROWS_PB: usize = 4;
5462 let total_rows = rank + n_inj;
5463 if parts.len() < t * streams * rank || (n_inj > 0 && inj_parts.len() < t * n_inj * streams) {
5464 return Err("hc_diet_stage1_mt_f32: output buffers too short".into());
5465 }
5466 let f = e.func("hc_diet_stage1_mt_f32");
5467 let cfg = LaunchConfig {
5468 grid_dim: (total_rows.div_ceil(ROWS_PB) as u32, 1, streams as u32),
5469 block_dim: (256, 1, 1),
5470 shared_mem_bytes: 0,
5471 };
5472 let (hi, ri, si, nji, rpb, ti) = (
5473 hidden as i32,
5474 rank as i32,
5475 streams as i32,
5476 n_inj as i32,
5477 ROWS_PB as i32,
5478 t as i32,
5479 );
5480 let winj = winj_b16.unwrap_or(wdown_b16);
5481 let stream = e.gpu.stream();
5482 let mut b = stream.launch_builder(&f);
5483 b.arg(ptrs)
5484 .arg(nw_stack)
5485 .arg(inv_in)
5486 .arg(wdown_b16)
5487 .arg(winj)
5488 .arg(&mut *parts)
5489 .arg(&mut *inj_parts)
5490 .arg(&hi)
5491 .arg(&ri)
5492 .arg(&si)
5493 .arg(&nji)
5494 .arg(&rpb)
5495 .arg(&ti);
5496 unsafe {
5497 b.launch(cfg)?;
5498 }
5499 Ok(())
5500}
5501
5502#[allow(clippy::too_many_arguments)]
5504fn launch_hc_diet_stage3_mt(
5505 e: &Engine,
5506 ptrs: &CudaSlice<u64>,
5507 nw_stack: &CudaSlice<f32>,
5508 inv_in: &CudaSlice<f32>,
5509 wup_b16: &CudaSlice<u8>,
5510 low_act: &CudaSlice<f32>,
5511 mixed: &mut CudaSlice<f32>,
5512 hidden: usize,
5513 rank: usize,
5514 streams: usize,
5515 t: usize,
5516) -> Res<()> {
5517 const DIMS_PB: usize = 8;
5518 if !(2..=12).contains(&t) || mixed.len() < t * hidden {
5519 return Err("hc_diet_stage3_mt_f32: geometry".into());
5520 }
5521 let smem = ((t * rank + DIMS_PB * streams * t) * 4) as u32;
5522 if smem > 96 * 1024 {
5523 return Err("hc_diet_stage3_mt_f32: smem over budget".into());
5524 }
5525 let f = e.func("hc_diet_stage3_mt_f32");
5526 let cfg = LaunchConfig {
5527 grid_dim: (hidden.div_ceil(DIMS_PB) as u32, 1, 1),
5528 block_dim: (256, 1, 1),
5529 shared_mem_bytes: smem,
5530 };
5531 let (hi, ri, si, dpb, ti) = (
5532 hidden as i32,
5533 rank as i32,
5534 streams as i32,
5535 DIMS_PB as i32,
5536 t as i32,
5537 );
5538 let inv_streams = 1.0f32 / streams as f32;
5539 let stream = e.gpu.stream();
5540 let mut b = stream.launch_builder(&f);
5541 b.arg(ptrs)
5542 .arg(nw_stack)
5543 .arg(inv_in)
5544 .arg(wup_b16)
5545 .arg(low_act)
5546 .arg(&mut *mixed)
5547 .arg(&hi)
5548 .arg(&ri)
5549 .arg(&si)
5550 .arg(&dpb)
5551 .arg(&ti)
5552 .arg(&inv_streams);
5553 unsafe {
5554 b.launch(cfg)?;
5555 }
5556 Ok(())
5557}
5558
5559fn launch_hc_write_planes(
5562 e: &Engine,
5563 ptrs: &CudaSlice<u64>,
5564 block_out: &CudaSlice<f32>,
5565 inj: &CudaSlice<f32>,
5566 hidden: usize,
5567 t: usize,
5568 streams: usize,
5569) -> Res<()> {
5570 let f = e.func("hc_write_planes_f32");
5571 let n = (t * hidden) as u32;
5572 let cfg = LaunchConfig {
5573 grid_dim: (n.div_ceil(256), streams as u32, 1),
5574 block_dim: (256, 1, 1),
5575 shared_mem_bytes: 0,
5576 };
5577 let (hi, ti) = (hidden as i32, t as i32);
5578 let stream = e.gpu.stream();
5579 let mut b = stream.launch_builder(&f);
5580 b.arg(ptrs).arg(block_out).arg(inj).arg(&hi).arg(&ti);
5581 unsafe {
5582 b.launch(cfg)?;
5583 }
5584 Ok(())
5585}
5586
5587fn launch_hc_inject_gates_b16(
5591 e: &Engine,
5592 normed: &CudaSlice<f32>,
5593 w: &CudaSlice<u8>,
5594 out: &mut CudaSlice<f32>,
5595 streams: usize,
5596 t: usize,
5597 hidden: usize,
5598) -> Res<()> {
5599 let f = e.func("hc_inject_gates_bf16w_f32");
5600 let cfg = LaunchConfig {
5601 grid_dim: (streams as u32, t as u32, 1),
5602 block_dim: (256, 1, 1),
5603 shared_mem_bytes: 0,
5604 };
5605 let (si, ti, hi) = (streams as i32, t as i32, hidden as i32);
5606 let inv = 1.0f32 / streams as f32;
5607 let stream = e.gpu.stream();
5608 let mut b = stream.launch_builder(&f);
5609 b.arg(normed)
5610 .arg(w)
5611 .arg(out)
5612 .arg(&si)
5613 .arg(&ti)
5614 .arg(&hi)
5615 .arg(&inv);
5616 unsafe {
5617 b.launch(cfg)?;
5618 }
5619 Ok(())
5620}
5621
5622fn bf16_twin(e: &Engine, data: &[f32], in_f: usize) -> Res<Option<CudaSlice<u8>>> {
5628 if in_f % 8 != 0 {
5629 return Ok(None);
5630 }
5631 let mut bytes = Vec::with_capacity(data.len() * 2);
5632 for &v in data {
5633 let bits = v.to_bits();
5634 if bits & 0xFFFF != 0 {
5635 return Ok(None);
5636 }
5637 bytes.extend_from_slice(&((bits >> 16) as u16).to_le_bytes());
5638 }
5639 Ok(Some(e.htod_bytes(&bytes)?))
5640}
5641
5642#[allow(clippy::too_many_arguments)]
5647fn launch_qmatvec_bf16w(
5648 e: &Engine,
5649 w: &CudaSlice<u8>,
5650 x: &CudaSlice<f32>,
5651 y: &mut CudaSlice<f32>,
5652 in_f: usize,
5653 out_f: usize,
5654 t: usize,
5655 batch: usize,
5656 w_bstride: usize,
5657 x_bstride: usize,
5658 x_tstride: usize,
5659 y_bstride: usize,
5660) -> Res<()> {
5661 if in_f % 8 != 0 || x_bstride % 8 != 0 || x_tstride % 8 != 0 {
5662 return Err("qmatvec_bf16w_f32: stride breaks the uint4/float4 vector width".into());
5663 }
5664 if y.len() < (batch - 1) * y_bstride + t * out_f {
5665 return Err("qmatvec_bf16w_f32: output buffer too short".into());
5666 }
5667 let f = e.func("qmatvec_bf16w_f32");
5668 let cfg = LaunchConfig {
5669 grid_dim: (out_f as u32, t as u32, batch as u32),
5670 block_dim: (128, 1, 1),
5671 shared_mem_bytes: 0,
5672 };
5673 let (inf, outf, ti) = (in_f as i32, out_f as i32, t as i32);
5674 let (wb, xb, xt, yb) = (
5675 w_bstride as i64,
5676 x_bstride as i64,
5677 x_tstride as i64,
5678 y_bstride as i64,
5679 );
5680 let stream = e.gpu.stream();
5681 let mut b = stream.launch_builder(&f);
5682 b.arg(w)
5683 .arg(x)
5684 .arg(y)
5685 .arg(&inf)
5686 .arg(&outf)
5687 .arg(&ti)
5688 .arg(&wb)
5689 .arg(&xb)
5690 .arg(&xt)
5691 .arg(&yb);
5692 unsafe {
5693 b.launch(cfg)?;
5694 }
5695 Ok(())
5696}
5697
5698fn bf16_stack_twin(e: &Engine, parts: &[&[f32]], in_f: usize) -> Res<Option<CudaSlice<u8>>> {
5704 let mut cat: Vec<f32> = Vec::with_capacity(parts.iter().map(|p| p.len()).sum());
5705 for p in parts {
5706 cat.extend_from_slice(p);
5707 }
5708 bf16_twin(e, &cat, in_f)
5709}
5710
5711fn need_stack_twin(e: &Engine, parts: &[&[f32]], in_f: usize, what: &str) -> Res<CudaSlice<u8>> {
5713 bf16_stack_twin(e, parts, in_f)?.ok_or_else(|| {
5714 format!("qwen4exp_gpu tp2: {what} has no exact bf16 stack twin (in_f {in_f})").into()
5715 })
5716}
5717
5718#[allow(clippy::too_many_arguments)]
5722fn launch_qmatvec_bf16w_off(
5723 e: &Engine,
5724 w_stack: &CudaSlice<u8>,
5725 row_off: usize,
5726 x: &CudaSlice<f32>,
5727 y: &mut CudaSlice<f32>,
5728 in_f: usize,
5729 out_f: usize,
5730 t: usize,
5731) -> Res<()> {
5732 if in_f % 8 != 0 {
5733 return Err("qmatvec_bf16w_f32: stride breaks the uint4/float4 vector width".into());
5734 }
5735 if y.len() < t * out_f {
5736 return Err("qmatvec_bf16w_f32: output buffer too short".into());
5737 }
5738 let byte_off = row_off * in_f * 2;
5739 if w_stack.len() < byte_off + out_f * in_f * 2 {
5740 return Err("qmatvec_bf16w_f32: stacked twin shorter than the row window".into());
5741 }
5742 let wv = w_stack.slice(byte_off..w_stack.len());
5743 let f = e.func("qmatvec_bf16w_f32");
5744 let cfg = LaunchConfig {
5745 grid_dim: (out_f as u32, t as u32, 1),
5746 block_dim: (128, 1, 1),
5747 shared_mem_bytes: 0,
5748 };
5749 let (inf, outf, ti) = (in_f as i32, out_f as i32, t as i32);
5750 let (wb, xb, xt, yb) = (0i64, 0i64, in_f as i64, 0i64);
5751 let stream = e.gpu.stream();
5752 let mut b = stream.launch_builder(&f);
5753 b.arg(&wv)
5754 .arg(x)
5755 .arg(y)
5756 .arg(&inf)
5757 .arg(&outf)
5758 .arg(&ti)
5759 .arg(&wb)
5760 .arg(&xb)
5761 .arg(&xt)
5762 .arg(&yb);
5763 unsafe {
5764 b.launch(cfg)?;
5765 }
5766 Ok(())
5767}
5768
5769#[allow(clippy::too_many_arguments)]
5775fn launch_qmatvec_bf16w_off_into(
5776 e: &Engine,
5777 w_stack: &CudaSlice<u8>,
5778 w_row_off: usize,
5779 x: &CudaSlice<f32>,
5780 x_off: usize,
5781 y: &mut CudaSlice<f32>,
5782 y_off: usize,
5783 in_f: usize,
5784 out_f: usize,
5785) -> Res<()> {
5786 if in_f % 8 != 0 {
5787 return Err("qmatvec_bf16w_f32: stride breaks the uint4/float4 vector width".into());
5788 }
5789 let byte_off = w_row_off * in_f * 2;
5790 if w_stack.len() < byte_off + out_f * in_f * 2 {
5791 return Err("qmatvec_bf16w_f32: bank shorter than the expert row window".into());
5792 }
5793 if x.len() < x_off + in_f || y.len() < y_off + out_f {
5794 return Err("qmatvec_bf16w_f32: operand views out of range".into());
5795 }
5796 let wv = w_stack.slice(byte_off..w_stack.len());
5797 let xv = x.slice(x_off..x_off + in_f);
5798 let mut yv = y.slice_mut(y_off..y_off + out_f);
5799 let f = e.func("qmatvec_bf16w_f32");
5800 let cfg = LaunchConfig {
5801 grid_dim: (out_f as u32, 1, 1),
5802 block_dim: (128, 1, 1),
5803 shared_mem_bytes: 0,
5804 };
5805 let (inf, outf, ti) = (in_f as i32, out_f as i32, 1i32);
5806 let (wb, xb, xt, yb) = (0i64, 0i64, in_f as i64, 0i64);
5807 let stream = e.gpu.stream();
5808 let mut b = stream.launch_builder(&f);
5809 b.arg(&wv)
5810 .arg(&xv)
5811 .arg(&mut yv)
5812 .arg(&inf)
5813 .arg(&outf)
5814 .arg(&ti)
5815 .arg(&wb)
5816 .arg(&xb)
5817 .arg(&xt)
5818 .arg(&yb);
5819 unsafe {
5820 b.launch(cfg)?;
5821 }
5822 Ok(())
5823}
5824
5825#[allow(clippy::too_many_arguments)]
5831fn launch_qmatvec_bf16w_sel(
5832 e: &Engine,
5833 bank: &CudaSlice<u8>,
5834 sel: &CudaSlice<i32>,
5835 sel_off: usize,
5836 x: &CudaSlice<f32>,
5837 x_off: usize,
5838 x_sstride: usize,
5841 y: &mut CudaSlice<f32>,
5842 n_sel: usize,
5843 in_f: usize,
5844 out_f: usize,
5845) -> Res<()> {
5846 if in_f % 8 != 0 {
5847 return Err("qmatvec_bf16w_sel_f32: stride breaks the uint4/float4 vector width".into());
5848 }
5849 if sel.len() < sel_off + n_sel
5850 || x.len() < x_off + (n_sel - 1) * x_sstride + in_f
5851 || y.len() < n_sel * out_f
5852 || n_sel == 0
5853 {
5854 return Err("qmatvec_bf16w_sel_f32: operand views out of range".into());
5855 }
5856 let sv = sel.slice(sel_off..sel_off + n_sel);
5857 let xv = x.slice(x_off..x.len());
5858 let f = e.func("qmatvec_bf16w_sel_f32");
5859 let cfg = LaunchConfig {
5860 grid_dim: (out_f as u32, 1, n_sel as u32),
5861 block_dim: (128, 1, 1),
5862 shared_mem_bytes: 0,
5863 };
5864 let (inf, outf, ns) = (in_f as i32, out_f as i32, n_sel as i32);
5865 let xs = x_sstride as i64;
5866 let stream = e.gpu.stream();
5867 let mut b = stream.launch_builder(&f);
5868 b.arg(bank)
5869 .arg(&sv)
5870 .arg(&xv)
5871 .arg(&mut *y)
5872 .arg(&inf)
5873 .arg(&outf)
5874 .arg(&ns)
5875 .arg(&xs);
5876 unsafe {
5877 b.launch(cfg)?;
5878 }
5879 Ok(())
5880}
5881
5882#[allow(clippy::too_many_arguments)]
5887fn launch_qmatvec_bf16w_mt(
5888 e: &Engine,
5889 w_stack: &CudaSlice<u8>,
5890 w_row_off: usize,
5891 x: &CudaSlice<f32>,
5892 y: &mut CudaSlice<f32>,
5893 in_f: usize,
5894 out_f: usize,
5895 t: usize,
5896) -> Res<()> {
5897 if in_f % 8 != 0 {
5898 return Err("qmatvec_bf16w_mt_f32: in_f % 8 != 0".into());
5899 }
5900 if !(2..=12).contains(&t) {
5901 return Err("qmatvec_bf16w_mt_f32: t out of range (2..=12)".into());
5902 }
5903 let byte_off = w_row_off * in_f * 2;
5904 if w_stack.len() < byte_off + out_f * in_f * 2 || y.len() < t * out_f || x.len() < t * in_f {
5905 return Err("qmatvec_bf16w_mt_f32: operands out of range".into());
5906 }
5907 let wv = w_stack.slice(byte_off..w_stack.len());
5908 let f = e.func("qmatvec_bf16w_mt_f32");
5909 let cfg = LaunchConfig {
5910 grid_dim: (out_f as u32, 1, 1),
5911 block_dim: (128, 1, 1),
5912 shared_mem_bytes: 0,
5913 };
5914 let (inf, outf, ti) = (in_f as i32, out_f as i32, t as i32);
5915 let (wb, xb, xt, yb) = (0i64, 0i64, in_f as i64, 0i64);
5916 let stream = e.gpu.stream();
5917 let mut b = stream.launch_builder(&f);
5918 b.arg(&wv)
5919 .arg(x)
5920 .arg(y)
5921 .arg(&inf)
5922 .arg(&outf)
5923 .arg(&ti)
5924 .arg(&wb)
5925 .arg(&xb)
5926 .arg(&xt)
5927 .arg(&yb);
5928 unsafe {
5929 b.launch(cfg)?;
5930 }
5931 Ok(())
5932}
5933
5934#[allow(clippy::too_many_arguments)]
5938fn linear_trunk_stacked_into(
5939 e: &Engine,
5940 w_f32: &CudaSlice<f32>,
5941 stack_b16: &Option<CudaSlice<u8>>,
5942 row_off: usize,
5943 x: &CudaSlice<f32>,
5944 y: &mut CudaSlice<f32>,
5945 t: usize,
5946 in_f: usize,
5947 out_f: usize,
5948) -> Res<()> {
5949 if trunk_bf16_on() {
5950 if let Some(w) = stack_b16 {
5951 if (2..=12).contains(&t) && verify_mt_on() {
5952 return launch_qmatvec_bf16w_mt(e, w, row_off, x, y, in_f, out_f, t);
5953 }
5954 return launch_qmatvec_bf16w_off(e, w, row_off, x, y, in_f, out_f, t);
5955 }
5956 }
5957 if w_f32.len() < in_f * out_f {
5958 return Err(
5959 "qwen4exp_gpu: trunk f32 original dropped (trunk_f32_diet) — the bf16 \
5960 twin path is required (keep trunk seams ON)"
5961 .into(),
5962 );
5963 }
5964 e.linear_device_into(x, w_f32, y, t, in_f, out_f)
5965}
5966
5967fn launch_qmatvec_bf16w_multi4(
5972 e: &Engine,
5973 w_stack: &CudaSlice<u8>,
5974 x: &CudaSlice<f32>,
5975 parts: &[(&CudaSlice<f32>, usize)],
5976 in_f: usize,
5977) -> Res<()> {
5978 if in_f % 8 != 0 {
5979 return Err("qmatvec_bf16w_multi4_f32: in_f % 8 != 0".into());
5980 }
5981 if parts.is_empty() || parts.len() > 4 {
5982 return Err("qmatvec_bf16w_multi4_f32: 1..=4 parts".into());
5983 }
5984 let total: usize = parts.iter().map(|&(_, r)| r).sum();
5985 if w_stack.len() < total * in_f * 2 {
5986 return Err("qmatvec_bf16w_multi4_f32: stacked twin shorter than the row plan".into());
5987 }
5988 let stream = e.gpu.stream();
5989 let mut ptrs = [0u64; 4];
5990 let mut rows = [0i32; 4];
5991 for (i, &(buf, r)) in parts.iter().enumerate() {
5992 if buf.len() < r {
5993 return Err("qmatvec_bf16w_multi4_f32: destination shorter than its rows".into());
5994 }
5995 ptrs[i] = buf.device_ptr(&stream).0;
5996 rows[i] = r as i32;
5997 }
5998 let f = e.func("qmatvec_bf16w_multi4_f32");
5999 let cfg = LaunchConfig {
6000 grid_dim: (total as u32, 1, 1),
6001 block_dim: (128, 1, 1),
6002 shared_mem_bytes: 0,
6003 };
6004 let inf = in_f as i32;
6005 let mut b = stream.launch_builder(&f);
6006 b.arg(w_stack)
6007 .arg(x)
6008 .arg(&ptrs[0])
6009 .arg(&rows[0])
6010 .arg(&ptrs[1])
6011 .arg(&rows[1])
6012 .arg(&ptrs[2])
6013 .arg(&rows[2])
6014 .arg(&ptrs[3])
6015 .arg(&rows[3])
6016 .arg(&inf);
6017 unsafe {
6018 b.launch(cfg)?;
6019 }
6020 Ok(())
6021}
6022
6023#[allow(clippy::too_many_arguments)]
6027fn linear_trunk_into(
6028 e: &Engine,
6029 w_f32: &CudaSlice<f32>,
6030 w_b16: &Option<CudaSlice<u8>>,
6031 x: &CudaSlice<f32>,
6032 y: &mut CudaSlice<f32>,
6033 t: usize,
6034 in_f: usize,
6035 out_f: usize,
6036) -> Res<()> {
6037 if trunk_bf16_on() {
6038 if let Some(w) = w_b16 {
6039 if (2..=12).contains(&t) && verify_mt_on() {
6040 return launch_qmatvec_bf16w_mt(e, w, 0, x, y, in_f, out_f, t);
6041 }
6042 return launch_qmatvec_bf16w(e, w, x, y, in_f, out_f, t, 1, 0, 0, in_f, 0);
6043 }
6044 }
6045 if w_f32.len() < in_f * out_f {
6046 return Err(
6047 "qwen4exp_gpu: trunk f32 original dropped (trunk_f32_diet) — the bf16 \
6048 twin path is required (keep trunk seams ON)"
6049 .into(),
6050 );
6051 }
6052 e.linear_device_into(x, w_f32, y, t, in_f, out_f)
6053}
6054
6055#[allow(clippy::too_many_arguments)]
6063fn launch_nvfp4_sel_matvec(
6064 e: &Engine,
6065 codes: &CudaSlice<u8>,
6066 scales: &CudaSlice<u8>,
6067 macros_dev: &CudaSlice<f32>,
6068 sel: &CudaSlice<i32>,
6069 x: &CudaSlice<f32>,
6070 y: &mut CudaSlice<f32>,
6071 n_sel: usize,
6072 in_f: usize,
6073 out_f: usize,
6074 x_stride: usize,
6075) -> Res<()> {
6076 if in_f % 16 != 0 {
6077 return Err("qmatvec_nvfp4_modelopt_sel_f32: in_f % 16 != 0".into());
6078 }
6079 if y.len() < n_sel * out_f {
6080 return Err("qmatvec_nvfp4_modelopt_sel_f32: output shorter than n_sel*out_f".into());
6081 }
6082 let grp = sel_group_resolve(sel_group_dn(), in_f, out_f);
6085 let v3 = grp.is_none() && sel_v3_on() && in_f % 32 == 0 && out_f % 4 == 0;
6086 let v2 = grp.is_none() && !v3 && sel_v2_on() && in_f % 32 == 0 && out_f % 2 == 0;
6087 let f = e.func(if grp.is_some() {
6088 "qmatvec_nvfp4_modelopt_sel_g_f32"
6089 } else if v3 {
6090 "qmatvec_nvfp4_modelopt_sel_f32_v3"
6091 } else if v2 {
6092 "qmatvec_nvfp4_modelopt_sel_f32_v2"
6093 } else {
6094 "qmatvec_nvfp4_modelopt_sel_f32"
6095 });
6096 let grid_x = match grp {
6104 Some((g, rows)) => out_f / ((32 / g) * rows),
6105 None if v3 => out_f / 4,
6106 None if v2 => out_f / 2,
6107 None => out_f,
6108 };
6109 let cfg = LaunchConfig {
6110 grid_dim: (grid_x as u32, n_sel as u32, 1),
6111 block_dim: (32, 1, 1),
6112 shared_mem_bytes: 0,
6113 };
6114 let (inf, outf) = (in_f as i32, out_f as i32);
6115 let xs = x_stride as i64;
6116 let (gi, ri) = grp.map_or((0i32, 0i32), |(g, rows)| (g as i32, rows as i32));
6117 let stream = e.gpu.stream();
6118 let mut b = stream.launch_builder(&f);
6119 b.arg(codes)
6120 .arg(scales)
6121 .arg(macros_dev)
6122 .arg(sel)
6123 .arg(x)
6124 .arg(y)
6125 .arg(&inf)
6126 .arg(&outf)
6127 .arg(&xs);
6128 if grp.is_some() {
6129 b.arg(&gi).arg(&ri);
6130 }
6131 unsafe {
6132 b.launch(cfg)?;
6133 }
6134 Ok(())
6135}
6136
6137#[allow(clippy::too_many_arguments)]
6143fn launch_nvfp4_sel_gu_silu(
6144 e: &Engine,
6145 gate: (&CudaSlice<u8>, &CudaSlice<u8>, &CudaSlice<f32>),
6146 up: (&CudaSlice<u8>, &CudaSlice<u8>, &CudaSlice<f32>),
6147 sel: Option<&CudaSlice<i32>>,
6148 pack_raw: u64,
6149 n_sel: usize,
6150 x: &CudaSlice<f32>,
6151 act: &mut CudaSlice<f32>,
6152 in_f: usize,
6153 ff: usize,
6154 tok: Option<(&CudaSlice<i32>, usize)>,
6157) -> Res<()> {
6158 if in_f % 32 != 0 || ff % 4 != 0 {
6159 return Err("qmatvec_nvfp4_modelopt_sel_gu_silu_f32: geometry".into());
6160 }
6161 if act.len() < n_sel * ff {
6162 return Err("qmatvec_nvfp4_modelopt_sel_gu_silu_f32: act buffer too short".into());
6163 }
6164 if sel.is_none() == (pack_raw == 0) {
6165 return Err("qmatvec_nvfp4_modelopt_sel_gu_silu_f32: exactly one of sel/pack".into());
6166 }
6167 let grp = sel_group_resolve(sel_group_gu(), in_f, ff);
6170 let f = e.func(if grp.is_some() {
6171 "qmatvec_nvfp4_modelopt_sel_gu_silu_g_f32"
6172 } else {
6173 "qmatvec_nvfp4_modelopt_sel_gu_silu_f32"
6174 });
6175 let grid_x = match grp {
6177 Some((g, rows)) => ff / ((32 / g) * rows),
6178 None => ff / 4,
6179 };
6180 let cfg = LaunchConfig {
6181 grid_dim: (grid_x as u32, n_sel as u32, 1),
6182 block_dim: (32, 1, 1),
6183 shared_mem_bytes: 0,
6184 };
6185 let (inf, ffi, ms) = (in_f as i32, ff as i32, n_sel as i32);
6186 let (gi, ri) = grp.map_or((0i32, 0i32), |(g, rows)| (g as i32, rows as i32));
6187 let stream = e.gpu.stream();
6188 let mut b = stream.launch_builder(&f);
6189 b.arg(gate.0)
6190 .arg(gate.1)
6191 .arg(gate.2)
6192 .arg(up.0)
6193 .arg(up.1)
6194 .arg(up.2);
6195 match sel {
6196 Some(s) => {
6197 b.arg(s);
6198 }
6199 None => {
6200 b.arg(gate.2);
6202 }
6203 }
6204 let stream2 = e.gpu.stream();
6205 let (tok_raw, x_tstride) = match tok {
6206 Some((tm, stride)) => (tm.device_ptr(&stream2).0, stride as i64),
6207 None => (0u64, 0i64),
6208 };
6209 b.arg(&pack_raw)
6210 .arg(&ms)
6211 .arg(x)
6212 .arg(&mut *act)
6213 .arg(&inf)
6214 .arg(&ffi)
6215 .arg(&tok_raw)
6216 .arg(&x_tstride);
6217 if grp.is_some() {
6218 b.arg(&gi).arg(&ri);
6219 }
6220 unsafe {
6221 b.launch(cfg)?;
6222 }
6223 Ok(())
6224}
6225
6226#[derive(Debug, Clone, Copy)]
6228pub struct MoeUnionRow {
6229 pub t: usize,
6231 pub slots: usize,
6233 pub union_size: usize,
6235 pub gu_us: f64,
6237 pub down_us: f64,
6239 pub gu_spread_rel: f64,
6244 pub down_spread_rel: f64,
6245}
6246
6247pub fn moe_union_cost_probe(
6284 e: &Engine,
6285 experts: usize,
6286 hidden: usize,
6287 ff: usize,
6288 selected: usize,
6289 t: usize,
6290 reps: usize,
6291) -> Res<Vec<MoeUnionRow>> {
6292 if hidden % 32 != 0 || ff % 4 != 0 {
6293 return Err("moe_union_cost_probe: needs the gufuse geometry (hidden%32, ff%4)".into());
6294 }
6295 if selected == 0 || t == 0 || reps == 0 {
6296 return Err("moe_union_cost_probe: selected/t/reps must be non-zero".into());
6297 }
6298 let code_byte = |i: usize| -> u8 { (i.wrapping_mul(2_654_435_761) >> 13) as u8 };
6301 let scale_byte = |i: usize| -> u8 { 0x38 | ((i.wrapping_mul(40_503) >> 7) & 0x07) as u8 };
6302 let mk = |n: usize, f: &dyn Fn(usize) -> u8| -> Res<CudaSlice<u8>> {
6303 let host: Vec<u8> = (0..n).map(f).collect();
6304 let d = e.htod_bytes(&host)?;
6305 drop(host);
6306 Ok(d)
6307 };
6308 let gu_codes_n = experts * ff * (hidden / 2);
6312 let gu_scales_n = experts * ff * (hidden / 16);
6313 let dn_codes_n = experts * hidden * (ff / 2);
6314 let dn_scales_n = experts * hidden * (ff / 16);
6315 let gc = mk(gu_codes_n, &code_byte)?;
6316 let gs = mk(gu_scales_n, &scale_byte)?;
6317 let uc = mk(gu_codes_n, &|i| code_byte(i ^ 0x5A5A_5A5A))?;
6318 let us = mk(gu_scales_n, &|i| scale_byte(i ^ 0x3C3C_3C3C))?;
6319 let dc = mk(dn_codes_n, &|i| code_byte(i ^ 0x0F0F_0F0F))?;
6320 let ds = mk(dn_scales_n, &|i| scale_byte(i ^ 0x1111_1111))?;
6321 let gm = e.htod(&vec![1.0f32; experts])?;
6322 let um = e.htod(&vec![1.0f32; experts])?;
6323 let dm = e.htod(&vec![1.0f32; experts])?;
6324 let mixed_h: Vec<f32> = (0..t * hidden)
6326 .map(|i| ((i.wrapping_mul(40_503) % 1000) as f32) / 4000.0 - 0.125)
6327 .collect();
6328 let mixed = e.htod(&mixed_h)?;
6329
6330 let pool: Vec<i32> = {
6332 let stride = (experts / (t * selected).max(1)).max(1);
6333 (0..t * selected)
6334 .map(|i| ((i * stride) % experts) as i32)
6335 .collect()
6336 };
6337
6338 let mut rows: Vec<MoeUnionRow> = Vec::new();
6339 let mut cells: Vec<(usize, usize)> = vec![(1, selected)];
6341 for new in 0..=selected {
6342 cells.push((t, selected + (t - 1) * new));
6343 }
6344 struct Cell {
6353 t: usize,
6354 slots: usize,
6355 union_size: usize,
6356 sel: CudaSlice<i32>,
6357 tokm: CudaSlice<i32>,
6358 act: CudaSlice<f32>,
6359 partial: CudaSlice<f32>,
6360 gu: Vec<f64>,
6361 dn: Vec<f64>,
6362 }
6363 let mut built: Vec<Cell> = Vec::with_capacity(cells.len());
6364 for (cells_t, want_union) in cells {
6365 let slots = cells_t * selected;
6366 let new = if cells_t > 1 {
6370 (want_union - selected) / (cells_t - 1)
6371 } else {
6372 0
6373 };
6374 let shared = selected - new;
6375 let mut sel_h: Vec<i32> = Vec::with_capacity(slots);
6376 let mut tok_h: Vec<i32> = Vec::with_capacity(slots);
6377 let mut fresh = selected;
6378 for col in 0..cells_t {
6379 if col == 0 {
6380 sel_h.extend_from_slice(&pool[0..selected]);
6381 } else {
6382 sel_h.extend_from_slice(&pool[0..shared]);
6383 for _ in 0..new {
6384 sel_h.push(pool[fresh % pool.len()]);
6385 fresh += 1;
6386 }
6387 }
6388 for _ in 0..selected {
6389 tok_h.push(col as i32);
6390 }
6391 }
6392 let union_size = {
6393 let mut u: Vec<i32> = sel_h.clone();
6394 u.sort_unstable();
6395 u.dedup();
6396 u.len()
6397 };
6398 built.push(Cell {
6399 t: cells_t,
6400 slots,
6401 union_size,
6402 sel: e.htod_i32(&sel_h)?,
6403 tokm: e.htod_i32(&tok_h)?,
6404 act: e.zeros(slots * ff)?,
6405 partial: e.zeros(slots * hidden)?,
6406 gu: Vec::with_capacity(reps),
6407 dn: Vec::with_capacity(reps),
6408 });
6409 }
6410 for rep in 0..(reps + 1) {
6413 for c in built.iter_mut() {
6414 let tok_arg = if c.t > 1 {
6415 Some((&c.tokm, hidden))
6416 } else {
6417 None
6418 };
6419 e.stream().synchronize()?;
6420 let t0 = std::time::Instant::now();
6421 launch_nvfp4_sel_gu_silu(
6422 e,
6423 (&gc, &gs, &gm),
6424 (&uc, &us, &um),
6425 Some(&c.sel),
6426 0,
6427 c.slots,
6428 &mixed,
6429 &mut c.act,
6430 hidden,
6431 ff,
6432 tok_arg,
6433 )?;
6434 e.stream().synchronize()?;
6435 let t1 = std::time::Instant::now();
6436 launch_nvfp4_sel_matvec(
6437 e,
6438 &dc,
6439 &ds,
6440 &dm,
6441 &c.sel,
6442 &c.act,
6443 &mut c.partial,
6444 c.slots,
6445 ff,
6446 hidden,
6447 ff,
6448 )?;
6449 e.stream().synchronize()?;
6450 let t2 = std::time::Instant::now();
6451 if rep > 0 {
6452 c.gu.push(t1.duration_since(t0).as_secs_f64() * 1e6);
6453 c.dn.push(t2.duration_since(t1).as_secs_f64() * 1e6);
6454 }
6455 }
6456 }
6457 let stat = |v: &[f64]| -> (f64, f64) {
6458 let mut s = v.to_vec();
6459 s.sort_by(|a, b| a.partial_cmp(b).unwrap());
6460 let med = s[s.len() / 2];
6461 let spread = if med > 0.0 {
6464 (s[s.len() - 1] - s[0]) / med
6465 } else {
6466 0.0
6467 };
6468 (med, spread)
6469 };
6470 for c in &built {
6471 let (gu_us, gu_spread) = stat(&c.gu);
6472 let (down_us, down_spread) = stat(&c.dn);
6473 rows.push(MoeUnionRow {
6474 t: c.t,
6475 slots: c.slots,
6476 union_size: c.union_size,
6477 gu_us,
6478 down_us,
6479 gu_spread_rel: gu_spread,
6480 down_spread_rel: down_spread,
6481 });
6482 }
6483 Ok(rows)
6484}
6485
6486#[derive(Debug, Clone)]
6488pub struct SelShapeRow {
6489 pub t: usize,
6491 pub slots: usize,
6493 pub arm: String,
6495 pub gu_shape: String,
6499 pub dn_shape: String,
6500 pub gu_grid_x: usize,
6501 pub dn_grid_x: usize,
6502 pub gu_us: f64,
6503 pub down_us: f64,
6504 pub gu_spread_rel: f64,
6505 pub down_spread_rel: f64,
6506}
6507
6508#[allow(clippy::too_many_arguments)]
6539pub fn sel_shape_cost_probe(
6540 e: &Engine,
6541 experts: usize,
6542 hidden: usize,
6543 ff: usize,
6544 selected: usize,
6545 t: usize,
6546 reps: usize,
6547 arms: &[String],
6548) -> Res<Vec<SelShapeRow>> {
6549 if hidden % 32 != 0 || ff % 4 != 0 {
6550 return Err("sel_shape_cost_probe: needs the gufuse geometry (hidden%32, ff%4)".into());
6551 }
6552 if selected == 0 || t == 0 || reps == 0 || arms.is_empty() {
6553 return Err("sel_shape_cost_probe: selected/t/reps/arms must be non-empty".into());
6554 }
6555 let saved = sel_group_spec();
6556 let out = sel_shape_cost_probe_inner(e, experts, hidden, ff, selected, t, reps, arms);
6557 set_sel_group(&saved);
6558 out
6559}
6560
6561#[allow(clippy::too_many_arguments)]
6562fn sel_shape_cost_probe_inner(
6563 e: &Engine,
6564 experts: usize,
6565 hidden: usize,
6566 ff: usize,
6567 selected: usize,
6568 t: usize,
6569 reps: usize,
6570 arms: &[String],
6571) -> Res<Vec<SelShapeRow>> {
6572 let code_byte = |i: usize| -> u8 { (i.wrapping_mul(2_654_435_761) >> 13) as u8 };
6578 let scale_byte = |i: usize| -> u8 { 0x38 | ((i.wrapping_mul(40_503) >> 7) & 0x07) as u8 };
6579 let mk = |n: usize, f: &dyn Fn(usize) -> u8| -> Res<CudaSlice<u8>> {
6580 let host: Vec<u8> = (0..n).map(f).collect();
6581 let d = e.htod_bytes(&host)?;
6582 drop(host);
6583 Ok(d)
6584 };
6585 let gc = mk(experts * ff * (hidden / 2), &code_byte)?;
6586 let gs = mk(experts * ff * (hidden / 16), &scale_byte)?;
6587 let uc = mk(experts * ff * (hidden / 2), &|i| code_byte(i ^ 0x5A5A_5A5A))?;
6588 let us = mk(experts * ff * (hidden / 16), &|i| {
6589 scale_byte(i ^ 0x3C3C_3C3C)
6590 })?;
6591 let dc = mk(experts * hidden * (ff / 2), &|i| code_byte(i ^ 0x0F0F_0F0F))?;
6592 let ds = mk(experts * hidden * (ff / 16), &|i| {
6593 scale_byte(i ^ 0x1111_1111)
6594 })?;
6595 let gm = e.htod(&vec![1.0f32; experts])?;
6596 let um = e.htod(&vec![1.0f32; experts])?;
6597 let dm = e.htod(&vec![1.0f32; experts])?;
6598 let mixed_h: Vec<f32> = (0..t * hidden)
6599 .map(|i| ((i.wrapping_mul(40_503) % 1000) as f32) / 4000.0 - 0.125)
6600 .collect();
6601 let mixed = e.htod(&mixed_h)?;
6602
6603 let slots = t * selected;
6607 let stride = (experts / slots.max(1)).max(1);
6608 let sel_h: Vec<i32> = (0..slots)
6609 .map(|i| ((i * stride) % experts) as i32)
6610 .collect();
6611 let tok_h: Vec<i32> = (0..slots).map(|i| (i / selected) as i32).collect();
6612 let sel = e.htod_i32(&sel_h)?;
6613 let tokm = e.htod_i32(&tok_h)?;
6614 let mut act = e.zeros(slots * ff)?;
6615 let mut partial = e.zeros(slots * hidden)?;
6616
6617 struct Arm {
6618 spec: String,
6619 gu_shape: String,
6620 dn_shape: String,
6621 gu_grid_x: usize,
6622 dn_grid_x: usize,
6623 gu: Vec<f64>,
6624 dn: Vec<f64>,
6625 }
6626 let describe = |code: u32, in_f: usize, out_f: usize| -> (String, usize) {
6627 match sel_group_resolve(code, in_f, out_f) {
6628 Some((g, rows)) => {
6629 let rpw = (32 / g) * rows;
6630 (format!("g{g}r{rows}/rpw{rpw}"), out_f / rpw)
6631 }
6632 None => ("shipped".to_string(), out_f / 4),
6633 }
6634 };
6635 let mut built: Vec<Arm> = Vec::with_capacity(arms.len());
6636 for spec in arms {
6637 if !set_sel_group(spec) {
6638 return Err(format!("sel_shape_cost_probe: bad arm spec {spec:?}").into());
6639 }
6640 let (gu_shape, gu_grid_x) = describe(sel_group_gu(), hidden, ff);
6641 let (dn_shape, dn_grid_x) = describe(sel_group_dn(), ff, hidden);
6642 built.push(Arm {
6643 spec: spec.clone(),
6644 gu_shape,
6645 dn_shape,
6646 gu_grid_x,
6647 dn_grid_x,
6648 gu: Vec::with_capacity(reps),
6649 dn: Vec::with_capacity(reps),
6650 });
6651 }
6652 let tok_arg = if t > 1 { Some((&tokm, hidden)) } else { None };
6653 for rep in 0..(reps + 1) {
6654 for a in built.iter_mut() {
6655 set_sel_group(&a.spec);
6659 e.stream().synchronize()?;
6660 let t0 = std::time::Instant::now();
6661 launch_nvfp4_sel_gu_silu(
6662 e,
6663 (&gc, &gs, &gm),
6664 (&uc, &us, &um),
6665 Some(&sel),
6666 0,
6667 slots,
6668 &mixed,
6669 &mut act,
6670 hidden,
6671 ff,
6672 tok_arg,
6673 )?;
6674 e.stream().synchronize()?;
6675 let t1 = std::time::Instant::now();
6676 launch_nvfp4_sel_matvec(
6677 e,
6678 &dc,
6679 &ds,
6680 &dm,
6681 &sel,
6682 &act,
6683 &mut partial,
6684 slots,
6685 ff,
6686 hidden,
6687 ff,
6688 )?;
6689 e.stream().synchronize()?;
6690 let t2 = std::time::Instant::now();
6691 if rep > 0 {
6692 a.gu.push(t1.duration_since(t0).as_secs_f64() * 1e6);
6693 a.dn.push(t2.duration_since(t1).as_secs_f64() * 1e6);
6694 }
6695 }
6696 }
6697 let stat = |v: &[f64]| -> (f64, f64) {
6698 let mut s = v.to_vec();
6699 s.sort_by(|a, b| a.partial_cmp(b).unwrap());
6700 let med = s[s.len() / 2];
6701 let spread = if med > 0.0 {
6702 (s[s.len() - 1] - s[0]) / med
6703 } else {
6704 0.0
6705 };
6706 (med, spread)
6707 };
6708 Ok(built
6709 .iter()
6710 .map(|a| {
6711 let (gu_us, gu_spread_rel) = stat(&a.gu);
6712 let (down_us, down_spread_rel) = stat(&a.dn);
6713 SelShapeRow {
6714 t,
6715 slots,
6716 arm: a.spec.clone(),
6717 gu_shape: a.gu_shape.clone(),
6718 dn_shape: a.dn_shape.clone(),
6719 gu_grid_x: a.gu_grid_x,
6720 dn_grid_x: a.dn_grid_x,
6721 gu_us,
6722 down_us,
6723 gu_spread_rel,
6724 down_spread_rel,
6725 }
6726 })
6727 .collect())
6728}
6729
6730#[allow(clippy::too_many_arguments)]
6735fn launch_axpy_rows_seq_at(
6736 e: &Engine,
6737 x: &CudaSlice<f32>,
6738 x_row0: usize,
6739 w: &CudaSlice<f32>,
6740 w_off: usize,
6741 y: &mut CudaSlice<f32>,
6742 y_row: usize,
6743 width: usize,
6744 n_rows: usize,
6745) -> Res<()> {
6746 if x.len() < (x_row0 + n_rows) * width
6747 || w.len() < w_off + n_rows
6748 || y.len() < (y_row + 1) * width
6749 {
6750 return Err("axpy_rows_seq_f32: window out of range".into());
6751 }
6752 let xv = x.slice(x_row0 * width..(x_row0 + n_rows) * width);
6753 let wv = w.slice(w_off..w_off + n_rows);
6754 let mut yv = y.slice_mut(y_row * width..(y_row + 1) * width);
6755 let f = e.func("axpy_rows_seq_f32");
6756 let cfg = LaunchConfig::for_num_elems(width as u32);
6757 let (wi, nr) = (width as i32, n_rows as i32);
6758 let stream = e.gpu.stream();
6759 let mut b = stream.launch_builder(&f);
6760 b.arg(&xv).arg(&wv).arg(&mut yv).arg(&wi).arg(&nr);
6761 unsafe {
6762 b.launch(cfg)?;
6763 }
6764 Ok(())
6765}
6766
6767pub fn gate_nvfp4_sel_matvec(e: &Engine) -> Res<String> {
6776 let mut lcg = 0x2545_f491_u64;
6777 let mut next_u32 = move || -> u32 {
6778 lcg = lcg
6779 .wrapping_mul(6364136223846793005)
6780 .wrapping_add(1442695040888963407);
6781 (lcg >> 33) as u32
6782 };
6783 let macros = [
6784 1.0f32,
6785 0.5,
6786 5.9945243e-5, 2.0,
6788 0.25,
6789 3.7e-3,
6790 1.0,
6791 8.0,
6792 ];
6793 let sel_host: Vec<i32> = vec![3, 5, 3, 0]; let n_sel = sel_host.len();
6795 let mut worst = (0.0f32, 0.0f32); for (mode, out_f, in_f) in [
6802 ("gate_up_v1", 16usize, 48usize),
6803 ("down_v1", 32, 16),
6804 ("gate_up_v1_oddrows", 7, 64),
6805 ("gate_up_v2", 16, 64),
6806 ("down_v2", 32, 32),
6807 ("gate_up_v3", 16, 64),
6808 ("down_v3", 32, 32),
6809 ("gate_up_v3_v2rows", 6, 64), ] {
6811 set_sel_v3(mode.contains("v3"));
6812 let n_expert = macros.len();
6813 let mut codes = vec![0u8; n_expert * out_f * in_f / 2];
6814 for byte in &mut codes {
6815 *byte = next_u32() as u8;
6816 }
6817 let mut scales = vec![0u8; n_expert * out_f * in_f / 16];
6818 for byte in &mut scales {
6819 *byte = (next_u32() as u8) & 0xBF; }
6821 scales[0] = 0x7F; scales[3] = 0xFF; let x_stride = if mode.starts_with("down") { in_f } else { 0 };
6824 let x_rows = if x_stride == 0 { 1 } else { n_sel };
6825 let x_host: Vec<f32> = (0..x_rows * in_f)
6826 .map(|_| (next_u32() % 2000) as f32 / 1000.0 - 1.0)
6827 .collect();
6828 let codes_dev = e.htod_bytes(&codes)?;
6829 let scales_dev = e.htod_bytes(&scales)?;
6830 let macros_dev = e.htod(¯os)?;
6831 let sel_dev = e.htod_i32(&sel_host)?;
6832 let x_dev = e.htod(&x_host)?;
6833 let mut y_dev = e.uninit(n_sel * out_f)?;
6834 launch_nvfp4_sel_matvec(
6835 e,
6836 &codes_dev,
6837 &scales_dev,
6838 ¯os_dev,
6839 &sel_dev,
6840 &x_dev,
6841 &mut y_dev,
6842 n_sel,
6843 in_f,
6844 out_f,
6845 x_stride,
6846 )?;
6847 let y = e.dtoh(&y_dev)?;
6848 let wbytes = out_f * in_f / 2;
6849 let sbytes = out_f * in_f / 16;
6850 for (slot, &expert) in sel_host.iter().enumerate() {
6851 let expert = expert as usize;
6852 let w = memra_gguf::dsv4::dequant_nvfp4_expert(
6853 &codes[expert * wbytes..(expert + 1) * wbytes],
6854 &scales[expert * sbytes..(expert + 1) * sbytes],
6855 macros[expert],
6856 out_f,
6857 in_f,
6858 );
6859 let xrow = &x_host[slot * x_stride..slot * x_stride + in_f];
6860 for o in 0..out_f {
6861 let mut want = 0.0f32;
6862 for i in 0..in_f {
6863 want += w[o * in_f + i] * xrow[i];
6864 }
6865 let got = y[slot * out_f + o];
6866 let abs = (want - got).abs();
6867 let rel = abs / want.abs().max(1.0);
6868 if abs > worst.0 {
6869 worst.0 = abs;
6870 }
6871 if rel > worst.1 {
6872 worst.1 = rel;
6873 }
6874 if rel > 1e-5 {
6875 return Err(format!(
6876 "nvfp4-sel-matvec oracle: {mode} slot {slot} row {o}: want {want} \
6877 got {got} (rel {rel:.3e})"
6878 )
6879 .into());
6880 }
6881 }
6882 }
6883 }
6884 set_sel_v3(SEL_V3_DEFAULT);
6885
6886 {
6891 set_sel_v3(true);
6892 let (ff, in_f) = (16usize, 64usize);
6893 let n_expert = macros.len();
6894 let mut mk = |seed: u8| -> (Vec<u8>, Vec<u8>) {
6895 let mut codes = vec![0u8; n_expert * ff * in_f / 2];
6896 for byte in &mut codes {
6897 *byte = (next_u32() as u8) ^ seed;
6898 }
6899 let mut scales = vec![0u8; n_expert * ff * in_f / 16];
6900 for byte in &mut scales {
6901 *byte = (next_u32() as u8) & 0xBF;
6902 }
6903 scales[1] = 0x7F; (codes, scales)
6905 };
6906 let (g_codes, g_scales) = mk(0x00);
6907 let (u_codes, u_scales) = mk(0x5A);
6908 let gmac: Vec<f32> = macros.to_vec();
6909 let umac: Vec<f32> = macros.iter().map(|m| m * 0.5).collect();
6910 let x_host: Vec<f32> = (0..in_f)
6911 .map(|_| (next_u32() % 2000) as f32 / 1000.0 - 1.0)
6912 .collect();
6913 let gc = e.htod_bytes(&g_codes)?;
6914 let gs = e.htod_bytes(&g_scales)?;
6915 let gm = e.htod(&gmac)?;
6916 let uc = e.htod_bytes(&u_codes)?;
6917 let us = e.htod_bytes(&u_scales)?;
6918 let um = e.htod(&umac)?;
6919 let sel_dev = e.htod_i32(&sel_host)?;
6920 let x_dev = e.htod(&x_host)?;
6921 let mut yg = e.uninit(n_sel * ff)?;
6923 let mut yu = e.uninit(n_sel * ff)?;
6924 launch_nvfp4_sel_matvec(
6925 e, &gc, &gs, &gm, &sel_dev, &x_dev, &mut yg, n_sel, in_f, ff, 0,
6926 )?;
6927 launch_nvfp4_sel_matvec(
6928 e, &uc, &us, &um, &sel_dev, &x_dev, &mut yu, n_sel, in_f, ff, 0,
6929 )?;
6930 let mut act_chain = e.zeros(n_sel * ff)?;
6931 e.silu_mul(&yg, &yu, &mut act_chain, n_sel * ff)?;
6932 let mut act_fused = e.zeros(n_sel * ff)?;
6934 launch_nvfp4_sel_gu_silu(
6935 e,
6936 (&gc, &gs, &gm),
6937 (&uc, &us, &um),
6938 Some(&sel_dev),
6939 0,
6940 n_sel,
6941 &x_dev,
6942 &mut act_fused,
6943 in_f,
6944 ff,
6945 None,
6946 )?;
6947 let a = e.dtoh(&act_chain)?;
6948 let b = e.dtoh(&act_fused)?;
6949 for (i, (&x1, &x2)) in a.iter().zip(&b).enumerate() {
6950 if x1.to_bits() != x2.to_bits() {
6951 return Err(format!(
6952 "nvfp4-sel-matvec oracle: gufuse idx {i} not bit-identical \
6953 (chain {x1} fused {x2})"
6954 )
6955 .into());
6956 }
6957 }
6958 let pack_bytes = tp2_pack_bytes(&sel_host[..2], &[0.5, 0.25], n_sel);
6961 let pack = e.htod_bytes(&pack_bytes)?;
6962 let pack_raw = {
6963 let stream = e.gpu.stream();
6964 pack.device_ptr(&stream).0
6965 };
6966 let sentinel = vec![-777.0f32; n_sel * ff];
6967 let mut act_pack = e.htod(&sentinel)?;
6968 launch_nvfp4_sel_gu_silu(
6969 e,
6970 (&gc, &gs, &gm),
6971 (&uc, &us, &um),
6972 None,
6973 pack_raw,
6974 n_sel,
6975 &x_dev,
6976 &mut act_pack,
6977 in_f,
6978 ff,
6979 None,
6980 )?;
6981 let c = e.dtoh(&act_pack)?;
6982 for slot in 0..n_sel {
6983 for o in 0..ff {
6984 let got = c[slot * ff + o];
6985 if slot < 2 {
6986 if got.to_bits() != a[slot * ff + o].to_bits() {
6987 return Err(format!(
6988 "nvfp4-sel-matvec oracle: gufuse pack slot {slot} o {o} \
6989 not bit-identical"
6990 )
6991 .into());
6992 }
6993 } else if got != -777.0 {
6994 return Err(format!(
6995 "nvfp4-sel-matvec oracle: gufuse pack dead slot {slot} written"
6996 )
6997 .into());
6998 }
6999 }
7000 }
7001 {
7004 let t2 = 2usize;
7005 let x2_host: Vec<f32> = (0..t2 * in_f)
7006 .map(|_| (next_u32() % 2000) as f32 / 1000.0 - 1.0)
7007 .collect();
7008 let x2 = e.htod(&x2_host)?;
7009 let tok_host: Vec<i32> = (0..n_sel).map(|s| (s % t2) as i32).collect();
7010 let tokm = e.htod_i32(&tok_host)?;
7011 let mut act_map = e.zeros(n_sel * ff)?;
7012 launch_nvfp4_sel_gu_silu(
7013 e,
7014 (&gc, &gs, &gm),
7015 (&uc, &us, &um),
7016 Some(&sel_dev),
7017 0,
7018 n_sel,
7019 &x2,
7020 &mut act_map,
7021 in_f,
7022 ff,
7023 Some((&tokm, in_f)),
7024 )?;
7025 let got = e.dtoh(&act_map)?;
7026 for tok in 0..t2 {
7027 let slots: Vec<usize> = (0..n_sel).filter(|s| s % t2 == tok).collect();
7028 let sel_tok: Vec<i32> = slots.iter().map(|&s| sel_host[s]).collect();
7029 let sel_tok_dev = e.htod_i32(&sel_tok)?;
7030 let xrow = e.htod(&x2_host[tok * in_f..(tok + 1) * in_f])?;
7031 let mut act_tok = e.zeros(sel_tok.len() * ff)?;
7032 launch_nvfp4_sel_gu_silu(
7033 e,
7034 (&gc, &gs, &gm),
7035 (&uc, &us, &um),
7036 Some(&sel_tok_dev),
7037 0,
7038 sel_tok.len(),
7039 &xrow,
7040 &mut act_tok,
7041 in_f,
7042 ff,
7043 None,
7044 )?;
7045 let want = e.dtoh(&act_tok)?;
7046 for (local, &slot) in slots.iter().enumerate() {
7047 for o in 0..ff {
7048 let a = got[slot * ff + o];
7049 let b = want[local * ff + o];
7050 if a.to_bits() != b.to_bits() {
7051 return Err(format!(
7052 "nvfp4-sel-matvec oracle: gufuse tok_map slot {slot} o {o} \
7053 not bit-identical (map {a} per-token {b})"
7054 )
7055 .into());
7056 }
7057 }
7058 }
7059 }
7060 }
7061 set_sel_v3(SEL_V3_DEFAULT);
7062 }
7063 Ok(format!(
7064 "nvfp4-sel-matvec kernel oracle: worst abs {:.3e} rel {:.3e} over gate_up+down \
7065 v1/v2/v3 modes, NaN scales + non-pow2 macros + duplicate slots; gufuse \
7066 BIT-IDENTICAL to the v3+silu chain incl. the count-gated pack twin + the \
7067 tok_map verify merge",
7068 worst.0, worst.1
7069 ))
7070}
7071
7072pub fn gate_nvfp4_sel_group(e: &Engine) -> Res<String> {
7099 let saved = sel_group_spec();
7100 let out = gate_nvfp4_sel_group_inner(e);
7101 set_sel_group(&saved);
7104 set_sel_v3(SEL_V3_DEFAULT);
7105 out
7106}
7107
7108fn gate_nvfp4_sel_group_inner(e: &Engine) -> Res<String> {
7109 let mut lcg = 0x2545_f491_u64; let mut next_u32 = move || -> u32 {
7111 lcg = lcg
7112 .wrapping_mul(6364136223846793005)
7113 .wrapping_add(1442695040888963407);
7114 (lcg >> 33) as u32
7115 };
7116 let macros = [
7117 1.0f32,
7118 0.5,
7119 5.9945243e-5, 2.0,
7121 0.25,
7122 3.7e-3,
7123 1.0,
7124 8.0,
7125 ];
7126 let n_expert = macros.len();
7127 let sel_host: Vec<i32> = vec![3, 5, 3, 0]; let n_sel = sel_host.len();
7129 let mut worst = (0.0f32, 0.0f32);
7130 let mut shapes_checked = 0usize;
7131 let mut bits_checked = 0usize;
7132 let mut calib: Vec<String> = Vec::new();
7133
7134 for (geom, out_f, in_f, per_slot_x) in [
7140 ("down_real", 2560usize, 640usize, true),
7141 ("gateup_real", 640, 2560, false),
7142 ("down_tiny", 32, 32, true),
7143 ("gateup_tiny", 16, 64, false),
7144 ] {
7145 let mut codes = vec![0u8; n_expert * out_f * in_f / 2];
7146 for byte in &mut codes {
7147 *byte = next_u32() as u8;
7148 }
7149 let mut scales = vec![0u8; n_expert * out_f * in_f / 16];
7150 for byte in &mut scales {
7151 *byte = (next_u32() as u8) & 0xBF; }
7153 scales[0] = 0x7F; scales[3] = 0xFF; let x_stride = if per_slot_x { in_f } else { 0 };
7156 let x_rows = if per_slot_x { n_sel } else { 1 };
7157 let x_host: Vec<f32> = (0..x_rows * in_f)
7158 .map(|_| (next_u32() % 2000) as f32 / 1000.0 - 1.0)
7159 .collect();
7160 let codes_dev = e.htod_bytes(&codes)?;
7161 let scales_dev = e.htod_bytes(&scales)?;
7162 let macros_dev = e.htod(¯os)?;
7163 let sel_dev = e.htod_i32(&sel_host)?;
7164 let x_dev = e.htod(&x_host)?;
7165 let run = |spec: &str| -> Res<Vec<f32>> {
7166 set_sel_group(spec);
7167 let mut y = e.uninit(n_sel * out_f)?;
7168 launch_nvfp4_sel_matvec(
7169 e,
7170 &codes_dev,
7171 &scales_dev,
7172 ¯os_dev,
7173 &sel_dev,
7174 &x_dev,
7175 &mut y,
7176 n_sel,
7177 in_f,
7178 out_f,
7179 x_stride,
7180 )?;
7181 e.dtoh(&y)
7182 };
7183 set_sel_v3(true);
7191 let shipped = run("off")?;
7192 let wbytes = out_f * in_f / 2;
7193 let sbytes = out_f * in_f / 16;
7194 let mut want = vec![0.0f32; n_sel * out_f];
7195 for (slot, &expert) in sel_host.iter().enumerate() {
7196 let expert = expert as usize;
7197 let w = memra_gguf::dsv4::dequant_nvfp4_expert(
7198 &codes[expert * wbytes..(expert + 1) * wbytes],
7199 &scales[expert * sbytes..(expert + 1) * sbytes],
7200 macros[expert],
7201 out_f,
7202 in_f,
7203 );
7204 let xrow = &x_host[slot * x_stride..slot * x_stride + in_f];
7205 for o in 0..out_f {
7206 let mut acc = 0.0f32;
7207 for i in 0..in_f {
7208 acc += w[o * in_f + i] * xrow[i];
7209 }
7210 want[slot * out_f + o] = acc;
7211 }
7212 }
7213 let ship_vs_host = want
7222 .iter()
7223 .zip(&shipped)
7224 .map(|(&w, &s)| (w - s).abs() / w.abs().max(1.0))
7225 .fold(0.0f32, f32::max);
7226 let class_tol = (4.0 * ship_vs_host).max(1e-5);
7230 calib.push(format!(
7231 "{geom} ship_vs_host={ship_vs_host:.3e} tol={class_tol:.3e}"
7232 ));
7233 for spec in [
7238 "dn:32:4", "dn:auto", "dn:16:4", "dn:16:2", "dn:8:4", "dn:8:2", "dn:8:1", "dn:4:4",
7239 "dn:4:2", "dn:4:1", "dn:2:4", "dn:2:2", "dn:2:1", "dn:1:1",
7240 ] {
7241 let Some((g, rows)) = sel_group_resolve(
7242 match spec {
7243 "dn:auto" => SEL_GROUP_AUTO,
7244 _ => {
7245 let (gs, rs) = spec.trim_start_matches("dn:").split_once(':').unwrap();
7246 (gs.parse::<u32>().unwrap() << 8) | rs.parse::<u32>().unwrap()
7247 }
7248 },
7249 in_f,
7250 out_f,
7251 ) else {
7252 continue; };
7254 let got = run(spec)?;
7255 shapes_checked += 1;
7256 if (g, rows) == (32, 4) {
7257 for (i, (&a, &b)) in shipped.iter().zip(&got).enumerate() {
7259 if a.to_bits() != b.to_bits() {
7260 return Err(format!(
7261 "sel-group oracle: {geom} g=32 rows=4 idx {i} NOT bit-identical to \
7262 the shipped v3 kernel (v3 {a} group {b}) — the sub-warp form must \
7263 degenerate to v3 exactly"
7264 )
7265 .into());
7266 }
7267 }
7268 bits_checked += shipped.len();
7269 }
7270 for (i, (&w, &got)) in want.iter().zip(&got).enumerate() {
7275 let abs = (w - got).abs();
7276 let rel = abs / w.abs().max(1.0);
7277 worst.0 = worst.0.max(abs);
7278 worst.1 = worst.1.max(rel);
7279 if rel > class_tol {
7280 return Err(format!(
7281 "sel-group oracle: {geom} {spec} (g={g} rows={rows}) idx {i} vs HOST \
7282 chain: want {w} got {got} (rel {rel:.3e} > tol {class_tol:.3e}, \
7283 shipped v3 itself is {ship_vs_host:.3e})"
7284 )
7285 .into());
7286 }
7287 }
7288 for (i, (&s, &got)) in shipped.iter().zip(&got).enumerate() {
7289 let rel = (s - got).abs() / s.abs().max(1.0);
7290 if rel > class_tol {
7291 return Err(format!(
7292 "sel-group oracle: {geom} {spec} (g={g} rows={rows}) idx {i} vs SHIPPED \
7293 v3: v3 {s} group {got} (rel {rel:.3e} > tol {class_tol:.3e})"
7294 )
7295 .into());
7296 }
7297 }
7298 }
7299 set_sel_group("off");
7300 }
7301
7302 for (geom, ff, in_f) in [("gu_real", 640usize, 2560usize), ("gu_tiny", 16, 64)] {
7306 let mut mk = |seed: u8| -> (Vec<u8>, Vec<u8>) {
7307 let mut codes = vec![0u8; n_expert * ff * in_f / 2];
7308 for byte in &mut codes {
7309 *byte = (next_u32() as u8) ^ seed;
7310 }
7311 let mut scales = vec![0u8; n_expert * ff * in_f / 16];
7312 for byte in &mut scales {
7313 *byte = (next_u32() as u8) & 0xBF;
7314 }
7315 scales[1] = 0x7F; (codes, scales)
7317 };
7318 let (g_codes, g_scales) = mk(0x00);
7319 let (u_codes, u_scales) = mk(0x5A);
7320 let gmac: Vec<f32> = macros.to_vec();
7321 let umac: Vec<f32> = macros.iter().map(|m| m * 0.5).collect();
7322 let x_host: Vec<f32> = (0..in_f)
7323 .map(|_| (next_u32() % 2000) as f32 / 1000.0 - 1.0)
7324 .collect();
7325 let gc = e.htod_bytes(&g_codes)?;
7326 let gs = e.htod_bytes(&g_scales)?;
7327 let gm = e.htod(&gmac)?;
7328 let uc = e.htod_bytes(&u_codes)?;
7329 let us = e.htod_bytes(&u_scales)?;
7330 let um = e.htod(&umac)?;
7331 let sel_dev = e.htod_i32(&sel_host)?;
7332 let x_dev = e.htod(&x_host)?;
7333 set_sel_group("off");
7334 set_sel_v3(true);
7335 let shipped_fused = {
7336 let mut act = e.zeros(n_sel * ff)?;
7337 launch_nvfp4_sel_gu_silu(
7338 e,
7339 (&gc, &gs, &gm),
7340 (&uc, &us, &um),
7341 Some(&sel_dev),
7342 0,
7343 n_sel,
7344 &x_dev,
7345 &mut act,
7346 in_f,
7347 ff,
7348 None,
7349 )?;
7350 e.dtoh(&act)?
7351 };
7352 for spec in ["32:4", "auto", "16:4", "16:2", "8:4", "8:1", "4:4"] {
7353 let Some((g, rows)) = sel_group_resolve(
7354 match spec {
7355 "auto" => SEL_GROUP_AUTO,
7356 _ => {
7357 let (gs, rs) = spec.split_once(':').unwrap();
7358 (gs.parse::<u32>().unwrap() << 8) | rs.parse::<u32>().unwrap()
7359 }
7360 },
7361 in_f,
7362 ff,
7363 ) else {
7364 continue;
7365 };
7366 set_sel_group(&format!("dn:{spec}+gu:off"));
7368 let mut yg = e.uninit(n_sel * ff)?;
7369 let mut yu = e.uninit(n_sel * ff)?;
7370 launch_nvfp4_sel_matvec(
7371 e, &gc, &gs, &gm, &sel_dev, &x_dev, &mut yg, n_sel, in_f, ff, 0,
7372 )?;
7373 launch_nvfp4_sel_matvec(
7374 e, &uc, &us, &um, &sel_dev, &x_dev, &mut yu, n_sel, in_f, ff, 0,
7375 )?;
7376 let mut act_chain = e.zeros(n_sel * ff)?;
7377 e.silu_mul(&yg, &yu, &mut act_chain, n_sel * ff)?;
7378 let chain = e.dtoh(&act_chain)?;
7379 set_sel_group(&format!("dn:off+gu:{spec}"));
7381 let mut act_fused = e.zeros(n_sel * ff)?;
7382 launch_nvfp4_sel_gu_silu(
7383 e,
7384 (&gc, &gs, &gm),
7385 (&uc, &us, &um),
7386 Some(&sel_dev),
7387 0,
7388 n_sel,
7389 &x_dev,
7390 &mut act_fused,
7391 in_f,
7392 ff,
7393 None,
7394 )?;
7395 let fused = e.dtoh(&act_fused)?;
7396 for (i, (&a, &b)) in chain.iter().zip(&fused).enumerate() {
7397 if a.to_bits() != b.to_bits() {
7398 return Err(format!(
7399 "sel-group oracle: {geom} gu {spec} (g={g} rows={rows}) idx {i} fused \
7400 NOT bit-identical to the same-shape chain (chain {a} fused {b})"
7401 )
7402 .into());
7403 }
7404 }
7405 bits_checked += chain.len();
7406 shapes_checked += 1;
7407 if (g, rows) == (32, 4) {
7408 for (i, (&a, &b)) in shipped_fused.iter().zip(&fused).enumerate() {
7409 if a.to_bits() != b.to_bits() {
7410 return Err(format!(
7411 "sel-group oracle: {geom} gu g=32 rows=4 idx {i} NOT bit-identical \
7412 to the shipped gufuse kernel (gufuse {a} group {b})"
7413 )
7414 .into());
7415 }
7416 }
7417 bits_checked += shipped_fused.len();
7418 }
7419 }
7420 set_sel_group("dn:off+gu:auto");
7423 if sel_group_resolve(SEL_GROUP_AUTO, in_f, ff).is_some() {
7424 let auto_plain = {
7425 let mut act = e.zeros(n_sel * ff)?;
7426 launch_nvfp4_sel_gu_silu(
7427 e,
7428 (&gc, &gs, &gm),
7429 (&uc, &us, &um),
7430 Some(&sel_dev),
7431 0,
7432 n_sel,
7433 &x_dev,
7434 &mut act,
7435 in_f,
7436 ff,
7437 None,
7438 )?;
7439 e.dtoh(&act)?
7440 };
7441 let pack_bytes = tp2_pack_bytes(&sel_host[..2], &[0.5, 0.25], n_sel);
7442 let pack = e.htod_bytes(&pack_bytes)?;
7443 let pack_raw = {
7444 let stream = e.gpu.stream();
7445 pack.device_ptr(&stream).0
7446 };
7447 let sentinel = vec![-777.0f32; n_sel * ff];
7448 let mut act_pack = e.htod(&sentinel)?;
7449 launch_nvfp4_sel_gu_silu(
7450 e,
7451 (&gc, &gs, &gm),
7452 (&uc, &us, &um),
7453 None,
7454 pack_raw,
7455 n_sel,
7456 &x_dev,
7457 &mut act_pack,
7458 in_f,
7459 ff,
7460 None,
7461 )?;
7462 let packed = e.dtoh(&act_pack)?;
7463 for slot in 0..n_sel {
7464 for o in 0..ff {
7465 let got = packed[slot * ff + o];
7466 if slot < 2 {
7467 if got.to_bits() != auto_plain[slot * ff + o].to_bits() {
7468 return Err(format!(
7469 "sel-group oracle: {geom} gu auto pack slot {slot} o {o} not \
7470 bit-identical to the sel-array arm"
7471 )
7472 .into());
7473 }
7474 } else if got != -777.0 {
7475 return Err(format!(
7476 "sel-group oracle: {geom} gu auto pack dead slot {slot} written"
7477 )
7478 .into());
7479 }
7480 }
7481 }
7482 let t2 = 2usize;
7484 let x2_host: Vec<f32> = (0..t2 * in_f)
7485 .map(|_| (next_u32() % 2000) as f32 / 1000.0 - 1.0)
7486 .collect();
7487 let x2 = e.htod(&x2_host)?;
7488 let tok_host: Vec<i32> = (0..n_sel).map(|s| (s % t2) as i32).collect();
7489 let tokm = e.htod_i32(&tok_host)?;
7490 let mut act_map = e.zeros(n_sel * ff)?;
7491 launch_nvfp4_sel_gu_silu(
7492 e,
7493 (&gc, &gs, &gm),
7494 (&uc, &us, &um),
7495 Some(&sel_dev),
7496 0,
7497 n_sel,
7498 &x2,
7499 &mut act_map,
7500 in_f,
7501 ff,
7502 Some((&tokm, in_f)),
7503 )?;
7504 let mapped = e.dtoh(&act_map)?;
7505 for tok in 0..t2 {
7506 let slots: Vec<usize> = (0..n_sel).filter(|s| s % t2 == tok).collect();
7507 let sel_tok: Vec<i32> = slots.iter().map(|&s| sel_host[s]).collect();
7508 let sel_tok_dev = e.htod_i32(&sel_tok)?;
7509 let xrow = e.htod(&x2_host[tok * in_f..(tok + 1) * in_f])?;
7510 let mut act_tok = e.zeros(sel_tok.len() * ff)?;
7511 launch_nvfp4_sel_gu_silu(
7512 e,
7513 (&gc, &gs, &gm),
7514 (&uc, &us, &um),
7515 Some(&sel_tok_dev),
7516 0,
7517 sel_tok.len(),
7518 &xrow,
7519 &mut act_tok,
7520 in_f,
7521 ff,
7522 None,
7523 )?;
7524 let want = e.dtoh(&act_tok)?;
7525 for (local, &slot) in slots.iter().enumerate() {
7526 for o in 0..ff {
7527 let a = mapped[slot * ff + o];
7528 let b = want[local * ff + o];
7529 if a.to_bits() != b.to_bits() {
7530 return Err(format!(
7531 "sel-group oracle: {geom} gu auto tok_map slot {slot} o {o} not \
7532 bit-identical (map {a} per-token {b})"
7533 )
7534 .into());
7535 }
7536 }
7537 }
7538 }
7539 bits_checked += auto_plain.len() + mapped.len();
7540 }
7541 set_sel_group("off");
7542 }
7543
7544 Ok(format!(
7545 "nvfp4-sel-GROUP kernel oracle: {shapes_checked} (geometry, shape) cells over REAL \
7546 MoE geometry (down 2560x640 pairs=20, gate_up 640x2560 pairs=80) + tiny, worst abs \
7547 {:.3e} rel {:.3e} vs the host decoder chain; (g=32,rows=4) BIT-IDENTICAL to the \
7548 shipped v3 and gufuse kernels and every shape's fused arm BIT-IDENTICAL to its \
7549 same-shape chain ({bits_checked} f32 byte-compared), incl. the count-gated pack \
7550 twin + the tok_map verify merge; NaN scales + non-pow2 macros + duplicate slots; \
7551 per-geometry class calibration [{}]",
7552 worst.0,
7553 worst.1,
7554 calib.join("; ")
7555 ))
7556}
7557
7558pub fn gate_hc_diet_kernels(e: &Engine) -> Res<String> {
7566 let (streams, hidden, rank, t) = (4usize, 2560usize, 320usize, 1usize);
7567 let wide = streams * hidden;
7568 let mut lcg = 0x8badf00d_u64;
7569 let mut next_f32 = move || -> f32 {
7570 lcg = lcg
7571 .wrapping_mul(6364136223846793005)
7572 .wrapping_add(1442695040888963407);
7573 (((lcg >> 33) as u32) % 2000) as f32 / 1000.0 - 1.0
7574 };
7575 let mut rand_vec = |n: usize| -> Vec<f32> { (0..n).map(|_| next_f32()).collect() };
7576 let to_b16_vals = |v: Vec<f32>| -> Vec<f32> {
7578 v.into_iter()
7579 .map(|x| f32::from_bits(x.to_bits() & 0xFFFF_0000))
7580 .collect()
7581 };
7582 let planes_host: Vec<Vec<f32>> = (0..streams).map(|_| rand_vec(t * hidden)).collect();
7583 let planes: Vec<CudaSlice<f32>> = planes_host
7584 .iter()
7585 .map(|v| e.htod(v))
7586 .collect::<Result<_, _>>()?;
7587 let ptr_vals: Vec<u64> = {
7588 let stream = e.gpu.stream();
7589 planes.iter().map(|p| p.device_ptr(&stream).0).collect()
7590 };
7591 let ptrs = e.htod_u64(&ptr_vals)?;
7592 let norm_stack_host = rand_vec(wide);
7593 let norm_stack = e.htod(&norm_stack_host)?;
7594 let down_host = to_b16_vals(rand_vec(streams * rank * hidden));
7595 let up_host = to_b16_vals(rand_vec(streams * hidden * rank));
7596 let inj_host = to_b16_vals(rand_vec(streams * wide));
7597 let down_b16 = bf16_twin(e, &down_host, hidden)?.ok_or("hc-diet oracle: down twin")?;
7598 let up_b16 = bf16_twin(e, &up_host, rank)?.ok_or("hc-diet oracle: up twin")?;
7599 let inj_b16 = bf16_twin(e, &inj_host, hidden)?.ok_or("hc-diet oracle: inject twin")?;
7600 let inj_f32 = e.htod(&inj_host)?;
7601 let eps = 1e-6f32;
7602
7603 let mut normed = e.zeros(streams * t * hidden)?;
7605 launch_hc_norm_planes(e, &ptrs, &norm_stack, &mut normed, hidden, t, streams, eps)?;
7606 let mut parts_c = e.zeros(streams * t * rank)?;
7607 launch_qmatvec_bf16w(
7608 e,
7609 &down_b16,
7610 &normed,
7611 &mut parts_c,
7612 hidden,
7613 rank,
7614 t,
7615 streams,
7616 rank * hidden,
7617 t * hidden,
7618 hidden,
7619 t * rank,
7620 )?;
7621 let mut low_c = e.zeros(t * rank)?;
7622 launch_hc_lowrank_reduce(e, &parts_c, &mut low_c, streams, t, rank)?;
7623 let mut gates_c = e.zeros(streams * t * hidden)?;
7624 launch_qmatvec_bf16w(
7625 e,
7626 &up_b16,
7627 &low_c,
7628 &mut gates_c,
7629 rank,
7630 hidden,
7631 t,
7632 streams,
7633 hidden * rank,
7634 0,
7635 rank,
7636 t * hidden,
7637 )?;
7638 let mut mixed_c = e.zeros(t * hidden)?;
7639 launch_hc_mix_epilogue(e, &gates_c, &normed, &mut mixed_c, streams, t, hidden)?;
7640 let mut partials_c = e.zeros(streams * t * 16)?;
7641 let mut all_c = e.zeros(streams * t)?;
7642 launch_hc_inject_two_stage(
7643 e,
7644 &normed,
7645 &inj_f32,
7646 Some(&inj_b16),
7647 &mut partials_c,
7648 &mut all_c,
7649 streams,
7650 t,
7651 hidden,
7652 16,
7653 )?;
7654
7655 let mut parts_d = e.zeros(streams * rank)?;
7657 let mut injp_d = e.zeros(streams * streams)?;
7658 let mut inv_d = e.zeros(streams)?;
7659 launch_hc_diet_stage1(
7660 e,
7661 &ptrs,
7662 &norm_stack,
7663 &down_b16,
7664 Some(&inj_b16),
7665 &mut parts_d,
7666 &mut injp_d,
7667 &mut inv_d,
7668 hidden,
7669 rank,
7670 streams,
7671 1,
7672 eps,
7673 )?;
7674 let mut low_d = e.zeros(rank)?;
7675 let mut all_d = e.zeros(streams)?;
7676 launch_hc_diet_stage2(
7677 e, &parts_d, &injp_d, &mut low_d, &mut all_d, rank, streams, 1, true,
7678 )?;
7679 let mut mixed_d = e.zeros(hidden)?;
7680 launch_hc_diet_stage3(
7681 e,
7682 &ptrs,
7683 &norm_stack,
7684 &inv_d,
7685 &up_b16,
7686 &low_d,
7687 &mut mixed_d,
7688 hidden,
7689 rank,
7690 streams,
7691 1,
7692 )?;
7693
7694 let mut worst = 0.0f32;
7695 let check = |name: &str, a: &[f32], b: &[f32], worst: &mut f32| -> Res<()> {
7696 for (i, (&x, &y)) in a.iter().zip(b).enumerate() {
7697 let rel = (x - y).abs() / y.abs().max(1.0);
7698 if rel > *worst {
7699 *worst = rel;
7700 }
7701 if rel > 1e-4 {
7702 return Err(format!(
7703 "hc-diet oracle: {name} idx {i}: diet {x} classic {y} (rel {rel:.3e})"
7704 )
7705 .into());
7706 }
7707 }
7708 Ok(())
7709 };
7710 check("low_act", &e.dtoh(&low_d)?, &e.dtoh(&low_c)?, &mut worst)?;
7711 check("inject", &e.dtoh(&all_d)?, &e.dtoh(&all_c)?, &mut worst)?;
7712 check("mixed", &e.dtoh(&mixed_d)?, &e.dtoh(&mixed_c)?, &mut worst)?;
7713
7714 {
7718 let t3 = 3usize;
7719 let planes3_host: Vec<Vec<f32>> = (0..streams).map(|_| rand_vec(t3 * hidden)).collect();
7720 let planes3: Vec<CudaSlice<f32>> = planes3_host
7721 .iter()
7722 .map(|v| e.htod(v))
7723 .collect::<Result<_, _>>()?;
7724 let ptr_vals3: Vec<u64> = {
7725 let stream = e.gpu.stream();
7726 planes3.iter().map(|p| p.device_ptr(&stream).0).collect()
7727 };
7728 let ptrs3 = e.htod_u64(&ptr_vals3)?;
7729 let mut parts3 = e.zeros(t3 * streams * rank)?;
7730 let mut injp3 = e.zeros(t3 * streams * streams)?;
7731 let mut inv3 = e.zeros(t3 * streams)?;
7732 launch_hc_diet_stage1(
7733 e,
7734 &ptrs3,
7735 &norm_stack,
7736 &down_b16,
7737 Some(&inj_b16),
7738 &mut parts3,
7739 &mut injp3,
7740 &mut inv3,
7741 hidden,
7742 rank,
7743 streams,
7744 t3,
7745 eps,
7746 )?;
7747 let mut low3 = e.zeros(t3 * rank)?;
7748 let mut all3 = e.zeros(streams * t3)?;
7749 launch_hc_diet_stage2(
7750 e, &parts3, &injp3, &mut low3, &mut all3, rank, streams, t3, true,
7751 )?;
7752 let mut mixed3 = e.zeros(t3 * hidden)?;
7753 launch_hc_diet_stage3(
7754 e,
7755 &ptrs3,
7756 &norm_stack,
7757 &inv3,
7758 &up_b16,
7759 &low3,
7760 &mut mixed3,
7761 hidden,
7762 rank,
7763 streams,
7764 t3,
7765 )?;
7766 let low3_h = e.dtoh(&low3)?;
7767 let all3_h = e.dtoh(&all3)?;
7768 let mixed3_h = e.dtoh(&mixed3)?;
7769 {
7772 let mut inv_mt = e.zeros(t3 * streams)?;
7773 launch_hc_diet_stage0_mt(e, &ptrs3, &mut inv_mt, hidden, streams, t3, eps)?;
7774 let mut parts_mt = e.zeros(t3 * streams * rank)?;
7775 let mut injp_mt = e.zeros(t3 * streams * streams)?;
7776 launch_hc_diet_stage1_mt(
7777 e,
7778 &ptrs3,
7779 &norm_stack,
7780 &inv_mt,
7781 &down_b16,
7782 Some(&inj_b16),
7783 &mut parts_mt,
7784 &mut injp_mt,
7785 hidden,
7786 rank,
7787 streams,
7788 t3,
7789 )?;
7790 let mut low_mt = e.zeros(t3 * rank)?;
7791 let mut all_mt = e.zeros(streams * t3)?;
7792 launch_hc_diet_stage2(
7793 e,
7794 &parts_mt,
7795 &injp_mt,
7796 &mut low_mt,
7797 &mut all_mt,
7798 rank,
7799 streams,
7800 t3,
7801 true,
7802 )?;
7803 let mut mixed_mt = e.zeros(t3 * hidden)?;
7804 launch_hc_diet_stage3_mt(
7805 e,
7806 &ptrs3,
7807 &norm_stack,
7808 &inv_mt,
7809 &up_b16,
7810 &low_mt,
7811 &mut mixed_mt,
7812 hidden,
7813 rank,
7814 streams,
7815 t3,
7816 )?;
7817 let bit_check_mt = |name: &str, a: &[f32], b: &[f32]| -> Res<()> {
7818 for (i, (&x, &y)) in a.iter().zip(b).enumerate() {
7819 if x.to_bits() != y.to_bits() {
7820 return Err(format!(
7821 "hc-diet mt oracle: {name} idx {i}: mt {x} vs grid {y} NOT \
7822 bit-identical"
7823 )
7824 .into());
7825 }
7826 }
7827 Ok(())
7828 };
7829 bit_check_mt("inv", &e.dtoh(&inv_mt)?, &e.dtoh(&inv3)?)?;
7830 bit_check_mt("low_act", &e.dtoh(&low_mt)?, &low3_h)?;
7831 bit_check_mt("inject", &e.dtoh(&all_mt)?, &all3_h)?;
7832 bit_check_mt("mixed", &e.dtoh(&mixed_mt)?, &mixed3_h)?;
7833 }
7834 let bit_check = |name: &str, a: &[f32], b: &[f32]| -> Res<()> {
7835 for (i, (&x, &y)) in a.iter().zip(b).enumerate() {
7836 if x.to_bits() != y.to_bits() {
7837 return Err(format!(
7838 "hc-diet t-ext oracle: {name} idx {i}: t3 {x} vs t1 {y} NOT bit-identical"
7839 )
7840 .into());
7841 }
7842 }
7843 Ok(())
7844 };
7845 for tok in 0..t3 {
7846 let ptr_tok: Vec<u64> = ptr_vals3
7847 .iter()
7848 .map(|&base| base + (tok * hidden * 4) as u64)
7849 .collect();
7850 let ptrs_tok = e.htod_u64(&ptr_tok)?;
7851 let mut parts1 = e.zeros(streams * rank)?;
7852 let mut injp1 = e.zeros(streams * streams)?;
7853 let mut inv1 = e.zeros(streams)?;
7854 launch_hc_diet_stage1(
7855 e,
7856 &ptrs_tok,
7857 &norm_stack,
7858 &down_b16,
7859 Some(&inj_b16),
7860 &mut parts1,
7861 &mut injp1,
7862 &mut inv1,
7863 hidden,
7864 rank,
7865 streams,
7866 1,
7867 eps,
7868 )?;
7869 let mut low1 = e.zeros(rank)?;
7870 let mut all1 = e.zeros(streams)?;
7871 launch_hc_diet_stage2(
7872 e, &parts1, &injp1, &mut low1, &mut all1, rank, streams, 1, true,
7873 )?;
7874 let mut mixed1 = e.zeros(hidden)?;
7875 launch_hc_diet_stage3(
7876 e,
7877 &ptrs_tok,
7878 &norm_stack,
7879 &inv1,
7880 &up_b16,
7881 &low1,
7882 &mut mixed1,
7883 hidden,
7884 rank,
7885 streams,
7886 1,
7887 )?;
7888 bit_check(
7889 "low_act",
7890 &low3_h[tok * rank..(tok + 1) * rank],
7891 &e.dtoh(&low1)?,
7892 )?;
7893 let all1_h = e.dtoh(&all1)?;
7894 let col: Vec<f32> = (0..streams).map(|s| all3_h[s * t3 + tok]).collect();
7895 bit_check("inject", &col, &all1_h)?;
7896 bit_check(
7897 "mixed",
7898 &mixed3_h[tok * hidden..(tok + 1) * hidden],
7899 &e.dtoh(&mixed1)?,
7900 )?;
7901 }
7902 }
7903 Ok(format!(
7904 "hc-diet real-geometry oracle: streams 4 hidden 2560 rank 320, worst rel \
7905 {worst:.3e} vs the classic fused chain at t 1; t 3 token-dim AND the mt \
7906 weight-shared stages BIT-IDENTICAL to per-token t 1 launches"
7907 ))
7908}
7909
7910pub fn gate_hc_micro_kernels(e: &Engine) -> Res<String> {
7925 let (streams, hidden, t) = (4usize, 2560usize, 10usize);
7926 let wide = streams * hidden;
7927 let mut lcg = 0x1357_9bdf_u64;
7928 let mut next_f32 = move || -> f32 {
7929 lcg = lcg
7930 .wrapping_mul(6364136223846793005)
7931 .wrapping_add(1442695040888963407);
7932 (((lcg >> 33) as u32) % 2000) as f32 / 1000.0 - 1.0
7933 };
7934 let mut rand_vec = |n: usize| -> Vec<f32> { (0..n).map(|_| next_f32()).collect() };
7935 let planes_host: Vec<Vec<f32>> = (0..streams).map(|_| rand_vec(t * hidden)).collect();
7936 let planes: Vec<CudaSlice<f32>> = planes_host
7937 .iter()
7938 .map(|v| e.htod(v))
7939 .collect::<Result<_, _>>()?;
7940 let ptr_vals: Vec<u64> = {
7941 let stream = e.gpu.stream();
7942 planes.iter().map(|p| p.device_ptr(&stream).0).collect()
7943 };
7944 let ptrs = e.htod_u64(&ptr_vals)?;
7945 let mut worst = 0.0f32;
7946 let check = |name: &str, a: &[f32], b: &[f32], worst: &mut f32| -> Res<()> {
7947 for (i, (&x, &y)) in a.iter().zip(b).enumerate() {
7948 let rel = (x - y).abs() / y.abs().max(1.0);
7949 if rel > *worst {
7950 *worst = rel;
7951 }
7952 if rel > 1e-4 {
7953 return Err(format!(
7954 "hc-micro oracle: {name} idx {i}: micro {x} classic {y} (rel {rel:.3e})"
7955 )
7956 .into());
7957 }
7958 }
7959 Ok(())
7960 };
7961
7962 let norm_stack_host = rand_vec(wide);
7964 let norm_stack = e.htod(&norm_stack_host)?;
7965 let eps = 1e-6f32;
7966 let mut normed_a = e.zeros(streams * t * hidden)?;
7967 launch_hc_norm_planes(
7968 e,
7969 &ptrs,
7970 &norm_stack,
7971 &mut normed_a,
7972 hidden,
7973 t,
7974 streams,
7975 eps,
7976 )?;
7977 let mut normed_b = e.zeros(streams * t * hidden)?;
7978 for s in 0..streams {
7979 let w = e.htod(&norm_stack_host[s * hidden..(s + 1) * hidden])?;
7980 let mut dst = normed_b.slice_mut(s * t * hidden..(s + 1) * t * hidden);
7981 launch_rms_norm_into_view(e, &planes[s], &w, &mut dst, hidden, t, eps)?;
7982 }
7983 check("norm", &e.dtoh(&normed_a)?, &e.dtoh(&normed_b)?, &mut worst)?;
7984
7985 let inj_w_host = rand_vec(streams * wide);
7987 let inj_w = e.htod(&inj_w_host)?;
7988 let mut all_a = e.zeros(streams * t)?;
7989 let mut partials = e.zeros(streams * t * 16)?;
7990 launch_hc_inject_two_stage(
7991 e,
7992 &normed_b,
7993 &inj_w,
7994 None,
7995 &mut partials,
7996 &mut all_a,
7997 streams,
7998 t,
7999 hidden,
8000 16,
8001 )?;
8002 let mut all_b = e.zeros(streams * t)?;
8003 launch_hc_inject_gates(e, &normed_b, &inj_w, &mut all_b, streams, t, hidden)?;
8004 check("inject", &e.dtoh(&all_a)?, &e.dtoh(&all_b)?, &mut worst)?;
8005
8006 let block_out = e.htod(&rand_vec(t * hidden))?;
8008 launch_hc_write_planes(e, &ptrs, &block_out, &all_b, hidden, t, streams)?;
8009 let mut expect: Vec<Vec<f32>> = Vec::with_capacity(streams);
8010 let all_host = e.dtoh(&all_b)?;
8011 let bo_host = e.dtoh(&block_out)?;
8012 for (s, base) in planes_host.iter().enumerate() {
8013 let mut rows = base.clone();
8014 for tok in 0..t {
8015 let g = all_host[s * t + tok];
8016 for d in 0..hidden {
8017 rows[tok * hidden + d] += bo_host[tok * hidden + d] * g;
8018 }
8019 }
8020 expect.push(rows);
8021 }
8022 for (s, plane) in planes.iter().enumerate() {
8023 check(
8024 &format!("write plane {s}"),
8025 &e.dtoh(plane)?,
8026 &expect[s],
8027 &mut worst,
8028 )?;
8029 }
8030 Ok(format!(
8031 "hc-micro real-geometry oracle: streams 4 hidden 2560 t 10, worst rel {worst:.3e} \
8032 over norm/inject/write vs the classic composition"
8033 ))
8034}
8035
8036pub fn gate_sdpa_blocklist(e: &Engine) -> Res<String> {
8054 let mut lcg = 0x51ee_7bad_u64;
8055 let mut next_f32 = move || -> f32 {
8056 lcg = lcg
8057 .wrapping_mul(6364136223846793005)
8058 .wrapping_add(1442695040888963407);
8059 (((lcg >> 33) as u32) % 2000) as f32 / 1000.0 - 1.0
8060 };
8061 let (hd, nh, nkv, t) = (256usize, 24usize, 2usize, 3usize);
8062 let block_size = 4usize;
8063 let scale = 1.0 / (hd as f32).sqrt();
8064 let mut bit_rows = 0usize;
8065 let mut worst_rel = 0.0f32;
8066 for (t_kv, vs_masked) in [(4096usize, true), (16384usize, false)] {
8067 let q_host: Vec<f32> = (0..t * nh * hd).map(|_| next_f32()).collect();
8068 let k_host: Vec<f32> = (0..t_kv * nkv * hd).map(|_| next_f32()).collect();
8069 let v_host: Vec<f32> = (0..t_kv * nkv * hd).map(|_| next_f32()).collect();
8070 let sels: Vec<RowSel> = (0..t)
8073 .map(|qt| {
8074 let visible = t_kv - t + qt + 1;
8075 let complete = visible / block_size;
8076 if qt == 0 && vs_masked {
8081 return RowSel {
8082 full: true,
8083 blocks: Vec::new(),
8084 visible,
8085 };
8086 }
8087 let stride = if qt == 1 { 3 } else { 7 };
8088 let blocks: Vec<u32> = (0..complete as u32)
8089 .rev()
8090 .step_by(stride)
8091 .take(512)
8092 .collect::<Vec<_>>()
8093 .into_iter()
8094 .rev()
8095 .collect();
8096 RowSel {
8097 full: false,
8098 blocks,
8099 visible,
8100 }
8101 })
8102 .collect();
8103 let (pos_flat, meta, max_count) = rowsel_positions(&sels, block_size);
8104 let q = e.htod(&q_host)?;
8105 let k = e.htod(&k_host)?;
8106 let v = e.htod(&v_host)?;
8107 let pos = e.htod_i32(&pos_flat)?;
8108 let meta_dev = e.htod_i32(&meta)?;
8109 let mut o_list = e.zeros(t * nh * hd)?;
8110 launch_sdpa_blocklist(
8111 e,
8112 &q,
8113 &k.slice(0..t_kv * nkv * hd),
8114 &v.slice(0..t_kv * nkv * hd),
8115 &mut o_list,
8116 &pos,
8117 &meta_dev,
8118 hd,
8119 nh,
8120 nkv,
8121 t,
8122 max_count,
8123 scale,
8124 )?;
8125 let ours = e.dtoh(&o_list)?;
8126 if vs_masked {
8127 let mask = rowsel_to_mask(&sels, block_size, t_kv);
8128 let mask_dev = e.htod_bytes(&mask)?;
8129 let mut o_mask = e.zeros(t * nh * hd)?;
8130 launch_sdpa_mask(
8131 e,
8132 &q,
8133 &k.slice(0..t_kv * nkv * hd),
8134 &v.slice(0..t_kv * nkv * hd),
8135 &mut o_mask,
8136 &mask_dev,
8137 hd,
8138 nh,
8139 nkv,
8140 t,
8141 t_kv,
8142 scale,
8143 )?;
8144 let masked = e.dtoh(&o_mask)?;
8145 for (i, (a, b)) in masked.iter().zip(ours.iter()).enumerate() {
8146 if a.to_bits() != b.to_bits() {
8147 return Err(format!(
8148 "sdpa_blocklist vs masked: bit mismatch at {i}: {a} vs {b} (t_kv {t_kv})"
8149 )
8150 .into());
8151 }
8152 }
8153 bit_rows = t * nh * hd;
8154 } else {
8155 for qt in 0..t {
8158 let off = meta[2 * qt] as usize;
8159 let count = meta[2 * qt + 1] as usize;
8160 for head in 0..nh {
8161 let kvh = head / (nh / nkv);
8162 let qrow = &q_host[(qt * nh + head) * hd..(qt * nh + head + 1) * hd];
8163 let mut scores: Vec<f32> = (0..count)
8164 .map(|i| {
8165 let p = pos_flat[off + i] as usize;
8166 let krow = &k_host[(p * nkv + kvh) * hd..(p * nkv + kvh + 1) * hd];
8167 let mut acc = 0.0f32;
8168 for d in 0..hd {
8169 acc += qrow[d] * krow[d];
8170 }
8171 acc * scale
8172 })
8173 .collect();
8174 let mx = scores.iter().copied().fold(-1e30f32, f32::max);
8175 let mut sum = 0.0f32;
8176 for s in scores.iter_mut() {
8177 *s = (*s - mx).exp();
8178 sum += *s;
8179 }
8180 let inv = 1.0 / sum;
8181 for s in scores.iter_mut() {
8182 *s *= inv;
8183 }
8184 for d in 0..hd {
8185 let mut acc = 0.0f32;
8186 for (i, s) in scores.iter().enumerate() {
8187 let p = pos_flat[off + i] as usize;
8188 acc += s * v_host[(p * nkv + kvh) * hd + d];
8189 }
8190 let got = ours[(qt * nh + head) * hd + d];
8191 let rel = (got - acc).abs() / acc.abs().max(1e-3);
8192 worst_rel = worst_rel.max(rel);
8193 if rel > 1e-4 {
8194 return Err(format!(
8195 "sdpa_blocklist vs host twin: rel {rel} at row {qt} head {head} \
8196 dim {d} (t_kv {t_kv})"
8197 )
8198 .into());
8199 }
8200 }
8201 }
8202 }
8203 }
8204 }
8205 Ok(format!(
8206 "sdpa-blocklist oracle: BIT-IDENTICAL to the masked kernel over {bit_rows} values \
8207 (t_kv 4096, full+stride selections); past the mask bound (t_kv 16384) worst rel \
8208 {worst_rel:.3e} vs the host twin"
8209 ))
8210}
8211
8212pub fn gate_kvq_kernels(e: &Engine) -> Res<String> {
8226 let mut lcg = 0x6b_7671_5eed_u64; let mut next_f32 = move || -> f32 {
8228 lcg = lcg
8229 .wrapping_mul(6364136223846793005)
8230 .wrapping_add(1442695040888963407);
8231 (((lcg >> 33) as u32) % 2000) as f32 / 1000.0 - 1.0
8232 };
8233 let mut report = Vec::new();
8234
8235 for &dim in &[512usize, 40usize] {
8237 let rows = 9usize;
8238 let mut host_rows_f: Vec<f32> = (0..rows * dim).map(|_| next_f32()).collect();
8239 for v in host_rows_f[0..dim].iter_mut() {
8242 *v = 0.0;
8243 }
8244 for v in host_rows_f[dim..2 * dim].iter_mut() {
8245 *v = 0.75;
8246 }
8247 for (i, v) in host_rows_f[2 * dim..3 * dim].iter_mut().enumerate() {
8248 *v = if i == 0 {
8250 1.0
8251 } else {
8252 (i as f32) * 0.5 / 127.0
8253 };
8254 }
8255 for v in host_rows_f[3 * dim..4 * dim].iter_mut() {
8257 *v *= 1e-40;
8258 }
8259 let dev_rows = e.htod(&host_rows_f)?;
8260 let mut kq = e.alloc_u8(rows * q8_row_bytes(dim))?;
8261 let mut vq = e.alloc_u8(rows * q5_row_bytes(dim))?;
8262 launch_q4e_kv_append(e, &dev_rows, &dev_rows, &mut kq, &mut vq, 0, rows, dim)?;
8263 let kq_host = e.dtoh_u8(&kq)?;
8264 let vq_host = e.dtoh_u8(&vq)?;
8265 let mut k_twin = Vec::new();
8266 let mut v_twin = Vec::new();
8267 for r in 0..rows {
8268 host_quant_q8_row(&host_rows_f[r * dim..(r + 1) * dim], dim, &mut k_twin);
8269 host_quant_q5_row(&host_rows_f[r * dim..(r + 1) * dim], dim, &mut v_twin);
8270 }
8271 if kq_host != k_twin {
8272 let i = kq_host.iter().zip(&k_twin).position(|(a, b)| a != b);
8273 return Err(format!("kvq q8 quantize twin: byte mismatch at {i:?} (dim {dim})").into());
8274 }
8275 if vq_host != v_twin {
8276 let i = vq_host.iter().zip(&v_twin).position(|(a, b)| a != b);
8277 return Err(format!("kvq q5 quantize twin: byte mismatch at {i:?} (dim {dim})").into());
8278 }
8279 let mut kf = e.zeros(rows * dim)?;
8281 let mut vf = e.zeros(rows * dim)?;
8282 launch_q4e_kv_dequant_rows(e, &kq, &vq, &mut kf, &mut vf, 0, rows, dim)?;
8283 let kf_host = e.dtoh(&kf)?;
8284 let vf_host = e.dtoh(&vf)?;
8285 let mut kf_twin = Vec::new();
8286 let mut vf_twin = Vec::new();
8287 host_deq_q8_rows(&kq_host, 0, rows, dim, &mut kf_twin);
8288 host_deq_q5_rows(&vq_host, 0, rows, dim, &mut vf_twin);
8289 for (i, (a, b)) in kf_host.iter().zip(&kf_twin).enumerate() {
8290 if a.to_bits() != b.to_bits() {
8291 return Err(format!("kvq q8 dequant twin: bit mismatch at {i} (dim {dim})").into());
8292 }
8293 }
8294 for (i, (a, b)) in vf_host.iter().zip(&vf_twin).enumerate() {
8295 if a.to_bits() != b.to_bits() {
8296 return Err(format!("kvq q5 dequant twin: bit mismatch at {i} (dim {dim})").into());
8297 }
8298 }
8299 report.push(format!("quant+dequant twins dim {dim}: BYTE/BIT-IDENTICAL"));
8300 }
8301
8302 {
8304 let (hd, nh, nkv, t) = (256usize, 24usize, 2usize, 3usize);
8305 let kv_dim = nkv * hd;
8306 let block_size = 4usize;
8307 let scale = 1.0 / (hd as f32).sqrt();
8308 let t_kv = 4096usize;
8309 let q_host: Vec<f32> = (0..t * nh * hd).map(|_| next_f32()).collect();
8310 let k_host: Vec<f32> = (0..t_kv * kv_dim).map(|_| next_f32()).collect();
8311 let v_host: Vec<f32> = (0..t_kv * kv_dim).map(|_| next_f32()).collect();
8312 let k_rows = e.htod(&k_host)?;
8313 let v_rows = e.htod(&v_host)?;
8314 let mut kq = e.alloc_u8(t_kv * q8_row_bytes(kv_dim))?;
8315 let mut vq = e.alloc_u8(t_kv * q5_row_bytes(kv_dim))?;
8316 launch_q4e_kv_append(e, &k_rows, &v_rows, &mut kq, &mut vq, 0, t_kv, kv_dim)?;
8317 let sels: Vec<RowSel> = (0..t)
8320 .map(|qt| {
8321 let visible = (t_kv - t + qt + 1).min(2052);
8322 if qt == 0 {
8323 return RowSel {
8324 full: true,
8325 blocks: Vec::new(),
8326 visible,
8327 };
8328 }
8329 let complete = (t_kv - t + qt + 1) / block_size;
8330 let stride = if qt == 1 { 3 } else { 7 };
8331 let blocks: Vec<u32> = (0..complete as u32)
8332 .rev()
8333 .step_by(stride)
8334 .take(512)
8335 .collect::<Vec<_>>()
8336 .into_iter()
8337 .rev()
8338 .collect();
8339 RowSel {
8340 full: false,
8341 blocks,
8342 visible: t_kv - t + qt + 1,
8343 }
8344 })
8345 .collect();
8346 let (pos_flat, meta, max_count) = rowsel_positions(&sels, block_size);
8347 let q = e.htod(&q_host)?;
8348 let pos = e.htod_i32(&pos_flat)?;
8349 let meta_dev = e.htod_i32(&meta)?;
8350 let mut o_fused = e.zeros(t * nh * hd)?;
8351 launch_q4e_sdpa_blocklist_q8q5(
8352 e,
8353 &q,
8354 &kq,
8355 &vq,
8356 &mut o_fused,
8357 &pos,
8358 &meta_dev,
8359 hd,
8360 nh,
8361 nkv,
8362 t,
8363 max_count,
8364 scale,
8365 )?;
8366 let mut k_deq = e.zeros(t_kv * kv_dim)?;
8367 let mut v_deq = e.zeros(t_kv * kv_dim)?;
8368 launch_q4e_kv_dequant_rows(e, &kq, &vq, &mut k_deq, &mut v_deq, 0, t_kv, kv_dim)?;
8369 let mut o_comp = e.zeros(t * nh * hd)?;
8370 launch_sdpa_blocklist(
8371 e,
8372 &q,
8373 &k_deq.slice(0..t_kv * kv_dim),
8374 &v_deq.slice(0..t_kv * kv_dim),
8375 &mut o_comp,
8376 &pos,
8377 &meta_dev,
8378 hd,
8379 nh,
8380 nkv,
8381 t,
8382 max_count,
8383 scale,
8384 )?;
8385 let fused = e.dtoh(&o_fused)?;
8386 let comp = e.dtoh(&o_comp)?;
8387 for (i, (a, b)) in fused.iter().zip(&comp).enumerate() {
8388 if a.to_bits() != b.to_bits() {
8389 return Err(format!(
8390 "kvq fused attention vs dequant composition: bit mismatch at {i}: {a} vs {b}"
8391 )
8392 .into());
8393 }
8394 }
8395 {
8407 let was = kv_hoist_on();
8408 set_kv_hoist(true);
8409 let mut o_hoist = e.zeros(t * nh * hd)?;
8410 let launched = launch_q4e_sdpa_blocklist_q8q5(
8411 e,
8412 &q,
8413 &kq,
8414 &vq,
8415 &mut o_hoist,
8416 &pos,
8417 &meta_dev,
8418 hd,
8419 nh,
8420 nkv,
8421 t,
8422 max_count,
8423 scale,
8424 );
8425 set_kv_hoist(was);
8426 launched?;
8427 let hoist = e.dtoh(&o_hoist)?;
8428 let mut worst: Option<(usize, f32, f32)> = None;
8429 for (i, (a, b)) in hoist.iter().zip(&fused).enumerate() {
8430 if a.to_bits() != b.to_bits() && worst.is_none() {
8431 worst = Some((i, *a, *b));
8432 }
8433 }
8434 if let Some((i, a, b)) = worst {
8435 return Err(format!(
8436 "kvhoist vs un-hoisted q8q5 blocklist: bit mismatch at {i}: {a} vs {b} \
8437 (hd={hd} nh={nh} nkv={nkv} t={t} t_kv={t_kv} max_count={max_count})"
8438 )
8439 .into());
8440 }
8441 report.push(format!(
8447 "kvhoist vs un-hoisted q8q5 blocklist: BIT-IDENTICAL over {} values \
8448 (real geometry hd={hd} nh={nh} nkv={nkv}, {} blocks/head slice, max_count={max_count})",
8449 t * nh * hd,
8450 hd / 32
8451 ));
8452 }
8453 report.push(format!(
8454 "fused q8q5 blocklist vs dequant+f32 composition: BIT-IDENTICAL over {} values",
8455 t * nh * hd
8456 ));
8457 }
8458
8459 {
8461 let idx_dim = 128usize;
8462 let qk_width = 5 * idx_dim; let rows = 7usize;
8464 let src_host: Vec<f32> = (0..rows * qk_width).map(|_| next_f32()).collect();
8465 let src = e.htod(&src_host)?;
8466 let q_off = 4 * idx_dim;
8467 let mut dst_q8 = e.alloc_u8((rows + 2) * q8_row_bytes(idx_dim))?;
8469 launch_q4e_idx_append_q8(e, &src, &mut dst_q8, rows, idx_dim, qk_width, q_off, 2)?;
8470 let got = e.dtoh_u8(&dst_q8)?;
8471 let mut twin = vec![0u8; 2 * q8_row_bytes(idx_dim)];
8472 for r in 0..rows {
8473 host_quant_q8_row(
8474 &src_host[r * qk_width + q_off..(r + 1) * qk_width],
8475 idx_dim,
8476 &mut twin,
8477 );
8478 }
8479 if got[2 * q8_row_bytes(idx_dim)..] != twin[2 * q8_row_bytes(idx_dim)..] {
8480 return Err("idxq q8 append twin: byte mismatch".into());
8481 }
8482 let mut dst_bf = unsafe { e.gpu.stream().alloc::<u16>((rows + 2) * idx_dim)? };
8484 e.gpu.stream().memset_zeros(&mut dst_bf)?;
8485 launch_q4e_idx_append_bf16(e, &src, &mut dst_bf, rows, idx_dim, qk_width, q_off, 2)?;
8486 let got_bf: Vec<u16> = {
8487 let v = e
8488 .gpu
8489 .stream()
8490 .clone_dtoh(&dst_bf.slice(0..(rows + 2) * idx_dim))?;
8491 e.gpu.stream().synchronize()?;
8492 v
8493 };
8494 for r in 0..rows {
8495 for c in 0..idx_dim {
8496 let want = f32_to_bf16_rne(src_host[r * qk_width + q_off + c]);
8497 if got_bf[(2 + r) * idx_dim + c] != want {
8498 return Err(format!("idxq bf16 append twin: mismatch row {r} col {c}").into());
8499 }
8500 }
8501 }
8502 report.push("idx q8/bf16 appenders vs host twins: BYTE-IDENTICAL".to_string());
8503 }
8504
8505 Ok(format!("kvq kernel oracles: {}", report.join("; ")))
8506}
8507
8508pub fn gate_qsa_index_score(e: &Engine) -> Res<String> {
8513 let mut lcg = 0xfeed_1234_u64;
8514 let mut next_f32 = move || -> f32 {
8515 lcg = lcg
8516 .wrapping_mul(6364136223846793005)
8517 .wrapping_add(1442695040888963407);
8518 (((lcg >> 33) as u32) % 4000) as f32 / 2000.0 - 1.0
8519 };
8520 let (heads, head_dim) = (4usize, 128usize);
8521 let scale = (head_dim as f32).sqrt();
8522 let budget = 512usize;
8523 let mut worst_rows = 0usize;
8524 for (rows, n_blocks) in [(1usize, 4096usize), (7, 1031)] {
8525 let q_host: Vec<f32> = (0..rows * heads * head_dim).map(|_| next_f32()).collect();
8526 let pooled_host: Vec<f32> = (0..n_blocks * head_dim).map(|_| next_f32()).collect();
8527 let q = e.htod(&q_host)?;
8528 let pooled = e.htod(&pooled_host)?;
8529 let mut scores_dev = e.uninit(rows * n_blocks)?;
8530 launch_qsa_index_score(
8531 e,
8532 &q,
8533 &pooled,
8534 &mut scores_dev,
8535 heads,
8536 head_dim,
8537 n_blocks,
8538 rows,
8539 scale,
8540 )?;
8541 let got = e.dtoh(&scores_dev)?;
8542 for row in 0..rows {
8543 let qr = &q_host[row * heads * head_dim..(row + 1) * heads * head_dim];
8544 let host = score_blocks(qr, &pooled_host, heads, head_dim, n_blocks, scale, 1);
8545 for (b, want) in host.iter().enumerate() {
8546 let g = got[row * n_blocks + b];
8547 if g.to_bits() != want.to_bits() {
8548 return Err(format!(
8549 "qsa_index_score: bit mismatch row {row} block {b}: {g} vs host {want}"
8550 )
8551 .into());
8552 }
8553 }
8554 let a = top_blocks_ascending(&host, budget, 1);
8555 let b = top_blocks_ascending(&got[row * n_blocks..(row + 1) * n_blocks], budget, 1);
8556 if a != b {
8557 return Err(format!("qsa_index_score: top-k set differs at row {row}").into());
8558 }
8559 worst_rows += 1;
8560 }
8561 }
8562 let mut pool_t_rows = 0usize;
8577 for (rows, n_blocks, cap_rows) in [(1usize, 1031usize, 4096usize), (5, 2048, 2048)] {
8578 let q_host: Vec<f32> = (0..rows * heads * head_dim).map(|_| next_f32()).collect();
8579 let pooled_host: Vec<f32> = (0..n_blocks * head_dim).map(|_| next_f32()).collect();
8580 let q = e.htod(&q_host)?;
8581 let mut mirror = e.zeros(cap_rows * head_dim * POOL_PLANES)?;
8584 {
8585 let mut view = mirror.slice_mut(0..n_blocks * head_dim);
8586 e.gpu.stream().memcpy_htod(&pooled_host, &mut view)?;
8587 }
8588 launch_qsa_pooled_transpose(e, &mut mirror, 0, n_blocks, head_dim, cap_rows)?;
8589 let was = pool_t_on();
8590 set_pool_t(true);
8591 let mut scores_dev = e.uninit(rows * n_blocks)?;
8592 let launched = launch_qsa_index_score(
8593 e,
8594 &q,
8595 &mirror,
8596 &mut scores_dev,
8597 heads,
8598 head_dim,
8599 n_blocks,
8600 rows,
8601 scale,
8602 );
8603 set_pool_t(was);
8604 launched?;
8605 let got = e.dtoh(&scores_dev)?;
8606 for row in 0..rows {
8607 let qr = &q_host[row * heads * head_dim..(row + 1) * heads * head_dim];
8608 let host = score_blocks(qr, &pooled_host, heads, head_dim, n_blocks, scale, 1);
8609 for (b, want) in host.iter().enumerate() {
8610 let g = got[row * n_blocks + b];
8611 if g.to_bits() != want.to_bits() {
8612 return Err(format!(
8613 "poolT qsa_index_score_f32_t: bit mismatch row {row} block {b}: \
8614 {g} vs host {want} (n_blocks={n_blocks} cap_rows={cap_rows})"
8615 )
8616 .into());
8617 }
8618 }
8619 if top_blocks_ascending(&host, budget, 1)
8620 != top_blocks_ascending(&got[row * n_blocks..(row + 1) * n_blocks], budget, 1)
8621 {
8622 return Err(format!(
8623 "poolT qsa_index_score_f32_t: top-{budget} set differs at row {row} \
8624 (n_blocks={n_blocks} cap_rows={cap_rows})"
8625 )
8626 .into());
8627 }
8628 pool_t_rows += 1;
8629 }
8630 }
8631 Ok(format!(
8632 "qsa-index-score oracle: device scores BIT-IDENTICAL to the host twin over \
8633 {worst_rows} rows (4096 + 1031 blocks, real 4x128 geometry) and top-512 sets equal; \
8634 poolT dim-major plane (transpose + transposed kernel) BIT-IDENTICAL to the SAME host \
8635 twin over {pool_t_rows} rows, incl. the pitch-trap case cap_rows=4096 != n_blocks=1031"
8636 ))
8637}
8638
8639pub fn gate_ple_ngram_cache() -> Res<String> {
8658 let max_ngram = 3usize;
8660 let heads_per_ngram = 8usize;
8661 let total_heads = (max_ngram - 1) * heads_per_ngram;
8662 let multipliers: Vec<i64> = vec![
8663 0x2545_F491_4F6C_DD1D,
8664 0x9E37_79B9_7F4A_7C15u64 as i64,
8665 0x1234_5678_9ABC_DEF1,
8666 ];
8667 let sizes: Vec<i64> = (0..total_heads)
8668 .map(|i| 2_500_012_160 - (i as i64) * 7)
8669 .collect();
8670 let offsets: Vec<i64> = (0..total_heads)
8671 .map(|i| (i as i64) * 2_500_012_160)
8672 .collect();
8673 let eos = 248_046u32;
8674 let full = |ids: &[u32]| -> Vec<i64> {
8675 host_ngram_ids(
8676 ids,
8677 &multipliers,
8678 &sizes,
8679 &offsets,
8680 max_ngram,
8681 heads_per_ngram,
8682 eos,
8683 )
8684 };
8685 let mut lcg = 0x0be1_10ca_u64;
8686 let mut next_tok = move || -> u32 {
8687 lcg = lcg
8688 .wrapping_mul(6364136223846793005)
8689 .wrapping_add(1442695040888963407);
8690 ((lcg >> 33) as u32) % 250_000
8691 };
8692 let mut checks = 0usize;
8693 let run = |label: &str, steps: Vec<Vec<u32>>| -> Res<usize> {
8694 let (mut ci, mut ch, mut ce) = (Vec::new(), Vec::new(), -1i64);
8696 let mut n = 0usize;
8697 for seq in &steps {
8698 host_ngram_ids_cached(
8699 &mut ci,
8700 &mut ch,
8701 &mut ce,
8702 seq,
8703 &multipliers,
8704 &sizes,
8705 &offsets,
8706 max_ngram,
8707 heads_per_ngram,
8708 eos,
8709 );
8710 let want = full(seq);
8711 if ci.len() != want.len() {
8712 return Err(format!(
8713 "plecache oracle {label}: cache has {} ids, twin {} at len {}",
8714 ci.len(),
8715 want.len(),
8716 seq.len()
8717 )
8718 .into());
8719 }
8720 if let Some(i) = ci.iter().zip(&want).position(|(a, b)| a != b) {
8721 return Err(format!(
8722 "plecache oracle {label}: id {i} differs at len {} (token {}, head {}): \
8723 cache {} vs twin {}",
8724 seq.len(),
8725 i / total_heads,
8726 i % total_heads,
8727 ci[i],
8728 want[i]
8729 )
8730 .into());
8731 }
8732 n += seq.len();
8733 }
8734 Ok(n)
8735 };
8736 {
8738 let base: Vec<u32> = (0..200).map(|_| next_tok()).collect();
8739 let steps: Vec<Vec<u32>> = (1..=base.len()).map(|n| base[..n].to_vec()).collect();
8740 checks += run("decode-growth", steps)?;
8741 }
8742 {
8744 let base: Vec<u32> = (0..600).map(|_| next_tok()).collect();
8745 let mut steps = Vec::new();
8746 let mut n = 0usize;
8747 for step in [7usize, 1, 64, 3, 128, 2, 200, 195] {
8748 n = (n + step).min(base.len());
8749 steps.push(base[..n].to_vec());
8750 }
8751 checks += run("prefill-chunks", steps)?;
8752 }
8753 {
8755 let mut base: Vec<u32> = (0..300).map(|_| next_tok()).collect();
8756 for p in [0usize, 1, 2, 37, 38, 100, 101, 102, 299] {
8757 base[p] = eos;
8758 }
8759 let steps: Vec<Vec<u32>> = (1..=base.len()).map(|n| base[..n].to_vec()).collect();
8760 checks += run("eos-segments", steps)?;
8761 }
8762 {
8764 let base: Vec<u32> = vec![eos; 40];
8765 let steps: Vec<Vec<u32>> = (1..=base.len()).map(|n| base[..n].to_vec()).collect();
8766 checks += run("all-eos", steps)?;
8767 }
8768 {
8770 let a: Vec<u32> = (0..300).map(|_| next_tok()).collect();
8771 let mut b = a.clone();
8772 b[150] = a[150].wrapping_add(1) % 250_000;
8773 let mut c = b.clone();
8774 c[7] = b[7].wrapping_add(3) % 250_000;
8775 let mut d = c.clone();
8776 d.truncate(9);
8777 d.extend((0..100).map(|_| next_tok()));
8778 checks += run(
8779 "rewind-divergent",
8780 vec![
8781 a.clone(),
8782 a[..151].to_vec(),
8783 b.clone(),
8784 b[..8].to_vec(),
8785 c.clone(),
8786 d.clone(),
8787 a.clone(),
8788 ],
8789 )?;
8790 }
8791 {
8793 let a: Vec<u32> = (0..250).map(|_| next_tok()).collect();
8794 let mut s: Vec<u32> = (0..11).map(|_| next_tok()).collect();
8795 s[0] = eos;
8796 checks += run("state-reuse", vec![a.clone(), s.clone(), a.clone(), s])?;
8797 }
8798 Ok(format!(
8799 "plecache oracle: incremental n-gram ids EXACT vs the full host_ngram_ids twin over \
8800 {checks} cumulative-sequence comparisons across 6 case families (decode one-at-a-time \
8801 growth, ragged prefill chunks, eos segment resets incl. adjacent + leading + trailing \
8802 eos, all-eos, repeated rewinds to DIVERGING prefixes, and shorter-unrelated-sequence \
8803 state reuse)"
8804 ))
8805}
8806
8807pub fn gate_seam_table() -> Res<String> {
8823 let all: &[&str] = seam_names();
8828 let boolean: Vec<&str> = all
8829 .iter()
8830 .copied()
8831 .filter(|n| seam_state(n).is_some())
8832 .collect();
8833 if boolean.len() < 20 || all.len() < boolean.len() + 2 {
8838 return Err(format!(
8839 "seam-table oracle: refusing to report on {} boolean names out of {} total — the \
8840 seam list collapsed, so every assertion below would be vacuous",
8841 boolean.len(),
8842 all.len()
8843 )
8844 .into());
8845 }
8846 let names: &[&str] = &boolean;
8847 let snapshot = || -> Res<Vec<bool>> {
8848 names
8849 .iter()
8850 .map(|n| {
8851 seam_state(n).ok_or_else(|| {
8852 Box::<dyn std::error::Error>::from(format!(
8853 "seam-table oracle: seam_state({n:?}) is None — the name is in set_seam \
8854 but not in seam_state, so save/restore around a measurement would \
8855 silently not restore it"
8856 ))
8857 })
8858 })
8859 .collect()
8860 };
8861 let restore = |v: &[bool]| {
8862 for (n, &b) in names.iter().zip(v) {
8863 set_seam(n, b, None);
8864 }
8865 };
8866 let entry = snapshot()?;
8867 let mut checks = 0usize;
8868 for (i, name) in names.iter().enumerate() {
8869 for &want in &[true, false, true] {
8870 let before = snapshot()?;
8871 if !set_seam(name, want, None) {
8872 restore(&entry);
8873 return Err(format!("seam-table oracle: set_seam({name:?}) refused").into());
8874 }
8875 let after = snapshot()?;
8876 if after[i] != want {
8877 restore(&entry);
8878 return Err(format!(
8879 "seam-table oracle: set_seam({name:?}, {want}) then seam_state read {} — the \
8880 two tables disagree on this name",
8881 after[i]
8882 )
8883 .into());
8884 }
8885 for (j, other) in names.iter().enumerate() {
8889 if j != i && after[j] != before[j] {
8890 restore(&entry);
8891 return Err(format!(
8892 "seam-table oracle: arming {name:?} also changed {other:?} ({} -> {}) — \
8893 two names share one switch",
8894 before[j], after[j]
8895 )
8896 .into());
8897 }
8898 }
8899 checks += 1;
8900 }
8901 }
8902 for name in all {
8906 if !seam_exists(name) {
8907 restore(&entry);
8908 return Err(format!(
8909 "seam-table oracle: seam_names() lists {name:?} but seam_exists refuses it"
8910 )
8911 .into());
8912 }
8913 if !set_seam(name, seam_state(name).unwrap_or(false), None) {
8914 restore(&entry);
8915 return Err(format!(
8916 "seam-table oracle: seam_names() lists {name:?} but set_seam refuses it"
8917 )
8918 .into());
8919 }
8920 }
8921 if seam_exists("definitely-not-a-seam") || set_seam("definitely-not-a-seam", true, None) {
8923 restore(&entry);
8924 return Err("seam-table oracle: an unknown seam name was accepted".into());
8925 }
8926 let before = snapshot()?;
8929 for name in all {
8930 let _ = seam_exists(name);
8931 }
8932 if snapshot()? != before {
8933 restore(&entry);
8934 return Err("seam-table oracle: seam_exists mutated a seam (it must be name-only)".into());
8935 }
8936 restore(&entry);
8937 if snapshot()? != entry {
8938 return Err("seam-table oracle: the gate did not restore the entry state".into());
8939 }
8940 Ok(format!(
8941 "seam-table oracle: {} boolean seam names of {} total, {checks} set/read cycles, each \
8942 verified to change its OWN state and NO other (the cross-check that catches an arm \
8943 wired to a neighbour's switch), every listed name accepted by both entry points, \
8944 unknown names refused by both, seam_exists proven side-effect-free, entry state restored",
8945 names.len(),
8946 all.len()
8947 ))
8948}
8949
8950pub fn gate_qsa_index_topk(e: &Engine) -> Res<String> {
8973 let budget = 512usize;
8974 let mut lcg = 0x1d5e_10ca_u64;
8975 let mut rows_checked = 0usize;
8976 let mut deepest = 0usize;
8977 let mut cases: Vec<(String, Vec<usize>, usize, Vec<f32>)> = Vec::new();
8979 let mut next_f32 = move || -> f32 {
8980 lcg = lcg
8981 .wrapping_mul(6364136223846793005)
8982 .wrapping_add(1442695040888963407);
8983 let r = ((lcg >> 33) as u32) % 1000;
8985 if r < 250 { 0.0 } else { (r as f32) / 250.0 }
8986 };
8987 for (label, counts) in [
8988 ("real-262k-depth", vec![65_536usize]),
8989 ("real-131k-depth", vec![32_768usize, 32_768]),
8990 ("shallow", vec![513usize, 1_031, 4_096]),
8991 ("ragged-batch", vec![2_049usize, 8_191, 65_536, 4_097]),
8992 ] {
8993 let stride = *counts.iter().max().unwrap();
8994 let slab: Vec<f32> = (0..counts.len() * stride).map(|_| next_f32()).collect();
8995 cases.push((label.to_string(), counts, stride, slab));
8996 }
8997 cases.push((
8999 "all-zero".into(),
9000 vec![65_536usize],
9001 65_536,
9002 vec![0.0f32; 65_536],
9003 ));
9004 {
9006 let n = 4_096usize;
9007 let mut v = vec![0.0f32; n];
9008 for (i, slot) in v.iter_mut().enumerate() {
9009 *slot = if i < 12 {
9010 100.0 - i as f32
9011 } else if i % 7 == 0 {
9012 2.5 } else {
9014 (i % 3) as f32 * 0.25
9015 };
9016 }
9017 cases.push(("dup-straddle".into(), vec![n], n, v));
9018 }
9019 {
9022 let n = 2_048usize;
9023 let mut v = vec![0.0f32; n];
9024 for (i, slot) in v.iter_mut().enumerate() {
9025 *slot = match i % 8 {
9026 0 => 0.0,
9027 1 => -0.0,
9028 2 => f32::from_bits(1), 3 => -f32::from_bits(1), 4 => -(i as f32) * 0.5,
9031 5 => f32::NAN,
9032 6 => -f32::NAN,
9033 _ => (i % 5) as f32,
9034 };
9035 }
9036 cases.push(("total-cmp-domain".into(), vec![n], n, v));
9037 }
9038 for (label, counts, stride, slab) in &cases {
9039 let scores = e.htod(slab)?;
9040 let picked = launch_qsa_index_topk(e, &scores, counts, *stride, budget)?;
9041 if picked.len() != counts.len() {
9042 return Err(format!("idxsel oracle {label}: {} rows back", picked.len()).into());
9043 }
9044 for (r, &complete) in counts.iter().enumerate() {
9045 let row = &slab[r * *stride..r * *stride + complete];
9046 let twin = top_blocks_ascending(row, budget, 1);
9047 if twin != picked[r] {
9048 let first = twin
9049 .iter()
9050 .zip(picked[r].iter())
9051 .position(|(a, b)| a != b)
9052 .unwrap_or(twin.len().min(picked[r].len()));
9053 return Err(format!(
9054 "idxsel oracle {label}: selection differs at row {r} (blocks {complete}), \
9055 first differing slot {first}: host {:?} vs device {:?}",
9056 twin.get(first),
9057 picked[r].get(first)
9058 )
9059 .into());
9060 }
9061 rows_checked += 1;
9062 deepest = deepest.max(complete);
9063 }
9064 }
9065 Ok(format!(
9066 "qsa-index-topk oracle: device selection ids + ASCENDING order EXACT vs \
9067 top_blocks_ascending over {rows_checked} rows / {} cases at budget {budget}, \
9068 deepest {deepest} blocks (= the 262,144-token window), incl. the all-zero, \
9069 boundary-straddling-duplicate and total_cmp-domain (signed zero / subnormal / \
9070 negative / NaN) tie classes",
9071 cases.len()
9072 ))
9073}
9074
9075pub fn gate_route_kernel(e: &Engine) -> Res<String> {
9086 let mut lcg = 0x00de_7710_u64;
9087 let mut next_f32 = move || -> f32 {
9088 lcg = lcg
9089 .wrapping_mul(6364136223846793005)
9090 .wrapping_add(1442695040888963407);
9091 (((lcg >> 33) as u32) % 8000) as f32 / 200.0 - 20.0 };
9093 const ULP_BOUND: u32 = 2;
9094 let mut worst_ulp: u32 = 0;
9095 let mut rows_checked = 0usize;
9096 let run = |e: &Engine,
9097 label: &str,
9098 logits_host: &[f32],
9099 experts: usize,
9100 selected: usize,
9101 rows: usize,
9102 worst_ulp: &mut u32|
9103 -> Res<()> {
9104 let logits = e.htod(logits_host)?;
9105 let mut sel = e.alloc_uninit::<i32>(rows * selected)?;
9106 let mut w = e.uninit(rows * selected)?;
9107 let mut tok = e.alloc_uninit::<i32>(rows * selected)?;
9108 launch_route_topk(
9109 e,
9110 &logits,
9111 &mut sel,
9112 &mut w,
9113 Some((&mut tok, 3)),
9114 experts,
9115 selected,
9116 rows,
9117 )?;
9118 let sel_h = e.gpu.stream().clone_dtoh(&sel.slice(0..rows * selected))?;
9119 let w_h = e.dtoh(&w)?;
9120 let tok_h = e.gpu.stream().clone_dtoh(&tok.slice(0..rows * selected))?;
9121 let k = selected.min(experts);
9122 for row in 0..rows {
9123 let twin =
9124 host_route_softmax_topk(&logits_host[row * experts..(row + 1) * experts], selected);
9125 if twin.len() != k {
9126 return Err(format!("route oracle {label}: host twin width {}", twin.len()).into());
9127 }
9128 for (j, &(idx, wt)) in twin.iter().enumerate() {
9129 let ds = sel_h[row * selected + j];
9130 let dw = w_h[row * selected + j];
9131 if ds != idx as i32 {
9132 return Err(format!(
9133 "route oracle {label}: selection mismatch row {row} slot {j}: \
9134 device {ds} vs host {idx}"
9135 )
9136 .into());
9137 }
9138 let ulp = (dw.to_bits() as i64 - wt.to_bits() as i64).unsigned_abs();
9139 let ulp = u32::try_from(ulp).unwrap_or(u32::MAX);
9140 if ulp > ULP_BOUND {
9141 return Err(format!(
9142 "route oracle {label}: weight ULP {ulp} > {ULP_BOUND} at row {row} \
9143 slot {j}: device {dw:e} vs host {wt:e}"
9144 )
9145 .into());
9146 }
9147 *worst_ulp = (*worst_ulp).max(ulp);
9148 if tok_h[row * selected + j] != (3 + row) as i32 {
9149 return Err(format!(
9150 "route oracle {label}: tok map wrong at row {row} slot {j}"
9151 )
9152 .into());
9153 }
9154 }
9155 }
9156 Ok(())
9157 };
9158 let (experts, selected) = (512usize, 10usize);
9160 for rows in [1usize, 6, 16] {
9161 let logits: Vec<f32> = (0..rows * experts).map(|_| next_f32()).collect();
9162 run(e, "real", &logits, experts, selected, rows, &mut worst_ulp)?;
9163 rows_checked += rows;
9164 }
9165 let mut tie_rows: Vec<(String, Vec<f32>)> = Vec::new();
9167 {
9168 let mut v: Vec<f32> = (0..experts).map(|i| -30.0 - (i as f32) * 0.01).collect();
9171 for (rank, slot) in [40usize, 7, 300, 11].iter().enumerate() {
9172 v[*slot] = 10.0 - rank as f32;
9173 }
9174 for slot in [500usize, 3, 77, 210, 8, 401, 129, 64, 255, 380, 17, 450] {
9175 v[slot] = 2.5;
9176 }
9177 tie_rows.push(("dup-straddle".into(), v));
9178 tie_rows.push(("all-equal".into(), vec![0.125f32; experts]));
9180 let mut v = vec![-200.0f32; experts];
9183 v[100] = 5.0;
9184 for (i, slot) in [479usize, 2, 33].iter().enumerate() {
9185 v[*slot] = -80.0 - i as f32; }
9187 tie_rows.push(("underflow".into(), v));
9188 }
9189 for (label, v) in &tie_rows {
9190 run(e, label, v, experts, selected, 1, &mut worst_ulp)?;
9191 rows_checked += 1;
9192 }
9193 for (ex, se) in [(64usize, 4usize), (16, 16), (128, 32)] {
9195 let logits: Vec<f32> = (0..3 * ex).map(|_| next_f32()).collect();
9196 run(e, "geom", &logits, ex, se, 3, &mut worst_ulp)?;
9197 rows_checked += 3;
9198 }
9199 Ok(format!(
9200 "route oracle: device selection ids+order EXACT vs host twin over {rows_checked} rows \
9201 (real 512/10 + tie straddle/all-equal/underflow + geometry edges), worst weight \
9202 ULP {worst_ulp} (bound {ULP_BOUND}), tok map exact"
9203 ))
9204}
9205
9206pub fn gate_gdn_step_kernels(e: &Engine) -> Res<String> {
9207 let mut lcg = 0x0bad_cafe_u64;
9208 let mut next_f32 = move || -> f32 {
9209 lcg = lcg
9210 .wrapping_mul(6364136223846793005)
9211 .wrapping_add(1442695040888963407);
9212 (((lcg >> 33) as u32) % 2000) as f32 / 1000.0 - 1.0
9213 };
9214 let mut worst = 0.0f32;
9215 for (nk, nv, hk, hv) in [(16usize, 48usize, 128usize, 128usize), (2, 4, 32, 8)] {
9216 let conv_dim = 2 * nk * hk + nv * hv;
9217 let qkv_host: Vec<f32> = (0..conv_dim).map(|_| next_f32()).collect();
9218 let g_log_host: Vec<f32> = (0..nv).map(|_| next_f32().abs() * -2.0).collect();
9219 let beta_host: Vec<f32> = (0..nv).map(|_| next_f32()).collect();
9220 let state_host: Vec<f32> = (0..nv * hv * hk).map(|_| next_f32()).collect();
9221 let qkv = e.htod(&qkv_host)?;
9222 let g_log = e.htod(&g_log_host)?;
9223 let beta = e.htod(&beta_host)?;
9224 let scale = 1.0 / (hk as f32).sqrt();
9225 let eps = 1e-6f32;
9226 let mut state_a = e.htod(&state_host)?;
9227 let mut o_a = e.zeros(nv * hv)?;
9228 launch_gdn_scan(
9229 e,
9230 &qkv,
9231 &g_log,
9232 &beta,
9233 &mut state_a,
9234 &mut o_a,
9235 nk,
9236 nv,
9237 hk,
9238 hv,
9239 1,
9240 scale,
9241 eps,
9242 )?;
9243 let mut state_b = e.htod(&state_host)?;
9244 let mut o_b = e.zeros(nv * hv)?;
9245 launch_gdn_scan_step(
9246 e,
9247 &qkv,
9248 &g_log,
9249 &beta,
9250 &mut state_b,
9251 &mut o_b,
9252 nk,
9253 nv,
9254 hk,
9255 hv,
9256 scale,
9257 eps,
9258 )?;
9259 for (name, reference, candidate) in [
9260 ("o", e.dtoh(&o_a)?, e.dtoh(&o_b)?),
9261 ("state", e.dtoh(&state_a)?, e.dtoh(&state_b)?),
9262 ] {
9263 for (i, (&r, &c)) in reference.iter().zip(&candidate).enumerate() {
9264 let rel = (r - c).abs() / r.abs().max(1.0);
9265 if rel > worst {
9266 worst = rel;
9267 }
9268 if rel > 1e-4 {
9269 return Err(format!(
9270 "gdn-step oracle: nk{nk}/nv{nv}/hk{hk}/hv{hv} {name} idx {i}: \
9271 naive {r} step {c} (rel {rel:.3e})"
9272 )
9273 .into());
9274 }
9275 }
9276 }
9277 }
9278 let (rows, cols) = (48usize, 128usize);
9280 let x = e.htod(&(0..rows * cols).map(|_| next_f32()).collect::<Vec<_>>())?;
9281 let w = e.htod(&(0..cols).map(|_| next_f32()).collect::<Vec<_>>())?;
9282 let z = e.htod(&(0..rows * cols).map(|_| next_f32()).collect::<Vec<_>>())?;
9283 let eps = 1e-6f32;
9284 let mut normed = e.zeros(rows * cols)?;
9285 e.rms_norm(&x, &w, &mut normed, cols, rows, eps)?;
9286 let mut sg = e.zeros(rows * cols)?;
9287 e.sigmoid(&z, &mut sg, rows * cols)?;
9288 let mut chain = e.zeros(rows * cols)?;
9289 e.mul(&normed, &sg, &mut chain, rows * cols)?;
9290 let mut fused = e.zeros(rows * cols)?;
9291 launch_rms_sigmul(e, &x, &w, &z, &mut fused, cols, rows, eps)?;
9292 let (chain_h, fused_h) = (e.dtoh(&chain)?, e.dtoh(&fused)?);
9293 for (i, (&a, &b)) in chain_h.iter().zip(&fused_h).enumerate() {
9294 if a.to_bits() != b.to_bits() {
9295 return Err(format!(
9296 "rms_sigmul oracle: idx {i} not bit-identical: chain {a:?} fused {b:?}"
9297 )
9298 .into());
9299 }
9300 }
9301 Ok(format!(
9302 "gdn-step kernel oracle: scan step twin worst rel {worst:.3e} over artifact + \
9303 hk32 geometries; rms_sigmul bit-identical to the norm/sigmoid/mul chain ({rows}x{cols})"
9304 ))
9305}
9306
9307pub fn gate_qmatvec_bf16(e: &Engine) -> Res<String> {
9308 let mut lcg = 0x9e37_79b9_u64;
9309 let mut next_u32 = move || -> u32 {
9310 lcg = lcg
9311 .wrapping_mul(6364136223846793005)
9312 .wrapping_add(1442695040888963407);
9313 (lcg >> 33) as u32
9314 };
9315 let mut worst = (0.0f32, 0.0f32);
9316 for (mode, batch, t, out_f, in_f, x_bstride) in [
9317 ("per_batch_x", 3usize, 2usize, 5usize, 48usize, 2 * 48usize),
9318 ("shared_x", 4, 3, 7, 16, 0usize),
9319 ] {
9320 let w_elems = batch * out_f * in_f;
9323 let mut w_bytes = Vec::with_capacity(w_elems * 2);
9324 let mut w_host = Vec::with_capacity(w_elems);
9325 for _ in 0..w_elems {
9326 let h = ((next_u32() % 0x4000) as u16) | (((next_u32() & 1) as u16) << 15);
9329 w_bytes.extend_from_slice(&h.to_le_bytes());
9330 w_host.push(f32::from_bits(u32::from(h) << 16));
9331 }
9332 let x_rows = if x_bstride == 0 { t } else { batch * t };
9333 let x_host: Vec<f32> = (0..x_rows * in_f)
9334 .map(|_| (next_u32() % 2000) as f32 / 1000.0 - 1.0)
9335 .collect();
9336 let w_dev = e.htod_bytes(&w_bytes)?;
9337 let x_dev = e.htod(&x_host)?;
9338 let mut y_dev = e.uninit(batch * t * out_f)?;
9339 launch_qmatvec_bf16w(
9340 e,
9341 &w_dev,
9342 &x_dev,
9343 &mut y_dev,
9344 in_f,
9345 out_f,
9346 t,
9347 batch,
9348 out_f * in_f,
9349 x_bstride,
9350 in_f,
9351 t * out_f,
9352 )?;
9353 let y = e.dtoh(&y_dev)?;
9354 for b in 0..batch {
9355 for tok in 0..t {
9356 let xrow = &x_host[b * x_bstride + tok * in_f..][..in_f];
9357 for o in 0..out_f {
9358 let wrow = &w_host[(b * out_f + o) * in_f..][..in_f];
9359 let mut want = 0.0f32;
9360 for i in 0..in_f {
9361 want += wrow[i] * xrow[i];
9362 }
9363 let got = y[(b * t + tok) * out_f + o];
9364 let abs = (want - got).abs();
9365 let rel = abs / want.abs().max(1.0);
9366 worst.0 = worst.0.max(abs);
9367 worst.1 = worst.1.max(rel);
9368 if rel > 1e-5 {
9369 return Err(format!(
9370 "bf16-matvec oracle: {mode} b {b} tok {tok} row {o}: want {want} \
9371 got {got} (rel {rel:.3e})"
9372 )
9373 .into());
9374 }
9375 }
9376 }
9377 }
9378 }
9379 {
9383 let (out_f, in_f, t) = (33usize, 64usize, 5usize);
9384 let w_elems = out_f * in_f;
9385 let mut w_bytes = Vec::with_capacity(w_elems * 2);
9386 for _ in 0..w_elems {
9387 let h = ((next_u32() % 0x4000) as u16) | (((next_u32() & 1) as u16) << 15);
9388 w_bytes.extend_from_slice(&h.to_le_bytes());
9389 }
9390 let x_host: Vec<f32> = (0..t * in_f)
9391 .map(|_| (next_u32() % 2000) as f32 / 1000.0 - 1.0)
9392 .collect();
9393 let w_dev = e.htod_bytes(&w_bytes)?;
9394 let x_dev = e.htod(&x_host)?;
9395 let mut y_grid = e.uninit(t * out_f)?;
9396 launch_qmatvec_bf16w(
9397 e,
9398 &w_dev,
9399 &x_dev,
9400 &mut y_grid,
9401 in_f,
9402 out_f,
9403 t,
9404 1,
9405 0,
9406 0,
9407 in_f,
9408 0,
9409 )?;
9410 let mut y_mt = e.uninit(t * out_f)?;
9411 launch_qmatvec_bf16w_mt(e, &w_dev, 0, &x_dev, &mut y_mt, in_f, out_f, t)?;
9412 let (a, b) = (e.dtoh(&y_grid)?, e.dtoh(&y_mt)?);
9413 for (i, (&x1, &x2)) in a.iter().zip(&b).enumerate() {
9414 if x1.to_bits() != x2.to_bits() {
9415 return Err(format!(
9416 "bf16-matvec mt oracle: idx {i}: grid {x1} vs mt {x2} NOT bit-identical"
9417 )
9418 .into());
9419 }
9420 }
9421 }
9422 {
9427 let (experts, out_f, in_f, n_sel) = (16usize, 24usize, 32usize, 6usize);
9428 let w_elems = experts * out_f * in_f;
9429 let mut w_bytes = Vec::with_capacity(w_elems * 2);
9430 for _ in 0..w_elems {
9431 let h = ((next_u32() % 0x4000) as u16) | (((next_u32() & 1) as u16) << 15);
9432 w_bytes.extend_from_slice(&h.to_le_bytes());
9433 }
9434 let sel_host: Vec<i32> = vec![7, 0, 15, 7, 3, 9]; let bank = e.htod_bytes(&w_bytes)?;
9436 let sel = e.htod_i32(&sel_host)?;
9437 for (label, x_rows, x_sstride) in [("shared-x", 1usize, 0usize), ("slot-x", n_sel, in_f)] {
9438 let x_host: Vec<f32> = (0..x_rows * in_f)
9439 .map(|_| (next_u32() % 2000) as f32 / 1000.0 - 1.0)
9440 .collect();
9441 let x_dev = e.htod(&x_host)?;
9442 let mut y_sel = e.uninit(n_sel * out_f)?;
9443 launch_qmatvec_bf16w_sel(
9444 e, &bank, &sel, 0, &x_dev, 0, x_sstride, &mut y_sel, n_sel, in_f, out_f,
9445 )?;
9446 let mut y_ref = e.uninit(n_sel * out_f)?;
9447 for (slot, &eid) in sel_host.iter().enumerate() {
9448 launch_qmatvec_bf16w_off_into(
9449 e,
9450 &bank,
9451 eid as usize * out_f,
9452 &x_dev,
9453 slot * x_sstride,
9454 &mut y_ref,
9455 slot * out_f,
9456 in_f,
9457 out_f,
9458 )?;
9459 }
9460 let (a, b) = (e.dtoh(&y_sel)?, e.dtoh(&y_ref)?);
9461 for (i, (&x1, &x2)) in a.iter().zip(&b).enumerate() {
9462 if x1.to_bits() != x2.to_bits() {
9463 return Err(format!(
9464 "bf16-matvec sel oracle ({label}): idx {i}: sel {x1} vs off_into {x2} \
9465 NOT bit-identical"
9466 )
9467 .into());
9468 }
9469 }
9470 }
9471 }
9472 Ok(format!(
9473 "bf16-matvec kernel oracle: worst abs {:.3e} rel {:.3e} over per-batch + shared-x \
9474 modes, batch>1, t>1, signed/denormal bf16; mt weight-shared twin BIT-IDENTICAL \
9475 at t 5; sel grouped twin BIT-IDENTICAL to the off_into chain (shared-x + slot-x, \
9476 duplicate slots)",
9477 worst.0, worst.1
9478 ))
9479}
9480
9481fn dequant_nvfp4_expert_f32(
9491 e: &Engine,
9492 codes: &CudaSlice<u8>,
9493 scales: &CudaSlice<u8>,
9494 macro_scale: f32,
9495 expert: usize,
9496 rows: usize,
9497 cols: usize,
9498) -> Res<CudaSlice<f32>> {
9499 let wbytes = rows * cols / 2;
9500 let sbytes = rows * cols / 16;
9501 let bf = e.alloc_u8(rows * cols * 2)?;
9502 let stream = e.gpu.stream();
9503 let wp = (codes.device_ptr(&stream).0 as usize + expert * wbytes) as *const c_void;
9504 let scp = (scales.device_ptr(&stream).0 as usize + expert * sbytes) as *const c_void;
9505 let dst = bf.device_ptr(&stream).0 as usize as *mut c_void;
9506 let rc = unsafe {
9507 crate::dsv4_ffi::memra_dsv4_nvfp4_deq_bf16(
9508 wp,
9509 scp,
9510 1.0, rows as i32,
9512 cols as i32,
9513 dst,
9514 stream.cu_stream() as *mut c_void,
9515 )
9516 };
9517 if rc != 0 {
9518 return Err(format!("memra_dsv4_nvfp4_deq_bf16 rc={rc}").into());
9519 }
9520 let mut out = e.bf16_to_f32(&bf.slice(0..rows * cols * 2), rows * cols)?;
9521 if macro_scale != 1.0 {
9522 e.scale_inplace(&mut out, macro_scale, rows * cols)?;
9523 }
9524 Ok(out)
9525}
9526
9527fn expect(weights: &ReferenceWeights, id: &TensorId) -> Res<ReferenceTensor> {
9530 weights
9531 .get(id)
9532 .cloned()
9533 .ok_or_else(|| format!("qwen4exp_gpu: missing weight {id:?}").into())
9534}
9535
9536fn family_id(key: String) -> TensorId {
9537 TensorId::Family {
9538 family: "qwen4_exp",
9539 key,
9540 }
9541}
9542
9543fn layer_id(index: u32, tensor: LayerTensor) -> TensorId {
9544 TensorId::Layer { index, tensor }
9545}
9546
9547fn upload(e: &Engine, tensor: &ReferenceTensor) -> Res<CudaSlice<f32>> {
9548 e.htod(&tensor.data)
9549}
9550
9551fn split_columns(data: &[f32], rows: usize, streams: usize, hidden: usize) -> Vec<Vec<f32>> {
9553 let wide = streams * hidden;
9554 (0..streams)
9555 .map(|s| {
9556 let mut out = Vec::with_capacity(rows * hidden);
9557 for row in 0..rows {
9558 out.extend_from_slice(
9559 &data[row * wide + s * hidden..row * wide + (s + 1) * hidden],
9560 );
9561 }
9562 out
9563 })
9564 .collect()
9565}
9566
9567fn split_rows(data: &[f32], streams: usize, hidden: usize, cols: usize) -> Vec<Vec<f32>> {
9569 (0..streams)
9570 .map(|s| data[s * hidden * cols..(s + 1) * hidden * cols].to_vec())
9571 .collect()
9572}
9573
9574fn load_gate(
9575 e: &Engine,
9576 weights: &ReferenceWeights,
9577 prefix: &str,
9578 sublayer: &str,
9579 streams: usize,
9580 hidden: usize,
9581 rank: usize,
9582 with_inject: bool,
9583) -> Res<GateW> {
9584 let wide = streams * hidden;
9585 let norm = expect(
9586 weights,
9587 &family_id(format!("{prefix}{sublayer}hc_norm.weight")),
9588 )?;
9589 let down = expect(
9590 weights,
9591 &family_id(format!("{prefix}{sublayer}input_mix_weight_down.weight")),
9592 )?;
9593 let up = expect(
9594 weights,
9595 &family_id(format!("{prefix}{sublayer}input_mix_weight_up.weight")),
9596 )?;
9597 if norm.data.len() != wide || down.data.len() != rank * wide || up.data.len() != wide * rank {
9598 return Err(format!("qwen4exp_gpu: gate {prefix}{sublayer} shape mismatch").into());
9599 }
9600 let norm_slices = split_rows(&norm.data, streams, hidden, 1);
9601 let down_slices = split_columns(&down.data, rank, streams, hidden);
9602 let up_slices = split_rows(&up.data, streams, hidden, rank);
9603 let stack = |slices: &[Vec<f32>]| -> Vec<f32> {
9606 let mut out = Vec::with_capacity(slices.len() * slices[0].len());
9607 for s in slices {
9608 out.extend_from_slice(s);
9609 }
9610 out
9611 };
9612 let down_b16 = bf16_twin(e, &stack(&down_slices), hidden)?;
9613 let up_b16 = bf16_twin(e, &stack(&up_slices), rank)?;
9614 let (inject, inject_b16) = if with_inject {
9615 let inject = expect(
9616 weights,
9617 &family_id(format!("{prefix}{sublayer}block_inject_weight.weight")),
9618 )?;
9619 if inject.data.len() != streams * wide {
9620 return Err(format!("qwen4exp_gpu: inject {prefix}{sublayer} shape mismatch").into());
9621 }
9622 (
9625 Some(e.htod(&inject.data)?),
9626 bf16_twin(e, &inject.data, hidden)?,
9627 )
9628 } else {
9629 (None, None)
9630 };
9631 Ok(GateW {
9632 norm_stack: e.htod(&stack(&norm_slices))?,
9633 norm: norm_slices
9634 .into_iter()
9635 .map(|v| e.htod(&v))
9636 .collect::<Result<_, _>>()?,
9637 down: down_slices
9638 .into_iter()
9639 .map(|v| e.htod(&v))
9640 .collect::<Result<_, _>>()?,
9641 up: up_slices
9642 .into_iter()
9643 .map(|v| e.htod(&v))
9644 .collect::<Result<_, _>>()?,
9645 inject,
9646 down_b16,
9647 up_b16,
9648 inject_b16,
9649 })
9650}
9651
9652#[derive(Default)]
9656pub struct ExternalParts {
9657 expert_banks: std::collections::BTreeMap<u32, ExpertBank>,
9658 ngram_tables: std::collections::BTreeMap<u32, NgramTable>,
9659}
9660
9661#[allow(clippy::too_many_arguments)]
9665fn build_layer_w(
9666 e: &Engine,
9667 weights: &ReferenceWeights,
9668 layer: &memra_gguf::model_plan::LayerPlan,
9669 prefix: &str,
9670 streams: usize,
9671 hidden: usize,
9672 rank: usize,
9673 bank_override: Option<ExpertBank>,
9674 table_override: Option<NgramTable>,
9675) -> Res<LayerW> {
9676 let ResidualTopology::GatedResidual { .. } = layer.residual else {
9677 return Err(format!("qwen4exp_gpu: layer {} is not gated-residual", layer.index).into());
9678 };
9679 let attn_gate = load_gate(
9680 e,
9681 weights,
9682 prefix,
9683 "attn_hyper_connection.",
9684 streams,
9685 hidden,
9686 rank,
9687 true,
9688 )?;
9689 let mlp_gate = load_gate(
9690 e,
9691 weights,
9692 prefix,
9693 "mlp_hyper_connection.",
9694 streams,
9695 hidden,
9696 rank,
9697 true,
9698 )?;
9699 let mixer = match &layer.attention {
9700 AttentionPlan::Full(attn) => {
9701 let overlay = layer.sparse_overlay.ok_or_else(|| {
9702 format!(
9703 "qwen4exp_gpu: QSA layer {} has no indexer overlay",
9704 layer.index
9705 )
9706 })?;
9707 let yarn = build_yarn(e, &attn.rope, Some(&overlay), layer.index)?;
9710 if attn.key_head_dim != attn.value_head_dim {
9714 return Err(format!(
9715 "qwen4exp_gpu: layer {} key_head_dim {} != value_head_dim {}",
9716 layer.index, attn.key_head_dim, attn.value_head_dim
9717 )
9718 .into());
9719 }
9720 let load_opt_norm = |tensor: LayerTensor| -> Res<Option<CudaSlice<f32>>> {
9721 match weights.get(&layer_id(layer.index, tensor)) {
9722 Some(t) => Ok(Some(e.htod(&t.data)?)),
9723 None if attn.qk_norm == TensorPresence::Required => {
9724 Err(format!("qwen4exp_gpu: layer {} missing qk norm", layer.index).into())
9725 }
9726 None => Ok(None),
9727 }
9728 };
9729 let wq_t = expect(weights, &layer_id(layer.index, LayerTensor::Query))?;
9730 let wk_t = expect(weights, &layer_id(layer.index, LayerTensor::Key))?;
9731 let wv_t = expect(weights, &layer_id(layer.index, LayerTensor::Value))?;
9732 let wo_t = expect(
9733 weights,
9734 &layer_id(layer.index, LayerTensor::AttentionOutput),
9735 )?;
9736 let o_in = (attn.query_heads * attn.key_head_dim) as usize;
9737 MixerW::Qsa(QsaW {
9738 attn: attn.clone(),
9739 overlay,
9740 yarn,
9741 proj_b16: bf16_stack_twin(e, &[&wq_t.data, &wk_t.data, &wv_t.data], hidden)?,
9742 wo_b16: bf16_twin(e, &wo_t.data, o_in)?,
9743 wq: upload(e, &wq_t)?,
9744 wk: upload(e, &wk_t)?,
9745 wv: upload(e, &wv_t)?,
9746 wo: upload(e, &wo_t)?,
9747 q_norm: load_opt_norm(LayerTensor::QueryNorm)?,
9748 k_norm: load_opt_norm(LayerTensor::KeyNorm)?,
9749 idx_proj: upload(
9750 e,
9751 &expect(
9752 weights,
9753 &family_id(format!("{prefix}self_attn.indexer.index_qk_proj.weight")),
9754 )?,
9755 )?,
9756 idx_q_norm: expect(
9757 weights,
9758 &family_id(format!("{prefix}self_attn.indexer.q_layernorm.weight")),
9759 )?
9760 .data,
9761 idx_k_norm: expect(
9762 weights,
9763 &family_id(format!("{prefix}self_attn.indexer.k_layernorm.weight")),
9764 )?
9765 .data,
9766 })
9767 }
9768 AttentionPlan::GatedDeltaNet(gdn) => {
9769 let qkv_t = expect(weights, &layer_id(layer.index, LayerTensor::GdnQkv))?;
9770 let z_t = expect(weights, &layer_id(layer.index, LayerTensor::GdnGate))?;
9771 let beta_t = expect(weights, &layer_id(layer.index, LayerTensor::GdnBeta))?;
9772 let alpha_t = expect(weights, &layer_id(layer.index, LayerTensor::GdnAlpha))?;
9773 let out_t = expect(weights, &layer_id(layer.index, LayerTensor::GdnOutput))?;
9774 let o_in = (gdn.value_heads * gdn.value_head_dim) as usize;
9775 MixerW::Gdn(GdnW {
9776 plan: *gdn,
9777 proj_b16: bf16_stack_twin(
9778 e,
9779 &[&qkv_t.data, &z_t.data, &beta_t.data, &alpha_t.data],
9780 hidden,
9781 )?,
9782 out_b16: bf16_twin(e, &out_t.data, o_in)?,
9783 qkv: upload(e, &qkv_t)?,
9784 z: upload(e, &z_t)?,
9785 beta: upload(e, &beta_t)?,
9786 alpha: upload(e, &alpha_t)?,
9787 conv_w: upload(
9788 e,
9789 &expect(weights, &layer_id(layer.index, LayerTensor::GdnConv1d))?,
9790 )?,
9791 a: upload(
9792 e,
9793 &expect(weights, &layer_id(layer.index, LayerTensor::GdnA))?,
9794 )?,
9795 dt: upload(
9796 e,
9797 &expect(weights, &layer_id(layer.index, LayerTensor::GdnDtBias))?,
9798 )?,
9799 norm: upload(
9800 e,
9801 &expect(weights, &layer_id(layer.index, LayerTensor::GdnNorm))?,
9802 )?,
9803 out: upload(e, &out_t)?,
9804 })
9805 }
9806 other => {
9807 return Err(format!(
9808 "qwen4exp_gpu: unsupported mixer {other:?} at layer {}",
9809 layer.index
9810 )
9811 .into());
9812 }
9813 };
9814 let MlpPlan::Moe(moe_plan) = &layer.mlp else {
9815 return Err(format!("qwen4exp_gpu: layer {} is not MoE", layer.index).into());
9816 };
9817 if !matches!(moe_plan.router, RouterPlan::Softmax) {
9818 return Err("qwen4exp_gpu: only the softmax router arm is implemented".into());
9819 }
9820 let shared = moe_plan
9821 .shared
9822 .as_ref()
9823 .ok_or("qwen4exp_gpu: missing shared expert plan")?;
9824 let bank = match bank_override {
9825 Some(bank) => bank,
9826 None => {
9827 let gate = expect(
9828 weights,
9829 &layer_id(layer.index, LayerTensor::MoeExpertGateBank),
9830 )?;
9831 let up = expect(
9832 weights,
9833 &layer_id(layer.index, LayerTensor::MoeExpertUpBank),
9834 )?;
9835 let down = expect(
9836 weights,
9837 &layer_id(layer.index, LayerTensor::MoeExpertDownBank),
9838 )?;
9839 let experts = moe_plan.expert_count as usize;
9840 let ff = moe_plan.expert_intermediate_size as usize;
9841 if gate.data.len() != experts * ff * hidden
9842 || up.data.len() != experts * ff * hidden
9843 || down.data.len() != experts * hidden * ff
9844 {
9845 return Err(format!(
9846 "qwen4exp_gpu: layer {} expert bank shape mismatch",
9847 layer.index
9848 )
9849 .into());
9850 }
9851 ExpertBank {
9852 gate: BankHalf::F32(e.htod(&gate.data)?),
9853 up: BankHalf::F32(e.htod(&up.data)?),
9854 down: BankHalf::F32(e.htod(&down.data)?),
9855 }
9856 }
9857 };
9858 let sh_gate_t = expect(weights, &layer_id(layer.index, LayerTensor::SharedMlpGate))?;
9859 let sh_up_t = expect(weights, &layer_id(layer.index, LayerTensor::SharedMlpUp))?;
9860 let sh_down_t = expect(weights, &layer_id(layer.index, LayerTensor::SharedMlpDown))?;
9861 let sff = shared.intermediate_size as usize;
9862 let router_t = expect(weights, &layer_id(layer.index, LayerTensor::MoeRouter))?;
9863 let moe = MoeW {
9864 plan: moe_plan.clone(),
9865 router_b16: bf16_twin(e, &router_t.data, hidden)?,
9866 router: upload(e, &router_t)?,
9867 bank,
9868 shared_gu_b16: bf16_stack_twin(e, &[&sh_gate_t.data, &sh_up_t.data], hidden)?,
9869 shared_down_b16: bf16_twin(e, &sh_down_t.data, sff)?,
9870 shared_gate: upload(e, &sh_gate_t)?,
9871 shared_up: upload(e, &sh_up_t)?,
9872 shared_down: upload(e, &sh_down_t)?,
9873 shared_input_gate: if shared.gated {
9874 Some(upload(
9875 e,
9876 &expect(
9877 weights,
9878 &layer_id(layer.index, LayerTensor::SharedMlpInputGate),
9879 )?,
9880 )?)
9881 } else {
9882 None
9883 },
9884 };
9885 let ple = match layer.ple.as_ref() {
9886 None => None,
9887 Some(ple_plan) => {
9888 let embed_dim = ple_plan.embed_dim as usize;
9889 let head_dim = ple_plan.head_embed_dim as usize;
9890 let wide = streams * hidden;
9891 let key_proj = expect(weights, &family_id(format!("{prefix}ple.key_proj.weight")))?;
9892 let conv_w = expect(weights, &family_id(format!("{prefix}ple.conv1d.weight")))?;
9893 if key_proj.data.len() != wide * embed_dim {
9894 return Err("qwen4exp_gpu: ple key_proj shape mismatch".into());
9895 }
9896 let norm_slices = |name: &str| -> Res<Vec<CudaSlice<f32>>> {
9897 let t = expect(weights, &family_id(format!("{prefix}ple.{name}.weight")))?;
9898 split_rows(&t.data, streams, hidden, 1)
9899 .into_iter()
9900 .map(|v| e.htod(&v))
9901 .collect::<Result<_, _>>()
9902 };
9903 let ints = |name: &str| -> Res<Vec<i64>> {
9904 let t = expect(
9905 weights,
9906 &family_id(format!("{prefix}ple.ple_embedding.{name}")),
9907 )?;
9908 t.ints
9909 .clone()
9910 .ok_or_else(|| "qwen4exp_gpu: n-gram buffer must be I64".into())
9911 };
9912 let table = match table_override {
9913 Some(table) => table,
9914 None => {
9915 let t = expect(
9916 weights,
9917 &family_id(format!("{prefix}ple.ple_embedding.ngram_embedding")),
9918 )?;
9919 if t.shape.len() != 2 || t.shape[1] != head_dim {
9920 return Err("qwen4exp_gpu: n-gram table shape mismatch".into());
9921 }
9922 NgramTable::F32(t.data)
9923 }
9924 };
9925 Some(PleW {
9926 plan: *ple_plan,
9927 key_proj: split_rows(&key_proj.data, streams, hidden, embed_dim)
9928 .into_iter()
9929 .map(|v| e.htod(&v))
9930 .collect::<Result<_, _>>()?,
9931 value_proj: upload(
9932 e,
9933 &expect(
9934 weights,
9935 &family_id(format!("{prefix}ple.value_proj.weight")),
9936 )?,
9937 )?,
9938 norm_key: norm_slices("norm_key")?,
9939 norm_query: norm_slices("norm_query")?,
9940 norm_conv: norm_slices("norm_conv")?,
9941 conv_w: split_rows(&conv_w.data, streams, hidden, ple_plan.conv_kernel as usize)
9942 .into_iter()
9943 .map(|v| e.htod(&v))
9944 .collect::<Result<_, _>>()?,
9945 multipliers: ints("layer_multipliers")?,
9946 sizes: ints("ngram_heads_vocab_sizes")?,
9947 offsets: ints("ngram_heads_offsets")?,
9948 table,
9949 })
9950 }
9951 };
9952 Ok(LayerW {
9953 index: layer.index,
9954 eps_attn: layer.pre_attention_norm.epsilon,
9955 eps_mlp: layer.pre_mlp_norm.epsilon,
9956 attn_gate,
9957 mlp_gate,
9958 mixer,
9959 moe,
9960 ple,
9961 })
9962}
9963
9964#[allow(clippy::too_many_arguments)]
9967fn build_mtp_w(
9968 e: &Engine,
9969 weights: &ReferenceWeights,
9970 block: &memra_gguf::model_plan::MtpBlockPlan,
9971 streams: usize,
9972 hidden: usize,
9973 rank: usize,
9974 bank_override: Option<ExpertBank>,
9975) -> Res<MtpW> {
9976 use memra_gguf::tensor_contract::MtpTensor;
9977 if block.input.fusion != memra_gguf::model_plan::MtpFusionPlan::SeparateProjections {
9978 return Err("qwen4exp_gpu: MTP block is not the separate-projections family".into());
9979 }
9980 let wide = streams * hidden;
9981 let depth = block.depth;
9982 let mtp_id = |tensor: MtpTensor| TensorId::Mtp { depth, tensor };
9983 let pre_e = expect(weights, &mtp_id(MtpTensor::EmbeddingNorm))?;
9984 let pre_h = expect(weights, &mtp_id(MtpTensor::HiddenNorm))?;
9985 let fc_e = expect(weights, &mtp_id(MtpTensor::EmbeddingProjection))?;
9986 let fc_h = expect(weights, &mtp_id(MtpTensor::HiddenProjection))?;
9987 if pre_e.data.len() != hidden
9988 || pre_h.data.len() != wide
9989 || fc_e.data.len() != hidden * hidden
9990 || fc_h.data.len() != hidden * hidden
9991 {
9992 return Err("qwen4exp_gpu: MTP fusion tensor shape mismatch".into());
9993 }
9994 let prefix = format!("mtp.layers.{depth}.");
9995 let layer = build_layer_w(
9996 e,
9997 weights,
9998 &block.layer,
9999 &prefix,
10000 streams,
10001 hidden,
10002 rank,
10003 bank_override,
10004 None,
10005 )?;
10006 let mixer = load_gate(
10007 e,
10008 weights,
10009 "mtp.hyper_connection_mixer.",
10010 "",
10011 streams,
10012 hidden,
10013 rank,
10014 false,
10015 )?;
10016 Ok(MtpW {
10017 eps_embed: block.input.embedding_norm.epsilon,
10018 eps_hidden: block.input.hidden_norm.epsilon,
10019 fc_embed_b16: bf16_twin(e, &fc_e.data, hidden)?,
10020 fc_hidden_b16: bf16_twin(e, &fc_h.data, hidden)?,
10021 pre_norm_embed: upload(e, &pre_e)?,
10022 pre_norm_hidden: upload(e, &pre_h)?,
10023 fc_embed: upload(e, &fc_e)?,
10024 fc_hidden: upload(e, &fc_h)?,
10025 layer,
10026 mixer,
10027 })
10028}
10029
10030impl Qwen4ExpGpu {
10031 pub fn from_reference_weights(
10035 e: &Engine,
10036 plan: &ModelPlan,
10037 weights: &ReferenceWeights,
10038 ) -> Res<Self> {
10039 Self::from_reference_weights_with(e, None, plan, weights, ExternalParts::default())
10040 }
10041
10042 fn from_reference_weights_with(
10043 e: &Engine,
10044 draft_e: Option<&Engine>,
10047 plan: &ModelPlan,
10048 weights: &ReferenceWeights,
10049 mut parts: ExternalParts,
10050 ) -> Res<Self> {
10051 let hidden = plan.hidden_size as usize;
10052 let vocab = plan.vocab_size as usize;
10053 let Some(mixer_plan) = plan.exit_mixer else {
10054 return Err("qwen4exp_gpu requires the gated-residual exit mixer".into());
10055 };
10056 let streams = mixer_plan.streams as usize;
10057 if streams > PLANE_SLOTS.len() {
10058 return Err("qwen4exp_gpu: hc_count exceeds the step-workspace slot table".into());
10059 }
10060 let rank = mixer_plan.bottleneck_rank as usize;
10061 if !plan.logits.is_empty() {
10062 return Err("qwen4exp_gpu: logits transforms are not part of this family".into());
10063 }
10064
10065 let embed = expect(weights, &TensorId::TokenEmbedding)?;
10066 if embed.data.len() != vocab * hidden {
10067 return Err("qwen4exp_gpu: embedding shape mismatch".into());
10068 }
10069 let (output, output_b16) = match weights.get(&TensorId::OutputProjection) {
10070 Some(tensor) => (e.htod(&tensor.data)?, bf16_twin(e, &tensor.data, hidden)?),
10071 None => (e.htod(&embed.data)?, bf16_twin(e, &embed.data, hidden)?),
10072 };
10073
10074 let mut layers = Vec::with_capacity(plan.layers.len());
10075 for layer in &plan.layers {
10076 let prefix = format!("trunk.layers.{}.", layer.index);
10077 layers.push(build_layer_w(
10078 e,
10079 weights,
10080 layer,
10081 &prefix,
10082 streams,
10083 hidden,
10084 rank,
10085 parts.expert_banks.remove(&layer.index),
10086 parts.ngram_tables.remove(&layer.index),
10087 )?);
10088 }
10089 let mtp = match plan.mtp_blocks.first() {
10093 Some(block)
10094 if weights
10095 .get(&TensorId::Mtp {
10096 depth: block.depth,
10097 tensor: memra_gguf::tensor_contract::MtpTensor::EmbeddingProjection,
10098 })
10099 .is_some() =>
10100 {
10101 Some(build_mtp_w(
10102 draft_e.unwrap_or(e),
10103 weights,
10104 block,
10105 streams,
10106 hidden,
10107 rank,
10108 parts.expert_banks.remove(&block.layer.index),
10109 )?)
10110 }
10111 _ => None,
10112 };
10113 let mtp_dev1 = match (draft_e, mtp.as_ref()) {
10116 (Some(de), Some(_)) => {
10117 let head_data: &[f32] = match weights.get(&TensorId::OutputProjection) {
10118 Some(tensor) => &tensor.data,
10119 None => &embed.data,
10120 };
10121 Some(MtpDev1 {
10122 dev: de.ctx().ordinal(),
10123 output: de.htod(head_data)?,
10124 output_b16: bf16_twin(de, head_data, hidden)?,
10125 })
10126 }
10127 (Some(_), None) => {
10128 return Err(
10129 "qwen4exp_gpu: a draft engine was given but no mtp.* rows were \
10130 materialized (LoadOptions::load_mtp)"
10131 .into(),
10132 );
10133 }
10134 _ => None,
10135 };
10136 let exit_mixer = load_gate(
10137 e,
10138 weights,
10139 "trunk.hyper_connection_mixer.",
10140 "",
10141 streams,
10142 hidden,
10143 rank,
10144 false,
10145 )?;
10146 Ok(Self {
10147 plan: plan.clone(),
10148 hidden,
10149 streams,
10150 vocab,
10151 embed_host: embed.data,
10152 output,
10153 output_b16,
10154 layers,
10155 exit_mixer,
10156 exit_eps: plan.output_norm.epsilon,
10157 mtp,
10158 mtp_dev1,
10159 draft_trim: None,
10160 draft_trim_parked: None,
10161 chain_embed: None,
10162 })
10163 }
10164
10165 pub fn build_draft_trim(&mut self, e: &Engine, ids: &[u32]) -> Res<()> {
10173 self.check_draft_engine(e)?;
10176 let n = ids.len();
10177 if n == 0 || n > self.vocab {
10178 return Err(format!("qwen4exp_gpu: draft trim wants 1..={} ids", self.vocab).into());
10179 }
10180 let mut seen = vec![false; self.vocab];
10181 for &id in ids {
10182 let id = id as usize;
10183 if id >= self.vocab {
10184 return Err(format!("qwen4exp_gpu: draft trim id {id} out of vocab").into());
10185 }
10186 if std::mem::replace(&mut seen[id], true) {
10187 return Err(format!("qwen4exp_gpu: draft trim id {id} repeats").into());
10188 }
10189 }
10190 let hidden = self.hidden;
10191 let (src_f32, src_b16) = match self.mtp_dev1.as_ref() {
10192 Some(d) => (&d.output, d.output_b16.as_ref()),
10193 None => (&self.output, self.output_b16.as_ref()),
10194 };
10195 let (head_b16, head) = match src_b16 {
10199 Some(full) => {
10200 let mut trim = e.alloc_u8_uninit(n * hidden * 2)?;
10201 for (row, &id) in ids.iter().enumerate() {
10202 e.copy_u8_range_into(
10203 &mut trim,
10204 row * hidden * 2,
10205 full,
10206 id as usize * hidden * 2,
10207 hidden * 2,
10208 )?;
10209 }
10210 (Some(trim), None)
10211 }
10212 None => {
10213 let mut head = e.uninit(n * hidden)?;
10214 for (row, &id) in ids.iter().enumerate() {
10215 e.copy_range_into(
10216 &mut head,
10217 row * hidden,
10218 src_f32,
10219 id as usize * hidden,
10220 hidden,
10221 )?;
10222 }
10223 (None, Some(head))
10224 }
10225 };
10226 self.draft_trim = Some(DraftTrim {
10227 n,
10228 d2t: ids.to_vec(),
10229 head,
10230 head_b16,
10231 });
10232 self.draft_trim_parked = None;
10233 Ok(())
10234 }
10235
10236 pub fn set_draft_trim(&mut self, on: bool) {
10239 if on {
10240 if let Some(t) = self.draft_trim_parked.take() {
10241 self.draft_trim = Some(t);
10242 }
10243 } else if let Some(t) = self.draft_trim.take() {
10244 self.draft_trim_parked = Some(t);
10245 }
10246 }
10247
10248 pub fn clear_draft_trim(&mut self) {
10250 self.draft_trim = None;
10251 self.draft_trim_parked = None;
10252 }
10253
10254 pub fn arm_spec_devchain(&mut self, de: &Engine) -> Res<()> {
10261 self.check_draft_engine(de)?;
10262 let hidden = self.hidden;
10263 let (rows, for_trim) = match self.draft_trim.as_ref() {
10264 Some(tr) => (tr.n, true),
10265 None => (self.vocab, false),
10266 };
10267 let src_row = |r: usize| -> &[f32] {
10268 let id = match self.draft_trim.as_ref() {
10269 Some(tr) => tr.d2t[r] as usize,
10270 None => r,
10271 };
10272 &self.embed_host[id * hidden..(id + 1) * hidden]
10273 };
10274 let clean = (0..rows).all(|r| src_row(r).iter().all(|x| x.to_bits() & 0xFFFF == 0));
10277 let (bytes, qt, row_bytes) = if clean {
10278 let mut b = vec![0u8; rows * hidden * 2];
10279 for r in 0..rows {
10280 for (j, &x) in src_row(r).iter().enumerate() {
10281 let h = (x.to_bits() >> 16) as u16;
10282 b[(r * hidden + j) * 2..(r * hidden + j) * 2 + 2]
10283 .copy_from_slice(&h.to_le_bytes());
10284 }
10285 }
10286 (b, crate::QT_BF16, hidden * 2)
10287 } else {
10288 let mut b = vec![0u8; rows * hidden * 4];
10289 for r in 0..rows {
10290 for (j, &x) in src_row(r).iter().enumerate() {
10291 b[(r * hidden + j) * 4..(r * hidden + j) * 4 + 4]
10292 .copy_from_slice(&x.to_le_bytes());
10293 }
10294 }
10295 (b, crate::QT_F32, hidden * 4)
10296 };
10297 let table = de.upload_u8(&bytes)?;
10298 println!(
10299 "[qwen4exp-spec] deferred-chain embed table armed: {} rows x {hidden} ({}, {:.1} MiB, dev {}{})",
10300 rows,
10301 if clean {
10302 "bf16 bit-clean"
10303 } else {
10304 "f32 fallback"
10305 },
10306 (rows * row_bytes) as f64 / (1024.0 * 1024.0),
10307 de.ctx().ordinal(),
10308 if for_trim { ", trim-rank order" } else { "" },
10309 );
10310 self.chain_embed = Some(ChainEmbed {
10311 table,
10312 qt,
10313 row_bytes,
10314 rows,
10315 for_trim,
10316 dev: de.ctx().ordinal(),
10317 });
10318 Ok(())
10319 }
10320
10321 pub fn clear_spec_devchain(&mut self) {
10323 self.chain_embed = None;
10324 }
10325
10326 pub fn draft_logits_width(&self) -> usize {
10329 match self.draft_trim.as_ref() {
10330 Some(t) => t.n,
10331 None => self.vocab,
10332 }
10333 }
10334
10335 fn draft_token(&self, row: u32) -> Res<u32> {
10338 match self.draft_trim.as_ref() {
10339 Some(t) => t
10340 .d2t
10341 .get(row as usize)
10342 .copied()
10343 .ok_or_else(|| format!("qwen4exp_gpu: draft row {row} outside the trim").into()),
10344 None => Ok(row),
10345 }
10346 }
10347
10348 pub fn trunk_f32_diet(&mut self, e: &Engine) -> Res<usize> {
10357 if !trunk_bf16_on() || !hc_fused_gate_on() {
10358 return Err(
10359 "qwen4exp_gpu: trunk_f32_diet requires the trunk-bf16 + fused-gate seams ON \
10360 (the bf16 paths must be the ones serving)"
10361 .into(),
10362 );
10363 }
10364 let mut freed = 0usize;
10365 fn stub(e: &Engine, s: &mut CudaSlice<f32>, freed: &mut usize) -> Res<()> {
10366 if s.len() > 1 {
10367 *freed += s.len() * 4;
10368 *s = e.zeros(1)?;
10369 }
10370 Ok(())
10371 }
10372 fn diet_gate(e: &Engine, g: &mut GateW, freed: &mut usize) -> Res<()> {
10373 if g.down_b16.is_none()
10374 || g.up_b16.is_none()
10375 || (g.inject.is_some() && g.inject_b16.is_none())
10376 {
10377 return Ok(()); }
10379 for s in g.down.iter_mut() {
10380 stub(e, s, freed)?;
10381 }
10382 for s in g.up.iter_mut() {
10383 stub(e, s, freed)?;
10384 }
10385 if let Some(inj) = g.inject.as_mut() {
10386 stub(e, inj, freed)?;
10387 }
10388 Ok(())
10389 }
10390 for layer in self.layers.iter_mut() {
10391 diet_gate(e, &mut layer.attn_gate, &mut freed)?;
10392 diet_gate(e, &mut layer.mlp_gate, &mut freed)?;
10393 match &mut layer.mixer {
10394 MixerW::Qsa(q) => {
10395 if q.proj_b16.is_some() {
10396 stub(e, &mut q.wq, &mut freed)?;
10397 stub(e, &mut q.wk, &mut freed)?;
10398 stub(e, &mut q.wv, &mut freed)?;
10399 }
10400 if q.wo_b16.is_some() {
10401 stub(e, &mut q.wo, &mut freed)?;
10402 }
10403 }
10404 MixerW::Gdn(g) => {
10405 if g.proj_b16.is_some() {
10406 stub(e, &mut g.qkv, &mut freed)?;
10407 stub(e, &mut g.z, &mut freed)?;
10408 stub(e, &mut g.beta, &mut freed)?;
10409 stub(e, &mut g.alpha, &mut freed)?;
10410 }
10411 if g.out_b16.is_some() {
10412 stub(e, &mut g.out, &mut freed)?;
10413 }
10414 }
10415 }
10416 let moe = &mut layer.moe;
10417 if moe.router_b16.is_some() {
10418 stub(e, &mut moe.router, &mut freed)?;
10419 }
10420 if moe.shared_gu_b16.is_some() {
10421 stub(e, &mut moe.shared_gate, &mut freed)?;
10422 stub(e, &mut moe.shared_up, &mut freed)?;
10423 }
10424 if moe.shared_down_b16.is_some() {
10425 stub(e, &mut moe.shared_down, &mut freed)?;
10426 }
10427 }
10428 diet_gate(e, &mut self.exit_mixer, &mut freed)?;
10429 if self.output_b16.is_some() {
10430 stub(e, &mut self.output, &mut freed)?;
10431 }
10432 Ok(freed)
10433 }
10434
10435 pub fn alloc_state(&self, e: &Engine, capacity: usize) -> Res<Qwen4ExpState> {
10436 self.alloc_state_reserve(e, capacity, capacity, None)
10437 }
10438
10439 pub fn alloc_state_reserve(
10444 &self,
10445 e: &Engine,
10446 capacity: usize,
10447 reserve: usize,
10448 kv_engine: Option<&Engine>,
10449 ) -> Res<Qwen4ExpState> {
10450 let kv_e = kv_engine.unwrap_or(e);
10451 if kv_e.ctx().ordinal() != e.ctx().ordinal() {
10457 let limit = peer_kv_max_cap();
10458 if capacity > limit {
10459 return Err(format!(
10460 "qwen4exp_gpu: peer-resident QSA KV refused — capacity {capacity} rows on \
10461 device {} while the attention runs on device {} (ceiling {limit} rows, \
10462 MEMRA_Q4E_PEER_KV_MAX_CAP). The block-list form is the only read path for a \
10463 quantized cache and it is a scatter reader (q4e_sdpa_blocklist_q8q5 phase 1 \
10464 is thread-per-position: 32 lanes on 32 rows, 32 sectors per load \
10465 instruction). Peer memory is not cached in the reading card's L2, so at this \
10466 depth ONE 2,048-token prefill chunk asks ~523 GB across the link and the run \
10467 never finishes — it does not deadlock, it just never arrives. Keep the QSA KV \
10468 on the compute card: it is 10,368 B/row across the 12 QSA layers (2.7 GiB at \
10469 262,144), while the allocation that forces a second card is the MTP draft \
10470 state (~17.6 GiB), which --mtp-dev1 / load_from_dir_dev1 already places \
10471 there.",
10472 kv_e.ctx().ordinal(),
10473 e.ctx().ordinal(),
10474 )
10475 .into());
10476 }
10477 }
10478 let mut layers = Vec::with_capacity(self.layers.len());
10479 for layer in &self.layers {
10480 let mixer = match &layer.mixer {
10481 MixerW::Qsa(qsa) => {
10482 let kv_width = qsa.attn.kv_heads as usize * qsa.attn.key_head_dim as usize;
10483 let v_width = qsa.attn.kv_heads as usize * qsa.attn.value_head_dim as usize;
10484 let kv = if kv_quant_on() {
10487 QsaKvStore::Q8Q5 {
10488 k: kv_e.alloc_u8(capacity * q8_row_bytes(kv_width))?,
10489 v: kv_e.alloc_u8(capacity * q5_row_bytes(v_width))?,
10490 }
10491 } else {
10492 QsaKvStore::F32 {
10493 k: kv_e.zeros(capacity * kv_width)?,
10494 v: kv_e.zeros(capacity * v_width)?,
10495 }
10496 };
10497 MixerState::Qsa {
10498 kv,
10499 raw_keys: IdxRawCache::new(idxq_mode()),
10500 pooled_keys: Vec::new(),
10501 pooled_dev: None,
10502 pooled_dev_rows: 0,
10503 raw_dev: None,
10504 raw_dev_rows: 0,
10505 idx_audit: (idxq_mode() != IdxQMode::F32 && idxq_audit_on()).then(|| {
10506 Box::new(IdxAudit {
10507 raw_f32: IdxRawCache::F32(Vec::new()),
10508 pooled_f32: Vec::new(),
10509 })
10510 }),
10511 }
10512 }
10513 MixerW::Gdn(gdn) => {
10514 let p = &gdn.plan;
10515 let conv_dim = 2 * (p.key_heads * p.key_head_dim) as usize
10516 + (p.value_heads * p.value_head_dim) as usize;
10517 let pad = p.conv_kernel as usize - 1;
10518 MixerState::Gdn {
10519 conv: e.zeros(pad * conv_dim)?,
10520 state: e
10521 .zeros((p.value_heads * p.value_head_dim * p.key_head_dim) as usize)?,
10522 }
10523 }
10524 };
10525 let ple = match layer.ple.as_ref() {
10526 None => None,
10527 Some(ple) => {
10528 let pad = (ple.plan.conv_kernel as usize - 1) * ple.plan.max_ngram as usize;
10529 let mut conv_hist = Vec::with_capacity(self.streams);
10530 for _ in 0..self.streams {
10531 conv_hist.push(e.zeros(pad * self.hidden)?);
10532 }
10533 Some(PleState {
10534 conv_hist,
10535 ngram_ids: Vec::new(),
10536 ngram_history: Vec::new(),
10537 ngram_last_eos: -1,
10538 })
10539 }
10540 };
10541 layers.push(LayerState { mixer, ple });
10542 }
10543 Ok(Qwen4ExpState {
10544 pos: 0,
10545 capacity,
10546 reserve,
10547 tokens: Vec::new(),
10548 layers,
10549 ws: StepPool::default(),
10550 graphs: StepGraphs::default(),
10551 tp2: None,
10552 verify: None,
10553 })
10554 }
10555
10556 pub fn prefill(&self, e: &Engine, ids: &[u32], state: &mut Qwen4ExpState) -> Res<Vec<f32>> {
10558 self.forward(e, ids, state, None)
10559 }
10560
10561 pub fn prefill_extend(
10568 &self,
10569 e: &Engine,
10570 ids: &[u32],
10571 state: &mut Qwen4ExpState,
10572 chunk: usize,
10573 ) -> Res<Vec<f32>> {
10574 if ids.is_empty() || chunk == 0 {
10575 return Err("qwen4exp_gpu: prefill_extend needs ids and a chunk size".into());
10576 }
10577 let mut last = Vec::new();
10578 for piece in ids.chunks(chunk) {
10579 let is_last =
10580 piece.as_ptr() as usize + piece.len() * 4 == ids.as_ptr() as usize + ids.len() * 4;
10581 let head = if is_last {
10582 HeadMode::LastRow
10583 } else {
10584 HeadMode::Skip
10585 };
10586 last = self.forward_with(e, piece, state, None, head)?;
10587 }
10588 Ok(last)
10589 }
10590
10591 pub fn decode_step(&self, e: &Engine, token: u32, state: &mut Qwen4ExpState) -> Res<Vec<f32>> {
10593 self.forward(e, &[token], state, None)
10594 }
10595
10596 pub fn prefill_captured(
10599 &self,
10600 e: &Engine,
10601 ids: &[u32],
10602 state: &mut Qwen4ExpState,
10603 ) -> Res<(Vec<f32>, PrefillCapture)> {
10604 let mut capture = PrefillCapture {
10605 layer_wide: Vec::with_capacity(self.layers.len()),
10606 exit_mixed: Vec::new(),
10607 };
10608 let logits = self.forward(e, ids, state, Some(&mut capture))?;
10609 Ok((logits, capture))
10610 }
10611
10612 fn planes_to_wide(&self, e: &Engine, planes: &[CudaSlice<f32>], t: usize) -> Res<Vec<f32>> {
10615 let hidden = self.hidden;
10616 let wide = self.streams * hidden;
10617 let mut out = vec![0.0f32; t * wide];
10618 for (s, plane) in planes.iter().enumerate() {
10619 let host = e.dtoh_view(&plane.slice(0..t * hidden))?;
10621 for row in 0..t {
10622 out[row * wide + s * hidden..row * wide + (s + 1) * hidden]
10623 .copy_from_slice(&host[row * hidden..(row + 1) * hidden]);
10624 }
10625 }
10626 Ok(out)
10627 }
10628
10629 fn forward(
10630 &self,
10631 e: &Engine,
10632 ids: &[u32],
10633 state: &mut Qwen4ExpState,
10634 capture: Option<&mut PrefillCapture>,
10635 ) -> Res<Vec<f32>> {
10636 self.forward_with(e, ids, state, capture, HeadMode::All)
10637 }
10638
10639 fn forward_with(
10640 &self,
10641 e: &Engine,
10642 ids: &[u32],
10643 state: &mut Qwen4ExpState,
10644 mut capture: Option<&mut PrefillCapture>,
10645 head: HeadMode,
10646 ) -> Res<Vec<f32>> {
10647 let t = ids.len();
10648 let hidden = self.hidden;
10649 if t == 0 {
10650 return Err("qwen4exp_gpu: empty input".into());
10651 }
10652 if head != HeadMode::All {
10653 if capture.is_some() {
10659 return Err("qwen4exp_gpu: prefill capture wants every logits row".into());
10660 }
10661 if let Some(v) = state.verify.as_ref()
10662 && (t == 1 || t <= v.k_cap)
10663 {
10664 return Err(
10665 "qwen4exp_gpu: head-skipping forward on a verify-exact chunk shape".into(),
10666 );
10667 }
10668 }
10669 if state.pos + t > state.capacity {
10670 return Err("qwen4exp_gpu: state capacity exceeded".into());
10671 }
10672 if state.tp2.is_some() {
10673 return Err(
10674 "qwen4exp_gpu: state already decoded in TP2 mode; single-card forward \
10675 requires a fresh state (the half-state migration is one-way)"
10676 .into(),
10677 );
10678 }
10679 let base_pos = state.pos;
10680 state.tokens.extend_from_slice(ids);
10681 if t > 1 {
10684 state.graphs = StepGraphs::default();
10685 }
10686 let graphs_mode = t == 1
10693 && decode_graphs_on()
10694 && step_ws_on()
10695 && hc_fused_gate_on()
10696 && !prof::on()
10697 && capture.is_none()
10698 && state.verify.is_none();
10699 let tokens = &state.tokens;
10700 let ws = &mut state.ws;
10701 let verify = state.verify.as_mut();
10705 let (exact, vfused, stash_gdn, stash_ple, stash_wide, argmax_sink, last_row_only) =
10706 match verify {
10707 Some(v) => {
10708 let vchunk = base_pos > 0 && t > 1 && t <= v.k_cap;
10721 let vfused = vchunk && verify_fused_on();
10722 let exact = vchunk && !vfused;
10723 let amx_t1 = t == 1 && v.want_argmax_t1;
10727 if exact {
10728 v.chunk = Some((base_pos, t));
10729 v.argmax.clear();
10730 } else if (vfused || amx_t1) && v.want_argmax {
10731 v.argmax.clear();
10732 }
10733 if vfused {
10734 v.fused_chunk = Some((base_pos, t));
10738 }
10739 (
10740 exact,
10741 vfused,
10742 Some(&mut v.gdn),
10743 Some(&mut v.ple),
10744 Some((&mut v.wide, v.ring_rows)),
10745 if (exact || vfused || amx_t1) && v.want_argmax {
10749 Some((&mut v.argmax, &mut v.toks))
10750 } else {
10751 None
10752 },
10753 v.last_row_only && t > 1 && !exact && !vfused,
10754 )
10755 }
10756 None => (false, false, None, None, None, None, false),
10757 };
10758 let mut stash_gdn = stash_gdn;
10759 let mut stash_ple = stash_ple;
10760 let cap = state.reserve.max(t);
10766
10767 let mut planes = prof_section(e, "entry.embed", || {
10770 let mut embedded = vec![0.0f32; t * hidden];
10771 for (row, &token) in ids.iter().enumerate() {
10772 let token = token as usize;
10773 if token >= self.vocab {
10774 return Err(format!("qwen4exp_gpu: token {token} out of range").into());
10775 }
10776 embedded[row * hidden..(row + 1) * hidden]
10777 .copy_from_slice(&self.embed_host[token * hidden..(token + 1) * hidden]);
10778 }
10779 let embedded_dev = ws.take_f32_h2d(e, "entry.embed", &embedded, cap * hidden)?;
10780 let mut planes: Vec<CudaSlice<f32>> = Vec::with_capacity(self.streams);
10781 for s in 0..self.streams {
10782 let mut plane = ws.take_f32(e, PLANE_SLOTS[s], t * hidden, cap * hidden)?;
10783 e.copy_into(&mut plane, 0, &embedded_dev, t * hidden)?;
10784 planes.push(plane);
10785 }
10786 ws.put_f32("entry.embed", embedded_dev);
10787 Ok(planes)
10788 })?;
10789
10790 let ptr_vals: Vec<u64> = {
10794 let stream = e.gpu.stream();
10795 planes.iter().map(|p| p.device_ptr(&stream).0).collect()
10796 };
10797 let ptrs = ws.take_u64_h2d(e, "hc.ptrs", &ptr_vals, 0)?;
10798
10799 if graphs_mode {
10800 if state.graphs.warm {
10801 return self.forward_graphs_tail(e, state, planes, ptrs, base_pos);
10802 }
10803 state.graphs.warm = true;
10806 }
10807
10808 for (li, (layer, lstate)) in self.layers.iter().zip(state.layers.iter_mut()).enumerate() {
10809 if let (Some(ple), Some(ple_state)) = (layer.ple.as_ref(), lstate.ple.as_mut()) {
10810 let ps = if exact {
10811 stash_ple
10812 .as_mut()
10813 .and_then(|v| v.get_mut(li))
10814 .and_then(|s| s.as_mut())
10815 } else {
10816 None
10817 };
10818 self.ple_block(
10819 e,
10820 layer,
10821 ple,
10822 &ple.table,
10823 ple_state,
10824 &mut planes,
10825 tokens,
10826 t,
10827 exact,
10828 ps,
10829 )?;
10830 }
10831 let (mixed, inject) = prof_section(e, "hyper.read", || {
10832 self.gate_read(
10833 e,
10834 ws,
10835 &ptrs,
10836 &layer.attn_gate,
10837 &planes,
10838 t,
10839 layer.eps_attn,
10840 exact,
10841 )
10842 })?;
10843 let block_out = match &layer.mixer {
10844 MixerW::Qsa(qsa) => self.qsa_forward(
10845 e,
10846 ws,
10847 layer,
10848 qsa,
10849 &mixed,
10850 &mut lstate.mixer,
10851 base_pos,
10852 t,
10853 0,
10854 exact,
10855 )?,
10856 MixerW::Gdn(gdn) => {
10857 let gs = if exact {
10858 stash_gdn
10859 .as_mut()
10860 .and_then(|v| v.get_mut(li))
10861 .and_then(|s| s.as_mut())
10862 } else {
10863 None
10864 };
10865 self.gdn_forward(e, ws, layer, gdn, &mixed, &mut lstate.mixer, t, gs)?
10866 }
10867 };
10868 ws.put_f32("hc.mixed", mixed);
10869 prof_section(e, "hyper.write", || {
10870 self.gate_write(e, &mut planes, &ptrs, &block_out, &inject, t)
10871 })?;
10872 ws.put_f32("mixer.out", block_out);
10873 put_inject(ws, inject);
10874 let (mixed, inject) = prof_section(e, "hyper.read", || {
10875 self.gate_read(
10876 e,
10877 ws,
10878 &ptrs,
10879 &layer.mlp_gate,
10880 &planes,
10881 t,
10882 layer.eps_mlp,
10883 exact,
10884 )
10885 })?;
10886 let grouped = exact || vfused || head != HeadMode::All || prefill_grouped_all_on();
10904 let mlp = self.moe_forward(e, ws, &layer.moe, &mixed, t, grouped, layer.index)?;
10905 ws.put_f32("hc.mixed", mixed);
10906 prof_section(e, "hyper.write", || {
10907 self.gate_write(e, &mut planes, &ptrs, &mlp, &inject, t)
10908 })?;
10909 ws.put_f32("moe.out", mlp);
10910 put_inject(ws, inject);
10911 if let Some(capture) = capture.as_deref_mut() {
10912 capture.layer_wide.push(self.planes_to_wide(e, &planes, t)?);
10913 }
10914 }
10915
10916 if let Some((wide_buf, ring_rows)) = stash_wide {
10920 let wide = self.streams * hidden;
10921 for (s, plane) in planes.iter().enumerate() {
10922 for tok in 0..t {
10923 e.copy_range_into(
10924 wide_buf,
10925 ((base_pos + tok) % ring_rows) * wide + s * hidden,
10926 plane,
10927 tok * hidden,
10928 hidden,
10929 )?;
10930 }
10931 }
10932 }
10933
10934 if head == HeadMode::Skip {
10937 ws.put_u64("hc.ptrs", ptrs);
10938 state.pos += t;
10939 for (s, plane) in planes.into_iter().enumerate() {
10940 ws.put_f32(PLANE_SLOTS[s], plane);
10941 }
10942 return Ok(Vec::new());
10943 }
10944
10945 let x = prof_section(e, "exit.mixer", || {
10947 Ok(self
10948 .gate_read_inner(
10949 e,
10950 ws,
10951 &ptrs,
10952 &self.exit_mixer,
10953 &planes,
10954 t,
10955 self.exit_eps,
10956 false,
10957 exact,
10958 )?
10959 .0)
10960 })?;
10961 ws.put_u64("hc.ptrs", ptrs);
10962 if let Some(capture) = capture.as_deref_mut() {
10963 capture.exit_mixed = e.dtoh(&x)?;
10964 }
10965 let head_rows = if head == HeadMode::LastRow { 1 } else { t };
10968 let logits = prof_section(e, "lm_head", || {
10969 let mut logits =
10970 ws.take_f32(e, "logits", head_rows * self.vocab, head_rows * self.vocab)?;
10971 let x_head = if head == HeadMode::LastRow {
10972 let mut last = ws.take_f32(e, "exit.last", hidden, hidden)?;
10973 e.copy_range_into(&mut last, 0, &x, (t - 1) * hidden, hidden)?;
10974 last
10975 } else {
10976 x
10977 };
10978 linear_trunk_into(
10979 e,
10980 &self.output,
10981 &self.output_b16,
10982 &x_head,
10983 &mut logits,
10984 head_rows,
10985 hidden,
10986 self.vocab,
10987 )?;
10988 ws.put_f32(
10989 if head == HeadMode::LastRow {
10990 "exit.last"
10991 } else {
10992 "hc.mixed"
10993 },
10994 x_head,
10995 );
10996 Ok(logits)
10997 })?;
10998 state.pos += t;
10999 if head == HeadMode::LastRow {
11000 let out = prof_section(e, "logits.dtoh", || {
11001 Ok(e.dtoh_view(&logits.slice(0..self.vocab))?)
11002 })?;
11003 ws.put_f32("logits", logits);
11004 for (s, plane) in planes.into_iter().enumerate() {
11005 ws.put_f32(PLANE_SLOTS[s], plane);
11006 }
11007 return Ok(out);
11008 }
11009 let out = if let Some((argmax_rows, toks)) = argmax_sink {
11012 prof_section(e, "logits.argmax", || {
11013 for row in 0..t {
11014 e.argmax_token_device_col(&logits, row, self.vocab, toks, row)?;
11015 }
11016 let host = e.gpu.stream().clone_dtoh(&toks.slice(0..t))?;
11017 argmax_rows.extend_from_slice(&host);
11018 Ok(Vec::new())
11019 })?
11020 } else if last_row_only {
11021 prof_section(e, "logits.dtoh", || {
11024 Ok(e.dtoh_view(&logits.slice((t - 1) * self.vocab..t * self.vocab))?)
11025 })?
11026 } else {
11027 prof_section(e, "logits.dtoh", || {
11028 Ok(e.dtoh_view(&logits.slice(0..t * self.vocab))?)
11029 })?
11030 };
11031 ws.put_f32("logits", logits);
11032 for (s, plane) in planes.into_iter().enumerate() {
11033 ws.put_f32(PLANE_SLOTS[s], plane);
11034 }
11035 Ok(out)
11036 }
11037
11038 #[allow(clippy::too_many_arguments)]
11042 fn gate_read(
11043 &self,
11044 e: &Engine,
11045 ws: &mut StepPool,
11046 ptrs: &CudaSlice<u64>,
11047 gate: &GateW,
11048 planes: &[CudaSlice<f32>],
11049 t: usize,
11050 eps: f32,
11051 exact: bool,
11052 ) -> Res<(CudaSlice<f32>, InjectOut)> {
11053 self.gate_read_inner(e, ws, ptrs, gate, planes, t, eps, true, exact)
11054 }
11055
11056 #[allow(clippy::too_many_arguments)]
11057 fn gate_read_inner(
11058 &self,
11059 e: &Engine,
11060 ws: &mut StepPool,
11061 ptrs: &CudaSlice<u64>,
11062 gate: &GateW,
11063 planes: &[CudaSlice<f32>],
11064 t: usize,
11065 eps: f32,
11066 with_inject: bool,
11067 exact: bool,
11072 ) -> Res<(CudaSlice<f32>, InjectOut)> {
11073 if !hc_fused_gate_on() {
11074 return self.gate_read_legacy(e, ws, gate, planes, t, eps, with_inject);
11075 }
11076 let hidden = self.hidden;
11077 let streams = self.streams;
11078 let rank = gate_rank(gate, hidden, streams)?;
11079 let micro_norm = micro_norm_on();
11080 let micro_inj = micro_inj_on();
11081 if hc_diet_on()
11085 && (t == 1 || exact)
11086 && trunk_bf16_on()
11087 && micro_inj
11088 && hidden % 8 == 0
11089 && rank % 8 == 0
11090 && gate.down_b16.is_some()
11091 && gate.up_b16.is_some()
11092 && (!with_inject || gate.inject_b16.is_some())
11093 {
11094 let mut parts = ws.take_f32(e, "hc.parts", t * streams * rank, 0)?;
11095 let mut injp = ws.take_f32(e, "hc.injp", t * streams * streams, 0)?;
11096 let mut inv = ws.take_f32(e, "hc.inv", t * streams, 0)?;
11097 let winj = if with_inject {
11098 gate.inject_b16.as_ref()
11099 } else {
11100 None
11101 };
11102 let mt = t > 1 && verify_mt_on() && (2..=12).contains(&t);
11106 if mt {
11107 launch_hc_diet_stage0_mt(e, ptrs, &mut inv, hidden, streams, t, eps)?;
11108 launch_hc_diet_stage1_mt(
11109 e,
11110 ptrs,
11111 &gate.norm_stack,
11112 &inv,
11113 gate.down_b16.as_ref().expect("guarded above"),
11114 winj,
11115 &mut parts,
11116 &mut injp,
11117 hidden,
11118 rank,
11119 streams,
11120 t,
11121 )?;
11122 } else {
11123 launch_hc_diet_stage1(
11124 e,
11125 ptrs,
11126 &gate.norm_stack,
11127 gate.down_b16.as_ref().expect("guarded above"),
11128 winj,
11129 &mut parts,
11130 &mut injp,
11131 &mut inv,
11132 hidden,
11133 rank,
11134 streams,
11135 t,
11136 eps,
11137 )?;
11138 }
11139 let mut low_act = ws.take_f32(e, "hc.low_act", t * rank, 0)?;
11140 let mut all = ws.take_f32(e, "hc.inj_all", streams * t, 0)?;
11141 launch_hc_diet_stage2(
11142 e,
11143 &parts,
11144 &injp,
11145 &mut low_act,
11146 &mut all,
11147 rank,
11148 streams,
11149 t,
11150 with_inject,
11151 )?;
11152 let mut mixed = ws.take_f32(e, "hc.mixed", t * hidden, 0)?;
11153 if mt && (t * rank + 8 * streams * t) * 4 <= 96 * 1024 {
11154 launch_hc_diet_stage3_mt(
11155 e,
11156 ptrs,
11157 &gate.norm_stack,
11158 &inv,
11159 gate.up_b16.as_ref().expect("guarded above"),
11160 &low_act,
11161 &mut mixed,
11162 hidden,
11163 rank,
11164 streams,
11165 t,
11166 )?;
11167 } else {
11168 launch_hc_diet_stage3(
11169 e,
11170 ptrs,
11171 &gate.norm_stack,
11172 &inv,
11173 gate.up_b16.as_ref().expect("guarded above"),
11174 &low_act,
11175 &mut mixed,
11176 hidden,
11177 rank,
11178 streams,
11179 t,
11180 )?;
11181 }
11182 ws.put_f32("hc.parts", parts);
11183 ws.put_f32("hc.injp", injp);
11184 ws.put_f32("hc.inv", inv);
11185 ws.put_f32("hc.low_act", low_act);
11186 let inject_out = if with_inject {
11187 InjectOut::Slab(all)
11188 } else {
11189 ws.put_f32("hc.inj_all", all);
11190 InjectOut::Rows(Vec::new())
11191 };
11192 return Ok((mixed, inject_out));
11193 }
11194
11195 let mut normed = ws.take_f32(e, "hc.normed", streams * t * hidden, 0)?;
11201 if micro_norm {
11202 launch_hc_norm_planes(
11204 e,
11205 ptrs,
11206 &gate.norm_stack,
11207 &mut normed,
11208 hidden,
11209 t,
11210 streams,
11211 eps,
11212 )?;
11213 } else {
11214 for s in 0..streams {
11215 let mut dst = normed.slice_mut(s * t * hidden..(s + 1) * t * hidden);
11216 launch_rms_norm_into_view(e, &planes[s], &gate.norm[s], &mut dst, hidden, t, eps)?;
11217 }
11218 }
11219 let trunk_b16 = trunk_bf16_on();
11223 let mut parts = ws.take_f32(e, "hc.parts", streams * t * rank, 0)?;
11224 if let (true, Some(w)) = (trunk_b16, gate.down_b16.as_ref()) {
11225 launch_qmatvec_bf16w(
11226 e,
11227 w,
11228 &normed,
11229 &mut parts,
11230 hidden,
11231 rank,
11232 t,
11233 streams,
11234 rank * hidden,
11235 t * hidden,
11236 hidden,
11237 t * rank,
11238 )?;
11239 } else {
11240 if gate.down[0].len() < rank * hidden {
11241 return Err(
11242 "qwen4exp_gpu: gate down f32 dropped (trunk_f32_diet) — keep the \
11243 trunk-bf16 seam ON"
11244 .into(),
11245 );
11246 }
11247 for s in 0..streams {
11248 let x = normed.slice(s * t * hidden..(s + 1) * t * hidden);
11249 let w = gate.down[s].slice(0..rank * hidden);
11250 let mut out = parts.slice_mut(s * t * rank..(s + 1) * t * rank);
11251 e.linear_device_into(&x, &w, &mut out, t, hidden, rank)?;
11252 }
11253 }
11254 let mut low_act = ws.take_f32(e, "hc.low_act", t * rank, 0)?;
11255 launch_hc_lowrank_reduce(e, &parts, &mut low_act, streams, t, rank)?;
11256 ws.put_f32("hc.parts", parts);
11257
11258 let mut gates = ws.take_f32(e, "hc.gates", streams * t * hidden, 0)?;
11261 if let (true, Some(w)) = (trunk_b16, gate.up_b16.as_ref()) {
11262 launch_qmatvec_bf16w(
11263 e,
11264 w,
11265 &low_act,
11266 &mut gates,
11267 rank,
11268 hidden,
11269 t,
11270 streams,
11271 hidden * rank,
11272 0,
11273 rank,
11274 t * hidden,
11275 )?;
11276 } else {
11277 if gate.up[0].len() < hidden * rank {
11278 return Err(
11279 "qwen4exp_gpu: gate up f32 dropped (trunk_f32_diet) — keep the \
11280 trunk-bf16 seam ON"
11281 .into(),
11282 );
11283 }
11284 for s in 0..streams {
11285 let x = low_act.slice(0..t * rank);
11286 let w = gate.up[s].slice(0..hidden * rank);
11287 let mut out = gates.slice_mut(s * t * hidden..(s + 1) * t * hidden);
11288 e.linear_device_into(&x, &w, &mut out, t, rank, hidden)?;
11289 }
11290 }
11291 let mut mixed = ws.take_f32(e, "hc.mixed", t * hidden, 0)?;
11292 launch_hc_mix_epilogue(e, &gates, &normed, &mut mixed, streams, t, hidden)?;
11293 ws.put_f32("hc.gates", gates);
11294 ws.put_f32("hc.low_act", low_act);
11295
11296 let mut inject_out = InjectOut::Rows(Vec::new());
11297 if with_inject {
11298 let inject = gate
11299 .inject
11300 .as_ref()
11301 .ok_or("qwen4exp_gpu: read gate missing inject weights")?;
11302 let inject_dropped = inject.len() < streams * streams * hidden;
11305 let inject_guard = || -> Res<()> {
11306 if inject_dropped {
11307 return Err("qwen4exp_gpu: inject f32 dropped (trunk_f32_diet) — keep \
11308 the trunk-bf16 seam ON"
11309 .into());
11310 }
11311 Ok(())
11312 };
11313 let mut all = ws.take_f32(e, "hc.inj_all", streams * t, 0)?;
11314 if micro_inj {
11315 const CHUNKS: usize = 16;
11318 let mut partials = ws.take_f32(e, "hc.inj_part", streams * t * CHUNKS, 0)?;
11319 let w_b16 = if trunk_b16 {
11320 gate.inject_b16.as_ref()
11321 } else {
11322 None
11323 };
11324 if w_b16.is_none() {
11325 inject_guard()?;
11326 }
11327 launch_hc_inject_two_stage(
11328 e,
11329 &normed,
11330 inject,
11331 w_b16,
11332 &mut partials,
11333 &mut all,
11334 streams,
11335 t,
11336 hidden,
11337 CHUNKS,
11338 )?;
11339 ws.put_f32("hc.inj_part", partials);
11340 inject_out = InjectOut::Slab(all);
11341 } else {
11342 if let (true, Some(w)) = (trunk_b16, gate.inject_b16.as_ref()) {
11345 launch_hc_inject_gates_b16(e, &normed, w, &mut all, streams, t, hidden)?;
11346 } else {
11347 inject_guard()?;
11348 launch_hc_inject_gates(e, &normed, inject, &mut all, streams, t, hidden)?;
11349 }
11350 let mut rows = Vec::with_capacity(streams);
11351 for s in 0..streams {
11352 let mut row = ws.take_f32(e, INJECT_SLOTS[s], t, 0)?;
11353 e.copy_range_into(&mut row, 0, &all, s * t, t)?;
11354 rows.push(row);
11355 }
11356 ws.put_f32("hc.inj_all", all);
11357 inject_out = InjectOut::Rows(rows);
11358 }
11359 }
11360 ws.put_f32("hc.normed", normed);
11361 Ok((mixed, inject_out))
11362 }
11363
11364 #[allow(clippy::too_many_arguments)]
11369 fn gate_read_legacy(
11370 &self,
11371 e: &Engine,
11372 _ws: &mut StepPool,
11373 gate: &GateW,
11374 planes: &[CudaSlice<f32>],
11375 t: usize,
11376 eps: f32,
11377 with_inject: bool,
11378 ) -> Res<(CudaSlice<f32>, InjectOut)> {
11379 let hidden = self.hidden;
11380 let streams = self.streams;
11381 let rank = gate_rank(gate, hidden, streams)?;
11382 if gate.down[0].len() < rank * hidden {
11383 return Err(
11384 "qwen4exp_gpu: gate f32 originals dropped (trunk_f32_diet) — the \
11385 legacy gate path needs them (keep hc seams ON)"
11386 .into(),
11387 );
11388 }
11389 let inv_streams = 1.0 / streams as f32; let mut normed = Vec::with_capacity(streams);
11392 for s in 0..streams {
11393 let mut dst = e.uninit(t * hidden)?;
11394 e.rms_norm(&planes[s], &gate.norm[s], &mut dst, hidden, t, eps)?;
11395 normed.push(dst);
11396 }
11397 let mut low = e.linear(&normed[0], &gate.down[0], t, hidden, rank)?;
11399 for s in 1..streams {
11400 let part = e.linear(&normed[s], &gate.down[s], t, hidden, rank)?;
11401 let mut view = low.slice_mut(0..t * rank);
11402 e.axpy_into(&part, 1.0, &mut view, t * rank)?;
11403 }
11404 e.scale_inplace(&mut low, inv_streams, t * rank)?;
11405 let ones = e.htod(&vec![1.0f32; t * rank.max(1)])?;
11406 let mut low_act = e.uninit(t * rank)?;
11407 e.silu_mul(&low, &ones, &mut low_act, t * rank)?;
11408
11409 let mut mixed = e.zeros(t * hidden)?;
11411 let mut gate_buf = e.uninit(t * hidden)?;
11412 let mut prod = e.uninit(t * hidden)?;
11413 for s in 0..streams {
11414 let g = e.linear(&low_act, &gate.up[s], t, rank, hidden)?;
11415 e.sigmoid(&g, &mut gate_buf, t * hidden)?;
11416 e.mul(&gate_buf, &normed[s], &mut prod, t * hidden)?;
11417 let mut view = mixed.slice_mut(0..t * hidden);
11418 e.axpy_into(&prod, 1.0, &mut view, t * hidden)?;
11419 }
11420 e.scale_inplace(&mut mixed, inv_streams, t * hidden)?;
11421
11422 let mut inject_out = Vec::new();
11423 if with_inject {
11424 let inject = gate
11425 .inject
11426 .as_ref()
11427 .ok_or("qwen4exp_gpu: read gate missing inject weights")?;
11428 let wide = streams * hidden;
11429 for s in 0..streams {
11430 let mut acc = {
11432 let w = inject.slice(s * wide..s * wide + hidden);
11433 let x = normed[0].slice(0..t * hidden);
11434 let mut out = e.uninit(t)?;
11435 e.linear_device_into(&x, &w, &mut out, t, hidden, 1)?;
11436 out
11437 };
11438 for s2 in 1..streams {
11439 let w = inject.slice(s * wide + s2 * hidden..s * wide + (s2 + 1) * hidden);
11440 let x = normed[s2].slice(0..t * hidden);
11441 let mut part = e.uninit(t)?;
11442 e.linear_device_into(&x, &w, &mut part, t, hidden, 1)?;
11443 let mut view = acc.slice_mut(0..t);
11444 e.axpy_into(&part, 1.0, &mut view, t)?;
11445 }
11446 e.scale_inplace(&mut acc, inv_streams, t)?;
11447 let mut sg = e.uninit(t)?;
11448 e.sigmoid(&acc, &mut sg, t)?;
11449 e.scale_inplace(&mut sg, 2.0, t)?;
11450 inject_out.push(sg);
11451 }
11452 }
11453 Ok((mixed, InjectOut::Rows(inject_out)))
11454 }
11455
11456 fn gate_write(
11460 &self,
11461 e: &Engine,
11462 planes: &mut [CudaSlice<f32>],
11463 ptrs: &CudaSlice<u64>,
11464 block_out: &CudaSlice<f32>,
11465 inject: &InjectOut,
11466 t: usize,
11467 ) -> Res<()> {
11468 match inject {
11469 InjectOut::Rows(rows) => {
11470 for (plane, inj) in planes.iter_mut().zip(rows) {
11471 e.add_scaled_rows(block_out, inj, plane, self.hidden, t)?;
11472 }
11473 Ok(())
11474 }
11475 InjectOut::Slab(slab) => {
11476 launch_hc_write_planes(e, ptrs, block_out, slab, self.hidden, t, self.streams)
11477 }
11478 }
11479 }
11480
11481 #[allow(clippy::too_many_arguments)]
11487 fn layer_interior(
11488 &self,
11489 e: &Engine,
11490 ws: &mut StepPool,
11491 ptrs: &CudaSlice<u64>,
11492 layer: &LayerW,
11493 lstate: &mut LayerState,
11494 planes: &mut [CudaSlice<f32>],
11495 tokens: &[u32],
11496 base_pos: usize,
11497 ) -> Res<()> {
11498 if let (Some(ple), Some(ple_state)) = (layer.ple.as_ref(), lstate.ple.as_mut()) {
11499 self.ple_block(
11500 e, layer, ple, &ple.table, ple_state, planes, tokens, 1, false, None,
11501 )?;
11502 }
11503 let (mixed, inject) = self.gate_read(
11504 e,
11505 ws,
11506 ptrs,
11507 &layer.attn_gate,
11508 planes,
11509 1,
11510 layer.eps_attn,
11511 false,
11512 )?;
11513 let block_out = match &layer.mixer {
11514 MixerW::Qsa(qsa) => self.qsa_forward(
11515 e,
11516 ws,
11517 layer,
11518 qsa,
11519 &mixed,
11520 &mut lstate.mixer,
11521 base_pos,
11522 1,
11523 0,
11524 false,
11525 )?,
11526 MixerW::Gdn(gdn) => {
11527 self.gdn_forward(e, ws, layer, gdn, &mixed, &mut lstate.mixer, 1, None)?
11528 }
11529 };
11530 ws.put_f32("hc.mixed", mixed);
11531 self.gate_write(e, planes, ptrs, &block_out, &inject, 1)?;
11532 ws.put_f32("mixer.out", block_out);
11533 put_inject(ws, inject);
11534 let (mixed, inject) = self.gate_read(
11535 e,
11536 ws,
11537 ptrs,
11538 &layer.mlp_gate,
11539 planes,
11540 1,
11541 layer.eps_mlp,
11542 false,
11543 )?;
11544 ws.put_f32("hc.mixed", mixed);
11545 put_inject(ws, inject);
11546 Ok(())
11547 }
11548
11549 fn moe_route_slots(&self, e: &Engine, ws: &mut StepPool, moe: &MoeW, layer: u32) -> Res<()> {
11555 let hidden = self.hidden;
11556 let experts = moe.plan.expert_count as usize;
11557 let selected = moe.plan.experts_per_token as usize;
11558 let mixed = ws.take_f32(e, "hc.mixed", hidden, 0)?;
11559 let mut router_out = ws.take_f32(e, "moe.router", experts, 0)?;
11560 let none: Option<CudaSlice<u8>> = None;
11561 let rb = if router_bf16_on() {
11562 &moe.router_b16
11563 } else {
11564 &none
11565 };
11566 linear_trunk_into(
11567 e,
11568 &moe.router,
11569 rb,
11570 &mixed,
11571 &mut router_out,
11572 1,
11573 hidden,
11574 experts,
11575 )?;
11576 if router_dev_on() && route_dev_geometry(experts, selected) {
11580 let mut sel = ws.take_i32_slot(e, "moe.sel", selected, 0)?;
11581 let mut w = ws.take_f32(e, "moe.w", selected, 0)?;
11582 route_topk_device(
11583 e,
11584 &router_out,
11585 &mut sel,
11586 &mut w,
11587 None,
11588 experts,
11589 selected,
11590 1,
11591 layer,
11592 )?;
11593 if route_sync_diag() {
11599 e.gpu.stream().synchronize()?;
11600 }
11601 ws.put_i32("moe.sel", sel);
11602 ws.put_f32("moe.w", w);
11603 ws.put_f32("moe.router", router_out);
11604 ws.put_f32("hc.mixed", mixed);
11605 return Ok(());
11606 }
11607 let logits = e.dtoh_view(&router_out.slice(0..experts))?;
11608 ws.put_f32("moe.router", router_out);
11609 ws.put_f32("hc.mixed", mixed);
11610 let route = host_route_softmax_topk(&logits, selected);
11611 let sel_host: Vec<i32> = route.iter().map(|&(x, _)| x as i32).collect();
11612 let w_host: Vec<f32> = route.iter().map(|&(_, w)| w).collect();
11613 ws.write_i32(e, "moe.sel", &sel_host)?;
11614 ws.write_f32(e, "moe.w", &w_host)?;
11615 Ok(())
11616 }
11617
11618 fn moe_grouped_tail_slots(
11623 &self,
11624 e: &Engine,
11625 ws: &mut StepPool,
11626 ptrs: &CudaSlice<u64>,
11627 moe: &MoeW,
11628 planes: &mut [CudaSlice<f32>],
11629 ) -> Res<()> {
11630 let hidden = self.hidden;
11631 let ff = moe.plan.expert_intermediate_size as usize;
11632 let n_sel = moe.plan.experts_per_token as usize;
11633 let (
11634 BankHalf::Nvfp4 {
11635 codes: gc,
11636 scales: gs,
11637 macros_dev: gm,
11638 ..
11639 },
11640 BankHalf::Nvfp4 {
11641 codes: uc,
11642 scales: us,
11643 macros_dev: um,
11644 ..
11645 },
11646 BankHalf::Nvfp4 {
11647 codes: dc,
11648 scales: ds,
11649 macros_dev: dm,
11650 ..
11651 },
11652 ) = (&moe.bank.gate, &moe.bank.up, &moe.bank.down)
11653 else {
11654 return Err("qwen4exp_gpu: grouped tail on a non-NVFP4 bank".into());
11655 };
11656 let mixed = ws.take_f32(e, "hc.mixed", hidden, 0)?;
11657 let sel = ws
11658 .i32s
11659 .remove("moe.sel")
11660 .ok_or("step workspace: moe.sel is not parked")?;
11661 let w_dev = ws.take_f32(e, "moe.w", n_sel, 0)?;
11662 let mut act = ws.take_f32(e, "moe.act", n_sel * ff, 0)?;
11663 if sel_gufuse_on() && hidden % 32 == 0 && ff % 4 == 0 {
11666 launch_nvfp4_sel_gu_silu(
11667 e,
11668 (gc, gs, gm),
11669 (uc, us, um),
11670 Some(&sel),
11671 0,
11672 n_sel,
11673 &mixed,
11674 &mut act,
11675 hidden,
11676 ff,
11677 None,
11678 )?;
11679 } else {
11680 let mut yg = ws.take_f32(e, "moe.yg", n_sel * ff, 0)?;
11681 let mut yu = ws.take_f32(e, "moe.yu", n_sel * ff, 0)?;
11682 launch_nvfp4_sel_matvec(e, gc, gs, gm, &sel, &mixed, &mut yg, n_sel, hidden, ff, 0)?;
11683 launch_nvfp4_sel_matvec(e, uc, us, um, &sel, &mixed, &mut yu, n_sel, hidden, ff, 0)?;
11684 e.silu_mul(&yg, &yu, &mut act, n_sel * ff)?;
11685 ws.put_f32("moe.yg", yg);
11686 ws.put_f32("moe.yu", yu);
11687 }
11688 let mut partial = ws.take_f32(e, "moe.partial", n_sel * hidden, 0)?;
11689 launch_nvfp4_sel_matvec(
11690 e,
11691 dc,
11692 ds,
11693 dm,
11694 &sel,
11695 &act,
11696 &mut partial,
11697 n_sel,
11698 ff,
11699 hidden,
11700 ff,
11701 )?;
11702 let mut out = ws.take_f32(e, "moe.out", hidden, 0)?;
11703 e.axpy_rows_seq_into(&partial, &w_dev, &mut out, hidden, n_sel)?;
11704 ws.put_i32("moe.sel", sel);
11705 ws.put_f32("moe.w", w_dev);
11706 ws.put_f32("moe.act", act);
11707 ws.put_f32("moe.partial", partial);
11708 let out = self.moe_shared_tail(e, ws, moe, &mixed, out, 1)?;
11709 ws.put_f32("hc.mixed", mixed);
11710 let inject = take_inject(e, ws, self.streams, 1)?;
11711 self.gate_write(e, planes, ptrs, &out, &inject, 1)?;
11712 ws.put_f32("moe.out", out);
11713 put_inject(ws, inject);
11714 Ok(())
11715 }
11716
11717 fn forward_graphs_tail(
11723 &self,
11724 e: &Engine,
11725 state: &mut Qwen4ExpState,
11726 mut planes: Vec<CudaSlice<f32>>,
11727 ptrs: CudaSlice<u64>,
11728 base_pos: usize,
11729 ) -> Res<Vec<f32>> {
11730 let mut graphs = std::mem::take(&mut state.graphs);
11731 if graphs.a.len() != self.layers.len() {
11732 graphs.a = (0..self.layers.len()).map(|_| None).collect();
11733 graphs.b = (0..self.layers.len()).map(|_| None).collect();
11734 }
11735 let ws = &mut state.ws;
11736 let tokens = &state.tokens;
11737 for (li, (layer, lstate)) in self.layers.iter().zip(state.layers.iter_mut()).enumerate() {
11738 let a_ok = matches!(layer.mixer, MixerW::Gdn(_)) && layer.ple.is_none();
11739 if a_ok {
11740 if graphs.a[li].is_none() {
11741 graphs.a[li] = Some(e.capture_graph_retained_nowarm(|eng| {
11742 self.layer_interior(
11743 eng,
11744 ws,
11745 &ptrs,
11746 layer,
11747 lstate,
11748 &mut planes,
11749 tokens,
11750 base_pos,
11751 )
11752 })?);
11753 }
11754 graphs.a[li].as_ref().unwrap().0.launch()?;
11755 } else {
11756 self.layer_interior(e, ws, &ptrs, layer, lstate, &mut planes, tokens, base_pos)?;
11757 }
11758 let b_ok = moe_sel_path_on()
11759 && matches!(
11760 (
11761 &layer.moe.bank.gate,
11762 &layer.moe.bank.up,
11763 &layer.moe.bank.down
11764 ),
11765 (
11766 BankHalf::Nvfp4 { .. },
11767 BankHalf::Nvfp4 { .. },
11768 BankHalf::Nvfp4 { .. }
11769 )
11770 );
11771 if b_ok {
11772 self.moe_route_slots(e, ws, &layer.moe, layer.index)?;
11773 if graphs.b[li].is_none() {
11774 graphs.b[li] = Some(e.capture_graph_retained_nowarm(|eng| {
11775 self.moe_grouped_tail_slots(eng, ws, &ptrs, &layer.moe, &mut planes)
11776 })?);
11777 }
11778 graphs.b[li].as_ref().unwrap().0.launch()?;
11779 } else {
11780 let mixed = ws.take_f32(e, "hc.mixed", self.hidden, 0)?;
11782 let mlp = self.moe_forward(e, ws, &layer.moe, &mixed, 1, false, layer.index)?;
11783 ws.put_f32("hc.mixed", mixed);
11784 let inject = take_inject(e, ws, self.streams, 1)?;
11785 self.gate_write(e, &mut planes, &ptrs, &mlp, &inject, 1)?;
11786 ws.put_f32("moe.out", mlp);
11787 put_inject(ws, inject);
11788 }
11789 }
11790 if graphs.exit.is_none() {
11791 graphs.exit = Some(e.capture_graph_retained_nowarm(|eng| {
11792 let x = self
11793 .gate_read_inner(
11794 eng,
11795 ws,
11796 &ptrs,
11797 &self.exit_mixer,
11798 &planes,
11799 1,
11800 self.exit_eps,
11801 false,
11802 false,
11803 )?
11804 .0;
11805 let mut logits = ws.take_f32(eng, "logits", self.vocab, 0)?;
11806 linear_trunk_into(
11807 eng,
11808 &self.output,
11809 &self.output_b16,
11810 &x,
11811 &mut logits,
11812 1,
11813 self.hidden,
11814 self.vocab,
11815 )?;
11816 ws.put_f32("hc.mixed", x);
11817 ws.put_f32("logits", logits);
11818 Ok(())
11819 })?);
11820 }
11821 graphs.exit.as_ref().unwrap().0.launch()?;
11822 let out = {
11823 let logits = ws.peek_f32("logits")?;
11824 e.dtoh_view(&logits.slice(0..self.vocab))?
11825 };
11826 for (s, plane) in planes.into_iter().enumerate() {
11827 ws.put_f32(PLANE_SLOTS[s], plane);
11828 }
11829 ws.put_u64("hc.ptrs", ptrs);
11830 state.pos += 1;
11831 state.graphs = graphs;
11832 Ok(out)
11833 }
11834
11835 #[allow(clippy::too_many_arguments)]
11843 fn qsa_update_select(
11844 &self,
11845 e: &Engine,
11846 ws: &mut StepPool,
11847 qsa: &QsaW,
11848 eps: f32,
11849 mixed: &CudaSlice<f32>,
11850 raw_keys: &mut IdxRawCache,
11851 pooled_keys: &mut Vec<f32>,
11852 pooled_dev: &mut Option<CudaSlice<f32>>,
11853 pooled_dev_rows: &mut usize,
11854 raw_dev: &mut Option<IdxRawDev>,
11855 raw_dev_rows: &mut usize,
11856 mut idx_audit: Option<&mut Box<IdxAudit>>,
11857 base_pos: usize,
11858 t: usize,
11859 pos_off: usize,
11860 exact: bool,
11861 ) -> Res<Vec<RowSel>> {
11862 let hidden = self.hidden;
11863 let base = qsa.attn.rope.base;
11864 let t_kv = base_pos + t;
11865 let overlay = &qsa.overlay;
11867 let idx_dim = overlay.head_dim as usize;
11868 let qk_width = (overlay.query_heads as usize + overlay.kv_heads as usize) * idx_dim;
11869 if overlay.kv_heads != 1 {
11870 return Err("qwen4exp_gpu: indexer with more than one key head".into());
11871 }
11872 let idx_proj = prof_section(e, "qsa.idx_proj", || {
11873 let mut idx_proj = ws.take_f32(e, "qsa.idxp", t * qk_width, 0)?;
11874 if exact && t > 1 {
11875 let wv = qsa.idx_proj.slice(0..qsa.idx_proj.len());
11876 for tok in 0..t {
11877 let xv = mixed.slice(tok * hidden..(tok + 1) * hidden);
11878 let mut yv = idx_proj.slice_mut(tok * qk_width..(tok + 1) * qk_width);
11879 e.linear_device_into(&xv, &wv, &mut yv, 1, hidden, qk_width)?;
11880 }
11881 } else {
11882 e.linear_device_into(mixed, &qsa.idx_proj, &mut idx_proj, t, hidden, qk_width)?;
11883 }
11884 Ok(idx_proj)
11885 })?;
11886 let dev_cache = idx_cache_on();
11896 let block_size = overlay.block_size as usize;
11897 let all_full = (base_pos + t) / block_size <= overlay.budget_blocks as usize;
11898 let host_rows = raw_keys.rows(idx_dim);
11899 if *raw_dev_rows > host_rows && !(dev_cache && all_full) {
11900 idx_materialize_host(e, raw_keys, raw_dev, *raw_dev_rows, idx_dim)?;
11904 }
11905 if dev_cache {
11906 let host_rows = raw_keys.rows(idx_dim);
11907 let base_rows = (*raw_dev_rows).max(host_rows);
11908 let cap_rows = (base_rows + t).next_power_of_two().max(64);
11909 let q_off = overlay.query_heads as usize * idx_dim;
11910 match &mut *raw_keys {
11911 IdxRawCache::F32(h) => {
11912 let want = (base_rows + t) * idx_dim;
11913 let grow = match raw_dev.as_ref() {
11914 Some(IdxRawDev::F32(m)) => m.len() < want,
11915 Some(_) => return Err("idxcache: device format lag on f32".into()),
11916 None => true,
11917 };
11918 if grow {
11919 let mut fresh = e.uninit(cap_rows * idx_dim)?;
11920 if let (Some(IdxRawDev::F32(old)), rows) = (raw_dev.as_ref(), *raw_dev_rows)
11921 {
11922 if rows > 0 {
11923 e.copy_range_into(&mut fresh, 0, old, 0, rows * idx_dim)?;
11924 }
11925 }
11926 *raw_dev = Some(IdxRawDev::F32(fresh));
11927 }
11928 let Some(IdxRawDev::F32(m)) = raw_dev.as_mut() else {
11929 unreachable!("allocated above");
11930 };
11931 if host_rows > *raw_dev_rows {
11932 let mut view = m.slice_mut(*raw_dev_rows * idx_dim..host_rows * idx_dim);
11934 e.gpu
11935 .stream()
11936 .memcpy_htod(&h[*raw_dev_rows * idx_dim..], &mut view)?;
11937 *raw_dev_rows = host_rows;
11938 }
11939 launch_copy_rows_col(
11940 e,
11941 &idx_proj,
11942 m,
11943 t,
11944 idx_dim,
11945 qk_width,
11946 q_off,
11947 *raw_dev_rows,
11948 )?;
11949 }
11950 IdxRawCache::Q8(h) => {
11951 let rb = q8_row_bytes(idx_dim);
11952 let want = (base_rows + t) * rb;
11953 let grow = match raw_dev.as_ref() {
11954 Some(IdxRawDev::Q8(m)) => m.len() < want,
11955 Some(_) => return Err("idxcache: device format lag on q8".into()),
11956 None => true,
11957 };
11958 if grow {
11959 let mut fresh = e.alloc_u8_uninit(cap_rows * rb)?;
11960 if let (Some(IdxRawDev::Q8(old)), rows) = (raw_dev.as_ref(), *raw_dev_rows)
11961 {
11962 if rows > 0 {
11963 let mut dst = fresh.slice_mut(0..rows * rb);
11964 e.gpu
11965 .stream()
11966 .memcpy_dtod(&old.slice(0..rows * rb), &mut dst)?;
11967 }
11968 }
11969 *raw_dev = Some(IdxRawDev::Q8(fresh));
11970 }
11971 let Some(IdxRawDev::Q8(m)) = raw_dev.as_mut() else {
11972 unreachable!("allocated above");
11973 };
11974 if host_rows > *raw_dev_rows {
11975 let mut view = m.slice_mut(*raw_dev_rows * rb..host_rows * rb);
11976 e.gpu
11977 .stream()
11978 .memcpy_htod(&h[*raw_dev_rows * rb..host_rows * rb], &mut view)?;
11979 *raw_dev_rows = host_rows;
11980 }
11981 launch_q4e_idx_append_q8(
11982 e,
11983 &idx_proj,
11984 m,
11985 t,
11986 idx_dim,
11987 qk_width,
11988 q_off,
11989 *raw_dev_rows,
11990 )?;
11991 }
11992 IdxRawCache::Bf16(h) => {
11993 let want = (base_rows + t) * idx_dim;
11994 let grow = match raw_dev.as_ref() {
11995 Some(IdxRawDev::Bf16(m)) => m.len() < want,
11996 Some(_) => return Err("idxcache: device format lag on bf16".into()),
11997 None => true,
11998 };
11999 if grow {
12000 let mut fresh = unsafe { e.gpu.stream().alloc::<u16>(cap_rows * idx_dim)? };
12001 if let (Some(IdxRawDev::Bf16(old)), rows) =
12002 (raw_dev.as_ref(), *raw_dev_rows)
12003 {
12004 if rows > 0 {
12005 let mut dst = fresh.slice_mut(0..rows * idx_dim);
12006 e.gpu
12007 .stream()
12008 .memcpy_dtod(&old.slice(0..rows * idx_dim), &mut dst)?;
12009 }
12010 }
12011 *raw_dev = Some(IdxRawDev::Bf16(fresh));
12012 }
12013 let Some(IdxRawDev::Bf16(m)) = raw_dev.as_mut() else {
12014 unreachable!("allocated above");
12015 };
12016 if host_rows > *raw_dev_rows {
12017 let mut view = m.slice_mut(*raw_dev_rows * idx_dim..host_rows * idx_dim);
12018 e.gpu.stream().memcpy_htod(
12019 &h[*raw_dev_rows * idx_dim..host_rows * idx_dim],
12020 &mut view,
12021 )?;
12022 *raw_dev_rows = host_rows;
12023 }
12024 launch_q4e_idx_append_bf16(
12025 e,
12026 &idx_proj,
12027 m,
12028 t,
12029 idx_dim,
12030 qk_width,
12031 q_off,
12032 *raw_dev_rows,
12033 )?;
12034 }
12035 }
12036 *raw_dev_rows += t;
12037 }
12038 if let Some(audit) = idx_audit.as_deref_mut() {
12042 let q_off = overlay.query_heads as usize * idx_dim;
12043 let rows_f = e.dtoh_view(&idx_proj.slice(0..t * qk_width))?;
12044 let IdxRawCache::F32(twin) = &mut audit.raw_f32 else {
12045 return Err("idxq audit: twin cache is not f32".into());
12046 };
12047 for row in 0..t {
12048 twin.extend_from_slice(&rows_f[row * qk_width + q_off..(row + 1) * qk_width]);
12049 }
12050 }
12051 let sels: Vec<RowSel> = if dev_cache && all_full {
12052 ws.put_f32("qsa.idxp", idx_proj);
12053 (0..t)
12054 .map(|qt| RowSel {
12055 full: true,
12056 blocks: Vec::new(),
12057 visible: base_pos + qt + 1,
12058 })
12059 .collect()
12060 } else {
12061 let idx_rows = e.dtoh_view(&idx_proj.slice(0..t * qk_width))?;
12062 ws.put_f32("qsa.idxp", idx_proj);
12063 let q_off = overlay.query_heads as usize * idx_dim;
12064 for row in 0..t {
12065 raw_keys.append_rows_f32(
12066 &idx_rows[row * qk_width + q_off..(row + 1) * qk_width],
12067 1,
12068 idx_dim,
12069 );
12070 }
12071 let dev_scorer = idx_dev_on();
12078 let sels = prof_section(e, "qsa.idx_host", || {
12079 indexer_select_rows(
12080 overlay,
12081 base,
12082 qsa.yarn.as_ref().map(|y| (y.ff_host.as_slice(), y.mscale)),
12083 eps,
12084 &qsa.idx_q_norm,
12085 &qsa.idx_k_norm,
12086 &idx_rows,
12087 raw_keys,
12088 pooled_keys,
12089 if dev_scorer {
12090 Some((e, pooled_dev, pooled_dev_rows))
12091 } else {
12092 None
12093 },
12094 base_pos,
12095 t,
12096 t_kv,
12097 pos_off,
12098 )
12099 })?;
12100 if let Some(audit) = idx_audit.as_deref_mut() {
12110 if t <= 8 && sels.iter().any(|s| !s.full) {
12111 let twin_sels = indexer_select_rows(
12112 overlay,
12113 base,
12114 qsa.yarn.as_ref().map(|y| (y.ff_host.as_slice(), y.mscale)),
12115 eps,
12116 &qsa.idx_q_norm,
12117 &qsa.idx_k_norm,
12118 &idx_rows,
12119 &audit.raw_f32,
12120 &mut audit.pooled_f32,
12121 None,
12122 base_pos,
12123 t,
12124 t_kv,
12125 pos_off,
12126 )?;
12127 use std::sync::atomic::Ordering::Relaxed;
12128 for (a, b) in sels.iter().zip(&twin_sels) {
12129 if a.full && b.full {
12130 continue;
12131 }
12132 IDXQ_AUDIT_ROWS.fetch_add(1, Relaxed);
12133 if a.full != b.full || a.blocks != b.blocks {
12134 IDXQ_AUDIT_FLIPPED.fetch_add(1, Relaxed);
12135 let mut diff = 0u64;
12136 let (sa, sb) = (&a.blocks, &b.blocks);
12137 let seta: std::collections::BTreeSet<_> = sa.iter().collect();
12138 let setb: std::collections::BTreeSet<_> = sb.iter().collect();
12139 diff += seta.symmetric_difference(&setb).count() as u64;
12140 IDXQ_AUDIT_BLOCKS.fetch_add(diff, Relaxed);
12141 }
12142 }
12143 }
12144 }
12145 sels
12146 };
12147 Ok(sels)
12148 }
12149
12150 fn qsa_forward(
12151 &self,
12152 e: &Engine,
12153 ws: &mut StepPool,
12154 layer: &LayerW,
12155 qsa: &QsaW,
12156 mixed: &CudaSlice<f32>,
12157 mstate: &mut MixerState,
12158 base_pos: usize,
12159 t: usize,
12160 pos_off: usize,
12163 exact: bool,
12167 ) -> Res<CudaSlice<f32>> {
12168 let MixerState::Qsa {
12169 kv,
12170 raw_keys,
12171 pooled_keys,
12172 pooled_dev,
12173 pooled_dev_rows,
12174 raw_dev,
12175 raw_dev_rows,
12176 idx_audit,
12177 } = mstate
12178 else {
12179 return Err(format!(
12180 "qwen4exp_gpu: QSA layer {} bound to non-QSA state",
12181 layer.index
12182 )
12183 .into());
12184 };
12185 let hidden = self.hidden;
12186 let nh = qsa.attn.query_heads as usize;
12187 let nkv = qsa.attn.kv_heads as usize;
12188 let hd = qsa.attn.key_head_dim as usize;
12189 let eps = layer.eps_attn;
12190 let cap = kv.capacity_rows(nkv * hd);
12192
12193 let n_rot = qsa.attn.rope.dimensions as usize;
12194 let base = qsa.attn.rope.base;
12195 let (q, gate) = prof_section(e, "qsa.proj", || {
12196 let mut q_fused = ws.take_f32(e, "qsa.qf", t * 2 * nh * hd, 0)?;
12197 let mut k_new = ws.take_f32(e, "qsa.k", t * nkv * hd, 0)?;
12198 let mut v_new = ws.take_f32(e, "qsa.v", t * nkv * hd, 0)?;
12199 if let (true, Some(stack)) = (
12203 t == 1 && proj_stack_on() && trunk_bf16_on(),
12204 qsa.proj_b16.as_ref(),
12205 ) {
12206 launch_qmatvec_bf16w_multi4(
12207 e,
12208 stack,
12209 mixed,
12210 &[
12211 (&q_fused, 2 * nh * hd),
12212 (&k_new, nkv * hd),
12213 (&v_new, nkv * hd),
12214 ],
12215 hidden,
12216 )?;
12217 } else {
12218 linear_trunk_stacked_into(
12219 e,
12220 &qsa.wq,
12221 &qsa.proj_b16,
12222 0,
12223 mixed,
12224 &mut q_fused,
12225 t,
12226 hidden,
12227 2 * nh * hd,
12228 )?;
12229 linear_trunk_stacked_into(
12230 e,
12231 &qsa.wk,
12232 &qsa.proj_b16,
12233 2 * nh * hd,
12234 mixed,
12235 &mut k_new,
12236 t,
12237 hidden,
12238 nkv * hd,
12239 )?;
12240 linear_trunk_stacked_into(
12241 e,
12242 &qsa.wv,
12243 &qsa.proj_b16,
12244 2 * nh * hd + nkv * hd,
12245 mixed,
12246 &mut v_new,
12247 t,
12248 hidden,
12249 nkv * hd,
12250 )?;
12251 }
12252 let mut q = ws.take_f32(e, "qsa.q", t * nh * hd, 0)?;
12253 let mut gate = ws.take_f32(e, "qsa.gate", t * nh * hd, 0)?;
12254 e.q_gate_split(&q_fused, &mut q, &mut gate, hd, nh, t)?;
12255 ws.put_f32("qsa.qf", q_fused);
12256 let mut q = if let Some(norm) = qsa.q_norm.as_ref() {
12257 let mut dst = ws.take_f32(e, "qsa.qn", t * nh * hd, 0)?;
12258 e.rms_norm(&q, norm, &mut dst, hd, t * nh, eps)?;
12259 ws.put_f32("qsa.q", q);
12260 dst
12261 } else {
12262 q
12263 };
12264 let mut k_new = if let Some(norm) = qsa.k_norm.as_ref() {
12265 let mut dst = ws.take_f32(e, "qsa.kn", t * nkv * hd, 0)?;
12266 e.rms_norm(&k_new, norm, &mut dst, hd, t * nkv, eps)?;
12267 ws.put_f32("qsa.k", k_new);
12268 dst
12269 } else {
12270 k_new
12271 };
12272 let positions: Vec<i32> = (0..t).map(|i| (base_pos + i + pos_off) as i32).collect();
12273 let pos_dev = ws.take_i32(e, "qsa.pos", &positions, 0)?;
12274 if let Some(yarn) = qsa.yarn.as_ref() {
12275 e.rope_neox_ffm(
12276 &mut q,
12277 &pos_dev,
12278 hd,
12279 n_rot,
12280 nh,
12281 t,
12282 base,
12283 1.0,
12284 &yarn.ff,
12285 yarn.mscale,
12286 )?;
12287 e.rope_neox_ffm(
12288 &mut k_new,
12289 &pos_dev,
12290 hd,
12291 n_rot,
12292 nkv,
12293 t,
12294 base,
12295 1.0,
12296 &yarn.ff,
12297 yarn.mscale,
12298 )?;
12299 } else {
12300 e.rope_neox(&mut q, &pos_dev, hd, n_rot, nh, t, base, 1.0)?;
12301 e.rope_neox(&mut k_new, &pos_dev, hd, n_rot, nkv, t, base, 1.0)?;
12302 }
12303 ws.put_i32("qsa.pos", pos_dev);
12304 match kv {
12306 QsaKvStore::F32 { k, v } => {
12307 e.copy_range_into(k, base_pos * nkv * hd, &k_new, 0, t * nkv * hd)?;
12308 e.copy_range_into(v, base_pos * nkv * hd, &v_new, 0, t * nkv * hd)?;
12309 }
12310 QsaKvStore::Q8Q5 { k, v } => {
12313 launch_q4e_kv_append(e, &k_new, &v_new, k, v, base_pos, t, nkv * hd)?;
12314 }
12315 }
12316 ws.put_f32(
12317 if qsa.k_norm.is_some() {
12318 "qsa.kn"
12319 } else {
12320 "qsa.k"
12321 },
12322 k_new,
12323 );
12324 ws.put_f32("qsa.v", v_new);
12325 Ok((q, gate))
12326 })?;
12327 let t_kv = base_pos + t;
12328 let sels = self.qsa_update_select(
12329 e,
12330 ws,
12331 qsa,
12332 eps,
12333 mixed,
12334 raw_keys,
12335 pooled_keys,
12336 pooled_dev,
12337 pooled_dev_rows,
12338 raw_dev,
12339 raw_dev_rows,
12340 idx_audit.as_mut(),
12341 base_pos,
12342 t,
12343 pos_off,
12344 exact,
12345 )?;
12346 let overlay = &qsa.overlay;
12347
12348 let scale = match qsa.attn.scale {
12349 memra_gguf::model_plan::AttentionScale::InverseSqrtKeyDim => 1.0 / (hd as f32).sqrt(),
12350 memra_gguf::model_plan::AttentionScale::Fixed(scale) => scale,
12351 };
12352 let long_att = if kv.is_quant() {
12376 if longatt_mode() == LongAttMode::Off {
12377 return Err(
12378 "qwen4exp_gpu: kvq requires the block-list attention form (longatt=off)".into(),
12379 );
12380 }
12381 true
12382 } else {
12383 match longatt_mode() {
12384 LongAttMode::Force => true,
12385 LongAttMode::Auto => t_kv > SDPA_MASK_TKV_BOUND || sels.iter().any(|s| !s.full),
12386 LongAttMode::Off => false,
12387 }
12388 };
12389 let block_size = overlay.block_size as usize;
12390 let attended = if long_att {
12391 let (pos_flat, meta, max_count) = rowsel_positions(&sels, block_size);
12392 let pos_dev = prof_section(e, "qsa.mask_h2d", || {
12393 ws.take_i32(e, "qsa.selpos", &pos_flat, 0)
12394 })?;
12395 let meta_dev = ws.take_i32(e, "qsa.selmeta", &meta, 0)?;
12396 let attended = prof_section(e, "qsa.sdpa", || {
12397 let mut attended = ws.take_f32(e, "qsa.att", t * nh * hd, 0)?;
12398 match kv {
12399 QsaKvStore::F32 { k, v } => {
12400 let k_view = k.slice(0..t_kv * nkv * hd);
12401 let v_view = v.slice(0..t_kv * nkv * hd);
12402 launch_sdpa_blocklist(
12403 e,
12404 &q,
12405 &k_view,
12406 &v_view,
12407 &mut attended,
12408 &pos_dev,
12409 &meta_dev,
12410 hd,
12411 nh,
12412 nkv,
12413 t,
12414 max_count,
12415 scale,
12416 )?;
12417 }
12418 QsaKvStore::Q8Q5 { k, v } => {
12419 launch_q4e_sdpa_blocklist_q8q5(
12420 e,
12421 &q,
12422 k,
12423 v,
12424 &mut attended,
12425 &pos_dev,
12426 &meta_dev,
12427 hd,
12428 nh,
12429 nkv,
12430 t,
12431 max_count,
12432 scale,
12433 )?;
12434 }
12435 }
12436 Ok(attended)
12437 })?;
12438 ws.put_i32("qsa.selpos", pos_dev);
12439 ws.put_i32("qsa.selmeta", meta_dev);
12440 attended
12441 } else {
12442 let QsaKvStore::F32 { k, v } = &*kv else {
12443 return Err("qwen4exp_gpu: masked SDPA reached with a quantized cache".into());
12444 };
12445 let mask = rowsel_to_mask(&sels, block_size, t_kv);
12446 let mask_dev = prof_section(e, "qsa.mask_h2d", || {
12447 ws.take_u8_h2d(e, "qsa.mask", &mask, t * cap.min(SDPA_MASK_TKV_BOUND))
12450 })?;
12451 let attended = prof_section(e, "qsa.sdpa", || {
12452 let mut attended = ws.take_f32(e, "qsa.att", t * nh * hd, 0)?;
12453 let k_view = k.slice(0..t_kv * nkv * hd);
12454 let v_view = v.slice(0..t_kv * nkv * hd);
12455 launch_sdpa_mask(
12456 e,
12457 &q,
12458 &k_view,
12459 &v_view,
12460 &mut attended,
12461 &mask_dev,
12462 hd,
12463 nh,
12464 nkv,
12465 t,
12466 t_kv,
12467 scale,
12468 )?;
12469 Ok(attended)
12470 })?;
12471 ws.put_u8("qsa.mask", mask_dev);
12472 attended
12473 };
12474 ws.put_f32(
12475 if qsa.q_norm.is_some() {
12476 "qsa.qn"
12477 } else {
12478 "qsa.q"
12479 },
12480 q,
12481 );
12482 let out = prof_section(e, "qsa.gate_wo", || {
12483 let mut sg = ws.take_f32(e, "qsa.sg", t * nh * hd, 0)?;
12485 e.sigmoid(&gate, &mut sg, t * nh * hd)?;
12486 let mut gated = ws.take_f32(e, "qsa.gated", t * nh * hd, 0)?;
12487 e.mul(&attended, &sg, &mut gated, t * nh * hd)?;
12488 let mut out = ws.take_f32(e, "mixer.out", t * hidden, 0)?;
12489 linear_trunk_into(
12490 e,
12491 &qsa.wo,
12492 &qsa.wo_b16,
12493 &gated,
12494 &mut out,
12495 t,
12496 nh * hd,
12497 hidden,
12498 )?;
12499 ws.put_f32("qsa.sg", sg);
12500 ws.put_f32("qsa.gated", gated);
12501 Ok(out)
12502 })?;
12503 ws.put_f32("qsa.att", attended);
12504 ws.put_f32("qsa.gate", gate);
12505 Ok(out)
12506 }
12507
12508 #[allow(clippy::too_many_arguments)]
12512 fn gdn_forward(
12513 &self,
12514 e: &Engine,
12515 ws: &mut StepPool,
12516 layer: &LayerW,
12517 gdn: &GdnW,
12518 mixed: &CudaSlice<f32>,
12519 mstate: &mut MixerState,
12520 t: usize,
12521 mut stash: Option<&mut GdnStash>,
12525 ) -> Res<CudaSlice<f32>> {
12526 let MixerState::Gdn { conv, state } = mstate else {
12527 return Err(format!(
12528 "qwen4exp_gpu: GDN layer {} bound to non-GDN state",
12529 layer.index
12530 )
12531 .into());
12532 };
12533 let hidden = self.hidden;
12534 let p = &gdn.plan;
12535 let (nk, nv) = (p.key_heads as usize, p.value_heads as usize);
12536 let (hk, hv) = (p.key_head_dim as usize, p.value_head_dim as usize);
12537 let kernel = p.conv_kernel as usize;
12538 let pad = kernel - 1;
12539 let conv_dim = 2 * nk * hk + nv * hv;
12540 let eps = layer.eps_attn;
12541
12542 let (qkv, z, beta_raw, g_log) = prof_section(e, "gdn.proj", || {
12543 let mut qkv = ws.take_f32(e, "gdn.qkv", t * conv_dim, 0)?;
12544 let mut z = ws.take_f32(e, "gdn.z", t * nv * hv, 0)?;
12545 let mut beta_raw = ws.take_f32(e, "gdn.beta", t * nv, 0)?;
12546 let mut alpha = ws.take_f32(e, "gdn.alpha", t * nv, 0)?;
12547 if let (true, Some(stack)) = (
12552 t == 1 && proj_stack_on() && trunk_bf16_on(),
12553 gdn.proj_b16.as_ref(),
12554 ) {
12555 launch_qmatvec_bf16w_multi4(
12556 e,
12557 stack,
12558 mixed,
12559 &[
12560 (&qkv, conv_dim),
12561 (&z, nv * hv),
12562 (&beta_raw, nv),
12563 (&alpha, nv),
12564 ],
12565 hidden,
12566 )?;
12567 } else {
12568 linear_trunk_stacked_into(
12569 e,
12570 &gdn.qkv,
12571 &gdn.proj_b16,
12572 0,
12573 mixed,
12574 &mut qkv,
12575 t,
12576 hidden,
12577 conv_dim,
12578 )?;
12579 linear_trunk_stacked_into(
12580 e,
12581 &gdn.z,
12582 &gdn.proj_b16,
12583 conv_dim,
12584 mixed,
12585 &mut z,
12586 t,
12587 hidden,
12588 nv * hv,
12589 )?;
12590 linear_trunk_stacked_into(
12591 e,
12592 &gdn.beta,
12593 &gdn.proj_b16,
12594 conv_dim + nv * hv,
12595 mixed,
12596 &mut beta_raw,
12597 t,
12598 hidden,
12599 nv,
12600 )?;
12601 linear_trunk_stacked_into(
12602 e,
12603 &gdn.alpha,
12604 &gdn.proj_b16,
12605 conv_dim + nv * hv + nv,
12606 mixed,
12607 &mut alpha,
12608 t,
12609 hidden,
12610 nv,
12611 )?;
12612 }
12613 let mut g_log = ws.take_f32(e, "gdn.glog", t * nv, 0)?;
12614 e.gdn_glog_v(&alpha.slice(0..t * nv), &gdn.dt, &gdn.a, &mut g_log, nv, t)?;
12615 ws.put_f32("gdn.alpha", alpha);
12616 Ok((qkv, z, beta_raw, g_log))
12617 })?;
12618
12619 let o = prof_section(e, "gdn.conv_scan", || {
12620 if let Some(st) = stash.as_deref_mut() {
12624 e.copy_range_into(&mut st.conv_pre, 0, conv, 0, pad * conv_dim)?;
12625 e.copy_range_into(&mut st.qkv_rows, 0, &qkv, 0, t * conv_dim)?;
12626 }
12627 let mut conv_out = ws.take_f32(e, "gdn.conv_out", t * conv_dim, 0)?;
12630 let mut o = ws.take_f32(e, "gdn.o", t * nv * hv, 0)?;
12631 let mut tmp = if t >= pad {
12632 None
12633 } else {
12634 Some(ws.take_f32(e, "gdn.tmp", (pad - t) * conv_dim, 0)?)
12635 };
12636 let scale = 1.0 / (hk as f32).sqrt();
12637 let step_ok = gdn_step_on() && hk % 32 == 0 && hk <= 1024;
12638 let chain = |eng: &Engine,
12649 conv: &mut CudaSlice<f32>,
12650 state: &mut CudaSlice<f32>,
12651 states_snap: Option<&mut CudaSlice<f32>>,
12652 conv_out: &mut CudaSlice<f32>,
12653 o: &mut CudaSlice<f32>,
12654 tmp: Option<&mut CudaSlice<f32>>|
12655 -> Res<()> {
12656 launch_dwconv(
12657 eng,
12658 &qkv,
12659 conv,
12660 &gdn.conv_w,
12661 conv_out,
12662 t,
12663 pad,
12664 conv_dim,
12665 kernel,
12666 1,
12667 1,
12668 )?;
12669 match states_snap {
12670 Some(states) => {
12671 let state_len = nv * hv * hk;
12672 for tok in 0..t {
12673 if step_ok {
12674 launch_gdn_scan_step_at(
12675 eng, conv_out, &g_log, &beta_raw, state, o, tok, nk, nv, hk,
12676 hv, scale, eps,
12677 )?;
12678 } else {
12679 launch_gdn_scan_at(
12680 eng, conv_out, &g_log, &beta_raw, state, o, tok, nk, nv, hk,
12681 hv, scale, eps,
12682 )?;
12683 }
12684 eng.copy_range_into(states, tok * state_len, state, 0, state_len)?;
12685 }
12686 }
12687 None if t == 1 && step_ok => {
12688 launch_gdn_scan_step(
12689 eng, conv_out, &g_log, &beta_raw, state, o, nk, nv, hk, hv, scale, eps,
12690 )?;
12691 }
12692 None => {
12693 launch_gdn_scan(
12694 eng, conv_out, &g_log, &beta_raw, state, o, nk, nv, hk, hv, t, scale,
12695 eps,
12696 )?;
12697 }
12698 }
12699 if t >= pad {
12702 eng.copy_range_into(conv, 0, &qkv, (t - pad) * conv_dim, pad * conv_dim)?;
12703 } else {
12704 let keep = pad - t;
12705 let tmp = tmp.ok_or("qwen4exp_gpu: gdn conv roll needs the tmp slot")?;
12706 eng.copy_range_into(tmp, 0, conv, t * conv_dim, keep * conv_dim)?;
12707 eng.copy_range_into(conv, 0, tmp, 0, keep * conv_dim)?;
12708 eng.copy_range_into(conv, keep * conv_dim, &qkv, 0, t * conv_dim)?;
12709 }
12710 Ok(())
12711 };
12712 let graphable = stash.is_some() && verify_graphs_on() && step_ws_on() && !prof::on();
12715 match stash.as_deref_mut() {
12716 Some(st) if graphable => {
12717 let entry = match st.scan_graph.take() {
12720 Some((gt, g)) if gt == t => Some(g),
12721 _ => None,
12722 };
12723 let warm = st.scan_warm == Some(t);
12724 st.scan_warm = Some(t);
12725 let entry = match (warm, entry) {
12727 (false, _) => {
12730 chain(
12731 e,
12732 conv,
12733 state,
12734 Some(&mut st.states),
12735 &mut conv_out,
12736 &mut o,
12737 tmp.as_mut(),
12738 )?;
12739 None
12740 }
12741 (true, Some(g)) => {
12743 g.0.launch()?;
12744 Some(g)
12745 }
12746 (true, None) => {
12751 let states = &mut st.states;
12752 let mut tmp_ref = tmp.as_mut();
12753 let g = e.capture_graph_retained_nowarm(|eng| {
12754 chain(
12755 eng,
12756 conv,
12757 state,
12758 Some(states),
12759 &mut conv_out,
12760 &mut o,
12761 tmp_ref.as_deref_mut(),
12762 )
12763 })?;
12764 g.0.launch()?;
12765 Some(g)
12766 }
12767 };
12768 if let Some(g) = entry {
12769 st.scan_graph = Some((t, g));
12770 }
12771 }
12772 Some(st) => chain(
12773 e,
12774 conv,
12775 state,
12776 Some(&mut st.states),
12777 &mut conv_out,
12778 &mut o,
12779 tmp.as_mut(),
12780 )?,
12781 None => chain(e, conv, state, None, &mut conv_out, &mut o, tmp.as_mut())?,
12782 }
12783 ws.put_f32("gdn.conv_out", conv_out);
12784 if let Some(tmp) = tmp {
12785 ws.put_f32("gdn.tmp", tmp);
12786 }
12787 Ok(o)
12788 })?;
12789 ws.put_f32("gdn.qkv", qkv);
12790 ws.put_f32("gdn.beta", beta_raw);
12791 ws.put_f32("gdn.glog", g_log);
12792
12793 let out = prof_section(e, "gdn.norm_gate_out", || {
12794 let mut gated = ws.take_f32(e, "gdn.gated", t * nv * hv, 0)?;
12795 match p.gate_activation {
12796 GdnGateActivation::Sigmoid if gdn_fuse_on() => {
12799 launch_rms_sigmul(e, &o, &gdn.norm, &z, &mut gated, hv, t * nv, eps)?;
12800 }
12801 GdnGateActivation::Sigmoid => {
12802 let mut normed = ws.take_f32(e, "gdn.normed", t * nv * hv, 0)?;
12803 e.rms_norm(&o, &gdn.norm, &mut normed, hv, t * nv, eps)?;
12804 let mut sg = ws.take_f32(e, "gdn.sg", t * nv * hv, 0)?;
12805 e.sigmoid(&z, &mut sg, t * nv * hv)?;
12806 e.mul(&normed, &sg, &mut gated, t * nv * hv)?;
12807 ws.put_f32("gdn.sg", sg);
12808 ws.put_f32("gdn.normed", normed);
12809 }
12810 GdnGateActivation::Silu => {
12811 let mut normed = ws.take_f32(e, "gdn.normed", t * nv * hv, 0)?;
12812 e.rms_norm(&o, &gdn.norm, &mut normed, hv, t * nv, eps)?;
12813 e.silu_mul(&z, &normed, &mut gated, t * nv * hv)?;
12814 ws.put_f32("gdn.normed", normed);
12815 }
12816 }
12817 let mut out = ws.take_f32(e, "mixer.out", t * hidden, 0)?;
12818 linear_trunk_into(
12819 e,
12820 &gdn.out,
12821 &gdn.out_b16,
12822 &gated,
12823 &mut out,
12824 t,
12825 nv * hv,
12826 hidden,
12827 )?;
12828 ws.put_f32("gdn.gated", gated);
12829 Ok(out)
12830 })?;
12831 ws.put_f32("gdn.z", z);
12832 ws.put_f32("gdn.o", o);
12833 Ok(out)
12834 }
12835
12836 fn moe_forward(
12840 &self,
12841 e: &Engine,
12842 ws: &mut StepPool,
12843 moe: &MoeW,
12844 mixed: &CudaSlice<f32>,
12845 t: usize,
12846 rows_grouped: bool,
12850 layer: u32,
12854 ) -> Res<CudaSlice<f32>> {
12855 let hidden = self.hidden;
12856 let experts = moe.plan.expert_count as usize;
12857 let selected = moe.plan.experts_per_token as usize;
12858 let ff = moe.plan.expert_intermediate_size as usize;
12859
12860 let nvfp4_bank = matches!(
12869 (&moe.bank.gate, &moe.bank.up, &moe.bank.down),
12870 (
12871 BankHalf::Nvfp4 { .. },
12872 BankHalf::Nvfp4 { .. },
12873 BankHalf::Nvfp4 { .. }
12874 )
12875 );
12876 let devbf16_bank = matches!(
12877 (&moe.bank.gate, &moe.bank.up, &moe.bank.down),
12878 (
12879 BankHalf::DeviceBf16(_),
12880 BankHalf::DeviceBf16(_),
12881 BankHalf::DeviceBf16(_)
12882 )
12883 );
12884 let use_dev_router = router_dev_on()
12885 && moe_sel_path_on()
12886 && route_dev_geometry(experts, selected)
12887 && ((nvfp4_bank
12888 && hidden % 32 == 0
12889 && ff % 4 == 0
12890 && (t == 1
12891 || (rows_grouped
12892 && verify_mt_on()
12893 && sel_gufuse_on()
12894 && t * selected <= 8192)))
12895 || (devbf16_bank && hidden % 8 == 0 && ff % 8 == 0 && (t == 1 || rows_grouped)));
12896 type DevRoute = (CudaSlice<i32>, CudaSlice<f32>, Option<CudaSlice<i32>>);
12899 let (routes, mut dev_route): (Vec<Vec<(usize, f32)>>, Option<DevRoute>) =
12900 prof_section(e, "moe.router", || {
12901 let mut router_out = ws.take_f32(e, "moe.router", t * experts, 0)?;
12902 let none: Option<CudaSlice<u8>> = None;
12903 let rb = if router_bf16_on() {
12904 &moe.router_b16
12905 } else {
12906 &none
12907 };
12908 linear_trunk_into(
12909 e,
12910 &moe.router,
12911 rb,
12912 mixed,
12913 &mut router_out,
12914 t,
12915 hidden,
12916 experts,
12917 )?;
12918 if use_dev_router {
12919 let mut sel = ws.take_i32_slot(e, "moe.sel", t * selected, 0)?;
12920 let mut w = ws.take_f32(e, "moe.w", t * selected, 0)?;
12921 let mut tokm = if t > 1 {
12922 Some(ws.take_i32_slot(e, "moe.tok", t * selected, 0)?)
12923 } else {
12924 None
12925 };
12926 route_topk_device(
12927 e,
12928 &router_out,
12929 &mut sel,
12930 &mut w,
12931 tokm.as_mut().map(|m| (m, 0)),
12932 experts,
12933 selected,
12934 t,
12935 layer,
12936 )?;
12937 ws.put_f32("moe.router", router_out);
12938 return Ok((Vec::new(), Some((sel, w, tokm))));
12939 }
12940 let logits = e.dtoh_view(&router_out.slice(0..t * experts))?;
12941 ws.put_f32("moe.router", router_out);
12942 let mut routes: Vec<Vec<(usize, f32)>> = Vec::with_capacity(t);
12943 for token in 0..t {
12944 routes.push(host_route_softmax_topk(
12945 &logits[token * experts..(token + 1) * experts],
12946 selected,
12947 ));
12948 }
12949 Ok((routes, None))
12950 })?;
12951 if (t == 1 || rows_grouped) && moe_sel_path_on() {
12959 if let (
12960 BankHalf::Nvfp4 {
12961 codes: gc,
12962 scales: gs,
12963 macros_dev: gm,
12964 ..
12965 },
12966 BankHalf::Nvfp4 {
12967 codes: uc,
12968 scales: us,
12969 macros_dev: um,
12970 ..
12971 },
12972 BankHalf::Nvfp4 {
12973 codes: dc,
12974 scales: ds,
12975 macros_dev: dm,
12976 ..
12977 },
12978 ) = (&moe.bank.gate, &moe.bank.up, &moe.bank.down)
12979 {
12980 if t > 1 && verify_mt_on() && sel_gufuse_on() && hidden % 32 == 0 && ff % 4 == 0 {
12986 if let Some((sel, w_dev, tokm)) = dev_route.take() {
12992 let tokm =
12993 tokm.ok_or("moe_forward: device route at t > 1 without a tok map")?;
12994 let out = prof_section(e, "moe.sel_grouped", || {
12995 let mut out = ws.take_f32(e, "moe.out", t * hidden, 0)?;
12996 let s_total = t * selected;
12997 let mut act = ws.take_f32(e, "moe.act", s_total * ff, 0)?;
12998 launch_nvfp4_sel_gu_silu(
12999 e,
13000 (gc, gs, gm),
13001 (uc, us, um),
13002 Some(&sel),
13003 0,
13004 s_total,
13005 mixed,
13006 &mut act,
13007 hidden,
13008 ff,
13009 Some((&tokm, hidden)),
13010 )?;
13011 let mut partial = ws.take_f32(e, "moe.partial", s_total * hidden, 0)?;
13012 launch_nvfp4_sel_matvec(
13013 e,
13014 dc,
13015 ds,
13016 dm,
13017 &sel,
13018 &act,
13019 &mut partial,
13020 s_total,
13021 ff,
13022 hidden,
13023 ff,
13024 )?;
13025 for tok in 0..t {
13026 launch_axpy_rows_seq_at(
13027 e,
13028 &partial,
13029 tok * selected,
13030 &w_dev,
13031 tok * selected,
13032 &mut out,
13033 tok,
13034 hidden,
13035 selected,
13036 )?;
13037 }
13038 ws.put_i32("moe.sel", sel);
13039 ws.put_i32("moe.tok", tokm);
13040 ws.put_f32("moe.w", w_dev);
13041 ws.put_f32("moe.act", act);
13042 ws.put_f32("moe.partial", partial);
13043 Ok(out)
13044 })?;
13045 return self.moe_shared_tail(e, ws, moe, mixed, out, t);
13046 }
13047 let out = prof_section(e, "moe.sel_grouped", || {
13048 let mut out = ws.take_f32(e, "moe.out", t * hidden, 0)?;
13049 const SLOT_CAP: usize = 8192;
13059 let tok_step = (SLOT_CAP / selected.max(1)).max(1);
13060 let mut tok0 = 0usize;
13061 while tok0 < t {
13062 let tok_n = tok_step.min(t - tok0);
13063 let batch = &routes[tok0..tok0 + tok_n];
13064 let mut sel_all: Vec<i32> = Vec::with_capacity(tok_n * selected);
13065 let mut w_all: Vec<f32> = Vec::with_capacity(tok_n * selected);
13066 let mut tok_all: Vec<i32> = Vec::with_capacity(tok_n * selected);
13067 let mut ranges: Vec<(usize, usize)> = Vec::with_capacity(tok_n);
13068 for (i, route) in batch.iter().enumerate() {
13069 ranges.push((sel_all.len(), route.len()));
13070 for &(eid, wgt) in route {
13071 sel_all.push(eid as i32);
13072 w_all.push(wgt);
13073 tok_all.push((tok0 + i) as i32);
13078 }
13079 }
13080 let s_total = sel_all.len();
13081 let sel = ws.take_i32(e, "moe.sel", &sel_all, 0)?;
13082 let w_dev = ws.take_f32_h2d(e, "moe.w", &w_all, 0)?;
13083 let tokm = ws.take_i32(e, "moe.tok", &tok_all, 0)?;
13084 let mut act = ws.take_f32(e, "moe.act", s_total * ff, 0)?;
13085 launch_nvfp4_sel_gu_silu(
13086 e,
13087 (gc, gs, gm),
13088 (uc, us, um),
13089 Some(&sel),
13090 0,
13091 s_total,
13092 mixed,
13093 &mut act,
13094 hidden,
13095 ff,
13096 Some((&tokm, hidden)),
13097 )?;
13098 let mut partial = ws.take_f32(e, "moe.partial", s_total * hidden, 0)?;
13099 launch_nvfp4_sel_matvec(
13100 e,
13101 dc,
13102 ds,
13103 dm,
13104 &sel,
13105 &act,
13106 &mut partial,
13107 s_total,
13108 ff,
13109 hidden,
13110 ff,
13111 )?;
13112 for (i, &(start, len)) in ranges.iter().enumerate() {
13113 launch_axpy_rows_seq_at(
13114 e,
13115 &partial,
13116 start,
13117 &w_dev,
13118 start,
13119 &mut out,
13120 tok0 + i,
13121 hidden,
13122 len,
13123 )?;
13124 }
13125 ws.put_i32("moe.sel", sel);
13126 ws.put_i32("moe.tok", tokm);
13127 ws.put_f32("moe.w", w_dev);
13128 ws.put_f32("moe.act", act);
13129 ws.put_f32("moe.partial", partial);
13130 tok0 += tok_n;
13131 }
13132 Ok(out)
13133 })?;
13134 return self.moe_shared_tail(e, ws, moe, mixed, out, t);
13135 }
13136 if let Some((sel, w_dev, _)) = dev_route.take() {
13140 let out = prof_section(e, "moe.sel_grouped", || {
13141 let mut out = ws.take_f32(e, "moe.out", hidden, 0)?;
13142 let mut act = ws.take_f32(e, "moe.act", selected * ff, 0)?;
13143 if sel_gufuse_on() && hidden % 32 == 0 && ff % 4 == 0 {
13144 launch_nvfp4_sel_gu_silu(
13145 e,
13146 (gc, gs, gm),
13147 (uc, us, um),
13148 Some(&sel),
13149 0,
13150 selected,
13151 mixed,
13152 &mut act,
13153 hidden,
13154 ff,
13155 None,
13156 )?;
13157 } else {
13158 let mut yg = ws.take_f32(e, "moe.yg", selected * ff, 0)?;
13159 let mut yu = ws.take_f32(e, "moe.yu", selected * ff, 0)?;
13160 launch_nvfp4_sel_matvec(
13161 e, gc, gs, gm, &sel, mixed, &mut yg, selected, hidden, ff, 0,
13162 )?;
13163 launch_nvfp4_sel_matvec(
13164 e, uc, us, um, &sel, mixed, &mut yu, selected, hidden, ff, 0,
13165 )?;
13166 e.silu_mul(&yg, &yu, &mut act, selected * ff)?;
13167 ws.put_f32("moe.yg", yg);
13168 ws.put_f32("moe.yu", yu);
13169 }
13170 let mut partial = ws.take_f32(e, "moe.partial", selected * hidden, 0)?;
13171 launch_nvfp4_sel_matvec(
13172 e,
13173 dc,
13174 ds,
13175 dm,
13176 &sel,
13177 &act,
13178 &mut partial,
13179 selected,
13180 ff,
13181 hidden,
13182 ff,
13183 )?;
13184 e.axpy_rows_seq_into(&partial, &w_dev, &mut out, hidden, selected)?;
13185 ws.put_i32("moe.sel", sel);
13186 ws.put_f32("moe.w", w_dev);
13187 ws.put_f32("moe.act", act);
13188 ws.put_f32("moe.partial", partial);
13189 Ok(out)
13190 })?;
13191 return self.moe_shared_tail(e, ws, moe, mixed, out, t);
13192 }
13193 let out = prof_section(e, "moe.sel_grouped", || {
13194 let mut out = ws.take_f32(e, "moe.out", t * hidden, 0)?;
13195 for (tok, route) in routes.iter().enumerate() {
13196 let n_sel = route.len();
13197 let sel_host: Vec<i32> = route.iter().map(|&(x, _)| x as i32).collect();
13198 let w_host: Vec<f32> = route.iter().map(|&(_, w)| w).collect();
13199 let sel = ws.take_i32(e, "moe.sel", &sel_host, 0)?;
13200 let w_dev = ws.take_f32_h2d(e, "moe.w", &w_host, 0)?;
13201 let x_tok = if t == 1 {
13206 None
13207 } else {
13208 let mut x = ws.take_f32(e, "moe.x", hidden, 0)?;
13209 e.copy_range_into(&mut x, 0, mixed, tok * hidden, hidden)?;
13210 Some(x)
13211 };
13212 let x_ref = x_tok.as_ref().unwrap_or(mixed);
13213 let mut act = ws.take_f32(e, "moe.act", n_sel * ff, 0)?;
13214 if sel_gufuse_on() && hidden % 32 == 0 && ff % 4 == 0 {
13217 launch_nvfp4_sel_gu_silu(
13218 e,
13219 (gc, gs, gm),
13220 (uc, us, um),
13221 Some(&sel),
13222 0,
13223 n_sel,
13224 x_ref,
13225 &mut act,
13226 hidden,
13227 ff,
13228 None,
13229 )?;
13230 } else {
13231 let mut yg = ws.take_f32(e, "moe.yg", n_sel * ff, 0)?;
13232 let mut yu = ws.take_f32(e, "moe.yu", n_sel * ff, 0)?;
13233 launch_nvfp4_sel_matvec(
13234 e, gc, gs, gm, &sel, x_ref, &mut yg, n_sel, hidden, ff, 0,
13235 )?;
13236 launch_nvfp4_sel_matvec(
13237 e, uc, us, um, &sel, x_ref, &mut yu, n_sel, hidden, ff, 0,
13238 )?;
13239 e.silu_mul(&yg, &yu, &mut act, n_sel * ff)?;
13240 ws.put_f32("moe.yg", yg);
13241 ws.put_f32("moe.yu", yu);
13242 }
13243 let mut partial = ws.take_f32(e, "moe.partial", n_sel * hidden, 0)?;
13244 launch_nvfp4_sel_matvec(
13245 e,
13246 dc,
13247 ds,
13248 dm,
13249 &sel,
13250 &act,
13251 &mut partial,
13252 n_sel,
13253 ff,
13254 hidden,
13255 ff,
13256 )?;
13257 if t == 1 {
13260 e.axpy_rows_seq_into(&partial, &w_dev, &mut out, hidden, n_sel)?;
13261 } else {
13262 let mut row = ws.take_f32(e, "moe.row", hidden, 0)?;
13263 e.axpy_rows_seq_into(&partial, &w_dev, &mut row, hidden, n_sel)?;
13264 e.copy_range_into(&mut out, tok * hidden, &row, 0, hidden)?;
13265 ws.put_f32("moe.row", row);
13266 }
13267 ws.put_i32("moe.sel", sel);
13268 ws.put_f32("moe.w", w_dev);
13269 ws.put_f32("moe.act", act);
13270 ws.put_f32("moe.partial", partial);
13271 if let Some(x) = x_tok {
13272 ws.put_f32("moe.x", x);
13273 }
13274 }
13275 Ok(out)
13276 })?;
13277 return self.moe_shared_tail(e, ws, moe, mixed, out, t);
13278 }
13279 if let (BankHalf::DeviceBf16(gb), BankHalf::DeviceBf16(ub), BankHalf::DeviceBf16(db)) =
13284 (&moe.bank.gate, &moe.bank.up, &moe.bank.down)
13285 {
13286 if let Some((sel, w_dev, _)) = dev_route.take() {
13293 let out = prof_section(e, "moe.sel_bf16", || {
13294 let mut out = ws.take_f32(e, "moe.out", t * hidden, 0)?;
13295 for tok in 0..t {
13296 let mut yg = ws.take_f32(e, "moe.yg", selected * ff, 0)?;
13297 let mut yu = ws.take_f32(e, "moe.yu", selected * ff, 0)?;
13298 launch_qmatvec_bf16w_sel(
13299 e,
13300 gb,
13301 &sel,
13302 tok * selected,
13303 mixed,
13304 tok * hidden,
13305 0,
13306 &mut yg,
13307 selected,
13308 hidden,
13309 ff,
13310 )?;
13311 launch_qmatvec_bf16w_sel(
13312 e,
13313 ub,
13314 &sel,
13315 tok * selected,
13316 mixed,
13317 tok * hidden,
13318 0,
13319 &mut yu,
13320 selected,
13321 hidden,
13322 ff,
13323 )?;
13324 let mut act = ws.take_f32(e, "moe.act", selected * ff, 0)?;
13325 e.silu_mul(&yg, &yu, &mut act, selected * ff)?;
13326 let mut partial =
13327 ws.take_f32(e, "moe.partial", selected * hidden, 0)?;
13328 launch_qmatvec_bf16w_sel(
13329 e,
13330 db,
13331 &sel,
13332 tok * selected,
13333 &act,
13334 0,
13335 ff,
13336 &mut partial,
13337 selected,
13338 ff,
13339 hidden,
13340 )?;
13341 launch_axpy_rows_seq_at(
13342 e,
13343 &partial,
13344 0,
13345 &w_dev,
13346 tok * selected,
13347 &mut out,
13348 tok,
13349 hidden,
13350 selected,
13351 )?;
13352 ws.put_f32("moe.yg", yg);
13353 ws.put_f32("moe.yu", yu);
13354 ws.put_f32("moe.act", act);
13355 ws.put_f32("moe.partial", partial);
13356 }
13357 ws.put_i32("moe.sel", sel);
13358 ws.put_f32("moe.w", w_dev);
13359 Ok(out)
13360 })?;
13361 return self.moe_shared_tail(e, ws, moe, mixed, out, t);
13362 }
13363 let out = prof_section(e, "moe.sel_bf16", || {
13364 let mut out = ws.take_f32(e, "moe.out", t * hidden, 0)?;
13365 for (tok, route) in routes.iter().enumerate() {
13366 let n_sel = route.len();
13367 let w_host: Vec<f32> = route.iter().map(|&(_, w)| w).collect();
13368 let w_dev = ws.take_f32_h2d(e, "moe.w", &w_host, 0)?;
13369 let mut yg = ws.take_f32(e, "moe.yg", n_sel * ff, 0)?;
13370 let mut yu = ws.take_f32(e, "moe.yu", n_sel * ff, 0)?;
13371 for (slot, &(eid, _)) in route.iter().enumerate() {
13372 launch_qmatvec_bf16w_off_into(
13373 e,
13374 gb,
13375 eid * ff,
13376 mixed,
13377 tok * hidden,
13378 &mut yg,
13379 slot * ff,
13380 hidden,
13381 ff,
13382 )?;
13383 launch_qmatvec_bf16w_off_into(
13384 e,
13385 ub,
13386 eid * ff,
13387 mixed,
13388 tok * hidden,
13389 &mut yu,
13390 slot * ff,
13391 hidden,
13392 ff,
13393 )?;
13394 }
13395 let mut act = ws.take_f32(e, "moe.act", n_sel * ff, 0)?;
13396 e.silu_mul(&yg, &yu, &mut act, n_sel * ff)?;
13397 let mut partial = ws.take_f32(e, "moe.partial", n_sel * hidden, 0)?;
13398 for (slot, &(eid, _)) in route.iter().enumerate() {
13399 launch_qmatvec_bf16w_off_into(
13400 e,
13401 db,
13402 eid * hidden,
13403 &act,
13404 slot * ff,
13405 &mut partial,
13406 slot * hidden,
13407 ff,
13408 hidden,
13409 )?;
13410 }
13411 let mut row = ws.take_f32(e, "moe.row", hidden, 0)?;
13412 e.axpy_rows_seq_into(&partial, &w_dev, &mut row, hidden, n_sel)?;
13413 e.copy_range_into(&mut out, tok * hidden, &row, 0, hidden)?;
13414 ws.put_f32("moe.row", row);
13415 ws.put_f32("moe.w", w_dev);
13416 ws.put_f32("moe.yg", yg);
13417 ws.put_f32("moe.yu", yu);
13418 ws.put_f32("moe.act", act);
13419 ws.put_f32("moe.partial", partial);
13420 }
13421 Ok(out)
13422 })?;
13423 return self.moe_shared_tail(e, ws, moe, mixed, out, t);
13424 }
13425 }
13426
13427 if dev_route.is_some() {
13431 return Err(
13432 "moe_forward: device route left unconsumed (engage guard drifted from the \
13433 dispatch arms)"
13434 .into(),
13435 );
13436 }
13437 let mut by_expert: Vec<Vec<(i32, i32, f32)>> = vec![Vec::new(); experts];
13439 for (token, token_routes) in routes.iter().enumerate() {
13440 for (slot, &(expert, weight)) in token_routes.iter().enumerate() {
13441 by_expert[expert].push((token as i32, slot as i32, weight));
13442 }
13443 }
13444 let mut slots = e.zeros(t * selected * hidden)?;
13445 let mut wbuf = e.zeros(t * selected)?;
13446 for (expert, entries) in by_expert.iter().enumerate() {
13447 if entries.is_empty() {
13448 continue;
13449 }
13450 let m_e = entries.len();
13451 let (tok_dev, slot_dev, w_dev, xg) = prof_section(e, "moe.idx_gather", || {
13452 let tok_idx: Vec<i32> = entries.iter().map(|&(tok, _, _)| tok).collect();
13453 let slot_idx: Vec<i32> = entries.iter().map(|&(_, slot, _)| slot).collect();
13454 let weights: Vec<f32> = entries.iter().map(|&(_, _, w)| w).collect();
13455 let tok_dev = e.htod_i32(&tok_idx)?;
13456 let slot_dev = e.htod_i32(&slot_idx)?;
13457 let w_dev = e.htod(&weights)?;
13458 let mut xg = e.uninit(m_e * hidden)?;
13459 e.gather_rows(mixed, &tok_dev, &mut xg, hidden, m_e)?;
13460 Ok((tok_dev, slot_dev, w_dev, xg))
13461 })?;
13462 let resolve = |half: &BankHalf,
13465 out_f: usize,
13466 in_f: usize|
13467 -> Res<(Option<CudaSlice<f32>>, usize)> {
13468 match half {
13469 BankHalf::F32(_) => Ok((None, expert * out_f * in_f)),
13470 BankHalf::Nvfp4 {
13471 codes,
13472 scales,
13473 macros,
13474 ..
13475 } => Ok((
13476 Some(dequant_nvfp4_expert_f32(
13477 e,
13478 codes,
13479 scales,
13480 macros[expert],
13481 expert,
13482 out_f,
13483 in_f,
13484 )?),
13485 0,
13486 )),
13487 BankHalf::HostBf16(bytes) => {
13490 let row_bytes = out_f * in_f * 2;
13491 let dev =
13492 e.htod_bytes(&bytes[expert * row_bytes..(expert + 1) * row_bytes])?;
13493 Ok((
13494 Some(e.bf16_to_f32(&dev.slice(0..row_bytes), out_f * in_f)?),
13495 0,
13496 ))
13497 }
13498 BankHalf::DeviceBf16(bytes) => {
13502 let row_bytes = out_f * in_f * 2;
13503 let view = bytes.slice(expert * row_bytes..(expert + 1) * row_bytes);
13504 Ok((Some(e.bf16_to_f32(&view, out_f * in_f)?), 0))
13505 }
13506 }
13507 };
13508 let ((gate_owned, gate_base), (up_owned, up_base), (down_owned, down_base)) =
13509 prof_section(e, "moe.dequant", || {
13510 Ok((
13511 resolve(&moe.bank.gate, ff, hidden)?,
13512 resolve(&moe.bank.up, ff, hidden)?,
13513 resolve(&moe.bank.down, hidden, ff)?,
13514 ))
13515 })?;
13516 let gate_view = match (&moe.bank.gate, &gate_owned) {
13517 (_, Some(owned)) => owned.slice(0..ff * hidden),
13518 (BankHalf::F32(bank), None) => bank.slice(gate_base..gate_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 up_view = match (&moe.bank.up, &up_owned) {
13527 (_, Some(owned)) => owned.slice(0..ff * hidden),
13528 (BankHalf::F32(bank), None) => bank.slice(up_base..up_base + ff * hidden),
13529 (
13530 BankHalf::Nvfp4 { .. } | BankHalf::HostBf16(_) | BankHalf::DeviceBf16(_),
13531 None,
13532 ) => {
13533 unreachable!("quantized/host/device-bf16 halves always resolve owned")
13534 }
13535 };
13536 let down_view = match (&moe.bank.down, &down_owned) {
13537 (_, Some(owned)) => owned.slice(0..hidden * ff),
13538 (BankHalf::F32(bank), None) => bank.slice(down_base..down_base + hidden * ff),
13539 (
13540 BankHalf::Nvfp4 { .. } | BankHalf::HostBf16(_) | BankHalf::DeviceBf16(_),
13541 None,
13542 ) => {
13543 unreachable!("quantized/host/device-bf16 halves always resolve owned")
13544 }
13545 };
13546 prof_section(e, "moe.expert_gemms", || {
13547 let down_out =
13548 run_routed_expert(e, &xg, &gate_view, &up_view, &down_view, m_e, hidden, ff)?;
13549 e.scatter_slot(
13550 &down_out, &tok_dev, &slot_dev, &w_dev, &mut slots, &mut wbuf, hidden,
13551 selected, m_e,
13552 )
13553 })?;
13554 }
13555 let out = prof_section(e, "moe.reduce", || {
13556 let mut out = e.zeros(t * hidden)?;
13557 e.reduce_slots(&slots, &wbuf, &mut out, hidden, selected, t)?;
13558 Ok(out)
13559 })?;
13560 self.moe_shared_tail(e, ws, moe, mixed, out, t)
13561 }
13562
13563 fn moe_shared_tail(
13566 &self,
13567 e: &Engine,
13568 ws: &mut StepPool,
13569 moe: &MoeW,
13570 mixed: &CudaSlice<f32>,
13571 mut out: CudaSlice<f32>,
13572 t: usize,
13573 ) -> Res<CudaSlice<f32>> {
13574 let hidden = self.hidden;
13575 let sff = moe
13576 .plan
13577 .shared
13578 .as_ref()
13579 .map(|s| s.intermediate_size as usize)
13580 .unwrap_or(0);
13581 if sff > 0 {
13582 prof_section(e, "moe.shared", || {
13583 let none: Option<CudaSlice<u8>> = None;
13586 let (gu, db) = if micro_shexp_on() {
13587 (&moe.shared_gu_b16, &moe.shared_down_b16)
13588 } else {
13589 (&none, &none)
13590 };
13591 let mut gate = ws.take_f32(e, "moe.sh_gate", t * sff, 0)?;
13592 let mut up = ws.take_f32(e, "moe.sh_up", t * sff, 0)?;
13593 if let (true, Some(stack)) =
13596 (t == 1 && proj_stack_on() && trunk_bf16_on(), gu.as_ref())
13597 {
13598 launch_qmatvec_bf16w_multi4(
13599 e,
13600 stack,
13601 mixed,
13602 &[(&gate, sff), (&up, sff)],
13603 hidden,
13604 )?;
13605 } else {
13606 linear_trunk_stacked_into(
13607 e,
13608 &moe.shared_gate,
13609 gu,
13610 0,
13611 mixed,
13612 &mut gate,
13613 t,
13614 hidden,
13615 sff,
13616 )?;
13617 linear_trunk_stacked_into(
13618 e,
13619 &moe.shared_up,
13620 gu,
13621 sff,
13622 mixed,
13623 &mut up,
13624 t,
13625 hidden,
13626 sff,
13627 )?;
13628 }
13629 let mut act = ws.take_f32(e, "moe.sh_act", t * sff, 0)?;
13630 e.silu_mul(&gate, &up, &mut act, t * sff)?;
13631 let mut shared = ws.take_f32(e, "moe.sh_down", t * hidden, 0)?;
13632 linear_trunk_into(e, &moe.shared_down, db, &act, &mut shared, t, sff, hidden)?;
13633 if let Some(input_gate) = moe.shared_input_gate.as_ref() {
13634 let mut g = ws.take_f32(e, "moe.g", t, 0)?;
13637 e.sigmoid_dot_rows_into(mixed, input_gate, &mut g, hidden, t)?;
13638 e.add_scaled_rows(&shared, &g, &mut out, hidden, t)?;
13639 ws.put_f32("moe.g", g);
13640 } else {
13641 let mut view = out.slice_mut(0..t * hidden);
13642 e.axpy_into(&shared, 1.0, &mut view, t * hidden)?;
13643 }
13644 ws.put_f32("moe.sh_gate", gate);
13645 ws.put_f32("moe.sh_up", up);
13646 ws.put_f32("moe.sh_act", act);
13647 ws.put_f32("moe.sh_down", shared);
13648 Ok(())
13649 })?;
13650 }
13651 Ok(out)
13652 }
13653
13654 #[allow(clippy::too_many_arguments)]
13658 fn ple_block(
13659 &self,
13660 e: &Engine,
13661 layer: &LayerW,
13662 ple: &PleW,
13663 table: &NgramTable,
13664 ple_state: &mut PleState,
13665 planes: &mut [CudaSlice<f32>],
13666 tokens: &[u32],
13667 t: usize,
13668 exact: bool,
13672 mut stash: Option<&mut PleStash>,
13673 ) -> Res<()> {
13674 let hidden = self.hidden;
13675 let streams = self.streams;
13676 let plan = &ple.plan;
13677 let heads = plan.ngram_heads as usize;
13678 let head_dim = plan.head_embed_dim as usize;
13679 let embed_dim = plan.embed_dim as usize;
13680 let kernel = plan.conv_kernel as usize;
13681 let max_ngram = plan.max_ngram as usize;
13682 let dilation = max_ngram;
13683 let pad = (kernel - 1) * dilation;
13684 let eps = layer.eps_attn;
13685
13686 let gathered = prof_section(e, "ple.host_ngram_gather", || {
13688 let total_heads = heads;
13689 let mut ids: Vec<i64> = Vec::new();
13693 if ple_cache_on() {
13694 host_ngram_ids_cached(
13695 &mut ple_state.ngram_ids,
13696 &mut ple_state.ngram_history,
13697 &mut ple_state.ngram_last_eos,
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 if ple_cache_audit_on() {
13707 let twin = host_ngram_ids(
13708 tokens,
13709 &ple.multipliers,
13710 &ple.sizes,
13711 &ple.offsets,
13712 max_ngram,
13713 heads / (max_ngram - 1),
13714 plan.eos_token_id,
13715 );
13716 let from = (tokens.len() - t) * total_heads;
13717 let mism = twin[from..]
13718 .iter()
13719 .zip(&ple_state.ngram_ids[from..])
13720 .filter(|(a, b)| a != b)
13721 .count() as u64;
13722 PLE_CACHE_AUDIT_ROWS.fetch_add(t as u64, std::sync::atomic::Ordering::Relaxed);
13723 PLE_CACHE_AUDIT_MISMATCH.fetch_add(mism, std::sync::atomic::Ordering::Relaxed);
13724 PLE_CACHE_AUDIT_MAX_FILL
13725 .fetch_max(tokens.len() as u64, std::sync::atomic::Ordering::Relaxed);
13726 if mism > 0 {
13727 return Err(format!(
13728 "plecache audit: {mism} cached n-gram ids differ from the full twin \
13729 at history {} (t={t})",
13730 tokens.len()
13731 )
13732 .into());
13733 }
13734 }
13735 } else {
13736 ids = host_ngram_ids(
13737 tokens,
13738 &ple.multipliers,
13739 &ple.sizes,
13740 &ple.offsets,
13741 max_ngram,
13742 heads / (max_ngram - 1),
13743 plan.eos_token_id,
13744 );
13745 }
13746 let all_ids: &[i64] = if ple_cache_on() {
13747 &ple_state.ngram_ids
13748 } else {
13749 &ids
13750 };
13751 let chunk_ids = &all_ids[(tokens.len() - t) * total_heads..];
13752 let table_rows = table.rows(head_dim);
13753 let mut gathered = vec![0.0f32; t * embed_dim];
13754 for token in 0..t {
13755 for head in 0..heads {
13756 let id = chunk_ids[token * total_heads + head];
13757 if id < 0 || id as usize >= table_rows {
13758 return Err("qwen4exp_gpu: n-gram id outside the embedding table".into());
13759 }
13760 table.gather_into(
13761 id as usize,
13762 head_dim,
13763 &mut gathered[token * embed_dim + head * head_dim
13764 ..token * embed_dim + (head + 1) * head_dim],
13765 );
13766 }
13767 }
13768 Ok(gathered)
13769 })?;
13770 let emb = prof_section(e, "ple.h2d", || e.htod(&gathered))?;
13771
13772 let lin_rows = |x: &CudaSlice<f32>,
13775 w: &CudaSlice<f32>,
13776 in_f: usize,
13777 out_f: usize|
13778 -> Res<CudaSlice<f32>> {
13779 let mut out = e.uninit(t * out_f)?;
13780 if exact && t > 1 {
13781 let wv = w.slice(0..w.len());
13782 for tok in 0..t {
13783 let xv = x.slice(tok * in_f..(tok + 1) * in_f);
13784 let mut yv = out.slice_mut(tok * out_f..(tok + 1) * out_f);
13785 e.linear_device_into(&xv, &wv, &mut yv, 1, in_f, out_f)?;
13786 }
13787 } else {
13788 e.linear_device_into(x, w, &mut out, t, in_f, out_f)?;
13789 }
13790 Ok(out)
13791 };
13792 let (value, mut dots_host) = prof_section(e, "ple.key_gate", || {
13793 let value = lin_rows(&emb, &ple.value_proj, embed_dim, hidden)?;
13794 let ones = e.htod(&vec![1.0f32; hidden])?;
13795 let mut dots_host = vec![0.0f32; streams * t];
13796 for s in 0..streams {
13797 let key = lin_rows(&emb, &ple.key_proj[s], embed_dim, hidden)?;
13798 let mut key_normed = e.uninit(t * hidden)?;
13799 e.rms_norm(&key, &ple.norm_key[s], &mut key_normed, hidden, t, eps)?;
13800 let mut query = e.uninit(t * hidden)?;
13801 e.rms_norm(&planes[s], &ple.norm_query[s], &mut query, hidden, t, eps)?;
13802 let mut prod = e.uninit(t * hidden)?;
13803 e.mul(&key_normed, &query, &mut prod, t * hidden)?;
13804 let dots = lin_rows(&prod, &ones, hidden, 1)?;
13805 dots_host[s * t..(s + 1) * t].copy_from_slice(&e.dtoh(&dots)?);
13806 }
13807 Ok((value, dots_host))
13808 })?;
13809 for dot in dots_host.iter_mut() {
13811 let gate = *dot / (hidden as f32).sqrt();
13812 let magnitude = gate.abs().max(1e-6).sqrt();
13813 let signed = if gate > 0.0 {
13814 magnitude
13815 } else if gate < 0.0 {
13816 -magnitude
13817 } else {
13818 0.0
13819 };
13820 *dot = host_sigmoid(signed);
13821 }
13822
13823 prof_section(e, "ple.conv_write", || {
13824 for s in 0..streams {
13825 let g = e.htod(&dots_host[s * t..(s + 1) * t])?;
13826 let mut gated = e.zeros(t * hidden)?;
13827 e.add_scaled_rows(&value, &g, &mut gated, hidden, t)?;
13828 let mut normed = e.uninit(t * hidden)?;
13829 e.rms_norm(&gated, &ple.norm_conv[s], &mut normed, hidden, t, eps)?;
13830 if let Some(st) = stash.as_deref_mut() {
13833 e.copy_range_into(
13834 &mut st.hist_pre[s],
13835 0,
13836 &ple_state.conv_hist[s],
13837 0,
13838 pad * hidden,
13839 )?;
13840 e.copy_range_into(&mut st.normed_rows[s], 0, &normed, 0, t * hidden)?;
13841 }
13842 launch_dwconv(
13844 e,
13845 &normed,
13846 &ple_state.conv_hist[s],
13847 &ple.conv_w[s],
13848 &mut gated,
13849 t,
13850 pad,
13851 hidden,
13852 kernel,
13853 dilation,
13854 2,
13855 )?;
13856 let hist = &mut ple_state.conv_hist[s];
13858 if t >= pad {
13859 e.copy_range_into(hist, 0, &normed, (t - pad) * hidden, pad * hidden)?;
13860 } else {
13861 let keep = pad - t;
13862 let mut tmp = e.uninit(keep * hidden)?;
13863 e.copy_range_into(&mut tmp, 0, hist, t * hidden, keep * hidden)?;
13864 e.copy_range_into(hist, 0, &tmp, 0, keep * hidden)?;
13865 e.copy_range_into(hist, keep * hidden, &normed, 0, t * hidden)?;
13866 }
13867 let mut view = planes[s].slice_mut(0..t * hidden);
13869 e.axpy_into(&gated, 1.0, &mut view, t * hidden)?;
13870 }
13871 Ok(())
13872 })
13873 }
13874}
13875
13876pub struct MtpDraftState {
13884 mixer: MixerState,
13885 rows: usize,
13887 pub committed: usize,
13890 capacity: usize,
13891 ws: StepPool,
13892}
13893
13894impl MtpDraftState {
13895 pub fn rows(&self) -> usize {
13896 self.rows
13897 }
13898}
13899
13900#[derive(Clone, Copy)]
13906enum DraftTokSrc<'a> {
13907 Host(&'a [u32]),
13908 HostDev(&'a [u32]),
13909 DevSlot(&'a CudaSlice<u32>, usize),
13910}
13911
13912impl Qwen4ExpGpu {
13913 pub fn has_mtp(&self) -> bool {
13914 self.mtp.is_some()
13915 }
13916
13917 pub fn mtp_on_dev1(&self) -> bool {
13920 self.mtp_dev1.is_some()
13921 }
13922
13923 fn check_draft_engine(&self, e: &Engine) -> Res<()> {
13927 if let Some(d) = self.mtp_dev1.as_ref() {
13928 if e.ctx().ordinal() != d.dev {
13929 return Err(format!(
13930 "qwen4exp_gpu: the draft lives on device {} (card-1 placement); \
13931 this call presented device {}",
13932 d.dev,
13933 e.ctx().ordinal()
13934 )
13935 .into());
13936 }
13937 }
13938 Ok(())
13939 }
13940
13941 pub fn mtp_state(&self, e: &Engine, capacity: usize) -> Res<MtpDraftState> {
13944 self.check_draft_engine(e)?;
13945 let mtp = self
13946 .mtp
13947 .as_ref()
13948 .ok_or("qwen4exp_gpu: no MTP block loaded (LoadOptions::load_mtp)")?;
13949 let MixerW::Qsa(qsa) = &mtp.layer.mixer else {
13950 return Err("qwen4exp_gpu: MTP mixer is not QSA".into());
13951 };
13952 let kv_width = qsa.attn.kv_heads as usize * qsa.attn.key_head_dim as usize;
13953 let v_width = qsa.attn.kv_heads as usize * qsa.attn.value_head_dim as usize;
13954 let kv = if kv_quant_on() {
13957 QsaKvStore::Q8Q5 {
13958 k: e.alloc_u8(capacity * q8_row_bytes(kv_width))?,
13959 v: e.alloc_u8(capacity * q5_row_bytes(v_width))?,
13960 }
13961 } else {
13962 QsaKvStore::F32 {
13963 k: e.zeros(capacity * kv_width)?,
13964 v: e.zeros(capacity * v_width)?,
13965 }
13966 };
13967 Ok(MtpDraftState {
13968 mixer: MixerState::Qsa {
13969 kv,
13970 raw_keys: IdxRawCache::new(idxq_mode()),
13971 pooled_keys: Vec::new(),
13972 pooled_dev: None,
13973 pooled_dev_rows: 0,
13974 raw_dev: None,
13975 raw_dev_rows: 0,
13976 idx_audit: None,
13977 },
13978 rows: 0,
13979 committed: 0,
13980 capacity,
13981 ws: StepPool::default(),
13982 })
13983 }
13984
13985 pub fn mtp_rewind(&self, dstate: &mut MtpDraftState, rows: usize) -> Res<()> {
13988 if rows > dstate.rows {
13989 return Err("qwen4exp_gpu: mtp_rewind past the cache".into());
13990 }
13991 let mtp = self.mtp.as_ref().ok_or("qwen4exp_gpu: no MTP block")?;
13992 let MixerW::Qsa(qsa) = &mtp.layer.mixer else {
13993 return Err("qwen4exp_gpu: MTP mixer is not QSA".into());
13994 };
13995 let MixerState::Qsa {
13996 raw_keys,
13997 pooled_keys,
13998 pooled_dev_rows,
13999 raw_dev_rows,
14000 ..
14001 } = &mut dstate.mixer
14002 else {
14003 return Err("qwen4exp_gpu: MTP state is not QSA".into());
14004 };
14005 let idx_dim = qsa.overlay.head_dim as usize;
14006 raw_keys.truncate_rows(rows, idx_dim);
14007 let block = qsa.overlay.block_size as usize;
14008 pooled_keys.truncate((rows / block) * idx_dim);
14009 *pooled_dev_rows = (*pooled_dev_rows).min(pooled_keys.len() / idx_dim);
14013 *raw_dev_rows = (*raw_dev_rows).min(rows);
14016 dstate.rows = rows;
14017 dstate.committed = dstate.committed.min(rows);
14018 Ok(())
14019 }
14020
14021 #[allow(clippy::too_many_arguments)]
14032 pub fn mtp_draft_forward(
14033 &self,
14034 e: &Engine,
14035 tokens: &[u32],
14036 hidden_wide: &CudaSlice<f32>,
14037 wide_off: usize,
14038 dstate: &mut MtpDraftState,
14039 pos_off: usize,
14040 logits_all: bool,
14044 ) -> Res<(CudaSlice<f32>, CudaSlice<f32>)> {
14045 self.mtp_draft_forward_impl(
14046 e,
14047 DraftTokSrc::Host(tokens),
14048 hidden_wide,
14049 wide_off,
14050 dstate,
14051 pos_off,
14052 logits_all,
14053 )
14054 }
14055
14056 fn mtp_draft_forward_devslot(
14060 &self,
14061 e: &Engine,
14062 toks: &CudaSlice<u32>,
14063 slot: usize,
14064 hidden_wide: &CudaSlice<f32>,
14065 wide_off: usize,
14066 dstate: &mut MtpDraftState,
14067 ) -> Res<(CudaSlice<f32>, CudaSlice<f32>)> {
14068 self.mtp_draft_forward_impl(
14069 e,
14070 DraftTokSrc::DevSlot(toks, slot),
14071 hidden_wide,
14072 wide_off,
14073 dstate,
14074 1,
14075 false,
14076 )
14077 }
14078
14079 fn mtp_draft_forward_spec(
14083 &self,
14084 e: &Engine,
14085 tokens: &[u32],
14086 dev_embed: bool,
14087 hidden_wide: &CudaSlice<f32>,
14088 wide_off: usize,
14089 dstate: &mut MtpDraftState,
14090 ) -> Res<(CudaSlice<f32>, CudaSlice<f32>)> {
14091 let src = if dev_embed {
14092 DraftTokSrc::HostDev(tokens)
14093 } else {
14094 DraftTokSrc::Host(tokens)
14095 };
14096 self.mtp_draft_forward_impl(e, src, hidden_wide, wide_off, dstate, 1, false)
14097 }
14098
14099 fn draft_consume_ring(
14106 &self,
14107 de: &Engine,
14108 tokens: &[u32],
14109 dev_embed: bool,
14110 seed: &CudaSlice<f32>,
14111 ring: usize,
14112 first_row: usize,
14113 dstate: &mut MtpDraftState,
14114 ) -> Res<(CudaSlice<f32>, CudaSlice<f32>, usize)> {
14115 let mut out: Option<(CudaSlice<f32>, CudaSlice<f32>, usize)> = None;
14116 let mut done = 0usize;
14117 while done < tokens.len() {
14118 let slot = (first_row + done) % ring;
14119 let len = (tokens.len() - done).min(ring - slot);
14120 let (l, c) = self.mtp_draft_forward_spec(
14121 de,
14122 &tokens[done..done + len],
14123 dev_embed,
14124 seed,
14125 slot,
14126 dstate,
14127 )?;
14128 if let Some((pl, pc, _)) = out.take() {
14129 self.mtp_recycle(dstate, pl, pc);
14130 }
14131 out = Some((l, c, len));
14132 done += len;
14133 }
14134 out.ok_or("qwen4exp_gpu: empty draft consume".into())
14135 }
14136
14137 #[allow(clippy::too_many_arguments)]
14138 fn mtp_draft_forward_impl(
14139 &self,
14140 e: &Engine,
14141 tok_src: DraftTokSrc<'_>,
14142 hidden_wide: &CudaSlice<f32>,
14143 wide_off: usize,
14144 dstate: &mut MtpDraftState,
14145 pos_off: usize,
14146 logits_all: bool,
14147 ) -> Res<(CudaSlice<f32>, CudaSlice<f32>)> {
14148 self.check_draft_engine(e)?;
14149 let mtp = self
14150 .mtp
14151 .as_ref()
14152 .ok_or("qwen4exp_gpu: no MTP block loaded (LoadOptions::load_mtp)")?;
14153 let t = match tok_src {
14154 DraftTokSrc::Host(tokens) | DraftTokSrc::HostDev(tokens) => tokens.len(),
14155 DraftTokSrc::DevSlot(..) => 1,
14156 };
14157 let hidden = self.hidden;
14158 let streams = self.streams;
14159 let wide = streams * hidden;
14160 if t == 0 {
14161 return Err("qwen4exp_gpu: empty draft input".into());
14162 }
14163 if dstate.rows + t > dstate.capacity {
14164 return Err("qwen4exp_gpu: draft state capacity exceeded".into());
14165 }
14166 if hidden_wide.len() < (wide_off + t) * wide {
14167 return Err("qwen4exp_gpu: draft hidden seed rows out of range".into());
14168 }
14169 let base = dstate.rows;
14170 let ws = &mut dstate.ws;
14171 let cap = dstate.capacity;
14172
14173 let mut planes = prof_section(e, "mtp.fuse", || {
14175 let emb = match tok_src {
14176 DraftTokSrc::Host(tokens) => {
14177 let mut embedded = vec![0.0f32; t * hidden];
14178 for (row, &token) in tokens.iter().enumerate() {
14179 let token = token as usize;
14180 if token >= self.vocab {
14181 return Err(
14182 format!("qwen4exp_gpu: draft token {token} out of range").into()
14183 );
14184 }
14185 embedded[row * hidden..(row + 1) * hidden].copy_from_slice(
14186 &self.embed_host[token * hidden..(token + 1) * hidden],
14187 );
14188 }
14189 ws.take_f32_h2d(e, "mtp.emb", &embedded, cap * hidden)?
14190 }
14191 DraftTokSrc::HostDev(tokens) => {
14192 let ce = self
14198 .chain_embed
14199 .as_ref()
14200 .filter(|ce| !ce.for_trim && ce.rows == self.vocab)
14201 .ok_or("qwen4exp_gpu: HostDev embed needs the full-vocab chain table")?;
14202 for &token in tokens {
14203 if token as usize >= self.vocab {
14204 return Err(
14205 format!("qwen4exp_gpu: draft token {token} out of range").into()
14206 );
14207 }
14208 }
14209 let tok_d = e.gpu.stream().clone_htod(tokens)?;
14210 let mut emb = ws.take_f32(e, "mtp.emb", t * hidden, cap * hidden)?;
14211 let tv = tok_d.slice(0..t);
14212 embed_gather_rows_into(
14213 e,
14214 &ce.table,
14215 &tv,
14216 &mut emb,
14217 t,
14218 hidden,
14219 ce.qt,
14220 ce.row_bytes,
14221 )?;
14222 emb
14223 }
14224 DraftTokSrc::DevSlot(toks, slot) => {
14225 let ce = self
14231 .chain_embed
14232 .as_ref()
14233 .ok_or("qwen4exp_gpu: deferred draft step without arm_spec_devchain")?;
14234 let mut emb = ws.take_f32(e, "mtp.emb", hidden, cap * hidden)?;
14235 let tv = toks.slice(slot..slot + 1);
14236 embed_gather_rows_into(
14237 e,
14238 &ce.table,
14239 &tv,
14240 &mut emb,
14241 1,
14242 hidden,
14243 ce.qt,
14244 ce.row_bytes,
14245 )?;
14246 emb
14247 }
14248 };
14249 let mut enorm = ws.take_f32(e, "mtp.enorm", t * hidden, 0)?;
14250 e.rms_norm(
14251 &emb,
14252 &mtp.pre_norm_embed,
14253 &mut enorm,
14254 hidden,
14255 t,
14256 mtp.eps_embed,
14257 )?;
14258 let mut evec = ws.take_f32(e, "mtp.evec", t * hidden, 0)?;
14259 linear_trunk_into(
14260 e,
14261 &mtp.fc_embed,
14262 &mtp.fc_embed_b16,
14263 &enorm,
14264 &mut evec,
14265 t,
14266 hidden,
14267 hidden,
14268 )?;
14269 let mut hin = ws.take_f32(e, "mtp.hin", t * wide, 0)?;
14272 e.copy_range_into(&mut hin, 0, hidden_wide, wide_off * wide, t * wide)?;
14273 let mut hnorm = ws.take_f32(e, "mtp.hnorm", t * wide, 0)?;
14274 e.rms_norm(
14275 &hin,
14276 &mtp.pre_norm_hidden,
14277 &mut hnorm,
14278 wide,
14279 t,
14280 mtp.eps_hidden,
14281 )?;
14282 let mut fused = ws.take_f32(e, "mtp.fused", t * wide, 0)?;
14285 linear_trunk_into(
14286 e,
14287 &mtp.fc_hidden,
14288 &mtp.fc_hidden_b16,
14289 &hnorm,
14290 &mut fused,
14291 t * streams,
14292 hidden,
14293 hidden,
14294 )?;
14295 let mut planes: Vec<CudaSlice<f32>> = Vec::with_capacity(streams);
14296 for s in 0..streams {
14297 let mut plane = ws.take_f32(e, PLANE_SLOTS[s], t * hidden, cap * hidden)?;
14298 for tok in 0..t {
14299 e.copy_range_into(
14300 &mut plane,
14301 tok * hidden,
14302 &fused,
14303 (tok * streams + s) * hidden,
14304 hidden,
14305 )?;
14306 }
14307 let mut view = plane.slice_mut(0..t * hidden);
14308 e.axpy_into(&evec, 1.0, &mut view, t * hidden)?;
14309 planes.push(plane);
14310 }
14311 ws.put_f32("mtp.emb", emb);
14312 ws.put_f32("mtp.enorm", enorm);
14313 ws.put_f32("mtp.evec", evec);
14314 ws.put_f32("mtp.hin", hin);
14315 ws.put_f32("mtp.hnorm", hnorm);
14316 ws.put_f32("mtp.fused", fused);
14317 Ok(planes)
14318 })?;
14319
14320 let ptr_vals: Vec<u64> = {
14321 let stream = e.gpu.stream();
14322 planes.iter().map(|p| p.device_ptr(&stream).0).collect()
14323 };
14324 let ptrs = ws.take_u64_h2d(e, "hc.ptrs", &ptr_vals, 0)?;
14325
14326 let layer = &mtp.layer;
14328 let (mixed, inject) = prof_section(e, "mtp.hyper.read", || {
14329 self.gate_read(
14330 e,
14331 ws,
14332 &ptrs,
14333 &layer.attn_gate,
14334 &planes,
14335 t,
14336 layer.eps_attn,
14337 false,
14338 )
14339 })?;
14340 let MixerW::Qsa(qsa) = &layer.mixer else {
14341 return Err("qwen4exp_gpu: MTP mixer is not QSA".into());
14342 };
14343 let block_out = prof_section(e, "mtp.qsa", || {
14344 self.qsa_forward(
14345 e,
14346 ws,
14347 layer,
14348 qsa,
14349 &mixed,
14350 &mut dstate.mixer,
14351 base,
14352 t,
14353 pos_off,
14354 false,
14355 )
14356 })?;
14357 ws.put_f32("hc.mixed", mixed);
14358 prof_section(e, "mtp.hyper.write", || {
14359 self.gate_write(e, &mut planes, &ptrs, &block_out, &inject, t)
14360 })?;
14361 ws.put_f32("mixer.out", block_out);
14362 put_inject(ws, inject);
14363 let (mixed, inject) = prof_section(e, "mtp.hyper.read", || {
14364 self.gate_read(
14365 e,
14366 ws,
14367 &ptrs,
14368 &layer.mlp_gate,
14369 &planes,
14370 t,
14371 layer.eps_mlp,
14372 false,
14373 )
14374 })?;
14375 let mlp = prof_section(e, "mtp.moe", || {
14376 self.moe_forward(e, ws, &layer.moe, &mixed, t, t <= 32, layer.index)
14379 })?;
14380 ws.put_f32("hc.mixed", mixed);
14381 prof_section(e, "mtp.hyper.write", || {
14382 self.gate_write(e, &mut planes, &ptrs, &mlp, &inject, t)
14383 })?;
14384 ws.put_f32("moe.out", mlp);
14385 put_inject(ws, inject);
14386
14387 let mut carrier = ws.take_f32(e, "mtp.carrier", t * wide, 0)?;
14389 for (s, plane) in planes.iter().enumerate() {
14390 for tok in 0..t {
14391 e.copy_range_into(
14392 &mut carrier,
14393 tok * wide + s * hidden,
14394 plane,
14395 tok * hidden,
14396 hidden,
14397 )?;
14398 }
14399 }
14400
14401 let x = prof_section(e, "mtp.exit", || {
14407 Ok(self
14408 .gate_read_inner(
14409 e,
14410 ws,
14411 &ptrs,
14412 &mtp.mixer,
14413 &planes,
14414 t,
14415 self.exit_eps,
14416 false,
14417 false,
14418 )?
14419 .0)
14420 })?;
14421 ws.put_u64("hc.ptrs", ptrs);
14422 let trim = self.draft_trim.as_ref();
14427 let out_f = trim.map_or(self.vocab, |t| t.n);
14428 let (head_w, head_b16) = match self.mtp_dev1.as_ref() {
14431 Some(d) => (&d.output, &d.output_b16),
14432 None => (&self.output, &self.output_b16),
14433 };
14434 let head_into =
14435 |e: &Engine, x: &CudaSlice<f32>, y: &mut CudaSlice<f32>, rows: usize| -> Res<()> {
14436 match trim {
14437 Some(trim) => linear_trim_into(e, trim, x, y, rows, hidden),
14438 None => linear_trunk_into(e, head_w, head_b16, x, y, rows, hidden, self.vocab),
14439 }
14440 };
14441 let logits = prof_section(e, "mtp.lm_head", || {
14442 if logits_all {
14443 let mut logits = ws.take_f32(e, "mtp.logits", t * out_f, 0)?;
14444 head_into(e, &x, &mut logits, t)?;
14445 return Ok(logits);
14446 }
14447 let mut logits = ws.take_f32(e, "mtp.logits", out_f, 0)?;
14448 let mut x_last = ws.take_f32(e, "mtp.xlast", hidden, 0)?;
14449 e.copy_range_into(&mut x_last, 0, &x, (t - 1) * hidden, hidden)?;
14450 head_into(e, &x_last, &mut logits, 1)?;
14451 ws.put_f32("mtp.xlast", x_last);
14452 Ok(logits)
14453 })?;
14454 ws.put_f32("hc.mixed", x);
14455 for (s, plane) in planes.into_iter().enumerate() {
14456 ws.put_f32(PLANE_SLOTS[s], plane);
14457 }
14458 dstate.rows += t;
14459 Ok((logits, carrier))
14460 }
14461
14462 pub fn mtp_recycle(
14465 &self,
14466 dstate: &mut MtpDraftState,
14467 logits: CudaSlice<f32>,
14468 carrier: CudaSlice<f32>,
14469 ) {
14470 dstate.ws.put_f32("mtp.logits", logits);
14471 dstate.ws.put_f32("mtp.carrier", carrier);
14472 }
14473}
14474
14475#[derive(Clone, Copy)]
14481pub struct SpecSamplerCfg {
14482 pub temperature: f32,
14483 pub top_p: f32,
14484 pub top_k: usize,
14485 pub seed: u64,
14486}
14487
14488struct SpecRng(u64);
14490
14491impl SpecRng {
14492 fn next_f32(&mut self) -> f32 {
14493 let mut x = self.0;
14494 x ^= x >> 12;
14495 x ^= x << 25;
14496 x ^= x >> 27;
14497 self.0 = x;
14498 let bits = (x.wrapping_mul(0x2545_F491_4F6C_DD1D) >> 40) as u32;
14499 bits as f32 / (1u64 << 24) as f32
14500 }
14501}
14502
14503fn sample_row(cfg: &SpecSamplerCfg, rng: &mut SpecRng, row: &[f32]) -> u32 {
14505 let k = cfg.top_k.max(1).min(row.len());
14506 let mut idx: Vec<u32> = (0..row.len() as u32).collect();
14507 idx.select_nth_unstable_by(k - 1, |&a, &b| row[b as usize].total_cmp(&row[a as usize]));
14508 let mut top: Vec<(u32, f32)> = idx[..k].iter().map(|&i| (i, row[i as usize])).collect();
14509 top.sort_by(|a, b| b.1.total_cmp(&a.1));
14510 let temp = cfg.temperature.max(1e-6);
14511 let mx = top[0].1;
14512 let mut probs: Vec<f32> = top.iter().map(|&(_, v)| ((v - mx) / temp).exp()).collect();
14513 let sum: f32 = probs.iter().sum();
14514 for p in &mut probs {
14515 *p /= sum;
14516 }
14517 let mut cut = probs.len();
14519 let mut acc = 0.0f32;
14520 for (i, &p) in probs.iter().enumerate() {
14521 acc += p;
14522 if acc >= cfg.top_p {
14523 cut = i + 1;
14524 break;
14525 }
14526 }
14527 let renorm: f32 = probs[..cut].iter().sum();
14528 let draw = rng.next_f32() * renorm;
14529 let mut acc = 0.0f32;
14530 for (i, &p) in probs[..cut].iter().enumerate() {
14531 acc += p;
14532 if draw < acc {
14533 return top[i].0;
14534 }
14535 }
14536 top[cut - 1].0
14537}
14538
14539fn host_argmax(row: &[f32]) -> usize {
14543 let mut best = 0usize;
14544 for (i, &v) in row.iter().enumerate() {
14545 if v > row[best] {
14546 best = i;
14547 }
14548 }
14549 best
14550}
14551
14552fn ring_pieces(ring: usize, off: usize, t: usize) -> Vec<(usize, usize)> {
14562 debug_assert!(t <= ring, "wide-ring consumer wider than the ring");
14563 let slot = off % ring;
14564 if slot + t <= ring {
14565 vec![(slot, t)]
14566 } else {
14567 vec![(slot, ring - slot), (0, t - (ring - slot))]
14568 }
14569}
14570
14571fn cross_wide_rows(
14572 e: &Engine,
14573 de: &Engine,
14574 src: &CudaSlice<f32>,
14575 dst: &mut CudaSlice<f32>,
14576 off: usize,
14577 t: usize,
14578 wide: usize,
14579) -> Res<f64> {
14580 let t0 = std::time::Instant::now();
14581 let stream = de.gpu.stream();
14582 let bytes = t * wide * 4;
14583 let byte_off = (off * wide * 4) as u64;
14584 let (sp, _g0) = src.device_ptr(&stream);
14585 let (dp, _g1) = dst.device_ptr_mut(&stream);
14586 unsafe {
14587 cudarc::driver::result::memcpy_peer_async(
14588 de.ctx().cu_ctx(),
14589 dp + byte_off,
14590 e.ctx().cu_ctx(),
14591 sp + byte_off,
14592 bytes,
14593 stream.cu_stream(),
14594 )?;
14595 }
14596 stream.synchronize()?;
14597 Ok(t0.elapsed().as_secs_f64() * 1e3)
14598}
14599
14600fn embed_gather_rows_into(
14605 e: &Engine,
14606 table: &CudaSlice<u8>,
14607 tok_v: &CudaView<u32>,
14608 x_out: &mut CudaSlice<f32>,
14609 t: usize,
14610 n_embd: usize,
14611 qtype: i32,
14612 row_bytes: usize,
14613) -> Res<()> {
14614 let f = e.func("embed_gather_u32_t");
14615 let cfg = LaunchConfig {
14616 grid_dim: (((n_embd as u32).div_ceil(256)).max(1), t as u32, 1),
14617 block_dim: (256, 1, 1),
14618 shared_mem_bytes: 0,
14619 };
14620 let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
14621 let stream = e.gpu.stream();
14622 let mut b = stream.launch_builder(&f);
14623 b.arg(table)
14624 .arg(tok_v)
14625 .arg(x_out)
14626 .arg(&ne)
14627 .arg(&qt)
14628 .arg(&rb)
14629 .arg(&ti);
14630 unsafe {
14631 b.launch(cfg)?;
14632 }
14633 Ok(())
14634}
14635
14636#[derive(Debug, Default, Clone)]
14638pub struct SpecReport {
14639 pub tokens: Vec<u32>,
14640 pub rounds: usize,
14641 pub drafted: u64,
14642 pub accepted: u64,
14643 pub accept_hist: Vec<u64>,
14645 pub draft_ms: f64,
14646 pub verify_ms: f64,
14647 pub prefill_ms: f64,
14648 pub total_ms: f64,
14649 pub chain_ms: f64,
14652 pub replay_ms: f64,
14653 pub draft_prefill_ms: f64,
14654 pub cross_ms: f64,
14657 pub cross_bytes: u64,
14658 pub k_decays: Vec<(usize, usize)>,
14660 pub spec_off_at: Option<usize>,
14663 pub plain_steps: usize,
14664 pub round_wall: Vec<(usize, f64)>,
14669 pub zero_draft_rounds: usize,
14672 pub guard_stops: usize,
14673}
14674
14675#[derive(Clone, Copy, Debug)]
14685pub struct DynKCfg {
14686 pub window: usize,
14687 pub thr: f64,
14688 pub k_floor: usize,
14689}
14690
14691#[derive(Clone, Copy, Debug, Default)]
14695pub struct SpecOpts {
14696 pub dynk: Option<DynKCfg>,
14698 pub adapt_k_lo: Option<usize>,
14701 pub pmin: f32,
14708 pub defer: bool,
14718 pub defer_guard_sync: bool,
14725 pub prefill_chunk: Option<usize>,
14731 pub wide_ring: Option<usize>,
14736}
14737
14738pub fn spec_guard_trunc(probs: &[f32], pmin: f32) -> usize {
14747 probs.iter().position(|&p| p < pmin).unwrap_or(probs.len())
14748}
14749
14750#[derive(Debug, Default, Clone)]
14756pub struct SpecTraceRound {
14757 pub round: usize,
14758 pub gen_pos: usize,
14760 pub base: usize,
14762 pub k: usize,
14763 pub a: usize,
14764 pub drafts: Vec<u32>,
14765 pub targets: Vec<u32>,
14767 pub draft_top1: f32,
14772 pub draft_top2: f32,
14773 pub draft_tgt_logit: f32,
14774 pub draft_tgt_rank: usize,
14775 pub target_top1: f32,
14776 pub target_top2: f32,
14777 pub target_draft_logit: f32,
14778 pub target_entropy: f64,
14779 pub carrier_rel_l2: Vec<f32>,
14783 pub carrier_cos: Vec<f32>,
14784}
14785
14786impl SpecReport {
14787 pub fn accept_rate(&self) -> f64 {
14788 if self.drafted == 0 {
14789 0.0
14790 } else {
14791 self.accepted as f64 / self.drafted as f64
14792 }
14793 }
14794 pub fn mean_accept_len(&self) -> f64 {
14796 if self.rounds == 0 {
14797 0.0
14798 } else {
14799 self.tokens.len() as f64 / self.rounds as f64
14800 }
14801 }
14802}
14803
14804impl Qwen4ExpGpu {
14805 pub fn spec_arm(&self, e: &Engine, state: &mut Qwen4ExpState, k_cap: usize) -> Res<()> {
14809 self.spec_arm_ring(e, state, k_cap, state.capacity)
14810 }
14811
14812 pub fn spec_arm_ring(
14819 &self,
14820 e: &Engine,
14821 state: &mut Qwen4ExpState,
14822 k_cap: usize,
14823 ring_rows: usize,
14824 ) -> Res<()> {
14825 let ring_rows = ring_rows.min(state.capacity).max(k_cap + 2);
14826 if let Some(v) = state.verify.as_ref() {
14827 if v.k_cap == k_cap && v.ring_rows == ring_rows {
14828 return Ok(());
14829 }
14830 }
14831 let wide = self.streams * self.hidden;
14832 let mut gdn = Vec::with_capacity(self.layers.len());
14833 let mut ple = Vec::with_capacity(self.layers.len());
14834 for layer in &self.layers {
14835 gdn.push(match &layer.mixer {
14836 MixerW::Gdn(g) => {
14837 let p = &g.plan;
14838 let (nk, nv) = (p.key_heads as usize, p.value_heads as usize);
14839 let (hk, hv) = (p.key_head_dim as usize, p.value_head_dim as usize);
14840 let conv_dim = 2 * nk * hk + nv * hv;
14841 let pad = p.conv_kernel as usize - 1;
14842 Some(GdnStash {
14843 states: e.zeros(k_cap * nv * hv * hk)?,
14844 conv_pre: e.zeros(pad * conv_dim)?,
14845 qkv_rows: e.zeros(k_cap * conv_dim)?,
14846 scan_graph: None,
14847 scan_warm: None,
14848 })
14849 }
14850 MixerW::Qsa(_) => None,
14851 });
14852 ple.push(match layer.ple.as_ref() {
14853 Some(pw) => {
14854 let pad = (pw.plan.conv_kernel as usize - 1) * pw.plan.max_ngram as usize;
14855 let mut hist_pre = Vec::with_capacity(self.streams);
14856 let mut normed_rows = Vec::with_capacity(self.streams);
14857 for _ in 0..self.streams {
14858 hist_pre.push(e.zeros(pad * self.hidden)?);
14859 normed_rows.push(e.zeros(k_cap * self.hidden)?);
14860 }
14861 Some(PleStash {
14862 hist_pre,
14863 normed_rows,
14864 })
14865 }
14866 None => None,
14867 });
14868 }
14869 state.verify = Some(VerifyStash {
14870 k_cap,
14871 chunk: None,
14872 fused_chunk: None,
14873 gdn,
14874 ple,
14875 wide: e.zeros(ring_rows * wide)?,
14876 ring_rows,
14877 wide_dev1: None,
14878 argmax: Vec::new(),
14879 toks: unsafe { e.gpu.stream().alloc::<u32>(k_cap)? },
14880 want_argmax: false,
14881 want_argmax_t1: false,
14882 last_row_only: false,
14883 });
14884 Ok(())
14885 }
14886
14887 pub fn spec_disarm(&self, state: &mut Qwen4ExpState) {
14888 state.verify = None;
14889 }
14890
14891 pub fn set_verify_want_argmax(&self, state: &mut Qwen4ExpState, on: bool) -> Res<()> {
14892 state
14893 .verify
14894 .as_mut()
14895 .ok_or("qwen4exp_gpu: verify not armed")?
14896 .want_argmax = on;
14897 Ok(())
14898 }
14899
14900 pub fn verify_argmax_rows<'s>(&self, state: &'s Qwen4ExpState) -> Res<&'s [u32]> {
14902 Ok(&state
14903 .verify
14904 .as_ref()
14905 .ok_or("qwen4exp_gpu: verify not armed")?
14906 .argmax)
14907 }
14908
14909 pub fn verify_rewind(&self, e: &Engine, state: &mut Qwen4ExpState, keep: usize) -> Res<()> {
14914 let Some(v) = state.verify.as_mut() else {
14915 return Err("qwen4exp_gpu: verify not armed".into());
14916 };
14917 let Some((base, t)) = v.chunk.take() else {
14918 if let Some((fb, ft)) = v.fused_chunk.take() {
14919 return Err(format!(
14920 "qwen4exp_gpu: verify chunk (base {fb}, t {ft}) ran the FUSED program \
14921 (`vfuse` cost instrument) — no per-column GDN/PLE stash exists, so it \
14922 cannot be rewound. vfuse is a timing probe on a throwaway state; drop \
14923 the seam to run a spec loop."
14924 )
14925 .into());
14926 }
14927 return Err("qwen4exp_gpu: no live verify chunk to rewind".into());
14928 };
14929 if keep == 0 || keep > t {
14930 return Err("qwen4exp_gpu: rewind keep out of range".into());
14931 }
14932 if keep == t {
14933 return Ok(());
14934 }
14935 state.pos = base + keep;
14936 state.tokens.truncate(base + keep);
14937 for (li, (layer, lstate)) in self.layers.iter().zip(state.layers.iter_mut()).enumerate() {
14938 match (&layer.mixer, &mut lstate.mixer) {
14939 (
14940 MixerW::Qsa(qsa),
14941 MixerState::Qsa {
14942 raw_keys,
14943 pooled_keys,
14944 pooled_dev_rows,
14945 raw_dev_rows,
14946 idx_audit,
14947 ..
14948 },
14949 ) => {
14950 let idx_dim = qsa.overlay.head_dim as usize;
14951 raw_keys.truncate_rows(base + keep, idx_dim);
14952 let block = qsa.overlay.block_size as usize;
14953 pooled_keys.truncate(((base + keep) / block) * idx_dim);
14954 *pooled_dev_rows = (*pooled_dev_rows).min(pooled_keys.len() / idx_dim);
14956 *raw_dev_rows = (*raw_dev_rows).min(base + keep);
14959 if let Some(audit) = idx_audit.as_deref_mut() {
14961 audit.raw_f32.truncate_rows(base + keep, idx_dim);
14962 audit.pooled_f32.truncate(((base + keep) / block) * idx_dim);
14963 }
14964 }
14965 (MixerW::Gdn(g), MixerState::Gdn { conv, state: rec }) => {
14966 let st = v.gdn[li]
14967 .as_mut()
14968 .ok_or("qwen4exp_gpu: GDN layer without a verify stash")?;
14969 let p = &g.plan;
14970 let (nk, nv) = (p.key_heads as usize, p.value_heads as usize);
14971 let (hk, hv) = (p.key_head_dim as usize, p.value_head_dim as usize);
14972 let conv_dim = 2 * nk * hk + nv * hv;
14973 let pad = p.conv_kernel as usize - 1;
14974 let state_len = nv * hv * hk;
14975 e.copy_range_into(rec, 0, &st.states, (keep - 1) * state_len, state_len)?;
14976 if keep >= pad {
14977 e.copy_range_into(
14978 conv,
14979 0,
14980 &st.qkv_rows,
14981 (keep - pad) * conv_dim,
14982 pad * conv_dim,
14983 )?;
14984 } else {
14985 let keep_hist = pad - keep;
14986 e.copy_range_into(
14987 conv,
14988 0,
14989 &st.conv_pre,
14990 keep * conv_dim,
14991 keep_hist * conv_dim,
14992 )?;
14993 e.copy_range_into(
14994 conv,
14995 keep_hist * conv_dim,
14996 &st.qkv_rows,
14997 0,
14998 keep * conv_dim,
14999 )?;
15000 }
15001 }
15002 _ => return Err("qwen4exp_gpu: mixer/state mismatch in rewind".into()),
15003 }
15004 if let (Some(pw), Some(ps)) = (layer.ple.as_ref(), lstate.ple.as_mut()) {
15005 let st = v.ple[li]
15006 .as_mut()
15007 .ok_or("qwen4exp_gpu: PLE layer without a verify stash")?;
15008 let pad = (pw.plan.conv_kernel as usize - 1) * pw.plan.max_ngram as usize;
15009 let hidden = self.hidden;
15010 for s in 0..self.streams {
15011 let hist = &mut ps.conv_hist[s];
15012 if keep >= pad {
15013 e.copy_range_into(
15014 hist,
15015 0,
15016 &st.normed_rows[s],
15017 (keep - pad) * hidden,
15018 pad * hidden,
15019 )?;
15020 } else {
15021 let keep_hist = pad - keep;
15022 e.copy_range_into(
15023 hist,
15024 0,
15025 &st.hist_pre[s],
15026 keep * hidden,
15027 keep_hist * hidden,
15028 )?;
15029 e.copy_range_into(
15030 hist,
15031 keep_hist * hidden,
15032 &st.normed_rows[s],
15033 0,
15034 keep * hidden,
15035 )?;
15036 }
15037 }
15038 }
15039 }
15040 Ok(())
15041 }
15042
15043 fn draft_row_argmax(
15052 &self,
15053 e: &Engine,
15054 logits: &CudaSlice<f32>,
15055 row: usize,
15056 conf: bool,
15057 ) -> Res<(u32, f32)> {
15058 let width = self.draft_logits_width();
15059 let mut tok = unsafe { e.gpu.stream().alloc::<u32>(1)? };
15060 e.argmax_token_device_col(logits, row, width, &mut tok, 0)?;
15061 let p = if conf {
15062 if row != 0 {
15063 return Err("qwen4exp_gpu: draft confidence reads row 0 (the chain shape)".into());
15065 }
15066 let pd = e.prob_of_token_device(logits, &tok, width)?;
15067 e.gpu.stream().clone_dtoh(&pd)?[0]
15068 } else {
15069 1.0
15070 };
15071 Ok((self.draft_token(e.gpu.stream().clone_dtoh(&tok)?[0])?, p))
15072 }
15073
15074 #[allow(clippy::too_many_arguments)]
15087 pub fn spec_generate(
15088 &self,
15089 e: &Engine,
15090 prompt: &[u32],
15091 max_new: usize,
15092 k: usize,
15093 state: &mut Qwen4ExpState,
15094 dstate: &mut MtpDraftState,
15095 sampler: Option<SpecSamplerCfg>,
15096 ) -> Res<SpecReport> {
15097 self.spec_generate_ext(
15098 e,
15099 e,
15100 prompt,
15101 max_new,
15102 k,
15103 state,
15104 dstate,
15105 sampler,
15106 SpecOpts::default(),
15107 None,
15108 )
15109 }
15110
15111 #[allow(clippy::too_many_arguments)]
15122 pub fn spec_generate_ext(
15123 &self,
15124 e: &Engine,
15125 de: &Engine,
15126 prompt: &[u32],
15127 max_new: usize,
15128 k: usize,
15129 state: &mut Qwen4ExpState,
15130 dstate: &mut MtpDraftState,
15131 sampler: Option<SpecSamplerCfg>,
15132 opts: SpecOpts,
15133 mut trace: Option<&mut Vec<SpecTraceRound>>,
15134 ) -> Res<SpecReport> {
15135 use std::time::Instant;
15136 if k == 0 {
15137 return Err("qwen4exp_gpu: spec needs k >= 1".into());
15138 }
15139 let n = prompt.len();
15140 if n < 2 {
15141 return Err("qwen4exp_gpu: spec needs a >= 2 token prompt".into());
15142 }
15143 if state.pos != 0 || dstate.rows != 0 {
15144 return Err("qwen4exp_gpu: spec_generate wants FRESH trunk + draft states".into());
15145 }
15146 if state.capacity < n + max_new + k + 2 || dstate.capacity < n + max_new + k + 2 {
15147 return Err("qwen4exp_gpu: state capacity too small for prompt + max_new + k".into());
15148 }
15149 self.check_draft_engine(de)?;
15150 let dev1 = self.mtp_dev1.is_some();
15151 if !dev1 && de.ctx().ordinal() != e.ctx().ordinal() {
15152 return Err(
15153 "qwen4exp_gpu: draft engine on another card, but the draft was not \
15154 built there (load_from_dir_dev1)"
15155 .into(),
15156 );
15157 }
15158 let vocab = self.vocab;
15159 let wide_w = self.streams * self.hidden;
15160 let greedy = sampler.is_none();
15161 let tracing = trace.is_some();
15162 let guard = opts.pmin > 0.0;
15163 let deferred = opts.defer;
15164 if deferred && tracing {
15165 return Err(
15166 "qwen4exp_gpu: spec defer + trace are mutually exclusive (the trace \
15167 instrument reads per-step host rows); run the trace on the host-chain arm"
15168 .into(),
15169 );
15170 }
15171 if deferred {
15172 let ce = self.chain_embed.as_ref().ok_or(
15173 "qwen4exp_gpu: SpecOpts::defer needs arm_spec_devchain on the draft engine",
15174 )?;
15175 if ce.dev != de.ctx().ordinal() {
15176 return Err(format!(
15177 "qwen4exp_gpu: the chain-embed table lives on device {} but the \
15178 draft engine is device {} — re-arm arm_spec_devchain",
15179 ce.dev,
15180 de.ctx().ordinal()
15181 )
15182 .into());
15183 }
15184 if ce.for_trim != self.draft_trim.is_some() || ce.rows != self.draft_logits_width() {
15185 return Err(
15186 "qwen4exp_gpu: the chain-embed table was armed for a different trim \
15187 state — re-arm arm_spec_devchain after trim changes"
15188 .into(),
15189 );
15190 }
15191 }
15192 let (mut chain_toks_d, mut chain_probs_d) = if deferred {
15195 (
15196 Some(unsafe { de.gpu.stream().alloc::<u32>(k)? }),
15197 Some(de.zeros(k)?),
15198 )
15199 } else {
15200 (None, None)
15201 };
15202 let mut rng = sampler
15203 .as_ref()
15204 .map(|cfg| SpecRng(cfg.seed | 1))
15205 .unwrap_or(SpecRng(1));
15206 let t_total = Instant::now();
15207 let mut report = SpecReport {
15208 accept_hist: vec![0; k + 1],
15209 ..Default::default()
15210 };
15211
15212 match opts.wide_ring {
15213 Some(ring) => {
15214 let chunk = opts
15215 .prefill_chunk
15216 .ok_or("qwen4exp_gpu: SpecOpts::wide_ring needs prefill_chunk")?;
15217 if ring < 2 * chunk || ring < 2 * (k + 2) {
15218 return Err("qwen4exp_gpu: wide_ring must cover 2 prefill chunks".into());
15219 }
15220 self.spec_arm_ring(e, state, k + 1, ring)?;
15221 }
15222 None => self.spec_arm(e, state, k + 1)?,
15223 }
15224 self.set_verify_want_argmax(state, false)?;
15225 if let Some(v) = state.verify.as_mut() {
15226 v.want_argmax_t1 = deferred && greedy && !tracing;
15229 v.last_row_only = deferred;
15230 }
15231 let ring = state.verify.as_ref().expect("armed above").ring_rows;
15234 if dev1 {
15235 let v = state.verify.as_mut().expect("armed above");
15236 if v.wide_dev1.as_ref().is_none_or(|m| m.len() < ring * wide_w) {
15237 v.wide_dev1 = Some(de.zeros(ring * wide_w)?);
15238 }
15239 }
15240 let dev_embed = deferred
15245 && self
15246 .chain_embed
15247 .as_ref()
15248 .is_some_and(|ce| !ce.for_trim && ce.rows == self.vocab);
15249 let t_prefill = Instant::now();
15250 let mut draft_prefill_ms = 0f64;
15251 let x0: u32 = match opts.prefill_chunk {
15252 Some(chunk) if n > chunk => {
15259 let mut b = 0usize;
15260 let mut last = Vec::new();
15261 let mut chunks = 0usize;
15268 while b < n {
15269 let mut t = chunk.min(n - b);
15270 if n - (b + t) > 0 && n - (b + t) <= k + 1 {
15272 t = n - b;
15273 }
15274 let is_last = b + t == n;
15275 let head = if is_last {
15276 HeadMode::LastRow
15277 } else {
15278 HeadMode::Skip
15279 };
15280 let piece = self.forward_with(e, &prompt[b..b + t], state, None, head)?;
15281 let t_draft = Instant::now();
15282 if dev1 {
15283 let v = state.verify.as_mut().expect("armed above");
15284 let VerifyStash {
15285 wide, wide_dev1, ..
15286 } = v;
15287 let mirror = wide_dev1.as_mut().expect("allocated above");
15288 for (slot, len) in ring_pieces(ring, b, t) {
15289 report.cross_ms +=
15290 cross_wide_rows(e, de, wide, mirror, slot, len, wide_w)?;
15291 }
15292 report.cross_bytes += (t * wide_w * 4) as u64;
15293 }
15294 let p0 = b.max(1);
15297 if b + t > p0 {
15298 let v = state.verify.as_ref().expect("armed above");
15299 let seed: &CudaSlice<f32> = v.wide_dev1.as_ref().unwrap_or(&v.wide);
15300 let (ld, cd, _) = self.draft_consume_ring(
15301 de,
15302 &prompt[p0..b + t],
15303 dev_embed,
15304 seed,
15305 ring,
15306 p0 - 1,
15307 dstate,
15308 )?;
15309 self.mtp_recycle(dstate, ld, cd);
15310 }
15311 draft_prefill_ms += t_draft.elapsed().as_secs_f64() * 1e3;
15312 b += t;
15313 chunks += 1;
15314 if chunks % 8 == 0 || is_last {
15315 println!(
15316 "# spec-prefill-progress\tfill={b}/{n}\tchunks={chunks}\t\
15317 elapsed_s={:.1}\tdraft_s={:.1}",
15318 t_prefill.elapsed().as_secs_f64(),
15319 draft_prefill_ms / 1e3,
15320 );
15321 }
15322 if is_last {
15323 last = piece;
15324 }
15325 }
15326 dstate.committed = n - 1;
15327 let shed_bytes = state.ws.shed();
15334 state.graphs = StepGraphs::default();
15335 println!(
15336 "# spec-prefill-shed\tworkspace_mib={:.1}\tchunks={chunks}",
15337 shed_bytes as f64 / (1024.0 * 1024.0),
15338 );
15339 debug_assert_eq!(last.len(), vocab);
15340 match sampler.as_ref() {
15341 None => host_argmax(&last) as u32,
15342 Some(cfg) => sample_row(cfg, &mut rng, &last),
15343 }
15344 }
15345 _ => {
15347 let prefill = self.forward(e, prompt, state, None)?;
15348 let last = &prefill[prefill.len() - vocab..];
15353 let x0 = match sampler.as_ref() {
15354 None => host_argmax(last) as u32,
15355 Some(cfg) => sample_row(cfg, &mut rng, last),
15356 };
15357 let t_draft0 = Instant::now();
15358 if dev1 {
15359 let v = state.verify.as_mut().expect("armed above");
15360 let VerifyStash {
15361 wide, wide_dev1, ..
15362 } = v;
15363 let mirror = wide_dev1.as_mut().expect("allocated above");
15364 for (slot, len) in ring_pieces(ring, 0, n) {
15365 report.cross_ms += cross_wide_rows(e, de, wide, mirror, slot, len, wide_w)?;
15366 }
15367 report.cross_bytes += (n * wide_w * 4) as u64;
15368 }
15369 {
15370 let v = state.verify.as_ref().expect("armed above");
15371 let seed: &CudaSlice<f32> = v.wide_dev1.as_ref().unwrap_or(&v.wide);
15372 if n >= 2 {
15373 let (ld, cd, _) = self.draft_consume_ring(
15374 de,
15375 &prompt[1..],
15376 dev_embed,
15377 seed,
15378 ring,
15379 0,
15380 dstate,
15381 )?;
15382 self.mtp_recycle(dstate, ld, cd);
15383 }
15384 dstate.committed = n - 1;
15385 }
15386 draft_prefill_ms += t_draft0.elapsed().as_secs_f64() * 1e3;
15387 x0
15388 }
15389 };
15390 report.prefill_ms = t_prefill.elapsed().as_secs_f64() * 1e3 - draft_prefill_ms;
15391 self.set_verify_want_argmax(state, greedy && !tracing)?;
15394 report.tokens.push(x0);
15396
15397 let t_boot = Instant::now();
15399 let (mut tip_logits, mut tip_carrier) = {
15400 let v = state.verify.as_ref().expect("armed above");
15401 let seed: &CudaSlice<f32> = v.wide_dev1.as_ref().unwrap_or(&v.wide);
15402 self.mtp_draft_forward_spec(de, &[x0], dev_embed, seed, (n - 1) % ring, dstate)?
15403 };
15404 let mut tip_rows = 1usize;
15405 dstate.committed = dstate.rows;
15406 report.draft_prefill_ms = draft_prefill_ms + t_boot.elapsed().as_secs_f64() * 1e3;
15407 report.draft_ms += report.draft_prefill_ms;
15408 prof::split_prefill();
15410
15411 let mut m = n; let mut tip = x0;
15413 let mut k_cur = k;
15416 let mut k_next = k;
15417 let mut window: Vec<usize> = Vec::new();
15418 let mut round_idx = 0usize;
15419 while report.tokens.len() < max_new {
15420 if k_cur == 0 {
15421 let row = self.forward(e, &[tip], state, None)?;
15425 let next: u32 = match sampler.as_ref() {
15426 None if deferred => self.verify_argmax_rows(state)?[0],
15429 None => host_argmax(&row) as u32,
15430 Some(cfg) => sample_row(cfg, &mut rng, &row),
15431 };
15432 report.tokens.push(next);
15433 report.plain_steps += 1;
15434 report
15435 .round_wall
15436 .push((report.tokens.len(), t_total.elapsed().as_secs_f64() * 1e3));
15437 m += 1;
15438 tip = next;
15439 continue;
15440 }
15441 let k_round = k_next.min(k_cur).max(1);
15442 let t_draft = Instant::now();
15447 let mut drafts: Vec<u32> = Vec::with_capacity(k_round);
15448 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()) {
15451 let width = self.draft_logits_width();
15456 de.argmax_token_device_col(&tip_logits, 0, width, toks, 0)?;
15457 if guard {
15458 de.prob_of_token_device_col(&tip_logits, toks, 0, probs, 0, width)?;
15459 }
15460 let mut prev_logits = tip_logits;
15461 let mut prev_carrier = tip_carrier;
15462 let mut prev_rows = tip_rows;
15463 let mut drafted = 1usize;
15465 let mut stopped = false;
15466 if guard && opts.defer_guard_sync {
15467 let p = de.gpu.stream().clone_dtoh(&probs.slice(0..1))?[0];
15471 if p < opts.pmin {
15472 drafted = 0;
15473 stopped = true;
15474 report.guard_stops += 1;
15475 }
15476 }
15477 while !stopped && drafted < k_round {
15478 let (l2, c2) = self.mtp_draft_forward_devslot(
15479 de,
15480 toks,
15481 drafted - 1,
15482 &prev_carrier,
15483 prev_rows - 1,
15484 dstate,
15485 )?;
15486 self.mtp_recycle(dstate, prev_logits, prev_carrier);
15487 prev_logits = l2;
15488 prev_carrier = c2;
15489 prev_rows = 1;
15490 de.argmax_token_device_col(&prev_logits, 0, width, toks, drafted)?;
15491 if guard {
15492 de.prob_of_token_device_col(
15493 &prev_logits,
15494 toks,
15495 drafted,
15496 probs,
15497 drafted,
15498 width,
15499 )?;
15500 if opts.defer_guard_sync {
15501 let p = de
15502 .gpu
15503 .stream()
15504 .clone_dtoh(&probs.slice(drafted..drafted + 1))?[0];
15505 if p < opts.pmin {
15506 report.guard_stops += 1;
15507 break;
15508 }
15509 }
15510 }
15511 drafted += 1;
15512 }
15513 self.mtp_recycle(dstate, prev_logits, prev_carrier);
15514 if drafted > 0 {
15519 let raw = de.gpu.stream().clone_dtoh(&toks.slice(0..drafted))?;
15520 let trunc = if guard && !opts.defer_guard_sync {
15521 let pw = de.gpu.stream().clone_dtoh(&probs.slice(0..drafted))?;
15522 let trunc = spec_guard_trunc(&pw, opts.pmin);
15523 if trunc < drafted {
15524 report.guard_stops += 1;
15525 }
15526 trunc
15527 } else {
15528 drafted
15529 };
15530 for &r in raw.iter().take(trunc) {
15531 drafts.push(self.draft_token(r)?);
15532 }
15533 }
15534 } else {
15535 let (d1, c1) = self.draft_row_argmax(de, &tip_logits, 0, guard)?;
15536 if !(guard && c1 < opts.pmin) {
15537 drafts.push(d1);
15538 if tracing {
15539 chain_rows_h
15540 .push(de.dtoh_view(&tip_logits.slice(0..self.draft_logits_width()))?);
15541 }
15542 } else {
15543 report.guard_stops += 1;
15544 }
15545 let mut prev_logits = tip_logits;
15546 let mut prev_carrier = tip_carrier;
15547 let mut prev_rows = tip_rows;
15548 while !drafts.is_empty() && drafts.len() < k_round {
15549 if tracing {
15550 seeds_h.push(de.dtoh_view(
15551 &prev_carrier.slice((prev_rows - 1) * wide_w..prev_rows * wide_w),
15552 )?);
15553 }
15554 let lastd = *drafts.last().expect("non-empty");
15555 let (l2, c2) = self.mtp_draft_forward(
15556 de,
15557 &[lastd],
15558 &prev_carrier,
15559 prev_rows - 1,
15560 dstate,
15561 1,
15562 false,
15563 )?;
15564 self.mtp_recycle(dstate, prev_logits, prev_carrier);
15565 prev_logits = l2;
15566 prev_carrier = c2;
15567 prev_rows = 1;
15568 let (dn, cn) = self.draft_row_argmax(de, &prev_logits, 0, guard)?;
15569 if guard && cn < opts.pmin {
15570 report.guard_stops += 1;
15571 break;
15572 }
15573 drafts.push(dn);
15574 if tracing {
15575 chain_rows_h
15576 .push(de.dtoh_view(&prev_logits.slice(0..self.draft_logits_width()))?);
15577 }
15578 }
15579 self.mtp_recycle(dstate, prev_logits, prev_carrier);
15580 }
15581 let chain_ms = t_draft.elapsed().as_secs_f64() * 1e3;
15582 report.chain_ms += chain_ms;
15583 report.draft_ms += chain_ms;
15584
15585 let t_ver = Instant::now();
15588 let mut chunk = Vec::with_capacity(drafts.len() + 1);
15589 chunk.push(tip);
15590 chunk.extend_from_slice(&drafts);
15591 let tlen = chunk.len();
15592 let host_logits = self.forward(e, &chunk, state, None)?;
15593 let targets: Vec<u32> = if greedy && !tracing && (tlen > 1 || deferred) {
15596 self.verify_argmax_rows(state)?.to_vec()
15597 } else if greedy {
15598 (0..tlen)
15599 .map(|row| host_argmax(&host_logits[row * vocab..(row + 1) * vocab]) as u32)
15600 .collect()
15601 } else {
15602 let cfg = sampler.as_ref().expect("sampled mode");
15603 (0..tlen)
15604 .map(|row| {
15605 sample_row(cfg, &mut rng, &host_logits[row * vocab..(row + 1) * vocab])
15606 })
15607 .collect()
15608 };
15609 report.verify_ms += t_ver.elapsed().as_secs_f64() * 1e3;
15610 if targets.len() != tlen {
15611 return Err("qwen4exp_gpu: verify produced the wrong row count".into());
15612 }
15613
15614 let mut a = 0usize;
15616 while a < drafts.len() && drafts[a] == targets[a] {
15617 a += 1;
15618 }
15619 report.rounds += 1;
15620 report.drafted += drafts.len() as u64;
15621 report.accepted += a as u64;
15622 report.accept_hist[a] += 1;
15623 if drafts.is_empty() {
15624 report.zero_draft_rounds += 1;
15625 }
15626 report.tokens.extend_from_slice(&targets[0..=a]);
15627
15628 if let Some(tr) = trace.as_deref_mut() {
15631 let mut rec = SpecTraceRound {
15632 round: round_idx,
15633 gen_pos: report.tokens.len() - (a + 1),
15634 base: m,
15635 k: drafts.len(),
15636 a,
15637 drafts: drafts.clone(),
15638 targets: targets.clone(),
15639 draft_top1: f32::NAN,
15640 draft_top2: f32::NAN,
15641 draft_tgt_logit: f32::NAN,
15642 draft_tgt_rank: 0,
15643 target_top1: f32::NAN,
15644 target_top2: f32::NAN,
15645 target_draft_logit: f32::NAN,
15646 target_entropy: 0.0,
15647 carrier_rel_l2: Vec::new(),
15648 carrier_cos: Vec::new(),
15649 };
15650 if a < drafts.len() {
15651 let drow = &chain_rows_h[a];
15652 let trow = &host_logits[a * vocab..(a + 1) * vocab];
15653 let tgt = targets[a] as usize;
15654 let dtok = drafts[a] as usize;
15655 let (mut d1v, mut d2v) = (f32::NEG_INFINITY, f32::NEG_INFINITY);
15656 let mut rank = 0usize;
15657 let dt = drow[tgt];
15658 for &v in drow.iter() {
15659 if v > d1v {
15660 d2v = d1v;
15661 d1v = v;
15662 } else if v > d2v {
15663 d2v = v;
15664 }
15665 if v > dt {
15666 rank += 1;
15667 }
15668 }
15669 let (mut t1v, mut t2v) = (f32::NEG_INFINITY, f32::NEG_INFINITY);
15670 for &v in trow.iter() {
15671 if v > t1v {
15672 t2v = t1v;
15673 t1v = v;
15674 } else if v > t2v {
15675 t2v = v;
15676 }
15677 }
15678 let mx = t1v as f64;
15680 let mut z = 0.0f64;
15681 let mut sxl = 0.0f64;
15682 for &v in trow.iter() {
15683 let ev = ((v as f64) - mx).exp();
15684 z += ev;
15685 sxl += ev * ((v as f64) - mx);
15686 }
15687 rec.draft_top1 = d1v;
15688 rec.draft_top2 = d2v;
15689 rec.draft_tgt_logit = dt;
15690 rec.draft_tgt_rank = rank;
15691 rec.target_top1 = t1v;
15692 rec.target_top2 = t2v;
15693 rec.target_draft_logit = trow[dtok];
15694 rec.target_entropy = z.ln() - sxl / z;
15695 }
15696 let v = state.verify.as_ref().expect("armed above");
15697 for (j, seed) in seeds_h.iter().enumerate() {
15698 let slot = (m + j) % ring;
15699 let truth = e.dtoh_view(&v.wide.slice(slot * wide_w..(slot + 1) * wide_w))?;
15700 let mut dd = 0.0f64;
15701 let mut tt = 0.0f64;
15702 let mut st = 0.0f64;
15703 let mut ss = 0.0f64;
15704 for (&s, &t) in seed.iter().zip(truth.iter()) {
15705 let (s, t) = (s as f64, t as f64);
15706 dd += (s - t) * (s - t);
15707 tt += t * t;
15708 st += s * t;
15709 ss += s * s;
15710 }
15711 rec.carrier_rel_l2
15712 .push((dd.sqrt() / tt.sqrt().max(1e-30)) as f32);
15713 rec.carrier_cos
15714 .push((st / (ss.sqrt() * tt.sqrt()).max(1e-30)) as f32);
15715 }
15716 tr.push(rec);
15717 }
15718
15719 if tlen > 1 {
15721 self.verify_rewind(e, state, a + 1)?;
15722 }
15723 self.mtp_rewind(dstate, m)?;
15724 let t_draft2 = Instant::now();
15725 let x_next = targets[a];
15726 let mut replay: Vec<u32> = drafts[0..a].to_vec();
15727 replay.push(x_next);
15728 if dev1 {
15729 let v = state.verify.as_mut().expect("armed above");
15730 let VerifyStash {
15731 wide, wide_dev1, ..
15732 } = v;
15733 let mirror = wide_dev1.as_mut().expect("allocated above");
15734 for (slot, len) in ring_pieces(ring, m, replay.len()) {
15735 report.cross_ms += cross_wide_rows(e, de, wide, mirror, slot, len, wide_w)?;
15736 }
15737 report.cross_bytes += (replay.len() * wide_w * 4) as u64;
15738 }
15739 let (l, c, last_len) = {
15740 let v = state.verify.as_ref().expect("armed above");
15741 let seed: &CudaSlice<f32> = v.wide_dev1.as_ref().unwrap_or(&v.wide);
15742 self.draft_consume_ring(de, &replay, dev_embed, seed, ring, m, dstate)?
15743 };
15744 tip_logits = l;
15745 tip_carrier = c;
15746 tip_rows = last_len;
15747 dstate.committed = dstate.rows;
15748 let replay_ms = t_draft2.elapsed().as_secs_f64() * 1e3;
15749 report.replay_ms += replay_ms;
15750 report.draft_ms += replay_ms;
15751 m += a + 1;
15752 tip = x_next;
15753 report
15754 .round_wall
15755 .push((report.tokens.len(), t_total.elapsed().as_secs_f64() * 1e3));
15756
15757 if let Some(lo) = opts.adapt_k_lo {
15759 k_next = (a + 1).clamp(lo.max(1), k);
15760 }
15761 if let Some(cfg) = opts.dynk {
15762 window.push(a);
15763 if window.len() >= cfg.window.max(1) {
15764 let mean = window.iter().sum::<usize>() as f64 / window.len() as f64;
15765 if mean < cfg.thr {
15766 let new_k = k_cur.saturating_sub(1).max(cfg.k_floor);
15767 if new_k < k_cur {
15768 k_cur = new_k;
15769 report.k_decays.push((round_idx, k_cur));
15770 if k_cur == 0 {
15771 report.spec_off_at = Some(report.tokens.len());
15772 }
15773 }
15774 }
15775 window.clear();
15776 }
15777 }
15778 round_idx += 1;
15779 }
15780 report.tokens.truncate(max_new);
15781 report.total_ms = t_total.elapsed().as_secs_f64() * 1e3;
15782 Ok(report)
15783 }
15784}
15785
15786enum BankTensorSrc {
15804 F32(Vec<f32>),
15806 Nvfp4 {
15814 codes: Vec<u8>,
15815 scales: Vec<u8>,
15816 macros: Vec<f32>,
15817 act_scale: Option<f32>,
15818 },
15819 Bf16(Vec<u8>),
15822}
15823
15824struct BankSrc {
15825 gate: BankTensorSrc, up: BankTensorSrc, down: BankTensorSrc, n_expert: usize,
15829 ff: usize,
15830 hidden: usize,
15831}
15832
15833struct FusedTensorPlan {
15836 name: String,
15837 shape: [usize; 3],
15838}
15839
15840struct PerExpertPlan {
15845 names: Vec<String>, out_f: usize,
15847 in_f: usize,
15848 quant: memra_gguf::tensor_contract::QuantConstraint,
15849}
15850
15851enum BankPlanSrc {
15852 Fused {
15854 gate_up: FusedTensorPlan,
15855 down: FusedTensorPlan,
15856 keep_bf16: bool,
15861 },
15862 PerExpert {
15864 gate: PerExpertPlan,
15865 up: PerExpertPlan,
15866 down: PerExpertPlan,
15867 },
15868}
15869
15870struct BankPlan {
15884 n_expert: usize,
15885 ff: usize,
15886 hidden: usize,
15887 src: BankPlanSrc,
15888}
15889
15890impl BankPlan {
15891 fn read(&self, model: &memra_gguf::safetensors::StModel) -> Res<BankSrc> {
15894 let (gate, up, down) = match &self.src {
15895 BankPlanSrc::Fused {
15896 gate_up,
15897 down,
15898 keep_bf16,
15899 } => {
15900 let fused = read_bank_tensor(
15901 model,
15902 &gate_up.name,
15903 gate_up.shape[0],
15904 gate_up.shape[1],
15905 gate_up.shape[2],
15906 *keep_bf16,
15907 )?;
15908 let (gate, up) = split_fused_gate_up(fused, self.n_expert, self.ff, self.hidden)?;
15909 let down = read_bank_tensor(
15910 model,
15911 &down.name,
15912 down.shape[0],
15913 down.shape[1],
15914 down.shape[2],
15915 *keep_bf16,
15916 )?;
15917 (gate, up, down)
15918 }
15919 BankPlanSrc::PerExpert { gate, up, down } => (
15920 read_per_expert_bank(model, gate)?,
15921 read_per_expert_bank(model, up)?,
15922 read_per_expert_bank(model, down)?,
15923 ),
15924 };
15925 Ok(BankSrc {
15926 gate,
15927 up,
15928 down,
15929 n_expert: self.n_expert,
15930 ff: self.ff,
15931 hidden: self.hidden,
15932 })
15933 }
15934}
15935
15936fn check_bank_header(model: &memra_gguf::safetensors::StModel, name: &str) -> Res<()> {
15946 let info = model
15947 .info(name)
15948 .ok_or_else(|| format!("qwen4exp_gpu: checkpoint is missing {name}"))?;
15949 match info.dtype.as_str() {
15950 "BF16" | "F32" | "U8" => Ok(()),
15951 other => Err(format!("qwen4exp_gpu: {name} bank dtype {other} unsupported").into()),
15952 }
15953}
15954
15955fn read_per_expert_bank(
15958 model: &memra_gguf::safetensors::StModel,
15959 plan: &PerExpertPlan,
15960) -> Res<BankTensorSrc> {
15961 let mut experts = Vec::with_capacity(plan.names.len());
15962 for name in &plan.names {
15963 experts.push(read_per_expert(
15964 model, name, plan.out_f, plan.in_f, plan.quant,
15965 )?);
15966 }
15967 assemble_per_expert_bank(experts)
15968}
15969
15970pub struct LoadedCheckpoint {
15979 pub plan: ModelPlan,
15980 pub weights: ReferenceWeights,
15981 model: memra_gguf::safetensors::StModel,
15982 bank_plans: std::collections::BTreeMap<u32, BankPlan>,
15983 tables: std::collections::BTreeMap<u32, Vec<u8>>, }
15985
15986fn norm_fold_add_one(name: &str) -> bool {
15992 name.contains("norm") && name.ends_with(".weight") && !name.ends_with("linear_attn.norm.weight")
15993}
15994
15995fn indexer_layernorm(name: &str) -> bool {
15999 name.contains(".indexer.")
16000 && (name.ends_with("q_layernorm.weight") || name.ends_with("k_layernorm.weight"))
16001}
16002
16003#[derive(Default, Clone, Copy)]
16005pub struct LoadOptions {
16006 pub host_bf16_banks: bool,
16011 pub indexer_norm_raw: bool,
16014 pub load_mtp: bool,
16019}
16020
16021fn bridge_transform(
16022 transform: memra_gguf::tensor_contract::TensorTransform,
16023) -> Res<memra_gguf::hf_mapping::TransformKind> {
16024 use memra_gguf::hf_mapping::TransformKind as K;
16025 use memra_gguf::tensor_contract::TensorTransform as T;
16026 Ok(match transform {
16027 T::Identity => K::Identity,
16028 T::NormAddOne => K::NormPlusOne,
16029 T::QkvVReorderRows => K::QkvVReorderRows,
16030 T::ZReorderRows => K::ZReorderRows,
16031 T::AbReorderRows => K::AbReorderRows,
16032 T::NegExpReorderHeads => K::NegExpReorderHeads,
16033 T::ReorderHeads => K::ReorderHeads,
16034 T::Conv1dSqueezeReorder => K::Conv1dSqueezeReorder,
16035 T::OutReorderColumns => K::OutReorderCols,
16036 other => return Err(format!("qwen4exp_gpu: unsupported transform {other:?}").into()),
16037 })
16038}
16039
16040fn dequant_float(
16041 name: &str,
16042 info: &memra_gguf::safetensors::StInfo,
16043 bytes: &[u8],
16044) -> Res<Vec<f32>> {
16045 let elements: usize = info.shape.iter().map(|&d| d as usize).product();
16046 match info.dtype.as_str() {
16047 "BF16" | "F32" => Ok(memra_gguf::dequant::dequantize(
16048 info.ggml_type()
16049 .map_err(|error| format!("qwen4exp_gpu: {name}: {error}"))?,
16050 bytes,
16051 elements,
16052 )),
16053 other => Err(format!("qwen4exp_gpu: {name} has unsupported float dtype {other}").into()),
16054 }
16055}
16056
16057fn read_i64(name: &str, info: &memra_gguf::safetensors::StInfo, bytes: &[u8]) -> Res<Vec<i64>> {
16058 if info.dtype != "I64" {
16059 return Err(format!("qwen4exp_gpu: {name} must be I64, got {}", info.dtype).into());
16060 }
16061 Ok(bytes
16062 .chunks_exact(8)
16063 .map(|chunk| i64::from_le_bytes(chunk.try_into().unwrap()))
16064 .collect())
16065}
16066
16067fn validate_macro(stem: &str, value: f32) -> Res<()> {
16075 if !(value.is_finite() && value > 0.0) {
16076 return Err(format!(
16077 "qwen4exp_gpu: {stem}.weight_scale_2 carries a non-finite/non-positive \
16078 macro {value}"
16079 )
16080 .into());
16081 }
16082 Ok(())
16083}
16084
16085fn read_bank_tensor(
16090 model: &memra_gguf::safetensors::StModel,
16091 name: &str,
16092 n_expert: usize,
16093 out_f: usize,
16094 in_f: usize,
16095 host_bf16: bool,
16096) -> Res<BankTensorSrc> {
16097 let (info, bytes) = model
16098 .raw(name)
16099 .ok_or_else(|| format!("qwen4exp_gpu: checkpoint is missing {name}"))?;
16100 match info.dtype.as_str() {
16101 "BF16" | "F32" => {
16102 if info.shape != [n_expert as u64, out_f as u64, in_f as u64] {
16103 return Err(format!("qwen4exp_gpu: {name} bank shape mismatch").into());
16104 }
16105 if host_bf16 && info.dtype == "BF16" {
16106 if bytes.len() != n_expert * out_f * in_f * 2 {
16107 return Err(format!("qwen4exp_gpu: {name} bank byte-length mismatch").into());
16108 }
16109 return Ok(BankTensorSrc::Bf16(bytes.to_vec()));
16110 }
16111 Ok(BankTensorSrc::F32(dequant_float(name, info, bytes)?))
16112 }
16113 "U8" => {
16114 if in_f % 16 != 0
16115 || info.shape != [n_expert as u64, out_f as u64, (in_f / 2) as u64]
16116 || bytes.len() != n_expert * out_f * in_f / 2
16117 {
16118 return Err(format!("qwen4exp_gpu: {name} NVFP4 code shape mismatch").into());
16119 }
16120 let stem = name.strip_suffix(".weight").unwrap_or(name);
16121 let scale_name = format!("{stem}.weight_scale");
16122 let (scale_info, scale_bytes) = model
16123 .raw(&scale_name)
16124 .ok_or_else(|| format!("qwen4exp_gpu: missing {scale_name}"))?;
16125 if scale_info.dtype != "F8_E4M3"
16126 || scale_info.shape != [n_expert as u64, out_f as u64, (in_f / 16) as u64]
16127 || scale_bytes.len() != n_expert * out_f * in_f / 16
16128 {
16129 return Err(format!("qwen4exp_gpu: {scale_name} shape mismatch").into());
16130 }
16131 let macros = match model.raw(&format!("{stem}.weight_scale_2")) {
16132 Some((macro_info, macro_bytes))
16133 if macro_info.dtype == "F32" && macro_bytes.len() == n_expert * 4 =>
16134 {
16135 macro_bytes
16136 .chunks_exact(4)
16137 .map(|chunk| f32::from_le_bytes(chunk.try_into().unwrap()))
16138 .collect()
16139 }
16140 None => vec![1.0; n_expert],
16141 _ => return Err(format!("qwen4exp_gpu: {stem}.weight_scale_2 malformed").into()),
16142 };
16143 for &m in ¯os {
16144 validate_macro(stem, m)?;
16145 }
16146 let act_scale = match model.raw(&format!("{stem}.input_scale")) {
16150 Some((is_info, is_bytes))
16151 if is_info.dtype == "F32" && is_bytes.len() == n_expert * 4 =>
16152 {
16153 let mut mx = 0.0f32;
16154 for chunk in is_bytes.chunks_exact(4) {
16155 let v = f32::from_le_bytes(chunk.try_into().unwrap());
16156 if !(v.is_finite() && v > 0.0) {
16157 return Err(format!(
16158 "qwen4exp_gpu: {stem}.input_scale carries a non-finite/\
16159 non-positive value {v}"
16160 )
16161 .into());
16162 }
16163 mx = mx.max(v);
16164 }
16165 Some(mx)
16166 }
16167 Some(_) => {
16168 return Err(format!("qwen4exp_gpu: {stem}.input_scale malformed").into());
16169 }
16170 None => None,
16171 };
16172 Ok(BankTensorSrc::Nvfp4 {
16173 codes: bytes.to_vec(),
16174 scales: scale_bytes.to_vec(),
16175 macros,
16176 act_scale,
16177 })
16178 }
16179 other => Err(format!("qwen4exp_gpu: {name} bank dtype {other} unsupported").into()),
16180 }
16181}
16182
16183fn split_fused_gate_up(
16187 fused: BankTensorSrc,
16188 n_expert: usize,
16189 ff: usize,
16190 hidden: usize,
16191) -> Res<(BankTensorSrc, BankTensorSrc)> {
16192 match fused {
16193 BankTensorSrc::F32(data) => {
16194 if data.len() != n_expert * 2 * ff * hidden {
16195 return Err("qwen4exp_gpu: fused gate_up bank size mismatch".into());
16196 }
16197 let mut gate = Vec::with_capacity(n_expert * ff * hidden);
16198 let mut up = Vec::with_capacity(n_expert * ff * hidden);
16199 for expert in 0..n_expert {
16200 let base = expert * 2 * ff * hidden;
16201 gate.extend_from_slice(&data[base..base + ff * hidden]);
16202 up.extend_from_slice(&data[base + ff * hidden..base + 2 * ff * hidden]);
16203 }
16204 Ok((BankTensorSrc::F32(gate), BankTensorSrc::F32(up)))
16205 }
16206 BankTensorSrc::Bf16(bytes) => {
16207 let row = hidden * 2; if bytes.len() != n_expert * 2 * ff * row {
16209 return Err("qwen4exp_gpu: fused bf16 gate_up bank size mismatch".into());
16210 }
16211 let mut gate = Vec::with_capacity(n_expert * ff * row);
16212 let mut up = Vec::with_capacity(n_expert * ff * row);
16213 for expert in 0..n_expert {
16214 let base = expert * 2 * ff * row;
16215 gate.extend_from_slice(&bytes[base..base + ff * row]);
16216 up.extend_from_slice(&bytes[base + ff * row..base + 2 * ff * row]);
16217 }
16218 Ok((BankTensorSrc::Bf16(gate), BankTensorSrc::Bf16(up)))
16219 }
16220 BankTensorSrc::Nvfp4 {
16221 codes,
16222 scales,
16223 macros,
16224 act_scale,
16225 } => {
16226 let code_row = hidden / 2;
16227 let scale_row = hidden / 16;
16228 let mut gate_codes = Vec::with_capacity(n_expert * ff * code_row);
16229 let mut up_codes = Vec::with_capacity(n_expert * ff * code_row);
16230 let mut gate_scales = Vec::with_capacity(n_expert * ff * scale_row);
16231 let mut up_scales = Vec::with_capacity(n_expert * ff * scale_row);
16232 for expert in 0..n_expert {
16233 let cbase = expert * 2 * ff * code_row;
16234 gate_codes.extend_from_slice(&codes[cbase..cbase + ff * code_row]);
16235 up_codes
16236 .extend_from_slice(&codes[cbase + ff * code_row..cbase + 2 * ff * code_row]);
16237 let sbase = expert * 2 * ff * scale_row;
16238 gate_scales.extend_from_slice(&scales[sbase..sbase + ff * scale_row]);
16239 up_scales
16240 .extend_from_slice(&scales[sbase + ff * scale_row..sbase + 2 * ff * scale_row]);
16241 }
16242 Ok((
16243 BankTensorSrc::Nvfp4 {
16244 codes: gate_codes,
16245 scales: gate_scales,
16246 macros: macros.clone(),
16247 act_scale,
16248 },
16249 BankTensorSrc::Nvfp4 {
16250 codes: up_codes,
16251 scales: up_scales,
16252 macros,
16253 act_scale,
16254 },
16255 ))
16256 }
16257 }
16258}
16259
16260enum PerExpertSrc {
16265 F32(Vec<f32>),
16266 Nvfp4 {
16267 codes: Vec<u8>,
16268 scales: Vec<u8>,
16269 macro_scale: f32,
16270 input_scale: Option<f32>,
16271 },
16272}
16273
16274fn read_per_expert(
16275 model: &memra_gguf::safetensors::StModel,
16276 name: &str,
16277 out_f: usize,
16278 in_f: usize,
16279 quant: memra_gguf::tensor_contract::QuantConstraint,
16280) -> Res<PerExpertSrc> {
16281 use memra_gguf::tensor_contract::QuantConstraint;
16282 let (info, bytes) = model
16283 .raw(name)
16284 .ok_or_else(|| format!("qwen4exp_gpu: checkpoint is missing {name}"))?;
16285 match quant {
16286 QuantConstraint::ExactFloat(_) => {
16287 if info.shape != [out_f as u64, in_f as u64] {
16288 return Err(format!("qwen4exp_gpu: {name} shape mismatch").into());
16289 }
16290 Ok(PerExpertSrc::F32(dequant_float(name, info, bytes)?))
16291 }
16292 QuantConstraint::Nvfp4 => {
16293 if info.dtype != "U8"
16294 || in_f % 16 != 0
16295 || info.shape != [out_f as u64, (in_f / 2) as u64]
16296 || bytes.len() != out_f * in_f / 2
16297 {
16298 return Err(format!("qwen4exp_gpu: {name} NVFP4 code shape mismatch").into());
16299 }
16300 let stem = name.strip_suffix(".weight").unwrap_or(name);
16301 let (scale_info, scale_bytes) = model
16302 .raw(&format!("{stem}.weight_scale"))
16303 .ok_or_else(|| format!("qwen4exp_gpu: missing {stem}.weight_scale"))?;
16304 if scale_info.dtype != "F8_E4M3"
16305 || scale_info.shape != [out_f as u64, (in_f / 16) as u64]
16306 || scale_bytes.len() != out_f * in_f / 16
16307 {
16308 return Err(format!("qwen4exp_gpu: {stem}.weight_scale shape mismatch").into());
16309 }
16310 let macro_scale = match model.raw(&format!("{stem}.weight_scale_2")) {
16311 Some((macro_info, macro_bytes))
16312 if macro_info.dtype == "F32" && macro_bytes.len() == 4 =>
16313 {
16314 f32::from_le_bytes(macro_bytes.try_into().unwrap())
16315 }
16316 None => 1.0,
16317 _ => return Err(format!("qwen4exp_gpu: {stem}.weight_scale_2 malformed").into()),
16318 };
16319 validate_macro(stem, macro_scale)?;
16320 let input_scale = match model.raw(&format!("{stem}.input_scale")) {
16325 Some((input_info, input_bytes)) => {
16326 if input_info.dtype != "F32" || input_bytes.len() != 4 {
16327 return Err(format!("qwen4exp_gpu: {stem}.input_scale malformed").into());
16328 }
16329 let v = f32::from_le_bytes(input_bytes.try_into().unwrap());
16330 if !(v.is_finite() && v > 0.0) {
16331 return Err(format!(
16332 "qwen4exp_gpu: {stem}.input_scale carries a non-finite/non-positive \
16333 value {v}"
16334 )
16335 .into());
16336 }
16337 Some(v)
16338 }
16339 None => None,
16340 };
16341 Ok(PerExpertSrc::Nvfp4 {
16342 codes: bytes.to_vec(),
16343 scales: scale_bytes.to_vec(),
16344 macro_scale,
16345 input_scale,
16346 })
16347 }
16348 other => Err(format!("qwen4exp_gpu: per-expert quant {other:?} unsupported").into()),
16349 }
16350}
16351
16352fn assemble_per_expert_bank(experts: Vec<PerExpertSrc>) -> Res<BankTensorSrc> {
16355 let mut f32_data: Vec<f32> = Vec::new();
16356 let mut codes: Vec<u8> = Vec::new();
16357 let mut scales: Vec<u8> = Vec::new();
16358 let mut macros: Vec<f32> = Vec::new();
16359 let mut act_scale: Option<f32> = None;
16360 let mut act_scale_complete = true;
16361 let mut kinds = (false, false);
16362 for expert in experts {
16363 match expert {
16364 PerExpertSrc::F32(data) => {
16365 kinds.0 = true;
16366 f32_data.extend_from_slice(&data);
16367 }
16368 PerExpertSrc::Nvfp4 {
16369 codes: c,
16370 scales: s,
16371 macro_scale,
16372 input_scale,
16373 } => {
16374 kinds.1 = true;
16375 codes.extend_from_slice(&c);
16376 scales.extend_from_slice(&s);
16377 macros.push(macro_scale);
16378 match input_scale {
16379 Some(v) => act_scale = Some(act_scale.map_or(v, |a: f32| a.max(v))),
16380 None => act_scale_complete = false,
16381 }
16382 }
16383 }
16384 }
16385 match kinds {
16386 (true, false) => Ok(BankTensorSrc::F32(f32_data)),
16387 (false, true) => Ok(BankTensorSrc::Nvfp4 {
16388 codes,
16389 scales,
16390 macros,
16391 act_scale: if act_scale_complete { act_scale } else { None },
16392 }),
16393 _ => Err("qwen4exp_gpu: mixed per-expert kinds within one projection".into()),
16394 }
16395}
16396
16397fn family_layer_index(key: &str) -> Option<u32> {
16399 key.strip_prefix("trunk.layers.")?
16400 .split('.')
16401 .next()?
16402 .parse()
16403 .ok()
16404}
16405
16406fn plan_layer_at(plan: &ModelPlan, index: u32) -> Option<&memra_gguf::model_plan::LayerPlan> {
16414 let n_trunk = plan.layers.len() as u32;
16415 if index < n_trunk {
16416 plan.layers.get(index as usize)
16417 } else {
16418 plan.mtp_blocks
16419 .iter()
16420 .find(|block| block.layer.index == index)
16421 .map(|block| &block.layer)
16422 }
16423}
16424
16425pub fn read_checkpoint(dir: &std::path::Path) -> Res<LoadedCheckpoint> {
16426 read_checkpoint_with(dir, LoadOptions::default())
16427}
16428
16429pub fn read_checkpoint_with(dir: &std::path::Path, opts: LoadOptions) -> Res<LoadedCheckpoint> {
16431 use memra_gguf::model_packs::qwen4_exp::{ExpertDialect, tensor_contract_for};
16432 use memra_gguf::tensor_contract::{TensorMatch, TensorOwner};
16433 let config = std::fs::read_to_string(dir.join("config.json"))?;
16434 let cfg =
16435 memra_gguf::config::ModelConfig::from_hf(&memra_gguf::config::HfConfig::parse(&config));
16436 let pack = memra_gguf::model_packs::for_config(&cfg)
16437 .ok_or("qwen4exp_gpu: no model pack matches this config")?;
16438 if pack.family != "qwen4_exp" {
16439 return Err(format!("qwen4exp_gpu: config resolves to pack {}", pack.family).into());
16440 }
16441 let plan = pack.compile_plan(&cfg)?;
16442 let model = memra_gguf::safetensors::StModel::open(dir)?;
16443 let dialect = if model
16445 .raw("model.language_model.layers.0.mlp.experts.0.gate_proj.weight")
16446 .is_some()
16447 {
16448 ExpertDialect::PerExpertModelopt
16449 } else {
16450 ExpertDialect::FusedBanks
16451 };
16452 let contract = tensor_contract_for(&cfg, &plan, dialect)?;
16453
16454 let mut weights = ReferenceWeights::new();
16455 let mut gate_up_banks: std::collections::BTreeMap<u32, FusedTensorPlan> = Default::default();
16456 let mut per_expert: std::collections::BTreeMap<
16460 (u32, u8),
16461 std::collections::BTreeMap<
16462 u32,
16463 (
16464 String,
16465 usize,
16466 usize,
16467 memra_gguf::tensor_contract::QuantConstraint,
16468 ),
16469 >,
16470 > = Default::default();
16471 let mut down_banks: std::collections::BTreeMap<u32, FusedTensorPlan> = Default::default();
16472 let mut tables: std::collections::BTreeMap<u32, Vec<u8>> = Default::default();
16473 let n_trunk = plan.layers.len() as u32;
16474
16475 for requirement in &contract.requirements {
16476 match requirement.owner {
16477 TensorOwner::Mtp(_) if !opts.load_mtp => continue,
16481 TensorOwner::Vision(_) => continue,
16482 TensorOwner::Global | TensorOwner::Layer(_) | TensorOwner::Mtp(_) => {}
16483 }
16484 if requirement.match_mode == TensorMatch::All {
16486 let TensorId::Family { key, .. } = &requirement.id else {
16487 return Err("qwen4exp_gpu: unexpected All-mode requirement".into());
16488 };
16489 let layer =
16490 family_layer_index(key).ok_or("qwen4exp_gpu: n-gram bank outside a trunk layer")?;
16491 let mut bytes = Vec::new();
16492 for name in &requirement.names {
16493 let (info, shard) = model
16494 .raw(name)
16495 .ok_or_else(|| format!("qwen4exp_gpu: checkpoint is missing {name}"))?;
16496 if info.dtype != "BF16" || info.shape != requirement.shape {
16497 return Err(format!("qwen4exp_gpu: {name} shard shape/dtype mismatch").into());
16498 }
16499 bytes.extend_from_slice(shard);
16500 }
16501 tables.insert(layer, bytes);
16502 continue;
16503 }
16504 let name = &requirement.names[0];
16505 if let TensorId::Family { key, .. } = &requirement.id {
16508 if key.ends_with(".ple_embedding.ngram_embedding") {
16509 let layer = family_layer_index(key)
16510 .ok_or("qwen4exp_gpu: n-gram table outside a trunk layer")?;
16511 let (info, bytes) = model
16512 .raw(name)
16513 .ok_or_else(|| format!("qwen4exp_gpu: checkpoint is missing {name}"))?;
16514 if info.dtype != "BF16" || info.shape != requirement.shape {
16515 return Err(format!("qwen4exp_gpu: {name} table shape/dtype mismatch").into());
16516 }
16517 tables.insert(layer, bytes.to_vec());
16518 continue;
16519 }
16520 }
16521 if let TensorId::Expert {
16523 layer,
16524 expert,
16525 tensor,
16526 } = requirement.id
16527 {
16528 let (out_f, in_f) = (requirement.shape[0] as usize, requirement.shape[1] as usize);
16529 check_bank_header(&model, name)?;
16530 let proj = match tensor {
16531 memra_gguf::tensor_contract::ExpertTensor::Gate => 0u8,
16532 memra_gguf::tensor_contract::ExpertTensor::Up => 1,
16533 memra_gguf::tensor_contract::ExpertTensor::Down => 2,
16534 };
16535 if per_expert
16536 .entry((layer, proj))
16537 .or_default()
16538 .insert(expert, (name.clone(), out_f, in_f, requirement.quant))
16539 .is_some()
16540 {
16541 return Err(format!(
16542 "qwen4exp_gpu: duplicate per-expert row layer {layer} expert {expert}"
16543 )
16544 .into());
16545 }
16546 continue;
16547 }
16548 if let TensorId::Layer { index, tensor } = requirement.id {
16550 if matches!(
16551 tensor,
16552 LayerTensor::MoeExpertGateUpBank | LayerTensor::MoeExpertDownBank
16553 ) {
16554 let shape = [
16555 requirement.shape[0] as usize,
16556 requirement.shape[1] as usize,
16557 requirement.shape[2] as usize,
16558 ];
16559 check_bank_header(&model, name)?;
16560 let address = FusedTensorPlan {
16561 name: name.clone(),
16562 shape,
16563 };
16564 if tensor == LayerTensor::MoeExpertGateUpBank {
16565 gate_up_banks.insert(index, address);
16566 } else {
16567 down_banks.insert(index, address);
16568 }
16569 continue;
16570 }
16571 }
16572 let (info, bytes) = model
16573 .raw(name)
16574 .ok_or_else(|| format!("qwen4exp_gpu: checkpoint is missing {name}"))?;
16575 if info.shape != requirement.shape {
16576 return Err(format!(
16577 "qwen4exp_gpu: {name} shape {:?} != contract {:?}",
16578 info.shape, requirement.shape
16579 )
16580 .into());
16581 }
16582 if info.dtype == "I64" {
16583 let ints = read_i64(name, info, bytes)?;
16584 let shape: Vec<usize> = info.shape.iter().map(|&d| d as usize).collect();
16585 weights.insert(
16586 requirement.id.clone(),
16587 ReferenceTensor::new_i64(shape, ints)?,
16588 );
16589 continue;
16590 }
16591 let mut data = dequant_float(name, info, bytes)?;
16592 if norm_fold_add_one(name) && !(opts.indexer_norm_raw && indexer_layernorm(name)) {
16593 for value in &mut data {
16594 *value += 1.0;
16595 }
16596 }
16597 let kind = bridge_transform(requirement.transform)?;
16598 let (ne_out, out_bytes) = kind.apply(&mut data, info.ne(), &cfg);
16599 let data: Vec<f32> = out_bytes
16600 .chunks_exact(4)
16601 .map(|chunk| f32::from_le_bytes(chunk.try_into().unwrap()))
16602 .collect();
16603 let mut shape: Vec<usize> = ne_out.iter().rev().map(|&d| d as usize).collect();
16604 if name.ends_with("ple.conv1d.weight") && shape.len() == 3 && shape[1] == 1 {
16607 shape = vec![shape[0], shape[2]];
16608 }
16609 if name.ends_with("mlp.shared_expert_gate.weight") && shape.len() == 2 && shape[0] == 1 {
16611 shape = vec![shape[1]];
16612 }
16613 weights.insert(requirement.id.clone(), ReferenceTensor::new(shape, data)?);
16614 }
16615
16616 let mut bank_plans = std::collections::BTreeMap::new();
16617 for (index, gate_up) in gate_up_banks {
16621 let down = down_banks
16622 .remove(&index)
16623 .ok_or_else(|| format!("qwen4exp_gpu: layer {index} has gate_up but no down bank"))?;
16624 let layer_plan = plan_layer_at(&plan, index)
16625 .ok_or_else(|| format!("qwen4exp_gpu: bank at unknown layer index {index}"))?;
16626 let MlpPlan::Moe(moe) = &layer_plan.mlp else {
16627 return Err(format!("qwen4exp_gpu: bank on non-MoE layer {index}").into());
16628 };
16629 bank_plans.insert(
16630 index,
16631 BankPlan {
16632 n_expert: moe.expert_count as usize,
16633 ff: moe.expert_intermediate_size as usize,
16634 hidden: plan.hidden_size as usize,
16635 src: BankPlanSrc::Fused {
16636 gate_up,
16637 down,
16638 keep_bf16: opts.host_bf16_banks || index >= n_trunk,
16641 },
16642 },
16643 );
16644 }
16645 if !down_banks.is_empty() {
16646 return Err("qwen4exp_gpu: down bank without a gate_up twin".into());
16647 }
16648 let mut per_layer: std::collections::BTreeMap<u32, [Option<PerExpertPlan>; 3]> =
16651 Default::default();
16652 for ((layer, proj), experts) in per_expert {
16653 let layer_plan = plan_layer_at(&plan, layer)
16654 .ok_or_else(|| format!("qwen4exp_gpu: per-expert rows at unknown layer {layer}"))?;
16655 let MlpPlan::Moe(moe) = &layer_plan.mlp else {
16656 return Err(format!("qwen4exp_gpu: per-expert rows on non-MoE layer {layer}").into());
16657 };
16658 let count = moe.expert_count as usize;
16659 if experts.len() != count || experts.keys().last().copied() != Some(count as u32 - 1) {
16662 return Err(format!(
16663 "qwen4exp_gpu: layer {layer} proj {proj} has {} experts, plan says {count}",
16664 experts.len()
16665 )
16666 .into());
16667 }
16668 let mut names = Vec::with_capacity(count);
16672 let mut geometry: Option<(usize, usize, memra_gguf::tensor_contract::QuantConstraint)> =
16673 None;
16674 for (name, out_f, in_f, quant) in experts.into_values() {
16675 match geometry {
16676 None => geometry = Some((out_f, in_f, quant)),
16677 Some((o, i, q)) if (o, i) == (out_f, in_f) && q == quant => {}
16678 Some((o, i, _)) => {
16679 return Err(format!(
16680 "qwen4exp_gpu: layer {layer} proj {proj} mixes expert geometry \
16681 ({out_f}, {in_f}) vs ({o}, {i}) or quant classes"
16682 )
16683 .into());
16684 }
16685 }
16686 names.push(name);
16687 }
16688 let (out_f, in_f, quant) = geometry
16689 .ok_or_else(|| format!("qwen4exp_gpu: layer {layer} proj {proj} has no expert rows"))?;
16690 per_layer.entry(layer).or_default()[proj as usize] = Some(PerExpertPlan {
16691 names,
16692 out_f,
16693 in_f,
16694 quant,
16695 });
16696 }
16697 for (layer, mut projections) in per_layer {
16698 let MlpPlan::Moe(moe) = &plan_layer_at(&plan, layer).expect("checked above").mlp else {
16699 unreachable!("checked above");
16700 };
16701 let take = |slot: &mut Option<PerExpertPlan>, what: &str| -> Res<PerExpertPlan> {
16702 slot.take()
16703 .ok_or_else(|| format!("qwen4exp_gpu: layer {layer} missing {what} experts").into())
16704 };
16705 bank_plans.insert(
16706 layer,
16707 BankPlan {
16708 n_expert: moe.expert_count as usize,
16709 ff: moe.expert_intermediate_size as usize,
16710 hidden: plan.hidden_size as usize,
16711 src: BankPlanSrc::PerExpert {
16712 gate: take(&mut projections[0], "gate")?,
16713 up: take(&mut projections[1], "up")?,
16714 down: take(&mut projections[2], "down")?,
16715 },
16716 },
16717 );
16718 }
16719 Ok(LoadedCheckpoint {
16720 plan,
16721 weights,
16722 model,
16723 bank_plans,
16724 tables,
16725 })
16726}
16727
16728pub struct BankFingerprint {
16736 pub layer: u32,
16737 pub projection: &'static str,
16739 pub kind: &'static str,
16741 pub bytes: usize,
16742 pub digest: String,
16744}
16745
16746impl LoadedCheckpoint {
16747 fn read_bank(&self, index: u32) -> Res<BankSrc> {
16750 self.bank_plans
16751 .get(&index)
16752 .ok_or_else(|| format!("qwen4exp_gpu: no bank source for layer {index}"))?
16753 .read(&self.model)
16754 }
16755
16756 pub fn bank_fingerprints(&self) -> Res<Vec<BankFingerprint>> {
16760 use sha2::{Digest, Sha256};
16761 let mut out = Vec::new();
16762 for (&layer, plan) in &self.bank_plans {
16763 let bank = plan.read(&self.model)?;
16764 for (projection, src) in [("gate", &bank.gate), ("up", &bank.up), ("down", &bank.down)]
16765 {
16766 let mut hasher = Sha256::new();
16767 let (kind, bytes) = match src {
16768 BankTensorSrc::F32(data) => {
16770 for value in data {
16771 hasher.update(value.to_le_bytes());
16772 }
16773 ("f32", data.len() * 4)
16774 }
16775 BankTensorSrc::Bf16(raw) => {
16776 hasher.update(raw);
16777 ("bf16", raw.len())
16778 }
16779 BankTensorSrc::Nvfp4 {
16780 codes,
16781 scales,
16782 macros,
16783 ..
16784 } => {
16785 hasher.update(codes);
16786 hasher.update(scales);
16787 for m in macros {
16788 hasher.update(m.to_le_bytes());
16789 }
16790 ("nvfp4", codes.len() + scales.len() + macros.len() * 4)
16791 }
16792 };
16793 out.push(BankFingerprint {
16794 layer,
16795 projection,
16796 kind,
16797 bytes,
16798 digest: hasher
16799 .finalize()
16800 .iter()
16801 .map(|b| format!("{b:02x}"))
16802 .collect(),
16803 });
16804 }
16805 }
16806 Ok(out)
16807 }
16808
16809 pub fn into_reference_weights(mut self) -> Res<ReferenceWeights> {
16813 let bank_plans = std::mem::take(&mut self.bank_plans);
16814 for (index, plan) in bank_plans {
16815 let bank = plan.read(&self.model)?;
16816 let gate = bank_to_f32(&bank.gate, bank.n_expert, bank.ff, bank.hidden)?;
16817 let up = bank_to_f32(&bank.up, bank.n_expert, bank.ff, bank.hidden)?;
16818 let down = bank_to_f32(&bank.down, bank.n_expert, bank.hidden, bank.ff)?;
16819 self.weights.insert(
16820 layer_id(index, LayerTensor::MoeExpertGateBank),
16821 ReferenceTensor::new(vec![bank.n_expert, bank.ff, bank.hidden], gate)?,
16822 );
16823 self.weights.insert(
16824 layer_id(index, LayerTensor::MoeExpertUpBank),
16825 ReferenceTensor::new(vec![bank.n_expert, bank.ff, bank.hidden], up)?,
16826 );
16827 self.weights.insert(
16828 layer_id(index, LayerTensor::MoeExpertDownBank),
16829 ReferenceTensor::new(vec![bank.n_expert, bank.hidden, bank.ff], down)?,
16830 );
16831 }
16832 for (index, bytes) in self.tables {
16833 let ple = self.plan.layers[index as usize]
16834 .ple
16835 .as_ref()
16836 .ok_or("qwen4exp_gpu: table on a non-PLE layer")?;
16837 let head_dim = ple.head_embed_dim as usize;
16838 let table = NgramTable::Bf16(bytes);
16839 let rows = table.rows(head_dim);
16840 let mut data = vec![0.0f32; rows * head_dim];
16841 for row in 0..rows {
16842 table.gather_into(
16843 row,
16844 head_dim,
16845 &mut data[row * head_dim..(row + 1) * head_dim],
16846 );
16847 }
16848 self.weights.insert(
16849 family_id(format!(
16850 "trunk.layers.{index}.ple.ple_embedding.ngram_embedding"
16851 )),
16852 ReferenceTensor::new(vec![rows, head_dim], data)?,
16853 );
16854 }
16855 Ok(self.weights)
16856 }
16857}
16858
16859impl LoadedCheckpoint {
16860 pub fn mtp_reference_weights(&self) -> Res<ReferenceWeights> {
16866 let mut weights = self.weights.clone();
16867 let n_trunk = self.plan.layers.len() as u32;
16868 for (index, plan) in &self.bank_plans {
16869 if *index < n_trunk {
16870 continue;
16871 }
16872 let bank = plan.read(&self.model)?;
16873 let gate = bank_to_f32(&bank.gate, bank.n_expert, bank.ff, bank.hidden)?;
16874 let up = bank_to_f32(&bank.up, bank.n_expert, bank.ff, bank.hidden)?;
16875 let down = bank_to_f32(&bank.down, bank.n_expert, bank.hidden, bank.ff)?;
16876 weights.insert(
16877 layer_id(*index, LayerTensor::MoeExpertGateBank),
16878 ReferenceTensor::new(vec![bank.n_expert, bank.ff, bank.hidden], gate)?,
16879 );
16880 weights.insert(
16881 layer_id(*index, LayerTensor::MoeExpertUpBank),
16882 ReferenceTensor::new(vec![bank.n_expert, bank.ff, bank.hidden], up)?,
16883 );
16884 weights.insert(
16885 layer_id(*index, LayerTensor::MoeExpertDownBank),
16886 ReferenceTensor::new(vec![bank.n_expert, bank.hidden, bank.ff], down)?,
16887 );
16888 }
16889 Ok(weights)
16890 }
16891}
16892
16893fn bank_to_f32(bank: &BankTensorSrc, n_expert: usize, out_f: usize, in_f: usize) -> Res<Vec<f32>> {
16896 match bank {
16897 BankTensorSrc::F32(data) => Ok(data.clone()),
16898 BankTensorSrc::Bf16(bytes) => Ok(bytes
16899 .chunks_exact(2)
16900 .map(|b| f32::from_bits(u32::from(u16::from_le_bytes([b[0], b[1]])) << 16))
16901 .collect()),
16902 BankTensorSrc::Nvfp4 {
16903 codes,
16904 scales,
16905 macros,
16906 ..
16907 } => {
16908 let mut out = Vec::with_capacity(n_expert * out_f * in_f);
16909 let wbytes = out_f * in_f / 2;
16910 let sbytes = out_f * in_f / 16;
16911 for expert in 0..n_expert {
16912 out.extend(memra_gguf::dsv4::dequant_nvfp4_expert(
16913 &codes[expert * wbytes..(expert + 1) * wbytes],
16914 &scales[expert * sbytes..(expert + 1) * sbytes],
16915 macros[expert],
16916 out_f,
16917 in_f,
16918 ));
16919 }
16920 Ok(out)
16921 }
16922 }
16923}
16924
16925impl Qwen4ExpGpu {
16926 pub fn load_from_dir(e: &Engine, dir: &std::path::Path) -> Res<Self> {
16931 Self::from_loaded_checkpoint(e, read_checkpoint(dir)?)
16932 }
16933
16934 pub fn load_from_dir_with(e: &Engine, dir: &std::path::Path, opts: LoadOptions) -> Res<Self> {
16936 Self::from_loaded_checkpoint(e, read_checkpoint_with(dir, opts)?)
16937 }
16938
16939 pub fn load_from_dir_dev1(
16944 e: &Engine,
16945 draft_e: &Engine,
16946 dir: &std::path::Path,
16947 opts: LoadOptions,
16948 ) -> Res<Self> {
16949 Self::from_loaded_checkpoint_dual(e, Some(draft_e), read_checkpoint_with(dir, opts)?)
16950 }
16951
16952 pub fn from_loaded_checkpoint(e: &Engine, checkpoint: LoadedCheckpoint) -> Res<Self> {
16955 Self::from_loaded_checkpoint_dual(e, None, checkpoint)
16956 }
16957
16958 pub fn from_loaded_checkpoint_dual(
16962 e: &Engine,
16963 draft_e: Option<&Engine>,
16964 checkpoint: LoadedCheckpoint,
16965 ) -> Res<Self> {
16966 let LoadedCheckpoint {
16967 plan,
16968 weights,
16969 model,
16970 bank_plans,
16971 tables,
16972 } = checkpoint;
16973 let mut parts = ExternalParts::default();
16974 let n_trunk = plan.layers.len() as u32;
16975 let upload_half = |e: &Engine, src: BankTensorSrc, device_bf16: bool| -> Res<BankHalf> {
16976 Ok(match src {
16977 BankTensorSrc::F32(data) => BankHalf::F32(e.htod(&data)?),
16978 BankTensorSrc::Nvfp4 {
16979 codes,
16980 scales,
16981 macros,
16982 ..
16983 } => BankHalf::Nvfp4 {
16984 codes: e.htod_bytes(&codes)?,
16985 scales: e.htod_bytes(&scales)?,
16986 macros_dev: e.htod(¯os)?,
16987 macros,
16988 },
16989 BankTensorSrc::Bf16(bytes) if device_bf16 => {
16993 BankHalf::DeviceBf16(e.htod_bytes(&bytes)?)
16994 }
16995 BankTensorSrc::Bf16(bytes) => BankHalf::HostBf16(bytes),
16996 })
16997 };
16998 for (index, bank_plan) in bank_plans {
17005 let bank = bank_plan.read(&model)?;
17006 let device_bf16 = index >= n_trunk;
17007 let bank_e = if device_bf16 { draft_e.unwrap_or(e) } else { e };
17009 parts.expert_banks.insert(
17010 index,
17011 ExpertBank {
17012 gate: upload_half(bank_e, bank.gate, device_bf16)?,
17013 up: upload_half(bank_e, bank.up, device_bf16)?,
17014 down: upload_half(bank_e, bank.down, device_bf16)?,
17015 },
17016 );
17017 }
17018 drop(model);
17021 for (index, bytes) in tables {
17022 parts.ngram_tables.insert(index, NgramTable::Bf16(bytes));
17023 }
17024 Self::from_reference_weights_with(e, draft_e, &plan, &weights, parts)
17025 }
17026}
17027
17028struct GdnHalfW {
17066 nk_h: usize,
17067 nv_h: usize,
17068 hk: usize,
17069 hv: usize,
17070 kernel: usize,
17071 gate_activation: GdnGateActivation,
17072 proj_b16: CudaSlice<u8>,
17075 out_b16: CudaSlice<u8>, conv_w: CudaSlice<f32>, a: CudaSlice<f32>, dt: CudaSlice<f32>, norm: CudaSlice<f32>, }
17081
17082struct QsaHalfW {
17084 nh_h: usize,
17085 nkv_h: usize,
17086 hd: usize,
17087 n_rot: usize,
17088 rope_base: f32,
17089 scale: f32,
17090 proj_b16: CudaSlice<u8>,
17093 wo_b16: CudaSlice<u8>, q_norm: Option<CudaSlice<f32>>,
17095 k_norm: Option<CudaSlice<f32>>,
17096 yarn: Option<YarnRopeW>,
17098}
17099
17100enum MixerHalfW {
17101 Gdn(GdnHalfW),
17102 Qsa(QsaHalfW),
17103}
17104
17105struct Nvfp4Half {
17107 codes: CudaSlice<u8>,
17108 scales: CudaSlice<u8>,
17109 macros_dev: CudaSlice<f32>,
17110}
17111
17112struct MoeHalfW {
17113 gate1: Nvfp4Half,
17115 up1: Nvfp4Half,
17116 down1: Nvfp4Half,
17117 shared_down0: CudaSlice<u8>, shared_down1: CudaSlice<u8>, shared_input_gate1: Option<CudaSlice<f32>>, shared_gu0_b16: CudaSlice<u8>,
17126 shared_gu1_b16: CudaSlice<u8>,
17127}
17128
17129struct Tp2LayerW {
17130 attn_gate1: GateW,
17131 mlp_gate1: GateW,
17132 mixer0: MixerHalfW,
17133 mixer1: MixerHalfW,
17134 moe: MoeHalfW,
17135 ple1: Option<PleW>,
17136 place: LayerPlacement,
17141}
17142
17143pub struct Tp2Shard {
17145 layers: Vec<Tp2LayerW>,
17146 exit_gate1: GateW,
17147 lm_head1: CudaSlice<u8>, vsplit: usize,
17149 stage0: [CudaSlice<f32>; 2], stage1: [CudaSlice<f32>; 2], stage0_raw: [u64; 2],
17156 stage1_raw: [u64; 2],
17157 ev0: [cudarc::driver::CudaEvent; 2], ev1: [cudarc::driver::CudaEvent; 2], }
17160
17161enum MixerHalfState {
17162 Gdn {
17163 conv: CudaSlice<f32>, state: CudaSlice<f32>, },
17166 Qsa {
17167 kv: QsaKvStore,
17171 },
17172}
17173
17174struct Tp2LayerState {
17175 m0: MixerHalfState,
17176 m1: MixerHalfState,
17177 ple1: Option<PleState>,
17178}
17179
17180struct Tp2State {
17181 ws1: StepPool,
17182 layers: Vec<Tp2LayerState>,
17183 graphs: Tp2Graphs,
17184 pf_stage0: Option<[CudaSlice<f32>; 2]>, pf_stage1: Option<[CudaSlice<f32>; 2]>,
17190 pf_stage0_raw: [u64; 2],
17191 pf_stage1_raw: [u64; 2],
17192 pf_rows: usize,
17193}
17194
17195#[derive(Default)]
17204struct Tp2Graphs {
17205 warm: bool,
17206 a: [Vec<Option<GraphEntry>>; 2],
17207 b: [Vec<Option<GraphEntry>>; 2],
17208 c: [Vec<Option<GraphEntry>>; 2],
17211 d: [Vec<Option<GraphEntry>>; 2],
17213 exit: [Option<GraphEntry>; 2],
17214}
17215
17216fn tp2_gdn_head_map(d: usize, nk: usize, nv: usize) -> Vec<usize> {
17220 let nk_h = nk / 2;
17221 let nv_h = nv / 2;
17222 (0..nv_h)
17223 .map(|j| (j / nk_h) * nk + (j % nk_h) + d * nk_h)
17224 .collect()
17225}
17226
17227fn gather_rows_host(src: &[f32], in_f: usize, rows: &[usize]) -> Vec<f32> {
17229 let mut out = Vec::with_capacity(rows.len() * in_f);
17230 for &r in rows {
17231 out.extend_from_slice(&src[r * in_f..(r + 1) * in_f]);
17232 }
17233 out
17234}
17235
17236fn gather_cols_host(
17238 src: &[f32],
17239 nrows: usize,
17240 ncols: usize,
17241 blocks: &[(usize, usize)],
17242) -> Vec<f32> {
17243 let width: usize = blocks.iter().map(|&(_, l)| l).sum();
17244 let mut out = Vec::with_capacity(nrows * width);
17245 for r in 0..nrows {
17246 for &(start, len) in blocks {
17247 out.extend_from_slice(&src[r * ncols + start..r * ncols + start + len]);
17248 }
17249 }
17250 out
17251}
17252
17253fn need_twin(e: &Engine, data: &[f32], in_f: usize, what: &str) -> Res<CudaSlice<u8>> {
17254 bf16_twin(e, data, in_f)?.ok_or_else(|| {
17255 format!("qwen4exp_gpu tp2: {what} has no exact bf16 twin (in_f {in_f})").into()
17256 })
17257}
17258
17259fn launch_push(e: &Engine, src: &CudaSlice<f32>, dst_raw: u64, n: usize) -> Res<()> {
17262 let f = e.func("q4e_push_f32");
17263 let cfg = LaunchConfig::for_num_elems(n as u32);
17264 let nl = n as i64;
17265 let stream = e.gpu.stream();
17266 let mut b = stream.launch_builder(&f);
17267 b.arg(src).arg(&dst_raw).arg(&nl);
17268 unsafe {
17269 b.launch(cfg)?;
17270 }
17271 Ok(())
17272}
17273
17274pub fn tp2_enable_p2p(e0: &Engine, e1: &Engine) -> Res<()> {
17278 use cudarc::driver::sys;
17279 for (src, dst) in [(e0, e1), (e1, e0)] {
17280 let mut can = 0i32;
17281 unsafe {
17282 sys::cuDeviceCanAccessPeer(&mut can, src.ctx().cu_device(), dst.ctx().cu_device())
17283 .result()?;
17284 }
17285 if can == 0 {
17286 return Err(format!(
17287 "qwen4exp_gpu tp2: dev{} cannot access dev{} over P2P",
17288 src.ctx().ordinal(),
17289 dst.ctx().ordinal()
17290 )
17291 .into());
17292 }
17293 src.ctx().bind_to_thread()?;
17294 let rc = unsafe { sys::cuCtxEnablePeerAccess(dst.ctx().cu_ctx(), 0) };
17295 use cudarc::driver::sys::cudaError_enum as E;
17296 if rc != E::CUDA_SUCCESS && rc != E::CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED {
17297 return Err(format!("qwen4exp_gpu tp2: cuCtxEnablePeerAccess failed: {rc:?}").into());
17298 }
17299 }
17300 for (owner, accessor) in [(e0, e1), (e1, e0)] {
17301 let device = cudarc::driver::result::device::get(owner.ctx().ordinal() as i32)?;
17302 let mut pool: sys::CUmemoryPool = std::ptr::null_mut();
17303 unsafe {
17304 sys::cuDeviceGetDefaultMemPool(&mut pool, device).result()?;
17305 }
17306 let desc = sys::CUmemAccessDesc {
17307 location: sys::CUmemLocation {
17308 type_: sys::CUmemLocationType::CU_MEM_LOCATION_TYPE_DEVICE,
17309 id: accessor.ctx().ordinal() as i32,
17310 },
17311 flags: sys::CUmemAccess_flags::CU_MEM_ACCESS_FLAGS_PROT_READWRITE,
17312 };
17313 let rc = unsafe { sys::cuMemPoolSetAccess(pool, &desc, 1) };
17314 if rc != sys::cudaError_enum::CUDA_SUCCESS {
17315 return Err(format!("qwen4exp_gpu tp2: cuMemPoolSetAccess failed: {rc:?}").into());
17316 }
17317 }
17318 Ok(())
17319}
17320
17321fn build_ple_replica(
17325 e: &Engine,
17326 weights: &ReferenceWeights,
17327 prefix: &str,
17328 ple_plan: &PleEmbeddingPlan,
17329 streams: usize,
17330 hidden: usize,
17331) -> Res<PleW> {
17332 let embed_dim = ple_plan.embed_dim as usize;
17333 let key_proj = expect(weights, &family_id(format!("{prefix}ple.key_proj.weight")))?;
17334 let conv_w = expect(weights, &family_id(format!("{prefix}ple.conv1d.weight")))?;
17335 let norm_slices = |name: &str| -> Res<Vec<CudaSlice<f32>>> {
17336 let t = expect(weights, &family_id(format!("{prefix}ple.{name}.weight")))?;
17337 split_rows(&t.data, streams, hidden, 1)
17338 .into_iter()
17339 .map(|v| e.htod(&v))
17340 .collect::<Result<_, _>>()
17341 };
17342 let ints = |name: &str| -> Res<Vec<i64>> {
17343 let t = expect(
17344 weights,
17345 &family_id(format!("{prefix}ple.ple_embedding.{name}")),
17346 )?;
17347 t.ints
17348 .clone()
17349 .ok_or_else(|| "qwen4exp_gpu: n-gram buffer must be I64".into())
17350 };
17351 Ok(PleW {
17352 plan: *ple_plan,
17353 key_proj: split_rows(&key_proj.data, streams, hidden, embed_dim)
17354 .into_iter()
17355 .map(|v| e.htod(&v))
17356 .collect::<Result<_, _>>()?,
17357 value_proj: upload(
17358 e,
17359 &expect(
17360 weights,
17361 &family_id(format!("{prefix}ple.value_proj.weight")),
17362 )?,
17363 )?,
17364 norm_key: norm_slices("norm_key")?,
17365 norm_query: norm_slices("norm_query")?,
17366 norm_conv: norm_slices("norm_conv")?,
17367 conv_w: split_rows(&conv_w.data, streams, hidden, ple_plan.conv_kernel as usize)
17368 .into_iter()
17369 .map(|v| e.htod(&v))
17370 .collect::<Result<_, _>>()?,
17371 multipliers: ints("layer_multipliers")?,
17372 sizes: ints("ngram_heads_vocab_sizes")?,
17373 offsets: ints("ngram_heads_offsets")?,
17374 table: NgramTable::F32(Vec::new()), })
17376}
17377
17378#[allow(clippy::too_many_arguments)]
17380fn build_gdn_half(
17381 e: &Engine,
17382 weights: &ReferenceWeights,
17383 index: u32,
17384 gdn: &GatedDeltaNetPlan,
17385 hidden: usize,
17386 d: usize,
17387) -> Res<GdnHalfW> {
17388 let (nk, nv) = (gdn.key_heads as usize, gdn.value_heads as usize);
17389 let (hk, hv) = (gdn.key_head_dim as usize, gdn.value_head_dim as usize);
17390 if nk % 2 != 0 || nv % nk != 0 {
17391 return Err(format!(
17392 "qwen4exp_gpu tp2: GDN layer {index} nk {nk} / nv {nv} does not split by key-head halves"
17393 )
17394 .into());
17395 }
17396 let (nk_h, nv_h) = (nk / 2, nv / 2);
17397 let head_map = tp2_gdn_head_map(d, nk, nv);
17398 let qkv = expect(weights, &layer_id(index, LayerTensor::GdnQkv))?;
17399 let z = expect(weights, &layer_id(index, LayerTensor::GdnGate))?;
17400 let beta = expect(weights, &layer_id(index, LayerTensor::GdnBeta))?;
17401 let alpha = expect(weights, &layer_id(index, LayerTensor::GdnAlpha))?;
17402 let out = expect(weights, &layer_id(index, LayerTensor::GdnOutput))?;
17403 let conv_w = expect(weights, &layer_id(index, LayerTensor::GdnConv1d))?;
17404 let a = expect(weights, &layer_id(index, LayerTensor::GdnA))?;
17405 let dt = expect(weights, &layer_id(index, LayerTensor::GdnDtBias))?;
17406 let norm = expect(weights, &layer_id(index, LayerTensor::GdnNorm))?;
17407 let kernel = gdn.conv_kernel as usize;
17408 let mut qkv_rows: Vec<usize> = Vec::with_capacity(2 * nk_h * hk + nv_h * hv);
17410 qkv_rows.extend(d * nk_h * hk..(d + 1) * nk_h * hk);
17411 qkv_rows.extend(nk * hk + d * nk_h * hk..nk * hk + (d + 1) * nk_h * hk);
17412 for &hm in &head_map {
17413 qkv_rows.extend(2 * nk * hk + hm * hv..2 * nk * hk + (hm + 1) * hv);
17414 }
17415 let mut z_rows: Vec<usize> = Vec::with_capacity(nv_h * hv);
17416 for &hm in &head_map {
17417 z_rows.extend(hm * hv..(hm + 1) * hv);
17418 }
17419 let out_blocks: Vec<(usize, usize)> = head_map.iter().map(|&hm| (hm * hv, hv)).collect();
17420 let qkv_c = gather_rows_host(&qkv.data, hidden, &qkv_rows);
17421 let z_c = gather_rows_host(&z.data, hidden, &z_rows);
17422 let beta_c = gather_rows_host(&beta.data, hidden, &head_map);
17423 let alpha_c = gather_rows_host(&alpha.data, hidden, &head_map);
17424 let out_c = gather_cols_host(&out.data, hidden, nv * hv, &out_blocks);
17425 let conv_c = gather_rows_host(&conv_w.data, kernel, &qkv_rows);
17426 let a_c: Vec<f32> = head_map.iter().map(|&hm| a.data[hm]).collect();
17427 let dt_c: Vec<f32> = head_map.iter().map(|&hm| dt.data[hm]).collect();
17428 Ok(GdnHalfW {
17429 nk_h,
17430 nv_h,
17431 hk,
17432 hv,
17433 kernel,
17434 gate_activation: gdn.gate_activation,
17435 proj_b16: need_stack_twin(
17436 e,
17437 &[&qkv_c, &z_c, &beta_c, &alpha_c],
17438 hidden,
17439 "tp2 gdn proj half",
17440 )?,
17441 out_b16: need_twin(e, &out_c, nv_h * hv, "tp2 gdn out half")?,
17442 conv_w: e.htod(&conv_c)?,
17443 a: e.htod(&a_c)?,
17444 dt: e.htod(&dt_c)?,
17445 norm: e.htod(&norm.data)?,
17446 })
17447}
17448
17449#[allow(clippy::too_many_arguments)]
17451fn build_qsa_half(
17452 e: &Engine,
17453 weights: &ReferenceWeights,
17454 index: u32,
17455 attn: &FullAttentionPlan,
17456 hidden: usize,
17457 d: usize,
17458) -> Res<QsaHalfW> {
17459 let nh = attn.query_heads as usize;
17460 let nkv = attn.kv_heads as usize;
17461 let hd = attn.key_head_dim as usize;
17462 if nh % 2 != 0 || nkv % 2 != 0 || nh % nkv != 0 {
17463 return Err(format!(
17464 "qwen4exp_gpu tp2: QSA layer {index} heads {nh}/{nkv} do not split in halves"
17465 )
17466 .into());
17467 }
17468 let (nh_h, nkv_h) = (nh / 2, nkv / 2);
17469 let wq = expect(weights, &layer_id(index, LayerTensor::Query))?;
17470 let wk = expect(weights, &layer_id(index, LayerTensor::Key))?;
17471 let wv = expect(weights, &layer_id(index, LayerTensor::Value))?;
17472 let wo = expect(weights, &layer_id(index, LayerTensor::AttentionOutput))?;
17473 let q_rows: Vec<usize> = (d * nh_h * 2 * hd..(d + 1) * nh_h * 2 * hd).collect();
17475 let kv_rows: Vec<usize> = (d * nkv_h * hd..(d + 1) * nkv_h * hd).collect();
17476 let wq_c = gather_rows_host(&wq.data, hidden, &q_rows);
17477 let wk_c = gather_rows_host(&wk.data, hidden, &kv_rows);
17478 let wv_c = gather_rows_host(&wv.data, hidden, &kv_rows);
17479 let wo_c = gather_cols_host(&wo.data, hidden, nh * hd, &[(d * nh_h * hd, nh_h * hd)]);
17480 let opt_norm = |tensor: LayerTensor| -> Res<Option<CudaSlice<f32>>> {
17481 match weights.get(&layer_id(index, tensor)) {
17482 Some(t) => Ok(Some(e.htod(&t.data)?)),
17483 None => Ok(None),
17484 }
17485 };
17486 let scale = match attn.scale {
17487 memra_gguf::model_plan::AttentionScale::InverseSqrtKeyDim => 1.0 / (hd as f32).sqrt(),
17488 memra_gguf::model_plan::AttentionScale::Fixed(scale) => scale,
17489 };
17490 Ok(QsaHalfW {
17491 nh_h,
17492 nkv_h,
17493 hd,
17494 n_rot: attn.rope.dimensions as usize,
17495 rope_base: attn.rope.base,
17496 scale,
17497 proj_b16: need_stack_twin(e, &[&wq_c, &wk_c, &wv_c], hidden, "tp2 qsa proj half")?,
17498 wo_b16: need_twin(e, &wo_c, nh_h * hd, "tp2 qsa o half")?,
17499 q_norm: opt_norm(LayerTensor::QueryNorm)?,
17500 k_norm: opt_norm(LayerTensor::KeyNorm)?,
17501 yarn: build_yarn(e, &attn.rope, None, index)?,
17503 })
17504}
17505
17506pub fn build_tp2_shard(e0: &Engine, e1: &Engine, ckpt: &LoadedCheckpoint) -> Res<Tp2Shard> {
17510 let plan = &ckpt.plan;
17511 let weights = &ckpt.weights;
17512 let hidden = plan.hidden_size as usize;
17513 let vocab = plan.vocab_size as usize;
17514 if vocab % 2 != 0 {
17515 return Err("qwen4exp_gpu tp2: odd vocab".into());
17516 }
17517 let mixer_plan = plan
17518 .exit_mixer
17519 .ok_or("qwen4exp_gpu tp2: missing exit mixer")?;
17520 let streams = mixer_plan.streams as usize;
17521 let rank = mixer_plan.bottleneck_rank as usize;
17522 let plan_experts = plan
17525 .layers
17526 .iter()
17527 .find_map(|l| match &l.mlp {
17528 MlpPlan::Moe(m) => Some(m.expert_count as usize),
17529 _ => None,
17530 })
17531 .ok_or("qwen4exp_gpu tp2: no MoE layer in the plan")?;
17532 let placement = match Tp2Placement::from_env(plan_experts)? {
17533 Some(p) => p,
17534 None => Tp2Placement::even(plan_experts),
17535 };
17536 println!(
17537 "# tp2-placement\tstrategy={}\tentry_rank={}\texperts={plan_experts}\tsource={}",
17538 placement.strategy(),
17539 placement.entry_rank(),
17540 placement.source()
17541 );
17542 let mut layers = Vec::with_capacity(plan.layers.len());
17543 for layer in &plan.layers {
17544 let prefix = format!("trunk.layers.{}.", layer.index);
17545 let _g1 = e1.gpu.enter_main()?;
17546 let attn_gate1 = load_gate(
17547 e1,
17548 weights,
17549 &prefix,
17550 "attn_hyper_connection.",
17551 streams,
17552 hidden,
17553 rank,
17554 true,
17555 )?;
17556 let mlp_gate1 = load_gate(
17557 e1,
17558 weights,
17559 &prefix,
17560 "mlp_hyper_connection.",
17561 streams,
17562 hidden,
17563 rank,
17564 true,
17565 )?;
17566 let ple1 = match layer.ple.as_ref() {
17567 None => None,
17568 Some(ple_plan) => Some(build_ple_replica(
17569 e1, weights, &prefix, ple_plan, streams, hidden,
17570 )?),
17571 };
17572 drop(_g1);
17573 let (mixer0, mixer1) = match &layer.attention {
17574 AttentionPlan::GatedDeltaNet(gdn) => {
17575 let _g0 = e0.gpu.enter_main()?;
17576 let m0 = MixerHalfW::Gdn(build_gdn_half(e0, weights, layer.index, gdn, hidden, 0)?);
17577 drop(_g0);
17578 let _g1 = e1.gpu.enter_main()?;
17579 let m1 = MixerHalfW::Gdn(build_gdn_half(e1, weights, layer.index, gdn, hidden, 1)?);
17580 (m0, m1)
17581 }
17582 AttentionPlan::Full(attn) => {
17583 let _g0 = e0.gpu.enter_main()?;
17584 let m0 =
17585 MixerHalfW::Qsa(build_qsa_half(e0, weights, layer.index, attn, hidden, 0)?);
17586 drop(_g0);
17587 let _g1 = e1.gpu.enter_main()?;
17588 let m1 =
17589 MixerHalfW::Qsa(build_qsa_half(e1, weights, layer.index, attn, hidden, 1)?);
17590 (m0, m1)
17591 }
17592 other => {
17593 return Err(format!("qwen4exp_gpu tp2: unsupported mixer {other:?}").into());
17594 }
17595 };
17596 let MlpPlan::Moe(moe_plan) = &layer.mlp else {
17598 return Err("qwen4exp_gpu tp2: non-MoE layer".into());
17599 };
17600 let experts = moe_plan.expert_count as usize;
17601 let ff = moe_plan.expert_intermediate_size as usize;
17602 if experts % 2 != 0 {
17603 return Err("qwen4exp_gpu tp2: odd expert count".into());
17604 }
17605 let bank = ckpt.read_bank(layer.index)?;
17612 let bank = &bank;
17613 let place = placement.layer(layer.index, experts)?;
17616 let upper =
17622 |src: &BankTensorSrc, out_f: usize, in_f: usize, what: &str| -> Res<Nvfp4Half> {
17623 let BankTensorSrc::Nvfp4 {
17624 codes,
17625 scales,
17626 macros,
17627 ..
17628 } = src
17629 else {
17630 return Err(format!("qwen4exp_gpu tp2: {what} bank is not NVFP4").into());
17631 };
17632 let wbytes = out_f * in_f / 2;
17633 let sbytes = out_f * in_f / 16;
17634 let need_codes = place.card1.len() * wbytes;
17635 let need_scales = place.card1.len() * sbytes;
17636 if codes.len() < experts * wbytes || scales.len() < experts * sbytes {
17637 return Err(format!(
17638 "qwen4exp_gpu tp2: {what} bank is {} code / {} scale bytes, too \
17639 small for {experts} experts x ({wbytes}, {sbytes})",
17640 codes.len(),
17641 scales.len()
17642 )
17643 .into());
17644 }
17645 let mut gcodes = Vec::with_capacity(need_codes);
17646 let mut gscales = Vec::with_capacity(need_scales);
17647 let mut gmacros = Vec::with_capacity(place.card1.len());
17648 for &eid in &place.card1 {
17649 let e = eid as usize;
17650 gcodes.extend_from_slice(&codes[e * wbytes..(e + 1) * wbytes]);
17651 gscales.extend_from_slice(&scales[e * sbytes..(e + 1) * sbytes]);
17652 gmacros.push(macros[e]);
17653 }
17654 Ok(Nvfp4Half {
17655 codes: e1.htod_bytes(&gcodes)?,
17656 scales: e1.htod_bytes(&gscales)?,
17657 macros_dev: e1.htod(&gmacros)?,
17658 })
17659 };
17660 let shared = moe_plan
17661 .shared
17662 .as_ref()
17663 .ok_or("qwen4exp_gpu tp2: missing shared expert")?;
17664 let sff = shared.intermediate_size as usize;
17665 if sff % 2 != 0 {
17666 return Err("qwen4exp_gpu tp2: odd shared ff".into());
17667 }
17668 let sffh = sff / 2;
17669 let sh_gate = expect(weights, &layer_id(layer.index, LayerTensor::SharedMlpGate))?;
17670 let sh_up = expect(weights, &layer_id(layer.index, LayerTensor::SharedMlpUp))?;
17671 let sh_down = expect(weights, &layer_id(layer.index, LayerTensor::SharedMlpDown))?;
17672 let sh_ig = if shared.gated {
17673 Some(expect(
17674 weights,
17675 &layer_id(layer.index, LayerTensor::SharedMlpInputGate),
17676 )?)
17677 } else {
17678 None
17679 };
17680 let moe = {
17681 let _g1 = e1.gpu.enter_main()?;
17682 let gate1 = upper(&bank.gate, ff, hidden, "gate")?;
17683 let up1 = upper(&bank.up, ff, hidden, "up")?;
17684 let down1 = upper(&bank.down, hidden, ff, "down")?;
17685 let shared_gu1_b16 = need_stack_twin(
17686 e1,
17687 &[&sh_gate.data[sffh * hidden..], &sh_up.data[sffh * hidden..]],
17688 hidden,
17689 "tp2 shared gate/up (card1)",
17690 )?;
17691 let down1_c = gather_cols_host(&sh_down.data, hidden, sff, &[(sffh, sffh)]);
17692 let shared_down1 = need_twin(e1, &down1_c, sffh, "tp2 shared down (card1)")?;
17693 let shared_input_gate1 = match sh_ig.as_ref() {
17694 Some(t) => Some(e1.htod(&t.data)?),
17695 None => None,
17696 };
17697 drop(_g1);
17698 let _g0 = e0.gpu.enter_main()?;
17699 let down0_c = gather_cols_host(&sh_down.data, hidden, sff, &[(0, sffh)]);
17700 let shared_down0 = need_twin(e0, &down0_c, sffh, "tp2 shared down (card0)")?;
17701 let shared_gu0_b16 = need_stack_twin(
17702 e0,
17703 &[&sh_gate.data[..sffh * hidden], &sh_up.data[..sffh * hidden]],
17704 hidden,
17705 "tp2 shared gate/up (card0)",
17706 )?;
17707 MoeHalfW {
17708 gate1,
17709 up1,
17710 down1,
17711 shared_down0,
17712 shared_down1,
17713 shared_input_gate1,
17714 shared_gu0_b16,
17715 shared_gu1_b16,
17716 }
17717 };
17718 layers.push(Tp2LayerW {
17719 attn_gate1,
17720 mlp_gate1,
17721 mixer0,
17722 mixer1,
17723 moe,
17724 ple1,
17725 place,
17726 });
17727 }
17728 let _g1 = e1.gpu.enter_main()?;
17729 let exit_gate1 = load_gate(
17730 e1,
17731 weights,
17732 "trunk.hyper_connection_mixer.",
17733 "",
17734 streams,
17735 hidden,
17736 rank,
17737 false,
17738 )?;
17739 let vsplit = vocab / 2;
17740 let head = match weights.get(&TensorId::OutputProjection) {
17741 Some(t) => &t.data,
17742 None => &expect(weights, &TensorId::TokenEmbedding)?.data.clone(),
17743 };
17744 let lm_head1 = need_twin(
17745 e1,
17746 &head[vsplit * hidden..],
17747 hidden,
17748 "tp2 lm_head upper half",
17749 )?;
17750 let stage1 = [e1.zeros(hidden)?, e1.zeros(hidden)?];
17751 let ev1 = [e1.ctx().new_event(None)?, e1.ctx().new_event(None)?];
17752 let stage1_raw = {
17753 let s = e1.gpu.stream();
17754 [stage1[0].device_ptr(&s).0, stage1[1].device_ptr(&s).0]
17755 };
17756 drop(_g1);
17757 let _g0 = e0.gpu.enter_main()?;
17758 let stage0 = [e0.zeros(hidden)?, e0.zeros(hidden)?];
17759 let ev0 = [e0.ctx().new_event(None)?, e0.ctx().new_event(None)?];
17760 let stage0_raw = {
17761 let s = e0.gpu.stream();
17762 [stage0[0].device_ptr(&s).0, stage0[1].device_ptr(&s).0]
17763 };
17764 Ok(Tp2Shard {
17765 layers,
17766 exit_gate1,
17767 lm_head1,
17768 vsplit,
17769 stage0,
17770 stage1,
17771 stage0_raw,
17772 stage1_raw,
17773 ev0,
17774 ev1,
17775 })
17776}
17777
17778impl Qwen4ExpGpu {
17779 pub fn load_from_dir_tp2(
17783 e0: &Engine,
17784 e1: &Engine,
17785 dir: &std::path::Path,
17786 opts: LoadOptions,
17787 ) -> Res<(Self, Tp2Shard)> {
17788 tp2_enable_p2p(e0, e1)?;
17789 let checkpoint = read_checkpoint_with(dir, opts)?;
17790 let shard = build_tp2_shard(e0, e1, &checkpoint)?;
17791 let model = Self::from_loaded_checkpoint(e0, checkpoint)?;
17792 Ok((model, shard))
17793 }
17794
17795 fn tp2_migrate(
17799 &self,
17800 e0: &Engine,
17801 e1: &Engine,
17802 _shard: &Tp2Shard,
17803 state: &mut Qwen4ExpState,
17804 ) -> Res<()> {
17805 let cap = state.capacity;
17806 let pos = state.pos;
17807 let mut tlayers = Vec::with_capacity(self.layers.len());
17808 for (layer, lstate) in self.layers.iter().zip(state.layers.iter_mut()) {
17809 let (m0, m1) = match (&layer.mixer, &mut lstate.mixer) {
17810 (
17811 MixerW::Gdn(gdn),
17812 MixerState::Gdn {
17813 conv,
17814 state: gstate,
17815 },
17816 ) => {
17817 let p = &gdn.plan;
17818 let (nk, nv) = (p.key_heads as usize, p.value_heads as usize);
17819 let (hk, hv) = (p.key_head_dim as usize, p.value_head_dim as usize);
17820 let (nk_h, nv_h) = (nk / 2, nv / 2);
17821 let pad = p.conv_kernel as usize - 1;
17822 let conv_dim = 2 * nk * hk + nv * hv;
17823 let conv_dim_h = 2 * nk_h * hk + nv_h * hv;
17824 let state_host = {
17825 let _g = e0.gpu.enter_main()?;
17826 e0.dtoh(gstate)?
17827 };
17828 let conv_host = {
17829 let _g = e0.gpu.enter_main()?;
17830 e0.dtoh(conv)?
17831 };
17832 let mut halves = Vec::with_capacity(2);
17833 for d in 0..2 {
17834 let head_map = tp2_gdn_head_map(d, nk, nv);
17835 let state_c = gather_rows_host(&state_host, hv * hk, &head_map);
17836 let mut blocks: Vec<(usize, usize)> = vec![
17837 (d * nk_h * hk, nk_h * hk),
17838 (nk * hk + d * nk_h * hk, nk_h * hk),
17839 ];
17840 blocks.extend(head_map.iter().map(|&hm| (2 * nk * hk + hm * hv, hv)));
17841 let conv_c = gather_cols_host(&conv_host, pad, conv_dim, &blocks);
17842 let e = if d == 0 { e0 } else { e1 };
17843 let _g = e.gpu.enter_main()?;
17844 let state_dev = e.htod(&state_c)?;
17845 let conv_dev = e.htod(&conv_c)?;
17846 debug_assert_eq!(conv_c.len(), pad * conv_dim_h);
17847 halves.push(MixerHalfState::Gdn {
17848 conv: conv_dev,
17849 state: state_dev,
17850 });
17851 }
17852 let m1 = halves.pop().expect("two halves");
17853 let m0 = halves.pop().expect("two halves");
17854 (m0, m1)
17855 }
17856 (MixerW::Qsa(qsa), MixerState::Qsa { kv, .. }) => {
17857 let nkv = qsa.attn.kv_heads as usize;
17858 let hd = qsa.attn.key_head_dim as usize;
17859 let nkv_h = nkv / 2;
17860 let mut halves = Vec::with_capacity(2);
17861 match &*kv {
17862 QsaKvStore::F32 { k, v } => {
17863 let (k_host, v_host) = {
17864 let _g = e0.gpu.enter_main()?;
17865 (
17866 e0.dtoh_view(&k.slice(0..pos * nkv * hd))?,
17867 e0.dtoh_view(&v.slice(0..pos * nkv * hd))?,
17868 )
17869 };
17870 for d in 0..2 {
17871 let block = [(d * nkv_h * hd, nkv_h * hd)];
17872 let k_c = gather_cols_host(&k_host, pos, nkv * hd, &block);
17873 let v_c = gather_cols_host(&v_host, pos, nkv * hd, &block);
17874 let e = if d == 0 { e0 } else { e1 };
17875 let _g = e.gpu.enter_main()?;
17876 let mut k_dev = e.zeros(cap * nkv_h * hd)?;
17877 let mut v_dev = e.zeros(cap * nkv_h * hd)?;
17878 if pos > 0 {
17879 let mut kv_view = k_dev.slice_mut(0..pos * nkv_h * hd);
17880 e.gpu.stream().memcpy_htod(&k_c, &mut kv_view)?;
17881 let mut vv_view = v_dev.slice_mut(0..pos * nkv_h * hd);
17882 e.gpu.stream().memcpy_htod(&v_c, &mut vv_view)?;
17883 }
17884 halves.push(MixerHalfState::Qsa {
17885 kv: QsaKvStore::F32 { k: k_dev, v: v_dev },
17886 });
17887 }
17888 }
17889 QsaKvStore::Q8Q5 { k, v } => {
17890 if hd % 32 != 0 {
17895 return Err("qwen4exp_gpu tp2: quantized halves need \
17896 hd % 32 == 0 (byte-aligned head blocks)"
17897 .into());
17898 }
17899 let (krb, vrb) = (q8_row_bytes(nkv * hd), q5_row_bytes(nkv * hd));
17900 let (krb_h, vrb_h) =
17901 (q8_row_bytes(nkv_h * hd), q5_row_bytes(nkv_h * hd));
17902 let (k_host, v_host) = {
17903 let _g = e0.gpu.enter_main()?;
17904 (
17905 e0.dtoh_u8_view(&k.slice(0..pos * krb))?,
17906 e0.dtoh_u8_view(&v.slice(0..pos * vrb))?,
17907 )
17908 };
17909 for d in 0..2 {
17910 let mut k_c = Vec::with_capacity(pos * krb_h);
17911 let mut v_c = Vec::with_capacity(pos * vrb_h);
17912 for r in 0..pos {
17913 let ko = r * krb + d * krb_h;
17914 k_c.extend_from_slice(&k_host[ko..ko + krb_h]);
17915 let vo = r * vrb + d * vrb_h;
17916 v_c.extend_from_slice(&v_host[vo..vo + vrb_h]);
17917 }
17918 let e = if d == 0 { e0 } else { e1 };
17919 let _g = e.gpu.enter_main()?;
17920 let mut k_dev = e.alloc_u8(cap * krb_h)?;
17921 let mut v_dev = e.alloc_u8(cap * vrb_h)?;
17922 if pos > 0 {
17923 let mut kv_view = k_dev.slice_mut(0..pos * krb_h);
17924 e.gpu.stream().memcpy_htod(&k_c, &mut kv_view)?;
17925 let mut vv_view = v_dev.slice_mut(0..pos * vrb_h);
17926 e.gpu.stream().memcpy_htod(&v_c, &mut vv_view)?;
17927 }
17928 halves.push(MixerHalfState::Qsa {
17929 kv: QsaKvStore::Q8Q5 { k: k_dev, v: v_dev },
17930 });
17931 }
17932 }
17933 }
17934 {
17938 let _g = e0.gpu.enter_main()?;
17939 *kv = match &*kv {
17940 QsaKvStore::F32 { .. } => QsaKvStore::F32 {
17941 k: e0.zeros(1)?,
17942 v: e0.zeros(1)?,
17943 },
17944 QsaKvStore::Q8Q5 { .. } => QsaKvStore::Q8Q5 {
17945 k: e0.alloc_u8(34)?,
17946 v: e0.alloc_u8(24)?,
17947 },
17948 };
17949 }
17950 let m1 = halves.pop().expect("two halves");
17951 let m0 = halves.pop().expect("two halves");
17952 (m0, m1)
17953 }
17954 _ => return Err("qwen4exp_gpu tp2: layer/state mixer mismatch".into()),
17955 };
17956 let ple1 = match lstate.ple.as_ref() {
17958 None => None,
17959 Some(ps) => {
17960 let mut conv_hist = Vec::with_capacity(ps.conv_hist.len());
17961 for h in &ps.conv_hist {
17962 let host = {
17963 let _g = e0.gpu.enter_main()?;
17964 e0.dtoh(h)?
17965 };
17966 let _g = e1.gpu.enter_main()?;
17967 conv_hist.push(e1.htod(&host)?);
17968 }
17969 Some(PleState {
17970 conv_hist,
17971 ngram_ids: Vec::new(),
17972 ngram_history: Vec::new(),
17973 ngram_last_eos: -1,
17974 })
17975 }
17976 };
17977 tlayers.push(Tp2LayerState { m0, m1, ple1 });
17978 }
17979 state.tp2 = Some(Tp2State {
17980 ws1: StepPool::default(),
17981 layers: tlayers,
17982 graphs: Tp2Graphs::default(),
17983 pf_stage0: None,
17984 pf_stage1: None,
17985 pf_stage0_raw: [0; 2],
17986 pf_stage1_raw: [0; 2],
17987 pf_rows: 0,
17988 });
17989 Ok(())
17990 }
17991
17992 #[allow(clippy::too_many_arguments)]
17996 fn gdn_forward_half(
17997 &self,
17998 e: &Engine,
17999 ws: &mut StepPool,
18000 eps: f32,
18001 h: &GdnHalfW,
18002 mixed: &CudaSlice<f32>,
18003 hstate: &mut MixerHalfState,
18004 t: usize,
18005 ) -> Res<CudaSlice<f32>> {
18006 let MixerHalfState::Gdn { conv, state } = hstate else {
18007 return Err("qwen4exp_gpu tp2: GDN half bound to non-GDN state".into());
18008 };
18009 let hidden = self.hidden;
18010 let (nk, nv, hk, hv) = (h.nk_h, h.nv_h, h.hk, h.hv);
18011 let kernel = h.kernel;
18012 let pad = kernel - 1;
18013 let conv_dim = 2 * nk * hk + nv * hv;
18014 let mut qkv = ws.take_f32(e, "gdn.qkv", t * conv_dim, 0)?;
18015 let mut z = ws.take_f32(e, "gdn.z", t * nv * hv, 0)?;
18016 let mut beta_raw = ws.take_f32(e, "gdn.beta", t * nv, 0)?;
18017 let mut alpha = ws.take_f32(e, "gdn.alpha", t * nv, 0)?;
18018 if t == 1 && proj_stack_on() {
18022 launch_qmatvec_bf16w_multi4(
18023 e,
18024 &h.proj_b16,
18025 mixed,
18026 &[
18027 (&qkv, conv_dim),
18028 (&z, nv * hv),
18029 (&beta_raw, nv),
18030 (&alpha, nv),
18031 ],
18032 hidden,
18033 )?;
18034 } else {
18035 launch_qmatvec_bf16w_off(e, &h.proj_b16, 0, mixed, &mut qkv, hidden, conv_dim, t)?;
18036 launch_qmatvec_bf16w_off(e, &h.proj_b16, conv_dim, mixed, &mut z, hidden, nv * hv, t)?;
18037 launch_qmatvec_bf16w_off(
18038 e,
18039 &h.proj_b16,
18040 conv_dim + nv * hv,
18041 mixed,
18042 &mut beta_raw,
18043 hidden,
18044 nv,
18045 t,
18046 )?;
18047 launch_qmatvec_bf16w_off(
18048 e,
18049 &h.proj_b16,
18050 conv_dim + nv * hv + nv,
18051 mixed,
18052 &mut alpha,
18053 hidden,
18054 nv,
18055 t,
18056 )?;
18057 }
18058 let mut g_log = ws.take_f32(e, "gdn.glog", t * nv, 0)?;
18059 e.gdn_glog_v(&alpha.slice(0..t * nv), &h.dt, &h.a, &mut g_log, nv, t)?;
18060 ws.put_f32("gdn.alpha", alpha);
18061 let mut conv_out = ws.take_f32(e, "gdn.conv_out", t * conv_dim, 0)?;
18062 launch_dwconv(
18063 e,
18064 &qkv,
18065 conv,
18066 &h.conv_w,
18067 &mut conv_out,
18068 t,
18069 pad,
18070 conv_dim,
18071 kernel,
18072 1,
18073 1,
18074 )?;
18075 let mut o = ws.take_f32(e, "gdn.o", t * nv * hv, 0)?;
18076 let scale = 1.0 / (hk as f32).sqrt();
18077 if t == 1 && gdn_step_on() && hk % 32 == 0 && hk <= 1024 {
18078 launch_gdn_scan_step(
18079 e, &conv_out, &g_log, &beta_raw, state, &mut o, nk, nv, hk, hv, scale, eps,
18080 )?;
18081 } else {
18082 launch_gdn_scan(
18083 e, &conv_out, &g_log, &beta_raw, state, &mut o, nk, nv, hk, hv, t, scale, eps,
18084 )?;
18085 }
18086 ws.put_f32("gdn.conv_out", conv_out);
18087 if t >= pad {
18089 e.copy_range_into(conv, 0, &qkv, (t - pad) * conv_dim, pad * conv_dim)?;
18090 } else {
18091 let keep = pad - t;
18092 let mut tmp = ws.take_f32(e, "gdn.tmp", keep * conv_dim, 0)?;
18093 e.copy_range_into(&mut tmp, 0, conv, t * conv_dim, keep * conv_dim)?;
18094 e.copy_range_into(conv, 0, &tmp, 0, keep * conv_dim)?;
18095 e.copy_range_into(conv, keep * conv_dim, &qkv, 0, t * conv_dim)?;
18096 ws.put_f32("gdn.tmp", tmp);
18097 }
18098 ws.put_f32("gdn.qkv", qkv);
18099 ws.put_f32("gdn.beta", beta_raw);
18100 ws.put_f32("gdn.glog", g_log);
18101 let mut gated = ws.take_f32(e, "gdn.gated", t * nv * hv, 0)?;
18102 match h.gate_activation {
18103 GdnGateActivation::Sigmoid if gdn_fuse_on() => {
18104 launch_rms_sigmul(e, &o, &h.norm, &z, &mut gated, hv, t * nv, eps)?;
18105 }
18106 GdnGateActivation::Sigmoid => {
18107 let mut normed = ws.take_f32(e, "gdn.normed", t * nv * hv, 0)?;
18108 e.rms_norm(&o, &h.norm, &mut normed, hv, t * nv, eps)?;
18109 let mut sg = ws.take_f32(e, "gdn.sg", t * nv * hv, 0)?;
18110 e.sigmoid(&z, &mut sg, t * nv * hv)?;
18111 e.mul(&normed, &sg, &mut gated, t * nv * hv)?;
18112 ws.put_f32("gdn.sg", sg);
18113 ws.put_f32("gdn.normed", normed);
18114 }
18115 GdnGateActivation::Silu => {
18116 let mut normed = ws.take_f32(e, "gdn.normed", t * nv * hv, 0)?;
18117 e.rms_norm(&o, &h.norm, &mut normed, hv, t * nv, eps)?;
18118 e.silu_mul(&z, &normed, &mut gated, t * nv * hv)?;
18119 ws.put_f32("gdn.normed", normed);
18120 }
18121 }
18122 let mut partial = ws.take_f32(e, "mixer.out", t * hidden, 0)?;
18123 launch_qmatvec_bf16w(
18124 e,
18125 &h.out_b16,
18126 &gated,
18127 &mut partial,
18128 nv * hv,
18129 hidden,
18130 t,
18131 1,
18132 0,
18133 0,
18134 nv * hv,
18135 0,
18136 )?;
18137 ws.put_f32("gdn.gated", gated);
18138 ws.put_f32("gdn.z", z);
18139 ws.put_f32("gdn.o", o);
18140 Ok(partial)
18141 }
18142
18143 #[allow(clippy::too_many_arguments)]
18147 fn qsa_half_proj(
18148 &self,
18149 e: &Engine,
18150 ws: &mut StepPool,
18151 eps: f32,
18152 h: &QsaHalfW,
18153 mixed: &CudaSlice<f32>,
18154 hstate: &mut MixerHalfState,
18155 base_pos: usize,
18156 t: usize,
18157 ) -> Res<(CudaSlice<f32>, CudaSlice<f32>)> {
18158 let MixerHalfState::Qsa { kv } = hstate else {
18159 return Err("qwen4exp_gpu tp2: QSA half bound to non-QSA state".into());
18160 };
18161 let hidden = self.hidden;
18162 let (nh, nkv, hd) = (h.nh_h, h.nkv_h, h.hd);
18163 let mut q_fused = ws.take_f32(e, "qsa.qf", t * 2 * nh * hd, 0)?;
18164 let mut k_new = ws.take_f32(e, "qsa.k", t * nkv * hd, 0)?;
18165 let mut v_new = ws.take_f32(e, "qsa.v", t * nkv * hd, 0)?;
18166 if t == 1 && proj_stack_on() {
18170 launch_qmatvec_bf16w_multi4(
18171 e,
18172 &h.proj_b16,
18173 mixed,
18174 &[
18175 (&q_fused, 2 * nh * hd),
18176 (&k_new, nkv * hd),
18177 (&v_new, nkv * hd),
18178 ],
18179 hidden,
18180 )?;
18181 } else {
18182 launch_qmatvec_bf16w_off(
18183 e,
18184 &h.proj_b16,
18185 0,
18186 mixed,
18187 &mut q_fused,
18188 hidden,
18189 2 * nh * hd,
18190 t,
18191 )?;
18192 launch_qmatvec_bf16w_off(
18193 e,
18194 &h.proj_b16,
18195 2 * nh * hd,
18196 mixed,
18197 &mut k_new,
18198 hidden,
18199 nkv * hd,
18200 t,
18201 )?;
18202 launch_qmatvec_bf16w_off(
18203 e,
18204 &h.proj_b16,
18205 2 * nh * hd + nkv * hd,
18206 mixed,
18207 &mut v_new,
18208 hidden,
18209 nkv * hd,
18210 t,
18211 )?;
18212 }
18213 let mut q = ws.take_f32(e, "qsa.q", t * nh * hd, 0)?;
18214 let mut gate = ws.take_f32(e, "qsa.gate", t * nh * hd, 0)?;
18215 e.q_gate_split(&q_fused, &mut q, &mut gate, hd, nh, t)?;
18216 ws.put_f32("qsa.qf", q_fused);
18217 let mut q = if let Some(norm) = h.q_norm.as_ref() {
18218 let mut dst = ws.take_f32(e, "qsa.qn", t * nh * hd, 0)?;
18219 e.rms_norm(&q, norm, &mut dst, hd, t * nh, eps)?;
18220 ws.put_f32("qsa.q", q);
18221 dst
18222 } else {
18223 q
18224 };
18225 let mut k_new = if let Some(norm) = h.k_norm.as_ref() {
18226 let mut dst = ws.take_f32(e, "qsa.kn", t * nkv * hd, 0)?;
18227 e.rms_norm(&k_new, norm, &mut dst, hd, t * nkv, eps)?;
18228 ws.put_f32("qsa.k", k_new);
18229 dst
18230 } else {
18231 k_new
18232 };
18233 let positions: Vec<i32> = (0..t).map(|i| (base_pos + i) as i32).collect();
18234 let pos_dev = ws.take_i32(e, "qsa.pos", &positions, 0)?;
18235 if let Some(yarn) = h.yarn.as_ref() {
18236 e.rope_neox_ffm(
18237 &mut q,
18238 &pos_dev,
18239 hd,
18240 h.n_rot,
18241 nh,
18242 t,
18243 h.rope_base,
18244 1.0,
18245 &yarn.ff,
18246 yarn.mscale,
18247 )?;
18248 e.rope_neox_ffm(
18249 &mut k_new,
18250 &pos_dev,
18251 hd,
18252 h.n_rot,
18253 nkv,
18254 t,
18255 h.rope_base,
18256 1.0,
18257 &yarn.ff,
18258 yarn.mscale,
18259 )?;
18260 } else {
18261 e.rope_neox(&mut q, &pos_dev, hd, h.n_rot, nh, t, h.rope_base, 1.0)?;
18262 e.rope_neox(&mut k_new, &pos_dev, hd, h.n_rot, nkv, t, h.rope_base, 1.0)?;
18263 }
18264 ws.put_i32("qsa.pos", pos_dev);
18265 match kv {
18266 QsaKvStore::F32 { k, v } => {
18267 e.copy_range_into(k, base_pos * nkv * hd, &k_new, 0, t * nkv * hd)?;
18268 e.copy_range_into(v, base_pos * nkv * hd, &v_new, 0, t * nkv * hd)?;
18269 }
18270 QsaKvStore::Q8Q5 { k, v } => {
18271 launch_q4e_kv_append(e, &k_new, &v_new, k, v, base_pos, t, nkv * hd)?;
18272 }
18273 }
18274 ws.put_f32(
18275 if h.k_norm.is_some() {
18276 "qsa.kn"
18277 } else {
18278 "qsa.k"
18279 },
18280 k_new,
18281 );
18282 ws.put_f32("qsa.v", v_new);
18283 Ok((q, gate))
18284 }
18285
18286 #[allow(clippy::too_many_arguments)]
18291 fn qsa_half_attend(
18292 &self,
18293 e: &Engine,
18294 ws: &mut StepPool,
18295 h: &QsaHalfW,
18296 hstate: &MixerHalfState,
18297 q: CudaSlice<f32>,
18298 gate: CudaSlice<f32>,
18299 pos_dev: &CudaSlice<i32>,
18300 meta_dev: &CudaSlice<i32>,
18301 max_count: usize,
18302 t: usize,
18303 t_kv: usize,
18304 ) -> Res<CudaSlice<f32>> {
18305 let MixerHalfState::Qsa { kv } = hstate else {
18306 return Err("qwen4exp_gpu tp2: QSA half bound to non-QSA state".into());
18307 };
18308 let hidden = self.hidden;
18309 let (nh, nkv, hd) = (h.nh_h, h.nkv_h, h.hd);
18310 let mut attended = ws.take_f32(e, "qsa.att", t * nh * hd, 0)?;
18311 match kv {
18312 QsaKvStore::F32 { k, v } => {
18313 let k_view = k.slice(0..t_kv * nkv * hd);
18314 let v_view = v.slice(0..t_kv * nkv * hd);
18315 launch_sdpa_blocklist(
18316 e,
18317 &q,
18318 &k_view,
18319 &v_view,
18320 &mut attended,
18321 pos_dev,
18322 meta_dev,
18323 hd,
18324 nh,
18325 nkv,
18326 t,
18327 max_count,
18328 h.scale,
18329 )?;
18330 }
18331 QsaKvStore::Q8Q5 { k, v } => {
18332 launch_q4e_sdpa_blocklist_q8q5(
18333 e,
18334 &q,
18335 k,
18336 v,
18337 &mut attended,
18338 pos_dev,
18339 meta_dev,
18340 hd,
18341 nh,
18342 nkv,
18343 t,
18344 max_count,
18345 h.scale,
18346 )?;
18347 }
18348 }
18349 ws.put_f32(
18350 if h.q_norm.is_some() {
18351 "qsa.qn"
18352 } else {
18353 "qsa.q"
18354 },
18355 q,
18356 );
18357 let mut sg = ws.take_f32(e, "qsa.sg", t * nh * hd, 0)?;
18358 e.sigmoid(&gate, &mut sg, t * nh * hd)?;
18359 let mut gated = ws.take_f32(e, "qsa.gated", t * nh * hd, 0)?;
18360 e.mul(&attended, &sg, &mut gated, t * nh * hd)?;
18361 let mut partial = ws.take_f32(e, "mixer.out", t * hidden, 0)?;
18362 launch_qmatvec_bf16w(
18363 e,
18364 &h.wo_b16,
18365 &gated,
18366 &mut partial,
18367 nh * hd,
18368 hidden,
18369 t,
18370 1,
18371 0,
18372 0,
18373 nh * hd,
18374 0,
18375 )?;
18376 ws.put_f32("qsa.sg", sg);
18377 ws.put_f32("qsa.gated", gated);
18378 ws.put_f32("qsa.att", attended);
18379 ws.put_f32("qsa.gate", gate);
18380 Ok(partial)
18381 }
18382
18383 #[allow(dead_code)]
18388 #[allow(clippy::too_many_arguments)]
18389 fn qsa_indexer_mask(
18390 &self,
18391 e: &Engine,
18392 ws: &mut StepPool,
18393 qsa: &QsaW,
18394 eps: f32,
18395 mixed: &CudaSlice<f32>,
18396 raw_keys: &mut IdxRawCache,
18397 pooled_keys: &mut Vec<f32>,
18398 base_pos: usize,
18399 ) -> Res<Vec<u8>> {
18400 let overlay = &qsa.overlay;
18401 let idx_dim = overlay.head_dim as usize;
18402 let qk_width = (overlay.query_heads as usize + overlay.kv_heads as usize) * idx_dim;
18403 let hidden = self.hidden;
18404 let mut idx_proj = ws.take_f32(e, "qsa.idxp", qk_width, 0)?;
18405 e.linear_device_into(mixed, &qsa.idx_proj, &mut idx_proj, 1, hidden, qk_width)?;
18406 let rows = e.dtoh_view(&idx_proj.slice(0..qk_width))?;
18407 ws.put_f32("qsa.idxp", idx_proj);
18408 raw_keys.append_rows_f32(
18409 &rows[overlay.query_heads as usize * idx_dim..qk_width],
18410 1,
18411 idx_dim,
18412 );
18413 indexer_mask_rows(
18414 overlay,
18415 qsa.attn.rope.base,
18416 qsa.yarn.as_ref().map(|y| (y.ff_host.as_slice(), y.mscale)),
18417 eps,
18418 &qsa.idx_q_norm,
18419 &qsa.idx_k_norm,
18420 &rows,
18421 raw_keys,
18422 pooled_keys,
18423 base_pos,
18424 1,
18425 base_pos + 1,
18426 0,
18427 )
18428 }
18429
18430 #[allow(clippy::too_many_arguments)]
18434 fn tp2_shared_half(
18435 &self,
18436 e: &Engine,
18437 ws: &mut StepPool,
18438 gu_b16: &CudaSlice<u8>,
18439 down_b16: &CudaSlice<u8>,
18440 input_gate: Option<&CudaSlice<f32>>,
18441 mixed: &CudaSlice<f32>,
18442 sffh: usize,
18443 t: usize,
18444 ) -> Res<(CudaSlice<f32>, Option<CudaSlice<f32>>)> {
18445 let hidden = self.hidden;
18446 let mut sh_gate = ws.take_f32(e, "moe.sh_gate", t * sffh, 0)?;
18447 let mut sh_up = ws.take_f32(e, "moe.sh_up", t * sffh, 0)?;
18448 if t == 1 && proj_stack_on() {
18451 launch_qmatvec_bf16w_multi4(
18452 e,
18453 gu_b16,
18454 mixed,
18455 &[(&sh_gate, sffh), (&sh_up, sffh)],
18456 hidden,
18457 )?;
18458 } else {
18459 launch_qmatvec_bf16w_off(e, gu_b16, 0, mixed, &mut sh_gate, hidden, sffh, t)?;
18460 launch_qmatvec_bf16w_off(e, gu_b16, sffh, mixed, &mut sh_up, hidden, sffh, t)?;
18461 }
18462 let mut act = ws.take_f32(e, "moe.sh_act", t * sffh, 0)?;
18463 e.silu_mul(&sh_gate, &sh_up, &mut act, t * sffh)?;
18464 let mut shared = ws.take_f32(e, "moe.sh_down", t * hidden, 0)?;
18465 launch_qmatvec_bf16w(
18466 e,
18467 down_b16,
18468 &act,
18469 &mut shared,
18470 sffh,
18471 hidden,
18472 t,
18473 1,
18474 0,
18475 0,
18476 sffh,
18477 0,
18478 )?;
18479 let g = match input_gate {
18480 Some(w) => {
18481 let mut g = ws.take_f32(e, "moe.g", t, 0)?;
18482 e.sigmoid_dot_rows_into(mixed, w, &mut g, hidden, t)?;
18483 Some(g)
18484 }
18485 None => None,
18486 };
18487 ws.put_f32("moe.sh_gate", sh_gate);
18488 ws.put_f32("moe.sh_up", sh_up);
18489 ws.put_f32("moe.sh_act", act);
18490 Ok((shared, g))
18491 }
18492}
18493
18494impl Qwen4ExpGpu {
18495 pub fn decode_step_tp2(
18500 &self,
18501 e0: &Engine,
18502 e1: &Engine,
18503 shard: &Tp2Shard,
18504 token: u32,
18505 state: &mut Qwen4ExpState,
18506 ) -> Res<Vec<f32>> {
18507 if !trunk_bf16_on() || !hc_fused_gate_on() {
18508 return Err(
18509 "qwen4exp_gpu tp2: requires set_trunk_bf16(true) and set_hc_fused_gate(true) \
18510 (replicated compute must run deterministic kernels)"
18511 .into(),
18512 );
18513 }
18514 if state.pos + 1 > state.capacity {
18515 return Err("qwen4exp_gpu: state capacity exceeded".into());
18516 }
18517 if state.tp2.is_none() {
18518 self.tp2_migrate(e0, e1, shard, state)?;
18519 state.graphs = StepGraphs::default();
18520 }
18521 let hidden = self.hidden;
18522 let vocab = self.vocab;
18523 let vsplit = shard.vsplit;
18524 let base_pos = state.pos;
18525 let reserve = state.reserve;
18526 state.tokens.push(token);
18527 let Qwen4ExpState {
18528 ref tokens,
18529 ws: ref mut ws0,
18530 ref mut tp2,
18531 layers: ref mut lstates,
18532 ..
18533 } = *state;
18534 let Tp2State {
18535 ws1,
18536 layers: tlayers,
18537 graphs: tgraphs,
18538 ..
18539 } = tp2.as_mut().expect("migrated above");
18540 let cap = reserve.max(1);
18543
18544 let token_us = token as usize;
18546 if token_us >= vocab {
18547 return Err(format!("qwen4exp_gpu: token {token_us} out of range").into());
18548 }
18549 let embedded = &self.embed_host[token_us * hidden..(token_us + 1) * hidden];
18550 let mut planes1: Vec<CudaSlice<f32>> = Vec::with_capacity(self.streams);
18551 let ptrs1 = {
18552 let _g = e1.gpu.enter_main()?;
18553 let embedded_dev = ws1.take_f32_h2d(e1, "entry.embed", embedded, cap * hidden)?;
18554 for s in 0..self.streams {
18555 let mut plane = ws1.take_f32(e1, PLANE_SLOTS[s], hidden, cap * hidden)?;
18556 e1.copy_into(&mut plane, 0, &embedded_dev, hidden)?;
18557 planes1.push(plane);
18558 }
18559 ws1.put_f32("entry.embed", embedded_dev);
18560 let ptr_vals: Vec<u64> = {
18561 let stream = e1.gpu.stream();
18562 planes1.iter().map(|p| p.device_ptr(&stream).0).collect()
18563 };
18564 ws1.take_u64_h2d(e1, "hc.ptrs", &ptr_vals, 0)?
18565 };
18566 let mut planes0: Vec<CudaSlice<f32>> = Vec::with_capacity(self.streams);
18567 let ptrs0 = {
18568 let _g = e0.gpu.enter_main()?;
18569 let embedded_dev = ws0.take_f32_h2d(e0, "entry.embed", embedded, cap * hidden)?;
18570 for s in 0..self.streams {
18571 let mut plane = ws0.take_f32(e0, PLANE_SLOTS[s], hidden, cap * hidden)?;
18572 e0.copy_into(&mut plane, 0, &embedded_dev, hidden)?;
18573 planes0.push(plane);
18574 }
18575 ws0.put_f32("entry.embed", embedded_dev);
18576 let ptr_vals: Vec<u64> = {
18577 let stream = e0.gpu.stream();
18578 planes0.iter().map(|p| p.device_ptr(&stream).0).collect()
18579 };
18580 ws0.take_u64_h2d(e0, "hc.ptrs", &ptr_vals, 0)?
18581 };
18582
18583 let use_graphs = decode_graphs_on() && step_ws_on();
18586 let graphs_live = use_graphs && tgraphs.warm;
18587 if use_graphs && !tgraphs.warm {
18588 tgraphs.warm = true;
18589 }
18590 if graphs_live && tgraphs.a[0].len() != self.layers.len() {
18591 for d in 0..2 {
18592 tgraphs.a[d] = (0..self.layers.len()).map(|_| None).collect();
18593 tgraphs.b[d] = (0..self.layers.len()).map(|_| None).collect();
18594 tgraphs.c[d] = (0..self.layers.len()).map(|_| None).collect();
18595 tgraphs.d[d] = (0..self.layers.len()).map(|_| None).collect();
18596 }
18597 }
18598 for (li, layer) in self.layers.iter().enumerate() {
18599 let lstate = &mut lstates[li];
18600 let tw = &shard.layers[li];
18601 let ts = &mut tlayers[li];
18602 let eps_a = layer.eps_attn;
18603 let eps_m = layer.eps_mlp;
18604 let moe = &layer.moe;
18605 let ff = moe.plan.expert_intermediate_size as usize;
18606 let experts = moe.plan.expert_count as usize;
18607 let selected = moe.plan.experts_per_token as usize;
18608 let sff = moe
18609 .plan
18610 .shared
18611 .as_ref()
18612 .map(|s| s.intermediate_size as usize)
18613 .unwrap_or(0);
18614 let sffh = sff / 2;
18615
18616 match (&layer.mixer, &tw.mixer0, &tw.mixer1) {
18618 (MixerW::Gdn(_), MixerHalfW::Gdn(h0), MixerHalfW::Gdn(h1)) => {
18619 {
18620 let _g = e1.gpu.enter_main()?;
18621 if let (Some(ple1), Some(ps1)) = (tw.ple1.as_ref(), ts.ple1.as_mut()) {
18622 let table = &layer.ple.as_ref().expect("ple plan").table;
18623 self.ple_block(
18624 e1,
18625 layer,
18626 ple1,
18627 table,
18628 ps1,
18629 &mut planes1,
18630 tokens,
18631 1,
18632 false,
18633 None,
18634 )?;
18635 }
18636 if graphs_live && layer.ple.is_none() {
18637 if tgraphs.a[1][li].is_none() {
18638 tgraphs.a[1][li] =
18639 Some(e1.capture_graph_retained_nowarm(|eng| {
18640 self.tp2_gdn_seg_a(
18641 eng,
18642 ws1,
18643 &ptrs1,
18644 &tw.attn_gate1,
18645 h1,
18646 &mut ts.m1,
18647 &planes1,
18648 eps_a,
18649 shard.stage0_raw[0],
18650 )
18651 })?);
18652 }
18653 tgraphs.a[1][li].as_ref().unwrap().0.launch()?;
18654 } else {
18655 self.tp2_gdn_seg_a(
18656 e1,
18657 ws1,
18658 &ptrs1,
18659 &tw.attn_gate1,
18660 h1,
18661 &mut ts.m1,
18662 &planes1,
18663 eps_a,
18664 shard.stage0_raw[0],
18665 )?;
18666 }
18667 shard.ev1[0].record(&e1.gpu.stream())?;
18668 }
18669 {
18670 let _g = e0.gpu.enter_main()?;
18671 if let (Some(ple), Some(ps)) = (layer.ple.as_ref(), lstate.ple.as_mut()) {
18672 self.ple_block(
18673 e0,
18674 layer,
18675 ple,
18676 &ple.table,
18677 ps,
18678 &mut planes0,
18679 tokens,
18680 1,
18681 false,
18682 None,
18683 )?;
18684 }
18685 if graphs_live && layer.ple.is_none() {
18686 if tgraphs.a[0][li].is_none() {
18687 tgraphs.a[0][li] =
18688 Some(e0.capture_graph_retained_nowarm(|eng| {
18689 self.tp2_gdn_seg_a(
18690 eng,
18691 ws0,
18692 &ptrs0,
18693 &layer.attn_gate,
18694 h0,
18695 &mut ts.m0,
18696 &planes0,
18697 eps_a,
18698 shard.stage1_raw[0],
18699 )
18700 })?);
18701 }
18702 tgraphs.a[0][li].as_ref().unwrap().0.launch()?;
18703 } else {
18704 self.tp2_gdn_seg_a(
18705 e0,
18706 ws0,
18707 &ptrs0,
18708 &layer.attn_gate,
18709 h0,
18710 &mut ts.m0,
18711 &planes0,
18712 eps_a,
18713 shard.stage1_raw[0],
18714 )?;
18715 }
18716 shard.ev0[0].record(&e0.gpu.stream())?;
18717 }
18718 }
18719 (MixerW::Qsa(qsa), MixerHalfW::Qsa(h0), MixerHalfW::Qsa(h1)) => {
18720 let (q1, g1, inj1) = {
18723 let _g = e1.gpu.enter_main()?;
18724 let (mixed1, inj1) = self.gate_read(
18725 e1,
18726 ws1,
18727 &ptrs1,
18728 &tw.attn_gate1,
18729 &planes1,
18730 1,
18731 eps_a,
18732 false,
18733 )?;
18734 let (q1, g1) = self
18735 .qsa_half_proj(e1, ws1, eps_a, h1, &mixed1, &mut ts.m1, base_pos, 1)?;
18736 ws1.put_f32("hc.mixed", mixed1);
18737 (q1, g1, inj1)
18738 };
18739 let (sels, q0, g0, inj0) = {
18744 let _g = e0.gpu.enter_main()?;
18745 let (mixed0, inj0) = self.gate_read(
18746 e0,
18747 ws0,
18748 &ptrs0,
18749 &layer.attn_gate,
18750 &planes0,
18751 1,
18752 eps_a,
18753 false,
18754 )?;
18755 let (q0, g0) = self
18756 .qsa_half_proj(e0, ws0, eps_a, h0, &mixed0, &mut ts.m0, base_pos, 1)?;
18757 let MixerState::Qsa {
18758 raw_keys,
18759 pooled_keys,
18760 pooled_dev,
18761 pooled_dev_rows,
18762 raw_dev,
18763 raw_dev_rows,
18764 idx_audit,
18765 ..
18766 } = &mut lstate.mixer
18767 else {
18768 return Err("qwen4exp_gpu tp2: QSA layer without raw-key cache".into());
18769 };
18770 let sels = self.qsa_update_select(
18771 e0,
18772 ws0,
18773 qsa,
18774 eps_a,
18775 &mixed0,
18776 raw_keys,
18777 pooled_keys,
18778 pooled_dev,
18779 pooled_dev_rows,
18780 raw_dev,
18781 raw_dev_rows,
18782 idx_audit.as_mut(),
18783 base_pos,
18784 1,
18785 0,
18786 false,
18787 )?;
18788 ws0.put_f32("hc.mixed", mixed0);
18789 (sels, q0, g0, inj0)
18790 };
18791 let t_kv = base_pos + 1;
18792 let block_size = qsa.overlay.block_size as usize;
18793 let (pos_flat, meta, max_count) = rowsel_positions(&sels, block_size);
18794 {
18795 let _g = e1.gpu.enter_main()?;
18796 let pos_dev = ws1.take_i32(e1, "qsa.selpos", &pos_flat, 0)?;
18797 let meta_dev = ws1.take_i32(e1, "qsa.selmeta", &meta, 0)?;
18798 let p1 = self.qsa_half_attend(
18799 e1, ws1, h1, &ts.m1, q1, g1, &pos_dev, &meta_dev, max_count, 1, t_kv,
18800 )?;
18801 ws1.put_i32("qsa.selpos", pos_dev);
18802 ws1.put_i32("qsa.selmeta", meta_dev);
18803 launch_push(e1, &p1, shard.stage0_raw[0], hidden)?;
18804 ws1.put_f32("mixer.out", p1);
18805 put_inject(ws1, inj1);
18806 shard.ev1[0].record(&e1.gpu.stream())?;
18807 }
18808 {
18809 let _g = e0.gpu.enter_main()?;
18810 let pos_dev = ws0.take_i32(e0, "qsa.selpos", &pos_flat, 0)?;
18811 let meta_dev = ws0.take_i32(e0, "qsa.selmeta", &meta, 0)?;
18812 let p0 = self.qsa_half_attend(
18813 e0, ws0, h0, &ts.m0, q0, g0, &pos_dev, &meta_dev, max_count, 1, t_kv,
18814 )?;
18815 ws0.put_i32("qsa.selpos", pos_dev);
18816 ws0.put_i32("qsa.selmeta", meta_dev);
18817 launch_push(e0, &p0, shard.stage1_raw[0], hidden)?;
18818 ws0.put_f32("mixer.out", p0);
18819 put_inject(ws0, inj0);
18820 shard.ev0[0].record(&e0.gpu.stream())?;
18821 }
18822 }
18823 _ => return Err("qwen4exp_gpu tp2: mixer/shard shape mismatch".into()),
18824 }
18825 {
18826 let _g = e0.gpu.enter_main()?;
18827 e0.gpu.stream().wait(&shard.ev1[0])?;
18828 }
18829 {
18830 let _g = e1.gpu.enter_main()?;
18831 e1.gpu.stream().wait(&shard.ev0[0])?;
18832 }
18833
18834 {
18836 let _g = e1.gpu.enter_main()?;
18837 if graphs_live {
18838 if tgraphs.b[1][li].is_none() {
18839 tgraphs.b[1][li] = Some(e1.capture_graph_retained_nowarm(|eng| {
18840 self.tp2_seg_b(
18841 eng,
18842 ws1,
18843 &ptrs1,
18844 &tw.mlp_gate1,
18845 &mut planes1,
18846 &shard.stage1[0],
18847 false,
18848 eps_m,
18849 Some((
18850 &tw.moe.shared_gu1_b16,
18851 &tw.moe.shared_down1,
18852 tw.moe.shared_input_gate1.as_ref(),
18853 sffh,
18854 )),
18855 )
18856 })?);
18857 }
18858 tgraphs.b[1][li].as_ref().unwrap().0.launch()?;
18859 } else {
18860 self.tp2_seg_b(
18861 e1,
18862 ws1,
18863 &ptrs1,
18864 &tw.mlp_gate1,
18865 &mut planes1,
18866 &shard.stage1[0],
18867 false,
18868 eps_m,
18869 Some((
18870 &tw.moe.shared_gu1_b16,
18871 &tw.moe.shared_down1,
18872 tw.moe.shared_input_gate1.as_ref(),
18873 sffh,
18874 )),
18875 )?;
18876 }
18877 }
18878 {
18879 let _g = e0.gpu.enter_main()?;
18880 if graphs_live {
18881 if tgraphs.b[0][li].is_none() {
18882 tgraphs.b[0][li] = Some(e0.capture_graph_retained_nowarm(|eng| {
18883 self.tp2_seg_b(
18884 eng,
18885 ws0,
18886 &ptrs0,
18887 &layer.mlp_gate,
18888 &mut planes0,
18889 &shard.stage0[0],
18890 true,
18891 eps_m,
18892 None,
18893 )
18894 })?);
18895 }
18896 tgraphs.b[0][li].as_ref().unwrap().0.launch()?;
18897 } else {
18898 self.tp2_seg_b(
18899 e0,
18900 ws0,
18901 &ptrs0,
18902 &layer.mlp_gate,
18903 &mut planes0,
18904 &shard.stage0[0],
18905 true,
18906 eps_m,
18907 None,
18908 )?;
18909 }
18910 }
18911
18912 let route = {
18915 let _g = e0.gpu.enter_main()?;
18916 let mixed0 = ws0.take_f32(e0, "hc.mixed", hidden, 0)?;
18917 let mut router_out = ws0.take_f32(e0, "moe.router", experts, 0)?;
18918 let none: Option<CudaSlice<u8>> = None;
18919 let rb = if router_bf16_on() {
18920 &moe.router_b16
18921 } else {
18922 &none
18923 };
18924 linear_trunk_into(
18925 e0,
18926 &moe.router,
18927 rb,
18928 &mixed0,
18929 &mut router_out,
18930 1,
18931 hidden,
18932 experts,
18933 )?;
18934 let logits = e0.dtoh_view(&router_out.slice(0..experts))?;
18935 ws0.put_f32("moe.router", router_out);
18936 ws0.put_f32("hc.mixed", mixed0);
18937 host_route_softmax_topk(&logits, selected)
18938 };
18939 let place = &tw.place;
18943 let mut sel0: Vec<i32> = Vec::with_capacity(selected);
18944 let mut w0: Vec<f32> = Vec::with_capacity(selected);
18945 let mut sel1: Vec<i32> = Vec::with_capacity(selected);
18946 let mut w1: Vec<f32> = Vec::with_capacity(selected);
18947 for &(expert, weight) in &route {
18948 if place.rank(expert) == 0 {
18949 sel0.push(place.local(expert) as i32);
18950 w0.push(weight);
18951 } else {
18952 sel1.push(place.local(expert) as i32);
18953 w1.push(weight);
18954 }
18955 }
18956 {
18957 let r0: Vec<Vec<(usize, f32)>> = vec![
18960 sel0.iter()
18961 .zip(&w0)
18962 .map(|(&s, &w)| (s as usize, w))
18963 .collect(),
18964 ];
18965 let r1: Vec<Vec<(usize, f32)>> = vec![
18966 sel1.iter()
18967 .zip(&w1)
18968 .map(|(&s, &w)| (s as usize, w))
18969 .collect(),
18970 ];
18971 tp2_count_split(&r0, &r1);
18972 trace_moe_routes(layer.index, 1, std::slice::from_ref(&route));
18973 }
18974 match tp2_gate_red()? {
18975 Tp2GateRed::None => {}
18976 Tp2GateRed::SkipPeerMoe => {
18978 sel1.clear();
18979 w1.clear();
18980 }
18981 Tp2GateRed::PeerLocalIds => {
18984 sel0.extend(sel1.drain(..));
18985 w0.extend(w1.drain(..));
18986 }
18987 Tp2GateRed::ReverseePeerWeights => w1.reverse(),
18988 }
18989 let max_sel = selected;
18990 {
18991 let _g = e1.gpu.enter_main()?;
18992 ws1.upsert_u8(e1, "moe.pack", &tp2_pack_bytes(&sel1, &w1, max_sel), 0)?;
18993 if graphs_live {
18994 if tgraphs.c[1][li].is_none() {
18995 tgraphs.c[1][li] = Some(e1.capture_graph_retained_nowarm(|eng| {
18996 self.tp2_seg_c(
18997 eng,
18998 ws1,
18999 (
19000 &tw.moe.gate1.codes,
19001 &tw.moe.gate1.scales,
19002 &tw.moe.gate1.macros_dev,
19003 ),
19004 (
19005 &tw.moe.up1.codes,
19006 &tw.moe.up1.scales,
19007 &tw.moe.up1.macros_dev,
19008 ),
19009 (
19010 &tw.moe.down1.codes,
19011 &tw.moe.down1.scales,
19012 &tw.moe.down1.macros_dev,
19013 ),
19014 ff,
19015 max_sel,
19016 None,
19017 tw.moe.shared_input_gate1.is_some(),
19018 shard.stage0_raw[1],
19019 )
19020 })?);
19021 }
19022 tgraphs.c[1][li].as_ref().unwrap().0.launch()?;
19023 } else {
19024 self.tp2_seg_c(
19025 e1,
19026 ws1,
19027 (
19028 &tw.moe.gate1.codes,
19029 &tw.moe.gate1.scales,
19030 &tw.moe.gate1.macros_dev,
19031 ),
19032 (
19033 &tw.moe.up1.codes,
19034 &tw.moe.up1.scales,
19035 &tw.moe.up1.macros_dev,
19036 ),
19037 (
19038 &tw.moe.down1.codes,
19039 &tw.moe.down1.scales,
19040 &tw.moe.down1.macros_dev,
19041 ),
19042 ff,
19043 max_sel,
19044 None,
19045 tw.moe.shared_input_gate1.is_some(),
19046 shard.stage0_raw[1],
19047 )?;
19048 }
19049 shard.ev1[1].record(&e1.gpu.stream())?;
19050 }
19051 {
19052 let _g = e0.gpu.enter_main()?;
19053 let (
19054 BankHalf::Nvfp4 {
19055 codes: gc,
19056 scales: gs,
19057 macros_dev: gm,
19058 ..
19059 },
19060 BankHalf::Nvfp4 {
19061 codes: uc,
19062 scales: us,
19063 macros_dev: um,
19064 ..
19065 },
19066 BankHalf::Nvfp4 {
19067 codes: dc,
19068 scales: ds,
19069 macros_dev: dm,
19070 ..
19071 },
19072 ) = (&moe.bank.gate, &moe.bank.up, &moe.bank.down)
19073 else {
19074 return Err("qwen4exp_gpu tp2: card0 bank is not NVFP4".into());
19075 };
19076 ws0.upsert_u8(e0, "moe.pack", &tp2_pack_bytes(&sel0, &w0, max_sel), 0)?;
19077 if graphs_live {
19078 if tgraphs.c[0][li].is_none() {
19079 tgraphs.c[0][li] = Some(e0.capture_graph_retained_nowarm(|eng| {
19080 self.tp2_seg_c(
19081 eng,
19082 ws0,
19083 (gc, gs, gm),
19084 (uc, us, um),
19085 (dc, ds, dm),
19086 ff,
19087 max_sel,
19088 Some((
19089 &tw.moe.shared_gu0_b16,
19090 &tw.moe.shared_down0,
19091 moe.shared_input_gate.as_ref(),
19092 sffh,
19093 )),
19094 false,
19095 shard.stage1_raw[1],
19096 )
19097 })?);
19098 }
19099 tgraphs.c[0][li].as_ref().unwrap().0.launch()?;
19100 } else {
19101 self.tp2_seg_c(
19102 e0,
19103 ws0,
19104 (gc, gs, gm),
19105 (uc, us, um),
19106 (dc, ds, dm),
19107 ff,
19108 max_sel,
19109 Some((
19110 &tw.moe.shared_gu0_b16,
19111 &tw.moe.shared_down0,
19112 moe.shared_input_gate.as_ref(),
19113 sffh,
19114 )),
19115 false,
19116 shard.stage1_raw[1],
19117 )?;
19118 }
19119 shard.ev0[1].record(&e0.gpu.stream())?;
19120 }
19121 {
19122 let _g = e1.gpu.enter_main()?;
19123 e1.gpu.stream().wait(&shard.ev0[1])?;
19124 if graphs_live {
19125 if tgraphs.d[1][li].is_none() {
19126 tgraphs.d[1][li] = Some(e1.capture_graph_retained_nowarm(|eng| {
19127 self.tp2_seg_d(eng, ws1, &ptrs1, &mut planes1, &shard.stage1[1], false)
19128 })?);
19129 }
19130 tgraphs.d[1][li].as_ref().unwrap().0.launch()?;
19131 } else {
19132 self.tp2_seg_d(e1, ws1, &ptrs1, &mut planes1, &shard.stage1[1], false)?;
19133 }
19134 }
19135 {
19136 let _g = e0.gpu.enter_main()?;
19137 e0.gpu.stream().wait(&shard.ev1[1])?;
19138 if graphs_live {
19139 if tgraphs.d[0][li].is_none() {
19140 tgraphs.d[0][li] = Some(e0.capture_graph_retained_nowarm(|eng| {
19141 self.tp2_seg_d(eng, ws0, &ptrs0, &mut planes0, &shard.stage0[1], true)
19142 })?);
19143 }
19144 tgraphs.d[0][li].as_ref().unwrap().0.launch()?;
19145 } else {
19146 self.tp2_seg_d(e0, ws0, &ptrs0, &mut planes0, &shard.stage0[1], true)?;
19147 }
19148 }
19149 }
19150
19151 {
19153 let _g = e1.gpu.enter_main()?;
19154 if graphs_live {
19155 if tgraphs.exit[1].is_none() {
19156 tgraphs.exit[1] = Some(e1.capture_graph_retained_nowarm(|eng| {
19157 self.tp2_seg_exit(
19158 eng,
19159 ws1,
19160 &ptrs1,
19161 &shard.exit_gate1,
19162 &planes1,
19163 &shard.lm_head1,
19164 vocab - vsplit,
19165 1, )
19167 })?);
19168 }
19169 tgraphs.exit[1].as_ref().unwrap().0.launch()?;
19170 } else {
19171 self.tp2_seg_exit(
19172 e1,
19173 ws1,
19174 &ptrs1,
19175 &shard.exit_gate1,
19176 &planes1,
19177 &shard.lm_head1,
19178 vocab - vsplit,
19179 1, )?;
19181 }
19182 }
19183 {
19184 let _g = e0.gpu.enter_main()?;
19185 let head0 = self
19186 .output_b16
19187 .as_ref()
19188 .ok_or("qwen4exp_gpu tp2: lm_head has no bf16 twin")?;
19189 if graphs_live {
19190 if tgraphs.exit[0].is_none() {
19191 tgraphs.exit[0] = Some(e0.capture_graph_retained_nowarm(|eng| {
19192 self.tp2_seg_exit(
19193 eng,
19194 ws0,
19195 &ptrs0,
19196 &self.exit_mixer,
19197 &planes0,
19198 head0,
19199 vsplit,
19200 1, )
19202 })?);
19203 }
19204 tgraphs.exit[0].as_ref().unwrap().0.launch()?;
19205 } else {
19206 self.tp2_seg_exit(
19207 e0,
19208 ws0,
19209 &ptrs0,
19210 &self.exit_mixer,
19211 &planes0,
19212 head0,
19213 vsplit,
19214 1, )?;
19216 }
19217 }
19218 let mut out = vec![0.0f32; vocab];
19219 {
19220 let _g = e0.gpu.enter_main()?;
19221 let logits0 = ws0.peek_f32("logits")?;
19222 let host0 = e0.dtoh_view(&logits0.slice(0..vsplit))?;
19223 out[..vsplit].copy_from_slice(&host0);
19224 }
19225 {
19226 let _g = e1.gpu.enter_main()?;
19227 let logits1 = ws1.peek_f32("logits")?;
19228 let host1 = e1.dtoh_view(&logits1.slice(0..vocab - vsplit))?;
19229 out[vsplit..].copy_from_slice(&host1);
19230 }
19231 for (s, plane) in planes0.into_iter().enumerate() {
19232 ws0.put_f32(PLANE_SLOTS[s], plane);
19233 }
19234 for (s, plane) in planes1.into_iter().enumerate() {
19235 ws1.put_f32(PLANE_SLOTS[s], plane);
19236 }
19237 ws0.put_u64("hc.ptrs", ptrs0);
19238 ws1.put_u64("hc.ptrs", ptrs1);
19239 state.pos += 1;
19240 Ok(out)
19241 }
19242}
19243
19244impl Qwen4ExpGpu {
19245 pub fn alloc_state_tp2(
19251 &self,
19252 e0: &Engine,
19253 e1: &Engine,
19254 shard: &Tp2Shard,
19255 capacity: usize,
19256 reserve: usize,
19257 ) -> Res<Qwen4ExpState> {
19258 let mut state = {
19262 let mut st = self.alloc_state_reserve(e0, 1, 1, None)?;
19269 st.capacity = capacity;
19270 st.reserve = reserve;
19271 st
19272 };
19273 let mut tlayers = Vec::with_capacity(self.layers.len());
19274 for (layer, tw) in self.layers.iter().zip(shard.layers.iter()) {
19275 let mk_half = |e: &Engine, hw: &MixerHalfW| -> Res<MixerHalfState> {
19276 let _g = e.gpu.enter_main()?;
19277 match hw {
19278 MixerHalfW::Gdn(h) => {
19279 let conv_dim = 2 * h.nk_h * h.hk + h.nv_h * h.hv;
19280 let pad = h.kernel - 1;
19281 Ok(MixerHalfState::Gdn {
19282 conv: e.zeros(pad * conv_dim)?,
19283 state: e.zeros(h.nv_h * h.hv * h.hk)?,
19284 })
19285 }
19286 MixerHalfW::Qsa(h) => {
19287 let kv_dim = h.nkv_h * h.hd;
19288 let kv = if kv_quant_on() {
19289 QsaKvStore::Q8Q5 {
19290 k: e.alloc_u8(capacity * q8_row_bytes(kv_dim))?,
19291 v: e.alloc_u8(capacity * q5_row_bytes(kv_dim))?,
19292 }
19293 } else {
19294 QsaKvStore::F32 {
19295 k: e.zeros(capacity * kv_dim)?,
19296 v: e.zeros(capacity * kv_dim)?,
19297 }
19298 };
19299 Ok(MixerHalfState::Qsa { kv })
19300 }
19301 }
19302 };
19303 let m0 = mk_half(e0, &tw.mixer0)?;
19304 let m1 = mk_half(e1, &tw.mixer1)?;
19305 let ple1 = match layer.ple.as_ref() {
19306 None => None,
19307 Some(ple) => {
19308 let pad = (ple.plan.conv_kernel as usize - 1) * ple.plan.max_ngram as usize;
19309 let _g = e1.gpu.enter_main()?;
19310 let mut conv_hist = Vec::with_capacity(self.streams);
19311 for _ in 0..self.streams {
19312 conv_hist.push(e1.zeros(pad * self.hidden)?);
19313 }
19314 Some(PleState {
19315 conv_hist,
19316 ngram_ids: Vec::new(),
19317 ngram_history: Vec::new(),
19318 ngram_last_eos: -1,
19319 })
19320 }
19321 };
19322 tlayers.push(Tp2LayerState { m0, m1, ple1 });
19323 }
19324 state.tp2 = Some(Tp2State {
19325 ws1: StepPool::default(),
19326 layers: tlayers,
19327 graphs: Tp2Graphs::default(),
19328 pf_stage0: None,
19329 pf_stage1: None,
19330 pf_stage0_raw: [0; 2],
19331 pf_stage1_raw: [0; 2],
19332 pf_rows: 0,
19333 });
19334 Ok(state)
19335 }
19336
19337 pub fn prefill_extend_tp2(
19342 &self,
19343 e0: &Engine,
19344 e1: &Engine,
19345 shard: &Tp2Shard,
19346 ids: &[u32],
19347 state: &mut Qwen4ExpState,
19348 chunk: usize,
19349 ) -> Res<Vec<f32>> {
19350 if ids.is_empty() || chunk == 0 {
19351 return Err("qwen4exp_gpu: prefill_extend_tp2 needs ids and a chunk size".into());
19352 }
19353 let mut last = Vec::new();
19354 for piece in ids.chunks(chunk) {
19355 let is_last =
19356 piece.as_ptr() as usize + piece.len() * 4 == ids.as_ptr() as usize + ids.len() * 4;
19357 let head = if is_last {
19358 HeadMode::LastRow
19359 } else {
19360 HeadMode::Skip
19361 };
19362 last = self.forward_tp2(e0, e1, shard, piece, state, head)?;
19363 }
19364 Ok(last)
19365 }
19366
19367 #[allow(clippy::too_many_arguments)]
19373 pub fn forward_tp2(
19374 &self,
19375 e0: &Engine,
19376 e1: &Engine,
19377 shard: &Tp2Shard,
19378 ids: &[u32],
19379 state: &mut Qwen4ExpState,
19380 head: HeadMode,
19381 ) -> Res<Vec<f32>> {
19382 if !trunk_bf16_on() || !hc_fused_gate_on() {
19383 return Err(
19384 "qwen4exp_gpu tp2: requires set_trunk_bf16(true) and set_hc_fused_gate(true)"
19385 .into(),
19386 );
19387 }
19388 let t = ids.len();
19389 if t == 0 {
19390 return Err("qwen4exp_gpu tp2: empty chunk".into());
19391 }
19392 if state.pos + t > state.capacity {
19393 return Err("qwen4exp_gpu: state capacity exceeded".into());
19394 }
19395 if state.tp2.is_none() {
19396 self.tp2_migrate(e0, e1, shard, state)?;
19397 state.graphs = StepGraphs::default();
19398 }
19399 let hidden = self.hidden;
19400 let vocab = self.vocab;
19401 let vsplit = shard.vsplit;
19402 let base_pos = state.pos;
19403 let reserve = state.reserve;
19404 state.tokens.extend_from_slice(ids);
19405 let Qwen4ExpState {
19406 ref tokens,
19407 ws: ref mut ws0,
19408 ref mut tp2,
19409 layers: ref mut lstates,
19410 ..
19411 } = *state;
19412 let tp2s = tp2.as_mut().expect("alloc'd or migrated above");
19413 if tp2s.pf_rows < t {
19416 {
19417 let _g = e1.gpu.enter_main()?;
19418 let s1 = [e1.zeros(t * hidden)?, e1.zeros(t * hidden)?];
19419 let s = e1.gpu.stream();
19420 tp2s.pf_stage1_raw = [s1[0].device_ptr(&s).0, s1[1].device_ptr(&s).0];
19421 tp2s.pf_stage1 = Some(s1);
19422 }
19423 {
19424 let _g = e0.gpu.enter_main()?;
19425 let s0 = [e0.zeros(t * hidden)?, e0.zeros(t * hidden)?];
19426 let s = e0.gpu.stream();
19427 tp2s.pf_stage0_raw = [s0[0].device_ptr(&s).0, s0[1].device_ptr(&s).0];
19428 tp2s.pf_stage0 = Some(s0);
19429 }
19430 tp2s.pf_rows = t;
19431 }
19432 let Tp2State {
19433 ws1,
19434 layers: tlayers,
19435 pf_stage0,
19436 pf_stage1,
19437 pf_stage0_raw,
19438 pf_stage1_raw,
19439 ..
19440 } = tp2s;
19441 let pf_stage0 = pf_stage0.as_ref().expect("sized above");
19442 let pf_stage1 = pf_stage1.as_ref().expect("sized above");
19443 let resv = reserve.max(t);
19444
19445 let mut embedded = vec![0.0f32; t * hidden];
19447 for (row, &token) in ids.iter().enumerate() {
19448 let token = token as usize;
19449 if token >= vocab {
19450 return Err(format!("qwen4exp_gpu: token {token} out of range").into());
19451 }
19452 embedded[row * hidden..(row + 1) * hidden]
19453 .copy_from_slice(&self.embed_host[token * hidden..(token + 1) * hidden]);
19454 }
19455 let mut planes1: Vec<CudaSlice<f32>> = Vec::with_capacity(self.streams);
19456 let ptrs1 = {
19457 let _g = e1.gpu.enter_main()?;
19458 let embedded_dev = ws1.take_f32_h2d(e1, "entry.embed", &embedded, resv * hidden)?;
19459 for s in 0..self.streams {
19460 let mut plane = ws1.take_f32(e1, PLANE_SLOTS[s], t * hidden, resv * hidden)?;
19461 e1.copy_into(&mut plane, 0, &embedded_dev, t * hidden)?;
19462 planes1.push(plane);
19463 }
19464 ws1.put_f32("entry.embed", embedded_dev);
19465 let ptr_vals: Vec<u64> = {
19466 let stream = e1.gpu.stream();
19467 planes1.iter().map(|p| p.device_ptr(&stream).0).collect()
19468 };
19469 ws1.take_u64_h2d(e1, "hc.ptrs", &ptr_vals, 0)?
19470 };
19471 let mut planes0: Vec<CudaSlice<f32>> = Vec::with_capacity(self.streams);
19472 let ptrs0 = {
19473 let _g = e0.gpu.enter_main()?;
19474 let embedded_dev = ws0.take_f32_h2d(e0, "entry.embed", &embedded, resv * hidden)?;
19475 for s in 0..self.streams {
19476 let mut plane = ws0.take_f32(e0, PLANE_SLOTS[s], t * hidden, resv * hidden)?;
19477 e0.copy_into(&mut plane, 0, &embedded_dev, t * hidden)?;
19478 planes0.push(plane);
19479 }
19480 ws0.put_f32("entry.embed", embedded_dev);
19481 let ptr_vals: Vec<u64> = {
19482 let stream = e0.gpu.stream();
19483 planes0.iter().map(|p| p.device_ptr(&stream).0).collect()
19484 };
19485 ws0.take_u64_h2d(e0, "hc.ptrs", &ptr_vals, 0)?
19486 };
19487
19488 for (li, layer) in self.layers.iter().enumerate() {
19489 let lstate = &mut lstates[li];
19490 let tw = &shard.layers[li];
19491 let ts = &mut tlayers[li];
19492 let eps_a = layer.eps_attn;
19493 let eps_m = layer.eps_mlp;
19494 let moe = &layer.moe;
19495 let ff = moe.plan.expert_intermediate_size as usize;
19496 let experts = moe.plan.expert_count as usize;
19497 let selected = moe.plan.experts_per_token as usize;
19498 let sff = moe
19499 .plan
19500 .shared
19501 .as_ref()
19502 .map(|s| s.intermediate_size as usize)
19503 .unwrap_or(0);
19504 let sffh = sff / 2;
19505
19506 if let (Some(ple), Some(ps)) = (layer.ple.as_ref(), lstate.ple.as_mut()) {
19508 let _g = e0.gpu.enter_main()?;
19509 self.ple_block(
19510 e0,
19511 layer,
19512 ple,
19513 &ple.table,
19514 ps,
19515 &mut planes0,
19516 tokens,
19517 t,
19518 false,
19519 None,
19520 )?;
19521 }
19522 if let (Some(ple1), Some(ps1)) = (tw.ple1.as_ref(), ts.ple1.as_mut()) {
19523 let table = &layer.ple.as_ref().expect("ple plan").table;
19524 let _g = e1.gpu.enter_main()?;
19525 self.ple_block(
19526 e1,
19527 layer,
19528 ple1,
19529 table,
19530 ps1,
19531 &mut planes1,
19532 tokens,
19533 t,
19534 false,
19535 None,
19536 )?;
19537 }
19538
19539 match (&layer.mixer, &tw.mixer0, &tw.mixer1) {
19541 (MixerW::Gdn(_), MixerHalfW::Gdn(h0), MixerHalfW::Gdn(h1)) => {
19542 {
19543 let _g = e1.gpu.enter_main()?;
19544 let (mixed1, inj1) = self.gate_read(
19545 e1,
19546 ws1,
19547 &ptrs1,
19548 &tw.attn_gate1,
19549 &planes1,
19550 t,
19551 eps_a,
19552 false,
19553 )?;
19554 let p1 =
19555 self.gdn_forward_half(e1, ws1, eps_a, h1, &mixed1, &mut ts.m1, t)?;
19556 ws1.put_f32("hc.mixed", mixed1);
19557 launch_push(e1, &p1, pf_stage0_raw[0], t * hidden)?;
19558 ws1.put_f32("mixer.out", p1);
19559 put_inject(ws1, inj1);
19560 shard.ev1[0].record(&e1.gpu.stream())?;
19561 }
19562 {
19563 let _g = e0.gpu.enter_main()?;
19564 let (mixed0, inj0) = self.gate_read(
19565 e0,
19566 ws0,
19567 &ptrs0,
19568 &layer.attn_gate,
19569 &planes0,
19570 t,
19571 eps_a,
19572 false,
19573 )?;
19574 let p0 =
19575 self.gdn_forward_half(e0, ws0, eps_a, h0, &mixed0, &mut ts.m0, t)?;
19576 ws0.put_f32("hc.mixed", mixed0);
19577 launch_push(e0, &p0, pf_stage1_raw[0], t * hidden)?;
19578 ws0.put_f32("mixer.out", p0);
19579 put_inject(ws0, inj0);
19580 shard.ev0[0].record(&e0.gpu.stream())?;
19581 }
19582 }
19583 (MixerW::Qsa(qsa), MixerHalfW::Qsa(h0), MixerHalfW::Qsa(h1)) => {
19584 let (q1, g1, inj1) = {
19585 let _g = e1.gpu.enter_main()?;
19586 let (mixed1, inj1) = self.gate_read(
19587 e1,
19588 ws1,
19589 &ptrs1,
19590 &tw.attn_gate1,
19591 &planes1,
19592 t,
19593 eps_a,
19594 false,
19595 )?;
19596 let (q1, g1) = self
19597 .qsa_half_proj(e1, ws1, eps_a, h1, &mixed1, &mut ts.m1, base_pos, t)?;
19598 ws1.put_f32("hc.mixed", mixed1);
19599 (q1, g1, inj1)
19600 };
19601 let (sels, q0, g0, inj0) = {
19602 let _g = e0.gpu.enter_main()?;
19603 let (mixed0, inj0) = self.gate_read(
19604 e0,
19605 ws0,
19606 &ptrs0,
19607 &layer.attn_gate,
19608 &planes0,
19609 t,
19610 eps_a,
19611 false,
19612 )?;
19613 let (q0, g0) = self
19614 .qsa_half_proj(e0, ws0, eps_a, h0, &mixed0, &mut ts.m0, base_pos, t)?;
19615 let MixerState::Qsa {
19616 raw_keys,
19617 pooled_keys,
19618 pooled_dev,
19619 pooled_dev_rows,
19620 raw_dev,
19621 raw_dev_rows,
19622 idx_audit,
19623 ..
19624 } = &mut lstate.mixer
19625 else {
19626 return Err("qwen4exp_gpu tp2: QSA layer without raw-key cache".into());
19627 };
19628 let sels = self.qsa_update_select(
19629 e0,
19630 ws0,
19631 qsa,
19632 eps_a,
19633 &mixed0,
19634 raw_keys,
19635 pooled_keys,
19636 pooled_dev,
19637 pooled_dev_rows,
19638 raw_dev,
19639 raw_dev_rows,
19640 idx_audit.as_mut(),
19641 base_pos,
19642 t,
19643 0,
19644 false,
19645 )?;
19646 ws0.put_f32("hc.mixed", mixed0);
19647 (sels, q0, g0, inj0)
19648 };
19649 let t_kv = base_pos + t;
19650 let block_size = qsa.overlay.block_size as usize;
19651 let (pos_flat, meta, max_count) = rowsel_positions(&sels, block_size);
19652 {
19653 let _g = e1.gpu.enter_main()?;
19654 let pos_dev = ws1.take_i32(e1, "qsa.selpos", &pos_flat, 0)?;
19655 let meta_dev = ws1.take_i32(e1, "qsa.selmeta", &meta, 0)?;
19656 let p1 = self.qsa_half_attend(
19657 e1, ws1, h1, &ts.m1, q1, g1, &pos_dev, &meta_dev, max_count, t, t_kv,
19658 )?;
19659 ws1.put_i32("qsa.selpos", pos_dev);
19660 ws1.put_i32("qsa.selmeta", meta_dev);
19661 launch_push(e1, &p1, pf_stage0_raw[0], t * hidden)?;
19662 ws1.put_f32("mixer.out", p1);
19663 put_inject(ws1, inj1);
19664 shard.ev1[0].record(&e1.gpu.stream())?;
19665 }
19666 {
19667 let _g = e0.gpu.enter_main()?;
19668 let pos_dev = ws0.take_i32(e0, "qsa.selpos", &pos_flat, 0)?;
19669 let meta_dev = ws0.take_i32(e0, "qsa.selmeta", &meta, 0)?;
19670 let p0 = self.qsa_half_attend(
19671 e0, ws0, h0, &ts.m0, q0, g0, &pos_dev, &meta_dev, max_count, t, t_kv,
19672 )?;
19673 ws0.put_i32("qsa.selpos", pos_dev);
19674 ws0.put_i32("qsa.selmeta", meta_dev);
19675 launch_push(e0, &p0, pf_stage1_raw[0], t * hidden)?;
19676 ws0.put_f32("mixer.out", p0);
19677 put_inject(ws0, inj0);
19678 shard.ev0[0].record(&e0.gpu.stream())?;
19679 }
19680 }
19681 _ => return Err("qwen4exp_gpu tp2: mixer/shard shape mismatch".into()),
19682 }
19683 {
19684 let _g = e0.gpu.enter_main()?;
19685 e0.gpu.stream().wait(&shard.ev1[0])?;
19686 }
19687 {
19688 let _g = e1.gpu.enter_main()?;
19689 e1.gpu.stream().wait(&shard.ev0[0])?;
19690 }
19691
19692 let join_write = |e: &Engine,
19694 ws: &mut StepPool,
19695 ptrs: &CudaSlice<u64>,
19696 planes: &mut [CudaSlice<f32>],
19697 stage: &CudaSlice<f32>,
19698 rank0: bool|
19699 -> Res<()> {
19700 let p = ws.take_f32(e, "mixer.out", t * hidden, 0)?;
19701 let mut out = ws.take_f32(e, "join.out", t * hidden, 0)?;
19702 if rank0 {
19703 e.add(&p, stage, &mut out, t * hidden)?;
19704 } else {
19705 e.add(stage, &p, &mut out, t * hidden)?;
19706 }
19707 let inj = take_inject(e, ws, self.streams, t)?;
19708 self.gate_write(e, planes, ptrs, &out, &inj, t)?;
19709 ws.put_f32("mixer.out", p);
19710 ws.put_f32("join.out", out);
19711 put_inject(ws, inj);
19712 Ok(())
19713 };
19714 {
19715 let _g = e1.gpu.enter_main()?;
19716 join_write(e1, ws1, &ptrs1, &mut planes1, &pf_stage1[0], false)?;
19717 }
19718 {
19719 let _g = e0.gpu.enter_main()?;
19720 join_write(e0, ws0, &ptrs0, &mut planes0, &pf_stage0[0], true)?;
19721 }
19722
19723 let mixed1 = {
19725 let _g = e1.gpu.enter_main()?;
19726 let (mixed1, injm1) =
19727 self.gate_read(e1, ws1, &ptrs1, &tw.mlp_gate1, &planes1, t, eps_m, false)?;
19728 put_inject(ws1, injm1);
19729 mixed1
19730 };
19731 let mixed0 = {
19732 let _g = e0.gpu.enter_main()?;
19733 let (mixed0, injm0) =
19734 self.gate_read(e0, ws0, &ptrs0, &layer.mlp_gate, &planes0, t, eps_m, false)?;
19735 put_inject(ws0, injm0);
19736 mixed0
19737 };
19738 let routes: Vec<Vec<(usize, f32)>> = {
19741 let _g = e0.gpu.enter_main()?;
19742 let mut router_out = ws0.take_f32(e0, "moe.router", t * experts, 0)?;
19743 let none: Option<CudaSlice<u8>> = None;
19744 let rb = if router_bf16_on() {
19745 &moe.router_b16
19746 } else {
19747 &none
19748 };
19749 linear_trunk_into(
19750 e0,
19751 &moe.router,
19752 rb,
19753 &mixed0,
19754 &mut router_out,
19755 t,
19756 hidden,
19757 experts,
19758 )?;
19759 let logits = e0.dtoh_view(&router_out.slice(0..t * experts))?;
19760 ws0.put_f32("moe.router", router_out);
19761 let mut routes = Vec::with_capacity(t);
19762 for token in 0..t {
19763 routes.push(host_route_softmax_topk(
19764 &logits[token * experts..(token + 1) * experts],
19765 selected,
19766 ));
19767 }
19768 routes
19769 };
19770 let place = &tw.place;
19774 let split_half = |home: bool| -> Vec<Vec<(usize, f32)>> {
19775 routes
19776 .iter()
19777 .map(|r| {
19778 r.iter()
19779 .filter(|&&(eid, _)| (place.rank(eid) == 0) == home)
19780 .map(|&(eid, w)| (place.local(eid), w))
19781 .collect()
19782 })
19783 .collect()
19784 };
19785 let mut routes0 = split_half(true);
19786 let mut routes1 = split_half(false);
19787 tp2_count_split(&routes0, &routes1);
19790 trace_moe_routes(layer.index, t, &routes);
19791 match tp2_gate_red()? {
19792 Tp2GateRed::None => {}
19793 Tp2GateRed::SkipPeerMoe => routes1.iter_mut().for_each(|r| r.clear()),
19794 Tp2GateRed::PeerLocalIds => {
19795 for (r0, r1) in routes0.iter_mut().zip(routes1.iter_mut()) {
19796 r0.append(r1);
19797 }
19798 }
19799 Tp2GateRed::ReverseePeerWeights => {
19800 for r in routes1.iter_mut() {
19801 let n = r.len();
19802 for i in 0..n / 2 {
19803 let (a, b) = (r[i].1, r[n - 1 - i].1);
19804 r[i].1 = b;
19805 r[n - 1 - i].1 = a;
19806 }
19807 }
19808 }
19809 }
19810 let (routes0, routes1) = (routes0, routes1);
19811 {
19813 let _g = e1.gpu.enter_main()?;
19814 let mut out1 = self.tp2_moe_rows(
19815 e1,
19816 ws1,
19817 (
19818 &tw.moe.gate1.codes,
19819 &tw.moe.gate1.scales,
19820 &tw.moe.gate1.macros_dev,
19821 ),
19822 (
19823 &tw.moe.up1.codes,
19824 &tw.moe.up1.scales,
19825 &tw.moe.up1.macros_dev,
19826 ),
19827 (
19828 &tw.moe.down1.codes,
19829 &tw.moe.down1.scales,
19830 &tw.moe.down1.macros_dev,
19831 ),
19832 &routes1,
19833 &mixed1,
19834 t,
19835 ff,
19836 )?;
19837 let (sh, g) = self.tp2_shared_half(
19838 e1,
19839 ws1,
19840 &tw.moe.shared_gu1_b16,
19841 &tw.moe.shared_down1,
19842 tw.moe.shared_input_gate1.as_ref(),
19843 &mixed1,
19844 sffh,
19845 t,
19846 )?;
19847 match g.as_ref() {
19848 Some(g) => e1.add_scaled_rows(&sh, g, &mut out1, hidden, t)?,
19849 None => {
19850 let mut summed = ws1.take_f32(e1, "moe.sum", t * hidden, 0)?;
19851 e1.add(&out1, &sh, &mut summed, t * hidden)?;
19852 ws1.put_f32("moe.out", out1);
19853 out1 = summed;
19854 }
19855 }
19856 ws1.put_f32("moe.sh_down", sh);
19857 if let Some(g) = g {
19858 ws1.put_f32("moe.g", g);
19859 }
19860 launch_push(e1, &out1, pf_stage0_raw[1], t * hidden)?;
19861 ws1.put_f32("moe.out", out1);
19862 ws1.put_f32("hc.mixed", mixed1);
19863 shard.ev1[1].record(&e1.gpu.stream())?;
19864 }
19865 {
19868 let _g = e0.gpu.enter_main()?;
19869 let (
19870 BankHalf::Nvfp4 {
19871 codes: gc,
19872 scales: gs,
19873 macros_dev: gm,
19874 ..
19875 },
19876 BankHalf::Nvfp4 {
19877 codes: uc,
19878 scales: us,
19879 macros_dev: um,
19880 ..
19881 },
19882 BankHalf::Nvfp4 {
19883 codes: dc,
19884 scales: ds,
19885 macros_dev: dm,
19886 ..
19887 },
19888 ) = (&moe.bank.gate, &moe.bank.up, &moe.bank.down)
19889 else {
19890 return Err("qwen4exp_gpu tp2: card0 bank is not NVFP4".into());
19891 };
19892 let mut out0 = self.tp2_moe_rows(
19893 e0,
19894 ws0,
19895 (gc, gs, gm),
19896 (uc, us, um),
19897 (dc, ds, dm),
19898 &routes0,
19899 &mixed0,
19900 t,
19901 ff,
19902 )?;
19903 let (sh, g) = self.tp2_shared_half(
19904 e0,
19905 ws0,
19906 &tw.moe.shared_gu0_b16,
19907 &tw.moe.shared_down0,
19908 moe.shared_input_gate.as_ref(),
19909 &mixed0,
19910 sffh,
19911 t,
19912 )?;
19913 match g.as_ref() {
19914 Some(g) => e0.add_scaled_rows(&sh, g, &mut out0, hidden, t)?,
19915 None => {
19916 let mut summed = ws0.take_f32(e0, "moe.sum", t * hidden, 0)?;
19917 e0.add(&out0, &sh, &mut summed, t * hidden)?;
19918 ws0.put_f32("moe.out", out0);
19919 out0 = summed;
19920 }
19921 }
19922 ws0.put_f32("moe.sh_down", sh);
19923 if let Some(g) = g {
19924 ws0.put_f32("moe.g", g);
19925 }
19926 launch_push(e0, &out0, pf_stage1_raw[1], t * hidden)?;
19927 ws0.put_f32("moe.out", out0);
19928 ws0.put_f32("hc.mixed", mixed0);
19929 shard.ev0[1].record(&e0.gpu.stream())?;
19930 }
19931 {
19932 let _g = e1.gpu.enter_main()?;
19933 e1.gpu.stream().wait(&shard.ev0[1])?;
19934 let p = ws1.take_f32(e1, "moe.out", t * hidden, 0)?;
19935 let mut out = ws1.take_f32(e1, "join.out", t * hidden, 0)?;
19936 e1.add(&pf_stage1[1], &p, &mut out, t * hidden)?;
19937 let inj = take_inject(e1, ws1, self.streams, t)?;
19938 self.gate_write(e1, &mut planes1, &ptrs1, &out, &inj, t)?;
19939 ws1.put_f32("moe.out", p);
19940 ws1.put_f32("join.out", out);
19941 put_inject(ws1, inj);
19942 }
19943 {
19944 let _g = e0.gpu.enter_main()?;
19945 e0.gpu.stream().wait(&shard.ev1[1])?;
19946 let p = ws0.take_f32(e0, "moe.out", t * hidden, 0)?;
19947 let mut out = ws0.take_f32(e0, "join.out", t * hidden, 0)?;
19948 e0.add(&p, &pf_stage0[1], &mut out, t * hidden)?;
19949 let inj = take_inject(e0, ws0, self.streams, t)?;
19950 self.gate_write(e0, &mut planes0, &ptrs0, &out, &inj, t)?;
19951 ws0.put_f32("moe.out", p);
19952 ws0.put_f32("join.out", out);
19953 put_inject(ws0, inj);
19954 }
19955 }
19956
19957 let head_rows = match head {
19974 HeadMode::All => t,
19975 _ => 1,
19976 };
19977 let mut out = vec![
19978 0.0f32;
19979 if head == HeadMode::Skip {
19980 0
19981 } else {
19982 head_rows * vocab
19983 }
19984 ];
19985 if head != HeadMode::Skip {
19986 {
19987 let _g = e1.gpu.enter_main()?;
19988 let mut exit_planes: Vec<CudaSlice<f32>> = Vec::with_capacity(self.streams);
19992 if head != HeadMode::All {
19993 for (s, plane) in planes1.iter().enumerate() {
19994 let mut row = ws1.take_f32(e1, EXIT_PLANE_SLOTS[s], hidden, hidden)?;
19995 e1.copy_range_into(&mut row, 0, plane, (t - 1) * hidden, hidden)?;
19996 exit_planes.push(row);
19997 }
19998 }
19999 let use_planes: &[CudaSlice<f32>] = if head == HeadMode::All {
20000 &planes1
20001 } else {
20002 &exit_planes
20003 };
20004 let ptr_vals: Vec<u64> = {
20005 let stream = e1.gpu.stream();
20006 use_planes.iter().map(|p| p.device_ptr(&stream).0).collect()
20007 };
20008 let eptrs = ws1.take_u64_h2d(e1, "exit.ptrs", &ptr_vals, 0)?;
20009 self.tp2_seg_exit(
20010 e1,
20011 ws1,
20012 &eptrs,
20013 &shard.exit_gate1,
20014 use_planes,
20015 &shard.lm_head1,
20016 vocab - vsplit,
20017 head_rows,
20018 )?;
20019 ws1.put_u64("exit.ptrs", eptrs);
20020 for (s, p) in exit_planes.into_iter().enumerate() {
20021 ws1.put_f32(EXIT_PLANE_SLOTS[s], p);
20022 }
20023 let logits1 = ws1.peek_f32("logits")?;
20024 let half1 = vocab - vsplit;
20025 let host1 = e1.dtoh_view(&logits1.slice(0..head_rows * half1))?;
20026 for r in 0..head_rows {
20029 out[r * vocab + vsplit..(r + 1) * vocab]
20030 .copy_from_slice(&host1[r * half1..(r + 1) * half1]);
20031 }
20032 }
20033 {
20034 let _g = e0.gpu.enter_main()?;
20035 let head0 = self
20036 .output_b16
20037 .as_ref()
20038 .ok_or("qwen4exp_gpu tp2: lm_head has no bf16 twin")?;
20039 let mut exit_planes: Vec<CudaSlice<f32>> = Vec::with_capacity(self.streams);
20040 if head != HeadMode::All {
20041 for (s, plane) in planes0.iter().enumerate() {
20042 let mut row = ws0.take_f32(e0, EXIT_PLANE_SLOTS[s], hidden, hidden)?;
20043 e0.copy_range_into(&mut row, 0, plane, (t - 1) * hidden, hidden)?;
20044 exit_planes.push(row);
20045 }
20046 }
20047 let use_planes: &[CudaSlice<f32>] = if head == HeadMode::All {
20048 &planes0
20049 } else {
20050 &exit_planes
20051 };
20052 let ptr_vals: Vec<u64> = {
20053 let stream = e0.gpu.stream();
20054 use_planes.iter().map(|p| p.device_ptr(&stream).0).collect()
20055 };
20056 let eptrs = ws0.take_u64_h2d(e0, "exit.ptrs", &ptr_vals, 0)?;
20057 self.tp2_seg_exit(
20058 e0,
20059 ws0,
20060 &eptrs,
20061 &self.exit_mixer,
20062 use_planes,
20063 head0,
20064 vsplit,
20065 head_rows,
20066 )?;
20067 ws0.put_u64("exit.ptrs", eptrs);
20068 for (s, p) in exit_planes.into_iter().enumerate() {
20069 ws0.put_f32(EXIT_PLANE_SLOTS[s], p);
20070 }
20071 let logits0 = ws0.peek_f32("logits")?;
20072 let host0 = e0.dtoh_view(&logits0.slice(0..head_rows * vsplit))?;
20073 for r in 0..head_rows {
20075 out[r * vocab..r * vocab + vsplit]
20076 .copy_from_slice(&host0[r * vsplit..(r + 1) * vsplit]);
20077 }
20078 }
20079 } else {
20080 {
20083 let _g = e0.gpu.enter_main()?;
20084 e0.gpu.stream().synchronize()?;
20085 }
20086 {
20087 let _g = e1.gpu.enter_main()?;
20088 e1.gpu.stream().synchronize()?;
20089 }
20090 }
20091 for (s, plane) in planes0.into_iter().enumerate() {
20092 ws0.put_f32(PLANE_SLOTS[s], plane);
20093 }
20094 for (s, plane) in planes1.into_iter().enumerate() {
20095 ws1.put_f32(PLANE_SLOTS[s], plane);
20096 }
20097 ws0.put_u64("hc.ptrs", ptrs0);
20098 ws1.put_u64("hc.ptrs", ptrs1);
20099 state.pos += t;
20100 Ok(out)
20101 }
20102
20103 #[allow(clippy::too_many_arguments)]
20109 fn tp2_moe_rows(
20110 &self,
20111 e: &Engine,
20112 ws: &mut StepPool,
20113 gate: (&CudaSlice<u8>, &CudaSlice<u8>, &CudaSlice<f32>),
20114 up: (&CudaSlice<u8>, &CudaSlice<u8>, &CudaSlice<f32>),
20115 down: (&CudaSlice<u8>, &CudaSlice<u8>, &CudaSlice<f32>),
20116 routes: &[Vec<(usize, f32)>],
20117 mixed: &CudaSlice<f32>,
20118 t: usize,
20119 ff: usize,
20120 ) -> Res<CudaSlice<f32>> {
20121 let hidden = self.hidden;
20122 if !(sel_gufuse_on() && hidden % 32 == 0 && ff % 4 == 0) {
20123 return Err(
20124 "qwen4exp_gpu tp2: prefill MoE needs the gufuse geometry (hidden%32, ff%4)".into(),
20125 );
20126 }
20127 let mut out = ws.take_f32(e, "moe.out", t * hidden, 0)?;
20128 {
20129 let mut view = out.slice_mut(0..t * hidden);
20130 e.memset_zeros_view(&mut view)?;
20131 }
20132 const SLOT_CAP: usize = 8192;
20133 let mut tok0 = 0usize;
20134 while tok0 < t {
20135 let mut tok_n = 0usize;
20137 let mut slots = 0usize;
20138 while tok0 + tok_n < t {
20139 let n = routes[tok0 + tok_n].len();
20140 if tok_n > 0 && slots + n > SLOT_CAP {
20141 break;
20142 }
20143 slots += n;
20144 tok_n += 1;
20145 }
20146 let batch = &routes[tok0..tok0 + tok_n];
20147 let mut sel_all: Vec<i32> = Vec::with_capacity(slots);
20148 let mut w_all: Vec<f32> = Vec::with_capacity(slots);
20149 let mut tok_all: Vec<i32> = Vec::with_capacity(slots);
20150 let mut ranges: Vec<(usize, usize)> = Vec::with_capacity(tok_n);
20151 for (i, route) in batch.iter().enumerate() {
20152 ranges.push((sel_all.len(), route.len()));
20153 for &(eid, wgt) in route {
20154 sel_all.push(eid as i32);
20155 w_all.push(wgt);
20156 tok_all.push((tok0 + i) as i32);
20157 }
20158 }
20159 let s_total = sel_all.len();
20160 if s_total > 0 {
20161 let sel = ws.take_i32(e, "moe.sel", &sel_all, 0)?;
20162 let w_dev = ws.take_f32_h2d(e, "moe.w", &w_all, 0)?;
20163 let tokm = ws.take_i32(e, "moe.tok", &tok_all, 0)?;
20164 let mut act = ws.take_f32(e, "moe.act", s_total * ff, 0)?;
20165 launch_nvfp4_sel_gu_silu(
20166 e,
20167 gate,
20168 up,
20169 Some(&sel),
20170 0,
20171 s_total,
20172 mixed,
20173 &mut act,
20174 hidden,
20175 ff,
20176 Some((&tokm, hidden)),
20177 )?;
20178 let mut partial = ws.take_f32(e, "moe.partial", s_total * hidden, 0)?;
20179 launch_nvfp4_sel_matvec(
20180 e,
20181 down.0,
20182 down.1,
20183 down.2,
20184 &sel,
20185 &act,
20186 &mut partial,
20187 s_total,
20188 ff,
20189 hidden,
20190 ff,
20191 )?;
20192 for (i, &(start, len)) in ranges.iter().enumerate() {
20193 if len > 0 {
20194 launch_axpy_rows_seq_at(
20195 e,
20196 &partial,
20197 start,
20198 &w_dev,
20199 start,
20200 &mut out,
20201 tok0 + i,
20202 hidden,
20203 len,
20204 )?;
20205 }
20206 }
20207 ws.put_i32("moe.sel", sel);
20208 ws.put_i32("moe.tok", tokm);
20209 ws.put_f32("moe.w", w_dev);
20210 ws.put_f32("moe.act", act);
20211 ws.put_f32("moe.partial", partial);
20212 }
20213 tok0 += tok_n;
20214 }
20215 Ok(out)
20216 }
20217
20218 #[allow(clippy::too_many_arguments)]
20221 fn tp2_gdn_seg_a(
20222 &self,
20223 e: &Engine,
20224 ws: &mut StepPool,
20225 ptrs: &CudaSlice<u64>,
20226 attn_gate: &GateW,
20227 h: &GdnHalfW,
20228 hstate: &mut MixerHalfState,
20229 planes: &[CudaSlice<f32>],
20230 eps: f32,
20231 push_raw: u64,
20232 ) -> Res<()> {
20233 let (mixed, inj) = self.gate_read(e, ws, ptrs, attn_gate, planes, 1, eps, false)?;
20234 let p = self.gdn_forward_half(e, ws, eps, h, &mixed, hstate, 1)?;
20235 ws.put_f32("hc.mixed", mixed);
20236 launch_push(e, &p, push_raw, self.hidden)?;
20237 ws.put_f32("mixer.out", p);
20238 put_inject(ws, inj);
20239 Ok(())
20240 }
20241
20242 #[allow(clippy::too_many_arguments)]
20247 fn tp2_seg_b(
20248 &self,
20249 e: &Engine,
20250 ws: &mut StepPool,
20251 ptrs: &CudaSlice<u64>,
20252 mlp_gate: &GateW,
20253 planes: &mut [CudaSlice<f32>],
20254 stage_recv: &CudaSlice<f32>,
20255 rank0: bool,
20256 eps_m: f32,
20257 shared: Option<(
20258 &CudaSlice<u8>,
20259 &CudaSlice<u8>,
20260 Option<&CudaSlice<f32>>,
20261 usize,
20262 )>,
20263 ) -> Res<()> {
20264 let hidden = self.hidden;
20265 let p = ws.take_f32(e, "mixer.out", hidden, 0)?;
20266 let mut out = ws.take_f32(e, "join.out", hidden, 0)?;
20267 if rank0 {
20268 e.add(&p, stage_recv, &mut out, hidden)?;
20269 } else {
20270 e.add(stage_recv, &p, &mut out, hidden)?;
20271 }
20272 let inj = take_inject(e, ws, self.streams, 1)?;
20273 self.gate_write(e, planes, ptrs, &out, &inj, 1)?;
20274 ws.put_f32("mixer.out", p);
20275 ws.put_f32("join.out", out);
20276 put_inject(ws, inj);
20277 let (mixed, injm) = self.gate_read(e, ws, ptrs, mlp_gate, planes, 1, eps_m, false)?;
20278 if let Some((gu_b16, d_b16, ig, sffh)) = shared {
20279 let (sh, gg) = self.tp2_shared_half(e, ws, gu_b16, d_b16, ig, &mixed, sffh, 1)?;
20280 ws.put_f32("moe.sh_down", sh);
20284 if let Some(gg) = gg {
20285 ws.put_f32("moe.g", gg);
20286 }
20287 }
20288 ws.put_f32("hc.mixed", mixed);
20289 put_inject(ws, injm);
20290 Ok(())
20291 }
20292
20293 #[allow(clippy::too_many_arguments)]
20296 fn tp2_seg_exit(
20306 &self,
20307 e: &Engine,
20308 ws: &mut StepPool,
20309 ptrs: &CudaSlice<u64>,
20310 gate: &GateW,
20311 planes: &[CudaSlice<f32>],
20312 head_b16: &CudaSlice<u8>,
20313 out_f: usize,
20314 rows: usize,
20315 ) -> Res<()> {
20316 let x = self
20317 .gate_read_inner(e, ws, ptrs, gate, planes, rows, self.exit_eps, false, false)?
20318 .0;
20319 let mut logits = ws.take_f32(e, "logits", rows * out_f, rows * out_f)?;
20320 launch_qmatvec_bf16w(
20321 e,
20322 head_b16,
20323 &x,
20324 &mut logits,
20325 self.hidden,
20326 out_f,
20327 rows,
20328 1,
20329 0,
20330 0,
20331 self.hidden,
20332 0,
20333 )?;
20334 ws.put_f32("hc.mixed", x);
20335 ws.put_f32("logits", logits);
20336 Ok(())
20337 }
20338}
20339
20340#[allow(clippy::too_many_arguments)]
20344fn launch_nvfp4_sel_matvec_pack(
20345 e: &Engine,
20346 codes: &CudaSlice<u8>,
20347 scales: &CudaSlice<u8>,
20348 macros_dev: &CudaSlice<f32>,
20349 pack_raw: u64,
20350 max_sel: usize,
20351 x: &CudaSlice<f32>,
20352 y: &mut CudaSlice<f32>,
20353 in_f: usize,
20354 out_f: usize,
20355 x_stride: usize,
20356) -> Res<()> {
20357 if in_f % 32 != 0 || out_f % 4 != 0 {
20358 return Err(
20359 "qmatvec_nvfp4_modelopt_sel_f32_v3c: geometry needs in_f%32==0 && out_f%4==0".into(),
20360 );
20361 }
20362 let f = e.func("qmatvec_nvfp4_modelopt_sel_f32_v3c");
20363 let cfg = LaunchConfig {
20364 grid_dim: ((out_f / 4) as u32, max_sel as u32, 1),
20365 block_dim: (32, 1, 1),
20366 shared_mem_bytes: 0,
20367 };
20368 let (inf, outf, ms) = (in_f as i32, out_f as i32, max_sel as i32);
20369 let xs = x_stride as i64;
20370 let stream = e.gpu.stream();
20371 let mut b = stream.launch_builder(&f);
20372 b.arg(codes)
20373 .arg(scales)
20374 .arg(macros_dev)
20375 .arg(&pack_raw)
20376 .arg(&ms)
20377 .arg(x)
20378 .arg(y)
20379 .arg(&inf)
20380 .arg(&outf)
20381 .arg(&xs);
20382 unsafe {
20383 b.launch(cfg)?;
20384 }
20385 Ok(())
20386}
20387
20388fn launch_axpy_rows_seq_pack(
20389 e: &Engine,
20390 x: &CudaSlice<f32>,
20391 pack_raw: u64,
20392 max_sel: usize,
20393 y: &mut CudaSlice<f32>,
20394 width: usize,
20395) -> Res<()> {
20396 let f = e.func("axpy_rows_seq_pack_f32");
20397 let cfg = LaunchConfig::for_num_elems(width as u32);
20398 let (ms, wi) = (max_sel as i32, width as i32);
20399 let stream = e.gpu.stream();
20400 let mut b = stream.launch_builder(&f);
20401 b.arg(x).arg(&pack_raw).arg(&ms).arg(y).arg(&wi);
20402 unsafe {
20403 b.launch(cfg)?;
20404 }
20405 Ok(())
20406}
20407
20408fn tp2_pack_bytes(sel: &[i32], w: &[f32], max_sel: usize) -> Vec<u8> {
20410 let mut out = Vec::with_capacity((2 * max_sel + 1) * 4);
20411 for i in 0..max_sel {
20412 out.extend_from_slice(&sel.get(i).copied().unwrap_or(0).to_le_bytes());
20413 }
20414 for i in 0..max_sel {
20415 out.extend_from_slice(&w.get(i).copied().unwrap_or(0.0).to_le_bytes());
20416 }
20417 out.extend_from_slice(&(sel.len() as i32).to_le_bytes());
20418 out
20419}
20420
20421impl Qwen4ExpGpu {
20422 #[allow(clippy::too_many_arguments)]
20427 fn tp2_seg_c(
20428 &self,
20429 e: &Engine,
20430 ws: &mut StepPool,
20431 gate: (&CudaSlice<u8>, &CudaSlice<u8>, &CudaSlice<f32>),
20432 up: (&CudaSlice<u8>, &CudaSlice<u8>, &CudaSlice<f32>),
20433 down: (&CudaSlice<u8>, &CudaSlice<u8>, &CudaSlice<f32>),
20434 ff: usize,
20435 max_sel: usize,
20436 shared_compute: Option<(
20437 &CudaSlice<u8>,
20438 &CudaSlice<u8>,
20439 Option<&CudaSlice<f32>>,
20440 usize,
20441 )>,
20442 shared_gated: bool,
20443 push_raw: u64,
20444 ) -> Res<()> {
20445 let hidden = self.hidden;
20446 let pack_raw = {
20447 let pack = ws.peek_u8("moe.pack")?;
20448 let stream = e.gpu.stream();
20449 pack.device_ptr(&stream).0
20450 };
20451 let mixed = ws.take_f32(e, "hc.mixed", hidden, 0)?;
20452 let mut act = ws.take_f32(e, "moe.act", max_sel * ff, 0)?;
20453 if sel_gufuse_on() && hidden % 32 == 0 && ff % 4 == 0 {
20457 launch_nvfp4_sel_gu_silu(
20458 e, gate, up, None, pack_raw, max_sel, &mixed, &mut act, hidden, ff, None,
20459 )?;
20460 } else {
20461 let mut yg = ws.take_f32(e, "moe.yg", max_sel * ff, 0)?;
20462 let mut yu = ws.take_f32(e, "moe.yu", max_sel * ff, 0)?;
20463 launch_nvfp4_sel_matvec_pack(
20464 e, gate.0, gate.1, gate.2, pack_raw, max_sel, &mixed, &mut yg, hidden, ff, 0,
20465 )?;
20466 launch_nvfp4_sel_matvec_pack(
20467 e, up.0, up.1, up.2, pack_raw, max_sel, &mixed, &mut yu, hidden, ff, 0,
20468 )?;
20469 e.silu_mul(&yg, &yu, &mut act, max_sel * ff)?;
20470 ws.put_f32("moe.yg", yg);
20471 ws.put_f32("moe.yu", yu);
20472 }
20473 let mut partial = ws.take_f32(e, "moe.partial", max_sel * hidden, 0)?;
20474 launch_nvfp4_sel_matvec_pack(
20475 e,
20476 down.0,
20477 down.1,
20478 down.2,
20479 pack_raw,
20480 max_sel,
20481 &act,
20482 &mut partial,
20483 ff,
20484 hidden,
20485 ff,
20486 )?;
20487 let mut r = ws.take_f32(e, "moe.out", hidden, 0)?;
20488 launch_axpy_rows_seq_pack(e, &partial, pack_raw, max_sel, &mut r, hidden)?;
20489 ws.put_f32("moe.act", act);
20490 ws.put_f32("moe.partial", partial);
20491 let (sh, g) = match shared_compute {
20492 Some((gu_b16, d_b16, ig, sffh)) => {
20493 self.tp2_shared_half(e, ws, gu_b16, d_b16, ig, &mixed, sffh, 1)?
20494 }
20495 None => {
20496 let sh = ws.take_f32(e, "moe.sh_down", hidden, 0)?;
20497 let g = if shared_gated {
20498 Some(ws.take_f32(e, "moe.g", 1, 0)?)
20499 } else {
20500 None
20501 };
20502 (sh, g)
20503 }
20504 };
20505 match g.as_ref() {
20506 Some(g) => e.add_scaled_rows(&sh, g, &mut r, hidden, 1)?,
20507 None => {
20508 let mut view = r.slice_mut(0..hidden);
20509 e.axpy_into(&sh, 1.0, &mut view, hidden)?;
20510 }
20511 }
20512 ws.put_f32("moe.sh_down", sh);
20513 if let Some(g) = g {
20514 ws.put_f32("moe.g", g);
20515 }
20516 launch_push(e, &r, push_raw, hidden)?;
20517 ws.put_f32("moe.out", r);
20518 ws.put_f32("hc.mixed", mixed);
20519 Ok(())
20520 }
20521
20522 #[allow(clippy::too_many_arguments)]
20524 fn tp2_seg_d(
20525 &self,
20526 e: &Engine,
20527 ws: &mut StepPool,
20528 ptrs: &CudaSlice<u64>,
20529 planes: &mut [CudaSlice<f32>],
20530 stage_recv: &CudaSlice<f32>,
20531 rank0: bool,
20532 ) -> Res<()> {
20533 let hidden = self.hidden;
20534 let mp = ws.take_f32(e, "moe.out", hidden, 0)?;
20535 let mut mo = ws.take_f32(e, "join.out", hidden, 0)?;
20536 if rank0 {
20537 e.add(&mp, stage_recv, &mut mo, hidden)?;
20538 } else {
20539 e.add(stage_recv, &mp, &mut mo, hidden)?;
20540 }
20541 let injm = take_inject(e, ws, self.streams, 1)?;
20542 self.gate_write(e, planes, ptrs, &mo, &injm, 1)?;
20543 ws.put_f32("moe.out", mp);
20544 ws.put_f32("join.out", mo);
20545 put_inject(ws, injm);
20546 Ok(())
20547 }
20548}
20549
20550#[cfg(test)]
20551mod sel_group_tests {
20552 use super::*;
20553
20554 static SEAM: std::sync::Mutex<()> = std::sync::Mutex::new(());
20558
20559 #[test]
20564 fn auto_resolves_the_measured_serving_shapes() {
20565 assert_eq!(sel_group_resolve(SEL_GROUP_AUTO, 640, 2560), Some((4, 4)));
20568 assert_eq!(sel_group_resolve(SEL_GROUP_AUTO, 2560, 640), Some((16, 4)));
20571 }
20572
20573 #[test]
20574 fn off_and_odd_in_f_take_the_shipped_kernel() {
20575 assert_eq!(sel_group_resolve(SEL_GROUP_OFF, 640, 2560), None);
20576 assert_eq!(sel_group_resolve(SEL_GROUP_AUTO, 48, 2560), None);
20578 }
20579
20580 #[test]
20584 fn auto_backs_off_rows_then_refuses_rather_than_tiling_raggedly() {
20585 assert_eq!(sel_group_resolve(SEL_GROUP_AUTO, 64, 32), Some((2, 2)));
20588 assert_eq!(sel_group_resolve(SEL_GROUP_AUTO, 64, 24), None);
20590 for &(in_f, out_f) in &[(640usize, 2560usize), (2560, 640), (32, 32), (64, 16)] {
20591 let (g, rows) = sel_group_resolve(SEL_GROUP_AUTO, in_f, out_f)
20592 .unwrap_or_else(|| panic!("auto refused {in_f}x{out_f}"));
20593 assert_eq!(
20594 out_f % ((32 / g) * rows),
20595 0,
20596 "{in_f}x{out_f} -> g{g} rows{rows}"
20597 );
20598 }
20599 }
20600
20601 #[test]
20605 fn explicit_pins_are_verbatim_and_still_tile_checked() {
20606 let _g = SEAM.lock().unwrap();
20607 assert!(set_sel_group("dn:8:1+gu:16:4"));
20608 assert_eq!(sel_group_resolve(sel_group_dn(), 640, 2560), Some((8, 1)));
20609 assert_eq!(sel_group_resolve(sel_group_gu(), 2560, 640), Some((16, 4)));
20610 assert!(set_sel_group("dn:1:4"));
20613 assert_eq!(sel_group_resolve(sel_group_dn(), 2560, 640), Some((1, 4)));
20614 assert_eq!(sel_group_resolve(sel_group_dn(), 2560, 96), None);
20615 set_sel_group("off");
20616 }
20617
20618 #[test]
20622 fn malformed_specs_apply_nothing_and_refuse() {
20623 let _g = SEAM.lock().unwrap();
20624 assert!(set_sel_group("dn:4:4+gu:16:4"));
20625 let before = sel_group_spec();
20626 for bad in [
20627 "dn:3:4", "dn:4:3", "dn:64:4", "dn:4", "xx:4:4", "dn:4:4+xx:1", "+",
20634 ] {
20635 assert!(!set_sel_group(bad), "{bad:?} was accepted");
20636 assert_eq!(
20637 sel_group_spec(),
20638 before,
20639 "{bad:?} mutated state while refusing"
20640 );
20641 }
20642 set_sel_group("off");
20643 }
20644
20645 #[test]
20649 fn seam_round_trips_through_the_boolean_harness() {
20650 let _g = SEAM.lock().unwrap();
20651 set_sel_group("off");
20652 assert_eq!(seam_state("selgroup"), Some(false));
20653 assert!(set_seam("selgroup", true, None));
20654 assert_eq!(seam_state("selgroup"), Some(true));
20655 assert_eq!(sel_group_spec(), "dn:auto+gu:auto");
20656 assert!(set_seam("selgroup", false, None));
20657 assert_eq!(seam_state("selgroup"), Some(false));
20658 assert_eq!(sel_group_spec(), "dn:off+gu:off");
20659 assert!(set_sel_group("dn:4:4+gu:off"));
20662 assert_eq!(seam_state("selgroup"), Some(true));
20663 set_sel_group("off");
20664 assert!(seam_names().contains(&"selgroup"));
20666 assert!(seam_exists("selgroup"));
20667 }
20668}
20669
20670#[cfg(test)]
20682mod tp2_placement_tests {
20683 use super::{LayerPlacement, Tp2Placement};
20684
20685 fn write_map(name: &str, body: &str) -> std::path::PathBuf {
20689 let path = std::env::temp_dir().join(format!(
20690 "memra-q4e-ep-map-{name}-{}.json",
20691 std::process::id()
20692 ));
20693 std::fs::write(&path, body).expect("write map fixture");
20694 path
20695 }
20696
20697 fn load(name: &str, body: &str, expert_count: usize) -> Result<Tp2Placement, String> {
20698 let path = write_map(name, body);
20699 let out = Tp2Placement::load(&path, expert_count).map_err(|e| e.to_string());
20700 let _ = std::fs::remove_file(&path);
20701 out
20702 }
20703
20704 fn doc(body: &str) -> String {
20706 format!(
20707 "{{\"format\": \"memra-ep-map-v1\", \"strategy\": \"coactivation\", \
20708 \"ranks\": 2, \"entry_rank\": 0, \"expert_count\": 4, {body}}}"
20709 )
20710 }
20711
20712 fn assert_refuses(name: &str, body: &str, expert_count: usize, clause: &str) {
20713 match load(name, body, expert_count) {
20714 Ok(_) => panic!("{name}: expected a refusal naming {clause:?}, but the map loaded"),
20715 Err(msg) => {
20716 assert!(
20717 msg.contains(clause),
20718 "{name}: refusal must name the broken clause {clause:?}, got: {msg}"
20719 );
20720 assert!(
20723 msg.contains("MEMRA_Q4E_EP_MAP"),
20724 "{name}: refusal must name the flag/file, got: {msg}"
20725 );
20726 }
20727 }
20728 }
20729
20730 #[test]
20738 fn even_split_is_the_contiguous_suffix_control_arm() {
20739 let p = Tp2Placement::even(512);
20740 assert_eq!(p.strategy(), "even");
20741 assert_eq!(p.entry_rank(), 0);
20742 assert!(p.source().contains("MEMRA_Q4E_EP_MAP unset"));
20743
20744 let l = p.layer(0, 512).expect("even split resolves every layer");
20745 assert!(l.is_even(), "the built-in even split must report as even");
20746 assert_eq!(l.card1.len(), 256);
20747 assert_eq!(l.card1, (256u32..512).collect::<Vec<_>>());
20749 for e in 0..256 {
20750 assert_eq!(l.rank(e), 0, "expert {e} belongs to card 0");
20751 assert_eq!(l.local(e), e, "card-0 local slot IS the global id");
20752 }
20753 for (slot, e) in (256..512).enumerate() {
20754 assert_eq!(l.rank(e), 1, "expert {e} belongs to card 1");
20755 assert_eq!(l.local(e), slot, "card-1 local slot is the gather position");
20756 }
20757 let l47 = p.layer(47, 512).expect("layer 47");
20759 assert_eq!(l47.card1, l.card1);
20760 }
20761
20762 #[test]
20766 fn measured_map_resolves_ascending_gather_and_local_slots() {
20767 let body = "\"layers\": [{\"layer\": 0, \"assignment\": [1, 0, 0, 1]}]";
20769 let p = load("measured", &doc(body), 4).expect("balanced map loads");
20770 assert_eq!(p.strategy(), "coactivation");
20771 let l = p.layer(0, 4).expect("layer 0");
20772
20773 assert!(
20774 !l.is_even(),
20775 "a non-suffix placement is not the control arm"
20776 );
20777 assert_eq!(l.card1, vec![0u32, 3]);
20780 assert_eq!((l.rank(0), l.rank(1), l.rank(2), l.rank(3)), (1, 0, 0, 1));
20781 assert_eq!(l.local(0), 0);
20783 assert_eq!(l.local(3), 1);
20784 assert_eq!(l.local(1), 1);
20786 assert_eq!(l.local(2), 2);
20787 }
20788
20789 #[test]
20793 fn is_even_rejects_a_balanced_permutation() {
20794 let body = "\"layers\": [{\"layer\": 0, \"assignment\": [0, 1, 1, 0]}]";
20795 let p = load("perm", &doc(body), 4).expect("balanced map loads");
20796 let l = p.layer(0, 4).expect("layer 0");
20797 assert_eq!(l.card1, vec![1u32, 2]);
20798 assert!(!l.is_even());
20799 }
20800
20801 #[test]
20804 fn an_explicit_even_map_matches_the_builtin_even_split() {
20805 let body = "\"layers\": [{\"layer\": 0, \"assignment\": [0, 0, 1, 1]}]";
20806 let p = load("explicit-even", &doc(body), 4).expect("even map loads");
20807 let l = p.layer(0, 4).expect("layer 0");
20808 let builtin = Tp2Placement::even(4).layer(0, 4).expect("builtin");
20809 assert!(l.is_even());
20810 assert_eq!(l.card1, builtin.card1);
20811 for e in 0..4 {
20812 assert_eq!(l.rank(e), builtin.rank(e), "rank of expert {e}");
20813 assert_eq!(l.local(e), builtin.local(e), "local slot of expert {e}");
20814 }
20815 }
20816
20817 #[test]
20824 fn refuses_a_foreign_format() {
20825 let body = "\"layers\": [{\"layer\": 0, \"assignment\": [0, 0, 1, 1]}]";
20826 let text = format!(
20827 "{{\"format\": \"memra-ep-map-v2\", \"ranks\": 2, \"expert_count\": 4, {body}}}"
20828 );
20829 assert_refuses("format", &text, 4, "memra-ep-map-v1");
20830 }
20831
20832 #[test]
20833 fn refuses_a_rank_count_that_is_not_two() {
20834 let text = "{\"format\": \"memra-ep-map-v1\", \"ranks\": 4, \"expert_count\": 4, \
20835 \"layers\": [{\"layer\": 0, \"assignment\": [0, 0, 1, 1]}]}";
20836 assert_refuses("ranks", text, 4, "exactly two cards");
20837 }
20838
20839 #[test]
20840 fn refuses_an_expert_count_that_is_not_the_plans() {
20841 let body = "\"layers\": [{\"layer\": 0, \"assignment\": [0, 0, 1, 1]}]";
20842 assert_refuses("experts", &doc(body), 8, "expert_count=4");
20843 }
20844
20845 #[test]
20846 fn refuses_an_entry_rank_outside_the_two_cards() {
20847 let text = "{\"format\": \"memra-ep-map-v1\", \"ranks\": 2, \"entry_rank\": 2, \
20848 \"expert_count\": 4, \
20849 \"layers\": [{\"layer\": 0, \"assignment\": [0, 0, 1, 1]}]}";
20850 assert_refuses("entry", text, 4, "entry_rank=2");
20851 }
20852
20853 #[test]
20854 fn refuses_a_document_with_no_layers_array() {
20855 assert_refuses("nolayers", &doc("\"strategy2\": 0"), 4, "no `layers` array");
20856 }
20857
20858 #[test]
20859 fn refuses_an_empty_layers_array() {
20860 assert_refuses(
20861 "emptylayers",
20862 &doc("\"layers\": []"),
20863 4,
20864 "`layers` is empty",
20865 );
20866 }
20867
20868 #[test]
20869 fn refuses_an_assignment_of_the_wrong_length() {
20870 let body = "\"layers\": [{\"layer\": 0, \"assignment\": [0, 1]}]";
20871 assert_refuses("shortassign", &doc(body), 4, "expected 4");
20872 }
20873
20874 #[test]
20875 fn refuses_a_rank_id_outside_the_two_cards() {
20876 let body = "\"layers\": [{\"layer\": 0, \"assignment\": [0, 0, 1, 7]}]";
20877 assert_refuses("badrank", &doc(body), 4, "expert 3");
20878 }
20879
20880 #[test]
20884 fn refuses_an_unbalanced_layer_and_names_the_rebalance_knob() {
20885 let body = "\"layers\": [{\"layer\": 0, \"assignment\": [0, 1, 1, 1]}]";
20886 assert_refuses("unbalanced", &doc(body), 4, "card 1 owns 3 experts");
20887 let body = "\"layers\": [{\"layer\": 0, \"assignment\": [0, 1, 1, 1]}]";
20888 assert_refuses("unbalanced2", &doc(body), 4, "--balance-tolerance");
20889 }
20890
20891 #[test]
20894 fn refuses_a_layer_the_map_does_not_cover() {
20895 let body = "\"layers\": [{\"layer\": 0, \"assignment\": [0, 0, 1, 1]}]";
20896 let p = load("partial", &doc(body), 4).expect("map loads");
20897 assert!(p.layer(0, 4).is_ok(), "the covered layer resolves");
20898 let msg = p
20899 .layer(1, 4)
20900 .expect_err("an uncovered MoE layer must refuse")
20901 .to_string();
20902 assert!(msg.contains("does not cover MoE layer 1"), "got: {msg}");
20903 assert!(
20904 msg.contains("partly-applied map is not a placement"),
20905 "the refusal must say WHY it is fail-closed, got: {msg}"
20906 );
20907 }
20908
20909 #[test]
20910 fn refuses_a_layer_whose_expert_count_disagrees_with_the_map() {
20911 let p = Tp2Placement::even(512);
20912 let msg = p
20913 .layer(0, 256)
20914 .expect_err("a layer geometry the map is not for must refuse")
20915 .to_string();
20916 assert!(msg.contains("map is for 512"), "got: {msg}");
20917 }
20918
20919 #[test]
20920 fn refuses_an_unreadable_map_path() {
20921 let missing = std::env::temp_dir().join(format!(
20922 "memra-q4e-ep-map-absent-{}.json",
20923 std::process::id()
20924 ));
20925 let _ = std::fs::remove_file(&missing);
20926 let msg = Tp2Placement::load(&missing, 4)
20927 .expect_err("an unreadable map must refuse at the load preflight")
20928 .to_string();
20929 assert!(msg.contains("MEMRA_Q4E_EP_MAP"), "got: {msg}");
20930 }
20931
20932 #[test]
20945 fn refuses_an_odd_routed_bank_on_both_paths() {
20946 let msg = Tp2Placement::even(5)
20948 .layer(0, 5)
20949 .expect_err("an odd bank has no two-card placement")
20950 .to_string();
20951 assert!(msg.contains("ODD"), "got: {msg}");
20952 assert!(msg.contains("EQUAL-size"), "got: {msg}");
20953 let balanced_odd = "{\"format\": \"memra-ep-map-v1\", \"ranks\": 2, \
20956 \"expert_count\": 5, \
20957 \"layers\": [{\"layer\": 0, \"assignment\": [0, 0, 0, 1, 1]}]}";
20958 assert_refuses("odd-balanced", balanced_odd, 5, "ODD");
20959 let unbalanced_odd = "{\"format\": \"memra-ep-map-v1\", \"ranks\": 2, \
20962 \"expert_count\": 5, \
20963 \"layers\": [{\"layer\": 0, \"assignment\": [0, 0, 1, 1, 1]}]}";
20964 assert_refuses("odd-unbalanced", unbalanced_odd, 5, "ODD");
20965 }
20966
20967 #[test]
20974 fn local_slots_are_a_bijection_at_the_serving_geometry() {
20975 let experts = 512usize;
20976 let half = experts / 2;
20977 let assignment: Vec<String> = (0..experts).map(|e| (e % 2).to_string()).collect();
20979 let body = format!(
20980 "\"layers\": [{{\"layer\": 0, \"assignment\": [{}]}}]",
20981 assignment.join(", ")
20982 );
20983 let text = format!(
20984 "{{\"format\": \"memra-ep-map-v1\", \"ranks\": 2, \"expert_count\": {experts}, \
20985 {body}}}"
20986 );
20987 let p = load("bijection", &text, experts).expect("balanced alternating map loads");
20988 let l: LayerPlacement = p.layer(0, experts).expect("layer 0");
20989
20990 assert_eq!(l.card1.len(), half, "card 1 must own exactly half the bank");
20991 assert!(
20992 l.card1.windows(2).all(|w| w[0] < w[1]),
20993 "the gather order must be strictly ascending"
20994 );
20995 let mut seen = vec![false; half];
20996 for e in 0..experts {
20997 match l.rank(e) {
20998 0 => assert_eq!(l.local(e), e, "card-0 slot is the global id"),
20999 1 => {
21000 let slot = l.local(e);
21001 assert!(slot < half, "card-1 slot {slot} outside its half-bank");
21002 assert!(!seen[slot], "card-1 slot {slot} claimed twice");
21003 seen[slot] = true;
21004 }
21005 r => panic!("expert {e} has rank {r}"),
21006 }
21007 }
21008 assert!(seen.into_iter().all(|s| s), "card-1 slots must be dense");
21009 assert!(!l.is_even());
21010 }
21011}