1use cubecl_ir::{
5 NamedRewrite, Scope,
6 dialect::{
7 base::OperationPtrExt,
8 cmp::{FEqualOp, FNotEqualOp},
9 general::CastOp,
10 },
11 interfaces::TypedExt,
12 prelude::*,
13 types::Fp8Format,
14};
15use enumset::EnumSet;
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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
138pub enum Fp8Container {
139 #[default]
141 Bytes,
142 Words,
145}
146
147pub type LowerMinifloatCastPass = MatchRewritePass<LowerMinifloatCast>;
148
149#[derive(new, Clone, Copy, Debug, Default, NamedRewrite)]
152pub struct LowerMinifloatCast {
153 native: EnumSet<Fp8Format>,
154 container: Fp8Container,
155}
156
157impl LowerMinifloatCast {
158 fn emulated(&self, ctx: &Context, value: impl Typed) -> Option<Fp8Format> {
159 Fp8Format::of_type(ctx, value.scalar_ty(ctx))
160 .filter(|format| !self.native.contains(*format))
161 }
162}
163
164impl MatchRewrite for LowerMinifloatCast {
165 fn r#match(&mut self, ctx: &Context, op: Ptr<Operation>) -> bool {
166 if !op.is_op::<CastOp>(ctx) {
167 return false;
168 }
169 self.emulated(ctx, op.operand(ctx, 0)).is_some()
170 || self.emulated(ctx, op.result(ctx)).is_some()
171 }
172
173 fn rewrite(
174 &mut self,
175 ctx: &mut Context,
176 rewriter: &mut MatchRewriter,
177 op: Ptr<Operation>,
178 ) -> Result<()> {
179 let scope = Scope::from_context_and_inserter(ctx, rewriter);
180 let input = op.operand(ctx, 0);
181 let result_ty = op.result(ctx).get_type(ctx);
182 let lanes = input.vector_size(ctx);
183 debug_assert_eq!(
184 lanes,
185 result_ty.vector_size(ctx),
186 "A cast keeps its vectorization, so one `N` describes both sides"
187 );
188 scope.register_size::<N>(lanes);
189
190 let mut value = input;
192 if let Some(format) = self.emulated(ctx, input) {
193 value = self.decode(&scope, value, format);
194 }
195 let value = match self.emulated(ctx, result_ty) {
196 Some(format) => self.encode(&scope, value, format, result_ty),
197 None => cast_value(&scope, value, result_ty),
198 };
199 rewriter.replace_operation_with_values(ctx, op, vec![value]);
200 Ok(())
201 }
202}
203
204impl LowerMinifloatCast {
205 fn decode(&self, scope: &Scope, value: Value, format: Fp8Format) -> Value {
206 let bits = match self.container {
207 Fp8Container::Bytes => {
208 let bytes =
209 reinterpret_value(scope, value, Vector::<u8, N>::__expand_as_type(scope));
210 cast_value(scope, bytes, Vector::<u32, N>::__expand_as_type(scope))
211 }
212 Fp8Container::Words => {
213 let words = reinterpret_value(scope, value, words_type(scope));
214 unpack_words::expand::<N, W>(scope, words.into()).read_value(scope)
215 }
216 };
217 fp8_bits_to_f32::expand::<N>(scope, bits.into(), format).read_value(scope)
218 }
219
220 fn encode(
221 &self,
222 scope: &Scope,
223 value: Value,
224 format: Fp8Format,
225 result_ty: TypeHandle,
226 ) -> Value {
227 let value = cast_value(scope, value, Vector::<f32, N>::__expand_as_type(scope));
228 let bits = f32_to_fp8_bits::expand::<N>(scope, value.into(), format).read_value(scope);
229 let container = match self.container {
230 Fp8Container::Bytes => {
231 cast_value(scope, bits, Vector::<u8, N>::__expand_as_type(scope))
232 }
233 Fp8Container::Words => {
234 register_words_size(scope);
235 pack_words::expand::<N, W>(scope, bits.into()).read_value(scope)
236 }
237 };
238 reinterpret_value(scope, container, result_ty)
239 }
240}
241
242pub type LowerMinifloatComparePass = MatchRewritePass<LowerMinifloatCompare>;
243
244#[derive(new, Clone, Copy, Debug, Default, NamedRewrite)]
257pub struct LowerMinifloatCompare {
258 container: Fp8Container,
259}
260
261impl MatchRewrite for LowerMinifloatCompare {
262 fn r#match(&mut self, ctx: &Context, op: Ptr<Operation>) -> bool {
263 if !op.is_op::<FEqualOp>(ctx) && !op.is_op::<FNotEqualOp>(ctx) {
264 return false;
265 }
266 Fp8Format::of_type(ctx, op.operand(ctx, 0).scalar_ty(ctx)).is_some()
267 }
268
269 fn rewrite(
270 &mut self,
271 ctx: &mut Context,
272 rewriter: &mut MatchRewriter,
273 op: Ptr<Operation>,
274 ) -> Result<()> {
275 let equal = op.is_op::<FEqualOp>(ctx);
276 let scope = Scope::from_context_and_inserter(ctx, rewriter);
277 let lhs = op.operand(scope.ctx(), 0);
278 let rhs = op.operand(scope.ctx(), 1);
279 scope.register_size::<N>(lhs.vector_size(scope.ctx()));
280
281 let lhs = self.lanes(&scope, lhs);
282 let rhs = self.lanes(&scope, rhs);
283 let value = match self.container {
284 Fp8Container::Bytes => compare_lanes::<u8>(&scope, equal, lhs, rhs),
285 Fp8Container::Words => compare_lanes::<u32>(&scope, equal, lhs, rhs),
286 };
287 rewriter.replace_operation_with_values(ctx, op, vec![value]);
288 Ok(())
289 }
290}
291
292impl LowerMinifloatCompare {
293 fn lanes(&self, scope: &Scope, value: Value) -> Value {
296 match self.container {
297 Fp8Container::Bytes => {
298 reinterpret_value(scope, value, Vector::<u8, N>::__expand_as_type(scope))
299 }
300 Fp8Container::Words => {
301 let words = reinterpret_value(scope, value, words_type(scope));
302 unpack_words::expand::<N, W>(scope, words.into()).read_value(scope)
303 }
304 }
305 }
306}
307
308fn compare_lanes<T: Int>(scope: &Scope, equal: bool, lhs: Value, rhs: Value) -> Value {
309 match equal {
310 true => bits_equal::expand::<T>(scope, lhs.into(), rhs.into()).read_value(scope),
311 false => bits_not_equal::expand::<T>(scope, lhs.into(), rhs.into()).read_value(scope),
312 }
313}
314
315#[cube]
316fn bits_equal<T: Int>(lhs: Vector<T, N>, rhs: Vector<T, N>) -> Vector<bool, N> {
317 lhs.equal(&rhs)
318}
319
320#[cube]
321fn bits_not_equal<T: Int>(lhs: Vector<T, N>, rhs: Vector<T, N>) -> Vector<bool, N> {
322 lhs.not_equal(&rhs)
323}
324
325define_size!(W);
326
327const LANES_PER_WORD: usize = (u32::BITS / u8::BITS) as usize;
328
329fn register_words_size(scope: &Scope) {
332 let lanes = N::__expand_value(scope);
333 assert!(
334 lanes.is_multiple_of(LANES_PER_WORD),
335 "fp8 is packed four lanes to a u32 on this backend: vectors of {lanes} lanes are not \
336 supported, use a vector size that is a multiple of {LANES_PER_WORD}"
337 );
338 scope.register_size::<W>(lanes / LANES_PER_WORD);
339}
340
341fn words_type(scope: &Scope) -> TypeHandle {
343 register_words_size(scope);
344 Vector::<u32, W>::__expand_as_type(scope)
345}
346
347#[cube]
348fn unpack_words<N: Size, W: Size>(words: Vector<u32, W>) -> Vector<u32, N> {
349 let mut lanes = Vector::<u32, N>::empty();
350 #[unroll]
351 for lane in 0..N::value() {
352 let word = words.extract(lane / LANES_PER_WORD);
353 let shift = comptime![(lane % LANES_PER_WORD) as u32 * u8::BITS];
354 lanes.insert(lane, (word >> shift) & 0xFF);
355 }
356 lanes
357}
358
359#[cube]
360fn pack_words<N: Size, W: Size>(lanes: Vector<u32, N>) -> Vector<u32, W> {
361 let mut words = Vector::<u32, W>::empty();
362 #[unroll]
363 for index in 0..W::value() {
364 let mut word = 0u32;
365 #[unroll]
366 for offset in 0..LANES_PER_WORD {
367 let shift = comptime![offset as u32 * u8::BITS];
368 word |= (lanes.extract(index * LANES_PER_WORD + offset) & 0xFF) << shift;
369 }
370 words.insert(index, word);
371 }
372 words
373}