use smallvec::SmallVec;
use crate::dtype::DType;
use crate::quant_scale::ScaleGranularity;
use crate::quantized::GgmlDType;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ScaleSpec {
pub dtype: DType,
pub granularity: ScaleGranularity,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Encoding {
GgmlBlock { ggml_dtype: GgmlDType },
AffineBlock {
packed: DType,
block_shape: SmallVec<[u32; 2]>,
scale: ScaleSpec,
zero_point: Option<ScaleSpec>,
},
Mx,
}
impl Encoding {
pub fn requires_scale_sibling(&self) -> bool {
matches!(self, Encoding::AffineBlock { .. })
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
pub struct SType(pub SmallVec<[Encoding; 1]>);
impl SType {
pub fn plain() -> Self {
SType(SmallVec::new())
}
pub fn from_layer(e: Encoding) -> Self {
let mut v = SmallVec::new();
v.push(e);
SType(v)
}
pub fn is_plain(&self) -> bool {
self.0.is_empty()
}
pub fn layers(&self) -> &[Encoding] {
&self.0
}
pub fn requires_scale_sibling(&self) -> bool {
self.0.iter().any(Encoding::requires_scale_sibling)
}
}
#[cfg(feature = "dlpack")]
impl SType {
pub fn to_fdx(
&self,
scale_buffer: Option<u32>,
) -> crate::Result<Option<crate::dlpack::sidecar::FDXQuant>> {
use crate::dlpack::codes::*;
use crate::dlpack::convert::{dtype_to_fdx, ggml_to_fdx, scale_granularity_to_fdx};
use crate::dlpack::sidecar::FDXQuant;
let Some(layer) = self.0.first() else {
return Ok(None);
};
Ok(match layer {
Encoding::GgmlBlock { ggml_dtype } => Some(FDXQuant {
family: FDX_QUANT_GGML_BLOCK,
ggml_dtype: ggml_to_fdx(*ggml_dtype),
block_ndim: 0,
_pad0: [0; 3],
block_shape: [0; 4],
block_axes: [-1; 4],
pack_order: 0,
_pad1: [0; 3],
scale_present: 0,
scale_dtype: FDX_DTYPE_NONE,
scale_placement: FDX_SCALE_PLACEMENT_INLINE,
scale_granularity: 0,
_pad2: [0; 3],
scale_buffer: FDX_BUFFER_INLINE,
zp_present: 0,
zp_dtype: FDX_DTYPE_NONE,
_pad3: 0,
zp_buffer: FDX_BUFFER_NONE,
scale_pair_act: 0,
scale_pair_weight: 0,
role: 0,
_pad4: 0,
reserved: [0; 6],
}),
Encoding::AffineBlock {
packed: _,
block_shape,
scale,
zero_point,
} => {
let mut bshape = [0u32; 4];
let mut baxes = [-1i32; 4];
let n = block_shape.len().min(4);
for i in 0..n {
bshape[i] = block_shape[i];
baxes[i] = i as i32; }
let (zp_present, zp_dtype) = match zero_point {
Some(zp) => (1u8, dtype_to_fdx(zp.dtype)?.namespace_code()),
None => (0u8, FDX_DTYPE_NONE),
};
Some(FDXQuant {
family: FDX_QUANT_AFFINE_BLOCK,
ggml_dtype: FDX_DTYPE_NONE, block_ndim: n as u8,
_pad0: [0; 3],
block_shape: bshape,
block_axes: baxes,
pack_order: 0,
_pad1: [0; 3],
scale_present: 1,
scale_dtype: dtype_to_fdx(scale.dtype)?.namespace_code(),
scale_placement: FDX_SCALE_PLACEMENT_SEPARATE_BUFFER, scale_granularity: scale_granularity_to_fdx(scale.granularity),
_pad2: [0; 3],
scale_buffer: scale_buffer.unwrap_or(FDX_BUFFER_NONE),
zp_present,
zp_dtype,
_pad3: 0,
zp_buffer: FDX_BUFFER_NONE,
scale_pair_act: 0, scale_pair_weight: 0,
role: 0,
_pad4: 0,
reserved: [0; 6],
})
}
Encoding::Mx => None,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::DType;
use crate::quant_scale::ScaleGranularity;
use crate::quantized::GgmlDType;
#[test]
fn default_stype_is_empty_plain() {
let s = SType::default();
assert!(
s.is_plain(),
"default SType must be plain (empty layer stack)"
);
assert_eq!(s.layers().len(), 0);
}
#[test]
fn stype_is_eq_and_hash() {
use std::collections::HashSet;
let a = SType::from_layer(Encoding::GgmlBlock {
ggml_dtype: GgmlDType::Q4_0,
});
let b = SType::from_layer(Encoding::GgmlBlock {
ggml_dtype: GgmlDType::Q4_0,
});
assert_eq!(a, b);
let mut set = HashSet::new();
set.insert(a);
assert!(set.contains(&b), "equal STypes must hash equal");
}
#[test]
fn affine_block_holds_static_scale_requirement() {
let enc = Encoding::AffineBlock {
packed: DType::F4,
block_shape: smallvec::smallvec![64],
scale: ScaleSpec {
dtype: DType::F32,
granularity: ScaleGranularity::PerChannel,
},
zero_point: None,
};
let s = SType::from_layer(enc.clone());
assert!(!s.is_plain());
assert_eq!(s.layers()[0], enc);
match &s.layers()[0] {
Encoding::AffineBlock {
packed,
block_shape,
..
} => {
assert_eq!(*packed, DType::F4);
assert_eq!(block_shape.as_slice(), &[64u32]);
}
_ => panic!("expected AffineBlock"),
}
}
#[test]
fn ggml_block_is_inline_single_layer() {
let s = SType::from_layer(Encoding::GgmlBlock {
ggml_dtype: GgmlDType::Q4K,
});
assert_eq!(s.layers().len(), 1);
assert!(
!s.requires_scale_sibling(),
"GGML scale is baked inline; no sibling operand required"
);
}
#[test]
fn affine_block_requires_scale_sibling() {
let s = SType::from_layer(Encoding::AffineBlock {
packed: DType::F4,
block_shape: smallvec::smallvec![64],
scale: ScaleSpec {
dtype: DType::F32,
granularity: ScaleGranularity::PerChannel,
},
zero_point: None,
});
assert!(s.requires_scale_sibling());
}
#[test]
fn gap190_affine_block_cannot_distinguish_codebook_from_linear() {
#[derive(Debug, Clone, Copy)]
enum QuantFamily {
Nf4,
Marlin,
}
fn descriptor(f: QuantFamily) -> Encoding {
let (packed, block_elems) = match f {
QuantFamily::Nf4 => (DType::F4, 64u32),
QuantFamily::Marlin => (DType::F4, 64u32),
};
Encoding::AffineBlock {
packed,
block_shape: smallvec::smallvec![block_elems],
scale: ScaleSpec {
dtype: DType::F32,
granularity: ScaleGranularity::PerChannel,
},
zero_point: None,
}
}
let nf4 = descriptor(QuantFamily::Nf4);
let marlin = descriptor(QuantFamily::Marlin);
assert_eq!(
nf4, marlin,
"GAP-190: a codebook family (NF4) and a linear family (Marlin) \
produce the SAME `Encoding::AffineBlock`, so nothing keyed on it — \
`structure_key` included — can tell them apart. If this now FAILS, \
a discriminator was added: re-read GAP-190 and make kernel \
selection stop keying on `structure_key` alone."
);
fn hash_of(e: &Encoding) -> u64 {
use std::hash::{Hash, Hasher};
let mut h = std::collections::hash_map::DefaultHasher::new();
e.hash(&mut h);
h.finish()
}
assert_eq!(
hash_of(&nf4),
hash_of(&marlin),
"GAP-190: the two families collide under Hash too, which is what a \
plan cache actually keys on"
);
let other_block = Encoding::AffineBlock {
packed: DType::F4,
block_shape: smallvec::smallvec![32],
scale: ScaleSpec {
dtype: DType::F32,
granularity: ScaleGranularity::PerChannel,
},
zero_point: None,
};
assert_ne!(
nf4, other_block,
"non-vacuity: a DIFFERENT block extent must compare unequal, or the \
collision assertion above proves nothing"
);
assert_ne!(
nf4,
Encoding::GgmlBlock {
ggml_dtype: GgmlDType::Q4K
},
"non-vacuity: a different ENCODING must compare unequal"
);
}
}
#[cfg(all(test, feature = "dlpack"))]
mod to_fdx_tests {
use super::*;
use crate::DType;
use crate::dlpack::codes::*;
use crate::quant_scale::ScaleGranularity;
use crate::quantized::GgmlDType;
#[test]
fn plain_projects_to_none() {
assert!(
SType::plain()
.to_fdx(None)
.expect("plain never declines")
.is_none()
);
}
#[test]
fn ggml_projects_inline() {
let q = SType::from_layer(Encoding::GgmlBlock {
ggml_dtype: GgmlDType::Q4_0,
})
.to_fdx(None)
.expect("encodes")
.expect("GGML projects");
assert_eq!(q.family, FDX_QUANT_GGML_BLOCK);
assert_eq!(q.ggml_dtype, FDX_GGML_Q4_0);
assert_eq!(q.scale_present, 0);
assert_eq!(q.scale_placement, FDX_SCALE_PLACEMENT_INLINE);
assert_eq!(q.scale_buffer, FDX_BUFFER_INLINE);
}
#[test]
fn affine_block_projects_separate_buffer() {
let enc = Encoding::AffineBlock {
packed: DType::F4,
block_shape: smallvec::smallvec![64],
scale: ScaleSpec {
dtype: DType::F32,
granularity: ScaleGranularity::PerChannel,
},
zero_point: None,
};
let q = SType::from_layer(enc.clone())
.to_fdx(None)
.expect("encodes")
.expect("projects");
assert_eq!(q.family, FDX_QUANT_AFFINE_BLOCK);
assert_eq!(q.ggml_dtype, FDX_DTYPE_NONE);
assert_eq!(q.block_ndim, 1);
assert_eq!(q.block_shape[0], 64);
assert_eq!(q.block_axes[0], 0);
assert_eq!(q.scale_present, 1);
assert_eq!(q.scale_placement, FDX_SCALE_PLACEMENT_SEPARATE_BUFFER);
assert_eq!(q.scale_dtype, FDX_DTYPE_F32);
assert_eq!(q.scale_granularity, FDX_SCALE_GRAN_PER_CHANNEL);
assert_eq!(q.scale_buffer, FDX_BUFFER_NONE);
assert_eq!(q.zp_present, 0);
let bound = SType::from_layer(enc)
.to_fdx(Some(3))
.expect("encodes")
.expect("projects");
assert_eq!(bound.scale_buffer, 3);
}
#[test]
fn affine_block_zero_point_projects() {
let q = SType::from_layer(Encoding::AffineBlock {
packed: DType::F4,
block_shape: smallvec::smallvec![32],
scale: ScaleSpec {
dtype: DType::F32,
granularity: ScaleGranularity::PerToken,
},
zero_point: Some(ScaleSpec {
dtype: DType::F32,
granularity: ScaleGranularity::PerToken,
}),
})
.to_fdx(None)
.expect("encodes")
.expect("projects");
assert_eq!(q.zp_present, 1);
assert_eq!(q.zp_dtype, FDX_DTYPE_F32);
}
#[test]
fn mx_projects_to_none() {
assert!(
SType::from_layer(Encoding::Mx)
.to_fdx(None)
.expect("Mx never declines")
.is_none()
);
}
}