1use cubecl::client::Client;
4use cubecl::e2m1x2;
5use cubecl::ir::features::TypeUsage;
6use cubecl::ir::types::Fp8Format;
7use cubecl::ir::{ElemType, FloatKind, IntKind};
8use cubecl::post_processing::fp4::E2M1_F16_LIFT;
9use cubecl::post_processing::minifloat::fp8_f16_lift;
10use cubecl::prelude::Scalar;
11use cubecl::quant::scheme::QuantValue;
12use cubecl::quant::scheme::ScaleDtype;
13
14#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
16pub(crate) enum Packing {
17 Plain,
19 Packed {
21 field: Field,
23 },
24}
25
26#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
28pub enum Field {
29 Quant(QuantValue),
30 Fp8(Fp8Format),
31 Float(FloatKind),
32 Index {
35 bits: usize,
36 },
37 ConvertedE2M1,
41 Placed {
47 value: QuantValue,
48 lifted: bool,
49 },
50}
51
52impl From<QuantValue> for Field {
53 fn from(value: QuantValue) -> Self {
54 Field::Quant(value)
55 }
56}
57
58impl From<Fp8Format> for Field {
59 fn from(format: Fp8Format) -> Self {
60 Field::Fp8(format)
61 }
62}
63
64impl From<FloatKind> for Field {
66 fn from(kind: FloatKind) -> Self {
67 Field::of_float(kind)
68 }
69}
70
71impl Field {
72 pub fn size_bits(self) -> usize {
74 match self {
75 Field::Quant(value) => value.size_bits(),
76 Field::Fp8(_) => 8,
77 Field::Float(kind) => Field::float_bits(kind),
78 Field::Index { bits } => bits,
79 Field::ConvertedE2M1 => QuantValue::E2M1.size_bits(),
80 Field::Placed { value, .. } => value.size_bits(),
81 }
82 }
83
84 pub fn per_word(self) -> usize {
86 u32::BITS as usize / self.size_bits()
87 }
88
89 pub fn holds_e2m1(self) -> bool {
92 matches!(
93 self,
94 Field::Quant(QuantValue::E2M1)
95 | Field::ConvertedE2M1
96 | Field::Placed {
97 value: QuantValue::E2M1,
98 ..
99 }
100 )
101 }
102}
103
104impl Field {
105 pub fn read_on(self, client: &Client) -> Field {
111 let converted_as = match self {
112 Field::Quant(QuantValue::E2M1) => Some(FloatKind::E2M1x2),
113 Field::Quant(QuantValue::E4M3) => Some(FloatKind::E4M3),
114 Field::Quant(QuantValue::E5M2) => Some(FloatKind::E5M2),
115 _ => None,
116 };
117 let emulated = converted_as.is_some_and(|kind| {
118 client
119 .features()
120 .conversion_is_emulated(ElemType::Float(kind))
121 });
122 let converts = e2m1x2::supported_uses(client).contains(TypeUsage::Conversion);
123 match self {
124 Field::Quant(value) if emulated => Field::Placed {
125 value,
126 lifted: true,
127 },
128 Field::Quant(QuantValue::E2M1) if converts => Field::ConvertedE2M1,
129 other => other,
130 }
131 }
132
133 pub(crate) fn placed(self) -> Field {
136 match self {
137 Field::Placed { value, .. } => Field::Placed {
138 value,
139 lifted: false,
140 },
141 other => other,
142 }
143 }
144
145 pub(crate) fn lift(self) -> f64 {
148 match self.decode() {
149 FieldDecode::Placed { lifted: false } => E2M1_F16_LIFT,
150 FieldDecode::PlacedByte {
151 format,
152 lifted: false,
153 } => fp8_f16_lift(format),
154 _ => 1.0,
155 }
156 }
157
158 pub fn of_float(kind: FloatKind) -> Field {
160 match kind {
161 FloatKind::E4M3 => Field::Fp8(Fp8Format::E4M3),
162 FloatKind::E5M2 => Field::Fp8(Fp8Format::E5M2),
163 FloatKind::UE8M0 => Field::Fp8(Fp8Format::UE8M0),
164 other => Field::Float(other),
165 }
166 }
167
168 pub fn of_scale(dtype: ScaleDtype) -> Field {
170 match ElemType::from_scale_dtype(dtype) {
171 ElemType::Float(kind) => Field::of_float(kind),
172 other => panic!("Field: a scale is stored as a float, and {dtype:?} is {other:?}"),
173 }
174 }
175
176 pub(crate) fn quantized_value(self) -> QuantValue {
184 match self {
185 Field::Quant(value) | Field::Placed { value, .. } => value,
186 Field::ConvertedE2M1 => QuantValue::E2M1,
187 other => panic!("Field: a value is quantized to a number format, not to {other:?}"),
188 }
189 }
190
191 pub(crate) fn scale_dtype(self) -> ScaleDtype {
198 match self {
199 Field::Fp8(Fp8Format::E4M3) => ScaleDtype::UE4M3,
200 Field::Fp8(Fp8Format::UE8M0) => ScaleDtype::UE8M0,
201 Field::Float(FloatKind::F32) => ScaleDtype::F32,
202 Field::Float(FloatKind::F16) => ScaleDtype::F16,
203 Field::Float(FloatKind::BF16) => ScaleDtype::BF16,
204 other => panic!("Field: a scale is stored as a float, not as {other:?}"),
205 }
206 }
207
208 pub(crate) fn float_bits(kind: FloatKind) -> usize {
210 match kind {
211 FloatKind::F32 => 32,
212 FloatKind::F16 | FloatKind::BF16 => 16,
213 other => panic!(
214 "Field::Float: a stored float is 16 or 32 bits wide, got {other:?}; an 8-bit \
215 code is Field::Fp8"
216 ),
217 }
218 }
219
220 pub(crate) fn decode(self) -> FieldDecode {
222 match self {
223 Field::Quant(
224 QuantValue::Q8F
225 | QuantValue::Q8S
226 | QuantValue::Q4F
227 | QuantValue::Q4S
228 | QuantValue::Q2F
229 | QuantValue::Q2S,
230 ) => FieldDecode::SignExtended,
231 Field::Quant(QuantValue::E2M1) => FieldDecode::Reinterpreted,
232 Field::Quant(QuantValue::E4M3) => FieldDecode::Byte(Fp8Format::E4M3),
233 Field::Quant(QuantValue::E5M2) => FieldDecode::Byte(Fp8Format::E5M2),
234 Field::Fp8(format) => FieldDecode::Byte(format),
235 Field::Float(kind) => FieldDecode::Bits(kind),
236 Field::Index { .. } => FieldDecode::Unsigned,
237 Field::ConvertedE2M1 => FieldDecode::Converted,
238 Field::Placed { value, lifted } => match Field::Quant(value).decode() {
240 FieldDecode::Reinterpreted => FieldDecode::Placed { lifted },
241 FieldDecode::Byte(format) => FieldDecode::PlacedByte { format, lifted },
242 other => {
243 panic!("Field::Placed: {value:?} reads back as {other:?}, not a float code")
244 }
245 },
246 }
247 }
248}
249
250#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
252pub(crate) enum FieldDecode {
253 SignExtended,
255 Unsigned,
257 Reinterpreted,
259 Converted,
261 Placed { lifted: bool },
264 PlacedByte { format: Fp8Format, lifted: bool },
267 Byte(Fp8Format),
269 Bits(FloatKind),
271}
272
273impl Packing {
274 pub(crate) fn quantized_value(self, element: ElemType) -> QuantValue {
282 match (self, element) {
283 (Packing::Packed { field }, _) => field.quantized_value(),
284 (Packing::Plain, ElemType::Int(IntKind::I8)) => QuantValue::Q8S,
285 (Packing::Plain, ElemType::Float(FloatKind::E4M3)) => QuantValue::E4M3,
286 (Packing::Plain, ElemType::Float(FloatKind::E5M2)) => QuantValue::E5M2,
287 (Packing::Plain, other) => {
288 panic!("Packing: no value is quantized to a plain {other:?}")
289 }
290 }
291 }
292
293 pub(crate) fn scale_dtype(self, element: ElemType) -> ScaleDtype {
300 match (self, element) {
301 (Packing::Packed { field }, _) => field.scale_dtype(),
302 (Packing::Plain, ElemType::Float(kind)) => Field::of_float(kind).scale_dtype(),
303 (Packing::Plain, other) => panic!("Packing: a scale is a float, not {other:?}"),
304 }
305 }
306
307 pub(crate) fn placed(self) -> Packing {
309 match self {
310 Packing::Packed { field } => Packing::Packed {
311 field: field.placed(),
312 },
313 Packing::Plain => Packing::Plain,
314 }
315 }
316
317 pub(crate) fn lift(self) -> f64 {
319 match self {
320 Packing::Packed { field } => field.lift(),
321 Packing::Plain => 1.0,
322 }
323 }
324
325 pub(crate) fn read_on(self, client: &Client) -> Packing {
327 match self {
328 Packing::Packed { field } => Packing::Packed {
329 field: field.read_on(client),
330 },
331 Packing::Plain => Packing::Plain,
332 }
333 }
334}
335
336impl Packing {
337 fn factor(&self) -> usize {
339 match self {
340 Packing::Plain => 1,
341 Packing::Packed { field } => field.per_word(),
342 }
343 }
344
345 pub(crate) fn physical(&self, served: usize) -> usize {
347 served / self.factor()
348 }
349
350 pub(crate) fn served(&self, bound: usize) -> usize {
352 bound * self.factor()
353 }
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359
360 #[test]
361 fn a_packing_narrows_the_line_it_stores() {
362 assert_eq!(Packing::Plain.physical(16), 16);
363 assert_eq!(
364 Packing::Packed {
365 field: QuantValue::Q4S.into()
366 }
367 .physical(16),
368 2
369 );
370 }
371
372 #[test]
373 fn a_field_states_how_many_fit_in_a_word() {
374 assert_eq!(
375 Packing::Packed {
376 field: QuantValue::Q4S.into()
377 }
378 .factor(),
379 8
380 );
381 assert_eq!(
382 Packing::Packed {
383 field: QuantValue::Q8S.into()
384 }
385 .factor(),
386 4
387 );
388 assert_eq!(
389 Packing::Packed {
390 field: QuantValue::Q2S.into()
391 }
392 .factor(),
393 16
394 );
395 assert_eq!(
396 Packing::Packed {
397 field: Fp8Format::UE8M0.into()
398 }
399 .factor(),
400 4
401 );
402 }
403
404 #[test]
405 fn a_float_element_names_its_own_field() {
406 assert_eq!(
407 Field::of_float(FloatKind::UE8M0),
408 Field::Fp8(Fp8Format::UE8M0)
409 );
410 assert_eq!(
411 Field::of_float(FloatKind::E4M3),
412 Field::Fp8(Fp8Format::E4M3)
413 );
414 assert_eq!(
415 Field::of_float(FloatKind::F16),
416 Field::Float(FloatKind::F16)
417 );
418 }
419
420 #[test]
421 fn a_stored_float_is_a_field_of_its_own_width() {
422 assert_eq!(Field::Float(FloatKind::F32).per_word(), 1);
423 assert_eq!(Field::Float(FloatKind::F16).per_word(), 2);
424 assert_eq!(Field::Float(FloatKind::BF16).size_bits(), 16);
425 assert_eq!(
426 Field::from(FloatKind::F32).decode(),
427 FieldDecode::Bits(FloatKind::F32)
428 );
429 }
430
431 #[test]
432 fn a_byte_field_decodes_through_its_format() {
433 assert_eq!(
434 Field::from(QuantValue::E4M3).decode(),
435 FieldDecode::Byte(Fp8Format::E4M3)
436 );
437 assert_eq!(
438 Field::from(Fp8Format::UE8M0).decode(),
439 FieldDecode::Byte(Fp8Format::UE8M0)
440 );
441 assert_eq!(
442 Field::from(QuantValue::E2M1).decode(),
443 FieldDecode::Reinterpreted
444 );
445 assert_eq!(Field::ConvertedE2M1.decode(), FieldDecode::Converted);
446 assert_eq!(Field::ConvertedE2M1.per_word(), 8);
447 }
448
449 #[test]
451 fn every_read_of_an_e2m1_code_holds_e2m1() {
452 let placed = |value, lifted| Field::Placed { value, lifted };
453 for field in [
454 Field::from(QuantValue::E2M1),
455 Field::ConvertedE2M1,
456 placed(QuantValue::E2M1, true),
457 placed(QuantValue::E2M1, false),
458 ] {
459 assert!(field.holds_e2m1(), "{field:?}");
460 }
461 for field in [
462 Field::from(QuantValue::E4M3),
463 placed(QuantValue::E4M3, true),
464 Field::from(Fp8Format::E4M3),
465 ] {
466 assert!(!field.holds_e2m1(), "{field:?}");
467 }
468 }
469
470 #[test]
475 fn a_placed_code_leaves_its_lift_to_the_reader() {
476 let placed = |value| Field::Placed {
477 value,
478 lifted: true,
479 };
480 for (value, lift) in [
481 (QuantValue::E2M1, 16384.0),
482 (QuantValue::E4M3, 256.0),
483 (QuantValue::E5M2, 1.0),
484 ] {
485 assert_eq!(placed(value).size_bits(), Field::from(value).size_bits());
486 assert_eq!(placed(value).lift(), 1.0, "{value:?}");
487 assert_eq!(placed(value).placed().lift(), lift, "{value:?}");
488 }
489 assert_eq!(
490 placed(QuantValue::E4M3).placed().decode(),
491 FieldDecode::PlacedByte {
492 format: Fp8Format::E4M3,
493 lifted: false
494 }
495 );
496 assert_eq!(
497 placed(QuantValue::E2M1).decode(),
498 FieldDecode::Placed { lifted: true }
499 );
500 }
501}