metrale_model_arch/glm5next_kda/
tp_bind.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-25: Applies a `super::tp::KdaTpPlan`: slices each KDA tensor to this rank before the
4//! binder sees it.
5//!
6//! Owner: model-arch (GLM-5.3-Flash KDA).
7//! Invariants:
8//! - [`KdaShardedSource`] presents each tensor whose name maps to a plan entry (`plan_name`) as
9//!   this rank's bytes with a local shape, and passes every other name through unchanged.
10//! - [`shard_bytes`] refuses a tensor whose size disagrees with the plan's full shape.
11//!
12//! The slicing happens upstream of `super::binding::bind_kda_weights`, which validates the local
13//! shapes against the local config, so TP=1 and TP=2 run the same binder code.
14//!
15//! Row slicing (`HeadRows`, `ChannelRows`) is one contiguous byte range. Column slicing
16//! (`ChannelCols`, only `o_proj`) is a strided gather: every row keeps its own middle slice. A
17//! row slice of `o_proj` has the same byte count at `tp_size = 2` and the wrong contents, which is
18//! why the tests compare values.
19
20use anyhow::{Result, bail};
21
22use super::binding::{KdaTensorSource, RawTensor};
23use super::tp::{KdaShard, KdaTensorPlan, KdaTpPlan};
24
25/// 2026-09-25: Plan name for a checkpoint tensor name: `self_attn.q_proj.weight` -> `q_proj`.
26/// `A_log` and `dt_bias` have no `.weight` suffix, so the suffix is stripped only when present.
27fn plan_name(checkpoint_name: &str) -> &str {
28    let n = checkpoint_name
29        .strip_prefix("self_attn.")
30        .unwrap_or(checkpoint_name);
31    n.strip_suffix(".weight").unwrap_or(n)
32}
33
34/// 2026-09-25: This rank's bytes for one tensor, given its plan entry. `full` is the whole on-disk
35/// tensor; a size mismatch is an error. Returns a new buffer because a column slice is not
36/// contiguous.
37pub fn shard_bytes(plan: &KdaTensorPlan, full: &[u8]) -> Result<Vec<u8>> {
38    let e = plan.elem_bytes;
39    let expect = plan.full_rows * plan.full_row_elems * e;
40    if full.len() != expect {
41        bail!(
42            "{}: {} B on disk, the plan's full shape [{}, {}] x {e} B implies {expect} B",
43            plan.name,
44            full.len(),
45            plan.full_rows,
46            plan.full_row_elems
47        );
48    }
49    Ok(match plan.kind {
50        KdaShard::Replicated => full.to_vec(),
51        // 2026-09-25: Contiguous: this rank's rows are adjacent on disk.
52        KdaShard::HeadRows | KdaShard::ChannelRows => {
53            let row = plan.full_row_elems * e;
54            let start = plan.src_row_offset * row;
55            full[start..start + plan.local_rows * row].to_vec()
56        }
57        // 2026-09-25: Strided: every row keeps its `[src_col_offset, +local_row_elems)` slice.
58        KdaShard::ChannelCols => {
59            let row = plan.full_row_elems * e;
60            let lo = plan.src_col_offset * e;
61            let width = plan.local_row_elems * e;
62            let mut out = Vec::with_capacity(plan.local_rows * width);
63            for r in 0..plan.local_rows {
64                let base = r * row + lo;
65                out.extend_from_slice(&full[base..base + width]);
66            }
67            out
68        }
69    })
70}
71
72/// 2026-09-25: The local shape the binder should see, keeping the on-disk rank rather than the
73/// plan's flat `[rows, row_elems]` view: the binder validates the conv tensors as rank 3.
74fn local_shape(plan: &KdaTensorPlan, disk_shape: &[usize]) -> Vec<usize> {
75    let mut s = disk_shape.to_vec();
76    match plan.kind {
77        KdaShard::Replicated => {}
78        KdaShard::HeadRows | KdaShard::ChannelRows => {
79            if let Some(first) = s.first_mut() {
80                *first = plan.local_rows;
81            }
82        }
83        KdaShard::ChannelCols => {
84            if let Some(last) = s.last_mut() {
85                *last = plan.local_row_elems;
86            }
87        }
88    }
89    s
90}
91
92/// 2026-09-25: A [`KdaTensorSource`] that yields this rank's slice of every planned KDA tensor.
93/// The loader wraps each layer's source in it and passes it to `bind_kda_weights`.
94pub struct KdaShardedSource<'a> {
95    inner: &'a dyn KdaTensorSource,
96    plan: &'a KdaTpPlan,
97    /// 2026-09-25: Sliced bytes, built in `new`: `KdaTensorSource::get` returns a borrow, so the
98    /// buffers must outlive the call.
99    sliced: std::collections::BTreeMap<String, (super::binding::KdaDtype, Vec<usize>, Vec<u8>)>,
100}
101
102impl<'a> KdaShardedSource<'a> {
103    pub fn new(inner: &'a dyn KdaTensorSource, plan: &'a KdaTpPlan) -> Result<Self> {
104        let mut sliced = std::collections::BTreeMap::new();
105        for name in inner.names() {
106            // 2026-09-25: Names without a plan entry are not sliced; `get` passes them through.
107            let Some(p) = plan.get(plan_name(&name)) else {
108                continue;
109            };
110            let Some(t) = inner.get(&name) else { continue };
111            sliced.insert(
112                name.clone(),
113                (t.dtype, local_shape(p, &t.shape), shard_bytes(p, t.bytes)?),
114            );
115        }
116        Ok(Self {
117            inner,
118            plan,
119            sliced,
120        })
121    }
122
123    pub fn plan(&self) -> &KdaTpPlan {
124        self.plan
125    }
126}
127
128impl KdaTensorSource for KdaShardedSource<'_> {
129    fn get(&self, name: &str) -> Option<RawTensor<'_>> {
130        match self.sliced.get(name) {
131            Some((dtype, shape, bytes)) => Some(RawTensor {
132                dtype: *dtype,
133                shape: shape.clone(),
134                bytes,
135            }),
136            // 2026-09-25: Not a planned KDA tensor: pass it through, so the binder's refusal of
137            // an unrecognised `self_attn` tensor still fires.
138            None => self.inner.get(name),
139        }
140    }
141
142    fn names(&self) -> Vec<String> {
143        self.inner.names()
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::super::binding::KdaDtype;
150    use super::*;
151
152    /// 2026-09-25: The `glm-5.3-flash` KDA geometry at TP=2 (hidden 4096, 64 heads of 128, conv
153    /// kernel 4, gate rank 128).
154    fn plan(rank: usize) -> KdaTpPlan {
155        KdaTpPlan::new(rank, 2, 4096, 128, 64, 4, 128).unwrap()
156    }
157
158    #[test]
159    fn plan_names_strip_the_prefix_but_not_a_missing_suffix() {
160        assert_eq!(plan_name("self_attn.q_proj.weight"), "q_proj");
161        assert_eq!(plan_name("self_attn.q_conv1d.weight"), "q_conv1d");
162        assert_eq!(plan_name("self_attn.A_log"), "A_log");
163        assert_eq!(plan_name("self_attn.dt_bias"), "dt_bias");
164        // 2026-09-25: Every plan entry must be reachable from some checkpoint name.
165        for t in &plan(0).tensors {
166            let candidates = [
167                format!("self_attn.{}.weight", t.name),
168                format!("self_attn.{}", t.name),
169            ];
170            assert!(
171                candidates.iter().any(|c| plan_name(c) == t.name),
172                "plan entry {} is unreachable from a checkpoint name",
173                t.name
174            );
175        }
176    }
177
178    /// 2026-09-25: Row slicing is contiguous; the two ranks partition the tensor exactly.
179    #[test]
180    fn channel_rows_partition_the_tensor() {
181        // 2026-09-25: A small geometry the plan accepts: it requires
182        // `2 * local_heads * head_dim % 256 == 0`, which heads = 4, head_dim = 64 at tp = 2 meets.
183        let p = KdaTpPlan::new(0, 2, 4, 64, 4, 2, 4).unwrap();
184        let q = p.get("q_proj").unwrap().clone();
185        assert_eq!(q.kind, KdaShard::ChannelRows);
186        // 2026-09-25: `[full_ch = 256, hidden = 4]` BF16, each byte equal to its row index.
187        let full: Vec<u8> = (0..=255u8).flat_map(|r| [r; 8]).collect();
188
189        let r0 = shard_bytes(&q, &full).unwrap();
190        let mut q1 = q.clone();
191        q1.src_row_offset = q.local_rows;
192        let r1 = shard_bytes(&q1, &full).unwrap();
193
194        assert_eq!(
195            r0.len() + r1.len(),
196            full.len(),
197            "the two ranks partition it"
198        );
199        let mut rejoined = r0.clone();
200        rejoined.extend_from_slice(&r1);
201        assert_eq!(rejoined, full, "and rejoin to the original, in order");
202        assert!(r0.iter().all(|b| *b < 128), "rank 0 keeps the low rows");
203        assert!(r1.iter().all(|b| *b >= 128), "rank 1 keeps the high rows");
204    }
205
206    /// 2026-09-25: `o_proj` is column-sliced. A row slice of the same tensor has the same length
207    /// and different contents, so only a value check separates them.
208    #[test]
209    fn o_proj_is_column_sliced_not_row_sliced() {
210        let p = KdaTpPlan::new(1, 2, 4, 64, 4, 2, 4).unwrap();
211        let o = p.get("o_proj").unwrap();
212        assert_eq!(o.kind, KdaShard::ChannelCols);
213        assert_eq!(o.full_rows, 4, "hidden");
214        assert_eq!(o.full_row_elems, 256, "heads * head_dim");
215        assert_eq!(o.local_rows, 4, "every row is kept");
216        assert_eq!(o.local_row_elems, 128, "half the input dim");
217
218        // 2026-09-25: `[4, 256]` BF16; each element's low byte is its column index.
219        let full: Vec<u8> = (0..4)
220            .flat_map(|_| (0..=255u8).flat_map(|c| [c, 0]))
221            .collect();
222        let got = shard_bytes(o, &full).unwrap();
223
224        // 2026-09-25: Rank 1 keeps columns 128..256 of every row.
225        let want: Vec<u8> = (0..4)
226            .flat_map(|_| (128..=255u8).flat_map(|c| [c, 0]))
227            .collect();
228        assert_eq!(got, want);
229
230        // 2026-09-25: A row slice of the same byte count is rows 2..4: same length, different
231        // bytes.
232        let row_sliced = &full[full.len() / 2..];
233        assert_eq!(row_sliced.len(), got.len());
234        assert_ne!(row_sliced, got.as_slice());
235    }
236
237    /// 2026-09-25: `o_norm` (`[head_dim]`) and the `_a` down-projections are copied unchanged to
238    /// every rank.
239    #[test]
240    fn o_norm_and_the_low_rank_down_projections_are_replicated() {
241        let p = plan(1);
242        for n in ["o_norm", "f_a_proj", "g_a_proj"] {
243            let t = p.get(n).unwrap();
244            assert_eq!(t.kind, KdaShard::Replicated, "{n}");
245            let full = vec![0xABu8; t.full_bytes()];
246            assert_eq!(shard_bytes(t, &full).unwrap(), full, "{n} must be verbatim");
247        }
248    }
249
250    /// 2026-09-25: `A_log` is per head (`[64]`) and `dt_bias` per channel (`[8192]`), so rank 1's
251    /// offsets differ by a factor of `head_dim`.
252    #[test]
253    fn a_log_and_dt_bias_shard_at_different_granularity() {
254        let p = plan(1);
255        let a = p.get("A_log").unwrap();
256        let d = p.get("dt_bias").unwrap();
257        assert_eq!((a.full_rows, a.local_rows), (64, 32), "A_log is per-head");
258        assert_eq!(
259            (d.full_rows, d.local_rows),
260            (8192, 4096),
261            "dt_bias is per-channel"
262        );
263        assert_eq!(d.src_row_offset, a.src_row_offset * 128);
264    }
265
266    /// 2026-09-25: At `tp_size = 1` every tensor's shard is the whole tensor, and no all-reduce is
267    /// needed.
268    #[test]
269    fn tp1_is_the_identity() {
270        let p = KdaTpPlan::new(0, 1, 4096, 128, 64, 4, 128).unwrap();
271        for t in &p.tensors {
272            assert_eq!(t.local_bytes(), t.full_bytes(), "{}", t.name);
273            let full = vec![0x5Au8; t.full_bytes()];
274            assert_eq!(shard_bytes(t, &full).unwrap(), full, "{}", t.name);
275        }
276        assert!(!p.needs_output_all_reduce());
277    }
278
279    /// 2026-09-25: A tensor whose on-disk size disagrees with the plan is refused, not truncated.
280    #[test]
281    fn a_size_mismatch_is_refused() {
282        let p = plan(0);
283        let q = p.get("q_proj").unwrap();
284        let err = shard_bytes(q, &vec![0u8; q.full_bytes() - 2]).unwrap_err();
285        assert!(err.to_string().contains("on disk"), "{err}");
286    }
287
288    /// 2026-09-25: The local shape keeps the on-disk rank (rank 3 for the conv tensors).
289    #[test]
290    fn local_shape_preserves_the_on_disk_rank() {
291        let p = plan(1);
292        let c = p.get("q_conv1d").unwrap();
293        assert_eq!(local_shape(c, &[8192, 1, 4]), vec![4096, 1, 4]);
294        let o = p.get("o_proj").unwrap();
295        assert_eq!(local_shape(o, &[4096, 8192]), vec![4096, 4096]);
296        let n = p.get("o_norm").unwrap();
297        assert_eq!(local_shape(n, &[128]), vec![128]);
298    }
299
300    /// 2026-09-25: Through the adapter, a planned tensor is this rank's slice with a local shape,
301    /// and an unplanned name passes through unchanged.
302    #[test]
303    fn the_adapter_presents_local_slices_and_passes_the_rest_through() {
304        struct Src(Vec<(String, KdaDtype, Vec<usize>, Vec<u8>)>);
305        impl KdaTensorSource for Src {
306            fn get(&self, name: &str) -> Option<RawTensor<'_>> {
307                self.0
308                    .iter()
309                    .find(|(n, ..)| n == name)
310                    .map(|(_, d, s, b)| RawTensor {
311                        dtype: *d,
312                        shape: s.clone(),
313                        bytes: b,
314                    })
315            }
316            fn names(&self) -> Vec<String> {
317                self.0.iter().map(|(n, ..)| n.clone()).collect()
318            }
319        }
320        let p = KdaTpPlan::new(1, 2, 4, 64, 4, 2, 4).unwrap();
321        let q = p.get("q_proj").unwrap();
322        let src = Src(vec![
323            (
324                "self_attn.q_proj.weight".into(),
325                KdaDtype::Bf16,
326                vec![256, 4],
327                (0..=255u8).flat_map(|r| [r; 8]).collect(),
328            ),
329            (
330                "input_layernorm.weight".into(),
331                KdaDtype::Bf16,
332                vec![4],
333                vec![0xEE; 8],
334            ),
335        ]);
336        let sh = KdaShardedSource::new(&src, &p).unwrap();
337
338        let t = sh.get("self_attn.q_proj.weight").unwrap();
339        assert_eq!(t.shape, vec![128, 4], "local rows, full row width");
340        assert_eq!(t.bytes.len(), q.local_bytes());
341        assert!(
342            t.bytes.iter().all(|b| *b >= 128),
343            "rank 1 keeps the high rows"
344        );
345
346        let n = sh.get("input_layernorm.weight").unwrap();
347        assert_eq!(n.bytes, vec![0xEE; 8]);
348        assert_eq!(sh.names().len(), 2, "the census still sees everything");
349    }
350}