spark_model/layers/glm5next_mlp/
build.rs1use anyhow::{Result, bail};
20use spark_runtime::gpu::{DevicePtr, GpuBackend};
21
22use super::Glm5NextMlpConfig;
23use super::weights::{
24 Glm5NextDenseMlpWeights, Glm5NextExpertPtrTable, Glm5NextExpertWeights, Glm5NextMoePtrTables,
25 Glm5NextMoeWeights, Nvfp4Proj,
26};
27
28pub type LoadFn<'a> = &'a dyn Fn(&str) -> Result<Vec<f32>>;
30pub type ExpertFn<'a> = &'a dyn Fn(usize) -> Result<Glm5NextExpertWeights>;
38
39fn row_slice(v: &[f32], row_elems: usize, start: usize, end: usize) -> Vec<f32> {
41 v[start * row_elems..end * row_elems].to_vec()
42}
43
44fn col_slice(v: &[f32], row_elems: usize, start: usize, end: usize) -> Vec<f32> {
46 v.chunks(row_elems)
47 .flat_map(|r| r[start..end].iter().copied())
48 .collect()
49}
50
51fn up_bf16(gpu: &dyn GpuBackend, v: &[f32]) -> Result<DevicePtr> {
52 let b: Vec<u8> = v
53 .iter()
54 .flat_map(|x| half::bf16::from_f32(*x).to_le_bytes())
55 .collect();
56 let p = gpu.alloc(b.len().max(1))?;
57 gpu.copy_h2d(&b, p)?;
58 Ok(p)
59}
60
61fn up_f32(gpu: &dyn GpuBackend, v: &[f32]) -> Result<DevicePtr> {
62 let b: Vec<u8> = v.iter().flat_map(|x| x.to_le_bytes()).collect();
63 let p = gpu.alloc(b.len().max(1))?;
64 gpu.copy_h2d(&b, p)?;
65 Ok(p)
66}
67
68pub fn build_dense_mlp(
72 gpu: &dyn GpuBackend,
73 cfg: &Glm5NextMlpConfig,
74 tp_rank: usize,
75 full_inter: usize,
76 prefix: &str,
77 load: LoadFn<'_>,
78) -> Result<Glm5NextDenseMlpWeights> {
79 let tp = cfg.tp_world_size;
80 if !full_inter.is_multiple_of(tp) {
81 bail!("GLM MLP {prefix}: intermediate {full_inter} does not divide over tp {tp}");
82 }
83 let local = full_inter / tp;
84 let lo = tp_rank * local;
85
86 let get = |n: &str| -> Result<Vec<f32>> { load(&format!("{prefix}.{n}")) };
87
88 let expect = |name: &str, v: &[f32], want: usize| -> Result<()> {
89 if v.len() != want {
90 bail!(
91 "GLM MLP {prefix}.{name}: {} elements, expected {want}",
92 v.len()
93 );
94 }
95 Ok(())
96 };
97
98 let gate = get("gate_proj.weight")?;
99 expect("gate_proj.weight", &gate, full_inter * cfg.hidden)?;
100 let up = get("up_proj.weight")?;
101 expect("up_proj.weight", &up, full_inter * cfg.hidden)?;
102 let down = get("down_proj.weight")?;
103 expect("down_proj.weight", &down, cfg.hidden * full_inter)?;
104
105 Ok(Glm5NextDenseMlpWeights {
106 gate_proj: up_bf16(gpu, &row_slice(&gate, cfg.hidden, lo, lo + local))?,
108 up_proj: up_bf16(gpu, &row_slice(&up, cfg.hidden, lo, lo + local))?,
109 down_proj: up_bf16(gpu, &col_slice(&down, full_inter, lo, lo + local))?,
111 })
112}
113
114fn build_expert_ptr_table(
120 gpu: &dyn GpuBackend,
121 cfg: &Glm5NextMlpConfig,
122 experts: &[Glm5NextExpertWeights],
123 proj: impl Fn(&Glm5NextExpertWeights) -> Nvfp4Proj,
124) -> Result<Glm5NextExpertPtrTable> {
125 let n = cfg.num_experts;
126 let mut packed = vec![0u8; n * 8];
127 let mut scale = vec![0u8; n * 8];
128 let mut scale2 = vec![0u8; n * 4];
129 for id in 0..n {
130 let Some(local) = cfg.local_slot(id) else {
131 continue; };
133 let p = proj(&experts[local]);
134 packed[id * 8..id * 8 + 8].copy_from_slice(&p.packed.0.to_le_bytes());
135 scale[id * 8..id * 8 + 8].copy_from_slice(&p.scale.0.to_le_bytes());
136 scale2[id * 4..id * 4 + 4].copy_from_slice(&p.scale_2.to_le_bytes());
137 }
138 let packed_ptrs = gpu.alloc(packed.len())?;
139 gpu.copy_h2d(&packed, packed_ptrs)?;
140 let scale_ptrs = gpu.alloc(scale.len())?;
141 gpu.copy_h2d(&scale, scale_ptrs)?;
142 let scale2_vals = gpu.alloc(scale2.len())?;
143 gpu.copy_h2d(&scale2, scale2_vals)?;
144 Ok(Glm5NextExpertPtrTable {
145 packed_ptrs,
146 scale_ptrs,
147 scale2_vals,
148 })
149}
150
151pub fn build_moe(
154 gpu: &dyn GpuBackend,
155 cfg: &Glm5NextMlpConfig,
156 tp_rank: usize,
157 full_shared_inter: usize,
158 load: LoadFn<'_>,
159 expert: ExpertFn<'_>,
160) -> Result<Glm5NextMoeWeights> {
161 let router = load("mlp.gate.weight")?;
165 if router.len() != cfg.num_experts * cfg.hidden {
166 bail!(
167 "GLM MoE mlp.gate.weight: {} elements, expected {} ({} experts x {} hidden)",
168 router.len(),
169 cfg.num_experts * cfg.hidden,
170 cfg.num_experts,
171 cfg.hidden
172 );
173 }
174 let bias = load("mlp.gate.e_score_correction_bias")?;
175 if bias.len() != cfg.num_experts {
176 bail!(
177 "GLM MoE e_score_correction_bias: {} entries, expected {}",
178 bias.len(),
179 cfg.num_experts
180 );
181 }
182
183 let shared = build_dense_mlp(
184 gpu,
185 cfg,
186 tp_rank,
187 full_shared_inter,
188 "mlp.shared_experts",
189 load,
190 )?;
191
192 let mut experts = Vec::with_capacity(cfg.local_experts);
195 for id in cfg.local_expert_range() {
196 experts.push(expert(id)?);
197 }
198
199 let ptrs = Glm5NextMoePtrTables {
200 gate: build_expert_ptr_table(gpu, cfg, &experts, |e| e.gate_proj)?,
201 up: build_expert_ptr_table(gpu, cfg, &experts, |e| e.up_proj)?,
202 down: build_expert_ptr_table(gpu, cfg, &experts, |e| e.down_proj)?,
203 };
204
205 Ok(Glm5NextMoeWeights {
206 router: up_bf16(gpu, &router)?,
207 router_bias: up_f32(gpu, &bias)?,
209 shared,
210 experts,
211 ptrs,
212 })
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
222 fn dense_slicing_takes_rows_for_gate_and_columns_for_down() {
223 let gate: Vec<f32> = (0..4)
225 .flat_map(|r| (0..3).map(move |c| (r * 10 + c) as f32))
226 .collect();
227 assert_eq!(
229 row_slice(&gate, 3, 2, 4),
230 vec![20., 21., 22., 30., 31., 32.]
231 );
232
233 let down: Vec<f32> = (0..3)
235 .flat_map(|r| (0..4).map(move |c| (r * 10 + c) as f32))
236 .collect();
237 assert_eq!(col_slice(&down, 4, 2, 4), vec![2., 3., 12., 13., 22., 23.]);
238 let sq: Vec<f32> = (0..16).map(|i| i as f32).collect();
241 let by_row = row_slice(&sq, 4, 2, 4);
242 let by_col = col_slice(&sq, 4, 2, 4);
243 assert_eq!(by_row.len(), by_col.len());
244 assert_ne!(by_row, by_col);
245 }
246}