metrale_config/
methods.rs1#![allow(unused_imports)]
9
10use super::{AttnPositionEncoding, LayerType, ModelConfig};
11
12impl ModelConfig {
13 pub fn eos_ids(&self) -> Vec<u32> {
16 if self.eos_token_ids.is_empty() {
17 vec![self.eos_token_id]
18 } else {
19 self.eos_token_ids.clone()
20 }
21 }
22
23 pub fn is_eos(&self, id: u32) -> bool {
24 if self.eos_token_ids.is_empty() {
25 id == self.eos_token_id
26 } else {
27 self.eos_token_ids.contains(&id)
28 }
29 }
30
31 pub fn gqa_ratio(&self) -> usize {
33 self.num_attention_heads
34 .checked_div(self.num_key_value_heads)
35 .unwrap_or(1)
36 }
37
38 pub fn layer_type_at(&self, layer_idx: usize) -> Option<LayerType> {
42 if layer_idx < self.num_hidden_layers {
43 return Some(self.layer_type(layer_idx));
44 }
45 self.mtp_layer_types
46 .get(layer_idx - self.num_hidden_layers)
47 .copied()
48 }
49
50 pub fn sparse_attention_layers(&self) -> Vec<usize> {
52 self.layer_types
53 .iter()
54 .enumerate()
55 .filter(|(_, t)| **t == LayerType::SparseAttention)
56 .map(|(i, _)| i)
57 .collect()
58 }
59
60 pub fn has_sparse_attention(&self) -> bool {
63 self.layer_types.contains(&LayerType::SparseAttention)
64 || self.mtp_layer_types.contains(&LayerType::SparseAttention)
65 }
66
67 pub fn layer_type(&self, layer_idx: usize) -> LayerType {
71 if !self.layer_types.is_empty() {
72 self.layer_types
73 .get(layer_idx)
74 .cloned()
75 .unwrap_or(LayerType::FullAttention)
76 } else if self.full_attention_interval > 0
77 && (layer_idx + 1).is_multiple_of(self.full_attention_interval)
78 {
79 LayerType::FullAttention
80 } else {
81 LayerType::LinearAttention
82 }
83 }
84
85 pub fn num_attention_layers(&self) -> usize {
89 if !self.layer_types.is_empty() {
90 self.layer_types.iter().filter(|t| t.is_attention()).count()
91 } else {
92 self.num_hidden_layers
93 .checked_div(self.full_attention_interval)
94 .unwrap_or(self.num_hidden_layers)
95 }
96 }
97
98 pub fn num_ssm_layers(&self) -> usize {
99 if !self.layer_types.is_empty() {
100 self.layer_types
101 .iter()
102 .filter(|t| **t == LayerType::LinearAttention)
103 .count()
104 } else {
105 self.num_hidden_layers - self.num_attention_layers()
106 }
107 }
108
109 pub fn has_recurrent_state(&self) -> bool {
113 self.num_ssm_layers() > 0
114 }
115
116 pub fn has_experts(&self) -> bool {
117 self.num_experts > 0
118 }
119
120 pub fn rotary_dim(&self) -> usize {
124 if self.attn_position_encoding == Some(AttnPositionEncoding::None) {
125 0
126 } else if self.rotary_dim > 0 {
127 self.rotary_dim
128 } else {
129 (self.partial_rotary_factor * self.head_dim as f64) as usize
130 }
131 }
132
133 pub fn ssm_qkvz_size(&self) -> usize {
134 let q = self.linear_num_key_heads * self.linear_key_head_dim;
135 let k = self.linear_num_key_heads * self.linear_key_head_dim;
136 let v = self.linear_num_value_heads * self.linear_value_head_dim;
137 let z = self.linear_num_value_heads * self.linear_value_head_dim;
138 q + k + v + z
139 }
140
141 pub fn ssm_qkv_size(&self) -> usize {
142 let q = self.linear_num_key_heads * self.linear_key_head_dim;
143 let k = self.linear_num_key_heads * self.linear_key_head_dim;
144 let v = self.linear_num_value_heads * self.linear_value_head_dim;
145 q + k + v
146 }
147
148 pub fn ssm_z_size(&self) -> usize {
149 self.linear_num_value_heads * self.linear_value_head_dim
150 }
151
152 pub fn ssm_ba_size(&self) -> usize {
153 self.linear_num_value_heads * 2
154 }
155
156 pub fn local_expert_range(&self) -> (usize, usize) {
159 if self.ep_world_size <= 1 {
160 return (0, self.num_experts);
161 }
162 let per_rank = self.num_experts / self.ep_world_size;
163 let start = self.ep_rank * per_rank;
164 let end = if self.ep_rank == self.ep_world_size - 1 {
165 self.num_experts
166 } else {
167 start + per_rank
168 };
169 (start, end)
170 }
171
172 pub fn is_local_expert(&self, expert_id: usize) -> bool {
173 let (start, end) = self.local_expert_range();
174 expert_id >= start && expert_id < end
175 }
176
177 pub fn tp_shard_range(&self, total: usize) -> (usize, usize) {
181 if self.tp_world_size <= 1 {
182 return (0, total);
183 }
184 debug_assert!(
185 total.is_multiple_of(self.tp_world_size),
186 "tp_shard_range: total={} not divisible by tp_world_size={}",
187 total,
188 self.tp_world_size,
189 );
190 let per_rank = total / self.tp_world_size;
191 let start = self.tp_rank * per_rank;
192 (start, start + per_rank)
193 }
194
195 pub fn tp_shard_dim(&self, total: usize) -> usize {
196 if self.tp_world_size <= 1 {
197 return total;
198 }
199 total / self.tp_world_size
200 }
201
202 pub fn layer_prefix(&self, layer_idx: usize) -> String {
205 if self.weight_prefix.is_empty() {
206 format!("model.layers.{layer_idx}")
207 } else {
208 format!("{}.layers.{layer_idx}", self.weight_prefix)
209 }
210 }
211
212 pub fn capabilities(&self) -> crate::capabilities::ModelCapabilities {
213 crate::capabilities::ModelCapabilities::from_config(self)
214 }
215
216 pub fn is_qwen35(&self) -> bool {
217 self.model_type == "qwen3_5_moe"
218 }
219
220 pub fn is_qwen35_dense(&self) -> bool {
221 self.model_type == "qwen3_5" && self.num_experts == 0
222 }
223
224 pub fn is_qwen3_vl(&self) -> bool {
225 if self.model_type == "qwen3_vl_moe" {
226 return true;
227 }
228 if self.model_type == "qwen3_5" && self.vision.is_some() {
229 return true;
230 }
231 false
232 }
233
234 pub fn skip_lm_head_quantization(&self) -> bool {
239 if let Some(force_bf16) = self.lm_head_bf16_override {
240 return force_bf16;
241 }
242 if self.kv_lora_rank > 0 {
243 return true;
244 }
245 if self.model_type == "laguna" {
246 return true;
247 }
248 if self.model_type == "gemma4" && self.num_experts == 0 {
249 return std::env::var("METRALE_GEMMA4_LMHEAD_NVFP4").ok().as_deref() != Some("1");
250 }
251 false
252 }
253
254 pub fn mamba2_d_inner(&self) -> usize {
255 self.mamba_num_heads * self.mamba_head_dim
256 }
257
258 pub fn mamba2_d_xbc(&self) -> usize {
259 self.mamba2_d_inner() + 2 * self.n_groups * self.ssm_state_size
260 }
261
262 pub fn mamba2_in_proj_size(&self) -> usize {
263 self.mamba2_d_inner() + self.mamba2_d_xbc() + self.mamba_num_heads
264 }
265
266 fn mamba2_geometry(&self) -> bool {
269 self.mamba_num_heads > 0 && self.mamba_head_dim > 0
270 }
271
272 pub fn has_mamba2_layers(&self) -> bool {
274 self.num_ssm_layers() > 0 && self.mamba2_geometry()
275 }
276
277 pub fn ssm_h_state_bytes(&self) -> usize {
278 if self.mamba2_geometry() {
279 self.mamba_num_heads * self.mamba_head_dim * self.ssm_state_size * 4
280 } else {
281 self.linear_num_value_heads * self.linear_value_head_dim * self.linear_key_head_dim * 4
282 }
283 }
284
285 pub fn ssm_conv_state_bytes(&self) -> usize {
286 let d_conv = self.linear_conv_kernel_dim;
287 if self.mamba2_geometry() {
288 self.mamba2_d_xbc() * d_conv * 4
289 } else {
290 let conv_dim = self.linear_num_key_heads * self.linear_key_head_dim * 2
291 + self.linear_num_value_heads * self.linear_value_head_dim;
292 conv_dim * d_conv * 4
293 }
294 }
295
296 pub fn ssm_state_norm_dims(&self) -> (usize, usize, usize) {
297 if self.mamba_num_heads > 0 && self.mamba_head_dim > 0 {
298 (
299 self.mamba_num_heads,
300 self.mamba_head_dim,
301 self.ssm_state_size,
302 )
303 } else {
304 (
305 self.linear_num_value_heads,
306 self.linear_key_head_dim,
307 self.linear_value_head_dim,
308 )
309 }
310 }
311
312 pub fn moe_input_size(&self) -> usize {
313 if self.moe_latent_size > 0 {
314 self.moe_latent_size
315 } else {
316 self.hidden_size
317 }
318 }
319
320 pub fn moe_intermediate_size_for(&self, layer: usize) -> usize {
323 self.moe_intermediate_sizes
324 .get(layer)
325 .copied()
326 .filter(|&s| s > 0)
327 .unwrap_or(self.moe_intermediate_size)
328 }
329
330 pub fn num_experts_per_tok_for(&self, layer: usize) -> usize {
333 self.num_experts_per_toks
334 .get(layer)
335 .copied()
336 .filter(|&k| k > 0)
337 .unwrap_or(self.num_experts_per_tok)
338 }
339
340 pub fn max_moe_intermediate_size(&self) -> usize {
341 self.moe_intermediate_sizes
342 .iter()
343 .copied()
344 .max()
345 .unwrap_or(0)
346 .max(self.moe_intermediate_size)
347 }
348
349 pub fn num_moe_layers(&self) -> usize {
350 self.layer_types
351 .iter()
352 .filter(|t| **t == LayerType::Moe)
353 .count()
354 }
355}