1use half::f16;
34
35pub const HF_AWQ_PACK_ORDER: [usize; 8] = [0, 2, 4, 6, 1, 3, 5, 7];
42
43#[derive(Clone, Debug, PartialEq)]
45pub struct AwqKernelBuffers {
46 pub weight: Vec<u32>,
48 pub zeros: Vec<u32>,
50 pub scales: Vec<f16>,
52 pub zeros_w: usize,
54}
55
56#[derive(Clone, Debug, PartialEq, Eq)]
58pub enum AwqRepackError {
59 BadGroupSize(usize),
61 BadDim {
63 what: &'static str,
64 value: usize,
65 multiple_of: usize,
66 },
67 BadLen {
69 what: &'static str,
70 got: usize,
71 want: usize,
72 },
73}
74
75impl std::fmt::Display for AwqRepackError {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 match self {
78 Self::BadGroupSize(g) => write!(f, "AWQ group size must be 64 or 128, got {g}"),
79 Self::BadDim {
80 what,
81 value,
82 multiple_of,
83 } => {
84 write!(
85 f,
86 "AWQ {what} = {value} must be a multiple of {multiple_of}"
87 )
88 }
89 Self::BadLen { what, got, want } => {
90 write!(f, "AWQ {what} has {got} elements, expected {want}")
91 }
92 }
93 }
94}
95
96impl std::error::Error for AwqRepackError {}
97
98pub fn awq_zeros_w(ic: usize, group_size: usize) -> Result<usize, AwqRepackError> {
101 if group_size != 64 && group_size != 128 {
102 return Err(AwqRepackError::BadGroupSize(group_size));
103 }
104 if !ic.is_multiple_of(group_size) {
105 return Err(AwqRepackError::BadDim {
106 what: "IC",
107 value: ic,
108 multiple_of: group_size,
109 });
110 }
111 let groups = ic / group_size;
112 let mult = 128 / group_size;
113 Ok(groups.div_ceil(8).div_ceil(mult) * mult)
114}
115
116fn check_len(what: &'static str, got: usize, want: usize) -> Result<(), AwqRepackError> {
117 if got == want {
118 Ok(())
119 } else {
120 Err(AwqRepackError::BadLen { what, got, want })
121 }
122}
123
124fn check_multiple(
125 what: &'static str,
126 value: usize,
127 multiple_of: usize,
128) -> Result<(), AwqRepackError> {
129 if value.is_multiple_of(multiple_of) {
130 Ok(())
131 } else {
132 Err(AwqRepackError::BadDim {
133 what,
134 value,
135 multiple_of,
136 })
137 }
138}
139
140fn unpack_hf(words: &[u32], rows: usize, oc: usize) -> Vec<u8> {
143 let wpr = oc / 8;
144 let mut out = vec![0u8; rows * oc];
145 for r in 0..rows {
146 for col in 0..wpr {
147 let w = words[r * wpr + col];
148 for (i, &logical) in HF_AWQ_PACK_ORDER.iter().enumerate() {
149 out[r * oc + col * 8 + logical] = ((w >> (4 * i)) & 0xF) as u8;
150 }
151 }
152 }
153 out
154}
155
156pub fn repack_hf_awq_for_kernel(
163 qweight: &[u32],
164 qzeros: &[u32],
165 scales: &[f16],
166 ic: usize,
167 oc: usize,
168 group_size: usize,
169) -> Result<AwqKernelBuffers, AwqRepackError> {
170 let zeros_w = awq_zeros_w(ic, group_size)?;
171 check_multiple("IC", ic, 8)?;
172 check_multiple("OC", oc, 8)?;
173 let groups = ic / group_size;
174 check_len("qweight", qweight.len(), ic * (oc / 8))?;
175 check_len("qzeros", qzeros.len(), groups * (oc / 8))?;
176 check_len("scales", scales.len(), groups * oc)?;
177
178 let q = unpack_hf(qweight, ic, oc); let z = unpack_hf(qzeros, groups, oc); Ok(AwqKernelBuffers {
181 weight: pack_oc_major(&q, ic, oc, ic / 8),
182 zeros: pack_oc_major(&z, groups, oc, zeros_w),
183 scales: transpose_scales(scales, groups, oc, zeros_w * 8),
184 zeros_w,
185 })
186}
187
188fn pack_oc_major(codes: &[u8], rows: usize, oc: usize, words_per_oc: usize) -> Vec<u32> {
191 let mut out = vec![0u32; oc * words_per_oc];
192 for o in 0..oc {
193 for r in 0..rows {
194 out[o * words_per_oc + r / 8] |= u32::from(codes[r * oc + o]) << (4 * (r % 8));
195 }
196 }
197 out
198}
199
200fn transpose_scales(scales: &[f16], groups: usize, oc: usize, sf_w: usize) -> Vec<f16> {
202 let mut out = vec![f16::ZERO; oc * sf_w];
203 for o in 0..oc {
204 for g in 0..groups {
205 out[o * sf_w + g] = scales[g * oc + o];
206 }
207 }
208 out
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 struct Rng(u64);
216 impl Rng {
217 fn next(&mut self) -> u64 {
218 self.0 ^= self.0 << 13;
219 self.0 ^= self.0 >> 7;
220 self.0 ^= self.0 << 17;
221 self.0
222 }
223 }
224
225 struct Logical {
227 q: Vec<u8>,
228 z: Vec<u8>,
229 s: Vec<f16>,
230 }
231
232 fn logical(ic: usize, oc: usize, group: usize) -> Logical {
233 let mut r = Rng(0xD1B5_4A32_D192_ED03 ^ ((ic * 31 + oc) * 31 + group) as u64);
234 let groups = ic / group;
235 Logical {
236 q: (0..ic * oc).map(|_| (r.next() & 0xF) as u8).collect(),
237 z: (0..groups * oc).map(|_| (r.next() & 0xF) as u8).collect(),
238 s: (0..groups * oc)
240 .map(|i| f16::from_f32(0.01 + i as f32 * 0.001))
241 .collect(),
242 }
243 }
244
245 fn pack_hf(codes: &[u8], rows: usize, oc: usize, order: [usize; 8]) -> Vec<u32> {
249 let mut out = vec![0u32; rows * oc / 8];
250 for r in 0..rows {
251 for col in 0..oc / 8 {
252 for i in 0..8 {
253 out[r * (oc / 8) + col] |=
254 u32::from(codes[r * oc + col * 8 + order[i]]) << (i * 4);
255 }
256 }
257 }
258 out
259 }
260
261 fn read_kernel(b: &AwqKernelBuffers, ic: usize, oc: usize, group: usize) -> Logical {
264 let groups = ic / group;
265 let sf_w = b.zeros_w * 8;
266 let mut q = vec![0u8; ic * oc];
267 let mut z = vec![0u8; groups * oc];
268 let mut s = vec![f16::ZERO; groups * oc];
269 for o in 0..oc {
270 for i in 0..ic {
271 q[i * oc + o] = ((b.weight[o * (ic / 8) + i / 8] >> (4 * (i % 8))) & 0xF) as u8;
272 }
273 for g in 0..groups {
274 z[g * oc + o] = ((b.zeros[o * b.zeros_w + g / 8] >> (4 * (g % 8))) & 0xF) as u8;
275 s[g * oc + o] = b.scales[o * sf_w + g];
276 }
277 }
278 Logical { q, z, s }
279 }
280
281 const SHAPES: [(usize, usize, usize); 4] = [
284 (128 * 9, 64, 128),
285 (64 * 8, 64, 64),
286 (128 * 16, 64, 128),
287 (64 * 24, 128, 64),
288 ];
289
290 #[test]
291 fn repack_round_trips_through_the_documented_kernel_read_formula() {
292 for (ic, oc, group) in SHAPES {
293 let l = logical(ic, oc, group);
294 let groups = ic / group;
295 let qw = pack_hf(&l.q, ic, oc, HF_AWQ_PACK_ORDER);
296 let qz = pack_hf(&l.z, groups, oc, HF_AWQ_PACK_ORDER);
297 let b = repack_hf_awq_for_kernel(&qw, &qz, &l.s, ic, oc, group).unwrap();
298 assert_eq!(b.weight.len(), oc * ic / 8, "{ic}x{oc} g{group}");
299 assert_eq!(b.zeros.len(), oc * b.zeros_w);
300 assert_eq!(b.scales.len(), oc * b.zeros_w * 8);
301 let back = read_kernel(&b, ic, oc, group);
302 assert!(back.q == l.q, "weights {ic}x{oc} g{group}");
303 assert!(back.z == l.z, "zeros {ic}x{oc} g{group}");
304 assert!(back.s == l.s, "scales {ic}x{oc} g{group}");
305 }
306 }
307
308 #[test]
309 fn zeros_w_matches_baracuda_padding_formula_and_rows_are_padded_with_zero() {
310 assert_eq!(awq_zeros_w(128 * 9, 128).unwrap(), 2); assert_eq!(awq_zeros_w(64 * 8, 64).unwrap(), 2); assert_eq!(awq_zeros_w(128 * 16, 128).unwrap(), 2); assert_eq!(awq_zeros_w(64 * 24, 64).unwrap(), 4); let (ic, oc, group) = SHAPES[0];
316 let l = logical(ic, oc, group);
317 let qw = pack_hf(&l.q, ic, oc, HF_AWQ_PACK_ORDER);
318 let qz = pack_hf(&l.z, ic / group, oc, HF_AWQ_PACK_ORDER);
319 let b = repack_hf_awq_for_kernel(&qw, &qz, &l.s, ic, oc, group).unwrap();
320 let groups = ic / group;
321 let sf_w = b.zeros_w * 8;
322 for o in 0..oc {
323 assert!(
324 b.scales[o * sf_w + groups..(o + 1) * sf_w]
325 .iter()
326 .all(|x| *x == f16::ZERO)
327 );
328 let last = b.zeros[o * b.zeros_w + groups / 8];
330 assert_eq!(last >> (4 * (groups % 8)), 0, "row {o} padding nibbles");
331 assert!(
332 b.zeros[o * b.zeros_w + groups / 8 + 1..(o + 1) * b.zeros_w]
333 .iter()
334 .all(|w| *w == 0)
335 );
336 }
337 }
338
339 #[test]
342 fn a_sequentially_packed_checkpoint_does_not_round_trip() {
343 let (ic, oc, group) = SHAPES[0];
344 let l = logical(ic, oc, group);
345 let seq = [0, 1, 2, 3, 4, 5, 6, 7];
346 let qw = pack_hf(&l.q, ic, oc, seq);
347 let qz = pack_hf(&l.z, ic / group, oc, seq);
348 let b = repack_hf_awq_for_kernel(&qw, &qz, &l.s, ic, oc, group).unwrap();
349 let back = read_kernel(&b, ic, oc, group);
350 assert!(back.q != l.q, "the AWQ order must matter for qweight");
351 assert!(back.z != l.z, "the AWQ order must matter for qzeros");
352 }
353
354 #[test]
355 fn bad_inputs_are_refused() {
356 let ok = |ic, oc, g, nw, nz, ns| {
357 repack_hf_awq_for_kernel(&vec![0; nw], &vec![0; nz], &vec![f16::ZERO; ns], ic, oc, g)
358 };
359 assert_eq!(
360 ok(256, 64, 32, 0, 0, 0),
361 Err(AwqRepackError::BadGroupSize(32))
362 );
363 assert!(matches!(
364 ok(200, 64, 128, 0, 0, 0),
365 Err(AwqRepackError::BadDim { what: "IC", .. })
366 ));
367 assert!(matches!(
368 ok(256, 60, 128, 0, 0, 0),
369 Err(AwqRepackError::BadDim { what: "OC", .. })
370 ));
371 assert!(matches!(
372 ok(256, 64, 128, 1, 0, 0),
373 Err(AwqRepackError::BadLen {
374 what: "qweight",
375 ..
376 })
377 ));
378 assert!(ok(256, 64, 128, 256 * 8, 2 * 8, 2 * 64).is_ok());
380 }
381}