avarok_core/kimi_k3/
weights_geometry.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2//! Replicated checkpoint geometry checked before device allocation.
3use crate::config::ModelConfig;
4use anyhow::{Context, Result, bail, ensure};
5
6/// Storage geometry for weights that remain full-width on every TP rank.
7/// These are the matrix/vector dimensions consumed by `cpu_bind::assemble`.
8/// Sharded projections are validated by `tp::plan_tensor_bytes` instead.
9pub fn expected_replicated_shape(name: &str, config: &ModelConfig) -> Result<Vec<usize>> {
10    let prefix = config.weight_prefix.trim_end_matches('.');
11    let canonical = if prefix.is_empty() || name == "lm_head.weight" {
12        name
13    } else {
14        name.strip_prefix(prefix)
15            .and_then(|rest| rest.strip_prefix('.'))
16            .context("K3 weight does not match configured text prefix")?
17    };
18    let h = config.hidden_size;
19    let shape = match canonical {
20        "model.embed_tokens.weight" | "lm_head.weight" => vec![config.vocab_size, h],
21        "model.norm.weight" | "model.output_attn_res_norm.weight" => vec![h],
22        "model.output_attn_res_proj.weight" => vec![1, h],
23        _ => layer_shape(canonical, config)?,
24    };
25    shape.iter().try_fold(1usize, |elements, &dimension| {
26        ensure!(dimension > 0, "{name}: zero replicated dimension");
27        elements
28            .checked_mul(dimension)
29            .context("K3 replicated shape overflow")
30    })?;
31    Ok(shape)
32}
33
34fn layer_shape(name: &str, c: &ModelConfig) -> Result<Vec<usize>> {
35    let (layer, suffix) = name
36        .strip_prefix("model.layers.")
37        .and_then(|rest| rest.split_once('.'))
38        .context("unknown replicated K3 tensor")?;
39    let layer = layer.parse::<usize>().context("invalid K3 layer index")?;
40    ensure!(
41        layer < c.layer_types.len(),
42        "K3 replicated layer outside configuration"
43    );
44    let h = c.hidden_size;
45    let lat = c.moe_latent_size;
46    let shared = if c.shared_expert_intermediate_size > 0 {
47        c.shared_expert_intermediate_size
48    } else {
49        c.moe_intermediate_size
50    };
51    Ok(match suffix {
52        "input_layernorm.weight"
53        | "post_attention_layernorm.weight"
54        | "mlp_res_norm.weight"
55        | "self_attention_res_norm.weight" => vec![h],
56        "mlp_res_proj.weight" | "self_attention_res_proj.weight" => vec![1, h],
57        "self_attn.f_a_proj.weight" => vec![c.linear_key_head_dim, h],
58        "self_attn.o_norm.weight" => vec![c.linear_key_head_dim],
59        "self_attn.kv_a_layernorm.weight" => vec![c.kv_lora_rank],
60        "self_attn.kv_a_proj_with_mqa.weight" => vec![
61            c.kv_lora_rank
62                .checked_add(c.qk_rope_head_dim)
63                .context("K3 KV width overflow")?,
64            h,
65        ],
66        "self_attn.q_a_layernorm.weight" => vec![c.q_lora_rank],
67        "self_attn.q_a_proj.weight" => vec![c.q_lora_rank, h],
68        "block_sparse_moe.gate.weight" => vec![c.num_experts, h],
69        "block_sparse_moe.gate.e_score_correction_bias" => vec![c.num_experts],
70        "block_sparse_moe.routed_expert_down_proj.weight" => vec![lat, h],
71        "block_sparse_moe.routed_expert_up_proj.weight" => vec![h, lat],
72        "block_sparse_moe.routed_expert_norm.weight" => vec![lat],
73        "block_sparse_moe.shared_experts.down_proj.weight" => vec![h, shared],
74        "block_sparse_moe.shared_experts.gate_proj.weight"
75        | "block_sparse_moe.shared_experts.up_proj.weight" => vec![shared, h],
76        _ => bail!("{name}: unknown replicated K3 projection"),
77    })
78}
79
80pub fn validate_replicated_shape(name: &str, shape: &[usize], config: &ModelConfig) -> Result<()> {
81    let expected = expected_replicated_shape(name, config)?;
82    // Residual scoring consumes one flat query row. Both vector and Linear
83    // [1,H] serialization have identical order and exactly H elements.
84    let flat_residual = name.ends_with("res_proj.weight")
85        && expected == [1, config.hidden_size]
86        && shape == [config.hidden_size];
87    ensure!(
88        shape == expected || flat_residual,
89        "{name}: replicated shape {shape:?} != {expected:?}"
90    );
91    Ok(())
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97    use crate::config::{LayerType, parse_config};
98
99    fn config() -> ModelConfig {
100        let mut c = parse_config(include_str!(
101            "../../../../docs/k3/fixtures/Kimi-K3-0.40B-config.json"
102        ))
103        .unwrap();
104        c.weight_prefix.clear();
105        c.hidden_size = 8;
106        c.vocab_size = 32;
107        c.layer_types = vec![LayerType::LinearAttention, LayerType::FullAttention];
108        c.moe_latent_size = 4;
109        c.moe_intermediate_size = 16;
110        c.shared_expert_intermediate_size = 12;
111        c.num_experts = 3;
112        c
113    }
114
115    #[test]
116    fn embedding_and_norm_reject_tiny_or_transposed_storage() {
117        let c = config();
118        assert!(validate_replicated_shape("model.embed_tokens.weight", &[32, 8], &c).is_ok());
119        for shape in [vec![1], vec![8, 32], vec![256]] {
120            assert!(validate_replicated_shape("model.embed_tokens.weight", &shape, &c).is_err());
121        }
122        assert!(validate_replicated_shape("model.norm.weight", &[8], &c).is_ok());
123        assert!(validate_replicated_shape("model.norm.weight", &[1], &c).is_err());
124    }
125
126    #[test]
127    fn router_and_shared_projections_keep_full_width_at_tp2() {
128        let mut c = config();
129        c.tp_world_size = 2;
130        c.tp_rank = 1;
131        let p = "model.layers.1.block_sparse_moe.";
132        for (suffix, shape) in [
133            ("gate.weight", vec![3, 8]),
134            ("gate.e_score_correction_bias", vec![3]),
135            ("routed_expert_down_proj.weight", vec![4, 8]),
136            ("routed_expert_up_proj.weight", vec![8, 4]),
137            ("routed_expert_norm.weight", vec![4]),
138            ("shared_experts.down_proj.weight", vec![8, 12]),
139            ("shared_experts.gate_proj.weight", vec![12, 8]),
140            ("shared_experts.up_proj.weight", vec![12, 8]),
141        ] {
142            let name = format!("{p}{suffix}");
143            assert_eq!(expected_replicated_shape(&name, &c).unwrap(), shape);
144            assert!(validate_replicated_shape(&name, &[1], &c).is_err());
145        }
146    }
147
148    #[test]
149    fn unknown_out_of_range_and_zero_dimensions_refuse() {
150        let mut c = config();
151        for name in [
152            "model.layers.9.input_layernorm.weight",
153            "model.layers.0.unknown.weight",
154        ] {
155            assert!(expected_replicated_shape(name, &c).is_err());
156        }
157        c.hidden_size = 0;
158        assert!(expected_replicated_shape("model.norm.weight", &c).is_err());
159    }
160
161    #[test]
162    fn prefixed_weights_and_residual_query_shapes_are_explicit() {
163        let mut c = config();
164        c.weight_prefix = "language_model".into();
165        assert_eq!(
166            expected_replicated_shape("language_model.model.norm.weight", &c).unwrap(),
167            [8]
168        );
169        assert!(expected_replicated_shape("other.model.norm.weight", &c).is_err());
170        assert_eq!(
171            expected_replicated_shape("lm_head.weight", &c).unwrap(),
172            [32, 8]
173        );
174        let name = "language_model.model.output_attn_res_proj.weight";
175        assert!(validate_replicated_shape(name, &[1, 8], &c).is_ok());
176        assert!(validate_replicated_shape(name, &[8], &c).is_ok());
177        assert!(validate_replicated_shape(name, &[8, 1], &c).is_err());
178    }
179
180    #[test]
181    fn every_replicated_required_name_has_a_geometry_matching_cpu_bind() {
182        use super::super::{
183            K3Graph, kda_from, mla_from, moe_from,
184            tp::{TpAxis, tensor_plan},
185        };
186        let c = config();
187        let graph = K3Graph::from_config(&c);
188        let inventory = super::super::cpu_bind::inventory(
189            &c,
190            &graph,
191            &kda_from(&c),
192            &mla_from(&c),
193            &moe_from(&c),
194        );
195        for (name, elements) in inventory {
196            let mixer = name
197                .split("model.layers.")
198                .nth(1)
199                .and_then(|tail| tail.split('.').next())
200                .and_then(|index| index.parse::<usize>().ok())
201                .map(|index| graph.layers[index].mixer)
202                .unwrap_or(super::super::MixerKind::Kda);
203            if tensor_plan(&name, mixer, &c).0 == TpAxis::Replicated {
204                let shape = expected_replicated_shape(&name, &c).unwrap();
205                assert_eq!(shape.iter().product::<usize>(), elements, "{name}");
206            }
207        }
208    }
209}