1use std::os::raw::c_void;
34
35use cudarc::driver::{CudaSlice, CudaView, DevicePtr, DevicePtrMut, LaunchConfig, PushKernelArg};
36use memra_gguf::model_plan::{
37 AttentionPlan, FullAttentionPlan, GatedDeltaNetPlan, GdnGateActivation, MicroBlockIndexPlan,
38 MlpPlan, ModelPlan, MoeMlpPlan, PleEmbeddingPlan, ResidualTopology, RopeFactors, RopePlan,
39 RouterPlan, TensorPresence, yarn_attention_factor, yarn_frequency_divisors,
40};
41use memra_gguf::tensor_contract::{LayerTensor, TensorId};
42use memra_reference::{ReferenceTensor, ReferenceWeights};
43
44use crate::Engine;
45
46type Res<T> = Result<T, Box<dyn std::error::Error>>;
47
48struct GateW {
54 norm: Vec<CudaSlice<f32>>,
56 norm_stack: CudaSlice<f32>,
59 down: Vec<CudaSlice<f32>>,
61 up: Vec<CudaSlice<f32>>,
63 inject: Option<CudaSlice<f32>>,
67 down_b16: Option<CudaSlice<u8>>,
72 up_b16: Option<CudaSlice<u8>>,
73 inject_b16: Option<CudaSlice<u8>>,
74}
75
76fn gate_rank(gate: &GateW, hidden: usize, streams: usize) -> Res<usize> {
79 if gate.down[0].len() >= hidden {
80 return Ok(gate.down[0].len() / hidden);
81 }
82 match gate.down_b16.as_ref() {
83 Some(w) => Ok(w.len() / (2 * streams * hidden)),
84 None => Err("qwen4exp_gpu: gate rank underivable (f32 dropped and no bf16 twin)".into()),
85 }
86}
87
88struct QsaW {
89 attn: FullAttentionPlan,
90 overlay: MicroBlockIndexPlan,
91 wq: CudaSlice<f32>, wk: CudaSlice<f32>, wv: CudaSlice<f32>, wo: CudaSlice<f32>, q_norm: Option<CudaSlice<f32>>,
96 k_norm: Option<CudaSlice<f32>>,
97 idx_proj: CudaSlice<f32>, idx_q_norm: Vec<f32>,
101 idx_k_norm: Vec<f32>,
102 proj_b16: Option<CudaSlice<u8>>,
105 wo_b16: Option<CudaSlice<u8>>,
106 yarn: Option<YarnRopeW>,
108}
109
110struct YarnRopeW {
118 ff: CudaSlice<f32>,
119 ff_host: Vec<f32>,
120 mscale: f32,
121}
122
123fn build_yarn(
128 e: &Engine,
129 rope: &RopePlan,
130 overlay: Option<&MicroBlockIndexPlan>,
131 layer: u32,
132) -> Res<Option<YarnRopeW>> {
133 match rope.factors {
134 RopeFactors::None => Ok(None),
135 RopeFactors::Yarn {
136 factor,
137 original_context,
138 beta_fast,
139 beta_slow,
140 } => {
141 if let Some(overlay) = overlay
142 && overlay.rope_dimensions != rope.dimensions
143 {
144 return Err(format!(
145 "qwen4exp_gpu: layer {layer} indexer rope width {} != attention rope \
146 width {} — the shared yarn table would be wrong",
147 overlay.rope_dimensions, rope.dimensions
148 )
149 .into());
150 }
151 let ff_host = yarn_frequency_divisors(
152 rope.dimensions,
153 rope.base,
154 factor,
155 original_context,
156 beta_fast,
157 beta_slow,
158 );
159 Ok(Some(YarnRopeW {
160 ff: e.htod(&ff_host)?,
161 ff_host,
162 mscale: yarn_attention_factor(factor),
163 }))
164 }
165 _ => Err(format!(
166 "qwen4exp_gpu: layer {layer}: only plain or yarn rope factors are supported"
167 )
168 .into()),
169 }
170}
171
172struct GdnW {
173 plan: GatedDeltaNetPlan,
174 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>>,
188 out_b16: Option<CudaSlice<u8>>,
189}
190
191enum MixerW {
192 Qsa(QsaW),
193 Gdn(GdnW),
194}
195
196enum BankHalf {
205 F32(CudaSlice<f32>),
206 Nvfp4 {
207 codes: CudaSlice<u8>,
208 scales: CudaSlice<u8>,
209 macros: Vec<f32>,
210 macros_dev: CudaSlice<f32>,
213 },
214 HostBf16(Vec<u8>),
221 DeviceBf16(CudaSlice<u8>),
228}
229
230struct ExpertBank {
231 gate: BankHalf, up: BankHalf, down: BankHalf, }
235
236struct MoeW {
237 plan: MoeMlpPlan,
238 router: CudaSlice<f32>, router_b16: Option<CudaSlice<u8>>,
241 bank: ExpertBank,
242 shared_gate: CudaSlice<f32>,
243 shared_up: CudaSlice<f32>,
244 shared_down: CudaSlice<f32>,
245 shared_input_gate: Option<CudaSlice<f32>>, shared_gu_b16: Option<CudaSlice<u8>>,
250 shared_down_b16: Option<CudaSlice<u8>>,
251}
252
253enum NgramTable {
255 F32(Vec<f32>),
256 Bf16(Vec<u8>),
257}
258
259impl NgramTable {
260 fn rows(&self, head_dim: usize) -> usize {
261 match self {
262 Self::F32(data) => data.len() / head_dim,
263 Self::Bf16(bytes) => bytes.len() / 2 / head_dim,
264 }
265 }
266
267 fn gather_into(&self, row: usize, head_dim: usize, dst: &mut [f32]) {
268 match self {
269 Self::F32(data) => {
270 dst.copy_from_slice(&data[row * head_dim..(row + 1) * head_dim]);
271 }
272 Self::Bf16(bytes) => {
273 let start = row * head_dim * 2;
274 for (i, out) in dst.iter_mut().enumerate() {
275 let b = u16::from_le_bytes([bytes[start + 2 * i], bytes[start + 2 * i + 1]]);
276 *out = f32::from_bits(u32::from(b) << 16);
277 }
278 }
279 }
280 }
281}
282
283struct PleW {
284 plan: PleEmbeddingPlan,
285 key_proj: Vec<CudaSlice<f32>>, value_proj: CudaSlice<f32>, norm_key: Vec<CudaSlice<f32>>, norm_query: Vec<CudaSlice<f32>>,
289 norm_conv: Vec<CudaSlice<f32>>,
290 conv_w: Vec<CudaSlice<f32>>, multipliers: Vec<i64>,
292 sizes: Vec<i64>,
293 offsets: Vec<i64>,
294 table: NgramTable,
295}
296
297struct LayerW {
298 index: u32,
299 eps_attn: f32,
300 eps_mlp: f32,
301 attn_gate: GateW,
302 mlp_gate: GateW,
303 mixer: MixerW,
304 moe: MoeW,
305 ple: Option<PleW>,
306}
307
308struct MtpW {
316 eps_embed: f32,
318 eps_hidden: f32,
319 pre_norm_embed: CudaSlice<f32>,
321 pre_norm_hidden: CudaSlice<f32>,
323 fc_embed: CudaSlice<f32>, fc_embed_b16: Option<CudaSlice<u8>>,
325 fc_hidden: CudaSlice<f32>, fc_hidden_b16: Option<CudaSlice<u8>>,
327 layer: LayerW,
330 mixer: GateW,
332}
333
334struct MtpDev1 {
345 dev: usize,
347 output: CudaSlice<f32>,
349 output_b16: Option<CudaSlice<u8>>,
353}
354
355struct DraftTrim {
363 n: usize,
365 d2t: Vec<u32>,
367 head_b16: Option<CudaSlice<u8>>,
372 head: Option<CudaSlice<f32>>,
375}
376
377fn linear_trim_into(
381 e: &Engine,
382 trim: &DraftTrim,
383 x: &CudaSlice<f32>,
384 y: &mut CudaSlice<f32>,
385 t: usize,
386 in_f: usize,
387) -> Res<()> {
388 if trunk_bf16_on() {
389 if let Some(w) = trim.head_b16.as_ref() {
390 if (2..=12).contains(&t) && verify_mt_on() {
391 return launch_qmatvec_bf16w_mt(e, w, 0, x, y, in_f, trim.n, t);
392 }
393 return launch_qmatvec_bf16w(e, w, x, y, in_f, trim.n, t, 1, 0, 0, in_f, 0);
394 }
395 }
396 let w = trim.head.as_ref().ok_or(
397 "qwen4exp_gpu: the draft trim was gathered bf16-only — the f32 head arm needs \
398 trunk_bf16 on (or a checkpoint without a bf16 lm-head twin)",
399 )?;
400 e.linear_device_into(x, w, y, t, in_f, trim.n)
401}
402
403pub struct Qwen4ExpGpu {
404 pub plan: ModelPlan,
405 hidden: usize,
406 streams: usize,
407 vocab: usize,
408 embed_host: Vec<f32>, output: CudaSlice<f32>, output_b16: Option<CudaSlice<u8>>,
412 layers: Vec<LayerW>,
413 exit_mixer: GateW,
414 exit_eps: f32,
415 mtp: Option<MtpW>,
418 mtp_dev1: Option<MtpDev1>,
423 draft_trim: Option<DraftTrim>,
427 draft_trim_parked: Option<DraftTrim>,
430 chain_embed: Option<ChainEmbed>,
435}
436
437struct ChainEmbed {
447 table: CudaSlice<u8>,
448 qt: i32,
449 row_bytes: usize,
450 rows: usize,
452 for_trim: bool,
454 dev: usize,
456}
457
458struct PleState {
461 conv_hist: Vec<CudaSlice<f32>>,
464 ngram_ids: Vec<i64>,
480 ngram_history: Vec<i64>,
481 ngram_last_eos: i64,
482}
483
484fn q8_row_bytes(dim: usize) -> usize {
488 dim.div_ceil(32) * 34
489}
490fn q5_row_bytes(dim: usize) -> usize {
492 dim.div_ceil(32) * 24
493}
494
495enum QsaKvStore {
500 F32 {
501 k: CudaSlice<f32>, v: CudaSlice<f32>, },
504 Q8Q5 {
505 k: CudaSlice<u8>, v: CudaSlice<u8>, },
508}
509
510impl QsaKvStore {
511 fn is_quant(&self) -> bool {
512 matches!(self, QsaKvStore::Q8Q5 { .. })
513 }
514 fn capacity_rows(&self, kv_dim: usize) -> usize {
515 match self {
516 QsaKvStore::F32 { k, .. } => k.len() / kv_dim,
517 QsaKvStore::Q8Q5 { k, .. } => k.len() / q8_row_bytes(kv_dim),
518 }
519 }
520}
521
522fn host_quant_q8_row(row: &[f32], dim: usize, out: &mut Vec<u8>) {
528 for b in 0..dim.div_ceil(32) {
529 let mut amax = 0.0f32;
530 for l in 0..32 {
531 let e = b * 32 + l;
532 let x = if e < dim { row[e] } else { 0.0 };
533 amax = amax.max(x.abs());
534 }
535 let d = amax / 127.0f32;
536 let mut id = if d != 0.0 { 1.0f32 / d } else { 0.0 };
537 if !id.is_finite() {
539 id = 0.0;
540 }
541 out.extend_from_slice(&memra_gguf::nvfp4_repack::f32_to_f16_bits(d).to_le_bytes());
542 for l in 0..32 {
543 let e = b * 32 + l;
544 let x = if e < dim { row[e] } else { 0.0 };
545 let q = ((x * id).round_ties_even() as i32).clamp(-127, 127);
546 out.push(q as i8 as u8);
547 }
548 }
549}
550
551fn host_deq_q8_rows(bytes: &[u8], row0: usize, rows: usize, dim: usize, out: &mut Vec<f32>) {
554 let rb = q8_row_bytes(dim);
555 for r in row0..row0 + rows {
556 let row = &bytes[r * rb..(r + 1) * rb];
557 for e in 0..dim {
558 let blk = &row[(e >> 5) * 34..];
559 let d = memra_gguf::dequant::fp16_to_f32(u16::from_le_bytes([blk[0], blk[1]]));
560 let q = blk[2 + (e & 31)] as i8 as f32;
561 out.push(d * q);
562 }
563 }
564}
565
566fn host_quant_q5_row(row: &[f32], dim: usize, out: &mut Vec<u8>) {
569 for b in 0..dim.div_ceil(32) {
570 let lane = |l: usize| -> f32 {
571 let e = b * 32 + l;
572 if e < dim { row[e] } else { 0.0 }
573 };
574 let mut mn = f32::INFINITY;
575 let mut mx = f32::NEG_INFINITY;
576 for l in 0..32 {
577 mn = mn.min(lane(l));
578 mx = mx.max(lane(l));
579 }
580 let d = (mx - mn) / 31.0f32;
581 let mut id = if d != 0.0 { 1.0f32 / d } else { 0.0 };
582 if !id.is_finite() {
584 id = 0.0;
585 }
586 let q5 = |l: usize| -> u32 {
587 (((lane(l) - mn) * id).round_ties_even() as i32).clamp(0, 31) as u32
588 };
589 let mut qh = 0u32;
590 for l in 0..32 {
591 qh |= ((q5(l) >> 4) & 1) << l;
592 }
593 out.extend_from_slice(&memra_gguf::nvfp4_repack::f32_to_f16_bits(d).to_le_bytes());
594 out.extend_from_slice(&memra_gguf::nvfp4_repack::f32_to_f16_bits(mn).to_le_bytes());
595 out.extend_from_slice(&qh.to_le_bytes());
596 for l in 0..16 {
597 out.push(((q5(l) & 0x0F) | ((q5(l + 16) & 0x0F) << 4)) as u8);
598 }
599 }
600}
601
602fn host_deq_q5_rows(bytes: &[u8], row0: usize, rows: usize, dim: usize, out: &mut Vec<f32>) {
604 let rb = q5_row_bytes(dim);
605 for r in row0..row0 + rows {
606 let row = &bytes[r * rb..(r + 1) * rb];
607 for e in 0..dim {
608 let blk = &row[(e >> 5) * 24..];
609 let d = memra_gguf::dequant::fp16_to_f32(u16::from_le_bytes([blk[0], blk[1]]));
610 let m = memra_gguf::dequant::fp16_to_f32(u16::from_le_bytes([blk[2], blk[3]]));
611 let qh = u32::from_le_bytes([blk[4], blk[5], blk[6], blk[7]]);
612 let lane = e & 31;
613 let lo = if lane < 16 {
614 blk[8 + lane] & 0x0F
615 } else {
616 blk[8 + lane - 16] >> 4
617 };
618 let q5 = (lo as u32) | (((qh >> lane) & 1) << 4);
619 out.push(d.mul_add(q5 as f32, m));
620 }
621 }
622}
623
624fn f32_to_bf16_rne(x: f32) -> u16 {
627 let bits = x.to_bits();
628 let rounding_bias = 0x7fff + ((bits >> 16) & 1);
629 (bits.wrapping_add(rounding_bias) >> 16) as u16
630}
631
632enum IdxRawCache {
636 F32(Vec<f32>),
637 Q8(Vec<u8>),
638 Bf16(Vec<u16>),
639}
640
641impl IdxRawCache {
642 fn new(mode: IdxQMode) -> Self {
643 match mode {
644 IdxQMode::F32 => IdxRawCache::F32(Vec::new()),
645 IdxQMode::Q8 => IdxRawCache::Q8(Vec::new()),
646 IdxQMode::Bf16 => IdxRawCache::Bf16(Vec::new()),
647 }
648 }
649 fn rows(&self, idx_dim: usize) -> usize {
650 match self {
651 IdxRawCache::F32(v) => v.len() / idx_dim,
652 IdxRawCache::Q8(v) => v.len() / q8_row_bytes(idx_dim),
653 IdxRawCache::Bf16(v) => v.len() / idx_dim,
654 }
655 }
656 fn truncate_rows(&mut self, rows: usize, idx_dim: usize) {
657 match self {
658 IdxRawCache::F32(v) => v.truncate(rows * idx_dim),
659 IdxRawCache::Q8(v) => v.truncate(rows * q8_row_bytes(idx_dim)),
660 IdxRawCache::Bf16(v) => v.truncate(rows * idx_dim),
661 }
662 }
663 fn append_rows_f32(&mut self, rows: &[f32], n: usize, idx_dim: usize) {
666 match self {
667 IdxRawCache::F32(v) => v.extend_from_slice(&rows[..n * idx_dim]),
668 IdxRawCache::Q8(v) => {
669 for r in 0..n {
670 host_quant_q8_row(&rows[r * idx_dim..(r + 1) * idx_dim], idx_dim, v);
671 }
672 }
673 IdxRawCache::Bf16(v) => {
674 v.extend(rows[..n * idx_dim].iter().map(|&x| f32_to_bf16_rne(x)));
675 }
676 }
677 }
678 fn rows_f32(&self, row0: usize, n: usize, idx_dim: usize, out: &mut Vec<f32>) {
680 out.clear();
681 match self {
682 IdxRawCache::F32(v) => out.extend_from_slice(&v[row0 * idx_dim..(row0 + n) * idx_dim]),
683 IdxRawCache::Q8(v) => host_deq_q8_rows(v, row0, n, idx_dim, out),
684 IdxRawCache::Bf16(v) => out.extend(
685 v[row0 * idx_dim..(row0 + n) * idx_dim]
686 .iter()
687 .map(|&b| memra_gguf::dequant::bf16_to_f32(b)),
688 ),
689 }
690 }
691}
692
693enum IdxRawDev {
697 F32(CudaSlice<f32>),
698 Q8(CudaSlice<u8>),
699 Bf16(CudaSlice<u16>),
700}
701
702fn idx_materialize_host(
706 e: &Engine,
707 raw_keys: &mut IdxRawCache,
708 raw_dev: &Option<IdxRawDev>,
709 raw_dev_rows: usize,
710 idx_dim: usize,
711) -> Res<()> {
712 let host_rows = raw_keys.rows(idx_dim);
713 if raw_dev_rows <= host_rows {
714 return Ok(());
715 }
716 let m = raw_dev
717 .as_ref()
718 .ok_or("idxcache: rows counted without a cache")?;
719 match (m, raw_keys) {
720 (IdxRawDev::F32(d), IdxRawCache::F32(h)) => {
721 let delta = e.dtoh_view(&d.slice(host_rows * idx_dim..raw_dev_rows * idx_dim))?;
722 h.extend_from_slice(&delta);
723 }
724 (IdxRawDev::Q8(d), IdxRawCache::Q8(h)) => {
725 let rb = q8_row_bytes(idx_dim);
726 let delta = e.dtoh_u8_view(&d.slice(host_rows * rb..raw_dev_rows * rb))?;
727 h.extend_from_slice(&delta);
728 }
729 (IdxRawDev::Bf16(d), IdxRawCache::Bf16(h)) => {
730 let delta = e
731 .gpu
732 .stream()
733 .clone_dtoh(&d.slice(host_rows * idx_dim..raw_dev_rows * idx_dim))?;
734 e.gpu.stream().synchronize()?;
735 h.extend_from_slice(&delta);
736 }
737 _ => return Err("idxcache: device/host raw-key formats disagree".into()),
738 }
739 Ok(())
740}
741
742struct IdxAudit {
745 raw_f32: IdxRawCache, pooled_f32: Vec<f32>,
747}
748
749enum MixerState {
750 Qsa {
751 kv: QsaKvStore,
752 raw_keys: IdxRawCache,
756 pooled_keys: Vec<f32>,
763 pooled_dev: Option<CudaSlice<f32>>,
767 pooled_dev_rows: usize,
769 raw_dev: Option<IdxRawDev>,
775 raw_dev_rows: usize,
777 idx_audit: Option<Box<IdxAudit>>,
779 },
780 Gdn {
781 conv: CudaSlice<f32>, state: CudaSlice<f32>, },
784}
785
786struct LayerState {
787 mixer: MixerState,
788 ple: Option<PleState>,
789}
790
791struct GdnStash {
794 states: CudaSlice<f32>,
796 conv_pre: CudaSlice<f32>,
798 qkv_rows: CudaSlice<f32>,
800 scan_graph: Option<(usize, GraphEntry)>,
807 scan_warm: Option<usize>,
811}
812
813struct PleStash {
816 hist_pre: Vec<CudaSlice<f32>>, normed_rows: Vec<CudaSlice<f32>>, }
819
820pub struct VerifyStash {
827 k_cap: usize,
828 chunk: Option<(usize, usize)>,
830 gdn: Vec<Option<GdnStash>>,
831 ple: Vec<Option<PleStash>>,
832 wide: CudaSlice<f32>,
837 ring_rows: usize,
838 wide_dev1: Option<CudaSlice<f32>>,
842 argmax: Vec<u32>,
844 toks: CudaSlice<u32>,
846 want_argmax: bool,
849 want_argmax_t1: bool,
854 last_row_only: bool,
858}
859
860pub struct Qwen4ExpState {
861 pos: usize,
862 capacity: usize,
863 reserve: usize,
869 tokens: Vec<u32>,
873 layers: Vec<LayerState>,
874 ws: StepPool,
876 graphs: StepGraphs,
878 tp2: Option<Tp2State>,
883 verify: Option<VerifyStash>,
885}
886
887#[derive(Default)]
898struct StepPool {
899 f32s: std::collections::BTreeMap<&'static str, CudaSlice<f32>>,
900 i32s: std::collections::BTreeMap<&'static str, CudaSlice<i32>>,
901 u8s: std::collections::BTreeMap<&'static str, CudaSlice<u8>>,
902 u64s: std::collections::BTreeMap<&'static str, CudaSlice<u64>>,
903}
904
905const EXIT_PLANE_SLOTS: [&str; 8] = [
910 "exit.p0", "exit.p1", "exit.p2", "exit.p3", "exit.p4", "exit.p5", "exit.p6", "exit.p7",
911];
912const PLANE_SLOTS: [&str; 8] = [
913 "plane.0", "plane.1", "plane.2", "plane.3", "plane.4", "plane.5", "plane.6", "plane.7",
914];
915const INJECT_SLOTS: [&str; 8] = [
916 "hc.inj.0", "hc.inj.1", "hc.inj.2", "hc.inj.3", "hc.inj.4", "hc.inj.5", "hc.inj.6", "hc.inj.7",
917];
918
919impl StepPool {
920 fn take_f32(
921 &mut self,
922 e: &Engine,
923 name: &'static str,
924 len: usize,
925 reserve: usize,
926 ) -> Res<CudaSlice<f32>> {
927 if step_ws_on() {
928 if let Some(buf) = self.f32s.remove(name) {
929 if buf.len() >= len {
930 return Ok(buf);
931 }
932 }
933 e.uninit(len.max(reserve))
934 } else {
935 e.uninit(len)
936 }
937 }
938
939 fn put_f32(&mut self, name: &'static str, buf: CudaSlice<f32>) {
940 if step_ws_on() {
941 self.f32s.insert(name, buf);
942 }
943 }
944
945 fn take_i32(
946 &mut self,
947 e: &Engine,
948 name: &'static str,
949 host: &[i32],
950 reserve: usize,
951 ) -> Res<CudaSlice<i32>> {
952 if step_ws_on() {
953 let mut buf = match self.i32s.remove(name) {
954 Some(buf) if buf.len() >= host.len() => buf,
955 _ => e.alloc_uninit::<i32>(host.len().max(reserve))?,
956 };
957 let mut view = buf.slice_mut(0..host.len());
958 e.gpu.stream().memcpy_htod(host, &mut view)?;
959 Ok(buf)
960 } else {
961 e.htod_i32(host)
962 }
963 }
964
965 fn put_i32(&mut self, name: &'static str, buf: CudaSlice<i32>) {
966 if step_ws_on() {
967 self.i32s.insert(name, buf);
968 }
969 }
970
971 fn take_i32_slot(
974 &mut self,
975 e: &Engine,
976 name: &'static str,
977 len: usize,
978 reserve: usize,
979 ) -> Res<CudaSlice<i32>> {
980 if step_ws_on() {
981 if let Some(buf) = self.i32s.remove(name) {
982 if buf.len() >= len {
983 return Ok(buf);
984 }
985 }
986 }
987 e.alloc_uninit::<i32>(len.max(reserve))
988 }
989
990 fn take_f32_h2d(
991 &mut self,
992 e: &Engine,
993 name: &'static str,
994 host: &[f32],
995 reserve: usize,
996 ) -> Res<CudaSlice<f32>> {
997 if step_ws_on() {
998 let mut buf = match self.f32s.remove(name) {
999 Some(buf) if buf.len() >= host.len() => buf,
1000 _ => e.uninit(host.len().max(reserve))?,
1001 };
1002 let mut view = buf.slice_mut(0..host.len());
1003 e.gpu.stream().memcpy_htod(host, &mut view)?;
1004 Ok(buf)
1005 } else {
1006 e.htod(host)
1007 }
1008 }
1009
1010 fn take_u8_h2d(
1011 &mut self,
1012 e: &Engine,
1013 name: &'static str,
1014 host: &[u8],
1015 reserve: usize,
1016 ) -> Res<CudaSlice<u8>> {
1017 if step_ws_on() {
1018 let mut buf = match self.u8s.remove(name) {
1019 Some(buf) if buf.len() >= host.len() => buf,
1020 _ => e.alloc_u8_uninit(host.len().max(reserve))?,
1021 };
1022 let mut view = buf.slice_mut(0..host.len());
1023 e.gpu.stream().memcpy_htod(host, &mut view)?;
1024 Ok(buf)
1025 } else {
1026 e.htod_bytes(host)
1027 }
1028 }
1029
1030 fn put_u8(&mut self, name: &'static str, buf: CudaSlice<u8>) {
1031 if step_ws_on() {
1032 self.u8s.insert(name, buf);
1033 }
1034 }
1035
1036 fn take_u8(
1039 &mut self,
1040 e: &Engine,
1041 name: &'static str,
1042 len: usize,
1043 reserve: usize,
1044 ) -> Res<CudaSlice<u8>> {
1045 if step_ws_on() {
1046 if let Some(buf) = self.u8s.remove(name) {
1047 if buf.len() >= len {
1048 return Ok(buf);
1049 }
1050 }
1051 }
1052 e.alloc_u8_uninit(len.max(reserve))
1053 }
1054
1055 fn upsert_u8(
1059 &mut self,
1060 e: &Engine,
1061 name: &'static str,
1062 host: &[u8],
1063 reserve: usize,
1064 ) -> Res<()> {
1065 if !self.u8s.contains_key(name) {
1066 let buf = self.take_u8(e, name, host.len(), reserve)?;
1067 self.put_u8(name, buf);
1068 }
1069 let buf = self
1070 .u8s
1071 .get_mut(name)
1072 .ok_or_else(|| format!("step workspace: slot {name} is not parked"))?;
1073 if buf.len() < host.len() {
1074 return Err(format!("step workspace: slot {name} is too small").into());
1075 }
1076 let mut view = buf.slice_mut(0..host.len());
1077 e.gpu.stream().memcpy_htod(host, &mut view)?;
1078 Ok(())
1079 }
1080
1081 fn peek_u8(&self, name: &'static str) -> Res<&CudaSlice<u8>> {
1084 self.u8s
1085 .get(name)
1086 .ok_or_else(|| format!("step workspace: slot {name} is not parked").into())
1087 }
1088
1089 fn take_u64_h2d(
1090 &mut self,
1091 e: &Engine,
1092 name: &'static str,
1093 host: &[u64],
1094 reserve: usize,
1095 ) -> Res<CudaSlice<u64>> {
1096 if step_ws_on() {
1097 let mut buf = match self.u64s.remove(name) {
1098 Some(buf) if buf.len() >= host.len() => buf,
1099 _ => e.alloc_uninit::<u64>(host.len().max(reserve))?,
1100 };
1101 let mut view = buf.slice_mut(0..host.len());
1102 e.gpu.stream().memcpy_htod(host, &mut view)?;
1103 Ok(buf)
1104 } else {
1105 e.htod_u64(host)
1106 }
1107 }
1108
1109 fn put_u64(&mut self, name: &'static str, buf: CudaSlice<u64>) {
1110 if step_ws_on() {
1111 self.u64s.insert(name, buf);
1112 }
1113 }
1114
1115 fn peek_f32(&self, name: &'static str) -> Res<&CudaSlice<f32>> {
1118 self.f32s
1119 .get(name)
1120 .ok_or_else(|| format!("step workspace: slot {name} is not parked").into())
1121 }
1122
1123 fn write_i32(&mut self, e: &Engine, name: &'static str, host: &[i32]) -> Res<()> {
1127 let buf = self
1128 .i32s
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 write_f32(&mut self, e: &Engine, name: &'static str, host: &[f32]) -> Res<()> {
1140 let buf = self
1141 .f32s
1142 .get_mut(name)
1143 .ok_or_else(|| format!("step workspace: slot {name} is not parked"))?;
1144 if buf.len() < host.len() {
1145 return Err(format!("step workspace: slot {name} is too small").into());
1146 }
1147 let mut view = buf.slice_mut(0..host.len());
1148 e.gpu.stream().memcpy_htod(host, &mut view)?;
1149 Ok(())
1150 }
1151}
1152
1153#[derive(Default)]
1164struct StepGraphs {
1165 warm: bool,
1168 a: Vec<Option<GraphEntry>>,
1169 b: Vec<Option<GraphEntry>>,
1170 exit: Option<GraphEntry>,
1171}
1172
1173type GraphEntry = (
1174 cudarc::driver::CudaGraph,
1175 Vec<Box<dyn std::any::Any + Send>>,
1176);
1177
1178impl Qwen4ExpState {
1179 pub fn position(&self) -> usize {
1180 self.pos
1181 }
1182}
1183
1184pub struct PrefillCapture {
1188 pub layer_wide: Vec<Vec<f32>>,
1190 pub exit_mixed: Vec<f32>,
1192}
1193
1194pub mod prof {
1203 use std::cell::RefCell;
1204 use std::collections::BTreeMap;
1205
1206 thread_local! {
1207 static STATE: RefCell<Option<BTreeMap<&'static str, (f64, u64)>>> =
1208 const { RefCell::new(None) };
1209 }
1210
1211 pub fn enable() {
1213 STATE.with(|s| *s.borrow_mut() = Some(BTreeMap::new()));
1214 }
1215
1216 pub fn on() -> bool {
1217 STATE.with(|s| s.borrow().is_some())
1218 }
1219
1220 pub fn take() -> Vec<(&'static str, f64, u64)> {
1222 STATE.with(|s| {
1223 s.borrow_mut()
1224 .take()
1225 .map(|map| map.into_iter().map(|(k, (t, c))| (k, t, c)).collect())
1226 .unwrap_or_default()
1227 })
1228 }
1229
1230 pub(super) fn add(name: &'static str, seconds: f64) {
1231 STATE.with(|s| {
1232 if let Some(map) = s.borrow_mut().as_mut() {
1233 let entry = map.entry(name).or_insert((0.0, 0));
1234 entry.0 += seconds;
1235 entry.1 += 1;
1236 }
1237 });
1238 }
1239}
1240
1241static MOE_SEL_PATH: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1246
1247pub fn set_moe_sel_path(on: bool) {
1248 MOE_SEL_PATH.store(on, std::sync::atomic::Ordering::Relaxed);
1249}
1250
1251fn moe_sel_path_on() -> bool {
1252 MOE_SEL_PATH.load(std::sync::atomic::Ordering::Relaxed)
1253}
1254
1255static HC_FUSED_GATE: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1259
1260pub fn set_hc_fused_gate(on: bool) {
1261 HC_FUSED_GATE.store(on, std::sync::atomic::Ordering::Relaxed);
1262}
1263
1264fn hc_fused_gate_on() -> bool {
1265 HC_FUSED_GATE.load(std::sync::atomic::Ordering::Relaxed)
1266}
1267
1268static TRUNK_BF16: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1277
1278pub fn set_trunk_bf16(on: bool) {
1279 TRUNK_BF16.store(on, std::sync::atomic::Ordering::Relaxed);
1280}
1281
1282fn trunk_bf16_on() -> bool {
1283 TRUNK_BF16.load(std::sync::atomic::Ordering::Relaxed)
1284}
1285
1286static PREFILL_GROUPED_ALL: std::sync::atomic::AtomicBool =
1311 std::sync::atomic::AtomicBool::new(false);
1312
1313pub fn set_prefill_grouped_all(on: bool) {
1314 PREFILL_GROUPED_ALL.store(on, std::sync::atomic::Ordering::Relaxed);
1315}
1316
1317fn prefill_grouped_all_on() -> bool {
1318 PREFILL_GROUPED_ALL.load(std::sync::atomic::Ordering::Relaxed)
1319}
1320
1321static STEP_WS: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1325
1326pub fn set_step_ws(on: bool) {
1327 STEP_WS.store(on, std::sync::atomic::Ordering::Relaxed);
1328}
1329
1330fn step_ws_on() -> bool {
1331 STEP_WS.load(std::sync::atomic::Ordering::Relaxed)
1332}
1333
1334static DECODE_GRAPHS: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1342
1343pub fn set_decode_graphs(on: bool) {
1344 DECODE_GRAPHS.store(on, std::sync::atomic::Ordering::Relaxed);
1345}
1346
1347fn decode_graphs_on() -> bool {
1348 DECODE_GRAPHS.load(std::sync::atomic::Ordering::Relaxed)
1349}
1350
1351static VERIFY_GRAPHS: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
1366
1367pub fn set_verify_graphs(on: bool) {
1368 VERIFY_GRAPHS.store(on, std::sync::atomic::Ordering::Relaxed);
1369}
1370
1371fn verify_graphs_on() -> bool {
1372 VERIFY_GRAPHS.load(std::sync::atomic::Ordering::Relaxed)
1373}
1374
1375static SEL_V2: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1381
1382pub fn set_sel_v2(on: bool) {
1383 SEL_V2.store(on, std::sync::atomic::Ordering::Relaxed);
1384}
1385
1386fn sel_v2_on() -> bool {
1387 SEL_V2.load(std::sync::atomic::Ordering::Relaxed)
1388}
1389
1390pub const SEL_V3_DEFAULT: bool = true;
1398static SEL_V3: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(SEL_V3_DEFAULT);
1399
1400pub fn set_sel_v3(on: bool) {
1401 SEL_V3.store(on, std::sync::atomic::Ordering::Relaxed);
1402}
1403
1404fn sel_v3_on() -> bool {
1405 SEL_V3.load(std::sync::atomic::Ordering::Relaxed)
1406}
1407
1408static HC_MICRO: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1416
1417pub fn set_hc_micro(on: bool) {
1418 HC_MICRO.store(on, std::sync::atomic::Ordering::Relaxed);
1419}
1420
1421fn hc_micro_on() -> bool {
1422 HC_MICRO.load(std::sync::atomic::Ordering::Relaxed)
1423}
1424
1425pub const GDN_STEP_DEFAULT: bool = true;
1435static GDN_STEP: std::sync::atomic::AtomicBool =
1436 std::sync::atomic::AtomicBool::new(GDN_STEP_DEFAULT);
1437
1438pub fn set_gdn_step(on: bool) {
1439 GDN_STEP.store(on, std::sync::atomic::Ordering::Relaxed);
1440}
1441
1442fn gdn_step_on() -> bool {
1443 GDN_STEP.load(std::sync::atomic::Ordering::Relaxed)
1444}
1445
1446pub const GDN_FUSE_DEFAULT: bool = true;
1454static GDN_FUSE: std::sync::atomic::AtomicBool =
1455 std::sync::atomic::AtomicBool::new(GDN_FUSE_DEFAULT);
1456
1457pub fn set_gdn_fuse(on: bool) {
1458 GDN_FUSE.store(on, std::sync::atomic::Ordering::Relaxed);
1459}
1460
1461fn gdn_fuse_on() -> bool {
1462 GDN_FUSE.load(std::sync::atomic::Ordering::Relaxed)
1463}
1464
1465pub const PROJ_STACK_DEFAULT: bool = true;
1476static PROJ_STACK: std::sync::atomic::AtomicBool =
1477 std::sync::atomic::AtomicBool::new(PROJ_STACK_DEFAULT);
1478
1479pub fn set_proj_stack(on: bool) {
1480 PROJ_STACK.store(on, std::sync::atomic::Ordering::Relaxed);
1481}
1482
1483fn proj_stack_on() -> bool {
1484 PROJ_STACK.load(std::sync::atomic::Ordering::Relaxed)
1485}
1486
1487pub const HC_DIET_DEFAULT: bool = true;
1500static HC_DIET: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(HC_DIET_DEFAULT);
1501
1502pub fn set_hc_diet(on: bool) {
1503 HC_DIET.store(on, std::sync::atomic::Ordering::Relaxed);
1504}
1505
1506fn hc_diet_on() -> bool {
1507 HC_DIET.load(std::sync::atomic::Ordering::Relaxed)
1508}
1509
1510pub const SEL_GUFUSE_DEFAULT: bool = true;
1523static SEL_GUFUSE: std::sync::atomic::AtomicBool =
1524 std::sync::atomic::AtomicBool::new(SEL_GUFUSE_DEFAULT);
1525
1526pub fn set_sel_gufuse(on: bool) {
1527 SEL_GUFUSE.store(on, std::sync::atomic::Ordering::Relaxed);
1528}
1529
1530fn sel_gufuse_on() -> bool {
1531 SEL_GUFUSE.load(std::sync::atomic::Ordering::Relaxed)
1532}
1533
1534pub const VERIFY_MT_DEFAULT: bool = true;
1545static VERIFY_MT: std::sync::atomic::AtomicBool =
1546 std::sync::atomic::AtomicBool::new(VERIFY_MT_DEFAULT);
1547
1548pub fn set_verify_mt(on: bool) {
1549 VERIFY_MT.store(on, std::sync::atomic::Ordering::Relaxed);
1550}
1551
1552fn verify_mt_on() -> bool {
1553 VERIFY_MT.load(std::sync::atomic::Ordering::Relaxed)
1554}
1555
1556pub const ROUTER_B16_DEFAULT: bool = true;
1565static ROUTER_B16: std::sync::atomic::AtomicBool =
1566 std::sync::atomic::AtomicBool::new(ROUTER_B16_DEFAULT);
1567
1568pub fn set_router_bf16(on: bool) {
1569 ROUTER_B16.store(on, std::sync::atomic::Ordering::Relaxed);
1570}
1571
1572fn router_bf16_on() -> bool {
1573 ROUTER_B16.load(std::sync::atomic::Ordering::Relaxed)
1574}
1575
1576const SDPA_MASK_TKV_BOUND: usize = 12288;
1583
1584static IDX_DEV: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1591fn idx_dev_on() -> bool {
1592 IDX_DEV.load(std::sync::atomic::Ordering::Relaxed)
1593}
1594pub fn set_idx_dev(on: bool) {
1595 IDX_DEV.store(on, std::sync::atomic::Ordering::Relaxed);
1596}
1597
1598pub const IDX_SEL_DEFAULT: bool = true;
1644static IDX_SEL: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(IDX_SEL_DEFAULT);
1645fn idx_sel_on() -> bool {
1646 IDX_SEL.load(std::sync::atomic::Ordering::Relaxed)
1647}
1648pub fn set_idx_sel(on: bool) {
1649 IDX_SEL.store(on, std::sync::atomic::Ordering::Relaxed);
1650}
1651pub const PLE_CACHE_DEFAULT: bool = true;
1716static PLE_CACHE: std::sync::atomic::AtomicBool =
1717 std::sync::atomic::AtomicBool::new(PLE_CACHE_DEFAULT);
1718fn ple_cache_on() -> bool {
1719 PLE_CACHE.load(std::sync::atomic::Ordering::Relaxed)
1720}
1721pub fn set_ple_cache(on: bool) {
1722 PLE_CACHE.store(on, std::sync::atomic::Ordering::Relaxed);
1723}
1724fn ple_cache_audit_on() -> bool {
1728 static C: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1729 *C.get_or_init(|| std::env::var("MEMRA_Q4E_PLECACHE_AUDIT").as_deref() == Ok("1"))
1730}
1731static PLE_CACHE_AUDIT_ROWS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
1732static PLE_CACHE_AUDIT_MISMATCH: std::sync::atomic::AtomicU64 =
1733 std::sync::atomic::AtomicU64::new(0);
1734static PLE_CACHE_AUDIT_MAX_FILL: std::sync::atomic::AtomicU64 =
1735 std::sync::atomic::AtomicU64::new(0);
1736pub fn ple_cache_audit_stats() -> (u64, u64, u64) {
1738 (
1739 PLE_CACHE_AUDIT_ROWS.load(std::sync::atomic::Ordering::Relaxed),
1740 PLE_CACHE_AUDIT_MISMATCH.load(std::sync::atomic::Ordering::Relaxed),
1741 PLE_CACHE_AUDIT_MAX_FILL.load(std::sync::atomic::Ordering::Relaxed),
1742 )
1743}
1744
1745fn idx_sel_audit_on() -> bool {
1752 static C: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1753 *C.get_or_init(|| std::env::var("MEMRA_Q4E_IDXSEL_AUDIT").as_deref() == Ok("1"))
1754}
1755static IDX_SEL_AUDIT_ROWS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
1756static IDX_SEL_AUDIT_MISMATCH: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
1757static IDX_SEL_AUDIT_MAX_BLOCKS: std::sync::atomic::AtomicU64 =
1758 std::sync::atomic::AtomicU64::new(0);
1759pub fn idx_sel_audit_stats() -> (u64, u64, u64) {
1761 (
1762 IDX_SEL_AUDIT_ROWS.load(std::sync::atomic::Ordering::Relaxed),
1763 IDX_SEL_AUDIT_MISMATCH.load(std::sync::atomic::Ordering::Relaxed),
1764 IDX_SEL_AUDIT_MAX_BLOCKS.load(std::sync::atomic::Ordering::Relaxed),
1765 )
1766}
1767
1768pub const ROUTER_DEV_DEFAULT: bool = true;
1788static ROUTER_DEV: std::sync::atomic::AtomicBool =
1789 std::sync::atomic::AtomicBool::new(ROUTER_DEV_DEFAULT);
1790fn router_dev_on() -> bool {
1791 ROUTER_DEV.load(std::sync::atomic::Ordering::Relaxed)
1792}
1793pub fn set_router_dev(on: bool) {
1794 ROUTER_DEV.store(on, std::sync::atomic::Ordering::Relaxed);
1795}
1796fn route_dev_geometry(experts: usize, selected: usize) -> bool {
1800 selected > 0
1803 && selected <= 32
1804 && selected <= experts
1805 && experts % 2 == 0
1806 && experts * 12 <= 48 * 1024
1807}
1808
1809fn router_audit_on() -> bool {
1816 static C: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1817 *C.get_or_init(|| std::env::var("MEMRA_Q4E_ROUTER_AUDIT").as_deref() == Ok("1"))
1818}
1819fn route_sync_diag() -> bool {
1824 static C: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1825 *C.get_or_init(|| std::env::var("MEMRA_Q4E_ROUTE_SYNC").as_deref() == Ok("1"))
1826}
1827
1828const ROUTE_AUDIT_ULP_BOUND: u32 = 8;
1829static ROUTE_AUDIT_ROWS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
1830static ROUTE_AUDIT_MAX_ULP: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
1831pub fn route_audit_stats() -> (u64, u32) {
1833 (
1834 ROUTE_AUDIT_ROWS.load(std::sync::atomic::Ordering::Relaxed),
1835 ROUTE_AUDIT_MAX_ULP.load(std::sync::atomic::Ordering::Relaxed),
1836 )
1837}
1838
1839pub const IDX_CACHE_DEFAULT: bool = true;
1853static IDX_CACHE: std::sync::atomic::AtomicBool =
1854 std::sync::atomic::AtomicBool::new(IDX_CACHE_DEFAULT);
1855fn idx_cache_on() -> bool {
1856 IDX_CACHE.load(std::sync::atomic::Ordering::Relaxed)
1857}
1858pub fn set_idx_cache(on: bool) {
1859 IDX_CACHE.store(on, std::sync::atomic::Ordering::Relaxed);
1860}
1861
1862pub const KV_QUANT_DEFAULT: bool = true;
1886static KV_QUANT: std::sync::atomic::AtomicBool =
1887 std::sync::atomic::AtomicBool::new(KV_QUANT_DEFAULT);
1888fn kv_quant_on() -> bool {
1889 KV_QUANT.load(std::sync::atomic::Ordering::Relaxed)
1890}
1891pub fn set_kv_quant(on: bool) {
1892 KV_QUANT.store(on, std::sync::atomic::Ordering::Relaxed);
1893}
1894
1895pub const KV_HOIST_DEFAULT: bool = false;
1922static KV_HOIST: std::sync::atomic::AtomicBool =
1923 std::sync::atomic::AtomicBool::new(KV_HOIST_DEFAULT);
1924fn kv_hoist_on() -> bool {
1925 KV_HOIST.load(std::sync::atomic::Ordering::Relaxed)
1926}
1927pub fn set_kv_hoist(on: bool) {
1928 KV_HOIST.store(on, std::sync::atomic::Ordering::Relaxed);
1929}
1930pub fn kv_hoist_is_on() -> bool {
1932 kv_hoist_on()
1933}
1934
1935pub const POOL_T_DEFAULT: bool = false;
1954static POOL_T: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(POOL_T_DEFAULT);
1955fn pool_t_on() -> bool {
1956 POOL_T.load(std::sync::atomic::Ordering::Relaxed)
1957}
1958pub fn set_pool_t(on: bool) {
1959 POOL_T.store(on, std::sync::atomic::Ordering::Relaxed);
1960}
1961pub fn pool_t_is_on() -> bool {
1963 pool_t_on()
1964}
1965
1966pub fn kv_quant_is_on() -> bool {
1971 kv_quant_on()
1972}
1973
1974#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1984pub enum IdxQMode {
1985 F32,
1986 Q8,
1987 Bf16,
1988}
1989static IDXQ_MODE: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(1);
1990fn idxq_mode() -> IdxQMode {
1991 match IDXQ_MODE.load(std::sync::atomic::Ordering::Relaxed) {
1992 1 => IdxQMode::Q8,
1993 2 => IdxQMode::Bf16,
1994 _ => IdxQMode::F32,
1995 }
1996}
1997pub fn set_idxq(mode: &str) {
1998 let v = match mode {
1999 "q8" | "1" => 1,
2000 "bf16" => 2,
2001 _ => 0,
2002 };
2003 IDXQ_MODE.store(v, std::sync::atomic::Ordering::Relaxed);
2004}
2005
2006pub fn idxq_mode_name() -> &'static str {
2008 match idxq_mode() {
2009 IdxQMode::F32 => "f32",
2010 IdxQMode::Q8 => "q8",
2011 IdxQMode::Bf16 => "bf16",
2012 }
2013}
2014
2015fn idxq_audit_on() -> bool {
2021 static C: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2022 *C.get_or_init(|| std::env::var("MEMRA_Q4E_IDXQ_AUDIT").as_deref() == Ok("1"))
2023}
2024static IDXQ_AUDIT_ROWS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
2025static IDXQ_AUDIT_FLIPPED: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
2026static IDXQ_AUDIT_BLOCKS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
2027pub fn idxq_audit_stats() -> (u64, u64, u64) {
2030 (
2031 IDXQ_AUDIT_ROWS.load(std::sync::atomic::Ordering::Relaxed),
2032 IDXQ_AUDIT_FLIPPED.load(std::sync::atomic::Ordering::Relaxed),
2033 IDXQ_AUDIT_BLOCKS.load(std::sync::atomic::Ordering::Relaxed),
2034 )
2035}
2036
2037#[derive(Clone, Copy, PartialEq, Eq)]
2042enum LongAttMode {
2043 Auto,
2044 Force,
2045 Off,
2046}
2047static LONGATT_MODE: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(0);
2048fn longatt_mode() -> LongAttMode {
2049 match LONGATT_MODE.load(std::sync::atomic::Ordering::Relaxed) {
2050 1 => LongAttMode::Force,
2051 2 => LongAttMode::Off,
2052 _ => LongAttMode::Auto,
2053 }
2054}
2055pub fn set_longatt(mode: &str) {
2056 let v = match mode {
2057 "force" | "1" => 1,
2058 "off" | "0" => 2,
2059 _ => 0,
2060 };
2061 LONGATT_MODE.store(v, std::sync::atomic::Ordering::Relaxed);
2062}
2063
2064#[derive(Debug, Clone)]
2096pub struct Tp2Placement {
2097 by_layer: std::collections::BTreeMap<u32, Vec<u8>>,
2099 expert_count: usize,
2100 entry_rank: u8,
2101 strategy: String,
2102 source: String,
2103}
2104
2105#[derive(Debug, Clone)]
2109pub struct LayerPlacement {
2110 pub card1: Vec<u32>,
2115 local_of: Vec<u32>,
2117 rank_of: Vec<u8>,
2119}
2120
2121impl LayerPlacement {
2122 #[inline]
2123 pub fn rank(&self, expert: usize) -> u8 {
2124 self.rank_of[expert]
2125 }
2126 #[inline]
2127 pub fn local(&self, expert: usize) -> usize {
2128 self.local_of[expert] as usize
2129 }
2130 pub fn is_even(&self) -> bool {
2132 let half = self.rank_of.len() / 2;
2133 self.card1.len() == half
2134 && self
2135 .card1
2136 .iter()
2137 .enumerate()
2138 .all(|(i, &e)| e as usize == half + i)
2139 }
2140}
2141
2142impl Tp2Placement {
2143 pub fn even(expert_count: usize) -> Self {
2145 Self {
2146 by_layer: std::collections::BTreeMap::new(),
2147 expert_count,
2148 entry_rank: 0,
2149 strategy: "even".to_string(),
2150 source: "built-in (MEMRA_Q4E_EP_MAP unset)".to_string(),
2151 }
2152 }
2153
2154 pub fn strategy(&self) -> &str {
2155 &self.strategy
2156 }
2157 pub fn source(&self) -> &str {
2158 &self.source
2159 }
2160 pub fn entry_rank(&self) -> u8 {
2161 self.entry_rank
2162 }
2163
2164 pub fn from_env(expert_count: usize) -> Res<Option<Self>> {
2166 let Ok(path) = std::env::var("MEMRA_Q4E_EP_MAP") else {
2167 return Ok(None);
2168 };
2169 if path.is_empty() || path == "0" {
2170 return Ok(None);
2171 }
2172 Some(Self::load(std::path::Path::new(&path), expert_count)).transpose()
2173 }
2174
2175 pub fn load(path: &std::path::Path, expert_count: usize) -> Res<Self> {
2176 let text = std::fs::read_to_string(path)
2177 .map_err(|e| format!("MEMRA_Q4E_EP_MAP {}: {e}", path.display()))?;
2178 let v = memra_tokenizer::json::parse(&text)
2179 .map_err(|e| format!("MEMRA_Q4E_EP_MAP {}: {e}", path.display()))?;
2180 let want = |k: &str| -> Res<Self> {
2183 Err(format!("MEMRA_Q4E_EP_MAP {}: {k}", path.display()).into())
2184 };
2185 match v.get("format").and_then(|f| f.as_str()) {
2186 Some("memra-ep-map-v1") => {}
2187 other => {
2188 return want(&format!(
2189 "format is {other:?}, expected \"memra-ep-map-v1\" (mint it with \
2190 tools/build_expert_placement_map.py)"
2191 ));
2192 }
2193 }
2194 let ranks = v.get("ranks").and_then(|r| r.as_u64()).unwrap_or(0);
2195 if ranks != 2 {
2196 return want(&format!(
2197 "ranks={ranks}, but the TP2 route is exactly two cards"
2198 ));
2199 }
2200 let map_experts = v.get("expert_count").and_then(|r| r.as_u64()).unwrap_or(0) as usize;
2201 if map_experts != expert_count {
2202 return want(&format!(
2203 "expert_count={map_experts} but this plan has {expert_count} experts"
2204 ));
2205 }
2206 let entry_rank = v.get("entry_rank").and_then(|r| r.as_u64()).unwrap_or(0) as u8;
2207 if entry_rank > 1 {
2208 return want(&format!("entry_rank={entry_rank} outside {{0,1}}"));
2209 }
2210 let strategy = v
2211 .get("strategy")
2212 .and_then(|s| s.as_str())
2213 .unwrap_or("unnamed")
2214 .to_string();
2215 let Some(layers) = v.get("layers").and_then(|l| l.as_arr()) else {
2216 return want("no `layers` array");
2217 };
2218 let half = expert_count / 2;
2219 let mut by_layer = std::collections::BTreeMap::new();
2220 for row in layers {
2221 let Some(index) = row.get("layer").and_then(|l| l.as_u64()) else {
2222 return want("a layer row without an integer `layer`");
2223 };
2224 let Some(assign) = row.get("assignment").and_then(|a| a.as_arr()) else {
2225 return want(&format!("layer {index}: no `assignment` array"));
2226 };
2227 if assign.len() != expert_count {
2228 return want(&format!(
2229 "layer {index}: assignment has {} entries, expected {expert_count}",
2230 assign.len()
2231 ));
2232 }
2233 let mut ranks_vec = Vec::with_capacity(expert_count);
2234 for (eid, a) in assign.iter().enumerate() {
2235 match a.as_u64() {
2236 Some(r) if r <= 1 => ranks_vec.push(r as u8),
2237 other => {
2238 return want(&format!(
2239 "layer {index} expert {eid}: rank {other:?} outside {{0,1}}"
2240 ));
2241 }
2242 }
2243 }
2244 let on1 = ranks_vec.iter().filter(|&&r| r == 1).count();
2245 if on1 != half {
2246 return want(&format!(
2247 "layer {index}: card 1 owns {on1} experts but the bank halves are \
2248 equal-size allocations, so it must own exactly {half} — rebalance \
2249 the map (build_expert_placement_map.py --balance-tolerance)"
2250 ));
2251 }
2252 by_layer.insert(index as u32, ranks_vec);
2253 }
2254 if by_layer.is_empty() {
2255 return want("`layers` is empty");
2256 }
2257 Ok(Self {
2258 by_layer,
2259 expert_count,
2260 entry_rank,
2261 strategy,
2262 source: path.display().to_string(),
2263 })
2264 }
2265
2266 pub fn layer(&self, index: u32, expert_count: usize) -> Res<LayerPlacement> {
2270 if expert_count != self.expert_count {
2271 return Err(format!(
2272 "qwen4exp_gpu tp2 placement: layer {index} has {expert_count} experts, \
2273 map is for {}",
2274 self.expert_count
2275 )
2276 .into());
2277 }
2278 let half = expert_count / 2;
2279 let rank_of: Vec<u8> = if self.by_layer.is_empty() {
2280 (0..expert_count).map(|e| u8::from(e >= half)).collect()
2281 } else {
2282 self.by_layer
2283 .get(&index)
2284 .ok_or_else(|| {
2285 format!(
2286 "qwen4exp_gpu tp2 placement: map {} does not cover MoE layer \
2287 {index} (fail-closed; a partly-applied map is not a placement)",
2288 self.source
2289 )
2290 })?
2291 .clone()
2292 };
2293 let card1: Vec<u32> = (0..expert_count)
2294 .filter(|&e| rank_of[e] == 1)
2295 .map(|e| e as u32)
2296 .collect();
2297 let mut local_of = vec![0u32; expert_count];
2298 for (slot, &eid) in card1.iter().enumerate() {
2299 local_of[eid as usize] = slot as u32;
2300 }
2301 for e in 0..expert_count {
2303 if rank_of[e] == 0 {
2304 local_of[e] = e as u32;
2305 }
2306 }
2307 Ok(LayerPlacement {
2308 card1,
2309 local_of,
2310 rank_of,
2311 })
2312 }
2313}
2314
2315static TP2_PEER_EXPERT_SLOTS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
2322static TP2_HOME_EXPERT_SLOTS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
2327static TP2_BOTH_TOUCH_ROWS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
2330static TP2_TOUCH_ROWS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
2331
2332pub fn tp2_expert_split_stats() -> (u64, u64, u64, u64) {
2334 use std::sync::atomic::Ordering::Relaxed;
2335 (
2336 TP2_PEER_EXPERT_SLOTS.load(Relaxed),
2337 TP2_HOME_EXPERT_SLOTS.load(Relaxed),
2338 TP2_BOTH_TOUCH_ROWS.load(Relaxed),
2339 TP2_TOUCH_ROWS.load(Relaxed),
2340 )
2341}
2342
2343fn tp2_count_split(routes0: &[Vec<(usize, f32)>], routes1: &[Vec<(usize, f32)>]) {
2344 use std::sync::atomic::Ordering::Relaxed;
2345 let (mut peer, mut home, mut both) = (0u64, 0u64, 0u64);
2346 for (r0, r1) in routes0.iter().zip(routes1.iter()) {
2347 home += r0.len() as u64;
2348 peer += r1.len() as u64;
2349 if !r0.is_empty() && !r1.is_empty() {
2350 both += 1;
2351 }
2352 }
2353 TP2_HOME_EXPERT_SLOTS.fetch_add(home, Relaxed);
2354 TP2_PEER_EXPERT_SLOTS.fetch_add(peer, Relaxed);
2355 TP2_BOTH_TOUCH_ROWS.fetch_add(both, Relaxed);
2356 TP2_TOUCH_ROWS.fetch_add(routes0.len() as u64, Relaxed);
2357}
2358
2359#[derive(Clone, Copy, PartialEq, Eq, Debug)]
2364pub enum Tp2GateRed {
2365 None,
2366 SkipPeerMoe,
2368 PeerLocalIds,
2371 ReverseePeerWeights,
2373}
2374
2375fn tp2_gate_red() -> Res<Tp2GateRed> {
2376 static C: std::sync::OnceLock<Result<Tp2GateRed, String>> = std::sync::OnceLock::new();
2377 C.get_or_init(
2378 || match std::env::var("MEMRA_Q4E_TP2_GATE_RED").as_deref() {
2379 Err(_) | Ok("") | Ok("0") | Ok("none") => Ok(Tp2GateRed::None),
2380 Ok("skip-peer-moe") => Ok(Tp2GateRed::SkipPeerMoe),
2381 Ok("peer-local-ids") => Ok(Tp2GateRed::PeerLocalIds),
2382 Ok("reverse-peer-weights") => Ok(Tp2GateRed::ReverseePeerWeights),
2383 Ok(other) => Err(format!(
2384 "MEMRA_Q4E_TP2_GATE_RED={other:?}: want skip-peer-moe|peer-local-ids|\
2385 reverse-peer-weights|none"
2386 )),
2387 },
2388 )
2389 .clone()
2390 .map_err(Into::into)
2391}
2392
2393fn trace_moe_routes(layer: u32, t: usize, routes: &[Vec<(usize, f32)>]) {
2411 use std::io::Write as _;
2412 static IDS: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
2413 static WEIGHTS: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
2414 let ids = IDS.get_or_init(|| {
2415 std::env::var("MEMRA_MOE_TRACE")
2416 .ok()
2417 .filter(|p| !p.is_empty())
2418 });
2419 let weights = WEIGHTS.get_or_init(|| {
2420 std::env::var("MEMRA_MOE_WEIGHT_TRACE")
2421 .ok()
2422 .filter(|p| !p.is_empty())
2423 });
2424 if ids.is_none() && weights.is_none() {
2425 return;
2426 }
2427 let flat: Vec<&(usize, f32)> = routes.iter().flatten().collect();
2431 let append = |path: &str, body: String| {
2432 if let Ok(mut f) = std::fs::OpenOptions::new()
2433 .create(true)
2434 .append(true)
2435 .open(path)
2436 {
2437 let _ = writeln!(f, "{layer} {t} {body}");
2438 }
2439 };
2440 if let Some(path) = ids {
2441 let body: Vec<String> = flat.iter().map(|(e, _)| e.to_string()).collect();
2442 append(path, body.join(","));
2443 }
2444 if let Some(path) = weights {
2445 let body: Vec<String> = flat.iter().map(|(e, w)| format!("{e}:{w:.9}")).collect();
2446 append(path, body.join(","));
2447 }
2448}
2449
2450pub fn set_seam(name: &str, on: bool, value: Option<&str>) -> bool {
2462 seam_dispatch(name, on, value, true)
2463}
2464
2465pub fn seam_state(name: &str) -> Option<bool> {
2474 Some(match name {
2475 "moe" => moe_sel_path_on(),
2476 "hc" => hc_fused_gate_on(),
2477 "trunk" => trunk_bf16_on(),
2478 "ws" => step_ws_on(),
2479 "graph" => decode_graphs_on(),
2480 "selv2" => sel_v2_on(),
2481 "hcmicro" => hc_micro_on(),
2482 "selv3" => sel_v3_on(),
2483 "gdnstep" => gdn_step_on(),
2484 "gdnfuse" => gdn_fuse_on(),
2485 "projstack" => proj_stack_on(),
2486 "hcdiet" => hc_diet_on(),
2487 "gufuse" => sel_gufuse_on(),
2488 "routerb16" => router_bf16_on(),
2489 "vgraph" => verify_graphs_on(),
2490 "idxdev" => idx_dev_on(),
2491 "idxsel" => idx_sel_on(),
2492 "plecache" => ple_cache_on(),
2493 "routerdev" => router_dev_on(),
2494 "idxcache" => idx_cache_on(),
2495 "kvq" => kv_quant_on(),
2496 "kvhoist" => kv_hoist_on(),
2497 "poolT" => pool_t_on(),
2498 _ => return None,
2499 })
2500}
2501
2502pub fn seam_exists(name: &str) -> bool {
2507 seam_dispatch(name, false, None, false)
2508}
2509
2510pub fn seam_names() -> &'static [&'static str] {
2519 &[
2520 "moe",
2521 "hc",
2522 "trunk",
2523 "ws",
2524 "graph",
2525 "selv2",
2526 "hcmicro",
2527 "selv3",
2528 "gdnstep",
2529 "gdnfuse",
2530 "projstack",
2531 "hcdiet",
2532 "gufuse",
2533 "routerb16",
2534 "vgraph",
2535 "longatt",
2536 "idxdev",
2537 "idxsel",
2538 "plecache",
2539 "routerdev",
2540 "idxcache",
2541 "kvq",
2542 "idxq",
2543 "kvhoist",
2544 "poolT",
2545 ]
2546}
2547
2548fn seam_dispatch(name: &str, on: bool, value: Option<&str>, apply: bool) -> bool {
2551 macro_rules! seam {
2552 ($call:expr) => {{
2553 if apply {
2554 $call;
2555 }
2556 true
2557 }};
2558 }
2559 match name {
2560 "moe" => seam!(set_moe_sel_path(on)),
2561 "hc" => seam!(set_hc_fused_gate(on)),
2562 "trunk" => seam!(set_trunk_bf16(on)),
2563 "ws" => seam!(set_step_ws(on)),
2564 "graph" => seam!(set_decode_graphs(on)),
2565 "selv2" => seam!(set_sel_v2(on)),
2566 "hcmicro" => seam!(set_hc_micro(on)),
2567 "selv3" => seam!(set_sel_v3(on)),
2568 "gdnstep" => seam!(set_gdn_step(on)),
2569 "gdnfuse" => seam!(set_gdn_fuse(on)),
2570 "projstack" => seam!(set_proj_stack(on)),
2571 "hcdiet" => seam!(set_hc_diet(on)),
2572 "gufuse" => seam!(set_sel_gufuse(on)),
2573 "routerb16" => seam!(set_router_bf16(on)),
2574 "vgraph" => seam!(set_verify_graphs(on)),
2575 "longatt" => seam!(set_longatt(if on { "force" } else { "off" })),
2576 "idxdev" => seam!(set_idx_dev(on)),
2577 "idxsel" => seam!(set_idx_sel(on)),
2578 "plecache" => seam!(set_ple_cache(on)),
2579 "routerdev" => seam!(set_router_dev(on)),
2580 "idxcache" => seam!(set_idx_cache(on)),
2581 "kvq" => seam!(set_kv_quant(on)),
2582 "kvhoist" => seam!(set_kv_hoist(on)),
2585 "poolT" => seam!(set_pool_t(on)),
2586 "idxq" => seam!(set_idxq(value.unwrap_or("q8"))),
2589 _ => {
2590 debug_assert!(
2591 !seam_names().contains(&name),
2592 "seam_names() lists {name:?} but seam_dispatch has no arm for it"
2593 );
2594 false
2595 }
2596 }
2597}
2598
2599pub fn apply_env_seams() {
2600 let Ok(spec) = std::env::var("MEMRA_Q4E_SEAMS") else {
2601 return;
2602 };
2603 for part in spec.split(',').filter(|p| !p.is_empty()) {
2604 let (name, on) = match part.split_once('=') {
2605 Some((n, v)) => (n, v != "0"),
2606 None => (part, true),
2607 };
2608 if !set_seam(name, on, part.split_once('=').map(|(_, v)| v)) {
2609 eprintln!("MEMRA_Q4E_SEAMS: unknown seam {name:?} ignored");
2610 }
2611 }
2612}
2613
2614fn micro_env_on(name: &'static str, cell: &'static std::sync::OnceLock<bool>) -> bool {
2618 *cell.get_or_init(|| std::env::var(name).as_deref() != Ok("0"))
2619}
2620
2621fn micro_norm_on() -> bool {
2622 static C: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2623 hc_micro_on() && micro_env_on("MEMRA_Q4E_MICRO_NORM", &C)
2624}
2625
2626fn micro_inj_on() -> bool {
2627 static C: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2628 hc_micro_on() && micro_env_on("MEMRA_Q4E_MICRO_INJ", &C)
2629}
2630
2631fn micro_shexp_on() -> bool {
2632 static C: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2633 hc_micro_on() && micro_env_on("MEMRA_Q4E_MICRO_SHEXP", &C)
2634}
2635
2636fn prof_section<T>(e: &Engine, name: &'static str, f: impl FnOnce() -> Res<T>) -> Res<T> {
2638 if !prof::on() {
2639 return f();
2640 }
2641 e.gpu.stream().synchronize()?;
2642 let t0 = std::time::Instant::now();
2643 let out = f()?;
2644 e.gpu.stream().synchronize()?;
2645 prof::add(name, t0.elapsed().as_secs_f64());
2646 Ok(out)
2647}
2648
2649fn host_sigmoid(x: f32) -> f32 {
2652 1.0 / (1.0 + (-x).exp())
2653}
2654
2655fn host_softmax(values: &mut [f32]) {
2657 let max = values.iter().copied().fold(f32::NEG_INFINITY, f32::max);
2658 let mut sum = 0.0;
2659 for value in values.iter_mut() {
2660 *value = (*value - max).exp();
2661 sum += *value;
2662 }
2663 for value in values {
2664 *value /= sum;
2665 }
2666}
2667
2668const ROUTE_DENOM_FLOOR: f32 = 6.103_515_6e-5;
2673
2674fn host_route_softmax_topk(logits: &[f32], selected: usize) -> Vec<(usize, f32)> {
2677 let mut weights = logits.to_vec();
2678 host_softmax(&mut weights);
2679 let mut indices: Vec<usize> = (0..logits.len()).collect();
2680 indices.sort_by(|&left, &right| {
2681 weights[right]
2682 .total_cmp(&weights[left])
2683 .then(left.cmp(&right))
2684 });
2685 indices.truncate(selected);
2686 let denominator = indices
2687 .iter()
2688 .map(|&index| weights[index])
2689 .sum::<f32>()
2690 .max(ROUTE_DENOM_FLOOR);
2691 indices
2692 .into_iter()
2693 .map(|index| (index, weights[index] / denominator))
2694 .collect()
2695}
2696
2697fn host_rms_norm(x: &mut [f32], width: usize, weight: &[f32], epsilon: f32) {
2699 for row in x.chunks_exact_mut(width) {
2700 let mean_square = row.iter().map(|v| v * v).sum::<f32>() / width as f32;
2701 let inverse = 1.0 / (mean_square + epsilon).sqrt();
2702 for (value, w) in row.iter_mut().zip(weight) {
2703 *value = *value * inverse * w;
2704 }
2705 }
2706}
2707
2708fn host_rope_at(
2713 values: &mut [f32],
2714 head_dim: usize,
2715 dimensions: usize,
2716 base: f32,
2717 yarn: Option<(&[f32], f32)>,
2718 position: usize,
2719) {
2720 let dimensions = dimensions.min(head_dim) / 2 * 2;
2721 let half = dimensions / 2;
2722 for head in values.chunks_exact_mut(head_dim) {
2723 for index in 0..half {
2724 let frequency = base.powf(-2.0 * index as f32 / dimensions as f32);
2725 let frequency = match yarn {
2726 Some((ff, _)) => frequency / ff[index],
2727 None => frequency,
2728 };
2729 let angle = position as f32 * frequency;
2730 let (sin, cos) = angle.sin_cos();
2731 let (sin, cos) = match yarn {
2732 Some((_, mscale)) => (sin * mscale, cos * mscale),
2733 None => (sin, cos),
2734 };
2735 let first = head[index];
2736 let second = head[index + half];
2737 head[index] = first * cos - second * sin;
2738 head[index + half] = first * sin + second * cos;
2739 }
2740 }
2741}
2742
2743#[derive(Clone, Copy, PartialEq, Eq)]
2746pub enum HeadMode {
2747 All,
2749 LastRow,
2751 Skip,
2753}
2754
2755struct RowSel {
2759 full: bool,
2761 blocks: Vec<u32>,
2763 visible: usize,
2766}
2767
2768#[allow(clippy::too_many_arguments)]
2774fn extend_pooled_keys(
2775 pooled_keys: &mut Vec<f32>,
2776 raw_keys: &IdxRawCache,
2777 head_dim: usize,
2778 block_size: usize,
2779 idx_k_norm: &[f32],
2780 epsilon: f32,
2781 rope_dims: usize,
2782 rope_base: f32,
2783 yarn: Option<(&[f32], f32)>,
2784 pos_off: usize,
2785) {
2786 let complete_total = raw_keys.rows(head_dim) / block_size;
2787 let cached = pooled_keys.len() / head_dim;
2788 let mut block_rows: Vec<f32> = Vec::new();
2789 for block in cached..complete_total {
2790 let start = block * block_size;
2791 raw_keys.rows_f32(start, block_size, head_dim, &mut block_rows);
2794 let mut pooled = vec![0.0f32; head_dim];
2795 for offset in 0..block_size {
2796 for dim in 0..head_dim {
2797 pooled[dim] += block_rows[offset * head_dim + dim];
2798 }
2799 }
2800 for value in &mut pooled {
2801 *value /= block_size as f32;
2802 }
2803 host_rms_norm(&mut pooled, head_dim, idx_k_norm, epsilon);
2804 host_rope_at(
2805 &mut pooled,
2806 head_dim,
2807 rope_dims,
2808 rope_base,
2809 yarn,
2810 start + pos_off,
2811 );
2812 pooled_keys.extend_from_slice(&pooled);
2813 }
2814}
2815
2816#[inline]
2819fn sel_cmp(scores: &[f32], a: u32, b: u32) -> std::cmp::Ordering {
2820 scores[b as usize]
2821 .total_cmp(&scores[a as usize])
2822 .then(a.cmp(&b))
2823}
2824
2825fn top_blocks_ascending(scores: &[f32], budget: usize, threads: usize) -> Vec<u32> {
2835 fn cut(scores: &[f32], idx: &mut Vec<u32>, budget: usize) {
2836 let k = budget.min(idx.len());
2837 if k < idx.len() {
2838 idx.select_nth_unstable_by(k - 1, |&a, &b| sel_cmp(scores, a, b));
2839 idx.truncate(k);
2840 }
2841 }
2842 let complete = scores.len();
2843 debug_assert!(budget < complete);
2844 const PAR_MIN: usize = 1 << 15;
2845 let mut candidates: Vec<u32> = if threads > 1 && complete >= PAR_MIN {
2846 let ranges: Vec<(u32, u32)> = {
2847 let per = complete.div_ceil(threads);
2848 (0..threads)
2849 .map(|i| ((i * per) as u32, ((i + 1) * per).min(complete) as u32))
2850 .filter(|(a, b)| a < b)
2851 .collect()
2852 };
2853 std::thread::scope(|scope| {
2854 let handles: Vec<_> = ranges
2855 .iter()
2856 .map(|&(a, b)| {
2857 scope.spawn(move || {
2858 let mut idx: Vec<u32> = (a..b).collect();
2859 cut(scores, &mut idx, budget);
2860 idx
2861 })
2862 })
2863 .collect();
2864 handles
2865 .into_iter()
2866 .flat_map(|h| h.join().unwrap())
2867 .collect()
2868 })
2869 } else {
2870 (0..complete as u32).collect()
2871 };
2872 cut(scores, &mut candidates, budget);
2873 candidates.sort_unstable();
2874 candidates
2875}
2876
2877fn score_blocks(
2882 query: &[f32],
2883 pooled_keys: &[f32],
2884 heads: usize,
2885 head_dim: usize,
2886 complete: usize,
2887 scale: f32,
2888 threads: usize,
2889) -> Vec<f32> {
2890 let mut scores = vec![0.0f32; complete];
2891 let run = |scores: &mut [f32], block0: usize| {
2892 for (i, slot) in scores.iter_mut().enumerate() {
2893 let block = block0 + i;
2894 let pooled = &pooled_keys[block * head_dim..(block + 1) * head_dim];
2895 let mut score = 0.0f32;
2896 for head in 0..heads {
2897 let mut dot = 0.0f32;
2898 for dim in 0..head_dim {
2899 dot += query[head * head_dim + dim] * pooled[dim];
2900 }
2901 score += dot.max(0.0);
2902 }
2903 *slot = score / scale;
2904 }
2905 };
2906 const PAR_MIN: usize = 1 << 14;
2907 if threads > 1 && complete >= PAR_MIN {
2908 let per = complete.div_ceil(threads);
2909 let run = &run;
2910 std::thread::scope(|scope| {
2911 for (i, chunk) in scores.chunks_mut(per).enumerate() {
2912 scope.spawn(move || run(chunk, i * per));
2913 }
2914 });
2915 } else {
2916 run(&mut scores, 0);
2917 }
2918 scores
2919}
2920
2921#[allow(clippy::too_many_arguments)]
2929#[allow(clippy::too_many_arguments)]
2930fn indexer_select_rows(
2931 overlay: &MicroBlockIndexPlan,
2932 rope_base: f32,
2933 yarn: Option<(&[f32], f32)>,
2936 epsilon: f32,
2937 idx_q_norm: &[f32],
2938 idx_k_norm: &[f32],
2939 proj_rows: &[f32], raw_keys: &IdxRawCache, pooled_keys: &mut Vec<f32>,
2942 mut dev: Option<(&Engine, &mut Option<CudaSlice<f32>>, &mut usize)>,
2948 base_pos: usize,
2949 t: usize,
2950 t_kv: usize,
2951 pos_off: usize,
2955) -> Res<Vec<RowSel>> {
2956 let heads = overlay.query_heads as usize;
2957 let head_dim = overlay.head_dim as usize;
2958 let block_size = overlay.block_size as usize;
2959 let budget_blocks = overlay.budget_blocks as usize;
2960 let rope_dims = overlay.rope_dimensions as usize;
2961 let qk_width = (heads + overlay.kv_heads as usize) * head_dim;
2962 let scale = (head_dim as f32).sqrt();
2963 debug_assert_eq!(raw_keys.rows(head_dim), t_kv);
2964 extend_pooled_keys(
2965 pooled_keys,
2966 raw_keys,
2967 head_dim,
2968 block_size,
2969 idx_k_norm,
2970 epsilon,
2971 rope_dims,
2972 rope_base,
2973 yarn,
2974 pos_off,
2975 );
2976 let threads = std::thread::available_parallelism()
2977 .map(|n| n.get())
2978 .unwrap_or(1);
2979 if let Some((e, mirror, mirrored)) = dev.as_mut() {
2983 let rows_needed: Vec<usize> = (0..t)
2984 .map(|qt| (base_pos + qt + 1) / block_size)
2985 .filter(|&c| c > budget_blocks)
2986 .collect();
2987 if let Some(&max_blocks) = rows_needed.iter().max() {
2988 let pooled_rows = pooled_keys.len() / head_dim;
2989 let want = pooled_rows.max(max_blocks);
2991 if mirror
2996 .as_ref()
2997 .is_none_or(|m| m.len() < want * head_dim * POOL_PLANES)
2998 {
2999 let cap_rows = want.next_power_of_two().max(1024);
3000 let fresh = e.zeros(cap_rows * head_dim * POOL_PLANES)?;
3001 **mirror = Some(fresh);
3008 **mirrored = 0;
3009 }
3010 let m = mirror.as_mut().expect("allocated above");
3011 if pooled_rows > **mirrored {
3012 let delta = &pooled_keys[**mirrored * head_dim..pooled_rows * head_dim];
3013 let mut view = m.slice_mut(**mirrored * head_dim..pooled_rows * head_dim);
3014 e.gpu.stream().memcpy_htod(delta, &mut view)?;
3015 let cap_rows = m.len() / (head_dim * POOL_PLANES);
3037 launch_qsa_pooled_transpose(
3038 e,
3039 m,
3040 **mirrored,
3041 pooled_rows - **mirrored,
3042 head_dim,
3043 cap_rows,
3044 )?;
3045 **mirrored = pooled_rows;
3046 }
3047 let mut sels: Vec<RowSel> = Vec::with_capacity(t);
3049 let mut queries: Vec<f32> = Vec::new();
3050 let mut scored_rows: Vec<usize> = Vec::new();
3051 for qt in 0..t {
3052 let row = base_pos + qt;
3053 let visible = row + 1;
3054 let complete = visible / block_size;
3055 if complete <= budget_blocks {
3056 sels.push(RowSel {
3057 full: true,
3058 blocks: Vec::new(),
3059 visible,
3060 });
3061 continue;
3062 }
3063 let mut query = proj_rows[qt * qk_width..qt * qk_width + heads * head_dim].to_vec();
3064 host_rms_norm(&mut query, head_dim, idx_q_norm, epsilon);
3065 host_rope_at(
3066 &mut query,
3067 head_dim,
3068 rope_dims,
3069 rope_base,
3070 yarn,
3071 row + pos_off,
3072 );
3073 queries.extend_from_slice(&query);
3074 scored_rows.push(qt);
3075 sels.push(RowSel {
3076 full: false,
3077 blocks: Vec::new(),
3078 visible,
3079 });
3080 }
3081 let score_cap_mf: usize = std::env::var("MEMRA_Q4E_IDX_SCORE_CAP_MF")
3098 .ok()
3099 .and_then(|v| v.parse::<usize>().ok())
3100 .filter(|v| *v > 0)
3101 .unwrap_or(32);
3102 let score_cap: usize = score_cap_mf << 20;
3103 #[allow(non_snake_case)]
3104 let SCORE_CAP = score_cap;
3105 let mut done = 0usize;
3106 while done < scored_rows.len() {
3107 let batch_max = scored_rows[done..]
3110 .iter()
3111 .map(|&qt| (base_pos + qt + 1) / block_size)
3112 .max()
3113 .unwrap_or(0);
3114 let per = (SCORE_CAP / batch_max.max(1)).max(1);
3115 let n = per.min(scored_rows.len() - done);
3116 let qslab = &queries[done * heads * head_dim..(done + n) * heads * head_dim];
3117 let q_dev = e.htod(qslab)?;
3118 let mut scores_dev = e.uninit(n * batch_max)?;
3119 launch_qsa_index_score(
3120 e,
3121 &q_dev,
3122 m,
3123 &mut scores_dev,
3124 heads,
3125 head_dim,
3126 batch_max,
3127 n,
3128 scale,
3129 )?;
3130 if idx_sel_on() {
3131 let counts: Vec<usize> = (0..n)
3136 .map(|i| (base_pos + scored_rows[done + i] + 1) / block_size)
3137 .collect();
3138 let picked =
3139 launch_qsa_index_topk(e, &scores_dev, &counts, batch_max, budget_blocks)?;
3140 if idx_sel_audit_on() {
3141 let host = e.dtoh(&scores_dev)?;
3142 let mut mismatched = 0u64;
3143 let mut deepest = 0u64;
3144 for i in 0..n {
3145 let complete = counts[i];
3146 let row_scores = &host[i * batch_max..i * batch_max + complete];
3147 let twin = top_blocks_ascending(row_scores, budget_blocks, threads);
3148 if twin != picked[i] {
3149 mismatched += 1;
3150 }
3151 deepest = deepest.max(complete as u64);
3152 }
3153 IDX_SEL_AUDIT_ROWS
3154 .fetch_add(n as u64, std::sync::atomic::Ordering::Relaxed);
3155 IDX_SEL_AUDIT_MISMATCH
3156 .fetch_add(mismatched, std::sync::atomic::Ordering::Relaxed);
3157 IDX_SEL_AUDIT_MAX_BLOCKS
3158 .fetch_max(deepest, std::sync::atomic::Ordering::Relaxed);
3159 if mismatched > 0 {
3160 return Err(format!(
3161 "idxsel audit: {mismatched} of {n} device selections differ \
3162 from the host twin (ids or order) at fill {t_kv}"
3163 )
3164 .into());
3165 }
3166 }
3167 for (i, blocks) in picked.into_iter().enumerate() {
3168 sels[scored_rows[done + i]].blocks = blocks;
3169 }
3170 } else {
3171 let host = e.dtoh(&scores_dev)?;
3172 for i in 0..n {
3173 let qt = scored_rows[done + i];
3174 let complete = (base_pos + qt + 1) / block_size;
3175 let row_scores = &host[i * batch_max..i * batch_max + complete];
3176 sels[qt].blocks = top_blocks_ascending(row_scores, budget_blocks, threads);
3177 }
3178 }
3179 done += n;
3180 }
3181 for sel in &sels {
3182 if sel.visible == 0
3183 || (!sel.full && sel.blocks.is_empty() && sel.visible % block_size == 0)
3184 {
3185 return Err("indexer selection left a query with no visible source".into());
3186 }
3187 }
3188 return Ok(sels);
3189 }
3190 }
3191 let pooled_ref: &[f32] = pooled_keys;
3192 let select_row = |qt: usize, threads_in_row: usize| -> RowSel {
3193 let row = base_pos + qt;
3194 let position = row + pos_off;
3195 let visible = row + 1;
3196 let complete = visible / block_size;
3197 if complete <= budget_blocks {
3204 return RowSel {
3205 full: true,
3206 blocks: Vec::new(),
3207 visible,
3208 };
3209 }
3210 let mut query = proj_rows[qt * qk_width..qt * qk_width + heads * head_dim].to_vec();
3211 host_rms_norm(&mut query, head_dim, idx_q_norm, epsilon);
3212 host_rope_at(&mut query, head_dim, rope_dims, rope_base, yarn, position);
3213 let scores = score_blocks(
3214 &query,
3215 pooled_ref,
3216 heads,
3217 head_dim,
3218 complete,
3219 scale,
3220 threads_in_row,
3221 );
3222 let blocks = top_blocks_ascending(&scores, budget_blocks, threads_in_row);
3223 RowSel {
3224 full: false,
3225 blocks,
3226 visible,
3227 }
3228 };
3229 const ROW_PAR_MIN_WORK: usize = 1 << 16;
3230 let total_scored_blocks: usize = (0..t)
3231 .map(|qt| {
3232 let complete = (base_pos + qt + 1) / block_size;
3233 if complete <= budget_blocks {
3234 0
3235 } else {
3236 complete
3237 }
3238 })
3239 .sum();
3240 let sels: Vec<RowSel> = if t > 1 && threads > 1 && total_scored_blocks >= ROW_PAR_MIN_WORK {
3241 let cursor = std::sync::atomic::AtomicUsize::new(0);
3244 let mut out: Vec<Option<RowSel>> = (0..t).map(|_| None).collect();
3245 let slots = std::sync::Mutex::new(&mut out);
3246 std::thread::scope(|scope| {
3247 for _ in 0..threads.min(t) {
3248 scope.spawn(|| {
3249 loop {
3250 let qt = cursor.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3251 if qt >= t {
3252 break;
3253 }
3254 let sel = select_row(qt, 1);
3255 slots.lock().unwrap()[qt] = Some(sel);
3256 }
3257 });
3258 }
3259 });
3260 out.into_iter().map(|s| s.unwrap()).collect()
3261 } else {
3262 (0..t).map(|qt| select_row(qt, threads)).collect()
3263 };
3264 for sel in &sels {
3265 if sel.visible == 0 || (!sel.full && sel.blocks.is_empty() && sel.visible % block_size == 0)
3266 {
3267 return Err("indexer selection left a query with no visible source".into());
3268 }
3269 }
3270 Ok(sels)
3271}
3272
3273fn rowsel_to_mask(sels: &[RowSel], block_size: usize, t_kv: usize) -> Vec<u8> {
3276 let t = sels.len();
3277 let mut mask = vec![0u8; t * t_kv];
3278 for (qt, sel) in sels.iter().enumerate() {
3279 let row = &mut mask[qt * t_kv..(qt + 1) * t_kv];
3280 if sel.full {
3281 for slot in row.iter_mut().take(sel.visible) {
3282 *slot = 1;
3283 }
3284 continue;
3285 }
3286 for &block in &sel.blocks {
3287 for offset in 0..block_size {
3288 row[block as usize * block_size + offset] = 1;
3289 }
3290 }
3291 let complete = sel.visible / block_size;
3292 for slot in row.iter_mut().take(sel.visible).skip(complete * block_size) {
3293 *slot = 1;
3294 }
3295 }
3296 mask
3297}
3298
3299fn rowsel_positions(sels: &[RowSel], block_size: usize) -> (Vec<i32>, Vec<i32>, usize) {
3304 let mut flat: Vec<i32> = Vec::new();
3305 let mut meta: Vec<i32> = Vec::with_capacity(sels.len() * 2);
3306 let mut max_count = 0usize;
3307 for sel in sels {
3308 let start = flat.len();
3309 if sel.full {
3310 flat.extend(0..sel.visible as i32);
3311 } else {
3312 for &block in &sel.blocks {
3313 let first = block as usize * block_size;
3314 flat.extend(first as i32..(first + block_size) as i32);
3315 }
3316 let complete = sel.visible / block_size;
3317 flat.extend((complete * block_size) as i32..sel.visible as i32);
3318 }
3319 let count = flat.len() - start;
3320 max_count = max_count.max(count);
3321 meta.push(start as i32);
3322 meta.push(count as i32);
3323 }
3324 (flat, meta, max_count)
3325}
3326
3327#[allow(clippy::too_many_arguments)]
3331fn launch_qsa_index_score(
3332 e: &Engine,
3333 q: &CudaSlice<f32>,
3334 pooled: &CudaSlice<f32>,
3335 out: &mut CudaSlice<f32>,
3336 heads: usize,
3337 head_dim: usize,
3338 n_blocks: usize,
3339 rows: usize,
3340 scale: f32,
3341) -> Res<()> {
3342 if rows == 0 || n_blocks == 0 {
3343 return Ok(());
3344 }
3345 if out.len() < rows * n_blocks {
3346 return Err("qsa_index_score_f32: score slab too short".into());
3347 }
3348 if rows > 65535 {
3349 return Err("qsa_index_score_f32: rows exceed grid.y (caller sub-batches)".into());
3350 }
3351 let cap_rows = pooled.len() / (head_dim * POOL_PLANES);
3356 let pool_t = pool_t_on();
3357 if pool_t && cap_rows < n_blocks {
3358 return Err("qsa_index_score_f32_t: pooled plane capacity below n_blocks".into());
3359 }
3360 let f = e.func(if pool_t {
3361 "qsa_index_score_f32_t"
3362 } else {
3363 "qsa_index_score_f32"
3364 });
3365 const TPB: usize = 128;
3366 let cfg = LaunchConfig {
3367 grid_dim: (n_blocks.div_ceil(TPB) as u32, rows as u32, 1),
3368 block_dim: (TPB as u32, 1, 1),
3369 shared_mem_bytes: 0,
3370 };
3371 let (h, hd, nb, r) = (heads as i32, head_dim as i32, n_blocks as i32, rows as i32);
3372 let pitch = cap_rows as i64;
3373 let stream = e.gpu.stream();
3374 if pool_t {
3375 let plane = pooled.slice(cap_rows * head_dim..cap_rows * head_dim * POOL_PLANES);
3378 let mut b = stream.launch_builder(&f);
3379 b.arg(q)
3380 .arg(&plane)
3381 .arg(&mut *out)
3382 .arg(&h)
3383 .arg(&hd)
3384 .arg(&nb)
3385 .arg(&r)
3386 .arg(&scale)
3387 .arg(&pitch);
3388 unsafe {
3389 b.launch(cfg)?;
3390 }
3391 return Ok(());
3392 }
3393 let mut b = stream.launch_builder(&f);
3394 b.arg(q)
3395 .arg(pooled)
3396 .arg(&mut *out)
3397 .arg(&h)
3398 .arg(&hd)
3399 .arg(&nb)
3400 .arg(&r)
3401 .arg(&scale);
3402 unsafe {
3403 b.launch(cfg)?;
3404 }
3405 Ok(())
3406}
3407
3408const POOL_PLANES: usize = 2;
3412
3413fn launch_qsa_pooled_transpose(
3416 e: &Engine,
3417 buf: &mut CudaSlice<f32>,
3418 r0: usize,
3419 rows: usize,
3420 head_dim: usize,
3421 cap_rows: usize,
3422) -> Res<()> {
3423 if rows == 0 {
3424 return Ok(());
3425 }
3426 if r0 + rows > cap_rows {
3427 return Err("qsa_pooled_transpose_f32: delta exceeds the plane capacity".into());
3428 }
3429 let f = e.func("qsa_pooled_transpose_f32");
3430 const TPB: usize = 128;
3431 let cfg = LaunchConfig {
3432 grid_dim: (rows.div_ceil(TPB) as u32, head_dim as u32, 1),
3433 block_dim: (TPB as u32, 1, 1),
3434 shared_mem_bytes: 0,
3435 };
3436 let (r, hd, r0i) = (rows as i32, head_dim as i32, r0 as i32);
3437 let cap = cap_rows as i64;
3438 let stream = e.gpu.stream();
3439 let mut b = stream.launch_builder(&f);
3440 b.arg(buf).arg(&r).arg(&hd).arg(&r0i).arg(&cap);
3441 unsafe {
3442 b.launch(cfg)?;
3443 }
3444 Ok(())
3445}
3446
3447fn launch_qsa_index_topk(
3453 e: &Engine,
3454 scores: &CudaSlice<f32>,
3455 counts: &[usize],
3456 stride: usize,
3457 budget: usize,
3458) -> Res<Vec<Vec<u32>>> {
3459 let rows = counts.len();
3460 if rows == 0 || budget == 0 {
3461 return Ok(Vec::new());
3462 }
3463 if rows > 65535 {
3464 return Err("qsa_index_topk_u32: rows exceed grid.x (caller sub-batches)".into());
3465 }
3466 if scores.len() < rows * stride {
3467 return Err("qsa_index_topk_u32: score slab too short".into());
3468 }
3469 for (r, &c) in counts.iter().enumerate() {
3470 if c <= budget || c > stride {
3471 return Err(format!(
3472 "qsa_index_topk_u32: row {r} block count {c} outside (budget {budget}, \
3473 stride {stride}] — the caller only routes scored rows here"
3474 )
3475 .into());
3476 }
3477 }
3478 let counts_i32: Vec<i32> = counts.iter().map(|&c| c as i32).collect();
3479 let counts_dev = e.htod_i32(&counts_i32)?;
3480 let mut out = e.htod_i32(&vec![-1i32; rows * budget])?;
3482 let f = e.func("qsa_index_topk_u32");
3483 let cfg = LaunchConfig {
3484 grid_dim: (rows as u32, 1, 1),
3485 block_dim: (256, 1, 1),
3486 shared_mem_bytes: 0,
3487 };
3488 let (st, bu, ro) = (stride as i32, budget as i32, rows as i32);
3489 let stream = e.gpu.stream();
3490 let mut b = stream.launch_builder(&f);
3491 b.arg(scores)
3492 .arg(&counts_dev)
3493 .arg(&mut out)
3494 .arg(&st)
3495 .arg(&bu)
3496 .arg(&ro);
3497 unsafe {
3498 b.launch(cfg)?;
3499 }
3500 let host = e.gpu.stream().clone_dtoh(&out)?;
3501 e.gpu.stream().synchronize()?;
3502 let mut out_rows: Vec<Vec<u32>> = Vec::with_capacity(rows);
3503 for r in 0..rows {
3504 let row = &host[r * budget..(r + 1) * budget];
3505 let mut blocks: Vec<u32> = Vec::with_capacity(budget);
3506 let mut prev: i64 = -1;
3507 for (j, &v) in row.iter().enumerate() {
3508 if v < 0 || (v as usize) >= counts[r] || (v as i64) <= prev {
3509 return Err(format!(
3510 "qsa_index_topk_u32: row {r} slot {j} = {v} is not a strictly ascending \
3511 in-range block id (blocks {}, budget {budget})",
3512 counts[r]
3513 )
3514 .into());
3515 }
3516 prev = v as i64;
3517 blocks.push(v as u32);
3518 }
3519 out_rows.push(blocks);
3520 }
3521 Ok(out_rows)
3522}
3523
3524#[allow(clippy::too_many_arguments)]
3528fn launch_copy_rows_col(
3529 e: &Engine,
3530 src: &CudaSlice<f32>,
3531 dst: &mut CudaSlice<f32>,
3532 rows: usize,
3533 width: usize,
3534 src_stride: usize,
3535 src_col: usize,
3536 dst_row: usize,
3537) -> Res<()> {
3538 if rows == 0 {
3539 return Ok(());
3540 }
3541 if src.len() < (rows - 1) * src_stride + src_col + width || dst.len() < (dst_row + rows) * width
3542 {
3543 return Err("copy_rows_col_f32: window out of range".into());
3544 }
3545 let f = e.func("copy_rows_col_f32");
3546 let total = rows * width;
3547 let cfg = LaunchConfig::for_num_elems(total as u32);
3548 let (r, w) = (rows as i32, width as i32);
3549 let (ss, sc, dr) = (src_stride as i64, src_col as i64, dst_row as i64);
3550 let stream = e.gpu.stream();
3551 let mut b = stream.launch_builder(&f);
3552 b.arg(src)
3553 .arg(&mut *dst)
3554 .arg(&r)
3555 .arg(&w)
3556 .arg(&ss)
3557 .arg(&sc)
3558 .arg(&dr);
3559 unsafe {
3560 b.launch(cfg)?;
3561 }
3562 Ok(())
3563}
3564
3565#[allow(clippy::too_many_arguments)]
3572fn launch_route_topk(
3573 e: &Engine,
3574 logits: &CudaSlice<f32>,
3575 sel: &mut CudaSlice<i32>,
3576 w: &mut CudaSlice<f32>,
3577 tok: Option<(&mut CudaSlice<i32>, usize)>,
3578 experts: usize,
3579 selected: usize,
3580 rows: usize,
3581) -> Res<()> {
3582 if rows == 0 {
3583 return Ok(());
3584 }
3585 if selected == 0 || selected > 32 || selected > experts {
3586 return Err("qwen4exp_route_topk_f32: selected out of range (caller guards)".into());
3587 }
3588 if experts % 2 != 0 {
3589 return Err("qwen4exp_route_topk_f32: odd expert count".into());
3592 }
3593 if logits.len() < rows * experts || sel.len() < rows * selected || w.len() < rows * selected {
3594 return Err("qwen4exp_route_topk_f32: buffer too short".into());
3595 }
3596 let smem = experts * 12; if smem > 48 * 1024 {
3598 return Err("qwen4exp_route_topk_f32: experts exceed the smem bound".into());
3599 }
3600 let stream = e.gpu.stream();
3601 let (tok_raw, tok_base) = match tok {
3602 Some((buf, base)) => {
3603 if buf.len() < rows * selected {
3604 return Err("qwen4exp_route_topk_f32: tok map too short".into());
3605 }
3606 (buf.device_ptr(&stream).0, base)
3607 }
3608 None => (0u64, 0usize),
3609 };
3610 let f = e.func("qwen4exp_route_topk_f32");
3611 let cfg = LaunchConfig {
3612 grid_dim: (rows as u32, 1, 1),
3613 block_dim: (128, 1, 1),
3614 shared_mem_bytes: smem as u32,
3615 };
3616 let (ex, se, ro, tb) = (
3617 experts as i32,
3618 selected as i32,
3619 rows as i32,
3620 tok_base as i32,
3621 );
3622 let floor = ROUTE_DENOM_FLOOR;
3623 let mut b = stream.launch_builder(&f);
3624 b.arg(logits)
3625 .arg(&mut *sel)
3626 .arg(&mut *w)
3627 .arg(&tok_raw)
3628 .arg(&ex)
3629 .arg(&se)
3630 .arg(&ro)
3631 .arg(&tb)
3632 .arg(&floor);
3633 unsafe {
3634 b.launch(cfg)?;
3635 }
3636 Ok(())
3637}
3638
3639#[allow(clippy::too_many_arguments)]
3643fn route_topk_device(
3644 e: &Engine,
3645 logits: &CudaSlice<f32>,
3646 sel: &mut CudaSlice<i32>,
3647 w: &mut CudaSlice<f32>,
3648 tok: Option<(&mut CudaSlice<i32>, usize)>,
3649 experts: usize,
3650 selected: usize,
3651 rows: usize,
3652 layer: u32,
3653) -> Res<()> {
3654 launch_route_topk(e, logits, sel, w, tok, experts, selected, rows)?;
3655 if !router_audit_on() {
3656 return Ok(());
3657 }
3658 let k = selected.min(experts);
3659 let lg = e.dtoh_view(&logits.slice(0..rows * experts))?;
3660 let sel_h = e.gpu.stream().clone_dtoh(&sel.slice(0..rows * selected))?;
3661 let w_h = e.gpu.stream().clone_dtoh(&w.slice(0..rows * selected))?;
3662 {
3676 let routes: Vec<Vec<(usize, f32)>> = (0..rows)
3677 .map(|row| {
3678 (0..selected)
3679 .map(|j| {
3680 (
3681 sel_h[row * selected + j].max(0) as usize,
3682 w_h[row * selected + j],
3683 )
3684 })
3685 .collect()
3686 })
3687 .collect();
3688 trace_moe_routes(layer, rows, &routes);
3689 }
3690 let mut worst: u32 = 0;
3691 for row in 0..rows {
3692 let twin = host_route_softmax_topk(&lg[row * experts..(row + 1) * experts], selected);
3693 if twin.len() != k {
3694 return Err("router audit: host twin emitted an unexpected selection width".into());
3695 }
3696 for (j, &(idx, wt)) in twin.iter().enumerate() {
3697 let ds = sel_h[row * selected + j];
3698 let dw = w_h[row * selected + j];
3699 if ds != idx as i32 {
3700 return Err(format!(
3701 "router audit: selection mismatch at row {row} slot {j}: \
3702 device {ds} vs host {idx} (host w {wt:e})"
3703 )
3704 .into());
3705 }
3706 let ulp = (dw.to_bits() as i64 - wt.to_bits() as i64).unsigned_abs();
3707 let ulp = u32::try_from(ulp).unwrap_or(u32::MAX);
3708 worst = worst.max(ulp);
3709 if ulp > ROUTE_AUDIT_ULP_BOUND {
3710 return Err(format!(
3711 "router audit: weight ULP {ulp} > bound {ROUTE_AUDIT_ULP_BOUND} at \
3712 row {row} slot {j}: device {dw:e} vs host {wt:e}"
3713 )
3714 .into());
3715 }
3716 }
3717 }
3718 ROUTE_AUDIT_ROWS.fetch_add(rows as u64, std::sync::atomic::Ordering::Relaxed);
3719 ROUTE_AUDIT_MAX_ULP.fetch_max(worst, std::sync::atomic::Ordering::Relaxed);
3720 Ok(())
3721}
3722
3723#[allow(clippy::too_many_arguments)]
3726fn indexer_mask_rows(
3727 overlay: &MicroBlockIndexPlan,
3728 rope_base: f32,
3729 yarn: Option<(&[f32], f32)>,
3730 epsilon: f32,
3731 idx_q_norm: &[f32],
3732 idx_k_norm: &[f32],
3733 proj_rows: &[f32],
3734 raw_keys: &IdxRawCache,
3735 pooled_keys: &mut Vec<f32>,
3736 base_pos: usize,
3737 t: usize,
3738 t_kv: usize,
3739 pos_off: usize,
3740) -> Res<Vec<u8>> {
3741 let sels = indexer_select_rows(
3742 overlay,
3743 rope_base,
3744 yarn,
3745 epsilon,
3746 idx_q_norm,
3747 idx_k_norm,
3748 proj_rows,
3749 raw_keys,
3750 pooled_keys,
3751 None,
3754 base_pos,
3755 t,
3756 t_kv,
3757 pos_off,
3758 )?;
3759 Ok(rowsel_to_mask(&sels, overlay.block_size as usize, t_kv))
3760}
3761
3762fn shift_right_ignore_eos(history: &[i64], shift: usize, eos: i64) -> Vec<i64> {
3764 if shift == 0 {
3765 return history.to_vec();
3766 }
3767 let mut last_eos_inclusive: i64 = -1;
3768 let mut output = Vec::with_capacity(history.len());
3769 for (position, &token) in history.iter().enumerate() {
3770 let previous_eos = last_eos_inclusive;
3771 if token == eos {
3772 last_eos_inclusive = position as i64;
3773 }
3774 let segment_start = previous_eos + 1;
3775 let position_in_segment = position as i64 - segment_start;
3776 let source = position as i64 - shift as i64;
3777 let valid = position_in_segment >= shift as i64 && source >= 0;
3778 output.push(if valid { history[source as usize] } else { eos });
3779 }
3780 output
3781}
3782
3783#[allow(clippy::too_many_arguments)]
3801fn host_ngram_ids_cached(
3802 cache_ids: &mut Vec<i64>,
3803 cache_history: &mut Vec<i64>,
3804 cache_last_eos: &mut i64,
3805 token_ids: &[u32],
3806 multipliers: &[i64],
3807 sizes: &[i64],
3808 offsets: &[i64],
3809 max_ngram: usize,
3810 heads_per_ngram: usize,
3811 eos_token_id: u32,
3812) {
3813 let context = max_ngram - 1;
3814 let eos = eos_token_id as i64;
3815 let total_heads = (max_ngram - 1) * heads_per_ngram;
3816 if cache_history.is_empty() {
3817 cache_history.extend(std::iter::repeat_n(eos, context));
3818 *cache_last_eos = context as i64 - 1; cache_ids.clear();
3820 }
3821 let cached_tokens = (cache_history.len() - context).min(cache_ids.len() / total_heads);
3822 let mut keep = cached_tokens.min(token_ids.len());
3824 for i in 0..keep {
3825 if cache_history[context + i] != token_ids[i] as i64 {
3826 keep = i;
3827 break;
3828 }
3829 }
3830 if keep < cached_tokens {
3831 cache_history.truncate(context + keep);
3833 cache_ids.truncate(keep * total_heads);
3834 *cache_last_eos = cache_history
3835 .iter()
3836 .rposition(|&v| v == eos)
3837 .map(|p| p as i64)
3838 .unwrap_or(-1);
3839 }
3840 for &token in &token_ids[keep..] {
3841 let position = cache_history.len();
3842 let value = token as i64;
3843 cache_history.push(value);
3844 let previous_eos = *cache_last_eos;
3846 if value == eos {
3847 *cache_last_eos = position as i64;
3848 }
3849 let segment_start = previous_eos + 1;
3850 let position_in_segment = position as i64 - segment_start;
3851 let shifted_at = |shift: usize| -> i64 {
3852 if shift == 0 {
3853 return cache_history[position];
3854 }
3855 let source = position as i64 - shift as i64;
3856 if position_in_segment >= shift as i64 && source >= 0 {
3857 cache_history[source as usize]
3858 } else {
3859 eos
3860 }
3861 };
3862 let mut row = vec![0i64; total_heads];
3864 for ngram in 2..=max_ngram {
3865 let head_start = (ngram - 2) * heads_per_ngram;
3866 let mut mixed = shifted_at(0).wrapping_mul(multipliers[0]);
3867 for shift in 1..ngram {
3868 mixed ^= shifted_at(shift).wrapping_mul(multipliers[shift]);
3869 }
3870 for head in 0..heads_per_ngram {
3871 let index = head_start + head;
3872 row[index] = mixed.rem_euclid(sizes[index]) + offsets[index];
3873 }
3874 }
3875 cache_ids.extend_from_slice(&row);
3876 }
3877 debug_assert_eq!(cache_ids.len(), token_ids.len() * total_heads);
3878 }
3882
3883fn host_ngram_ids(
3886 token_ids: &[u32],
3887 multipliers: &[i64],
3888 sizes: &[i64],
3889 offsets: &[i64],
3890 max_ngram: usize,
3891 heads_per_ngram: usize,
3892 eos_token_id: u32,
3893) -> Vec<i64> {
3894 let context = max_ngram - 1;
3895 let eos = eos_token_id as i64;
3896 let total_heads = (max_ngram - 1) * heads_per_ngram;
3897 let mut history = Vec::with_capacity(context + token_ids.len());
3898 history.extend(std::iter::repeat_n(eos, context));
3899 history.extend(token_ids.iter().map(|&token| token as i64));
3900 let shifted: Vec<Vec<i64>> = (0..max_ngram)
3901 .map(|shift| shift_right_ignore_eos(&history, shift, eos))
3902 .collect();
3903 let tokens = token_ids.len();
3904 let mut ids = vec![0i64; tokens * total_heads];
3905 for ngram in 2..=max_ngram {
3906 let head_start = (ngram - 2) * heads_per_ngram;
3907 for token in 0..tokens {
3908 let position = context + token;
3909 let mut mixed = shifted[0][position].wrapping_mul(multipliers[0]);
3910 for (shift, row) in shifted.iter().enumerate().take(ngram).skip(1) {
3911 mixed ^= row[position].wrapping_mul(multipliers[shift]);
3912 }
3913 for head in 0..heads_per_ngram {
3914 let index = head_start + head;
3915 ids[token * total_heads + index] = mixed.rem_euclid(sizes[index]) + offsets[index];
3916 }
3917 }
3918 }
3919 ids
3920}
3921
3922#[allow(clippy::too_many_arguments)]
3925fn launch_sdpa_mask(
3926 e: &Engine,
3927 q: &CudaSlice<f32>,
3928 k: &CudaView<'_, f32>,
3929 v: &CudaView<'_, f32>,
3930 o: &mut CudaSlice<f32>,
3931 mask: &CudaSlice<u8>,
3932 head_dim: usize,
3933 n_head: usize,
3934 n_head_kv: usize,
3935 t: usize,
3936 t_kv: usize,
3937 scale: f32,
3938) -> Res<()> {
3939 if t_kv * 4 > 48 * 1024 {
3940 return Err(
3941 "sdpa_naive_mask_f32: T_kv exceeds the smem bound; the gmem twin is perf-lane work"
3942 .into(),
3943 );
3944 }
3945 let f = e.func("sdpa_naive_mask_f32");
3946 let cfg = LaunchConfig {
3947 grid_dim: (n_head as u32, t as u32, 1),
3948 block_dim: (128, 1, 1),
3949 shared_mem_bytes: (t_kv * 4) as u32,
3950 };
3951 let (hd, nh, nkv, ti, tkvi) = (
3952 head_dim as i32,
3953 n_head as i32,
3954 n_head_kv as i32,
3955 t as i32,
3956 t_kv as i32,
3957 );
3958 let stream = e.gpu.stream();
3959 let mut b = stream.launch_builder(&f);
3960 b.arg(q)
3961 .arg(k)
3962 .arg(v)
3963 .arg(o)
3964 .arg(mask)
3965 .arg(&hd)
3966 .arg(&nh)
3967 .arg(&nkv)
3968 .arg(&ti)
3969 .arg(&tkvi)
3970 .arg(&scale);
3971 unsafe {
3972 b.launch(cfg)?;
3973 }
3974 Ok(())
3975}
3976
3977#[allow(clippy::too_many_arguments)]
3983fn launch_sdpa_blocklist(
3984 e: &Engine,
3985 q: &CudaSlice<f32>,
3986 k: &CudaView<'_, f32>,
3987 v: &CudaView<'_, f32>,
3988 o: &mut CudaSlice<f32>,
3989 pos: &CudaSlice<i32>,
3990 meta: &CudaSlice<i32>,
3991 head_dim: usize,
3992 n_head: usize,
3993 n_head_kv: usize,
3994 t: usize,
3995 max_count: usize,
3996 scale: f32,
3997) -> Res<()> {
3998 let smem = (max_count * 8) as u32;
4001 if smem > 48 * 1024 {
4002 return Err("sdpa_blocklist_f32: selection exceeds the smem budget".into());
4003 }
4004 let f = e.func("sdpa_blocklist_f32");
4005 let cfg = LaunchConfig {
4006 grid_dim: (n_head as u32, t as u32, 1),
4007 block_dim: (128, 1, 1),
4008 shared_mem_bytes: smem,
4009 };
4010 let (hd, nh, nkv, ti, mc) = (
4011 head_dim as i32,
4012 n_head as i32,
4013 n_head_kv as i32,
4014 t as i32,
4015 max_count as i32,
4016 );
4017 let stream = e.gpu.stream();
4018 let mut b = stream.launch_builder(&f);
4019 b.arg(q)
4020 .arg(k)
4021 .arg(v)
4022 .arg(o)
4023 .arg(pos)
4024 .arg(meta)
4025 .arg(&hd)
4026 .arg(&nh)
4027 .arg(&nkv)
4028 .arg(&ti)
4029 .arg(&mc)
4030 .arg(&scale);
4031 unsafe {
4032 b.launch(cfg)?;
4033 }
4034 Ok(())
4035}
4036
4037#[allow(clippy::too_many_arguments)]
4040fn launch_q4e_kv_append(
4041 e: &Engine,
4042 k_rows: &CudaSlice<f32>,
4043 v_rows: &CudaSlice<f32>,
4044 k: &mut CudaSlice<u8>,
4045 v: &mut CudaSlice<u8>,
4046 base_pos: usize,
4047 t: usize,
4048 kv_dim: usize,
4049) -> Res<()> {
4050 let f = e.func("q4e_kv_append_q8q5_rows");
4051 let blocks = kv_dim.div_ceil(32);
4052 let cfg = LaunchConfig {
4053 grid_dim: (blocks as u32, t as u32, 1),
4054 block_dim: (32, 1, 1),
4055 shared_mem_bytes: 0,
4056 };
4057 let (t0, dk, dv) = (base_pos as i32, kv_dim as i32, kv_dim as i32);
4058 let (ktb, vtb) = (q8_row_bytes(kv_dim) as i64, q5_row_bytes(kv_dim) as i64);
4059 let stream = e.gpu.stream();
4060 let mut b = stream.launch_builder(&f);
4061 b.arg(k_rows)
4062 .arg(v_rows)
4063 .arg(k)
4064 .arg(v)
4065 .arg(&t0)
4066 .arg(&dk)
4067 .arg(&dv)
4068 .arg(&ktb)
4069 .arg(&vtb);
4070 unsafe {
4071 b.launch(cfg)?;
4072 }
4073 Ok(())
4074}
4075
4076#[allow(clippy::too_many_arguments)]
4078fn launch_q4e_kv_dequant_rows(
4079 e: &Engine,
4080 k: &CudaSlice<u8>,
4081 v: &CudaSlice<u8>,
4082 k_out: &mut CudaSlice<f32>,
4083 v_out: &mut CudaSlice<f32>,
4084 r0: usize,
4085 rows: usize,
4086 kv_dim: usize,
4087) -> Res<()> {
4088 let f = e.func("q4e_kv_dequant_rows");
4089 let blocks = kv_dim.div_ceil(32);
4090 let cfg = LaunchConfig {
4091 grid_dim: (blocks as u32, rows as u32, 1),
4092 block_dim: (32, 1, 1),
4093 shared_mem_bytes: 0,
4094 };
4095 let (r0i, dk, dv) = (r0 as i32, kv_dim as i32, kv_dim as i32);
4096 let (ktb, vtb) = (q8_row_bytes(kv_dim) as i64, q5_row_bytes(kv_dim) as i64);
4097 let stream = e.gpu.stream();
4098 let mut b = stream.launch_builder(&f);
4099 b.arg(k)
4100 .arg(v)
4101 .arg(k_out)
4102 .arg(v_out)
4103 .arg(&r0i)
4104 .arg(&dk)
4105 .arg(&dv)
4106 .arg(&ktb)
4107 .arg(&vtb);
4108 unsafe {
4109 b.launch(cfg)?;
4110 }
4111 Ok(())
4112}
4113
4114#[allow(clippy::too_many_arguments)]
4117fn launch_q4e_sdpa_blocklist_q8q5(
4118 e: &Engine,
4119 q: &CudaSlice<f32>,
4120 k: &CudaSlice<u8>,
4121 v: &CudaSlice<u8>,
4122 o: &mut CudaSlice<f32>,
4123 pos: &CudaSlice<i32>,
4124 meta: &CudaSlice<i32>,
4125 head_dim: usize,
4126 n_head: usize,
4127 n_head_kv: usize,
4128 t: usize,
4129 max_count: usize,
4130 scale: f32,
4131) -> Res<()> {
4132 let smem = (max_count * 8) as u32;
4133 if smem > 48 * 1024 {
4134 return Err("q4e_sdpa_blocklist_q8q5: selection exceeds the smem budget".into());
4135 }
4136 let f = e.func(if kv_hoist_on() {
4138 "q4e_sdpa_blocklist_q8q5_hoist"
4139 } else {
4140 "q4e_sdpa_blocklist_q8q5"
4141 });
4142 let cfg = LaunchConfig {
4143 grid_dim: (n_head as u32, t as u32, 1),
4144 block_dim: (128, 1, 1),
4145 shared_mem_bytes: smem,
4146 };
4147 let kv_dim = n_head_kv * head_dim;
4148 let (hd, nh, nkv, ti, mc) = (
4149 head_dim as i32,
4150 n_head as i32,
4151 n_head_kv as i32,
4152 t as i32,
4153 max_count as i32,
4154 );
4155 let (ktb, vtb) = (q8_row_bytes(kv_dim) as i64, q5_row_bytes(kv_dim) as i64);
4156 let stream = e.gpu.stream();
4157 let mut b = stream.launch_builder(&f);
4158 b.arg(q)
4159 .arg(k)
4160 .arg(v)
4161 .arg(o)
4162 .arg(pos)
4163 .arg(meta)
4164 .arg(&hd)
4165 .arg(&nh)
4166 .arg(&nkv)
4167 .arg(&ti)
4168 .arg(&mc)
4169 .arg(&scale)
4170 .arg(&ktb)
4171 .arg(&vtb);
4172 unsafe {
4173 b.launch(cfg)?;
4174 }
4175 Ok(())
4176}
4177
4178#[allow(clippy::too_many_arguments)]
4181fn launch_q4e_idx_append_q8(
4182 e: &Engine,
4183 src: &CudaSlice<f32>,
4184 dst: &mut CudaSlice<u8>,
4185 rows: usize,
4186 width: usize,
4187 src_stride: usize,
4188 src_col: usize,
4189 dst_row: usize,
4190) -> Res<()> {
4191 let f = e.func("q4e_idx_append_q8");
4192 let cfg = LaunchConfig {
4193 grid_dim: (width.div_ceil(32) as u32, rows as u32, 1),
4194 block_dim: (32, 1, 1),
4195 shared_mem_bytes: 0,
4196 };
4197 let (r, w) = (rows as i32, width as i32);
4198 let (ss, sc, dr) = (src_stride as i64, src_col as i64, dst_row as i64);
4199 let stream = e.gpu.stream();
4200 let mut b = stream.launch_builder(&f);
4201 b.arg(src)
4202 .arg(dst)
4203 .arg(&r)
4204 .arg(&w)
4205 .arg(&ss)
4206 .arg(&sc)
4207 .arg(&dr);
4208 unsafe {
4209 b.launch(cfg)?;
4210 }
4211 Ok(())
4212}
4213
4214#[allow(clippy::too_many_arguments)]
4216fn launch_q4e_idx_append_bf16(
4217 e: &Engine,
4218 src: &CudaSlice<f32>,
4219 dst: &mut CudaSlice<u16>,
4220 rows: usize,
4221 width: usize,
4222 src_stride: usize,
4223 src_col: usize,
4224 dst_row: usize,
4225) -> Res<()> {
4226 let f = e.func("q4e_idx_append_bf16");
4227 let total = rows * width;
4228 let cfg = LaunchConfig {
4229 grid_dim: (total.div_ceil(256) as u32, 1, 1),
4230 block_dim: (256, 1, 1),
4231 shared_mem_bytes: 0,
4232 };
4233 let (r, w) = (rows as i32, width as i32);
4234 let (ss, sc, dr) = (src_stride as i64, src_col as i64, dst_row as i64);
4235 let stream = e.gpu.stream();
4236 let mut b = stream.launch_builder(&f);
4237 b.arg(src)
4238 .arg(dst)
4239 .arg(&r)
4240 .arg(&w)
4241 .arg(&ss)
4242 .arg(&sc)
4243 .arg(&dr);
4244 unsafe {
4245 b.launch(cfg)?;
4246 }
4247 Ok(())
4248}
4249
4250#[allow(clippy::too_many_arguments)]
4251fn launch_gdn_scan(
4252 e: &Engine,
4253 qkv: &CudaSlice<f32>,
4254 g_log: &CudaSlice<f32>,
4255 beta_raw: &CudaSlice<f32>,
4256 state: &mut CudaSlice<f32>,
4257 o: &mut CudaSlice<f32>,
4258 nk: usize,
4259 nv: usize,
4260 hk: usize,
4261 hv: usize,
4262 t: usize,
4263 scale: f32,
4264 eps: f32,
4265) -> Res<()> {
4266 if hk > 128 {
4267 return Err("gdn_scan_naive_f32: hk > 128".into());
4268 }
4269 let f = e.func("gdn_scan_naive_f32");
4270 let cfg = LaunchConfig {
4271 grid_dim: (nv as u32, 1, 1),
4272 block_dim: (hv as u32, 1, 1),
4273 shared_mem_bytes: ((2 * hk + 2) * 4) as u32,
4274 };
4275 let (nki, nvi, hki, hvi, ti) = (nk as i32, nv as i32, hk as i32, hv as i32, t as i32);
4276 let stream = e.gpu.stream();
4277 let mut b = stream.launch_builder(&f);
4278 b.arg(qkv)
4279 .arg(g_log)
4280 .arg(beta_raw)
4281 .arg(state)
4282 .arg(o)
4283 .arg(&nki)
4284 .arg(&nvi)
4285 .arg(&hki)
4286 .arg(&hvi)
4287 .arg(&ti)
4288 .arg(&scale)
4289 .arg(&eps);
4290 unsafe {
4291 b.launch(cfg)?;
4292 }
4293 Ok(())
4294}
4295
4296#[allow(clippy::too_many_arguments)]
4300fn launch_gdn_scan_step(
4301 e: &Engine,
4302 qkv: &CudaSlice<f32>,
4303 g_log: &CudaSlice<f32>,
4304 beta_raw: &CudaSlice<f32>,
4305 state: &mut CudaSlice<f32>,
4306 o: &mut CudaSlice<f32>,
4307 nk: usize,
4308 nv: usize,
4309 hk: usize,
4310 hv: usize,
4311 scale: f32,
4312 eps: f32,
4313) -> Res<()> {
4314 if hk % 32 != 0 || hk > 1024 {
4315 return Err("gdn_scan_step_f32: hk must be a multiple of 32 and <= 1024".into());
4316 }
4317 let f = e.func("gdn_scan_step_f32");
4318 let cfg = LaunchConfig {
4319 grid_dim: (nv as u32, hv as u32, 1),
4320 block_dim: (hk as u32, 1, 1),
4321 shared_mem_bytes: 0,
4322 };
4323 let (nki, nvi, hki, hvi) = (nk as i32, nv as i32, hk as i32, hv as i32);
4324 let stream = e.gpu.stream();
4325 let mut b = stream.launch_builder(&f);
4326 b.arg(qkv)
4327 .arg(g_log)
4328 .arg(beta_raw)
4329 .arg(state)
4330 .arg(o)
4331 .arg(&nki)
4332 .arg(&nvi)
4333 .arg(&hki)
4334 .arg(&hvi)
4335 .arg(&scale)
4336 .arg(&eps);
4337 unsafe {
4338 b.launch(cfg)?;
4339 }
4340 Ok(())
4341}
4342
4343#[allow(clippy::too_many_arguments)]
4347fn launch_gdn_scan_step_at(
4348 e: &Engine,
4349 conv_out: &CudaSlice<f32>,
4350 g_log: &CudaSlice<f32>,
4351 beta_raw: &CudaSlice<f32>,
4352 state: &mut CudaSlice<f32>,
4353 o: &mut CudaSlice<f32>,
4354 tok: usize,
4355 nk: usize,
4356 nv: usize,
4357 hk: usize,
4358 hv: usize,
4359 scale: f32,
4360 eps: f32,
4361) -> Res<()> {
4362 if hk % 32 != 0 || hk > 1024 {
4363 return Err("gdn_scan_step_f32: hk must be a multiple of 32 and <= 1024".into());
4364 }
4365 let conv_dim = 2 * nk * hk + nv * hv;
4366 let qv = conv_out.slice(tok * conv_dim..(tok + 1) * conv_dim);
4367 let gv = g_log.slice(tok * nv..(tok + 1) * nv);
4368 let bv = beta_raw.slice(tok * nv..(tok + 1) * nv);
4369 let mut ov = o.slice_mut(tok * nv * hv..(tok + 1) * nv * hv);
4370 let f = e.func("gdn_scan_step_f32");
4371 let cfg = LaunchConfig {
4372 grid_dim: (nv as u32, hv as u32, 1),
4373 block_dim: (hk as u32, 1, 1),
4374 shared_mem_bytes: 0,
4375 };
4376 let (nki, nvi, hki, hvi) = (nk as i32, nv as i32, hk as i32, hv as i32);
4377 let stream = e.gpu.stream();
4378 let mut b = stream.launch_builder(&f);
4379 b.arg(&qv)
4380 .arg(&gv)
4381 .arg(&bv)
4382 .arg(&mut *state)
4383 .arg(&mut ov)
4384 .arg(&nki)
4385 .arg(&nvi)
4386 .arg(&hki)
4387 .arg(&hvi)
4388 .arg(&scale)
4389 .arg(&eps);
4390 unsafe {
4391 b.launch(cfg)?;
4392 }
4393 Ok(())
4394}
4395
4396#[allow(clippy::too_many_arguments)]
4400fn launch_gdn_scan_at(
4401 e: &Engine,
4402 conv_out: &CudaSlice<f32>,
4403 g_log: &CudaSlice<f32>,
4404 beta_raw: &CudaSlice<f32>,
4405 state: &mut CudaSlice<f32>,
4406 o: &mut CudaSlice<f32>,
4407 tok: usize,
4408 nk: usize,
4409 nv: usize,
4410 hk: usize,
4411 hv: usize,
4412 scale: f32,
4413 eps: f32,
4414) -> Res<()> {
4415 if hk > 128 {
4416 return Err("gdn_scan_naive_f32: hk > 128".into());
4417 }
4418 let conv_dim = 2 * nk * hk + nv * hv;
4419 let qv = conv_out.slice(tok * conv_dim..(tok + 1) * conv_dim);
4420 let gv = g_log.slice(tok * nv..(tok + 1) * nv);
4421 let bv = beta_raw.slice(tok * nv..(tok + 1) * nv);
4422 let mut ov = o.slice_mut(tok * nv * hv..(tok + 1) * nv * hv);
4423 let f = e.func("gdn_scan_naive_f32");
4424 let cfg = LaunchConfig {
4425 grid_dim: (nv as u32, 1, 1),
4426 block_dim: (hv as u32, 1, 1),
4427 shared_mem_bytes: ((2 * hk + 2) * 4) as u32,
4428 };
4429 let (nki, nvi, hki, hvi, ti) = (nk as i32, nv as i32, hk as i32, hv as i32, 1i32);
4430 let stream = e.gpu.stream();
4431 let mut b = stream.launch_builder(&f);
4432 b.arg(&qv)
4433 .arg(&gv)
4434 .arg(&bv)
4435 .arg(&mut *state)
4436 .arg(&mut ov)
4437 .arg(&nki)
4438 .arg(&nvi)
4439 .arg(&hki)
4440 .arg(&hvi)
4441 .arg(&ti)
4442 .arg(&scale)
4443 .arg(&eps);
4444 unsafe {
4445 b.launch(cfg)?;
4446 }
4447 Ok(())
4448}
4449
4450#[allow(clippy::too_many_arguments)]
4454fn launch_rms_sigmul(
4455 e: &Engine,
4456 x: &CudaSlice<f32>,
4457 w: &CudaSlice<f32>,
4458 z: &CudaSlice<f32>,
4459 dst: &mut CudaSlice<f32>,
4460 ncols: usize,
4461 nrows: usize,
4462 eps: f32,
4463) -> Res<()> {
4464 let f = e.func("rms_sigmul_f32");
4465 let cfg = LaunchConfig {
4466 grid_dim: (nrows as u32, 1, 1),
4467 block_dim: (crate::rms_block(), 1, 1),
4468 shared_mem_bytes: 0,
4469 };
4470 let (nc, ep) = (ncols as i32, eps);
4471 let stream = e.gpu.stream();
4472 let mut b = stream.launch_builder(&f);
4473 b.arg(x).arg(w).arg(z).arg(dst).arg(&nc).arg(&ep);
4474 unsafe {
4475 b.launch(cfg)?;
4476 }
4477 Ok(())
4478}
4479
4480#[allow(clippy::too_many_arguments)]
4481fn launch_dwconv(
4482 e: &Engine,
4483 x: &CudaSlice<f32>,
4484 hist: &CudaSlice<f32>,
4485 w: &CudaSlice<f32>,
4486 y: &mut CudaSlice<f32>,
4487 t: usize,
4488 th: usize,
4489 c: usize,
4490 k: usize,
4491 dilation: usize,
4492 mode: i32,
4493) -> Res<()> {
4494 let f = e.func("dwconv_causal_f32");
4495 let cfg = LaunchConfig::for_num_elems((t * c) as u32);
4496 let (ti, thi, ci, ki, di) = (t as i32, th as i32, c as i32, k as i32, dilation as i32);
4497 let stream = e.gpu.stream();
4498 let mut b = stream.launch_builder(&f);
4499 b.arg(x)
4500 .arg(hist)
4501 .arg(w)
4502 .arg(y)
4503 .arg(&ti)
4504 .arg(&thi)
4505 .arg(&ci)
4506 .arg(&ki)
4507 .arg(&di)
4508 .arg(&mode);
4509 unsafe {
4510 b.launch(cfg)?;
4511 }
4512 Ok(())
4513}
4514
4515#[allow(clippy::too_many_arguments)]
4517fn run_routed_expert(
4518 e: &Engine,
4519 xg: &CudaSlice<f32>,
4520 gate: &CudaView<'_, f32>,
4521 up: &CudaView<'_, f32>,
4522 down: &CudaView<'_, f32>,
4523 m_e: usize,
4524 hidden: usize,
4525 ff: usize,
4526) -> Res<CudaSlice<f32>> {
4527 let xg_view = xg.slice(0..m_e * hidden);
4528 let mut gate_out = e.uninit(m_e * ff)?;
4529 e.linear_device_into(&xg_view, gate, &mut gate_out, m_e, hidden, ff)?;
4530 let mut up_out = e.uninit(m_e * ff)?;
4531 e.linear_device_into(&xg_view, up, &mut up_out, m_e, hidden, ff)?;
4532 let mut act = e.uninit(m_e * ff)?;
4533 e.silu_mul(&gate_out, &up_out, &mut act, m_e * ff)?;
4534 let mut down_out = e.uninit(m_e * hidden)?;
4535 e.linear_device_into(
4536 &act.slice(0..m_e * ff),
4537 down,
4538 &mut down_out,
4539 m_e,
4540 ff,
4541 hidden,
4542 )?;
4543 Ok(down_out)
4544}
4545
4546fn launch_rms_norm_into_view(
4552 e: &Engine,
4553 x: &CudaSlice<f32>,
4554 w: &CudaSlice<f32>,
4555 dst: &mut cudarc::driver::CudaViewMut<'_, f32>,
4556 ncols: usize,
4557 nrows: usize,
4558 eps: f32,
4559) -> Res<()> {
4560 let kname = if Engine::norm_ilp_on() {
4561 "rms_norm_f32_v2"
4562 } else {
4563 "rms_norm_f32"
4564 };
4565 let f = e.func(kname);
4566 let cfg = LaunchConfig {
4567 grid_dim: (nrows as u32, 1, 1),
4568 block_dim: (crate::rms_block(), 1, 1),
4569 shared_mem_bytes: 0,
4570 };
4571 let (nc, ep) = (ncols as i32, eps);
4572 let stream = e.gpu.stream();
4573 let mut b = stream.launch_builder(&f);
4574 b.arg(x).arg(w).arg(dst).arg(&nc).arg(&ep);
4575 unsafe {
4576 b.launch(cfg)?;
4577 }
4578 Ok(())
4579}
4580
4581fn launch_hc_lowrank_reduce(
4583 e: &Engine,
4584 parts: &CudaSlice<f32>,
4585 low_act: &mut CudaSlice<f32>,
4586 streams: usize,
4587 t: usize,
4588 rank: usize,
4589) -> Res<()> {
4590 let f = e.func("hc_lowrank_reduce_f32");
4591 let cfg = LaunchConfig::for_num_elems((t * rank) as u32);
4592 let (si, ti, ri) = (streams as i32, t as i32, rank as i32);
4593 let inv = 1.0f32 / streams as f32;
4594 let stream = e.gpu.stream();
4595 let mut b = stream.launch_builder(&f);
4596 b.arg(parts)
4597 .arg(low_act)
4598 .arg(&si)
4599 .arg(&ti)
4600 .arg(&ri)
4601 .arg(&inv);
4602 unsafe {
4603 b.launch(cfg)?;
4604 }
4605 Ok(())
4606}
4607
4608fn launch_hc_mix_epilogue(
4610 e: &Engine,
4611 gates: &CudaSlice<f32>,
4612 normed: &CudaSlice<f32>,
4613 mixed: &mut CudaSlice<f32>,
4614 streams: usize,
4615 t: usize,
4616 hidden: usize,
4617) -> Res<()> {
4618 let f = e.func("hc_mix_epilogue_f32");
4619 let cfg = LaunchConfig::for_num_elems((t * hidden) as u32);
4620 let (si, ti, hi) = (streams as i32, t as i32, hidden as i32);
4621 let inv = 1.0f32 / streams as f32;
4622 let stream = e.gpu.stream();
4623 let mut b = stream.launch_builder(&f);
4624 b.arg(gates)
4625 .arg(normed)
4626 .arg(mixed)
4627 .arg(&si)
4628 .arg(&ti)
4629 .arg(&hi)
4630 .arg(&inv);
4631 unsafe {
4632 b.launch(cfg)?;
4633 }
4634 Ok(())
4635}
4636
4637fn launch_hc_inject_gates(
4639 e: &Engine,
4640 normed: &CudaSlice<f32>,
4641 w: &CudaSlice<f32>,
4642 out: &mut CudaSlice<f32>,
4643 streams: usize,
4644 t: usize,
4645 hidden: usize,
4646) -> Res<()> {
4647 let f = e.func("hc_inject_gates_f32");
4648 let cfg = LaunchConfig {
4649 grid_dim: (streams as u32, t as u32, 1),
4650 block_dim: (256, 1, 1),
4651 shared_mem_bytes: 0,
4652 };
4653 let (si, ti, hi) = (streams as i32, t as i32, hidden as i32);
4654 let inv = 1.0f32 / streams as f32;
4655 let stream = e.gpu.stream();
4656 let mut b = stream.launch_builder(&f);
4657 b.arg(normed)
4658 .arg(w)
4659 .arg(out)
4660 .arg(&si)
4661 .arg(&ti)
4662 .arg(&hi)
4663 .arg(&inv);
4664 unsafe {
4665 b.launch(cfg)?;
4666 }
4667 Ok(())
4668}
4669
4670enum InjectOut {
4674 Rows(Vec<CudaSlice<f32>>),
4675 Slab(CudaSlice<f32>),
4676}
4677
4678fn put_inject(ws: &mut StepPool, inject: InjectOut) {
4681 match inject {
4682 InjectOut::Rows(rows) => {
4683 for (s, row) in rows.into_iter().enumerate() {
4684 ws.put_f32(INJECT_SLOTS[s], row);
4685 }
4686 }
4687 InjectOut::Slab(slab) => ws.put_f32("hc.inj_all", slab),
4688 }
4689}
4690
4691fn take_inject(e: &Engine, ws: &mut StepPool, streams: usize, t: usize) -> Res<InjectOut> {
4694 if micro_inj_on() && hc_fused_gate_on() {
4697 Ok(InjectOut::Slab(ws.take_f32(
4698 e,
4699 "hc.inj_all",
4700 streams * t,
4701 0,
4702 )?))
4703 } else {
4704 let mut rows = Vec::with_capacity(streams);
4705 for s in 0..streams {
4706 rows.push(ws.take_f32(e, INJECT_SLOTS[s], t, 0)?);
4707 }
4708 Ok(InjectOut::Rows(rows))
4709 }
4710}
4711
4712#[allow(clippy::too_many_arguments)]
4715fn launch_hc_norm_planes(
4716 e: &Engine,
4717 ptrs: &CudaSlice<u64>,
4718 w_stack: &CudaSlice<f32>,
4719 dst: &mut CudaSlice<f32>,
4720 hidden: usize,
4721 t: usize,
4722 streams: usize,
4723 eps: f32,
4724) -> Res<()> {
4725 let f = e.func("hc_norm_planes_f32");
4726 let cfg = LaunchConfig {
4727 grid_dim: (t as u32, streams as u32, 1),
4728 block_dim: (256, 1, 1),
4729 shared_mem_bytes: 0,
4730 };
4731 let (hi, ti) = (hidden as i32, t as i32);
4732 let stream = e.gpu.stream();
4733 let mut b = stream.launch_builder(&f);
4734 b.arg(ptrs)
4735 .arg(w_stack)
4736 .arg(dst)
4737 .arg(&hi)
4738 .arg(&ti)
4739 .arg(&eps);
4740 unsafe {
4741 b.launch(cfg)?;
4742 }
4743 Ok(())
4744}
4745
4746#[allow(clippy::too_many_arguments)]
4750fn launch_hc_inject_two_stage(
4751 e: &Engine,
4752 normed: &CudaSlice<f32>,
4753 w_f32: &CudaSlice<f32>,
4754 w_b16: Option<&CudaSlice<u8>>,
4755 partials: &mut CudaSlice<f32>,
4756 out: &mut CudaSlice<f32>,
4757 streams: usize,
4758 t: usize,
4759 hidden: usize,
4760 chunks: usize,
4761) -> Res<()> {
4762 let cfg = LaunchConfig {
4763 grid_dim: (streams as u32, t as u32, chunks as u32),
4764 block_dim: (256, 1, 1),
4765 shared_mem_bytes: 0,
4766 };
4767 let (si, ti, hi, ci) = (streams as i32, t as i32, hidden as i32, chunks as i32);
4768 let stream = e.gpu.stream();
4769 if let Some(w) = w_b16 {
4770 let f = e.func("hc_inject_partials_bf16w_f32");
4771 let mut b = stream.launch_builder(&f);
4772 b.arg(normed)
4773 .arg(w)
4774 .arg(&mut *partials)
4775 .arg(&si)
4776 .arg(&ti)
4777 .arg(&hi)
4778 .arg(&ci);
4779 unsafe {
4780 b.launch(cfg)?;
4781 }
4782 } else {
4783 let f = e.func("hc_inject_partials_f32");
4784 let mut b = stream.launch_builder(&f);
4785 b.arg(normed)
4786 .arg(w_f32)
4787 .arg(&mut *partials)
4788 .arg(&si)
4789 .arg(&ti)
4790 .arg(&hi)
4791 .arg(&ci);
4792 unsafe {
4793 b.launch(cfg)?;
4794 }
4795 }
4796 let rows = (streams * t) as i32;
4797 let inv = 1.0f32 / streams as f32;
4798 let f = e.func("hc_inject_reduce_f32");
4799 let cfg = LaunchConfig::for_num_elems((streams * t) as u32);
4800 let mut b = stream.launch_builder(&f);
4801 b.arg(&*partials).arg(out).arg(&rows).arg(&ci).arg(&inv);
4802 unsafe {
4803 b.launch(cfg)?;
4804 }
4805 Ok(())
4806}
4807
4808#[allow(clippy::too_many_arguments)]
4812#[allow(clippy::too_many_arguments)]
4813fn launch_hc_diet_stage1(
4814 e: &Engine,
4815 ptrs: &CudaSlice<u64>,
4816 nw_stack: &CudaSlice<f32>,
4817 wdown_b16: &CudaSlice<u8>,
4818 winj_b16: Option<&CudaSlice<u8>>,
4819 parts: &mut CudaSlice<f32>,
4820 inj_parts: &mut CudaSlice<f32>,
4821 inv_out: &mut CudaSlice<f32>,
4822 hidden: usize,
4823 rank: usize,
4824 streams: usize,
4825 t: usize,
4826 eps: f32,
4827) -> Res<()> {
4828 if hidden % 8 != 0 {
4829 return Err("hc_diet_stage1_f32: hidden % 8 != 0".into());
4830 }
4831 let n_inj = if winj_b16.is_some() { streams } else { 0 };
4832 const ROWS_PB: usize = 4;
4833 let total_rows = rank + n_inj;
4834 if parts.len() < t * streams * rank
4835 || (n_inj > 0 && inj_parts.len() < t * n_inj * streams)
4836 || inv_out.len() < t * streams
4837 {
4838 return Err("hc_diet_stage1_f32: output buffers too short".into());
4839 }
4840 let f = e.func("hc_diet_stage1_f32");
4841 let cfg = LaunchConfig {
4842 grid_dim: (
4843 total_rows.div_ceil(ROWS_PB) as u32,
4844 t as u32,
4845 streams as u32,
4846 ),
4847 block_dim: (256, 1, 1),
4848 shared_mem_bytes: (hidden * 4) as u32,
4849 };
4850 let (hi, ri, si, nji, rpb) = (
4851 hidden as i32,
4852 rank as i32,
4853 streams as i32,
4854 n_inj as i32,
4855 ROWS_PB as i32,
4856 );
4857 let winj = winj_b16.unwrap_or(wdown_b16); let stream = e.gpu.stream();
4859 let mut b = stream.launch_builder(&f);
4860 b.arg(ptrs)
4861 .arg(nw_stack)
4862 .arg(wdown_b16)
4863 .arg(winj)
4864 .arg(&mut *parts)
4865 .arg(&mut *inj_parts)
4866 .arg(&mut *inv_out)
4867 .arg(&hi)
4868 .arg(&ri)
4869 .arg(&si)
4870 .arg(&nji)
4871 .arg(&rpb)
4872 .arg(&eps);
4873 unsafe {
4874 b.launch(cfg)?;
4875 }
4876 Ok(())
4877}
4878
4879#[allow(clippy::too_many_arguments)]
4882#[allow(clippy::too_many_arguments)]
4883fn launch_hc_diet_stage2(
4884 e: &Engine,
4885 parts: &CudaSlice<f32>,
4886 inj_parts: &CudaSlice<f32>,
4887 low_act: &mut CudaSlice<f32>,
4888 inj_all: &mut CudaSlice<f32>,
4889 rank: usize,
4890 streams: usize,
4891 t: usize,
4892 with_inject: bool,
4893) -> Res<()> {
4894 let n_inj = if with_inject { streams } else { 0 };
4895 if low_act.len() < t * rank || (n_inj > 0 && inj_all.len() < n_inj * t) {
4896 return Err("hc_diet_stage2_f32: output buffers too short".into());
4897 }
4898 let f = e.func("hc_diet_stage2_f32");
4899 let cfg = LaunchConfig {
4900 grid_dim: (((rank + n_inj) as u32).div_ceil(256), t as u32, 1),
4901 block_dim: (256, 1, 1),
4902 shared_mem_bytes: 0,
4903 };
4904 let (ri, si, nji, ti) = (rank as i32, streams as i32, n_inj as i32, t as i32);
4905 let inv = 1.0f32 / streams as f32;
4906 let stream = e.gpu.stream();
4907 let mut b = stream.launch_builder(&f);
4908 b.arg(parts)
4909 .arg(inj_parts)
4910 .arg(&mut *low_act)
4911 .arg(&mut *inj_all)
4912 .arg(&ri)
4913 .arg(&si)
4914 .arg(&nji)
4915 .arg(&ti)
4916 .arg(&inv);
4917 unsafe {
4918 b.launch(cfg)?;
4919 }
4920 Ok(())
4921}
4922
4923#[allow(clippy::too_many_arguments)]
4926fn launch_hc_diet_stage3(
4927 e: &Engine,
4928 ptrs: &CudaSlice<u64>,
4929 nw_stack: &CudaSlice<f32>,
4930 inv_in: &CudaSlice<f32>,
4931 wup_b16: &CudaSlice<u8>,
4932 low_act: &CudaSlice<f32>,
4933 mixed: &mut CudaSlice<f32>,
4934 hidden: usize,
4935 rank: usize,
4936 streams: usize,
4937 t: usize,
4938) -> Res<()> {
4939 const DIMS_PB: usize = 8;
4940 if mixed.len() < t * hidden {
4941 return Err("hc_diet_stage3_f32: output buffer too short".into());
4942 }
4943 let f = e.func("hc_diet_stage3_f32");
4944 let cfg = LaunchConfig {
4945 grid_dim: (hidden.div_ceil(DIMS_PB) as u32, t as u32, 1),
4946 block_dim: (256, 1, 1),
4947 shared_mem_bytes: ((rank + DIMS_PB * streams) * 4) as u32,
4948 };
4949 let (hi, ri, si, dpb) = (hidden as i32, rank as i32, streams as i32, DIMS_PB as i32);
4950 let inv_streams = 1.0f32 / streams as f32;
4951 let stream = e.gpu.stream();
4952 let mut b = stream.launch_builder(&f);
4953 b.arg(ptrs)
4954 .arg(nw_stack)
4955 .arg(inv_in)
4956 .arg(wup_b16)
4957 .arg(low_act)
4958 .arg(&mut *mixed)
4959 .arg(&hi)
4960 .arg(&ri)
4961 .arg(&si)
4962 .arg(&dpb)
4963 .arg(&inv_streams);
4964 unsafe {
4965 b.launch(cfg)?;
4966 }
4967 Ok(())
4968}
4969
4970fn launch_hc_diet_stage0_mt(
4973 e: &Engine,
4974 ptrs: &CudaSlice<u64>,
4975 inv_out: &mut CudaSlice<f32>,
4976 hidden: usize,
4977 streams: usize,
4978 t: usize,
4979 eps: f32,
4980) -> Res<()> {
4981 if inv_out.len() < t * streams {
4982 return Err("hc_diet_stage0_mt_f32: inv buffer too short".into());
4983 }
4984 let f = e.func("hc_diet_stage0_mt_f32");
4985 let cfg = LaunchConfig {
4986 grid_dim: (t as u32, streams as u32, 1),
4987 block_dim: (256, 1, 1),
4988 shared_mem_bytes: 0,
4989 };
4990 let (hi, si, ti) = (hidden as i32, streams as i32, t as i32);
4991 let stream = e.gpu.stream();
4992 let mut b = stream.launch_builder(&f);
4993 b.arg(ptrs)
4994 .arg(&mut *inv_out)
4995 .arg(&hi)
4996 .arg(&si)
4997 .arg(&ti)
4998 .arg(&eps);
4999 unsafe {
5000 b.launch(cfg)?;
5001 }
5002 Ok(())
5003}
5004
5005#[allow(clippy::too_many_arguments)]
5008fn launch_hc_diet_stage1_mt(
5009 e: &Engine,
5010 ptrs: &CudaSlice<u64>,
5011 nw_stack: &CudaSlice<f32>,
5012 inv_in: &CudaSlice<f32>,
5013 wdown_b16: &CudaSlice<u8>,
5014 winj_b16: Option<&CudaSlice<u8>>,
5015 parts: &mut CudaSlice<f32>,
5016 inj_parts: &mut CudaSlice<f32>,
5017 hidden: usize,
5018 rank: usize,
5019 streams: usize,
5020 t: usize,
5021) -> Res<()> {
5022 if hidden % 8 != 0 || !(2..=12).contains(&t) {
5023 return Err("hc_diet_stage1_mt_f32: geometry".into());
5024 }
5025 let n_inj = if winj_b16.is_some() { streams } else { 0 };
5026 const ROWS_PB: usize = 4;
5027 let total_rows = rank + n_inj;
5028 if parts.len() < t * streams * rank || (n_inj > 0 && inj_parts.len() < t * n_inj * streams) {
5029 return Err("hc_diet_stage1_mt_f32: output buffers too short".into());
5030 }
5031 let f = e.func("hc_diet_stage1_mt_f32");
5032 let cfg = LaunchConfig {
5033 grid_dim: (total_rows.div_ceil(ROWS_PB) as u32, 1, streams as u32),
5034 block_dim: (256, 1, 1),
5035 shared_mem_bytes: 0,
5036 };
5037 let (hi, ri, si, nji, rpb, ti) = (
5038 hidden as i32,
5039 rank as i32,
5040 streams as i32,
5041 n_inj as i32,
5042 ROWS_PB as i32,
5043 t as i32,
5044 );
5045 let winj = winj_b16.unwrap_or(wdown_b16);
5046 let stream = e.gpu.stream();
5047 let mut b = stream.launch_builder(&f);
5048 b.arg(ptrs)
5049 .arg(nw_stack)
5050 .arg(inv_in)
5051 .arg(wdown_b16)
5052 .arg(winj)
5053 .arg(&mut *parts)
5054 .arg(&mut *inj_parts)
5055 .arg(&hi)
5056 .arg(&ri)
5057 .arg(&si)
5058 .arg(&nji)
5059 .arg(&rpb)
5060 .arg(&ti);
5061 unsafe {
5062 b.launch(cfg)?;
5063 }
5064 Ok(())
5065}
5066
5067#[allow(clippy::too_many_arguments)]
5069fn launch_hc_diet_stage3_mt(
5070 e: &Engine,
5071 ptrs: &CudaSlice<u64>,
5072 nw_stack: &CudaSlice<f32>,
5073 inv_in: &CudaSlice<f32>,
5074 wup_b16: &CudaSlice<u8>,
5075 low_act: &CudaSlice<f32>,
5076 mixed: &mut CudaSlice<f32>,
5077 hidden: usize,
5078 rank: usize,
5079 streams: usize,
5080 t: usize,
5081) -> Res<()> {
5082 const DIMS_PB: usize = 8;
5083 if !(2..=12).contains(&t) || mixed.len() < t * hidden {
5084 return Err("hc_diet_stage3_mt_f32: geometry".into());
5085 }
5086 let smem = ((t * rank + DIMS_PB * streams * t) * 4) as u32;
5087 if smem > 96 * 1024 {
5088 return Err("hc_diet_stage3_mt_f32: smem over budget".into());
5089 }
5090 let f = e.func("hc_diet_stage3_mt_f32");
5091 let cfg = LaunchConfig {
5092 grid_dim: (hidden.div_ceil(DIMS_PB) as u32, 1, 1),
5093 block_dim: (256, 1, 1),
5094 shared_mem_bytes: smem,
5095 };
5096 let (hi, ri, si, dpb, ti) = (
5097 hidden as i32,
5098 rank as i32,
5099 streams as i32,
5100 DIMS_PB as i32,
5101 t as i32,
5102 );
5103 let inv_streams = 1.0f32 / streams as f32;
5104 let stream = e.gpu.stream();
5105 let mut b = stream.launch_builder(&f);
5106 b.arg(ptrs)
5107 .arg(nw_stack)
5108 .arg(inv_in)
5109 .arg(wup_b16)
5110 .arg(low_act)
5111 .arg(&mut *mixed)
5112 .arg(&hi)
5113 .arg(&ri)
5114 .arg(&si)
5115 .arg(&dpb)
5116 .arg(&ti)
5117 .arg(&inv_streams);
5118 unsafe {
5119 b.launch(cfg)?;
5120 }
5121 Ok(())
5122}
5123
5124fn launch_hc_write_planes(
5127 e: &Engine,
5128 ptrs: &CudaSlice<u64>,
5129 block_out: &CudaSlice<f32>,
5130 inj: &CudaSlice<f32>,
5131 hidden: usize,
5132 t: usize,
5133 streams: usize,
5134) -> Res<()> {
5135 let f = e.func("hc_write_planes_f32");
5136 let n = (t * hidden) as u32;
5137 let cfg = LaunchConfig {
5138 grid_dim: (n.div_ceil(256), streams as u32, 1),
5139 block_dim: (256, 1, 1),
5140 shared_mem_bytes: 0,
5141 };
5142 let (hi, ti) = (hidden as i32, t as i32);
5143 let stream = e.gpu.stream();
5144 let mut b = stream.launch_builder(&f);
5145 b.arg(ptrs).arg(block_out).arg(inj).arg(&hi).arg(&ti);
5146 unsafe {
5147 b.launch(cfg)?;
5148 }
5149 Ok(())
5150}
5151
5152fn launch_hc_inject_gates_b16(
5156 e: &Engine,
5157 normed: &CudaSlice<f32>,
5158 w: &CudaSlice<u8>,
5159 out: &mut CudaSlice<f32>,
5160 streams: usize,
5161 t: usize,
5162 hidden: usize,
5163) -> Res<()> {
5164 let f = e.func("hc_inject_gates_bf16w_f32");
5165 let cfg = LaunchConfig {
5166 grid_dim: (streams as u32, t as u32, 1),
5167 block_dim: (256, 1, 1),
5168 shared_mem_bytes: 0,
5169 };
5170 let (si, ti, hi) = (streams as i32, t as i32, hidden as i32);
5171 let inv = 1.0f32 / streams as f32;
5172 let stream = e.gpu.stream();
5173 let mut b = stream.launch_builder(&f);
5174 b.arg(normed)
5175 .arg(w)
5176 .arg(out)
5177 .arg(&si)
5178 .arg(&ti)
5179 .arg(&hi)
5180 .arg(&inv);
5181 unsafe {
5182 b.launch(cfg)?;
5183 }
5184 Ok(())
5185}
5186
5187fn bf16_twin(e: &Engine, data: &[f32], in_f: usize) -> Res<Option<CudaSlice<u8>>> {
5193 if in_f % 8 != 0 {
5194 return Ok(None);
5195 }
5196 let mut bytes = Vec::with_capacity(data.len() * 2);
5197 for &v in data {
5198 let bits = v.to_bits();
5199 if bits & 0xFFFF != 0 {
5200 return Ok(None);
5201 }
5202 bytes.extend_from_slice(&((bits >> 16) as u16).to_le_bytes());
5203 }
5204 Ok(Some(e.htod_bytes(&bytes)?))
5205}
5206
5207#[allow(clippy::too_many_arguments)]
5212fn launch_qmatvec_bf16w(
5213 e: &Engine,
5214 w: &CudaSlice<u8>,
5215 x: &CudaSlice<f32>,
5216 y: &mut CudaSlice<f32>,
5217 in_f: usize,
5218 out_f: usize,
5219 t: usize,
5220 batch: usize,
5221 w_bstride: usize,
5222 x_bstride: usize,
5223 x_tstride: usize,
5224 y_bstride: usize,
5225) -> Res<()> {
5226 if in_f % 8 != 0 || x_bstride % 8 != 0 || x_tstride % 8 != 0 {
5227 return Err("qmatvec_bf16w_f32: stride breaks the uint4/float4 vector width".into());
5228 }
5229 if y.len() < (batch - 1) * y_bstride + t * out_f {
5230 return Err("qmatvec_bf16w_f32: output buffer too short".into());
5231 }
5232 let f = e.func("qmatvec_bf16w_f32");
5233 let cfg = LaunchConfig {
5234 grid_dim: (out_f as u32, t as u32, batch as u32),
5235 block_dim: (128, 1, 1),
5236 shared_mem_bytes: 0,
5237 };
5238 let (inf, outf, ti) = (in_f as i32, out_f as i32, t as i32);
5239 let (wb, xb, xt, yb) = (
5240 w_bstride as i64,
5241 x_bstride as i64,
5242 x_tstride as i64,
5243 y_bstride as i64,
5244 );
5245 let stream = e.gpu.stream();
5246 let mut b = stream.launch_builder(&f);
5247 b.arg(w)
5248 .arg(x)
5249 .arg(y)
5250 .arg(&inf)
5251 .arg(&outf)
5252 .arg(&ti)
5253 .arg(&wb)
5254 .arg(&xb)
5255 .arg(&xt)
5256 .arg(&yb);
5257 unsafe {
5258 b.launch(cfg)?;
5259 }
5260 Ok(())
5261}
5262
5263fn bf16_stack_twin(e: &Engine, parts: &[&[f32]], in_f: usize) -> Res<Option<CudaSlice<u8>>> {
5269 let mut cat: Vec<f32> = Vec::with_capacity(parts.iter().map(|p| p.len()).sum());
5270 for p in parts {
5271 cat.extend_from_slice(p);
5272 }
5273 bf16_twin(e, &cat, in_f)
5274}
5275
5276fn need_stack_twin(e: &Engine, parts: &[&[f32]], in_f: usize, what: &str) -> Res<CudaSlice<u8>> {
5278 bf16_stack_twin(e, parts, in_f)?.ok_or_else(|| {
5279 format!("qwen4exp_gpu tp2: {what} has no exact bf16 stack twin (in_f {in_f})").into()
5280 })
5281}
5282
5283#[allow(clippy::too_many_arguments)]
5287fn launch_qmatvec_bf16w_off(
5288 e: &Engine,
5289 w_stack: &CudaSlice<u8>,
5290 row_off: usize,
5291 x: &CudaSlice<f32>,
5292 y: &mut CudaSlice<f32>,
5293 in_f: usize,
5294 out_f: usize,
5295 t: usize,
5296) -> Res<()> {
5297 if in_f % 8 != 0 {
5298 return Err("qmatvec_bf16w_f32: stride breaks the uint4/float4 vector width".into());
5299 }
5300 if y.len() < t * out_f {
5301 return Err("qmatvec_bf16w_f32: output buffer too short".into());
5302 }
5303 let byte_off = row_off * in_f * 2;
5304 if w_stack.len() < byte_off + out_f * in_f * 2 {
5305 return Err("qmatvec_bf16w_f32: stacked twin shorter than the row window".into());
5306 }
5307 let wv = w_stack.slice(byte_off..w_stack.len());
5308 let f = e.func("qmatvec_bf16w_f32");
5309 let cfg = LaunchConfig {
5310 grid_dim: (out_f as u32, t as u32, 1),
5311 block_dim: (128, 1, 1),
5312 shared_mem_bytes: 0,
5313 };
5314 let (inf, outf, ti) = (in_f as i32, out_f as i32, t as i32);
5315 let (wb, xb, xt, yb) = (0i64, 0i64, in_f as i64, 0i64);
5316 let stream = e.gpu.stream();
5317 let mut b = stream.launch_builder(&f);
5318 b.arg(&wv)
5319 .arg(x)
5320 .arg(y)
5321 .arg(&inf)
5322 .arg(&outf)
5323 .arg(&ti)
5324 .arg(&wb)
5325 .arg(&xb)
5326 .arg(&xt)
5327 .arg(&yb);
5328 unsafe {
5329 b.launch(cfg)?;
5330 }
5331 Ok(())
5332}
5333
5334#[allow(clippy::too_many_arguments)]
5340fn launch_qmatvec_bf16w_off_into(
5341 e: &Engine,
5342 w_stack: &CudaSlice<u8>,
5343 w_row_off: usize,
5344 x: &CudaSlice<f32>,
5345 x_off: usize,
5346 y: &mut CudaSlice<f32>,
5347 y_off: usize,
5348 in_f: usize,
5349 out_f: usize,
5350) -> Res<()> {
5351 if in_f % 8 != 0 {
5352 return Err("qmatvec_bf16w_f32: stride breaks the uint4/float4 vector width".into());
5353 }
5354 let byte_off = w_row_off * in_f * 2;
5355 if w_stack.len() < byte_off + out_f * in_f * 2 {
5356 return Err("qmatvec_bf16w_f32: bank shorter than the expert row window".into());
5357 }
5358 if x.len() < x_off + in_f || y.len() < y_off + out_f {
5359 return Err("qmatvec_bf16w_f32: operand views out of range".into());
5360 }
5361 let wv = w_stack.slice(byte_off..w_stack.len());
5362 let xv = x.slice(x_off..x_off + in_f);
5363 let mut yv = y.slice_mut(y_off..y_off + out_f);
5364 let f = e.func("qmatvec_bf16w_f32");
5365 let cfg = LaunchConfig {
5366 grid_dim: (out_f as u32, 1, 1),
5367 block_dim: (128, 1, 1),
5368 shared_mem_bytes: 0,
5369 };
5370 let (inf, outf, ti) = (in_f as i32, out_f as i32, 1i32);
5371 let (wb, xb, xt, yb) = (0i64, 0i64, in_f as i64, 0i64);
5372 let stream = e.gpu.stream();
5373 let mut b = stream.launch_builder(&f);
5374 b.arg(&wv)
5375 .arg(&xv)
5376 .arg(&mut yv)
5377 .arg(&inf)
5378 .arg(&outf)
5379 .arg(&ti)
5380 .arg(&wb)
5381 .arg(&xb)
5382 .arg(&xt)
5383 .arg(&yb);
5384 unsafe {
5385 b.launch(cfg)?;
5386 }
5387 Ok(())
5388}
5389
5390#[allow(clippy::too_many_arguments)]
5396fn launch_qmatvec_bf16w_sel(
5397 e: &Engine,
5398 bank: &CudaSlice<u8>,
5399 sel: &CudaSlice<i32>,
5400 sel_off: usize,
5401 x: &CudaSlice<f32>,
5402 x_off: usize,
5403 x_sstride: usize,
5406 y: &mut CudaSlice<f32>,
5407 n_sel: usize,
5408 in_f: usize,
5409 out_f: usize,
5410) -> Res<()> {
5411 if in_f % 8 != 0 {
5412 return Err("qmatvec_bf16w_sel_f32: stride breaks the uint4/float4 vector width".into());
5413 }
5414 if sel.len() < sel_off + n_sel
5415 || x.len() < x_off + (n_sel - 1) * x_sstride + in_f
5416 || y.len() < n_sel * out_f
5417 || n_sel == 0
5418 {
5419 return Err("qmatvec_bf16w_sel_f32: operand views out of range".into());
5420 }
5421 let sv = sel.slice(sel_off..sel_off + n_sel);
5422 let xv = x.slice(x_off..x.len());
5423 let f = e.func("qmatvec_bf16w_sel_f32");
5424 let cfg = LaunchConfig {
5425 grid_dim: (out_f as u32, 1, n_sel as u32),
5426 block_dim: (128, 1, 1),
5427 shared_mem_bytes: 0,
5428 };
5429 let (inf, outf, ns) = (in_f as i32, out_f as i32, n_sel as i32);
5430 let xs = x_sstride as i64;
5431 let stream = e.gpu.stream();
5432 let mut b = stream.launch_builder(&f);
5433 b.arg(bank)
5434 .arg(&sv)
5435 .arg(&xv)
5436 .arg(&mut *y)
5437 .arg(&inf)
5438 .arg(&outf)
5439 .arg(&ns)
5440 .arg(&xs);
5441 unsafe {
5442 b.launch(cfg)?;
5443 }
5444 Ok(())
5445}
5446
5447#[allow(clippy::too_many_arguments)]
5452fn launch_qmatvec_bf16w_mt(
5453 e: &Engine,
5454 w_stack: &CudaSlice<u8>,
5455 w_row_off: usize,
5456 x: &CudaSlice<f32>,
5457 y: &mut CudaSlice<f32>,
5458 in_f: usize,
5459 out_f: usize,
5460 t: usize,
5461) -> Res<()> {
5462 if in_f % 8 != 0 {
5463 return Err("qmatvec_bf16w_mt_f32: in_f % 8 != 0".into());
5464 }
5465 if !(2..=12).contains(&t) {
5466 return Err("qmatvec_bf16w_mt_f32: t out of range (2..=12)".into());
5467 }
5468 let byte_off = w_row_off * in_f * 2;
5469 if w_stack.len() < byte_off + out_f * in_f * 2 || y.len() < t * out_f || x.len() < t * in_f {
5470 return Err("qmatvec_bf16w_mt_f32: operands out of range".into());
5471 }
5472 let wv = w_stack.slice(byte_off..w_stack.len());
5473 let f = e.func("qmatvec_bf16w_mt_f32");
5474 let cfg = LaunchConfig {
5475 grid_dim: (out_f as u32, 1, 1),
5476 block_dim: (128, 1, 1),
5477 shared_mem_bytes: 0,
5478 };
5479 let (inf, outf, ti) = (in_f as i32, out_f as i32, t as i32);
5480 let (wb, xb, xt, yb) = (0i64, 0i64, in_f as i64, 0i64);
5481 let stream = e.gpu.stream();
5482 let mut b = stream.launch_builder(&f);
5483 b.arg(&wv)
5484 .arg(x)
5485 .arg(y)
5486 .arg(&inf)
5487 .arg(&outf)
5488 .arg(&ti)
5489 .arg(&wb)
5490 .arg(&xb)
5491 .arg(&xt)
5492 .arg(&yb);
5493 unsafe {
5494 b.launch(cfg)?;
5495 }
5496 Ok(())
5497}
5498
5499#[allow(clippy::too_many_arguments)]
5503fn linear_trunk_stacked_into(
5504 e: &Engine,
5505 w_f32: &CudaSlice<f32>,
5506 stack_b16: &Option<CudaSlice<u8>>,
5507 row_off: usize,
5508 x: &CudaSlice<f32>,
5509 y: &mut CudaSlice<f32>,
5510 t: usize,
5511 in_f: usize,
5512 out_f: usize,
5513) -> Res<()> {
5514 if trunk_bf16_on() {
5515 if let Some(w) = stack_b16 {
5516 if (2..=12).contains(&t) && verify_mt_on() {
5517 return launch_qmatvec_bf16w_mt(e, w, row_off, x, y, in_f, out_f, t);
5518 }
5519 return launch_qmatvec_bf16w_off(e, w, row_off, x, y, in_f, out_f, t);
5520 }
5521 }
5522 if w_f32.len() < in_f * out_f {
5523 return Err(
5524 "qwen4exp_gpu: trunk f32 original dropped (trunk_f32_diet) — the bf16 \
5525 twin path is required (keep trunk seams ON)"
5526 .into(),
5527 );
5528 }
5529 e.linear_device_into(x, w_f32, y, t, in_f, out_f)
5530}
5531
5532fn launch_qmatvec_bf16w_multi4(
5537 e: &Engine,
5538 w_stack: &CudaSlice<u8>,
5539 x: &CudaSlice<f32>,
5540 parts: &[(&CudaSlice<f32>, usize)],
5541 in_f: usize,
5542) -> Res<()> {
5543 if in_f % 8 != 0 {
5544 return Err("qmatvec_bf16w_multi4_f32: in_f % 8 != 0".into());
5545 }
5546 if parts.is_empty() || parts.len() > 4 {
5547 return Err("qmatvec_bf16w_multi4_f32: 1..=4 parts".into());
5548 }
5549 let total: usize = parts.iter().map(|&(_, r)| r).sum();
5550 if w_stack.len() < total * in_f * 2 {
5551 return Err("qmatvec_bf16w_multi4_f32: stacked twin shorter than the row plan".into());
5552 }
5553 let stream = e.gpu.stream();
5554 let mut ptrs = [0u64; 4];
5555 let mut rows = [0i32; 4];
5556 for (i, &(buf, r)) in parts.iter().enumerate() {
5557 if buf.len() < r {
5558 return Err("qmatvec_bf16w_multi4_f32: destination shorter than its rows".into());
5559 }
5560 ptrs[i] = buf.device_ptr(&stream).0;
5561 rows[i] = r as i32;
5562 }
5563 let f = e.func("qmatvec_bf16w_multi4_f32");
5564 let cfg = LaunchConfig {
5565 grid_dim: (total as u32, 1, 1),
5566 block_dim: (128, 1, 1),
5567 shared_mem_bytes: 0,
5568 };
5569 let inf = in_f as i32;
5570 let mut b = stream.launch_builder(&f);
5571 b.arg(w_stack)
5572 .arg(x)
5573 .arg(&ptrs[0])
5574 .arg(&rows[0])
5575 .arg(&ptrs[1])
5576 .arg(&rows[1])
5577 .arg(&ptrs[2])
5578 .arg(&rows[2])
5579 .arg(&ptrs[3])
5580 .arg(&rows[3])
5581 .arg(&inf);
5582 unsafe {
5583 b.launch(cfg)?;
5584 }
5585 Ok(())
5586}
5587
5588#[allow(clippy::too_many_arguments)]
5592fn linear_trunk_into(
5593 e: &Engine,
5594 w_f32: &CudaSlice<f32>,
5595 w_b16: &Option<CudaSlice<u8>>,
5596 x: &CudaSlice<f32>,
5597 y: &mut CudaSlice<f32>,
5598 t: usize,
5599 in_f: usize,
5600 out_f: usize,
5601) -> Res<()> {
5602 if trunk_bf16_on() {
5603 if let Some(w) = w_b16 {
5604 if (2..=12).contains(&t) && verify_mt_on() {
5605 return launch_qmatvec_bf16w_mt(e, w, 0, x, y, in_f, out_f, t);
5606 }
5607 return launch_qmatvec_bf16w(e, w, x, y, in_f, out_f, t, 1, 0, 0, in_f, 0);
5608 }
5609 }
5610 if w_f32.len() < in_f * out_f {
5611 return Err(
5612 "qwen4exp_gpu: trunk f32 original dropped (trunk_f32_diet) — the bf16 \
5613 twin path is required (keep trunk seams ON)"
5614 .into(),
5615 );
5616 }
5617 e.linear_device_into(x, w_f32, y, t, in_f, out_f)
5618}
5619
5620#[allow(clippy::too_many_arguments)]
5628fn launch_nvfp4_sel_matvec(
5629 e: &Engine,
5630 codes: &CudaSlice<u8>,
5631 scales: &CudaSlice<u8>,
5632 macros_dev: &CudaSlice<f32>,
5633 sel: &CudaSlice<i32>,
5634 x: &CudaSlice<f32>,
5635 y: &mut CudaSlice<f32>,
5636 n_sel: usize,
5637 in_f: usize,
5638 out_f: usize,
5639 x_stride: usize,
5640) -> Res<()> {
5641 if in_f % 16 != 0 {
5642 return Err("qmatvec_nvfp4_modelopt_sel_f32: in_f % 16 != 0".into());
5643 }
5644 if y.len() < n_sel * out_f {
5645 return Err("qmatvec_nvfp4_modelopt_sel_f32: output shorter than n_sel*out_f".into());
5646 }
5647 let v3 = sel_v3_on() && in_f % 32 == 0 && out_f % 4 == 0;
5648 let v2 = !v3 && sel_v2_on() && in_f % 32 == 0 && out_f % 2 == 0;
5649 let f = e.func(if v3 {
5650 "qmatvec_nvfp4_modelopt_sel_f32_v3"
5651 } else if v2 {
5652 "qmatvec_nvfp4_modelopt_sel_f32_v2"
5653 } else {
5654 "qmatvec_nvfp4_modelopt_sel_f32"
5655 });
5656 let grid_x = if v3 {
5661 out_f / 4
5662 } else if v2 {
5663 out_f / 2
5664 } else {
5665 out_f
5666 };
5667 let cfg = LaunchConfig {
5668 grid_dim: (grid_x as u32, n_sel as u32, 1),
5669 block_dim: (32, 1, 1),
5670 shared_mem_bytes: 0,
5671 };
5672 let (inf, outf) = (in_f as i32, out_f as i32);
5673 let xs = x_stride as i64;
5674 let stream = e.gpu.stream();
5675 let mut b = stream.launch_builder(&f);
5676 b.arg(codes)
5677 .arg(scales)
5678 .arg(macros_dev)
5679 .arg(sel)
5680 .arg(x)
5681 .arg(y)
5682 .arg(&inf)
5683 .arg(&outf)
5684 .arg(&xs);
5685 unsafe {
5686 b.launch(cfg)?;
5687 }
5688 Ok(())
5689}
5690
5691#[allow(clippy::too_many_arguments)]
5697#[allow(clippy::too_many_arguments)]
5698fn launch_nvfp4_sel_gu_silu(
5699 e: &Engine,
5700 gate: (&CudaSlice<u8>, &CudaSlice<u8>, &CudaSlice<f32>),
5701 up: (&CudaSlice<u8>, &CudaSlice<u8>, &CudaSlice<f32>),
5702 sel: Option<&CudaSlice<i32>>,
5703 pack_raw: u64,
5704 n_sel: usize,
5705 x: &CudaSlice<f32>,
5706 act: &mut CudaSlice<f32>,
5707 in_f: usize,
5708 ff: usize,
5709 tok: Option<(&CudaSlice<i32>, usize)>,
5712) -> Res<()> {
5713 if in_f % 32 != 0 || ff % 4 != 0 {
5714 return Err("qmatvec_nvfp4_modelopt_sel_gu_silu_f32: geometry".into());
5715 }
5716 if act.len() < n_sel * ff {
5717 return Err("qmatvec_nvfp4_modelopt_sel_gu_silu_f32: act buffer too short".into());
5718 }
5719 if sel.is_none() == (pack_raw == 0) {
5720 return Err("qmatvec_nvfp4_modelopt_sel_gu_silu_f32: exactly one of sel/pack".into());
5721 }
5722 let f = e.func("qmatvec_nvfp4_modelopt_sel_gu_silu_f32");
5723 let cfg = LaunchConfig {
5725 grid_dim: ((ff / 4) as u32, n_sel as u32, 1),
5726 block_dim: (32, 1, 1),
5727 shared_mem_bytes: 0,
5728 };
5729 let (inf, ffi, ms) = (in_f as i32, ff as i32, n_sel as i32);
5730 let stream = e.gpu.stream();
5731 let mut b = stream.launch_builder(&f);
5732 b.arg(gate.0)
5733 .arg(gate.1)
5734 .arg(gate.2)
5735 .arg(up.0)
5736 .arg(up.1)
5737 .arg(up.2);
5738 match sel {
5739 Some(s) => {
5740 b.arg(s);
5741 }
5742 None => {
5743 b.arg(gate.2);
5745 }
5746 }
5747 let stream2 = e.gpu.stream();
5748 let (tok_raw, x_tstride) = match tok {
5749 Some((tm, stride)) => (tm.device_ptr(&stream2).0, stride as i64),
5750 None => (0u64, 0i64),
5751 };
5752 b.arg(&pack_raw)
5753 .arg(&ms)
5754 .arg(x)
5755 .arg(&mut *act)
5756 .arg(&inf)
5757 .arg(&ffi)
5758 .arg(&tok_raw)
5759 .arg(&x_tstride);
5760 unsafe {
5761 b.launch(cfg)?;
5762 }
5763 Ok(())
5764}
5765
5766#[allow(clippy::too_many_arguments)]
5771fn launch_axpy_rows_seq_at(
5772 e: &Engine,
5773 x: &CudaSlice<f32>,
5774 x_row0: usize,
5775 w: &CudaSlice<f32>,
5776 w_off: usize,
5777 y: &mut CudaSlice<f32>,
5778 y_row: usize,
5779 width: usize,
5780 n_rows: usize,
5781) -> Res<()> {
5782 if x.len() < (x_row0 + n_rows) * width
5783 || w.len() < w_off + n_rows
5784 || y.len() < (y_row + 1) * width
5785 {
5786 return Err("axpy_rows_seq_f32: window out of range".into());
5787 }
5788 let xv = x.slice(x_row0 * width..(x_row0 + n_rows) * width);
5789 let wv = w.slice(w_off..w_off + n_rows);
5790 let mut yv = y.slice_mut(y_row * width..(y_row + 1) * width);
5791 let f = e.func("axpy_rows_seq_f32");
5792 let cfg = LaunchConfig::for_num_elems(width as u32);
5793 let (wi, nr) = (width as i32, n_rows as i32);
5794 let stream = e.gpu.stream();
5795 let mut b = stream.launch_builder(&f);
5796 b.arg(&xv).arg(&wv).arg(&mut yv).arg(&wi).arg(&nr);
5797 unsafe {
5798 b.launch(cfg)?;
5799 }
5800 Ok(())
5801}
5802
5803pub fn gate_nvfp4_sel_matvec(e: &Engine) -> Res<String> {
5812 let mut lcg = 0x2545_f491_u64;
5813 let mut next_u32 = move || -> u32 {
5814 lcg = lcg
5815 .wrapping_mul(6364136223846793005)
5816 .wrapping_add(1442695040888963407);
5817 (lcg >> 33) as u32
5818 };
5819 let macros = [
5820 1.0f32,
5821 0.5,
5822 5.9945243e-5, 2.0,
5824 0.25,
5825 3.7e-3,
5826 1.0,
5827 8.0,
5828 ];
5829 let sel_host: Vec<i32> = vec![3, 5, 3, 0]; let n_sel = sel_host.len();
5831 let mut worst = (0.0f32, 0.0f32); for (mode, out_f, in_f) in [
5838 ("gate_up_v1", 16usize, 48usize),
5839 ("down_v1", 32, 16),
5840 ("gate_up_v1_oddrows", 7, 64),
5841 ("gate_up_v2", 16, 64),
5842 ("down_v2", 32, 32),
5843 ("gate_up_v3", 16, 64),
5844 ("down_v3", 32, 32),
5845 ("gate_up_v3_v2rows", 6, 64), ] {
5847 set_sel_v3(mode.contains("v3"));
5848 let n_expert = macros.len();
5849 let mut codes = vec![0u8; n_expert * out_f * in_f / 2];
5850 for byte in &mut codes {
5851 *byte = next_u32() as u8;
5852 }
5853 let mut scales = vec![0u8; n_expert * out_f * in_f / 16];
5854 for byte in &mut scales {
5855 *byte = (next_u32() as u8) & 0xBF; }
5857 scales[0] = 0x7F; scales[3] = 0xFF; let x_stride = if mode.starts_with("down") { in_f } else { 0 };
5860 let x_rows = if x_stride == 0 { 1 } else { n_sel };
5861 let x_host: Vec<f32> = (0..x_rows * in_f)
5862 .map(|_| (next_u32() % 2000) as f32 / 1000.0 - 1.0)
5863 .collect();
5864 let codes_dev = e.htod_bytes(&codes)?;
5865 let scales_dev = e.htod_bytes(&scales)?;
5866 let macros_dev = e.htod(¯os)?;
5867 let sel_dev = e.htod_i32(&sel_host)?;
5868 let x_dev = e.htod(&x_host)?;
5869 let mut y_dev = e.uninit(n_sel * out_f)?;
5870 launch_nvfp4_sel_matvec(
5871 e,
5872 &codes_dev,
5873 &scales_dev,
5874 ¯os_dev,
5875 &sel_dev,
5876 &x_dev,
5877 &mut y_dev,
5878 n_sel,
5879 in_f,
5880 out_f,
5881 x_stride,
5882 )?;
5883 let y = e.dtoh(&y_dev)?;
5884 let wbytes = out_f * in_f / 2;
5885 let sbytes = out_f * in_f / 16;
5886 for (slot, &expert) in sel_host.iter().enumerate() {
5887 let expert = expert as usize;
5888 let w = memra_gguf::dsv4::dequant_nvfp4_expert(
5889 &codes[expert * wbytes..(expert + 1) * wbytes],
5890 &scales[expert * sbytes..(expert + 1) * sbytes],
5891 macros[expert],
5892 out_f,
5893 in_f,
5894 );
5895 let xrow = &x_host[slot * x_stride..slot * x_stride + in_f];
5896 for o in 0..out_f {
5897 let mut want = 0.0f32;
5898 for i in 0..in_f {
5899 want += w[o * in_f + i] * xrow[i];
5900 }
5901 let got = y[slot * out_f + o];
5902 let abs = (want - got).abs();
5903 let rel = abs / want.abs().max(1.0);
5904 if abs > worst.0 {
5905 worst.0 = abs;
5906 }
5907 if rel > worst.1 {
5908 worst.1 = rel;
5909 }
5910 if rel > 1e-5 {
5911 return Err(format!(
5912 "nvfp4-sel-matvec oracle: {mode} slot {slot} row {o}: want {want} \
5913 got {got} (rel {rel:.3e})"
5914 )
5915 .into());
5916 }
5917 }
5918 }
5919 }
5920 set_sel_v3(SEL_V3_DEFAULT);
5921
5922 {
5927 set_sel_v3(true);
5928 let (ff, in_f) = (16usize, 64usize);
5929 let n_expert = macros.len();
5930 let mut mk = |seed: u8| -> (Vec<u8>, Vec<u8>) {
5931 let mut codes = vec![0u8; n_expert * ff * in_f / 2];
5932 for byte in &mut codes {
5933 *byte = (next_u32() as u8) ^ seed;
5934 }
5935 let mut scales = vec![0u8; n_expert * ff * in_f / 16];
5936 for byte in &mut scales {
5937 *byte = (next_u32() as u8) & 0xBF;
5938 }
5939 scales[1] = 0x7F; (codes, scales)
5941 };
5942 let (g_codes, g_scales) = mk(0x00);
5943 let (u_codes, u_scales) = mk(0x5A);
5944 let gmac: Vec<f32> = macros.to_vec();
5945 let umac: Vec<f32> = macros.iter().map(|m| m * 0.5).collect();
5946 let x_host: Vec<f32> = (0..in_f)
5947 .map(|_| (next_u32() % 2000) as f32 / 1000.0 - 1.0)
5948 .collect();
5949 let gc = e.htod_bytes(&g_codes)?;
5950 let gs = e.htod_bytes(&g_scales)?;
5951 let gm = e.htod(&gmac)?;
5952 let uc = e.htod_bytes(&u_codes)?;
5953 let us = e.htod_bytes(&u_scales)?;
5954 let um = e.htod(&umac)?;
5955 let sel_dev = e.htod_i32(&sel_host)?;
5956 let x_dev = e.htod(&x_host)?;
5957 let mut yg = e.uninit(n_sel * ff)?;
5959 let mut yu = e.uninit(n_sel * ff)?;
5960 launch_nvfp4_sel_matvec(
5961 e, &gc, &gs, &gm, &sel_dev, &x_dev, &mut yg, n_sel, in_f, ff, 0,
5962 )?;
5963 launch_nvfp4_sel_matvec(
5964 e, &uc, &us, &um, &sel_dev, &x_dev, &mut yu, n_sel, in_f, ff, 0,
5965 )?;
5966 let mut act_chain = e.zeros(n_sel * ff)?;
5967 e.silu_mul(&yg, &yu, &mut act_chain, n_sel * ff)?;
5968 let mut act_fused = e.zeros(n_sel * ff)?;
5970 launch_nvfp4_sel_gu_silu(
5971 e,
5972 (&gc, &gs, &gm),
5973 (&uc, &us, &um),
5974 Some(&sel_dev),
5975 0,
5976 n_sel,
5977 &x_dev,
5978 &mut act_fused,
5979 in_f,
5980 ff,
5981 None,
5982 )?;
5983 let a = e.dtoh(&act_chain)?;
5984 let b = e.dtoh(&act_fused)?;
5985 for (i, (&x1, &x2)) in a.iter().zip(&b).enumerate() {
5986 if x1.to_bits() != x2.to_bits() {
5987 return Err(format!(
5988 "nvfp4-sel-matvec oracle: gufuse idx {i} not bit-identical \
5989 (chain {x1} fused {x2})"
5990 )
5991 .into());
5992 }
5993 }
5994 let pack_bytes = tp2_pack_bytes(&sel_host[..2], &[0.5, 0.25], n_sel);
5997 let pack = e.htod_bytes(&pack_bytes)?;
5998 let pack_raw = {
5999 let stream = e.gpu.stream();
6000 pack.device_ptr(&stream).0
6001 };
6002 let sentinel = vec![-777.0f32; n_sel * ff];
6003 let mut act_pack = e.htod(&sentinel)?;
6004 launch_nvfp4_sel_gu_silu(
6005 e,
6006 (&gc, &gs, &gm),
6007 (&uc, &us, &um),
6008 None,
6009 pack_raw,
6010 n_sel,
6011 &x_dev,
6012 &mut act_pack,
6013 in_f,
6014 ff,
6015 None,
6016 )?;
6017 let c = e.dtoh(&act_pack)?;
6018 for slot in 0..n_sel {
6019 for o in 0..ff {
6020 let got = c[slot * ff + o];
6021 if slot < 2 {
6022 if got.to_bits() != a[slot * ff + o].to_bits() {
6023 return Err(format!(
6024 "nvfp4-sel-matvec oracle: gufuse pack slot {slot} o {o} \
6025 not bit-identical"
6026 )
6027 .into());
6028 }
6029 } else if got != -777.0 {
6030 return Err(format!(
6031 "nvfp4-sel-matvec oracle: gufuse pack dead slot {slot} written"
6032 )
6033 .into());
6034 }
6035 }
6036 }
6037 {
6040 let t2 = 2usize;
6041 let x2_host: Vec<f32> = (0..t2 * in_f)
6042 .map(|_| (next_u32() % 2000) as f32 / 1000.0 - 1.0)
6043 .collect();
6044 let x2 = e.htod(&x2_host)?;
6045 let tok_host: Vec<i32> = (0..n_sel).map(|s| (s % t2) as i32).collect();
6046 let tokm = e.htod_i32(&tok_host)?;
6047 let mut act_map = e.zeros(n_sel * ff)?;
6048 launch_nvfp4_sel_gu_silu(
6049 e,
6050 (&gc, &gs, &gm),
6051 (&uc, &us, &um),
6052 Some(&sel_dev),
6053 0,
6054 n_sel,
6055 &x2,
6056 &mut act_map,
6057 in_f,
6058 ff,
6059 Some((&tokm, in_f)),
6060 )?;
6061 let got = e.dtoh(&act_map)?;
6062 for tok in 0..t2 {
6063 let slots: Vec<usize> = (0..n_sel).filter(|s| s % t2 == tok).collect();
6064 let sel_tok: Vec<i32> = slots.iter().map(|&s| sel_host[s]).collect();
6065 let sel_tok_dev = e.htod_i32(&sel_tok)?;
6066 let xrow = e.htod(&x2_host[tok * in_f..(tok + 1) * in_f])?;
6067 let mut act_tok = e.zeros(sel_tok.len() * ff)?;
6068 launch_nvfp4_sel_gu_silu(
6069 e,
6070 (&gc, &gs, &gm),
6071 (&uc, &us, &um),
6072 Some(&sel_tok_dev),
6073 0,
6074 sel_tok.len(),
6075 &xrow,
6076 &mut act_tok,
6077 in_f,
6078 ff,
6079 None,
6080 )?;
6081 let want = e.dtoh(&act_tok)?;
6082 for (local, &slot) in slots.iter().enumerate() {
6083 for o in 0..ff {
6084 let a = got[slot * ff + o];
6085 let b = want[local * ff + o];
6086 if a.to_bits() != b.to_bits() {
6087 return Err(format!(
6088 "nvfp4-sel-matvec oracle: gufuse tok_map slot {slot} o {o} \
6089 not bit-identical (map {a} per-token {b})"
6090 )
6091 .into());
6092 }
6093 }
6094 }
6095 }
6096 }
6097 set_sel_v3(SEL_V3_DEFAULT);
6098 }
6099 Ok(format!(
6100 "nvfp4-sel-matvec kernel oracle: worst abs {:.3e} rel {:.3e} over gate_up+down \
6101 v1/v2/v3 modes, NaN scales + non-pow2 macros + duplicate slots; gufuse \
6102 BIT-IDENTICAL to the v3+silu chain incl. the count-gated pack twin + the \
6103 tok_map verify merge",
6104 worst.0, worst.1
6105 ))
6106}
6107
6108pub fn gate_hc_diet_kernels(e: &Engine) -> Res<String> {
6116 let (streams, hidden, rank, t) = (4usize, 2560usize, 320usize, 1usize);
6117 let wide = streams * hidden;
6118 let mut lcg = 0x8badf00d_u64;
6119 let mut next_f32 = move || -> f32 {
6120 lcg = lcg
6121 .wrapping_mul(6364136223846793005)
6122 .wrapping_add(1442695040888963407);
6123 (((lcg >> 33) as u32) % 2000) as f32 / 1000.0 - 1.0
6124 };
6125 let mut rand_vec = |n: usize| -> Vec<f32> { (0..n).map(|_| next_f32()).collect() };
6126 let to_b16_vals = |v: Vec<f32>| -> Vec<f32> {
6128 v.into_iter()
6129 .map(|x| f32::from_bits(x.to_bits() & 0xFFFF_0000))
6130 .collect()
6131 };
6132 let planes_host: Vec<Vec<f32>> = (0..streams).map(|_| rand_vec(t * hidden)).collect();
6133 let planes: Vec<CudaSlice<f32>> = planes_host
6134 .iter()
6135 .map(|v| e.htod(v))
6136 .collect::<Result<_, _>>()?;
6137 let ptr_vals: Vec<u64> = {
6138 let stream = e.gpu.stream();
6139 planes.iter().map(|p| p.device_ptr(&stream).0).collect()
6140 };
6141 let ptrs = e.htod_u64(&ptr_vals)?;
6142 let norm_stack_host = rand_vec(wide);
6143 let norm_stack = e.htod(&norm_stack_host)?;
6144 let down_host = to_b16_vals(rand_vec(streams * rank * hidden));
6145 let up_host = to_b16_vals(rand_vec(streams * hidden * rank));
6146 let inj_host = to_b16_vals(rand_vec(streams * wide));
6147 let down_b16 = bf16_twin(e, &down_host, hidden)?.ok_or("hc-diet oracle: down twin")?;
6148 let up_b16 = bf16_twin(e, &up_host, rank)?.ok_or("hc-diet oracle: up twin")?;
6149 let inj_b16 = bf16_twin(e, &inj_host, hidden)?.ok_or("hc-diet oracle: inject twin")?;
6150 let inj_f32 = e.htod(&inj_host)?;
6151 let eps = 1e-6f32;
6152
6153 let mut normed = e.zeros(streams * t * hidden)?;
6155 launch_hc_norm_planes(e, &ptrs, &norm_stack, &mut normed, hidden, t, streams, eps)?;
6156 let mut parts_c = e.zeros(streams * t * rank)?;
6157 launch_qmatvec_bf16w(
6158 e,
6159 &down_b16,
6160 &normed,
6161 &mut parts_c,
6162 hidden,
6163 rank,
6164 t,
6165 streams,
6166 rank * hidden,
6167 t * hidden,
6168 hidden,
6169 t * rank,
6170 )?;
6171 let mut low_c = e.zeros(t * rank)?;
6172 launch_hc_lowrank_reduce(e, &parts_c, &mut low_c, streams, t, rank)?;
6173 let mut gates_c = e.zeros(streams * t * hidden)?;
6174 launch_qmatvec_bf16w(
6175 e,
6176 &up_b16,
6177 &low_c,
6178 &mut gates_c,
6179 rank,
6180 hidden,
6181 t,
6182 streams,
6183 hidden * rank,
6184 0,
6185 rank,
6186 t * hidden,
6187 )?;
6188 let mut mixed_c = e.zeros(t * hidden)?;
6189 launch_hc_mix_epilogue(e, &gates_c, &normed, &mut mixed_c, streams, t, hidden)?;
6190 let mut partials_c = e.zeros(streams * t * 16)?;
6191 let mut all_c = e.zeros(streams * t)?;
6192 launch_hc_inject_two_stage(
6193 e,
6194 &normed,
6195 &inj_f32,
6196 Some(&inj_b16),
6197 &mut partials_c,
6198 &mut all_c,
6199 streams,
6200 t,
6201 hidden,
6202 16,
6203 )?;
6204
6205 let mut parts_d = e.zeros(streams * rank)?;
6207 let mut injp_d = e.zeros(streams * streams)?;
6208 let mut inv_d = e.zeros(streams)?;
6209 launch_hc_diet_stage1(
6210 e,
6211 &ptrs,
6212 &norm_stack,
6213 &down_b16,
6214 Some(&inj_b16),
6215 &mut parts_d,
6216 &mut injp_d,
6217 &mut inv_d,
6218 hidden,
6219 rank,
6220 streams,
6221 1,
6222 eps,
6223 )?;
6224 let mut low_d = e.zeros(rank)?;
6225 let mut all_d = e.zeros(streams)?;
6226 launch_hc_diet_stage2(
6227 e, &parts_d, &injp_d, &mut low_d, &mut all_d, rank, streams, 1, true,
6228 )?;
6229 let mut mixed_d = e.zeros(hidden)?;
6230 launch_hc_diet_stage3(
6231 e,
6232 &ptrs,
6233 &norm_stack,
6234 &inv_d,
6235 &up_b16,
6236 &low_d,
6237 &mut mixed_d,
6238 hidden,
6239 rank,
6240 streams,
6241 1,
6242 )?;
6243
6244 let mut worst = 0.0f32;
6245 let check = |name: &str, a: &[f32], b: &[f32], worst: &mut f32| -> Res<()> {
6246 for (i, (&x, &y)) in a.iter().zip(b).enumerate() {
6247 let rel = (x - y).abs() / y.abs().max(1.0);
6248 if rel > *worst {
6249 *worst = rel;
6250 }
6251 if rel > 1e-4 {
6252 return Err(format!(
6253 "hc-diet oracle: {name} idx {i}: diet {x} classic {y} (rel {rel:.3e})"
6254 )
6255 .into());
6256 }
6257 }
6258 Ok(())
6259 };
6260 check("low_act", &e.dtoh(&low_d)?, &e.dtoh(&low_c)?, &mut worst)?;
6261 check("inject", &e.dtoh(&all_d)?, &e.dtoh(&all_c)?, &mut worst)?;
6262 check("mixed", &e.dtoh(&mixed_d)?, &e.dtoh(&mixed_c)?, &mut worst)?;
6263
6264 {
6268 let t3 = 3usize;
6269 let planes3_host: Vec<Vec<f32>> = (0..streams).map(|_| rand_vec(t3 * hidden)).collect();
6270 let planes3: Vec<CudaSlice<f32>> = planes3_host
6271 .iter()
6272 .map(|v| e.htod(v))
6273 .collect::<Result<_, _>>()?;
6274 let ptr_vals3: Vec<u64> = {
6275 let stream = e.gpu.stream();
6276 planes3.iter().map(|p| p.device_ptr(&stream).0).collect()
6277 };
6278 let ptrs3 = e.htod_u64(&ptr_vals3)?;
6279 let mut parts3 = e.zeros(t3 * streams * rank)?;
6280 let mut injp3 = e.zeros(t3 * streams * streams)?;
6281 let mut inv3 = e.zeros(t3 * streams)?;
6282 launch_hc_diet_stage1(
6283 e,
6284 &ptrs3,
6285 &norm_stack,
6286 &down_b16,
6287 Some(&inj_b16),
6288 &mut parts3,
6289 &mut injp3,
6290 &mut inv3,
6291 hidden,
6292 rank,
6293 streams,
6294 t3,
6295 eps,
6296 )?;
6297 let mut low3 = e.zeros(t3 * rank)?;
6298 let mut all3 = e.zeros(streams * t3)?;
6299 launch_hc_diet_stage2(
6300 e, &parts3, &injp3, &mut low3, &mut all3, rank, streams, t3, true,
6301 )?;
6302 let mut mixed3 = e.zeros(t3 * hidden)?;
6303 launch_hc_diet_stage3(
6304 e,
6305 &ptrs3,
6306 &norm_stack,
6307 &inv3,
6308 &up_b16,
6309 &low3,
6310 &mut mixed3,
6311 hidden,
6312 rank,
6313 streams,
6314 t3,
6315 )?;
6316 let low3_h = e.dtoh(&low3)?;
6317 let all3_h = e.dtoh(&all3)?;
6318 let mixed3_h = e.dtoh(&mixed3)?;
6319 {
6322 let mut inv_mt = e.zeros(t3 * streams)?;
6323 launch_hc_diet_stage0_mt(e, &ptrs3, &mut inv_mt, hidden, streams, t3, eps)?;
6324 let mut parts_mt = e.zeros(t3 * streams * rank)?;
6325 let mut injp_mt = e.zeros(t3 * streams * streams)?;
6326 launch_hc_diet_stage1_mt(
6327 e,
6328 &ptrs3,
6329 &norm_stack,
6330 &inv_mt,
6331 &down_b16,
6332 Some(&inj_b16),
6333 &mut parts_mt,
6334 &mut injp_mt,
6335 hidden,
6336 rank,
6337 streams,
6338 t3,
6339 )?;
6340 let mut low_mt = e.zeros(t3 * rank)?;
6341 let mut all_mt = e.zeros(streams * t3)?;
6342 launch_hc_diet_stage2(
6343 e,
6344 &parts_mt,
6345 &injp_mt,
6346 &mut low_mt,
6347 &mut all_mt,
6348 rank,
6349 streams,
6350 t3,
6351 true,
6352 )?;
6353 let mut mixed_mt = e.zeros(t3 * hidden)?;
6354 launch_hc_diet_stage3_mt(
6355 e,
6356 &ptrs3,
6357 &norm_stack,
6358 &inv_mt,
6359 &up_b16,
6360 &low_mt,
6361 &mut mixed_mt,
6362 hidden,
6363 rank,
6364 streams,
6365 t3,
6366 )?;
6367 let bit_check_mt = |name: &str, a: &[f32], b: &[f32]| -> Res<()> {
6368 for (i, (&x, &y)) in a.iter().zip(b).enumerate() {
6369 if x.to_bits() != y.to_bits() {
6370 return Err(format!(
6371 "hc-diet mt oracle: {name} idx {i}: mt {x} vs grid {y} NOT \
6372 bit-identical"
6373 )
6374 .into());
6375 }
6376 }
6377 Ok(())
6378 };
6379 bit_check_mt("inv", &e.dtoh(&inv_mt)?, &e.dtoh(&inv3)?)?;
6380 bit_check_mt("low_act", &e.dtoh(&low_mt)?, &low3_h)?;
6381 bit_check_mt("inject", &e.dtoh(&all_mt)?, &all3_h)?;
6382 bit_check_mt("mixed", &e.dtoh(&mixed_mt)?, &mixed3_h)?;
6383 }
6384 let bit_check = |name: &str, a: &[f32], b: &[f32]| -> Res<()> {
6385 for (i, (&x, &y)) in a.iter().zip(b).enumerate() {
6386 if x.to_bits() != y.to_bits() {
6387 return Err(format!(
6388 "hc-diet t-ext oracle: {name} idx {i}: t3 {x} vs t1 {y} NOT bit-identical"
6389 )
6390 .into());
6391 }
6392 }
6393 Ok(())
6394 };
6395 for tok in 0..t3 {
6396 let ptr_tok: Vec<u64> = ptr_vals3
6397 .iter()
6398 .map(|&base| base + (tok * hidden * 4) as u64)
6399 .collect();
6400 let ptrs_tok = e.htod_u64(&ptr_tok)?;
6401 let mut parts1 = e.zeros(streams * rank)?;
6402 let mut injp1 = e.zeros(streams * streams)?;
6403 let mut inv1 = e.zeros(streams)?;
6404 launch_hc_diet_stage1(
6405 e,
6406 &ptrs_tok,
6407 &norm_stack,
6408 &down_b16,
6409 Some(&inj_b16),
6410 &mut parts1,
6411 &mut injp1,
6412 &mut inv1,
6413 hidden,
6414 rank,
6415 streams,
6416 1,
6417 eps,
6418 )?;
6419 let mut low1 = e.zeros(rank)?;
6420 let mut all1 = e.zeros(streams)?;
6421 launch_hc_diet_stage2(
6422 e, &parts1, &injp1, &mut low1, &mut all1, rank, streams, 1, true,
6423 )?;
6424 let mut mixed1 = e.zeros(hidden)?;
6425 launch_hc_diet_stage3(
6426 e,
6427 &ptrs_tok,
6428 &norm_stack,
6429 &inv1,
6430 &up_b16,
6431 &low1,
6432 &mut mixed1,
6433 hidden,
6434 rank,
6435 streams,
6436 1,
6437 )?;
6438 bit_check(
6439 "low_act",
6440 &low3_h[tok * rank..(tok + 1) * rank],
6441 &e.dtoh(&low1)?,
6442 )?;
6443 let all1_h = e.dtoh(&all1)?;
6444 let col: Vec<f32> = (0..streams).map(|s| all3_h[s * t3 + tok]).collect();
6445 bit_check("inject", &col, &all1_h)?;
6446 bit_check(
6447 "mixed",
6448 &mixed3_h[tok * hidden..(tok + 1) * hidden],
6449 &e.dtoh(&mixed1)?,
6450 )?;
6451 }
6452 }
6453 Ok(format!(
6454 "hc-diet real-geometry oracle: streams 4 hidden 2560 rank 320, worst rel \
6455 {worst:.3e} vs the classic fused chain at t 1; t 3 token-dim AND the mt \
6456 weight-shared stages BIT-IDENTICAL to per-token t 1 launches"
6457 ))
6458}
6459
6460pub fn gate_hc_micro_kernels(e: &Engine) -> Res<String> {
6475 let (streams, hidden, t) = (4usize, 2560usize, 10usize);
6476 let wide = streams * hidden;
6477 let mut lcg = 0x1357_9bdf_u64;
6478 let mut next_f32 = move || -> f32 {
6479 lcg = lcg
6480 .wrapping_mul(6364136223846793005)
6481 .wrapping_add(1442695040888963407);
6482 (((lcg >> 33) as u32) % 2000) as f32 / 1000.0 - 1.0
6483 };
6484 let mut rand_vec = |n: usize| -> Vec<f32> { (0..n).map(|_| next_f32()).collect() };
6485 let planes_host: Vec<Vec<f32>> = (0..streams).map(|_| rand_vec(t * hidden)).collect();
6486 let planes: Vec<CudaSlice<f32>> = planes_host
6487 .iter()
6488 .map(|v| e.htod(v))
6489 .collect::<Result<_, _>>()?;
6490 let ptr_vals: Vec<u64> = {
6491 let stream = e.gpu.stream();
6492 planes.iter().map(|p| p.device_ptr(&stream).0).collect()
6493 };
6494 let ptrs = e.htod_u64(&ptr_vals)?;
6495 let mut worst = 0.0f32;
6496 let check = |name: &str, a: &[f32], b: &[f32], worst: &mut f32| -> Res<()> {
6497 for (i, (&x, &y)) in a.iter().zip(b).enumerate() {
6498 let rel = (x - y).abs() / y.abs().max(1.0);
6499 if rel > *worst {
6500 *worst = rel;
6501 }
6502 if rel > 1e-4 {
6503 return Err(format!(
6504 "hc-micro oracle: {name} idx {i}: micro {x} classic {y} (rel {rel:.3e})"
6505 )
6506 .into());
6507 }
6508 }
6509 Ok(())
6510 };
6511
6512 let norm_stack_host = rand_vec(wide);
6514 let norm_stack = e.htod(&norm_stack_host)?;
6515 let eps = 1e-6f32;
6516 let mut normed_a = e.zeros(streams * t * hidden)?;
6517 launch_hc_norm_planes(
6518 e,
6519 &ptrs,
6520 &norm_stack,
6521 &mut normed_a,
6522 hidden,
6523 t,
6524 streams,
6525 eps,
6526 )?;
6527 let mut normed_b = e.zeros(streams * t * hidden)?;
6528 for s in 0..streams {
6529 let w = e.htod(&norm_stack_host[s * hidden..(s + 1) * hidden])?;
6530 let mut dst = normed_b.slice_mut(s * t * hidden..(s + 1) * t * hidden);
6531 launch_rms_norm_into_view(e, &planes[s], &w, &mut dst, hidden, t, eps)?;
6532 }
6533 check("norm", &e.dtoh(&normed_a)?, &e.dtoh(&normed_b)?, &mut worst)?;
6534
6535 let inj_w_host = rand_vec(streams * wide);
6537 let inj_w = e.htod(&inj_w_host)?;
6538 let mut all_a = e.zeros(streams * t)?;
6539 let mut partials = e.zeros(streams * t * 16)?;
6540 launch_hc_inject_two_stage(
6541 e,
6542 &normed_b,
6543 &inj_w,
6544 None,
6545 &mut partials,
6546 &mut all_a,
6547 streams,
6548 t,
6549 hidden,
6550 16,
6551 )?;
6552 let mut all_b = e.zeros(streams * t)?;
6553 launch_hc_inject_gates(e, &normed_b, &inj_w, &mut all_b, streams, t, hidden)?;
6554 check("inject", &e.dtoh(&all_a)?, &e.dtoh(&all_b)?, &mut worst)?;
6555
6556 let block_out = e.htod(&rand_vec(t * hidden))?;
6558 launch_hc_write_planes(e, &ptrs, &block_out, &all_b, hidden, t, streams)?;
6559 let mut expect: Vec<Vec<f32>> = Vec::with_capacity(streams);
6560 let all_host = e.dtoh(&all_b)?;
6561 let bo_host = e.dtoh(&block_out)?;
6562 for (s, base) in planes_host.iter().enumerate() {
6563 let mut rows = base.clone();
6564 for tok in 0..t {
6565 let g = all_host[s * t + tok];
6566 for d in 0..hidden {
6567 rows[tok * hidden + d] += bo_host[tok * hidden + d] * g;
6568 }
6569 }
6570 expect.push(rows);
6571 }
6572 for (s, plane) in planes.iter().enumerate() {
6573 check(
6574 &format!("write plane {s}"),
6575 &e.dtoh(plane)?,
6576 &expect[s],
6577 &mut worst,
6578 )?;
6579 }
6580 Ok(format!(
6581 "hc-micro real-geometry oracle: streams 4 hidden 2560 t 10, worst rel {worst:.3e} \
6582 over norm/inject/write vs the classic composition"
6583 ))
6584}
6585
6586pub fn gate_sdpa_blocklist(e: &Engine) -> Res<String> {
6604 let mut lcg = 0x51ee_7bad_u64;
6605 let mut next_f32 = move || -> f32 {
6606 lcg = lcg
6607 .wrapping_mul(6364136223846793005)
6608 .wrapping_add(1442695040888963407);
6609 (((lcg >> 33) as u32) % 2000) as f32 / 1000.0 - 1.0
6610 };
6611 let (hd, nh, nkv, t) = (256usize, 24usize, 2usize, 3usize);
6612 let block_size = 4usize;
6613 let scale = 1.0 / (hd as f32).sqrt();
6614 let mut bit_rows = 0usize;
6615 let mut worst_rel = 0.0f32;
6616 for (t_kv, vs_masked) in [(4096usize, true), (16384usize, false)] {
6617 let q_host: Vec<f32> = (0..t * nh * hd).map(|_| next_f32()).collect();
6618 let k_host: Vec<f32> = (0..t_kv * nkv * hd).map(|_| next_f32()).collect();
6619 let v_host: Vec<f32> = (0..t_kv * nkv * hd).map(|_| next_f32()).collect();
6620 let sels: Vec<RowSel> = (0..t)
6623 .map(|qt| {
6624 let visible = t_kv - t + qt + 1;
6625 let complete = visible / block_size;
6626 if qt == 0 && vs_masked {
6631 return RowSel {
6632 full: true,
6633 blocks: Vec::new(),
6634 visible,
6635 };
6636 }
6637 let stride = if qt == 1 { 3 } else { 7 };
6638 let blocks: Vec<u32> = (0..complete as u32)
6639 .rev()
6640 .step_by(stride)
6641 .take(512)
6642 .collect::<Vec<_>>()
6643 .into_iter()
6644 .rev()
6645 .collect();
6646 RowSel {
6647 full: false,
6648 blocks,
6649 visible,
6650 }
6651 })
6652 .collect();
6653 let (pos_flat, meta, max_count) = rowsel_positions(&sels, block_size);
6654 let q = e.htod(&q_host)?;
6655 let k = e.htod(&k_host)?;
6656 let v = e.htod(&v_host)?;
6657 let pos = e.htod_i32(&pos_flat)?;
6658 let meta_dev = e.htod_i32(&meta)?;
6659 let mut o_list = e.zeros(t * nh * hd)?;
6660 launch_sdpa_blocklist(
6661 e,
6662 &q,
6663 &k.slice(0..t_kv * nkv * hd),
6664 &v.slice(0..t_kv * nkv * hd),
6665 &mut o_list,
6666 &pos,
6667 &meta_dev,
6668 hd,
6669 nh,
6670 nkv,
6671 t,
6672 max_count,
6673 scale,
6674 )?;
6675 let ours = e.dtoh(&o_list)?;
6676 if vs_masked {
6677 let mask = rowsel_to_mask(&sels, block_size, t_kv);
6678 let mask_dev = e.htod_bytes(&mask)?;
6679 let mut o_mask = e.zeros(t * nh * hd)?;
6680 launch_sdpa_mask(
6681 e,
6682 &q,
6683 &k.slice(0..t_kv * nkv * hd),
6684 &v.slice(0..t_kv * nkv * hd),
6685 &mut o_mask,
6686 &mask_dev,
6687 hd,
6688 nh,
6689 nkv,
6690 t,
6691 t_kv,
6692 scale,
6693 )?;
6694 let masked = e.dtoh(&o_mask)?;
6695 for (i, (a, b)) in masked.iter().zip(ours.iter()).enumerate() {
6696 if a.to_bits() != b.to_bits() {
6697 return Err(format!(
6698 "sdpa_blocklist vs masked: bit mismatch at {i}: {a} vs {b} (t_kv {t_kv})"
6699 )
6700 .into());
6701 }
6702 }
6703 bit_rows = t * nh * hd;
6704 } else {
6705 for qt in 0..t {
6708 let off = meta[2 * qt] as usize;
6709 let count = meta[2 * qt + 1] as usize;
6710 for head in 0..nh {
6711 let kvh = head / (nh / nkv);
6712 let qrow = &q_host[(qt * nh + head) * hd..(qt * nh + head + 1) * hd];
6713 let mut scores: Vec<f32> = (0..count)
6714 .map(|i| {
6715 let p = pos_flat[off + i] as usize;
6716 let krow = &k_host[(p * nkv + kvh) * hd..(p * nkv + kvh + 1) * hd];
6717 let mut acc = 0.0f32;
6718 for d in 0..hd {
6719 acc += qrow[d] * krow[d];
6720 }
6721 acc * scale
6722 })
6723 .collect();
6724 let mx = scores.iter().copied().fold(-1e30f32, f32::max);
6725 let mut sum = 0.0f32;
6726 for s in scores.iter_mut() {
6727 *s = (*s - mx).exp();
6728 sum += *s;
6729 }
6730 let inv = 1.0 / sum;
6731 for s in scores.iter_mut() {
6732 *s *= inv;
6733 }
6734 for d in 0..hd {
6735 let mut acc = 0.0f32;
6736 for (i, s) in scores.iter().enumerate() {
6737 let p = pos_flat[off + i] as usize;
6738 acc += s * v_host[(p * nkv + kvh) * hd + d];
6739 }
6740 let got = ours[(qt * nh + head) * hd + d];
6741 let rel = (got - acc).abs() / acc.abs().max(1e-3);
6742 worst_rel = worst_rel.max(rel);
6743 if rel > 1e-4 {
6744 return Err(format!(
6745 "sdpa_blocklist vs host twin: rel {rel} at row {qt} head {head} \
6746 dim {d} (t_kv {t_kv})"
6747 )
6748 .into());
6749 }
6750 }
6751 }
6752 }
6753 }
6754 }
6755 Ok(format!(
6756 "sdpa-blocklist oracle: BIT-IDENTICAL to the masked kernel over {bit_rows} values \
6757 (t_kv 4096, full+stride selections); past the mask bound (t_kv 16384) worst rel \
6758 {worst_rel:.3e} vs the host twin"
6759 ))
6760}
6761
6762pub fn gate_kvq_kernels(e: &Engine) -> Res<String> {
6776 let mut lcg = 0x6b76_715ee_du64; let mut next_f32 = move || -> f32 {
6778 lcg = lcg
6779 .wrapping_mul(6364136223846793005)
6780 .wrapping_add(1442695040888963407);
6781 (((lcg >> 33) as u32) % 2000) as f32 / 1000.0 - 1.0
6782 };
6783 let mut report = Vec::new();
6784
6785 for &dim in &[512usize, 40usize] {
6787 let rows = 9usize;
6788 let mut host_rows_f: Vec<f32> = (0..rows * dim).map(|_| next_f32()).collect();
6789 for v in host_rows_f[0..dim].iter_mut() {
6792 *v = 0.0;
6793 }
6794 for v in host_rows_f[dim..2 * dim].iter_mut() {
6795 *v = 0.75;
6796 }
6797 for (i, v) in host_rows_f[2 * dim..3 * dim].iter_mut().enumerate() {
6798 *v = if i == 0 {
6800 1.0
6801 } else {
6802 (i as f32) * 0.5 / 127.0
6803 };
6804 }
6805 for v in host_rows_f[3 * dim..4 * dim].iter_mut() {
6807 *v *= 1e-40;
6808 }
6809 let dev_rows = e.htod(&host_rows_f)?;
6810 let mut kq = e.alloc_u8(rows * q8_row_bytes(dim))?;
6811 let mut vq = e.alloc_u8(rows * q5_row_bytes(dim))?;
6812 launch_q4e_kv_append(e, &dev_rows, &dev_rows, &mut kq, &mut vq, 0, rows, dim)?;
6813 let kq_host = e.dtoh_u8(&kq)?;
6814 let vq_host = e.dtoh_u8(&vq)?;
6815 let mut k_twin = Vec::new();
6816 let mut v_twin = Vec::new();
6817 for r in 0..rows {
6818 host_quant_q8_row(&host_rows_f[r * dim..(r + 1) * dim], dim, &mut k_twin);
6819 host_quant_q5_row(&host_rows_f[r * dim..(r + 1) * dim], dim, &mut v_twin);
6820 }
6821 if kq_host != k_twin {
6822 let i = kq_host.iter().zip(&k_twin).position(|(a, b)| a != b);
6823 return Err(format!("kvq q8 quantize twin: byte mismatch at {i:?} (dim {dim})").into());
6824 }
6825 if vq_host != v_twin {
6826 let i = vq_host.iter().zip(&v_twin).position(|(a, b)| a != b);
6827 return Err(format!("kvq q5 quantize twin: byte mismatch at {i:?} (dim {dim})").into());
6828 }
6829 let mut kf = e.zeros(rows * dim)?;
6831 let mut vf = e.zeros(rows * dim)?;
6832 launch_q4e_kv_dequant_rows(e, &kq, &vq, &mut kf, &mut vf, 0, rows, dim)?;
6833 let kf_host = e.dtoh(&kf)?;
6834 let vf_host = e.dtoh(&vf)?;
6835 let mut kf_twin = Vec::new();
6836 let mut vf_twin = Vec::new();
6837 host_deq_q8_rows(&kq_host, 0, rows, dim, &mut kf_twin);
6838 host_deq_q5_rows(&vq_host, 0, rows, dim, &mut vf_twin);
6839 for (i, (a, b)) in kf_host.iter().zip(&kf_twin).enumerate() {
6840 if a.to_bits() != b.to_bits() {
6841 return Err(format!("kvq q8 dequant twin: bit mismatch at {i} (dim {dim})").into());
6842 }
6843 }
6844 for (i, (a, b)) in vf_host.iter().zip(&vf_twin).enumerate() {
6845 if a.to_bits() != b.to_bits() {
6846 return Err(format!("kvq q5 dequant twin: bit mismatch at {i} (dim {dim})").into());
6847 }
6848 }
6849 report.push(format!("quant+dequant twins dim {dim}: BYTE/BIT-IDENTICAL"));
6850 }
6851
6852 {
6854 let (hd, nh, nkv, t) = (256usize, 24usize, 2usize, 3usize);
6855 let kv_dim = nkv * hd;
6856 let block_size = 4usize;
6857 let scale = 1.0 / (hd as f32).sqrt();
6858 let t_kv = 4096usize;
6859 let q_host: Vec<f32> = (0..t * nh * hd).map(|_| next_f32()).collect();
6860 let k_host: Vec<f32> = (0..t_kv * kv_dim).map(|_| next_f32()).collect();
6861 let v_host: Vec<f32> = (0..t_kv * kv_dim).map(|_| next_f32()).collect();
6862 let k_rows = e.htod(&k_host)?;
6863 let v_rows = e.htod(&v_host)?;
6864 let mut kq = e.alloc_u8(t_kv * q8_row_bytes(kv_dim))?;
6865 let mut vq = e.alloc_u8(t_kv * q5_row_bytes(kv_dim))?;
6866 launch_q4e_kv_append(e, &k_rows, &v_rows, &mut kq, &mut vq, 0, t_kv, kv_dim)?;
6867 let sels: Vec<RowSel> = (0..t)
6870 .map(|qt| {
6871 let visible = (t_kv - t + qt + 1).min(2052);
6872 if qt == 0 {
6873 return RowSel {
6874 full: true,
6875 blocks: Vec::new(),
6876 visible,
6877 };
6878 }
6879 let complete = (t_kv - t + qt + 1) / block_size;
6880 let stride = if qt == 1 { 3 } else { 7 };
6881 let blocks: Vec<u32> = (0..complete as u32)
6882 .rev()
6883 .step_by(stride)
6884 .take(512)
6885 .collect::<Vec<_>>()
6886 .into_iter()
6887 .rev()
6888 .collect();
6889 RowSel {
6890 full: false,
6891 blocks,
6892 visible: t_kv - t + qt + 1,
6893 }
6894 })
6895 .collect();
6896 let (pos_flat, meta, max_count) = rowsel_positions(&sels, block_size);
6897 let q = e.htod(&q_host)?;
6898 let pos = e.htod_i32(&pos_flat)?;
6899 let meta_dev = e.htod_i32(&meta)?;
6900 let mut o_fused = e.zeros(t * nh * hd)?;
6901 launch_q4e_sdpa_blocklist_q8q5(
6902 e,
6903 &q,
6904 &kq,
6905 &vq,
6906 &mut o_fused,
6907 &pos,
6908 &meta_dev,
6909 hd,
6910 nh,
6911 nkv,
6912 t,
6913 max_count,
6914 scale,
6915 )?;
6916 let mut k_deq = e.zeros(t_kv * kv_dim)?;
6917 let mut v_deq = e.zeros(t_kv * kv_dim)?;
6918 launch_q4e_kv_dequant_rows(e, &kq, &vq, &mut k_deq, &mut v_deq, 0, t_kv, kv_dim)?;
6919 let mut o_comp = e.zeros(t * nh * hd)?;
6920 launch_sdpa_blocklist(
6921 e,
6922 &q,
6923 &k_deq.slice(0..t_kv * kv_dim),
6924 &v_deq.slice(0..t_kv * kv_dim),
6925 &mut o_comp,
6926 &pos,
6927 &meta_dev,
6928 hd,
6929 nh,
6930 nkv,
6931 t,
6932 max_count,
6933 scale,
6934 )?;
6935 let fused = e.dtoh(&o_fused)?;
6936 let comp = e.dtoh(&o_comp)?;
6937 for (i, (a, b)) in fused.iter().zip(&comp).enumerate() {
6938 if a.to_bits() != b.to_bits() {
6939 return Err(format!(
6940 "kvq fused attention vs dequant composition: bit mismatch at {i}: {a} vs {b}"
6941 )
6942 .into());
6943 }
6944 }
6945 {
6957 let was = kv_hoist_on();
6958 set_kv_hoist(true);
6959 let mut o_hoist = e.zeros(t * nh * hd)?;
6960 let launched = launch_q4e_sdpa_blocklist_q8q5(
6961 e,
6962 &q,
6963 &kq,
6964 &vq,
6965 &mut o_hoist,
6966 &pos,
6967 &meta_dev,
6968 hd,
6969 nh,
6970 nkv,
6971 t,
6972 max_count,
6973 scale,
6974 );
6975 set_kv_hoist(was);
6976 launched?;
6977 let hoist = e.dtoh(&o_hoist)?;
6978 let mut worst: Option<(usize, f32, f32)> = None;
6979 for (i, (a, b)) in hoist.iter().zip(&fused).enumerate() {
6980 if a.to_bits() != b.to_bits() && worst.is_none() {
6981 worst = Some((i, *a, *b));
6982 }
6983 }
6984 if let Some((i, a, b)) = worst {
6985 return Err(format!(
6986 "kvhoist vs un-hoisted q8q5 blocklist: bit mismatch at {i}: {a} vs {b} \
6987 (hd={hd} nh={nh} nkv={nkv} t={t} t_kv={t_kv} max_count={max_count})"
6988 )
6989 .into());
6990 }
6991 report.push(format!(
6997 "kvhoist vs un-hoisted q8q5 blocklist: BIT-IDENTICAL over {} values \
6998 (real geometry hd={hd} nh={nh} nkv={nkv}, {} blocks/head slice, max_count={max_count})",
6999 t * nh * hd,
7000 hd / 32
7001 ));
7002 }
7003 report.push(format!(
7004 "fused q8q5 blocklist vs dequant+f32 composition: BIT-IDENTICAL over {} values",
7005 t * nh * hd
7006 ));
7007 }
7008
7009 {
7011 let idx_dim = 128usize;
7012 let qk_width = 5 * idx_dim; let rows = 7usize;
7014 let src_host: Vec<f32> = (0..rows * qk_width).map(|_| next_f32()).collect();
7015 let src = e.htod(&src_host)?;
7016 let q_off = 4 * idx_dim;
7017 let mut dst_q8 = e.alloc_u8((rows + 2) * q8_row_bytes(idx_dim))?;
7019 launch_q4e_idx_append_q8(e, &src, &mut dst_q8, rows, idx_dim, qk_width, q_off, 2)?;
7020 let got = e.dtoh_u8(&dst_q8)?;
7021 let mut twin = vec![0u8; 2 * q8_row_bytes(idx_dim)];
7022 for r in 0..rows {
7023 host_quant_q8_row(
7024 &src_host[r * qk_width + q_off..(r + 1) * qk_width],
7025 idx_dim,
7026 &mut twin,
7027 );
7028 }
7029 if got[2 * q8_row_bytes(idx_dim)..] != twin[2 * q8_row_bytes(idx_dim)..] {
7030 return Err("idxq q8 append twin: byte mismatch".into());
7031 }
7032 let mut dst_bf = unsafe { e.gpu.stream().alloc::<u16>((rows + 2) * idx_dim)? };
7034 e.gpu.stream().memset_zeros(&mut dst_bf)?;
7035 launch_q4e_idx_append_bf16(e, &src, &mut dst_bf, rows, idx_dim, qk_width, q_off, 2)?;
7036 let got_bf: Vec<u16> = {
7037 let v = e
7038 .gpu
7039 .stream()
7040 .clone_dtoh(&dst_bf.slice(0..(rows + 2) * idx_dim))?;
7041 e.gpu.stream().synchronize()?;
7042 v
7043 };
7044 for r in 0..rows {
7045 for c in 0..idx_dim {
7046 let want = f32_to_bf16_rne(src_host[r * qk_width + q_off + c]);
7047 if got_bf[(2 + r) * idx_dim + c] != want {
7048 return Err(format!("idxq bf16 append twin: mismatch row {r} col {c}").into());
7049 }
7050 }
7051 }
7052 report.push("idx q8/bf16 appenders vs host twins: BYTE-IDENTICAL".to_string());
7053 }
7054
7055 Ok(format!("kvq kernel oracles: {}", report.join("; ")))
7056}
7057
7058pub fn gate_qsa_index_score(e: &Engine) -> Res<String> {
7063 let mut lcg = 0xfeed_1234_u64;
7064 let mut next_f32 = move || -> f32 {
7065 lcg = lcg
7066 .wrapping_mul(6364136223846793005)
7067 .wrapping_add(1442695040888963407);
7068 (((lcg >> 33) as u32) % 4000) as f32 / 2000.0 - 1.0
7069 };
7070 let (heads, head_dim) = (4usize, 128usize);
7071 let scale = (head_dim as f32).sqrt();
7072 let budget = 512usize;
7073 let mut worst_rows = 0usize;
7074 for (rows, n_blocks) in [(1usize, 4096usize), (7, 1031)] {
7075 let q_host: Vec<f32> = (0..rows * heads * head_dim).map(|_| next_f32()).collect();
7076 let pooled_host: Vec<f32> = (0..n_blocks * head_dim).map(|_| next_f32()).collect();
7077 let q = e.htod(&q_host)?;
7078 let pooled = e.htod(&pooled_host)?;
7079 let mut scores_dev = e.uninit(rows * n_blocks)?;
7080 launch_qsa_index_score(
7081 e,
7082 &q,
7083 &pooled,
7084 &mut scores_dev,
7085 heads,
7086 head_dim,
7087 n_blocks,
7088 rows,
7089 scale,
7090 )?;
7091 let got = e.dtoh(&scores_dev)?;
7092 for row in 0..rows {
7093 let qr = &q_host[row * heads * head_dim..(row + 1) * heads * head_dim];
7094 let host = score_blocks(qr, &pooled_host, heads, head_dim, n_blocks, scale, 1);
7095 for (b, want) in host.iter().enumerate() {
7096 let g = got[row * n_blocks + b];
7097 if g.to_bits() != want.to_bits() {
7098 return Err(format!(
7099 "qsa_index_score: bit mismatch row {row} block {b}: {g} vs host {want}"
7100 )
7101 .into());
7102 }
7103 }
7104 let a = top_blocks_ascending(&host, budget, 1);
7105 let b = top_blocks_ascending(&got[row * n_blocks..(row + 1) * n_blocks], budget, 1);
7106 if a != b {
7107 return Err(format!("qsa_index_score: top-k set differs at row {row}").into());
7108 }
7109 worst_rows += 1;
7110 }
7111 }
7112 let mut pool_t_rows = 0usize;
7127 for (rows, n_blocks, cap_rows) in [(1usize, 1031usize, 4096usize), (5, 2048, 2048)] {
7128 let q_host: Vec<f32> = (0..rows * heads * head_dim).map(|_| next_f32()).collect();
7129 let pooled_host: Vec<f32> = (0..n_blocks * head_dim).map(|_| next_f32()).collect();
7130 let q = e.htod(&q_host)?;
7131 let mut mirror = e.zeros(cap_rows * head_dim * POOL_PLANES)?;
7134 {
7135 let mut view = mirror.slice_mut(0..n_blocks * head_dim);
7136 e.gpu.stream().memcpy_htod(&pooled_host, &mut view)?;
7137 }
7138 launch_qsa_pooled_transpose(e, &mut mirror, 0, n_blocks, head_dim, cap_rows)?;
7139 let was = pool_t_on();
7140 set_pool_t(true);
7141 let mut scores_dev = e.uninit(rows * n_blocks)?;
7142 let launched = launch_qsa_index_score(
7143 e,
7144 &q,
7145 &mirror,
7146 &mut scores_dev,
7147 heads,
7148 head_dim,
7149 n_blocks,
7150 rows,
7151 scale,
7152 );
7153 set_pool_t(was);
7154 launched?;
7155 let got = e.dtoh(&scores_dev)?;
7156 for row in 0..rows {
7157 let qr = &q_host[row * heads * head_dim..(row + 1) * heads * head_dim];
7158 let host = score_blocks(qr, &pooled_host, heads, head_dim, n_blocks, scale, 1);
7159 for (b, want) in host.iter().enumerate() {
7160 let g = got[row * n_blocks + b];
7161 if g.to_bits() != want.to_bits() {
7162 return Err(format!(
7163 "poolT qsa_index_score_f32_t: bit mismatch row {row} block {b}: \
7164 {g} vs host {want} (n_blocks={n_blocks} cap_rows={cap_rows})"
7165 )
7166 .into());
7167 }
7168 }
7169 if top_blocks_ascending(&host, budget, 1)
7170 != top_blocks_ascending(&got[row * n_blocks..(row + 1) * n_blocks], budget, 1)
7171 {
7172 return Err(format!(
7173 "poolT qsa_index_score_f32_t: top-{budget} set differs at row {row} \
7174 (n_blocks={n_blocks} cap_rows={cap_rows})"
7175 )
7176 .into());
7177 }
7178 pool_t_rows += 1;
7179 }
7180 }
7181 Ok(format!(
7182 "qsa-index-score oracle: device scores BIT-IDENTICAL to the host twin over \
7183 {worst_rows} rows (4096 + 1031 blocks, real 4x128 geometry) and top-512 sets equal; \
7184 poolT dim-major plane (transpose + transposed kernel) BIT-IDENTICAL to the SAME host \
7185 twin over {pool_t_rows} rows, incl. the pitch-trap case cap_rows=4096 != n_blocks=1031"
7186 ))
7187}
7188
7189pub fn gate_ple_ngram_cache() -> Res<String> {
7208 let max_ngram = 3usize;
7210 let heads_per_ngram = 8usize;
7211 let total_heads = (max_ngram - 1) * heads_per_ngram;
7212 let multipliers: Vec<i64> = vec![
7213 0x2545_F491_4F6C_DD1D,
7214 0x9E37_79B9_7F4A_7C15u64 as i64,
7215 0x1234_5678_9ABC_DEF1,
7216 ];
7217 let sizes: Vec<i64> = (0..total_heads)
7218 .map(|i| 2_500_012_160 - (i as i64) * 7)
7219 .collect();
7220 let offsets: Vec<i64> = (0..total_heads)
7221 .map(|i| (i as i64) * 2_500_012_160)
7222 .collect();
7223 let eos = 248_046u32;
7224 let full = |ids: &[u32]| -> Vec<i64> {
7225 host_ngram_ids(
7226 ids,
7227 &multipliers,
7228 &sizes,
7229 &offsets,
7230 max_ngram,
7231 heads_per_ngram,
7232 eos,
7233 )
7234 };
7235 let mut lcg = 0x0be1_10ca_u64;
7236 let mut next_tok = move || -> u32 {
7237 lcg = lcg
7238 .wrapping_mul(6364136223846793005)
7239 .wrapping_add(1442695040888963407);
7240 ((lcg >> 33) as u32) % 250_000
7241 };
7242 let mut checks = 0usize;
7243 let mut run = |label: &str, steps: Vec<Vec<u32>>| -> Res<usize> {
7244 let (mut ci, mut ch, mut ce) = (Vec::new(), Vec::new(), -1i64);
7246 let mut n = 0usize;
7247 for seq in &steps {
7248 host_ngram_ids_cached(
7249 &mut ci,
7250 &mut ch,
7251 &mut ce,
7252 seq,
7253 &multipliers,
7254 &sizes,
7255 &offsets,
7256 max_ngram,
7257 heads_per_ngram,
7258 eos,
7259 );
7260 let want = full(seq);
7261 if ci.len() != want.len() {
7262 return Err(format!(
7263 "plecache oracle {label}: cache has {} ids, twin {} at len {}",
7264 ci.len(),
7265 want.len(),
7266 seq.len()
7267 )
7268 .into());
7269 }
7270 if let Some(i) = ci.iter().zip(&want).position(|(a, b)| a != b) {
7271 return Err(format!(
7272 "plecache oracle {label}: id {i} differs at len {} (token {}, head {}): \
7273 cache {} vs twin {}",
7274 seq.len(),
7275 i / total_heads,
7276 i % total_heads,
7277 ci[i],
7278 want[i]
7279 )
7280 .into());
7281 }
7282 n += seq.len();
7283 }
7284 Ok(n)
7285 };
7286 {
7288 let base: Vec<u32> = (0..200).map(|_| next_tok()).collect();
7289 let steps: Vec<Vec<u32>> = (1..=base.len()).map(|n| base[..n].to_vec()).collect();
7290 checks += run("decode-growth", steps)?;
7291 }
7292 {
7294 let base: Vec<u32> = (0..600).map(|_| next_tok()).collect();
7295 let mut steps = Vec::new();
7296 let mut n = 0usize;
7297 for step in [7usize, 1, 64, 3, 128, 2, 200, 195] {
7298 n = (n + step).min(base.len());
7299 steps.push(base[..n].to_vec());
7300 }
7301 checks += run("prefill-chunks", steps)?;
7302 }
7303 {
7305 let mut base: Vec<u32> = (0..300).map(|_| next_tok()).collect();
7306 for p in [0usize, 1, 2, 37, 38, 100, 101, 102, 299] {
7307 base[p] = eos;
7308 }
7309 let steps: Vec<Vec<u32>> = (1..=base.len()).map(|n| base[..n].to_vec()).collect();
7310 checks += run("eos-segments", steps)?;
7311 }
7312 {
7314 let base: Vec<u32> = vec![eos; 40];
7315 let steps: Vec<Vec<u32>> = (1..=base.len()).map(|n| base[..n].to_vec()).collect();
7316 checks += run("all-eos", steps)?;
7317 }
7318 {
7320 let a: Vec<u32> = (0..300).map(|_| next_tok()).collect();
7321 let mut b = a.clone();
7322 b[150] = a[150].wrapping_add(1) % 250_000;
7323 let mut c = b.clone();
7324 c[7] = b[7].wrapping_add(3) % 250_000;
7325 let mut d = c.clone();
7326 d.truncate(9);
7327 d.extend((0..100).map(|_| next_tok()));
7328 checks += run(
7329 "rewind-divergent",
7330 vec![
7331 a.clone(),
7332 a[..151].to_vec(),
7333 b.clone(),
7334 b[..8].to_vec(),
7335 c.clone(),
7336 d.clone(),
7337 a.clone(),
7338 ],
7339 )?;
7340 }
7341 {
7343 let a: Vec<u32> = (0..250).map(|_| next_tok()).collect();
7344 let mut s: Vec<u32> = (0..11).map(|_| next_tok()).collect();
7345 s[0] = eos;
7346 checks += run("state-reuse", vec![a.clone(), s.clone(), a.clone(), s])?;
7347 }
7348 Ok(format!(
7349 "plecache oracle: incremental n-gram ids EXACT vs the full host_ngram_ids twin over \
7350 {checks} cumulative-sequence comparisons across 6 case families (decode one-at-a-time \
7351 growth, ragged prefill chunks, eos segment resets incl. adjacent + leading + trailing \
7352 eos, all-eos, repeated rewinds to DIVERGING prefixes, and shorter-unrelated-sequence \
7353 state reuse)"
7354 ))
7355}
7356
7357pub fn gate_seam_table() -> Res<String> {
7373 let all: &[&str] = seam_names();
7378 let boolean: Vec<&str> = all
7379 .iter()
7380 .copied()
7381 .filter(|n| seam_state(n).is_some())
7382 .collect();
7383 if boolean.len() < 20 || all.len() < boolean.len() + 2 {
7388 return Err(format!(
7389 "seam-table oracle: refusing to report on {} boolean names out of {} total — the \
7390 seam list collapsed, so every assertion below would be vacuous",
7391 boolean.len(),
7392 all.len()
7393 )
7394 .into());
7395 }
7396 let names: &[&str] = &boolean;
7397 let snapshot = || -> Res<Vec<bool>> {
7398 names
7399 .iter()
7400 .map(|n| {
7401 seam_state(n).ok_or_else(|| {
7402 Box::<dyn std::error::Error>::from(format!(
7403 "seam-table oracle: seam_state({n:?}) is None — the name is in set_seam \
7404 but not in seam_state, so save/restore around a measurement would \
7405 silently not restore it"
7406 ))
7407 })
7408 })
7409 .collect()
7410 };
7411 let restore = |v: &[bool]| {
7412 for (n, &b) in names.iter().zip(v) {
7413 set_seam(n, b, None);
7414 }
7415 };
7416 let entry = snapshot()?;
7417 let mut checks = 0usize;
7418 for (i, name) in names.iter().enumerate() {
7419 for &want in &[true, false, true] {
7420 let before = snapshot()?;
7421 if !set_seam(name, want, None) {
7422 restore(&entry);
7423 return Err(format!("seam-table oracle: set_seam({name:?}) refused").into());
7424 }
7425 let after = snapshot()?;
7426 if after[i] != want {
7427 restore(&entry);
7428 return Err(format!(
7429 "seam-table oracle: set_seam({name:?}, {want}) then seam_state read {} — the \
7430 two tables disagree on this name",
7431 after[i]
7432 )
7433 .into());
7434 }
7435 for (j, other) in names.iter().enumerate() {
7439 if j != i && after[j] != before[j] {
7440 restore(&entry);
7441 return Err(format!(
7442 "seam-table oracle: arming {name:?} also changed {other:?} ({} -> {}) — \
7443 two names share one switch",
7444 before[j], after[j]
7445 )
7446 .into());
7447 }
7448 }
7449 checks += 1;
7450 }
7451 }
7452 for name in all {
7456 if !seam_exists(name) {
7457 restore(&entry);
7458 return Err(format!(
7459 "seam-table oracle: seam_names() lists {name:?} but seam_exists refuses it"
7460 )
7461 .into());
7462 }
7463 if !set_seam(name, seam_state(name).unwrap_or(false), None) {
7464 restore(&entry);
7465 return Err(format!(
7466 "seam-table oracle: seam_names() lists {name:?} but set_seam refuses it"
7467 )
7468 .into());
7469 }
7470 }
7471 if seam_exists("definitely-not-a-seam") || set_seam("definitely-not-a-seam", true, None) {
7473 restore(&entry);
7474 return Err("seam-table oracle: an unknown seam name was accepted".into());
7475 }
7476 let before = snapshot()?;
7479 for name in all {
7480 let _ = seam_exists(name);
7481 }
7482 if snapshot()? != before {
7483 restore(&entry);
7484 return Err("seam-table oracle: seam_exists mutated a seam (it must be name-only)".into());
7485 }
7486 restore(&entry);
7487 if snapshot()? != entry {
7488 return Err("seam-table oracle: the gate did not restore the entry state".into());
7489 }
7490 Ok(format!(
7491 "seam-table oracle: {} boolean seam names of {} total, {checks} set/read cycles, each \
7492 verified to change its OWN state and NO other (the cross-check that catches an arm \
7493 wired to a neighbour's switch), every listed name accepted by both entry points, \
7494 unknown names refused by both, seam_exists proven side-effect-free, entry state restored",
7495 names.len(),
7496 all.len()
7497 ))
7498}
7499
7500pub fn gate_qsa_index_topk(e: &Engine) -> Res<String> {
7523 let budget = 512usize;
7524 let mut lcg = 0x1d5e_10ca_u64;
7525 let mut rows_checked = 0usize;
7526 let mut deepest = 0usize;
7527 let mut cases: Vec<(String, Vec<usize>, usize, Vec<f32>)> = Vec::new();
7529 let mut next_f32 = move || -> f32 {
7530 lcg = lcg
7531 .wrapping_mul(6364136223846793005)
7532 .wrapping_add(1442695040888963407);
7533 let r = ((lcg >> 33) as u32) % 1000;
7535 if r < 250 { 0.0 } else { (r as f32) / 250.0 }
7536 };
7537 for (label, counts) in [
7538 ("real-262k-depth", vec![65_536usize]),
7539 ("real-131k-depth", vec![32_768usize, 32_768]),
7540 ("shallow", vec![513usize, 1_031, 4_096]),
7541 ("ragged-batch", vec![2_049usize, 8_191, 65_536, 4_097]),
7542 ] {
7543 let stride = *counts.iter().max().unwrap();
7544 let slab: Vec<f32> = (0..counts.len() * stride).map(|_| next_f32()).collect();
7545 cases.push((label.to_string(), counts, stride, slab));
7546 }
7547 cases.push((
7549 "all-zero".into(),
7550 vec![65_536usize],
7551 65_536,
7552 vec![0.0f32; 65_536],
7553 ));
7554 {
7556 let n = 4_096usize;
7557 let mut v = vec![0.0f32; n];
7558 for (i, slot) in v.iter_mut().enumerate() {
7559 *slot = if i < 12 {
7560 100.0 - i as f32
7561 } else if i % 7 == 0 {
7562 2.5 } else {
7564 (i % 3) as f32 * 0.25
7565 };
7566 }
7567 cases.push(("dup-straddle".into(), vec![n], n, v));
7568 }
7569 {
7572 let n = 2_048usize;
7573 let mut v = vec![0.0f32; n];
7574 for (i, slot) in v.iter_mut().enumerate() {
7575 *slot = match i % 8 {
7576 0 => 0.0,
7577 1 => -0.0,
7578 2 => f32::from_bits(1), 3 => -f32::from_bits(1), 4 => -(i as f32) * 0.5,
7581 5 => f32::NAN,
7582 6 => -f32::NAN,
7583 _ => (i % 5) as f32,
7584 };
7585 }
7586 cases.push(("total-cmp-domain".into(), vec![n], n, v));
7587 }
7588 for (label, counts, stride, slab) in &cases {
7589 let scores = e.htod(slab)?;
7590 let picked = launch_qsa_index_topk(e, &scores, counts, *stride, budget)?;
7591 if picked.len() != counts.len() {
7592 return Err(format!("idxsel oracle {label}: {} rows back", picked.len()).into());
7593 }
7594 for (r, &complete) in counts.iter().enumerate() {
7595 let row = &slab[r * *stride..r * *stride + complete];
7596 let twin = top_blocks_ascending(row, budget, 1);
7597 if twin != picked[r] {
7598 let first = twin
7599 .iter()
7600 .zip(picked[r].iter())
7601 .position(|(a, b)| a != b)
7602 .unwrap_or(twin.len().min(picked[r].len()));
7603 return Err(format!(
7604 "idxsel oracle {label}: selection differs at row {r} (blocks {complete}), \
7605 first differing slot {first}: host {:?} vs device {:?}",
7606 twin.get(first),
7607 picked[r].get(first)
7608 )
7609 .into());
7610 }
7611 rows_checked += 1;
7612 deepest = deepest.max(complete);
7613 }
7614 }
7615 Ok(format!(
7616 "qsa-index-topk oracle: device selection ids + ASCENDING order EXACT vs \
7617 top_blocks_ascending over {rows_checked} rows / {} cases at budget {budget}, \
7618 deepest {deepest} blocks (= the 262,144-token window), incl. the all-zero, \
7619 boundary-straddling-duplicate and total_cmp-domain (signed zero / subnormal / \
7620 negative / NaN) tie classes",
7621 cases.len()
7622 ))
7623}
7624
7625pub fn gate_route_kernel(e: &Engine) -> Res<String> {
7636 let mut lcg = 0x00de_7710_u64;
7637 let mut next_f32 = move || -> f32 {
7638 lcg = lcg
7639 .wrapping_mul(6364136223846793005)
7640 .wrapping_add(1442695040888963407);
7641 (((lcg >> 33) as u32) % 8000) as f32 / 200.0 - 20.0 };
7643 const ULP_BOUND: u32 = 2;
7644 let mut worst_ulp: u32 = 0;
7645 let mut rows_checked = 0usize;
7646 let run = |e: &Engine,
7647 label: &str,
7648 logits_host: &[f32],
7649 experts: usize,
7650 selected: usize,
7651 rows: usize,
7652 worst_ulp: &mut u32|
7653 -> Res<()> {
7654 let logits = e.htod(logits_host)?;
7655 let mut sel = e.alloc_uninit::<i32>(rows * selected)?;
7656 let mut w = e.uninit(rows * selected)?;
7657 let mut tok = e.alloc_uninit::<i32>(rows * selected)?;
7658 launch_route_topk(
7659 e,
7660 &logits,
7661 &mut sel,
7662 &mut w,
7663 Some((&mut tok, 3)),
7664 experts,
7665 selected,
7666 rows,
7667 )?;
7668 let sel_h = e.gpu.stream().clone_dtoh(&sel.slice(0..rows * selected))?;
7669 let w_h = e.dtoh(&w)?;
7670 let tok_h = e.gpu.stream().clone_dtoh(&tok.slice(0..rows * selected))?;
7671 let k = selected.min(experts);
7672 for row in 0..rows {
7673 let twin =
7674 host_route_softmax_topk(&logits_host[row * experts..(row + 1) * experts], selected);
7675 if twin.len() != k {
7676 return Err(format!("route oracle {label}: host twin width {}", twin.len()).into());
7677 }
7678 for (j, &(idx, wt)) in twin.iter().enumerate() {
7679 let ds = sel_h[row * selected + j];
7680 let dw = w_h[row * selected + j];
7681 if ds != idx as i32 {
7682 return Err(format!(
7683 "route oracle {label}: selection mismatch row {row} slot {j}: \
7684 device {ds} vs host {idx}"
7685 )
7686 .into());
7687 }
7688 let ulp = (dw.to_bits() as i64 - wt.to_bits() as i64).unsigned_abs();
7689 let ulp = u32::try_from(ulp).unwrap_or(u32::MAX);
7690 if ulp > ULP_BOUND {
7691 return Err(format!(
7692 "route oracle {label}: weight ULP {ulp} > {ULP_BOUND} at row {row} \
7693 slot {j}: device {dw:e} vs host {wt:e}"
7694 )
7695 .into());
7696 }
7697 *worst_ulp = (*worst_ulp).max(ulp);
7698 if tok_h[row * selected + j] != (3 + row) as i32 {
7699 return Err(format!(
7700 "route oracle {label}: tok map wrong at row {row} slot {j}"
7701 )
7702 .into());
7703 }
7704 }
7705 }
7706 Ok(())
7707 };
7708 let (experts, selected) = (512usize, 10usize);
7710 for rows in [1usize, 6, 16] {
7711 let logits: Vec<f32> = (0..rows * experts).map(|_| next_f32()).collect();
7712 run(e, "real", &logits, experts, selected, rows, &mut worst_ulp)?;
7713 rows_checked += rows;
7714 }
7715 let mut tie_rows: Vec<(String, Vec<f32>)> = Vec::new();
7717 {
7718 let mut v: Vec<f32> = (0..experts).map(|i| -30.0 - (i as f32) * 0.01).collect();
7721 for (rank, slot) in [40usize, 7, 300, 11].iter().enumerate() {
7722 v[*slot] = 10.0 - rank as f32;
7723 }
7724 for slot in [500usize, 3, 77, 210, 8, 401, 129, 64, 255, 380, 17, 450] {
7725 v[slot] = 2.5;
7726 }
7727 tie_rows.push(("dup-straddle".into(), v));
7728 tie_rows.push(("all-equal".into(), vec![0.125f32; experts]));
7730 let mut v = vec![-200.0f32; experts];
7733 v[100] = 5.0;
7734 for (i, slot) in [479usize, 2, 33].iter().enumerate() {
7735 v[*slot] = -80.0 - i as f32; }
7737 tie_rows.push(("underflow".into(), v));
7738 }
7739 for (label, v) in &tie_rows {
7740 run(e, label, v, experts, selected, 1, &mut worst_ulp)?;
7741 rows_checked += 1;
7742 }
7743 for (ex, se) in [(64usize, 4usize), (16, 16), (128, 32)] {
7745 let logits: Vec<f32> = (0..3 * ex).map(|_| next_f32()).collect();
7746 run(e, "geom", &logits, ex, se, 3, &mut worst_ulp)?;
7747 rows_checked += 3;
7748 }
7749 Ok(format!(
7750 "route oracle: device selection ids+order EXACT vs host twin over {rows_checked} rows \
7751 (real 512/10 + tie straddle/all-equal/underflow + geometry edges), worst weight \
7752 ULP {worst_ulp} (bound {ULP_BOUND}), tok map exact"
7753 ))
7754}
7755
7756pub fn gate_gdn_step_kernels(e: &Engine) -> Res<String> {
7757 let mut lcg = 0x0bad_cafe_u64;
7758 let mut next_f32 = move || -> f32 {
7759 lcg = lcg
7760 .wrapping_mul(6364136223846793005)
7761 .wrapping_add(1442695040888963407);
7762 (((lcg >> 33) as u32) % 2000) as f32 / 1000.0 - 1.0
7763 };
7764 let mut worst = 0.0f32;
7765 for (nk, nv, hk, hv) in [(16usize, 48usize, 128usize, 128usize), (2, 4, 32, 8)] {
7766 let conv_dim = 2 * nk * hk + nv * hv;
7767 let qkv_host: Vec<f32> = (0..conv_dim).map(|_| next_f32()).collect();
7768 let g_log_host: Vec<f32> = (0..nv).map(|_| next_f32().abs() * -2.0).collect();
7769 let beta_host: Vec<f32> = (0..nv).map(|_| next_f32()).collect();
7770 let state_host: Vec<f32> = (0..nv * hv * hk).map(|_| next_f32()).collect();
7771 let qkv = e.htod(&qkv_host)?;
7772 let g_log = e.htod(&g_log_host)?;
7773 let beta = e.htod(&beta_host)?;
7774 let scale = 1.0 / (hk as f32).sqrt();
7775 let eps = 1e-6f32;
7776 let mut state_a = e.htod(&state_host)?;
7777 let mut o_a = e.zeros(nv * hv)?;
7778 launch_gdn_scan(
7779 e,
7780 &qkv,
7781 &g_log,
7782 &beta,
7783 &mut state_a,
7784 &mut o_a,
7785 nk,
7786 nv,
7787 hk,
7788 hv,
7789 1,
7790 scale,
7791 eps,
7792 )?;
7793 let mut state_b = e.htod(&state_host)?;
7794 let mut o_b = e.zeros(nv * hv)?;
7795 launch_gdn_scan_step(
7796 e,
7797 &qkv,
7798 &g_log,
7799 &beta,
7800 &mut state_b,
7801 &mut o_b,
7802 nk,
7803 nv,
7804 hk,
7805 hv,
7806 scale,
7807 eps,
7808 )?;
7809 for (name, reference, candidate) in [
7810 ("o", e.dtoh(&o_a)?, e.dtoh(&o_b)?),
7811 ("state", e.dtoh(&state_a)?, e.dtoh(&state_b)?),
7812 ] {
7813 for (i, (&r, &c)) in reference.iter().zip(&candidate).enumerate() {
7814 let rel = (r - c).abs() / r.abs().max(1.0);
7815 if rel > worst {
7816 worst = rel;
7817 }
7818 if rel > 1e-4 {
7819 return Err(format!(
7820 "gdn-step oracle: nk{nk}/nv{nv}/hk{hk}/hv{hv} {name} idx {i}: \
7821 naive {r} step {c} (rel {rel:.3e})"
7822 )
7823 .into());
7824 }
7825 }
7826 }
7827 }
7828 let (rows, cols) = (48usize, 128usize);
7830 let x = e.htod(&(0..rows * cols).map(|_| next_f32()).collect::<Vec<_>>())?;
7831 let w = e.htod(&(0..cols).map(|_| next_f32()).collect::<Vec<_>>())?;
7832 let z = e.htod(&(0..rows * cols).map(|_| next_f32()).collect::<Vec<_>>())?;
7833 let eps = 1e-6f32;
7834 let mut normed = e.zeros(rows * cols)?;
7835 e.rms_norm(&x, &w, &mut normed, cols, rows, eps)?;
7836 let mut sg = e.zeros(rows * cols)?;
7837 e.sigmoid(&z, &mut sg, rows * cols)?;
7838 let mut chain = e.zeros(rows * cols)?;
7839 e.mul(&normed, &sg, &mut chain, rows * cols)?;
7840 let mut fused = e.zeros(rows * cols)?;
7841 launch_rms_sigmul(e, &x, &w, &z, &mut fused, cols, rows, eps)?;
7842 let (chain_h, fused_h) = (e.dtoh(&chain)?, e.dtoh(&fused)?);
7843 for (i, (&a, &b)) in chain_h.iter().zip(&fused_h).enumerate() {
7844 if a.to_bits() != b.to_bits() {
7845 return Err(format!(
7846 "rms_sigmul oracle: idx {i} not bit-identical: chain {a:?} fused {b:?}"
7847 )
7848 .into());
7849 }
7850 }
7851 Ok(format!(
7852 "gdn-step kernel oracle: scan step twin worst rel {worst:.3e} over artifact + \
7853 hk32 geometries; rms_sigmul bit-identical to the norm/sigmoid/mul chain ({rows}x{cols})"
7854 ))
7855}
7856
7857pub fn gate_qmatvec_bf16(e: &Engine) -> Res<String> {
7858 let mut lcg = 0x9e37_79b9_u64;
7859 let mut next_u32 = move || -> u32 {
7860 lcg = lcg
7861 .wrapping_mul(6364136223846793005)
7862 .wrapping_add(1442695040888963407);
7863 (lcg >> 33) as u32
7864 };
7865 let mut worst = (0.0f32, 0.0f32);
7866 for (mode, batch, t, out_f, in_f, x_bstride) in [
7867 ("per_batch_x", 3usize, 2usize, 5usize, 48usize, 2 * 48usize),
7868 ("shared_x", 4, 3, 7, 16, 0usize),
7869 ] {
7870 let w_elems = batch * out_f * in_f;
7873 let mut w_bytes = Vec::with_capacity(w_elems * 2);
7874 let mut w_host = Vec::with_capacity(w_elems);
7875 for _ in 0..w_elems {
7876 let h = ((next_u32() % 0x4000) as u16) | (((next_u32() & 1) as u16) << 15);
7879 w_bytes.extend_from_slice(&h.to_le_bytes());
7880 w_host.push(f32::from_bits(u32::from(h) << 16));
7881 }
7882 let x_rows = if x_bstride == 0 { t } else { batch * t };
7883 let x_host: Vec<f32> = (0..x_rows * in_f)
7884 .map(|_| (next_u32() % 2000) as f32 / 1000.0 - 1.0)
7885 .collect();
7886 let w_dev = e.htod_bytes(&w_bytes)?;
7887 let x_dev = e.htod(&x_host)?;
7888 let mut y_dev = e.uninit(batch * t * out_f)?;
7889 launch_qmatvec_bf16w(
7890 e,
7891 &w_dev,
7892 &x_dev,
7893 &mut y_dev,
7894 in_f,
7895 out_f,
7896 t,
7897 batch,
7898 out_f * in_f,
7899 x_bstride,
7900 in_f,
7901 t * out_f,
7902 )?;
7903 let y = e.dtoh(&y_dev)?;
7904 for b in 0..batch {
7905 for tok in 0..t {
7906 let xrow = &x_host[b * x_bstride + tok * in_f..][..in_f];
7907 for o in 0..out_f {
7908 let wrow = &w_host[(b * out_f + o) * in_f..][..in_f];
7909 let mut want = 0.0f32;
7910 for i in 0..in_f {
7911 want += wrow[i] * xrow[i];
7912 }
7913 let got = y[(b * t + tok) * out_f + o];
7914 let abs = (want - got).abs();
7915 let rel = abs / want.abs().max(1.0);
7916 worst.0 = worst.0.max(abs);
7917 worst.1 = worst.1.max(rel);
7918 if rel > 1e-5 {
7919 return Err(format!(
7920 "bf16-matvec oracle: {mode} b {b} tok {tok} row {o}: want {want} \
7921 got {got} (rel {rel:.3e})"
7922 )
7923 .into());
7924 }
7925 }
7926 }
7927 }
7928 }
7929 {
7933 let (out_f, in_f, t) = (33usize, 64usize, 5usize);
7934 let w_elems = out_f * in_f;
7935 let mut w_bytes = Vec::with_capacity(w_elems * 2);
7936 for _ in 0..w_elems {
7937 let h = ((next_u32() % 0x4000) as u16) | (((next_u32() & 1) as u16) << 15);
7938 w_bytes.extend_from_slice(&h.to_le_bytes());
7939 }
7940 let x_host: Vec<f32> = (0..t * in_f)
7941 .map(|_| (next_u32() % 2000) as f32 / 1000.0 - 1.0)
7942 .collect();
7943 let w_dev = e.htod_bytes(&w_bytes)?;
7944 let x_dev = e.htod(&x_host)?;
7945 let mut y_grid = e.uninit(t * out_f)?;
7946 launch_qmatvec_bf16w(
7947 e,
7948 &w_dev,
7949 &x_dev,
7950 &mut y_grid,
7951 in_f,
7952 out_f,
7953 t,
7954 1,
7955 0,
7956 0,
7957 in_f,
7958 0,
7959 )?;
7960 let mut y_mt = e.uninit(t * out_f)?;
7961 launch_qmatvec_bf16w_mt(e, &w_dev, 0, &x_dev, &mut y_mt, in_f, out_f, t)?;
7962 let (a, b) = (e.dtoh(&y_grid)?, e.dtoh(&y_mt)?);
7963 for (i, (&x1, &x2)) in a.iter().zip(&b).enumerate() {
7964 if x1.to_bits() != x2.to_bits() {
7965 return Err(format!(
7966 "bf16-matvec mt oracle: idx {i}: grid {x1} vs mt {x2} NOT bit-identical"
7967 )
7968 .into());
7969 }
7970 }
7971 }
7972 {
7977 let (experts, out_f, in_f, n_sel) = (16usize, 24usize, 32usize, 6usize);
7978 let w_elems = experts * out_f * in_f;
7979 let mut w_bytes = Vec::with_capacity(w_elems * 2);
7980 for _ in 0..w_elems {
7981 let h = ((next_u32() % 0x4000) as u16) | (((next_u32() & 1) as u16) << 15);
7982 w_bytes.extend_from_slice(&h.to_le_bytes());
7983 }
7984 let sel_host: Vec<i32> = vec![7, 0, 15, 7, 3, 9]; let bank = e.htod_bytes(&w_bytes)?;
7986 let sel = e.htod_i32(&sel_host)?;
7987 for (label, x_rows, x_sstride) in [("shared-x", 1usize, 0usize), ("slot-x", n_sel, in_f)] {
7988 let x_host: Vec<f32> = (0..x_rows * in_f)
7989 .map(|_| (next_u32() % 2000) as f32 / 1000.0 - 1.0)
7990 .collect();
7991 let x_dev = e.htod(&x_host)?;
7992 let mut y_sel = e.uninit(n_sel * out_f)?;
7993 launch_qmatvec_bf16w_sel(
7994 e, &bank, &sel, 0, &x_dev, 0, x_sstride, &mut y_sel, n_sel, in_f, out_f,
7995 )?;
7996 let mut y_ref = e.uninit(n_sel * out_f)?;
7997 for (slot, &eid) in sel_host.iter().enumerate() {
7998 launch_qmatvec_bf16w_off_into(
7999 e,
8000 &bank,
8001 eid as usize * out_f,
8002 &x_dev,
8003 slot * x_sstride,
8004 &mut y_ref,
8005 slot * out_f,
8006 in_f,
8007 out_f,
8008 )?;
8009 }
8010 let (a, b) = (e.dtoh(&y_sel)?, e.dtoh(&y_ref)?);
8011 for (i, (&x1, &x2)) in a.iter().zip(&b).enumerate() {
8012 if x1.to_bits() != x2.to_bits() {
8013 return Err(format!(
8014 "bf16-matvec sel oracle ({label}): idx {i}: sel {x1} vs off_into {x2} \
8015 NOT bit-identical"
8016 )
8017 .into());
8018 }
8019 }
8020 }
8021 }
8022 Ok(format!(
8023 "bf16-matvec kernel oracle: worst abs {:.3e} rel {:.3e} over per-batch + shared-x \
8024 modes, batch>1, t>1, signed/denormal bf16; mt weight-shared twin BIT-IDENTICAL \
8025 at t 5; sel grouped twin BIT-IDENTICAL to the off_into chain (shared-x + slot-x, \
8026 duplicate slots)",
8027 worst.0, worst.1
8028 ))
8029}
8030
8031fn dequant_nvfp4_expert_f32(
8041 e: &Engine,
8042 codes: &CudaSlice<u8>,
8043 scales: &CudaSlice<u8>,
8044 macro_scale: f32,
8045 expert: usize,
8046 rows: usize,
8047 cols: usize,
8048) -> Res<CudaSlice<f32>> {
8049 let wbytes = rows * cols / 2;
8050 let sbytes = rows * cols / 16;
8051 let bf = e.alloc_u8(rows * cols * 2)?;
8052 let stream = e.gpu.stream();
8053 let wp = (codes.device_ptr(&stream).0 as usize + expert * wbytes) as *const c_void;
8054 let scp = (scales.device_ptr(&stream).0 as usize + expert * sbytes) as *const c_void;
8055 let dst = bf.device_ptr(&stream).0 as usize as *mut c_void;
8056 let rc = unsafe {
8057 crate::dsv4_ffi::memra_dsv4_nvfp4_deq_bf16(
8058 wp,
8059 scp,
8060 1.0, rows as i32,
8062 cols as i32,
8063 dst,
8064 stream.cu_stream() as *mut c_void,
8065 )
8066 };
8067 if rc != 0 {
8068 return Err(format!("memra_dsv4_nvfp4_deq_bf16 rc={rc}").into());
8069 }
8070 let mut out = e.bf16_to_f32(&bf.slice(0..rows * cols * 2), rows * cols)?;
8071 if macro_scale != 1.0 {
8072 e.scale_inplace(&mut out, macro_scale, rows * cols)?;
8073 }
8074 Ok(out)
8075}
8076
8077fn expect(weights: &ReferenceWeights, id: &TensorId) -> Res<ReferenceTensor> {
8080 weights
8081 .get(id)
8082 .cloned()
8083 .ok_or_else(|| format!("qwen4exp_gpu: missing weight {id:?}").into())
8084}
8085
8086fn family_id(key: String) -> TensorId {
8087 TensorId::Family {
8088 family: "qwen4_exp",
8089 key,
8090 }
8091}
8092
8093fn layer_id(index: u32, tensor: LayerTensor) -> TensorId {
8094 TensorId::Layer { index, tensor }
8095}
8096
8097fn upload(e: &Engine, tensor: &ReferenceTensor) -> Res<CudaSlice<f32>> {
8098 e.htod(&tensor.data)
8099}
8100
8101fn split_columns(data: &[f32], rows: usize, streams: usize, hidden: usize) -> Vec<Vec<f32>> {
8103 let wide = streams * hidden;
8104 (0..streams)
8105 .map(|s| {
8106 let mut out = Vec::with_capacity(rows * hidden);
8107 for row in 0..rows {
8108 out.extend_from_slice(
8109 &data[row * wide + s * hidden..row * wide + (s + 1) * hidden],
8110 );
8111 }
8112 out
8113 })
8114 .collect()
8115}
8116
8117fn split_rows(data: &[f32], streams: usize, hidden: usize, cols: usize) -> Vec<Vec<f32>> {
8119 (0..streams)
8120 .map(|s| data[s * hidden * cols..(s + 1) * hidden * cols].to_vec())
8121 .collect()
8122}
8123
8124fn load_gate(
8125 e: &Engine,
8126 weights: &ReferenceWeights,
8127 prefix: &str,
8128 sublayer: &str,
8129 streams: usize,
8130 hidden: usize,
8131 rank: usize,
8132 with_inject: bool,
8133) -> Res<GateW> {
8134 let wide = streams * hidden;
8135 let norm = expect(
8136 weights,
8137 &family_id(format!("{prefix}{sublayer}hc_norm.weight")),
8138 )?;
8139 let down = expect(
8140 weights,
8141 &family_id(format!("{prefix}{sublayer}input_mix_weight_down.weight")),
8142 )?;
8143 let up = expect(
8144 weights,
8145 &family_id(format!("{prefix}{sublayer}input_mix_weight_up.weight")),
8146 )?;
8147 if norm.data.len() != wide || down.data.len() != rank * wide || up.data.len() != wide * rank {
8148 return Err(format!("qwen4exp_gpu: gate {prefix}{sublayer} shape mismatch").into());
8149 }
8150 let norm_slices = split_rows(&norm.data, streams, hidden, 1);
8151 let down_slices = split_columns(&down.data, rank, streams, hidden);
8152 let up_slices = split_rows(&up.data, streams, hidden, rank);
8153 let stack = |slices: &[Vec<f32>]| -> Vec<f32> {
8156 let mut out = Vec::with_capacity(slices.len() * slices[0].len());
8157 for s in slices {
8158 out.extend_from_slice(s);
8159 }
8160 out
8161 };
8162 let down_b16 = bf16_twin(e, &stack(&down_slices), hidden)?;
8163 let up_b16 = bf16_twin(e, &stack(&up_slices), rank)?;
8164 let (inject, inject_b16) = if with_inject {
8165 let inject = expect(
8166 weights,
8167 &family_id(format!("{prefix}{sublayer}block_inject_weight.weight")),
8168 )?;
8169 if inject.data.len() != streams * wide {
8170 return Err(format!("qwen4exp_gpu: inject {prefix}{sublayer} shape mismatch").into());
8171 }
8172 (
8175 Some(e.htod(&inject.data)?),
8176 bf16_twin(e, &inject.data, hidden)?,
8177 )
8178 } else {
8179 (None, None)
8180 };
8181 Ok(GateW {
8182 norm_stack: e.htod(&stack(&norm_slices))?,
8183 norm: norm_slices
8184 .into_iter()
8185 .map(|v| e.htod(&v))
8186 .collect::<Result<_, _>>()?,
8187 down: down_slices
8188 .into_iter()
8189 .map(|v| e.htod(&v))
8190 .collect::<Result<_, _>>()?,
8191 up: up_slices
8192 .into_iter()
8193 .map(|v| e.htod(&v))
8194 .collect::<Result<_, _>>()?,
8195 inject,
8196 down_b16,
8197 up_b16,
8198 inject_b16,
8199 })
8200}
8201
8202#[derive(Default)]
8206pub struct ExternalParts {
8207 expert_banks: std::collections::BTreeMap<u32, ExpertBank>,
8208 ngram_tables: std::collections::BTreeMap<u32, NgramTable>,
8209}
8210
8211#[allow(clippy::too_many_arguments)]
8215fn build_layer_w(
8216 e: &Engine,
8217 weights: &ReferenceWeights,
8218 layer: &memra_gguf::model_plan::LayerPlan,
8219 prefix: &str,
8220 streams: usize,
8221 hidden: usize,
8222 rank: usize,
8223 bank_override: Option<ExpertBank>,
8224 table_override: Option<NgramTable>,
8225) -> Res<LayerW> {
8226 let ResidualTopology::GatedResidual { .. } = layer.residual else {
8227 return Err(format!("qwen4exp_gpu: layer {} is not gated-residual", layer.index).into());
8228 };
8229 let attn_gate = load_gate(
8230 e,
8231 weights,
8232 &prefix,
8233 "attn_hyper_connection.",
8234 streams,
8235 hidden,
8236 rank,
8237 true,
8238 )?;
8239 let mlp_gate = load_gate(
8240 e,
8241 weights,
8242 &prefix,
8243 "mlp_hyper_connection.",
8244 streams,
8245 hidden,
8246 rank,
8247 true,
8248 )?;
8249 let mixer = match &layer.attention {
8250 AttentionPlan::Full(attn) => {
8251 let overlay = layer.sparse_overlay.ok_or_else(|| {
8252 format!(
8253 "qwen4exp_gpu: QSA layer {} has no indexer overlay",
8254 layer.index
8255 )
8256 })?;
8257 let yarn = build_yarn(e, &attn.rope, Some(&overlay), layer.index)?;
8260 if attn.key_head_dim != attn.value_head_dim {
8264 return Err(format!(
8265 "qwen4exp_gpu: layer {} key_head_dim {} != value_head_dim {}",
8266 layer.index, attn.key_head_dim, attn.value_head_dim
8267 )
8268 .into());
8269 }
8270 let load_opt_norm = |tensor: LayerTensor| -> Res<Option<CudaSlice<f32>>> {
8271 match weights.get(&layer_id(layer.index, tensor)) {
8272 Some(t) => Ok(Some(e.htod(&t.data)?)),
8273 None if attn.qk_norm == TensorPresence::Required => {
8274 Err(format!("qwen4exp_gpu: layer {} missing qk norm", layer.index).into())
8275 }
8276 None => Ok(None),
8277 }
8278 };
8279 let wq_t = expect(weights, &layer_id(layer.index, LayerTensor::Query))?;
8280 let wk_t = expect(weights, &layer_id(layer.index, LayerTensor::Key))?;
8281 let wv_t = expect(weights, &layer_id(layer.index, LayerTensor::Value))?;
8282 let wo_t = expect(
8283 weights,
8284 &layer_id(layer.index, LayerTensor::AttentionOutput),
8285 )?;
8286 let o_in = (attn.query_heads * attn.key_head_dim) as usize;
8287 MixerW::Qsa(QsaW {
8288 attn: attn.clone(),
8289 overlay,
8290 yarn,
8291 proj_b16: bf16_stack_twin(e, &[&wq_t.data, &wk_t.data, &wv_t.data], hidden)?,
8292 wo_b16: bf16_twin(e, &wo_t.data, o_in)?,
8293 wq: upload(e, &wq_t)?,
8294 wk: upload(e, &wk_t)?,
8295 wv: upload(e, &wv_t)?,
8296 wo: upload(e, &wo_t)?,
8297 q_norm: load_opt_norm(LayerTensor::QueryNorm)?,
8298 k_norm: load_opt_norm(LayerTensor::KeyNorm)?,
8299 idx_proj: upload(
8300 e,
8301 &expect(
8302 weights,
8303 &family_id(format!("{prefix}self_attn.indexer.index_qk_proj.weight")),
8304 )?,
8305 )?,
8306 idx_q_norm: expect(
8307 weights,
8308 &family_id(format!("{prefix}self_attn.indexer.q_layernorm.weight")),
8309 )?
8310 .data,
8311 idx_k_norm: expect(
8312 weights,
8313 &family_id(format!("{prefix}self_attn.indexer.k_layernorm.weight")),
8314 )?
8315 .data,
8316 })
8317 }
8318 AttentionPlan::GatedDeltaNet(gdn) => {
8319 let qkv_t = expect(weights, &layer_id(layer.index, LayerTensor::GdnQkv))?;
8320 let z_t = expect(weights, &layer_id(layer.index, LayerTensor::GdnGate))?;
8321 let beta_t = expect(weights, &layer_id(layer.index, LayerTensor::GdnBeta))?;
8322 let alpha_t = expect(weights, &layer_id(layer.index, LayerTensor::GdnAlpha))?;
8323 let out_t = expect(weights, &layer_id(layer.index, LayerTensor::GdnOutput))?;
8324 let o_in = (gdn.value_heads * gdn.value_head_dim) as usize;
8325 MixerW::Gdn(GdnW {
8326 plan: *gdn,
8327 proj_b16: bf16_stack_twin(
8328 e,
8329 &[&qkv_t.data, &z_t.data, &beta_t.data, &alpha_t.data],
8330 hidden,
8331 )?,
8332 out_b16: bf16_twin(e, &out_t.data, o_in)?,
8333 qkv: upload(e, &qkv_t)?,
8334 z: upload(e, &z_t)?,
8335 beta: upload(e, &beta_t)?,
8336 alpha: upload(e, &alpha_t)?,
8337 conv_w: upload(
8338 e,
8339 &expect(weights, &layer_id(layer.index, LayerTensor::GdnConv1d))?,
8340 )?,
8341 a: upload(
8342 e,
8343 &expect(weights, &layer_id(layer.index, LayerTensor::GdnA))?,
8344 )?,
8345 dt: upload(
8346 e,
8347 &expect(weights, &layer_id(layer.index, LayerTensor::GdnDtBias))?,
8348 )?,
8349 norm: upload(
8350 e,
8351 &expect(weights, &layer_id(layer.index, LayerTensor::GdnNorm))?,
8352 )?,
8353 out: upload(e, &out_t)?,
8354 })
8355 }
8356 other => {
8357 return Err(format!(
8358 "qwen4exp_gpu: unsupported mixer {other:?} at layer {}",
8359 layer.index
8360 )
8361 .into());
8362 }
8363 };
8364 let MlpPlan::Moe(moe_plan) = &layer.mlp else {
8365 return Err(format!("qwen4exp_gpu: layer {} is not MoE", layer.index).into());
8366 };
8367 if !matches!(moe_plan.router, RouterPlan::Softmax) {
8368 return Err("qwen4exp_gpu: only the softmax router arm is implemented".into());
8369 }
8370 let shared = moe_plan
8371 .shared
8372 .as_ref()
8373 .ok_or("qwen4exp_gpu: missing shared expert plan")?;
8374 let bank = match bank_override {
8375 Some(bank) => bank,
8376 None => {
8377 let gate = expect(
8378 weights,
8379 &layer_id(layer.index, LayerTensor::MoeExpertGateBank),
8380 )?;
8381 let up = expect(
8382 weights,
8383 &layer_id(layer.index, LayerTensor::MoeExpertUpBank),
8384 )?;
8385 let down = expect(
8386 weights,
8387 &layer_id(layer.index, LayerTensor::MoeExpertDownBank),
8388 )?;
8389 let experts = moe_plan.expert_count as usize;
8390 let ff = moe_plan.expert_intermediate_size as usize;
8391 if gate.data.len() != experts * ff * hidden
8392 || up.data.len() != experts * ff * hidden
8393 || down.data.len() != experts * hidden * ff
8394 {
8395 return Err(format!(
8396 "qwen4exp_gpu: layer {} expert bank shape mismatch",
8397 layer.index
8398 )
8399 .into());
8400 }
8401 ExpertBank {
8402 gate: BankHalf::F32(e.htod(&gate.data)?),
8403 up: BankHalf::F32(e.htod(&up.data)?),
8404 down: BankHalf::F32(e.htod(&down.data)?),
8405 }
8406 }
8407 };
8408 let sh_gate_t = expect(weights, &layer_id(layer.index, LayerTensor::SharedMlpGate))?;
8409 let sh_up_t = expect(weights, &layer_id(layer.index, LayerTensor::SharedMlpUp))?;
8410 let sh_down_t = expect(weights, &layer_id(layer.index, LayerTensor::SharedMlpDown))?;
8411 let sff = shared.intermediate_size as usize;
8412 let router_t = expect(weights, &layer_id(layer.index, LayerTensor::MoeRouter))?;
8413 let moe = MoeW {
8414 plan: moe_plan.clone(),
8415 router_b16: bf16_twin(e, &router_t.data, hidden)?,
8416 router: upload(e, &router_t)?,
8417 bank,
8418 shared_gu_b16: bf16_stack_twin(e, &[&sh_gate_t.data, &sh_up_t.data], hidden)?,
8419 shared_down_b16: bf16_twin(e, &sh_down_t.data, sff)?,
8420 shared_gate: upload(e, &sh_gate_t)?,
8421 shared_up: upload(e, &sh_up_t)?,
8422 shared_down: upload(e, &sh_down_t)?,
8423 shared_input_gate: if shared.gated {
8424 Some(upload(
8425 e,
8426 &expect(
8427 weights,
8428 &layer_id(layer.index, LayerTensor::SharedMlpInputGate),
8429 )?,
8430 )?)
8431 } else {
8432 None
8433 },
8434 };
8435 let ple = match layer.ple.as_ref() {
8436 None => None,
8437 Some(ple_plan) => {
8438 let embed_dim = ple_plan.embed_dim as usize;
8439 let head_dim = ple_plan.head_embed_dim as usize;
8440 let wide = streams * hidden;
8441 let key_proj = expect(weights, &family_id(format!("{prefix}ple.key_proj.weight")))?;
8442 let conv_w = expect(weights, &family_id(format!("{prefix}ple.conv1d.weight")))?;
8443 if key_proj.data.len() != wide * embed_dim {
8444 return Err("qwen4exp_gpu: ple key_proj shape mismatch".into());
8445 }
8446 let norm_slices = |name: &str| -> Res<Vec<CudaSlice<f32>>> {
8447 let t = expect(weights, &family_id(format!("{prefix}ple.{name}.weight")))?;
8448 split_rows(&t.data, streams, hidden, 1)
8449 .into_iter()
8450 .map(|v| e.htod(&v))
8451 .collect::<Result<_, _>>()
8452 .map_err(Into::into)
8453 };
8454 let ints = |name: &str| -> Res<Vec<i64>> {
8455 let t = expect(
8456 weights,
8457 &family_id(format!("{prefix}ple.ple_embedding.{name}")),
8458 )?;
8459 t.ints
8460 .clone()
8461 .ok_or_else(|| "qwen4exp_gpu: n-gram buffer must be I64".into())
8462 };
8463 let table = match table_override {
8464 Some(table) => table,
8465 None => {
8466 let t = expect(
8467 weights,
8468 &family_id(format!("{prefix}ple.ple_embedding.ngram_embedding")),
8469 )?;
8470 if t.shape.len() != 2 || t.shape[1] != head_dim {
8471 return Err("qwen4exp_gpu: n-gram table shape mismatch".into());
8472 }
8473 NgramTable::F32(t.data)
8474 }
8475 };
8476 Some(PleW {
8477 plan: *ple_plan,
8478 key_proj: split_rows(&key_proj.data, streams, hidden, embed_dim)
8479 .into_iter()
8480 .map(|v| e.htod(&v))
8481 .collect::<Result<_, _>>()?,
8482 value_proj: upload(
8483 e,
8484 &expect(
8485 weights,
8486 &family_id(format!("{prefix}ple.value_proj.weight")),
8487 )?,
8488 )?,
8489 norm_key: norm_slices("norm_key")?,
8490 norm_query: norm_slices("norm_query")?,
8491 norm_conv: norm_slices("norm_conv")?,
8492 conv_w: split_rows(&conv_w.data, streams, hidden, ple_plan.conv_kernel as usize)
8493 .into_iter()
8494 .map(|v| e.htod(&v))
8495 .collect::<Result<_, _>>()?,
8496 multipliers: ints("layer_multipliers")?,
8497 sizes: ints("ngram_heads_vocab_sizes")?,
8498 offsets: ints("ngram_heads_offsets")?,
8499 table,
8500 })
8501 }
8502 };
8503 Ok(LayerW {
8504 index: layer.index,
8505 eps_attn: layer.pre_attention_norm.epsilon,
8506 eps_mlp: layer.pre_mlp_norm.epsilon,
8507 attn_gate,
8508 mlp_gate,
8509 mixer,
8510 moe,
8511 ple,
8512 })
8513}
8514
8515#[allow(clippy::too_many_arguments)]
8518fn build_mtp_w(
8519 e: &Engine,
8520 weights: &ReferenceWeights,
8521 block: &memra_gguf::model_plan::MtpBlockPlan,
8522 streams: usize,
8523 hidden: usize,
8524 rank: usize,
8525 bank_override: Option<ExpertBank>,
8526) -> Res<MtpW> {
8527 use memra_gguf::tensor_contract::MtpTensor;
8528 if block.input.fusion != memra_gguf::model_plan::MtpFusionPlan::SeparateProjections {
8529 return Err("qwen4exp_gpu: MTP block is not the separate-projections family".into());
8530 }
8531 let wide = streams * hidden;
8532 let depth = block.depth;
8533 let mtp_id = |tensor: MtpTensor| TensorId::Mtp { depth, tensor };
8534 let pre_e = expect(weights, &mtp_id(MtpTensor::EmbeddingNorm))?;
8535 let pre_h = expect(weights, &mtp_id(MtpTensor::HiddenNorm))?;
8536 let fc_e = expect(weights, &mtp_id(MtpTensor::EmbeddingProjection))?;
8537 let fc_h = expect(weights, &mtp_id(MtpTensor::HiddenProjection))?;
8538 if pre_e.data.len() != hidden
8539 || pre_h.data.len() != wide
8540 || fc_e.data.len() != hidden * hidden
8541 || fc_h.data.len() != hidden * hidden
8542 {
8543 return Err("qwen4exp_gpu: MTP fusion tensor shape mismatch".into());
8544 }
8545 let prefix = format!("mtp.layers.{depth}.");
8546 let layer = build_layer_w(
8547 e,
8548 weights,
8549 &block.layer,
8550 &prefix,
8551 streams,
8552 hidden,
8553 rank,
8554 bank_override,
8555 None,
8556 )?;
8557 let mixer = load_gate(
8558 e,
8559 weights,
8560 "mtp.hyper_connection_mixer.",
8561 "",
8562 streams,
8563 hidden,
8564 rank,
8565 false,
8566 )?;
8567 Ok(MtpW {
8568 eps_embed: block.input.embedding_norm.epsilon,
8569 eps_hidden: block.input.hidden_norm.epsilon,
8570 fc_embed_b16: bf16_twin(e, &fc_e.data, hidden)?,
8571 fc_hidden_b16: bf16_twin(e, &fc_h.data, hidden)?,
8572 pre_norm_embed: upload(e, &pre_e)?,
8573 pre_norm_hidden: upload(e, &pre_h)?,
8574 fc_embed: upload(e, &fc_e)?,
8575 fc_hidden: upload(e, &fc_h)?,
8576 layer,
8577 mixer,
8578 })
8579}
8580
8581impl Qwen4ExpGpu {
8582 pub fn from_reference_weights(
8586 e: &Engine,
8587 plan: &ModelPlan,
8588 weights: &ReferenceWeights,
8589 ) -> Res<Self> {
8590 Self::from_reference_weights_with(e, None, plan, weights, ExternalParts::default())
8591 }
8592
8593 fn from_reference_weights_with(
8594 e: &Engine,
8595 draft_e: Option<&Engine>,
8598 plan: &ModelPlan,
8599 weights: &ReferenceWeights,
8600 mut parts: ExternalParts,
8601 ) -> Res<Self> {
8602 let hidden = plan.hidden_size as usize;
8603 let vocab = plan.vocab_size as usize;
8604 let Some(mixer_plan) = plan.exit_mixer else {
8605 return Err("qwen4exp_gpu requires the gated-residual exit mixer".into());
8606 };
8607 let streams = mixer_plan.streams as usize;
8608 if streams > PLANE_SLOTS.len() {
8609 return Err("qwen4exp_gpu: hc_count exceeds the step-workspace slot table".into());
8610 }
8611 let rank = mixer_plan.bottleneck_rank as usize;
8612 if !plan.logits.is_empty() {
8613 return Err("qwen4exp_gpu: logits transforms are not part of this family".into());
8614 }
8615
8616 let embed = expect(weights, &TensorId::TokenEmbedding)?;
8617 if embed.data.len() != vocab * hidden {
8618 return Err("qwen4exp_gpu: embedding shape mismatch".into());
8619 }
8620 let (output, output_b16) = match weights.get(&TensorId::OutputProjection) {
8621 Some(tensor) => (e.htod(&tensor.data)?, bf16_twin(e, &tensor.data, hidden)?),
8622 None => (e.htod(&embed.data)?, bf16_twin(e, &embed.data, hidden)?),
8623 };
8624
8625 let mut layers = Vec::with_capacity(plan.layers.len());
8626 for layer in &plan.layers {
8627 let prefix = format!("trunk.layers.{}.", layer.index);
8628 layers.push(build_layer_w(
8629 e,
8630 weights,
8631 layer,
8632 &prefix,
8633 streams,
8634 hidden,
8635 rank,
8636 parts.expert_banks.remove(&layer.index),
8637 parts.ngram_tables.remove(&layer.index),
8638 )?);
8639 }
8640 let mtp = match plan.mtp_blocks.first() {
8644 Some(block)
8645 if weights
8646 .get(&TensorId::Mtp {
8647 depth: block.depth,
8648 tensor: memra_gguf::tensor_contract::MtpTensor::EmbeddingProjection,
8649 })
8650 .is_some() =>
8651 {
8652 Some(build_mtp_w(
8653 draft_e.unwrap_or(e),
8654 weights,
8655 block,
8656 streams,
8657 hidden,
8658 rank,
8659 parts.expert_banks.remove(&block.layer.index),
8660 )?)
8661 }
8662 _ => None,
8663 };
8664 let mtp_dev1 = match (draft_e, mtp.as_ref()) {
8667 (Some(de), Some(_)) => {
8668 let head_data: &[f32] = match weights.get(&TensorId::OutputProjection) {
8669 Some(tensor) => &tensor.data,
8670 None => &embed.data,
8671 };
8672 Some(MtpDev1 {
8673 dev: de.ctx().ordinal(),
8674 output: de.htod(head_data)?,
8675 output_b16: bf16_twin(de, head_data, hidden)?,
8676 })
8677 }
8678 (Some(_), None) => {
8679 return Err(
8680 "qwen4exp_gpu: a draft engine was given but no mtp.* rows were \
8681 materialized (LoadOptions::load_mtp)"
8682 .into(),
8683 );
8684 }
8685 _ => None,
8686 };
8687 let exit_mixer = load_gate(
8688 e,
8689 weights,
8690 "trunk.hyper_connection_mixer.",
8691 "",
8692 streams,
8693 hidden,
8694 rank,
8695 false,
8696 )?;
8697 Ok(Self {
8698 plan: plan.clone(),
8699 hidden,
8700 streams,
8701 vocab,
8702 embed_host: embed.data,
8703 output,
8704 output_b16,
8705 layers,
8706 exit_mixer,
8707 exit_eps: plan.output_norm.epsilon,
8708 mtp,
8709 mtp_dev1,
8710 draft_trim: None,
8711 draft_trim_parked: None,
8712 chain_embed: None,
8713 })
8714 }
8715
8716 pub fn build_draft_trim(&mut self, e: &Engine, ids: &[u32]) -> Res<()> {
8724 self.check_draft_engine(e)?;
8727 let n = ids.len();
8728 if n == 0 || n > self.vocab {
8729 return Err(format!("qwen4exp_gpu: draft trim wants 1..={} ids", self.vocab).into());
8730 }
8731 let mut seen = vec![false; self.vocab];
8732 for &id in ids {
8733 let id = id as usize;
8734 if id >= self.vocab {
8735 return Err(format!("qwen4exp_gpu: draft trim id {id} out of vocab").into());
8736 }
8737 if std::mem::replace(&mut seen[id], true) {
8738 return Err(format!("qwen4exp_gpu: draft trim id {id} repeats").into());
8739 }
8740 }
8741 let hidden = self.hidden;
8742 let (src_f32, src_b16) = match self.mtp_dev1.as_ref() {
8743 Some(d) => (&d.output, d.output_b16.as_ref()),
8744 None => (&self.output, self.output_b16.as_ref()),
8745 };
8746 let (head_b16, head) = match src_b16 {
8750 Some(full) => {
8751 let mut trim = e.alloc_u8_uninit(n * hidden * 2)?;
8752 for (row, &id) in ids.iter().enumerate() {
8753 e.copy_u8_range_into(
8754 &mut trim,
8755 row * hidden * 2,
8756 full,
8757 id as usize * hidden * 2,
8758 hidden * 2,
8759 )?;
8760 }
8761 (Some(trim), None)
8762 }
8763 None => {
8764 let mut head = e.uninit(n * hidden)?;
8765 for (row, &id) in ids.iter().enumerate() {
8766 e.copy_range_into(
8767 &mut head,
8768 row * hidden,
8769 src_f32,
8770 id as usize * hidden,
8771 hidden,
8772 )?;
8773 }
8774 (None, Some(head))
8775 }
8776 };
8777 self.draft_trim = Some(DraftTrim {
8778 n,
8779 d2t: ids.to_vec(),
8780 head,
8781 head_b16,
8782 });
8783 self.draft_trim_parked = None;
8784 Ok(())
8785 }
8786
8787 pub fn set_draft_trim(&mut self, on: bool) {
8790 if on {
8791 if let Some(t) = self.draft_trim_parked.take() {
8792 self.draft_trim = Some(t);
8793 }
8794 } else if let Some(t) = self.draft_trim.take() {
8795 self.draft_trim_parked = Some(t);
8796 }
8797 }
8798
8799 pub fn clear_draft_trim(&mut self) {
8801 self.draft_trim = None;
8802 self.draft_trim_parked = None;
8803 }
8804
8805 pub fn arm_spec_devchain(&mut self, de: &Engine) -> Res<()> {
8812 self.check_draft_engine(de)?;
8813 let hidden = self.hidden;
8814 let (rows, for_trim) = match self.draft_trim.as_ref() {
8815 Some(tr) => (tr.n, true),
8816 None => (self.vocab, false),
8817 };
8818 let src_row = |r: usize| -> &[f32] {
8819 let id = match self.draft_trim.as_ref() {
8820 Some(tr) => tr.d2t[r] as usize,
8821 None => r,
8822 };
8823 &self.embed_host[id * hidden..(id + 1) * hidden]
8824 };
8825 let clean = (0..rows).all(|r| src_row(r).iter().all(|x| x.to_bits() & 0xFFFF == 0));
8828 let (bytes, qt, row_bytes) = if clean {
8829 let mut b = vec![0u8; rows * hidden * 2];
8830 for r in 0..rows {
8831 for (j, &x) in src_row(r).iter().enumerate() {
8832 let h = (x.to_bits() >> 16) as u16;
8833 b[(r * hidden + j) * 2..(r * hidden + j) * 2 + 2]
8834 .copy_from_slice(&h.to_le_bytes());
8835 }
8836 }
8837 (b, crate::QT_BF16, hidden * 2)
8838 } else {
8839 let mut b = vec![0u8; rows * hidden * 4];
8840 for r in 0..rows {
8841 for (j, &x) in src_row(r).iter().enumerate() {
8842 b[(r * hidden + j) * 4..(r * hidden + j) * 4 + 4]
8843 .copy_from_slice(&x.to_le_bytes());
8844 }
8845 }
8846 (b, crate::QT_F32, hidden * 4)
8847 };
8848 let table = de.upload_u8(&bytes)?;
8849 println!(
8850 "[qwen4exp-spec] deferred-chain embed table armed: {} rows x {hidden} ({}, {:.1} MiB, dev {}{})",
8851 rows,
8852 if clean {
8853 "bf16 bit-clean"
8854 } else {
8855 "f32 fallback"
8856 },
8857 (rows * row_bytes) as f64 / (1024.0 * 1024.0),
8858 de.ctx().ordinal(),
8859 if for_trim { ", trim-rank order" } else { "" },
8860 );
8861 self.chain_embed = Some(ChainEmbed {
8862 table,
8863 qt,
8864 row_bytes,
8865 rows,
8866 for_trim,
8867 dev: de.ctx().ordinal(),
8868 });
8869 Ok(())
8870 }
8871
8872 pub fn clear_spec_devchain(&mut self) {
8874 self.chain_embed = None;
8875 }
8876
8877 pub fn draft_logits_width(&self) -> usize {
8880 match self.draft_trim.as_ref() {
8881 Some(t) => t.n,
8882 None => self.vocab,
8883 }
8884 }
8885
8886 fn draft_token(&self, row: u32) -> Res<u32> {
8889 match self.draft_trim.as_ref() {
8890 Some(t) => t
8891 .d2t
8892 .get(row as usize)
8893 .copied()
8894 .ok_or_else(|| format!("qwen4exp_gpu: draft row {row} outside the trim").into()),
8895 None => Ok(row),
8896 }
8897 }
8898
8899 pub fn trunk_f32_diet(&mut self, e: &Engine) -> Res<usize> {
8908 if !trunk_bf16_on() || !hc_fused_gate_on() {
8909 return Err(
8910 "qwen4exp_gpu: trunk_f32_diet requires the trunk-bf16 + fused-gate seams ON \
8911 (the bf16 paths must be the ones serving)"
8912 .into(),
8913 );
8914 }
8915 let mut freed = 0usize;
8916 fn stub(e: &Engine, s: &mut CudaSlice<f32>, freed: &mut usize) -> Res<()> {
8917 if s.len() > 1 {
8918 *freed += s.len() * 4;
8919 *s = e.zeros(1)?;
8920 }
8921 Ok(())
8922 }
8923 fn diet_gate(e: &Engine, g: &mut GateW, freed: &mut usize) -> Res<()> {
8924 if g.down_b16.is_none()
8925 || g.up_b16.is_none()
8926 || (g.inject.is_some() && g.inject_b16.is_none())
8927 {
8928 return Ok(()); }
8930 for s in g.down.iter_mut() {
8931 stub(e, s, freed)?;
8932 }
8933 for s in g.up.iter_mut() {
8934 stub(e, s, freed)?;
8935 }
8936 if let Some(inj) = g.inject.as_mut() {
8937 stub(e, inj, freed)?;
8938 }
8939 Ok(())
8940 }
8941 for layer in self.layers.iter_mut() {
8942 diet_gate(e, &mut layer.attn_gate, &mut freed)?;
8943 diet_gate(e, &mut layer.mlp_gate, &mut freed)?;
8944 match &mut layer.mixer {
8945 MixerW::Qsa(q) => {
8946 if q.proj_b16.is_some() {
8947 stub(e, &mut q.wq, &mut freed)?;
8948 stub(e, &mut q.wk, &mut freed)?;
8949 stub(e, &mut q.wv, &mut freed)?;
8950 }
8951 if q.wo_b16.is_some() {
8952 stub(e, &mut q.wo, &mut freed)?;
8953 }
8954 }
8955 MixerW::Gdn(g) => {
8956 if g.proj_b16.is_some() {
8957 stub(e, &mut g.qkv, &mut freed)?;
8958 stub(e, &mut g.z, &mut freed)?;
8959 stub(e, &mut g.beta, &mut freed)?;
8960 stub(e, &mut g.alpha, &mut freed)?;
8961 }
8962 if g.out_b16.is_some() {
8963 stub(e, &mut g.out, &mut freed)?;
8964 }
8965 }
8966 }
8967 let moe = &mut layer.moe;
8968 if moe.router_b16.is_some() {
8969 stub(e, &mut moe.router, &mut freed)?;
8970 }
8971 if moe.shared_gu_b16.is_some() {
8972 stub(e, &mut moe.shared_gate, &mut freed)?;
8973 stub(e, &mut moe.shared_up, &mut freed)?;
8974 }
8975 if moe.shared_down_b16.is_some() {
8976 stub(e, &mut moe.shared_down, &mut freed)?;
8977 }
8978 }
8979 diet_gate(e, &mut self.exit_mixer, &mut freed)?;
8980 if self.output_b16.is_some() {
8981 stub(e, &mut self.output, &mut freed)?;
8982 }
8983 Ok(freed)
8984 }
8985
8986 pub fn alloc_state(&self, e: &Engine, capacity: usize) -> Res<Qwen4ExpState> {
8987 self.alloc_state_reserve(e, capacity, capacity, None)
8988 }
8989
8990 pub fn alloc_state_reserve(
8995 &self,
8996 e: &Engine,
8997 capacity: usize,
8998 reserve: usize,
8999 kv_engine: Option<&Engine>,
9000 ) -> Res<Qwen4ExpState> {
9001 let kv_e = kv_engine.unwrap_or(e);
9002 let mut layers = Vec::with_capacity(self.layers.len());
9003 for layer in &self.layers {
9004 let mixer = match &layer.mixer {
9005 MixerW::Qsa(qsa) => {
9006 let kv_width = qsa.attn.kv_heads as usize * qsa.attn.key_head_dim as usize;
9007 let v_width = qsa.attn.kv_heads as usize * qsa.attn.value_head_dim as usize;
9008 let kv = if kv_quant_on() {
9011 QsaKvStore::Q8Q5 {
9012 k: kv_e.alloc_u8(capacity * q8_row_bytes(kv_width))?,
9013 v: kv_e.alloc_u8(capacity * q5_row_bytes(v_width))?,
9014 }
9015 } else {
9016 QsaKvStore::F32 {
9017 k: kv_e.zeros(capacity * kv_width)?,
9018 v: kv_e.zeros(capacity * v_width)?,
9019 }
9020 };
9021 MixerState::Qsa {
9022 kv,
9023 raw_keys: IdxRawCache::new(idxq_mode()),
9024 pooled_keys: Vec::new(),
9025 pooled_dev: None,
9026 pooled_dev_rows: 0,
9027 raw_dev: None,
9028 raw_dev_rows: 0,
9029 idx_audit: (idxq_mode() != IdxQMode::F32 && idxq_audit_on()).then(|| {
9030 Box::new(IdxAudit {
9031 raw_f32: IdxRawCache::F32(Vec::new()),
9032 pooled_f32: Vec::new(),
9033 })
9034 }),
9035 }
9036 }
9037 MixerW::Gdn(gdn) => {
9038 let p = &gdn.plan;
9039 let conv_dim = 2 * (p.key_heads * p.key_head_dim) as usize
9040 + (p.value_heads * p.value_head_dim) as usize;
9041 let pad = p.conv_kernel as usize - 1;
9042 MixerState::Gdn {
9043 conv: e.zeros(pad * conv_dim)?,
9044 state: e
9045 .zeros((p.value_heads * p.value_head_dim * p.key_head_dim) as usize)?,
9046 }
9047 }
9048 };
9049 let ple = match layer.ple.as_ref() {
9050 None => None,
9051 Some(ple) => {
9052 let pad = (ple.plan.conv_kernel as usize - 1) * ple.plan.max_ngram as usize;
9053 let mut conv_hist = Vec::with_capacity(self.streams);
9054 for _ in 0..self.streams {
9055 conv_hist.push(e.zeros(pad * self.hidden)?);
9056 }
9057 Some(PleState {
9058 conv_hist,
9059 ngram_ids: Vec::new(),
9060 ngram_history: Vec::new(),
9061 ngram_last_eos: -1,
9062 })
9063 }
9064 };
9065 layers.push(LayerState { mixer, ple });
9066 }
9067 Ok(Qwen4ExpState {
9068 pos: 0,
9069 capacity,
9070 reserve,
9071 tokens: Vec::new(),
9072 layers,
9073 ws: StepPool::default(),
9074 graphs: StepGraphs::default(),
9075 tp2: None,
9076 verify: None,
9077 })
9078 }
9079
9080 pub fn prefill(&self, e: &Engine, ids: &[u32], state: &mut Qwen4ExpState) -> Res<Vec<f32>> {
9082 self.forward(e, ids, state, None)
9083 }
9084
9085 pub fn prefill_extend(
9092 &self,
9093 e: &Engine,
9094 ids: &[u32],
9095 state: &mut Qwen4ExpState,
9096 chunk: usize,
9097 ) -> Res<Vec<f32>> {
9098 if ids.is_empty() || chunk == 0 {
9099 return Err("qwen4exp_gpu: prefill_extend needs ids and a chunk size".into());
9100 }
9101 let mut last = Vec::new();
9102 for piece in ids.chunks(chunk) {
9103 let is_last =
9104 piece.as_ptr() as usize + piece.len() * 4 == ids.as_ptr() as usize + ids.len() * 4;
9105 let head = if is_last {
9106 HeadMode::LastRow
9107 } else {
9108 HeadMode::Skip
9109 };
9110 last = self.forward_with(e, piece, state, None, head)?;
9111 }
9112 Ok(last)
9113 }
9114
9115 pub fn decode_step(&self, e: &Engine, token: u32, state: &mut Qwen4ExpState) -> Res<Vec<f32>> {
9117 self.forward(e, &[token], state, None)
9118 }
9119
9120 pub fn prefill_captured(
9123 &self,
9124 e: &Engine,
9125 ids: &[u32],
9126 state: &mut Qwen4ExpState,
9127 ) -> Res<(Vec<f32>, PrefillCapture)> {
9128 let mut capture = PrefillCapture {
9129 layer_wide: Vec::with_capacity(self.layers.len()),
9130 exit_mixed: Vec::new(),
9131 };
9132 let logits = self.forward(e, ids, state, Some(&mut capture))?;
9133 Ok((logits, capture))
9134 }
9135
9136 fn planes_to_wide(&self, e: &Engine, planes: &[CudaSlice<f32>], t: usize) -> Res<Vec<f32>> {
9139 let hidden = self.hidden;
9140 let wide = self.streams * hidden;
9141 let mut out = vec![0.0f32; t * wide];
9142 for (s, plane) in planes.iter().enumerate() {
9143 let host = e.dtoh_view(&plane.slice(0..t * hidden))?;
9145 for row in 0..t {
9146 out[row * wide + s * hidden..row * wide + (s + 1) * hidden]
9147 .copy_from_slice(&host[row * hidden..(row + 1) * hidden]);
9148 }
9149 }
9150 Ok(out)
9151 }
9152
9153 fn forward(
9154 &self,
9155 e: &Engine,
9156 ids: &[u32],
9157 state: &mut Qwen4ExpState,
9158 capture: Option<&mut PrefillCapture>,
9159 ) -> Res<Vec<f32>> {
9160 self.forward_with(e, ids, state, capture, HeadMode::All)
9161 }
9162
9163 fn forward_with(
9164 &self,
9165 e: &Engine,
9166 ids: &[u32],
9167 state: &mut Qwen4ExpState,
9168 mut capture: Option<&mut PrefillCapture>,
9169 head: HeadMode,
9170 ) -> Res<Vec<f32>> {
9171 let t = ids.len();
9172 let hidden = self.hidden;
9173 if t == 0 {
9174 return Err("qwen4exp_gpu: empty input".into());
9175 }
9176 if head != HeadMode::All {
9177 if capture.is_some() {
9183 return Err("qwen4exp_gpu: prefill capture wants every logits row".into());
9184 }
9185 if let Some(v) = state.verify.as_ref()
9186 && (t == 1 || t <= v.k_cap)
9187 {
9188 return Err(
9189 "qwen4exp_gpu: head-skipping forward on a verify-exact chunk shape".into(),
9190 );
9191 }
9192 }
9193 if state.pos + t > state.capacity {
9194 return Err("qwen4exp_gpu: state capacity exceeded".into());
9195 }
9196 if state.tp2.is_some() {
9197 return Err(
9198 "qwen4exp_gpu: state already decoded in TP2 mode; single-card forward \
9199 requires a fresh state (the half-state migration is one-way)"
9200 .into(),
9201 );
9202 }
9203 let base_pos = state.pos;
9204 state.tokens.extend_from_slice(ids);
9205 if t > 1 {
9208 state.graphs = StepGraphs::default();
9209 }
9210 let graphs_mode = t == 1
9217 && decode_graphs_on()
9218 && step_ws_on()
9219 && hc_fused_gate_on()
9220 && !prof::on()
9221 && capture.is_none()
9222 && state.verify.is_none();
9223 let tokens = &state.tokens;
9224 let ws = &mut state.ws;
9225 let verify = state.verify.as_mut();
9229 let (exact, stash_gdn, stash_ple, stash_wide, argmax_sink, last_row_only) = match verify {
9230 Some(v) => {
9231 let exact = base_pos > 0 && t > 1 && t <= v.k_cap;
9241 let amx_t1 = t == 1 && v.want_argmax_t1;
9245 if exact {
9246 v.chunk = Some((base_pos, t));
9247 v.argmax.clear();
9248 } else if amx_t1 && v.want_argmax {
9249 v.argmax.clear();
9250 }
9251 (
9252 exact,
9253 Some(&mut v.gdn),
9254 Some(&mut v.ple),
9255 Some((&mut v.wide, v.ring_rows)),
9256 if (exact || amx_t1) && v.want_argmax {
9257 Some((&mut v.argmax, &mut v.toks))
9258 } else {
9259 None
9260 },
9261 v.last_row_only && t > 1 && !exact,
9262 )
9263 }
9264 None => (false, None, None, None, None, false),
9265 };
9266 let mut stash_gdn = stash_gdn;
9267 let mut stash_ple = stash_ple;
9268 let cap = state.reserve.max(t);
9274
9275 let mut planes = prof_section(e, "entry.embed", || {
9278 let mut embedded = vec![0.0f32; t * hidden];
9279 for (row, &token) in ids.iter().enumerate() {
9280 let token = token as usize;
9281 if token >= self.vocab {
9282 return Err(format!("qwen4exp_gpu: token {token} out of range").into());
9283 }
9284 embedded[row * hidden..(row + 1) * hidden]
9285 .copy_from_slice(&self.embed_host[token * hidden..(token + 1) * hidden]);
9286 }
9287 let embedded_dev = ws.take_f32_h2d(e, "entry.embed", &embedded, cap * hidden)?;
9288 let mut planes: Vec<CudaSlice<f32>> = Vec::with_capacity(self.streams);
9289 for s in 0..self.streams {
9290 let mut plane = ws.take_f32(e, PLANE_SLOTS[s], t * hidden, cap * hidden)?;
9291 e.copy_into(&mut plane, 0, &embedded_dev, t * hidden)?;
9292 planes.push(plane);
9293 }
9294 ws.put_f32("entry.embed", embedded_dev);
9295 Ok(planes)
9296 })?;
9297
9298 let ptr_vals: Vec<u64> = {
9302 let stream = e.gpu.stream();
9303 planes.iter().map(|p| p.device_ptr(&stream).0).collect()
9304 };
9305 let ptrs = ws.take_u64_h2d(e, "hc.ptrs", &ptr_vals, 0)?;
9306
9307 if graphs_mode {
9308 if state.graphs.warm {
9309 return self.forward_graphs_tail(e, state, planes, ptrs, base_pos);
9310 }
9311 state.graphs.warm = true;
9314 }
9315
9316 for (li, (layer, lstate)) in self.layers.iter().zip(state.layers.iter_mut()).enumerate() {
9317 if let (Some(ple), Some(ple_state)) = (layer.ple.as_ref(), lstate.ple.as_mut()) {
9318 let ps = if exact {
9319 stash_ple
9320 .as_mut()
9321 .and_then(|v| v.get_mut(li))
9322 .and_then(|s| s.as_mut())
9323 } else {
9324 None
9325 };
9326 self.ple_block(
9327 e,
9328 layer,
9329 ple,
9330 &ple.table,
9331 ple_state,
9332 &mut planes,
9333 tokens,
9334 t,
9335 exact,
9336 ps,
9337 )?;
9338 }
9339 let (mixed, inject) = prof_section(e, "hyper.read", || {
9340 self.gate_read(
9341 e,
9342 ws,
9343 &ptrs,
9344 &layer.attn_gate,
9345 &planes,
9346 t,
9347 layer.eps_attn,
9348 exact,
9349 )
9350 })?;
9351 let block_out = match &layer.mixer {
9352 MixerW::Qsa(qsa) => self.qsa_forward(
9353 e,
9354 ws,
9355 layer,
9356 qsa,
9357 &mixed,
9358 &mut lstate.mixer,
9359 base_pos,
9360 t,
9361 0,
9362 exact,
9363 )?,
9364 MixerW::Gdn(gdn) => {
9365 let gs = if exact {
9366 stash_gdn
9367 .as_mut()
9368 .and_then(|v| v.get_mut(li))
9369 .and_then(|s| s.as_mut())
9370 } else {
9371 None
9372 };
9373 self.gdn_forward(e, ws, layer, gdn, &mixed, &mut lstate.mixer, t, gs)?
9374 }
9375 };
9376 ws.put_f32("hc.mixed", mixed);
9377 prof_section(e, "hyper.write", || {
9378 self.gate_write(e, &mut planes, &ptrs, &block_out, &inject, t)
9379 })?;
9380 ws.put_f32("mixer.out", block_out);
9381 put_inject(ws, inject);
9382 let (mixed, inject) = prof_section(e, "hyper.read", || {
9383 self.gate_read(
9384 e,
9385 ws,
9386 &ptrs,
9387 &layer.mlp_gate,
9388 &planes,
9389 t,
9390 layer.eps_mlp,
9391 exact,
9392 )
9393 })?;
9394 let grouped = exact || head != HeadMode::All || prefill_grouped_all_on();
9406 let mlp = self.moe_forward(e, ws, &layer.moe, &mixed, t, grouped, layer.index)?;
9407 ws.put_f32("hc.mixed", mixed);
9408 prof_section(e, "hyper.write", || {
9409 self.gate_write(e, &mut planes, &ptrs, &mlp, &inject, t)
9410 })?;
9411 ws.put_f32("moe.out", mlp);
9412 put_inject(ws, inject);
9413 if let Some(capture) = capture.as_deref_mut() {
9414 capture.layer_wide.push(self.planes_to_wide(e, &planes, t)?);
9415 }
9416 }
9417
9418 if let Some((wide_buf, ring_rows)) = stash_wide {
9422 let wide = self.streams * hidden;
9423 for (s, plane) in planes.iter().enumerate() {
9424 for tok in 0..t {
9425 e.copy_range_into(
9426 wide_buf,
9427 ((base_pos + tok) % ring_rows) * wide + s * hidden,
9428 plane,
9429 tok * hidden,
9430 hidden,
9431 )?;
9432 }
9433 }
9434 }
9435
9436 if head == HeadMode::Skip {
9439 ws.put_u64("hc.ptrs", ptrs);
9440 state.pos += t;
9441 for (s, plane) in planes.into_iter().enumerate() {
9442 ws.put_f32(PLANE_SLOTS[s], plane);
9443 }
9444 return Ok(Vec::new());
9445 }
9446
9447 let x = prof_section(e, "exit.mixer", || {
9449 Ok(self
9450 .gate_read_inner(
9451 e,
9452 ws,
9453 &ptrs,
9454 &self.exit_mixer,
9455 &planes,
9456 t,
9457 self.exit_eps,
9458 false,
9459 exact,
9460 )?
9461 .0)
9462 })?;
9463 ws.put_u64("hc.ptrs", ptrs);
9464 if let Some(capture) = capture.as_deref_mut() {
9465 capture.exit_mixed = e.dtoh(&x)?;
9466 }
9467 let head_rows = if head == HeadMode::LastRow { 1 } else { t };
9470 let logits = prof_section(e, "lm_head", || {
9471 let mut logits =
9472 ws.take_f32(e, "logits", head_rows * self.vocab, head_rows * self.vocab)?;
9473 let x_head = if head == HeadMode::LastRow {
9474 let mut last = ws.take_f32(e, "exit.last", hidden, hidden)?;
9475 e.copy_range_into(&mut last, 0, &x, (t - 1) * hidden, hidden)?;
9476 last
9477 } else {
9478 x
9479 };
9480 linear_trunk_into(
9481 e,
9482 &self.output,
9483 &self.output_b16,
9484 &x_head,
9485 &mut logits,
9486 head_rows,
9487 hidden,
9488 self.vocab,
9489 )?;
9490 ws.put_f32(
9491 if head == HeadMode::LastRow {
9492 "exit.last"
9493 } else {
9494 "hc.mixed"
9495 },
9496 x_head,
9497 );
9498 Ok(logits)
9499 })?;
9500 state.pos += t;
9501 if head == HeadMode::LastRow {
9502 let out = prof_section(e, "logits.dtoh", || {
9503 Ok(e.dtoh_view(&logits.slice(0..self.vocab))?)
9504 })?;
9505 ws.put_f32("logits", logits);
9506 for (s, plane) in planes.into_iter().enumerate() {
9507 ws.put_f32(PLANE_SLOTS[s], plane);
9508 }
9509 return Ok(out);
9510 }
9511 let out = if let Some((argmax_rows, toks)) = argmax_sink {
9514 prof_section(e, "logits.argmax", || {
9515 for row in 0..t {
9516 e.argmax_token_device_col(&logits, row, self.vocab, toks, row)?;
9517 }
9518 let host = e.gpu.stream().clone_dtoh(&toks.slice(0..t))?;
9519 argmax_rows.extend_from_slice(&host);
9520 Ok(Vec::new())
9521 })?
9522 } else if last_row_only {
9523 prof_section(e, "logits.dtoh", || {
9526 Ok(e.dtoh_view(&logits.slice((t - 1) * self.vocab..t * self.vocab))?)
9527 })?
9528 } else {
9529 prof_section(e, "logits.dtoh", || {
9530 Ok(e.dtoh_view(&logits.slice(0..t * self.vocab))?)
9531 })?
9532 };
9533 ws.put_f32("logits", logits);
9534 for (s, plane) in planes.into_iter().enumerate() {
9535 ws.put_f32(PLANE_SLOTS[s], plane);
9536 }
9537 Ok(out)
9538 }
9539
9540 #[allow(clippy::too_many_arguments)]
9544 #[allow(clippy::too_many_arguments)]
9545 fn gate_read(
9546 &self,
9547 e: &Engine,
9548 ws: &mut StepPool,
9549 ptrs: &CudaSlice<u64>,
9550 gate: &GateW,
9551 planes: &[CudaSlice<f32>],
9552 t: usize,
9553 eps: f32,
9554 exact: bool,
9555 ) -> Res<(CudaSlice<f32>, InjectOut)> {
9556 self.gate_read_inner(e, ws, ptrs, gate, planes, t, eps, true, exact)
9557 }
9558
9559 #[allow(clippy::too_many_arguments)]
9560 #[allow(clippy::too_many_arguments)]
9561 fn gate_read_inner(
9562 &self,
9563 e: &Engine,
9564 ws: &mut StepPool,
9565 ptrs: &CudaSlice<u64>,
9566 gate: &GateW,
9567 planes: &[CudaSlice<f32>],
9568 t: usize,
9569 eps: f32,
9570 with_inject: bool,
9571 exact: bool,
9576 ) -> Res<(CudaSlice<f32>, InjectOut)> {
9577 if !hc_fused_gate_on() {
9578 return self.gate_read_legacy(e, ws, gate, planes, t, eps, with_inject);
9579 }
9580 let hidden = self.hidden;
9581 let streams = self.streams;
9582 let rank = gate_rank(gate, hidden, streams)?;
9583 let micro_norm = micro_norm_on();
9584 let micro_inj = micro_inj_on();
9585 if hc_diet_on()
9589 && (t == 1 || exact)
9590 && trunk_bf16_on()
9591 && micro_inj
9592 && hidden % 8 == 0
9593 && rank % 8 == 0
9594 && gate.down_b16.is_some()
9595 && gate.up_b16.is_some()
9596 && (!with_inject || gate.inject_b16.is_some())
9597 {
9598 let mut parts = ws.take_f32(e, "hc.parts", t * streams * rank, 0)?;
9599 let mut injp = ws.take_f32(e, "hc.injp", t * streams * streams, 0)?;
9600 let mut inv = ws.take_f32(e, "hc.inv", t * streams, 0)?;
9601 let winj = if with_inject {
9602 gate.inject_b16.as_ref()
9603 } else {
9604 None
9605 };
9606 let mt = t > 1 && verify_mt_on() && (2..=12).contains(&t);
9610 if mt {
9611 launch_hc_diet_stage0_mt(e, ptrs, &mut inv, hidden, streams, t, eps)?;
9612 launch_hc_diet_stage1_mt(
9613 e,
9614 ptrs,
9615 &gate.norm_stack,
9616 &inv,
9617 gate.down_b16.as_ref().expect("guarded above"),
9618 winj,
9619 &mut parts,
9620 &mut injp,
9621 hidden,
9622 rank,
9623 streams,
9624 t,
9625 )?;
9626 } else {
9627 launch_hc_diet_stage1(
9628 e,
9629 ptrs,
9630 &gate.norm_stack,
9631 gate.down_b16.as_ref().expect("guarded above"),
9632 winj,
9633 &mut parts,
9634 &mut injp,
9635 &mut inv,
9636 hidden,
9637 rank,
9638 streams,
9639 t,
9640 eps,
9641 )?;
9642 }
9643 let mut low_act = ws.take_f32(e, "hc.low_act", t * rank, 0)?;
9644 let mut all = ws.take_f32(e, "hc.inj_all", streams * t, 0)?;
9645 launch_hc_diet_stage2(
9646 e,
9647 &parts,
9648 &injp,
9649 &mut low_act,
9650 &mut all,
9651 rank,
9652 streams,
9653 t,
9654 with_inject,
9655 )?;
9656 let mut mixed = ws.take_f32(e, "hc.mixed", t * hidden, 0)?;
9657 if mt && (t * rank + 8 * streams * t) * 4 <= 96 * 1024 {
9658 launch_hc_diet_stage3_mt(
9659 e,
9660 ptrs,
9661 &gate.norm_stack,
9662 &inv,
9663 gate.up_b16.as_ref().expect("guarded above"),
9664 &low_act,
9665 &mut mixed,
9666 hidden,
9667 rank,
9668 streams,
9669 t,
9670 )?;
9671 } else {
9672 launch_hc_diet_stage3(
9673 e,
9674 ptrs,
9675 &gate.norm_stack,
9676 &inv,
9677 gate.up_b16.as_ref().expect("guarded above"),
9678 &low_act,
9679 &mut mixed,
9680 hidden,
9681 rank,
9682 streams,
9683 t,
9684 )?;
9685 }
9686 ws.put_f32("hc.parts", parts);
9687 ws.put_f32("hc.injp", injp);
9688 ws.put_f32("hc.inv", inv);
9689 ws.put_f32("hc.low_act", low_act);
9690 let inject_out = if with_inject {
9691 InjectOut::Slab(all)
9692 } else {
9693 ws.put_f32("hc.inj_all", all);
9694 InjectOut::Rows(Vec::new())
9695 };
9696 return Ok((mixed, inject_out));
9697 }
9698
9699 let mut normed = ws.take_f32(e, "hc.normed", streams * t * hidden, 0)?;
9705 if micro_norm {
9706 launch_hc_norm_planes(
9708 e,
9709 ptrs,
9710 &gate.norm_stack,
9711 &mut normed,
9712 hidden,
9713 t,
9714 streams,
9715 eps,
9716 )?;
9717 } else {
9718 for s in 0..streams {
9719 let mut dst = normed.slice_mut(s * t * hidden..(s + 1) * t * hidden);
9720 launch_rms_norm_into_view(e, &planes[s], &gate.norm[s], &mut dst, hidden, t, eps)?;
9721 }
9722 }
9723 let trunk_b16 = trunk_bf16_on();
9727 let mut parts = ws.take_f32(e, "hc.parts", streams * t * rank, 0)?;
9728 if let (true, Some(w)) = (trunk_b16, gate.down_b16.as_ref()) {
9729 launch_qmatvec_bf16w(
9730 e,
9731 w,
9732 &normed,
9733 &mut parts,
9734 hidden,
9735 rank,
9736 t,
9737 streams,
9738 rank * hidden,
9739 t * hidden,
9740 hidden,
9741 t * rank,
9742 )?;
9743 } else {
9744 if gate.down[0].len() < rank * hidden {
9745 return Err(
9746 "qwen4exp_gpu: gate down f32 dropped (trunk_f32_diet) — keep the \
9747 trunk-bf16 seam ON"
9748 .into(),
9749 );
9750 }
9751 for s in 0..streams {
9752 let x = normed.slice(s * t * hidden..(s + 1) * t * hidden);
9753 let w = gate.down[s].slice(0..rank * hidden);
9754 let mut out = parts.slice_mut(s * t * rank..(s + 1) * t * rank);
9755 e.linear_device_into(&x, &w, &mut out, t, hidden, rank)?;
9756 }
9757 }
9758 let mut low_act = ws.take_f32(e, "hc.low_act", t * rank, 0)?;
9759 launch_hc_lowrank_reduce(e, &parts, &mut low_act, streams, t, rank)?;
9760 ws.put_f32("hc.parts", parts);
9761
9762 let mut gates = ws.take_f32(e, "hc.gates", streams * t * hidden, 0)?;
9765 if let (true, Some(w)) = (trunk_b16, gate.up_b16.as_ref()) {
9766 launch_qmatvec_bf16w(
9767 e,
9768 w,
9769 &low_act,
9770 &mut gates,
9771 rank,
9772 hidden,
9773 t,
9774 streams,
9775 hidden * rank,
9776 0,
9777 rank,
9778 t * hidden,
9779 )?;
9780 } else {
9781 if gate.up[0].len() < hidden * rank {
9782 return Err(
9783 "qwen4exp_gpu: gate up f32 dropped (trunk_f32_diet) — keep the \
9784 trunk-bf16 seam ON"
9785 .into(),
9786 );
9787 }
9788 for s in 0..streams {
9789 let x = low_act.slice(0..t * rank);
9790 let w = gate.up[s].slice(0..hidden * rank);
9791 let mut out = gates.slice_mut(s * t * hidden..(s + 1) * t * hidden);
9792 e.linear_device_into(&x, &w, &mut out, t, rank, hidden)?;
9793 }
9794 }
9795 let mut mixed = ws.take_f32(e, "hc.mixed", t * hidden, 0)?;
9796 launch_hc_mix_epilogue(e, &gates, &normed, &mut mixed, streams, t, hidden)?;
9797 ws.put_f32("hc.gates", gates);
9798 ws.put_f32("hc.low_act", low_act);
9799
9800 let mut inject_out = InjectOut::Rows(Vec::new());
9801 if with_inject {
9802 let inject = gate
9803 .inject
9804 .as_ref()
9805 .ok_or("qwen4exp_gpu: read gate missing inject weights")?;
9806 let inject_dropped = inject.len() < streams * streams * hidden;
9809 let inject_guard = || -> Res<()> {
9810 if inject_dropped {
9811 return Err("qwen4exp_gpu: inject f32 dropped (trunk_f32_diet) — keep \
9812 the trunk-bf16 seam ON"
9813 .into());
9814 }
9815 Ok(())
9816 };
9817 let mut all = ws.take_f32(e, "hc.inj_all", streams * t, 0)?;
9818 if micro_inj {
9819 const CHUNKS: usize = 16;
9822 let mut partials = ws.take_f32(e, "hc.inj_part", streams * t * CHUNKS, 0)?;
9823 let w_b16 = if trunk_b16 {
9824 gate.inject_b16.as_ref()
9825 } else {
9826 None
9827 };
9828 if w_b16.is_none() {
9829 inject_guard()?;
9830 }
9831 launch_hc_inject_two_stage(
9832 e,
9833 &normed,
9834 inject,
9835 w_b16,
9836 &mut partials,
9837 &mut all,
9838 streams,
9839 t,
9840 hidden,
9841 CHUNKS,
9842 )?;
9843 ws.put_f32("hc.inj_part", partials);
9844 inject_out = InjectOut::Slab(all);
9845 } else {
9846 if let (true, Some(w)) = (trunk_b16, gate.inject_b16.as_ref()) {
9849 launch_hc_inject_gates_b16(e, &normed, w, &mut all, streams, t, hidden)?;
9850 } else {
9851 inject_guard()?;
9852 launch_hc_inject_gates(e, &normed, inject, &mut all, streams, t, hidden)?;
9853 }
9854 let mut rows = Vec::with_capacity(streams);
9855 for s in 0..streams {
9856 let mut row = ws.take_f32(e, INJECT_SLOTS[s], t, 0)?;
9857 e.copy_range_into(&mut row, 0, &all, s * t, t)?;
9858 rows.push(row);
9859 }
9860 ws.put_f32("hc.inj_all", all);
9861 inject_out = InjectOut::Rows(rows);
9862 }
9863 }
9864 ws.put_f32("hc.normed", normed);
9865 Ok((mixed, inject_out))
9866 }
9867
9868 #[allow(clippy::too_many_arguments)]
9873 fn gate_read_legacy(
9874 &self,
9875 e: &Engine,
9876 _ws: &mut StepPool,
9877 gate: &GateW,
9878 planes: &[CudaSlice<f32>],
9879 t: usize,
9880 eps: f32,
9881 with_inject: bool,
9882 ) -> Res<(CudaSlice<f32>, InjectOut)> {
9883 let hidden = self.hidden;
9884 let streams = self.streams;
9885 let rank = gate_rank(gate, hidden, streams)?;
9886 if gate.down[0].len() < rank * hidden {
9887 return Err(
9888 "qwen4exp_gpu: gate f32 originals dropped (trunk_f32_diet) — the \
9889 legacy gate path needs them (keep hc seams ON)"
9890 .into(),
9891 );
9892 }
9893 let inv_streams = 1.0 / streams as f32; let mut normed = Vec::with_capacity(streams);
9896 for s in 0..streams {
9897 let mut dst = e.uninit(t * hidden)?;
9898 e.rms_norm(&planes[s], &gate.norm[s], &mut dst, hidden, t, eps)?;
9899 normed.push(dst);
9900 }
9901 let mut low = e.linear(&normed[0], &gate.down[0], t, hidden, rank)?;
9903 for s in 1..streams {
9904 let part = e.linear(&normed[s], &gate.down[s], t, hidden, rank)?;
9905 let mut view = low.slice_mut(0..t * rank);
9906 e.axpy_into(&part, 1.0, &mut view, t * rank)?;
9907 }
9908 e.scale_inplace(&mut low, inv_streams, t * rank)?;
9909 let ones = e.htod(&vec![1.0f32; t * rank.max(1)])?;
9910 let mut low_act = e.uninit(t * rank)?;
9911 e.silu_mul(&low, &ones, &mut low_act, t * rank)?;
9912
9913 let mut mixed = e.zeros(t * hidden)?;
9915 let mut gate_buf = e.uninit(t * hidden)?;
9916 let mut prod = e.uninit(t * hidden)?;
9917 for s in 0..streams {
9918 let g = e.linear(&low_act, &gate.up[s], t, rank, hidden)?;
9919 e.sigmoid(&g, &mut gate_buf, t * hidden)?;
9920 e.mul(&gate_buf, &normed[s], &mut prod, t * hidden)?;
9921 let mut view = mixed.slice_mut(0..t * hidden);
9922 e.axpy_into(&prod, 1.0, &mut view, t * hidden)?;
9923 }
9924 e.scale_inplace(&mut mixed, inv_streams, t * hidden)?;
9925
9926 let mut inject_out = Vec::new();
9927 if with_inject {
9928 let inject = gate
9929 .inject
9930 .as_ref()
9931 .ok_or("qwen4exp_gpu: read gate missing inject weights")?;
9932 let wide = streams * hidden;
9933 for s in 0..streams {
9934 let mut acc = {
9936 let w = inject.slice(s * wide..s * wide + hidden);
9937 let x = normed[0].slice(0..t * hidden);
9938 let mut out = e.uninit(t)?;
9939 e.linear_device_into(&x, &w, &mut out, t, hidden, 1)?;
9940 out
9941 };
9942 for s2 in 1..streams {
9943 let w = inject.slice(s * wide + s2 * hidden..s * wide + (s2 + 1) * hidden);
9944 let x = normed[s2].slice(0..t * hidden);
9945 let mut part = e.uninit(t)?;
9946 e.linear_device_into(&x, &w, &mut part, t, hidden, 1)?;
9947 let mut view = acc.slice_mut(0..t);
9948 e.axpy_into(&part, 1.0, &mut view, t)?;
9949 }
9950 e.scale_inplace(&mut acc, inv_streams, t)?;
9951 let mut sg = e.uninit(t)?;
9952 e.sigmoid(&acc, &mut sg, t)?;
9953 e.scale_inplace(&mut sg, 2.0, t)?;
9954 inject_out.push(sg);
9955 }
9956 }
9957 Ok((mixed, InjectOut::Rows(inject_out)))
9958 }
9959
9960 fn gate_write(
9964 &self,
9965 e: &Engine,
9966 planes: &mut [CudaSlice<f32>],
9967 ptrs: &CudaSlice<u64>,
9968 block_out: &CudaSlice<f32>,
9969 inject: &InjectOut,
9970 t: usize,
9971 ) -> Res<()> {
9972 match inject {
9973 InjectOut::Rows(rows) => {
9974 for (plane, inj) in planes.iter_mut().zip(rows) {
9975 e.add_scaled_rows(block_out, inj, plane, self.hidden, t)?;
9976 }
9977 Ok(())
9978 }
9979 InjectOut::Slab(slab) => {
9980 launch_hc_write_planes(e, ptrs, block_out, slab, self.hidden, t, self.streams)
9981 }
9982 }
9983 }
9984
9985 #[allow(clippy::too_many_arguments)]
9991 #[allow(clippy::too_many_arguments)]
9992 fn layer_interior(
9993 &self,
9994 e: &Engine,
9995 ws: &mut StepPool,
9996 ptrs: &CudaSlice<u64>,
9997 layer: &LayerW,
9998 lstate: &mut LayerState,
9999 planes: &mut [CudaSlice<f32>],
10000 tokens: &[u32],
10001 base_pos: usize,
10002 ) -> Res<()> {
10003 if let (Some(ple), Some(ple_state)) = (layer.ple.as_ref(), lstate.ple.as_mut()) {
10004 self.ple_block(
10005 e, layer, ple, &ple.table, ple_state, planes, tokens, 1, false, None,
10006 )?;
10007 }
10008 let (mixed, inject) = self.gate_read(
10009 e,
10010 ws,
10011 ptrs,
10012 &layer.attn_gate,
10013 planes,
10014 1,
10015 layer.eps_attn,
10016 false,
10017 )?;
10018 let block_out = match &layer.mixer {
10019 MixerW::Qsa(qsa) => self.qsa_forward(
10020 e,
10021 ws,
10022 layer,
10023 qsa,
10024 &mixed,
10025 &mut lstate.mixer,
10026 base_pos,
10027 1,
10028 0,
10029 false,
10030 )?,
10031 MixerW::Gdn(gdn) => {
10032 self.gdn_forward(e, ws, layer, gdn, &mixed, &mut lstate.mixer, 1, None)?
10033 }
10034 };
10035 ws.put_f32("hc.mixed", mixed);
10036 self.gate_write(e, planes, ptrs, &block_out, &inject, 1)?;
10037 ws.put_f32("mixer.out", block_out);
10038 put_inject(ws, inject);
10039 let (mixed, inject) = self.gate_read(
10040 e,
10041 ws,
10042 ptrs,
10043 &layer.mlp_gate,
10044 planes,
10045 1,
10046 layer.eps_mlp,
10047 false,
10048 )?;
10049 ws.put_f32("hc.mixed", mixed);
10050 put_inject(ws, inject);
10051 Ok(())
10052 }
10053
10054 fn moe_route_slots(&self, e: &Engine, ws: &mut StepPool, moe: &MoeW, layer: u32) -> Res<()> {
10060 let hidden = self.hidden;
10061 let experts = moe.plan.expert_count as usize;
10062 let selected = moe.plan.experts_per_token as usize;
10063 let mixed = ws.take_f32(e, "hc.mixed", hidden, 0)?;
10064 let mut router_out = ws.take_f32(e, "moe.router", experts, 0)?;
10065 let none: Option<CudaSlice<u8>> = None;
10066 let rb = if router_bf16_on() {
10067 &moe.router_b16
10068 } else {
10069 &none
10070 };
10071 linear_trunk_into(
10072 e,
10073 &moe.router,
10074 rb,
10075 &mixed,
10076 &mut router_out,
10077 1,
10078 hidden,
10079 experts,
10080 )?;
10081 if router_dev_on() && route_dev_geometry(experts, selected) {
10085 let mut sel = ws.take_i32_slot(e, "moe.sel", selected, 0)?;
10086 let mut w = ws.take_f32(e, "moe.w", selected, 0)?;
10087 route_topk_device(
10088 e,
10089 &router_out,
10090 &mut sel,
10091 &mut w,
10092 None,
10093 experts,
10094 selected,
10095 1,
10096 layer,
10097 )?;
10098 if route_sync_diag() {
10104 e.gpu.stream().synchronize()?;
10105 }
10106 ws.put_i32("moe.sel", sel);
10107 ws.put_f32("moe.w", w);
10108 ws.put_f32("moe.router", router_out);
10109 ws.put_f32("hc.mixed", mixed);
10110 return Ok(());
10111 }
10112 let logits = e.dtoh_view(&router_out.slice(0..experts))?;
10113 ws.put_f32("moe.router", router_out);
10114 ws.put_f32("hc.mixed", mixed);
10115 let route = host_route_softmax_topk(&logits, selected);
10116 let sel_host: Vec<i32> = route.iter().map(|&(x, _)| x as i32).collect();
10117 let w_host: Vec<f32> = route.iter().map(|&(_, w)| w).collect();
10118 ws.write_i32(e, "moe.sel", &sel_host)?;
10119 ws.write_f32(e, "moe.w", &w_host)?;
10120 Ok(())
10121 }
10122
10123 fn moe_grouped_tail_slots(
10128 &self,
10129 e: &Engine,
10130 ws: &mut StepPool,
10131 ptrs: &CudaSlice<u64>,
10132 moe: &MoeW,
10133 planes: &mut [CudaSlice<f32>],
10134 ) -> Res<()> {
10135 let hidden = self.hidden;
10136 let ff = moe.plan.expert_intermediate_size as usize;
10137 let n_sel = moe.plan.experts_per_token as usize;
10138 let (
10139 BankHalf::Nvfp4 {
10140 codes: gc,
10141 scales: gs,
10142 macros_dev: gm,
10143 ..
10144 },
10145 BankHalf::Nvfp4 {
10146 codes: uc,
10147 scales: us,
10148 macros_dev: um,
10149 ..
10150 },
10151 BankHalf::Nvfp4 {
10152 codes: dc,
10153 scales: ds,
10154 macros_dev: dm,
10155 ..
10156 },
10157 ) = (&moe.bank.gate, &moe.bank.up, &moe.bank.down)
10158 else {
10159 return Err("qwen4exp_gpu: grouped tail on a non-NVFP4 bank".into());
10160 };
10161 let mixed = ws.take_f32(e, "hc.mixed", hidden, 0)?;
10162 let sel = ws
10163 .i32s
10164 .remove("moe.sel")
10165 .ok_or("step workspace: moe.sel is not parked")?;
10166 let w_dev = ws.take_f32(e, "moe.w", n_sel, 0)?;
10167 let mut act = ws.take_f32(e, "moe.act", n_sel * ff, 0)?;
10168 if sel_gufuse_on() && hidden % 32 == 0 && ff % 4 == 0 {
10171 launch_nvfp4_sel_gu_silu(
10172 e,
10173 (gc, gs, gm),
10174 (uc, us, um),
10175 Some(&sel),
10176 0,
10177 n_sel,
10178 &mixed,
10179 &mut act,
10180 hidden,
10181 ff,
10182 None,
10183 )?;
10184 } else {
10185 let mut yg = ws.take_f32(e, "moe.yg", n_sel * ff, 0)?;
10186 let mut yu = ws.take_f32(e, "moe.yu", n_sel * ff, 0)?;
10187 launch_nvfp4_sel_matvec(e, gc, gs, gm, &sel, &mixed, &mut yg, n_sel, hidden, ff, 0)?;
10188 launch_nvfp4_sel_matvec(e, uc, us, um, &sel, &mixed, &mut yu, n_sel, hidden, ff, 0)?;
10189 e.silu_mul(&yg, &yu, &mut act, n_sel * ff)?;
10190 ws.put_f32("moe.yg", yg);
10191 ws.put_f32("moe.yu", yu);
10192 }
10193 let mut partial = ws.take_f32(e, "moe.partial", n_sel * hidden, 0)?;
10194 launch_nvfp4_sel_matvec(
10195 e,
10196 dc,
10197 ds,
10198 dm,
10199 &sel,
10200 &act,
10201 &mut partial,
10202 n_sel,
10203 ff,
10204 hidden,
10205 ff,
10206 )?;
10207 let mut out = ws.take_f32(e, "moe.out", hidden, 0)?;
10208 e.axpy_rows_seq_into(&partial, &w_dev, &mut out, hidden, n_sel)?;
10209 ws.put_i32("moe.sel", sel);
10210 ws.put_f32("moe.w", w_dev);
10211 ws.put_f32("moe.act", act);
10212 ws.put_f32("moe.partial", partial);
10213 let out = self.moe_shared_tail(e, ws, moe, &mixed, out, 1)?;
10214 ws.put_f32("hc.mixed", mixed);
10215 let inject = take_inject(e, ws, self.streams, 1)?;
10216 self.gate_write(e, planes, ptrs, &out, &inject, 1)?;
10217 ws.put_f32("moe.out", out);
10218 put_inject(ws, inject);
10219 Ok(())
10220 }
10221
10222 fn forward_graphs_tail(
10228 &self,
10229 e: &Engine,
10230 state: &mut Qwen4ExpState,
10231 mut planes: Vec<CudaSlice<f32>>,
10232 ptrs: CudaSlice<u64>,
10233 base_pos: usize,
10234 ) -> Res<Vec<f32>> {
10235 let mut graphs = std::mem::take(&mut state.graphs);
10236 if graphs.a.len() != self.layers.len() {
10237 graphs.a = (0..self.layers.len()).map(|_| None).collect();
10238 graphs.b = (0..self.layers.len()).map(|_| None).collect();
10239 }
10240 let ws = &mut state.ws;
10241 let tokens = &state.tokens;
10242 for (li, (layer, lstate)) in self.layers.iter().zip(state.layers.iter_mut()).enumerate() {
10243 let a_ok = matches!(layer.mixer, MixerW::Gdn(_)) && layer.ple.is_none();
10244 if a_ok {
10245 if graphs.a[li].is_none() {
10246 graphs.a[li] = Some(e.capture_graph_retained_nowarm(|eng| {
10247 self.layer_interior(
10248 eng,
10249 ws,
10250 &ptrs,
10251 layer,
10252 lstate,
10253 &mut planes,
10254 tokens,
10255 base_pos,
10256 )
10257 })?);
10258 }
10259 graphs.a[li].as_ref().unwrap().0.launch()?;
10260 } else {
10261 self.layer_interior(e, ws, &ptrs, layer, lstate, &mut planes, tokens, base_pos)?;
10262 }
10263 let b_ok = moe_sel_path_on()
10264 && matches!(
10265 (
10266 &layer.moe.bank.gate,
10267 &layer.moe.bank.up,
10268 &layer.moe.bank.down
10269 ),
10270 (
10271 BankHalf::Nvfp4 { .. },
10272 BankHalf::Nvfp4 { .. },
10273 BankHalf::Nvfp4 { .. }
10274 )
10275 );
10276 if b_ok {
10277 self.moe_route_slots(e, ws, &layer.moe, layer.index)?;
10278 if graphs.b[li].is_none() {
10279 graphs.b[li] = Some(e.capture_graph_retained_nowarm(|eng| {
10280 self.moe_grouped_tail_slots(eng, ws, &ptrs, &layer.moe, &mut planes)
10281 })?);
10282 }
10283 graphs.b[li].as_ref().unwrap().0.launch()?;
10284 } else {
10285 let mixed = ws.take_f32(e, "hc.mixed", self.hidden, 0)?;
10287 let mlp = self.moe_forward(e, ws, &layer.moe, &mixed, 1, false, layer.index)?;
10288 ws.put_f32("hc.mixed", mixed);
10289 let inject = take_inject(e, ws, self.streams, 1)?;
10290 self.gate_write(e, &mut planes, &ptrs, &mlp, &inject, 1)?;
10291 ws.put_f32("moe.out", mlp);
10292 put_inject(ws, inject);
10293 }
10294 }
10295 if graphs.exit.is_none() {
10296 graphs.exit = Some(e.capture_graph_retained_nowarm(|eng| {
10297 let x = self
10298 .gate_read_inner(
10299 eng,
10300 ws,
10301 &ptrs,
10302 &self.exit_mixer,
10303 &planes,
10304 1,
10305 self.exit_eps,
10306 false,
10307 false,
10308 )?
10309 .0;
10310 let mut logits = ws.take_f32(eng, "logits", self.vocab, 0)?;
10311 linear_trunk_into(
10312 eng,
10313 &self.output,
10314 &self.output_b16,
10315 &x,
10316 &mut logits,
10317 1,
10318 self.hidden,
10319 self.vocab,
10320 )?;
10321 ws.put_f32("hc.mixed", x);
10322 ws.put_f32("logits", logits);
10323 Ok(())
10324 })?);
10325 }
10326 graphs.exit.as_ref().unwrap().0.launch()?;
10327 let out = {
10328 let logits = ws.peek_f32("logits")?;
10329 e.dtoh_view(&logits.slice(0..self.vocab))?
10330 };
10331 for (s, plane) in planes.into_iter().enumerate() {
10332 ws.put_f32(PLANE_SLOTS[s], plane);
10333 }
10334 ws.put_u64("hc.ptrs", ptrs);
10335 state.pos += 1;
10336 state.graphs = graphs;
10337 Ok(out)
10338 }
10339
10340 #[allow(clippy::too_many_arguments)]
10343 #[allow(clippy::too_many_arguments)]
10344 #[allow(clippy::too_many_arguments)]
10345
10346 #[allow(clippy::too_many_arguments)]
10351 fn qsa_update_select(
10352 &self,
10353 e: &Engine,
10354 ws: &mut StepPool,
10355 qsa: &QsaW,
10356 eps: f32,
10357 mixed: &CudaSlice<f32>,
10358 raw_keys: &mut IdxRawCache,
10359 pooled_keys: &mut Vec<f32>,
10360 pooled_dev: &mut Option<CudaSlice<f32>>,
10361 pooled_dev_rows: &mut usize,
10362 raw_dev: &mut Option<IdxRawDev>,
10363 raw_dev_rows: &mut usize,
10364 mut idx_audit: Option<&mut Box<IdxAudit>>,
10365 base_pos: usize,
10366 t: usize,
10367 pos_off: usize,
10368 exact: bool,
10369 ) -> Res<Vec<RowSel>> {
10370 let hidden = self.hidden;
10371 let base = qsa.attn.rope.base;
10372 let t_kv = base_pos + t;
10373 let overlay = &qsa.overlay;
10375 let idx_dim = overlay.head_dim as usize;
10376 let qk_width = (overlay.query_heads as usize + overlay.kv_heads as usize) * idx_dim;
10377 if overlay.kv_heads != 1 {
10378 return Err("qwen4exp_gpu: indexer with more than one key head".into());
10379 }
10380 let idx_proj = prof_section(e, "qsa.idx_proj", || {
10381 let mut idx_proj = ws.take_f32(e, "qsa.idxp", t * qk_width, 0)?;
10382 if exact && t > 1 {
10383 let wv = qsa.idx_proj.slice(0..qsa.idx_proj.len());
10384 for tok in 0..t {
10385 let xv = mixed.slice(tok * hidden..(tok + 1) * hidden);
10386 let mut yv = idx_proj.slice_mut(tok * qk_width..(tok + 1) * qk_width);
10387 e.linear_device_into(&xv, &wv, &mut yv, 1, hidden, qk_width)?;
10388 }
10389 } else {
10390 e.linear_device_into(mixed, &qsa.idx_proj, &mut idx_proj, t, hidden, qk_width)?;
10391 }
10392 Ok(idx_proj)
10393 })?;
10394 let dev_cache = idx_cache_on();
10404 let block_size = overlay.block_size as usize;
10405 let all_full = (base_pos + t) / block_size <= overlay.budget_blocks as usize;
10406 let host_rows = raw_keys.rows(idx_dim);
10407 if *raw_dev_rows > host_rows && !(dev_cache && all_full) {
10408 idx_materialize_host(e, raw_keys, raw_dev, *raw_dev_rows, idx_dim)?;
10412 }
10413 if dev_cache {
10414 let host_rows = raw_keys.rows(idx_dim);
10415 let base_rows = (*raw_dev_rows).max(host_rows);
10416 let cap_rows = (base_rows + t).next_power_of_two().max(64);
10417 let q_off = overlay.query_heads as usize * idx_dim;
10418 match &mut *raw_keys {
10419 IdxRawCache::F32(h) => {
10420 let want = (base_rows + t) * idx_dim;
10421 let grow = match raw_dev.as_ref() {
10422 Some(IdxRawDev::F32(m)) => m.len() < want,
10423 Some(_) => return Err("idxcache: device format lag on f32".into()),
10424 None => true,
10425 };
10426 if grow {
10427 let mut fresh = e.uninit(cap_rows * idx_dim)?;
10428 if let (Some(IdxRawDev::F32(old)), rows) = (raw_dev.as_ref(), *raw_dev_rows)
10429 {
10430 if rows > 0 {
10431 e.copy_range_into(&mut fresh, 0, old, 0, rows * idx_dim)?;
10432 }
10433 }
10434 *raw_dev = Some(IdxRawDev::F32(fresh));
10435 }
10436 let Some(IdxRawDev::F32(m)) = raw_dev.as_mut() else {
10437 unreachable!("allocated above");
10438 };
10439 if host_rows > *raw_dev_rows {
10440 let mut view = m.slice_mut(*raw_dev_rows * idx_dim..host_rows * idx_dim);
10442 e.gpu
10443 .stream()
10444 .memcpy_htod(&h[*raw_dev_rows * idx_dim..], &mut view)?;
10445 *raw_dev_rows = host_rows;
10446 }
10447 launch_copy_rows_col(
10448 e,
10449 &idx_proj,
10450 m,
10451 t,
10452 idx_dim,
10453 qk_width,
10454 q_off,
10455 *raw_dev_rows,
10456 )?;
10457 }
10458 IdxRawCache::Q8(h) => {
10459 let rb = q8_row_bytes(idx_dim);
10460 let want = (base_rows + t) * rb;
10461 let grow = match raw_dev.as_ref() {
10462 Some(IdxRawDev::Q8(m)) => m.len() < want,
10463 Some(_) => return Err("idxcache: device format lag on q8".into()),
10464 None => true,
10465 };
10466 if grow {
10467 let mut fresh = e.alloc_u8_uninit(cap_rows * rb)?;
10468 if let (Some(IdxRawDev::Q8(old)), rows) = (raw_dev.as_ref(), *raw_dev_rows)
10469 {
10470 if rows > 0 {
10471 let mut dst = fresh.slice_mut(0..rows * rb);
10472 e.gpu
10473 .stream()
10474 .memcpy_dtod(&old.slice(0..rows * rb), &mut dst)?;
10475 }
10476 }
10477 *raw_dev = Some(IdxRawDev::Q8(fresh));
10478 }
10479 let Some(IdxRawDev::Q8(m)) = raw_dev.as_mut() else {
10480 unreachable!("allocated above");
10481 };
10482 if host_rows > *raw_dev_rows {
10483 let mut view = m.slice_mut(*raw_dev_rows * rb..host_rows * rb);
10484 e.gpu
10485 .stream()
10486 .memcpy_htod(&h[*raw_dev_rows * rb..host_rows * rb], &mut view)?;
10487 *raw_dev_rows = host_rows;
10488 }
10489 launch_q4e_idx_append_q8(
10490 e,
10491 &idx_proj,
10492 m,
10493 t,
10494 idx_dim,
10495 qk_width,
10496 q_off,
10497 *raw_dev_rows,
10498 )?;
10499 }
10500 IdxRawCache::Bf16(h) => {
10501 let want = (base_rows + t) * idx_dim;
10502 let grow = match raw_dev.as_ref() {
10503 Some(IdxRawDev::Bf16(m)) => m.len() < want,
10504 Some(_) => return Err("idxcache: device format lag on bf16".into()),
10505 None => true,
10506 };
10507 if grow {
10508 let mut fresh = unsafe { e.gpu.stream().alloc::<u16>(cap_rows * idx_dim)? };
10509 if let (Some(IdxRawDev::Bf16(old)), rows) =
10510 (raw_dev.as_ref(), *raw_dev_rows)
10511 {
10512 if rows > 0 {
10513 let mut dst = fresh.slice_mut(0..rows * idx_dim);
10514 e.gpu
10515 .stream()
10516 .memcpy_dtod(&old.slice(0..rows * idx_dim), &mut dst)?;
10517 }
10518 }
10519 *raw_dev = Some(IdxRawDev::Bf16(fresh));
10520 }
10521 let Some(IdxRawDev::Bf16(m)) = raw_dev.as_mut() else {
10522 unreachable!("allocated above");
10523 };
10524 if host_rows > *raw_dev_rows {
10525 let mut view = m.slice_mut(*raw_dev_rows * idx_dim..host_rows * idx_dim);
10526 e.gpu.stream().memcpy_htod(
10527 &h[*raw_dev_rows * idx_dim..host_rows * idx_dim],
10528 &mut view,
10529 )?;
10530 *raw_dev_rows = host_rows;
10531 }
10532 launch_q4e_idx_append_bf16(
10533 e,
10534 &idx_proj,
10535 m,
10536 t,
10537 idx_dim,
10538 qk_width,
10539 q_off,
10540 *raw_dev_rows,
10541 )?;
10542 }
10543 }
10544 *raw_dev_rows += t;
10545 }
10546 if let Some(audit) = idx_audit.as_deref_mut() {
10550 let q_off = overlay.query_heads as usize * idx_dim;
10551 let rows_f = e.dtoh_view(&idx_proj.slice(0..t * qk_width))?;
10552 let IdxRawCache::F32(twin) = &mut audit.raw_f32 else {
10553 return Err("idxq audit: twin cache is not f32".into());
10554 };
10555 for row in 0..t {
10556 twin.extend_from_slice(&rows_f[row * qk_width + q_off..(row + 1) * qk_width]);
10557 }
10558 }
10559 let sels: Vec<RowSel> = if dev_cache && all_full {
10560 ws.put_f32("qsa.idxp", idx_proj);
10561 (0..t)
10562 .map(|qt| RowSel {
10563 full: true,
10564 blocks: Vec::new(),
10565 visible: base_pos + qt + 1,
10566 })
10567 .collect()
10568 } else {
10569 let idx_rows = e.dtoh_view(&idx_proj.slice(0..t * qk_width))?;
10570 ws.put_f32("qsa.idxp", idx_proj);
10571 let q_off = overlay.query_heads as usize * idx_dim;
10572 for row in 0..t {
10573 raw_keys.append_rows_f32(
10574 &idx_rows[row * qk_width + q_off..(row + 1) * qk_width],
10575 1,
10576 idx_dim,
10577 );
10578 }
10579 let dev_scorer = idx_dev_on();
10586 let sels = prof_section(e, "qsa.idx_host", || {
10587 indexer_select_rows(
10588 overlay,
10589 base,
10590 qsa.yarn.as_ref().map(|y| (y.ff_host.as_slice(), y.mscale)),
10591 eps,
10592 &qsa.idx_q_norm,
10593 &qsa.idx_k_norm,
10594 &idx_rows,
10595 raw_keys,
10596 pooled_keys,
10597 if dev_scorer {
10598 Some((e, pooled_dev, pooled_dev_rows))
10599 } else {
10600 None
10601 },
10602 base_pos,
10603 t,
10604 t_kv,
10605 pos_off,
10606 )
10607 })?;
10608 if let Some(audit) = idx_audit.as_deref_mut() {
10618 if t <= 8 && sels.iter().any(|s| !s.full) {
10619 let twin_sels = indexer_select_rows(
10620 overlay,
10621 base,
10622 qsa.yarn.as_ref().map(|y| (y.ff_host.as_slice(), y.mscale)),
10623 eps,
10624 &qsa.idx_q_norm,
10625 &qsa.idx_k_norm,
10626 &idx_rows,
10627 &audit.raw_f32,
10628 &mut audit.pooled_f32,
10629 None,
10630 base_pos,
10631 t,
10632 t_kv,
10633 pos_off,
10634 )?;
10635 use std::sync::atomic::Ordering::Relaxed;
10636 for (a, b) in sels.iter().zip(&twin_sels) {
10637 if a.full && b.full {
10638 continue;
10639 }
10640 IDXQ_AUDIT_ROWS.fetch_add(1, Relaxed);
10641 if a.full != b.full || a.blocks != b.blocks {
10642 IDXQ_AUDIT_FLIPPED.fetch_add(1, Relaxed);
10643 let mut diff = 0u64;
10644 let (sa, sb) = (&a.blocks, &b.blocks);
10645 let seta: std::collections::BTreeSet<_> = sa.iter().collect();
10646 let setb: std::collections::BTreeSet<_> = sb.iter().collect();
10647 diff += seta.symmetric_difference(&setb).count() as u64;
10648 IDXQ_AUDIT_BLOCKS.fetch_add(diff, Relaxed);
10649 }
10650 }
10651 }
10652 }
10653 sels
10654 };
10655 Ok(sels)
10656 }
10657
10658 fn qsa_forward(
10659 &self,
10660 e: &Engine,
10661 ws: &mut StepPool,
10662 layer: &LayerW,
10663 qsa: &QsaW,
10664 mixed: &CudaSlice<f32>,
10665 mstate: &mut MixerState,
10666 base_pos: usize,
10667 t: usize,
10668 pos_off: usize,
10671 exact: bool,
10675 ) -> Res<CudaSlice<f32>> {
10676 let MixerState::Qsa {
10677 kv,
10678 raw_keys,
10679 pooled_keys,
10680 pooled_dev,
10681 pooled_dev_rows,
10682 raw_dev,
10683 raw_dev_rows,
10684 idx_audit,
10685 } = mstate
10686 else {
10687 return Err(format!(
10688 "qwen4exp_gpu: QSA layer {} bound to non-QSA state",
10689 layer.index
10690 )
10691 .into());
10692 };
10693 let hidden = self.hidden;
10694 let nh = qsa.attn.query_heads as usize;
10695 let nkv = qsa.attn.kv_heads as usize;
10696 let hd = qsa.attn.key_head_dim as usize;
10697 let eps = layer.eps_attn;
10698 let cap = kv.capacity_rows(nkv * hd);
10700
10701 let n_rot = qsa.attn.rope.dimensions as usize;
10702 let base = qsa.attn.rope.base;
10703 let (q, gate) = prof_section(e, "qsa.proj", || {
10704 let mut q_fused = ws.take_f32(e, "qsa.qf", t * 2 * nh * hd, 0)?;
10705 let mut k_new = ws.take_f32(e, "qsa.k", t * nkv * hd, 0)?;
10706 let mut v_new = ws.take_f32(e, "qsa.v", t * nkv * hd, 0)?;
10707 if let (true, Some(stack)) = (
10711 t == 1 && proj_stack_on() && trunk_bf16_on(),
10712 qsa.proj_b16.as_ref(),
10713 ) {
10714 launch_qmatvec_bf16w_multi4(
10715 e,
10716 stack,
10717 mixed,
10718 &[
10719 (&q_fused, 2 * nh * hd),
10720 (&k_new, nkv * hd),
10721 (&v_new, nkv * hd),
10722 ],
10723 hidden,
10724 )?;
10725 } else {
10726 linear_trunk_stacked_into(
10727 e,
10728 &qsa.wq,
10729 &qsa.proj_b16,
10730 0,
10731 mixed,
10732 &mut q_fused,
10733 t,
10734 hidden,
10735 2 * nh * hd,
10736 )?;
10737 linear_trunk_stacked_into(
10738 e,
10739 &qsa.wk,
10740 &qsa.proj_b16,
10741 2 * nh * hd,
10742 mixed,
10743 &mut k_new,
10744 t,
10745 hidden,
10746 nkv * hd,
10747 )?;
10748 linear_trunk_stacked_into(
10749 e,
10750 &qsa.wv,
10751 &qsa.proj_b16,
10752 2 * nh * hd + nkv * hd,
10753 mixed,
10754 &mut v_new,
10755 t,
10756 hidden,
10757 nkv * hd,
10758 )?;
10759 }
10760 let mut q = ws.take_f32(e, "qsa.q", t * nh * hd, 0)?;
10761 let mut gate = ws.take_f32(e, "qsa.gate", t * nh * hd, 0)?;
10762 e.q_gate_split(&q_fused, &mut q, &mut gate, hd, nh, t)?;
10763 ws.put_f32("qsa.qf", q_fused);
10764 let mut q = if let Some(norm) = qsa.q_norm.as_ref() {
10765 let mut dst = ws.take_f32(e, "qsa.qn", t * nh * hd, 0)?;
10766 e.rms_norm(&q, norm, &mut dst, hd, t * nh, eps)?;
10767 ws.put_f32("qsa.q", q);
10768 dst
10769 } else {
10770 q
10771 };
10772 let mut k_new = if let Some(norm) = qsa.k_norm.as_ref() {
10773 let mut dst = ws.take_f32(e, "qsa.kn", t * nkv * hd, 0)?;
10774 e.rms_norm(&k_new, norm, &mut dst, hd, t * nkv, eps)?;
10775 ws.put_f32("qsa.k", k_new);
10776 dst
10777 } else {
10778 k_new
10779 };
10780 let positions: Vec<i32> = (0..t).map(|i| (base_pos + i + pos_off) as i32).collect();
10781 let pos_dev = ws.take_i32(e, "qsa.pos", &positions, 0)?;
10782 if let Some(yarn) = qsa.yarn.as_ref() {
10783 e.rope_neox_ffm(
10784 &mut q,
10785 &pos_dev,
10786 hd,
10787 n_rot,
10788 nh,
10789 t,
10790 base,
10791 1.0,
10792 &yarn.ff,
10793 yarn.mscale,
10794 )?;
10795 e.rope_neox_ffm(
10796 &mut k_new,
10797 &pos_dev,
10798 hd,
10799 n_rot,
10800 nkv,
10801 t,
10802 base,
10803 1.0,
10804 &yarn.ff,
10805 yarn.mscale,
10806 )?;
10807 } else {
10808 e.rope_neox(&mut q, &pos_dev, hd, n_rot, nh, t, base, 1.0)?;
10809 e.rope_neox(&mut k_new, &pos_dev, hd, n_rot, nkv, t, base, 1.0)?;
10810 }
10811 ws.put_i32("qsa.pos", pos_dev);
10812 match kv {
10814 QsaKvStore::F32 { k, v } => {
10815 e.copy_range_into(k, base_pos * nkv * hd, &k_new, 0, t * nkv * hd)?;
10816 e.copy_range_into(v, base_pos * nkv * hd, &v_new, 0, t * nkv * hd)?;
10817 }
10818 QsaKvStore::Q8Q5 { k, v } => {
10821 launch_q4e_kv_append(e, &k_new, &v_new, k, v, base_pos, t, nkv * hd)?;
10822 }
10823 }
10824 ws.put_f32(
10825 if qsa.k_norm.is_some() {
10826 "qsa.kn"
10827 } else {
10828 "qsa.k"
10829 },
10830 k_new,
10831 );
10832 ws.put_f32("qsa.v", v_new);
10833 Ok((q, gate))
10834 })?;
10835 let t_kv = base_pos + t;
10836 let sels = self.qsa_update_select(
10837 e,
10838 ws,
10839 qsa,
10840 eps,
10841 mixed,
10842 raw_keys,
10843 pooled_keys,
10844 pooled_dev,
10845 pooled_dev_rows,
10846 raw_dev,
10847 raw_dev_rows,
10848 idx_audit.as_mut(),
10849 base_pos,
10850 t,
10851 pos_off,
10852 exact,
10853 )?;
10854 let overlay = &qsa.overlay;
10855
10856 let scale = match qsa.attn.scale {
10857 memra_gguf::model_plan::AttentionScale::InverseSqrtKeyDim => 1.0 / (hd as f32).sqrt(),
10858 memra_gguf::model_plan::AttentionScale::Fixed(scale) => scale,
10859 };
10860 let long_att = if kv.is_quant() {
10884 if longatt_mode() == LongAttMode::Off {
10885 return Err(
10886 "qwen4exp_gpu: kvq requires the block-list attention form (longatt=off)".into(),
10887 );
10888 }
10889 true
10890 } else {
10891 match longatt_mode() {
10892 LongAttMode::Force => true,
10893 LongAttMode::Auto => t_kv > SDPA_MASK_TKV_BOUND || sels.iter().any(|s| !s.full),
10894 LongAttMode::Off => false,
10895 }
10896 };
10897 let block_size = overlay.block_size as usize;
10898 let attended = if long_att {
10899 let (pos_flat, meta, max_count) = rowsel_positions(&sels, block_size);
10900 let pos_dev = prof_section(e, "qsa.mask_h2d", || {
10901 ws.take_i32(e, "qsa.selpos", &pos_flat, 0)
10902 })?;
10903 let meta_dev = ws.take_i32(e, "qsa.selmeta", &meta, 0)?;
10904 let attended = prof_section(e, "qsa.sdpa", || {
10905 let mut attended = ws.take_f32(e, "qsa.att", t * nh * hd, 0)?;
10906 match kv {
10907 QsaKvStore::F32 { k, v } => {
10908 let k_view = k.slice(0..t_kv * nkv * hd);
10909 let v_view = v.slice(0..t_kv * nkv * hd);
10910 launch_sdpa_blocklist(
10911 e,
10912 &q,
10913 &k_view,
10914 &v_view,
10915 &mut attended,
10916 &pos_dev,
10917 &meta_dev,
10918 hd,
10919 nh,
10920 nkv,
10921 t,
10922 max_count,
10923 scale,
10924 )?;
10925 }
10926 QsaKvStore::Q8Q5 { k, v } => {
10927 launch_q4e_sdpa_blocklist_q8q5(
10928 e,
10929 &q,
10930 k,
10931 v,
10932 &mut attended,
10933 &pos_dev,
10934 &meta_dev,
10935 hd,
10936 nh,
10937 nkv,
10938 t,
10939 max_count,
10940 scale,
10941 )?;
10942 }
10943 }
10944 Ok(attended)
10945 })?;
10946 ws.put_i32("qsa.selpos", pos_dev);
10947 ws.put_i32("qsa.selmeta", meta_dev);
10948 attended
10949 } else {
10950 let QsaKvStore::F32 { k, v } = &*kv else {
10951 return Err("qwen4exp_gpu: masked SDPA reached with a quantized cache".into());
10952 };
10953 let mask = rowsel_to_mask(&sels, block_size, t_kv);
10954 let mask_dev = prof_section(e, "qsa.mask_h2d", || {
10955 ws.take_u8_h2d(e, "qsa.mask", &mask, t * cap.min(SDPA_MASK_TKV_BOUND))
10958 })?;
10959 let attended = prof_section(e, "qsa.sdpa", || {
10960 let mut attended = ws.take_f32(e, "qsa.att", t * nh * hd, 0)?;
10961 let k_view = k.slice(0..t_kv * nkv * hd);
10962 let v_view = v.slice(0..t_kv * nkv * hd);
10963 launch_sdpa_mask(
10964 e,
10965 &q,
10966 &k_view,
10967 &v_view,
10968 &mut attended,
10969 &mask_dev,
10970 hd,
10971 nh,
10972 nkv,
10973 t,
10974 t_kv,
10975 scale,
10976 )?;
10977 Ok(attended)
10978 })?;
10979 ws.put_u8("qsa.mask", mask_dev);
10980 attended
10981 };
10982 ws.put_f32(
10983 if qsa.q_norm.is_some() {
10984 "qsa.qn"
10985 } else {
10986 "qsa.q"
10987 },
10988 q,
10989 );
10990 let out = prof_section(e, "qsa.gate_wo", || {
10991 let mut sg = ws.take_f32(e, "qsa.sg", t * nh * hd, 0)?;
10993 e.sigmoid(&gate, &mut sg, t * nh * hd)?;
10994 let mut gated = ws.take_f32(e, "qsa.gated", t * nh * hd, 0)?;
10995 e.mul(&attended, &sg, &mut gated, t * nh * hd)?;
10996 let mut out = ws.take_f32(e, "mixer.out", t * hidden, 0)?;
10997 linear_trunk_into(
10998 e,
10999 &qsa.wo,
11000 &qsa.wo_b16,
11001 &gated,
11002 &mut out,
11003 t,
11004 nh * hd,
11005 hidden,
11006 )?;
11007 ws.put_f32("qsa.sg", sg);
11008 ws.put_f32("qsa.gated", gated);
11009 Ok(out)
11010 })?;
11011 ws.put_f32("qsa.att", attended);
11012 ws.put_f32("qsa.gate", gate);
11013 Ok(out)
11014 }
11015
11016 #[allow(clippy::too_many_arguments)]
11020 fn gdn_forward(
11021 &self,
11022 e: &Engine,
11023 ws: &mut StepPool,
11024 layer: &LayerW,
11025 gdn: &GdnW,
11026 mixed: &CudaSlice<f32>,
11027 mstate: &mut MixerState,
11028 t: usize,
11029 mut stash: Option<&mut GdnStash>,
11033 ) -> Res<CudaSlice<f32>> {
11034 let MixerState::Gdn { conv, state } = mstate else {
11035 return Err(format!(
11036 "qwen4exp_gpu: GDN layer {} bound to non-GDN state",
11037 layer.index
11038 )
11039 .into());
11040 };
11041 let hidden = self.hidden;
11042 let p = &gdn.plan;
11043 let (nk, nv) = (p.key_heads as usize, p.value_heads as usize);
11044 let (hk, hv) = (p.key_head_dim as usize, p.value_head_dim as usize);
11045 let kernel = p.conv_kernel as usize;
11046 let pad = kernel - 1;
11047 let conv_dim = 2 * nk * hk + nv * hv;
11048 let eps = layer.eps_attn;
11049
11050 let (qkv, z, beta_raw, g_log) = prof_section(e, "gdn.proj", || {
11051 let mut qkv = ws.take_f32(e, "gdn.qkv", t * conv_dim, 0)?;
11052 let mut z = ws.take_f32(e, "gdn.z", t * nv * hv, 0)?;
11053 let mut beta_raw = ws.take_f32(e, "gdn.beta", t * nv, 0)?;
11054 let mut alpha = ws.take_f32(e, "gdn.alpha", t * nv, 0)?;
11055 if let (true, Some(stack)) = (
11060 t == 1 && proj_stack_on() && trunk_bf16_on(),
11061 gdn.proj_b16.as_ref(),
11062 ) {
11063 launch_qmatvec_bf16w_multi4(
11064 e,
11065 stack,
11066 mixed,
11067 &[
11068 (&qkv, conv_dim),
11069 (&z, nv * hv),
11070 (&beta_raw, nv),
11071 (&alpha, nv),
11072 ],
11073 hidden,
11074 )?;
11075 } else {
11076 linear_trunk_stacked_into(
11077 e,
11078 &gdn.qkv,
11079 &gdn.proj_b16,
11080 0,
11081 mixed,
11082 &mut qkv,
11083 t,
11084 hidden,
11085 conv_dim,
11086 )?;
11087 linear_trunk_stacked_into(
11088 e,
11089 &gdn.z,
11090 &gdn.proj_b16,
11091 conv_dim,
11092 mixed,
11093 &mut z,
11094 t,
11095 hidden,
11096 nv * hv,
11097 )?;
11098 linear_trunk_stacked_into(
11099 e,
11100 &gdn.beta,
11101 &gdn.proj_b16,
11102 conv_dim + nv * hv,
11103 mixed,
11104 &mut beta_raw,
11105 t,
11106 hidden,
11107 nv,
11108 )?;
11109 linear_trunk_stacked_into(
11110 e,
11111 &gdn.alpha,
11112 &gdn.proj_b16,
11113 conv_dim + nv * hv + nv,
11114 mixed,
11115 &mut alpha,
11116 t,
11117 hidden,
11118 nv,
11119 )?;
11120 }
11121 let mut g_log = ws.take_f32(e, "gdn.glog", t * nv, 0)?;
11122 e.gdn_glog_v(&alpha.slice(0..t * nv), &gdn.dt, &gdn.a, &mut g_log, nv, t)?;
11123 ws.put_f32("gdn.alpha", alpha);
11124 Ok((qkv, z, beta_raw, g_log))
11125 })?;
11126
11127 let o = prof_section(e, "gdn.conv_scan", || {
11128 if let Some(st) = stash.as_deref_mut() {
11132 e.copy_range_into(&mut st.conv_pre, 0, conv, 0, pad * conv_dim)?;
11133 e.copy_range_into(&mut st.qkv_rows, 0, &qkv, 0, t * conv_dim)?;
11134 }
11135 let mut conv_out = ws.take_f32(e, "gdn.conv_out", t * conv_dim, 0)?;
11138 let mut o = ws.take_f32(e, "gdn.o", t * nv * hv, 0)?;
11139 let mut tmp = if t >= pad {
11140 None
11141 } else {
11142 Some(ws.take_f32(e, "gdn.tmp", (pad - t) * conv_dim, 0)?)
11143 };
11144 let scale = 1.0 / (hk as f32).sqrt();
11145 let step_ok = gdn_step_on() && hk % 32 == 0 && hk <= 1024;
11146 let chain = |eng: &Engine,
11157 conv: &mut CudaSlice<f32>,
11158 state: &mut CudaSlice<f32>,
11159 states_snap: Option<&mut CudaSlice<f32>>,
11160 conv_out: &mut CudaSlice<f32>,
11161 o: &mut CudaSlice<f32>,
11162 tmp: Option<&mut CudaSlice<f32>>|
11163 -> Res<()> {
11164 launch_dwconv(
11165 eng,
11166 &qkv,
11167 conv,
11168 &gdn.conv_w,
11169 conv_out,
11170 t,
11171 pad,
11172 conv_dim,
11173 kernel,
11174 1,
11175 1,
11176 )?;
11177 match states_snap {
11178 Some(states) => {
11179 let state_len = nv * hv * hk;
11180 for tok in 0..t {
11181 if step_ok {
11182 launch_gdn_scan_step_at(
11183 eng, conv_out, &g_log, &beta_raw, state, o, tok, nk, nv, hk,
11184 hv, scale, eps,
11185 )?;
11186 } else {
11187 launch_gdn_scan_at(
11188 eng, conv_out, &g_log, &beta_raw, state, o, tok, nk, nv, hk,
11189 hv, scale, eps,
11190 )?;
11191 }
11192 eng.copy_range_into(states, tok * state_len, state, 0, state_len)?;
11193 }
11194 }
11195 None if t == 1 && step_ok => {
11196 launch_gdn_scan_step(
11197 eng, conv_out, &g_log, &beta_raw, state, o, nk, nv, hk, hv, scale, eps,
11198 )?;
11199 }
11200 None => {
11201 launch_gdn_scan(
11202 eng, conv_out, &g_log, &beta_raw, state, o, nk, nv, hk, hv, t, scale,
11203 eps,
11204 )?;
11205 }
11206 }
11207 if t >= pad {
11210 eng.copy_range_into(conv, 0, &qkv, (t - pad) * conv_dim, pad * conv_dim)?;
11211 } else {
11212 let keep = pad - t;
11213 let tmp = tmp.ok_or("qwen4exp_gpu: gdn conv roll needs the tmp slot")?;
11214 eng.copy_range_into(tmp, 0, conv, t * conv_dim, keep * conv_dim)?;
11215 eng.copy_range_into(conv, 0, tmp, 0, keep * conv_dim)?;
11216 eng.copy_range_into(conv, keep * conv_dim, &qkv, 0, t * conv_dim)?;
11217 }
11218 Ok(())
11219 };
11220 let graphable = stash.is_some() && verify_graphs_on() && step_ws_on() && !prof::on();
11223 match stash.as_deref_mut() {
11224 Some(st) if graphable => {
11225 let entry = match st.scan_graph.take() {
11228 Some((gt, g)) if gt == t => Some(g),
11229 _ => None,
11230 };
11231 let warm = st.scan_warm == Some(t);
11232 st.scan_warm = Some(t);
11233 let entry = match (warm, entry) {
11235 (false, _) => {
11238 chain(
11239 e,
11240 conv,
11241 state,
11242 Some(&mut st.states),
11243 &mut conv_out,
11244 &mut o,
11245 tmp.as_mut(),
11246 )?;
11247 None
11248 }
11249 (true, Some(g)) => {
11251 g.0.launch()?;
11252 Some(g)
11253 }
11254 (true, None) => {
11259 let states = &mut st.states;
11260 let mut tmp_ref = tmp.as_mut();
11261 let g = e.capture_graph_retained_nowarm(|eng| {
11262 chain(
11263 eng,
11264 conv,
11265 state,
11266 Some(states),
11267 &mut conv_out,
11268 &mut o,
11269 tmp_ref.as_deref_mut(),
11270 )
11271 })?;
11272 g.0.launch()?;
11273 Some(g)
11274 }
11275 };
11276 if let Some(g) = entry {
11277 st.scan_graph = Some((t, g));
11278 }
11279 }
11280 Some(st) => chain(
11281 e,
11282 conv,
11283 state,
11284 Some(&mut st.states),
11285 &mut conv_out,
11286 &mut o,
11287 tmp.as_mut(),
11288 )?,
11289 None => chain(e, conv, state, None, &mut conv_out, &mut o, tmp.as_mut())?,
11290 }
11291 ws.put_f32("gdn.conv_out", conv_out);
11292 if let Some(tmp) = tmp {
11293 ws.put_f32("gdn.tmp", tmp);
11294 }
11295 Ok(o)
11296 })?;
11297 ws.put_f32("gdn.qkv", qkv);
11298 ws.put_f32("gdn.beta", beta_raw);
11299 ws.put_f32("gdn.glog", g_log);
11300
11301 let out = prof_section(e, "gdn.norm_gate_out", || {
11302 let mut gated = ws.take_f32(e, "gdn.gated", t * nv * hv, 0)?;
11303 match p.gate_activation {
11304 GdnGateActivation::Sigmoid if gdn_fuse_on() => {
11307 launch_rms_sigmul(e, &o, &gdn.norm, &z, &mut gated, hv, t * nv, eps)?;
11308 }
11309 GdnGateActivation::Sigmoid => {
11310 let mut normed = ws.take_f32(e, "gdn.normed", t * nv * hv, 0)?;
11311 e.rms_norm(&o, &gdn.norm, &mut normed, hv, t * nv, eps)?;
11312 let mut sg = ws.take_f32(e, "gdn.sg", t * nv * hv, 0)?;
11313 e.sigmoid(&z, &mut sg, t * nv * hv)?;
11314 e.mul(&normed, &sg, &mut gated, t * nv * hv)?;
11315 ws.put_f32("gdn.sg", sg);
11316 ws.put_f32("gdn.normed", normed);
11317 }
11318 GdnGateActivation::Silu => {
11319 let mut normed = ws.take_f32(e, "gdn.normed", t * nv * hv, 0)?;
11320 e.rms_norm(&o, &gdn.norm, &mut normed, hv, t * nv, eps)?;
11321 e.silu_mul(&z, &normed, &mut gated, t * nv * hv)?;
11322 ws.put_f32("gdn.normed", normed);
11323 }
11324 }
11325 let mut out = ws.take_f32(e, "mixer.out", t * hidden, 0)?;
11326 linear_trunk_into(
11327 e,
11328 &gdn.out,
11329 &gdn.out_b16,
11330 &gated,
11331 &mut out,
11332 t,
11333 nv * hv,
11334 hidden,
11335 )?;
11336 ws.put_f32("gdn.gated", gated);
11337 Ok(out)
11338 })?;
11339 ws.put_f32("gdn.z", z);
11340 ws.put_f32("gdn.o", o);
11341 Ok(out)
11342 }
11343
11344 fn moe_forward(
11348 &self,
11349 e: &Engine,
11350 ws: &mut StepPool,
11351 moe: &MoeW,
11352 mixed: &CudaSlice<f32>,
11353 t: usize,
11354 rows_grouped: bool,
11358 layer: u32,
11362 ) -> Res<CudaSlice<f32>> {
11363 let hidden = self.hidden;
11364 let experts = moe.plan.expert_count as usize;
11365 let selected = moe.plan.experts_per_token as usize;
11366 let ff = moe.plan.expert_intermediate_size as usize;
11367
11368 let nvfp4_bank = matches!(
11377 (&moe.bank.gate, &moe.bank.up, &moe.bank.down),
11378 (
11379 BankHalf::Nvfp4 { .. },
11380 BankHalf::Nvfp4 { .. },
11381 BankHalf::Nvfp4 { .. }
11382 )
11383 );
11384 let devbf16_bank = matches!(
11385 (&moe.bank.gate, &moe.bank.up, &moe.bank.down),
11386 (
11387 BankHalf::DeviceBf16(_),
11388 BankHalf::DeviceBf16(_),
11389 BankHalf::DeviceBf16(_)
11390 )
11391 );
11392 let use_dev_router = router_dev_on()
11393 && moe_sel_path_on()
11394 && route_dev_geometry(experts, selected)
11395 && ((nvfp4_bank
11396 && hidden % 32 == 0
11397 && ff % 4 == 0
11398 && (t == 1
11399 || (rows_grouped
11400 && verify_mt_on()
11401 && sel_gufuse_on()
11402 && t * selected <= 8192)))
11403 || (devbf16_bank && hidden % 8 == 0 && ff % 8 == 0 && (t == 1 || rows_grouped)));
11404 type DevRoute = (CudaSlice<i32>, CudaSlice<f32>, Option<CudaSlice<i32>>);
11407 let (routes, mut dev_route): (Vec<Vec<(usize, f32)>>, Option<DevRoute>) =
11408 prof_section(e, "moe.router", || {
11409 let mut router_out = ws.take_f32(e, "moe.router", t * experts, 0)?;
11410 let none: Option<CudaSlice<u8>> = None;
11411 let rb = if router_bf16_on() {
11412 &moe.router_b16
11413 } else {
11414 &none
11415 };
11416 linear_trunk_into(
11417 e,
11418 &moe.router,
11419 rb,
11420 mixed,
11421 &mut router_out,
11422 t,
11423 hidden,
11424 experts,
11425 )?;
11426 if use_dev_router {
11427 let mut sel = ws.take_i32_slot(e, "moe.sel", t * selected, 0)?;
11428 let mut w = ws.take_f32(e, "moe.w", t * selected, 0)?;
11429 let mut tokm = if t > 1 {
11430 Some(ws.take_i32_slot(e, "moe.tok", t * selected, 0)?)
11431 } else {
11432 None
11433 };
11434 route_topk_device(
11435 e,
11436 &router_out,
11437 &mut sel,
11438 &mut w,
11439 tokm.as_mut().map(|m| (m, 0)),
11440 experts,
11441 selected,
11442 t,
11443 layer,
11444 )?;
11445 ws.put_f32("moe.router", router_out);
11446 return Ok((Vec::new(), Some((sel, w, tokm))));
11447 }
11448 let logits = e.dtoh_view(&router_out.slice(0..t * experts))?;
11449 ws.put_f32("moe.router", router_out);
11450 let mut routes: Vec<Vec<(usize, f32)>> = Vec::with_capacity(t);
11451 for token in 0..t {
11452 routes.push(host_route_softmax_topk(
11453 &logits[token * experts..(token + 1) * experts],
11454 selected,
11455 ));
11456 }
11457 Ok((routes, None))
11458 })?;
11459 if (t == 1 || rows_grouped) && moe_sel_path_on() {
11467 if let (
11468 BankHalf::Nvfp4 {
11469 codes: gc,
11470 scales: gs,
11471 macros_dev: gm,
11472 ..
11473 },
11474 BankHalf::Nvfp4 {
11475 codes: uc,
11476 scales: us,
11477 macros_dev: um,
11478 ..
11479 },
11480 BankHalf::Nvfp4 {
11481 codes: dc,
11482 scales: ds,
11483 macros_dev: dm,
11484 ..
11485 },
11486 ) = (&moe.bank.gate, &moe.bank.up, &moe.bank.down)
11487 {
11488 if t > 1 && verify_mt_on() && sel_gufuse_on() && hidden % 32 == 0 && ff % 4 == 0 {
11494 if let Some((sel, w_dev, tokm)) = dev_route.take() {
11500 let tokm =
11501 tokm.ok_or("moe_forward: device route at t > 1 without a tok map")?;
11502 let out = prof_section(e, "moe.sel_grouped", || {
11503 let mut out = ws.take_f32(e, "moe.out", t * hidden, 0)?;
11504 let s_total = t * selected;
11505 let mut act = ws.take_f32(e, "moe.act", s_total * ff, 0)?;
11506 launch_nvfp4_sel_gu_silu(
11507 e,
11508 (gc, gs, gm),
11509 (uc, us, um),
11510 Some(&sel),
11511 0,
11512 s_total,
11513 mixed,
11514 &mut act,
11515 hidden,
11516 ff,
11517 Some((&tokm, hidden)),
11518 )?;
11519 let mut partial = ws.take_f32(e, "moe.partial", s_total * hidden, 0)?;
11520 launch_nvfp4_sel_matvec(
11521 e,
11522 dc,
11523 ds,
11524 dm,
11525 &sel,
11526 &act,
11527 &mut partial,
11528 s_total,
11529 ff,
11530 hidden,
11531 ff,
11532 )?;
11533 for tok in 0..t {
11534 launch_axpy_rows_seq_at(
11535 e,
11536 &partial,
11537 tok * selected,
11538 &w_dev,
11539 tok * selected,
11540 &mut out,
11541 tok,
11542 hidden,
11543 selected,
11544 )?;
11545 }
11546 ws.put_i32("moe.sel", sel);
11547 ws.put_i32("moe.tok", tokm);
11548 ws.put_f32("moe.w", w_dev);
11549 ws.put_f32("moe.act", act);
11550 ws.put_f32("moe.partial", partial);
11551 Ok(out)
11552 })?;
11553 return self.moe_shared_tail(e, ws, moe, mixed, out, t);
11554 }
11555 let out = prof_section(e, "moe.sel_grouped", || {
11556 let mut out = ws.take_f32(e, "moe.out", t * hidden, 0)?;
11557 const SLOT_CAP: usize = 8192;
11567 let tok_step = (SLOT_CAP / selected.max(1)).max(1);
11568 let mut tok0 = 0usize;
11569 while tok0 < t {
11570 let tok_n = tok_step.min(t - tok0);
11571 let batch = &routes[tok0..tok0 + tok_n];
11572 let mut sel_all: Vec<i32> = Vec::with_capacity(tok_n * selected);
11573 let mut w_all: Vec<f32> = Vec::with_capacity(tok_n * selected);
11574 let mut tok_all: Vec<i32> = Vec::with_capacity(tok_n * selected);
11575 let mut ranges: Vec<(usize, usize)> = Vec::with_capacity(tok_n);
11576 for (i, route) in batch.iter().enumerate() {
11577 ranges.push((sel_all.len(), route.len()));
11578 for &(eid, wgt) in route {
11579 sel_all.push(eid as i32);
11580 w_all.push(wgt);
11581 tok_all.push((tok0 + i) as i32);
11586 }
11587 }
11588 let s_total = sel_all.len();
11589 let sel = ws.take_i32(e, "moe.sel", &sel_all, 0)?;
11590 let w_dev = ws.take_f32_h2d(e, "moe.w", &w_all, 0)?;
11591 let tokm = ws.take_i32(e, "moe.tok", &tok_all, 0)?;
11592 let mut act = ws.take_f32(e, "moe.act", s_total * ff, 0)?;
11593 launch_nvfp4_sel_gu_silu(
11594 e,
11595 (gc, gs, gm),
11596 (uc, us, um),
11597 Some(&sel),
11598 0,
11599 s_total,
11600 mixed,
11601 &mut act,
11602 hidden,
11603 ff,
11604 Some((&tokm, hidden)),
11605 )?;
11606 let mut partial = ws.take_f32(e, "moe.partial", s_total * hidden, 0)?;
11607 launch_nvfp4_sel_matvec(
11608 e,
11609 dc,
11610 ds,
11611 dm,
11612 &sel,
11613 &act,
11614 &mut partial,
11615 s_total,
11616 ff,
11617 hidden,
11618 ff,
11619 )?;
11620 for (i, &(start, len)) in ranges.iter().enumerate() {
11621 launch_axpy_rows_seq_at(
11622 e,
11623 &partial,
11624 start,
11625 &w_dev,
11626 start,
11627 &mut out,
11628 tok0 + i,
11629 hidden,
11630 len,
11631 )?;
11632 }
11633 ws.put_i32("moe.sel", sel);
11634 ws.put_i32("moe.tok", tokm);
11635 ws.put_f32("moe.w", w_dev);
11636 ws.put_f32("moe.act", act);
11637 ws.put_f32("moe.partial", partial);
11638 tok0 += tok_n;
11639 }
11640 Ok(out)
11641 })?;
11642 return self.moe_shared_tail(e, ws, moe, mixed, out, t);
11643 }
11644 if let Some((sel, w_dev, _)) = dev_route.take() {
11648 let out = prof_section(e, "moe.sel_grouped", || {
11649 let mut out = ws.take_f32(e, "moe.out", hidden, 0)?;
11650 let mut act = ws.take_f32(e, "moe.act", selected * ff, 0)?;
11651 if sel_gufuse_on() && hidden % 32 == 0 && ff % 4 == 0 {
11652 launch_nvfp4_sel_gu_silu(
11653 e,
11654 (gc, gs, gm),
11655 (uc, us, um),
11656 Some(&sel),
11657 0,
11658 selected,
11659 mixed,
11660 &mut act,
11661 hidden,
11662 ff,
11663 None,
11664 )?;
11665 } else {
11666 let mut yg = ws.take_f32(e, "moe.yg", selected * ff, 0)?;
11667 let mut yu = ws.take_f32(e, "moe.yu", selected * ff, 0)?;
11668 launch_nvfp4_sel_matvec(
11669 e, gc, gs, gm, &sel, mixed, &mut yg, selected, hidden, ff, 0,
11670 )?;
11671 launch_nvfp4_sel_matvec(
11672 e, uc, us, um, &sel, mixed, &mut yu, selected, hidden, ff, 0,
11673 )?;
11674 e.silu_mul(&yg, &yu, &mut act, selected * ff)?;
11675 ws.put_f32("moe.yg", yg);
11676 ws.put_f32("moe.yu", yu);
11677 }
11678 let mut partial = ws.take_f32(e, "moe.partial", selected * hidden, 0)?;
11679 launch_nvfp4_sel_matvec(
11680 e,
11681 dc,
11682 ds,
11683 dm,
11684 &sel,
11685 &act,
11686 &mut partial,
11687 selected,
11688 ff,
11689 hidden,
11690 ff,
11691 )?;
11692 e.axpy_rows_seq_into(&partial, &w_dev, &mut out, hidden, selected)?;
11693 ws.put_i32("moe.sel", sel);
11694 ws.put_f32("moe.w", w_dev);
11695 ws.put_f32("moe.act", act);
11696 ws.put_f32("moe.partial", partial);
11697 Ok(out)
11698 })?;
11699 return self.moe_shared_tail(e, ws, moe, mixed, out, t);
11700 }
11701 let out = prof_section(e, "moe.sel_grouped", || {
11702 let mut out = ws.take_f32(e, "moe.out", t * hidden, 0)?;
11703 for (tok, route) in routes.iter().enumerate() {
11704 let n_sel = route.len();
11705 let sel_host: Vec<i32> = route.iter().map(|&(x, _)| x as i32).collect();
11706 let w_host: Vec<f32> = route.iter().map(|&(_, w)| w).collect();
11707 let sel = ws.take_i32(e, "moe.sel", &sel_host, 0)?;
11708 let w_dev = ws.take_f32_h2d(e, "moe.w", &w_host, 0)?;
11709 let x_tok = if t == 1 {
11714 None
11715 } else {
11716 let mut x = ws.take_f32(e, "moe.x", hidden, 0)?;
11717 e.copy_range_into(&mut x, 0, mixed, tok * hidden, hidden)?;
11718 Some(x)
11719 };
11720 let x_ref = x_tok.as_ref().unwrap_or(mixed);
11721 let mut act = ws.take_f32(e, "moe.act", n_sel * ff, 0)?;
11722 if sel_gufuse_on() && hidden % 32 == 0 && ff % 4 == 0 {
11725 launch_nvfp4_sel_gu_silu(
11726 e,
11727 (gc, gs, gm),
11728 (uc, us, um),
11729 Some(&sel),
11730 0,
11731 n_sel,
11732 x_ref,
11733 &mut act,
11734 hidden,
11735 ff,
11736 None,
11737 )?;
11738 } else {
11739 let mut yg = ws.take_f32(e, "moe.yg", n_sel * ff, 0)?;
11740 let mut yu = ws.take_f32(e, "moe.yu", n_sel * ff, 0)?;
11741 launch_nvfp4_sel_matvec(
11742 e, gc, gs, gm, &sel, x_ref, &mut yg, n_sel, hidden, ff, 0,
11743 )?;
11744 launch_nvfp4_sel_matvec(
11745 e, uc, us, um, &sel, x_ref, &mut yu, n_sel, hidden, ff, 0,
11746 )?;
11747 e.silu_mul(&yg, &yu, &mut act, n_sel * ff)?;
11748 ws.put_f32("moe.yg", yg);
11749 ws.put_f32("moe.yu", yu);
11750 }
11751 let mut partial = ws.take_f32(e, "moe.partial", n_sel * hidden, 0)?;
11752 launch_nvfp4_sel_matvec(
11753 e,
11754 dc,
11755 ds,
11756 dm,
11757 &sel,
11758 &act,
11759 &mut partial,
11760 n_sel,
11761 ff,
11762 hidden,
11763 ff,
11764 )?;
11765 if t == 1 {
11768 e.axpy_rows_seq_into(&partial, &w_dev, &mut out, hidden, n_sel)?;
11769 } else {
11770 let mut row = ws.take_f32(e, "moe.row", hidden, 0)?;
11771 e.axpy_rows_seq_into(&partial, &w_dev, &mut row, hidden, n_sel)?;
11772 e.copy_range_into(&mut out, tok * hidden, &row, 0, hidden)?;
11773 ws.put_f32("moe.row", row);
11774 }
11775 ws.put_i32("moe.sel", sel);
11776 ws.put_f32("moe.w", w_dev);
11777 ws.put_f32("moe.act", act);
11778 ws.put_f32("moe.partial", partial);
11779 if let Some(x) = x_tok {
11780 ws.put_f32("moe.x", x);
11781 }
11782 }
11783 Ok(out)
11784 })?;
11785 return self.moe_shared_tail(e, ws, moe, mixed, out, t);
11786 }
11787 if let (BankHalf::DeviceBf16(gb), BankHalf::DeviceBf16(ub), BankHalf::DeviceBf16(db)) =
11792 (&moe.bank.gate, &moe.bank.up, &moe.bank.down)
11793 {
11794 if let Some((sel, w_dev, _)) = dev_route.take() {
11801 let out = prof_section(e, "moe.sel_bf16", || {
11802 let mut out = ws.take_f32(e, "moe.out", t * hidden, 0)?;
11803 for tok in 0..t {
11804 let mut yg = ws.take_f32(e, "moe.yg", selected * ff, 0)?;
11805 let mut yu = ws.take_f32(e, "moe.yu", selected * ff, 0)?;
11806 launch_qmatvec_bf16w_sel(
11807 e,
11808 gb,
11809 &sel,
11810 tok * selected,
11811 mixed,
11812 tok * hidden,
11813 0,
11814 &mut yg,
11815 selected,
11816 hidden,
11817 ff,
11818 )?;
11819 launch_qmatvec_bf16w_sel(
11820 e,
11821 ub,
11822 &sel,
11823 tok * selected,
11824 mixed,
11825 tok * hidden,
11826 0,
11827 &mut yu,
11828 selected,
11829 hidden,
11830 ff,
11831 )?;
11832 let mut act = ws.take_f32(e, "moe.act", selected * ff, 0)?;
11833 e.silu_mul(&yg, &yu, &mut act, selected * ff)?;
11834 let mut partial =
11835 ws.take_f32(e, "moe.partial", selected * hidden, 0)?;
11836 launch_qmatvec_bf16w_sel(
11837 e,
11838 db,
11839 &sel,
11840 tok * selected,
11841 &act,
11842 0,
11843 ff,
11844 &mut partial,
11845 selected,
11846 ff,
11847 hidden,
11848 )?;
11849 launch_axpy_rows_seq_at(
11850 e,
11851 &partial,
11852 0,
11853 &w_dev,
11854 tok * selected,
11855 &mut out,
11856 tok,
11857 hidden,
11858 selected,
11859 )?;
11860 ws.put_f32("moe.yg", yg);
11861 ws.put_f32("moe.yu", yu);
11862 ws.put_f32("moe.act", act);
11863 ws.put_f32("moe.partial", partial);
11864 }
11865 ws.put_i32("moe.sel", sel);
11866 ws.put_f32("moe.w", w_dev);
11867 Ok(out)
11868 })?;
11869 return self.moe_shared_tail(e, ws, moe, mixed, out, t);
11870 }
11871 let out = prof_section(e, "moe.sel_bf16", || {
11872 let mut out = ws.take_f32(e, "moe.out", t * hidden, 0)?;
11873 for (tok, route) in routes.iter().enumerate() {
11874 let n_sel = route.len();
11875 let w_host: Vec<f32> = route.iter().map(|&(_, w)| w).collect();
11876 let w_dev = ws.take_f32_h2d(e, "moe.w", &w_host, 0)?;
11877 let mut yg = ws.take_f32(e, "moe.yg", n_sel * ff, 0)?;
11878 let mut yu = ws.take_f32(e, "moe.yu", n_sel * ff, 0)?;
11879 for (slot, &(eid, _)) in route.iter().enumerate() {
11880 launch_qmatvec_bf16w_off_into(
11881 e,
11882 gb,
11883 eid * ff,
11884 mixed,
11885 tok * hidden,
11886 &mut yg,
11887 slot * ff,
11888 hidden,
11889 ff,
11890 )?;
11891 launch_qmatvec_bf16w_off_into(
11892 e,
11893 ub,
11894 eid * ff,
11895 mixed,
11896 tok * hidden,
11897 &mut yu,
11898 slot * ff,
11899 hidden,
11900 ff,
11901 )?;
11902 }
11903 let mut act = ws.take_f32(e, "moe.act", n_sel * ff, 0)?;
11904 e.silu_mul(&yg, &yu, &mut act, n_sel * ff)?;
11905 let mut partial = ws.take_f32(e, "moe.partial", n_sel * hidden, 0)?;
11906 for (slot, &(eid, _)) in route.iter().enumerate() {
11907 launch_qmatvec_bf16w_off_into(
11908 e,
11909 db,
11910 eid * hidden,
11911 &act,
11912 slot * ff,
11913 &mut partial,
11914 slot * hidden,
11915 ff,
11916 hidden,
11917 )?;
11918 }
11919 let mut row = ws.take_f32(e, "moe.row", hidden, 0)?;
11920 e.axpy_rows_seq_into(&partial, &w_dev, &mut row, hidden, n_sel)?;
11921 e.copy_range_into(&mut out, tok * hidden, &row, 0, hidden)?;
11922 ws.put_f32("moe.row", row);
11923 ws.put_f32("moe.w", w_dev);
11924 ws.put_f32("moe.yg", yg);
11925 ws.put_f32("moe.yu", yu);
11926 ws.put_f32("moe.act", act);
11927 ws.put_f32("moe.partial", partial);
11928 }
11929 Ok(out)
11930 })?;
11931 return self.moe_shared_tail(e, ws, moe, mixed, out, t);
11932 }
11933 }
11934
11935 if dev_route.is_some() {
11939 return Err(
11940 "moe_forward: device route left unconsumed (engage guard drifted from the \
11941 dispatch arms)"
11942 .into(),
11943 );
11944 }
11945 let mut by_expert: Vec<Vec<(i32, i32, f32)>> = vec![Vec::new(); experts];
11947 for (token, token_routes) in routes.iter().enumerate() {
11948 for (slot, &(expert, weight)) in token_routes.iter().enumerate() {
11949 by_expert[expert].push((token as i32, slot as i32, weight));
11950 }
11951 }
11952 let mut slots = e.zeros(t * selected * hidden)?;
11953 let mut wbuf = e.zeros(t * selected)?;
11954 for (expert, entries) in by_expert.iter().enumerate() {
11955 if entries.is_empty() {
11956 continue;
11957 }
11958 let m_e = entries.len();
11959 let (tok_dev, slot_dev, w_dev, xg) = prof_section(e, "moe.idx_gather", || {
11960 let tok_idx: Vec<i32> = entries.iter().map(|&(tok, _, _)| tok).collect();
11961 let slot_idx: Vec<i32> = entries.iter().map(|&(_, slot, _)| slot).collect();
11962 let weights: Vec<f32> = entries.iter().map(|&(_, _, w)| w).collect();
11963 let tok_dev = e.htod_i32(&tok_idx)?;
11964 let slot_dev = e.htod_i32(&slot_idx)?;
11965 let w_dev = e.htod(&weights)?;
11966 let mut xg = e.uninit(m_e * hidden)?;
11967 e.gather_rows(mixed, &tok_dev, &mut xg, hidden, m_e)?;
11968 Ok((tok_dev, slot_dev, w_dev, xg))
11969 })?;
11970 let resolve = |half: &BankHalf,
11973 out_f: usize,
11974 in_f: usize|
11975 -> Res<(Option<CudaSlice<f32>>, usize)> {
11976 match half {
11977 BankHalf::F32(_) => Ok((None, expert * out_f * in_f)),
11978 BankHalf::Nvfp4 {
11979 codes,
11980 scales,
11981 macros,
11982 ..
11983 } => Ok((
11984 Some(dequant_nvfp4_expert_f32(
11985 e,
11986 codes,
11987 scales,
11988 macros[expert],
11989 expert,
11990 out_f,
11991 in_f,
11992 )?),
11993 0,
11994 )),
11995 BankHalf::HostBf16(bytes) => {
11998 let row_bytes = out_f * in_f * 2;
11999 let dev =
12000 e.htod_bytes(&bytes[expert * row_bytes..(expert + 1) * row_bytes])?;
12001 Ok((
12002 Some(e.bf16_to_f32(&dev.slice(0..row_bytes), out_f * in_f)?),
12003 0,
12004 ))
12005 }
12006 BankHalf::DeviceBf16(bytes) => {
12010 let row_bytes = out_f * in_f * 2;
12011 let view = bytes.slice(expert * row_bytes..(expert + 1) * row_bytes);
12012 Ok((Some(e.bf16_to_f32(&view, out_f * in_f)?), 0))
12013 }
12014 }
12015 };
12016 let ((gate_owned, gate_base), (up_owned, up_base), (down_owned, down_base)) =
12017 prof_section(e, "moe.dequant", || {
12018 Ok((
12019 resolve(&moe.bank.gate, ff, hidden)?,
12020 resolve(&moe.bank.up, ff, hidden)?,
12021 resolve(&moe.bank.down, hidden, ff)?,
12022 ))
12023 })?;
12024 let gate_view = match (&moe.bank.gate, &gate_owned) {
12025 (_, Some(owned)) => owned.slice(0..ff * hidden),
12026 (BankHalf::F32(bank), None) => bank.slice(gate_base..gate_base + ff * hidden),
12027 (
12028 BankHalf::Nvfp4 { .. } | BankHalf::HostBf16(_) | BankHalf::DeviceBf16(_),
12029 None,
12030 ) => {
12031 unreachable!("quantized/host/device-bf16 halves always resolve owned")
12032 }
12033 };
12034 let up_view = match (&moe.bank.up, &up_owned) {
12035 (_, Some(owned)) => owned.slice(0..ff * hidden),
12036 (BankHalf::F32(bank), None) => bank.slice(up_base..up_base + ff * hidden),
12037 (
12038 BankHalf::Nvfp4 { .. } | BankHalf::HostBf16(_) | BankHalf::DeviceBf16(_),
12039 None,
12040 ) => {
12041 unreachable!("quantized/host/device-bf16 halves always resolve owned")
12042 }
12043 };
12044 let down_view = match (&moe.bank.down, &down_owned) {
12045 (_, Some(owned)) => owned.slice(0..hidden * ff),
12046 (BankHalf::F32(bank), None) => bank.slice(down_base..down_base + hidden * ff),
12047 (
12048 BankHalf::Nvfp4 { .. } | BankHalf::HostBf16(_) | BankHalf::DeviceBf16(_),
12049 None,
12050 ) => {
12051 unreachable!("quantized/host/device-bf16 halves always resolve owned")
12052 }
12053 };
12054 prof_section(e, "moe.expert_gemms", || {
12055 let down_out =
12056 run_routed_expert(e, &xg, &gate_view, &up_view, &down_view, m_e, hidden, ff)?;
12057 e.scatter_slot(
12058 &down_out, &tok_dev, &slot_dev, &w_dev, &mut slots, &mut wbuf, hidden,
12059 selected, m_e,
12060 )
12061 })?;
12062 }
12063 let out = prof_section(e, "moe.reduce", || {
12064 let mut out = e.zeros(t * hidden)?;
12065 e.reduce_slots(&slots, &wbuf, &mut out, hidden, selected, t)?;
12066 Ok(out)
12067 })?;
12068 self.moe_shared_tail(e, ws, moe, mixed, out, t)
12069 }
12070
12071 fn moe_shared_tail(
12074 &self,
12075 e: &Engine,
12076 ws: &mut StepPool,
12077 moe: &MoeW,
12078 mixed: &CudaSlice<f32>,
12079 mut out: CudaSlice<f32>,
12080 t: usize,
12081 ) -> Res<CudaSlice<f32>> {
12082 let hidden = self.hidden;
12083 let sff = moe
12084 .plan
12085 .shared
12086 .as_ref()
12087 .map(|s| s.intermediate_size as usize)
12088 .unwrap_or(0);
12089 if sff > 0 {
12090 prof_section(e, "moe.shared", || {
12091 let none: Option<CudaSlice<u8>> = None;
12094 let (gu, db) = if micro_shexp_on() {
12095 (&moe.shared_gu_b16, &moe.shared_down_b16)
12096 } else {
12097 (&none, &none)
12098 };
12099 let mut gate = ws.take_f32(e, "moe.sh_gate", t * sff, 0)?;
12100 let mut up = ws.take_f32(e, "moe.sh_up", t * sff, 0)?;
12101 if let (true, Some(stack)) =
12104 (t == 1 && proj_stack_on() && trunk_bf16_on(), gu.as_ref())
12105 {
12106 launch_qmatvec_bf16w_multi4(
12107 e,
12108 stack,
12109 mixed,
12110 &[(&gate, sff), (&up, sff)],
12111 hidden,
12112 )?;
12113 } else {
12114 linear_trunk_stacked_into(
12115 e,
12116 &moe.shared_gate,
12117 gu,
12118 0,
12119 mixed,
12120 &mut gate,
12121 t,
12122 hidden,
12123 sff,
12124 )?;
12125 linear_trunk_stacked_into(
12126 e,
12127 &moe.shared_up,
12128 gu,
12129 sff,
12130 mixed,
12131 &mut up,
12132 t,
12133 hidden,
12134 sff,
12135 )?;
12136 }
12137 let mut act = ws.take_f32(e, "moe.sh_act", t * sff, 0)?;
12138 e.silu_mul(&gate, &up, &mut act, t * sff)?;
12139 let mut shared = ws.take_f32(e, "moe.sh_down", t * hidden, 0)?;
12140 linear_trunk_into(e, &moe.shared_down, db, &act, &mut shared, t, sff, hidden)?;
12141 if let Some(input_gate) = moe.shared_input_gate.as_ref() {
12142 let mut g = ws.take_f32(e, "moe.g", t, 0)?;
12145 e.sigmoid_dot_rows_into(mixed, input_gate, &mut g, hidden, t)?;
12146 e.add_scaled_rows(&shared, &g, &mut out, hidden, t)?;
12147 ws.put_f32("moe.g", g);
12148 } else {
12149 let mut view = out.slice_mut(0..t * hidden);
12150 e.axpy_into(&shared, 1.0, &mut view, t * hidden)?;
12151 }
12152 ws.put_f32("moe.sh_gate", gate);
12153 ws.put_f32("moe.sh_up", up);
12154 ws.put_f32("moe.sh_act", act);
12155 ws.put_f32("moe.sh_down", shared);
12156 Ok(())
12157 })?;
12158 }
12159 Ok(out)
12160 }
12161
12162 #[allow(clippy::too_many_arguments)]
12166 #[allow(clippy::too_many_arguments)]
12167 fn ple_block(
12168 &self,
12169 e: &Engine,
12170 layer: &LayerW,
12171 ple: &PleW,
12172 table: &NgramTable,
12173 ple_state: &mut PleState,
12174 planes: &mut [CudaSlice<f32>],
12175 tokens: &[u32],
12176 t: usize,
12177 exact: bool,
12181 mut stash: Option<&mut PleStash>,
12182 ) -> Res<()> {
12183 let hidden = self.hidden;
12184 let streams = self.streams;
12185 let plan = &ple.plan;
12186 let heads = plan.ngram_heads as usize;
12187 let head_dim = plan.head_embed_dim as usize;
12188 let embed_dim = plan.embed_dim as usize;
12189 let kernel = plan.conv_kernel as usize;
12190 let max_ngram = plan.max_ngram as usize;
12191 let dilation = max_ngram;
12192 let pad = (kernel - 1) * dilation;
12193 let eps = layer.eps_attn;
12194
12195 let gathered = prof_section(e, "ple.host_ngram_gather", || {
12197 let total_heads = heads;
12198 let mut ids: Vec<i64> = Vec::new();
12202 if ple_cache_on() {
12203 host_ngram_ids_cached(
12204 &mut ple_state.ngram_ids,
12205 &mut ple_state.ngram_history,
12206 &mut ple_state.ngram_last_eos,
12207 tokens,
12208 &ple.multipliers,
12209 &ple.sizes,
12210 &ple.offsets,
12211 max_ngram,
12212 heads / (max_ngram - 1),
12213 plan.eos_token_id,
12214 );
12215 if ple_cache_audit_on() {
12216 let twin = host_ngram_ids(
12217 tokens,
12218 &ple.multipliers,
12219 &ple.sizes,
12220 &ple.offsets,
12221 max_ngram,
12222 heads / (max_ngram - 1),
12223 plan.eos_token_id,
12224 );
12225 let from = (tokens.len() - t) * total_heads;
12226 let mism = twin[from..]
12227 .iter()
12228 .zip(&ple_state.ngram_ids[from..])
12229 .filter(|(a, b)| a != b)
12230 .count() as u64;
12231 PLE_CACHE_AUDIT_ROWS.fetch_add(t as u64, std::sync::atomic::Ordering::Relaxed);
12232 PLE_CACHE_AUDIT_MISMATCH.fetch_add(mism, std::sync::atomic::Ordering::Relaxed);
12233 PLE_CACHE_AUDIT_MAX_FILL
12234 .fetch_max(tokens.len() as u64, std::sync::atomic::Ordering::Relaxed);
12235 if mism > 0 {
12236 return Err(format!(
12237 "plecache audit: {mism} cached n-gram ids differ from the full twin \
12238 at history {} (t={t})",
12239 tokens.len()
12240 )
12241 .into());
12242 }
12243 }
12244 } else {
12245 ids = host_ngram_ids(
12246 tokens,
12247 &ple.multipliers,
12248 &ple.sizes,
12249 &ple.offsets,
12250 max_ngram,
12251 heads / (max_ngram - 1),
12252 plan.eos_token_id,
12253 );
12254 }
12255 let all_ids: &[i64] = if ple_cache_on() {
12256 &ple_state.ngram_ids
12257 } else {
12258 &ids
12259 };
12260 let chunk_ids = &all_ids[(tokens.len() - t) * total_heads..];
12261 let table_rows = table.rows(head_dim);
12262 let mut gathered = vec![0.0f32; t * embed_dim];
12263 for token in 0..t {
12264 for head in 0..heads {
12265 let id = chunk_ids[token * total_heads + head];
12266 if id < 0 || id as usize >= table_rows {
12267 return Err("qwen4exp_gpu: n-gram id outside the embedding table".into());
12268 }
12269 table.gather_into(
12270 id as usize,
12271 head_dim,
12272 &mut gathered[token * embed_dim + head * head_dim
12273 ..token * embed_dim + (head + 1) * head_dim],
12274 );
12275 }
12276 }
12277 Ok(gathered)
12278 })?;
12279 let emb = prof_section(e, "ple.h2d", || e.htod(&gathered))?;
12280
12281 let lin_rows = |x: &CudaSlice<f32>,
12284 w: &CudaSlice<f32>,
12285 in_f: usize,
12286 out_f: usize|
12287 -> Res<CudaSlice<f32>> {
12288 let mut out = e.uninit(t * out_f)?;
12289 if exact && t > 1 {
12290 let wv = w.slice(0..w.len());
12291 for tok in 0..t {
12292 let xv = x.slice(tok * in_f..(tok + 1) * in_f);
12293 let mut yv = out.slice_mut(tok * out_f..(tok + 1) * out_f);
12294 e.linear_device_into(&xv, &wv, &mut yv, 1, in_f, out_f)?;
12295 }
12296 } else {
12297 e.linear_device_into(x, w, &mut out, t, in_f, out_f)?;
12298 }
12299 Ok(out)
12300 };
12301 let (value, mut dots_host) = prof_section(e, "ple.key_gate", || {
12302 let value = lin_rows(&emb, &ple.value_proj, embed_dim, hidden)?;
12303 let ones = e.htod(&vec![1.0f32; hidden])?;
12304 let mut dots_host = vec![0.0f32; streams * t];
12305 for s in 0..streams {
12306 let key = lin_rows(&emb, &ple.key_proj[s], embed_dim, hidden)?;
12307 let mut key_normed = e.uninit(t * hidden)?;
12308 e.rms_norm(&key, &ple.norm_key[s], &mut key_normed, hidden, t, eps)?;
12309 let mut query = e.uninit(t * hidden)?;
12310 e.rms_norm(&planes[s], &ple.norm_query[s], &mut query, hidden, t, eps)?;
12311 let mut prod = e.uninit(t * hidden)?;
12312 e.mul(&key_normed, &query, &mut prod, t * hidden)?;
12313 let dots = lin_rows(&prod, &ones, hidden, 1)?;
12314 dots_host[s * t..(s + 1) * t].copy_from_slice(&e.dtoh(&dots)?);
12315 }
12316 Ok((value, dots_host))
12317 })?;
12318 for dot in dots_host.iter_mut() {
12320 let gate = *dot / (hidden as f32).sqrt();
12321 let magnitude = gate.abs().max(1e-6).sqrt();
12322 let signed = if gate > 0.0 {
12323 magnitude
12324 } else if gate < 0.0 {
12325 -magnitude
12326 } else {
12327 0.0
12328 };
12329 *dot = host_sigmoid(signed);
12330 }
12331
12332 prof_section(e, "ple.conv_write", || {
12333 for s in 0..streams {
12334 let g = e.htod(&dots_host[s * t..(s + 1) * t])?;
12335 let mut gated = e.zeros(t * hidden)?;
12336 e.add_scaled_rows(&value, &g, &mut gated, hidden, t)?;
12337 let mut normed = e.uninit(t * hidden)?;
12338 e.rms_norm(&gated, &ple.norm_conv[s], &mut normed, hidden, t, eps)?;
12339 if let Some(st) = stash.as_deref_mut() {
12342 e.copy_range_into(
12343 &mut st.hist_pre[s],
12344 0,
12345 &ple_state.conv_hist[s],
12346 0,
12347 pad * hidden,
12348 )?;
12349 e.copy_range_into(&mut st.normed_rows[s], 0, &normed, 0, t * hidden)?;
12350 }
12351 launch_dwconv(
12353 e,
12354 &normed,
12355 &ple_state.conv_hist[s],
12356 &ple.conv_w[s],
12357 &mut gated,
12358 t,
12359 pad,
12360 hidden,
12361 kernel,
12362 dilation,
12363 2,
12364 )?;
12365 let hist = &mut ple_state.conv_hist[s];
12367 if t >= pad {
12368 e.copy_range_into(hist, 0, &normed, (t - pad) * hidden, pad * hidden)?;
12369 } else {
12370 let keep = pad - t;
12371 let mut tmp = e.uninit(keep * hidden)?;
12372 e.copy_range_into(&mut tmp, 0, hist, t * hidden, keep * hidden)?;
12373 e.copy_range_into(hist, 0, &tmp, 0, keep * hidden)?;
12374 e.copy_range_into(hist, keep * hidden, &normed, 0, t * hidden)?;
12375 }
12376 let mut view = planes[s].slice_mut(0..t * hidden);
12378 e.axpy_into(&gated, 1.0, &mut view, t * hidden)?;
12379 }
12380 Ok(())
12381 })
12382 }
12383}
12384
12385pub struct MtpDraftState {
12393 mixer: MixerState,
12394 rows: usize,
12396 pub committed: usize,
12399 capacity: usize,
12400 ws: StepPool,
12401}
12402
12403impl MtpDraftState {
12404 pub fn rows(&self) -> usize {
12405 self.rows
12406 }
12407}
12408
12409#[derive(Clone, Copy)]
12415enum DraftTokSrc<'a> {
12416 Host(&'a [u32]),
12417 HostDev(&'a [u32]),
12418 DevSlot(&'a CudaSlice<u32>, usize),
12419}
12420
12421impl Qwen4ExpGpu {
12422 pub fn has_mtp(&self) -> bool {
12423 self.mtp.is_some()
12424 }
12425
12426 pub fn mtp_on_dev1(&self) -> bool {
12429 self.mtp_dev1.is_some()
12430 }
12431
12432 fn check_draft_engine(&self, e: &Engine) -> Res<()> {
12436 if let Some(d) = self.mtp_dev1.as_ref() {
12437 if e.ctx().ordinal() != d.dev {
12438 return Err(format!(
12439 "qwen4exp_gpu: the draft lives on device {} (card-1 placement); \
12440 this call presented device {}",
12441 d.dev,
12442 e.ctx().ordinal()
12443 )
12444 .into());
12445 }
12446 }
12447 Ok(())
12448 }
12449
12450 pub fn mtp_state(&self, e: &Engine, capacity: usize) -> Res<MtpDraftState> {
12453 self.check_draft_engine(e)?;
12454 let mtp = self
12455 .mtp
12456 .as_ref()
12457 .ok_or("qwen4exp_gpu: no MTP block loaded (LoadOptions::load_mtp)")?;
12458 let MixerW::Qsa(qsa) = &mtp.layer.mixer else {
12459 return Err("qwen4exp_gpu: MTP mixer is not QSA".into());
12460 };
12461 let kv_width = qsa.attn.kv_heads as usize * qsa.attn.key_head_dim as usize;
12462 let v_width = qsa.attn.kv_heads as usize * qsa.attn.value_head_dim as usize;
12463 let kv = if kv_quant_on() {
12466 QsaKvStore::Q8Q5 {
12467 k: e.alloc_u8(capacity * q8_row_bytes(kv_width))?,
12468 v: e.alloc_u8(capacity * q5_row_bytes(v_width))?,
12469 }
12470 } else {
12471 QsaKvStore::F32 {
12472 k: e.zeros(capacity * kv_width)?,
12473 v: e.zeros(capacity * v_width)?,
12474 }
12475 };
12476 Ok(MtpDraftState {
12477 mixer: MixerState::Qsa {
12478 kv,
12479 raw_keys: IdxRawCache::new(idxq_mode()),
12480 pooled_keys: Vec::new(),
12481 pooled_dev: None,
12482 pooled_dev_rows: 0,
12483 raw_dev: None,
12484 raw_dev_rows: 0,
12485 idx_audit: None,
12486 },
12487 rows: 0,
12488 committed: 0,
12489 capacity,
12490 ws: StepPool::default(),
12491 })
12492 }
12493
12494 pub fn mtp_rewind(&self, dstate: &mut MtpDraftState, rows: usize) -> Res<()> {
12497 if rows > dstate.rows {
12498 return Err("qwen4exp_gpu: mtp_rewind past the cache".into());
12499 }
12500 let mtp = self.mtp.as_ref().ok_or("qwen4exp_gpu: no MTP block")?;
12501 let MixerW::Qsa(qsa) = &mtp.layer.mixer else {
12502 return Err("qwen4exp_gpu: MTP mixer is not QSA".into());
12503 };
12504 let MixerState::Qsa {
12505 raw_keys,
12506 pooled_keys,
12507 pooled_dev_rows,
12508 raw_dev_rows,
12509 ..
12510 } = &mut dstate.mixer
12511 else {
12512 return Err("qwen4exp_gpu: MTP state is not QSA".into());
12513 };
12514 let idx_dim = qsa.overlay.head_dim as usize;
12515 raw_keys.truncate_rows(rows, idx_dim);
12516 let block = qsa.overlay.block_size as usize;
12517 pooled_keys.truncate((rows / block) * idx_dim);
12518 *pooled_dev_rows = (*pooled_dev_rows).min(pooled_keys.len() / idx_dim);
12522 *raw_dev_rows = (*raw_dev_rows).min(rows);
12525 dstate.rows = rows;
12526 dstate.committed = dstate.committed.min(rows);
12527 Ok(())
12528 }
12529
12530 #[allow(clippy::too_many_arguments)]
12541 pub fn mtp_draft_forward(
12542 &self,
12543 e: &Engine,
12544 tokens: &[u32],
12545 hidden_wide: &CudaSlice<f32>,
12546 wide_off: usize,
12547 dstate: &mut MtpDraftState,
12548 pos_off: usize,
12549 logits_all: bool,
12553 ) -> Res<(CudaSlice<f32>, CudaSlice<f32>)> {
12554 self.mtp_draft_forward_impl(
12555 e,
12556 DraftTokSrc::Host(tokens),
12557 hidden_wide,
12558 wide_off,
12559 dstate,
12560 pos_off,
12561 logits_all,
12562 )
12563 }
12564
12565 fn mtp_draft_forward_devslot(
12569 &self,
12570 e: &Engine,
12571 toks: &CudaSlice<u32>,
12572 slot: usize,
12573 hidden_wide: &CudaSlice<f32>,
12574 wide_off: usize,
12575 dstate: &mut MtpDraftState,
12576 ) -> Res<(CudaSlice<f32>, CudaSlice<f32>)> {
12577 self.mtp_draft_forward_impl(
12578 e,
12579 DraftTokSrc::DevSlot(toks, slot),
12580 hidden_wide,
12581 wide_off,
12582 dstate,
12583 1,
12584 false,
12585 )
12586 }
12587
12588 fn mtp_draft_forward_spec(
12592 &self,
12593 e: &Engine,
12594 tokens: &[u32],
12595 dev_embed: bool,
12596 hidden_wide: &CudaSlice<f32>,
12597 wide_off: usize,
12598 dstate: &mut MtpDraftState,
12599 ) -> Res<(CudaSlice<f32>, CudaSlice<f32>)> {
12600 let src = if dev_embed {
12601 DraftTokSrc::HostDev(tokens)
12602 } else {
12603 DraftTokSrc::Host(tokens)
12604 };
12605 self.mtp_draft_forward_impl(e, src, hidden_wide, wide_off, dstate, 1, false)
12606 }
12607
12608 fn draft_consume_ring(
12615 &self,
12616 de: &Engine,
12617 tokens: &[u32],
12618 dev_embed: bool,
12619 seed: &CudaSlice<f32>,
12620 ring: usize,
12621 first_row: usize,
12622 dstate: &mut MtpDraftState,
12623 ) -> Res<(CudaSlice<f32>, CudaSlice<f32>, usize)> {
12624 let mut out: Option<(CudaSlice<f32>, CudaSlice<f32>, usize)> = None;
12625 let mut done = 0usize;
12626 while done < tokens.len() {
12627 let slot = (first_row + done) % ring;
12628 let len = (tokens.len() - done).min(ring - slot);
12629 let (l, c) = self.mtp_draft_forward_spec(
12630 de,
12631 &tokens[done..done + len],
12632 dev_embed,
12633 seed,
12634 slot,
12635 dstate,
12636 )?;
12637 if let Some((pl, pc, _)) = out.take() {
12638 self.mtp_recycle(dstate, pl, pc);
12639 }
12640 out = Some((l, c, len));
12641 done += len;
12642 }
12643 out.ok_or("qwen4exp_gpu: empty draft consume".into())
12644 }
12645
12646 #[allow(clippy::too_many_arguments)]
12647 fn mtp_draft_forward_impl(
12648 &self,
12649 e: &Engine,
12650 tok_src: DraftTokSrc<'_>,
12651 hidden_wide: &CudaSlice<f32>,
12652 wide_off: usize,
12653 dstate: &mut MtpDraftState,
12654 pos_off: usize,
12655 logits_all: bool,
12656 ) -> Res<(CudaSlice<f32>, CudaSlice<f32>)> {
12657 self.check_draft_engine(e)?;
12658 let mtp = self
12659 .mtp
12660 .as_ref()
12661 .ok_or("qwen4exp_gpu: no MTP block loaded (LoadOptions::load_mtp)")?;
12662 let t = match tok_src {
12663 DraftTokSrc::Host(tokens) | DraftTokSrc::HostDev(tokens) => tokens.len(),
12664 DraftTokSrc::DevSlot(..) => 1,
12665 };
12666 let hidden = self.hidden;
12667 let streams = self.streams;
12668 let wide = streams * hidden;
12669 if t == 0 {
12670 return Err("qwen4exp_gpu: empty draft input".into());
12671 }
12672 if dstate.rows + t > dstate.capacity {
12673 return Err("qwen4exp_gpu: draft state capacity exceeded".into());
12674 }
12675 if hidden_wide.len() < (wide_off + t) * wide {
12676 return Err("qwen4exp_gpu: draft hidden seed rows out of range".into());
12677 }
12678 let base = dstate.rows;
12679 let ws = &mut dstate.ws;
12680 let cap = dstate.capacity;
12681
12682 let mut planes = prof_section(e, "mtp.fuse", || {
12684 let emb = match tok_src {
12685 DraftTokSrc::Host(tokens) => {
12686 let mut embedded = vec![0.0f32; t * hidden];
12687 for (row, &token) in tokens.iter().enumerate() {
12688 let token = token as usize;
12689 if token >= self.vocab {
12690 return Err(
12691 format!("qwen4exp_gpu: draft token {token} out of range").into()
12692 );
12693 }
12694 embedded[row * hidden..(row + 1) * hidden].copy_from_slice(
12695 &self.embed_host[token * hidden..(token + 1) * hidden],
12696 );
12697 }
12698 ws.take_f32_h2d(e, "mtp.emb", &embedded, cap * hidden)?
12699 }
12700 DraftTokSrc::HostDev(tokens) => {
12701 let ce = self
12707 .chain_embed
12708 .as_ref()
12709 .filter(|ce| !ce.for_trim && ce.rows == self.vocab)
12710 .ok_or("qwen4exp_gpu: HostDev embed needs the full-vocab chain table")?;
12711 for &token in tokens {
12712 if token as usize >= self.vocab {
12713 return Err(
12714 format!("qwen4exp_gpu: draft token {token} out of range").into()
12715 );
12716 }
12717 }
12718 let tok_d = e.gpu.stream().clone_htod(tokens)?;
12719 let mut emb = ws.take_f32(e, "mtp.emb", t * hidden, cap * hidden)?;
12720 let tv = tok_d.slice(0..t);
12721 embed_gather_rows_into(
12722 e,
12723 &ce.table,
12724 &tv,
12725 &mut emb,
12726 t,
12727 hidden,
12728 ce.qt,
12729 ce.row_bytes,
12730 )?;
12731 emb
12732 }
12733 DraftTokSrc::DevSlot(toks, slot) => {
12734 let ce = self
12740 .chain_embed
12741 .as_ref()
12742 .ok_or("qwen4exp_gpu: deferred draft step without arm_spec_devchain")?;
12743 let mut emb = ws.take_f32(e, "mtp.emb", hidden, cap * hidden)?;
12744 let tv = toks.slice(slot..slot + 1);
12745 embed_gather_rows_into(
12746 e,
12747 &ce.table,
12748 &tv,
12749 &mut emb,
12750 1,
12751 hidden,
12752 ce.qt,
12753 ce.row_bytes,
12754 )?;
12755 emb
12756 }
12757 };
12758 let mut enorm = ws.take_f32(e, "mtp.enorm", t * hidden, 0)?;
12759 e.rms_norm(
12760 &emb,
12761 &mtp.pre_norm_embed,
12762 &mut enorm,
12763 hidden,
12764 t,
12765 mtp.eps_embed,
12766 )?;
12767 let mut evec = ws.take_f32(e, "mtp.evec", t * hidden, 0)?;
12768 linear_trunk_into(
12769 e,
12770 &mtp.fc_embed,
12771 &mtp.fc_embed_b16,
12772 &enorm,
12773 &mut evec,
12774 t,
12775 hidden,
12776 hidden,
12777 )?;
12778 let mut hin = ws.take_f32(e, "mtp.hin", t * wide, 0)?;
12781 e.copy_range_into(&mut hin, 0, hidden_wide, wide_off * wide, t * wide)?;
12782 let mut hnorm = ws.take_f32(e, "mtp.hnorm", t * wide, 0)?;
12783 e.rms_norm(
12784 &hin,
12785 &mtp.pre_norm_hidden,
12786 &mut hnorm,
12787 wide,
12788 t,
12789 mtp.eps_hidden,
12790 )?;
12791 let mut fused = ws.take_f32(e, "mtp.fused", t * wide, 0)?;
12794 linear_trunk_into(
12795 e,
12796 &mtp.fc_hidden,
12797 &mtp.fc_hidden_b16,
12798 &hnorm,
12799 &mut fused,
12800 t * streams,
12801 hidden,
12802 hidden,
12803 )?;
12804 let mut planes: Vec<CudaSlice<f32>> = Vec::with_capacity(streams);
12805 for s in 0..streams {
12806 let mut plane = ws.take_f32(e, PLANE_SLOTS[s], t * hidden, cap * hidden)?;
12807 for tok in 0..t {
12808 e.copy_range_into(
12809 &mut plane,
12810 tok * hidden,
12811 &fused,
12812 (tok * streams + s) * hidden,
12813 hidden,
12814 )?;
12815 }
12816 let mut view = plane.slice_mut(0..t * hidden);
12817 e.axpy_into(&evec, 1.0, &mut view, t * hidden)?;
12818 planes.push(plane);
12819 }
12820 ws.put_f32("mtp.emb", emb);
12821 ws.put_f32("mtp.enorm", enorm);
12822 ws.put_f32("mtp.evec", evec);
12823 ws.put_f32("mtp.hin", hin);
12824 ws.put_f32("mtp.hnorm", hnorm);
12825 ws.put_f32("mtp.fused", fused);
12826 Ok(planes)
12827 })?;
12828
12829 let ptr_vals: Vec<u64> = {
12830 let stream = e.gpu.stream();
12831 planes.iter().map(|p| p.device_ptr(&stream).0).collect()
12832 };
12833 let ptrs = ws.take_u64_h2d(e, "hc.ptrs", &ptr_vals, 0)?;
12834
12835 let layer = &mtp.layer;
12837 let (mixed, inject) = prof_section(e, "mtp.hyper.read", || {
12838 self.gate_read(
12839 e,
12840 ws,
12841 &ptrs,
12842 &layer.attn_gate,
12843 &planes,
12844 t,
12845 layer.eps_attn,
12846 false,
12847 )
12848 })?;
12849 let MixerW::Qsa(qsa) = &layer.mixer else {
12850 return Err("qwen4exp_gpu: MTP mixer is not QSA".into());
12851 };
12852 let block_out = prof_section(e, "mtp.qsa", || {
12853 self.qsa_forward(
12854 e,
12855 ws,
12856 layer,
12857 qsa,
12858 &mixed,
12859 &mut dstate.mixer,
12860 base,
12861 t,
12862 pos_off,
12863 false,
12864 )
12865 })?;
12866 ws.put_f32("hc.mixed", mixed);
12867 prof_section(e, "mtp.hyper.write", || {
12868 self.gate_write(e, &mut planes, &ptrs, &block_out, &inject, t)
12869 })?;
12870 ws.put_f32("mixer.out", block_out);
12871 put_inject(ws, inject);
12872 let (mixed, inject) = prof_section(e, "mtp.hyper.read", || {
12873 self.gate_read(
12874 e,
12875 ws,
12876 &ptrs,
12877 &layer.mlp_gate,
12878 &planes,
12879 t,
12880 layer.eps_mlp,
12881 false,
12882 )
12883 })?;
12884 let mlp = prof_section(e, "mtp.moe", || {
12885 self.moe_forward(e, ws, &layer.moe, &mixed, t, t <= 32, layer.index)
12888 })?;
12889 ws.put_f32("hc.mixed", mixed);
12890 prof_section(e, "mtp.hyper.write", || {
12891 self.gate_write(e, &mut planes, &ptrs, &mlp, &inject, t)
12892 })?;
12893 ws.put_f32("moe.out", mlp);
12894 put_inject(ws, inject);
12895
12896 let mut carrier = ws.take_f32(e, "mtp.carrier", t * wide, 0)?;
12898 for (s, plane) in planes.iter().enumerate() {
12899 for tok in 0..t {
12900 e.copy_range_into(
12901 &mut carrier,
12902 tok * wide + s * hidden,
12903 plane,
12904 tok * hidden,
12905 hidden,
12906 )?;
12907 }
12908 }
12909
12910 let x = prof_section(e, "mtp.exit", || {
12916 Ok(self
12917 .gate_read_inner(
12918 e,
12919 ws,
12920 &ptrs,
12921 &mtp.mixer,
12922 &planes,
12923 t,
12924 self.exit_eps,
12925 false,
12926 false,
12927 )?
12928 .0)
12929 })?;
12930 ws.put_u64("hc.ptrs", ptrs);
12931 let trim = self.draft_trim.as_ref();
12936 let out_f = trim.map_or(self.vocab, |t| t.n);
12937 let (head_w, head_b16) = match self.mtp_dev1.as_ref() {
12940 Some(d) => (&d.output, &d.output_b16),
12941 None => (&self.output, &self.output_b16),
12942 };
12943 let head_into =
12944 |e: &Engine, x: &CudaSlice<f32>, y: &mut CudaSlice<f32>, rows: usize| -> Res<()> {
12945 match trim {
12946 Some(trim) => linear_trim_into(e, trim, x, y, rows, hidden),
12947 None => linear_trunk_into(e, head_w, head_b16, x, y, rows, hidden, self.vocab),
12948 }
12949 };
12950 let logits = prof_section(e, "mtp.lm_head", || {
12951 if logits_all {
12952 let mut logits = ws.take_f32(e, "mtp.logits", t * out_f, 0)?;
12953 head_into(e, &x, &mut logits, t)?;
12954 return Ok(logits);
12955 }
12956 let mut logits = ws.take_f32(e, "mtp.logits", out_f, 0)?;
12957 let mut x_last = ws.take_f32(e, "mtp.xlast", hidden, 0)?;
12958 e.copy_range_into(&mut x_last, 0, &x, (t - 1) * hidden, hidden)?;
12959 head_into(e, &x_last, &mut logits, 1)?;
12960 ws.put_f32("mtp.xlast", x_last);
12961 Ok(logits)
12962 })?;
12963 ws.put_f32("hc.mixed", x);
12964 for (s, plane) in planes.into_iter().enumerate() {
12965 ws.put_f32(PLANE_SLOTS[s], plane);
12966 }
12967 dstate.rows += t;
12968 Ok((logits, carrier))
12969 }
12970
12971 pub fn mtp_recycle(
12974 &self,
12975 dstate: &mut MtpDraftState,
12976 logits: CudaSlice<f32>,
12977 carrier: CudaSlice<f32>,
12978 ) {
12979 dstate.ws.put_f32("mtp.logits", logits);
12980 dstate.ws.put_f32("mtp.carrier", carrier);
12981 }
12982}
12983
12984#[derive(Clone, Copy)]
12990pub struct SpecSamplerCfg {
12991 pub temperature: f32,
12992 pub top_p: f32,
12993 pub top_k: usize,
12994 pub seed: u64,
12995}
12996
12997struct SpecRng(u64);
12999
13000impl SpecRng {
13001 fn next_f32(&mut self) -> f32 {
13002 let mut x = self.0;
13003 x ^= x >> 12;
13004 x ^= x << 25;
13005 x ^= x >> 27;
13006 self.0 = x;
13007 let bits = (x.wrapping_mul(0x2545_F491_4F6C_DD1D) >> 40) as u32;
13008 bits as f32 / (1u64 << 24) as f32
13009 }
13010}
13011
13012fn sample_row(cfg: &SpecSamplerCfg, rng: &mut SpecRng, row: &[f32]) -> u32 {
13014 let k = cfg.top_k.max(1).min(row.len());
13015 let mut idx: Vec<u32> = (0..row.len() as u32).collect();
13016 idx.select_nth_unstable_by(k - 1, |&a, &b| row[b as usize].total_cmp(&row[a as usize]));
13017 let mut top: Vec<(u32, f32)> = idx[..k].iter().map(|&i| (i, row[i as usize])).collect();
13018 top.sort_by(|a, b| b.1.total_cmp(&a.1));
13019 let temp = cfg.temperature.max(1e-6);
13020 let mx = top[0].1;
13021 let mut probs: Vec<f32> = top.iter().map(|&(_, v)| ((v - mx) / temp).exp()).collect();
13022 let sum: f32 = probs.iter().sum();
13023 for p in &mut probs {
13024 *p /= sum;
13025 }
13026 let mut cut = probs.len();
13028 let mut acc = 0.0f32;
13029 for (i, &p) in probs.iter().enumerate() {
13030 acc += p;
13031 if acc >= cfg.top_p {
13032 cut = i + 1;
13033 break;
13034 }
13035 }
13036 let renorm: f32 = probs[..cut].iter().sum();
13037 let draw = rng.next_f32() * renorm;
13038 let mut acc = 0.0f32;
13039 for (i, &p) in probs[..cut].iter().enumerate() {
13040 acc += p;
13041 if draw < acc {
13042 return top[i].0;
13043 }
13044 }
13045 top[cut - 1].0
13046}
13047
13048fn host_argmax(row: &[f32]) -> usize {
13052 let mut best = 0usize;
13053 for (i, &v) in row.iter().enumerate() {
13054 if v > row[best] {
13055 best = i;
13056 }
13057 }
13058 best
13059}
13060
13061fn ring_pieces(ring: usize, off: usize, t: usize) -> Vec<(usize, usize)> {
13071 debug_assert!(t <= ring, "wide-ring consumer wider than the ring");
13072 let slot = off % ring;
13073 if slot + t <= ring {
13074 vec![(slot, t)]
13075 } else {
13076 vec![(slot, ring - slot), (0, t - (ring - slot))]
13077 }
13078}
13079
13080fn cross_wide_rows(
13081 e: &Engine,
13082 de: &Engine,
13083 src: &CudaSlice<f32>,
13084 dst: &mut CudaSlice<f32>,
13085 off: usize,
13086 t: usize,
13087 wide: usize,
13088) -> Res<f64> {
13089 let t0 = std::time::Instant::now();
13090 let stream = de.gpu.stream();
13091 let bytes = t * wide * 4;
13092 let byte_off = (off * wide * 4) as u64;
13093 let (sp, _g0) = src.device_ptr(&stream);
13094 let (dp, _g1) = dst.device_ptr_mut(&stream);
13095 unsafe {
13096 cudarc::driver::result::memcpy_peer_async(
13097 de.ctx().cu_ctx(),
13098 dp + byte_off,
13099 e.ctx().cu_ctx(),
13100 sp + byte_off,
13101 bytes,
13102 stream.cu_stream(),
13103 )?;
13104 }
13105 stream.synchronize()?;
13106 Ok(t0.elapsed().as_secs_f64() * 1e3)
13107}
13108
13109fn embed_gather_rows_into(
13114 e: &Engine,
13115 table: &CudaSlice<u8>,
13116 tok_v: &CudaView<u32>,
13117 x_out: &mut CudaSlice<f32>,
13118 t: usize,
13119 n_embd: usize,
13120 qtype: i32,
13121 row_bytes: usize,
13122) -> Res<()> {
13123 let f = e.func("embed_gather_u32_t");
13124 let cfg = LaunchConfig {
13125 grid_dim: (((n_embd as u32).div_ceil(256)).max(1), t as u32, 1),
13126 block_dim: (256, 1, 1),
13127 shared_mem_bytes: 0,
13128 };
13129 let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
13130 let stream = e.gpu.stream();
13131 let mut b = stream.launch_builder(&f);
13132 b.arg(table)
13133 .arg(tok_v)
13134 .arg(x_out)
13135 .arg(&ne)
13136 .arg(&qt)
13137 .arg(&rb)
13138 .arg(&ti);
13139 unsafe {
13140 b.launch(cfg)?;
13141 }
13142 Ok(())
13143}
13144
13145#[derive(Debug, Default, Clone)]
13147pub struct SpecReport {
13148 pub tokens: Vec<u32>,
13149 pub rounds: usize,
13150 pub drafted: u64,
13151 pub accepted: u64,
13152 pub accept_hist: Vec<u64>,
13154 pub draft_ms: f64,
13155 pub verify_ms: f64,
13156 pub prefill_ms: f64,
13157 pub total_ms: f64,
13158 pub chain_ms: f64,
13161 pub replay_ms: f64,
13162 pub draft_prefill_ms: f64,
13163 pub cross_ms: f64,
13166 pub cross_bytes: u64,
13167 pub k_decays: Vec<(usize, usize)>,
13169 pub spec_off_at: Option<usize>,
13172 pub plain_steps: usize,
13173 pub round_wall: Vec<(usize, f64)>,
13178 pub zero_draft_rounds: usize,
13181 pub guard_stops: usize,
13182}
13183
13184#[derive(Clone, Copy, Debug)]
13194pub struct DynKCfg {
13195 pub window: usize,
13196 pub thr: f64,
13197 pub k_floor: usize,
13198}
13199
13200#[derive(Clone, Copy, Debug, Default)]
13204pub struct SpecOpts {
13205 pub dynk: Option<DynKCfg>,
13207 pub adapt_k_lo: Option<usize>,
13210 pub pmin: f32,
13217 pub defer: bool,
13227 pub defer_guard_sync: bool,
13234 pub prefill_chunk: Option<usize>,
13240 pub wide_ring: Option<usize>,
13245}
13246
13247pub fn spec_guard_trunc(probs: &[f32], pmin: f32) -> usize {
13256 probs.iter().position(|&p| p < pmin).unwrap_or(probs.len())
13257}
13258
13259#[derive(Debug, Default, Clone)]
13265pub struct SpecTraceRound {
13266 pub round: usize,
13267 pub gen_pos: usize,
13269 pub base: usize,
13271 pub k: usize,
13272 pub a: usize,
13273 pub drafts: Vec<u32>,
13274 pub targets: Vec<u32>,
13276 pub draft_top1: f32,
13281 pub draft_top2: f32,
13282 pub draft_tgt_logit: f32,
13283 pub draft_tgt_rank: usize,
13284 pub target_top1: f32,
13285 pub target_top2: f32,
13286 pub target_draft_logit: f32,
13287 pub target_entropy: f64,
13288 pub carrier_rel_l2: Vec<f32>,
13292 pub carrier_cos: Vec<f32>,
13293}
13294
13295impl SpecReport {
13296 pub fn accept_rate(&self) -> f64 {
13297 if self.drafted == 0 {
13298 0.0
13299 } else {
13300 self.accepted as f64 / self.drafted as f64
13301 }
13302 }
13303 pub fn mean_accept_len(&self) -> f64 {
13305 if self.rounds == 0 {
13306 0.0
13307 } else {
13308 self.tokens.len() as f64 / self.rounds as f64
13309 }
13310 }
13311}
13312
13313impl Qwen4ExpGpu {
13314 pub fn spec_arm(&self, e: &Engine, state: &mut Qwen4ExpState, k_cap: usize) -> Res<()> {
13318 self.spec_arm_ring(e, state, k_cap, state.capacity)
13319 }
13320
13321 pub fn spec_arm_ring(
13328 &self,
13329 e: &Engine,
13330 state: &mut Qwen4ExpState,
13331 k_cap: usize,
13332 ring_rows: usize,
13333 ) -> Res<()> {
13334 let ring_rows = ring_rows.min(state.capacity).max(k_cap + 2);
13335 if let Some(v) = state.verify.as_ref() {
13336 if v.k_cap == k_cap && v.ring_rows == ring_rows {
13337 return Ok(());
13338 }
13339 }
13340 let wide = self.streams * self.hidden;
13341 let mut gdn = Vec::with_capacity(self.layers.len());
13342 let mut ple = Vec::with_capacity(self.layers.len());
13343 for layer in &self.layers {
13344 gdn.push(match &layer.mixer {
13345 MixerW::Gdn(g) => {
13346 let p = &g.plan;
13347 let (nk, nv) = (p.key_heads as usize, p.value_heads as usize);
13348 let (hk, hv) = (p.key_head_dim as usize, p.value_head_dim as usize);
13349 let conv_dim = 2 * nk * hk + nv * hv;
13350 let pad = p.conv_kernel as usize - 1;
13351 Some(GdnStash {
13352 states: e.zeros(k_cap * nv * hv * hk)?,
13353 conv_pre: e.zeros(pad * conv_dim)?,
13354 qkv_rows: e.zeros(k_cap * conv_dim)?,
13355 scan_graph: None,
13356 scan_warm: None,
13357 })
13358 }
13359 MixerW::Qsa(_) => None,
13360 });
13361 ple.push(match layer.ple.as_ref() {
13362 Some(pw) => {
13363 let pad = (pw.plan.conv_kernel as usize - 1) * pw.plan.max_ngram as usize;
13364 let mut hist_pre = Vec::with_capacity(self.streams);
13365 let mut normed_rows = Vec::with_capacity(self.streams);
13366 for _ in 0..self.streams {
13367 hist_pre.push(e.zeros(pad * self.hidden)?);
13368 normed_rows.push(e.zeros(k_cap * self.hidden)?);
13369 }
13370 Some(PleStash {
13371 hist_pre,
13372 normed_rows,
13373 })
13374 }
13375 None => None,
13376 });
13377 }
13378 state.verify = Some(VerifyStash {
13379 k_cap,
13380 chunk: None,
13381 gdn,
13382 ple,
13383 wide: e.zeros(ring_rows * wide)?,
13384 ring_rows,
13385 wide_dev1: None,
13386 argmax: Vec::new(),
13387 toks: unsafe { e.gpu.stream().alloc::<u32>(k_cap)? },
13388 want_argmax: false,
13389 want_argmax_t1: false,
13390 last_row_only: false,
13391 });
13392 Ok(())
13393 }
13394
13395 pub fn spec_disarm(&self, state: &mut Qwen4ExpState) {
13396 state.verify = None;
13397 }
13398
13399 pub fn set_verify_want_argmax(&self, state: &mut Qwen4ExpState, on: bool) -> Res<()> {
13400 state
13401 .verify
13402 .as_mut()
13403 .ok_or("qwen4exp_gpu: verify not armed")?
13404 .want_argmax = on;
13405 Ok(())
13406 }
13407
13408 pub fn verify_argmax_rows<'s>(&self, state: &'s Qwen4ExpState) -> Res<&'s [u32]> {
13410 Ok(&state
13411 .verify
13412 .as_ref()
13413 .ok_or("qwen4exp_gpu: verify not armed")?
13414 .argmax)
13415 }
13416
13417 pub fn verify_rewind(&self, e: &Engine, state: &mut Qwen4ExpState, keep: usize) -> Res<()> {
13422 let Some(v) = state.verify.as_mut() else {
13423 return Err("qwen4exp_gpu: verify not armed".into());
13424 };
13425 let Some((base, t)) = v.chunk.take() else {
13426 return Err("qwen4exp_gpu: no live verify chunk to rewind".into());
13427 };
13428 if keep == 0 || keep > t {
13429 return Err("qwen4exp_gpu: rewind keep out of range".into());
13430 }
13431 if keep == t {
13432 return Ok(());
13433 }
13434 state.pos = base + keep;
13435 state.tokens.truncate(base + keep);
13436 for (li, (layer, lstate)) in self.layers.iter().zip(state.layers.iter_mut()).enumerate() {
13437 match (&layer.mixer, &mut lstate.mixer) {
13438 (
13439 MixerW::Qsa(qsa),
13440 MixerState::Qsa {
13441 raw_keys,
13442 pooled_keys,
13443 pooled_dev_rows,
13444 raw_dev_rows,
13445 idx_audit,
13446 ..
13447 },
13448 ) => {
13449 let idx_dim = qsa.overlay.head_dim as usize;
13450 raw_keys.truncate_rows(base + keep, idx_dim);
13451 let block = qsa.overlay.block_size as usize;
13452 pooled_keys.truncate(((base + keep) / block) * idx_dim);
13453 *pooled_dev_rows = (*pooled_dev_rows).min(pooled_keys.len() / idx_dim);
13455 *raw_dev_rows = (*raw_dev_rows).min(base + keep);
13458 if let Some(audit) = idx_audit.as_deref_mut() {
13460 audit.raw_f32.truncate_rows(base + keep, idx_dim);
13461 audit.pooled_f32.truncate(((base + keep) / block) * idx_dim);
13462 }
13463 }
13464 (MixerW::Gdn(g), MixerState::Gdn { conv, state: rec }) => {
13465 let st = v.gdn[li]
13466 .as_mut()
13467 .ok_or("qwen4exp_gpu: GDN layer without a verify stash")?;
13468 let p = &g.plan;
13469 let (nk, nv) = (p.key_heads as usize, p.value_heads as usize);
13470 let (hk, hv) = (p.key_head_dim as usize, p.value_head_dim as usize);
13471 let conv_dim = 2 * nk * hk + nv * hv;
13472 let pad = p.conv_kernel as usize - 1;
13473 let state_len = nv * hv * hk;
13474 e.copy_range_into(rec, 0, &st.states, (keep - 1) * state_len, state_len)?;
13475 if keep >= pad {
13476 e.copy_range_into(
13477 conv,
13478 0,
13479 &st.qkv_rows,
13480 (keep - pad) * conv_dim,
13481 pad * conv_dim,
13482 )?;
13483 } else {
13484 let keep_hist = pad - keep;
13485 e.copy_range_into(
13486 conv,
13487 0,
13488 &st.conv_pre,
13489 keep * conv_dim,
13490 keep_hist * conv_dim,
13491 )?;
13492 e.copy_range_into(
13493 conv,
13494 keep_hist * conv_dim,
13495 &st.qkv_rows,
13496 0,
13497 keep * conv_dim,
13498 )?;
13499 }
13500 }
13501 _ => return Err("qwen4exp_gpu: mixer/state mismatch in rewind".into()),
13502 }
13503 if let (Some(pw), Some(ps)) = (layer.ple.as_ref(), lstate.ple.as_mut()) {
13504 let st = v.ple[li]
13505 .as_mut()
13506 .ok_or("qwen4exp_gpu: PLE layer without a verify stash")?;
13507 let pad = (pw.plan.conv_kernel as usize - 1) * pw.plan.max_ngram as usize;
13508 let hidden = self.hidden;
13509 for s in 0..self.streams {
13510 let hist = &mut ps.conv_hist[s];
13511 if keep >= pad {
13512 e.copy_range_into(
13513 hist,
13514 0,
13515 &st.normed_rows[s],
13516 (keep - pad) * hidden,
13517 pad * hidden,
13518 )?;
13519 } else {
13520 let keep_hist = pad - keep;
13521 e.copy_range_into(
13522 hist,
13523 0,
13524 &st.hist_pre[s],
13525 keep * hidden,
13526 keep_hist * hidden,
13527 )?;
13528 e.copy_range_into(
13529 hist,
13530 keep_hist * hidden,
13531 &st.normed_rows[s],
13532 0,
13533 keep * hidden,
13534 )?;
13535 }
13536 }
13537 }
13538 }
13539 Ok(())
13540 }
13541
13542 fn draft_row_argmax(
13551 &self,
13552 e: &Engine,
13553 logits: &CudaSlice<f32>,
13554 row: usize,
13555 conf: bool,
13556 ) -> Res<(u32, f32)> {
13557 let width = self.draft_logits_width();
13558 let mut tok = unsafe { e.gpu.stream().alloc::<u32>(1)? };
13559 e.argmax_token_device_col(logits, row, width, &mut tok, 0)?;
13560 let p = if conf {
13561 if row != 0 {
13562 return Err("qwen4exp_gpu: draft confidence reads row 0 (the chain shape)".into());
13564 }
13565 let pd = e.prob_of_token_device(logits, &tok, width)?;
13566 e.gpu.stream().clone_dtoh(&pd)?[0]
13567 } else {
13568 1.0
13569 };
13570 Ok((self.draft_token(e.gpu.stream().clone_dtoh(&tok)?[0])?, p))
13571 }
13572
13573 #[allow(clippy::too_many_arguments)]
13586 pub fn spec_generate(
13587 &self,
13588 e: &Engine,
13589 prompt: &[u32],
13590 max_new: usize,
13591 k: usize,
13592 state: &mut Qwen4ExpState,
13593 dstate: &mut MtpDraftState,
13594 sampler: Option<SpecSamplerCfg>,
13595 ) -> Res<SpecReport> {
13596 self.spec_generate_ext(
13597 e,
13598 e,
13599 prompt,
13600 max_new,
13601 k,
13602 state,
13603 dstate,
13604 sampler,
13605 SpecOpts::default(),
13606 None,
13607 )
13608 }
13609
13610 #[allow(clippy::too_many_arguments)]
13621 pub fn spec_generate_ext(
13622 &self,
13623 e: &Engine,
13624 de: &Engine,
13625 prompt: &[u32],
13626 max_new: usize,
13627 k: usize,
13628 state: &mut Qwen4ExpState,
13629 dstate: &mut MtpDraftState,
13630 sampler: Option<SpecSamplerCfg>,
13631 opts: SpecOpts,
13632 mut trace: Option<&mut Vec<SpecTraceRound>>,
13633 ) -> Res<SpecReport> {
13634 use std::time::Instant;
13635 if k == 0 {
13636 return Err("qwen4exp_gpu: spec needs k >= 1".into());
13637 }
13638 let n = prompt.len();
13639 if n < 2 {
13640 return Err("qwen4exp_gpu: spec needs a >= 2 token prompt".into());
13641 }
13642 if state.pos != 0 || dstate.rows != 0 {
13643 return Err("qwen4exp_gpu: spec_generate wants FRESH trunk + draft states".into());
13644 }
13645 if state.capacity < n + max_new + k + 2 || dstate.capacity < n + max_new + k + 2 {
13646 return Err("qwen4exp_gpu: state capacity too small for prompt + max_new + k".into());
13647 }
13648 self.check_draft_engine(de)?;
13649 let dev1 = self.mtp_dev1.is_some();
13650 if !dev1 && de.ctx().ordinal() != e.ctx().ordinal() {
13651 return Err(
13652 "qwen4exp_gpu: draft engine on another card, but the draft was not \
13653 built there (load_from_dir_dev1)"
13654 .into(),
13655 );
13656 }
13657 let vocab = self.vocab;
13658 let wide_w = self.streams * self.hidden;
13659 let greedy = sampler.is_none();
13660 let tracing = trace.is_some();
13661 let guard = opts.pmin > 0.0;
13662 let deferred = opts.defer;
13663 if deferred && tracing {
13664 return Err(
13665 "qwen4exp_gpu: spec defer + trace are mutually exclusive (the trace \
13666 instrument reads per-step host rows); run the trace on the host-chain arm"
13667 .into(),
13668 );
13669 }
13670 if deferred {
13671 let ce = self.chain_embed.as_ref().ok_or(
13672 "qwen4exp_gpu: SpecOpts::defer needs arm_spec_devchain on the draft engine",
13673 )?;
13674 if ce.dev != de.ctx().ordinal() {
13675 return Err(format!(
13676 "qwen4exp_gpu: the chain-embed table lives on device {} but the \
13677 draft engine is device {} — re-arm arm_spec_devchain",
13678 ce.dev,
13679 de.ctx().ordinal()
13680 )
13681 .into());
13682 }
13683 if ce.for_trim != self.draft_trim.is_some() || ce.rows != self.draft_logits_width() {
13684 return Err(
13685 "qwen4exp_gpu: the chain-embed table was armed for a different trim \
13686 state — re-arm arm_spec_devchain after trim changes"
13687 .into(),
13688 );
13689 }
13690 }
13691 let (mut chain_toks_d, mut chain_probs_d) = if deferred {
13694 (
13695 Some(unsafe { de.gpu.stream().alloc::<u32>(k)? }),
13696 Some(de.zeros(k)?),
13697 )
13698 } else {
13699 (None, None)
13700 };
13701 let mut rng = sampler
13702 .as_ref()
13703 .map(|cfg| SpecRng(cfg.seed | 1))
13704 .unwrap_or(SpecRng(1));
13705 let t_total = Instant::now();
13706 let mut report = SpecReport {
13707 accept_hist: vec![0; k + 1],
13708 ..Default::default()
13709 };
13710
13711 match opts.wide_ring {
13712 Some(ring) => {
13713 let chunk = opts
13714 .prefill_chunk
13715 .ok_or("qwen4exp_gpu: SpecOpts::wide_ring needs prefill_chunk")?;
13716 if ring < 2 * chunk || ring < 2 * (k + 2) {
13717 return Err("qwen4exp_gpu: wide_ring must cover 2 prefill chunks".into());
13718 }
13719 self.spec_arm_ring(e, state, k + 1, ring)?;
13720 }
13721 None => self.spec_arm(e, state, k + 1)?,
13722 }
13723 self.set_verify_want_argmax(state, false)?;
13724 if let Some(v) = state.verify.as_mut() {
13725 v.want_argmax_t1 = deferred && greedy && !tracing;
13728 v.last_row_only = deferred;
13729 }
13730 let ring = state.verify.as_ref().expect("armed above").ring_rows;
13733 if dev1 {
13734 let v = state.verify.as_mut().expect("armed above");
13735 if v.wide_dev1.as_ref().is_none_or(|m| m.len() < ring * wide_w) {
13736 v.wide_dev1 = Some(de.zeros(ring * wide_w)?);
13737 }
13738 }
13739 let dev_embed = deferred
13744 && self
13745 .chain_embed
13746 .as_ref()
13747 .is_some_and(|ce| !ce.for_trim && ce.rows == self.vocab);
13748 let t_prefill = Instant::now();
13749 let mut draft_prefill_ms = 0f64;
13750 let x0: u32 = match opts.prefill_chunk {
13751 Some(chunk) if n > chunk => {
13758 let mut b = 0usize;
13759 let mut last = Vec::new();
13760 while b < n {
13761 let mut t = chunk.min(n - b);
13762 if n - (b + t) > 0 && n - (b + t) <= k + 1 {
13764 t = n - b;
13765 }
13766 let is_last = b + t == n;
13767 let head = if is_last {
13768 HeadMode::LastRow
13769 } else {
13770 HeadMode::Skip
13771 };
13772 let piece = self.forward_with(e, &prompt[b..b + t], state, None, head)?;
13773 let t_draft = Instant::now();
13774 if dev1 {
13775 let v = state.verify.as_mut().expect("armed above");
13776 let VerifyStash {
13777 wide, wide_dev1, ..
13778 } = v;
13779 let mirror = wide_dev1.as_mut().expect("allocated above");
13780 for (slot, len) in ring_pieces(ring, b, t) {
13781 report.cross_ms +=
13782 cross_wide_rows(e, de, wide, mirror, slot, len, wide_w)?;
13783 }
13784 report.cross_bytes += (t * wide_w * 4) as u64;
13785 }
13786 let p0 = b.max(1);
13789 if b + t > p0 {
13790 let v = state.verify.as_ref().expect("armed above");
13791 let seed: &CudaSlice<f32> = v.wide_dev1.as_ref().unwrap_or(&v.wide);
13792 let (ld, cd, _) = self.draft_consume_ring(
13793 de,
13794 &prompt[p0..b + t],
13795 dev_embed,
13796 seed,
13797 ring,
13798 p0 - 1,
13799 dstate,
13800 )?;
13801 self.mtp_recycle(dstate, ld, cd);
13802 }
13803 draft_prefill_ms += t_draft.elapsed().as_secs_f64() * 1e3;
13804 b += t;
13805 if is_last {
13806 last = piece;
13807 }
13808 }
13809 dstate.committed = n - 1;
13810 debug_assert_eq!(last.len(), vocab);
13811 match sampler.as_ref() {
13812 None => host_argmax(&last) as u32,
13813 Some(cfg) => sample_row(cfg, &mut rng, &last),
13814 }
13815 }
13816 _ => {
13818 let prefill = self.forward(e, prompt, state, None)?;
13819 let last = &prefill[prefill.len() - vocab..];
13824 let x0 = match sampler.as_ref() {
13825 None => host_argmax(last) as u32,
13826 Some(cfg) => sample_row(cfg, &mut rng, last),
13827 };
13828 let t_draft0 = Instant::now();
13829 if dev1 {
13830 let v = state.verify.as_mut().expect("armed above");
13831 let VerifyStash {
13832 wide, wide_dev1, ..
13833 } = v;
13834 let mirror = wide_dev1.as_mut().expect("allocated above");
13835 for (slot, len) in ring_pieces(ring, 0, n) {
13836 report.cross_ms += cross_wide_rows(e, de, wide, mirror, slot, len, wide_w)?;
13837 }
13838 report.cross_bytes += (n * wide_w * 4) as u64;
13839 }
13840 {
13841 let v = state.verify.as_ref().expect("armed above");
13842 let seed: &CudaSlice<f32> = v.wide_dev1.as_ref().unwrap_or(&v.wide);
13843 if n >= 2 {
13844 let (ld, cd, _) = self.draft_consume_ring(
13845 de,
13846 &prompt[1..],
13847 dev_embed,
13848 seed,
13849 ring,
13850 0,
13851 dstate,
13852 )?;
13853 self.mtp_recycle(dstate, ld, cd);
13854 }
13855 dstate.committed = n - 1;
13856 }
13857 draft_prefill_ms += t_draft0.elapsed().as_secs_f64() * 1e3;
13858 x0
13859 }
13860 };
13861 report.prefill_ms = t_prefill.elapsed().as_secs_f64() * 1e3 - draft_prefill_ms;
13862 self.set_verify_want_argmax(state, greedy && !tracing)?;
13865 report.tokens.push(x0);
13867
13868 let t_boot = Instant::now();
13870 let (mut tip_logits, mut tip_carrier) = {
13871 let v = state.verify.as_ref().expect("armed above");
13872 let seed: &CudaSlice<f32> = v.wide_dev1.as_ref().unwrap_or(&v.wide);
13873 self.mtp_draft_forward_spec(de, &[x0], dev_embed, seed, (n - 1) % ring, dstate)?
13874 };
13875 let mut tip_rows = 1usize;
13876 dstate.committed = dstate.rows;
13877 report.draft_prefill_ms = draft_prefill_ms + t_boot.elapsed().as_secs_f64() * 1e3;
13878 report.draft_ms += report.draft_prefill_ms;
13879
13880 let mut m = n; let mut tip = x0;
13882 let mut k_cur = k;
13885 let mut k_next = k;
13886 let mut window: Vec<usize> = Vec::new();
13887 let mut round_idx = 0usize;
13888 while report.tokens.len() < max_new {
13889 if k_cur == 0 {
13890 let row = self.forward(e, &[tip], state, None)?;
13894 let next: u32 = match sampler.as_ref() {
13895 None if deferred => self.verify_argmax_rows(state)?[0],
13898 None => host_argmax(&row) as u32,
13899 Some(cfg) => sample_row(cfg, &mut rng, &row),
13900 };
13901 report.tokens.push(next);
13902 report.plain_steps += 1;
13903 report
13904 .round_wall
13905 .push((report.tokens.len(), t_total.elapsed().as_secs_f64() * 1e3));
13906 m += 1;
13907 tip = next;
13908 continue;
13909 }
13910 let k_round = k_next.min(k_cur).max(1);
13911 let t_draft = Instant::now();
13916 let mut drafts: Vec<u32> = Vec::with_capacity(k_round);
13917 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()) {
13920 let width = self.draft_logits_width();
13925 de.argmax_token_device_col(&tip_logits, 0, width, toks, 0)?;
13926 if guard {
13927 de.prob_of_token_device_col(&tip_logits, toks, 0, probs, 0, width)?;
13928 }
13929 let mut prev_logits = tip_logits;
13930 let mut prev_carrier = tip_carrier;
13931 let mut prev_rows = tip_rows;
13932 let mut drafted = 1usize;
13934 let mut stopped = false;
13935 if guard && opts.defer_guard_sync {
13936 let p = de.gpu.stream().clone_dtoh(&probs.slice(0..1))?[0];
13940 if p < opts.pmin {
13941 drafted = 0;
13942 stopped = true;
13943 report.guard_stops += 1;
13944 }
13945 }
13946 while !stopped && drafted < k_round {
13947 let (l2, c2) = self.mtp_draft_forward_devslot(
13948 de,
13949 toks,
13950 drafted - 1,
13951 &prev_carrier,
13952 prev_rows - 1,
13953 dstate,
13954 )?;
13955 self.mtp_recycle(dstate, prev_logits, prev_carrier);
13956 prev_logits = l2;
13957 prev_carrier = c2;
13958 prev_rows = 1;
13959 de.argmax_token_device_col(&prev_logits, 0, width, toks, drafted)?;
13960 if guard {
13961 de.prob_of_token_device_col(
13962 &prev_logits,
13963 toks,
13964 drafted,
13965 probs,
13966 drafted,
13967 width,
13968 )?;
13969 if opts.defer_guard_sync {
13970 let p = de
13971 .gpu
13972 .stream()
13973 .clone_dtoh(&probs.slice(drafted..drafted + 1))?[0];
13974 if p < opts.pmin {
13975 report.guard_stops += 1;
13976 break;
13977 }
13978 }
13979 }
13980 drafted += 1;
13981 }
13982 self.mtp_recycle(dstate, prev_logits, prev_carrier);
13983 if drafted > 0 {
13988 let raw = de.gpu.stream().clone_dtoh(&toks.slice(0..drafted))?;
13989 let trunc = if guard && !opts.defer_guard_sync {
13990 let pw = de.gpu.stream().clone_dtoh(&probs.slice(0..drafted))?;
13991 let trunc = spec_guard_trunc(&pw, opts.pmin);
13992 if trunc < drafted {
13993 report.guard_stops += 1;
13994 }
13995 trunc
13996 } else {
13997 drafted
13998 };
13999 for &r in raw.iter().take(trunc) {
14000 drafts.push(self.draft_token(r)?);
14001 }
14002 }
14003 } else {
14004 let (d1, c1) = self.draft_row_argmax(de, &tip_logits, 0, guard)?;
14005 if !(guard && c1 < opts.pmin) {
14006 drafts.push(d1);
14007 if tracing {
14008 chain_rows_h
14009 .push(de.dtoh_view(&tip_logits.slice(0..self.draft_logits_width()))?);
14010 }
14011 } else {
14012 report.guard_stops += 1;
14013 }
14014 let mut prev_logits = tip_logits;
14015 let mut prev_carrier = tip_carrier;
14016 let mut prev_rows = tip_rows;
14017 while !drafts.is_empty() && drafts.len() < k_round {
14018 if tracing {
14019 seeds_h.push(de.dtoh_view(
14020 &prev_carrier.slice((prev_rows - 1) * wide_w..prev_rows * wide_w),
14021 )?);
14022 }
14023 let lastd = *drafts.last().expect("non-empty");
14024 let (l2, c2) = self.mtp_draft_forward(
14025 de,
14026 &[lastd],
14027 &prev_carrier,
14028 prev_rows - 1,
14029 dstate,
14030 1,
14031 false,
14032 )?;
14033 self.mtp_recycle(dstate, prev_logits, prev_carrier);
14034 prev_logits = l2;
14035 prev_carrier = c2;
14036 prev_rows = 1;
14037 let (dn, cn) = self.draft_row_argmax(de, &prev_logits, 0, guard)?;
14038 if guard && cn < opts.pmin {
14039 report.guard_stops += 1;
14040 break;
14041 }
14042 drafts.push(dn);
14043 if tracing {
14044 chain_rows_h
14045 .push(de.dtoh_view(&prev_logits.slice(0..self.draft_logits_width()))?);
14046 }
14047 }
14048 self.mtp_recycle(dstate, prev_logits, prev_carrier);
14049 }
14050 let chain_ms = t_draft.elapsed().as_secs_f64() * 1e3;
14051 report.chain_ms += chain_ms;
14052 report.draft_ms += chain_ms;
14053
14054 let t_ver = Instant::now();
14057 let mut chunk = Vec::with_capacity(drafts.len() + 1);
14058 chunk.push(tip);
14059 chunk.extend_from_slice(&drafts);
14060 let tlen = chunk.len();
14061 let host_logits = self.forward(e, &chunk, state, None)?;
14062 let targets: Vec<u32> = if greedy && !tracing && (tlen > 1 || deferred) {
14065 self.verify_argmax_rows(state)?.to_vec()
14066 } else if greedy {
14067 (0..tlen)
14068 .map(|row| host_argmax(&host_logits[row * vocab..(row + 1) * vocab]) as u32)
14069 .collect()
14070 } else {
14071 let cfg = sampler.as_ref().expect("sampled mode");
14072 (0..tlen)
14073 .map(|row| {
14074 sample_row(cfg, &mut rng, &host_logits[row * vocab..(row + 1) * vocab])
14075 })
14076 .collect()
14077 };
14078 report.verify_ms += t_ver.elapsed().as_secs_f64() * 1e3;
14079 if targets.len() != tlen {
14080 return Err("qwen4exp_gpu: verify produced the wrong row count".into());
14081 }
14082
14083 let mut a = 0usize;
14085 while a < drafts.len() && drafts[a] == targets[a] {
14086 a += 1;
14087 }
14088 report.rounds += 1;
14089 report.drafted += drafts.len() as u64;
14090 report.accepted += a as u64;
14091 report.accept_hist[a] += 1;
14092 if drafts.is_empty() {
14093 report.zero_draft_rounds += 1;
14094 }
14095 report.tokens.extend_from_slice(&targets[0..=a]);
14096
14097 if let Some(tr) = trace.as_deref_mut() {
14100 let mut rec = SpecTraceRound {
14101 round: round_idx,
14102 gen_pos: report.tokens.len() - (a + 1),
14103 base: m,
14104 k: drafts.len(),
14105 a,
14106 drafts: drafts.clone(),
14107 targets: targets.clone(),
14108 draft_top1: f32::NAN,
14109 draft_top2: f32::NAN,
14110 draft_tgt_logit: f32::NAN,
14111 draft_tgt_rank: 0,
14112 target_top1: f32::NAN,
14113 target_top2: f32::NAN,
14114 target_draft_logit: f32::NAN,
14115 target_entropy: 0.0,
14116 carrier_rel_l2: Vec::new(),
14117 carrier_cos: Vec::new(),
14118 };
14119 if a < drafts.len() {
14120 let drow = &chain_rows_h[a];
14121 let trow = &host_logits[a * vocab..(a + 1) * vocab];
14122 let tgt = targets[a] as usize;
14123 let dtok = drafts[a] as usize;
14124 let (mut d1v, mut d2v) = (f32::NEG_INFINITY, f32::NEG_INFINITY);
14125 let mut rank = 0usize;
14126 let dt = drow[tgt];
14127 for &v in drow.iter() {
14128 if v > d1v {
14129 d2v = d1v;
14130 d1v = v;
14131 } else if v > d2v {
14132 d2v = v;
14133 }
14134 if v > dt {
14135 rank += 1;
14136 }
14137 }
14138 let (mut t1v, mut t2v) = (f32::NEG_INFINITY, f32::NEG_INFINITY);
14139 for &v in trow.iter() {
14140 if v > t1v {
14141 t2v = t1v;
14142 t1v = v;
14143 } else if v > t2v {
14144 t2v = v;
14145 }
14146 }
14147 let mx = t1v as f64;
14149 let mut z = 0.0f64;
14150 let mut sxl = 0.0f64;
14151 for &v in trow.iter() {
14152 let ev = ((v as f64) - mx).exp();
14153 z += ev;
14154 sxl += ev * ((v as f64) - mx);
14155 }
14156 rec.draft_top1 = d1v;
14157 rec.draft_top2 = d2v;
14158 rec.draft_tgt_logit = dt;
14159 rec.draft_tgt_rank = rank;
14160 rec.target_top1 = t1v;
14161 rec.target_top2 = t2v;
14162 rec.target_draft_logit = trow[dtok];
14163 rec.target_entropy = z.ln() - sxl / z;
14164 }
14165 let v = state.verify.as_ref().expect("armed above");
14166 for (j, seed) in seeds_h.iter().enumerate() {
14167 let slot = (m + j) % ring;
14168 let truth = e.dtoh_view(&v.wide.slice(slot * wide_w..(slot + 1) * wide_w))?;
14169 let mut dd = 0.0f64;
14170 let mut tt = 0.0f64;
14171 let mut st = 0.0f64;
14172 let mut ss = 0.0f64;
14173 for (&s, &t) in seed.iter().zip(truth.iter()) {
14174 let (s, t) = (s as f64, t as f64);
14175 dd += (s - t) * (s - t);
14176 tt += t * t;
14177 st += s * t;
14178 ss += s * s;
14179 }
14180 rec.carrier_rel_l2
14181 .push((dd.sqrt() / tt.sqrt().max(1e-30)) as f32);
14182 rec.carrier_cos
14183 .push((st / (ss.sqrt() * tt.sqrt()).max(1e-30)) as f32);
14184 }
14185 tr.push(rec);
14186 }
14187
14188 if tlen > 1 {
14190 self.verify_rewind(e, state, a + 1)?;
14191 }
14192 self.mtp_rewind(dstate, m)?;
14193 let t_draft2 = Instant::now();
14194 let x_next = targets[a];
14195 let mut replay: Vec<u32> = drafts[0..a].to_vec();
14196 replay.push(x_next);
14197 if dev1 {
14198 let v = state.verify.as_mut().expect("armed above");
14199 let VerifyStash {
14200 wide, wide_dev1, ..
14201 } = v;
14202 let mirror = wide_dev1.as_mut().expect("allocated above");
14203 for (slot, len) in ring_pieces(ring, m, replay.len()) {
14204 report.cross_ms += cross_wide_rows(e, de, wide, mirror, slot, len, wide_w)?;
14205 }
14206 report.cross_bytes += (replay.len() * wide_w * 4) as u64;
14207 }
14208 let (l, c, last_len) = {
14209 let v = state.verify.as_ref().expect("armed above");
14210 let seed: &CudaSlice<f32> = v.wide_dev1.as_ref().unwrap_or(&v.wide);
14211 self.draft_consume_ring(de, &replay, dev_embed, seed, ring, m, dstate)?
14212 };
14213 tip_logits = l;
14214 tip_carrier = c;
14215 tip_rows = last_len;
14216 dstate.committed = dstate.rows;
14217 let replay_ms = t_draft2.elapsed().as_secs_f64() * 1e3;
14218 report.replay_ms += replay_ms;
14219 report.draft_ms += replay_ms;
14220 m += a + 1;
14221 tip = x_next;
14222 report
14223 .round_wall
14224 .push((report.tokens.len(), t_total.elapsed().as_secs_f64() * 1e3));
14225
14226 if let Some(lo) = opts.adapt_k_lo {
14228 k_next = (a + 1).clamp(lo.max(1), k);
14229 }
14230 if let Some(cfg) = opts.dynk {
14231 window.push(a);
14232 if window.len() >= cfg.window.max(1) {
14233 let mean = window.iter().sum::<usize>() as f64 / window.len() as f64;
14234 if mean < cfg.thr {
14235 let new_k = k_cur.saturating_sub(1).max(cfg.k_floor);
14236 if new_k < k_cur {
14237 k_cur = new_k;
14238 report.k_decays.push((round_idx, k_cur));
14239 if k_cur == 0 {
14240 report.spec_off_at = Some(report.tokens.len());
14241 }
14242 }
14243 }
14244 window.clear();
14245 }
14246 }
14247 round_idx += 1;
14248 }
14249 report.tokens.truncate(max_new);
14250 report.total_ms = t_total.elapsed().as_secs_f64() * 1e3;
14251 Ok(report)
14252 }
14253}
14254
14255enum BankTensorSrc {
14273 F32(Vec<f32>),
14275 Nvfp4 {
14283 codes: Vec<u8>,
14284 scales: Vec<u8>,
14285 macros: Vec<f32>,
14286 act_scale: Option<f32>,
14287 },
14288 Bf16(Vec<u8>),
14291}
14292
14293struct BankSrc {
14294 gate: BankTensorSrc, up: BankTensorSrc, down: BankTensorSrc, n_expert: usize,
14298 ff: usize,
14299 hidden: usize,
14300}
14301
14302pub struct LoadedCheckpoint {
14306 pub plan: ModelPlan,
14307 pub weights: ReferenceWeights,
14308 banks: std::collections::BTreeMap<u32, BankSrc>,
14309 tables: std::collections::BTreeMap<u32, Vec<u8>>, }
14311
14312fn norm_fold_add_one(name: &str) -> bool {
14318 name.contains("norm") && name.ends_with(".weight") && !name.ends_with("linear_attn.norm.weight")
14319}
14320
14321fn indexer_layernorm(name: &str) -> bool {
14325 name.contains(".indexer.")
14326 && (name.ends_with("q_layernorm.weight") || name.ends_with("k_layernorm.weight"))
14327}
14328
14329#[derive(Default, Clone, Copy)]
14331pub struct LoadOptions {
14332 pub host_bf16_banks: bool,
14337 pub indexer_norm_raw: bool,
14340 pub load_mtp: bool,
14345}
14346
14347fn bridge_transform(
14348 transform: memra_gguf::tensor_contract::TensorTransform,
14349) -> Res<memra_gguf::hf_mapping::TransformKind> {
14350 use memra_gguf::hf_mapping::TransformKind as K;
14351 use memra_gguf::tensor_contract::TensorTransform as T;
14352 Ok(match transform {
14353 T::Identity => K::Identity,
14354 T::NormAddOne => K::NormPlusOne,
14355 T::QkvVReorderRows => K::QkvVReorderRows,
14356 T::ZReorderRows => K::ZReorderRows,
14357 T::AbReorderRows => K::AbReorderRows,
14358 T::NegExpReorderHeads => K::NegExpReorderHeads,
14359 T::ReorderHeads => K::ReorderHeads,
14360 T::Conv1dSqueezeReorder => K::Conv1dSqueezeReorder,
14361 T::OutReorderColumns => K::OutReorderCols,
14362 other => return Err(format!("qwen4exp_gpu: unsupported transform {other:?}").into()),
14363 })
14364}
14365
14366fn dequant_float(
14367 name: &str,
14368 info: &memra_gguf::safetensors::StInfo,
14369 bytes: &[u8],
14370) -> Res<Vec<f32>> {
14371 let elements: usize = info.shape.iter().map(|&d| d as usize).product();
14372 match info.dtype.as_str() {
14373 "BF16" | "F32" => Ok(memra_gguf::dequant::dequantize(
14374 info.ggml_type(),
14375 bytes,
14376 elements,
14377 )),
14378 other => Err(format!("qwen4exp_gpu: {name} has unsupported float dtype {other}").into()),
14379 }
14380}
14381
14382fn read_i64(name: &str, info: &memra_gguf::safetensors::StInfo, bytes: &[u8]) -> Res<Vec<i64>> {
14383 if info.dtype != "I64" {
14384 return Err(format!("qwen4exp_gpu: {name} must be I64, got {}", info.dtype).into());
14385 }
14386 Ok(bytes
14387 .chunks_exact(8)
14388 .map(|chunk| i64::from_le_bytes(chunk.try_into().unwrap()))
14389 .collect())
14390}
14391
14392fn validate_macro(stem: &str, value: f32) -> Res<()> {
14400 if !(value.is_finite() && value > 0.0) {
14401 return Err(format!(
14402 "qwen4exp_gpu: {stem}.weight_scale_2 carries a non-finite/non-positive \
14403 macro {value}"
14404 )
14405 .into());
14406 }
14407 Ok(())
14408}
14409
14410fn read_bank_tensor(
14415 model: &memra_gguf::safetensors::StModel,
14416 name: &str,
14417 n_expert: usize,
14418 out_f: usize,
14419 in_f: usize,
14420 host_bf16: bool,
14421) -> Res<BankTensorSrc> {
14422 let (info, bytes) = model
14423 .raw(name)
14424 .ok_or_else(|| format!("qwen4exp_gpu: checkpoint is missing {name}"))?;
14425 match info.dtype.as_str() {
14426 "BF16" | "F32" => {
14427 if info.shape != [n_expert as u64, out_f as u64, in_f as u64] {
14428 return Err(format!("qwen4exp_gpu: {name} bank shape mismatch").into());
14429 }
14430 if host_bf16 && info.dtype == "BF16" {
14431 if bytes.len() != n_expert * out_f * in_f * 2 {
14432 return Err(format!("qwen4exp_gpu: {name} bank byte-length mismatch").into());
14433 }
14434 return Ok(BankTensorSrc::Bf16(bytes.to_vec()));
14435 }
14436 Ok(BankTensorSrc::F32(dequant_float(name, info, bytes)?))
14437 }
14438 "U8" => {
14439 if in_f % 16 != 0
14440 || info.shape != [n_expert as u64, out_f as u64, (in_f / 2) as u64]
14441 || bytes.len() != n_expert * out_f * in_f / 2
14442 {
14443 return Err(format!("qwen4exp_gpu: {name} NVFP4 code shape mismatch").into());
14444 }
14445 let stem = name.strip_suffix(".weight").unwrap_or(name);
14446 let scale_name = format!("{stem}.weight_scale");
14447 let (scale_info, scale_bytes) = model
14448 .raw(&scale_name)
14449 .ok_or_else(|| format!("qwen4exp_gpu: missing {scale_name}"))?;
14450 if scale_info.dtype != "F8_E4M3"
14451 || scale_info.shape != [n_expert as u64, out_f as u64, (in_f / 16) as u64]
14452 || scale_bytes.len() != n_expert * out_f * in_f / 16
14453 {
14454 return Err(format!("qwen4exp_gpu: {scale_name} shape mismatch").into());
14455 }
14456 let macros = match model.raw(&format!("{stem}.weight_scale_2")) {
14457 Some((macro_info, macro_bytes))
14458 if macro_info.dtype == "F32" && macro_bytes.len() == n_expert * 4 =>
14459 {
14460 macro_bytes
14461 .chunks_exact(4)
14462 .map(|chunk| f32::from_le_bytes(chunk.try_into().unwrap()))
14463 .collect()
14464 }
14465 None => vec![1.0; n_expert],
14466 _ => return Err(format!("qwen4exp_gpu: {stem}.weight_scale_2 malformed").into()),
14467 };
14468 for &m in ¯os {
14469 validate_macro(stem, m)?;
14470 }
14471 let act_scale = match model.raw(&format!("{stem}.input_scale")) {
14475 Some((is_info, is_bytes))
14476 if is_info.dtype == "F32" && is_bytes.len() == n_expert * 4 =>
14477 {
14478 let mut mx = 0.0f32;
14479 for chunk in is_bytes.chunks_exact(4) {
14480 let v = f32::from_le_bytes(chunk.try_into().unwrap());
14481 if !(v.is_finite() && v > 0.0) {
14482 return Err(format!(
14483 "qwen4exp_gpu: {stem}.input_scale carries a non-finite/\
14484 non-positive value {v}"
14485 )
14486 .into());
14487 }
14488 mx = mx.max(v);
14489 }
14490 Some(mx)
14491 }
14492 Some(_) => {
14493 return Err(format!("qwen4exp_gpu: {stem}.input_scale malformed").into());
14494 }
14495 None => None,
14496 };
14497 Ok(BankTensorSrc::Nvfp4 {
14498 codes: bytes.to_vec(),
14499 scales: scale_bytes.to_vec(),
14500 macros,
14501 act_scale,
14502 })
14503 }
14504 other => Err(format!("qwen4exp_gpu: {name} bank dtype {other} unsupported").into()),
14505 }
14506}
14507
14508fn split_fused_gate_up(
14512 fused: BankTensorSrc,
14513 n_expert: usize,
14514 ff: usize,
14515 hidden: usize,
14516) -> Res<(BankTensorSrc, BankTensorSrc)> {
14517 match fused {
14518 BankTensorSrc::F32(data) => {
14519 if data.len() != n_expert * 2 * ff * hidden {
14520 return Err("qwen4exp_gpu: fused gate_up bank size mismatch".into());
14521 }
14522 let mut gate = Vec::with_capacity(n_expert * ff * hidden);
14523 let mut up = Vec::with_capacity(n_expert * ff * hidden);
14524 for expert in 0..n_expert {
14525 let base = expert * 2 * ff * hidden;
14526 gate.extend_from_slice(&data[base..base + ff * hidden]);
14527 up.extend_from_slice(&data[base + ff * hidden..base + 2 * ff * hidden]);
14528 }
14529 Ok((BankTensorSrc::F32(gate), BankTensorSrc::F32(up)))
14530 }
14531 BankTensorSrc::Bf16(bytes) => {
14532 let row = hidden * 2; if bytes.len() != n_expert * 2 * ff * row {
14534 return Err("qwen4exp_gpu: fused bf16 gate_up bank size mismatch".into());
14535 }
14536 let mut gate = Vec::with_capacity(n_expert * ff * row);
14537 let mut up = Vec::with_capacity(n_expert * ff * row);
14538 for expert in 0..n_expert {
14539 let base = expert * 2 * ff * row;
14540 gate.extend_from_slice(&bytes[base..base + ff * row]);
14541 up.extend_from_slice(&bytes[base + ff * row..base + 2 * ff * row]);
14542 }
14543 Ok((BankTensorSrc::Bf16(gate), BankTensorSrc::Bf16(up)))
14544 }
14545 BankTensorSrc::Nvfp4 {
14546 codes,
14547 scales,
14548 macros,
14549 act_scale,
14550 } => {
14551 let code_row = hidden / 2;
14552 let scale_row = hidden / 16;
14553 let mut gate_codes = Vec::with_capacity(n_expert * ff * code_row);
14554 let mut up_codes = Vec::with_capacity(n_expert * ff * code_row);
14555 let mut gate_scales = Vec::with_capacity(n_expert * ff * scale_row);
14556 let mut up_scales = Vec::with_capacity(n_expert * ff * scale_row);
14557 for expert in 0..n_expert {
14558 let cbase = expert * 2 * ff * code_row;
14559 gate_codes.extend_from_slice(&codes[cbase..cbase + ff * code_row]);
14560 up_codes
14561 .extend_from_slice(&codes[cbase + ff * code_row..cbase + 2 * ff * code_row]);
14562 let sbase = expert * 2 * ff * scale_row;
14563 gate_scales.extend_from_slice(&scales[sbase..sbase + ff * scale_row]);
14564 up_scales
14565 .extend_from_slice(&scales[sbase + ff * scale_row..sbase + 2 * ff * scale_row]);
14566 }
14567 Ok((
14568 BankTensorSrc::Nvfp4 {
14569 codes: gate_codes,
14570 scales: gate_scales,
14571 macros: macros.clone(),
14572 act_scale,
14573 },
14574 BankTensorSrc::Nvfp4 {
14575 codes: up_codes,
14576 scales: up_scales,
14577 macros,
14578 act_scale,
14579 },
14580 ))
14581 }
14582 }
14583}
14584
14585enum PerExpertSrc {
14590 F32(Vec<f32>),
14591 Nvfp4 {
14592 codes: Vec<u8>,
14593 scales: Vec<u8>,
14594 macro_scale: f32,
14595 input_scale: Option<f32>,
14596 },
14597}
14598
14599fn read_per_expert(
14600 model: &memra_gguf::safetensors::StModel,
14601 name: &str,
14602 out_f: usize,
14603 in_f: usize,
14604 quant: memra_gguf::tensor_contract::QuantConstraint,
14605) -> Res<PerExpertSrc> {
14606 use memra_gguf::tensor_contract::QuantConstraint;
14607 let (info, bytes) = model
14608 .raw(name)
14609 .ok_or_else(|| format!("qwen4exp_gpu: checkpoint is missing {name}"))?;
14610 match quant {
14611 QuantConstraint::ExactFloat(_) => {
14612 if info.shape != [out_f as u64, in_f as u64] {
14613 return Err(format!("qwen4exp_gpu: {name} shape mismatch").into());
14614 }
14615 Ok(PerExpertSrc::F32(dequant_float(name, info, bytes)?))
14616 }
14617 QuantConstraint::Nvfp4 => {
14618 if info.dtype != "U8"
14619 || in_f % 16 != 0
14620 || info.shape != [out_f as u64, (in_f / 2) as u64]
14621 || bytes.len() != out_f * in_f / 2
14622 {
14623 return Err(format!("qwen4exp_gpu: {name} NVFP4 code shape mismatch").into());
14624 }
14625 let stem = name.strip_suffix(".weight").unwrap_or(name);
14626 let (scale_info, scale_bytes) = model
14627 .raw(&format!("{stem}.weight_scale"))
14628 .ok_or_else(|| format!("qwen4exp_gpu: missing {stem}.weight_scale"))?;
14629 if scale_info.dtype != "F8_E4M3"
14630 || scale_info.shape != [out_f as u64, (in_f / 16) as u64]
14631 || scale_bytes.len() != out_f * in_f / 16
14632 {
14633 return Err(format!("qwen4exp_gpu: {stem}.weight_scale shape mismatch").into());
14634 }
14635 let macro_scale = match model.raw(&format!("{stem}.weight_scale_2")) {
14636 Some((macro_info, macro_bytes))
14637 if macro_info.dtype == "F32" && macro_bytes.len() == 4 =>
14638 {
14639 f32::from_le_bytes(macro_bytes.try_into().unwrap())
14640 }
14641 None => 1.0,
14642 _ => return Err(format!("qwen4exp_gpu: {stem}.weight_scale_2 malformed").into()),
14643 };
14644 validate_macro(stem, macro_scale)?;
14645 let input_scale = match model.raw(&format!("{stem}.input_scale")) {
14650 Some((input_info, input_bytes)) => {
14651 if input_info.dtype != "F32" || input_bytes.len() != 4 {
14652 return Err(format!("qwen4exp_gpu: {stem}.input_scale malformed").into());
14653 }
14654 let v = f32::from_le_bytes(input_bytes.try_into().unwrap());
14655 if !(v.is_finite() && v > 0.0) {
14656 return Err(format!(
14657 "qwen4exp_gpu: {stem}.input_scale carries a non-finite/non-positive \
14658 value {v}"
14659 )
14660 .into());
14661 }
14662 Some(v)
14663 }
14664 None => None,
14665 };
14666 Ok(PerExpertSrc::Nvfp4 {
14667 codes: bytes.to_vec(),
14668 scales: scale_bytes.to_vec(),
14669 macro_scale,
14670 input_scale,
14671 })
14672 }
14673 other => Err(format!("qwen4exp_gpu: per-expert quant {other:?} unsupported").into()),
14674 }
14675}
14676
14677fn assemble_per_expert_bank(experts: Vec<PerExpertSrc>) -> Res<BankTensorSrc> {
14680 let mut f32_data: Vec<f32> = Vec::new();
14681 let mut codes: Vec<u8> = Vec::new();
14682 let mut scales: Vec<u8> = Vec::new();
14683 let mut macros: Vec<f32> = Vec::new();
14684 let mut act_scale: Option<f32> = None;
14685 let mut act_scale_complete = true;
14686 let mut kinds = (false, false);
14687 for expert in experts {
14688 match expert {
14689 PerExpertSrc::F32(data) => {
14690 kinds.0 = true;
14691 f32_data.extend_from_slice(&data);
14692 }
14693 PerExpertSrc::Nvfp4 {
14694 codes: c,
14695 scales: s,
14696 macro_scale,
14697 input_scale,
14698 } => {
14699 kinds.1 = true;
14700 codes.extend_from_slice(&c);
14701 scales.extend_from_slice(&s);
14702 macros.push(macro_scale);
14703 match input_scale {
14704 Some(v) => act_scale = Some(act_scale.map_or(v, |a: f32| a.max(v))),
14705 None => act_scale_complete = false,
14706 }
14707 }
14708 }
14709 }
14710 match kinds {
14711 (true, false) => Ok(BankTensorSrc::F32(f32_data)),
14712 (false, true) => Ok(BankTensorSrc::Nvfp4 {
14713 codes,
14714 scales,
14715 macros,
14716 act_scale: if act_scale_complete { act_scale } else { None },
14717 }),
14718 _ => Err("qwen4exp_gpu: mixed per-expert kinds within one projection".into()),
14719 }
14720}
14721
14722fn family_layer_index(key: &str) -> Option<u32> {
14724 key.strip_prefix("trunk.layers.")?
14725 .split('.')
14726 .next()?
14727 .parse()
14728 .ok()
14729}
14730
14731fn plan_layer_at(plan: &ModelPlan, index: u32) -> Option<&memra_gguf::model_plan::LayerPlan> {
14739 let n_trunk = plan.layers.len() as u32;
14740 if index < n_trunk {
14741 plan.layers.get(index as usize)
14742 } else {
14743 plan.mtp_blocks
14744 .iter()
14745 .find(|block| block.layer.index == index)
14746 .map(|block| &block.layer)
14747 }
14748}
14749
14750pub fn read_checkpoint(dir: &std::path::Path) -> Res<LoadedCheckpoint> {
14751 read_checkpoint_with(dir, LoadOptions::default())
14752}
14753
14754pub fn read_checkpoint_with(dir: &std::path::Path, opts: LoadOptions) -> Res<LoadedCheckpoint> {
14756 use memra_gguf::model_packs::qwen4_exp::{ExpertDialect, tensor_contract_for};
14757 use memra_gguf::tensor_contract::{TensorMatch, TensorOwner};
14758 let config = std::fs::read_to_string(dir.join("config.json"))?;
14759 let cfg =
14760 memra_gguf::config::ModelConfig::from_hf(&memra_gguf::config::HfConfig::parse(&config));
14761 let pack = memra_gguf::model_packs::for_config(&cfg)
14762 .ok_or("qwen4exp_gpu: no model pack matches this config")?;
14763 if pack.family != "qwen4_exp" {
14764 return Err(format!("qwen4exp_gpu: config resolves to pack {}", pack.family).into());
14765 }
14766 let plan = pack.compile_plan(&cfg)?;
14767 let model = memra_gguf::safetensors::StModel::open(dir)?;
14768 let dialect = if model
14770 .raw("model.language_model.layers.0.mlp.experts.0.gate_proj.weight")
14771 .is_some()
14772 {
14773 ExpertDialect::PerExpertModelopt
14774 } else {
14775 ExpertDialect::FusedBanks
14776 };
14777 let contract = tensor_contract_for(&cfg, &plan, dialect)?;
14778
14779 let mut weights = ReferenceWeights::new();
14780 let mut gate_up_banks: std::collections::BTreeMap<u32, BankTensorSrc> = Default::default();
14781 let mut per_expert: std::collections::BTreeMap<
14785 (u32, u8),
14786 std::collections::BTreeMap<u32, PerExpertSrc>,
14787 > = Default::default();
14788 let mut down_banks: std::collections::BTreeMap<u32, BankTensorSrc> = Default::default();
14789 let mut tables: std::collections::BTreeMap<u32, Vec<u8>> = Default::default();
14790 let n_trunk = plan.layers.len() as u32;
14791
14792 for requirement in &contract.requirements {
14793 match requirement.owner {
14794 TensorOwner::Mtp(_) if !opts.load_mtp => continue,
14798 TensorOwner::Vision(_) => continue,
14799 TensorOwner::Global | TensorOwner::Layer(_) | TensorOwner::Mtp(_) => {}
14800 }
14801 if requirement.match_mode == TensorMatch::All {
14803 let TensorId::Family { key, .. } = &requirement.id else {
14804 return Err("qwen4exp_gpu: unexpected All-mode requirement".into());
14805 };
14806 let layer =
14807 family_layer_index(key).ok_or("qwen4exp_gpu: n-gram bank outside a trunk layer")?;
14808 let mut bytes = Vec::new();
14809 for name in &requirement.names {
14810 let (info, shard) = model
14811 .raw(name)
14812 .ok_or_else(|| format!("qwen4exp_gpu: checkpoint is missing {name}"))?;
14813 if info.dtype != "BF16" || info.shape != requirement.shape {
14814 return Err(format!("qwen4exp_gpu: {name} shard shape/dtype mismatch").into());
14815 }
14816 bytes.extend_from_slice(shard);
14817 }
14818 tables.insert(layer, bytes);
14819 continue;
14820 }
14821 let name = &requirement.names[0];
14822 if let TensorId::Family { key, .. } = &requirement.id {
14825 if key.ends_with(".ple_embedding.ngram_embedding") {
14826 let layer = family_layer_index(key)
14827 .ok_or("qwen4exp_gpu: n-gram table outside a trunk layer")?;
14828 let (info, bytes) = model
14829 .raw(name)
14830 .ok_or_else(|| format!("qwen4exp_gpu: checkpoint is missing {name}"))?;
14831 if info.dtype != "BF16" || info.shape != requirement.shape {
14832 return Err(format!("qwen4exp_gpu: {name} table shape/dtype mismatch").into());
14833 }
14834 tables.insert(layer, bytes.to_vec());
14835 continue;
14836 }
14837 }
14838 if let TensorId::Expert {
14840 layer,
14841 expert,
14842 tensor,
14843 } = requirement.id
14844 {
14845 let (out_f, in_f) = (requirement.shape[0] as usize, requirement.shape[1] as usize);
14846 let src = read_per_expert(&model, name, out_f, in_f, requirement.quant)?;
14847 let proj = match tensor {
14848 memra_gguf::tensor_contract::ExpertTensor::Gate => 0u8,
14849 memra_gguf::tensor_contract::ExpertTensor::Up => 1,
14850 memra_gguf::tensor_contract::ExpertTensor::Down => 2,
14851 };
14852 if per_expert
14853 .entry((layer, proj))
14854 .or_default()
14855 .insert(expert, src)
14856 .is_some()
14857 {
14858 return Err(format!(
14859 "qwen4exp_gpu: duplicate per-expert row layer {layer} expert {expert}"
14860 )
14861 .into());
14862 }
14863 continue;
14864 }
14865 if let TensorId::Layer { index, tensor } = requirement.id {
14867 if matches!(
14868 tensor,
14869 LayerTensor::MoeExpertGateUpBank | LayerTensor::MoeExpertDownBank
14870 ) {
14871 let (n_expert, out_f, in_f) = (
14872 requirement.shape[0] as usize,
14873 requirement.shape[1] as usize,
14874 requirement.shape[2] as usize,
14875 );
14876 let keep_bf16 = opts.host_bf16_banks || index >= n_trunk;
14879 let bank = read_bank_tensor(&model, name, n_expert, out_f, in_f, keep_bf16)?;
14880 if tensor == LayerTensor::MoeExpertGateUpBank {
14881 gate_up_banks.insert(index, bank);
14882 } else {
14883 down_banks.insert(index, bank);
14884 }
14885 continue;
14886 }
14887 }
14888 let (info, bytes) = model
14889 .raw(name)
14890 .ok_or_else(|| format!("qwen4exp_gpu: checkpoint is missing {name}"))?;
14891 if info.shape != requirement.shape {
14892 return Err(format!(
14893 "qwen4exp_gpu: {name} shape {:?} != contract {:?}",
14894 info.shape, requirement.shape
14895 )
14896 .into());
14897 }
14898 if info.dtype == "I64" {
14899 let ints = read_i64(name, info, bytes)?;
14900 let shape: Vec<usize> = info.shape.iter().map(|&d| d as usize).collect();
14901 weights.insert(
14902 requirement.id.clone(),
14903 ReferenceTensor::new_i64(shape, ints)?,
14904 );
14905 continue;
14906 }
14907 let mut data = dequant_float(name, info, bytes)?;
14908 if norm_fold_add_one(name) && !(opts.indexer_norm_raw && indexer_layernorm(name)) {
14909 for value in &mut data {
14910 *value += 1.0;
14911 }
14912 }
14913 let kind = bridge_transform(requirement.transform)?;
14914 let (ne_out, out_bytes) = kind.apply(&mut data, info.ne(), &cfg);
14915 let data: Vec<f32> = out_bytes
14916 .chunks_exact(4)
14917 .map(|chunk| f32::from_le_bytes(chunk.try_into().unwrap()))
14918 .collect();
14919 let mut shape: Vec<usize> = ne_out.iter().rev().map(|&d| d as usize).collect();
14920 if name.ends_with("ple.conv1d.weight") && shape.len() == 3 && shape[1] == 1 {
14923 shape = vec![shape[0], shape[2]];
14924 }
14925 if name.ends_with("mlp.shared_expert_gate.weight") && shape.len() == 2 && shape[0] == 1 {
14927 shape = vec![shape[1]];
14928 }
14929 weights.insert(requirement.id.clone(), ReferenceTensor::new(shape, data)?);
14930 }
14931
14932 let mut banks = std::collections::BTreeMap::new();
14933 for (index, gate_up) in gate_up_banks {
14936 let down = down_banks
14937 .remove(&index)
14938 .ok_or_else(|| format!("qwen4exp_gpu: layer {index} has gate_up but no down bank"))?;
14939 let layer_plan = plan_layer_at(&plan, index)
14940 .ok_or_else(|| format!("qwen4exp_gpu: bank at unknown layer index {index}"))?;
14941 let MlpPlan::Moe(moe) = &layer_plan.mlp else {
14942 return Err(format!("qwen4exp_gpu: bank on non-MoE layer {index}").into());
14943 };
14944 let (n_expert, ff) = (
14945 moe.expert_count as usize,
14946 moe.expert_intermediate_size as usize,
14947 );
14948 let hidden = plan.hidden_size as usize;
14949 let (gate, up) = split_fused_gate_up(gate_up, n_expert, ff, hidden)?;
14950 banks.insert(
14951 index,
14952 BankSrc {
14953 gate,
14954 up,
14955 down,
14956 n_expert,
14957 ff,
14958 hidden,
14959 },
14960 );
14961 }
14962 if !down_banks.is_empty() {
14963 return Err("qwen4exp_gpu: down bank without a gate_up twin".into());
14964 }
14965 let mut per_layer: std::collections::BTreeMap<u32, [Option<BankTensorSrc>; 3]> =
14967 Default::default();
14968 for ((layer, proj), experts) in per_expert {
14969 let layer_plan = plan_layer_at(&plan, layer)
14970 .ok_or_else(|| format!("qwen4exp_gpu: per-expert rows at unknown layer {layer}"))?;
14971 let MlpPlan::Moe(moe) = &layer_plan.mlp else {
14972 return Err(format!("qwen4exp_gpu: per-expert rows on non-MoE layer {layer}").into());
14973 };
14974 let count = moe.expert_count as usize;
14975 if experts.len() != count || experts.keys().last().copied() != Some(count as u32 - 1) {
14978 return Err(format!(
14979 "qwen4exp_gpu: layer {layer} proj {proj} has {} experts, plan says {count}",
14980 experts.len()
14981 )
14982 .into());
14983 }
14984 per_layer.entry(layer).or_default()[proj as usize] =
14985 Some(assemble_per_expert_bank(experts.into_values().collect())?);
14986 }
14987 for (layer, mut projections) in per_layer {
14988 let MlpPlan::Moe(moe) = &plan_layer_at(&plan, layer).expect("checked above").mlp else {
14989 unreachable!("checked above");
14990 };
14991 let take = |slot: &mut Option<BankTensorSrc>, what: &str| -> Res<BankTensorSrc> {
14992 slot.take()
14993 .ok_or_else(|| format!("qwen4exp_gpu: layer {layer} missing {what} experts").into())
14994 };
14995 banks.insert(
14996 layer,
14997 BankSrc {
14998 gate: take(&mut projections[0], "gate")?,
14999 up: take(&mut projections[1], "up")?,
15000 down: take(&mut projections[2], "down")?,
15001 n_expert: moe.expert_count as usize,
15002 ff: moe.expert_intermediate_size as usize,
15003 hidden: plan.hidden_size as usize,
15004 },
15005 );
15006 }
15007 Ok(LoadedCheckpoint {
15008 plan,
15009 weights,
15010 banks,
15011 tables,
15012 })
15013}
15014
15015impl LoadedCheckpoint {
15016 pub fn into_reference_weights(mut self) -> Res<ReferenceWeights> {
15020 for (index, bank) in self.banks {
15021 let gate = bank_to_f32(&bank.gate, bank.n_expert, bank.ff, bank.hidden)?;
15022 let up = bank_to_f32(&bank.up, bank.n_expert, bank.ff, bank.hidden)?;
15023 let down = bank_to_f32(&bank.down, bank.n_expert, bank.hidden, bank.ff)?;
15024 self.weights.insert(
15025 layer_id(index, LayerTensor::MoeExpertGateBank),
15026 ReferenceTensor::new(vec![bank.n_expert, bank.ff, bank.hidden], gate)?,
15027 );
15028 self.weights.insert(
15029 layer_id(index, LayerTensor::MoeExpertUpBank),
15030 ReferenceTensor::new(vec![bank.n_expert, bank.ff, bank.hidden], up)?,
15031 );
15032 self.weights.insert(
15033 layer_id(index, LayerTensor::MoeExpertDownBank),
15034 ReferenceTensor::new(vec![bank.n_expert, bank.hidden, bank.ff], down)?,
15035 );
15036 }
15037 for (index, bytes) in self.tables {
15038 let ple = self.plan.layers[index as usize]
15039 .ple
15040 .as_ref()
15041 .ok_or("qwen4exp_gpu: table on a non-PLE layer")?;
15042 let head_dim = ple.head_embed_dim as usize;
15043 let table = NgramTable::Bf16(bytes);
15044 let rows = table.rows(head_dim);
15045 let mut data = vec![0.0f32; rows * head_dim];
15046 for row in 0..rows {
15047 table.gather_into(
15048 row,
15049 head_dim,
15050 &mut data[row * head_dim..(row + 1) * head_dim],
15051 );
15052 }
15053 self.weights.insert(
15054 family_id(format!(
15055 "trunk.layers.{index}.ple.ple_embedding.ngram_embedding"
15056 )),
15057 ReferenceTensor::new(vec![rows, head_dim], data)?,
15058 );
15059 }
15060 Ok(self.weights)
15061 }
15062}
15063
15064impl LoadedCheckpoint {
15065 pub fn mtp_reference_weights(&self) -> Res<ReferenceWeights> {
15071 let mut weights = self.weights.clone();
15072 let n_trunk = self.plan.layers.len() as u32;
15073 for (index, bank) in &self.banks {
15074 if *index < n_trunk {
15075 continue;
15076 }
15077 let gate = bank_to_f32(&bank.gate, bank.n_expert, bank.ff, bank.hidden)?;
15078 let up = bank_to_f32(&bank.up, bank.n_expert, bank.ff, bank.hidden)?;
15079 let down = bank_to_f32(&bank.down, bank.n_expert, bank.hidden, bank.ff)?;
15080 weights.insert(
15081 layer_id(*index, LayerTensor::MoeExpertGateBank),
15082 ReferenceTensor::new(vec![bank.n_expert, bank.ff, bank.hidden], gate)?,
15083 );
15084 weights.insert(
15085 layer_id(*index, LayerTensor::MoeExpertUpBank),
15086 ReferenceTensor::new(vec![bank.n_expert, bank.ff, bank.hidden], up)?,
15087 );
15088 weights.insert(
15089 layer_id(*index, LayerTensor::MoeExpertDownBank),
15090 ReferenceTensor::new(vec![bank.n_expert, bank.hidden, bank.ff], down)?,
15091 );
15092 }
15093 Ok(weights)
15094 }
15095}
15096
15097fn bank_to_f32(bank: &BankTensorSrc, n_expert: usize, out_f: usize, in_f: usize) -> Res<Vec<f32>> {
15100 match bank {
15101 BankTensorSrc::F32(data) => Ok(data.clone()),
15102 BankTensorSrc::Bf16(bytes) => Ok(bytes
15103 .chunks_exact(2)
15104 .map(|b| f32::from_bits(u32::from(u16::from_le_bytes([b[0], b[1]])) << 16))
15105 .collect()),
15106 BankTensorSrc::Nvfp4 {
15107 codes,
15108 scales,
15109 macros,
15110 ..
15111 } => {
15112 let mut out = Vec::with_capacity(n_expert * out_f * in_f);
15113 let wbytes = out_f * in_f / 2;
15114 let sbytes = out_f * in_f / 16;
15115 for expert in 0..n_expert {
15116 out.extend(memra_gguf::dsv4::dequant_nvfp4_expert(
15117 &codes[expert * wbytes..(expert + 1) * wbytes],
15118 &scales[expert * sbytes..(expert + 1) * sbytes],
15119 macros[expert],
15120 out_f,
15121 in_f,
15122 ));
15123 }
15124 Ok(out)
15125 }
15126 }
15127}
15128
15129impl Qwen4ExpGpu {
15130 pub fn load_from_dir(e: &Engine, dir: &std::path::Path) -> Res<Self> {
15135 Self::from_loaded_checkpoint(e, read_checkpoint(dir)?)
15136 }
15137
15138 pub fn load_from_dir_with(e: &Engine, dir: &std::path::Path, opts: LoadOptions) -> Res<Self> {
15140 Self::from_loaded_checkpoint(e, read_checkpoint_with(dir, opts)?)
15141 }
15142
15143 pub fn load_from_dir_dev1(
15148 e: &Engine,
15149 draft_e: &Engine,
15150 dir: &std::path::Path,
15151 opts: LoadOptions,
15152 ) -> Res<Self> {
15153 Self::from_loaded_checkpoint_dual(e, Some(draft_e), read_checkpoint_with(dir, opts)?)
15154 }
15155
15156 pub fn from_loaded_checkpoint(e: &Engine, checkpoint: LoadedCheckpoint) -> Res<Self> {
15159 Self::from_loaded_checkpoint_dual(e, None, checkpoint)
15160 }
15161
15162 pub fn from_loaded_checkpoint_dual(
15166 e: &Engine,
15167 draft_e: Option<&Engine>,
15168 checkpoint: LoadedCheckpoint,
15169 ) -> Res<Self> {
15170 let LoadedCheckpoint {
15171 plan,
15172 weights,
15173 banks,
15174 tables,
15175 } = checkpoint;
15176 let mut parts = ExternalParts::default();
15177 let n_trunk = plan.layers.len() as u32;
15178 let upload_half = |e: &Engine, src: BankTensorSrc, device_bf16: bool| -> Res<BankHalf> {
15179 Ok(match src {
15180 BankTensorSrc::F32(data) => BankHalf::F32(e.htod(&data)?),
15181 BankTensorSrc::Nvfp4 {
15182 codes,
15183 scales,
15184 macros,
15185 ..
15186 } => BankHalf::Nvfp4 {
15187 codes: e.htod_bytes(&codes)?,
15188 scales: e.htod_bytes(&scales)?,
15189 macros_dev: e.htod(¯os)?,
15190 macros,
15191 },
15192 BankTensorSrc::Bf16(bytes) if device_bf16 => {
15196 BankHalf::DeviceBf16(e.htod_bytes(&bytes)?)
15197 }
15198 BankTensorSrc::Bf16(bytes) => BankHalf::HostBf16(bytes),
15199 })
15200 };
15201 for (index, bank) in banks {
15202 let device_bf16 = index >= n_trunk;
15203 let bank_e = if device_bf16 { draft_e.unwrap_or(e) } else { e };
15205 parts.expert_banks.insert(
15206 index,
15207 ExpertBank {
15208 gate: upload_half(bank_e, bank.gate, device_bf16)?,
15209 up: upload_half(bank_e, bank.up, device_bf16)?,
15210 down: upload_half(bank_e, bank.down, device_bf16)?,
15211 },
15212 );
15213 }
15214 for (index, bytes) in tables {
15215 parts.ngram_tables.insert(index, NgramTable::Bf16(bytes));
15216 }
15217 Self::from_reference_weights_with(e, draft_e, &plan, &weights, parts)
15218 }
15219}
15220
15221struct GdnHalfW {
15259 nk_h: usize,
15260 nv_h: usize,
15261 hk: usize,
15262 hv: usize,
15263 kernel: usize,
15264 gate_activation: GdnGateActivation,
15265 proj_b16: CudaSlice<u8>,
15268 out_b16: CudaSlice<u8>, conv_w: CudaSlice<f32>, a: CudaSlice<f32>, dt: CudaSlice<f32>, norm: CudaSlice<f32>, }
15274
15275struct QsaHalfW {
15277 nh_h: usize,
15278 nkv_h: usize,
15279 hd: usize,
15280 n_rot: usize,
15281 rope_base: f32,
15282 scale: f32,
15283 proj_b16: CudaSlice<u8>,
15286 wo_b16: CudaSlice<u8>, q_norm: Option<CudaSlice<f32>>,
15288 k_norm: Option<CudaSlice<f32>>,
15289 yarn: Option<YarnRopeW>,
15291}
15292
15293enum MixerHalfW {
15294 Gdn(GdnHalfW),
15295 Qsa(QsaHalfW),
15296}
15297
15298struct Nvfp4Half {
15300 codes: CudaSlice<u8>,
15301 scales: CudaSlice<u8>,
15302 macros_dev: CudaSlice<f32>,
15303}
15304
15305struct MoeHalfW {
15306 gate1: Nvfp4Half,
15308 up1: Nvfp4Half,
15309 down1: Nvfp4Half,
15310 shared_down0: CudaSlice<u8>, shared_down1: CudaSlice<u8>, shared_input_gate1: Option<CudaSlice<f32>>, shared_gu0_b16: CudaSlice<u8>,
15319 shared_gu1_b16: CudaSlice<u8>,
15320}
15321
15322struct Tp2LayerW {
15323 attn_gate1: GateW,
15324 mlp_gate1: GateW,
15325 mixer0: MixerHalfW,
15326 mixer1: MixerHalfW,
15327 moe: MoeHalfW,
15328 ple1: Option<PleW>,
15329 place: LayerPlacement,
15334}
15335
15336pub struct Tp2Shard {
15338 layers: Vec<Tp2LayerW>,
15339 exit_gate1: GateW,
15340 lm_head1: CudaSlice<u8>, vsplit: usize,
15342 stage0: [CudaSlice<f32>; 2], stage1: [CudaSlice<f32>; 2], stage0_raw: [u64; 2],
15349 stage1_raw: [u64; 2],
15350 ev0: [cudarc::driver::CudaEvent; 2], ev1: [cudarc::driver::CudaEvent; 2], }
15353
15354enum MixerHalfState {
15355 Gdn {
15356 conv: CudaSlice<f32>, state: CudaSlice<f32>, },
15359 Qsa {
15360 kv: QsaKvStore,
15364 },
15365}
15366
15367struct Tp2LayerState {
15368 m0: MixerHalfState,
15369 m1: MixerHalfState,
15370 ple1: Option<PleState>,
15371}
15372
15373struct Tp2State {
15374 ws1: StepPool,
15375 layers: Vec<Tp2LayerState>,
15376 graphs: Tp2Graphs,
15377 pf_stage0: Option<[CudaSlice<f32>; 2]>, pf_stage1: Option<[CudaSlice<f32>; 2]>,
15383 pf_stage0_raw: [u64; 2],
15384 pf_stage1_raw: [u64; 2],
15385 pf_rows: usize,
15386}
15387
15388#[derive(Default)]
15397struct Tp2Graphs {
15398 warm: bool,
15399 a: [Vec<Option<GraphEntry>>; 2],
15400 b: [Vec<Option<GraphEntry>>; 2],
15401 c: [Vec<Option<GraphEntry>>; 2],
15404 d: [Vec<Option<GraphEntry>>; 2],
15406 exit: [Option<GraphEntry>; 2],
15407}
15408
15409fn tp2_gdn_head_map(d: usize, nk: usize, nv: usize) -> Vec<usize> {
15413 let nk_h = nk / 2;
15414 let nv_h = nv / 2;
15415 (0..nv_h)
15416 .map(|j| (j / nk_h) * nk + (j % nk_h) + d * nk_h)
15417 .collect()
15418}
15419
15420fn gather_rows_host(src: &[f32], in_f: usize, rows: &[usize]) -> Vec<f32> {
15422 let mut out = Vec::with_capacity(rows.len() * in_f);
15423 for &r in rows {
15424 out.extend_from_slice(&src[r * in_f..(r + 1) * in_f]);
15425 }
15426 out
15427}
15428
15429fn gather_cols_host(
15431 src: &[f32],
15432 nrows: usize,
15433 ncols: usize,
15434 blocks: &[(usize, usize)],
15435) -> Vec<f32> {
15436 let width: usize = blocks.iter().map(|&(_, l)| l).sum();
15437 let mut out = Vec::with_capacity(nrows * width);
15438 for r in 0..nrows {
15439 for &(start, len) in blocks {
15440 out.extend_from_slice(&src[r * ncols + start..r * ncols + start + len]);
15441 }
15442 }
15443 out
15444}
15445
15446fn need_twin(e: &Engine, data: &[f32], in_f: usize, what: &str) -> Res<CudaSlice<u8>> {
15447 bf16_twin(e, data, in_f)?.ok_or_else(|| {
15448 format!("qwen4exp_gpu tp2: {what} has no exact bf16 twin (in_f {in_f})").into()
15449 })
15450}
15451
15452fn launch_push(e: &Engine, src: &CudaSlice<f32>, dst_raw: u64, n: usize) -> Res<()> {
15455 let f = e.func("q4e_push_f32");
15456 let cfg = LaunchConfig::for_num_elems(n as u32);
15457 let nl = n as i64;
15458 let stream = e.gpu.stream();
15459 let mut b = stream.launch_builder(&f);
15460 b.arg(src).arg(&dst_raw).arg(&nl);
15461 unsafe {
15462 b.launch(cfg)?;
15463 }
15464 Ok(())
15465}
15466
15467pub fn tp2_enable_p2p(e0: &Engine, e1: &Engine) -> Res<()> {
15471 use cudarc::driver::sys;
15472 for (src, dst) in [(e0, e1), (e1, e0)] {
15473 let mut can = 0i32;
15474 unsafe {
15475 sys::cuDeviceCanAccessPeer(&mut can, src.ctx().cu_device(), dst.ctx().cu_device())
15476 .result()?;
15477 }
15478 if can == 0 {
15479 return Err(format!(
15480 "qwen4exp_gpu tp2: dev{} cannot access dev{} over P2P",
15481 src.ctx().ordinal(),
15482 dst.ctx().ordinal()
15483 )
15484 .into());
15485 }
15486 src.ctx().bind_to_thread()?;
15487 let rc = unsafe { sys::cuCtxEnablePeerAccess(dst.ctx().cu_ctx(), 0) };
15488 use cudarc::driver::sys::cudaError_enum as E;
15489 if rc != E::CUDA_SUCCESS && rc != E::CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED {
15490 return Err(format!("qwen4exp_gpu tp2: cuCtxEnablePeerAccess failed: {rc:?}").into());
15491 }
15492 }
15493 for (owner, accessor) in [(e0, e1), (e1, e0)] {
15494 let device = cudarc::driver::result::device::get(owner.ctx().ordinal() as i32)?;
15495 let mut pool: sys::CUmemoryPool = std::ptr::null_mut();
15496 unsafe {
15497 sys::cuDeviceGetDefaultMemPool(&mut pool, device).result()?;
15498 }
15499 let desc = sys::CUmemAccessDesc {
15500 location: sys::CUmemLocation {
15501 type_: sys::CUmemLocationType::CU_MEM_LOCATION_TYPE_DEVICE,
15502 id: accessor.ctx().ordinal() as i32,
15503 },
15504 flags: sys::CUmemAccess_flags::CU_MEM_ACCESS_FLAGS_PROT_READWRITE,
15505 };
15506 let rc = unsafe { sys::cuMemPoolSetAccess(pool, &desc, 1) };
15507 if rc != sys::cudaError_enum::CUDA_SUCCESS {
15508 return Err(format!("qwen4exp_gpu tp2: cuMemPoolSetAccess failed: {rc:?}").into());
15509 }
15510 }
15511 Ok(())
15512}
15513
15514fn build_ple_replica(
15518 e: &Engine,
15519 weights: &ReferenceWeights,
15520 prefix: &str,
15521 ple_plan: &PleEmbeddingPlan,
15522 streams: usize,
15523 hidden: usize,
15524) -> Res<PleW> {
15525 let embed_dim = ple_plan.embed_dim as usize;
15526 let key_proj = expect(weights, &family_id(format!("{prefix}ple.key_proj.weight")))?;
15527 let conv_w = expect(weights, &family_id(format!("{prefix}ple.conv1d.weight")))?;
15528 let norm_slices = |name: &str| -> Res<Vec<CudaSlice<f32>>> {
15529 let t = expect(weights, &family_id(format!("{prefix}ple.{name}.weight")))?;
15530 split_rows(&t.data, streams, hidden, 1)
15531 .into_iter()
15532 .map(|v| e.htod(&v))
15533 .collect::<Result<_, _>>()
15534 .map_err(Into::into)
15535 };
15536 let ints = |name: &str| -> Res<Vec<i64>> {
15537 let t = expect(
15538 weights,
15539 &family_id(format!("{prefix}ple.ple_embedding.{name}")),
15540 )?;
15541 t.ints
15542 .clone()
15543 .ok_or_else(|| "qwen4exp_gpu: n-gram buffer must be I64".into())
15544 };
15545 Ok(PleW {
15546 plan: *ple_plan,
15547 key_proj: split_rows(&key_proj.data, streams, hidden, embed_dim)
15548 .into_iter()
15549 .map(|v| e.htod(&v))
15550 .collect::<Result<_, _>>()?,
15551 value_proj: upload(
15552 e,
15553 &expect(
15554 weights,
15555 &family_id(format!("{prefix}ple.value_proj.weight")),
15556 )?,
15557 )?,
15558 norm_key: norm_slices("norm_key")?,
15559 norm_query: norm_slices("norm_query")?,
15560 norm_conv: norm_slices("norm_conv")?,
15561 conv_w: split_rows(&conv_w.data, streams, hidden, ple_plan.conv_kernel as usize)
15562 .into_iter()
15563 .map(|v| e.htod(&v))
15564 .collect::<Result<_, _>>()?,
15565 multipliers: ints("layer_multipliers")?,
15566 sizes: ints("ngram_heads_vocab_sizes")?,
15567 offsets: ints("ngram_heads_offsets")?,
15568 table: NgramTable::F32(Vec::new()), })
15570}
15571
15572#[allow(clippy::too_many_arguments)]
15574fn build_gdn_half(
15575 e: &Engine,
15576 weights: &ReferenceWeights,
15577 index: u32,
15578 gdn: &GatedDeltaNetPlan,
15579 hidden: usize,
15580 d: usize,
15581) -> Res<GdnHalfW> {
15582 let (nk, nv) = (gdn.key_heads as usize, gdn.value_heads as usize);
15583 let (hk, hv) = (gdn.key_head_dim as usize, gdn.value_head_dim as usize);
15584 if nk % 2 != 0 || nv % nk != 0 {
15585 return Err(format!(
15586 "qwen4exp_gpu tp2: GDN layer {index} nk {nk} / nv {nv} does not split by key-head halves"
15587 )
15588 .into());
15589 }
15590 let (nk_h, nv_h) = (nk / 2, nv / 2);
15591 let head_map = tp2_gdn_head_map(d, nk, nv);
15592 let qkv = expect(weights, &layer_id(index, LayerTensor::GdnQkv))?;
15593 let z = expect(weights, &layer_id(index, LayerTensor::GdnGate))?;
15594 let beta = expect(weights, &layer_id(index, LayerTensor::GdnBeta))?;
15595 let alpha = expect(weights, &layer_id(index, LayerTensor::GdnAlpha))?;
15596 let out = expect(weights, &layer_id(index, LayerTensor::GdnOutput))?;
15597 let conv_w = expect(weights, &layer_id(index, LayerTensor::GdnConv1d))?;
15598 let a = expect(weights, &layer_id(index, LayerTensor::GdnA))?;
15599 let dt = expect(weights, &layer_id(index, LayerTensor::GdnDtBias))?;
15600 let norm = expect(weights, &layer_id(index, LayerTensor::GdnNorm))?;
15601 let kernel = gdn.conv_kernel as usize;
15602 let mut qkv_rows: Vec<usize> = Vec::with_capacity(2 * nk_h * hk + nv_h * hv);
15604 qkv_rows.extend(d * nk_h * hk..(d + 1) * nk_h * hk);
15605 qkv_rows.extend(nk * hk + d * nk_h * hk..nk * hk + (d + 1) * nk_h * hk);
15606 for &hm in &head_map {
15607 qkv_rows.extend(2 * nk * hk + hm * hv..2 * nk * hk + (hm + 1) * hv);
15608 }
15609 let mut z_rows: Vec<usize> = Vec::with_capacity(nv_h * hv);
15610 for &hm in &head_map {
15611 z_rows.extend(hm * hv..(hm + 1) * hv);
15612 }
15613 let out_blocks: Vec<(usize, usize)> = head_map.iter().map(|&hm| (hm * hv, hv)).collect();
15614 let qkv_c = gather_rows_host(&qkv.data, hidden, &qkv_rows);
15615 let z_c = gather_rows_host(&z.data, hidden, &z_rows);
15616 let beta_c = gather_rows_host(&beta.data, hidden, &head_map);
15617 let alpha_c = gather_rows_host(&alpha.data, hidden, &head_map);
15618 let out_c = gather_cols_host(&out.data, hidden, nv * hv, &out_blocks);
15619 let conv_c = gather_rows_host(&conv_w.data, kernel, &qkv_rows);
15620 let a_c: Vec<f32> = head_map.iter().map(|&hm| a.data[hm]).collect();
15621 let dt_c: Vec<f32> = head_map.iter().map(|&hm| dt.data[hm]).collect();
15622 Ok(GdnHalfW {
15623 nk_h,
15624 nv_h,
15625 hk,
15626 hv,
15627 kernel,
15628 gate_activation: gdn.gate_activation,
15629 proj_b16: need_stack_twin(
15630 e,
15631 &[&qkv_c, &z_c, &beta_c, &alpha_c],
15632 hidden,
15633 "tp2 gdn proj half",
15634 )?,
15635 out_b16: need_twin(e, &out_c, nv_h * hv, "tp2 gdn out half")?,
15636 conv_w: e.htod(&conv_c)?,
15637 a: e.htod(&a_c)?,
15638 dt: e.htod(&dt_c)?,
15639 norm: e.htod(&norm.data)?,
15640 })
15641}
15642
15643#[allow(clippy::too_many_arguments)]
15645fn build_qsa_half(
15646 e: &Engine,
15647 weights: &ReferenceWeights,
15648 index: u32,
15649 attn: &FullAttentionPlan,
15650 hidden: usize,
15651 d: usize,
15652) -> Res<QsaHalfW> {
15653 let nh = attn.query_heads as usize;
15654 let nkv = attn.kv_heads as usize;
15655 let hd = attn.key_head_dim as usize;
15656 if nh % 2 != 0 || nkv % 2 != 0 || nh % nkv != 0 {
15657 return Err(format!(
15658 "qwen4exp_gpu tp2: QSA layer {index} heads {nh}/{nkv} do not split in halves"
15659 )
15660 .into());
15661 }
15662 let (nh_h, nkv_h) = (nh / 2, nkv / 2);
15663 let wq = expect(weights, &layer_id(index, LayerTensor::Query))?;
15664 let wk = expect(weights, &layer_id(index, LayerTensor::Key))?;
15665 let wv = expect(weights, &layer_id(index, LayerTensor::Value))?;
15666 let wo = expect(weights, &layer_id(index, LayerTensor::AttentionOutput))?;
15667 let q_rows: Vec<usize> = (d * nh_h * 2 * hd..(d + 1) * nh_h * 2 * hd).collect();
15669 let kv_rows: Vec<usize> = (d * nkv_h * hd..(d + 1) * nkv_h * hd).collect();
15670 let wq_c = gather_rows_host(&wq.data, hidden, &q_rows);
15671 let wk_c = gather_rows_host(&wk.data, hidden, &kv_rows);
15672 let wv_c = gather_rows_host(&wv.data, hidden, &kv_rows);
15673 let wo_c = gather_cols_host(&wo.data, hidden, nh * hd, &[(d * nh_h * hd, nh_h * hd)]);
15674 let opt_norm = |tensor: LayerTensor| -> Res<Option<CudaSlice<f32>>> {
15675 match weights.get(&layer_id(index, tensor)) {
15676 Some(t) => Ok(Some(e.htod(&t.data)?)),
15677 None => Ok(None),
15678 }
15679 };
15680 let scale = match attn.scale {
15681 memra_gguf::model_plan::AttentionScale::InverseSqrtKeyDim => 1.0 / (hd as f32).sqrt(),
15682 memra_gguf::model_plan::AttentionScale::Fixed(scale) => scale,
15683 };
15684 Ok(QsaHalfW {
15685 nh_h,
15686 nkv_h,
15687 hd,
15688 n_rot: attn.rope.dimensions as usize,
15689 rope_base: attn.rope.base,
15690 scale,
15691 proj_b16: need_stack_twin(e, &[&wq_c, &wk_c, &wv_c], hidden, "tp2 qsa proj half")?,
15692 wo_b16: need_twin(e, &wo_c, nh_h * hd, "tp2 qsa o half")?,
15693 q_norm: opt_norm(LayerTensor::QueryNorm)?,
15694 k_norm: opt_norm(LayerTensor::KeyNorm)?,
15695 yarn: build_yarn(e, &attn.rope, None, index)?,
15697 })
15698}
15699
15700pub fn build_tp2_shard(e0: &Engine, e1: &Engine, ckpt: &LoadedCheckpoint) -> Res<Tp2Shard> {
15704 let plan = &ckpt.plan;
15705 let weights = &ckpt.weights;
15706 let hidden = plan.hidden_size as usize;
15707 let vocab = plan.vocab_size as usize;
15708 if vocab % 2 != 0 {
15709 return Err("qwen4exp_gpu tp2: odd vocab".into());
15710 }
15711 let mixer_plan = plan
15712 .exit_mixer
15713 .ok_or("qwen4exp_gpu tp2: missing exit mixer")?;
15714 let streams = mixer_plan.streams as usize;
15715 let rank = mixer_plan.bottleneck_rank as usize;
15716 let plan_experts = plan
15719 .layers
15720 .iter()
15721 .find_map(|l| match &l.mlp {
15722 MlpPlan::Moe(m) => Some(m.expert_count as usize),
15723 _ => None,
15724 })
15725 .ok_or("qwen4exp_gpu tp2: no MoE layer in the plan")?;
15726 let placement = match Tp2Placement::from_env(plan_experts)? {
15727 Some(p) => p,
15728 None => Tp2Placement::even(plan_experts),
15729 };
15730 println!(
15731 "# tp2-placement\tstrategy={}\tentry_rank={}\texperts={plan_experts}\tsource={}",
15732 placement.strategy(),
15733 placement.entry_rank(),
15734 placement.source()
15735 );
15736 let mut layers = Vec::with_capacity(plan.layers.len());
15737 for layer in &plan.layers {
15738 let prefix = format!("trunk.layers.{}.", layer.index);
15739 let _g1 = e1.gpu.enter_main()?;
15740 let attn_gate1 = load_gate(
15741 e1,
15742 weights,
15743 &prefix,
15744 "attn_hyper_connection.",
15745 streams,
15746 hidden,
15747 rank,
15748 true,
15749 )?;
15750 let mlp_gate1 = load_gate(
15751 e1,
15752 weights,
15753 &prefix,
15754 "mlp_hyper_connection.",
15755 streams,
15756 hidden,
15757 rank,
15758 true,
15759 )?;
15760 let ple1 = match layer.ple.as_ref() {
15761 None => None,
15762 Some(ple_plan) => Some(build_ple_replica(
15763 e1, weights, &prefix, ple_plan, streams, hidden,
15764 )?),
15765 };
15766 drop(_g1);
15767 let (mixer0, mixer1) = match &layer.attention {
15768 AttentionPlan::GatedDeltaNet(gdn) => {
15769 let _g0 = e0.gpu.enter_main()?;
15770 let m0 = MixerHalfW::Gdn(build_gdn_half(e0, weights, layer.index, gdn, hidden, 0)?);
15771 drop(_g0);
15772 let _g1 = e1.gpu.enter_main()?;
15773 let m1 = MixerHalfW::Gdn(build_gdn_half(e1, weights, layer.index, gdn, hidden, 1)?);
15774 (m0, m1)
15775 }
15776 AttentionPlan::Full(attn) => {
15777 let _g0 = e0.gpu.enter_main()?;
15778 let m0 =
15779 MixerHalfW::Qsa(build_qsa_half(e0, weights, layer.index, attn, hidden, 0)?);
15780 drop(_g0);
15781 let _g1 = e1.gpu.enter_main()?;
15782 let m1 =
15783 MixerHalfW::Qsa(build_qsa_half(e1, weights, layer.index, attn, hidden, 1)?);
15784 (m0, m1)
15785 }
15786 other => {
15787 return Err(format!("qwen4exp_gpu tp2: unsupported mixer {other:?}").into());
15788 }
15789 };
15790 let MlpPlan::Moe(moe_plan) = &layer.mlp else {
15792 return Err("qwen4exp_gpu tp2: non-MoE layer".into());
15793 };
15794 let experts = moe_plan.expert_count as usize;
15795 let ff = moe_plan.expert_intermediate_size as usize;
15796 if experts % 2 != 0 {
15797 return Err("qwen4exp_gpu tp2: odd expert count".into());
15798 }
15799 let bank = ckpt
15800 .banks
15801 .get(&layer.index)
15802 .ok_or("qwen4exp_gpu tp2: missing bank source")?;
15803 let place = placement.layer(layer.index, experts)?;
15806 let upper =
15812 |src: &BankTensorSrc, out_f: usize, in_f: usize, what: &str| -> Res<Nvfp4Half> {
15813 let BankTensorSrc::Nvfp4 {
15814 codes,
15815 scales,
15816 macros,
15817 ..
15818 } = src
15819 else {
15820 return Err(format!("qwen4exp_gpu tp2: {what} bank is not NVFP4").into());
15821 };
15822 let wbytes = out_f * in_f / 2;
15823 let sbytes = out_f * in_f / 16;
15824 let need_codes = place.card1.len() * wbytes;
15825 let need_scales = place.card1.len() * sbytes;
15826 if codes.len() < experts * wbytes || scales.len() < experts * sbytes {
15827 return Err(format!(
15828 "qwen4exp_gpu tp2: {what} bank is {} code / {} scale bytes, too \
15829 small for {experts} experts x ({wbytes}, {sbytes})",
15830 codes.len(),
15831 scales.len()
15832 )
15833 .into());
15834 }
15835 let mut gcodes = Vec::with_capacity(need_codes);
15836 let mut gscales = Vec::with_capacity(need_scales);
15837 let mut gmacros = Vec::with_capacity(place.card1.len());
15838 for &eid in &place.card1 {
15839 let e = eid as usize;
15840 gcodes.extend_from_slice(&codes[e * wbytes..(e + 1) * wbytes]);
15841 gscales.extend_from_slice(&scales[e * sbytes..(e + 1) * sbytes]);
15842 gmacros.push(macros[e]);
15843 }
15844 Ok(Nvfp4Half {
15845 codes: e1.htod_bytes(&gcodes)?,
15846 scales: e1.htod_bytes(&gscales)?,
15847 macros_dev: e1.htod(&gmacros)?,
15848 })
15849 };
15850 let shared = moe_plan
15851 .shared
15852 .as_ref()
15853 .ok_or("qwen4exp_gpu tp2: missing shared expert")?;
15854 let sff = shared.intermediate_size as usize;
15855 if sff % 2 != 0 {
15856 return Err("qwen4exp_gpu tp2: odd shared ff".into());
15857 }
15858 let sffh = sff / 2;
15859 let sh_gate = expect(weights, &layer_id(layer.index, LayerTensor::SharedMlpGate))?;
15860 let sh_up = expect(weights, &layer_id(layer.index, LayerTensor::SharedMlpUp))?;
15861 let sh_down = expect(weights, &layer_id(layer.index, LayerTensor::SharedMlpDown))?;
15862 let sh_ig = if shared.gated {
15863 Some(expect(
15864 weights,
15865 &layer_id(layer.index, LayerTensor::SharedMlpInputGate),
15866 )?)
15867 } else {
15868 None
15869 };
15870 let moe = {
15871 let _g1 = e1.gpu.enter_main()?;
15872 let gate1 = upper(&bank.gate, ff, hidden, "gate")?;
15873 let up1 = upper(&bank.up, ff, hidden, "up")?;
15874 let down1 = upper(&bank.down, hidden, ff, "down")?;
15875 let shared_gu1_b16 = need_stack_twin(
15876 e1,
15877 &[&sh_gate.data[sffh * hidden..], &sh_up.data[sffh * hidden..]],
15878 hidden,
15879 "tp2 shared gate/up (card1)",
15880 )?;
15881 let down1_c = gather_cols_host(&sh_down.data, hidden, sff, &[(sffh, sffh)]);
15882 let shared_down1 = need_twin(e1, &down1_c, sffh, "tp2 shared down (card1)")?;
15883 let shared_input_gate1 = match sh_ig.as_ref() {
15884 Some(t) => Some(e1.htod(&t.data)?),
15885 None => None,
15886 };
15887 drop(_g1);
15888 let _g0 = e0.gpu.enter_main()?;
15889 let down0_c = gather_cols_host(&sh_down.data, hidden, sff, &[(0, sffh)]);
15890 let shared_down0 = need_twin(e0, &down0_c, sffh, "tp2 shared down (card0)")?;
15891 let shared_gu0_b16 = need_stack_twin(
15892 e0,
15893 &[&sh_gate.data[..sffh * hidden], &sh_up.data[..sffh * hidden]],
15894 hidden,
15895 "tp2 shared gate/up (card0)",
15896 )?;
15897 MoeHalfW {
15898 gate1,
15899 up1,
15900 down1,
15901 shared_down0,
15902 shared_down1,
15903 shared_input_gate1,
15904 shared_gu0_b16,
15905 shared_gu1_b16,
15906 }
15907 };
15908 layers.push(Tp2LayerW {
15909 attn_gate1,
15910 mlp_gate1,
15911 mixer0,
15912 mixer1,
15913 moe,
15914 ple1,
15915 place,
15916 });
15917 }
15918 let _g1 = e1.gpu.enter_main()?;
15919 let exit_gate1 = load_gate(
15920 e1,
15921 weights,
15922 "trunk.hyper_connection_mixer.",
15923 "",
15924 streams,
15925 hidden,
15926 rank,
15927 false,
15928 )?;
15929 let vsplit = vocab / 2;
15930 let head = match weights.get(&TensorId::OutputProjection) {
15931 Some(t) => &t.data,
15932 None => &expect(weights, &TensorId::TokenEmbedding)?.data.clone(),
15933 };
15934 let lm_head1 = need_twin(
15935 e1,
15936 &head[vsplit * hidden..],
15937 hidden,
15938 "tp2 lm_head upper half",
15939 )?;
15940 let stage1 = [e1.zeros(hidden)?, e1.zeros(hidden)?];
15941 let ev1 = [e1.ctx().new_event(None)?, e1.ctx().new_event(None)?];
15942 let stage1_raw = {
15943 let s = e1.gpu.stream();
15944 [
15945 stage1[0].device_ptr(&s).0 as u64,
15946 stage1[1].device_ptr(&s).0 as u64,
15947 ]
15948 };
15949 drop(_g1);
15950 let _g0 = e0.gpu.enter_main()?;
15951 let stage0 = [e0.zeros(hidden)?, e0.zeros(hidden)?];
15952 let ev0 = [e0.ctx().new_event(None)?, e0.ctx().new_event(None)?];
15953 let stage0_raw = {
15954 let s = e0.gpu.stream();
15955 [
15956 stage0[0].device_ptr(&s).0 as u64,
15957 stage0[1].device_ptr(&s).0 as u64,
15958 ]
15959 };
15960 Ok(Tp2Shard {
15961 layers,
15962 exit_gate1,
15963 lm_head1,
15964 vsplit,
15965 stage0,
15966 stage1,
15967 stage0_raw,
15968 stage1_raw,
15969 ev0,
15970 ev1,
15971 })
15972}
15973
15974impl Qwen4ExpGpu {
15975 pub fn load_from_dir_tp2(
15979 e0: &Engine,
15980 e1: &Engine,
15981 dir: &std::path::Path,
15982 opts: LoadOptions,
15983 ) -> Res<(Self, Tp2Shard)> {
15984 tp2_enable_p2p(e0, e1)?;
15985 let checkpoint = read_checkpoint_with(dir, opts)?;
15986 let shard = build_tp2_shard(e0, e1, &checkpoint)?;
15987 let model = Self::from_loaded_checkpoint(e0, checkpoint)?;
15988 Ok((model, shard))
15989 }
15990
15991 fn tp2_migrate(
15995 &self,
15996 e0: &Engine,
15997 e1: &Engine,
15998 _shard: &Tp2Shard,
15999 state: &mut Qwen4ExpState,
16000 ) -> Res<()> {
16001 let cap = state.capacity;
16002 let pos = state.pos;
16003 let mut tlayers = Vec::with_capacity(self.layers.len());
16004 for (layer, lstate) in self.layers.iter().zip(state.layers.iter_mut()) {
16005 let (m0, m1) = match (&layer.mixer, &mut lstate.mixer) {
16006 (
16007 MixerW::Gdn(gdn),
16008 MixerState::Gdn {
16009 conv,
16010 state: gstate,
16011 },
16012 ) => {
16013 let p = &gdn.plan;
16014 let (nk, nv) = (p.key_heads as usize, p.value_heads as usize);
16015 let (hk, hv) = (p.key_head_dim as usize, p.value_head_dim as usize);
16016 let (nk_h, nv_h) = (nk / 2, nv / 2);
16017 let pad = p.conv_kernel as usize - 1;
16018 let conv_dim = 2 * nk * hk + nv * hv;
16019 let conv_dim_h = 2 * nk_h * hk + nv_h * hv;
16020 let state_host = {
16021 let _g = e0.gpu.enter_main()?;
16022 e0.dtoh(gstate)?
16023 };
16024 let conv_host = {
16025 let _g = e0.gpu.enter_main()?;
16026 e0.dtoh(conv)?
16027 };
16028 let mut halves = Vec::with_capacity(2);
16029 for d in 0..2 {
16030 let head_map = tp2_gdn_head_map(d, nk, nv);
16031 let state_c = gather_rows_host(&state_host, hv * hk, &head_map);
16032 let mut blocks: Vec<(usize, usize)> = vec![
16033 (d * nk_h * hk, nk_h * hk),
16034 (nk * hk + d * nk_h * hk, nk_h * hk),
16035 ];
16036 blocks.extend(head_map.iter().map(|&hm| (2 * nk * hk + hm * hv, hv)));
16037 let conv_c = gather_cols_host(&conv_host, pad, conv_dim, &blocks);
16038 let e = if d == 0 { e0 } else { e1 };
16039 let _g = e.gpu.enter_main()?;
16040 let state_dev = e.htod(&state_c)?;
16041 let conv_dev = e.htod(&conv_c)?;
16042 debug_assert_eq!(conv_c.len(), pad * conv_dim_h);
16043 halves.push(MixerHalfState::Gdn {
16044 conv: conv_dev,
16045 state: state_dev,
16046 });
16047 }
16048 let m1 = halves.pop().expect("two halves");
16049 let m0 = halves.pop().expect("two halves");
16050 (m0, m1)
16051 }
16052 (MixerW::Qsa(qsa), MixerState::Qsa { kv, .. }) => {
16053 let nkv = qsa.attn.kv_heads as usize;
16054 let hd = qsa.attn.key_head_dim as usize;
16055 let nkv_h = nkv / 2;
16056 let mut halves = Vec::with_capacity(2);
16057 match &*kv {
16058 QsaKvStore::F32 { k, v } => {
16059 let (k_host, v_host) = {
16060 let _g = e0.gpu.enter_main()?;
16061 (
16062 e0.dtoh_view(&k.slice(0..pos * nkv * hd))?,
16063 e0.dtoh_view(&v.slice(0..pos * nkv * hd))?,
16064 )
16065 };
16066 for d in 0..2 {
16067 let block = [(d * nkv_h * hd, nkv_h * hd)];
16068 let k_c = gather_cols_host(&k_host, pos, nkv * hd, &block);
16069 let v_c = gather_cols_host(&v_host, pos, nkv * hd, &block);
16070 let e = if d == 0 { e0 } else { e1 };
16071 let _g = e.gpu.enter_main()?;
16072 let mut k_dev = e.zeros(cap * nkv_h * hd)?;
16073 let mut v_dev = e.zeros(cap * nkv_h * hd)?;
16074 if pos > 0 {
16075 let mut kv_view = k_dev.slice_mut(0..pos * nkv_h * hd);
16076 e.gpu.stream().memcpy_htod(&k_c, &mut kv_view)?;
16077 let mut vv_view = v_dev.slice_mut(0..pos * nkv_h * hd);
16078 e.gpu.stream().memcpy_htod(&v_c, &mut vv_view)?;
16079 }
16080 halves.push(MixerHalfState::Qsa {
16081 kv: QsaKvStore::F32 { k: k_dev, v: v_dev },
16082 });
16083 }
16084 }
16085 QsaKvStore::Q8Q5 { k, v } => {
16086 if hd % 32 != 0 {
16091 return Err("qwen4exp_gpu tp2: quantized halves need \
16092 hd % 32 == 0 (byte-aligned head blocks)"
16093 .into());
16094 }
16095 let (krb, vrb) = (q8_row_bytes(nkv * hd), q5_row_bytes(nkv * hd));
16096 let (krb_h, vrb_h) =
16097 (q8_row_bytes(nkv_h * hd), q5_row_bytes(nkv_h * hd));
16098 let (k_host, v_host) = {
16099 let _g = e0.gpu.enter_main()?;
16100 (
16101 e0.dtoh_u8_view(&k.slice(0..pos * krb))?,
16102 e0.dtoh_u8_view(&v.slice(0..pos * vrb))?,
16103 )
16104 };
16105 for d in 0..2 {
16106 let mut k_c = Vec::with_capacity(pos * krb_h);
16107 let mut v_c = Vec::with_capacity(pos * vrb_h);
16108 for r in 0..pos {
16109 let ko = r * krb + d * krb_h;
16110 k_c.extend_from_slice(&k_host[ko..ko + krb_h]);
16111 let vo = r * vrb + d * vrb_h;
16112 v_c.extend_from_slice(&v_host[vo..vo + vrb_h]);
16113 }
16114 let e = if d == 0 { e0 } else { e1 };
16115 let _g = e.gpu.enter_main()?;
16116 let mut k_dev = e.alloc_u8(cap * krb_h)?;
16117 let mut v_dev = e.alloc_u8(cap * vrb_h)?;
16118 if pos > 0 {
16119 let mut kv_view = k_dev.slice_mut(0..pos * krb_h);
16120 e.gpu.stream().memcpy_htod(&k_c, &mut kv_view)?;
16121 let mut vv_view = v_dev.slice_mut(0..pos * vrb_h);
16122 e.gpu.stream().memcpy_htod(&v_c, &mut vv_view)?;
16123 }
16124 halves.push(MixerHalfState::Qsa {
16125 kv: QsaKvStore::Q8Q5 { k: k_dev, v: v_dev },
16126 });
16127 }
16128 }
16129 }
16130 {
16134 let _g = e0.gpu.enter_main()?;
16135 *kv = match &*kv {
16136 QsaKvStore::F32 { .. } => QsaKvStore::F32 {
16137 k: e0.zeros(1)?,
16138 v: e0.zeros(1)?,
16139 },
16140 QsaKvStore::Q8Q5 { .. } => QsaKvStore::Q8Q5 {
16141 k: e0.alloc_u8(34)?,
16142 v: e0.alloc_u8(24)?,
16143 },
16144 };
16145 }
16146 let m1 = halves.pop().expect("two halves");
16147 let m0 = halves.pop().expect("two halves");
16148 (m0, m1)
16149 }
16150 _ => return Err("qwen4exp_gpu tp2: layer/state mixer mismatch".into()),
16151 };
16152 let ple1 = match lstate.ple.as_ref() {
16154 None => None,
16155 Some(ps) => {
16156 let mut conv_hist = Vec::with_capacity(ps.conv_hist.len());
16157 for h in &ps.conv_hist {
16158 let host = {
16159 let _g = e0.gpu.enter_main()?;
16160 e0.dtoh(h)?
16161 };
16162 let _g = e1.gpu.enter_main()?;
16163 conv_hist.push(e1.htod(&host)?);
16164 }
16165 Some(PleState {
16166 conv_hist,
16167 ngram_ids: Vec::new(),
16168 ngram_history: Vec::new(),
16169 ngram_last_eos: -1,
16170 })
16171 }
16172 };
16173 tlayers.push(Tp2LayerState { m0, m1, ple1 });
16174 }
16175 state.tp2 = Some(Tp2State {
16176 ws1: StepPool::default(),
16177 layers: tlayers,
16178 graphs: Tp2Graphs::default(),
16179 pf_stage0: None,
16180 pf_stage1: None,
16181 pf_stage0_raw: [0; 2],
16182 pf_stage1_raw: [0; 2],
16183 pf_rows: 0,
16184 });
16185 Ok(())
16186 }
16187
16188 #[allow(clippy::too_many_arguments)]
16192 fn gdn_forward_half(
16193 &self,
16194 e: &Engine,
16195 ws: &mut StepPool,
16196 eps: f32,
16197 h: &GdnHalfW,
16198 mixed: &CudaSlice<f32>,
16199 hstate: &mut MixerHalfState,
16200 t: usize,
16201 ) -> Res<CudaSlice<f32>> {
16202 let MixerHalfState::Gdn { conv, state } = hstate else {
16203 return Err("qwen4exp_gpu tp2: GDN half bound to non-GDN state".into());
16204 };
16205 let hidden = self.hidden;
16206 let (nk, nv, hk, hv) = (h.nk_h, h.nv_h, h.hk, h.hv);
16207 let kernel = h.kernel;
16208 let pad = kernel - 1;
16209 let conv_dim = 2 * nk * hk + nv * hv;
16210 let mut qkv = ws.take_f32(e, "gdn.qkv", t * conv_dim, 0)?;
16211 let mut z = ws.take_f32(e, "gdn.z", t * nv * hv, 0)?;
16212 let mut beta_raw = ws.take_f32(e, "gdn.beta", t * nv, 0)?;
16213 let mut alpha = ws.take_f32(e, "gdn.alpha", t * nv, 0)?;
16214 if t == 1 && proj_stack_on() {
16218 launch_qmatvec_bf16w_multi4(
16219 e,
16220 &h.proj_b16,
16221 mixed,
16222 &[
16223 (&qkv, conv_dim),
16224 (&z, nv * hv),
16225 (&beta_raw, nv),
16226 (&alpha, nv),
16227 ],
16228 hidden,
16229 )?;
16230 } else {
16231 launch_qmatvec_bf16w_off(e, &h.proj_b16, 0, mixed, &mut qkv, hidden, conv_dim, t)?;
16232 launch_qmatvec_bf16w_off(e, &h.proj_b16, conv_dim, mixed, &mut z, hidden, nv * hv, t)?;
16233 launch_qmatvec_bf16w_off(
16234 e,
16235 &h.proj_b16,
16236 conv_dim + nv * hv,
16237 mixed,
16238 &mut beta_raw,
16239 hidden,
16240 nv,
16241 t,
16242 )?;
16243 launch_qmatvec_bf16w_off(
16244 e,
16245 &h.proj_b16,
16246 conv_dim + nv * hv + nv,
16247 mixed,
16248 &mut alpha,
16249 hidden,
16250 nv,
16251 t,
16252 )?;
16253 }
16254 let mut g_log = ws.take_f32(e, "gdn.glog", t * nv, 0)?;
16255 e.gdn_glog_v(&alpha.slice(0..t * nv), &h.dt, &h.a, &mut g_log, nv, t)?;
16256 ws.put_f32("gdn.alpha", alpha);
16257 let mut conv_out = ws.take_f32(e, "gdn.conv_out", t * conv_dim, 0)?;
16258 launch_dwconv(
16259 e,
16260 &qkv,
16261 conv,
16262 &h.conv_w,
16263 &mut conv_out,
16264 t,
16265 pad,
16266 conv_dim,
16267 kernel,
16268 1,
16269 1,
16270 )?;
16271 let mut o = ws.take_f32(e, "gdn.o", t * nv * hv, 0)?;
16272 let scale = 1.0 / (hk as f32).sqrt();
16273 if t == 1 && gdn_step_on() && hk % 32 == 0 && hk <= 1024 {
16274 launch_gdn_scan_step(
16275 e, &conv_out, &g_log, &beta_raw, state, &mut o, nk, nv, hk, hv, scale, eps,
16276 )?;
16277 } else {
16278 launch_gdn_scan(
16279 e, &conv_out, &g_log, &beta_raw, state, &mut o, nk, nv, hk, hv, t, scale, eps,
16280 )?;
16281 }
16282 ws.put_f32("gdn.conv_out", conv_out);
16283 if t >= pad {
16285 e.copy_range_into(conv, 0, &qkv, (t - pad) * conv_dim, pad * conv_dim)?;
16286 } else {
16287 let keep = pad - t;
16288 let mut tmp = ws.take_f32(e, "gdn.tmp", keep * conv_dim, 0)?;
16289 e.copy_range_into(&mut tmp, 0, conv, t * conv_dim, keep * conv_dim)?;
16290 e.copy_range_into(conv, 0, &tmp, 0, keep * conv_dim)?;
16291 e.copy_range_into(conv, keep * conv_dim, &qkv, 0, t * conv_dim)?;
16292 ws.put_f32("gdn.tmp", tmp);
16293 }
16294 ws.put_f32("gdn.qkv", qkv);
16295 ws.put_f32("gdn.beta", beta_raw);
16296 ws.put_f32("gdn.glog", g_log);
16297 let mut gated = ws.take_f32(e, "gdn.gated", t * nv * hv, 0)?;
16298 match h.gate_activation {
16299 GdnGateActivation::Sigmoid if gdn_fuse_on() => {
16300 launch_rms_sigmul(e, &o, &h.norm, &z, &mut gated, hv, t * nv, eps)?;
16301 }
16302 GdnGateActivation::Sigmoid => {
16303 let mut normed = ws.take_f32(e, "gdn.normed", t * nv * hv, 0)?;
16304 e.rms_norm(&o, &h.norm, &mut normed, hv, t * nv, eps)?;
16305 let mut sg = ws.take_f32(e, "gdn.sg", t * nv * hv, 0)?;
16306 e.sigmoid(&z, &mut sg, t * nv * hv)?;
16307 e.mul(&normed, &sg, &mut gated, t * nv * hv)?;
16308 ws.put_f32("gdn.sg", sg);
16309 ws.put_f32("gdn.normed", normed);
16310 }
16311 GdnGateActivation::Silu => {
16312 let mut normed = ws.take_f32(e, "gdn.normed", t * nv * hv, 0)?;
16313 e.rms_norm(&o, &h.norm, &mut normed, hv, t * nv, eps)?;
16314 e.silu_mul(&z, &normed, &mut gated, t * nv * hv)?;
16315 ws.put_f32("gdn.normed", normed);
16316 }
16317 }
16318 let mut partial = ws.take_f32(e, "mixer.out", t * hidden, 0)?;
16319 launch_qmatvec_bf16w(
16320 e,
16321 &h.out_b16,
16322 &gated,
16323 &mut partial,
16324 nv * hv,
16325 hidden,
16326 t,
16327 1,
16328 0,
16329 0,
16330 nv * hv,
16331 0,
16332 )?;
16333 ws.put_f32("gdn.gated", gated);
16334 ws.put_f32("gdn.z", z);
16335 ws.put_f32("gdn.o", o);
16336 Ok(partial)
16337 }
16338
16339 #[allow(clippy::too_many_arguments)]
16343 fn qsa_half_proj(
16344 &self,
16345 e: &Engine,
16346 ws: &mut StepPool,
16347 eps: f32,
16348 h: &QsaHalfW,
16349 mixed: &CudaSlice<f32>,
16350 hstate: &mut MixerHalfState,
16351 base_pos: usize,
16352 t: usize,
16353 ) -> Res<(CudaSlice<f32>, CudaSlice<f32>)> {
16354 let MixerHalfState::Qsa { kv } = hstate else {
16355 return Err("qwen4exp_gpu tp2: QSA half bound to non-QSA state".into());
16356 };
16357 let hidden = self.hidden;
16358 let (nh, nkv, hd) = (h.nh_h, h.nkv_h, h.hd);
16359 let mut q_fused = ws.take_f32(e, "qsa.qf", t * 2 * nh * hd, 0)?;
16360 let mut k_new = ws.take_f32(e, "qsa.k", t * nkv * hd, 0)?;
16361 let mut v_new = ws.take_f32(e, "qsa.v", t * nkv * hd, 0)?;
16362 if t == 1 && proj_stack_on() {
16366 launch_qmatvec_bf16w_multi4(
16367 e,
16368 &h.proj_b16,
16369 mixed,
16370 &[
16371 (&q_fused, 2 * nh * hd),
16372 (&k_new, nkv * hd),
16373 (&v_new, nkv * hd),
16374 ],
16375 hidden,
16376 )?;
16377 } else {
16378 launch_qmatvec_bf16w_off(
16379 e,
16380 &h.proj_b16,
16381 0,
16382 mixed,
16383 &mut q_fused,
16384 hidden,
16385 2 * nh * hd,
16386 t,
16387 )?;
16388 launch_qmatvec_bf16w_off(
16389 e,
16390 &h.proj_b16,
16391 2 * nh * hd,
16392 mixed,
16393 &mut k_new,
16394 hidden,
16395 nkv * hd,
16396 t,
16397 )?;
16398 launch_qmatvec_bf16w_off(
16399 e,
16400 &h.proj_b16,
16401 2 * nh * hd + nkv * hd,
16402 mixed,
16403 &mut v_new,
16404 hidden,
16405 nkv * hd,
16406 t,
16407 )?;
16408 }
16409 let mut q = ws.take_f32(e, "qsa.q", t * nh * hd, 0)?;
16410 let mut gate = ws.take_f32(e, "qsa.gate", t * nh * hd, 0)?;
16411 e.q_gate_split(&q_fused, &mut q, &mut gate, hd, nh, t)?;
16412 ws.put_f32("qsa.qf", q_fused);
16413 let mut q = if let Some(norm) = h.q_norm.as_ref() {
16414 let mut dst = ws.take_f32(e, "qsa.qn", t * nh * hd, 0)?;
16415 e.rms_norm(&q, norm, &mut dst, hd, t * nh, eps)?;
16416 ws.put_f32("qsa.q", q);
16417 dst
16418 } else {
16419 q
16420 };
16421 let mut k_new = if let Some(norm) = h.k_norm.as_ref() {
16422 let mut dst = ws.take_f32(e, "qsa.kn", t * nkv * hd, 0)?;
16423 e.rms_norm(&k_new, norm, &mut dst, hd, t * nkv, eps)?;
16424 ws.put_f32("qsa.k", k_new);
16425 dst
16426 } else {
16427 k_new
16428 };
16429 let positions: Vec<i32> = (0..t).map(|i| (base_pos + i) as i32).collect();
16430 let pos_dev = ws.take_i32(e, "qsa.pos", &positions, 0)?;
16431 if let Some(yarn) = h.yarn.as_ref() {
16432 e.rope_neox_ffm(
16433 &mut q,
16434 &pos_dev,
16435 hd,
16436 h.n_rot,
16437 nh,
16438 t,
16439 h.rope_base,
16440 1.0,
16441 &yarn.ff,
16442 yarn.mscale,
16443 )?;
16444 e.rope_neox_ffm(
16445 &mut k_new,
16446 &pos_dev,
16447 hd,
16448 h.n_rot,
16449 nkv,
16450 t,
16451 h.rope_base,
16452 1.0,
16453 &yarn.ff,
16454 yarn.mscale,
16455 )?;
16456 } else {
16457 e.rope_neox(&mut q, &pos_dev, hd, h.n_rot, nh, t, h.rope_base, 1.0)?;
16458 e.rope_neox(&mut k_new, &pos_dev, hd, h.n_rot, nkv, t, h.rope_base, 1.0)?;
16459 }
16460 ws.put_i32("qsa.pos", pos_dev);
16461 match kv {
16462 QsaKvStore::F32 { k, v } => {
16463 e.copy_range_into(k, base_pos * nkv * hd, &k_new, 0, t * nkv * hd)?;
16464 e.copy_range_into(v, base_pos * nkv * hd, &v_new, 0, t * nkv * hd)?;
16465 }
16466 QsaKvStore::Q8Q5 { k, v } => {
16467 launch_q4e_kv_append(e, &k_new, &v_new, k, v, base_pos, t, nkv * hd)?;
16468 }
16469 }
16470 ws.put_f32(
16471 if h.k_norm.is_some() {
16472 "qsa.kn"
16473 } else {
16474 "qsa.k"
16475 },
16476 k_new,
16477 );
16478 ws.put_f32("qsa.v", v_new);
16479 Ok((q, gate))
16480 }
16481
16482 #[allow(clippy::too_many_arguments)]
16487 fn qsa_half_attend(
16488 &self,
16489 e: &Engine,
16490 ws: &mut StepPool,
16491 h: &QsaHalfW,
16492 hstate: &MixerHalfState,
16493 q: CudaSlice<f32>,
16494 gate: CudaSlice<f32>,
16495 pos_dev: &CudaSlice<i32>,
16496 meta_dev: &CudaSlice<i32>,
16497 max_count: usize,
16498 t: usize,
16499 t_kv: usize,
16500 ) -> Res<CudaSlice<f32>> {
16501 let MixerHalfState::Qsa { kv } = hstate else {
16502 return Err("qwen4exp_gpu tp2: QSA half bound to non-QSA state".into());
16503 };
16504 let hidden = self.hidden;
16505 let (nh, nkv, hd) = (h.nh_h, h.nkv_h, h.hd);
16506 let mut attended = ws.take_f32(e, "qsa.att", t * nh * hd, 0)?;
16507 match kv {
16508 QsaKvStore::F32 { k, v } => {
16509 let k_view = k.slice(0..t_kv * nkv * hd);
16510 let v_view = v.slice(0..t_kv * nkv * hd);
16511 launch_sdpa_blocklist(
16512 e,
16513 &q,
16514 &k_view,
16515 &v_view,
16516 &mut attended,
16517 pos_dev,
16518 meta_dev,
16519 hd,
16520 nh,
16521 nkv,
16522 t,
16523 max_count,
16524 h.scale,
16525 )?;
16526 }
16527 QsaKvStore::Q8Q5 { k, v } => {
16528 launch_q4e_sdpa_blocklist_q8q5(
16529 e,
16530 &q,
16531 k,
16532 v,
16533 &mut attended,
16534 pos_dev,
16535 meta_dev,
16536 hd,
16537 nh,
16538 nkv,
16539 t,
16540 max_count,
16541 h.scale,
16542 )?;
16543 }
16544 }
16545 ws.put_f32(
16546 if h.q_norm.is_some() {
16547 "qsa.qn"
16548 } else {
16549 "qsa.q"
16550 },
16551 q,
16552 );
16553 let mut sg = ws.take_f32(e, "qsa.sg", t * nh * hd, 0)?;
16554 e.sigmoid(&gate, &mut sg, t * nh * hd)?;
16555 let mut gated = ws.take_f32(e, "qsa.gated", t * nh * hd, 0)?;
16556 e.mul(&attended, &sg, &mut gated, t * nh * hd)?;
16557 let mut partial = ws.take_f32(e, "mixer.out", t * hidden, 0)?;
16558 launch_qmatvec_bf16w(
16559 e,
16560 &h.wo_b16,
16561 &gated,
16562 &mut partial,
16563 nh * hd,
16564 hidden,
16565 t,
16566 1,
16567 0,
16568 0,
16569 nh * hd,
16570 0,
16571 )?;
16572 ws.put_f32("qsa.sg", sg);
16573 ws.put_f32("qsa.gated", gated);
16574 ws.put_f32("qsa.att", attended);
16575 ws.put_f32("qsa.gate", gate);
16576 Ok(partial)
16577 }
16578
16579 #[allow(clippy::too_many_arguments)]
16582 fn qsa_indexer_mask(
16583 &self,
16584 e: &Engine,
16585 ws: &mut StepPool,
16586 qsa: &QsaW,
16587 eps: f32,
16588 mixed: &CudaSlice<f32>,
16589 raw_keys: &mut IdxRawCache,
16590 pooled_keys: &mut Vec<f32>,
16591 base_pos: usize,
16592 ) -> Res<Vec<u8>> {
16593 let overlay = &qsa.overlay;
16594 let idx_dim = overlay.head_dim as usize;
16595 let qk_width = (overlay.query_heads as usize + overlay.kv_heads as usize) * idx_dim;
16596 let hidden = self.hidden;
16597 let mut idx_proj = ws.take_f32(e, "qsa.idxp", qk_width, 0)?;
16598 e.linear_device_into(mixed, &qsa.idx_proj, &mut idx_proj, 1, hidden, qk_width)?;
16599 let rows = e.dtoh_view(&idx_proj.slice(0..qk_width))?;
16600 ws.put_f32("qsa.idxp", idx_proj);
16601 raw_keys.append_rows_f32(
16602 &rows[overlay.query_heads as usize * idx_dim..qk_width],
16603 1,
16604 idx_dim,
16605 );
16606 indexer_mask_rows(
16607 overlay,
16608 qsa.attn.rope.base,
16609 qsa.yarn.as_ref().map(|y| (y.ff_host.as_slice(), y.mscale)),
16610 eps,
16611 &qsa.idx_q_norm,
16612 &qsa.idx_k_norm,
16613 &rows,
16614 raw_keys,
16615 pooled_keys,
16616 base_pos,
16617 1,
16618 base_pos + 1,
16619 0,
16620 )
16621 }
16622
16623 #[allow(clippy::too_many_arguments)]
16627 fn tp2_shared_half(
16628 &self,
16629 e: &Engine,
16630 ws: &mut StepPool,
16631 gu_b16: &CudaSlice<u8>,
16632 down_b16: &CudaSlice<u8>,
16633 input_gate: Option<&CudaSlice<f32>>,
16634 mixed: &CudaSlice<f32>,
16635 sffh: usize,
16636 t: usize,
16637 ) -> Res<(CudaSlice<f32>, Option<CudaSlice<f32>>)> {
16638 let hidden = self.hidden;
16639 let mut sh_gate = ws.take_f32(e, "moe.sh_gate", t * sffh, 0)?;
16640 let mut sh_up = ws.take_f32(e, "moe.sh_up", t * sffh, 0)?;
16641 if t == 1 && proj_stack_on() {
16644 launch_qmatvec_bf16w_multi4(
16645 e,
16646 gu_b16,
16647 mixed,
16648 &[(&sh_gate, sffh), (&sh_up, sffh)],
16649 hidden,
16650 )?;
16651 } else {
16652 launch_qmatvec_bf16w_off(e, gu_b16, 0, mixed, &mut sh_gate, hidden, sffh, t)?;
16653 launch_qmatvec_bf16w_off(e, gu_b16, sffh, mixed, &mut sh_up, hidden, sffh, t)?;
16654 }
16655 let mut act = ws.take_f32(e, "moe.sh_act", t * sffh, 0)?;
16656 e.silu_mul(&sh_gate, &sh_up, &mut act, t * sffh)?;
16657 let mut shared = ws.take_f32(e, "moe.sh_down", t * hidden, 0)?;
16658 launch_qmatvec_bf16w(
16659 e,
16660 down_b16,
16661 &act,
16662 &mut shared,
16663 sffh,
16664 hidden,
16665 t,
16666 1,
16667 0,
16668 0,
16669 sffh,
16670 0,
16671 )?;
16672 let g = match input_gate {
16673 Some(w) => {
16674 let mut g = ws.take_f32(e, "moe.g", t, 0)?;
16675 e.sigmoid_dot_rows_into(mixed, w, &mut g, hidden, t)?;
16676 Some(g)
16677 }
16678 None => None,
16679 };
16680 ws.put_f32("moe.sh_gate", sh_gate);
16681 ws.put_f32("moe.sh_up", sh_up);
16682 ws.put_f32("moe.sh_act", act);
16683 Ok((shared, g))
16684 }
16685}
16686
16687impl Qwen4ExpGpu {
16688 pub fn decode_step_tp2(
16693 &self,
16694 e0: &Engine,
16695 e1: &Engine,
16696 shard: &Tp2Shard,
16697 token: u32,
16698 state: &mut Qwen4ExpState,
16699 ) -> Res<Vec<f32>> {
16700 if !trunk_bf16_on() || !hc_fused_gate_on() {
16701 return Err(
16702 "qwen4exp_gpu tp2: requires set_trunk_bf16(true) and set_hc_fused_gate(true) \
16703 (replicated compute must run deterministic kernels)"
16704 .into(),
16705 );
16706 }
16707 if state.pos + 1 > state.capacity {
16708 return Err("qwen4exp_gpu: state capacity exceeded".into());
16709 }
16710 if state.tp2.is_none() {
16711 self.tp2_migrate(e0, e1, shard, state)?;
16712 state.graphs = StepGraphs::default();
16713 }
16714 let hidden = self.hidden;
16715 let vocab = self.vocab;
16716 let vsplit = shard.vsplit;
16717 let base_pos = state.pos;
16718 let reserve = state.reserve;
16719 state.tokens.push(token);
16720 let Qwen4ExpState {
16721 ref tokens,
16722 ws: ref mut ws0,
16723 ref mut tp2,
16724 layers: ref mut lstates,
16725 ..
16726 } = *state;
16727 let Tp2State {
16728 ws1,
16729 layers: tlayers,
16730 graphs: tgraphs,
16731 ..
16732 } = tp2.as_mut().expect("migrated above");
16733 let cap = reserve.max(1);
16736
16737 let token_us = token as usize;
16739 if token_us >= vocab {
16740 return Err(format!("qwen4exp_gpu: token {token_us} out of range").into());
16741 }
16742 let embedded = &self.embed_host[token_us * hidden..(token_us + 1) * hidden];
16743 let mut planes1: Vec<CudaSlice<f32>> = Vec::with_capacity(self.streams);
16744 let ptrs1 = {
16745 let _g = e1.gpu.enter_main()?;
16746 let embedded_dev = ws1.take_f32_h2d(e1, "entry.embed", embedded, cap * hidden)?;
16747 for s in 0..self.streams {
16748 let mut plane = ws1.take_f32(e1, PLANE_SLOTS[s], hidden, cap * hidden)?;
16749 e1.copy_into(&mut plane, 0, &embedded_dev, hidden)?;
16750 planes1.push(plane);
16751 }
16752 ws1.put_f32("entry.embed", embedded_dev);
16753 let ptr_vals: Vec<u64> = {
16754 let stream = e1.gpu.stream();
16755 planes1.iter().map(|p| p.device_ptr(&stream).0).collect()
16756 };
16757 ws1.take_u64_h2d(e1, "hc.ptrs", &ptr_vals, 0)?
16758 };
16759 let mut planes0: Vec<CudaSlice<f32>> = Vec::with_capacity(self.streams);
16760 let ptrs0 = {
16761 let _g = e0.gpu.enter_main()?;
16762 let embedded_dev = ws0.take_f32_h2d(e0, "entry.embed", embedded, cap * hidden)?;
16763 for s in 0..self.streams {
16764 let mut plane = ws0.take_f32(e0, PLANE_SLOTS[s], hidden, cap * hidden)?;
16765 e0.copy_into(&mut plane, 0, &embedded_dev, hidden)?;
16766 planes0.push(plane);
16767 }
16768 ws0.put_f32("entry.embed", embedded_dev);
16769 let ptr_vals: Vec<u64> = {
16770 let stream = e0.gpu.stream();
16771 planes0.iter().map(|p| p.device_ptr(&stream).0).collect()
16772 };
16773 ws0.take_u64_h2d(e0, "hc.ptrs", &ptr_vals, 0)?
16774 };
16775
16776 let use_graphs = decode_graphs_on() && step_ws_on();
16779 let graphs_live = use_graphs && tgraphs.warm;
16780 if use_graphs && !tgraphs.warm {
16781 tgraphs.warm = true;
16782 }
16783 if graphs_live && tgraphs.a[0].len() != self.layers.len() {
16784 for d in 0..2 {
16785 tgraphs.a[d] = (0..self.layers.len()).map(|_| None).collect();
16786 tgraphs.b[d] = (0..self.layers.len()).map(|_| None).collect();
16787 tgraphs.c[d] = (0..self.layers.len()).map(|_| None).collect();
16788 tgraphs.d[d] = (0..self.layers.len()).map(|_| None).collect();
16789 }
16790 }
16791 for (li, layer) in self.layers.iter().enumerate() {
16792 let lstate = &mut lstates[li];
16793 let tw = &shard.layers[li];
16794 let ts = &mut tlayers[li];
16795 let eps_a = layer.eps_attn;
16796 let eps_m = layer.eps_mlp;
16797 let moe = &layer.moe;
16798 let ff = moe.plan.expert_intermediate_size as usize;
16799 let experts = moe.plan.expert_count as usize;
16800 let selected = moe.plan.experts_per_token as usize;
16801 let sff = moe
16802 .plan
16803 .shared
16804 .as_ref()
16805 .map(|s| s.intermediate_size as usize)
16806 .unwrap_or(0);
16807 let sffh = sff / 2;
16808
16809 match (&layer.mixer, &tw.mixer0, &tw.mixer1) {
16811 (MixerW::Gdn(_), MixerHalfW::Gdn(h0), MixerHalfW::Gdn(h1)) => {
16812 {
16813 let _g = e1.gpu.enter_main()?;
16814 if let (Some(ple1), Some(ps1)) = (tw.ple1.as_ref(), ts.ple1.as_mut()) {
16815 let table = &layer.ple.as_ref().expect("ple plan").table;
16816 self.ple_block(
16817 e1,
16818 layer,
16819 ple1,
16820 table,
16821 ps1,
16822 &mut planes1,
16823 tokens,
16824 1,
16825 false,
16826 None,
16827 )?;
16828 }
16829 if graphs_live && layer.ple.is_none() {
16830 if tgraphs.a[1][li].is_none() {
16831 tgraphs.a[1][li] =
16832 Some(e1.capture_graph_retained_nowarm(|eng| {
16833 self.tp2_gdn_seg_a(
16834 eng,
16835 ws1,
16836 &ptrs1,
16837 &tw.attn_gate1,
16838 h1,
16839 &mut ts.m1,
16840 &planes1,
16841 eps_a,
16842 shard.stage0_raw[0],
16843 )
16844 })?);
16845 }
16846 tgraphs.a[1][li].as_ref().unwrap().0.launch()?;
16847 } else {
16848 self.tp2_gdn_seg_a(
16849 e1,
16850 ws1,
16851 &ptrs1,
16852 &tw.attn_gate1,
16853 h1,
16854 &mut ts.m1,
16855 &planes1,
16856 eps_a,
16857 shard.stage0_raw[0],
16858 )?;
16859 }
16860 shard.ev1[0].record(&e1.gpu.stream())?;
16861 }
16862 {
16863 let _g = e0.gpu.enter_main()?;
16864 if let (Some(ple), Some(ps)) = (layer.ple.as_ref(), lstate.ple.as_mut()) {
16865 self.ple_block(
16866 e0,
16867 layer,
16868 ple,
16869 &ple.table,
16870 ps,
16871 &mut planes0,
16872 tokens,
16873 1,
16874 false,
16875 None,
16876 )?;
16877 }
16878 if graphs_live && layer.ple.is_none() {
16879 if tgraphs.a[0][li].is_none() {
16880 tgraphs.a[0][li] =
16881 Some(e0.capture_graph_retained_nowarm(|eng| {
16882 self.tp2_gdn_seg_a(
16883 eng,
16884 ws0,
16885 &ptrs0,
16886 &layer.attn_gate,
16887 h0,
16888 &mut ts.m0,
16889 &planes0,
16890 eps_a,
16891 shard.stage1_raw[0],
16892 )
16893 })?);
16894 }
16895 tgraphs.a[0][li].as_ref().unwrap().0.launch()?;
16896 } else {
16897 self.tp2_gdn_seg_a(
16898 e0,
16899 ws0,
16900 &ptrs0,
16901 &layer.attn_gate,
16902 h0,
16903 &mut ts.m0,
16904 &planes0,
16905 eps_a,
16906 shard.stage1_raw[0],
16907 )?;
16908 }
16909 shard.ev0[0].record(&e0.gpu.stream())?;
16910 }
16911 }
16912 (MixerW::Qsa(qsa), MixerHalfW::Qsa(h0), MixerHalfW::Qsa(h1)) => {
16913 let (q1, g1, inj1) = {
16916 let _g = e1.gpu.enter_main()?;
16917 let (mixed1, inj1) = self.gate_read(
16918 e1,
16919 ws1,
16920 &ptrs1,
16921 &tw.attn_gate1,
16922 &planes1,
16923 1,
16924 eps_a,
16925 false,
16926 )?;
16927 let (q1, g1) = self
16928 .qsa_half_proj(e1, ws1, eps_a, h1, &mixed1, &mut ts.m1, base_pos, 1)?;
16929 ws1.put_f32("hc.mixed", mixed1);
16930 (q1, g1, inj1)
16931 };
16932 let (sels, q0, g0, inj0) = {
16937 let _g = e0.gpu.enter_main()?;
16938 let (mixed0, inj0) = self.gate_read(
16939 e0,
16940 ws0,
16941 &ptrs0,
16942 &layer.attn_gate,
16943 &planes0,
16944 1,
16945 eps_a,
16946 false,
16947 )?;
16948 let (q0, g0) = self
16949 .qsa_half_proj(e0, ws0, eps_a, h0, &mixed0, &mut ts.m0, base_pos, 1)?;
16950 let MixerState::Qsa {
16951 raw_keys,
16952 pooled_keys,
16953 pooled_dev,
16954 pooled_dev_rows,
16955 raw_dev,
16956 raw_dev_rows,
16957 idx_audit,
16958 ..
16959 } = &mut lstate.mixer
16960 else {
16961 return Err("qwen4exp_gpu tp2: QSA layer without raw-key cache".into());
16962 };
16963 let sels = self.qsa_update_select(
16964 e0,
16965 ws0,
16966 qsa,
16967 eps_a,
16968 &mixed0,
16969 raw_keys,
16970 pooled_keys,
16971 pooled_dev,
16972 pooled_dev_rows,
16973 raw_dev,
16974 raw_dev_rows,
16975 idx_audit.as_mut(),
16976 base_pos,
16977 1,
16978 0,
16979 false,
16980 )?;
16981 ws0.put_f32("hc.mixed", mixed0);
16982 (sels, q0, g0, inj0)
16983 };
16984 let t_kv = base_pos + 1;
16985 let block_size = qsa.overlay.block_size as usize;
16986 let (pos_flat, meta, max_count) = rowsel_positions(&sels, block_size);
16987 {
16988 let _g = e1.gpu.enter_main()?;
16989 let pos_dev = ws1.take_i32(e1, "qsa.selpos", &pos_flat, 0)?;
16990 let meta_dev = ws1.take_i32(e1, "qsa.selmeta", &meta, 0)?;
16991 let p1 = self.qsa_half_attend(
16992 e1, ws1, h1, &ts.m1, q1, g1, &pos_dev, &meta_dev, max_count, 1, t_kv,
16993 )?;
16994 ws1.put_i32("qsa.selpos", pos_dev);
16995 ws1.put_i32("qsa.selmeta", meta_dev);
16996 launch_push(e1, &p1, shard.stage0_raw[0], hidden)?;
16997 ws1.put_f32("mixer.out", p1);
16998 put_inject(ws1, inj1);
16999 shard.ev1[0].record(&e1.gpu.stream())?;
17000 }
17001 {
17002 let _g = e0.gpu.enter_main()?;
17003 let pos_dev = ws0.take_i32(e0, "qsa.selpos", &pos_flat, 0)?;
17004 let meta_dev = ws0.take_i32(e0, "qsa.selmeta", &meta, 0)?;
17005 let p0 = self.qsa_half_attend(
17006 e0, ws0, h0, &ts.m0, q0, g0, &pos_dev, &meta_dev, max_count, 1, t_kv,
17007 )?;
17008 ws0.put_i32("qsa.selpos", pos_dev);
17009 ws0.put_i32("qsa.selmeta", meta_dev);
17010 launch_push(e0, &p0, shard.stage1_raw[0], hidden)?;
17011 ws0.put_f32("mixer.out", p0);
17012 put_inject(ws0, inj0);
17013 shard.ev0[0].record(&e0.gpu.stream())?;
17014 }
17015 }
17016 _ => return Err("qwen4exp_gpu tp2: mixer/shard shape mismatch".into()),
17017 }
17018 {
17019 let _g = e0.gpu.enter_main()?;
17020 e0.gpu.stream().wait(&shard.ev1[0])?;
17021 }
17022 {
17023 let _g = e1.gpu.enter_main()?;
17024 e1.gpu.stream().wait(&shard.ev0[0])?;
17025 }
17026
17027 {
17029 let _g = e1.gpu.enter_main()?;
17030 if graphs_live {
17031 if tgraphs.b[1][li].is_none() {
17032 tgraphs.b[1][li] = Some(e1.capture_graph_retained_nowarm(|eng| {
17033 self.tp2_seg_b(
17034 eng,
17035 ws1,
17036 &ptrs1,
17037 &tw.mlp_gate1,
17038 &mut planes1,
17039 &shard.stage1[0],
17040 false,
17041 eps_m,
17042 Some((
17043 &tw.moe.shared_gu1_b16,
17044 &tw.moe.shared_down1,
17045 tw.moe.shared_input_gate1.as_ref(),
17046 sffh,
17047 )),
17048 )
17049 })?);
17050 }
17051 tgraphs.b[1][li].as_ref().unwrap().0.launch()?;
17052 } else {
17053 self.tp2_seg_b(
17054 e1,
17055 ws1,
17056 &ptrs1,
17057 &tw.mlp_gate1,
17058 &mut planes1,
17059 &shard.stage1[0],
17060 false,
17061 eps_m,
17062 Some((
17063 &tw.moe.shared_gu1_b16,
17064 &tw.moe.shared_down1,
17065 tw.moe.shared_input_gate1.as_ref(),
17066 sffh,
17067 )),
17068 )?;
17069 }
17070 }
17071 {
17072 let _g = e0.gpu.enter_main()?;
17073 if graphs_live {
17074 if tgraphs.b[0][li].is_none() {
17075 tgraphs.b[0][li] = Some(e0.capture_graph_retained_nowarm(|eng| {
17076 self.tp2_seg_b(
17077 eng,
17078 ws0,
17079 &ptrs0,
17080 &layer.mlp_gate,
17081 &mut planes0,
17082 &shard.stage0[0],
17083 true,
17084 eps_m,
17085 None,
17086 )
17087 })?);
17088 }
17089 tgraphs.b[0][li].as_ref().unwrap().0.launch()?;
17090 } else {
17091 self.tp2_seg_b(
17092 e0,
17093 ws0,
17094 &ptrs0,
17095 &layer.mlp_gate,
17096 &mut planes0,
17097 &shard.stage0[0],
17098 true,
17099 eps_m,
17100 None,
17101 )?;
17102 }
17103 }
17104
17105 let route = {
17108 let _g = e0.gpu.enter_main()?;
17109 let mixed0 = ws0.take_f32(e0, "hc.mixed", hidden, 0)?;
17110 let mut router_out = ws0.take_f32(e0, "moe.router", experts, 0)?;
17111 let none: Option<CudaSlice<u8>> = None;
17112 let rb = if router_bf16_on() {
17113 &moe.router_b16
17114 } else {
17115 &none
17116 };
17117 linear_trunk_into(
17118 e0,
17119 &moe.router,
17120 rb,
17121 &mixed0,
17122 &mut router_out,
17123 1,
17124 hidden,
17125 experts,
17126 )?;
17127 let logits = e0.dtoh_view(&router_out.slice(0..experts))?;
17128 ws0.put_f32("moe.router", router_out);
17129 ws0.put_f32("hc.mixed", mixed0);
17130 host_route_softmax_topk(&logits, selected)
17131 };
17132 let place = &tw.place;
17136 let mut sel0: Vec<i32> = Vec::with_capacity(selected);
17137 let mut w0: Vec<f32> = Vec::with_capacity(selected);
17138 let mut sel1: Vec<i32> = Vec::with_capacity(selected);
17139 let mut w1: Vec<f32> = Vec::with_capacity(selected);
17140 for &(expert, weight) in &route {
17141 if place.rank(expert) == 0 {
17142 sel0.push(place.local(expert) as i32);
17143 w0.push(weight);
17144 } else {
17145 sel1.push(place.local(expert) as i32);
17146 w1.push(weight);
17147 }
17148 }
17149 {
17150 let r0: Vec<Vec<(usize, f32)>> = vec![
17153 sel0.iter()
17154 .zip(&w0)
17155 .map(|(&s, &w)| (s as usize, w))
17156 .collect(),
17157 ];
17158 let r1: Vec<Vec<(usize, f32)>> = vec![
17159 sel1.iter()
17160 .zip(&w1)
17161 .map(|(&s, &w)| (s as usize, w))
17162 .collect(),
17163 ];
17164 tp2_count_split(&r0, &r1);
17165 trace_moe_routes(layer.index, 1, std::slice::from_ref(&route));
17166 }
17167 match tp2_gate_red()? {
17168 Tp2GateRed::None => {}
17169 Tp2GateRed::SkipPeerMoe => {
17171 sel1.clear();
17172 w1.clear();
17173 }
17174 Tp2GateRed::PeerLocalIds => {
17177 sel0.extend(sel1.drain(..));
17178 w0.extend(w1.drain(..));
17179 }
17180 Tp2GateRed::ReverseePeerWeights => w1.reverse(),
17181 }
17182 let max_sel = selected;
17183 {
17184 let _g = e1.gpu.enter_main()?;
17185 ws1.upsert_u8(e1, "moe.pack", &tp2_pack_bytes(&sel1, &w1, max_sel), 0)?;
17186 if graphs_live {
17187 if tgraphs.c[1][li].is_none() {
17188 tgraphs.c[1][li] = Some(e1.capture_graph_retained_nowarm(|eng| {
17189 self.tp2_seg_c(
17190 eng,
17191 ws1,
17192 (
17193 &tw.moe.gate1.codes,
17194 &tw.moe.gate1.scales,
17195 &tw.moe.gate1.macros_dev,
17196 ),
17197 (
17198 &tw.moe.up1.codes,
17199 &tw.moe.up1.scales,
17200 &tw.moe.up1.macros_dev,
17201 ),
17202 (
17203 &tw.moe.down1.codes,
17204 &tw.moe.down1.scales,
17205 &tw.moe.down1.macros_dev,
17206 ),
17207 ff,
17208 max_sel,
17209 None,
17210 tw.moe.shared_input_gate1.is_some(),
17211 shard.stage0_raw[1],
17212 )
17213 })?);
17214 }
17215 tgraphs.c[1][li].as_ref().unwrap().0.launch()?;
17216 } else {
17217 self.tp2_seg_c(
17218 e1,
17219 ws1,
17220 (
17221 &tw.moe.gate1.codes,
17222 &tw.moe.gate1.scales,
17223 &tw.moe.gate1.macros_dev,
17224 ),
17225 (
17226 &tw.moe.up1.codes,
17227 &tw.moe.up1.scales,
17228 &tw.moe.up1.macros_dev,
17229 ),
17230 (
17231 &tw.moe.down1.codes,
17232 &tw.moe.down1.scales,
17233 &tw.moe.down1.macros_dev,
17234 ),
17235 ff,
17236 max_sel,
17237 None,
17238 tw.moe.shared_input_gate1.is_some(),
17239 shard.stage0_raw[1],
17240 )?;
17241 }
17242 shard.ev1[1].record(&e1.gpu.stream())?;
17243 }
17244 {
17245 let _g = e0.gpu.enter_main()?;
17246 let (
17247 BankHalf::Nvfp4 {
17248 codes: gc,
17249 scales: gs,
17250 macros_dev: gm,
17251 ..
17252 },
17253 BankHalf::Nvfp4 {
17254 codes: uc,
17255 scales: us,
17256 macros_dev: um,
17257 ..
17258 },
17259 BankHalf::Nvfp4 {
17260 codes: dc,
17261 scales: ds,
17262 macros_dev: dm,
17263 ..
17264 },
17265 ) = (&moe.bank.gate, &moe.bank.up, &moe.bank.down)
17266 else {
17267 return Err("qwen4exp_gpu tp2: card0 bank is not NVFP4".into());
17268 };
17269 ws0.upsert_u8(e0, "moe.pack", &tp2_pack_bytes(&sel0, &w0, max_sel), 0)?;
17270 if graphs_live {
17271 if tgraphs.c[0][li].is_none() {
17272 tgraphs.c[0][li] = Some(e0.capture_graph_retained_nowarm(|eng| {
17273 self.tp2_seg_c(
17274 eng,
17275 ws0,
17276 (gc, gs, gm),
17277 (uc, us, um),
17278 (dc, ds, dm),
17279 ff,
17280 max_sel,
17281 Some((
17282 &tw.moe.shared_gu0_b16,
17283 &tw.moe.shared_down0,
17284 moe.shared_input_gate.as_ref(),
17285 sffh,
17286 )),
17287 false,
17288 shard.stage1_raw[1],
17289 )
17290 })?);
17291 }
17292 tgraphs.c[0][li].as_ref().unwrap().0.launch()?;
17293 } else {
17294 self.tp2_seg_c(
17295 e0,
17296 ws0,
17297 (gc, gs, gm),
17298 (uc, us, um),
17299 (dc, ds, dm),
17300 ff,
17301 max_sel,
17302 Some((
17303 &tw.moe.shared_gu0_b16,
17304 &tw.moe.shared_down0,
17305 moe.shared_input_gate.as_ref(),
17306 sffh,
17307 )),
17308 false,
17309 shard.stage1_raw[1],
17310 )?;
17311 }
17312 shard.ev0[1].record(&e0.gpu.stream())?;
17313 }
17314 {
17315 let _g = e1.gpu.enter_main()?;
17316 e1.gpu.stream().wait(&shard.ev0[1])?;
17317 if graphs_live {
17318 if tgraphs.d[1][li].is_none() {
17319 tgraphs.d[1][li] = Some(e1.capture_graph_retained_nowarm(|eng| {
17320 self.tp2_seg_d(eng, ws1, &ptrs1, &mut planes1, &shard.stage1[1], false)
17321 })?);
17322 }
17323 tgraphs.d[1][li].as_ref().unwrap().0.launch()?;
17324 } else {
17325 self.tp2_seg_d(e1, ws1, &ptrs1, &mut planes1, &shard.stage1[1], false)?;
17326 }
17327 }
17328 {
17329 let _g = e0.gpu.enter_main()?;
17330 e0.gpu.stream().wait(&shard.ev1[1])?;
17331 if graphs_live {
17332 if tgraphs.d[0][li].is_none() {
17333 tgraphs.d[0][li] = Some(e0.capture_graph_retained_nowarm(|eng| {
17334 self.tp2_seg_d(eng, ws0, &ptrs0, &mut planes0, &shard.stage0[1], true)
17335 })?);
17336 }
17337 tgraphs.d[0][li].as_ref().unwrap().0.launch()?;
17338 } else {
17339 self.tp2_seg_d(e0, ws0, &ptrs0, &mut planes0, &shard.stage0[1], true)?;
17340 }
17341 }
17342 }
17343
17344 {
17346 let _g = e1.gpu.enter_main()?;
17347 if graphs_live {
17348 if tgraphs.exit[1].is_none() {
17349 tgraphs.exit[1] = Some(e1.capture_graph_retained_nowarm(|eng| {
17350 self.tp2_seg_exit(
17351 eng,
17352 ws1,
17353 &ptrs1,
17354 &shard.exit_gate1,
17355 &planes1,
17356 &shard.lm_head1,
17357 vocab - vsplit,
17358 1, )
17360 })?);
17361 }
17362 tgraphs.exit[1].as_ref().unwrap().0.launch()?;
17363 } else {
17364 self.tp2_seg_exit(
17365 e1,
17366 ws1,
17367 &ptrs1,
17368 &shard.exit_gate1,
17369 &planes1,
17370 &shard.lm_head1,
17371 vocab - vsplit,
17372 1, )?;
17374 }
17375 }
17376 {
17377 let _g = e0.gpu.enter_main()?;
17378 let head0 = self
17379 .output_b16
17380 .as_ref()
17381 .ok_or("qwen4exp_gpu tp2: lm_head has no bf16 twin")?;
17382 if graphs_live {
17383 if tgraphs.exit[0].is_none() {
17384 tgraphs.exit[0] = Some(e0.capture_graph_retained_nowarm(|eng| {
17385 self.tp2_seg_exit(
17386 eng,
17387 ws0,
17388 &ptrs0,
17389 &self.exit_mixer,
17390 &planes0,
17391 head0,
17392 vsplit,
17393 1, )
17395 })?);
17396 }
17397 tgraphs.exit[0].as_ref().unwrap().0.launch()?;
17398 } else {
17399 self.tp2_seg_exit(
17400 e0,
17401 ws0,
17402 &ptrs0,
17403 &self.exit_mixer,
17404 &planes0,
17405 head0,
17406 vsplit,
17407 1, )?;
17409 }
17410 }
17411 let mut out = vec![0.0f32; vocab];
17412 {
17413 let _g = e0.gpu.enter_main()?;
17414 let logits0 = ws0.peek_f32("logits")?;
17415 let host0 = e0.dtoh_view(&logits0.slice(0..vsplit))?;
17416 out[..vsplit].copy_from_slice(&host0);
17417 }
17418 {
17419 let _g = e1.gpu.enter_main()?;
17420 let logits1 = ws1.peek_f32("logits")?;
17421 let host1 = e1.dtoh_view(&logits1.slice(0..vocab - vsplit))?;
17422 out[vsplit..].copy_from_slice(&host1);
17423 }
17424 for (s, plane) in planes0.into_iter().enumerate() {
17425 ws0.put_f32(PLANE_SLOTS[s], plane);
17426 }
17427 for (s, plane) in planes1.into_iter().enumerate() {
17428 ws1.put_f32(PLANE_SLOTS[s], plane);
17429 }
17430 ws0.put_u64("hc.ptrs", ptrs0);
17431 ws1.put_u64("hc.ptrs", ptrs1);
17432 state.pos += 1;
17433 Ok(out)
17434 }
17435}
17436
17437impl Qwen4ExpGpu {
17438 pub fn alloc_state_tp2(
17444 &self,
17445 e0: &Engine,
17446 e1: &Engine,
17447 shard: &Tp2Shard,
17448 capacity: usize,
17449 reserve: usize,
17450 ) -> Res<Qwen4ExpState> {
17451 let mut state = {
17455 let mut st = self.alloc_state_reserve(e0, 1, 1, None)?;
17462 st.capacity = capacity;
17463 st.reserve = reserve;
17464 st
17465 };
17466 let mut tlayers = Vec::with_capacity(self.layers.len());
17467 for (layer, tw) in self.layers.iter().zip(shard.layers.iter()) {
17468 let mk_half = |e: &Engine, hw: &MixerHalfW| -> Res<MixerHalfState> {
17469 let _g = e.gpu.enter_main()?;
17470 match hw {
17471 MixerHalfW::Gdn(h) => {
17472 let conv_dim = 2 * h.nk_h * h.hk + h.nv_h * h.hv;
17473 let pad = h.kernel - 1;
17474 Ok(MixerHalfState::Gdn {
17475 conv: e.zeros(pad * conv_dim)?,
17476 state: e.zeros(h.nv_h * h.hv * h.hk)?,
17477 })
17478 }
17479 MixerHalfW::Qsa(h) => {
17480 let kv_dim = h.nkv_h * h.hd;
17481 let kv = if kv_quant_on() {
17482 QsaKvStore::Q8Q5 {
17483 k: e.alloc_u8(capacity * q8_row_bytes(kv_dim))?,
17484 v: e.alloc_u8(capacity * q5_row_bytes(kv_dim))?,
17485 }
17486 } else {
17487 QsaKvStore::F32 {
17488 k: e.zeros(capacity * kv_dim)?,
17489 v: e.zeros(capacity * kv_dim)?,
17490 }
17491 };
17492 Ok(MixerHalfState::Qsa { kv })
17493 }
17494 }
17495 };
17496 let m0 = mk_half(e0, &tw.mixer0)?;
17497 let m1 = mk_half(e1, &tw.mixer1)?;
17498 let ple1 = match layer.ple.as_ref() {
17499 None => None,
17500 Some(ple) => {
17501 let pad = (ple.plan.conv_kernel as usize - 1) * ple.plan.max_ngram as usize;
17502 let _g = e1.gpu.enter_main()?;
17503 let mut conv_hist = Vec::with_capacity(self.streams);
17504 for _ in 0..self.streams {
17505 conv_hist.push(e1.zeros(pad * self.hidden)?);
17506 }
17507 Some(PleState {
17508 conv_hist,
17509 ngram_ids: Vec::new(),
17510 ngram_history: Vec::new(),
17511 ngram_last_eos: -1,
17512 })
17513 }
17514 };
17515 tlayers.push(Tp2LayerState { m0, m1, ple1 });
17516 }
17517 state.tp2 = Some(Tp2State {
17518 ws1: StepPool::default(),
17519 layers: tlayers,
17520 graphs: Tp2Graphs::default(),
17521 pf_stage0: None,
17522 pf_stage1: None,
17523 pf_stage0_raw: [0; 2],
17524 pf_stage1_raw: [0; 2],
17525 pf_rows: 0,
17526 });
17527 Ok(state)
17528 }
17529
17530 pub fn prefill_extend_tp2(
17535 &self,
17536 e0: &Engine,
17537 e1: &Engine,
17538 shard: &Tp2Shard,
17539 ids: &[u32],
17540 state: &mut Qwen4ExpState,
17541 chunk: usize,
17542 ) -> Res<Vec<f32>> {
17543 if ids.is_empty() || chunk == 0 {
17544 return Err("qwen4exp_gpu: prefill_extend_tp2 needs ids and a chunk size".into());
17545 }
17546 let mut last = Vec::new();
17547 for piece in ids.chunks(chunk) {
17548 let is_last =
17549 piece.as_ptr() as usize + piece.len() * 4 == ids.as_ptr() as usize + ids.len() * 4;
17550 let head = if is_last {
17551 HeadMode::LastRow
17552 } else {
17553 HeadMode::Skip
17554 };
17555 last = self.forward_tp2(e0, e1, shard, piece, state, head)?;
17556 }
17557 Ok(last)
17558 }
17559
17560 #[allow(clippy::too_many_arguments)]
17566 pub fn forward_tp2(
17567 &self,
17568 e0: &Engine,
17569 e1: &Engine,
17570 shard: &Tp2Shard,
17571 ids: &[u32],
17572 state: &mut Qwen4ExpState,
17573 head: HeadMode,
17574 ) -> Res<Vec<f32>> {
17575 if !trunk_bf16_on() || !hc_fused_gate_on() {
17576 return Err(
17577 "qwen4exp_gpu tp2: requires set_trunk_bf16(true) and set_hc_fused_gate(true)"
17578 .into(),
17579 );
17580 }
17581 let t = ids.len();
17582 if t == 0 {
17583 return Err("qwen4exp_gpu tp2: empty chunk".into());
17584 }
17585 if state.pos + t > state.capacity {
17586 return Err("qwen4exp_gpu: state capacity exceeded".into());
17587 }
17588 if state.tp2.is_none() {
17589 self.tp2_migrate(e0, e1, shard, state)?;
17590 state.graphs = StepGraphs::default();
17591 }
17592 let hidden = self.hidden;
17593 let vocab = self.vocab;
17594 let vsplit = shard.vsplit;
17595 let base_pos = state.pos;
17596 let reserve = state.reserve;
17597 state.tokens.extend_from_slice(ids);
17598 let Qwen4ExpState {
17599 ref tokens,
17600 ws: ref mut ws0,
17601 ref mut tp2,
17602 layers: ref mut lstates,
17603 ..
17604 } = *state;
17605 let tp2s = tp2.as_mut().expect("alloc'd or migrated above");
17606 if tp2s.pf_rows < t {
17609 {
17610 let _g = e1.gpu.enter_main()?;
17611 let s1 = [e1.zeros(t * hidden)?, e1.zeros(t * hidden)?];
17612 let s = e1.gpu.stream();
17613 tp2s.pf_stage1_raw = [s1[0].device_ptr(&s).0 as u64, s1[1].device_ptr(&s).0 as u64];
17614 tp2s.pf_stage1 = Some(s1);
17615 }
17616 {
17617 let _g = e0.gpu.enter_main()?;
17618 let s0 = [e0.zeros(t * hidden)?, e0.zeros(t * hidden)?];
17619 let s = e0.gpu.stream();
17620 tp2s.pf_stage0_raw = [s0[0].device_ptr(&s).0 as u64, s0[1].device_ptr(&s).0 as u64];
17621 tp2s.pf_stage0 = Some(s0);
17622 }
17623 tp2s.pf_rows = t;
17624 }
17625 let Tp2State {
17626 ws1,
17627 layers: tlayers,
17628 pf_stage0,
17629 pf_stage1,
17630 pf_stage0_raw,
17631 pf_stage1_raw,
17632 ..
17633 } = tp2s;
17634 let pf_stage0 = pf_stage0.as_ref().expect("sized above");
17635 let pf_stage1 = pf_stage1.as_ref().expect("sized above");
17636 let resv = reserve.max(t);
17637
17638 let mut embedded = vec![0.0f32; t * hidden];
17640 for (row, &token) in ids.iter().enumerate() {
17641 let token = token as usize;
17642 if token >= vocab {
17643 return Err(format!("qwen4exp_gpu: token {token} out of range").into());
17644 }
17645 embedded[row * hidden..(row + 1) * hidden]
17646 .copy_from_slice(&self.embed_host[token * hidden..(token + 1) * hidden]);
17647 }
17648 let mut planes1: Vec<CudaSlice<f32>> = Vec::with_capacity(self.streams);
17649 let ptrs1 = {
17650 let _g = e1.gpu.enter_main()?;
17651 let embedded_dev = ws1.take_f32_h2d(e1, "entry.embed", &embedded, resv * hidden)?;
17652 for s in 0..self.streams {
17653 let mut plane = ws1.take_f32(e1, PLANE_SLOTS[s], t * hidden, resv * hidden)?;
17654 e1.copy_into(&mut plane, 0, &embedded_dev, t * hidden)?;
17655 planes1.push(plane);
17656 }
17657 ws1.put_f32("entry.embed", embedded_dev);
17658 let ptr_vals: Vec<u64> = {
17659 let stream = e1.gpu.stream();
17660 planes1.iter().map(|p| p.device_ptr(&stream).0).collect()
17661 };
17662 ws1.take_u64_h2d(e1, "hc.ptrs", &ptr_vals, 0)?
17663 };
17664 let mut planes0: Vec<CudaSlice<f32>> = Vec::with_capacity(self.streams);
17665 let ptrs0 = {
17666 let _g = e0.gpu.enter_main()?;
17667 let embedded_dev = ws0.take_f32_h2d(e0, "entry.embed", &embedded, resv * hidden)?;
17668 for s in 0..self.streams {
17669 let mut plane = ws0.take_f32(e0, PLANE_SLOTS[s], t * hidden, resv * hidden)?;
17670 e0.copy_into(&mut plane, 0, &embedded_dev, t * hidden)?;
17671 planes0.push(plane);
17672 }
17673 ws0.put_f32("entry.embed", embedded_dev);
17674 let ptr_vals: Vec<u64> = {
17675 let stream = e0.gpu.stream();
17676 planes0.iter().map(|p| p.device_ptr(&stream).0).collect()
17677 };
17678 ws0.take_u64_h2d(e0, "hc.ptrs", &ptr_vals, 0)?
17679 };
17680
17681 for (li, layer) in self.layers.iter().enumerate() {
17682 let lstate = &mut lstates[li];
17683 let tw = &shard.layers[li];
17684 let ts = &mut tlayers[li];
17685 let eps_a = layer.eps_attn;
17686 let eps_m = layer.eps_mlp;
17687 let moe = &layer.moe;
17688 let ff = moe.plan.expert_intermediate_size as usize;
17689 let experts = moe.plan.expert_count as usize;
17690 let selected = moe.plan.experts_per_token as usize;
17691 let sff = moe
17692 .plan
17693 .shared
17694 .as_ref()
17695 .map(|s| s.intermediate_size as usize)
17696 .unwrap_or(0);
17697 let sffh = sff / 2;
17698
17699 if let (Some(ple), Some(ps)) = (layer.ple.as_ref(), lstate.ple.as_mut()) {
17701 let _g = e0.gpu.enter_main()?;
17702 self.ple_block(
17703 e0,
17704 layer,
17705 ple,
17706 &ple.table,
17707 ps,
17708 &mut planes0,
17709 tokens,
17710 t,
17711 false,
17712 None,
17713 )?;
17714 }
17715 if let (Some(ple1), Some(ps1)) = (tw.ple1.as_ref(), ts.ple1.as_mut()) {
17716 let table = &layer.ple.as_ref().expect("ple plan").table;
17717 let _g = e1.gpu.enter_main()?;
17718 self.ple_block(
17719 e1,
17720 layer,
17721 ple1,
17722 table,
17723 ps1,
17724 &mut planes1,
17725 tokens,
17726 t,
17727 false,
17728 None,
17729 )?;
17730 }
17731
17732 match (&layer.mixer, &tw.mixer0, &tw.mixer1) {
17734 (MixerW::Gdn(_), MixerHalfW::Gdn(h0), MixerHalfW::Gdn(h1)) => {
17735 {
17736 let _g = e1.gpu.enter_main()?;
17737 let (mixed1, inj1) = self.gate_read(
17738 e1,
17739 ws1,
17740 &ptrs1,
17741 &tw.attn_gate1,
17742 &planes1,
17743 t,
17744 eps_a,
17745 false,
17746 )?;
17747 let p1 =
17748 self.gdn_forward_half(e1, ws1, eps_a, h1, &mixed1, &mut ts.m1, t)?;
17749 ws1.put_f32("hc.mixed", mixed1);
17750 launch_push(e1, &p1, pf_stage0_raw[0], t * hidden)?;
17751 ws1.put_f32("mixer.out", p1);
17752 put_inject(ws1, inj1);
17753 shard.ev1[0].record(&e1.gpu.stream())?;
17754 }
17755 {
17756 let _g = e0.gpu.enter_main()?;
17757 let (mixed0, inj0) = self.gate_read(
17758 e0,
17759 ws0,
17760 &ptrs0,
17761 &layer.attn_gate,
17762 &planes0,
17763 t,
17764 eps_a,
17765 false,
17766 )?;
17767 let p0 =
17768 self.gdn_forward_half(e0, ws0, eps_a, h0, &mixed0, &mut ts.m0, t)?;
17769 ws0.put_f32("hc.mixed", mixed0);
17770 launch_push(e0, &p0, pf_stage1_raw[0], t * hidden)?;
17771 ws0.put_f32("mixer.out", p0);
17772 put_inject(ws0, inj0);
17773 shard.ev0[0].record(&e0.gpu.stream())?;
17774 }
17775 }
17776 (MixerW::Qsa(qsa), MixerHalfW::Qsa(h0), MixerHalfW::Qsa(h1)) => {
17777 let (q1, g1, inj1) = {
17778 let _g = e1.gpu.enter_main()?;
17779 let (mixed1, inj1) = self.gate_read(
17780 e1,
17781 ws1,
17782 &ptrs1,
17783 &tw.attn_gate1,
17784 &planes1,
17785 t,
17786 eps_a,
17787 false,
17788 )?;
17789 let (q1, g1) = self
17790 .qsa_half_proj(e1, ws1, eps_a, h1, &mixed1, &mut ts.m1, base_pos, t)?;
17791 ws1.put_f32("hc.mixed", mixed1);
17792 (q1, g1, inj1)
17793 };
17794 let (sels, q0, g0, inj0) = {
17795 let _g = e0.gpu.enter_main()?;
17796 let (mixed0, inj0) = self.gate_read(
17797 e0,
17798 ws0,
17799 &ptrs0,
17800 &layer.attn_gate,
17801 &planes0,
17802 t,
17803 eps_a,
17804 false,
17805 )?;
17806 let (q0, g0) = self
17807 .qsa_half_proj(e0, ws0, eps_a, h0, &mixed0, &mut ts.m0, base_pos, t)?;
17808 let MixerState::Qsa {
17809 raw_keys,
17810 pooled_keys,
17811 pooled_dev,
17812 pooled_dev_rows,
17813 raw_dev,
17814 raw_dev_rows,
17815 idx_audit,
17816 ..
17817 } = &mut lstate.mixer
17818 else {
17819 return Err("qwen4exp_gpu tp2: QSA layer without raw-key cache".into());
17820 };
17821 let sels = self.qsa_update_select(
17822 e0,
17823 ws0,
17824 qsa,
17825 eps_a,
17826 &mixed0,
17827 raw_keys,
17828 pooled_keys,
17829 pooled_dev,
17830 pooled_dev_rows,
17831 raw_dev,
17832 raw_dev_rows,
17833 idx_audit.as_mut(),
17834 base_pos,
17835 t,
17836 0,
17837 false,
17838 )?;
17839 ws0.put_f32("hc.mixed", mixed0);
17840 (sels, q0, g0, inj0)
17841 };
17842 let t_kv = base_pos + t;
17843 let block_size = qsa.overlay.block_size as usize;
17844 let (pos_flat, meta, max_count) = rowsel_positions(&sels, block_size);
17845 {
17846 let _g = e1.gpu.enter_main()?;
17847 let pos_dev = ws1.take_i32(e1, "qsa.selpos", &pos_flat, 0)?;
17848 let meta_dev = ws1.take_i32(e1, "qsa.selmeta", &meta, 0)?;
17849 let p1 = self.qsa_half_attend(
17850 e1, ws1, h1, &ts.m1, q1, g1, &pos_dev, &meta_dev, max_count, t, t_kv,
17851 )?;
17852 ws1.put_i32("qsa.selpos", pos_dev);
17853 ws1.put_i32("qsa.selmeta", meta_dev);
17854 launch_push(e1, &p1, pf_stage0_raw[0], t * hidden)?;
17855 ws1.put_f32("mixer.out", p1);
17856 put_inject(ws1, inj1);
17857 shard.ev1[0].record(&e1.gpu.stream())?;
17858 }
17859 {
17860 let _g = e0.gpu.enter_main()?;
17861 let pos_dev = ws0.take_i32(e0, "qsa.selpos", &pos_flat, 0)?;
17862 let meta_dev = ws0.take_i32(e0, "qsa.selmeta", &meta, 0)?;
17863 let p0 = self.qsa_half_attend(
17864 e0, ws0, h0, &ts.m0, q0, g0, &pos_dev, &meta_dev, max_count, t, t_kv,
17865 )?;
17866 ws0.put_i32("qsa.selpos", pos_dev);
17867 ws0.put_i32("qsa.selmeta", meta_dev);
17868 launch_push(e0, &p0, pf_stage1_raw[0], t * hidden)?;
17869 ws0.put_f32("mixer.out", p0);
17870 put_inject(ws0, inj0);
17871 shard.ev0[0].record(&e0.gpu.stream())?;
17872 }
17873 }
17874 _ => return Err("qwen4exp_gpu tp2: mixer/shard shape mismatch".into()),
17875 }
17876 {
17877 let _g = e0.gpu.enter_main()?;
17878 e0.gpu.stream().wait(&shard.ev1[0])?;
17879 }
17880 {
17881 let _g = e1.gpu.enter_main()?;
17882 e1.gpu.stream().wait(&shard.ev0[0])?;
17883 }
17884
17885 let join_write = |e: &Engine,
17887 ws: &mut StepPool,
17888 ptrs: &CudaSlice<u64>,
17889 planes: &mut [CudaSlice<f32>],
17890 stage: &CudaSlice<f32>,
17891 rank0: bool|
17892 -> Res<()> {
17893 let p = ws.take_f32(e, "mixer.out", t * hidden, 0)?;
17894 let mut out = ws.take_f32(e, "join.out", t * hidden, 0)?;
17895 if rank0 {
17896 e.add(&p, stage, &mut out, t * hidden)?;
17897 } else {
17898 e.add(stage, &p, &mut out, t * hidden)?;
17899 }
17900 let inj = take_inject(e, ws, self.streams, t)?;
17901 self.gate_write(e, planes, ptrs, &out, &inj, t)?;
17902 ws.put_f32("mixer.out", p);
17903 ws.put_f32("join.out", out);
17904 put_inject(ws, inj);
17905 Ok(())
17906 };
17907 {
17908 let _g = e1.gpu.enter_main()?;
17909 join_write(e1, ws1, &ptrs1, &mut planes1, &pf_stage1[0], false)?;
17910 }
17911 {
17912 let _g = e0.gpu.enter_main()?;
17913 join_write(e0, ws0, &ptrs0, &mut planes0, &pf_stage0[0], true)?;
17914 }
17915
17916 let mixed1 = {
17918 let _g = e1.gpu.enter_main()?;
17919 let (mixed1, injm1) =
17920 self.gate_read(e1, ws1, &ptrs1, &tw.mlp_gate1, &planes1, t, eps_m, false)?;
17921 put_inject(ws1, injm1);
17922 mixed1
17923 };
17924 let mixed0 = {
17925 let _g = e0.gpu.enter_main()?;
17926 let (mixed0, injm0) =
17927 self.gate_read(e0, ws0, &ptrs0, &layer.mlp_gate, &planes0, t, eps_m, false)?;
17928 put_inject(ws0, injm0);
17929 mixed0
17930 };
17931 let routes: Vec<Vec<(usize, f32)>> = {
17934 let _g = e0.gpu.enter_main()?;
17935 let mut router_out = ws0.take_f32(e0, "moe.router", t * experts, 0)?;
17936 let none: Option<CudaSlice<u8>> = None;
17937 let rb = if router_bf16_on() {
17938 &moe.router_b16
17939 } else {
17940 &none
17941 };
17942 linear_trunk_into(
17943 e0,
17944 &moe.router,
17945 rb,
17946 &mixed0,
17947 &mut router_out,
17948 t,
17949 hidden,
17950 experts,
17951 )?;
17952 let logits = e0.dtoh_view(&router_out.slice(0..t * experts))?;
17953 ws0.put_f32("moe.router", router_out);
17954 let mut routes = Vec::with_capacity(t);
17955 for token in 0..t {
17956 routes.push(host_route_softmax_topk(
17957 &logits[token * experts..(token + 1) * experts],
17958 selected,
17959 ));
17960 }
17961 routes
17962 };
17963 let place = &tw.place;
17967 let split_half = |home: bool| -> Vec<Vec<(usize, f32)>> {
17968 routes
17969 .iter()
17970 .map(|r| {
17971 r.iter()
17972 .filter(|&&(eid, _)| (place.rank(eid) == 0) == home)
17973 .map(|&(eid, w)| (place.local(eid), w))
17974 .collect()
17975 })
17976 .collect()
17977 };
17978 let mut routes0 = split_half(true);
17979 let mut routes1 = split_half(false);
17980 tp2_count_split(&routes0, &routes1);
17983 trace_moe_routes(layer.index, t, &routes);
17984 match tp2_gate_red()? {
17985 Tp2GateRed::None => {}
17986 Tp2GateRed::SkipPeerMoe => routes1.iter_mut().for_each(|r| r.clear()),
17987 Tp2GateRed::PeerLocalIds => {
17988 for (r0, r1) in routes0.iter_mut().zip(routes1.iter_mut()) {
17989 r0.append(r1);
17990 }
17991 }
17992 Tp2GateRed::ReverseePeerWeights => {
17993 for r in routes1.iter_mut() {
17994 let n = r.len();
17995 for i in 0..n / 2 {
17996 let (a, b) = (r[i].1, r[n - 1 - i].1);
17997 r[i].1 = b;
17998 r[n - 1 - i].1 = a;
17999 }
18000 }
18001 }
18002 }
18003 let (routes0, routes1) = (routes0, routes1);
18004 {
18006 let _g = e1.gpu.enter_main()?;
18007 let mut out1 = self.tp2_moe_rows(
18008 e1,
18009 ws1,
18010 (
18011 &tw.moe.gate1.codes,
18012 &tw.moe.gate1.scales,
18013 &tw.moe.gate1.macros_dev,
18014 ),
18015 (
18016 &tw.moe.up1.codes,
18017 &tw.moe.up1.scales,
18018 &tw.moe.up1.macros_dev,
18019 ),
18020 (
18021 &tw.moe.down1.codes,
18022 &tw.moe.down1.scales,
18023 &tw.moe.down1.macros_dev,
18024 ),
18025 &routes1,
18026 &mixed1,
18027 t,
18028 ff,
18029 )?;
18030 let (sh, g) = self.tp2_shared_half(
18031 e1,
18032 ws1,
18033 &tw.moe.shared_gu1_b16,
18034 &tw.moe.shared_down1,
18035 tw.moe.shared_input_gate1.as_ref(),
18036 &mixed1,
18037 sffh,
18038 t,
18039 )?;
18040 match g.as_ref() {
18041 Some(g) => e1.add_scaled_rows(&sh, g, &mut out1, hidden, t)?,
18042 None => {
18043 let mut summed = ws1.take_f32(e1, "moe.sum", t * hidden, 0)?;
18044 e1.add(&out1, &sh, &mut summed, t * hidden)?;
18045 ws1.put_f32("moe.out", out1);
18046 out1 = summed;
18047 }
18048 }
18049 ws1.put_f32("moe.sh_down", sh);
18050 if let Some(g) = g {
18051 ws1.put_f32("moe.g", g);
18052 }
18053 launch_push(e1, &out1, pf_stage0_raw[1], t * hidden)?;
18054 ws1.put_f32("moe.out", out1);
18055 ws1.put_f32("hc.mixed", mixed1);
18056 shard.ev1[1].record(&e1.gpu.stream())?;
18057 }
18058 {
18061 let _g = e0.gpu.enter_main()?;
18062 let (
18063 BankHalf::Nvfp4 {
18064 codes: gc,
18065 scales: gs,
18066 macros_dev: gm,
18067 ..
18068 },
18069 BankHalf::Nvfp4 {
18070 codes: uc,
18071 scales: us,
18072 macros_dev: um,
18073 ..
18074 },
18075 BankHalf::Nvfp4 {
18076 codes: dc,
18077 scales: ds,
18078 macros_dev: dm,
18079 ..
18080 },
18081 ) = (&moe.bank.gate, &moe.bank.up, &moe.bank.down)
18082 else {
18083 return Err("qwen4exp_gpu tp2: card0 bank is not NVFP4".into());
18084 };
18085 let mut out0 = self.tp2_moe_rows(
18086 e0,
18087 ws0,
18088 (gc, gs, gm),
18089 (uc, us, um),
18090 (dc, ds, dm),
18091 &routes0,
18092 &mixed0,
18093 t,
18094 ff,
18095 )?;
18096 let (sh, g) = self.tp2_shared_half(
18097 e0,
18098 ws0,
18099 &tw.moe.shared_gu0_b16,
18100 &tw.moe.shared_down0,
18101 moe.shared_input_gate.as_ref(),
18102 &mixed0,
18103 sffh,
18104 t,
18105 )?;
18106 match g.as_ref() {
18107 Some(g) => e0.add_scaled_rows(&sh, g, &mut out0, hidden, t)?,
18108 None => {
18109 let mut summed = ws0.take_f32(e0, "moe.sum", t * hidden, 0)?;
18110 e0.add(&out0, &sh, &mut summed, t * hidden)?;
18111 ws0.put_f32("moe.out", out0);
18112 out0 = summed;
18113 }
18114 }
18115 ws0.put_f32("moe.sh_down", sh);
18116 if let Some(g) = g {
18117 ws0.put_f32("moe.g", g);
18118 }
18119 launch_push(e0, &out0, pf_stage1_raw[1], t * hidden)?;
18120 ws0.put_f32("moe.out", out0);
18121 ws0.put_f32("hc.mixed", mixed0);
18122 shard.ev0[1].record(&e0.gpu.stream())?;
18123 }
18124 {
18125 let _g = e1.gpu.enter_main()?;
18126 e1.gpu.stream().wait(&shard.ev0[1])?;
18127 let p = ws1.take_f32(e1, "moe.out", t * hidden, 0)?;
18128 let mut out = ws1.take_f32(e1, "join.out", t * hidden, 0)?;
18129 e1.add(&pf_stage1[1], &p, &mut out, t * hidden)?;
18130 let inj = take_inject(e1, ws1, self.streams, t)?;
18131 self.gate_write(e1, &mut planes1, &ptrs1, &out, &inj, t)?;
18132 ws1.put_f32("moe.out", p);
18133 ws1.put_f32("join.out", out);
18134 put_inject(ws1, inj);
18135 }
18136 {
18137 let _g = e0.gpu.enter_main()?;
18138 e0.gpu.stream().wait(&shard.ev1[1])?;
18139 let p = ws0.take_f32(e0, "moe.out", t * hidden, 0)?;
18140 let mut out = ws0.take_f32(e0, "join.out", t * hidden, 0)?;
18141 e0.add(&p, &pf_stage0[1], &mut out, t * hidden)?;
18142 let inj = take_inject(e0, ws0, self.streams, t)?;
18143 self.gate_write(e0, &mut planes0, &ptrs0, &out, &inj, t)?;
18144 ws0.put_f32("moe.out", p);
18145 ws0.put_f32("join.out", out);
18146 put_inject(ws0, inj);
18147 }
18148 }
18149
18150 let head_rows = match head {
18167 HeadMode::All => t,
18168 _ => 1,
18169 };
18170 let mut out = vec![
18171 0.0f32;
18172 if head == HeadMode::Skip {
18173 0
18174 } else {
18175 head_rows * vocab
18176 }
18177 ];
18178 if head != HeadMode::Skip {
18179 {
18180 let _g = e1.gpu.enter_main()?;
18181 let mut exit_planes: Vec<CudaSlice<f32>> = Vec::with_capacity(self.streams);
18185 if head != HeadMode::All {
18186 for (s, plane) in planes1.iter().enumerate() {
18187 let mut row = ws1.take_f32(e1, EXIT_PLANE_SLOTS[s], hidden, hidden)?;
18188 e1.copy_range_into(&mut row, 0, plane, (t - 1) * hidden, hidden)?;
18189 exit_planes.push(row);
18190 }
18191 }
18192 let use_planes: &[CudaSlice<f32>] = if head == HeadMode::All {
18193 &planes1
18194 } else {
18195 &exit_planes
18196 };
18197 let ptr_vals: Vec<u64> = {
18198 let stream = e1.gpu.stream();
18199 use_planes.iter().map(|p| p.device_ptr(&stream).0).collect()
18200 };
18201 let eptrs = ws1.take_u64_h2d(e1, "exit.ptrs", &ptr_vals, 0)?;
18202 self.tp2_seg_exit(
18203 e1,
18204 ws1,
18205 &eptrs,
18206 &shard.exit_gate1,
18207 use_planes,
18208 &shard.lm_head1,
18209 vocab - vsplit,
18210 head_rows,
18211 )?;
18212 ws1.put_u64("exit.ptrs", eptrs);
18213 for (s, p) in exit_planes.into_iter().enumerate() {
18214 ws1.put_f32(EXIT_PLANE_SLOTS[s], p);
18215 }
18216 let logits1 = ws1.peek_f32("logits")?;
18217 let half1 = vocab - vsplit;
18218 let host1 = e1.dtoh_view(&logits1.slice(0..head_rows * half1))?;
18219 for r in 0..head_rows {
18222 out[r * vocab + vsplit..(r + 1) * vocab]
18223 .copy_from_slice(&host1[r * half1..(r + 1) * half1]);
18224 }
18225 }
18226 {
18227 let _g = e0.gpu.enter_main()?;
18228 let head0 = self
18229 .output_b16
18230 .as_ref()
18231 .ok_or("qwen4exp_gpu tp2: lm_head has no bf16 twin")?;
18232 let mut exit_planes: Vec<CudaSlice<f32>> = Vec::with_capacity(self.streams);
18233 if head != HeadMode::All {
18234 for (s, plane) in planes0.iter().enumerate() {
18235 let mut row = ws0.take_f32(e0, EXIT_PLANE_SLOTS[s], hidden, hidden)?;
18236 e0.copy_range_into(&mut row, 0, plane, (t - 1) * hidden, hidden)?;
18237 exit_planes.push(row);
18238 }
18239 }
18240 let use_planes: &[CudaSlice<f32>] = if head == HeadMode::All {
18241 &planes0
18242 } else {
18243 &exit_planes
18244 };
18245 let ptr_vals: Vec<u64> = {
18246 let stream = e0.gpu.stream();
18247 use_planes.iter().map(|p| p.device_ptr(&stream).0).collect()
18248 };
18249 let eptrs = ws0.take_u64_h2d(e0, "exit.ptrs", &ptr_vals, 0)?;
18250 self.tp2_seg_exit(
18251 e0,
18252 ws0,
18253 &eptrs,
18254 &self.exit_mixer,
18255 use_planes,
18256 head0,
18257 vsplit,
18258 head_rows,
18259 )?;
18260 ws0.put_u64("exit.ptrs", eptrs);
18261 for (s, p) in exit_planes.into_iter().enumerate() {
18262 ws0.put_f32(EXIT_PLANE_SLOTS[s], p);
18263 }
18264 let logits0 = ws0.peek_f32("logits")?;
18265 let host0 = e0.dtoh_view(&logits0.slice(0..head_rows * vsplit))?;
18266 for r in 0..head_rows {
18268 out[r * vocab..r * vocab + vsplit]
18269 .copy_from_slice(&host0[r * vsplit..(r + 1) * vsplit]);
18270 }
18271 }
18272 } else {
18273 {
18276 let _g = e0.gpu.enter_main()?;
18277 e0.gpu.stream().synchronize()?;
18278 }
18279 {
18280 let _g = e1.gpu.enter_main()?;
18281 e1.gpu.stream().synchronize()?;
18282 }
18283 }
18284 for (s, plane) in planes0.into_iter().enumerate() {
18285 ws0.put_f32(PLANE_SLOTS[s], plane);
18286 }
18287 for (s, plane) in planes1.into_iter().enumerate() {
18288 ws1.put_f32(PLANE_SLOTS[s], plane);
18289 }
18290 ws0.put_u64("hc.ptrs", ptrs0);
18291 ws1.put_u64("hc.ptrs", ptrs1);
18292 state.pos += t;
18293 Ok(out)
18294 }
18295
18296 #[allow(clippy::too_many_arguments)]
18302 fn tp2_moe_rows(
18303 &self,
18304 e: &Engine,
18305 ws: &mut StepPool,
18306 gate: (&CudaSlice<u8>, &CudaSlice<u8>, &CudaSlice<f32>),
18307 up: (&CudaSlice<u8>, &CudaSlice<u8>, &CudaSlice<f32>),
18308 down: (&CudaSlice<u8>, &CudaSlice<u8>, &CudaSlice<f32>),
18309 routes: &[Vec<(usize, f32)>],
18310 mixed: &CudaSlice<f32>,
18311 t: usize,
18312 ff: usize,
18313 ) -> Res<CudaSlice<f32>> {
18314 let hidden = self.hidden;
18315 if !(sel_gufuse_on() && hidden % 32 == 0 && ff % 4 == 0) {
18316 return Err(
18317 "qwen4exp_gpu tp2: prefill MoE needs the gufuse geometry (hidden%32, ff%4)".into(),
18318 );
18319 }
18320 let mut out = ws.take_f32(e, "moe.out", t * hidden, 0)?;
18321 {
18322 let mut view = out.slice_mut(0..t * hidden);
18323 e.memset_zeros_view(&mut view)?;
18324 }
18325 const SLOT_CAP: usize = 8192;
18326 let mut tok0 = 0usize;
18327 while tok0 < t {
18328 let mut tok_n = 0usize;
18330 let mut slots = 0usize;
18331 while tok0 + tok_n < t {
18332 let n = routes[tok0 + tok_n].len();
18333 if tok_n > 0 && slots + n > SLOT_CAP {
18334 break;
18335 }
18336 slots += n;
18337 tok_n += 1;
18338 }
18339 let batch = &routes[tok0..tok0 + tok_n];
18340 let mut sel_all: Vec<i32> = Vec::with_capacity(slots);
18341 let mut w_all: Vec<f32> = Vec::with_capacity(slots);
18342 let mut tok_all: Vec<i32> = Vec::with_capacity(slots);
18343 let mut ranges: Vec<(usize, usize)> = Vec::with_capacity(tok_n);
18344 for (i, route) in batch.iter().enumerate() {
18345 ranges.push((sel_all.len(), route.len()));
18346 for &(eid, wgt) in route {
18347 sel_all.push(eid as i32);
18348 w_all.push(wgt);
18349 tok_all.push((tok0 + i) as i32);
18350 }
18351 }
18352 let s_total = sel_all.len();
18353 if s_total > 0 {
18354 let sel = ws.take_i32(e, "moe.sel", &sel_all, 0)?;
18355 let w_dev = ws.take_f32_h2d(e, "moe.w", &w_all, 0)?;
18356 let tokm = ws.take_i32(e, "moe.tok", &tok_all, 0)?;
18357 let mut act = ws.take_f32(e, "moe.act", s_total * ff, 0)?;
18358 launch_nvfp4_sel_gu_silu(
18359 e,
18360 gate,
18361 up,
18362 Some(&sel),
18363 0,
18364 s_total,
18365 mixed,
18366 &mut act,
18367 hidden,
18368 ff,
18369 Some((&tokm, hidden)),
18370 )?;
18371 let mut partial = ws.take_f32(e, "moe.partial", s_total * hidden, 0)?;
18372 launch_nvfp4_sel_matvec(
18373 e,
18374 down.0,
18375 down.1,
18376 down.2,
18377 &sel,
18378 &act,
18379 &mut partial,
18380 s_total,
18381 ff,
18382 hidden,
18383 ff,
18384 )?;
18385 for (i, &(start, len)) in ranges.iter().enumerate() {
18386 if len > 0 {
18387 launch_axpy_rows_seq_at(
18388 e,
18389 &partial,
18390 start,
18391 &w_dev,
18392 start,
18393 &mut out,
18394 tok0 + i,
18395 hidden,
18396 len,
18397 )?;
18398 }
18399 }
18400 ws.put_i32("moe.sel", sel);
18401 ws.put_i32("moe.tok", tokm);
18402 ws.put_f32("moe.w", w_dev);
18403 ws.put_f32("moe.act", act);
18404 ws.put_f32("moe.partial", partial);
18405 }
18406 tok0 += tok_n;
18407 }
18408 Ok(out)
18409 }
18410
18411 #[allow(clippy::too_many_arguments)]
18414 fn tp2_gdn_seg_a(
18415 &self,
18416 e: &Engine,
18417 ws: &mut StepPool,
18418 ptrs: &CudaSlice<u64>,
18419 attn_gate: &GateW,
18420 h: &GdnHalfW,
18421 hstate: &mut MixerHalfState,
18422 planes: &[CudaSlice<f32>],
18423 eps: f32,
18424 push_raw: u64,
18425 ) -> Res<()> {
18426 let (mixed, inj) = self.gate_read(e, ws, ptrs, attn_gate, planes, 1, eps, false)?;
18427 let p = self.gdn_forward_half(e, ws, eps, h, &mixed, hstate, 1)?;
18428 ws.put_f32("hc.mixed", mixed);
18429 launch_push(e, &p, push_raw, self.hidden)?;
18430 ws.put_f32("mixer.out", p);
18431 put_inject(ws, inj);
18432 Ok(())
18433 }
18434
18435 #[allow(clippy::too_many_arguments)]
18440 fn tp2_seg_b(
18441 &self,
18442 e: &Engine,
18443 ws: &mut StepPool,
18444 ptrs: &CudaSlice<u64>,
18445 mlp_gate: &GateW,
18446 planes: &mut [CudaSlice<f32>],
18447 stage_recv: &CudaSlice<f32>,
18448 rank0: bool,
18449 eps_m: f32,
18450 shared: Option<(
18451 &CudaSlice<u8>,
18452 &CudaSlice<u8>,
18453 Option<&CudaSlice<f32>>,
18454 usize,
18455 )>,
18456 ) -> Res<()> {
18457 let hidden = self.hidden;
18458 let p = ws.take_f32(e, "mixer.out", hidden, 0)?;
18459 let mut out = ws.take_f32(e, "join.out", hidden, 0)?;
18460 if rank0 {
18461 e.add(&p, stage_recv, &mut out, hidden)?;
18462 } else {
18463 e.add(stage_recv, &p, &mut out, hidden)?;
18464 }
18465 let inj = take_inject(e, ws, self.streams, 1)?;
18466 self.gate_write(e, planes, ptrs, &out, &inj, 1)?;
18467 ws.put_f32("mixer.out", p);
18468 ws.put_f32("join.out", out);
18469 put_inject(ws, inj);
18470 let (mixed, injm) = self.gate_read(e, ws, ptrs, mlp_gate, planes, 1, eps_m, false)?;
18471 if let Some((gu_b16, d_b16, ig, sffh)) = shared {
18472 let (sh, gg) = self.tp2_shared_half(e, ws, gu_b16, d_b16, ig, &mixed, sffh, 1)?;
18473 ws.put_f32("moe.sh_down", sh);
18477 if let Some(gg) = gg {
18478 ws.put_f32("moe.g", gg);
18479 }
18480 }
18481 ws.put_f32("hc.mixed", mixed);
18482 put_inject(ws, injm);
18483 Ok(())
18484 }
18485
18486 #[allow(clippy::too_many_arguments)]
18489 fn tp2_seg_exit(
18499 &self,
18500 e: &Engine,
18501 ws: &mut StepPool,
18502 ptrs: &CudaSlice<u64>,
18503 gate: &GateW,
18504 planes: &[CudaSlice<f32>],
18505 head_b16: &CudaSlice<u8>,
18506 out_f: usize,
18507 rows: usize,
18508 ) -> Res<()> {
18509 let x = self
18510 .gate_read_inner(e, ws, ptrs, gate, planes, rows, self.exit_eps, false, false)?
18511 .0;
18512 let mut logits = ws.take_f32(e, "logits", rows * out_f, rows * out_f)?;
18513 launch_qmatvec_bf16w(
18514 e,
18515 head_b16,
18516 &x,
18517 &mut logits,
18518 self.hidden,
18519 out_f,
18520 rows,
18521 1,
18522 0,
18523 0,
18524 self.hidden,
18525 0,
18526 )?;
18527 ws.put_f32("hc.mixed", x);
18528 ws.put_f32("logits", logits);
18529 Ok(())
18530 }
18531}
18532
18533#[allow(clippy::too_many_arguments)]
18537fn launch_nvfp4_sel_matvec_pack(
18538 e: &Engine,
18539 codes: &CudaSlice<u8>,
18540 scales: &CudaSlice<u8>,
18541 macros_dev: &CudaSlice<f32>,
18542 pack_raw: u64,
18543 max_sel: usize,
18544 x: &CudaSlice<f32>,
18545 y: &mut CudaSlice<f32>,
18546 in_f: usize,
18547 out_f: usize,
18548 x_stride: usize,
18549) -> Res<()> {
18550 if in_f % 32 != 0 || out_f % 4 != 0 {
18551 return Err(
18552 "qmatvec_nvfp4_modelopt_sel_f32_v3c: geometry needs in_f%32==0 && out_f%4==0".into(),
18553 );
18554 }
18555 let f = e.func("qmatvec_nvfp4_modelopt_sel_f32_v3c");
18556 let cfg = LaunchConfig {
18557 grid_dim: ((out_f / 4) as u32, max_sel as u32, 1),
18558 block_dim: (32, 1, 1),
18559 shared_mem_bytes: 0,
18560 };
18561 let (inf, outf, ms) = (in_f as i32, out_f as i32, max_sel as i32);
18562 let xs = x_stride as i64;
18563 let stream = e.gpu.stream();
18564 let mut b = stream.launch_builder(&f);
18565 b.arg(codes)
18566 .arg(scales)
18567 .arg(macros_dev)
18568 .arg(&pack_raw)
18569 .arg(&ms)
18570 .arg(x)
18571 .arg(y)
18572 .arg(&inf)
18573 .arg(&outf)
18574 .arg(&xs);
18575 unsafe {
18576 b.launch(cfg)?;
18577 }
18578 Ok(())
18579}
18580
18581fn launch_axpy_rows_seq_pack(
18582 e: &Engine,
18583 x: &CudaSlice<f32>,
18584 pack_raw: u64,
18585 max_sel: usize,
18586 y: &mut CudaSlice<f32>,
18587 width: usize,
18588) -> Res<()> {
18589 let f = e.func("axpy_rows_seq_pack_f32");
18590 let cfg = LaunchConfig::for_num_elems(width as u32);
18591 let (ms, wi) = (max_sel as i32, width as i32);
18592 let stream = e.gpu.stream();
18593 let mut b = stream.launch_builder(&f);
18594 b.arg(x).arg(&pack_raw).arg(&ms).arg(y).arg(&wi);
18595 unsafe {
18596 b.launch(cfg)?;
18597 }
18598 Ok(())
18599}
18600
18601fn tp2_pack_bytes(sel: &[i32], w: &[f32], max_sel: usize) -> Vec<u8> {
18603 let mut out = Vec::with_capacity((2 * max_sel + 1) * 4);
18604 for i in 0..max_sel {
18605 out.extend_from_slice(&sel.get(i).copied().unwrap_or(0).to_le_bytes());
18606 }
18607 for i in 0..max_sel {
18608 out.extend_from_slice(&w.get(i).copied().unwrap_or(0.0).to_le_bytes());
18609 }
18610 out.extend_from_slice(&(sel.len() as i32).to_le_bytes());
18611 out
18612}
18613
18614impl Qwen4ExpGpu {
18615 #[allow(clippy::too_many_arguments)]
18620 fn tp2_seg_c(
18621 &self,
18622 e: &Engine,
18623 ws: &mut StepPool,
18624 gate: (&CudaSlice<u8>, &CudaSlice<u8>, &CudaSlice<f32>),
18625 up: (&CudaSlice<u8>, &CudaSlice<u8>, &CudaSlice<f32>),
18626 down: (&CudaSlice<u8>, &CudaSlice<u8>, &CudaSlice<f32>),
18627 ff: usize,
18628 max_sel: usize,
18629 shared_compute: Option<(
18630 &CudaSlice<u8>,
18631 &CudaSlice<u8>,
18632 Option<&CudaSlice<f32>>,
18633 usize,
18634 )>,
18635 shared_gated: bool,
18636 push_raw: u64,
18637 ) -> Res<()> {
18638 let hidden = self.hidden;
18639 let pack_raw = {
18640 let pack = ws.peek_u8("moe.pack")?;
18641 let stream = e.gpu.stream();
18642 pack.device_ptr(&stream).0
18643 };
18644 let mixed = ws.take_f32(e, "hc.mixed", hidden, 0)?;
18645 let mut act = ws.take_f32(e, "moe.act", max_sel * ff, 0)?;
18646 if sel_gufuse_on() && hidden % 32 == 0 && ff % 4 == 0 {
18650 launch_nvfp4_sel_gu_silu(
18651 e, gate, up, None, pack_raw, max_sel, &mixed, &mut act, hidden, ff, None,
18652 )?;
18653 } else {
18654 let mut yg = ws.take_f32(e, "moe.yg", max_sel * ff, 0)?;
18655 let mut yu = ws.take_f32(e, "moe.yu", max_sel * ff, 0)?;
18656 launch_nvfp4_sel_matvec_pack(
18657 e, gate.0, gate.1, gate.2, pack_raw, max_sel, &mixed, &mut yg, hidden, ff, 0,
18658 )?;
18659 launch_nvfp4_sel_matvec_pack(
18660 e, up.0, up.1, up.2, pack_raw, max_sel, &mixed, &mut yu, hidden, ff, 0,
18661 )?;
18662 e.silu_mul(&yg, &yu, &mut act, max_sel * ff)?;
18663 ws.put_f32("moe.yg", yg);
18664 ws.put_f32("moe.yu", yu);
18665 }
18666 let mut partial = ws.take_f32(e, "moe.partial", max_sel * hidden, 0)?;
18667 launch_nvfp4_sel_matvec_pack(
18668 e,
18669 down.0,
18670 down.1,
18671 down.2,
18672 pack_raw,
18673 max_sel,
18674 &act,
18675 &mut partial,
18676 ff,
18677 hidden,
18678 ff,
18679 )?;
18680 let mut r = ws.take_f32(e, "moe.out", hidden, 0)?;
18681 launch_axpy_rows_seq_pack(e, &partial, pack_raw, max_sel, &mut r, hidden)?;
18682 ws.put_f32("moe.act", act);
18683 ws.put_f32("moe.partial", partial);
18684 let (sh, g) = match shared_compute {
18685 Some((gu_b16, d_b16, ig, sffh)) => {
18686 self.tp2_shared_half(e, ws, gu_b16, d_b16, ig, &mixed, sffh, 1)?
18687 }
18688 None => {
18689 let sh = ws.take_f32(e, "moe.sh_down", hidden, 0)?;
18690 let g = if shared_gated {
18691 Some(ws.take_f32(e, "moe.g", 1, 0)?)
18692 } else {
18693 None
18694 };
18695 (sh, g)
18696 }
18697 };
18698 match g.as_ref() {
18699 Some(g) => e.add_scaled_rows(&sh, g, &mut r, hidden, 1)?,
18700 None => {
18701 let mut view = r.slice_mut(0..hidden);
18702 e.axpy_into(&sh, 1.0, &mut view, hidden)?;
18703 }
18704 }
18705 ws.put_f32("moe.sh_down", sh);
18706 if let Some(g) = g {
18707 ws.put_f32("moe.g", g);
18708 }
18709 launch_push(e, &r, push_raw, hidden)?;
18710 ws.put_f32("moe.out", r);
18711 ws.put_f32("hc.mixed", mixed);
18712 Ok(())
18713 }
18714
18715 #[allow(clippy::too_many_arguments)]
18717 fn tp2_seg_d(
18718 &self,
18719 e: &Engine,
18720 ws: &mut StepPool,
18721 ptrs: &CudaSlice<u64>,
18722 planes: &mut [CudaSlice<f32>],
18723 stage_recv: &CudaSlice<f32>,
18724 rank0: bool,
18725 ) -> Res<()> {
18726 let hidden = self.hidden;
18727 let mp = ws.take_f32(e, "moe.out", hidden, 0)?;
18728 let mut mo = ws.take_f32(e, "join.out", hidden, 0)?;
18729 if rank0 {
18730 e.add(&mp, stage_recv, &mut mo, hidden)?;
18731 } else {
18732 e.add(stage_recv, &mp, &mut mo, hidden)?;
18733 }
18734 let injm = take_inject(e, ws, self.streams, 1)?;
18735 self.gate_write(e, planes, ptrs, &mo, &injm, 1)?;
18736 ws.put_f32("moe.out", mp);
18737 ws.put_f32("join.out", mo);
18738 put_inject(ws, injm);
18739 Ok(())
18740 }
18741}