use half::f16;
pub const HF_AWQ_PACK_ORDER: [usize; 8] = [0, 2, 4, 6, 1, 3, 5, 7];
#[derive(Clone, Debug, PartialEq)]
pub struct AwqKernelBuffers {
pub weight: Vec<u32>,
pub zeros: Vec<u32>,
pub scales: Vec<f16>,
pub zeros_w: usize,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AwqRepackError {
BadGroupSize(usize),
BadDim {
what: &'static str,
value: usize,
multiple_of: usize,
},
BadLen {
what: &'static str,
got: usize,
want: usize,
},
}
impl std::fmt::Display for AwqRepackError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::BadGroupSize(g) => write!(f, "AWQ group size must be 64 or 128, got {g}"),
Self::BadDim {
what,
value,
multiple_of,
} => {
write!(
f,
"AWQ {what} = {value} must be a multiple of {multiple_of}"
)
}
Self::BadLen { what, got, want } => {
write!(f, "AWQ {what} has {got} elements, expected {want}")
}
}
}
}
impl std::error::Error for AwqRepackError {}
pub fn awq_zeros_w(ic: usize, group_size: usize) -> Result<usize, AwqRepackError> {
if group_size != 64 && group_size != 128 {
return Err(AwqRepackError::BadGroupSize(group_size));
}
if !ic.is_multiple_of(group_size) {
return Err(AwqRepackError::BadDim {
what: "IC",
value: ic,
multiple_of: group_size,
});
}
let groups = ic / group_size;
let mult = 128 / group_size;
Ok(groups.div_ceil(8).div_ceil(mult) * mult)
}
fn check_len(what: &'static str, got: usize, want: usize) -> Result<(), AwqRepackError> {
if got == want {
Ok(())
} else {
Err(AwqRepackError::BadLen { what, got, want })
}
}
fn check_multiple(
what: &'static str,
value: usize,
multiple_of: usize,
) -> Result<(), AwqRepackError> {
if value.is_multiple_of(multiple_of) {
Ok(())
} else {
Err(AwqRepackError::BadDim {
what,
value,
multiple_of,
})
}
}
fn unpack_hf(words: &[u32], rows: usize, oc: usize) -> Vec<u8> {
let wpr = oc / 8;
let mut out = vec![0u8; rows * oc];
for r in 0..rows {
for col in 0..wpr {
let w = words[r * wpr + col];
for (i, &logical) in HF_AWQ_PACK_ORDER.iter().enumerate() {
out[r * oc + col * 8 + logical] = ((w >> (4 * i)) & 0xF) as u8;
}
}
}
out
}
pub fn repack_hf_awq_for_kernel(
qweight: &[u32],
qzeros: &[u32],
scales: &[f16],
ic: usize,
oc: usize,
group_size: usize,
) -> Result<AwqKernelBuffers, AwqRepackError> {
let zeros_w = awq_zeros_w(ic, group_size)?;
check_multiple("IC", ic, 8)?;
check_multiple("OC", oc, 8)?;
let groups = ic / group_size;
check_len("qweight", qweight.len(), ic * (oc / 8))?;
check_len("qzeros", qzeros.len(), groups * (oc / 8))?;
check_len("scales", scales.len(), groups * oc)?;
let q = unpack_hf(qweight, ic, oc); let z = unpack_hf(qzeros, groups, oc); Ok(AwqKernelBuffers {
weight: pack_oc_major(&q, ic, oc, ic / 8),
zeros: pack_oc_major(&z, groups, oc, zeros_w),
scales: transpose_scales(scales, groups, oc, zeros_w * 8),
zeros_w,
})
}
fn pack_oc_major(codes: &[u8], rows: usize, oc: usize, words_per_oc: usize) -> Vec<u32> {
let mut out = vec![0u32; oc * words_per_oc];
for o in 0..oc {
for r in 0..rows {
out[o * words_per_oc + r / 8] |= u32::from(codes[r * oc + o]) << (4 * (r % 8));
}
}
out
}
fn transpose_scales(scales: &[f16], groups: usize, oc: usize, sf_w: usize) -> Vec<f16> {
let mut out = vec![f16::ZERO; oc * sf_w];
for o in 0..oc {
for g in 0..groups {
out[o * sf_w + g] = scales[g * oc + o];
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
struct Rng(u64);
impl Rng {
fn next(&mut self) -> u64 {
self.0 ^= self.0 << 13;
self.0 ^= self.0 >> 7;
self.0 ^= self.0 << 17;
self.0
}
}
struct Logical {
q: Vec<u8>,
z: Vec<u8>,
s: Vec<f16>,
}
fn logical(ic: usize, oc: usize, group: usize) -> Logical {
let mut r = Rng(0xD1B5_4A32_D192_ED03 ^ ((ic * 31 + oc) * 31 + group) as u64);
let groups = ic / group;
Logical {
q: (0..ic * oc).map(|_| (r.next() & 0xF) as u8).collect(),
z: (0..groups * oc).map(|_| (r.next() & 0xF) as u8).collect(),
s: (0..groups * oc)
.map(|i| f16::from_f32(0.01 + i as f32 * 0.001))
.collect(),
}
}
fn pack_hf(codes: &[u8], rows: usize, oc: usize, order: [usize; 8]) -> Vec<u32> {
let mut out = vec![0u32; rows * oc / 8];
for r in 0..rows {
for col in 0..oc / 8 {
for i in 0..8 {
out[r * (oc / 8) + col] |=
u32::from(codes[r * oc + col * 8 + order[i]]) << (i * 4);
}
}
}
out
}
fn read_kernel(b: &AwqKernelBuffers, ic: usize, oc: usize, group: usize) -> Logical {
let groups = ic / group;
let sf_w = b.zeros_w * 8;
let mut q = vec![0u8; ic * oc];
let mut z = vec![0u8; groups * oc];
let mut s = vec![f16::ZERO; groups * oc];
for o in 0..oc {
for i in 0..ic {
q[i * oc + o] = ((b.weight[o * (ic / 8) + i / 8] >> (4 * (i % 8))) & 0xF) as u8;
}
for g in 0..groups {
z[g * oc + o] = ((b.zeros[o * b.zeros_w + g / 8] >> (4 * (g % 8))) & 0xF) as u8;
s[g * oc + o] = b.scales[o * sf_w + g];
}
}
Logical { q, z, s }
}
const SHAPES: [(usize, usize, usize); 4] = [
(128 * 9, 64, 128),
(64 * 8, 64, 64),
(128 * 16, 64, 128),
(64 * 24, 128, 64),
];
#[test]
fn repack_round_trips_through_the_documented_kernel_read_formula() {
for (ic, oc, group) in SHAPES {
let l = logical(ic, oc, group);
let groups = ic / group;
let qw = pack_hf(&l.q, ic, oc, HF_AWQ_PACK_ORDER);
let qz = pack_hf(&l.z, groups, oc, HF_AWQ_PACK_ORDER);
let b = repack_hf_awq_for_kernel(&qw, &qz, &l.s, ic, oc, group).unwrap();
assert_eq!(b.weight.len(), oc * ic / 8, "{ic}x{oc} g{group}");
assert_eq!(b.zeros.len(), oc * b.zeros_w);
assert_eq!(b.scales.len(), oc * b.zeros_w * 8);
let back = read_kernel(&b, ic, oc, group);
assert!(back.q == l.q, "weights {ic}x{oc} g{group}");
assert!(back.z == l.z, "zeros {ic}x{oc} g{group}");
assert!(back.s == l.s, "scales {ic}x{oc} g{group}");
}
}
#[test]
fn zeros_w_matches_baracuda_padding_formula_and_rows_are_padded_with_zero() {
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];
let l = logical(ic, oc, group);
let qw = pack_hf(&l.q, ic, oc, HF_AWQ_PACK_ORDER);
let qz = pack_hf(&l.z, ic / group, oc, HF_AWQ_PACK_ORDER);
let b = repack_hf_awq_for_kernel(&qw, &qz, &l.s, ic, oc, group).unwrap();
let groups = ic / group;
let sf_w = b.zeros_w * 8;
for o in 0..oc {
assert!(
b.scales[o * sf_w + groups..(o + 1) * sf_w]
.iter()
.all(|x| *x == f16::ZERO)
);
let last = b.zeros[o * b.zeros_w + groups / 8];
assert_eq!(last >> (4 * (groups % 8)), 0, "row {o} padding nibbles");
assert!(
b.zeros[o * b.zeros_w + groups / 8 + 1..(o + 1) * b.zeros_w]
.iter()
.all(|w| *w == 0)
);
}
}
#[test]
fn a_sequentially_packed_checkpoint_does_not_round_trip() {
let (ic, oc, group) = SHAPES[0];
let l = logical(ic, oc, group);
let seq = [0, 1, 2, 3, 4, 5, 6, 7];
let qw = pack_hf(&l.q, ic, oc, seq);
let qz = pack_hf(&l.z, ic / group, oc, seq);
let b = repack_hf_awq_for_kernel(&qw, &qz, &l.s, ic, oc, group).unwrap();
let back = read_kernel(&b, ic, oc, group);
assert!(back.q != l.q, "the AWQ order must matter for qweight");
assert!(back.z != l.z, "the AWQ order must matter for qzeros");
}
#[test]
fn bad_inputs_are_refused() {
let ok = |ic, oc, g, nw, nz, ns| {
repack_hf_awq_for_kernel(&vec![0; nw], &vec![0; nz], &vec![f16::ZERO; ns], ic, oc, g)
};
assert_eq!(
ok(256, 64, 32, 0, 0, 0),
Err(AwqRepackError::BadGroupSize(32))
);
assert!(matches!(
ok(200, 64, 128, 0, 0, 0),
Err(AwqRepackError::BadDim { what: "IC", .. })
));
assert!(matches!(
ok(256, 60, 128, 0, 0, 0),
Err(AwqRepackError::BadDim { what: "OC", .. })
));
assert!(matches!(
ok(256, 64, 128, 1, 0, 0),
Err(AwqRepackError::BadLen {
what: "qweight",
..
})
));
assert!(ok(256, 64, 128, 256 * 8, 2 * 8, 2 * 64).is_ok());
}
}