use crate::error::InferenceError;
const ENC_ZERO: u8 = 0b00;
const ENC_POS: u8 = 0b01;
const ENC_NEG: u8 = 0b10;
#[inline(always)]
fn encode_ternary(v: i8) -> u8 {
match v {
1 => ENC_POS,
-1 => ENC_NEG,
_ => ENC_ZERO,
}
}
#[inline(always)]
fn decode_ternary(bits: u8) -> i8 {
match bits & 0x03 {
ENC_POS => 1,
ENC_NEG => -1,
_ => 0,
}
}
#[inline]
pub fn packed_row_bytes(k: usize) -> usize {
k.div_ceil(4)
}
fn pack_row(row: &[f32]) -> Result<(Vec<u8>, f32), InferenceError> {
if let Some((index, value)) = row.iter().enumerate().find(|(_, value)| !value.is_finite()) {
return Err(InferenceError::InvalidInput(format!(
"BitNet weight row contains non-finite value {value} at index {index}"
)));
}
let abs_sum: f32 = row.iter().map(|v| v.abs()).sum();
let alpha = if row.is_empty() {
0.0
} else {
abs_sum / row.len() as f32
};
let k = row.len();
let num_bytes = packed_row_bytes(k);
let mut packed = vec![0u8; num_bytes];
if alpha > 0.0 {
let inv_alpha = 1.0 / alpha;
for (i, &w) in row.iter().enumerate() {
let scaled = (w * inv_alpha).round().clamp(-1.0, 1.0) as i8;
let enc = encode_ternary(scaled);
let byte_idx = i / 4;
let bit_offset = (i % 4) * 2;
packed[byte_idx] |= enc << bit_offset;
}
}
Ok((packed, alpha))
}
pub fn pack_ternary(
weights: &[f32],
n: usize,
k: usize,
) -> Result<(Vec<u8>, Vec<f32>), InferenceError> {
let expected_len = n.checked_mul(k).ok_or_else(|| {
InferenceError::InvalidInput(format!("BitNet weight geometry overflows: n={n}, k={k}"))
})?;
if weights.len() != expected_len {
return Err(InferenceError::InvalidInput(format!(
"BitNet weights length {} does not match n*k ({n}*{k}={expected_len})",
weights.len()
)));
}
let row_bytes = packed_row_bytes(k);
let packed_len = n.checked_mul(row_bytes).ok_or_else(|| {
InferenceError::InvalidInput(format!(
"BitNet packed geometry overflows: n={n}, row_bytes={row_bytes}"
))
})?;
let mut packed = vec![0u8; packed_len];
let mut alphas = vec![0.0f32; n];
for row_idx in 0..n {
let row = &weights[row_idx * k..(row_idx + 1) * k];
let (row_packed, alpha) = pack_row(row)?;
packed[row_idx * row_bytes..(row_idx + 1) * row_bytes].copy_from_slice(&row_packed);
alphas[row_idx] = alpha;
}
Ok((packed, alphas))
}
#[inline]
pub fn unpack_weight(packed: &[u8], k: usize, row_idx: usize, col_idx: usize) -> i8 {
let row_bytes = packed_row_bytes(k);
let base = row_idx * row_bytes;
let byte_idx = base + col_idx / 4;
let bit_offset = (col_idx % 4) * 2;
decode_ternary(packed[byte_idx] >> bit_offset)
}
pub fn quantize_activation(x: &[f32]) -> (Vec<i8>, f32) {
let abs_max = x.iter().fold(0.0f32, |m, &v| m.max(v.abs()));
if abs_max == 0.0 {
return (vec![0i8; x.len()], 0.0);
}
let gamma = abs_max / 127.0;
let inv_gamma = 1.0 / gamma;
let quantized: Vec<i8> = x
.iter()
.map(|&v| (v * inv_gamma).round().clamp(-127.0, 127.0) as i8)
.collect();
(quantized, gamma)
}
pub fn matvec_ternary_scalar(
x_q: &[i8],
x_scale: f32,
packed_w: &[u8],
alphas: &[f32],
n: usize,
k: usize,
output: &mut [f32],
) {
let row_bytes = packed_row_bytes(k);
crate::forward::cpu::validate_ternary_matvec_args(
x_q.len(),
alphas.len(),
packed_w.len(),
output.len(),
n,
k,
row_bytes,
"matvec_ternary_scalar",
);
for row in 0..n {
let row_base = row * row_bytes;
let mut acc: i32 = 0;
let full_bytes = k / 4;
for byte_idx in 0..full_bytes {
let byte_val = packed_w[row_base + byte_idx];
let x_base = byte_idx * 4;
let w0 = decode_ternary(byte_val);
let w1 = decode_ternary(byte_val >> 2);
let w2 = decode_ternary(byte_val >> 4);
let w3 = decode_ternary(byte_val >> 6);
acc += w0 as i32 * x_q[x_base] as i32;
acc += w1 as i32 * x_q[x_base + 1] as i32;
acc += w2 as i32 * x_q[x_base + 2] as i32;
acc += w3 as i32 * x_q[x_base + 3] as i32;
}
let rem_start = full_bytes * 4;
if rem_start < k {
let byte_val = packed_w[row_base + full_bytes];
for (j, &xj) in x_q.iter().enumerate().take(k).skip(rem_start) {
let bit_offset = (j % 4) * 2;
let w = decode_ternary(byte_val >> bit_offset);
acc += w as i32 * xj as i32;
}
}
output[row] = alphas[row] * x_scale * acc as f32;
}
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
pub unsafe fn matvec_ternary_neon(
x_q: &[i8],
x_scale: f32,
packed_w: &[u8],
alphas: &[f32],
n: usize,
k: usize,
output: &mut [f32],
) {
use std::arch::aarch64::*;
let row_bytes = packed_row_bytes(k);
crate::forward::cpu::validate_ternary_matvec_args(
x_q.len(),
alphas.len(),
packed_w.len(),
output.len(),
n,
k,
row_bytes,
"matvec_ternary_neon",
);
let mask_2bit = vdupq_n_u8(0x03);
for (row, alpha) in alphas.iter().enumerate().take(n) {
let row_base = row * row_bytes;
let mut acc0 = vdupq_n_s32(0);
let mut acc1 = vdupq_n_s32(0);
let mut acc2 = vdupq_n_s32(0);
let mut acc3 = vdupq_n_s32(0);
let chunks_32 = k / 32;
for chunk in 0..chunks_32 {
let w_offset = row_base + chunk * 8;
let x_offset = chunk * 32;
let mut ternary = [0i8; 32];
for bi in 0..8 {
let byte_val = *packed_w.get_unchecked(w_offset + bi);
ternary[bi * 4] = decode_ternary(byte_val);
ternary[bi * 4 + 1] = decode_ternary(byte_val >> 2);
ternary[bi * 4 + 2] = decode_ternary(byte_val >> 4);
ternary[bi * 4 + 3] = decode_ternary(byte_val >> 6);
}
let w_lo = vld1q_s8(ternary.as_ptr());
let w_hi = vld1q_s8(ternary.as_ptr().add(16));
let x_lo = vld1q_s8(x_q.as_ptr().add(x_offset));
let x_hi = vld1q_s8(x_q.as_ptr().add(x_offset + 16));
let prod_lo_lo = vmull_s8(vget_low_s8(w_lo), vget_low_s8(x_lo));
let prod_lo_hi = vmull_s8(vget_high_s8(w_lo), vget_high_s8(x_lo));
let prod_hi_lo = vmull_s8(vget_low_s8(w_hi), vget_low_s8(x_hi));
let prod_hi_hi = vmull_s8(vget_high_s8(w_hi), vget_high_s8(x_hi));
acc0 = vaddq_s32(acc0, vpaddlq_s16(prod_lo_lo));
acc1 = vaddq_s32(acc1, vpaddlq_s16(prod_lo_hi));
acc2 = vaddq_s32(acc2, vpaddlq_s16(prod_hi_lo));
acc3 = vaddq_s32(acc3, vpaddlq_s16(prod_hi_hi));
}
let sum4 = vaddq_s32(vaddq_s32(acc0, acc1), vaddq_s32(acc2, acc3));
let mut acc_scalar: i32 = vaddvq_s32(sum4);
let rem_start = chunks_32 * 32;
for j in rem_start..k {
let byte_idx = row_base + j / 4;
let bit_offset = (j % 4) * 2;
let w = decode_ternary(*packed_w.get_unchecked(byte_idx) >> bit_offset);
acc_scalar += w as i32 * *x_q.get_unchecked(j) as i32;
}
*output.get_unchecked_mut(row) = alpha * x_scale * acc_scalar as f32;
}
let _ = mask_2bit;
}
pub fn matmul_ternary(x: &[f32], packed_w: &[u8], alphas: &[f32], n: usize, k: usize) -> Vec<f32> {
let row_bytes = packed_row_bytes(k);
crate::forward::cpu::validate_ternary_matvec_args(
x.len(),
alphas.len(),
packed_w.len(),
n, n,
k,
row_bytes,
"matmul_ternary",
);
let (x_q, gamma) = quantize_activation(x);
let mut output = vec![0.0f32; n];
#[cfg(target_arch = "aarch64")]
{
unsafe {
matvec_ternary_neon(&x_q, gamma, packed_w, alphas, n, k, &mut output);
}
output
}
#[cfg(not(target_arch = "aarch64"))]
{
matvec_ternary_scalar(&x_q, gamma, packed_w, alphas, n, k, &mut output);
output
}
}
#[cfg(test)]
fn matvec_f32_reference(x: &[f32], weights: &[f32], n: usize, k: usize) -> Vec<f32> {
let mut output = vec![0.0f32; n];
for i in 0..n {
let mut sum = 0.0f32;
for j in 0..k {
sum += weights[i * k + j] * x[j];
}
output[i] = sum;
}
output
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_decode_roundtrip() {
assert_eq!(decode_ternary(encode_ternary(0)), 0);
assert_eq!(decode_ternary(encode_ternary(1)), 1);
assert_eq!(decode_ternary(encode_ternary(-1)), -1);
}
#[test]
fn test_decode_ternary_masks_correctly() {
assert_eq!(decode_ternary(0b11_10_01_00), 0);
assert_eq!(decode_ternary(0b11_10_01_00 >> 2), 1);
assert_eq!(decode_ternary(0b11_10_01_00 >> 4), -1);
assert_eq!(decode_ternary(0b11), 0);
}
#[test]
fn test_pack_unpack_roundtrip_simple() {
let alpha = 0.5; let k = 8;
let n = 2;
let row0_ternary: [i8; 8] = [1, -1, 0, 1, -1, -1, 1, 0];
let row1_ternary: [i8; 8] = [0, 1, 1, -1, 0, 0, -1, 1];
let mut weights = vec![0.0f32; n * k];
for (i, &t) in row0_ternary.iter().enumerate() {
weights[i] = t as f32 * alpha;
}
for (i, &t) in row1_ternary.iter().enumerate() {
weights[k + i] = t as f32 * alpha;
}
let (packed, alphas) = pack_ternary(&weights, n, k).expect("finite test weights");
let expected_alpha0 = 6.0 * alpha / 8.0;
assert!(
(alphas[0] - expected_alpha0).abs() < 1e-6,
"alpha[0]={} expected={}",
alphas[0],
expected_alpha0
);
for j in 0..k {
let w0 = unpack_weight(&packed, k, 0, j);
assert_eq!(
w0, row0_ternary[j],
"row=0 col={}: got {} expected {}",
j, w0, row0_ternary[j]
);
}
for j in 0..k {
let w1 = unpack_weight(&packed, k, 1, j);
assert_eq!(
w1, row1_ternary[j],
"row=1 col={}: got {} expected {}",
j, w1, row1_ternary[j]
);
}
}
#[test]
fn test_pack_unpack_non_multiple_of_4() {
let k = 7;
let n = 1;
let weights = vec![1.0f32; k];
let (packed, alphas) = pack_ternary(&weights, n, k).expect("finite test weights");
assert!(alphas[0] > 0.0);
assert_eq!(packed.len(), packed_row_bytes(k));
for j in 0..k {
assert_eq!(unpack_weight(&packed, k, 0, j), 1, "col={j}");
}
}
#[test]
fn pack_ternary_rejects_non_finite_weights() {
for non_finite in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
let result = pack_ternary(&[0.25, non_finite, -0.5, 1.0], 1, 4);
assert!(
matches!(result, Err(InferenceError::InvalidInput(_))),
"non-finite weight {non_finite} must be rejected"
);
}
}
#[test]
fn pack_ternary_rejects_mismatched_geometry() {
let result = pack_ternary(&[0.25, -0.5, 1.0], 1, 4);
assert!(
matches!(result, Err(InferenceError::InvalidInput(_))),
"weights whose length differs from n*k must be rejected"
);
}
#[test]
fn test_quantize_activation_basic() {
let x = vec![1.0, -1.0, 0.5, -0.5, 0.0];
let (q, gamma) = quantize_activation(&x);
assert!((gamma - 1.0 / 127.0).abs() < 1e-6);
assert_eq!(q[0], 127); assert_eq!(q[1], -127); assert!((q[2] as i32 - 64).unsigned_abs() <= 1);
assert!((q[3] as i32 + 64).unsigned_abs() <= 1);
assert_eq!(q[4], 0);
}
#[test]
fn test_quantize_activation_all_zero() {
let x = vec![0.0; 10];
let (q, gamma) = quantize_activation(&x);
assert_eq!(gamma, 0.0);
assert!(q.iter().all(|&v| v == 0));
}
#[test]
fn test_quantize_activation_symmetric_range() {
let x = vec![-1.0, 1.0, -0.99999, 0.99999];
let (q, _gamma) = quantize_activation(&x);
assert_eq!(q[0], -127, "symmetric range: -1.0 must map to -127");
assert_eq!(q[1], 127, "symmetric range: +1.0 must map to +127");
for &v in &q {
assert!(
v >= -127,
"quantized value {v} violates symmetric [-127, 127] range"
);
}
}
#[test]
fn test_quantize_activation_roundtrip_approx() {
let x = vec![0.3, -0.7, 1.5, -2.0, 0.0, 0.01];
let (q, gamma) = quantize_activation(&x);
let bucket = 2.0 * gamma;
for (i, &original) in x.iter().enumerate() {
let reconstructed = q[i] as f32 * gamma;
if original.abs() > bucket {
let rel_error = (reconstructed - original).abs() / original.abs();
assert!(
rel_error < 0.02,
"index {i}: original={original} reconstructed={reconstructed} rel_error={rel_error}"
);
}
}
}
#[test]
fn test_matvec_scalar_simple() {
let n = 2;
let k = 4;
let weights = vec![1.0f32; n * k];
let (packed, alphas) = pack_ternary(&weights, n, k).expect("finite test weights");
let x = vec![1.0, 2.0, 3.0, 4.0];
let (x_q, gamma) = quantize_activation(&x);
let mut output = vec![0.0f32; n];
matvec_ternary_scalar(&x_q, gamma, &packed, &alphas, n, k, &mut output);
for i in 0..n {
assert!(
(output[i] - 10.0).abs() < 0.5,
"row {}: got {}, expected ~10.0",
i,
output[i]
);
}
}
#[test]
fn test_matvec_scalar_identity_pattern() {
let n = 4;
let k = 4;
let mut weights = vec![0.0f32; n * k];
for i in 0..n {
weights[i * k + i] = 1.0;
}
let (packed, alphas) = pack_ternary(&weights, n, k).expect("finite test weights");
let x = vec![10.0, 20.0, 30.0, 40.0];
let (x_q, gamma) = quantize_activation(&x);
let mut output = vec![0.0f32; n];
matvec_ternary_scalar(&x_q, gamma, &packed, &alphas, n, k, &mut output);
for i in 0..n {
assert!(
output[i] > 0.0,
"row {} should be positive, got {}",
i,
output[i]
);
}
for i in 0..n - 1 {
assert!(
output[i + 1] > output[i],
"ordering violated: output[{}]={} should be < output[{}]={}",
i,
output[i],
i + 1,
output[i + 1]
);
}
}
#[test]
fn test_matvec_scalar_all_zero_weights() {
let n = 3;
let k = 8;
let weights = vec![0.0f32; n * k];
let (packed, alphas) = pack_ternary(&weights, n, k).expect("finite test weights");
let x = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let (x_q, gamma) = quantize_activation(&x);
let mut output = vec![999.0f32; n];
matvec_ternary_scalar(&x_q, gamma, &packed, &alphas, n, k, &mut output);
for i in 0..n {
assert_eq!(output[i], 0.0, "row {} should be 0, got {}", i, output[i]);
}
}
#[test]
fn test_matvec_scalar_all_negative_weights() {
let n = 1;
let k = 8;
let weights = vec![-1.0f32; n * k];
let (packed, alphas) = pack_ternary(&weights, n, k).expect("finite test weights");
let x = vec![1.0; k]; let (x_q, gamma) = quantize_activation(&x);
let mut output = vec![0.0f32; n];
matvec_ternary_scalar(&x_q, gamma, &packed, &alphas, n, k, &mut output);
assert!(
(output[0] + 8.0).abs() < 0.5,
"got {}, expected ~-8.0",
output[0]
);
}
#[test]
fn test_matvec_scalar_matches_f32_reference() {
let n = 4;
let k = 16;
let mut weights = vec![0.0f32; n * k];
for i in 0..n {
for j in 0..k {
let idx = i * k + j;
weights[idx] = match idx % 5 {
0 => 0.5,
1 => -0.5,
2 => 0.0,
3 => 0.7,
_ => -0.3,
};
}
}
let x: Vec<f32> = (0..k).map(|i| (i as f32 - 8.0) * 0.1).collect();
let ref_output = matvec_f32_reference(&x, &weights, n, k);
let ternary_output = matmul_ternary(
&x,
&{
let (p, _) = pack_ternary(&weights, n, k).expect("finite test weights");
p
},
&{
let (_, a) = pack_ternary(&weights, n, k).expect("finite test weights");
a
},
n,
k,
);
for i in 0..n {
let abs_err = (ternary_output[i] - ref_output[i]).abs();
let scale = ref_output[i].abs().max(1.0);
assert!(
abs_err / scale < 0.5,
"row {}: ternary={} ref={} err={}",
i,
ternary_output[i],
ref_output[i],
abs_err
);
}
}
#[test]
fn test_matvec_scalar_non_multiple_of_4() {
let n = 2;
let k = 7;
let weights = vec![1.0f32; n * k];
let (packed, alphas) = pack_ternary(&weights, n, k).expect("finite test weights");
let x: Vec<f32> = (0..k).map(|i| (i + 1) as f32).collect(); let (x_q, gamma) = quantize_activation(&x);
let mut output = vec![0.0f32; n];
matvec_ternary_scalar(&x_q, gamma, &packed, &alphas, n, k, &mut output);
for i in 0..n {
assert!(
(output[i] - 28.0).abs() < 1.5,
"row {}: got {}, expected ~28.0",
i,
output[i]
);
}
}
#[cfg(target_arch = "aarch64")]
mod neon_tests {
use super::*;
#[test]
fn test_neon_matches_scalar_small() {
let n = 4;
let k = 32; let mut weights = vec![0.0f32; n * k];
for i in 0..n * k {
weights[i] = match i % 3 {
0 => 0.8,
1 => -0.6,
_ => 0.0,
};
}
let (packed, alphas) = pack_ternary(&weights, n, k).expect("finite test weights");
let x: Vec<f32> = (0..k).map(|i| (i as f32 - 16.0) * 0.1).collect();
let (x_q, gamma) = quantize_activation(&x);
let mut scalar_out = vec![0.0f32; n];
let mut neon_out = vec![0.0f32; n];
matvec_ternary_scalar(&x_q, gamma, &packed, &alphas, n, k, &mut scalar_out);
unsafe {
matvec_ternary_neon(&x_q, gamma, &packed, &alphas, n, k, &mut neon_out);
}
for i in 0..n {
assert!(
(neon_out[i] - scalar_out[i]).abs() < 1e-6,
"row {}: neon={} scalar={}",
i,
neon_out[i],
scalar_out[i]
);
}
}
#[test]
fn test_neon_matches_scalar_large() {
let n = 8;
let k = 256;
let mut weights = vec![0.0f32; n * k];
for i in 0..n * k {
weights[i] = match (i * 7 + 3) % 5 {
0 => 1.0,
1 => -1.0,
2 => 0.5,
3 => -0.5,
_ => 0.0,
};
}
let (packed, alphas) = pack_ternary(&weights, n, k).expect("finite test weights");
let x: Vec<f32> = (0..k)
.map(|i| ((i * 13 % 100) as f32 - 50.0) * 0.01)
.collect();
let (x_q, gamma) = quantize_activation(&x);
let mut scalar_out = vec![0.0f32; n];
let mut neon_out = vec![0.0f32; n];
matvec_ternary_scalar(&x_q, gamma, &packed, &alphas, n, k, &mut scalar_out);
unsafe {
matvec_ternary_neon(&x_q, gamma, &packed, &alphas, n, k, &mut neon_out);
}
for i in 0..n {
assert!(
(neon_out[i] - scalar_out[i]).abs() < 1e-4,
"row {}: neon={} scalar={}",
i,
neon_out[i],
scalar_out[i]
);
}
}
#[test]
fn test_neon_matches_scalar_with_remainder() {
let n = 3;
let k = 100;
let weights: Vec<f32> = (0..n * k)
.map(|i| match i % 4 {
0 => 0.9,
1 => -0.7,
2 => 0.0,
_ => 0.4,
})
.collect();
let (packed, alphas) = pack_ternary(&weights, n, k).expect("finite test weights");
let x: Vec<f32> = (0..k).map(|i| (i as f32 * 0.01) - 0.5).collect();
let (x_q, gamma) = quantize_activation(&x);
let mut scalar_out = vec![0.0f32; n];
let mut neon_out = vec![0.0f32; n];
matvec_ternary_scalar(&x_q, gamma, &packed, &alphas, n, k, &mut scalar_out);
unsafe {
matvec_ternary_neon(&x_q, gamma, &packed, &alphas, n, k, &mut neon_out);
}
for i in 0..n {
assert!(
(neon_out[i] - scalar_out[i]).abs() < 1e-4,
"row {}: neon={} scalar={}",
i,
neon_out[i],
scalar_out[i]
);
}
}
#[test]
#[should_panic(expected = "x_q too short for k")]
fn matvec_ternary_neon_rejects_short_x_q() {
let n = 2;
let k = 32;
let weights = vec![1.0f32; n * k];
let (packed, alphas) = pack_ternary(&weights, n, k).expect("finite test weights");
let x_q = vec![1i8; k - 1]; let mut output = vec![0.0f32; n];
unsafe {
matvec_ternary_neon(&x_q, 1.0, &packed, &alphas, n, k, &mut output);
}
}
#[test]
fn test_neon_all_zero_weights() {
let n = 2;
let k = 64;
let weights = vec![0.0f32; n * k];
let (packed, alphas) = pack_ternary(&weights, n, k).expect("finite test weights");
let x = vec![5.0f32; k];
let (x_q, gamma) = quantize_activation(&x);
let mut output = vec![999.0f32; n];
unsafe {
matvec_ternary_neon(&x_q, gamma, &packed, &alphas, n, k, &mut output);
}
for i in 0..n {
assert_eq!(output[i], 0.0, "row {} should be 0, got {}", i, output[i]);
}
}
#[test]
fn test_neon_all_positive_weights() {
let n = 1;
let k = 64;
let weights = vec![1.0f32; n * k]; let (packed, alphas) = pack_ternary(&weights, n, k).expect("finite test weights");
let x = vec![1.0f32; k]; let (x_q, gamma) = quantize_activation(&x);
let mut output = vec![0.0f32; n];
unsafe {
matvec_ternary_neon(&x_q, gamma, &packed, &alphas, n, k, &mut output);
}
assert!(
(output[0] - 64.0).abs() < 1.5,
"got {}, expected ~64.0",
output[0]
);
}
}
#[test]
fn test_matmul_ternary_dispatch() {
let n = 4;
let k = 32;
let weights = vec![1.0f32; n * k];
let (packed, alphas) = pack_ternary(&weights, n, k).expect("finite test weights");
let x = vec![1.0f32; k];
let output = matmul_ternary(&x, &packed, &alphas, n, k);
assert_eq!(output.len(), n);
for i in 0..n {
assert!(
(output[i] - k as f32).abs() < 1.5,
"row {}: got {}, expected ~{}",
i,
output[i],
k
);
}
}
#[test]
#[should_panic(expected = "x_q too short for k")]
fn matmul_ternary_rejects_short_activation() {
let n = 2;
let k = 32;
let weights = vec![1.0f32; n * k];
let (packed, alphas) = pack_ternary(&weights, n, k).expect("finite test weights");
let x = vec![1.0f32; k - 1]; let _ = matmul_ternary(&x, &packed, &alphas, n, k);
}
#[test]
#[should_panic(expected = "alphas too short for n")]
fn matmul_ternary_rejects_short_alphas() {
let n = 2;
let k = 32;
let weights = vec![1.0f32; n * k];
let (packed, _alphas) = pack_ternary(&weights, n, k).expect("finite test weights");
let x = vec![1.0f32; k];
let short_alphas = vec![1.0f32; n - 1];
let _ = matmul_ternary(&x, &packed, &short_alphas, n, k);
}
#[test]
#[should_panic(expected = "packed_w too short for n*packed_row_bytes")]
fn matmul_ternary_rejects_short_packed_w() {
let n = 2;
let k = 32;
let weights = vec![1.0f32; n * k];
let (packed, alphas) = pack_ternary(&weights, n, k).expect("finite test weights");
let short_packed = &packed[..packed.len() - 1];
let x = vec![1.0f32; k];
let _ = matmul_ternary(&x, short_packed, &alphas, n, k);
}
#[test]
#[should_panic(expected = "x_q too short for k")]
fn matvec_ternary_scalar_rejects_short_x_q() {
let n = 2;
let k = 32;
let weights = vec![1.0f32; n * k];
let (packed, alphas) = pack_ternary(&weights, n, k).expect("finite test weights");
let x_q = vec![1i8; k - 1]; let mut output = vec![0.0f32; n];
matvec_ternary_scalar(&x_q, 1.0, &packed, &alphas, n, k, &mut output);
}
#[test]
#[should_panic(expected = "x_q too short for k")]
fn matvec_ternary_scalar_rejects_short_x_q_remainder_path() {
let n = 2;
let k = 5;
let weights = vec![1.0f32; n * k];
let (packed, alphas) = pack_ternary(&weights, n, k).expect("finite test weights");
let x_q = vec![1i8; k - 1]; let mut output = vec![0.0f32; n];
matvec_ternary_scalar(&x_q, 1.0, &packed, &alphas, n, k, &mut output);
}
#[test]
fn matvec_ternary_scalar_accepts_oversized_buffers() {
let n = 2;
let k = 32;
let weights = vec![1.0f32; n * k];
let (packed, alphas) = pack_ternary(&weights, n, k).expect("finite test weights");
let x_q = vec![1i8; k + 4]; let mut output = vec![0.0f32; n + 4]; matvec_ternary_scalar(&x_q, 1.0, &packed, &alphas, n, k, &mut output);
for row in output.iter().take(n) {
assert!((*row - k as f32).abs() < 1.5);
}
}
#[test]
fn test_packed_row_bytes() {
assert_eq!(packed_row_bytes(0), 0);
assert_eq!(packed_row_bytes(1), 1);
assert_eq!(packed_row_bytes(4), 1);
assert_eq!(packed_row_bytes(5), 2);
assert_eq!(packed_row_bytes(8), 2);
assert_eq!(packed_row_bytes(9), 3);
assert_eq!(packed_row_bytes(2560), 640); }
}