|
4 | 4 |
|
5 | 5 | import cutlass |
6 | 6 | import cutlass.cute as cute |
| 7 | +from cutlass._mlir import ir |
| 8 | +from cutlass._mlir.dialects import llvm, vector |
| 9 | +from cutlass.cutlass_dsl import T, dsl_user_op |
7 | 10 | import torch |
8 | 11 |
|
9 | 12 | from transformer_nuggets.cute.blockscaled_tma import ( |
|
19 | 22 | NVFP4_TMA_PROFILE_TAGS = BLOCKSCALED_TMA_PROFILE_TAGS |
20 | 23 |
|
21 | 24 |
|
| 25 | +@dsl_user_op |
| 26 | +def decode_e2m1x8( |
| 27 | + packed: cutlass.Uint32, |
| 28 | + *, |
| 29 | + loc: ir.Location | None = None, |
| 30 | + ip: ir.InsertionPoint | None = None, |
| 31 | +) -> cute.TensorSSA: |
| 32 | + """Decode one packed E2M1 word with the native four-instruction conversion.""" |
| 33 | + converted = llvm.inline_asm( |
| 34 | + llvm.StructType.get_literal([T.i32(), T.i32(), T.i32(), T.i32()]), |
| 35 | + [packed.ir_value(loc=loc, ip=ip)], |
| 36 | + """{ |
| 37 | + .reg .b8 b0, b1, b2, b3; |
| 38 | + mov.b32 {b0, b1, b2, b3}, $4; |
| 39 | + cvt.rn.f16x2.e2m1x2 $0, b0; |
| 40 | + cvt.rn.f16x2.e2m1x2 $1, b1; |
| 41 | + cvt.rn.f16x2.e2m1x2 $2, b2; |
| 42 | + cvt.rn.f16x2.e2m1x2 $3, b3; |
| 43 | + }""", |
| 44 | + "=r,=r,=r,=r,r", |
| 45 | + has_side_effects=False, |
| 46 | + is_align_stack=False, |
| 47 | + loc=loc, |
| 48 | + ip=ip, |
| 49 | + ) |
| 50 | + converted_words = vector.from_elements( |
| 51 | + ir.VectorType.get([4], T.i32(), loc=loc), |
| 52 | + [llvm.extractvalue(T.i32(), converted, [word], loc=loc, ip=ip) for word in range(4)], |
| 53 | + loc=loc, |
| 54 | + ip=ip, |
| 55 | + ) |
| 56 | + converted_values = llvm.bitcast( |
| 57 | + ir.VectorType.get([8], cutlass.Float16.mlir_type, loc=loc), |
| 58 | + converted_words, |
| 59 | + loc=loc, |
| 60 | + ip=ip, |
| 61 | + ) |
| 62 | + return cute.TensorSSA(converted_values, (8,), cutlass.Float16, loc=loc, ip=ip) |
| 63 | + |
| 64 | + |
22 | 65 | class Nvfp4TmaGemv(BlockscaledTmaGemv): |
23 | 66 | """Compute M=1 NVFP4 GEMV using scaled_mm-compatible storage.""" |
24 | 67 |
|
@@ -59,7 +102,14 @@ def __init__( |
59 | 102 |
|
60 | 103 | @cute.jit |
61 | 104 | def decode_lane_values(self, raw_values: cute.Tensor): |
62 | | - """Decode 32 packed E2M1 values into two 16-value groups.""" |
| 105 | + """Decode 32 packed E2M1 values into register-resident FP16 groups.""" |
| 106 | + if cutlass.const_expr(self.k <= 4096): |
| 107 | + return ( |
| 108 | + decode_e2m1x8(raw_values[0, 0]), |
| 109 | + decode_e2m1x8(raw_values[0, 1]), |
| 110 | + decode_e2m1x8(raw_values[0, 2]), |
| 111 | + decode_e2m1x8(raw_values[0, 3]), |
| 112 | + ) |
63 | 113 | return ( |
64 | 114 | cute.recast_tensor(raw_values, cutlass.Float4E2M1FN) |
65 | 115 | .load() |
@@ -169,21 +219,38 @@ def accumulate_scaled_products( |
169 | 219 | weight_scales, |
170 | 220 | ): |
171 | 221 | """Accumulate two independently scaled 16-value E2M1 blocks.""" |
172 | | - fp16_reduction_width = 8 if self.k <= 4096 else 2 |
173 | | - products = ( |
174 | | - (x_values * w_values) |
175 | | - .reshape((fp16_reduction_width, 16 // fp16_reduction_width, 2)) |
176 | | - .reduce( |
| 222 | + if cutlass.const_expr(self.k <= 4096): |
| 223 | + partial0 = (x_values[0] * w_values[0]).reduce( |
| 224 | + cute.ReductionOp.ADD, cutlass.Float16(0.0), 0 |
| 225 | + ) |
| 226 | + partial1 = (x_values[1] * w_values[1]).reduce( |
| 227 | + cute.ReductionOp.ADD, cutlass.Float16(0.0), 0 |
| 228 | + ) |
| 229 | + partial2 = (x_values[2] * w_values[2]).reduce( |
| 230 | + cute.ReductionOp.ADD, cutlass.Float16(0.0), 0 |
| 231 | + ) |
| 232 | + partial3 = (x_values[3] * w_values[3]).reduce( |
| 233 | + cute.ReductionOp.ADD, cutlass.Float16(0.0), 0 |
| 234 | + ) |
| 235 | + products = ( |
| 236 | + cutlass.Float32(partial0) + cutlass.Float32(partial1), |
| 237 | + cutlass.Float32(partial2) + cutlass.Float32(partial3), |
| 238 | + ) |
| 239 | + else: |
| 240 | + products = ( |
| 241 | + (x_values * w_values) |
| 242 | + .reshape((2, 8, 2)) |
| 243 | + .reduce( |
| 244 | + cute.ReductionOp.ADD, |
| 245 | + cutlass.Float16(0.0), |
| 246 | + (1, None, None), |
| 247 | + ) |
| 248 | + ) |
| 249 | + products = products.to(cutlass.Float32).reduce( |
177 | 250 | cute.ReductionOp.ADD, |
178 | | - cutlass.Float16(0.0), |
179 | | - (1, None, None), |
| 251 | + cutlass.Float32(0.0), |
| 252 | + (1, None), |
180 | 253 | ) |
181 | | - ) |
182 | | - products = products.to(cutlass.Float32).reduce( |
183 | | - cute.ReductionOp.ADD, |
184 | | - cutlass.Float32(0.0), |
185 | | - (1, None), |
186 | | - ) |
187 | 254 | for scale_idx in cutlass.range_constexpr(2): |
188 | 255 | accumulator += products[scale_idx] * input_scales[scale_idx] * weight_scales[scale_idx] |
189 | 256 | return accumulator |
|
0 commit comments