Skip to main content

memra_engine/
hyper.rs

1//! mHC — manifold-constrained hyper-connections, the `ResidualTopology::HyperConnections`
2//! residual program (glm5_next / GLM-5.3-Flash, and the dsv4 class).
3//!
4//! ARITHMETIC CONTRACT. Truth is `memra_reference::execute`'s `execute_hyper_layer`, which is
5//! itself built from `memra_gguf::dsv4_forward::{hc_expand, hc_pre, hc_post, hc_split_sinkhorn,
6//! hc_head}`. Every stage below cites the reference stage it reproduces. The vendor module the
7//! reference was derived from is
8//! `research/glm53-flash-bringup-20260827/modular_glm5_next-ref.py`.
9//!
10//! A trunk layer under this topology is NOT `x + attn; x += mlp`. Per site (attention, then
11//! MLP), with the stream state `x [tokens, streams, hidden]`:
12//!
13//! ```text
14//!   mixes[t, :]   = fn_w · x[t, :, :]                     (rows = (2+streams)*streams)
15//!   mixes[t, :]  *= rsqrt(mean(x[t]^2) + eps)             (over the whole streams*hidden slab)
16//!   pre/post/comb = sinkhorn(mixes[t, :], scale, base)    (per token, per site)
17//!   y[t, :]       = Σ_c pre[t, c] · x[t, c, :]            (collapse streams -> 1)
18//!   f             = branch(rms_norm(y))                   (the mixer or the FFN, unchanged)
19//!   x'[t, k, :]   = post[t, k] · f[t, :] + Σ_j comb[t, j, k] · x[t, j, :]
20//! ```
21//!
22//! SINKHORN IS PER TOKEN AND PER SITE, NOT A LOAD-TIME PRECOMPUTE. `mixes` is
23//! `x @ fn_wᵀ` rescaled by the token's own RMS — an ACTIVATION, so the Sinkhorn normalization
24//! that turns it into `comb` cannot be hoisted to load even though the weights are static
25//! (`dsv4_forward.rs` `hc_pre`, the `matmul` + `rsq` block immediately before
26//! `hc_split_sinkhorn`). It runs on device, once per (token, layer, site).
27//!
28//! MEMORY LAYOUT: TOKEN-MAJOR `[tokens, streams, hidden]`, element `(t, k, i)` at
29//! `(t*streams + k)*hidden + i`. Forced, not chosen: it is the layout of `hc_expand` in the
30//! reference and of every kernel in the `memra_dsv4_hc_*` family, and it makes one token's
31//! `streams*hidden` slab contiguous — which is exactly the `[s, w]` operand the mixes GEMM and
32//! `memra_dsv4_rowsq_scale` want. Streams-major would have cost a transpose at both ends of
33//! every site. Any graph capture over these buffers sees one flat `t*streams*hidden` slab.
34//!
35//! KERNELS: no new math. `cu/dsv4_gpu.cu` already carries this exact program for the dsv4 GPU
36//! fork (`crate::dsv4_gpu`) and is compiled unconditionally into this crate, so the site mixing
37//! is `memra_dsv4_{rowsq_scale, hc_sinkhorn_m, hc_collapse, hc_post}` plus `hc_mean`/`hc_head_pre_m`
38//! at the exit, and the mixes GEMM is `Engine::linear` (cuBLASLt f32 — the tiny
39//! `[rows, streams*hidden]` operand is the wrong shape for the f64 island `dots` kernel the dsv4
40//! decode path uses, and this is a serving trunk, not a byte-parity oracle). The one kernel that
41//! did not exist, `memra_dsv4_hc_expand`, was added next to its inverse `memra_dsv4_hc_mean`.
42//! The `dsv4_` prefix is that translation unit's namespace, not a model claim — the reference
43//! reaches into `memra_gguf::dsv4_forward` for glm5_next in exactly the same way.
44//!
45//! NO ENV FLAG. The topology, its stream count, its epsilon, its Sinkhorn iteration count and
46//! its collapse are read from the compiled `ModelPlan`. There is nothing here to switch.
47
48use 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/// The trunk-wide hyper-connection topology, read off the plan at load.
71#[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    /// `(2 + streams) * streams` — pre gates, post gates, then the `streams x streams`
81    /// combination block, in that row order (`hc_split_sinkhorn`).
82    pub fn rows(&self) -> usize {
83        (2 + self.streams) * self.streams
84    }
85
86    /// The plan's topology, or `None` for a serial/gemma trunk. Refuses a trunk whose layers
87    /// disagree: the state carried between layers is one shape, so a per-layer stream count is
88    /// not a thing this executor can mean. Mirrors `memra_reference`'s `hyper_topology`.
89    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
145/// One site's learned mixing parameters. `fn_w` is consumed as ROW-MAJOR `[rows,
146/// streams*hidden]` — the layout `memra_reference::hyper_set` and `dsv4_forward::HcSet` read, and
147/// the `[out_f, in_f]` operand `Engine::linear` wants. Only the element count is checked at load;
148/// the checkpoint dialect's `ne` ordering is not consulted, so the two readers cannot fork.
149pub struct HyperSite {
150    pub fn_w: CudaSlice<f32>,
151    pub base: CudaSlice<f32>,
152    pub scale: CudaSlice<f32>,
153}
154
155/// The six per-layer hc tensors, present iff the plan declares HyperConnections for the trunk.
156pub struct HyperLayer {
157    pub attn: HyperSite,
158    pub mlp: HyperSite,
159}
160
161/// Gated-head exit weights (`HcCollapse::GatedHead`, the dsv4 class). Absent under
162/// `HcCollapse::Mean`, which has no learned head (`Glm5NextTextHyperHead` is an unweighted mean).
163pub struct HyperHead {
164    pub fn_w: CudaSlice<f32>,
165    pub base: CudaSlice<f32>,
166    pub scale: CudaSlice<f32>,
167}
168
169/// A loaded float tensor's device data, or a refusal naming the tensor. `GpuTensor::float_data`
170/// panics on the quantized/bf16 variants; an hc parameter arriving in one of those is a
171/// checkpoint the trunk cannot serve, and it must say which tensor and why.
172fn 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
197/// Load one site's trio, refusing loudly — by name — on the first absent tensor. There is no
198/// serial fallback: a plan that declares HyperConnections and a checkpoint that does not carry
199/// them describe two different functions, and guessing which one to compute is the failure this
200/// refusal exists to prevent.
201fn 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        // Three gate scales — pre, post, combination — regardless of stream count
216        // (`hc_split_sinkhorn` asserts `scale.len() == 3`).
217        ("scale", 3),
218    ] {
219        // The ggml spellings `add_hyper_connections` (memra-gguf tensor_contract) emits.
220        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    /// `None` unless the collapse is gated. `hc_head`'s trio is shaped differently from a site's:
258    /// `rows == streams` and one scale (`dsv4_forward::hc_head`).
259    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        // The dsv4 checkpoint spellings (crate::dsv4_gpu's `hc_head_*` loads). The
271        // TensorContract has no HyperHead rows — nothing in the GGUF/safetensors schema emits
272        // them yet — so a gated-head trunk on THIS path refuses by name below until it does.
273        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
298/// The per-token post gates and combination matrix a site's `hc_pre` produced, held for that
299/// site's `hc_post`. `post` is `[tokens, streams]`, `comb` is `[tokens, streams, streams]`.
300pub struct HcMix {
301    pub post: CudaSlice<f32>,
302    pub comb: CudaSlice<f32>,
303}
304
305/// Engagement counter for the fused pre-chain door's `=1` arm: incremented at the arm's own
306/// call site, announced once per boot — the spec-engagement receipt the gate and any box A/B
307/// arm must show ([bf16-mmv] RESIDENT lesson: engagement lines are receipts, never inferred).
308pub static HC_FUSED_PRE_DISPATCHES: std::sync::atomic::AtomicU64 =
309    std::sync::atomic::AtomicU64::new(0);
310
311/// Engagement counter for the fused pre-chain door's `=2` arm (lane/b200-sinkhorn-fusion-
312/// 20260902 follow-up), same discipline as `HC_FUSED_PRE_DISPATCHES`.
313pub static HC_FUSED_PRE_V2_DISPATCHES: std::sync::atomic::AtomicU64 =
314    std::sync::atomic::AtomicU64::new(0);
315
316/// The three states of `MEMRA_HC_FUSED_PRE` (default OFF): the unfused three-kernel chain,
317/// the `=1` fused kernel (`memra_dsv4_hc_pre_fused`, lane/glm5-decode-diet 2026-08-31), or
318/// the `=2` fused kernel (`memra_dsv4_hc_pre_fused_v2`, lane/b200-sinkhorn-fusion-20260902 —
319/// same stages, warp-scoped Sinkhorn sync). Any other value (unset, `0`, or unrecognized)
320/// stays `Off`, the existing "read per call" rollback-seam contract.
321#[derive(Clone, Copy, PartialEq, Eq, Debug)]
322pub enum HcFusedPreArm {
323    Off,
324    V1,
325    V2,
326}
327
328/// `MEMRA_HC_FUSED_PRE` (default OFF, both `1` and `2` opt in): the three-kernel site
329/// pre-chain (rowsq_scale + Sinkhorn + collapse) runs as ONE launch per site — bit-identical
330/// to the unfused chain by construction in both arms (verbatim bodies, asserted bytewise in
331/// `hc_fused_pre_gpu.rs` for `=1` and by `hc-fused-gate` for `=1` vs `=2`). Read PER CALL
332/// (the `MEMRA_MOE_FUSED_EPI` rollback-seam precedent), so arms can alternate inside one
333/// process and the flag is a live rollback seam.
334fn 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
341/// The pure parse behind [`hc_fused_pre_arm`] (arch-keyed since 2026-09-04): `1` = V1, `2` = V2,
342/// `0` = the unfused chain; UNSET follows the build arch: V2 on `100a` (the served posture on
343/// the 2x B200 pair since 2026-09-02, receipts in darklanes research/glm5-b200-20260902/LANE.md
344/// and the FLAGS row), the unfused chain on every other build until it has its own receipt.
345pub 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
355/// Model entry (`hc_expand`): `[tokens, hidden]` embeddings -> `[tokens, streams, hidden]`.
356pub 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
382/// One site's pre-branch half (`hc_pre`): mixes GEMM, per-token RMS rescale, Sinkhorn, stream
383/// collapse. Returns the branch input `[tokens, hidden]` and the gates its `post` half needs.
384pub 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
397/// `pre` with the DECODE-EXACT mixing GEMM: each token's mix coefficients come from the
398/// SAME m=1 cuBLASLt program the serial T=1 decode step runs (`linear_t1_into` is `linear`
399/// at m == 1 on a row view — same config, same weight pointer, same input bytes), instead
400/// of one m=t call whose n-dependent reduction split changes every output bit (the lt_ndep
401/// probe documented on `Engine::linear_decode_exact`). Everything after the GEMM is the
402/// per-token kernel set `pre` already runs — block-per-token programs whose per-token bytes
403/// do not depend on t. This is the entry the BATCHED hyper decode walk uses so that row b
404/// of a B-row tick is bit-identical to that session's solo `decode_step_hyper` step.
405pub 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
426/// The per-token half `pre` and `pre_exact` share: RMS rescale of the mix coefficients,
427/// Sinkhorn, stream collapse. Every kernel here is a block-per-token program (grid over t),
428/// so per-token output bytes are invariant to t — the two entries differ ONLY in how the
429/// mixes GEMM reduces.
430fn 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/// `pre_finish`'s kernel arms on caller-owned outputs — shared by the allocating entry above
461/// and the persistent-workspace decode walk (`pre_t1_ws`), so the two cannot drift. Both arms
462/// fully overwrite every output element, which is what makes workspace reuse byte-identical.
463#[allow(clippy::too_many_arguments)] // allow: the parameter list mirrors the kernel/FFI contract; the workspace caller passes disjoint field borrows
464fn 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    // FUSED PRE-CHAIN DOOR (lane/glm5-decode-diet; `=2` arm lane/b200-sinkhorn-fusion-
484    // 20260902). Engages at any t (block-per-token, per-token bytes t-invariant like the
485    // unfused chain) whenever the stream count fits the kernel's static shared arrays;
486    // every other shape falls through to the unchanged three-kernel program below. Both
487    // kernels read the RAW mixes and apply the rowsq rescale internally, so the in-place
488    // scale write below is subsumed (nothing reads the scaled mixes after this function
489    // either way).
490    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                // v3 is v2 with the width as a parameter and the register-Sinkhorn door on it.
515                // It is selected when EITHER door is set: at width 128 v3 is bit-identical to v2
516                // (same partition), so routing MEMRA_HC_PRE_SINK_REG=1 through v3 at the default
517                // width changes only the Sinkhorn stage. Measured 2026-09-03: with the guard on
518                // width alone, `MEMRA_HC_PRE_SINK_REG=1` at block 128 dispatched v2 and the door
519                // was unreachable — the announce line said `kernel=hc_pre_fused_v2` and the arm
520                // read 55.86 against a 55.94 baseline, a measurement of nothing.
521                HcFusedPreArm::V2 if crate::hc_pre_block() != 128 || crate::hc_pre_sink_reg() => {
522                    // MEMRA_HC_PRE_V4: the register schedule first; 40025 (shape does not fit)
523                    // falls through to v3, any other non-zero rc is v4's error and is reported
524                    // as such rather than masked by a v3 retry.
525                    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        // The announce says which KERNEL ran, not which arm was asked for: under
607        // MEMRA_HC_PRE_BLOCK != 128 the V2 arm dispatches `_v3` with a wider block, and a
608        // counter line reading `arm=2` while `_v3` executes is the kind of quiet mismatch a
609        // later reader has to re-derive from nsys. The counter itself stays V2's (the arm is
610        // still V2; the width is a property of that arm), and the width is printed.
611        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
687/// Persistent T=1 decode workspace for the hc glue (lane/glm5-decode-diet lever 2,
688/// `MEMRA_HC_DECODE_WS`). One per engine (pp stage), pooled on the `Engine` like
689/// `fa_part_pool`/`router_stage`: the launch-diet census measured 2,358
690/// `cuMemAllocAsync+Free` calls/token (~2.5 ms of host time feeding the sync-serialized
691/// drain cycles), and the hc glue chain — mixes, gates, comb, collapse y, the two norm
692/// scratches and the two per-site post outputs — re-allocated all of it every token. Every
693/// buffer here is FULLY OVERWRITTEN before any read on every step (GEMV beta=0, block-per-
694/// token kernels, rms_norm, hc_post), which is what makes reuse byte-identical: the same
695/// kernels read and write the same values, only the allocator calls disappear.
696///
697/// The stream-state ping-pong deliberately has ONE slot (`xb`): the walk swaps the owned
698/// in-flight state `x` with `xb` after each site's `hc_post`, so the pair rotates without a
699/// copy and the walk still returns an owned buffer to the caller (no signature churn at the
700/// stage boundary — the ppN transport consumes it exactly as before).
701pub 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    /// Attention-site rms_norm scratch (the walk's `h`).
708    pub h: CudaSlice<f32>,
709    /// MLP-site rms_norm scratch (the walk's `z`).
710    pub z: CudaSlice<f32>,
711    /// The `hc_post` output slot the walk ping-pongs with the in-flight stream state.
712    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    /// A pooled workspace is only reusable for the same trunk geometry; anything else is
735    /// rebuilt (one engine serves one loaded model in practice, this is a guard, not a path).
736    pub fn matches(&self, topology: &HyperTopology, hidden: usize) -> bool {
737        self.streams == topology.streams && self.hidden == hidden
738    }
739}
740
741/// `pre` at T=1 into the workspace: the SAME m=1 mixes program the allocating entry runs
742/// (`linear_t1_into` is `linear` at m == 1 — same cuBLASLt config, same weight pointer, same
743/// input bytes; the `pre_exact` note), then the shared `pre_finish_into` arms. Byte-identical
744/// to `pre(e, topology, site, x, 1, hidden)` with the outputs landing in `ws` instead of
745/// fresh allocations.
746pub 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/// `post` at T=1 into the workspace's `xb` slot (the caller swaps `xb` with its in-flight
780/// state). Reads the gates `pre_t1_ws` left in `ws.post`/`ws.comb` — the same kernel, the
781/// same operand bytes as the allocating `post`.
782/// Which of the decode workspace's two norm scratches `pre_t1_ws_zq8` writes: the attention
783/// site's `h` or the MLP site's `z`. Passed as a tag rather than a `&mut` so the function can
784/// take the whole workspace by one mutable borrow and destructure it inside.
785#[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
794/// The hc mixes projection at t=1: the native kernel under `MEMRA_HC_MIXES_KERNEL=1` where the
795/// shape fits, cuBLASLt (`linear_t1_into`) otherwise and by default. One seam for every site.
796fn 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
819/// Sites served by the v4 register schedule (`MEMRA_HC_PRE_V4=1`).
820pub static HC_PRE_V4_DISPATCHES: std::sync::atomic::AtomicU64 =
821    std::sync::atomic::AtomicU64::new(0);
822
823/// Sites served by v4z (`MEMRA_HC_PRE_V4Z=1` under `MEMRA_HC_PRE_ZQ8=1`).
824pub 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/// The self-check body of `pre_t1_ws_zq8` (`MEMRA_HC_PRE_ZQ8=2`): fused into scratch, then the
836/// two-launch program into the workspace (which is what the walk then consumes), then a host
837/// compare of pre/post/comb/y/z/q/d. Prints `[hc-pre-zq8-check]` lines: one per site with a
838/// mismatch (first eight differing words with both bit patterns), plus a running summary every
839/// 256 sites.
840#[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    // fused program into scratch
866    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    // the two-launch program into the workspace, exactly as the walk runs it
930    {
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/// `pre_t1_ws` and the `rms_norm_zq8` that consumes its `y`, as ONE launch (door
1013/// `MEMRA_HC_PRE_ZQ8`, lane/hcpre-zq8-fusion-20260905). Returns the q8_1 pair the walk would
1014/// otherwise get from `Engine::rms_norm_zq8_f32`, with `z` (the normed f32 row) written to
1015/// `ws.h` or `ws.z` per `dst`. Returns `None` -- without launching anything -- wherever the
1016/// fused kernel's preconditions do not hold, so the caller runs the two-launch program
1017/// unchanged: the fused pre arm must be V2 (the v3 kernel is what this body was generated
1018/// from), the norm's block must fit inside the pre-chain's, and the site must be within the
1019/// v3 kernel's warp-0 invariant.
1020#[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    // MEMRA_HC_PRE_ZQ8=2 (self-check, 2026-09-05): the served tape forks between the fused and
1047    // the two-launch program while the kernel gate is bitwise green on the served card. This arm
1048    // runs the fused kernel into SCRATCH, then returns None so the walk runs the real two-launch
1049    // program into the workspace, and compares all seven outputs on the host, printing the first
1050    // differing words per site with the site ordinal. Diagnostic only: dtoh per site.
1051    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            // MEMRA_HC_PRE_V4Z: the v4 schedule with the norm replayed in-block; 40025
1082            // (shape) falls through to the zq8 kernel, any other rc is v4z's own.
1083            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
1182/// One site's post-branch half (`hc_post`): `out[t, k, :] = post[t, k]·f[t, :] + Σ_j
1183/// comb[t, j, k]·residual[t, j, :]`. `residual` is the site's INPUT stream state, not the
1184/// layer's — the MLP site's residual is the attention site's output.
1185pub 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
1216/// UNWEIGHTED stream-mean contraction `[tokens, streams, hidden]` -> `[tokens, hidden]` —
1217/// the `hc_contract` the glm5 DFlash2 drafter's aux-hidden features are defined by (the
1218/// probe's capture seam: mean over the hc_mult stream blocks of the completed layer output,
1219/// == the SGLang glm5_next integration's pinned definition). Deliberately NOT keyed on
1220/// `topology.collapse`: the drafter contract is the mean by definition, whatever the trunk
1221/// exit does (for glm5_next the exit IS `Mean`, so this is also the collapse kernel).
1222pub 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
1248/// Trunk exit: `[tokens, streams, hidden]` -> `[tokens, hidden]`, keyed on the plan's collapse.
1249/// `Mean` is glm5_next's unweighted mean (`Glm5NextTextHyperHead`); `GatedHead` is dsv4's
1250/// sigmoid-gated pre-only collapse (`dsv4_forward::hc_head`) and needs the head trio.
1251pub 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        // pre gates + post gates + the streams x streams combination block.
1413        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}