metrale_circuit/
format.rs1use std::fmt;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
16pub enum Scale {
17 PerTensor,
19 PerToken,
21 PerChannel,
23 Group(u32),
25 Block(u32, u32),
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
31pub enum Format {
32 Bf16,
34 F32,
36 I32,
38 Fp8E4m3 {
40 scale: Scale,
42 },
43 Nvfp4 {
45 group: u32,
47 },
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
52#[error(
53 "unknown format `{0}` (expected bf16, f32, i32, fp8/<token|tensor|channel|g<n>|block<r>x<c>> or nvfp4/g<n>)"
54)]
55pub struct FormatError(pub String);
56
57impl Format {
58 pub fn parse(s: &str) -> Result<Self, FormatError> {
61 let bad = || FormatError(s.to_string());
62 match s {
63 "bf16" => return Ok(Format::Bf16),
64 "f32" => return Ok(Format::F32),
65 "i32" => return Ok(Format::I32),
66 _ => {}
67 }
68 let (head, tail) = s.split_once('/').ok_or_else(bad)?;
69 match head {
70 "fp8" => Ok(Format::Fp8E4m3 {
71 scale: parse_scale(tail).ok_or_else(bad)?,
72 }),
73 "nvfp4" => {
74 let group = tail.strip_prefix('g').and_then(positive).ok_or_else(bad)?;
75 Ok(Format::Nvfp4 { group })
76 }
77 _ => Err(bad()),
78 }
79 }
80
81 pub fn name(&self) -> String {
83 match self {
84 Format::Bf16 => "bf16".into(),
85 Format::F32 => "f32".into(),
86 Format::I32 => "i32".into(),
87 Format::Fp8E4m3 { scale } => format!("fp8/{}", scale_name(*scale)),
88 Format::Nvfp4 { group } => format!("nvfp4/g{group}"),
89 }
90 }
91
92 pub fn is_plain(&self) -> bool {
94 matches!(self, Format::Bf16 | Format::F32 | Format::I32)
95 }
96
97 pub fn is_edge_format(&self) -> bool {
99 match self {
100 Format::Bf16 | Format::F32 | Format::I32 | Format::Nvfp4 { .. } => true,
101 Format::Fp8E4m3 { scale } => {
102 matches!(scale, Scale::PerTensor | Scale::PerToken | Scale::Group(_))
103 }
104 }
105 }
106
107 pub fn weight_bytes(&self, n: u64, k: u64) -> Option<u64> {
111 match self {
112 Format::Nvfp4 { .. } => self
113 .bytes(n, k)?
114 .checked_sub(n.checked_mul(4)?)?
115 .checked_add(4),
116 _ => self.bytes(n, k),
117 }
118 }
119
120 pub fn bytes(&self, rows: u64, dim: u64) -> Option<u64> {
123 let elems = rows.checked_mul(dim)?;
124 match self {
125 Format::Bf16 => elems.checked_mul(2),
126 Format::F32 | Format::I32 => elems.checked_mul(4),
127 Format::Fp8E4m3 { scale } => {
128 let scales = match scale {
129 Scale::PerTensor => 1,
130 Scale::PerToken => rows,
131 Scale::PerChannel => dim,
132 Scale::Group(g) => {
133 let g = u64::from(*g);
134 if !dim.is_multiple_of(g) {
135 return None;
136 }
137 rows.checked_mul(dim / g)?
138 }
139 Scale::Block(r, c) => {
140 let (r, c) = (u64::from(*r), u64::from(*c));
141 rows.div_ceil(r).checked_mul(dim.div_ceil(c))?
142 }
143 };
144 elems.checked_add(scales.checked_mul(4)?)
145 }
146 Format::Nvfp4 { group } => {
147 let g = u64::from(*group);
148 if !dim.is_multiple_of(g) || !elems.is_multiple_of(2) {
149 return None;
150 }
151 (elems / 2)
155 .checked_add(elems / g)?
156 .checked_add(rows.checked_mul(4)?)
157 }
158 }
159 }
160}
161
162impl fmt::Display for Format {
163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164 f.write_str(&self.name())
165 }
166}
167
168fn positive(s: &str) -> Option<u32> {
169 s.parse::<u32>().ok().filter(|&n| n > 0)
170}
171
172fn parse_scale(s: &str) -> Option<Scale> {
173 match s {
174 "tensor" => Some(Scale::PerTensor),
175 "token" => Some(Scale::PerToken),
176 "channel" => Some(Scale::PerChannel),
177 _ => {
178 if let Some(g) = s.strip_prefix('g') {
179 return positive(g).map(Scale::Group);
180 }
181 let (r, c) = s.strip_prefix("block")?.split_once('x')?;
182 Some(Scale::Block(positive(r)?, positive(c)?))
183 }
184 }
185}
186
187fn scale_name(s: Scale) -> String {
188 match s {
189 Scale::PerTensor => "tensor".into(),
190 Scale::PerToken => "token".into(),
191 Scale::PerChannel => "channel".into(),
192 Scale::Group(g) => format!("g{g}"),
193 Scale::Block(r, c) => format!("block{r}x{c}"),
194 }
195}
196
197#[cfg(test)]
198#[path = "format_tests.rs"]
199mod format_tests;