1use super::*;
9
10impl ModelWeightLoader for Glm5NextWeightLoader {
11 fn binds_vision_encoder(&self) -> bool {
16 metrale_config::glm_vision_enabled()
17 }
18
19 fn load_vision_encoder(
22 &self,
23 store: &WeightStore,
24 config: &ModelConfig,
25 gpu: &dyn GpuBackend,
26 ) -> Result<Option<metrale_model_layers::layers::VisionTower>> {
27 crate::weight_loader::glm5_next_vision::load_glm5_next_vision(store, config, gpu)
28 }
29
30 fn defer_predicate(
35 &self,
36 config: &ModelConfig,
37 ) -> Option<metrale_model_weights::weights::DeferHook> {
38 let num_layers = config.num_hidden_layers;
39 Some(std::sync::Arc::new(
40 move |name: &str, dtype: WeightDtype| is_full_width_mtp_expert(name, dtype, num_layers),
41 ))
42 }
43
44 fn supports_tp(&self) -> bool {
47 true
48 }
49
50 fn load_layers(
51 &self,
52 store: &WeightStore,
53 config: &ModelConfig,
54 gpu: &dyn GpuBackend,
55 _layer_kv_dtypes: &[KvCacheDtype],
56 ) -> Result<Vec<Box<dyn TransformerLayer>>> {
57 let skeleton = Glm5NextTextSkeleton::from_config(config)?;
58 let kda_cfg = Glm5NextKdaConfig {
61 hidden: config.hidden_size,
62 heads: config.linear_num_value_heads,
63 head_dim: config.linear_value_head_dim,
64 conv_kernel: config.linear_conv_kernel_dim,
65 gate_lower_bound: config.linear_gate_lower_bound,
66 rms_norm_eps: config.rms_norm_eps as f32,
67 l2_eps: 1e-6,
68 chunk: 32,
69 };
70 kda_cfg.validate()?;
71 let gate_rank = {
74 let n = qualify(0, "self_attn.f_a_proj.weight");
75 let t = store.get(&n).with_context(|| {
76 format!("glm5_next: {n} is needed to size the KDA gate bottleneck")
77 })?;
78 *t.shape.first().context("f_a_proj has no rows")?
79 };
80 let kda_plan = KdaTpPlan::from_config(config, gate_rank)?;
81 let dsa_cfg = Glm5NextDsaConfig::from_config(config)?;
82 let mlp_cfg = Glm5NextMlpConfig::from_config(config)?;
83
84 let kda_kernels = Glm5NextKdaKernels::resolve(gpu)?;
85 let dsa_kernels = Glm5NextDsaKernels::resolve(gpu)?;
86 let dsa_layer_kernels = Glm5NextDsaLayerKernels::resolve(gpu)?;
87 let mlp_kernels = Glm5NextMlpKernels::resolve(gpu)?;
88 let mhc_kernels_probe = Glm5NextMhcKernels::resolve(gpu)?;
89 let rms_norm_k = gpu.kernel("rms_norm_vanilla", "rms_norm_vanilla")?;
90 let add_k = metrale_model_layers::layers::try_kernel(gpu, "bf16_add", "bf16_add_inplace");
94
95 let verify_k = (metrale_model_layers::layers::ops::DENSE_GEMV_BATCHM_MAX_M as usize)
100 .max(crate::glm5next_layer::PREFILL_ROWS)
101 .max(crate::glm5next_layer::prefill_rows());
102 let kda_ws = std::sync::Arc::new(crate::glm5next_kda::Glm5NextKdaWorkspace::new(
103 gpu, &kda_cfg, verify_k,
104 )?);
105
106 let mlp_ws_bytes = crate::glm5next_mlp::forward::mlp_ws_total_bytes(&mlp_cfg, verify_k);
110 let shared_mlp_ws = if crate::glm5next_mlp::forward::mlp_ws_shared() {
111 tracing::info!(
112 "GLM MLP workspace: SHARED, 1 x {:.1} MB for {} layers at {verify_k} rows \
113 (per-layer would be {:.1} MB)",
114 mlp_ws_bytes as f64 / 1e6,
115 skeleton.layers.len(),
116 (mlp_ws_bytes * skeleton.layers.len()) as f64 / 1e6,
117 );
118 Some(std::sync::Arc::new(
119 crate::glm5next_mlp::forward::Glm5NextMlpWorkspace::new(gpu, &mlp_cfg, verify_k)?,
120 ))
121 } else {
122 tracing::warn!(
123 "GLM MLP workspace: PER-LAYER, {} x {:.1} MB at {verify_k} rows",
124 skeleton.layers.len(),
125 mlp_ws_bytes as f64 / 1e6,
126 );
127 None
128 };
129
130 let dsa_plan = crate::glm5next_dsa::tp::DsaTpPlan::new(
131 config.tp_rank,
132 config.tp_world_size.max(1),
133 &dsa_cfg,
134 )?;
135 let last = skeleton.layers.len() - 1;
136 let mut out: Vec<Box<dyn TransformerLayer>> = Vec::with_capacity(skeleton.layers.len());
137
138 let mut attn_layer_idx = 0usize;
142
143 for sl in &skeleton.layers {
144 let idx = sl.index;
145 let t_layer = std::time::Instant::now();
146 let src = LayerSource::collect(gpu, store, idx)
147 .with_context(|| format!("glm5_next: collecting layer {idx}"))?;
148 let t_collect = t_layer.elapsed();
149
150 let mixer = match sl.mixer {
151 Mixer::Kda => {
152 let sharded = KdaShardedSource::new(&src, &kda_plan)?;
153 let (w, _report) = bind_kda_weights(gpu, &kda_cfg, idx, &sharded)?;
154 Glm5NextMixer::Kda {
155 layer: Box::new(Glm5NextKdaLayer::new(idx, kda_cfg, w, kda_kernels)?),
156 ws: kda_ws.clone(),
157 cfg: kda_cfg,
158 }
159 }
160 Mixer::Dsa => {
161 let load = |n: &str| src.f32(n);
162 let w = build_dsa_weights(gpu, &dsa_cfg, &dsa_plan, &load)?;
163 Glm5NextMixer::Dsa(Box::new(Glm5NextDsaLayer {
164 persist_bt: std::env::var("METRALE_GLM_DSA_ALLOC_PER_STEP").as_deref()
165 != Ok("1"),
166 cfg: dsa_cfg,
167 weights: w,
168 kernels: dsa_layer_kernels,
169 select_kernels: dsa_kernels,
170 decode_kernel:
171 crate::glm5next_dsa::attend::Glm5NextDsaDecodeKernel::resolve(gpu)?,
172 workspace: crate::glm5next_dsa::layer::Glm5NextDsaWorkspace::new(
173 gpu, &dsa_cfg, verify_k,
174 )?,
175 layer_idx: idx,
176 attn_layer_idx: {
177 let a = attn_layer_idx;
178 attn_layer_idx += 1;
179 a
180 },
181 rms_eps: config.rms_norm_eps as f32,
182 kv_scale: 1.0,
183 }))
184 }
185 };
186
187 let t_mixer = t_layer.elapsed();
188
189 let load = |n: &str| src.f32(n);
190 let mlp = match sl.mlp {
191 Mlp::Dense => Glm5NextMlpSite::Dense(mlp_build::build_dense_mlp(
192 gpu,
193 &mlp_cfg,
194 config.tp_rank,
195 config.intermediate_size,
196 "mlp",
197 &load,
198 )?),
199 Mlp::RoutedMoe => {
200 let expert = |id: usize| bind_expert(gpu, store, idx, id);
201 Glm5NextMlpSite::Moe(Box::new(mlp_build::build_moe(
202 gpu,
203 &mlp_cfg,
204 config.tp_rank,
205 config.shared_expert_intermediate_size,
206 &load,
207 &expert,
208 )?))
209 }
210 };
211
212 let t_mlp = t_layer.elapsed();
213 tracing::info!(
214 "glm5_next layer {idx} built: collect {:.2}s mixer {:.2}s mlp {:.2}s \
215 (mixer={:?} mlp={:?})",
216 t_collect.as_secs_f64(),
217 (t_mixer - t_collect).as_secs_f64(),
218 (t_mlp - t_mixer).as_secs_f64(),
219 sl.mixer,
220 sl.mlp,
221 );
222
223 let mhc = if sl.hyper_connection {
224 Some(Glm5NextMhc {
225 kernels: mhc_kernels_probe,
226 attn: bind_mhc_site(gpu, &src, "attn", config.hc_mult, config.hidden_size)?,
227 ffn: bind_mhc_site(gpu, &src, "ffn", config.hc_mult, config.hidden_size)?,
228 hc_mult: config.hc_mult,
229 sinkhorn_iters: config.hc_sinkhorn_iters,
230 hc_eps: config.hc_eps,
231 })
232 } else {
233 None
234 };
235
236 out.push(Box::new(Glm5NextLayer {
237 layer_idx: idx,
238 mixer,
239 mlp,
240 mlp_cfg,
241 mlp_kernels,
242 mlp_ws: match &shared_mlp_ws {
243 Some(ws) => ws.clone(),
244 None => std::sync::Arc::new(
245 crate::glm5next_mlp::forward::Glm5NextMlpWorkspace::new(
246 gpu, &mlp_cfg, verify_k,
247 )?,
248 ),
249 },
250 mhc,
251 input_norm: upload_f32_as_bf16(gpu, &src.f32("input_layernorm.weight")?)?,
252 post_attn_norm: upload_f32_as_bf16(
253 gpu,
254 &src.f32("post_attention_layernorm.weight")?,
255 )?,
256 rms_norm_k,
257 add_k,
258 rms_eps: config.rms_norm_eps as f32,
259 hidden: config.hidden_size,
260 mixer_all_reduce: match sl.mixer {
261 Mixer::Kda => kda_plan.needs_output_all_reduce(),
262 Mixer::Dsa => dsa_plan.needs_output_all_reduce(),
263 },
264 is_first: idx == 0,
265 is_last: idx == last,
266 }));
267 }
268 Ok(out)
269 }
270
271 fn load_embedding(
272 &self,
273 store: &WeightStore,
274 _config: &ModelConfig,
275 _gpu: &dyn GpuBackend,
276 ) -> Result<DenseWeight> {
277 dense(store, "model.language_model.embed_tokens.weight")
278 }
279
280 fn load_final_norm(
281 &self,
282 store: &WeightStore,
283 _config: &ModelConfig,
284 _gpu: &dyn GpuBackend,
285 ) -> Result<DenseWeight> {
286 dense(store, "model.language_model.norm.weight")
287 }
288
289 fn load_lm_head(
290 &self,
291 store: &WeightStore,
292 _config: &ModelConfig,
293 _gpu: &dyn GpuBackend,
294 ) -> Result<DenseWeight> {
295 dense(store, "lm_head.weight")
296 }
297
298 fn load_mtp_weights(
301 &self,
302 _store: &WeightStore,
303 _config: &ModelConfig,
304 _gpu: &dyn GpuBackend,
305 ) -> Result<Option<crate::weight_loader::MtpWeights>> {
306 Ok(None)
307 }
308
309 fn prune_after_load(
312 &self,
313 store: &mut WeightStore,
314 config: &ModelConfig,
315 gpu: &dyn GpuBackend,
316 ) -> Result<()> {
317 let n = config.num_hidden_layers;
318 let (count, bytes) = store.free_matching(gpu, |name| is_reuploaded(name, n))?;
319 tracing::info!(
320 "glm5_next: released {count} store tensors ({:.2} GB) already re-uploaded by the \
321 binders; routed experts and the MTP block kept",
322 bytes as f64 / 1e9,
323 );
324 let quantized: std::collections::BTreeSet<String> = store
325 .names()
326 .filter(|n| {
327 store
328 .get(n)
329 .is_ok_and(|t| is_quantized_expert_weight(n, t.dtype))
330 })
331 .map(str::to_string)
332 .collect();
333 if !quantized.is_empty() {
334 let (qcount, qbytes) = store.free_matching(gpu, |name| quantized.contains(name))?;
335 tracing::info!(
336 "glm5_next: released {qcount} full-width BF16 routed-expert tensors \
337 ({:.2} GB) quantised to NVFP4 at bind time",
338 qbytes as f64 / 1e9,
339 );
340 }
341 Ok(())
342 }
343}