#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TernaryScale {
#[default]
PerTensor,
PerChannel,
}
impl TernaryScale {
pub fn parse(s: &str) -> Result<Self, String> {
match s.trim().to_ascii_lowercase().as_str() {
"tensor" | "per-tensor" | "per_tensor" | "bitnet" => Ok(Self::PerTensor),
"channel" | "per-channel" | "per_channel" | "row" => Ok(Self::PerChannel),
other => Err(format!(
"unknown TernaryScale {other:?}; expected per-tensor | per-channel"
)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TernaryConfig {
pub scale: TernaryScale,
pub eps: f32,
}
impl Default for TernaryConfig {
fn default() -> Self {
Self {
scale: TernaryScale::PerTensor,
eps: BITNET_EPS,
}
}
}
pub const BITNET_EPS: f32 = 1e-5;
pub const BITNET_ACT_QMAX: f32 = 127.0;
#[derive(Debug, Clone, PartialEq)]
pub struct TernaryWeights {
pub codes: Vec<i8>,
pub scales: Vec<f32>,
pub rows: usize,
pub cols: usize,
}
impl TernaryWeights {
pub fn row_scale(&self, row: usize) -> f32 {
if self.scales.len() == 1 {
self.scales[0]
} else {
self.scales[row]
}
}
pub fn row_scales(&self) -> Vec<f32> {
(0..self.rows).map(|r| self.row_scale(r)).collect()
}
pub fn dequantize(&self) -> Vec<f32> {
let mut out = Vec::with_capacity(self.codes.len());
for r in 0..self.rows {
let s = self.row_scale(r);
let row = &self.codes[r * self.cols..(r + 1) * self.cols];
out.extend(row.iter().map(|&c| f32::from(c) * s));
}
out
}
pub fn sparsity(&self) -> f32 {
if self.codes.is_empty() {
return 0.0;
}
self.codes.iter().filter(|&&c| c == 0).count() as f32 / self.codes.len() as f32
}
}
pub fn absmean_scale(w: &[f32], eps: f32) -> f32 {
if w.is_empty() {
return eps;
}
let sum: f64 = w.iter().map(|&v| f64::from(v.abs())).sum();
((sum / w.len() as f64) as f32).max(eps)
}
#[inline]
pub fn ternary_code(w: f32, gamma: f32) -> i8 {
(w / gamma).round_ties_even().clamp(-1.0, 1.0) as i8
}
pub fn ternarize(w: &[f32], rows: usize, cols: usize, cfg: &TernaryConfig) -> TernaryWeights {
assert_eq!(
w.len(),
rows * cols,
"ternarize: len {} != rows*cols {}",
w.len(),
rows * cols
);
let scales = match cfg.scale {
TernaryScale::PerTensor => vec![absmean_scale(w, cfg.eps)],
TernaryScale::PerChannel => (0..rows)
.map(|r| absmean_scale(&w[r * cols..(r + 1) * cols], cfg.eps))
.collect(),
};
let mut codes = Vec::with_capacity(w.len());
for r in 0..rows {
let g = if scales.len() == 1 {
scales[0]
} else {
scales[r]
};
codes.extend(
w[r * cols..(r + 1) * cols]
.iter()
.map(|&v| ternary_code(v, g)),
);
}
TernaryWeights {
codes,
scales,
rows,
cols,
}
}
pub fn fake_ternarize(w: &[f32], rows: usize, cols: usize, cfg: &TernaryConfig) -> Vec<f32> {
ternarize(w, rows, cols, cfg).dequantize()
}
pub fn quantize_act_per_token(
x: &[f32],
tokens: usize,
features: usize,
eps: f32,
) -> (Vec<i8>, Vec<f32>) {
assert_eq!(x.len(), tokens * features);
let mut codes = Vec::with_capacity(x.len());
let mut scales = Vec::with_capacity(tokens);
for t in 0..tokens {
let row = &x[t * features..(t + 1) * features];
let amax = row.iter().fold(0.0f32, |m, v| m.max(v.abs())).max(eps);
let s = BITNET_ACT_QMAX / amax;
scales.push(s);
codes.extend(
row.iter()
.map(|&v| (v * s).round_ties_even().clamp(-128.0, 127.0) as i8),
);
}
(codes, scales)
}
pub fn fake_quantize_act_per_token(
x: &[f32],
tokens: usize,
features: usize,
eps: f32,
) -> Vec<f32> {
let (codes, scales) = quantize_act_per_token(x, tokens, features, eps);
codes
.chunks(features.max(1))
.zip(&scales)
.flat_map(|(row, &s)| row.iter().map(move |&c| f32::from(c) / s))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bitnet_absmean_codes() {
let w = [0.9, -0.1, 0.4, -1.2];
let t = ternarize(&w, 1, 4, &TernaryConfig::default());
assert!((t.scales[0] - 0.65).abs() < 1e-7);
assert_eq!(t.codes, vec![1, 0, 1, -1]);
assert_eq!(t.sparsity(), 0.25);
}
#[test]
fn per_channel_has_row_scales() {
let w = [1.0, -1.0, 0.0, 0.0, 10.0, 0.0];
let cfg = TernaryConfig {
scale: TernaryScale::PerChannel,
..TernaryConfig::default()
};
let t = ternarize(&w, 2, 3, &cfg);
assert_eq!(t.scales.len(), 2);
assert_eq!(t.codes, vec![1, -1, 0, 0, 1, 0]);
let pt = ternarize(&w, 2, 3, &TernaryConfig::default());
assert_eq!(&pt.codes[..3], &[0, 0, 0]);
}
#[test]
fn all_zero_weights_use_eps() {
let t = ternarize(&[0.0; 4], 2, 2, &TernaryConfig::default());
assert_eq!(t.scales, vec![BITNET_EPS]);
assert!(t.codes.iter().all(|&c| c == 0));
}
#[test]
fn ties_round_to_even() {
assert_eq!(ternary_code(0.5, 1.0), 0);
assert_eq!(ternary_code(-0.5, 1.0), 0);
assert_eq!(ternary_code(1.5, 1.0), 1);
}
#[test]
fn act_per_token_roundtrip() {
let x = [0.5, -1.0, 0.25, 2.0, 0.0, -0.5];
let (codes, scales) = quantize_act_per_token(&x, 2, 3, BITNET_EPS);
assert_eq!(&codes[..3], &[64, -127, 32]);
assert_eq!(&codes[3..], &[127, 0, -32]);
assert!((scales[0] - 127.0).abs() < 1e-6);
let back = fake_quantize_act_per_token(&x, 2, 3, BITNET_EPS);
for (a, b) in x.iter().zip(&back) {
assert!((a - b).abs() <= 2.0 / 127.0 * 0.5 + 1e-6, "{a} vs {b}");
}
}
}