use crate::NodeId;
use std::collections::HashMap;
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum QuantScheme {
Int8Block { block_size: u32 },
Int8BlockAsym { block_size: u32 },
Int4Block { block_size: u32 },
Fp8E4m3,
Fp8E5m2,
GgufQ4K,
GgufQ5K,
GgufQ6K,
GgufQ8K,
GgufQ2K,
GgufQ3K,
GgufQ4_0,
GgufQ4_1,
GgufQ5_0,
GgufQ5_1,
GgufQ8_0,
Nvfp4Block,
GgufIQ4NL,
GgufIQ4XS,
GgufIQ2XXS,
GgufIQ2XS,
GgufIQ2S,
GgufIQ3XXS,
GgufIQ3S,
GgufIQ1S,
GgufIQ1M,
GgufTQ1_0,
GgufTQ2_0,
GgufMXFP4,
GgufNVFP4,
}
impl QuantScheme {
pub const fn bits_per_element_x10(self) -> u32 {
match self {
Self::Int8Block { .. } | Self::Int8BlockAsym { .. } => 80,
Self::Int4Block { .. } => 40,
Self::Fp8E4m3 | Self::Fp8E5m2 => 80,
Self::GgufQ4K => 45, Self::GgufQ5K => 55, Self::GgufQ6K => 66, Self::GgufQ8K => 91, Self::GgufQ2K => 26, Self::GgufQ3K => 34, Self::GgufQ4_0 => 45, Self::GgufQ4_1 => 50, Self::GgufQ5_0 => 55, Self::GgufQ5_1 => 60, Self::GgufQ8_0 => 85, Self::Nvfp4Block => 40,
Self::GgufIQ4NL => 45,
Self::GgufIQ4XS => 42, Self::GgufIQ2XXS => 20,
Self::GgufIQ2XS => 23,
Self::GgufIQ2S => 25,
Self::GgufIQ3XXS => 30,
Self::GgufIQ3S => 34,
Self::GgufIQ1S => 15,
Self::GgufIQ1M => 17,
Self::GgufTQ1_0 => 16, Self::GgufTQ2_0 => 20,
Self::GgufMXFP4 => 42,
Self::GgufNVFP4 => 45,
}
}
pub const fn bits_per_element(self) -> u32 {
self.bits_per_element_x10() / 10
}
pub const fn has_scale(self) -> bool {
matches!(
self,
Self::Int8Block { .. }
| Self::Int8BlockAsym { .. }
| Self::Int4Block { .. }
| Self::Nvfp4Block
)
}
pub const fn scale_is_fp8(self) -> bool {
matches!(self, Self::Nvfp4Block)
}
pub const fn nvfp4_group_size(self) -> u32 {
match self {
Self::Nvfp4Block => crate::nvfp4::NVFP4_GROUP_SIZE as u32,
_ => 0,
}
}
pub const fn has_zero_point(self) -> bool {
matches!(self, Self::Int8BlockAsym { .. })
}
pub const fn gguf_block_size(self) -> u32 {
match self {
Self::GgufQ4K
| Self::GgufQ5K
| Self::GgufQ6K
| Self::GgufQ8K
| Self::GgufQ2K
| Self::GgufQ3K
| Self::GgufIQ4XS
| Self::GgufIQ2XXS
| Self::GgufIQ2XS
| Self::GgufIQ2S
| Self::GgufIQ3XXS
| Self::GgufIQ3S
| Self::GgufIQ1S
| Self::GgufIQ1M
| Self::GgufTQ1_0
| Self::GgufTQ2_0 => 256,
Self::GgufQ4_0
| Self::GgufQ4_1
| Self::GgufQ5_0
| Self::GgufQ5_1
| Self::GgufQ8_0
| Self::GgufIQ4NL
| Self::GgufMXFP4 => 32,
Self::GgufNVFP4 => 16,
_ => 0,
}
}
pub const fn gguf_block_bytes(self) -> u32 {
match self {
Self::GgufQ4K => 144, Self::GgufQ5K => 176, Self::GgufQ6K => 210, Self::GgufQ8K => 292, Self::GgufQ2K => 84, Self::GgufQ3K => 110, Self::GgufQ4_0 => 18, Self::GgufQ4_1 => 20, Self::GgufQ5_0 => 22, Self::GgufQ5_1 => 24, Self::GgufQ8_0 => 34, Self::GgufIQ4NL => 18,
Self::GgufIQ4XS => 136,
Self::GgufIQ2XXS => 66,
Self::GgufIQ2XS => 74,
Self::GgufIQ2S => 82,
Self::GgufIQ3XXS => 98,
Self::GgufIQ3S => 110,
Self::GgufIQ1S => 50,
Self::GgufIQ1M => 56,
Self::GgufTQ1_0 => 54,
Self::GgufTQ2_0 => 66,
Self::GgufMXFP4 => 17,
Self::GgufNVFP4 => 9,
_ => 0,
}
}
pub const fn is_gguf(self) -> bool {
matches!(
self,
Self::GgufQ4K
| Self::GgufQ5K
| Self::GgufQ6K
| Self::GgufQ8K
| Self::GgufQ2K
| Self::GgufQ3K
| Self::GgufQ4_0
| Self::GgufQ4_1
| Self::GgufQ5_0
| Self::GgufQ5_1
| Self::GgufQ8_0
| Self::GgufIQ4NL
| Self::GgufIQ4XS
| Self::GgufIQ2XXS
| Self::GgufIQ2XS
| Self::GgufIQ2S
| Self::GgufIQ3XXS
| Self::GgufIQ3S
| Self::GgufIQ1S
| Self::GgufIQ1M
| Self::GgufTQ1_0
| Self::GgufTQ2_0
| Self::GgufMXFP4
| Self::GgufNVFP4
)
}
}
impl std::fmt::Display for QuantScheme {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Int8Block { block_size } => write!(f, "int8/{block_size}"),
Self::Int8BlockAsym { block_size } => write!(f, "int8a/{block_size}"),
Self::Int4Block { block_size } => write!(f, "int4/{block_size}"),
Self::Fp8E4m3 => write!(f, "fp8e4m3"),
Self::Fp8E5m2 => write!(f, "fp8e5m2"),
Self::GgufQ4K => write!(f, "gguf_q4k"),
Self::GgufQ5K => write!(f, "gguf_q5k"),
Self::GgufQ6K => write!(f, "gguf_q6k"),
Self::GgufQ8K => write!(f, "gguf_q8k"),
Self::GgufQ2K => write!(f, "gguf_q2k"),
Self::GgufQ3K => write!(f, "gguf_q3k"),
Self::GgufQ4_0 => write!(f, "gguf_q4_0"),
Self::GgufQ4_1 => write!(f, "gguf_q4_1"),
Self::GgufQ5_0 => write!(f, "gguf_q5_0"),
Self::GgufQ5_1 => write!(f, "gguf_q5_1"),
Self::GgufQ8_0 => write!(f, "gguf_q8_0"),
Self::Nvfp4Block => write!(f, "nvfp4/16"),
Self::GgufIQ4NL => write!(f, "gguf_iq4_nl"),
Self::GgufIQ4XS => write!(f, "gguf_iq4_xs"),
Self::GgufIQ2XXS => write!(f, "gguf_iq2_xxs"),
Self::GgufIQ2XS => write!(f, "gguf_iq2_xs"),
Self::GgufIQ2S => write!(f, "gguf_iq2_s"),
Self::GgufIQ3XXS => write!(f, "gguf_iq3_xxs"),
Self::GgufIQ3S => write!(f, "gguf_iq3_s"),
Self::GgufIQ1S => write!(f, "gguf_iq1_s"),
Self::GgufIQ1M => write!(f, "gguf_iq1_m"),
Self::GgufTQ1_0 => write!(f, "gguf_tq1_0"),
Self::GgufTQ2_0 => write!(f, "gguf_tq2_0"),
Self::GgufMXFP4 => write!(f, "gguf_mxfp4"),
Self::GgufNVFP4 => write!(f, "gguf_nvfp4"),
}
}
}
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ScaledFormat {
F8E4M3,
F8E5M2,
F8E4M3Fnuz,
F8E5M2Fnuz,
F6E2M3,
F6E3M2,
F4E2M1,
}
impl ScaledFormat {
pub const fn bit_width(self) -> u32 {
match self {
Self::F8E4M3 | Self::F8E5M2 | Self::F8E4M3Fnuz | Self::F8E5M2Fnuz => 8,
Self::F6E2M3 | Self::F6E3M2 => 6,
Self::F4E2M1 => 4,
}
}
pub const fn fields(self) -> (u32, u32, i32) {
match self {
Self::F8E4M3 => (4, 3, 7),
Self::F8E5M2 => (5, 2, 15),
Self::F8E4M3Fnuz => (4, 3, 8),
Self::F8E5M2Fnuz => (5, 2, 16),
Self::F6E2M3 => (2, 3, 1),
Self::F6E3M2 => (3, 2, 3),
Self::F4E2M1 => (2, 1, 1),
}
}
pub const fn is_fnuz(self) -> bool {
matches!(self, Self::F8E4M3Fnuz | Self::F8E5M2Fnuz)
}
pub const fn has_inf(self) -> bool {
matches!(self, Self::F8E5M2)
}
pub fn max_finite(self) -> f32 {
crate::lowp_codec::max_finite(self)
}
pub const fn kernel_id(self) -> u32 {
match self {
Self::F8E4M3 => 0,
Self::F8E5M2 => 1,
Self::F8E4M3Fnuz => 2,
Self::F8E5M2Fnuz => 3,
Self::F6E2M3 => 4,
Self::F6E3M2 => 5,
Self::F4E2M1 => 6,
}
}
pub const fn is_native_fp8(self) -> bool {
matches!(self, Self::F8E4M3 | Self::F8E5M2)
}
}
impl std::fmt::Display for ScaledFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Self::F8E4M3 => "f8e4m3",
Self::F8E5M2 => "f8e5m2",
Self::F8E4M3Fnuz => "f8e4m3fnuz",
Self::F8E5M2Fnuz => "f8e5m2fnuz",
Self::F6E2M3 => "f6e2m3",
Self::F6E3M2 => "f6e3m2",
Self::F4E2M1 => "f4e2m1",
};
f.write_str(s)
}
}
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ScaleLayout {
PerTensor,
BlockMxE8M0 { block: u32 },
Nvfp4 { group: u32 },
}
impl ScaleLayout {
pub const fn mx() -> Self {
Self::BlockMxE8M0 { block: 32 }
}
pub fn nvfp4() -> Self {
Self::Nvfp4 {
group: crate::nvfp4::NVFP4_GROUP_SIZE as u32,
}
}
pub const fn scale_dtype(self) -> crate::DType {
match self {
Self::PerTensor => crate::DType::F32,
Self::BlockMxE8M0 { .. } | Self::Nvfp4 { .. } => crate::DType::U8,
}
}
pub const fn block(self) -> u32 {
match self {
Self::PerTensor => 1,
Self::BlockMxE8M0 { block } => block,
Self::Nvfp4 { group } => group,
}
}
pub const fn mode_block(self) -> (u32, u32) {
match self {
Self::PerTensor => (0, 1),
Self::BlockMxE8M0 { block } => (1, block),
Self::Nvfp4 { group } => (2, group),
}
}
}
impl std::fmt::Display for ScaleLayout {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::PerTensor => write!(f, "per_tensor"),
Self::BlockMxE8M0 { block } => write!(f, "mx_e8m0/{block}"),
Self::Nvfp4 { group } => write!(f, "nvfp4/{group}"),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct QuantMap {
map: HashMap<NodeId, QuantScheme>,
}
impl QuantMap {
pub fn new() -> Self {
Self::default()
}
pub fn get(&self, id: NodeId) -> Option<QuantScheme> {
self.map.get(&id).copied()
}
pub fn insert(&mut self, id: NodeId, scheme: QuantScheme) -> Option<QuantScheme> {
self.map.insert(id, scheme)
}
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
pub fn len(&self) -> usize {
self.map.len()
}
pub fn iter(&self) -> impl Iterator<Item = (&NodeId, &QuantScheme)> {
self.map.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scheme_traits() {
assert_eq!(
QuantScheme::Int4Block { block_size: 32 }.bits_per_element(),
4
);
assert!(QuantScheme::Int8BlockAsym { block_size: 64 }.has_zero_point());
assert!(!QuantScheme::Fp8E4m3.has_scale());
}
#[test]
fn quant_map_lookup() {
let mut q = QuantMap::new();
let id = NodeId(7);
q.insert(id, QuantScheme::Int8Block { block_size: 32 });
assert_eq!(q.get(id), Some(QuantScheme::Int8Block { block_size: 32 }));
assert_eq!(q.get(NodeId(99)), None);
}
}