1use super::affine;
6use crate::model::{
7 CheckpointConventions, Preparation, TensorRole, describe_raw, qwen::tensor_role,
8};
9use crate::qwen4_exp::Qwen4ExpConfig;
10use crate::tensors::{Dtype, ModelWeights, TensorInfo};
11use anyhow::{Context, Result, ensure};
12use half::bf16;
13use memmap2::Advice;
14use std::collections::HashMap;
15use std::io::Write;
16use std::ops::Range;
17use std::path::Path;
18
19#[derive(Clone, Copy)]
20enum Part {
21 Weight,
22 Scale,
23 Bias,
24}
25
26#[derive(Clone, Copy)]
27struct Quantized {
28 group: usize,
29 part: Part,
30 segment: usize,
32 stride: usize,
33 start: usize,
34}
35
36#[derive(Clone, Copy)]
37enum Conversion {
38 Copy,
39 AddOne,
40 Quantize(Quantized),
41}
42
43pub(super) struct Tensor {
44 pub dtype: Dtype,
45 pub shape: Vec<usize>,
46 pub nbytes: usize,
47 source: TensorInfo,
48 conversion: Conversion,
49}
50
51pub(super) struct Source {
52 raw: ModelWeights,
53 pub tensors: HashMap<String, Tensor>,
54 pub config: Option<serde_json::Value>,
56}
57
58impl Source {
59 pub fn load(dir: &Path) -> Result<Self> {
60 let raw = ModelWeights::load_raw(dir)?;
61 let config = serde_json::from_slice(&std::fs::read(dir.join("config.json"))?)?;
62 let description = describe_raw(&raw.checkpoint, &config)?;
63
64 if let Preparation::Unsupported { reasons } = description.preparation() {
65 anyhow::bail!(
66 "unsupported checkpoint: {}",
67 serde_json::to_string(&reasons)?
68 );
69 }
70
71 let native = matches!(
72 description.format.conventions,
73 CheckpointConventions::Qwen4ExpHuggingFace
74 );
75 let mut source = Self {
76 raw,
77 tensors: HashMap::new(),
78 config: None,
79 };
80
81 if !native {
82 for (name, info) in &source.raw.tensors {
83 source.tensors.insert(name.clone(), Tensor::copy(info));
84 }
85
86 return Ok(source);
87 }
88
89 let cfg = Qwen4ExpConfig::load(dir)?;
90 let tensors = source.raw.tensors.clone();
91
92 for (name, info) in tensors {
93 source
94 .import(&name, &info)
95 .with_context(|| format!("importing {name}"))?;
96 }
97
98 source.configure(dir, &cfg)?;
99 eprintln!("BF16 input: quantizing to affine Q4 while packing");
100
101 Ok(source)
102 }
103
104 pub fn tensor(&self, name: &str) -> Result<&Tensor> {
105 self.tensors
106 .get(name)
107 .with_context(|| format!("tensor {name:?} not found"))
108 }
109
110 pub fn estimated_bytes(&self) -> u64 {
111 self.tensors.values().map(|t| t.nbytes as u64).sum()
112 }
113
114 pub fn prefetch(&self, tensor: &Tensor) {
115 if self.config.is_some() {
117 return;
118 }
119
120 let t = &tensor.source;
121 let _ = self
122 .raw
123 .checkpoint
124 .objects
125 .mapping(crate::model::ObjectId(t.shard))
126 .expect("validated source object")
127 .advise_range(Advice::WillNeed, t.offset, t.nbytes);
128 }
129
130 pub fn bytes(&self, tensor: &Tensor) -> Result<&[u8]> {
131 ensure!(
132 matches!(tensor.conversion, Conversion::Copy),
133 "tensor requires conversion"
134 );
135
136 Ok(self.raw.tensor_bytes(&tensor.source))
137 }
138
139 pub fn write(&self, tensor: &Tensor, range: Range<usize>, out: &mut impl Write) -> Result<()> {
140 ensure!(
141 range.start <= range.end && range.end <= tensor.nbytes,
142 "tensor write out of bounds"
143 );
144
145 let bytes = self.raw.tensor_bytes(&tensor.source);
146
147 match tensor.conversion {
148 Conversion::Copy => out.write_all(&bytes[range])?,
149 Conversion::AddOne => write_norm(bytes, range, out)?,
150 Conversion::Quantize(q) => q.write(bytes, range, out)?,
151 }
152
153 Ok(())
154 }
155
156 fn insert(&mut self, name: String, tensor: Tensor) -> Result<()> {
157 ensure!(
158 !self.tensors.contains_key(&name),
159 "duplicate normalized tensor {name}"
160 );
161 self.tensors.insert(name, tensor);
162
163 Ok(())
164 }
165
166 fn import(&mut self, name: &str, info: &TensorInfo) -> Result<()> {
167 let Some(name) = normalized_name(name) else {
168 return Ok(());
169 };
170
171 ensure!(
172 matches!(info.dtype, Dtype::BF16 | Dtype::I64),
173 "native import expects BF16 weights or I64 metadata"
174 );
175 ensure!(info.shape.iter().all(|&n| n > 0), "empty tensor");
176
177 if let Some(prefix) = name.strip_suffix(".experts.gate_up_proj") {
178 return self.fused_experts(prefix, info);
179 }
180
181 if let Some(prefix) = name.strip_suffix(".experts.down_proj") {
182 ensure!(
183 info.shape.len() == 3,
184 "expected [experts, hidden, intermediate]"
185 );
186
187 return self.quantized(
188 &format!("{prefix}.switch_mlp.down_proj"),
189 info,
190 &info.shape,
191 0,
192 1,
193 );
194 }
195
196 if quantized_weight(&name, info) {
197 return self.quantized(name.trim_end_matches(".weight"), info, &info.shape, 0, 1);
198 }
199
200 let mut tensor = Tensor::copy(info);
201
202 if folded_norm(&name) {
203 ensure!(
204 info.dtype == Dtype::BF16 && info.shape.len() == 1,
205 "expected BF16 norm vector"
206 );
207
208 tensor.conversion = Conversion::AddOne;
209 }
210
211 self.insert(name, tensor)
212 }
213
214 fn fused_experts(&mut self, prefix: &str, info: &TensorInfo) -> Result<()> {
215 ensure!(
216 info.shape.len() == 3 && info.shape[1].is_multiple_of(2),
217 "expected [experts, 2 * intermediate, hidden]"
218 );
219
220 let shape = [info.shape[0], info.shape[1] / 2, info.shape[2]];
221
222 for (half, projection) in ["gate_proj", "up_proj"].iter().enumerate() {
223 self.quantized(
224 &format!("{prefix}.switch_mlp.{projection}"),
225 info,
226 &shape,
227 half,
228 2,
229 )?;
230 }
231
232 Ok(())
233 }
234
235 fn quantized(
236 &mut self,
237 prefix: &str,
238 info: &TensorInfo,
239 shape: &[usize],
240 half: usize,
241 halves: usize,
242 ) -> Result<()> {
243 ensure!(info.dtype == Dtype::BF16, "expected BF16 matrix");
244
245 let width = *shape.last().context("matrix shape missing")?;
246 let group = if prefix.contains(".ngram_embedding.") {
247 [64, 32, 16, 8]
248 .into_iter()
249 .find(|g| width.is_multiple_of(*g))
250 .context("n-gram width must be divisible by 8")?
251 } else {
252 ensure!(
253 width.is_multiple_of(64),
254 "matrix width {width} must be divisible by 64"
255 );
256
257 64
258 };
259 let segment = shape[shape.len() - 2] * width;
260
261 for (suffix, part, dtype, divisor) in [
262 ("weight", Part::Weight, Dtype::U32, 8),
263 ("scales", Part::Scale, Dtype::BF16, group),
264 ("biases", Part::Bias, Dtype::BF16, group),
265 ] {
266 let mut output_shape = shape.to_vec();
267 *output_shape.last_mut().unwrap() /= divisor;
268 let nbytes = output_shape.iter().product::<usize>() * dtype.size();
269 let tensor = Tensor {
270 dtype,
271 shape: output_shape,
272 nbytes,
273 source: info.clone(),
274 conversion: Conversion::Quantize(Quantized {
275 group,
276 part,
277 segment,
278 stride: segment * halves,
279 start: segment * half,
280 }),
281 };
282
283 self.insert(format!("{prefix}.{suffix}"), tensor)?;
284 }
285
286 Ok(())
287 }
288
289 fn configure(&mut self, dir: &Path, cfg: &Qwen4ExpConfig) -> Result<()> {
290 let mut config: serde_json::Value =
291 serde_json::from_slice(&std::fs::read(dir.join("config.json"))?)?;
292 let has_mtp = self.tensors.keys().any(|name| name.starts_with("mtp."));
293
294 if has_mtp {
295 ensure!(
296 cfg.mtp_num_hidden_layers > 0,
297 "MTP weights present but config has no MTP layers"
298 );
299 self.tensor("mtp.fc_embedding.weight")?;
300 self.tensor("mtp.fc_hidden.weight")?;
301 } else {
302 let text = if config.get("text_config").is_some() {
303 &mut config["text_config"]
304 } else {
305 &mut config
306 };
307 text["mtp_num_hidden_layers"] = 0.into();
308
309 if let Some(mtp) = text.get_mut("mtp") {
310 mtp["num_hidden_layers"] = 0.into();
311 }
312
313 eprintln!("no MTP weights found; the packed model will use ordinary decode");
314 }
315
316 self.config = Some(config);
317
318 Ok(())
319 }
320}
321
322impl Tensor {
323 fn copy(source: &TensorInfo) -> Self {
324 Self {
325 dtype: source.dtype,
326 shape: source.shape.clone(),
327 nbytes: source.nbytes,
328 source: source.clone(),
329 conversion: Conversion::Copy,
330 }
331 }
332}
333
334impl Quantized {
335 fn write(self, source: &[u8], range: Range<usize>, out: &mut impl Write) -> Result<()> {
336 let size = match self.part {
337 Part::Weight => self.group / 2,
338 _ => 2,
339 };
340
341 ensure!(
342 range.start.is_multiple_of(size) && range.end.is_multiple_of(size),
343 "unaligned quantization group"
344 );
345
346 for index in range.start / size..range.end / size {
347 let element = index * self.group;
348 let offset =
349 (self.start + element / self.segment * self.stride + element % self.segment) * 2;
350 let bytes = source
351 .get(offset..offset + self.group * 2)
352 .context("fused tensor range out of bounds")?;
353 let group = affine::quantize(bytes)?;
354 let data = match self.part {
355 Part::Weight => &group.weight[..size],
356 Part::Scale => &group.scale,
357 Part::Bias => &group.bias,
358 };
359
360 out.write_all(data)?;
361 }
362
363 Ok(())
364 }
365}
366
367fn normalized_name(name: &str) -> Option<String> {
368 if let Some(suffix) = name.strip_prefix("model.language_model.") {
369 return Some(format!("language_model.model.{suffix}"));
370 }
371
372 if name == "lm_head.weight" {
373 return Some("language_model.lm_head.weight".into());
374 }
375
376 if name.starts_with("mtp.") {
377 return Some(name.into());
378 }
379
380 if name.starts_with("model.visual.") || name.starts_with("vision_tower.") {
381 return None;
382 }
383
384 name.strip_prefix("model.")
386 .map(|suffix| format!("language_model.model.{suffix}"))
387}
388
389fn quantized_weight(name: &str, info: &TensorInfo) -> bool {
390 matches!(
391 tensor_role(name, info.shape.len()),
392 TensorRole::Projection | TensorRole::Embedding | TensorRole::NgramEmbedding
393 )
394}
395
396fn folded_norm(name: &str) -> bool {
397 [
398 "hc_norm",
399 "q_norm",
400 "k_norm",
401 "q_layernorm",
402 "k_layernorm",
403 "norm_key",
404 "norm_query",
405 "norm_conv",
406 ]
407 .iter()
408 .any(|norm| name.ends_with(&format!(".{norm}.weight")))
409}
410
411fn write_norm(bytes: &[u8], range: Range<usize>, out: &mut impl Write) -> Result<()> {
412 ensure!(
413 range.start.is_multiple_of(2) && range.end.is_multiple_of(2),
414 "unaligned BF16 norm"
415 );
416
417 for pair in bytes[range].as_chunks::<2>().0 {
418 let value = bf16::from_bits(u16::from_le_bytes(*pair)).to_f32();
419
420 ensure!(value.is_finite(), "non-finite norm weight");
421 out.write_all(&bf16::from_f32(1.0 + value).to_le_bytes())?;
422 }
423
424 Ok(())
425}
426
427#[cfg(test)]
428#[path = "../../../tests/unit/qwen4_exp/pack/source.rs"]
429mod tests;