1use crate::Engine;
49use crate::dsv4_ffi as k;
50use crate::dsv4_ffi::ck;
51use crate::model::GpuTensor;
52use cudarc::driver::{CudaSlice, CudaStream, DevicePtr, DevicePtrMut};
53use memra_gguf::model_plan::{HcCollapse, ModelPlan, ResidualTopology};
54use memra_gguf::source::TensorSource;
55use std::os::raw::c_void;
56
57type Res<T> = Result<T, Box<dyn std::error::Error>>;
58
59fn sp(stream: &CudaStream) -> *mut c_void {
60 stream.cu_stream() as *mut c_void
61}
62
63macro_rules! dpf {
64 ($slice:expr, $stream:expr) => {{ $slice.device_ptr($stream).0 as *const f32 }};
65}
66macro_rules! dpm {
67 ($slice:expr, $stream:expr) => {{ $slice.device_ptr_mut($stream).0 as *mut f32 }};
68}
69
70#[derive(Debug, Clone, Copy, PartialEq)]
72pub struct HyperTopology {
73 pub streams: usize,
74 pub epsilon: f32,
75 pub sinkhorn_iterations: u32,
76 pub collapse: HcCollapse,
77}
78
79impl HyperTopology {
80 pub fn rows(&self) -> usize {
83 (2 + self.streams) * self.streams
84 }
85
86 pub fn from_plan(plan: &ModelPlan) -> Result<Option<Self>, String> {
90 let mut found: Option<Self> = None;
91 for layer in &plan.layers {
92 let ResidualTopology::HyperConnections {
93 streams,
94 epsilon,
95 sinkhorn_iterations,
96 collapse,
97 } = layer.residual
98 else {
99 if found.is_some() {
100 return Err(format!(
101 "layer {} declares a serial/gemma residual while an earlier trunk layer \
102 declares HyperConnections; the topology must be uniform across the trunk",
103 layer.index
104 ));
105 }
106 continue;
107 };
108 let this = Self {
109 streams: streams as usize,
110 epsilon,
111 sinkhorn_iterations,
112 collapse,
113 };
114 if streams == 0 || epsilon <= 0.0 || sinkhorn_iterations == 0 {
115 return Err(format!(
116 "layer {}: HyperConnections need streams > 0, epsilon > 0 and \
117 sinkhorn_iterations > 0, got streams={streams} epsilon={epsilon} \
118 iterations={sinkhorn_iterations}",
119 layer.index
120 ));
121 }
122 match found {
123 None if layer.index != plan.layers[0].index => {
124 return Err(format!(
125 "layer {} declares HyperConnections but earlier trunk layers do not; the \
126 topology must be uniform across the trunk",
127 layer.index
128 ));
129 }
130 None => found = Some(this),
131 Some(first) if first != this => {
132 return Err(format!(
133 "layer {} declares {this:?} but the trunk opened with {first:?}; the \
134 topology must be uniform across the trunk",
135 layer.index
136 ));
137 }
138 Some(_) => {}
139 }
140 }
141 Ok(found)
142 }
143}
144
145pub struct HyperSite {
150 pub fn_w: CudaSlice<f32>,
151 pub base: CudaSlice<f32>,
152 pub scale: CudaSlice<f32>,
153}
154
155pub struct HyperLayer {
157 pub attn: HyperSite,
158 pub mlp: HyperSite,
159}
160
161pub struct HyperHead {
164 pub fn_w: CudaSlice<f32>,
165 pub base: CudaSlice<f32>,
166 pub scale: CudaSlice<f32>,
167}
168
169fn float_data<'a>(name: &str, t: &'a GpuTensor, want: usize) -> Result<&'a CudaSlice<f32>, String> {
173 let data = match t {
174 GpuTensor::Float { data, .. } => data,
175 GpuTensor::Quant { .. } => {
176 return Err(format!(
177 "{name}: hyper-connection parameters must be f32-resident, got a quantized \
178 tensor; re-mint this tensor unquantized (the whole hc program is an f32 island)"
179 ));
180 }
181 GpuTensor::FloatBf16 { .. } => {
182 return Err(format!(
183 "{name}: hyper-connection parameters must be f32-resident, got a bf16-resident \
184 matmul weight"
185 ));
186 }
187 };
188 if data.len() != want {
189 return Err(format!(
190 "{name}: {} elements, the plan's HyperConnections require {want}",
191 data.len()
192 ));
193 }
194 Ok(data)
195}
196
197fn load_site(
202 e: &Engine,
203 src: &dyn TensorSource,
204 il: u32,
205 topology: &HyperTopology,
206 hidden: usize,
207 site: &str,
208) -> Res<HyperSite> {
209 let rows = topology.rows();
210 let width = topology.streams * hidden;
211 let mut out: Vec<CudaSlice<f32>> = Vec::with_capacity(3);
212 for (suffix, want) in [
213 ("fn", rows * width),
214 ("base", rows),
215 ("scale", 3),
218 ] {
219 let name = format!("blk.{il}.{site}_{suffix}");
221 if !src.has(&name) {
222 return Err(format!(
223 "{name} is absent, but the compiled ModelPlan declares \
224 ResidualTopology::HyperConnections{{ streams: {} }} for layer {il}. Refusing to \
225 load: a serial residual would compute a different model, silently.",
226 topology.streams
227 )
228 .into());
229 }
230 let loaded = GpuTensor::load_from_source(e, src, &name)?;
231 out.push(e.clone_dtod(float_data(&name, &loaded, want)?)?);
232 }
233 let mut out = out.into_iter();
234 Ok(HyperSite {
235 fn_w: out.next().expect("function"),
236 base: out.next().expect("base"),
237 scale: out.next().expect("scale"),
238 })
239}
240
241impl HyperLayer {
242 pub fn load(
243 e: &Engine,
244 src: &dyn TensorSource,
245 il: u32,
246 topology: &HyperTopology,
247 hidden: usize,
248 ) -> Res<Self> {
249 Ok(Self {
250 attn: load_site(e, src, il, topology, hidden, "hc_attn")?,
251 mlp: load_site(e, src, il, topology, hidden, "hc_ffn")?,
252 })
253 }
254}
255
256impl HyperHead {
257 pub fn load(
260 e: &Engine,
261 src: &dyn TensorSource,
262 topology: &HyperTopology,
263 hidden: usize,
264 ) -> Res<Option<Self>> {
265 if topology.collapse != HcCollapse::GatedHead {
266 return Ok(None);
267 }
268 let streams = topology.streams;
269 let mut out: Vec<CudaSlice<f32>> = Vec::with_capacity(3);
270 for (name, want) in [
274 ("hc_head_fn", streams * streams * hidden),
275 ("hc_head_base", streams),
276 ("hc_head_scale", 1),
277 ] {
278 if !src.has(name) {
279 return Err(format!(
280 "{name} is absent, but the compiled ModelPlan declares \
281 HcCollapse::GatedHead. Refusing to load: collapsing with an unweighted mean \
282 instead would compute a different model, silently."
283 )
284 .into());
285 }
286 let loaded = GpuTensor::load_from_source(e, src, name)?;
287 out.push(e.clone_dtod(float_data(name, &loaded, want)?)?);
288 }
289 let mut out = out.into_iter();
290 Ok(Some(Self {
291 fn_w: out.next().expect("function"),
292 base: out.next().expect("base"),
293 scale: out.next().expect("scale"),
294 }))
295 }
296}
297
298pub struct HcMix {
301 pub post: CudaSlice<f32>,
302 pub comb: CudaSlice<f32>,
303}
304
305pub static HC_FUSED_PRE_DISPATCHES: std::sync::atomic::AtomicU64 =
309 std::sync::atomic::AtomicU64::new(0);
310
311pub static HC_FUSED_PRE_V2_DISPATCHES: std::sync::atomic::AtomicU64 =
314 std::sync::atomic::AtomicU64::new(0);
315
316#[derive(Clone, Copy, PartialEq, Eq, Debug)]
322pub enum HcFusedPreArm {
323 Off,
324 V1,
325 V2,
326}
327
328fn hc_fused_pre_arm() -> HcFusedPreArm {
335 hc_fused_pre_arm_from(
336 std::env::var("MEMRA_HC_FUSED_PRE").ok().as_deref(),
337 env!("MEMRA_BUILT_CUDA_ARCH"),
338 )
339}
340
341pub fn hc_fused_pre_arm_from(v: Option<&str>, built_arch: &str) -> HcFusedPreArm {
346 match v.map(str::trim) {
347 Some("1") => HcFusedPreArm::V1,
348 Some("2") => HcFusedPreArm::V2,
349 Some("0") => HcFusedPreArm::Off,
350 _ if built_arch == "100a" => HcFusedPreArm::V2,
351 _ => HcFusedPreArm::Off,
352 }
353}
354
355pub fn expand(
357 e: &Engine,
358 topology: &HyperTopology,
359 embedded: &CudaSlice<f32>,
360 t: usize,
361 hidden: usize,
362) -> Res<CudaSlice<f32>> {
363 let streams = topology.streams;
364 let mut out = e.uninit(t * streams * hidden)?;
365 let stream = e.stream();
366 unsafe {
367 ck(
368 "hc_expand",
369 k::memra_dsv4_hc_expand(
370 dpf!(embedded, &stream),
371 dpm!(out, &stream),
372 t as i32,
373 streams as i32,
374 hidden as i32,
375 sp(&stream),
376 ),
377 )?;
378 }
379 Ok(out)
380}
381
382pub fn pre(
385 e: &Engine,
386 topology: &HyperTopology,
387 site: &HyperSite,
388 x: &CudaSlice<f32>,
389 t: usize,
390 hidden: usize,
391) -> Res<(CudaSlice<f32>, HcMix)> {
392 let width = topology.streams * hidden;
393 let mixes = e.linear(x, &site.fn_w, t, width, topology.rows())?;
394 pre_finish(e, topology, site, x, mixes, t, hidden)
395}
396
397pub fn pre_exact(
406 e: &Engine,
407 topology: &HyperTopology,
408 site: &HyperSite,
409 x: &CudaSlice<f32>,
410 t: usize,
411 hidden: usize,
412) -> Res<(CudaSlice<f32>, HcMix)> {
413 let rows = topology.rows();
414 let width = topology.streams * hidden;
415 let mut mixes = e.uninit(t * rows)?;
416 for r in 0..t {
417 let xr = x.slice(r * width..(r + 1) * width);
418 let wv = site.fn_w.slice(0..site.fn_w.len());
419 let mut yr = mixes.slice_mut(r * rows..(r + 1) * rows);
420 hc_mixes_into(e, &xr, &wv, &mut yr, width, rows)
421 .map_err(|err| format!("hc pre_exact row {r}: {err}"))?;
422 }
423 pre_finish(e, topology, site, x, mixes, t, hidden)
424}
425
426fn pre_finish(
431 e: &Engine,
432 topology: &HyperTopology,
433 site: &HyperSite,
434 x: &CudaSlice<f32>,
435 mut mixes: CudaSlice<f32>,
436 t: usize,
437 hidden: usize,
438) -> Res<(CudaSlice<f32>, HcMix)> {
439 let streams = topology.streams;
440 let mut pre_gates = e.uninit(t * streams)?;
441 let mut post = e.uninit(t * streams)?;
442 let mut comb = e.uninit(t * streams * streams)?;
443 let mut y = e.uninit(t * hidden)?;
444 pre_finish_into(
445 e,
446 topology,
447 site,
448 x,
449 &mut mixes,
450 &mut pre_gates,
451 &mut post,
452 &mut comb,
453 &mut y,
454 t,
455 hidden,
456 )?;
457 Ok((y, HcMix { post, comb }))
458}
459
460#[allow(clippy::too_many_arguments)] fn pre_finish_into(
465 e: &Engine,
466 topology: &HyperTopology,
467 site: &HyperSite,
468 x: &CudaSlice<f32>,
469 mixes: &mut CudaSlice<f32>,
470 pre_gates: &mut CudaSlice<f32>,
471 post: &mut CudaSlice<f32>,
472 comb: &mut CudaSlice<f32>,
473 y: &mut CudaSlice<f32>,
474 t: usize,
475 hidden: usize,
476) -> Res<()> {
477 let streams = topology.streams;
478 let rows = topology.rows();
479 let width = streams * hidden;
480 let eps = topology.epsilon;
481 let stream = e.stream();
482
483 let fused_arm = hc_fused_pre_arm();
491 if fused_arm != HcFusedPreArm::Off && streams <= 8 {
492 let (label, rc) = unsafe {
493 match fused_arm {
494 HcFusedPreArm::V1 => (
495 "hc_pre_fused",
496 k::memra_dsv4_hc_pre_fused(
497 dpf!(x, &stream),
498 dpf!(mixes, &stream),
499 dpf!(site.scale, &stream),
500 dpf!(site.base, &stream),
501 dpm!(pre_gates, &stream),
502 dpm!(post, &stream),
503 dpm!(comb, &stream),
504 dpm!(y, &stream),
505 t as i32,
506 streams as i32,
507 hidden as i32,
508 topology.sinkhorn_iterations as i32,
509 eps,
510 std::ptr::null_mut(),
511 sp(&stream),
512 ),
513 ),
514 HcFusedPreArm::V2 if crate::hc_pre_block() != 128 || crate::hc_pre_sink_reg() => {
522 let v4 = if crate::hc_pre_v4_on() {
526 let rc = k::memra_dsv4_hc_pre_v4(
527 dpf!(x, &stream),
528 dpf!(mixes, &stream),
529 dpf!(site.scale, &stream),
530 dpf!(site.base, &stream),
531 dpm!(pre_gates, &stream),
532 dpm!(post, &stream),
533 dpm!(comb, &stream),
534 dpm!(y, &stream),
535 t as i32,
536 streams as i32,
537 hidden as i32,
538 topology.sinkhorn_iterations as i32,
539 eps,
540 std::ptr::null_mut(),
541 crate::hc_pre_block() as i32,
542 sp(&stream),
543 );
544 if rc == 40025 {
545 None
546 } else {
547 if rc == 0 {
548 HC_PRE_V4_DISPATCHES
549 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
550 }
551 Some(("hc_pre_v4", rc))
552 }
553 } else {
554 None
555 };
556 match v4 {
557 Some(done) => done,
558 None => (
559 "hc_pre_fused_v3",
560 k::memra_dsv4_hc_pre_fused_v3(
561 dpf!(x, &stream),
562 dpf!(mixes, &stream),
563 dpf!(site.scale, &stream),
564 dpf!(site.base, &stream),
565 dpm!(pre_gates, &stream),
566 dpm!(post, &stream),
567 dpm!(comb, &stream),
568 dpm!(y, &stream),
569 t as i32,
570 streams as i32,
571 hidden as i32,
572 topology.sinkhorn_iterations as i32,
573 eps,
574 std::ptr::null_mut(),
575 crate::hc_pre_block() as i32,
576 crate::hc_pre_sink_reg() as i32,
577 sp(&stream),
578 ),
579 ),
580 }
581 }
582 HcFusedPreArm::V2 => (
583 "hc_pre_fused_v2",
584 k::memra_dsv4_hc_pre_fused_v2(
585 dpf!(x, &stream),
586 dpf!(mixes, &stream),
587 dpf!(site.scale, &stream),
588 dpf!(site.base, &stream),
589 dpm!(pre_gates, &stream),
590 dpm!(post, &stream),
591 dpm!(comb, &stream),
592 dpm!(y, &stream),
593 t as i32,
594 streams as i32,
595 hidden as i32,
596 topology.sinkhorn_iterations as i32,
597 eps,
598 std::ptr::null_mut(),
599 sp(&stream),
600 ),
601 ),
602 HcFusedPreArm::Off => unreachable!("guarded by the enclosing if"),
603 }
604 };
605 ck(label, rc)?;
606 let block = crate::hc_pre_block();
612 let (counter, tag) = match fused_arm {
613 HcFusedPreArm::V1 => (&HC_FUSED_PRE_DISPATCHES, "1"),
614 HcFusedPreArm::V2 => (&HC_FUSED_PRE_V2_DISPATCHES, "2"),
615 HcFusedPreArm::Off => unreachable!("guarded by the enclosing if"),
616 };
617 if counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed) == 0 {
618 let kern = if fused_arm == HcFusedPreArm::V2
619 && HC_PRE_V4_DISPATCHES.load(std::sync::atomic::Ordering::Relaxed) > 0
620 {
621 "hc_pre_v4"
622 } else if fused_arm == HcFusedPreArm::V2 && (block != 128 || crate::hc_pre_sink_reg()) {
623 "hc_pre_fused_v3"
624 } else if fused_arm == HcFusedPreArm::V2 {
625 "hc_pre_fused_v2"
626 } else {
627 "hc_pre_fused"
628 };
629 eprintln!(
630 "[hc-fused-pre] engaged streams={streams} hidden={hidden} t={t} arm={tag} \
631 kernel={kern} block={block} sinkhorn={} (one launch replaces rowsq_scale + \
632 sinkhorn + collapse per site; MEMRA_HC_FUSED_PRE={tag}, MEMRA_HC_PRE_BLOCK={block})",
633 if crate::hc_pre_sink_reg() {
634 "registers"
635 } else {
636 "shared"
637 }
638 );
639 }
640 return Ok(());
641 }
642 unsafe {
643 ck(
644 "hc rowsq_scale",
645 k::memra_dsv4_rowsq_scale(
646 dpf!(x, &stream),
647 dpm!(mixes, &stream),
648 t as i32,
649 width as i32,
650 rows as i32,
651 eps,
652 sp(&stream),
653 ),
654 )?;
655 ck(
656 "hc_sinkhorn",
657 k::memra_dsv4_hc_sinkhorn_m(
658 dpf!(mixes, &stream),
659 dpf!(site.scale, &stream),
660 dpf!(site.base, &stream),
661 dpm!(pre_gates, &stream),
662 dpm!(post, &stream),
663 dpm!(comb, &stream),
664 t as i32,
665 streams as i32,
666 topology.sinkhorn_iterations as i32,
667 eps,
668 sp(&stream),
669 ),
670 )?;
671 ck(
672 "hc_collapse",
673 k::memra_dsv4_hc_collapse(
674 dpf!(x, &stream),
675 dpf!(pre_gates, &stream),
676 dpm!(y, &stream),
677 t as i32,
678 streams as i32,
679 hidden as i32,
680 sp(&stream),
681 ),
682 )?;
683 }
684 Ok(())
685}
686
687pub struct HyperDecodeWs {
702 pub mixes: CudaSlice<f32>,
703 pub pre: CudaSlice<f32>,
704 pub post: CudaSlice<f32>,
705 pub comb: CudaSlice<f32>,
706 pub y: CudaSlice<f32>,
707 pub h: CudaSlice<f32>,
709 pub z: CudaSlice<f32>,
711 pub xb: CudaSlice<f32>,
713 streams: usize,
714 hidden: usize,
715}
716
717impl HyperDecodeWs {
718 pub fn new(e: &Engine, topology: &HyperTopology, hidden: usize) -> Res<Self> {
719 let streams = topology.streams;
720 Ok(Self {
721 mixes: e.uninit(topology.rows())?,
722 pre: e.uninit(streams)?,
723 post: e.uninit(streams)?,
724 comb: e.uninit(streams * streams)?,
725 y: e.uninit(hidden)?,
726 h: e.uninit(hidden)?,
727 z: e.uninit(hidden)?,
728 xb: e.uninit(streams * hidden)?,
729 streams,
730 hidden,
731 })
732 }
733
734 pub fn matches(&self, topology: &HyperTopology, hidden: usize) -> bool {
737 self.streams == topology.streams && self.hidden == hidden
738 }
739}
740
741pub fn pre_t1_ws(
747 e: &Engine,
748 topology: &HyperTopology,
749 site: &HyperSite,
750 x: &CudaSlice<f32>,
751 ws: &mut HyperDecodeWs,
752 hidden: usize,
753) -> Res<()> {
754 let rows = topology.rows();
755 let width = topology.streams * hidden;
756 {
757 let xr = x.slice(0..width);
758 let wv = site.fn_w.slice(0..site.fn_w.len());
759 let mut yr = ws.mixes.slice_mut(0..rows);
760 hc_mixes_into(e, &xr, &wv, &mut yr, width, rows)
761 .map_err(|err| format!("hc pre_t1_ws mixes: {err}"))?;
762 }
763 let ws = &mut *ws;
764 pre_finish_into(
765 e,
766 topology,
767 site,
768 x,
769 &mut ws.mixes,
770 &mut ws.pre,
771 &mut ws.post,
772 &mut ws.comb,
773 &mut ws.y,
774 1,
775 hidden,
776 )
777}
778
779#[derive(Clone, Copy, PartialEq, Eq, Debug)]
786pub enum NormDst {
787 H,
788 Z,
789}
790
791pub static HC_MIXES_KERNEL_DISPATCHES: std::sync::atomic::AtomicU64 =
792 std::sync::atomic::AtomicU64::new(0);
793
794fn hc_mixes_into(
797 e: &Engine,
798 x: &cudarc::driver::CudaView<'_, f32>,
799 w: &cudarc::driver::CudaView<'_, f32>,
800 y: &mut cudarc::driver::CudaViewMut<'_, f32>,
801 in_f: usize,
802 out_f: usize,
803) -> Result<(), Box<dyn std::error::Error>> {
804 if Engine::hc_mixes_kernel_on() && e.hc_mixes_gemv_into(x, w, y, in_f, out_f)? {
805 if HC_MIXES_KERNEL_DISPATCHES.fetch_add(1, std::sync::atomic::Ordering::Relaxed) == 0 {
806 eprintln!(
807 "[hc-mixes-kernel] engaged in_f={in_f} out_f={out_f} (native hc_mixes_gemv_f32 \
808 in place of cuBLASLt dot+reduce; MEMRA_HC_MIXES_KERNEL=1, numeric class)"
809 );
810 }
811 return Ok(());
812 }
813 e.linear_t1_into(x, w, y, in_f, out_f)
814}
815
816pub static HC_PRE_ZQ8_DISPATCHES: std::sync::atomic::AtomicU64 =
817 std::sync::atomic::AtomicU64::new(0);
818
819pub static HC_PRE_V4_DISPATCHES: std::sync::atomic::AtomicU64 =
821 std::sync::atomic::AtomicU64::new(0);
822
823pub static HC_PRE_V4Z_DISPATCHES: std::sync::atomic::AtomicU64 =
825 std::sync::atomic::AtomicU64::new(0);
826
827fn hc_pre_zq8_selfcheck() -> bool {
828 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
829 *ON.get_or_init(|| std::env::var("MEMRA_HC_PRE_ZQ8").as_deref() == Ok("2"))
830}
831
832static HC_PRE_ZQ8_CHECK_SITES: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
833static HC_PRE_ZQ8_CHECK_BAD: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
834
835#[allow(clippy::too_many_arguments)]
841fn pre_t1_ws_zq8_selfcheck(
842 e: &Engine,
843 topology: &HyperTopology,
844 site: &HyperSite,
845 x: &CudaSlice<f32>,
846 ws: &mut HyperDecodeWs,
847 hidden: usize,
848 norm_w: &CudaSlice<f32>,
849 dst: NormDst,
850 eps_norm: f32,
851) -> Res<()> {
852 let streams = topology.streams;
853 let rows = topology.rows();
854 let width = streams * hidden;
855 let block = crate::hc_pre_block();
856 let rms_bd = crate::rms_block() as usize;
857 let stream = e.stream();
858 {
859 let xr = x.slice(0..width);
860 let wv = site.fn_w.slice(0..site.fn_w.len());
861 let mut yr = ws.mixes.slice_mut(0..rows);
862 e.linear_t1_into(&xr, &wv, &mut yr, width, rows)
863 .map_err(|err| format!("hc pre_t1_ws_zq8 selfcheck mixes: {err}"))?;
864 }
865 let mut s_pre = e.uninit(streams)?;
867 let mut s_post = e.uninit(streams)?;
868 let mut s_comb = e.uninit(streams * streams)?;
869 let mut s_y = e.uninit(hidden)?;
870 let mut s_z = e.uninit(hidden)?;
871 let mut s_q = e.uninit_i8(hidden)?;
872 let mut s_d = e.uninit(hidden / 32)?;
873 unsafe {
874 ck(
875 "hc_pre_zq8 (selfcheck fused)",
876 if crate::hc_pre_v4z_on() {
877 k::memra_dsv4_hc_pre_v4z(
878 dpf!(x, &stream),
879 dpf!(&ws.mixes, &stream),
880 dpf!(site.scale, &stream),
881 dpf!(site.base, &stream),
882 dpm!(&mut s_pre, &stream),
883 dpm!(&mut s_post, &stream),
884 dpm!(&mut s_comb, &stream),
885 dpm!(&mut s_y, &stream),
886 1,
887 streams as i32,
888 hidden as i32,
889 topology.sinkhorn_iterations as i32,
890 topology.epsilon,
891 std::ptr::null_mut(),
892 dpf!(norm_w, &stream),
893 dpm!(&mut s_z, &stream),
894 s_q.device_ptr_mut(&stream).0 as *mut i8,
895 s_d.device_ptr_mut(&stream).0 as *mut f32,
896 eps_norm,
897 rms_bd as i32,
898 sp(&stream),
899 )
900 } else {
901 k::memra_dsv4_hc_pre_zq8(
902 dpf!(x, &stream),
903 dpf!(&ws.mixes, &stream),
904 dpf!(site.scale, &stream),
905 dpf!(site.base, &stream),
906 dpm!(&mut s_pre, &stream),
907 dpm!(&mut s_post, &stream),
908 dpm!(&mut s_comb, &stream),
909 dpm!(&mut s_y, &stream),
910 1,
911 streams as i32,
912 hidden as i32,
913 topology.sinkhorn_iterations as i32,
914 topology.epsilon,
915 std::ptr::null_mut(),
916 block as i32,
917 crate::hc_pre_sink_reg() as i32,
918 dpf!(norm_w, &stream),
919 dpm!(&mut s_z, &stream),
920 s_q.device_ptr_mut(&stream).0 as *mut i8,
921 s_d.device_ptr_mut(&stream).0 as *mut f32,
922 rms_bd as i32,
923 eps_norm,
924 sp(&stream),
925 )
926 },
927 )?;
928 }
929 {
931 let ws2 = &mut *ws;
932 pre_finish_into(
933 e,
934 topology,
935 site,
936 x,
937 &mut ws2.mixes,
938 &mut ws2.pre,
939 &mut ws2.post,
940 &mut ws2.comb,
941 &mut ws2.y,
942 1,
943 hidden,
944 )?;
945 }
946 let (r_q, r_d) = {
947 let zdst: &mut CudaSlice<f32> = match dst {
948 NormDst::H => &mut ws.h,
949 NormDst::Z => &mut ws.z,
950 };
951 e.rms_norm_zq8_f32(&ws.y, norm_w, zdst, hidden, 1, eps_norm)?
952 };
953 stream.synchronize()?;
954 let ord = HC_PRE_ZQ8_CHECK_SITES.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
955 let f = |a: &[f32], b: &[f32], name: &str| -> Vec<String> {
956 a.iter()
957 .zip(b)
958 .enumerate()
959 .filter(|(_, (p, q))| p.to_bits() != q.to_bits())
960 .take(8)
961 .map(|(i, (p, q))| {
962 format!(
963 "{name}[{i}] fused={:#010x} two={:#010x}",
964 p.to_bits(),
965 q.to_bits()
966 )
967 })
968 .collect()
969 };
970 let zref = match dst {
971 NormDst::H => e.dtoh(&ws.h)?,
972 NormDst::Z => e.dtoh(&ws.z)?,
973 };
974 let mut bad: Vec<String> = Vec::new();
975 bad.extend(f(&e.dtoh(&s_pre)?, &e.dtoh(&ws.pre)?[..streams], "pre"));
976 bad.extend(f(&e.dtoh(&s_post)?, &e.dtoh(&ws.post)?[..streams], "post"));
977 bad.extend(f(
978 &e.dtoh(&s_comb)?,
979 &e.dtoh(&ws.comb)?[..streams * streams],
980 "comb",
981 ));
982 bad.extend(f(&e.dtoh(&s_y)?, &e.dtoh(&ws.y)?[..hidden], "y"));
983 bad.extend(f(&e.dtoh(&s_z)?, &zref[..hidden], "z"));
984 bad.extend(f(&e.dtoh(&s_d)?, &e.dtoh(&r_d)?[..hidden / 32], "d"));
985 let (sq, rq) = (e.dtoh_i8(&s_q)?, e.dtoh_i8(&r_q)?);
986 let qbad: Vec<String> = sq
987 .iter()
988 .zip(rq.iter())
989 .enumerate()
990 .filter(|(_, (p, q))| p != q)
991 .take(8)
992 .map(|(i, (p, q))| format!("q[{i}] fused={p} two={q}"))
993 .collect();
994 bad.extend(qbad);
995 if !bad.is_empty() {
996 HC_PRE_ZQ8_CHECK_BAD.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
997 eprintln!(
998 "[hc-pre-zq8-check] site #{ord} dst={dst:?} MISMATCH: {}",
999 bad.join("; ")
1000 );
1001 }
1002 if ord.is_multiple_of(256) {
1003 eprintln!(
1004 "[hc-pre-zq8-check] {} sites compared, {} with a mismatch",
1005 ord + 1,
1006 HC_PRE_ZQ8_CHECK_BAD.load(std::sync::atomic::Ordering::Relaxed)
1007 );
1008 }
1009 Ok(())
1010}
1011
1012#[allow(clippy::too_many_arguments)]
1021pub fn pre_t1_ws_zq8(
1022 e: &Engine,
1023 topology: &HyperTopology,
1024 site: &HyperSite,
1025 x: &CudaSlice<f32>,
1026 ws: &mut HyperDecodeWs,
1027 hidden: usize,
1028 norm_w: &CudaSlice<f32>,
1029 dst: NormDst,
1030 eps_norm: f32,
1031) -> Res<Option<(CudaSlice<i8>, CudaSlice<f32>)>> {
1032 let streams = topology.streams;
1033 let rows = topology.rows();
1034 let width = streams * hidden;
1035 let block = crate::hc_pre_block();
1036 let rms_bd = crate::rms_block() as usize;
1037 if hc_fused_pre_arm() != HcFusedPreArm::V2
1038 || streams > 8
1039 || rows > 32
1040 || rms_bd > block
1041 || !rms_bd.is_multiple_of(32)
1042 || !hidden.is_multiple_of(32)
1043 {
1044 return Ok(None);
1045 }
1046 if hc_pre_zq8_selfcheck() {
1052 return pre_t1_ws_zq8_selfcheck(e, topology, site, x, ws, hidden, norm_w, dst, eps_norm)
1053 .map(|()| None);
1054 }
1055 let stream = e.stream();
1056 {
1057 let xr = x.slice(0..width);
1058 let wv = site.fn_w.slice(0..site.fn_w.len());
1059 let mut yr = ws.mixes.slice_mut(0..rows);
1060 hc_mixes_into(e, &xr, &wv, &mut yr, width, rows)
1061 .map_err(|err| format!("hc pre_t1_ws_zq8 mixes: {err}"))?;
1062 }
1063 let mut q = e.uninit_i8(hidden)?;
1064 let mut d = e.uninit(hidden / 32)?;
1065 let HyperDecodeWs {
1066 mixes,
1067 pre,
1068 post,
1069 comb,
1070 y,
1071 h,
1072 z,
1073 ..
1074 } = ws;
1075 let zdst: &mut CudaSlice<f32> = match dst {
1076 NormDst::H => h,
1077 NormDst::Z => z,
1078 };
1079 unsafe {
1080 ck("hc_pre_zq8", {
1081 let v4z = if crate::hc_pre_v4z_on() {
1084 let rc = k::memra_dsv4_hc_pre_v4z(
1085 dpf!(x, &stream),
1086 dpf!(mixes, &stream),
1087 dpf!(site.scale, &stream),
1088 dpf!(site.base, &stream),
1089 dpm!(pre, &stream),
1090 dpm!(post, &stream),
1091 dpm!(comb, &stream),
1092 dpm!(y, &stream),
1093 1,
1094 streams as i32,
1095 hidden as i32,
1096 topology.sinkhorn_iterations as i32,
1097 topology.epsilon,
1098 std::ptr::null_mut(),
1099 dpf!(norm_w, &stream),
1100 dpm!(zdst, &stream),
1101 q.device_ptr_mut(&stream).0 as *mut i8,
1102 d.device_ptr_mut(&stream).0 as *mut f32,
1103 eps_norm,
1104 rms_bd as i32,
1105 sp(&stream),
1106 );
1107 if rc == 40025 { None } else { Some(rc) }
1108 } else {
1109 None
1110 };
1111 match v4z {
1112 Some(rc) => {
1113 HC_PRE_V4Z_DISPATCHES.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1114 rc
1115 }
1116 None => k::memra_dsv4_hc_pre_zq8(
1117 dpf!(x, &stream),
1118 dpf!(mixes, &stream),
1119 dpf!(site.scale, &stream),
1120 dpf!(site.base, &stream),
1121 dpm!(pre, &stream),
1122 dpm!(post, &stream),
1123 dpm!(comb, &stream),
1124 dpm!(y, &stream),
1125 1,
1126 streams as i32,
1127 hidden as i32,
1128 topology.sinkhorn_iterations as i32,
1129 topology.epsilon,
1130 std::ptr::null_mut(),
1131 block as i32,
1132 crate::hc_pre_sink_reg() as i32,
1133 dpf!(norm_w, &stream),
1134 dpm!(zdst, &stream),
1135 q.device_ptr_mut(&stream).0 as *mut i8,
1136 d.device_ptr_mut(&stream).0 as *mut f32,
1137 rms_bd as i32,
1138 eps_norm,
1139 sp(&stream),
1140 ),
1141 }
1142 })?;
1143 }
1144 if HC_PRE_ZQ8_DISPATCHES.fetch_add(1, std::sync::atomic::Ordering::Relaxed) == 0 {
1145 eprintln!(
1146 "[hc-pre-zq8] engaged streams={streams} hidden={hidden} block={block} rms_bd={rms_bd} \
1147 (one launch replaces hc_pre_fused_v3 + rms_norm_zq8_f32_v2 per site; MEMRA_HC_PRE_ZQ8=1)"
1148 );
1149 }
1150 Ok(Some((q, d)))
1151}
1152
1153pub fn post_t1_ws(
1154 e: &Engine,
1155 topology: &HyperTopology,
1156 f: &CudaSlice<f32>,
1157 residual: &CudaSlice<f32>,
1158 ws: &mut HyperDecodeWs,
1159 hidden: usize,
1160) -> Res<()> {
1161 let stream = e.stream();
1162 let ws = &mut *ws;
1163 unsafe {
1164 ck(
1165 "hc_post",
1166 k::memra_dsv4_hc_post(
1167 dpf!(f, &stream),
1168 dpf!(residual, &stream),
1169 dpf!(ws.post, &stream),
1170 dpf!(ws.comb, &stream),
1171 dpm!(ws.xb, &stream),
1172 1,
1173 topology.streams as i32,
1174 hidden as i32,
1175 sp(&stream),
1176 ),
1177 )?;
1178 }
1179 Ok(())
1180}
1181
1182pub fn post(
1186 e: &Engine,
1187 topology: &HyperTopology,
1188 f: &CudaSlice<f32>,
1189 residual: &CudaSlice<f32>,
1190 mix: &HcMix,
1191 t: usize,
1192 hidden: usize,
1193) -> Res<CudaSlice<f32>> {
1194 let streams = topology.streams;
1195 let mut out = e.uninit(t * streams * hidden)?;
1196 let stream = e.stream();
1197 unsafe {
1198 ck(
1199 "hc_post",
1200 k::memra_dsv4_hc_post(
1201 dpf!(f, &stream),
1202 dpf!(residual, &stream),
1203 dpf!(mix.post, &stream),
1204 dpf!(mix.comb, &stream),
1205 dpm!(out, &stream),
1206 t as i32,
1207 streams as i32,
1208 hidden as i32,
1209 sp(&stream),
1210 ),
1211 )?;
1212 }
1213 Ok(out)
1214}
1215
1216pub fn contract_mean(
1223 e: &Engine,
1224 topology: &HyperTopology,
1225 x: &CudaSlice<f32>,
1226 t: usize,
1227 hidden: usize,
1228) -> Res<CudaSlice<f32>> {
1229 let streams = topology.streams;
1230 let stream = e.stream();
1231 let mut out = e.uninit(t * hidden)?;
1232 unsafe {
1233 ck(
1234 "hc_mean",
1235 k::memra_dsv4_hc_mean(
1236 dpf!(x, &stream),
1237 dpm!(out, &stream),
1238 t as i32,
1239 streams as i32,
1240 hidden as i32,
1241 sp(&stream),
1242 ),
1243 )?;
1244 }
1245 Ok(out)
1246}
1247
1248pub fn collapse(
1252 e: &Engine,
1253 topology: &HyperTopology,
1254 head: Option<&HyperHead>,
1255 x: &CudaSlice<f32>,
1256 t: usize,
1257 hidden: usize,
1258) -> Res<CudaSlice<f32>> {
1259 let streams = topology.streams;
1260 let stream = e.stream();
1261 let mut out = e.uninit(t * hidden)?;
1262 match topology.collapse {
1263 HcCollapse::Mean => unsafe {
1264 ck(
1265 "hc_mean",
1266 k::memra_dsv4_hc_mean(
1267 dpf!(x, &stream),
1268 dpm!(out, &stream),
1269 t as i32,
1270 streams as i32,
1271 hidden as i32,
1272 sp(&stream),
1273 ),
1274 )?;
1275 },
1276 HcCollapse::GatedHead => {
1277 let head = head.ok_or_else(|| {
1278 "HcCollapse::GatedHead reached the trunk exit with no head trio loaded".to_string()
1279 })?;
1280 let width = streams * hidden;
1281 let mut mixes = e.linear(x, &head.fn_w, t, width, streams)?;
1282 let mut gates = e.uninit(t * streams)?;
1283 unsafe {
1284 ck(
1285 "hc_head rowsq_scale",
1286 k::memra_dsv4_rowsq_scale(
1287 dpf!(x, &stream),
1288 dpm!(mixes, &stream),
1289 t as i32,
1290 width as i32,
1291 streams as i32,
1292 topology.epsilon,
1293 sp(&stream),
1294 ),
1295 )?;
1296 ck(
1297 "hc_head_pre",
1298 k::memra_dsv4_hc_head_pre_m(
1299 dpf!(mixes, &stream),
1300 dpf!(head.scale, &stream),
1301 dpf!(head.base, &stream),
1302 dpm!(gates, &stream),
1303 t as i32,
1304 streams as i32,
1305 topology.epsilon,
1306 sp(&stream),
1307 ),
1308 )?;
1309 ck(
1310 "hc_head collapse",
1311 k::memra_dsv4_hc_collapse(
1312 dpf!(x, &stream),
1313 dpf!(gates, &stream),
1314 dpm!(out, &stream),
1315 t as i32,
1316 streams as i32,
1317 hidden as i32,
1318 sp(&stream),
1319 ),
1320 )?;
1321 }
1322 }
1323 }
1324 Ok(out)
1325}
1326
1327#[cfg(test)]
1328mod tests {
1329 use super::*;
1330 use memra_gguf::model_plan::{
1331 ActivationPlan, AttentionPlan, DenseMlpPlan, DraftSourcePlan, KimiDeltaNetPlan, LayerPlan,
1332 MlpPlan, NormKind, NormPlan, StatePlan, WeightTransform,
1333 };
1334
1335 fn norm() -> NormPlan {
1336 NormPlan {
1337 kind: NormKind::Rms,
1338 epsilon: 1e-5,
1339 weight_transform: WeightTransform::Identity,
1340 }
1341 }
1342
1343 fn layer(index: u32, residual: ResidualTopology) -> LayerPlan {
1344 LayerPlan {
1345 index,
1346 pre_attention_norm: norm(),
1347 attention: AttentionPlan::KimiDeltaNet(KimiDeltaNetPlan {
1348 num_heads: 1,
1349 head_dim: 128,
1350 conv_kernel: 4,
1351 gate_lower_bound: -5.0,
1352 }),
1353 pre_mlp_norm: norm(),
1354 mlp: MlpPlan::Dense(DenseMlpPlan {
1355 intermediate_size: 16,
1356 activation: ActivationPlan::Silu,
1357 }),
1358 residual,
1359 state: StatePlan::Recurrent {
1360 conv_width: 384,
1361 conv_kernel: 4,
1362 state_width: 16384,
1363 },
1364 ple: None,
1365 sparse_overlay: None,
1366 }
1367 }
1368
1369 fn plan(residuals: [ResidualTopology; 2]) -> ModelPlan {
1370 ModelPlan {
1371 arch: memra_gguf::config::Arch::Glm5Next,
1372 hidden_size: 8,
1373 vocab_size: 16,
1374 context_length: 32,
1375 embedding_scale: 1.0,
1376 vision: None,
1377 multimodal: None,
1378 layers: vec![layer(0, residuals[0]), layer(1, residuals[1])],
1379 output_norm: norm(),
1380 logits: Vec::new(),
1381 mtp_blocks: Vec::new(),
1382 drafter: None,
1383 exit_mixer: None,
1384 draft_source: DraftSourcePlan::Embedded,
1385 sampling_defaults: None,
1386 partition_boundaries: Vec::new(),
1387 }
1388 }
1389
1390 fn hc(streams: u32) -> ResidualTopology {
1391 ResidualTopology::HyperConnections {
1392 streams,
1393 epsilon: 1e-6,
1394 sinkhorn_iterations: 20,
1395 collapse: HcCollapse::Mean,
1396 }
1397 }
1398
1399 #[test]
1400 fn serial_trunk_has_no_topology() {
1401 let plan = plan([ResidualTopology::Serial, ResidualTopology::Serial]);
1402 assert!(HyperTopology::from_plan(&plan).unwrap().is_none());
1403 }
1404
1405 #[test]
1406 fn uniform_trunk_yields_the_plans_constants() {
1407 let plan = plan([hc(4), hc(4)]);
1408 let topology = HyperTopology::from_plan(&plan).unwrap().unwrap();
1409 assert_eq!(topology.streams, 4);
1410 assert_eq!(topology.sinkhorn_iterations, 20);
1411 assert_eq!(topology.collapse, HcCollapse::Mean);
1412 assert_eq!(topology.rows(), 24);
1414 }
1415
1416 #[test]
1417 fn a_mixed_trunk_is_refused_in_both_orders() {
1418 for residuals in [
1419 [hc(4), ResidualTopology::Serial],
1420 [ResidualTopology::Serial, hc(4)],
1421 [hc(4), hc(2)],
1422 ] {
1423 assert!(
1424 HyperTopology::from_plan(&plan(residuals)).is_err(),
1425 "a non-uniform trunk must be refused, not silently keyed off layer 0"
1426 );
1427 }
1428 }
1429
1430 #[test]
1431 fn zero_iterations_are_refused() {
1432 let bad = ResidualTopology::HyperConnections {
1433 streams: 4,
1434 epsilon: 1e-6,
1435 sinkhorn_iterations: 0,
1436 collapse: HcCollapse::Mean,
1437 };
1438 assert!(HyperTopology::from_plan(&plan([bad, bad])).is_err());
1439 }
1440}