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