1use cubecl_ir::EnumSet;
5use cubecl_ir::{
6 NamedRewrite, Scope,
7 dialect::{
8 base::OperationPtrExt,
9 cmp::{FEqualOp, FNotEqualOp},
10 general::CastOp,
11 },
12 interfaces::TypedExt,
13 prelude::*,
14 types::Fp8Format,
15};
16use pliron::r#type::TypeHandle;
17
18use crate::{self as cubecl, prelude::*};
19
20define_size!(N);
21
22const F32_MANTISSA_BITS: u32 = f32::MANTISSA_DIGITS - 1;
23const F32_MANTISSA_MASK: u32 = (1 << F32_MANTISSA_BITS) - 1;
24const F32_MAGNITUDE_MASK: u32 = u32::MAX >> 1;
25const F32_EXPONENT_BIAS: u32 = (f32::MAX_EXP - 1) as u32;
26const F32_INFINITY_BITS: u32 = f32::INFINITY.to_bits();
27const F32_NAN_BITS: u32 = f32::NAN.to_bits();
28const FP8_SIGN_BIT: u32 = 1 << (u8::BITS - 1);
29const FP8_MAGNITUDE_MASK: u32 = FP8_SIGN_BIT - 1;
30const SIGN_SHIFT: u32 = u32::BITS - u8::BITS;
31
32#[cube]
34pub fn fp8_bits_to_f32<N: Size>(
35 bits: Vector<u32, N>,
36 #[comptime] format: Fp8Format,
37) -> Vector<f32, N> {
38 let mantissa_bits = comptime![format.mantissa_bits()];
39 let exponent_mask = comptime![(1u32 << format.exponent_bits()) - 1];
40 let mantissa_mask = comptime![(1u32 << mantissa_bits) - 1];
41 let rebias = comptime![F32_EXPONENT_BIAS - format.bias()];
42 let mantissa_shift = comptime![F32_MANTISSA_BITS - mantissa_bits];
43 let subnormal_step = comptime![format.subnormal_step()];
44
45 let sign = (bits & Vector::new(FP8_SIGN_BIT)) << Vector::new(SIGN_SHIFT);
46 let exponent = (bits >> Vector::new(mantissa_bits)) & Vector::new(exponent_mask);
47 let mantissa = bits & Vector::new(mantissa_mask);
48
49 let normal = sign
50 | ((exponent + Vector::new(rebias)) << Vector::new(F32_MANTISSA_BITS))
51 | (mantissa << Vector::new(mantissa_shift));
52 let subnormal = sign
53 | Vector::<u32, N>::reinterpret(
54 Vector::<f32, N>::cast_from(mantissa) * Vector::new(subnormal_step),
55 );
56 let value = select_many(exponent.equal(&Vector::new(0u32)), subnormal, normal);
57
58 let nan = sign | Vector::new(F32_NAN_BITS);
59 let result = if comptime![format.has_infinity()] {
60 let inf = sign | Vector::new(F32_INFINITY_BITS);
61 let special = select_many(mantissa.equal(&Vector::new(0u32)), inf, nan);
62 select_many(exponent.equal(&Vector::new(exponent_mask)), special, value)
63 } else {
64 let magnitude = bits & Vector::new(FP8_MAGNITUDE_MASK);
65 select_many(
66 magnitude.equal(&Vector::new(FP8_MAGNITUDE_MASK)),
67 nan,
68 value,
69 )
70 };
71
72 Vector::<f32, N>::reinterpret(result)
73}
74
75#[cube]
78pub fn f32_to_fp8_bits<N: Size>(
79 value: Vector<f32, N>,
80 #[comptime] format: Fp8Format,
81) -> Vector<u32, N> {
82 let mantissa_bits = comptime![format.mantissa_bits()];
83 let mantissa_shift = comptime![F32_MANTISSA_BITS - mantissa_bits];
84 let rebias = comptime![format.bias().wrapping_sub(F32_EXPONENT_BIAS)];
85 let half_ulp = comptime![1u32 << (mantissa_shift - 1)];
86 let subnormal_scale = comptime![1.0 / format.subnormal_step()];
87 let min_normal = comptime![format.min_normal()];
88 let max_value = comptime![format.max_value()];
89 let max_code = comptime![format.max_code()];
90 let nan_code = comptime![format.nan_code()];
91
92 let bits = Vector::<u32, N>::reinterpret(value);
93 let sign = (bits >> Vector::new(SIGN_SHIFT)) & Vector::new(FP8_SIGN_BIT);
94 let magnitude_bits = bits & Vector::new(F32_MAGNITUDE_MASK);
95 let magnitude = Vector::<f32, N>::reinterpret(magnitude_bits);
96
97 let steps = magnitude * Vector::new(subnormal_scale);
101 let truncated = Vector::<u32, N>::cast_from(steps);
102 let fraction = steps - Vector::<f32, N>::cast_from(truncated);
103 let above_half = fraction.greater_than(&Vector::new(0.5f32));
104 let tie_to_odd = fraction
105 .equal(&Vector::new(0.5f32))
106 .vec_and((truncated & Vector::new(1u32)).equal(&Vector::new(1u32)));
107 let round_up = Vector::<u32, N>::cast_from(above_half.or(tie_to_odd));
108 let subnormal = truncated + round_up;
109
110 let exponent = (magnitude_bits >> Vector::new(F32_MANTISSA_BITS)) + Vector::new(rebias);
111 let mantissa = magnitude_bits & Vector::new(F32_MANTISSA_MASK);
112 let lsb = (mantissa >> Vector::new(mantissa_shift)) & Vector::new(1u32);
113 let rounded =
114 ((exponent << Vector::new(F32_MANTISSA_BITS)) | mantissa) + Vector::new(half_ulp - 1) + lsb;
115 let normal = rounded >> Vector::new(mantissa_shift);
116
117 let code = select_many(
118 magnitude.less_than(&Vector::new(min_normal)),
119 subnormal,
120 normal,
121 );
122 let code = select_many(
123 magnitude.greater_than(&Vector::new(max_value)),
124 Vector::new(max_code),
125 code,
126 );
127 let code = select_many(
128 magnitude_bits.greater_than(&Vector::new(F32_INFINITY_BITS)),
129 Vector::new(nan_code),
130 code,
131 );
132
133 code | sign
134}
135
136#[cube]
145pub fn ue8m0_bits_to_f32<N: Size>(bits: Vector<u32, N>) -> Vector<f32, N> {
146 let code = bits & Vector::new(0xFFu32);
147
148 let shifted = code << Vector::new(F32_MANTISSA_BITS);
150
151 let quiet_bit = Vector::new(1u32 << (F32_MANTISSA_BITS - 1));
156 let is_nan = code.equal(&Vector::new(comptime![Fp8Format::UE8M0.nan_code()]));
157 let word = select_many(is_nan, shifted | quiet_bit, shifted);
158
159 let min = comptime![Fp8Format::UE8M0.min_normal()];
161 select_many(
162 code.equal(&Vector::new(0u32)),
163 Vector::new(min),
164 Vector::<f32, N>::reinterpret(word),
165 )
166}
167
168#[cube]
176pub fn f32_to_ue8m0_bits<N: Size>(value: Vector<f32, N>) -> Vector<u32, N> {
177 let min = comptime![Fp8Format::UE8M0.min_normal()];
178 let max = comptime![Fp8Format::UE8M0.max_value()];
179 let max_code = comptime![Fp8Format::UE8M0.max_code()];
180
181 let bits = Vector::<u32, N>::reinterpret(value);
184 let carried = (bits + Vector::new(F32_MANTISSA_MASK)) & Vector::new(!F32_MANTISSA_MASK);
185 let code = carried >> Vector::new(F32_MANTISSA_BITS);
186
187 let code = select_many(value.less_equal(&Vector::new(min)), Vector::new(0u32), code);
191 let code = select_many(
192 value.greater_equal(&Vector::new(max)),
193 Vector::new(max_code),
194 code,
195 );
196 select_many(
197 value.not_equal(&value),
198 Vector::new(comptime![Fp8Format::UE8M0.nan_code()]),
199 code,
200 )
201}
202
203#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
205pub enum Fp8Container {
206 #[default]
208 Bytes,
209 Words,
212}
213
214pub type LowerMinifloatCastPass = MatchRewritePass<LowerMinifloatCast>;
215
216#[derive(new, Clone, Copy, Debug, Default, NamedRewrite)]
219pub struct LowerMinifloatCast {
220 native: EnumSet<Fp8Format>,
221 container: Fp8Container,
222}
223
224impl LowerMinifloatCast {
225 fn emulated(&self, ctx: &Context, value: impl Typed) -> Option<Fp8Format> {
226 Fp8Format::of_type(ctx, value.scalar_ty(ctx))
227 .filter(|format| !self.native.contains(*format))
228 }
229}
230
231impl MatchRewrite for LowerMinifloatCast {
232 fn r#match(&mut self, ctx: &Context, op: Ptr<Operation>) -> bool {
233 if !op.is_op::<CastOp>(ctx) {
234 return false;
235 }
236 self.emulated(ctx, op.operand(ctx, 0)).is_some()
237 || self.emulated(ctx, op.result(ctx)).is_some()
238 }
239
240 fn rewrite(
241 &mut self,
242 ctx: &mut Context,
243 rewriter: &mut MatchRewriter,
244 op: Ptr<Operation>,
245 ) -> Result<()> {
246 let scope = Scope::from_context_and_inserter(ctx, rewriter);
247 let input = op.operand(ctx, 0);
248 let result_ty = op.result(ctx).get_type(ctx);
249 let lanes = input.vector_size(ctx);
250 debug_assert_eq!(
251 lanes,
252 result_ty.vector_size(ctx),
253 "A cast keeps its vectorization, so one `N` describes both sides"
254 );
255 scope.register_size::<N>(lanes);
256
257 let mut value = input;
259 if let Some(format) = self.emulated(ctx, input) {
260 value = self.decode(&scope, value, format);
261 }
262 let value = match self.emulated(ctx, result_ty) {
263 Some(format) => self.encode(&scope, value, format, result_ty),
264 None => cast_value(&scope, value, result_ty),
265 };
266 rewriter.replace_operation_with_values(ctx, op, vec![value]);
267 Ok(())
268 }
269}
270
271impl LowerMinifloatCast {
272 fn decode(&self, scope: &Scope, value: Value, format: Fp8Format) -> Value {
273 let bits = match self.container {
274 Fp8Container::Bytes => {
275 let bytes =
276 reinterpret_value(scope, value, Vector::<u8, N>::__expand_as_type(scope));
277 cast_value(scope, bytes, Vector::<u32, N>::__expand_as_type(scope))
278 }
279 Fp8Container::Words => {
280 let words = reinterpret_value(scope, value, words_type(scope));
281 unpack_words::expand::<N, W>(scope, words.into()).read_value(scope)
282 }
283 };
284 match format {
285 Fp8Format::UE8M0 => ue8m0_bits_to_f32::expand::<N>(scope, bits.into()),
286 _ => fp8_bits_to_f32::expand::<N>(scope, bits.into(), format),
287 }
288 .read_value(scope)
289 }
290
291 fn encode(
292 &self,
293 scope: &Scope,
294 value: Value,
295 format: Fp8Format,
296 result_ty: TypeHandle,
297 ) -> Value {
298 let value = cast_value(scope, value, Vector::<f32, N>::__expand_as_type(scope));
299 let bits = match format {
300 Fp8Format::UE8M0 => f32_to_ue8m0_bits::expand::<N>(scope, value.into()),
301 _ => f32_to_fp8_bits::expand::<N>(scope, value.into(), format),
302 }
303 .read_value(scope);
304 let container = match self.container {
305 Fp8Container::Bytes => {
306 cast_value(scope, bits, Vector::<u8, N>::__expand_as_type(scope))
307 }
308 Fp8Container::Words => {
309 register_words_size(scope);
310 pack_words::expand::<N, W>(scope, bits.into()).read_value(scope)
311 }
312 };
313 reinterpret_value(scope, container, result_ty)
314 }
315}
316
317pub type LowerMinifloatComparePass = MatchRewritePass<LowerMinifloatCompare>;
318
319#[derive(new, Clone, Copy, Debug, Default, NamedRewrite)]
332pub struct LowerMinifloatCompare {
333 container: Fp8Container,
334}
335
336impl MatchRewrite for LowerMinifloatCompare {
337 fn r#match(&mut self, ctx: &Context, op: Ptr<Operation>) -> bool {
338 if !op.is_op::<FEqualOp>(ctx) && !op.is_op::<FNotEqualOp>(ctx) {
339 return false;
340 }
341 Fp8Format::of_type(ctx, op.operand(ctx, 0).scalar_ty(ctx)).is_some()
342 }
343
344 fn rewrite(
345 &mut self,
346 ctx: &mut Context,
347 rewriter: &mut MatchRewriter,
348 op: Ptr<Operation>,
349 ) -> Result<()> {
350 let equal = op.is_op::<FEqualOp>(ctx);
351 let scope = Scope::from_context_and_inserter(ctx, rewriter);
352 let lhs = op.operand(scope.ctx(), 0);
353 let rhs = op.operand(scope.ctx(), 1);
354 scope.register_size::<N>(lhs.vector_size(scope.ctx()));
355
356 let lhs = self.lanes(&scope, lhs);
357 let rhs = self.lanes(&scope, rhs);
358 let value = match self.container {
359 Fp8Container::Bytes => compare_lanes::<u8>(&scope, equal, lhs, rhs),
360 Fp8Container::Words => compare_lanes::<u32>(&scope, equal, lhs, rhs),
361 };
362 rewriter.replace_operation_with_values(ctx, op, vec![value]);
363 Ok(())
364 }
365}
366
367impl LowerMinifloatCompare {
368 fn lanes(&self, scope: &Scope, value: Value) -> Value {
371 match self.container {
372 Fp8Container::Bytes => {
373 reinterpret_value(scope, value, Vector::<u8, N>::__expand_as_type(scope))
374 }
375 Fp8Container::Words => {
376 let words = reinterpret_value(scope, value, words_type(scope));
377 unpack_words::expand::<N, W>(scope, words.into()).read_value(scope)
378 }
379 }
380 }
381}
382
383fn compare_lanes<T: Int>(scope: &Scope, equal: bool, lhs: Value, rhs: Value) -> Value {
384 match equal {
385 true => bits_equal::expand::<T>(scope, lhs.into(), rhs.into()).read_value(scope),
386 false => bits_not_equal::expand::<T>(scope, lhs.into(), rhs.into()).read_value(scope),
387 }
388}
389
390#[cube]
391fn bits_equal<T: Int>(lhs: Vector<T, N>, rhs: Vector<T, N>) -> Vector<bool, N> {
392 lhs.equal(&rhs)
393}
394
395#[cube]
396fn bits_not_equal<T: Int>(lhs: Vector<T, N>, rhs: Vector<T, N>) -> Vector<bool, N> {
397 lhs.not_equal(&rhs)
398}
399
400define_size!(W);
401
402const LANES_PER_WORD: usize = (u32::BITS / u8::BITS) as usize;
403
404fn register_words_size(scope: &Scope) {
407 let lanes = N::__expand_value(scope);
408 assert!(
409 lanes.is_multiple_of(LANES_PER_WORD),
410 "fp8 is packed four lanes to a u32 on this backend: vectors of {lanes} lanes are not \
411 supported, use a vector size that is a multiple of {LANES_PER_WORD}"
412 );
413 scope.register_size::<W>(lanes / LANES_PER_WORD);
414}
415
416fn words_type(scope: &Scope) -> TypeHandle {
418 register_words_size(scope);
419 Vector::<u32, W>::__expand_as_type(scope)
420}
421
422#[cube]
423fn unpack_words<N: Size, W: Size>(words: Vector<u32, W>) -> Vector<u32, N> {
424 let mut lanes = Vector::<u32, N>::empty();
425 #[unroll]
426 for lane in 0..N::value() {
427 let word = words.extract(lane / LANES_PER_WORD);
428 let shift = comptime![(lane % LANES_PER_WORD) as u32 * u8::BITS];
429 lanes.insert(lane, (word >> shift) & 0xFF);
430 }
431 lanes
432}
433
434#[cube]
435fn pack_words<N: Size, W: Size>(lanes: Vector<u32, N>) -> Vector<u32, W> {
436 let mut words = Vector::<u32, W>::empty();
437 #[unroll]
438 for index in 0..W::value() {
439 let mut word = 0u32;
440 #[unroll]
441 for offset in 0..LANES_PER_WORD {
442 let shift = comptime![offset as u32 * u8::BITS];
443 word |= (lanes.extract(index * LANES_PER_WORD + offset) & 0xFF) << shift;
444 }
445 words.insert(index, word);
446 }
447 words
448}