1use std::collections::BTreeMap;
39use std::fmt;
40
41use anyhow::{Result, bail, ensure};
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
45pub enum ActQuantFormat {
46 Bf16,
48 Fp8,
51 Nvfp4,
53 Declared,
55 Adaptive,
57}
58
59impl ActQuantFormat {
60 pub const ALL: [Self; 5] = [
62 Self::Bf16,
63 Self::Fp8,
64 Self::Nvfp4,
65 Self::Declared,
66 Self::Adaptive,
67 ];
68
69 pub fn name(self) -> &'static str {
71 match self {
72 Self::Bf16 => "bf16",
73 Self::Fp8 => "fp8",
74 Self::Nvfp4 => "nvfp4",
75 Self::Declared => "declared",
76 Self::Adaptive => "adaptive",
77 }
78 }
79
80 fn parse(s: &str) -> Result<Self> {
81 Self::ALL
82 .into_iter()
83 .find(|f| f.name() == s)
84 .ok_or_else(|| {
85 anyhow::anyhow!(
86 "unknown activation format {s:?} (one of {})",
87 Self::ALL.map(Self::name).join(", ")
88 )
89 })
90 }
91}
92
93#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
95pub enum ProjFamily {
96 Gdn,
98 Attn,
100 Ffn,
102 Moe,
104 LmHead,
106}
107
108impl ProjFamily {
109 pub const ALL: [Self; 5] = [Self::Gdn, Self::Attn, Self::Ffn, Self::Moe, Self::LmHead];
111
112 pub fn name(self) -> &'static str {
113 match self {
114 Self::Gdn => "gdn",
115 Self::Attn => "attn",
116 Self::Ffn => "ffn",
117 Self::Moe => "moe",
118 Self::LmHead => "lm_head",
119 }
120 }
121
122 fn parse(s: &str) -> Result<Self> {
123 Self::ALL
124 .into_iter()
125 .find(|f| f.name() == s)
126 .ok_or_else(|| {
127 anyhow::anyhow!(
128 "unknown projection family {s:?} (one of {})",
129 Self::ALL.map(Self::name).join(", ")
130 )
131 })
132 }
133}
134
135#[derive(Clone, Copy, Debug, PartialEq, Eq)]
137pub struct Rung {
138 pub lo: u32,
139 pub hi: Option<u32>,
140 pub format: ActQuantFormat,
141}
142
143#[derive(Clone, Debug, PartialEq, Eq)]
145pub struct Ladder(Vec<Rung>);
146
147impl Ladder {
148 pub fn uniform(format: ActQuantFormat) -> Self {
150 Self(vec![Rung {
151 lo: 1,
152 hi: None,
153 format,
154 }])
155 }
156
157 pub fn rungs(&self) -> &[Rung] {
158 &self.0
159 }
160
161 pub fn format(&self, rows: u32) -> ActQuantFormat {
163 let rows = rows.max(1);
164 self.0
165 .iter()
166 .find(|r| rows >= r.lo && r.hi.is_none_or(|hi| rows <= hi))
167 .map(|r| r.format)
168 .expect("a validated ladder covers every row count from 1")
169 }
170
171 pub fn is_uniform_invariant(&self) -> bool {
173 self.0.len() == 1 && self.0[0].format != ActQuantFormat::Adaptive
174 }
175
176 fn parse(s: &str) -> Result<Self> {
177 let s = s.trim();
178 ensure!(!s.is_empty(), "empty ladder");
179 if !s.contains('=') {
180 return Ok(Self::uniform(ActQuantFormat::parse(s)?));
181 }
182 let mut rungs = Vec::new();
183 for part in s.split(';') {
184 let (range, format) = part
185 .split_once('=')
186 .ok_or_else(|| anyhow::anyhow!("rung {part:?} is not RANGE=FORMAT"))?;
187 let (lo, hi) = parse_range(range.trim())?;
188 rungs.push(Rung {
189 lo,
190 hi,
191 format: ActQuantFormat::parse(format.trim())?,
192 });
193 }
194 Self::validated(rungs)
195 }
196
197 fn validated(rungs: Vec<Rung>) -> Result<Self> {
198 let mut next = 1u32;
199 for (i, r) in rungs.iter().enumerate() {
200 ensure!(
201 r.lo == next,
202 "rung {} starts at row {} but must start at row {next} (rungs run from row 1 \
203 in order, without gaps or overlaps)",
204 i + 1,
205 r.lo
206 );
207 match r.hi {
208 Some(hi) => {
209 ensure!(hi >= r.lo, "rung {} ends at {hi}, before it starts", i + 1);
210 next = hi
211 .checked_add(1)
212 .ok_or_else(|| anyhow::anyhow!("rung {} ends past u32", i + 1))?;
213 }
214 None => {
215 ensure!(
216 i + 1 == rungs.len(),
217 "rung {} is open-ended but rungs follow it",
218 i + 1
219 );
220 return Ok(Self(merge_equal(rungs)));
221 }
222 }
223 }
224 bail!("the last rung must be open-ended (N-), so every row count has a format")
225 }
226}
227
228fn merge_equal(rungs: Vec<Rung>) -> Vec<Rung> {
230 let mut out: Vec<Rung> = Vec::with_capacity(rungs.len());
231 for r in rungs {
232 match out.last_mut() {
233 Some(prev) if prev.format == r.format => prev.hi = r.hi,
234 _ => out.push(r),
235 }
236 }
237 out
238}
239
240fn parse_row(s: &str) -> Result<u32> {
241 let n: u32 = s
242 .parse()
243 .map_err(|_| anyhow::anyhow!("row bound {s:?} is not a whole number"))?;
244 ensure!(n >= 1, "rows are counted from 1");
245 Ok(n)
246}
247
248fn parse_range(s: &str) -> Result<(u32, Option<u32>)> {
249 match s.split_once('-') {
250 None => {
251 let n = parse_row(s)?;
252 Ok((n, Some(n)))
253 }
254 Some((lo, "")) => Ok((parse_row(lo)?, None)),
255 Some((lo, hi)) => Ok((parse_row(lo)?, Some(parse_row(hi)?))),
256 }
257}
258
259impl fmt::Display for Ladder {
260 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
261 if self.0.len() == 1 {
262 return f.write_str(self.0[0].format.name());
263 }
264 for (i, r) in self.0.iter().enumerate() {
265 if i > 0 {
266 f.write_str(";")?;
267 }
268 match r.hi {
269 Some(hi) if hi == r.lo => write!(f, "{}", r.lo)?,
270 Some(hi) => write!(f, "{}-{hi}", r.lo)?,
271 None => write!(f, "{}-", r.lo)?,
272 }
273 write!(f, "={}", r.format.name())?;
274 }
275 Ok(())
276 }
277}
278
279#[derive(Clone, Debug, PartialEq, Eq)]
281pub struct ActivationQuantization {
282 base: Ladder,
283 families: BTreeMap<ProjFamily, Ladder>,
284}
285
286impl Default for ActivationQuantization {
287 fn default() -> Self {
289 Self::uniform(ActQuantFormat::Declared)
290 }
291}
292
293impl ActivationQuantization {
294 pub fn uniform(format: ActQuantFormat) -> Self {
296 Self {
297 base: Ladder::uniform(format),
298 families: BTreeMap::new(),
299 }
300 }
301
302 pub fn adaptive() -> Self {
304 Self::uniform(ActQuantFormat::Adaptive)
305 }
306
307 pub fn parse(s: &str) -> Result<Self> {
308 let mut parts = s.split(',');
309 let base = Ladder::parse(parts.next().unwrap_or(""))
310 .map_err(|e| anyhow::anyhow!("--activation-quantization {s:?}: {e}"))?;
311 let mut families = BTreeMap::new();
312 for part in parts {
313 let (fam, ladder) = part.split_once(':').ok_or_else(|| {
314 anyhow::anyhow!(
315 "--activation-quantization {s:?}: override {part:?} is not FAMILY:LADDER"
316 )
317 })?;
318 let fam = ProjFamily::parse(fam.trim())
319 .map_err(|e| anyhow::anyhow!("--activation-quantization {s:?}: {e}"))?;
320 let ladder = Ladder::parse(ladder).map_err(|e| {
321 anyhow::anyhow!("--activation-quantization {s:?}, {}: {e}", fam.name())
322 })?;
323 ensure!(
324 families.insert(fam, ladder).is_none(),
325 "--activation-quantization {s:?}: family {} is overridden twice",
326 fam.name()
327 );
328 }
329 families.retain(|_, l| *l != base);
332 Ok(Self { base, families })
333 }
334
335 pub fn ladder(&self, family: ProjFamily) -> &Ladder {
337 self.families.get(&family).unwrap_or(&self.base)
338 }
339
340 pub fn route(&self, family: ProjFamily, rows: u32) -> ActQuantFormat {
342 self.ladder(family).format(rows)
343 }
344
345 pub fn is_adaptive(&self) -> bool {
347 ProjFamily::ALL.iter().all(|&f| {
348 self.ladder(f)
349 .rungs()
350 .iter()
351 .all(|r| r.format == ActQuantFormat::Adaptive)
352 })
353 }
354
355 pub fn is_invariant(&self, family: ProjFamily) -> bool {
358 self.ladder(family).is_uniform_invariant()
359 }
360}
361
362impl fmt::Display for ActivationQuantization {
363 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
364 write!(f, "{}", self.base)?;
365 for (fam, l) in &self.families {
366 write!(f, ",{}:{l}", fam.name())?;
367 }
368 Ok(())
369 }
370}
371
372#[cfg(test)]
373#[path = "activation_quantization_tests.rs"]
374mod activation_quantization_tests;