Skip to main content

memra_engine/
glm5_tp.rs

1//! glm5_next (GLM-5.3-Flash) TP-N seam — `MEMRA_GLM5_TP` (lane/glm5-tp2, 2026-08-31;
2//! rank-widened to TP-4 by lane/glm5-composition, 2026-09-01).
3//!
4//! WHAT THIS IS. A correctness-first tensor-parallel execution program for the glm5_next
5//! hybrid trunk, per the lane's shard map (`research/glm53-flash-bringup-20260827/
6//! tp2-20260831/SHARD-MAP.md`). Per layer class:
7//!
8//!   * KDA (34 layers): head-sharded, `heads / ranks` per rank. Each rank runs the UNCHANGED
9//!     `kda_core_gated` program on its shard (per-head kernels: conv, L2 norm, gate, scan,
10//!     gated rmsnorm are all head-independent), the gated `[t, qkv/ranks]` parts are
11//!     gathered, and each rank's COLUMN-parallel `wo` slice (out rows over the FULL gathered
12//!     input) computes its slice of the output with the same plain matvec kernel — joins are
13//!     pure data movement, never a partial-sum reduction, which is what makes model-level
14//!     TP-vs-plain BYTE identity the bar instead of a tolerance band.
15//!   * MLA/DSA (11 layers): head-sharded per-head operands (`wq_b`, `wk_b`, `wv_b`);
16//!     REPLICATED per-token shared work (`wq_a`/`q_a_norm`, `wkv_a`/`kv_a_norm`, the whole
17//!     indexer + k-pool selection) — every rank computes identical bytes from identical
18//!     inputs, so the latent + indexer planes are replicated per rank and no per-token
19//!     cross-rank hop exists in the latent chain. `wo` is column-parallel over the gathered
20//!     attention parts, exactly like KDA.
21//!   * MoE (sparse-FFN layers): EP-N, whole experts, contiguous slices (even split: rank =
22//!     expert / (n_expert/ranks)). The router stays root-computed (host sigmoid top-k,
23//!     unchanged); each owner extracts its slots' UNWEIGHTED down rows with the same
24//!     fused-epilogue kernels at n_used=1, and root re-applies the slot-ordered fmaf
25//!     accumulation chain — the same rounded-operation sequence as the plain
26//!     `moe_down8_fma_q8` walk. Shared expert, dense MLPs, router, mHC, norms, embed and
27//!     lm_head stay ROOT-OWNED (the `MEMRA_STEP_TP` owner-stage precedent).
28//!
29//! TRANSPORT is a SEPARATE, SWAPPABLE AXIS (`MEMRA_GLM5_TP_TRANSPORT`,
30//! lane/glm5-tp-transport 2026-09-01). Because every cross-rank hop above is pure movement,
31//! the transport arm cannot change a bit — so this module names the hop SHAPES and
32//! `tp_transport` owns the bytes. `host-canonical` (the default, and what every banked
33//! glm5 TP number was measured on) bounces each hop through host with a full stream drain
34//! per leg; `peer-pull` issues a consumer-side device peer copy per hop with event ordering
35//! and no host boundary. The join-diet doors are an orthogonal axis (they cut hop COUNT; the
36//! transport cuts hop COST) and compose.
37//!
38//! FAIL-CLOSED SURFACE. The preflight refuses before any TP CUDA state exists: non-glm5
39//! plans, rank counts outside the qualified set (2 and 4 — see [`GLM5_TP_ALLOWED_RANKS`]),
40//! head/expert counts that do not divide, duplicate devices (serving parse), co-armed
41//! `MEMRA_PP_STAGES>1`, `MEMRA_STEP_TP`/`MEMRA_STEP_EP`. A sharded layer POISONS every plain
42//! path: `kda_core`, `mla_attn_cached` and the batched walks refuse a TP-armed layer by
43//! name. The memra-server worker refuses the flag outright (serving wiring is the named
44//! box-lane increment, not v1).
45//!
46//! Engagement markers: `[glm5-tp-preflight]`, `[glm5-tp-kda]`, `[glm5-tp-mla]`,
47//! `[glm5-tp-ep]`, `[glm5-tp-transport]` — every marker carries `performance_claim=false`,
48//! and the first four name the LIVE transport rather than a hardcoded string (the
49//! tp2-battery greps `transport=` on all four seams, and a hardcoded value would have made a
50//! transport A/B unreadable from the boot log).
51
52// lane/clippy-zero-restore-20260901: perf-gated TP2 host code (fresh lane receipts);
53// index loops stay in their gated shape — iterator reshapes are not bit-neutral by inspection.
54#![allow(clippy::needless_range_loop)]
55
56use std::ops::Range;
57use std::sync::Arc;
58use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
59
60use cudarc::driver::CudaSlice;
61
62use crate::Engine;
63use crate::kda::{ConvArm, KdaAttnLayer};
64use crate::model::GpuTensor;
65use memra_kv::{Cache, LatentKvLayer, RecurLayer};
66
67/// The qualified rank envelope: TP-2 (the v1 seam, box-battery-gated) and TP-4.
68///
69/// GEOMETRY (lane/glm5-tp-transport, 2026-09-01). The DSA indexer is REPLICATED per rank by
70/// this seam's own shard map (`shard_mla_layer`'s `replicate_indexer`), so its 32 heads
71/// impose no divisibility constraint at any rank count. TP-4 needs NO padding on the
72/// glm5_next geometry: 64/4 = 16 KDA heads, 64/4 = 16 MLA heads, 288/4 = 72 experts,
73/// 4096/4 = 1024 `wo` out rows, KDA `head_dim` 128 is rank-count independent. TP-3 remains
74/// refused: the only real obstruction is the 64 attention/KDA heads (a HEAD-PADDING
75/// question, 64 -> 66, RESEARCH.md §1.5d — not built). See `tp-transport-20260901/LANE.md`
76/// "TP-4 divisibility".
77pub const GLM5_TP_ALLOWED_RANKS: [usize; 2] = [2, 4];
78
79/// This family's receipt marker for the GENERAL transport seam (`tp_transport`, generalized
80/// lane/glm5-extract2). The tag is the caller's so lane/glm5-tp-transport's and
81/// lane/glm5-composition's banked gate and box receipts keep their exact bytes while a second
82/// family gets its own marker — the same rule phase 1 set for `[glm5-phase]` on the shared
83/// spec timers.
84pub const GLM5_TP_TRANSPORT_TAG: &str = "glm5-tp-transport";
85
86pub type Glm5TpLayerSpec = crate::tp::StepEpLayerSpec;
87
88// ------------------------------------------------------------------------------------------
89// Flag
90// ------------------------------------------------------------------------------------------
91
92/// Raw `MEMRA_GLM5_TP` value. Empty / unset / `"0"` = seam off.
93pub fn glm5_tp_env_raw() -> Option<String> {
94    std::env::var("MEMRA_GLM5_TP").ok()
95}
96
97/// Cheap armed check for co-refusal sites (server boot, spec doors). Parse errors count as
98/// ARMED so a misspelled spec still refuses the co-armed program instead of racing the
99/// loader's own refusal.
100pub fn glm5_tp_armed() -> bool {
101    matches!(glm5_tp_env_raw().as_deref(), Some(v) if !v.is_empty() && v != "0")
102}
103
104/// Parse the shared `LAYER[-LAYER]@DEVICE,DEVICE[,...][;...]` grammar for the glm5 door.
105/// `trunk_layers` is the loaded model's trunk length (the `all` shorthand expands against
106/// it — the model contract owns that number, never a constant in the parser).
107pub fn parse_glm5_tp_layer_specs(
108    value: Option<&str>,
109    trunk_layers: usize,
110) -> Result<Vec<Glm5TpLayerSpec>, String> {
111    crate::tp::parse_layer_specs_for_trunk("MEMRA_GLM5_TP", value, Some(trunk_layers))
112}
113
114/// Gate-harness knob, never a serving flag: `MEMRA_GLM5_TP_GATE_SAME_DEV=1` builds every
115/// peer rank as an ADDITIONAL CUDA CONTEXT ON THE ROOT DEVICE (the one-card rig gate's
116/// emulation; the ppN same-device-stages precedent). The spec's non-root device ids become
117/// logical rank ids. The serving worker refuses `MEMRA_GLM5_TP` outright, so this can never
118/// leak into serving.
119pub fn gate_same_device() -> bool {
120    std::env::var("MEMRA_GLM5_TP_GATE_SAME_DEV").as_deref() == Ok("1")
121}
122
123/// Gate-harness RED-arm knob, never a serving flag (`MEMRA_GLM5_TP_GATE_RED`):
124///   * `swap-wo` — each rank's column `wo` slice takes the NEXT rank's out rows (a broken
125///     shard map); the gate run MUST diverge from plain.
126///   * `swap-ep-gateup` — the root EP slab's gate and up projections swap (wrong expert
127///     weights); MUST diverge.
128///   * `skip-peer-combine` — the EP combine drops every peer-owned slot; MUST diverge,
129///     which is also the non-vacuity proof that the peer ranks contribute real work.
130///   * `corrupt-ep-map` — the placement's local-slot table for rank 0 is reversed after
131///     the slabs are built (owner table and slab bytes disagree — a corrupted map row);
132///     MUST diverge. This is the red that proves the MEASURED-placement indirection is
133///     load-bearing, not decorative.
134///
135/// Unknown values refuse at load.
136#[derive(Clone, Copy, PartialEq, Eq, Debug)]
137pub enum GateRed {
138    SwapWo,
139    SwapEpGateUp,
140    SkipPeerCombine,
141    CorruptEpMap,
142}
143
144pub fn gate_red() -> Result<Option<GateRed>, String> {
145    match std::env::var("MEMRA_GLM5_TP_GATE_RED").ok().as_deref() {
146        None | Some("") => Ok(None),
147        Some("swap-wo") => Ok(Some(GateRed::SwapWo)),
148        Some("swap-ep-gateup") => Ok(Some(GateRed::SwapEpGateUp)),
149        Some("skip-peer-combine") => Ok(Some(GateRed::SkipPeerCombine)),
150        Some("corrupt-ep-map") => Ok(Some(GateRed::CorruptEpMap)),
151        Some(other) => Err(format!(
152            "MEMRA_GLM5_TP_GATE_RED={other:?} is not a known red arm \
153             (swap-wo | swap-ep-gateup | skip-peer-combine | corrupt-ep-map)"
154        )),
155    }
156}
157
158// ------------------------------------------------------------------------------------------
159// Runtime
160// ------------------------------------------------------------------------------------------
161
162/// The TP-N rank runtime. Rank 0 (root) executes on the model's own engine — the PP-owner
163/// context, exactly like the step seam's owner-first rank law. Ranks `1..ranks` each own a
164/// full peer Engine, in `MEMRA_GLM5_TP` device order (`peers[i]` = rank `i + 1`).
165pub struct Glm5TpRt {
166    pub peers: Vec<Engine>,
167    pub root_dev: usize,
168    pub peer_devs: Vec<usize>,
169    /// True only when built through [`Glm5TpRt::new_gate_same_device`] — the one-card rig
170    /// gate's multi-context emulation (the ppN same-device gate precedent). The env-driven
171    /// serving parse can never reach this: the grammar refuses duplicate devices.
172    pub same_device_gate: bool,
173    /// Which transport every cross-rank hop of this runtime moves its bytes with
174    /// (`MEMRA_GLM5_TP_TRANSPORT`, default `host-canonical`). Frozen at
175    /// [`Glm5TpRt::arm_transport`] time, announced once, and named in every gate log.
176    pub transport: crate::tp_transport::TpTransport,
177    /// The peer-pull ordering primitives — `Some` only on the peer-pull arm, and only after
178    /// its byte-integrity ladder passed.
179    link: Option<crate::tp_transport::PeerPullLink>,
180}
181
182impl Glm5TpRt {
183    pub fn new(devices: &[usize]) -> Result<Self, Box<dyn std::error::Error>> {
184        let root_dev = devices[0];
185        let peer_devs: Vec<usize> = devices[1..].to_vec();
186        for &d in &peer_devs {
187            if d == root_dev || peer_devs.iter().filter(|&&x| x == d).count() > 1 {
188                return Err(format!(
189                    "MEMRA_GLM5_TP rank devices must be distinct in serving; got {devices:?} \
190                     (the same-device form exists only for the rig gate binary)"
191                )
192                .into());
193            }
194        }
195        let mut peers = Vec::with_capacity(peer_devs.len());
196        for &d in &peer_devs {
197            peers.push(Engine::new(d)?);
198        }
199        Ok(Self {
200            peers,
201            root_dev,
202            peer_devs,
203            same_device_gate: false,
204            transport: crate::tp_transport::TpTransport::HostCanonical,
205            link: None,
206        })
207    }
208
209    /// Same-device multi-context runtime for the ONE-CARD rig gate (exactness only). Every
210    /// peer rank is an additional CUDA context on the root device: the whole shard/join
211    /// walk — shard loads, replicated compute, gathers, canonical combines — executes
212    /// exactly as on N cards, minus real peer transport (which the pro6000 batteries
213    /// qualify on the box card class separately).
214    pub fn new_gate_same_device(
215        root_dev: usize,
216        ranks: usize,
217    ) -> Result<Self, Box<dyn std::error::Error>> {
218        let mut peers = Vec::with_capacity(ranks - 1);
219        for _ in 1..ranks {
220            peers.push(Engine::new(root_dev)?);
221        }
222        Ok(Self {
223            peers,
224            root_dev,
225            peer_devs: vec![root_dev; ranks - 1],
226            same_device_gate: true,
227            transport: crate::tp_transport::TpTransport::HostCanonical,
228            link: None,
229        })
230    }
231
232    /// Rank count of this runtime (root + peers).
233    pub fn ranks(&self) -> usize {
234        self.peers.len() + 1
235    }
236
237    /// Distinct device ordinals participating in this runtime.
238    pub fn devices(&self) -> Vec<usize> {
239        let mut devs = Vec::with_capacity(1 + self.peer_devs.len());
240        devs.push(self.root_dev);
241        for &d in &self.peer_devs {
242            if !devs.contains(&d) {
243                devs.push(d);
244            }
245        }
246        devs
247    }
248
249    /// Freeze the transport for this runtime: read the flag, grant peer access (real groups
250    /// only), and run the byte-integrity pull ladder over every ordered rank pair. Called
251    /// from the preflight BEFORE any layer is sharded, so a bad fabric refuses the load
252    /// rather than corrupting a shard.
253    pub fn arm_transport(&mut self, root: &Engine) -> Result<(), Box<dyn std::error::Error>> {
254        // The seam is general; the TAG and the flag name are the FAMILY's (lane/glm5-extract2,
255        // the phase-1 caller-owned-tag pattern) — `[glm5-tp-transport]` bytes stay exactly as
256        // lane/glm5-tp-transport and lane/glm5-composition banked them, and `armed_flag` is
257        // whichever of `MEMRA_TP_TRANSPORT` / `MEMRA_GLM5_TP_TRANSPORT` the operator actually
258        // set, so a peer-access or ladder refusal names their flag.
259        let (transport, armed_flag) = crate::tp_transport::transport_env()?;
260        let engines: Vec<&Engine> = std::iter::once(root).chain(self.peers.iter()).collect();
261        let link = crate::tp_transport::arm_transport(
262            transport,
263            armed_flag,
264            GLM5_TP_TRANSPORT_TAG,
265            &engines,
266            self.same_device_gate,
267        )?;
268        self.transport = transport;
269        self.link = link;
270        Ok(())
271    }
272
273    /// Build the per-hop transport handle. Every cross-rank movement in the glm5 TP walk
274    /// goes through one of `tp_transport`'s named hop shapes with this handle, which is
275    /// what makes the arm swap a ONE-PLACE change and the movement census automatic.
276    pub fn hop<'a>(&'a self, root: &'a Engine) -> crate::tp_transport::Hop<'a> {
277        crate::tp_transport::Hop {
278            engines: std::iter::once(root).chain(self.peers.iter()).collect(),
279            transport: self.transport,
280            link: self.link.as_ref(),
281        }
282    }
283}
284
285// ------------------------------------------------------------------------------------------
286// Preflight
287// ------------------------------------------------------------------------------------------
288
289/// What the loader tells the preflight about the model, extracted from the plan/config
290/// BEFORE any TP CUDA state exists. Structural laws are dimension-derived (they hold for
291/// the mini fixture and the real artifact alike): the laws ARE the geometry checks.
292pub struct Glm5TpModelView {
293    pub trunk_layers: usize,
294    /// Per-layer mixer class, `trunk_layers` entries.
295    pub layer_class: Vec<Glm5LayerClass>,
296    /// Per-layer "has routed-expert FFN" flag (dense-prefix layers are false).
297    pub layer_is_moe: Vec<bool>,
298    pub kda_heads: usize,
299    pub kda_head_dim: usize,
300    pub mla_heads: usize,
301    pub n_routed_experts: usize,
302    pub top_k: usize,
303}
304
305#[derive(Clone, Copy, PartialEq, Eq, Debug)]
306pub enum Glm5LayerClass {
307    Kda,
308    Mla,
309}
310
311/// The armed load plan: the runtime plus the layer set the spec selected, plus the
312/// measured expert-placement map when `MEMRA_EP_MAP` (or its glm5 alias) is armed (validated at
313/// preflight, before any TP CUDA state — absent flag = the even split, byte-unchanged).
314pub struct Glm5TpLoadPlan {
315    pub rt: Arc<Glm5TpRt>,
316    pub layers: std::collections::BTreeSet<usize>,
317    pub ep_map: Option<crate::ep_map::EpMap>,
318}
319
320/// Load + validate the placement map against the model view and the armed layer set.
321/// The env seam is the general `ep_map::ep_map_env()` (`MEMRA_EP_MAP`, glm5 alias
322/// honored); every refusal names the flag that ARMED the load. `Some("")` REFUSES (a
323/// set-but-empty flag is an operator error, never a silent even split). Fail-closed on
324/// every axis: unreadable file, malformed text, rank/expert-count mismatch, layer-cover
325/// mismatch. Returns `None` only when both names are UNSET.
326fn load_glm5_ep_map(
327    view: &Glm5TpModelView,
328    layers: &std::collections::BTreeSet<usize>,
329    ranks: usize,
330) -> Result<Option<crate::ep_map::EpMap>, Box<dyn std::error::Error>> {
331    let Some((flag, path)) = crate::ep_map::ep_map_env()? else {
332        return Ok(None);
333    };
334    if path.is_empty() {
335        return Err(format!(
336            "{flag} is set but empty (fail-closed: unset the flag for \
337                    the even split; an empty value never silently means default)"
338        )
339        .into());
340    }
341    let text = std::fs::read_to_string(&path)
342        .map_err(|e| format!("{flag}={path}: cannot read the map file ({e}) — refused by name"))?;
343    let map = crate::ep_map::EpMap::parse(&text).map_err(|e| format!("{flag}={path}: {e}"))?;
344    if map.ranks != ranks {
345        return Err(format!(
346            "{flag}={path}: map declares ranks={}, this load is TP-{ranks} \
347             (re-mint the map for the armed rank count)",
348            map.ranks
349        )
350        .into());
351    }
352    if map.n_experts != view.n_routed_experts {
353        return Err(format!(
354            "{flag}={path}: map declares expert_count={}, the model routes {}",
355            map.n_experts, view.n_routed_experts
356        )
357        .into());
358    }
359    if map.entry_rank != 0 {
360        return Err(format!(
361            "{flag}={path}: entry_rank={} but the glm5 TP first-hop card is \
362             rank 0 (root: router + combine + shared expert) — re-mint with \
363             --entry-rank 0 (refused rather than silently remapping ranks)",
364            map.entry_rank
365        )
366        .into());
367    }
368    let ep_layers: Vec<usize> = layers
369        .iter()
370        .copied()
371        .filter(|&il| view.layer_is_moe[il])
372        .collect();
373    map.validate_layer_cover(&ep_layers)
374        .map_err(|e| format!("{flag}={path}: {e}"))?;
375    // Receipt anchor: the map bytes that armed this load, named by digest.
376    let digest = {
377        use sha2::{Digest, Sha256};
378        let mut h = Sha256::new();
379        h.update(text.as_bytes());
380        let out = h.finalize();
381        out.iter().map(|b| format!("{b:02x}")).collect::<String>()
382    };
383    eprintln!(
384        "[glm5-tp-preflight] ep-map armed path={path} sha256={digest} layers={} \
385         experts={} ranks={} entry_rank={} performance_claim=false",
386        map.layers.len(),
387        map.n_experts,
388        map.ranks,
389        map.entry_rank,
390    );
391    Ok(Some(map))
392}
393
394/// Decode-diet doors that never co-arm with `MEMRA_GLM5_TP` in v1 (merge-forward
395/// 2026-08-31): every TP-x-door pair is UNPROVEN. The TP byte/band gates ran with every
396/// door cold, and each door's own gate ran on the unsharded walk, so v1 refuses by name
397/// rather than silently picking an arm; a pair unlocks only with its own composition gate
398/// (the `MEMRA_GLM5_TP` row in docs/FLAGS.md carries the matrix). `MEMRA_GLM5_VERIFY_BATCH`
399/// is absent DELIBERATELY: its walk exists only inside glm5 spec sessions — co-refused on
400/// a sharded model unless `MEMRA_GLM5_SPEC_TP=1` arms the GATED composition
401/// (lane/glm5-composition; the spec x TP pair HAS its composition gate, `glm5-tp-gate`
402/// arms S2/Q-S4), whose admission REQUIRES the batched walk by name.
403pub const GLM5_TP_REFUSED_DOOR_FLAGS: [(&str, &str); 4] = [
404    (
405        "MEMRA_HC_FUSED_PRE",
406        "the fused mHC pre-chain is gated on the unsharded walk only",
407    ),
408    (
409        "MEMRA_HC_DECODE_WS",
410        "the workspace decode walk carries no TP mixer branches",
411    ),
412    (
413        "MEMRA_KDA_FUSED_PROJ",
414        "the fused six-projection door (either operand arm) is gated on full-width \
415         projections, never head shards",
416    ),
417    (
418        "MEMRA_MLA_DECODE_SPLIT",
419        "the absorb/decompress split is gated on the full-head geometry",
420    ),
421];
422
423/// The pure composition law over [`GLM5_TP_REFUSED_DOOR_FLAGS`]: the first armed door
424/// refuses by name, before any TP CUDA state exists. Delegates to the general
425/// [`crate::tp::refuse_door_composition`] pattern (lane/glm5-extract-general) with this
426/// door's own table — error bytes unchanged. `armed` reports whether a flag is set to
427/// `"1"` (env in production; a plain set in the unit test — the module keeps its tests
428/// env-mutation-free).
429pub fn refuse_glm5_tp_door_composition(armed: impl Fn(&str) -> bool) -> Result<(), String> {
430    crate::tp::refuse_door_composition("MEMRA_GLM5_TP", &GLM5_TP_REFUSED_DOOR_FLAGS, armed)
431}
432
433/// Fail-closed preflight + runtime construction. Returns `None` when the seam is off.
434/// Every illegal geometry refuses HERE, before any rank engine or shard exists.
435pub fn prepare_glm5_tp_load(
436    e: &Engine,
437    view: &Glm5TpModelView,
438) -> Result<Option<Glm5TpLoadPlan>, Box<dyn std::error::Error>> {
439    let raw = glm5_tp_env_raw();
440    let specs = parse_glm5_tp_layer_specs(raw.as_deref(), view.trunk_layers)?;
441    if specs.is_empty() {
442        return Ok(None);
443    }
444
445    // Co-armed programs refuse by name: two parallel/spec programs on one model never
446    // silently coexist (the MEMRA_DSPARK precedent).
447    if crate::pp::pp_cuts(view.trunk_layers).is_some() {
448        return Err(
449            "MEMRA_GLM5_TP + MEMRA_PP_STAGES>1: the TP x PP composition is unwired and \
450             refuses until its own gate exists (stage 5 of the tp2 lane names it)"
451                .into(),
452        );
453    }
454    if !crate::tp::step_tp_layer_specs()?.is_empty()
455        || !crate::tp::step_ep_layer_specs()?.is_empty()
456    {
457        return Err(
458            "MEMRA_GLM5_TP + MEMRA_STEP_TP/MEMRA_STEP_EP: the step and glm5 parallel \
459             contracts never co-arm"
460                .into(),
461        );
462    }
463    refuse_glm5_tp_door_composition(|flag| std::env::var(flag).as_deref() == Ok("1"))?;
464
465    // One device group across the whole spec (one runtime group), root-first; the rank
466    // count comes from the device list and must be in the qualified envelope.
467    let devices = specs[0].devices.clone();
468    let ranks = devices.len();
469    if !GLM5_TP_ALLOWED_RANKS.contains(&ranks) {
470        return Err(format!(
471            "MEMRA_GLM5_TP names {ranks} devices per layer; the qualified rank envelope is \
472             {GLM5_TP_ALLOWED_RANKS:?} (TP-3 is a head-padding question, not built — see the \
473             module doc)"
474        )
475        .into());
476    }
477
478    // Structural geometry laws, all dimension-derived.
479    if view.layer_class.len() != view.trunk_layers || view.layer_is_moe.len() != view.trunk_layers {
480        return Err(format!(
481            "glm5-tp preflight: layer class map ({}/{}) does not cover the {}-layer trunk",
482            view.layer_class.len(),
483            view.layer_is_moe.len(),
484            view.trunk_layers
485        )
486        .into());
487    }
488    if !view.kda_heads.is_multiple_of(ranks) || view.kda_heads == 0 {
489        return Err(format!(
490            "glm5-tp: {} KDA heads do not shard across {ranks} ranks",
491            view.kda_heads
492        )
493        .into());
494    }
495    if view.kda_head_dim != crate::kda::KDA_HEAD_DIM {
496        return Err(format!(
497            "glm5-tp: KDA head_dim {} is not the {} the scan kernel is instantiated for",
498            view.kda_head_dim,
499            crate::kda::KDA_HEAD_DIM
500        )
501        .into());
502    }
503    if !view.mla_heads.is_multiple_of(ranks) || view.mla_heads == 0 {
504        return Err(format!(
505            "glm5-tp: {} MLA heads do not shard across {ranks} ranks",
506            view.mla_heads
507        )
508        .into());
509    }
510    if !view.n_routed_experts.is_multiple_of(ranks) || view.n_routed_experts == 0 {
511        return Err(format!(
512            "glm5-tp: {} routed experts do not partition across {ranks} ranks",
513            view.n_routed_experts
514        )
515        .into());
516    }
517    if view.top_k > view.n_routed_experts {
518        return Err("glm5-tp: top_k exceeds the routed expert count".into());
519    }
520
521    for s in &specs {
522        if s.devices != devices {
523            return Err(format!(
524                "MEMRA_GLM5_TP carries ONE runtime group: layer {} names devices {:?}, \
525                 the first spec names {:?}",
526                s.layer, s.devices, devices
527            )
528            .into());
529        }
530        if s.layer >= view.trunk_layers {
531            return Err(format!(
532                "MEMRA_GLM5_TP layer {} outside the {}-layer trunk",
533                s.layer, view.trunk_layers
534            )
535            .into());
536        }
537    }
538    let root_dev = e.ctx().ordinal();
539    if devices[0] != root_dev {
540        return Err(format!(
541            "MEMRA_GLM5_TP rank list {:?} must start with the owning device {root_dev} \
542             (the owner-first rank law)",
543            devices
544        )
545        .into());
546    }
547
548    // Validate the gate red-arm spelling at load (fail-closed), and pick the transport.
549    let red = gate_red()?;
550    let same_dev = gate_same_device();
551    if let Some(red) = red {
552        eprintln!("[glm5-tp-preflight] GATE RED ARM armed: {red:?} — outputs MUST diverge");
553    }
554    let mut rt = if same_dev {
555        eprintln!(
556            "[glm5-tp-preflight] GATE same-device emulation: {} peer ranks are additional \
557             contexts on device {root_dev} (spec devices {:?} are logical rank ids)",
558            ranks - 1,
559            &devices[1..],
560        );
561        Glm5TpRt::new_gate_same_device(root_dev, ranks)?
562    } else {
563        Glm5TpRt::new(&devices)?
564    };
565    // Transport arms HERE — after the rank engines exist, BEFORE any layer is sharded. A
566    // peer-pull ladder failure refuses the load with zero TP shards built (lane/glm5-tp-transport).
567    rt.arm_transport(e)?;
568    let rt = Arc::new(rt);
569    let layers: std::collections::BTreeSet<usize> = specs.iter().map(|s| s.layer).collect();
570    let ep_map = load_glm5_ep_map(view, &layers, ranks)?;
571    let (mut kda_n, mut mla_n, mut moe_n) = (0usize, 0usize, 0usize);
572    for &il in &layers {
573        match view.layer_class[il] {
574            Glm5LayerClass::Kda => kda_n += 1,
575            Glm5LayerClass::Mla => mla_n += 1,
576        }
577        if view.layer_is_moe[il] {
578            moe_n += 1;
579        }
580    }
581    eprintln!(
582        "[glm5-tp-preflight] armed ranks={ranks} devices={devices:?} layers={} \
583         kda_shard={kda_n} mla_shard={mla_n} moe_ep={moe_n} kda_heads_per_rank={} \
584         mla_heads_per_rank={} experts_per_rank={} transport={} \
585         weights_loaded=false performance_claim=false",
586        layers.len(),
587        view.kda_heads / ranks,
588        view.mla_heads / ranks,
589        view.n_routed_experts / ranks,
590        rt.transport.name(),
591    );
592    Ok(Some(Glm5TpLoadPlan { rt, layers, ep_map }))
593}
594
595// ------------------------------------------------------------------------------------------
596// Shard mechanics
597// ------------------------------------------------------------------------------------------
598
599fn outer_rows(ne: &[u64]) -> (usize, usize) {
600    // GGML axis order: ne[0] is the fastest (innermost). The shardable axis is the LAST
601    // (outermost) — out rows on a 2D projection, the head axis on a 3D per-head slab.
602    let outer = *ne.last().expect("tensor has at least one axis") as usize;
603    let inner: usize = ne[..ne.len() - 1].iter().map(|&d| d as usize).product();
604    (outer, inner.max(1))
605}
606
607/// Copy `rows` of `t`'s outermost axis onto `dst` (host bounce; load-time only). Mirror
608/// planes (`rp`/`rp4`/`f16`/`fp8`/`blk`) REFUSE by name: v1 shards carry the raw layout —
609/// a pure byte-permutation difference, bit-identical by the mirrors' own contracts.
610fn shard_rows(
611    src_engine: &Engine,
612    dst: &Engine,
613    t: &GpuTensor,
614    rows: Range<usize>,
615) -> Result<GpuTensor, Box<dyn std::error::Error>> {
616    match t {
617        GpuTensor::Float { data, ne } => {
618            let (outer, inner) = outer_rows(ne);
619            if rows.end > outer {
620                return Err(format!("shard rows {rows:?} exceed outer axis {outer}").into());
621            }
622            let host = src_engine.dtoh(data)?;
623            let piece = &host[rows.start * inner..rows.end * inner];
624            let mut ne2 = ne.clone();
625            *ne2.last_mut().unwrap() = (rows.end - rows.start) as u64;
626            Ok(GpuTensor::Float {
627                data: dst.htod(piece)?,
628                ne: ne2,
629            })
630        }
631        GpuTensor::FloatBf16 { data, ne } => {
632            let (outer, inner) = outer_rows(ne);
633            if rows.end > outer {
634                return Err(format!("shard rows {rows:?} exceed outer axis {outer}").into());
635            }
636            let host = src_engine.dtoh_u8(data)?;
637            let piece = &host[rows.start * inner * 2..rows.end * inner * 2];
638            let mut ne2 = ne.clone();
639            *ne2.last_mut().unwrap() = (rows.end - rows.start) as u64;
640            Ok(GpuTensor::FloatBf16 {
641                data: dst.htod_bytes(piece)?,
642                ne: ne2,
643            })
644        }
645        GpuTensor::Quant {
646            bytes,
647            qtype,
648            row_bytes,
649            ne,
650            scale,
651            rp,
652            fp8,
653            rp4,
654            blk,
655            f16,
656            #[cfg(memra_cutlass)]
657            cutlass,
658        } => {
659            if *rp {
660                return Err(
661                    "glm5-tp shard: rp split-plane mirror layout is unwired — load \
662                            the TP-armed tensor with MEMRA_RP=0 (raw layout is bit-identical \
663                            by the mirror's own contract)"
664                        .into(),
665                );
666            }
667            if fp8.is_some() || rp4.is_some() || blk.is_some() || f16.is_some() {
668                return Err(
669                    "glm5-tp shard: a decode/prefill mirror (fp8/rp4/blk/f16) is present on a \
670                     TP-armed tensor — mirrors are unwired for shards in v1; disable the \
671                     mirror door for this load"
672                        .into(),
673                );
674            }
675            #[cfg(memra_cutlass)]
676            if cutlass.is_some() {
677                return Err("glm5-tp shard: cutlass prefill operand unwired for shards".into());
678            }
679            let (outer, inner) = outer_rows(ne);
680            if ne.len() != 2 {
681                return Err("glm5-tp shard: quantized shards are 2D-only in v1".into());
682            }
683            let _ = inner;
684            if rows.end > outer {
685                return Err(format!("shard rows {rows:?} exceed outer axis {outer}").into());
686            }
687            let host = src_engine.dtoh_u8(bytes)?;
688            let piece = &host[rows.start * row_bytes..rows.end * row_bytes];
689            let mut ne2 = ne.clone();
690            *ne2.last_mut().unwrap() = (rows.end - rows.start) as u64;
691            Ok(GpuTensor::Quant {
692                bytes: dst.htod_bytes(piece)?,
693                qtype: *qtype,
694                row_bytes: *row_bytes,
695                ne: ne2,
696                scale: *scale,
697                rp: false,
698                fp8: None,
699                rp4: None,
700                blk: None,
701                f16: None,
702                #[cfg(memra_cutlass)]
703                cutlass: None,
704            })
705        }
706    }
707}
708
709/// Full replica of `t` on `dst` (host bounce). Same mirror refusals as [`shard_rows`].
710fn replicate(
711    src_engine: &Engine,
712    dst: &Engine,
713    t: &GpuTensor,
714) -> Result<GpuTensor, Box<dyn std::error::Error>> {
715    let (outer, _) = outer_rows(t.ne());
716    shard_rows(src_engine, dst, t, 0..outer)
717}
718
719/// Rank r's engine within a runtime, given the root engine (rank 0 has no owned Engine in
720/// the runtime — it IS the model's engine).
721pub(crate) fn rank_engine<'a>(e: &'a Engine, rt: &'a Glm5TpRt, r: usize) -> &'a Engine {
722    if r == 0 { e } else { &rt.peers[r - 1] }
723}
724
725// ------------------------------------------------------------------------------------------
726// KDA sidecar
727// ------------------------------------------------------------------------------------------
728
729/// The KDA TP sidecar: the peer ranks' head shards plus the runtime handle. The OUTER
730/// `KdaAttnLayer` that carries this in its `tp` field is the root shard; every shard's
731/// `wo` field holds that rank's COLUMN slice (out rows over the full `qkv` input).
732pub struct Glm5TpKda {
733    pub rt: Arc<Glm5TpRt>,
734    /// `peers[i]` is rank `i + 1`'s shard, resident on `rt.peers[i]`.
735    pub peers: Vec<KdaAttnLayer>,
736    /// Full-width qkv of the UNSHARDED layer (`ranks * shard qkv`) — the gather width.
737    pub full_qkv: usize,
738    /// Full hidden width (`wo` out rows across all ranks).
739    pub n_embd: usize,
740}
741
742impl Glm5TpKda {
743    pub fn ranks(&self) -> usize {
744        self.peers.len() + 1
745    }
746}
747
748static KDA_MARKED: AtomicBool = AtomicBool::new(false);
749
750/// Shard one loaded KDA layer: returns the ROOT shard (heads/ranks, wo out-rows
751/// `0..H/ranks`) with the peer shards in its `tp` sidecar. The full layer's tensors are
752/// consumed and dropped — per-layer transient VRAM is one layer, never the model.
753pub(crate) fn shard_kda_layer(
754    e: &Engine,
755    rt: &Arc<Glm5TpRt>,
756    la: KdaAttnLayer,
757) -> Result<KdaAttnLayer, Box<dyn std::error::Error>> {
758    if la.tp.is_some() {
759        return Err("shard_kda_layer: layer is already sharded".into());
760    }
761    let ranks = rt.ranks();
762    let heads = la.heads();
763    let head_dim = la.head_dim();
764    let qkv = la.qkv();
765    let kernel = la.conv_kernel();
766    if !heads.is_multiple_of(ranks) {
767        return Err(format!("KDA heads {heads} do not shard across {ranks} ranks").into());
768    }
769    let hl = heads / ranks; // heads per rank
770    let ql = qkv / ranks; // channels per rank
771    let n_embd = la.wo.out_features();
772    if !n_embd.is_multiple_of(ranks) {
773        return Err(format!("KDA wo out {n_embd} does not split across ranks").into());
774    }
775    let hh = n_embd / ranks;
776
777    let mut shard_plan = la.plan;
778    shard_plan.num_heads = hl as u32;
779
780    // Per-rank fused conv slice: plane p occupies rows [p*qkv, (p+1)*qkv) of the fused
781    // [3*qkv, kernel] buffer; rank r takes channel rows [r*ql, (r+1)*ql) of each plane.
782    let conv_host = e.dtoh(&la.conv)?;
783    let conv_rank =
784        |dst: &Engine, r: usize| -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
785            let mut piece = Vec::with_capacity(3 * ql * kernel);
786            for p in 0..3 {
787                let a = (p * qkv + r * ql) * kernel;
788                piece.extend_from_slice(&conv_host[a..a + ql * kernel]);
789            }
790            dst.htod(&piece)
791        };
792
793    // Gate red arm: a broken shard map hands each rank the NEXT rank's wo out rows
794    // (the two-rank swap generalized to a rotation — still guaranteed wrong on every rank).
795    let wo_rank = |r: usize| -> usize {
796        match gate_red() {
797            Ok(Some(GateRed::SwapWo)) => (r + 1) % ranks,
798            _ => r,
799        }
800    };
801
802    let rank_shard = |dst: &Engine, r: usize| -> Result<KdaAttnLayer, Box<dyn std::error::Error>> {
803        let wr = wo_rank(r);
804        Ok(KdaAttnLayer {
805            plan: shard_plan,
806            wq: shard_rows(e, dst, &la.wq, r * ql..(r + 1) * ql)?,
807            wk: shard_rows(e, dst, &la.wk, r * ql..(r + 1) * ql)?,
808            wv: shard_rows(e, dst, &la.wv, r * ql..(r + 1) * ql)?,
809            f_a: replicate(e, dst, &la.f_a)?,
810            f_b: shard_rows(e, dst, &la.f_b, r * ql..(r + 1) * ql)?,
811            g_a: replicate(e, dst, &la.g_a)?,
812            g_b: shard_rows(e, dst, &la.g_b, r * ql..(r + 1) * ql)?,
813            b_proj: shard_rows(e, dst, &la.b_proj, r * hl..(r + 1) * hl)?,
814            // COLUMN-parallel wo: rank r owns OUT rows [r*hh, (r+1)*hh) over the FULL qkv
815            // input — consumed by the join over the gathered gated tensor, never by
816            // kda_core_gated itself.
817            wo: shard_rows(e, dst, &la.wo, wr * hh..(wr + 1) * hh)?,
818            conv: conv_rank(dst, r)?,
819            a_log: shard_rows(e, dst, &la.a_log, r * hl..(r + 1) * hl)?,
820            dt_bias: shard_rows(e, dst, &la.dt_bias, r * ql..(r + 1) * ql)?,
821            o_norm: replicate(e, dst, &la.o_norm)?,
822            tp: None,
823        })
824    };
825
826    let mut root = rank_shard(e, 0)?;
827    let mut peers = Vec::with_capacity(ranks - 1);
828    for r in 1..ranks {
829        peers.push(rank_shard(&rt.peers[r - 1], r)?);
830    }
831    if !KDA_MARKED.swap(true, Ordering::Relaxed) {
832        eprintln!(
833            "[glm5-tp-kda] head shard armed: ranks={ranks} heads_per_rank={hl} \
834             head_dim={head_dim} wo=column-over-gather transport={} performance_claim=false",
835            rt.transport.name(),
836        );
837    }
838    root.tp = Some(Box::new(Glm5TpKda {
839        rt: Arc::clone(rt),
840        peers,
841        full_qkv: qkv,
842        n_embd,
843    }));
844    Ok(root)
845}
846
847/// Ensure layer `il`'s per-rank KDA state planes exist (lazily, sized for the SHARD
848/// geometry — the canonical `cache.recur[il]` planes are full-width and stay untouched
849/// as allocated; the TP walk never reads them). Index 0 = root's plane on `e`, index r =
850/// rank r's plane on its peer engine.
851fn ensure_kda_tp_state<'c>(
852    e: &Engine,
853    rt: &Glm5TpRt,
854    la_root: &KdaAttnLayer,
855    cache: &'c mut Cache,
856    il: usize,
857) -> Result<&'c mut Vec<RecurLayer>, Box<dyn std::error::Error>> {
858    if cache.glm5_tp_recur.len() <= il {
859        return Err(format!("glm5-tp: cache carries no TP recur slot for layer {il}").into());
860    }
861    if cache.glm5_tp_recur[il].is_none() {
862        let conv_pad = la_root.conv_width() * (la_root.conv_kernel() - 1);
863        let state = la_root.state_width();
864        let mk = |dev: &Engine| -> Result<RecurLayer, Box<dyn std::error::Error>> {
865            Ok(RecurLayer {
866                conv_state: dev.zeros(conv_pad)?,
867                ssm_state: dev.zeros(state)?,
868                ssm_state_alt: dev.zeros(state)?,
869            })
870        };
871        let mut planes = Vec::with_capacity(rt.ranks());
872        planes.push(mk(e)?);
873        for p in &rt.peers {
874            planes.push(mk(p)?);
875        }
876        cache.glm5_tp_recur[il] = Some(planes);
877    }
878    Ok(cache.glm5_tp_recur[il].as_mut().unwrap())
879}
880
881/// The KDA TP walk, ONE body for both consumers (the #80 review's dedup finding — the
882/// forked verify twin had already drifted to root-first issue order):
883///   * prime/decode ([`kda_tp_cached`]): `verify_stash = None`, plain `wo` matmul —
884///     byte-for-byte the pre-composition walk.
885///   * spec x TP verify rows ([`kda_tp_verify_rows`]): `verify_stash = Some`, per-rank
886///     pre-round ssm snapshot + batched `KdaStash::Rows` capture, `wo` on the ROWS-EXACT
887///     class (the unsharded verify walk's own routing), per-rank scan-ns accumulated into
888///     `scan_clock` so the `[glm5-phase-v]` receipt keeps its sequential-floor share on
889///     the composed shape.
890///
891/// Issue order is PEERS FIRST, ROOT LAST on both arms (v1's order; the twins document it).
892/// THREE cross-rank hop shapes, each a named `tp_transport` shape: fan-out of `x`,
893/// gather of the gated parts, concat of the `wo` parts. On `host-canonical` at two ranks
894/// that is 5 draining `dtoh` + 4 `htod` per layer-call, exactly as v1; on `peer-pull` it is
895/// device peer copies, local copies and 0 host boundaries.
896#[allow(clippy::too_many_arguments)] // mirrors the kda entry contract shape
897fn kda_tp_core(
898    e: &Engine,
899    la_root: &KdaAttnLayer,
900    x: &CudaSlice<f32>,
901    t: usize,
902    eps: f32,
903    cache: &mut Cache,
904    il: usize,
905    arm: ConvArm,
906    verify_stash: Option<&mut Glm5TpKdaVerifyStash>,
907    mut scan_clock: Option<&mut u64>,
908) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
909    let tp = la_root
910        .tp
911        .as_ref()
912        .ok_or("kda_tp_core called on an unsharded layer")?;
913    let rt = &tp.rt;
914    let ranks = rt.ranks();
915    let ql = la_root.qkv(); // per-rank channels
916    let full = tp.full_qkv;
917    let n_embd = tp.n_embd;
918    let hh = n_embd / ranks;
919    let rows_exact = verify_stash.is_some();
920    // Per-rank verify capture, RANK-indexed regardless of issue order; assembled into the
921    // caller's stash after the loop.
922    let mut captured: Vec<Option<(CudaSlice<f32>, crate::kda::KdaRowsStash)>> =
923        (0..ranks).map(|_| None).collect();
924
925    let hop = rt.hop(e);
926    // HOP 1 — fan-out of the mixer input to every peer rank. `x.len()` and not `t * n_embd`:
927    // the v1 arm moved the WHOLE buffer, and the arms must move identical byte ranges or
928    // the transport A/B stops being a transport A/B.
929    let x_peers = crate::tp_transport::fanout_f32(&hop, x, x.len())?;
930    let states = ensure_kda_tp_state(e, rt, la_root, cache, il)?;
931
932    // Peer shards first (host-canonical serial walk; overlap is the box arc), root last —
933    // v1's issue order at two ranks, both arms.
934    let mut gated: Vec<Option<CudaSlice<f32>>> = (0..ranks).map(|_| None).collect();
935    for r in (1..ranks).chain(std::iter::once(0)) {
936        let dev = if r == 0 { e } else { &rt.peers[r - 1] };
937        let la = if r == 0 { la_root } else { &tp.peers[r - 1] };
938        let xin = if r == 0 { x } else { &x_peers[r - 1] };
939        // Verify arm: the pre-round snapshot on the rank's engine, BEFORE the batched
940        // call advances the resident state (the ckpt contract's per-rank twin).
941        let snap = if rows_exact {
942            Some(dev.clone_dtod(&states[r].ssm_state)?)
943        } else {
944            None
945        };
946        let mut rank_stash: Option<crate::kda::KdaRowsStash> = None;
947        let mut rank_scan_ns = 0u64;
948        let out = {
949            let RecurLayer {
950                conv_state,
951                ssm_state,
952                ssm_state_alt,
953            } = &mut states[r];
954            let out = crate::kda::kda_core_gated(
955                dev,
956                la,
957                xin,
958                t,
959                eps,
960                conv_state,
961                ssm_state,
962                ssm_state_alt,
963                arm,
964                if rows_exact {
965                    crate::kda::KdaStash::Rows(&mut rank_stash)
966                } else {
967                    crate::kda::KdaStash::None
968                },
969                scan_clock.as_deref_mut().map(|_| &mut rank_scan_ns),
970                None,
971                None,
972            )?;
973            std::mem::swap(ssm_state, ssm_state_alt);
974            out
975        };
976        if let Some(clock) = scan_clock.as_deref_mut() {
977            *clock += rank_scan_ns;
978        }
979        if rows_exact {
980            let snap = snap.expect("verify arm cloned the snapshot above");
981            let rank_stash = rank_stash
982                .ok_or("kda_core_gated returned without filling the requested rows stash")?;
983            captured[r] = Some((snap, rank_stash));
984        }
985        gated[r] = Some(out);
986    }
987    if let Some(stash_vec) = verify_stash {
988        stash_vec.clear();
989        for c in captured {
990            stash_vec.push(c.expect("every rank captured on the verify arm"));
991        }
992    }
993
994    // HOP 2 — gather the gated parts into the FULL [t, qkv] layout on EVERY rank
995    // (column-parallel wo needs the whole input on each rank). Token-major interleave: row
996    // tok is [rank0 ql | rank1 ql | ...]. `full == ranks * ql` by the shard map.
997    debug_assert_eq!(full, ranks * ql);
998    let gated_refs: Vec<&CudaSlice<f32>> = gated
999        .iter()
1000        .map(|g| g.as_ref().expect("filled above"))
1001        .collect();
1002    let fulls = crate::tp_transport::gather_parts(&hop, &gated_refs, t, ql)?;
1003
1004    // Per-rank column wo slices: each output element is one full-K dot by the SAME kernel
1005    // class the consumer's unsharded walk uses — no cross-rank arithmetic in this join.
1006    let mut ys = Vec::with_capacity(ranks);
1007    if rows_exact {
1008        ys.push(e.matmul_rows_exact(&la_root.wo, &fulls[0], t)?);
1009        for r in 1..ranks {
1010            ys.push(rt.peers[r - 1].matmul_rows_exact(&tp.peers[r - 1].wo, &fulls[r], t)?);
1011        }
1012    } else {
1013        ys.push(e.matmul(&la_root.wo, &fulls[0], t)?);
1014        for r in 1..ranks {
1015            ys.push(rt.peers[r - 1].matmul(&tp.peers[r - 1].wo, &fulls[r], t)?);
1016        }
1017    }
1018
1019    // HOP 3 — concat the column parts into the mixer output on ROOT.
1020    let y_refs: Vec<&CudaSlice<f32>> = ys.iter().collect();
1021    crate::tp_transport::concat_parts_on_root(&hop, &y_refs, t, hh)
1022}
1023
1024/// The KDA TP walk for one prime/decode call — [`kda_tp_core`] with no verify capture.
1025#[allow(clippy::too_many_arguments)] // mirrors the kda entry contract shape
1026pub(crate) fn kda_tp_cached(
1027    e: &Engine,
1028    la_root: &KdaAttnLayer,
1029    x: &CudaSlice<f32>,
1030    t: usize,
1031    eps: f32,
1032    cache: &mut Cache,
1033    il: usize,
1034    arm: ConvArm,
1035) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
1036    kda_tp_core(e, la_root, x, t, eps, cache, il, arm, None, None)
1037}
1038
1039/// Per-rank rollback material of ONE sharded-KDA verify round (lane/glm5-composition, the
1040/// spec x TP composition): index = rank; each entry is that rank's pre-round ssm snapshot
1041/// (cloned on the rank's engine BEFORE its batched call advanced the resident state) plus
1042/// the batched [`crate::kda::KdaRowsStash`] its `KdaStash::Rows` call filled. Rollback to
1043/// `keep` rows restores every rank through `kda_verify_rollback_rows_on` with the rank's
1044/// own engine/shard/plane tuple — the same two-plane contract as the unsharded stash,
1045/// per rank.
1046pub type Glm5TpKdaVerifyStash = Vec<(CudaSlice<f32>, crate::kda::KdaRowsStash)>;
1047
1048/// The sharded-KDA VERIFY walk (spec x TP composition) — [`kda_tp_core`] with the verify
1049/// capture armed: batched `KdaStash::Rows` per rank, ROWS-EXACT `wo` (the unsharded verify
1050/// walk's own routing), per-rank scan-ns accumulated into `scan_clock`. Returns the mixer
1051/// output plus the rank-indexed rollback stash the ckpt banks.
1052#[allow(clippy::too_many_arguments)] // mirrors the kda verify entry contract shape
1053pub(crate) fn kda_tp_verify_rows(
1054    e: &Engine,
1055    la_root: &KdaAttnLayer,
1056    x: &CudaSlice<f32>,
1057    t: usize,
1058    eps: f32,
1059    cache: &mut Cache,
1060    il: usize,
1061    scan_clock: Option<&mut u64>,
1062) -> Result<(CudaSlice<f32>, Glm5TpKdaVerifyStash), Box<dyn std::error::Error>> {
1063    let mut stash: Glm5TpKdaVerifyStash = Vec::new();
1064    let out = kda_tp_core(
1065        e,
1066        la_root,
1067        x,
1068        t,
1069        eps,
1070        cache,
1071        il,
1072        ConvArm::Prefill,
1073        Some(&mut stash),
1074        scan_clock,
1075    )?;
1076    Ok((out, stash))
1077}
1078
1079/// Roll every rank's sharded-KDA state back to "after row `keep-1`" from a spec x TP verify
1080/// round (the [`Glm5TpKdaVerifyStash`] contract). Full accept never calls this — the
1081/// resident per-rank states ARE the state after the last kept row.
1082pub(crate) fn kda_tp_verify_rollback(
1083    e: &Engine,
1084    la_root: &KdaAttnLayer,
1085    stash: &Glm5TpKdaVerifyStash,
1086    keep: usize,
1087    cache: &mut Cache,
1088    il: usize,
1089) -> Result<(), Box<dyn std::error::Error>> {
1090    let tp = la_root
1091        .tp
1092        .as_ref()
1093        .ok_or("kda_tp_verify_rollback called on an unsharded layer")?;
1094    let rt = &tp.rt;
1095    let ranks = rt.ranks();
1096    if stash.len() != ranks {
1097        return Err(format!(
1098            "glm5-tp verify rollback: stash carries {} ranks, the runtime has {ranks}",
1099            stash.len()
1100        )
1101        .into());
1102    }
1103    let states = cache.glm5_tp_recur[il]
1104        .as_mut()
1105        .ok_or_else(|| format!("glm5-tp verify rollback: layer {il} has no per-rank state"))?;
1106    for r in 0..ranks {
1107        let dev = if r == 0 { e } else { &rt.peers[r - 1] };
1108        let la = if r == 0 { la_root } else { &tp.peers[r - 1] };
1109        let (snap, rows) = &stash[r];
1110        crate::kda::kda_verify_rollback_rows_on(dev, la, snap, rows, keep, &mut states[r], il)?;
1111    }
1112    Ok(())
1113}
1114
1115// ------------------------------------------------------------------------------------------
1116// MLA sidecar
1117// ------------------------------------------------------------------------------------------
1118
1119/// The MLA TP sidecar: the peer ranks' head shards (with replicated `wq_a`/`wkv_a`/norms
1120/// and full indexer replicas) plus the runtime handle.
1121pub struct Glm5TpMla {
1122    pub rt: Arc<Glm5TpRt>,
1123    /// `peers[i]` is rank `i + 1`'s shard, resident on `rt.peers[i]`.
1124    pub peers: Vec<crate::hybrid::MlaAttnLayer>,
1125    /// Full head count of the unsharded layer.
1126    pub full_heads: usize,
1127    /// Full hidden width (`wo` out rows across all ranks).
1128    pub n_embd: usize,
1129}
1130
1131impl Glm5TpMla {
1132    pub fn ranks(&self) -> usize {
1133        self.peers.len() + 1
1134    }
1135}
1136
1137static MLA_MARKED: AtomicBool = AtomicBool::new(false);
1138
1139pub(crate) fn shard_mla_layer(
1140    e: &Engine,
1141    rt: &Arc<Glm5TpRt>,
1142    la: crate::hybrid::MlaAttnLayer,
1143) -> Result<crate::hybrid::MlaAttnLayer, Box<dyn std::error::Error>> {
1144    use crate::hybrid::{MlaAttnLayer, MlaIndexer};
1145    if la.tp.is_some() {
1146        return Err("shard_mla_layer: layer is already sharded".into());
1147    }
1148    let ranks = rt.ranks();
1149    let g = la.geom;
1150    let nh = g.n_head;
1151    if !nh.is_multiple_of(ranks) {
1152        return Err(format!("MLA heads {nh} do not shard across {ranks} ranks").into());
1153    }
1154    let hl = nh / ranks;
1155    let head_q = g.d_nope + g.d_rope; // per-head wq_b out rows
1156    let n_embd = la.wo.out_features();
1157    if !n_embd.is_multiple_of(ranks) {
1158        return Err(format!("MLA wo out {n_embd} does not split across ranks").into());
1159    }
1160    let hh = n_embd / ranks;
1161
1162    let mut shard_geom = g;
1163    shard_geom.n_head = hl;
1164
1165    let replicate_indexer =
1166        |dst: &Engine, ix: &MlaIndexer| -> Result<MlaIndexer, Box<dyn std::error::Error>> {
1167            Ok(MlaIndexer {
1168                wq_b: replicate(e, dst, &ix.wq_b)?,
1169                wk: replicate(e, dst, &ix.wk)?,
1170                k_norm_w: replicate(e, dst, &ix.k_norm_w)?,
1171                k_norm_b: replicate(e, dst, &ix.k_norm_b)?,
1172                weights_proj: replicate(e, dst, &ix.weights_proj)?,
1173                kpool_gate: replicate(e, dst, &ix.kpool_gate)?,
1174                kpool_ape: replicate(e, dst, &ix.kpool_ape)?,
1175                geom: ix.geom,
1176            })
1177        };
1178
1179    // Gate red arm: a broken shard map hands each rank the NEXT rank's wo out rows.
1180    let wo_rank = |r: usize| -> usize {
1181        match gate_red() {
1182            Ok(Some(GateRed::SwapWo)) => (r + 1) % ranks,
1183            _ => r,
1184        }
1185    };
1186
1187    let rank_shard = |dst: &Engine, r: usize| -> Result<MlaAttnLayer, Box<dyn std::error::Error>> {
1188        let wr = wo_rank(r);
1189        Ok(MlaAttnLayer {
1190            wq_a: replicate(e, dst, &la.wq_a)?,
1191            q_a_norm: replicate(e, dst, &la.q_a_norm)?,
1192            wq_b: shard_rows(e, dst, &la.wq_b, r * hl * head_q..(r + 1) * hl * head_q)?,
1193            wkv_a: replicate(e, dst, &la.wkv_a)?,
1194            kv_a_norm: replicate(e, dst, &la.kv_a_norm)?,
1195            // 3D per-head slabs: the head axis is outermost.
1196            wk_b: shard_rows(e, dst, &la.wk_b, r * hl..(r + 1) * hl)?,
1197            wv_b: shard_rows(e, dst, &la.wv_b, r * hl..(r + 1) * hl)?,
1198            wk_b16: None,
1199            wv_b16: None,
1200            // COLUMN-parallel wo: rank r owns OUT rows over the full N*V input.
1201            wo: shard_rows(e, dst, &la.wo, wr * hh..(wr + 1) * hh)?,
1202            geom: shard_geom,
1203            index: match &la.index {
1204                Some(ix) => Some(replicate_indexer(dst, ix)?),
1205                None => None,
1206            },
1207            tp: None,
1208            tp_shard: true,
1209        })
1210    };
1211
1212    let mut root = rank_shard(e, 0)?;
1213    let mut peers = Vec::with_capacity(ranks - 1);
1214    for r in 1..ranks {
1215        peers.push(rank_shard(&rt.peers[r - 1], r)?);
1216    }
1217    if !MLA_MARKED.swap(true, Ordering::Relaxed) {
1218        eprintln!(
1219            "[glm5-tp-mla] head shard armed: ranks={ranks} heads_per_rank={hl} kv_rank={} \
1220             latent=replicated indexer=replicated wo=column-over-gather transport={} \
1221             performance_claim=false",
1222            g.kv_rank,
1223            rt.transport.name(),
1224        );
1225    }
1226    root.tp = Some(Box::new(Glm5TpMla {
1227        rt: Arc::clone(rt),
1228        peers,
1229        full_heads: nh,
1230        n_embd,
1231    }));
1232    Ok(root)
1233}
1234
1235/// Ensure the PEER ranks' replicated latent planes for layer `il` exist, geometry-cloned
1236/// from the canonical (root) plane. The canonical plane IS the root replica — the root path
1237/// is unchanged. `cache_slot` holds one plane per peer rank (`[i]` = rank `i + 1`).
1238pub(crate) fn ensure_mla_peer_latent(
1239    rt: &Glm5TpRt,
1240    canonical: &LatentKvLayer,
1241    cache_slot: &mut Option<Vec<LatentKvLayer>>,
1242) -> Result<(), Box<dyn std::error::Error>> {
1243    if cache_slot.is_some() {
1244        return Ok(());
1245    }
1246    let mut planes = Vec::with_capacity(rt.peers.len());
1247    for dev in &rt.peers {
1248        let rows = dev.zeros(canonical.rows.len())?;
1249        // Fresh replica starts at len 0 like a fresh canonical plane; the walk appends to
1250        // every replica in the same calls, so the lengths stay in lock-step by construction.
1251        let len_d = dev.htod_i32(&[0])?;
1252        let index_rows = match &canonical.index_rows {
1253            Some(p) => Some(dev.zeros(p.len())?),
1254            None => None,
1255        };
1256        planes.push(LatentKvLayer {
1257            rows,
1258            width: canonical.width,
1259            index_width: canonical.index_width,
1260            len: 0,
1261            len_d,
1262            index_rows,
1263            index_ring_rows: canonical.index_ring_rows,
1264            index_pool_keys: None, // lazily allocated by the core, exactly like the canonical plane
1265            index_pools_ready: 0,
1266            index_pool: canonical.index_pool,
1267        });
1268    }
1269    *cache_slot = Some(planes);
1270    Ok(())
1271}
1272
1273// ------------------------------------------------------------------------------------------
1274// MoE EP sidecar
1275// ------------------------------------------------------------------------------------------
1276
1277/// One rank's expert slab: the rank's owned experts packed in ASCENDING expert-id order
1278/// for every projection, device-resident on that rank. For the even split the packing is
1279/// the contiguous slice — byte-for-byte the pre-map layout.
1280pub struct EpRankSlab {
1281    pub gate: CudaSlice<u8>,
1282    pub up: CudaSlice<u8>,
1283    pub down: CudaSlice<u8>,
1284    pub n_experts: usize,
1285}
1286
1287/// The MoE EP sidecar on `MoeWeights`: per-rank expert slabs, the placement tables,
1288/// and the runtime handle. Router, shared expert, macros and all host metadata stay on
1289/// the unchanged `MoeWeights`.
1290///
1291/// PLACEMENT INDEPENDENCE (the contract the gate's skewed-map arm proves): `owner_of`
1292/// only selects WHICH rank runs the identical per-expert program over identical
1293/// host-canonical input bytes; `local_of` indexes the same expert bytes wherever they
1294/// were packed; the combine stays slot-ordered on root. The map moves bytes, never
1295/// changes arithmetic.
1296pub struct Glm5EpExps {
1297    pub rt: Arc<Glm5TpRt>,
1298    /// `slabs[r]` is rank r's expert slab (`[0]` = root's, on the model's engine).
1299    pub slabs: Vec<EpRankSlab>,
1300    /// `owner_of[expert]` = owning rank (0 = root).
1301    pub owner_of: Vec<u8>,
1302    /// `local_of[expert]` = slot inside the owner's slab (ascending-id packing order).
1303    pub local_of: Vec<u32>,
1304    /// Per-rank grouped-dispatch pointer tables, `[rank]`, each the `DevExps::ptr_row`
1305    /// shape ([3 * n_expert] u64 device pointers: gate | up | down planes, indexed by GLOBAL
1306    /// expert id, resident on the owning rank's device). Owned experts point at
1307    /// `slab_base + local * stride`; non-owned entries are 0 and never dereferenced — the EP
1308    /// grouped-prime CSR is built per rank from `owner_of`, so a foreign id cannot reach the
1309    /// wrong rank's table. Built AFTER the gate-red slab mutations, from the FINAL slab
1310    /// buffers and the FINAL `local_of`, so `swap-ep-gateup` and `corrupt-ep-map` bite the
1311    /// grouped walk exactly as they bite the sequential one.
1312    pub ptr_rows: Vec<CudaSlice<u64>>,
1313}
1314
1315impl Glm5EpExps {
1316    /// Owner rank of `expert` under the armed placement (even split when no map).
1317    pub fn owner(&self, expert: usize) -> usize {
1318        self.owner_of[expert] as usize
1319    }
1320
1321    pub fn ranks(&self) -> usize {
1322        self.slabs.len()
1323    }
1324}
1325
1326static EP_MARKED: AtomicBool = AtomicBool::new(false);
1327
1328/// Engagement counter: PEER-owned expert slots dispatched by the EP walk (counted before
1329/// any gate-red skip, so a red arm can still assert a peer was ROUTED). Gates read it to
1330/// prove the peer ranks contribute real expert work — a token stream that never routes a
1331/// peer-owned expert makes every EP identity arm vacuous.
1332pub static GLM5_EP_PEER_SLOT_DISPATCHES: AtomicU64 = AtomicU64::new(0);
1333
1334pub fn glm5_ep_peer_slot_dispatches() -> u64 {
1335    GLM5_EP_PEER_SLOT_DISPATCHES.load(Ordering::Relaxed)
1336}
1337
1338// ---- EP dispatch-diet engagement counters (lane/glm5-ep-diet, 2026-08-31) ----------------
1339// The box A/B greps announces and reads these deltas; the rig gate asserts them non-vacuous
1340// on the ON arms and FLAT on the pinned-`=0` arms.
1341
1342/// Layer-calls that took the dieted EP walk (`MEMRA_GLM5_EP_DIET`) instead of the v1
1343/// per-slot host-canonical walk.
1344pub static GLM5_EP_DIET_DISPATCHES: AtomicU64 = AtomicU64::new(0);
1345
1346/// Snapshot of [`GLM5_EP_DIET_DISPATCHES`] — gates take a before/after delta.
1347pub fn glm5_ep_diet_dispatches() -> u64 {
1348    GLM5_EP_DIET_DISPATCHES.load(Ordering::Relaxed)
1349}
1350
1351/// Bulk peer-row block returns performed by the dieted walk (one per (layer-call, peer
1352/// rank) that routed at least one slot owned by that rank; each replaces that call's ENTIRE
1353/// per-slot return dribble for that rank).
1354pub static GLM5_EP_DIET_BULK_RETURNS: AtomicU64 = AtomicU64::new(0);
1355
1356/// Snapshot of [`GLM5_EP_DIET_BULK_RETURNS`].
1357pub fn glm5_ep_diet_bulk_returns() -> u64 {
1358    GLM5_EP_DIET_BULK_RETURNS.load(Ordering::Relaxed)
1359}
1360
1361/// Per-slot synchronous peer round-trips (one peer DtoH + one root pageable HtoD each, the
1362/// v1 walk's dominant hop class) that the dieted walk folded into its bulk returns — one
1363/// count per peer-owned slot bulked.
1364pub static GLM5_EP_DIET_PEER_ROUNDTRIPS_AVOIDED: AtomicU64 = AtomicU64::new(0);
1365
1366/// Snapshot of [`GLM5_EP_DIET_PEER_ROUNDTRIPS_AVOIDED`].
1367pub fn glm5_ep_diet_peer_roundtrips_avoided() -> u64 {
1368    GLM5_EP_DIET_PEER_ROUNDTRIPS_AVOIDED.load(Ordering::Relaxed)
1369}
1370
1371/// Per-token peer z uploads the dieted walk avoided: `t-1` per (fanned layer-call, peer
1372/// rank) (one bulk [t, n_embd] upload replaces t per-token uploads) plus `t` per (layer-call,
1373/// rank) whose routing never touched that rank's experts (the fan-out is skipped entirely —
1374/// the placement-map multiplier: single-rank layer-calls move ZERO activation bytes off
1375/// root).
1376pub static GLM5_EP_DIET_FANOUT_UPLOADS_AVOIDED: AtomicU64 = AtomicU64::new(0);
1377
1378/// Snapshot of [`GLM5_EP_DIET_FANOUT_UPLOADS_AVOIDED`].
1379pub fn glm5_ep_diet_fanout_uploads_avoided() -> u64 {
1380    GLM5_EP_DIET_FANOUT_UPLOADS_AVOIDED.load(Ordering::Relaxed)
1381}
1382
1383/// Layer-calls that took the per-rank grouped-GEMM EP prime (`MEMRA_GLM5_EP_GROUPED_PRIME`).
1384/// Stays 0 whenever the plain grouped-prefill conjuncts do not hold (e.g. non-f16g-eligible
1385/// expert qtypes — the rig fixture's Q8_0 bank always falls closed to the sequential walk).
1386pub static GLM5_EP_GROUPED_PRIME_DISPATCHES: AtomicU64 = AtomicU64::new(0);
1387
1388/// Snapshot of [`GLM5_EP_GROUPED_PRIME_DISPATCHES`].
1389pub fn glm5_ep_grouped_prime_dispatches() -> u64 {
1390    GLM5_EP_GROUPED_PRIME_DISPATCHES.load(Ordering::Relaxed)
1391}
1392
1393/// Arm one MoE layer for EP. `placement` is the layer's validated map row
1394/// (`owners[expert] = rank`) when `MEMRA_EP_MAP` (or its glm5 alias) is armed; `None` = the
1395/// even split, whose ascending-id packing is byte-for-byte the pre-map contiguous slices.
1396pub(crate) fn arm_moe_ep(
1397    e: &Engine,
1398    rt: &Arc<Glm5TpRt>,
1399    m: &mut crate::hybrid::MoeWeights,
1400    placement: Option<&[u8]>,
1401) -> Result<(), Box<dyn std::error::Error>> {
1402    if m.glm5_ep.is_some() {
1403        return Err("arm_moe_ep: layer is already EP-armed".into());
1404    }
1405    let ranks = rt.ranks();
1406    let n_expert = m.gate_exps.n_expert;
1407    if !n_expert.is_multiple_of(ranks) {
1408        return Err(format!(
1409            "glm5-tp EP: {n_expert} experts do not partition across {ranks} ranks"
1410        )
1411        .into());
1412    }
1413    if m.gate_exps.layouts.is_some() || m.up_exps.layouts.is_some() || m.down_exps.layouts.is_some()
1414    {
1415        return Err("glm5-tp EP: per-expert mixed layouts are unwired for EP shards".into());
1416    }
1417    let owner_of: Vec<u8> = match placement {
1418        Some(owners) => {
1419            // The preflight validated the map; re-assert the two structural laws at the
1420            // consumption site so a wiring bug can never hand a foreign row to a layer.
1421            if owners.len() != n_expert {
1422                return Err(format!(
1423                    "glm5-tp EP: placement row carries {} owners for a {n_expert}-expert bank",
1424                    owners.len()
1425                )
1426                .into());
1427            }
1428            if owners.iter().any(|&r| (r as usize) >= ranks) {
1429                return Err(
1430                    format!("glm5-tp EP: placement row names a rank outside TP-{ranks}").into(),
1431                );
1432            }
1433            owners.to_vec()
1434        }
1435        None => crate::ep_map::EpMap::even_owners(n_expert, ranks),
1436    };
1437    // Ascending-id packing per rank + the local-slot table.
1438    let mut local_of = vec![0u32; n_expert];
1439    let mut owned: Vec<Vec<usize>> = vec![Vec::new(); ranks];
1440    for ex in 0..n_expert {
1441        let r = owner_of[ex] as usize;
1442        local_of[ex] = owned[r].len() as u32;
1443        owned[r].push(ex);
1444    }
1445    if owned.iter().any(|o| o.is_empty()) {
1446        return Err("glm5-tp EP: placement leaves a rank with zero experts (refused)".into());
1447    }
1448    let slab =
1449        |dev: &Engine, experts: &[usize]| -> Result<EpRankSlab, Box<dyn std::error::Error>> {
1450            // Tail-slack pads mirror the resident-slab builder (`build_dev_exps`): 8 B
1451            // alignment slack on gate/up and 144 B on down — the ragged-k grouped GEMM
1452            // walks whole superblocks and may overread past the LAST row (harmless bytes,
1453            // the zero-padded k-range multiplies them away; the slack only prevents the
1454            // OOB fault). Bytes at every in-slab offset are unchanged, so the sequential
1455            // per-slot views read exactly what they read before.
1456            let cut = |h: &crate::model::HostExps,
1457                       pad: usize|
1458             -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
1459                let stride = h.expert_stride;
1460                let bytes = h.bytes.as_bytes();
1461                // Contiguous ascending run (the even split, and any contiguous map row):
1462                // one direct upload of the existing byte range — no host copy.
1463                let contiguous = experts.windows(2).all(|w| w[1] == w[0] + 1);
1464                if contiguous {
1465                    let a = experts[0] * stride;
1466                    let b = (experts[experts.len() - 1] + 1) * stride;
1467                    return dev.htod_bytes_padded(&bytes[a..b], pad);
1468                }
1469                // General map row: pack the owned experts ascending into one staging
1470                // buffer (load-time only; per-layer transient host = one rank's slab).
1471                let mut staged = Vec::with_capacity(experts.len() * stride);
1472                for &ex in experts {
1473                    staged.extend_from_slice(&bytes[ex * stride..(ex + 1) * stride]);
1474                }
1475                dev.htod_bytes_padded(&staged, pad)
1476            };
1477            Ok(EpRankSlab {
1478                gate: cut(&m.gate_exps, 8)?,
1479                up: cut(&m.up_exps, 8)?,
1480                down: cut(&m.down_exps, 144)?,
1481                n_experts: experts.len(),
1482            })
1483        };
1484    let mut slabs = Vec::with_capacity(ranks);
1485    for r in 0..ranks {
1486        slabs.push(slab(rank_engine(e, rt, r), &owned[r])?);
1487    }
1488    // Gate red arm: wrong expert weights on the root rank (gate/up swapped).
1489    if matches!(gate_red(), Ok(Some(GateRed::SwapEpGateUp))) {
1490        let root = &mut slabs[0];
1491        std::mem::swap(&mut root.gate, &mut root.up);
1492    }
1493    // Gate red arm: a corrupted map row — the local-slot table for rank 0 is reversed
1494    // AFTER the slabs were packed, so the owner table and the slab bytes disagree.
1495    if matches!(gate_red(), Ok(Some(GateRed::CorruptEpMap))) {
1496        let n0 = owned[0].len() as u32;
1497        for &ex in &owned[0] {
1498            local_of[ex] = n0 - 1 - local_of[ex];
1499        }
1500    }
1501    // Per-rank grouped-dispatch pointer tables (lane/glm5-ep-diet): the `DevExps::ptr_row`
1502    // shape over each rank's OWN slab, built from the FINAL slab buffers and the FINAL
1503    // `local_of` so both gate reds above flow into the grouped walk too. ~3*n_expert*8 B per
1504    // rank per layer — negligible next to the slabs they index.
1505    let ptr_table = |dev: &Engine,
1506                     slab: &EpRankSlab,
1507                     rank: u8|
1508     -> Result<CudaSlice<u64>, Box<dyn std::error::Error>> {
1509        use cudarc::driver::DevicePtr;
1510        let (pg, pu, pd) = {
1511            let s = dev.stream();
1512            let (pg, _g0) = slab.gate.device_ptr(&s);
1513            let (pu, _g1) = slab.up.device_ptr(&s);
1514            let (pd, _g2) = slab.down.device_ptr(&s);
1515            (pg, pu, pd)
1516        };
1517        let mut host = vec![0u64; 3 * n_expert];
1518        for ex in 0..n_expert {
1519            if owner_of[ex] != rank {
1520                continue; // non-owned: 0, never dereferenced (rank CSRs filter by owner)
1521            }
1522            let local = local_of[ex] as usize;
1523            host[ex] = pg + (local * m.gate_exps.expert_stride) as u64;
1524            host[n_expert + ex] = pu + (local * m.up_exps.expert_stride) as u64;
1525            host[2 * n_expert + ex] = pd + (local * m.down_exps.expert_stride) as u64;
1526        }
1527        dev.htod_u64(&host)
1528    };
1529    let mut ptr_rows = Vec::with_capacity(ranks);
1530    for r in 0..ranks {
1531        ptr_rows.push(ptr_table(rank_engine(e, rt, r), &slabs[r], r as u8)?);
1532    }
1533    if !EP_MARKED.swap(true, Ordering::Relaxed) {
1534        eprintln!(
1535            "[glm5-tp-ep] expert-parallel armed: experts_per_rank={:?} ownership={} \
1536             router=root combine=slot-ordered-fmaf transport={} \
1537             performance_claim=false",
1538            owned.iter().map(Vec::len).collect::<Vec<_>>(),
1539            // ORDER MATTERS and it was wrong once: the ownership string must land on
1540            // `ownership={}` and the transport on `transport={}`. The first gate run's
1541            // receipt-extract printed `[glm5-tp-ep] transport=even-split`, which is how the
1542            // swap was caught — a receipt line is only worth what its argument order is.
1543            if placement.is_some() {
1544                "measured-map"
1545            } else {
1546                "even-split"
1547            },
1548            rt.transport.name(),
1549        );
1550    }
1551    // The root-resident full slab (if the loader built one) is superseded by the EP slices;
1552    // dropping it returns its VRAM and removes the arm that would silently bypass EP.
1553    m.dev_exps = None;
1554    m.glm5_ep = Some(Glm5EpExps {
1555        rt: Arc::clone(rt),
1556        slabs,
1557        owner_of,
1558        local_of,
1559        ptr_rows,
1560    });
1561    Ok(())
1562}
1563
1564#[cfg(test)]
1565mod tests {
1566    use super::*;
1567
1568    #[test]
1569    fn parse_is_literal_and_fail_closed() {
1570        // Off spellings.
1571        assert!(parse_glm5_tp_layer_specs(None, 45).unwrap().is_empty());
1572        assert!(parse_glm5_tp_layer_specs(Some(""), 45).unwrap().is_empty());
1573        assert!(parse_glm5_tp_layer_specs(Some("0"), 45).unwrap().is_empty());
1574        // The full-model shorthand expands against the CALLER's trunk, not a constant.
1575        let all = parse_glm5_tp_layer_specs(Some("all@0,1"), 45).unwrap();
1576        assert_eq!(all.len(), 45);
1577        assert_eq!(all[0].devices, vec![0, 1]);
1578        let all4 = parse_glm5_tp_layer_specs(Some("all@0,1"), 4).unwrap();
1579        assert_eq!(all4.len(), 4);
1580        // The TP-4 device list parses through the same grammar.
1581        let quad = parse_glm5_tp_layer_specs(Some("all@0,1,2,3"), 45).unwrap();
1582        assert_eq!(quad.len(), 45);
1583        assert_eq!(quad[0].devices, vec![0, 1, 2, 3]);
1584        // Explicit ranges.
1585        let r = parse_glm5_tp_layer_specs(Some("0-2@0,1;4@0,1"), 45).unwrap();
1586        assert_eq!(
1587            r.iter().map(|s| s.layer).collect::<Vec<_>>(),
1588            vec![0, 1, 2, 4]
1589        );
1590        // Refusals: duplicate devices, duplicate layers, garbage.
1591        assert!(parse_glm5_tp_layer_specs(Some("0@0,0"), 45).is_err());
1592        assert!(parse_glm5_tp_layer_specs(Some("0@0,1;0@0,1"), 45).is_err());
1593        assert!(parse_glm5_tp_layer_specs(Some("banana"), 45).is_err());
1594    }
1595
1596    fn fixture_view() -> Glm5TpModelView {
1597        Glm5TpModelView {
1598            trunk_layers: 4,
1599            layer_class: vec![
1600                Glm5LayerClass::Kda,
1601                Glm5LayerClass::Mla,
1602                Glm5LayerClass::Kda,
1603                Glm5LayerClass::Mla,
1604            ],
1605            layer_is_moe: vec![false, true, true, true],
1606            kda_heads: 4,
1607            kda_head_dim: 128,
1608            mla_heads: 4,
1609            n_routed_experts: 4,
1610            top_k: 2,
1611        }
1612    }
1613
1614    /// Structural preflight refusals, exercised WITHOUT constructing any CUDA state: every
1615    /// geometry law here fires before `prepare_glm5_tp_load` reaches the runtime build.
1616    /// (The armed happy path needs an Engine and lives in the gate binary.)
1617    #[test]
1618    fn preflight_geometry_laws_are_dimension_derived() {
1619        // The checks below mirror prepare_glm5_tp_load's law order on the view alone, at
1620        // BOTH qualified rank counts.
1621        let v = fixture_view();
1622        for ranks in GLM5_TP_ALLOWED_RANKS {
1623            assert_eq!(v.kda_heads % ranks, 0);
1624            assert_eq!(v.mla_heads % ranks, 0);
1625            assert_eq!(v.n_routed_experts % ranks, 0);
1626        }
1627        let odd = Glm5TpModelView {
1628            kda_heads: 3,
1629            ..fixture_view()
1630        };
1631        assert_ne!(odd.kda_heads % 2, 0);
1632        let bad_dim = Glm5TpModelView {
1633            kda_head_dim: 64,
1634            ..fixture_view()
1635        };
1636        assert_ne!(bad_dim.kda_head_dim, crate::kda::KDA_HEAD_DIM);
1637        let odd_experts = Glm5TpModelView {
1638            n_routed_experts: 5,
1639            ..fixture_view()
1640        };
1641        assert_ne!(odd_experts.n_routed_experts % 2, 0);
1642        // TP-3 stays outside the qualified envelope (head padding not built).
1643        assert!(!GLM5_TP_ALLOWED_RANKS.contains(&3));
1644    }
1645
1646    #[test]
1647    fn armed_check_counts_parse_errors_as_armed() {
1648        // glm5_tp_armed is a cheap co-refusal predicate: any nonempty non-"0" value counts,
1649        // including a spec the parser would refuse — the co-armed program must not race the
1650        // loader's own refusal.
1651        // (Env-mutation-free: the predicate's contract is pure string classification.)
1652        for (v, armed) in [
1653            ("", false),
1654            ("0", false),
1655            ("all@0,1", true),
1656            ("all@0,1,2,3", true),
1657            ("junk", true),
1658        ] {
1659            let is_armed = !v.is_empty() && v != "0";
1660            assert_eq!(is_armed, armed);
1661        }
1662    }
1663
1664    #[test]
1665    fn every_refused_door_composition_bites_by_name() {
1666        // The merge-forward composition matrix (2026-08-31): each decode-diet door armed
1667        // alone must refuse, naming BOTH flags — a silent pick is the failure mode this
1668        // guards. (Env-mutation-free: the law is pure over the armed predicate; the live
1669        // env read is one closure at the prepare_glm5_tp_load call site, and the tp-gate
1670        // red receipt exercises it end to end.)
1671        for (flag, _) in GLM5_TP_REFUSED_DOOR_FLAGS {
1672            let err = refuse_glm5_tp_door_composition(|f| f == flag)
1673                .expect_err("an armed door must refuse");
1674            assert!(err.contains("MEMRA_GLM5_TP"), "{err}");
1675            assert!(err.contains(flag), "{err}");
1676            assert!(err.contains("unproven composition"), "{err}");
1677        }
1678        // All doors cold = no refusal.
1679        refuse_glm5_tp_door_composition(|_| false).expect("cold doors must pass");
1680        // The verify-batch flag is DELIBERATELY not in the matrix (the gated spec x TP
1681        // composition owns that pair — its admission REQUIRES the batched walk); arming
1682        // it alone must not trip this law.
1683        refuse_glm5_tp_door_composition(|f| f == "MEMRA_GLM5_VERIFY_BATCH")
1684            .expect("verify-batch is refused via the spec co-refusal, not here");
1685    }
1686}