use alloc::vec::Vec;
pub use burn_std::{BlockLayout, QPARAM_ALIGN, params_shape};
use burn_std::{FloatDType, QuantScheme, ScaleDtype, Shape, quantization::global_scale_dtype};
use super::{Calibration, QuantizationParametersPrimitive};
use crate::{Backend, TensorMetadata, get_device_settings};
fn reduce_blocks<B: Backend>(
tensor: B::FloatTensorPrimitive,
scheme: &QuantScheme,
reduce: impl Fn(B::FloatTensorPrimitive, usize) -> B::FloatTensorPrimitive,
) -> B::FloatTensorPrimitive {
let shape = tensor.shape();
let block = scheme
.block_size()
.expect("only a block scheme has blocks to reduce");
let block = block.to_dim_vec(shape.num_dims());
let mut split = Vec::with_capacity(2 * shape.num_dims());
for (&dim, &extent) in shape.iter().zip(&block) {
let extent = extent as usize;
assert!(
dim.is_multiple_of(extent),
"Tensor {shape:?} must be evenly divisible by block size {block:?}"
);
split.push(dim / extent);
split.push(extent);
}
let mut blocks = B::float_reshape(tensor, Shape::from(split.clone()));
for axis in (1..split.len()).step_by(2).filter(|&axis| split[axis] > 1) {
blocks = reduce(blocks, axis);
}
B::float_reshape(blocks, params_shape(&shape, scheme))
}
pub fn compute_range<B: Backend>(
scheme: &QuantScheme,
tensor: B::FloatTensorPrimitive,
calibration: &Calibration,
) -> (B::FloatTensorPrimitive, B::FloatTensorPrimitive) {
match calibration {
Calibration::MinMax => match scheme.block_size() {
None => (B::float_min(tensor.clone()), B::float_max(tensor)),
Some(_) => (
reduce_blocks::<B>(tensor.clone(), scheme, B::float_min_dim),
reduce_blocks::<B>(tensor, scheme, B::float_max_dim),
),
},
Calibration::AbsMean => {
assert!(
global_scale_dtype(scheme).is_none(),
"AbsMean calibration has no two-level form: BitNet's gamma is a mean over \
the whole tensor or block, which a per-tensor scale cannot decompose"
);
let gamma = match scheme.block_size() {
None => B::float_mean(B::float_abs(tensor)),
Some(_) => reduce_blocks::<B>(B::float_abs(tensor), scheme, B::float_mean_dim),
};
let neg_gamma = B::float_neg(gamma.clone());
(neg_gamma, gamma)
}
}
}
pub fn compute_q_params<B: Backend>(
scheme: &QuantScheme,
min: B::FloatTensorPrimitive,
max: B::FloatTensorPrimitive,
) -> QuantizationParametersPrimitive<B> {
let bool_dtype = get_device_settings::<B>(&min.device()).bool_dtype;
let (a, b) = scheme.value.range();
let min_abs = B::float_abs(min);
let max_abs = B::float_abs(max);
let mask = B::float_lower(min_abs.clone(), max_abs.clone(), bool_dtype);
let values_range =
B::float_mul_scalar(B::float_mask_where(min_abs, mask, max_abs), 2f32.into());
let scales = B::float_div_scalar(values_range, (b - a).into());
let (scales, global) = match global_scale_dtype(scheme) {
None => (scales, None),
Some(_) => {
let (scales, global) = normalize_scales::<B>(scales, scheme.scale_dtype());
(scales, Some(global))
}
};
QuantizationParametersPrimitive { scales, global }
}
fn normalize_scales<B: Backend>(
scales: B::FloatTensorPrimitive,
block_dtype: ScaleDtype,
) -> (B::FloatTensorPrimitive, B::FloatTensorPrimitive) {
assert!(
block_dtype.max_representable() <= burn_std::f16::MAX.to_f32(),
"block scales in {block_dtype:?} reach f32's range, which a per-tensor scale cannot absorb"
);
let dtype = scales.dtype().into();
let scales_f32 = B::float_cast(scales, FloatDType::F32);
let global = B::float_div_scalar(
B::float_max(scales_f32.clone()),
block_dtype.max_representable().into(),
);
let global = B::float_clamp_min(global, f32::MIN_POSITIVE.into());
let broadcast = Shape::from(core::iter::repeat_n(1usize, scales_f32.shape().num_dims()));
let scales = B::float_div(scales_f32, B::float_reshape(global.clone(), broadcast));
(B::float_cast(scales, dtype), global)
}