1use crate::Device;
26use crate::lazy::Tensor;
27use fuel_ir::{Result, Shape};
28use fuel_quantized::awq_repack::repack_hf_awq_for_kernel;
29use half::f16;
30use safetensors::tensor::TensorView;
31use std::sync::Arc;
32
33pub struct AwqWeight {
37 pub weight: Tensor,
39 pub zeros: Tensor,
41 pub scales: Tensor,
43 pub oc: usize,
45 pub ic: usize,
47 pub group_size: usize,
49}
50
51impl AwqWeight {
52 pub fn matmul(&self, activations: &Tensor, split_k_iters: i32) -> Result<Tensor> {
63 if split_k_iters <= 0 {
64 return Err(fuel_ir::Error::Msg(format!(
65 "AwqWeight::matmul: split_k_iters={split_k_iters} must be positive"
66 ))
67 .bt());
68 }
69 Ok(Tensor::from_graph_tensor(
70 activations.graph_tensor().awq_matmul(
71 self.weight.graph_tensor(),
72 self.zeros.graph_tensor(),
73 self.scales.graph_tensor(),
74 self.group_size,
75 split_k_iters,
76 ),
77 ))
78 }
79}
80
81pub fn awq_from_hf_buffers(
84 qweight: &[u32],
85 qzeros: &[u32],
86 scales: &[f16],
87 ic: usize,
88 oc: usize,
89 group_size: usize,
90 device: &Device,
91) -> Result<AwqWeight> {
92 let b = repack_hf_awq_for_kernel(qweight, qzeros, scales, ic, oc, group_size)
93 .map_err(|e| fuel_ir::Error::Msg(format!("awq_from_hf_buffers: {e}")).bt())?;
94 let anchor = Tensor::from_f32(vec![0.0_f32], Shape::from_dims(&[1]), device)?;
95 let g = anchor.graph_tensor();
96 let weight = g.const_u32_like(b.weight, Shape::from_dims(&[oc, ic / 8]))?;
97 let zeros = g.const_u32_like(b.zeros, Shape::from_dims(&[oc, b.zeros_w]))?;
98 let scales = g.const_f16_like(b.scales, Shape::from_dims(&[oc, 8 * b.zeros_w]))?;
99 Ok(AwqWeight {
100 weight: Tensor::from_graph_tensor(weight),
101 zeros: Tensor::from_graph_tensor(zeros),
102 scales: Tensor::from_graph_tensor(scales),
103 oc,
104 ic,
105 group_size,
106 })
107}
108
109fn read_u32_words(view: TensorView<'_>, key: &str, rows: usize, cols: usize) -> Result<Vec<u32>> {
110 if !matches!(
111 view.dtype(),
112 safetensors::Dtype::I32 | safetensors::Dtype::U32
113 ) {
114 return Err(fuel_ir::Error::Msg(format!(
115 "load_awq_layer: {key:?} dtype={:?}, expected I32 or U32",
116 view.dtype()
117 ))
118 .bt());
119 }
120 if view.shape() != [rows, cols] {
121 return Err(fuel_ir::Error::Msg(format!(
122 "load_awq_layer: {key:?} shape={:?}, expected [{rows}, {cols}]",
123 view.shape()
124 ))
125 .bt());
126 }
127 Ok(view
129 .data()
130 .as_chunks::<4>()
131 .0
132 .iter()
133 .map(|c| u32::from_le_bytes(*c))
134 .collect())
135}
136
137fn read_f16_scales(view: TensorView<'_>, key: &str, groups: usize, oc: usize) -> Result<Vec<f16>> {
138 if view.dtype() != safetensors::Dtype::F16 {
139 return Err(fuel_ir::Error::Msg(format!(
140 "load_awq_layer: {key:?} dtype={:?}, expected F16",
141 view.dtype()
142 ))
143 .bt());
144 }
145 if view.shape() != [groups, oc] {
146 return Err(fuel_ir::Error::Msg(format!(
147 "load_awq_layer: {key:?} shape={:?}, expected [{groups}, {oc}]",
148 view.shape()
149 ))
150 .bt());
151 }
152 Ok(view
153 .data()
154 .as_chunks::<2>()
155 .0
156 .iter()
157 .map(|c| f16::from_bits(u16::from_le_bytes(*c)))
158 .collect())
159}
160
161pub fn load_awq_layer(
167 st: &safetensors::SafeTensors,
168 prefix: &str,
169 ic: usize,
170 oc: usize,
171 group_size: usize,
172 device: &Device,
173) -> Result<AwqWeight> {
174 let (qweight, qzeros, scales) = read_hf_layer(
175 |key| {
176 st.tensor(key).map_err(|e| {
177 fuel_ir::Error::Msg(format!(
178 "load_awq_layer: tensor {key:?} not found in safetensors: {e}"
179 ))
180 .bt()
181 })
182 },
183 prefix,
184 ic,
185 oc,
186 group_size,
187 )?;
188 awq_from_hf_buffers(&qweight, &qzeros, &scales, ic, oc, group_size, device)
189}
190
191fn check_geometry(ic: usize, oc: usize, group_size: usize) -> Result<()> {
192 if group_size == 0 || !ic.is_multiple_of(group_size) || !oc.is_multiple_of(8) {
193 return Err(fuel_ir::Error::Msg(format!(
194 "load_awq_layer: ic={ic} must be a multiple of group_size={group_size} \
195 and oc={oc} a multiple of 8"
196 ))
197 .bt());
198 }
199 Ok(())
200}
201
202fn read_hf_layer<'a>(
206 get: impl Fn(&str) -> Result<TensorView<'a>>,
207 prefix: &str,
208 ic: usize,
209 oc: usize,
210 group_size: usize,
211) -> Result<(Vec<u32>, Vec<u32>, Vec<f16>)> {
212 check_geometry(ic, oc, group_size)?;
213 let groups = ic / group_size;
214 let (kw, kz, ks) = (
215 format!("{prefix}.qweight"),
216 format!("{prefix}.qzeros"),
217 format!("{prefix}.scales"),
218 );
219 let qweight = read_u32_words(get(&kw)?, &kw, ic, oc / 8)?;
220 let qzeros = read_u32_words(get(&kz)?, &kz, groups, oc / 8)?;
221 let scales = read_f16_scales(get(&ks)?, &ks, groups, oc)?;
222 Ok((qweight, qzeros, scales))
223}
224
225#[derive(Debug, Clone)]
230pub struct AwqHostWeight {
231 pub weight: Arc<[u32]>,
233 pub zeros: Arc<[u32]>,
235 pub scales: Arc<[f16]>,
237 pub zeros_w: usize,
239 pub oc: usize,
241 pub ic: usize,
243 pub group_size: usize,
245}
246
247impl AwqHostWeight {
248 pub fn from_hf_buffers(
251 qweight: &[u32],
252 qzeros: &[u32],
253 scales: &[f16],
254 ic: usize,
255 oc: usize,
256 group_size: usize,
257 ) -> Result<Self> {
258 let b =
259 repack_hf_awq_for_kernel(qweight, qzeros, scales, ic, oc, group_size).map_err(|e| {
260 fuel_ir::Error::Msg(format!("AwqHostWeight::from_hf_buffers: {e}")).bt()
261 })?;
262 Ok(Self {
263 weight: Arc::from(b.weight),
264 zeros: Arc::from(b.zeros),
265 scales: Arc::from(b.scales),
266 zeros_w: b.zeros_w,
267 oc,
268 ic,
269 group_size,
270 })
271 }
272
273 pub fn load<'a>(
276 get: impl Fn(&str) -> Result<TensorView<'a>>,
277 prefix: &str,
278 ic: usize,
279 oc: usize,
280 group_size: usize,
281 ) -> Result<Self> {
282 let (qweight, qzeros, scales) = read_hf_layer(get, prefix, ic, oc, group_size)?;
283 Self::from_hf_buffers(&qweight, &qzeros, &scales, ic, oc, group_size)
284 }
285
286 pub fn matmul(&self, activations: &Tensor, split_k_iters: i32) -> Result<Tensor> {
291 if split_k_iters <= 0 {
292 return Err(fuel_ir::Error::Msg(format!(
293 "AwqHostWeight::matmul: split_k_iters={split_k_iters} must be positive"
294 ))
295 .bt());
296 }
297 let g = activations.graph_tensor();
298 let weight = g.const_u32_like(
299 Arc::clone(&self.weight),
300 Shape::from_dims(&[self.oc, self.ic / 8]),
301 )?;
302 let zeros = g.const_u32_like(
303 Arc::clone(&self.zeros),
304 Shape::from_dims(&[self.oc, self.zeros_w]),
305 )?;
306 let scales = g.const_f16_like(
307 Arc::clone(&self.scales),
308 Shape::from_dims(&[self.oc, 8 * self.zeros_w]),
309 )?;
310 Ok(Tensor::from_graph_tensor(g.awq_matmul(
311 &weight,
312 &zeros,
313 &scales,
314 self.group_size,
315 split_k_iters,
316 )))
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323 use fuel_quantized::awq_repack::HF_AWQ_PACK_ORDER;
324 use safetensors::tensor::TensorView;
325 use std::collections::HashMap;
326
327 const IC: usize = 128;
328 const OC: usize = 64;
329 const GROUP: usize = 64;
330
331 struct Logical {
332 q: Vec<u8>, z: Vec<u8>, s: Vec<f16>,
335 }
336
337 fn logical() -> Logical {
338 let groups = IC / GROUP;
339 let q = (0..IC * OC).map(|i| ((i * 7 + i / 5) % 16) as u8).collect();
340 let z = (0..groups * OC).map(|i| ((i * 3 + 5) % 16) as u8).collect();
341 let s = (0..groups * OC)
342 .map(|i| f16::from_f32(0.02 + 0.005 * (i % 11) as f32))
343 .collect();
344 Logical { q, z, s }
345 }
346
347 fn pack_hf(codes: &[u8], rows: usize, oc: usize) -> Vec<u8> {
350 let mut out = Vec::with_capacity(rows * oc / 2);
351 for r in 0..rows {
352 for col in 0..oc / 8 {
353 let mut w = 0u32;
354 for i in 0..8 {
355 w |= u32::from(codes[r * oc + col * 8 + HF_AWQ_PACK_ORDER[i]]) << (i * 4);
356 }
357 out.extend_from_slice(&w.to_le_bytes());
358 }
359 }
360 out
361 }
362
363 fn checkpoint(l: &Logical, qdtype: safetensors::Dtype, sdtype: safetensors::Dtype) -> Vec<u8> {
364 let groups = IC / GROUP;
365 let qw = pack_hf(&l.q, IC, OC);
366 let qz = pack_hf(&l.z, groups, OC);
367 let sc: Vec<u8> = l.s.iter().flat_map(|h| h.to_bits().to_le_bytes()).collect();
368 let mut t: HashMap<String, TensorView<'_>> = HashMap::new();
369 t.insert(
370 "l.qweight".into(),
371 TensorView::new(qdtype, vec![IC, OC / 8], &qw).unwrap(),
372 );
373 t.insert(
374 "l.qzeros".into(),
375 TensorView::new(qdtype, vec![groups, OC / 8], &qz).unwrap(),
376 );
377 t.insert(
378 "l.scales".into(),
379 TensorView::new(sdtype, vec![groups, OC], &sc).unwrap(),
380 );
381 safetensors::serialize(&t, None::<HashMap<String, String>>).unwrap()
382 }
383
384 fn reference(l: &Logical, x: &[f32], m: usize) -> Vec<f64> {
386 let mut y = vec![0.0f64; m * OC];
387 for r in 0..m {
388 for o in 0..OC {
389 for i in 0..IC {
390 let g = i / GROUP;
391 let w = (f64::from(l.q[i * OC + o]) - f64::from(l.z[g * OC + o]))
392 * f64::from(l.s[g * OC + o].to_f32());
393 y[r * OC + o] += f64::from(x[r * IC + i]) * w;
394 }
395 }
396 }
397 y
398 }
399
400 fn run(bytes: &[u8], x: &[f32], m: usize) -> Vec<f32> {
401 let st = safetensors::SafeTensors::deserialize(bytes).unwrap();
402 let w = load_awq_layer(&st, "l", IC, OC, GROUP, &Device::cpu()).unwrap();
403 assert_eq!((w.oc, w.ic, w.group_size), (OC, IC, GROUP));
404 let act = Tensor::from_graph_tensor(
405 w.weight
406 .graph_tensor()
407 .const_f32_like(x.to_vec(), Shape::from_dims(&[m, IC]))
408 .unwrap(),
409 );
410 let y = w.matmul(&act, 8).unwrap();
413 let graph = y.graph_tensor().graph().clone();
414 let roots = fuel_graph::opt::RuleRegistry::lowering_only()
415 .optimize_to_fixpoint(&graph, &[y.graph_tensor().id()]);
416 crate::pipelined_bridge::realize_one_as::<f32>(&graph, roots[0], &Device::cpu())
417 .expect("realize lowered awq_matmul")
418 }
419
420 fn host_weight(bytes: &[u8]) -> AwqHostWeight {
421 let st = safetensors::SafeTensors::deserialize(bytes).unwrap();
422 AwqHostWeight::load(
423 |k| {
424 st.tensor(k)
425 .map_err(|e| fuel_ir::Error::Msg(format!("{k}: {e}")).bt())
426 },
427 "l",
428 IC,
429 OC,
430 GROUP,
431 )
432 .unwrap()
433 }
434
435 fn realize_lowered(y: &Tensor) -> Vec<f32> {
436 let graph = y.graph_tensor().graph().clone();
437 let roots = fuel_graph::opt::RuleRegistry::lowering_only()
438 .optimize_to_fixpoint(&graph, &[y.graph_tensor().id()]);
439 crate::pipelined_bridge::realize_one_as::<f32>(&graph, roots[0], &Device::cpu())
440 .expect("realize lowered awq_matmul")
441 }
442
443 #[test]
447 fn weight_storage_awq_apply_linear_matches_the_reference() {
448 use crate::lazy::WeightStorage;
449 let l = logical();
450 let w = WeightStorage::Awq(Arc::new(host_weight(&checkpoint(
451 &l,
452 safetensors::Dtype::I32,
453 safetensors::Dtype::F16,
454 ))));
455 assert_eq!(w.elem_count(), IC * OC);
456 let m = 3;
457 let x: Vec<f32> = (0..m * IC)
458 .map(|i| ((i * 5) % 17) as f32 * 0.1 - 0.8)
459 .collect();
460 let anchor = Tensor::from_f32(vec![0.0], Shape::from_dims(&[1]), &Device::cpu()).unwrap();
461 let act = anchor
463 .const_f32_like(x.clone(), Shape::from_dims(&[1, m, IC]))
464 .unwrap();
465 let got = realize_lowered(&w.apply_linear(&act, IC, OC).unwrap());
466 let want = reference(&l, &x, m);
467 assert_eq!(got.len(), m * OC);
468 for (g, w) in got.iter().zip(&want) {
469 assert!(
470 (f64::from(*g) - w).abs() <= 1e-3 * w.abs().max(1.0),
471 "{g} vs {w}"
472 );
473 }
474 assert!(w.apply_linear(&act, IC, OC + 8).is_err());
477 assert!(w.const_like(&act, Shape::from_dims(&[IC, OC])).is_err());
478 }
479
480 #[test]
481 fn host_weight_refuses_a_non_positive_split_k() {
482 let l = logical();
483 let w = host_weight(&checkpoint(
484 &l,
485 safetensors::Dtype::I32,
486 safetensors::Dtype::F16,
487 ));
488 let anchor = Tensor::from_f32(vec![0.0], Shape::from_dims(&[1]), &Device::cpu()).unwrap();
489 let act = anchor
490 .const_f32_like(vec![0.0; IC], Shape::from_dims(&[1, IC]))
491 .unwrap();
492 assert!(w.matmul(&act, 0).is_err());
493 assert!(w.matmul(&act, -1).is_err());
494 assert!(
495 w.matmul(&act, 1).is_ok(),
496 "control: a positive value is accepted"
497 );
498 }
499
500 #[test]
501 fn loaded_layer_matches_the_dequantise_then_matmul_reference() {
502 let l = logical();
503 let m = 3;
504 let x: Vec<f32> = (0..m * IC)
505 .map(|i| ((i * 5) % 17) as f32 * 0.1 - 0.8)
506 .collect();
507 let want = reference(&l, &x, m);
508 for qd in [safetensors::Dtype::I32, safetensors::Dtype::U32] {
509 let got = run(&checkpoint(&l, qd, safetensors::Dtype::F16), &x, m);
510 assert_eq!(got.len(), m * OC);
511 let max_err = got
512 .iter()
513 .zip(&want)
514 .map(|(g, w)| (f64::from(*g) - w).abs())
515 .fold(0.0, f64::max);
516 let scale = want.iter().fold(0.0f64, |a, w| a.max(w.abs()));
517 assert!(
518 max_err <= 1e-3 * scale.max(1.0),
519 "{qd:?}: max_err={max_err} scale={scale}"
520 );
521 }
522 }
523
524 #[test]
528 fn a_sequentially_read_checkpoint_disagrees_with_the_reference() {
529 let l = logical();
530 let x: Vec<f32> = (0..IC).map(|i| ((i * 5) % 17) as f32 * 0.1 - 0.8).collect();
531 let want = reference(&l, &x, 1);
532 let groups = IC / GROUP;
535 let seq = |codes: &[u8], rows: usize| -> Vec<u8> {
536 let mut out = Vec::new();
537 for r in 0..rows {
538 for col in 0..OC / 8 {
539 let mut w = 0u32;
540 for i in 0..8 {
541 w |= u32::from(codes[r * OC + col * 8 + i]) << (i * 4);
542 }
543 out.extend_from_slice(&w.to_le_bytes());
544 }
545 }
546 out
547 };
548 let (qw, qz) = (seq(&l.q, IC), seq(&l.z, groups));
549 let sc: Vec<u8> = l.s.iter().flat_map(|h| h.to_bits().to_le_bytes()).collect();
550 let mut t: HashMap<String, TensorView<'_>> = HashMap::new();
551 t.insert(
552 "l.qweight".into(),
553 TensorView::new(safetensors::Dtype::I32, vec![IC, OC / 8], &qw).unwrap(),
554 );
555 t.insert(
556 "l.qzeros".into(),
557 TensorView::new(safetensors::Dtype::I32, vec![groups, OC / 8], &qz).unwrap(),
558 );
559 t.insert(
560 "l.scales".into(),
561 TensorView::new(safetensors::Dtype::F16, vec![groups, OC], &sc).unwrap(),
562 );
563 let bytes = safetensors::serialize(&t, None::<HashMap<String, String>>).unwrap();
564 let got = run(&bytes, &x, 1);
565 let max_err = got
566 .iter()
567 .zip(&want)
568 .map(|(g, w)| (f64::from(*g) - w).abs())
569 .fold(0.0, f64::max);
570 assert!(max_err > 1e-2, "sequential read matched: max_err={max_err}");
571 }
572
573 #[test]
574 fn wrong_dtype_shape_and_missing_tensor_are_refused() {
575 let l = logical();
576 let ok = checkpoint(&l, safetensors::Dtype::I32, safetensors::Dtype::F16);
577 let st = safetensors::SafeTensors::deserialize(&ok).unwrap();
578 assert!(load_awq_layer(&st, "l", IC, OC * 2, GROUP, &Device::cpu()).is_err());
580 assert!(load_awq_layer(&st, "l", IC, OC, 128, &Device::cpu()).is_err());
581 assert!(load_awq_layer(&st, "nope", IC, OC, GROUP, &Device::cpu()).is_err());
583 assert!(load_awq_layer(&st, "l", IC, OC, 0, &Device::cpu()).is_err());
585 let bad = checkpoint(&l, safetensors::Dtype::I32, safetensors::Dtype::BF16);
587 let st = safetensors::SafeTensors::deserialize(&bad).unwrap();
588 let e = load_awq_layer(&st, "l", IC, OC, GROUP, &Device::cpu()).err();
589 assert!(e.is_some_and(|e| format!("{e}").contains("expected F16")));
590 }
591 #[test]
592 fn non_positive_split_k_iters_is_a_typed_error() {
593 let l = logical();
594 let bytes = checkpoint(&l, safetensors::Dtype::I32, safetensors::Dtype::F16);
595 let st = safetensors::SafeTensors::deserialize(&bytes).unwrap();
596 let w = load_awq_layer(&st, "l", IC, OC, GROUP, &Device::cpu()).unwrap();
597 let act = Tensor::from_graph_tensor(
598 w.weight
599 .graph_tensor()
600 .const_f32_like(vec![0.0f32; IC], Shape::from_dims(&[1, IC]))
601 .unwrap(),
602 );
603 for bad in [0, -1, i32::MIN] {
604 let Err(e) = w.matmul(&act, bad) else {
605 panic!("split_k_iters={bad} must be refused");
606 };
607 assert!(format!("{e}").contains("split_k_iters"), "{e}");
608 }
609 assert!(
610 w.matmul(&act, 1).is_ok(),
611 "control: a positive value is accepted"
612 );
613 }
614
615 fn checkpoint_file() -> Option<std::path::PathBuf> {
631 use fuel_test_support::hardware::{Hardware, Missing, skip};
632 let Some(dir) = std::env::var_os("FUEL_QWEN3_AWQ_DIR") else {
633 skip(
634 Hardware::Hub,
635 Missing::device(
636 "FUEL_QWEN3_AWQ_DIR is not set (Orion-zhen/Qwen3-0.6B-AWQ, GPL-3.0, never committed)",
637 ),
638 );
639 return None;
640 };
641 let file = std::path::Path::new(&dir).join("model.safetensors");
642 if !file.is_file() {
643 skip(
644 Hardware::Hub,
645 Missing::device(format!("{} does not exist", file.display())),
646 );
647 return None;
648 }
649 Some(file)
650 }
651
652 fn cut(st: &safetensors::SafeTensors<'_>, name: &str, rows: usize, cols: usize) -> Vec<u8> {
654 let view = st.tensor(name).expect(name);
655 let elem = view.dtype().bitsize() / 8;
656 let stride = view.shape()[1] * elem;
657 let data = view.data();
658 let mut out = Vec::with_capacity(rows * cols * elem);
659 for r in 0..rows {
660 out.extend_from_slice(&data[r * stride..r * stride + cols * elem]);
661 }
662 out
663 }
664
665 fn real_slice_bytes() -> Option<Vec<u8>> {
668 let file = checkpoint_file()?;
669 let bytes = std::fs::read(&file).expect("read model.safetensors");
670 let st = safetensors::SafeTensors::deserialize(&bytes).expect("parse model.safetensors");
671 let p = "model.layers.0.mlp.gate_proj";
672 let qw = cut(&st, &format!("{p}.qweight"), 256, 8);
673 let qz = cut(&st, &format!("{p}.qzeros"), 2, 8);
674 let sc = cut(&st, &format!("{p}.scales"), 2, 64);
675 let mut t: HashMap<String, TensorView<'_>> = HashMap::new();
676 let i32s = safetensors::Dtype::I32;
677 t.insert(
678 "l.qweight".into(),
679 TensorView::new(i32s, vec![256, 8], &qw).unwrap(),
680 );
681 t.insert(
682 "l.qzeros".into(),
683 TensorView::new(i32s, vec![2, 8], &qz).unwrap(),
684 );
685 t.insert(
686 "l.scales".into(),
687 TensorView::new(safetensors::Dtype::F16, vec![2, 64], &sc).unwrap(),
688 );
689 Some(safetensors::serialize(&t, None::<HashMap<String, String>>).unwrap())
690 }
691
692 fn real_slice() -> Option<(Vec<u8>, Vec<u8>, Vec<f16>)> {
693 let bytes = real_slice_bytes()?;
694 let st = safetensors::SafeTensors::deserialize(&bytes).unwrap();
695 let words = |k: &str| -> Vec<u32> {
696 st.tensor(k)
697 .unwrap()
698 .data()
699 .as_chunks::<4>()
700 .0
701 .iter()
702 .map(|c| u32::from_le_bytes(*c))
703 .collect()
704 };
705 let unpack = |w: &[u32], rows: usize| -> Vec<u8> {
706 let mut out = vec![0u8; rows * 64];
707 for r in 0..rows {
708 for col in 0..8 {
709 for i in 0..8 {
710 out[r * 64 + col * 8 + HF_AWQ_PACK_ORDER[i]] =
711 ((w[r * 8 + col] >> (4 * i)) & 0xF) as u8;
712 }
713 }
714 }
715 out
716 };
717 let scales = st
718 .tensor("l.scales")
719 .unwrap()
720 .data()
721 .as_chunks::<2>()
722 .0
723 .iter()
724 .map(|c| f16::from_bits(u16::from_le_bytes(*c)))
725 .collect();
726 Some((
727 unpack(&words("l.qweight"), 256),
728 unpack(&words("l.qzeros"), 2),
729 scales,
730 ))
731 }
732
733 #[test]
734 fn real_checkpoint_zeros_are_stored_as_is_not_minus_one() {
735 let Some((q, z, _)) = real_slice() else {
736 return;
737 };
738 let mean_shift = |shift: f64| -> f64 {
739 let mut sum = 0.0;
740 for i in 0..256 {
741 for o in 0..64 {
742 sum += f64::from(q[i * 64 + o]) - (f64::from(z[(i / 128) * 64 + o]) + shift);
743 }
744 }
745 sum / (256.0 * 64.0)
746 };
747 let as_is = mean_shift(0.0);
748 assert!(as_is.abs() < 0.35, "mean(q - z) = {as_is}, expected ~0");
749 assert!(mean_shift(1.0) < -0.65, "plus-one reading should be ~-1");
753 assert!(mean_shift(-1.0) > 0.65, "minus-one reading should be ~+1");
754 }
755
756 #[test]
757 fn real_checkpoint_slice_loads_and_matches_the_as_is_reference() {
758 let Some((q, z, s)) = real_slice() else {
759 return;
760 };
761 let bytes = real_slice_bytes().expect("the slice was available a line ago");
762 let st = safetensors::SafeTensors::deserialize(&bytes).unwrap();
763 let w = load_awq_layer(&st, "l", 256, 64, 128, &Device::cpu()).unwrap();
764 let x: Vec<f32> = (0..256)
765 .map(|i| ((i * 5) % 17) as f32 * 0.1 - 0.8)
766 .collect();
767 let act = Tensor::from_graph_tensor(
768 w.weight
769 .graph_tensor()
770 .const_f32_like(x.clone(), Shape::from_dims(&[1, 256]))
771 .unwrap(),
772 );
773 let y = w.matmul(&act, 8).unwrap();
774 let graph = y.graph_tensor().graph().clone();
775 let roots = fuel_graph::opt::RuleRegistry::lowering_only()
776 .optimize_to_fixpoint(&graph, &[y.graph_tensor().id()]);
777 let got = crate::pipelined_bridge::realize_one_as::<f32>(&graph, roots[0], &Device::cpu())
778 .unwrap();
779 for o in 0..64 {
780 let mut want = 0.0f64;
781 for i in 0..256 {
782 let g = i / 128;
783 want += f64::from(x[i])
784 * (f64::from(q[i * 64 + o]) - f64::from(z[g * 64 + o]))
785 * f64::from(s[g * 64 + o].to_f32());
786 }
787 assert!(
788 (f64::from(got[o]) - want).abs() <= 1e-3 * want.abs().max(1.0),
789 "col {o}: got {} want {want}",
790 got[o]
791 );
792 }
793 }
794}