spark_model/kimi_k3/
tp.rs1use atlas_core::config::ModelConfig;
10use atlas_core::kimi_k3::{MixerKind, MlpKind};
11
12use crate::tp_shard::TpShardKind;
13
14pub fn supports_tp() -> bool {
16 true
17}
18
19pub fn tensor_plan(
21 name: &str,
22 mixer: MixerKind,
23 _mlp: MlpKind,
24 config: &ModelConfig,
25) -> (TpShardKind, usize, usize) {
26 let tp = config.tp_world_size.max(1);
27 let h = config.hidden_size;
28 let kda_heads = config.linear_num_key_heads * tp;
29 let kda_d = config.linear_key_head_dim;
30 let kda_q = kda_heads * kda_d;
31 let conv_k = config.linear_conv_kernel_dim.max(1);
32 let mla_heads = config.num_attention_heads * tp;
33 let qk = config.qk_nope_head_dim + config.qk_rope_head_dim;
34 let dv = mla_heads * config.v_head_dim;
35 let kv_b = mla_heads * (config.qk_nope_head_dim + config.v_head_dim);
36 let inter = config.intermediate_size;
37 let eh = config.moe_intermediate_size;
38 let lat = config.moe_latent_size;
39
40 if name.ends_with(".self_attn.q_proj.weight")
41 || name.ends_with(".self_attn.k_proj.weight")
42 || name.ends_with(".self_attn.v_proj.weight")
43 {
44 return (TpShardKind::ColumnParallel, kda_q, h);
45 }
46 if name.ends_with(".self_attn.q_conv1d.weight")
47 || name.ends_with(".self_attn.k_conv1d.weight")
48 || name.ends_with(".self_attn.v_conv1d.weight")
49 {
50 return (TpShardKind::ColumnParallel, kda_q, conv_k);
51 }
52 if name.ends_with(".self_attn.g_proj.weight") {
53 let n = match mixer {
54 MixerKind::Kda => kda_q,
55 MixerKind::Mla => dv,
56 };
57 return (TpShardKind::ColumnParallel, n, h);
58 }
59 if name.ends_with(".self_attn.o_proj.weight") {
60 let inn = match mixer {
61 MixerKind::Kda => kda_q,
62 MixerKind::Mla => dv,
63 };
64 return (TpShardKind::RowParallel, h, inn);
65 }
66 if name.ends_with(".self_attn.b_proj.weight") {
67 return (TpShardKind::ColumnParallel, kda_heads, h);
68 }
69 if name.ends_with(".self_attn.A_log") {
70 return (TpShardKind::ColumnParallel, kda_heads, 1);
71 }
72 if name.ends_with(".self_attn.dt_bias") {
73 return (TpShardKind::ColumnParallel, kda_q, 1);
74 }
75 if name.ends_with(".self_attn.f_b_proj.weight") {
76 return (TpShardKind::ColumnParallel, kda_q, kda_d);
77 }
78 if name.ends_with(".self_attn.q_b_proj.weight") {
79 return (
80 TpShardKind::ColumnParallel,
81 mla_heads * qk,
82 config.q_lora_rank,
83 );
84 }
85 if name.ends_with(".self_attn.kv_b_proj.weight") {
86 return (TpShardKind::ColumnParallel, kv_b, config.kv_lora_rank);
87 }
88 if name.ends_with(".mlp.gate_proj.weight") || name.ends_with(".mlp.up_proj.weight") {
89 return (TpShardKind::ColumnParallel, inter, h);
90 }
91 if name.ends_with(".mlp.down_proj.weight") {
92 return (TpShardKind::RowParallel, h, inter);
93 }
94 if name.contains(".block_sparse_moe.experts.") {
95 if name.ends_with(".w1.weight") || name.ends_with(".w3.weight") {
96 return (TpShardKind::ColumnParallel, eh, lat);
97 }
98 if name.ends_with(".w2.weight") {
99 return (TpShardKind::RowParallel, lat, eh);
100 }
101 }
102 (TpShardKind::Replicated, 1, 1)
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use atlas_core::config::parse_config;
109
110 const TWIN: &str = include_str!("../../../../docs/k3/fixtures/Kimi-K3-0.40B-config.json");
111
112 fn twin() -> ModelConfig {
113 parse_config(TWIN).expect("0.40B twin")
114 }
115
116 fn divide_heads_for_tp(config: &mut ModelConfig, rank: usize, size: usize) {
117 config.tp_rank = rank;
118 config.tp_world_size = size;
119 if size > 1 {
120 config.num_attention_heads /= size;
121 config.num_key_value_heads /= size;
122 config.linear_num_key_heads /= size;
123 config.linear_num_value_heads /= size;
124 }
125 }
126
127 #[test]
128 fn kimi_k3_supports_tp() {
129 assert!(supports_tp(), "loader must not refuse --tp-size 2");
130 }
131
132 #[test]
133 fn kda_q_is_column_o_is_row() {
134 let mut c = twin();
135 divide_heads_for_tp(&mut c, 0, 2);
136 let q = tensor_plan(
137 "language_model.model.layers.0.self_attn.q_proj.weight",
138 MixerKind::Kda,
139 MlpKind::Dense,
140 &c,
141 );
142 let o = tensor_plan(
143 "language_model.model.layers.0.self_attn.o_proj.weight",
144 MixerKind::Kda,
145 MlpKind::Dense,
146 &c,
147 );
148 assert_eq!(q, (TpShardKind::ColumnParallel, 8 * 32, 1024));
149 assert_eq!(o, (TpShardKind::RowParallel, 1024, 8 * 32));
150 let embed = tensor_plan(
151 "language_model.model.embed_tokens.weight",
152 MixerKind::Kda,
153 MlpKind::Dense,
154 &c,
155 );
156 assert_eq!(embed.0, TpShardKind::Replicated);
157 }
158
159 #[test]
160 fn rank0_and_rank1_q_proj_full_out_match() {
161 let mut c0 = twin();
162 divide_heads_for_tp(&mut c0, 0, 2);
163 let mut c1 = twin();
164 divide_heads_for_tp(&mut c1, 1, 2);
165 let q0 = tensor_plan(
166 "language_model.model.layers.0.self_attn.q_proj.weight",
167 MixerKind::Kda,
168 MlpKind::Dense,
169 &c0,
170 );
171 let q1 = tensor_plan(
172 "language_model.model.layers.0.self_attn.q_proj.weight",
173 MixerKind::Kda,
174 MlpKind::Dense,
175 &c1,
176 );
177 assert_eq!(q0, q1, "full sizes reconstruct from local*tp on both ranks");
178 assert_eq!(c0.tp_rank, 0);
179 assert_eq!(c1.tp_rank, 1);
180 assert_ne!(c0.tp_rank, c1.tp_rank);
181 }
182
183 fn round_bf16(x: f32) -> f32 {
184 let bits = atlas_core::numeric::f32_to_bf16(x);
185 atlas_core::numeric::bf16_bytes_to_f32(bits.to_le_bytes())
186 }
187
188 #[test]
189 fn production_7168_bf16_allreduce_rounding_is_measured() {
190 let n = 7168;
193 let a = vec![1.0f32 / 3.0; n];
194 let b = vec![2.0f32 / 3.0; n];
195 let mut max = 0.0f32;
196 for i in 0..n {
197 let f32s = a[i] + b[i];
198 let bfs = round_bf16(a[i]) + round_bf16(b[i]);
199 max = max.max((f32s - bfs).abs());
200 }
201 assert!(max > 0.0, "1/3 is not exact in BF16");
202 assert!(
203 max < 0.01,
204 "7168-wide double BF16 round before NCCL max_abs={max}"
205 );
206 eprintln!("K3 TP BF16 allreduce max_abs @7168 (1/3+2/3) = {max}");
207 }
208}