fp6.py 60 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830
  1. """
  2. MX-FP6 (W6A6) utilities for SM120/SM121 CuTe DSL kernels.
  3. Provides FP6 conversion intrinsics, 4-in-3-byte memory packing, inline-PTX
  4. ``mxf8f6f4`` MMA helpers, block quantizers (32-element UE8M0 blocks), and
  5. pure-Torch reference paths for parity testing.
  6. """
  7. from __future__ import annotations
  8. import functools
  9. import math
  10. from typing import Literal, Tuple
  11. import cutlass
  12. import cutlass.cute as cute
  13. import torch
  14. import torch.nn.functional as F
  15. from cutlass import Float32, Int32, Int64, Uint8, Uint32, Uint64
  16. from cutlass.cutlass_dsl import T, dsl_user_op
  17. from cutlass._mlir.dialects import llvm
  18. from b12x._lib.intrinsics import (
  19. align_up,
  20. cvt_f32_to_ue8m0,
  21. fabs_f32,
  22. fmax_f32,
  23. pack_f32x2_to_bfloat2,
  24. rcp_approx_ftz,
  25. swizzle_block_scale,
  26. ue8m0_to_output_scale,
  27. )
  28. # =============================================================================
  29. # Constants
  30. # =============================================================================
  31. FLOAT6_E3M2_MAX = 28.0
  32. FLOAT6_E2M3_MAX = 7.5
  33. # FP8 E4M3 activations (W6A8): same mxf8f6f4 MMA kind, UE8M0 per-32 scales and
  34. # byte containers as the FP6 paths; only the operand format / fmt_max differ.
  35. FLOAT8_E4M3_MAX = 448.0
  36. SF_VEC_SIZE_FP6 = 32
  37. COPY_BITS = 128
  38. # Max representable magnitude per operand sub-format, used by the global-scale
  39. # recipe below (e4m3 covers the W6A8 activation path).
  40. _GS_FMT_MAX = {
  41. "e3m2": FLOAT6_E3M2_MAX,
  42. "e2m3": FLOAT6_E2M3_MAX,
  43. "e4m3": FLOAT8_E4M3_MAX,
  44. }
  45. def mx_gs_numerator(fmt: str) -> float:
  46. """Numerator of the amax->global-scale recipe: ``gs = numerator / amax``.
  47. Fmt-aware ``f8e4m3_max * fmt_max`` (e3m2: 12544.0, e2m3: 3360.0, e4m3:
  48. 200704.0 — all exactly representable in f32). Single source of truth for
  49. the host paths (``fp6_dense_weights``) and the fused small-M kernel
  50. (``bf16_to_fp6_small_m``), which must stay bit-identical. Under the ceil
  51. UE8M0 containment rule the gs largely cancels; the numerator only nudges
  52. tie-breaking at power-of-two boundaries, so this is a foot-gun removal,
  53. not an accuracy lever.
  54. """
  55. f8_max = float(torch.finfo(torch.float8_e4m3fn).max)
  56. return f8_max * _GS_FMT_MAX[fmt]
  57. Fp6Format = Literal["e3m2", "e2m3"]
  58. # =============================================================================
  59. # Torch FP6 decode / encode (LUT, OCP MX FP6 bias=1)
  60. # =============================================================================
  61. def _decode_fp6_e3m2(bits: int) -> float:
  62. # OCP MX FP6 E3M2: 3 exponent bits, bias 3, no inf/nan (exp=7 is a normal).
  63. bits &= 0x3F
  64. sign = -1.0 if (bits >> 5) & 1 else 1.0
  65. exp = (bits >> 2) & 0x7
  66. mant = bits & 0x3
  67. if exp == 0:
  68. if mant == 0:
  69. return 0.0 if sign > 0 else -0.0
  70. return sign * (2.0 ** (1 - 3)) * (mant / 4.0)
  71. return sign * (2.0 ** (exp - 3)) * (1.0 + mant / 4.0)
  72. def _decode_fp6_e2m3(bits: int) -> float:
  73. # OCP MX FP6 E2M3: 2 exponent bits, bias 1, no inf/nan (exp=3 is a normal).
  74. bits &= 0x3F
  75. sign = -1.0 if (bits >> 5) & 1 else 1.0
  76. exp = (bits >> 3) & 0x3
  77. mant = bits & 0x7
  78. if exp == 0:
  79. if mant == 0:
  80. return 0.0 if sign > 0 else -0.0
  81. return sign * (2.0 ** (1 - 1)) * (mant / 8.0)
  82. return sign * (2.0 ** (exp - 1)) * (1.0 + mant / 8.0)
  83. @functools.lru_cache(maxsize=1)
  84. def _fp6_e3m2_lut() -> Tuple[Tuple[float, ...], Tuple[int, ...]]:
  85. values = tuple(_decode_fp6_e3m2(i) for i in range(64))
  86. codes = tuple(range(64))
  87. return values, codes
  88. @functools.lru_cache(maxsize=1)
  89. def _fp6_e2m3_lut() -> Tuple[Tuple[float, ...], Tuple[int, ...]]:
  90. values = tuple(_decode_fp6_e2m3(i) for i in range(64))
  91. codes = tuple(range(64))
  92. return values, codes
  93. def build_fp6_decode_lut(
  94. fmt: Fp6Format,
  95. *,
  96. device: torch.device | str | None = None,
  97. dtype: torch.dtype = torch.float32,
  98. ) -> torch.Tensor:
  99. """Build the 64-entry FP6 code->value decode table as a 1-D tensor.
  100. The returned tensor is indexed directly by the 6-bit FP6 code (0..63), so a
  101. decode-focused micro kernel can resolve ``code -> value`` with a single shared-
  102. memory load instead of bit-twiddling the OCP minifloat fields per element.
  103. """
  104. decode = _decode_fp6_e3m2 if fmt == "e3m2" else _decode_fp6_e2m3
  105. values = [decode(i) for i in range(64)]
  106. return torch.tensor(values, dtype=dtype, device=device)
  107. @cute.jit
  108. def fp6_decode_code(lut: cute.Tensor, code: Uint32) -> Float32:
  109. """Decode one 6-bit FP6 code to f32 via a 64-entry decode LUT (``build_fp6_decode_lut``)."""
  110. return Float32(lut[Int32(code & Uint32(0x3F))])
  111. @cute.jit
  112. def fp6_decode_bytes4(
  113. lut: cute.Tensor, word: Uint32
  114. ) -> Tuple[Float32, Float32, Float32, Float32]:
  115. """Decode four FP6 byte-containers packed in a uint32 (one code per byte, bits[5:0])."""
  116. c0 = word & Uint32(0x3F)
  117. c1 = (word >> Uint32(8)) & Uint32(0x3F)
  118. c2 = (word >> Uint32(16)) & Uint32(0x3F)
  119. c3 = (word >> Uint32(24)) & Uint32(0x3F)
  120. return (
  121. Float32(lut[Int32(c0)]),
  122. Float32(lut[Int32(c1)]),
  123. Float32(lut[Int32(c2)]),
  124. Float32(lut[Int32(c3)]),
  125. )
  126. @cute.jit
  127. def fp6_unpack4_codes(
  128. b0: Uint32, b1: Uint32, b2: Uint32
  129. ) -> Tuple[Uint32, Uint32, Uint32, Uint32]:
  130. """Unpack three 3:4-packed FP6 bytes into four 6-bit codes (little-endian 24-bit lane).
  131. Inverse of ``pack_4_fp6_codes`` / device-side reader for the on-wire packed
  132. weight storage ``(..., 3*K//4)``: the four codes of element group ``g`` live in
  133. bytes ``[3g, 3g+1, 3g+2]``.
  134. """
  135. bits = (
  136. (b0 & Uint32(0xFF))
  137. | ((b1 & Uint32(0xFF)) << Uint32(8))
  138. | ((b2 & Uint32(0xFF)) << Uint32(16))
  139. )
  140. return (
  141. bits & Uint32(0x3F),
  142. (bits >> Uint32(6)) & Uint32(0x3F),
  143. (bits >> Uint32(12)) & Uint32(0x3F),
  144. (bits >> Uint32(18)) & Uint32(0x3F),
  145. )
  146. @cute.jit
  147. def mxfp6_swizzled_scale_offset(
  148. row: Int32, block: Int32, cols_padded_div4: Int32
  149. ) -> Int64:
  150. """Flat byte offset (within one expert) of the UE8M0 scale for logical ``(row, block)``.
  151. Inverts the cutlass block-scale swizzle produced by ``swizzle_block_scale`` for
  152. MX-FP6 storage shaped ``(pad128(rows), pad4(num_blocks))``. ``cols_padded_div4``
  153. is ``align_up(num_blocks, 4) // 4``. Matches ``unswizzle_mxfp6_scales``:
  154. flat = ((((row//128)*cpd4 + block//4)*32 + row%32)*4 + (row%128)//32)*4 + block%4
  155. Row-group * stride products are widened to Int64 before multiplication so
  156. the flat offset cannot wrap for large scale arrays (coding guideline: ID *
  157. stride arithmetic is Int64).
  158. """
  159. r0 = Int64(row >> Int32(7)) # row // 128
  160. r1 = Int64((row >> Int32(5)) & Int32(3)) # (row % 128) // 32
  161. r2 = Int64(row & Int32(31)) # row % 32
  162. c0 = Int64(block >> Int32(2)) # block // 4
  163. c1 = Int64(block & Int32(3)) # block % 4
  164. return (
  165. (
  166. (((r0 * Int64(cols_padded_div4) + c0) * Int64(32) + r2) * Int64(4) + r1)
  167. * Int64(4)
  168. )
  169. + c1
  170. )
  171. def _encode_fp6_nearest(value: float, fmt: Fp6Format) -> int:
  172. if fmt == "e3m2":
  173. lut_vals, lut_codes = _fp6_e3m2_lut()
  174. max_val = FLOAT6_E3M2_MAX
  175. else:
  176. lut_vals, lut_codes = _fp6_e2m3_lut()
  177. max_val = FLOAT6_E2M3_MAX
  178. if value == 0.0:
  179. return 0
  180. value = float(max(-max_val, min(max_val, value)))
  181. best_code = 0
  182. best_dist = float("inf")
  183. for code, lut_v in zip(lut_codes, lut_vals):
  184. if math.isnan(lut_v):
  185. continue
  186. dist = abs(lut_v - value)
  187. if dist < best_dist:
  188. best_dist = dist
  189. best_code = code
  190. elif dist == best_dist and (code & 1) == 0 and (best_code & 1) == 1:
  191. # Exact midpoint: round half to even (pick the even mantissa LSB),
  192. # matching the hardware ``cvt.rn`` the GPU quantizer uses. Strict-``<``
  193. # alone keeps the first/lower code, which disagrees with round-to-even
  194. # whenever the *higher* representable value has the even mantissa bit.
  195. best_code = code
  196. return best_code
  197. def fp6_quantize_values_torch(x: torch.Tensor, fmt: Fp6Format = "e3m2") -> torch.Tensor:
  198. """Quantize float32 values to the nearest FP6 representable values (Torch LUT)."""
  199. decode = _decode_fp6_e3m2 if fmt == "e3m2" else _decode_fp6_e2m3
  200. flat = x.detach().float().reshape(-1)
  201. out = torch.empty_like(flat)
  202. for i in range(flat.numel()):
  203. code = _encode_fp6_nearest(float(flat[i].item()), fmt)
  204. out[i] = decode(code)
  205. return out.reshape(x.shape)
  206. def pack_4_fp6_codes(c0: int, c1: int, c2: int, c3: int) -> Tuple[int, int, int]:
  207. """Pack four 6-bit FP6 codes into three bytes (little-endian 24-bit lane)."""
  208. bits = (c0 & 0x3F) | ((c1 & 0x3F) << 6) | ((c2 & 0x3F) << 12) | ((c3 & 0x3F) << 18)
  209. return bits & 0xFF, (bits >> 8) & 0xFF, (bits >> 16) & 0xFF
  210. def unpack_4_fp6_codes(b0: int, b1: int, b2: int) -> Tuple[int, int, int, int]:
  211. bits = (b0 & 0xFF) | ((b1 & 0xFF) << 8) | ((b2 & 0xFF) << 16)
  212. return bits & 0x3F, (bits >> 6) & 0x3F, (bits >> 12) & 0x3F, (bits >> 18) & 0x3F
  213. def pack_fp6_codes_tensor(codes: torch.Tensor) -> torch.Tensor:
  214. """Pack 6-bit FP6 codes shaped ``[..., N]`` (N divisible by 4) into ``[..., 3*N//4]`` uint8.
  215. Vectorized (no Python per-group loop); bit-identical to the historical
  216. ``pack_4_fp6_codes`` little-endian 24-bit lane layout.
  217. """
  218. if codes.shape[-1] % 4 != 0:
  219. raise ValueError(f"last dim must be divisible by 4, got {codes.shape[-1]}")
  220. *lead, n = codes.shape
  221. groups = n // 4
  222. c = codes.to(torch.int32).reshape(*lead, groups, 4)
  223. bits = (
  224. (c[..., 0] & 0x3F)
  225. | ((c[..., 1] & 0x3F) << 6)
  226. | ((c[..., 2] & 0x3F) << 12)
  227. | ((c[..., 3] & 0x3F) << 18)
  228. )
  229. out = torch.empty((*lead, groups * 3), dtype=torch.uint8, device=codes.device)
  230. packed = out.reshape(*lead, groups, 3)
  231. packed[..., 0] = (bits & 0xFF).to(torch.uint8)
  232. packed[..., 1] = ((bits >> 8) & 0xFF).to(torch.uint8)
  233. packed[..., 2] = ((bits >> 16) & 0xFF).to(torch.uint8)
  234. return out
  235. def unpack_fp6_packed_tensor(packed: torch.Tensor, num_fp6: int) -> torch.Tensor:
  236. """Unpack ``[..., 3*num_fp6//4]`` uint8 packed FP6 into 6-bit integer codes ``[..., num_fp6]``."""
  237. if num_fp6 % 4 != 0:
  238. raise ValueError(f"num_fp6 must be divisible by 4, got {num_fp6}")
  239. groups = num_fp6 // 4
  240. *lead, packed_cols = packed.shape
  241. if packed_cols != groups * 3:
  242. raise ValueError(f"packed last dim {packed_cols} != 3*num_fp6/4 ({groups * 3})")
  243. out = torch.empty((*lead, num_fp6), dtype=torch.uint8, device=packed.device)
  244. flat_packed = packed.reshape(-1, 3)
  245. # Each 3-byte group expands to 4 codes; mirror the packing layout where
  246. # ``pack_fp6_codes_tensor`` reshaped codes to ``(-1, 4)``. Indexing
  247. # ``flat_out`` per-group requires a 4-wide view, not a ``num_fp6``-wide one.
  248. flat_out = out.reshape(-1, 4)
  249. for g in range(flat_packed.shape[0]):
  250. b0, b1, b2 = (int(flat_packed[g, i].item()) for i in range(3))
  251. c0, c1, c2, c3 = unpack_4_fp6_codes(b0, b1, b2)
  252. flat_out[g, 0] = c0
  253. flat_out[g, 1] = c1
  254. flat_out[g, 2] = c2
  255. flat_out[g, 3] = c3
  256. return out
  257. def expand_mxfp6_packed_to_bytes(packed: torch.Tensor, num_fp6: int) -> torch.Tensor:
  258. """Vectorized expansion of 3:4-packed FP6 into one byte per code.
  259. Input ``packed`` is ``[..., 3*num_fp6//4]`` uint8 (the on-disk / wire format).
  260. Output is ``[..., num_fp6]`` uint8 where each element holds a single 6-bit FP6
  261. code in bits ``[5:0]`` (bits ``[7:6]`` are zero). This is the byte-container
  262. layout the ``mxf8f6f4`` warp MMA consumes (8 bits per FP6 value), letting the
  263. dense/MoE GEMM reuse cutlass's native 8-bit smem/TMA/ldmatrix path while the
  264. MMA instruction reinterprets each byte as e3m2/e2m3.
  265. Unlike :func:`unpack_fp6_packed_tensor` this is a pure-tensor (vectorized)
  266. transform suitable for the per-launch load boundary.
  267. """
  268. if num_fp6 % 4 != 0:
  269. raise ValueError(f"num_fp6 must be divisible by 4, got {num_fp6}")
  270. groups = num_fp6 // 4
  271. *lead, packed_cols = packed.shape
  272. if packed_cols != groups * 3:
  273. raise ValueError(
  274. f"packed last dim {packed_cols} != 3*num_fp6/4 ({groups * 3})"
  275. )
  276. p = packed.reshape(*lead, groups, 3).to(torch.int32)
  277. bits = p[..., 0] | (p[..., 1] << 8) | (p[..., 2] << 16)
  278. c0 = bits & 0x3F
  279. c1 = (bits >> 6) & 0x3F
  280. c2 = (bits >> 12) & 0x3F
  281. c3 = (bits >> 18) & 0x3F
  282. out = torch.stack((c0, c1, c2, c3), dim=-1).reshape(*lead, num_fp6)
  283. return out.to(torch.uint8)
  284. def unswizzle_mxfp6_scales(
  285. scales_ue8m0: torch.Tensor,
  286. rows: int,
  287. num_blocks: int,
  288. ) -> torch.Tensor:
  289. """Invert the MX-FP6 block-scale swizzle to row-major ``(-1, num_blocks)`` bytes.
  290. ``quantize_grouped_mxfp6_torch`` (and the kernel's SF TMA path) store UE8M0
  291. scales in the cutlass block-scale swizzle, in one of two equivalent shapes:
  292. * the 6-D grouped view from :func:`as_grouped_mxfp6_scale_view`
  293. ``(32, 4, rows_padded//128, 4, cols_padded//4, batch)``; or
  294. * a flat / 2-D ``(rows_padded, cols_padded)`` buffer (e.g. a kernel TMA dump).
  295. Both are inverted here back to row-major ``[row, block]`` bytes so the reference
  296. dequantizer applies the correct per-block scale (the swizzle is invisible under
  297. uniform scales, which is why it went unnoticed). The forward permutes used by
  298. :func:`swizzle_block_scale` / :func:`as_grouped_mxfp6_scale_view` are
  299. involutions on the swapped axes, so the inverse reuses the same index pattern.
  300. """
  301. rows_padded = align_up(rows, 128)
  302. cols_padded = align_up(num_blocks, 4)
  303. if scales_ue8m0.ndim == 6:
  304. batch = scales_ue8m0.shape[5]
  305. orig = (
  306. scales_ue8m0.permute(5, 2, 1, 0, 4, 3)
  307. .contiguous()
  308. .reshape(batch, rows_padded, cols_padded)
  309. )
  310. return orig[:, :rows, :num_blocks].reshape(-1, num_blocks)
  311. sw = scales_ue8m0.reshape(rows_padded // 128, cols_padded // 4, 32, 4, 4)
  312. orig = sw.permute(0, 3, 2, 1, 4).contiguous().reshape(rows_padded, cols_padded)
  313. return orig[:rows, :num_blocks].reshape(-1, num_blocks)
  314. def dequant_mxfp6_torch(
  315. packed: torch.Tensor,
  316. scales_ue8m0: torch.Tensor,
  317. *,
  318. num_fp6: int,
  319. fmt: Fp6Format = "e3m2",
  320. global_scale: torch.Tensor | None = None,
  321. scales_swizzled: bool = True,
  322. ) -> torch.Tensor:
  323. """Dequantize MX-FP6 packed uint8 tensor to float32.
  324. ``packed`` is ``[..., 3*num_fp6//4]`` uint8. ``scales_ue8m0`` carries one UE8M0
  325. byte per 32-element block. By default (``scales_swizzled=True``) the scales are
  326. assumed to be in the cutlass block-scale swizzle produced by
  327. ``quantize_grouped_mxfp6_torch`` and are inverted to row-major before use; pass
  328. ``scales_swizzled=False`` when the scales are already plain row-major
  329. ``[row, block]`` bytes.
  330. """
  331. decode = _decode_fp6_e3m2 if fmt == "e3m2" else _decode_fp6_e2m3
  332. codes = unpack_fp6_packed_tensor(packed, num_fp6)
  333. flat_codes = codes.reshape(-1, num_fp6)
  334. flat_out = torch.empty(flat_codes.shape[0], num_fp6, dtype=torch.float32, device=packed.device)
  335. num_blocks = num_fp6 // SF_VEC_SIZE_FP6
  336. if scales_swizzled:
  337. scale_flat = unswizzle_mxfp6_scales(scales_ue8m0, flat_codes.shape[0], num_blocks)
  338. else:
  339. scale_flat = scales_ue8m0.reshape(-1, num_blocks)
  340. if scale_flat.shape[0] != flat_out.shape[0]:
  341. raise ValueError("scale row count does not match packed row count")
  342. gs = 1.0
  343. if global_scale is not None:
  344. gs = float(global_scale.reshape(-1)[0].item()) if global_scale.numel() > 1 else float(global_scale.item())
  345. # ``quantize`` stores ``code ~= x * gs / block_scale`` (scaled *up* by the global
  346. # scale), so reconstructing the original value divides it back out. Using ``* gs``
  347. # returns ``x * gs**2``; that cancels under the scale-invariant cosine checks but
  348. # blows the absolute-tolerance round-trip test by ~gs**2.
  349. inv_gs = 1.0 / gs if gs != 0.0 else 0.0
  350. for row in range(flat_out.shape[0]):
  351. for blk in range(num_fp6 // SF_VEC_SIZE_FP6):
  352. ue = int(scale_flat[row, blk].view(torch.uint8).item())
  353. block_scale = 0.0 if ue == 0 else float(torch.pow(torch.tensor(2.0), float(ue - 127)))
  354. for j in range(SF_VEC_SIZE_FP6):
  355. idx = blk * SF_VEC_SIZE_FP6 + j
  356. flat_out[row, idx] = decode(int(flat_codes[row, idx].item())) * block_scale * inv_gs
  357. return flat_out.reshape(*codes.shape[:-1], num_fp6)
  358. def _ue8m0_scale_from_block_max(block_max: torch.Tensor, fmt_max: float) -> torch.Tensor:
  359. """Compute per-block UE8M0 scale bytes from block amax (Torch reference)."""
  360. block_max = block_max.float().clamp(min=0.0)
  361. ratio = block_max / fmt_max
  362. ratio = torch.where(ratio <= 0, torch.zeros_like(ratio), ratio)
  363. log2_val = torch.log2(ratio.clamp(min=1e-38))
  364. exp_int = torch.ceil(log2_val).to(torch.int32)
  365. ue = (exp_int + 127).clamp(0, 255).to(torch.uint8)
  366. return torch.where(block_max <= 0, torch.zeros_like(ue), ue)
  367. def pack_grouped_fp6_values(
  368. values: torch.Tensor,
  369. fmt: Fp6Format = "e3m2",
  370. ) -> torch.Tensor:
  371. """Pack grouped FP6 float values ``[G, R, C]`` into uint8 ``[R, 3*C//4, G]``."""
  372. num_groups, rows, cols = values.shape
  373. if cols % 4 != 0:
  374. raise ValueError(f"cols must be divisible by 4 for FP6 packing, got {cols}")
  375. codes = torch.empty((num_groups, rows, cols), dtype=torch.uint8, device=values.device)
  376. for g in range(num_groups):
  377. for r in range(rows):
  378. for c in range(cols):
  379. codes[g, r, c] = _encode_fp6_nearest(float(values[g, r, c].item()), fmt)
  380. packed = torch.empty((rows, cols * 3 // 4, num_groups), dtype=torch.uint8, device=values.device)
  381. for g in range(num_groups):
  382. packed[..., g] = pack_fp6_codes_tensor(codes[g])
  383. return packed
  384. def as_grouped_mxfp6_scale_view(
  385. scale_storage: torch.Tensor,
  386. rows: int,
  387. cols: int,
  388. ) -> torch.Tensor:
  389. """View swizzled UE8M0 scales for MX-FP6 (``sf_vec_size=32``)."""
  390. batch = scale_storage.shape[0]
  391. rows_padded = align_up(rows, 128)
  392. cols_padded = align_up(cols // SF_VEC_SIZE_FP6, 4)
  393. sf = scale_storage.view(torch.float8_e8m0fnu)
  394. sf = sf.view(batch, rows_padded // 128, cols_padded // 4, 32, 4, 4)
  395. return sf.permute(3, 4, 1, 5, 2, 0)
  396. def quantize_grouped_mxfp6_torch(
  397. input_tensor: torch.Tensor,
  398. row_counts: torch.Tensor,
  399. global_scale: torch.Tensor,
  400. *,
  401. fmt: Fp6Format = "e3m2",
  402. activation_fmt: Fp6Format | None = None,
  403. bf16_round: bool = False,
  404. ) -> tuple[torch.Tensor, torch.Tensor]:
  405. """Pure-Torch grouped MX-FP6 quantization with UE8M0 block scales.
  406. ``bf16_round`` rounds each scaled value through bfloat16 before FP6 encoding,
  407. faithfully modelling the GPU quantize kernel: the direct ``f32->e3m2`` cvt
  408. faults on sm_120, so the kernel runs ``f32 -> bf16 -> e3m2``. Without this the
  409. two paths disagree by 1 ULP on the handful of values sitting exactly on an FP6
  410. rounding boundary that bf16's coarser mantissa nudges across.
  411. """
  412. if activation_fmt is not None:
  413. fmt = activation_fmt
  414. fmt_max = FLOAT6_E3M2_MAX if fmt == "e3m2" else FLOAT6_E2M3_MAX
  415. num_groups, rows, cols = input_tensor.shape
  416. if cols % SF_VEC_SIZE_FP6 != 0:
  417. raise ValueError(f"cols must be divisible by {SF_VEC_SIZE_FP6}, got {cols}")
  418. if cols % 4 != 0:
  419. raise ValueError(f"cols must be divisible by 4 for FP6 packing, got {cols}")
  420. if global_scale.numel() == 1:
  421. global_scale = global_scale.expand(num_groups).contiguous()
  422. quantized = torch.zeros((num_groups, rows, cols), dtype=torch.float32, device=input_tensor.device)
  423. scales = torch.zeros(
  424. (num_groups, rows, cols // SF_VEC_SIZE_FP6),
  425. dtype=torch.uint8,
  426. device=input_tensor.device,
  427. )
  428. for group_idx in range(num_groups):
  429. valid_rows = int(row_counts[group_idx].item())
  430. if valid_rows == 0:
  431. continue
  432. x = input_tensor[group_idx, :valid_rows].float()
  433. gs = float(global_scale[group_idx].item())
  434. sliced = x.view(valid_rows, cols // SF_VEC_SIZE_FP6, SF_VEC_SIZE_FP6)
  435. block_max = sliced.abs().amax(dim=-1)
  436. ue = _ue8m0_scale_from_block_max(block_max * gs, fmt_max)
  437. block_scale = torch.where(
  438. ue == 0,
  439. torch.zeros_like(block_max),
  440. torch.pow(torch.tensor(2.0, device=x.device), ue.float() - 127.0),
  441. )
  442. output_scale = torch.where(
  443. block_scale == 0,
  444. torch.zeros_like(block_scale),
  445. gs / block_scale,
  446. )
  447. scaled = sliced * output_scale.unsqueeze(-1)
  448. if bf16_round:
  449. scaled = scaled.to(torch.bfloat16).to(torch.float32)
  450. clipped = scaled.clamp(-fmt_max, fmt_max)
  451. q = fp6_quantize_values_torch(clipped.view(valid_rows, cols), fmt=fmt)
  452. quantized[group_idx, :valid_rows] = q
  453. scales[group_idx, :valid_rows] = ue
  454. # Pack to [R, 3*C//4, G]
  455. packed_groups = []
  456. for group_idx in range(num_groups):
  457. valid_rows = int(row_counts[group_idx].item())
  458. if valid_rows == 0:
  459. packed_groups.append(
  460. torch.zeros((rows, cols * 3 // 4), dtype=torch.uint8, device=input_tensor.device)
  461. )
  462. continue
  463. codes = torch.zeros((valid_rows, cols), dtype=torch.uint8, device=input_tensor.device)
  464. for r in range(valid_rows):
  465. for c in range(cols):
  466. codes[r, c] = _encode_fp6_nearest(float(quantized[group_idx, r, c].item()), fmt)
  467. packed_groups.append(pack_fp6_codes_tensor(codes))
  468. packed = torch.stack(
  469. [p if p.shape[0] == rows else torch.cat([p, torch.zeros(rows - p.shape[0], cols * 3 // 4, dtype=torch.uint8, device=p.device)], dim=0) for p in packed_groups],
  470. dim=-1,
  471. )
  472. # ``scales`` already holds raw UE8M0 exponent bytes; reinterpret the bits as
  473. # e8m0 (``.view``) rather than numerically converting (``.to`` would map byte
  474. # 127/129 to the nearest power-of-2 and collapse distinct scales).
  475. swizzled = swizzle_block_scale(scales.view(torch.float8_e8m0fnu))
  476. scale_view = as_grouped_mxfp6_scale_view(swizzled.view(torch.uint8), rows, cols)
  477. return packed, scale_view
  478. def silu_mul_quantize_grouped_mxfp6_torch(
  479. input_tensor: torch.Tensor,
  480. row_counts: torch.Tensor,
  481. global_scale: torch.Tensor,
  482. *,
  483. fmt: Fp6Format = "e3m2",
  484. ) -> tuple[torch.Tensor, torch.Tensor]:
  485. cols = input_tensor.shape[-1] // 2
  486. left = input_tensor[..., :cols].float()
  487. right = input_tensor[..., cols:].float()
  488. activated = (F.silu(left) * right).to(input_tensor.dtype).to(torch.float32)
  489. return quantize_grouped_mxfp6_torch(activated, row_counts, global_scale, fmt=fmt)
  490. def relu2_quantize_grouped_mxfp6_torch(
  491. input_tensor: torch.Tensor,
  492. row_counts: torch.Tensor,
  493. global_scale: torch.Tensor,
  494. *,
  495. fmt: Fp6Format = "e3m2",
  496. ) -> tuple[torch.Tensor, torch.Tensor]:
  497. activated = torch.square(F.relu(input_tensor.float()))
  498. activated = activated.to(input_tensor.dtype).to(torch.float32)
  499. return quantize_grouped_mxfp6_torch(activated, row_counts, global_scale, fmt=fmt)
  500. # =============================================================================
  501. # PTX FP6 conversion intrinsics
  502. # =============================================================================
  503. @dsl_user_op
  504. def cvt_bf16x2_to_e3m2x2(src: Uint32, *, loc=None, ip=None) -> Uint32:
  505. """Convert packed bf16x2 to packed e3m2x2 in the low 16 bits of a u32."""
  506. return Uint32(
  507. llvm.inline_asm(
  508. T.i32(),
  509. [Uint32(src).ir_value(loc=loc, ip=ip)],
  510. """
  511. {
  512. .reg .b16 out, zero;
  513. cvt.rn.satfinite.e3m2x2.bf16x2 out, $1;
  514. mov.u16 zero, 0;
  515. mov.b32 $0, {out, zero};
  516. }
  517. """,
  518. "=r,r",
  519. has_side_effects=False,
  520. is_align_stack=False,
  521. asm_dialect=llvm.AsmDialect.AD_ATT,
  522. loc=loc,
  523. ip=ip,
  524. )
  525. )
  526. @dsl_user_op
  527. def cvt_bf16x2x2_to_e3m2x4(lo: Uint32, hi: Uint32, *, loc=None, ip=None) -> Uint32:
  528. """Convert two bf16x2 values and pack the e3m2x2 results into one u32."""
  529. return Uint32(
  530. llvm.inline_asm(
  531. T.i32(),
  532. [Uint32(lo).ir_value(loc=loc, ip=ip), Uint32(hi).ir_value(loc=loc, ip=ip)],
  533. """
  534. {
  535. .reg .b16 out_lo, out_hi;
  536. cvt.rn.satfinite.e3m2x2.bf16x2 out_lo, $1;
  537. cvt.rn.satfinite.e3m2x2.bf16x2 out_hi, $2;
  538. mov.b32 $0, {out_lo, out_hi};
  539. }
  540. """,
  541. "=r,r,r",
  542. has_side_effects=False,
  543. is_align_stack=False,
  544. asm_dialect=llvm.AsmDialect.AD_ATT,
  545. loc=loc,
  546. ip=ip,
  547. )
  548. )
  549. @dsl_user_op
  550. def cvt_bf16x2_to_e2m3x2(src: Uint32, *, loc=None, ip=None) -> Uint32:
  551. """Convert packed bf16x2 to packed e2m3x2 in the low 16 bits of a u32."""
  552. return Uint32(
  553. llvm.inline_asm(
  554. T.i32(),
  555. [Uint32(src).ir_value(loc=loc, ip=ip)],
  556. """
  557. {
  558. .reg .b16 out, zero;
  559. cvt.rn.satfinite.e2m3x2.bf16x2 out, $1;
  560. mov.u16 zero, 0;
  561. mov.b32 $0, {out, zero};
  562. }
  563. """,
  564. "=r,r",
  565. has_side_effects=False,
  566. is_align_stack=False,
  567. asm_dialect=llvm.AsmDialect.AD_ATT,
  568. loc=loc,
  569. ip=ip,
  570. )
  571. )
  572. @dsl_user_op
  573. def cvt_bf16x2x2_to_e2m3x4(lo: Uint32, hi: Uint32, *, loc=None, ip=None) -> Uint32:
  574. """Convert two bf16x2 values and pack the e2m3x2 results into one u32."""
  575. return Uint32(
  576. llvm.inline_asm(
  577. T.i32(),
  578. [Uint32(lo).ir_value(loc=loc, ip=ip), Uint32(hi).ir_value(loc=loc, ip=ip)],
  579. """
  580. {
  581. .reg .b16 out_lo, out_hi;
  582. cvt.rn.satfinite.e2m3x2.bf16x2 out_lo, $1;
  583. cvt.rn.satfinite.e2m3x2.bf16x2 out_hi, $2;
  584. mov.b32 $0, {out_lo, out_hi};
  585. }
  586. """,
  587. "=r,r,r",
  588. has_side_effects=False,
  589. is_align_stack=False,
  590. asm_dialect=llvm.AsmDialect.AD_ATT,
  591. loc=loc,
  592. ip=ip,
  593. )
  594. )
  595. @dsl_user_op
  596. def cvt_f32_to_e3m2x2(
  597. hi: Float32,
  598. lo: Float32,
  599. *,
  600. loc=None,
  601. ip=None,
  602. ) -> Uint32:
  603. """Convert two float32 values to two E3M2 byte containers (16-bit pair)."""
  604. return Uint32(
  605. llvm.inline_asm(
  606. T.i32(),
  607. [
  608. Float32(lo).ir_value(loc=loc, ip=ip),
  609. Float32(hi).ir_value(loc=loc, ip=ip),
  610. ],
  611. """
  612. {
  613. .reg .b16 out, zero;
  614. cvt.rn.satfinite.e3m2x2.f32 out, $2, $1;
  615. mov.u16 zero, 0;
  616. mov.b32 $0, {out, zero};
  617. }
  618. """,
  619. "=r,f,f",
  620. has_side_effects=False,
  621. is_align_stack=False,
  622. asm_dialect=llvm.AsmDialect.AD_ATT,
  623. loc=loc,
  624. ip=ip,
  625. )
  626. )
  627. @dsl_user_op
  628. def cvt_f32_to_e4m3x2(
  629. hi: Float32,
  630. lo: Float32,
  631. *,
  632. loc=None,
  633. ip=None,
  634. ) -> Uint32:
  635. """Convert two float32 values to two FP8 E4M3 bytes (16-bit pair).
  636. ``cvt.rn.satfinite.e4m3x2.f32`` is PTX ISA 7.8 (sm_89), so unlike the
  637. bf16x2-source FP6 forms it assembles under vLLM's negotiated PTX version.
  638. """
  639. return Uint32(
  640. llvm.inline_asm(
  641. T.i32(),
  642. [
  643. Float32(lo).ir_value(loc=loc, ip=ip),
  644. Float32(hi).ir_value(loc=loc, ip=ip),
  645. ],
  646. """
  647. {
  648. .reg .b16 out, zero;
  649. cvt.rn.satfinite.e4m3x2.f32 out, $2, $1;
  650. mov.u16 zero, 0;
  651. mov.b32 $0, {out, zero};
  652. }
  653. """,
  654. "=r,f,f",
  655. has_side_effects=False,
  656. is_align_stack=False,
  657. asm_dialect=llvm.AsmDialect.AD_ATT,
  658. loc=loc,
  659. ip=ip,
  660. )
  661. )
  662. @dsl_user_op
  663. def cvt_f32_to_e2m3x2(
  664. hi: Float32,
  665. lo: Float32,
  666. *,
  667. loc=None,
  668. ip=None,
  669. ) -> Uint32:
  670. """Convert two float32 values to two E2M3 byte containers (16-bit pair)."""
  671. return Uint32(
  672. llvm.inline_asm(
  673. T.i32(),
  674. [
  675. Float32(lo).ir_value(loc=loc, ip=ip),
  676. Float32(hi).ir_value(loc=loc, ip=ip),
  677. ],
  678. """
  679. {
  680. .reg .b16 out, zero;
  681. cvt.rn.satfinite.e2m3x2.f32 out, $2, $1;
  682. mov.u16 zero, 0;
  683. mov.b32 $0, {out, zero};
  684. }
  685. """,
  686. "=r,f,f",
  687. has_side_effects=False,
  688. is_align_stack=False,
  689. asm_dialect=llvm.AsmDialect.AD_ATT,
  690. loc=loc,
  691. ip=ip,
  692. )
  693. )
  694. @dsl_user_op
  695. def _make_i16_const(value: int, *, loc=None, ip=None):
  696. i16_ty = cutlass._mlir.ir.IntegerType.get_signless(16)
  697. return cutlass._mlir.ir.Operation.create(
  698. "llvm.mlir.constant",
  699. results=[i16_ty],
  700. attributes={"value": cutlass._mlir.ir.IntegerAttr.get(i16_ty, int(value))},
  701. ).result
  702. def _mxfp6_mma_inline(
  703. ptx_types: str,
  704. d0: Float32,
  705. d1: Float32,
  706. d2: Float32,
  707. d3: Float32,
  708. a0: Uint32,
  709. a1: Uint32,
  710. a2: Uint32,
  711. a3: Uint32,
  712. b0: Uint32,
  713. b1: Uint32,
  714. sfa: Uint32,
  715. sfb: Uint32,
  716. bid_a: int,
  717. tid_a: int,
  718. bid_b: int,
  719. tid_b: int,
  720. *,
  721. loc=None,
  722. ip=None,
  723. ) -> Tuple[Float32, Float32, Float32, Float32]:
  724. bid_a_i16 = _make_i16_const(bid_a, loc=loc, ip=ip)
  725. tid_a_i16 = _make_i16_const(tid_a, loc=loc, ip=ip)
  726. bid_b_i16 = _make_i16_const(bid_b, loc=loc, ip=ip)
  727. tid_b_i16 = _make_i16_const(tid_b, loc=loc, ip=ip)
  728. ptx = (
  729. f"mma.sync.aligned.kind::mxf8f6f4.block_scale.scale_vec::1X."
  730. f"m16n8k32.row.col.f32.{ptx_types}.f32.ue8m0"
  731. )
  732. result = llvm.inline_asm(
  733. llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
  734. [
  735. Uint32(a0).ir_value(loc=loc, ip=ip),
  736. Uint32(a1).ir_value(loc=loc, ip=ip),
  737. Uint32(a2).ir_value(loc=loc, ip=ip),
  738. Uint32(a3).ir_value(loc=loc, ip=ip),
  739. Uint32(b0).ir_value(loc=loc, ip=ip),
  740. Uint32(b1).ir_value(loc=loc, ip=ip),
  741. Uint32(sfa).ir_value(loc=loc, ip=ip),
  742. bid_a_i16,
  743. tid_a_i16,
  744. Uint32(sfb).ir_value(loc=loc, ip=ip),
  745. bid_b_i16,
  746. tid_b_i16,
  747. Float32(d0).ir_value(loc=loc, ip=ip),
  748. Float32(d1).ir_value(loc=loc, ip=ip),
  749. Float32(d2).ir_value(loc=loc, ip=ip),
  750. Float32(d3).ir_value(loc=loc, ip=ip),
  751. ],
  752. f"""
  753. {ptx}
  754. {{$0, $1, $2, $3}},
  755. {{$4, $5, $6, $7}},
  756. {{$8, $9}},
  757. {{$0, $1, $2, $3}},
  758. {{$10}},
  759. {{$11, $12}},
  760. {{$13}},
  761. {{$14, $15}};
  762. """,
  763. "=f,=f,=f,=f,r,r,r,r,r,r,r,h,h,r,h,h,0,1,2,3",
  764. has_side_effects=False,
  765. is_align_stack=False,
  766. asm_dialect=llvm.AsmDialect.AD_ATT,
  767. loc=loc,
  768. ip=ip,
  769. )
  770. return (
  771. Float32(llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)),
  772. Float32(llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)),
  773. Float32(llvm.extractvalue(T.f32(), result, [2], loc=loc, ip=ip)),
  774. Float32(llvm.extractvalue(T.f32(), result, [3], loc=loc, ip=ip)),
  775. )
  776. @dsl_user_op
  777. def mxfp6_mma_m16n8k32_f32_e3m2_e3m2(
  778. d0: Float32,
  779. d1: Float32,
  780. d2: Float32,
  781. d3: Float32,
  782. a0: Uint32,
  783. a1: Uint32,
  784. a2: Uint32,
  785. a3: Uint32,
  786. b0: Uint32,
  787. b1: Uint32,
  788. sfa: Uint32,
  789. sfb: Uint32,
  790. bid_a: int = 0,
  791. tid_a: int = 0,
  792. bid_b: int = 0,
  793. tid_b: int = 0,
  794. *,
  795. loc=None,
  796. ip=None,
  797. ) -> Tuple[Float32, Float32, Float32, Float32]:
  798. """Warp MMA for SM120 MX-FP6 ``m16n8k32`` with E3M2 x E3M2 operands."""
  799. return _mxfp6_mma_inline(
  800. "e3m2.e3m2", d0, d1, d2, d3, a0, a1, a2, a3, b0, b1, sfa, sfb,
  801. bid_a, tid_a, bid_b, tid_b, loc=loc, ip=ip,
  802. )
  803. @dsl_user_op
  804. def mxfp6_mma_m16n8k32_f32_e2m3_e2m3(
  805. d0: Float32,
  806. d1: Float32,
  807. d2: Float32,
  808. d3: Float32,
  809. a0: Uint32,
  810. a1: Uint32,
  811. a2: Uint32,
  812. a3: Uint32,
  813. b0: Uint32,
  814. b1: Uint32,
  815. sfa: Uint32,
  816. sfb: Uint32,
  817. bid_a: int = 0,
  818. tid_a: int = 0,
  819. bid_b: int = 0,
  820. tid_b: int = 0,
  821. *,
  822. loc=None,
  823. ip=None,
  824. ) -> Tuple[Float32, Float32, Float32, Float32]:
  825. """Warp MMA for SM120 MX-FP6 ``m16n8k32`` with E2M3 x E2M3 operands."""
  826. return _mxfp6_mma_inline(
  827. "e2m3.e2m3", d0, d1, d2, d3, a0, a1, a2, a3, b0, b1, sfa, sfb,
  828. bid_a, tid_a, bid_b, tid_b, loc=loc, ip=ip,
  829. )
  830. @dsl_user_op
  831. def mxfp6_mma_m16n8k32_f32_e2m3_e3m2(
  832. d0: Float32,
  833. d1: Float32,
  834. d2: Float32,
  835. d3: Float32,
  836. a0: Uint32,
  837. a1: Uint32,
  838. a2: Uint32,
  839. a3: Uint32,
  840. b0: Uint32,
  841. b1: Uint32,
  842. sfa: Uint32,
  843. sfb: Uint32,
  844. bid_a: int = 0,
  845. tid_a: int = 0,
  846. bid_b: int = 0,
  847. tid_b: int = 0,
  848. *,
  849. loc=None,
  850. ip=None,
  851. ) -> Tuple[Float32, Float32, Float32, Float32]:
  852. """Warp MMA for SM120 MX-FP6 ``m16n8k32`` with E2M3 x E3M2 operands (default weight/act)."""
  853. return _mxfp6_mma_inline(
  854. "e2m3.e3m2", d0, d1, d2, d3, a0, a1, a2, a3, b0, b1, sfa, sfb,
  855. bid_a, tid_a, bid_b, tid_b, loc=loc, ip=ip,
  856. )
  857. @dsl_user_op
  858. def mxfp6_mma_m16n8k32_f32_e3m2_e2m3(
  859. d0: Float32,
  860. d1: Float32,
  861. d2: Float32,
  862. d3: Float32,
  863. a0: Uint32,
  864. a1: Uint32,
  865. a2: Uint32,
  866. a3: Uint32,
  867. b0: Uint32,
  868. b1: Uint32,
  869. sfa: Uint32,
  870. sfb: Uint32,
  871. bid_a: int = 0,
  872. tid_a: int = 0,
  873. bid_b: int = 0,
  874. tid_b: int = 0,
  875. *,
  876. loc=None,
  877. ip=None,
  878. ) -> Tuple[Float32, Float32, Float32, Float32]:
  879. """Warp MMA for SM120 MX-FP6 ``m16n8k32`` with E3M2 x E2M3 operands."""
  880. return _mxfp6_mma_inline(
  881. "e3m2.e2m3", d0, d1, d2, d3, a0, a1, a2, a3, b0, b1, sfa, sfb,
  882. bid_a, tid_a, bid_b, tid_b, loc=loc, ip=ip,
  883. )
  884. # W6A8 variants: FP8 E4M3 activations against FP6 weights. Same mxf8f6f4 MMA
  885. # kind / scales / containers; only the activation operand's type qualifier
  886. # changes. The activation side is operand A in the MoE kernel and operand B in
  887. # the dense kernel, so both orientations exist for each weight format.
  888. @dsl_user_op
  889. def mxfp6_mma_m16n8k32_f32_e4m3_e2m3(
  890. d0: Float32,
  891. d1: Float32,
  892. d2: Float32,
  893. d3: Float32,
  894. a0: Uint32,
  895. a1: Uint32,
  896. a2: Uint32,
  897. a3: Uint32,
  898. b0: Uint32,
  899. b1: Uint32,
  900. sfa: Uint32,
  901. sfb: Uint32,
  902. bid_a: int = 0,
  903. tid_a: int = 0,
  904. bid_b: int = 0,
  905. tid_b: int = 0,
  906. *,
  907. loc=None,
  908. ip=None,
  909. ) -> Tuple[Float32, Float32, Float32, Float32]:
  910. """Warp MMA ``m16n8k32`` with E4M3 (act) x E2M3 (weight) operands."""
  911. return _mxfp6_mma_inline(
  912. "e4m3.e2m3", d0, d1, d2, d3, a0, a1, a2, a3, b0, b1, sfa, sfb,
  913. bid_a, tid_a, bid_b, tid_b, loc=loc, ip=ip,
  914. )
  915. @dsl_user_op
  916. def mxfp6_mma_m16n8k32_f32_e4m3_e3m2(
  917. d0: Float32,
  918. d1: Float32,
  919. d2: Float32,
  920. d3: Float32,
  921. a0: Uint32,
  922. a1: Uint32,
  923. a2: Uint32,
  924. a3: Uint32,
  925. b0: Uint32,
  926. b1: Uint32,
  927. sfa: Uint32,
  928. sfb: Uint32,
  929. bid_a: int = 0,
  930. tid_a: int = 0,
  931. bid_b: int = 0,
  932. tid_b: int = 0,
  933. *,
  934. loc=None,
  935. ip=None,
  936. ) -> Tuple[Float32, Float32, Float32, Float32]:
  937. """Warp MMA ``m16n8k32`` with E4M3 (act) x E3M2 (weight) operands."""
  938. return _mxfp6_mma_inline(
  939. "e4m3.e3m2", d0, d1, d2, d3, a0, a1, a2, a3, b0, b1, sfa, sfb,
  940. bid_a, tid_a, bid_b, tid_b, loc=loc, ip=ip,
  941. )
  942. @dsl_user_op
  943. def mxfp6_mma_m16n8k32_f32_e2m3_e4m3(
  944. d0: Float32,
  945. d1: Float32,
  946. d2: Float32,
  947. d3: Float32,
  948. a0: Uint32,
  949. a1: Uint32,
  950. a2: Uint32,
  951. a3: Uint32,
  952. b0: Uint32,
  953. b1: Uint32,
  954. sfa: Uint32,
  955. sfb: Uint32,
  956. bid_a: int = 0,
  957. tid_a: int = 0,
  958. bid_b: int = 0,
  959. tid_b: int = 0,
  960. *,
  961. loc=None,
  962. ip=None,
  963. ) -> Tuple[Float32, Float32, Float32, Float32]:
  964. """Warp MMA ``m16n8k32`` with E2M3 (weight) x E4M3 (act) operands."""
  965. return _mxfp6_mma_inline(
  966. "e2m3.e4m3", d0, d1, d2, d3, a0, a1, a2, a3, b0, b1, sfa, sfb,
  967. bid_a, tid_a, bid_b, tid_b, loc=loc, ip=ip,
  968. )
  969. @dsl_user_op
  970. def mxfp6_mma_m16n8k32_f32_e3m2_e4m3(
  971. d0: Float32,
  972. d1: Float32,
  973. d2: Float32,
  974. d3: Float32,
  975. a0: Uint32,
  976. a1: Uint32,
  977. a2: Uint32,
  978. a3: Uint32,
  979. b0: Uint32,
  980. b1: Uint32,
  981. sfa: Uint32,
  982. sfb: Uint32,
  983. bid_a: int = 0,
  984. tid_a: int = 0,
  985. bid_b: int = 0,
  986. tid_b: int = 0,
  987. *,
  988. loc=None,
  989. ip=None,
  990. ) -> Tuple[Float32, Float32, Float32, Float32]:
  991. """Warp MMA ``m16n8k32`` with E3M2 (weight) x E4M3 (act) operands."""
  992. return _mxfp6_mma_inline(
  993. "e3m2.e4m3", d0, d1, d2, d3, a0, a1, a2, a3, b0, b1, sfa, sfb,
  994. bid_a, tid_a, bid_b, tid_b, loc=loc, ip=ip,
  995. )
  996. # =============================================================================
  997. # Memory pack / unpack (4 FP6 codes -> 3 bytes; byte containers for MMA)
  998. # =============================================================================
  999. @cute.jit
  1000. def pack_4_fp6_codes_to_3bytes(
  1001. c0: Uint32,
  1002. c1: Uint32,
  1003. c2: Uint32,
  1004. c3: Uint32,
  1005. ) -> Tuple[Uint8, Uint8, Uint8]:
  1006. """Pack four 6-bit FP6 codes into three bytes."""
  1007. mask = Uint32(0x3F)
  1008. bits = (c0 & mask) | ((c1 & mask) << 6) | ((c2 & mask) << 12) | ((c3 & mask) << 18)
  1009. return (
  1010. Uint8(bits & Uint32(0xFF)),
  1011. Uint8((bits >> 8) & Uint32(0xFF)),
  1012. Uint8((bits >> 16) & Uint32(0xFF)),
  1013. )
  1014. @cute.jit
  1015. def unpack_3bytes_to_4_fp6_codes(
  1016. b0: Uint8,
  1017. b1: Uint8,
  1018. b2: Uint8,
  1019. ) -> Tuple[Uint32, Uint32, Uint32, Uint32]:
  1020. """Unpack three bytes into four 6-bit FP6 codes."""
  1021. bits = Uint32(b0) | (Uint32(b1) << 8) | (Uint32(b2) << 16)
  1022. mask = Uint32(0x3F)
  1023. return (
  1024. bits & mask,
  1025. (bits >> 6) & mask,
  1026. (bits >> 12) & mask,
  1027. (bits >> 18) & mask,
  1028. )
  1029. @cute.jit
  1030. def unpack_3bytes_to_4_byte_containers(
  1031. b0: Uint8,
  1032. b1: Uint8,
  1033. b2: Uint8,
  1034. ) -> Tuple[Uint32, Uint32, Uint32, Uint32]:
  1035. """Unpack three packed bytes into four FP6 byte-container registers (low 6 bits set)."""
  1036. c0, c1, c2, c3 = unpack_3bytes_to_4_fp6_codes(b0, b1, b2)
  1037. return c0, c1, c2, c3
  1038. @cute.jit
  1039. def pack_4_byte_containers_to_3bytes(
  1040. c0: Uint32,
  1041. c1: Uint32,
  1042. c2: Uint32,
  1043. c3: Uint32,
  1044. ) -> Tuple[Uint8, Uint8, Uint8]:
  1045. """Pack four FP6 byte-container registers (6-bit payloads) into three bytes."""
  1046. return pack_4_fp6_codes_to_3bytes(c0 & Uint32(0x3F), c1 & Uint32(0x3F), c2 & Uint32(0x3F), c3 & Uint32(0x3F))
  1047. @cute.jit
  1048. def cvt_e3m2x8_f32(
  1049. v0: Float32,
  1050. v1: Float32,
  1051. v2: Float32,
  1052. v3: Float32,
  1053. v4: Float32,
  1054. v5: Float32,
  1055. v6: Float32,
  1056. v7: Float32,
  1057. ) -> Uint64:
  1058. """Convert eight float32 values to eight E3M2 byte containers in a uint64."""
  1059. # Use the standard f32-source cvt (``cvt.rn.satfinite.e3m2x2.f32``). The
  1060. # bf16x2-source form is a PTX ISA 9.1-only feature, and vLLM's in-process
  1061. # compiler negotiates a lower PTX version (nvJitLink), so the bf16x2 cvt fails
  1062. # to assemble inside a vLLM worker. ``cvt_f32_to_e3m2x2(hi, lo)`` reproduces the
  1063. # byte order the old ``pack_f32x2_to_bfloat2(lo, hi)`` bf16x2 path produced.
  1064. p0 = cvt_f32_to_e3m2x2(v1, v0)
  1065. p1 = cvt_f32_to_e3m2x2(v3, v2)
  1066. p2 = cvt_f32_to_e3m2x2(v5, v4)
  1067. p3 = cvt_f32_to_e3m2x2(v7, v6)
  1068. return (
  1069. Uint64(p3) << Uint64(48)
  1070. | Uint64(p2) << Uint64(32)
  1071. | Uint64(p1) << Uint64(16)
  1072. | Uint64(p0)
  1073. )
  1074. @cute.jit
  1075. def cvt_e2m3x8_f32(
  1076. v0: Float32,
  1077. v1: Float32,
  1078. v2: Float32,
  1079. v3: Float32,
  1080. v4: Float32,
  1081. v5: Float32,
  1082. v6: Float32,
  1083. v7: Float32,
  1084. ) -> Uint64:
  1085. """Convert eight float32 values to eight E2M3 byte containers in a uint64."""
  1086. # See cvt_e3m2x8_f32: use the standard f32-source cvt so the kernel assembles
  1087. # under vLLM's lower negotiated PTX version (the bf16x2 form needs PTX ISA 9.1).
  1088. p0 = cvt_f32_to_e2m3x2(v1, v0)
  1089. p1 = cvt_f32_to_e2m3x2(v3, v2)
  1090. p2 = cvt_f32_to_e2m3x2(v5, v4)
  1091. p3 = cvt_f32_to_e2m3x2(v7, v6)
  1092. return (
  1093. Uint64(p3) << Uint64(48)
  1094. | Uint64(p2) << Uint64(32)
  1095. | Uint64(p1) << Uint64(16)
  1096. | Uint64(p0)
  1097. )
  1098. @cute.jit
  1099. def cvt_e4m3x8_f32(
  1100. v0: Float32,
  1101. v1: Float32,
  1102. v2: Float32,
  1103. v3: Float32,
  1104. v4: Float32,
  1105. v5: Float32,
  1106. v6: Float32,
  1107. v7: Float32,
  1108. ) -> Uint64:
  1109. """Convert eight float32 values to eight FP8 E4M3 bytes in a uint64."""
  1110. p0 = cvt_f32_to_e4m3x2(v1, v0)
  1111. p1 = cvt_f32_to_e4m3x2(v3, v2)
  1112. p2 = cvt_f32_to_e4m3x2(v5, v4)
  1113. p3 = cvt_f32_to_e4m3x2(v7, v6)
  1114. return (
  1115. Uint64(p3) << Uint64(48)
  1116. | Uint64(p2) << Uint64(32)
  1117. | Uint64(p1) << Uint64(16)
  1118. | Uint64(p0)
  1119. )
  1120. @cute.jit
  1121. def pack_8_byte_containers_to_6bytes(
  1122. b0: Uint32,
  1123. b1: Uint32,
  1124. b2: Uint32,
  1125. b3: Uint32,
  1126. b4: Uint32,
  1127. b5: Uint32,
  1128. b6: Uint32,
  1129. b7: Uint32,
  1130. ) -> Tuple[Uint8, Uint8, Uint8, Uint8, Uint8, Uint8]:
  1131. """Pack eight 6-bit FP6 byte-container registers into six bytes."""
  1132. m = Uint32(0x3F)
  1133. bits = (
  1134. (b0 & m)
  1135. | ((b1 & m) << 6)
  1136. | ((b2 & m) << 12)
  1137. | ((b3 & m) << 18)
  1138. | ((b4 & m) << 24)
  1139. | ((b5 & m) << 30)
  1140. | ((b6 & m) << 36)
  1141. | ((b7 & m) << 42)
  1142. )
  1143. return (
  1144. Uint8(bits & Uint32(0xFF)),
  1145. Uint8((bits >> 8) & Uint32(0xFF)),
  1146. Uint8((bits >> 16) & Uint32(0xFF)),
  1147. Uint8((bits >> 24) & Uint32(0xFF)),
  1148. Uint8((bits >> 32) & Uint32(0xFF)),
  1149. Uint8((bits >> 40) & Uint32(0xFF)),
  1150. )
  1151. # =============================================================================
  1152. # Block quantizers (32-element blocks, UE8M0 scales)
  1153. # =============================================================================
  1154. @cute.jit
  1155. def max_abs_32(values: cute.Tensor) -> Float32:
  1156. """Maximum absolute value across 32 float32 elements."""
  1157. result = fabs_f32(values[0])
  1158. for i in cutlass.range_constexpr(1, 32):
  1159. result = fmax_f32(result, fabs_f32(values[i]))
  1160. return result
  1161. @cute.jit
  1162. def quantize_and_pack_8_e3m2(y_f32: cute.Tensor, inv_scale: Float32) -> Uint64:
  1163. """Quantize eight float32 values to E3M2 byte containers."""
  1164. q = cute.make_rmem_tensor((8,), Float32)
  1165. for i in cutlass.range_constexpr(8):
  1166. q[i] = y_f32[i] * inv_scale
  1167. return cvt_e3m2x8_f32(q[0], q[1], q[2], q[3], q[4], q[5], q[6], q[7])
  1168. @cute.jit
  1169. def quantize_and_pack_8_e2m3(y_f32: cute.Tensor, inv_scale: Float32) -> Uint64:
  1170. """Quantize eight float32 values to E2M3 byte containers."""
  1171. q = cute.make_rmem_tensor((8,), Float32)
  1172. for i in cutlass.range_constexpr(8):
  1173. q[i] = y_f32[i] * inv_scale
  1174. return cvt_e2m3x8_f32(q[0], q[1], q[2], q[3], q[4], q[5], q[6], q[7])
  1175. @cute.jit
  1176. def pack_32_byte_containers_to_24bytes(
  1177. containers: cute.Tensor,
  1178. ) -> Tuple[Uint64, Uint64, Uint64]:
  1179. """Pack 32 FP6 byte-container codes (6-bit) from a length-32 tensor into three uint64."""
  1180. packed_bytes = cute.make_rmem_tensor((24,), Uint8)
  1181. for gi in cutlass.range_constexpr(8):
  1182. base = gi * 4
  1183. b0, b1, b2 = pack_4_byte_containers_to_3bytes(
  1184. containers[base],
  1185. containers[base + 1],
  1186. containers[base + 2],
  1187. containers[base + 3],
  1188. )
  1189. off = gi * 3
  1190. packed_bytes[off] = b0
  1191. packed_bytes[off + 1] = b1
  1192. packed_bytes[off + 2] = b2
  1193. lo = Uint64(0)
  1194. mid = Uint64(0)
  1195. hi = Uint64(0)
  1196. for i in cutlass.range_constexpr(8):
  1197. lo = lo | (Uint64(packed_bytes[i]) << Uint64(i * 8))
  1198. mid = mid | (Uint64(packed_bytes[8 + i]) << Uint64(i * 8))
  1199. hi = hi | (Uint64(packed_bytes[16 + i]) << Uint64(i * 8))
  1200. return lo, mid, hi
  1201. @cute.jit
  1202. def pack_32_byte_containers_to_4xu64(
  1203. containers: cute.Tensor,
  1204. ) -> Tuple[Uint64, Uint64, Uint64, Uint64]:
  1205. """32 FP6 byte-containers -> four u64 lanes, one code per 8-bit byte lane.
  1206. Unpacked analogue of :func:`pack_32_byte_containers_to_24bytes`: no 3:4
  1207. compression, each byte holds one 6-bit code in bits[5:0]. Lets the quantizer
  1208. emit the byte-container layout the ``mxf8f6f4`` GEMM consumes directly with
  1209. four vectorized u64 stores per 32-element block.
  1210. """
  1211. q0 = Uint64(0)
  1212. q1 = Uint64(0)
  1213. q2 = Uint64(0)
  1214. q3 = Uint64(0)
  1215. for i in cutlass.range_constexpr(8):
  1216. sh = Uint64(i * 8)
  1217. q0 = q0 | (Uint64(containers[i]) << sh)
  1218. q1 = q1 | (Uint64(containers[8 + i]) << sh)
  1219. q2 = q2 | (Uint64(containers[16 + i]) << sh)
  1220. q3 = q3 | (Uint64(containers[24 + i]) << sh)
  1221. return q0, q1, q2, q3
  1222. @cute.jit
  1223. def _quantize_to_containers_e3m2(
  1224. values: cute.Tensor,
  1225. inv_scale: Float32,
  1226. ) -> cute.Tensor:
  1227. containers = cute.make_rmem_tensor((32,), Uint32)
  1228. for chunk in cutlass.range_constexpr(4):
  1229. q = cute.make_rmem_tensor((8,), Float32)
  1230. for j in cutlass.range_constexpr(8):
  1231. q[j] = values[chunk * 8 + j] * inv_scale
  1232. packed64 = cvt_e3m2x8_f32(q[0], q[1], q[2], q[3], q[4], q[5], q[6], q[7])
  1233. for j in cutlass.range_constexpr(8):
  1234. containers[chunk * 8 + j] = Uint32(
  1235. (packed64 >> Uint64(j * 8)) & Uint64(0xFF)
  1236. )
  1237. return containers
  1238. @cute.jit
  1239. def _quantize_to_containers_e2m3(
  1240. values: cute.Tensor,
  1241. inv_scale: Float32,
  1242. ) -> cute.Tensor:
  1243. containers = cute.make_rmem_tensor((32,), Uint32)
  1244. for chunk in cutlass.range_constexpr(4):
  1245. q = cute.make_rmem_tensor((8,), Float32)
  1246. for j in cutlass.range_constexpr(8):
  1247. q[j] = values[chunk * 8 + j] * inv_scale
  1248. packed64 = cvt_e2m3x8_f32(q[0], q[1], q[2], q[3], q[4], q[5], q[6], q[7])
  1249. for j in cutlass.range_constexpr(8):
  1250. containers[chunk * 8 + j] = Uint32(
  1251. (packed64 >> Uint64(j * 8)) & Uint64(0xFF)
  1252. )
  1253. return containers
  1254. @cute.jit
  1255. def _quantize_to_containers_e4m3(
  1256. values: cute.Tensor,
  1257. inv_scale: Float32,
  1258. ) -> cute.Tensor:
  1259. containers = cute.make_rmem_tensor((32,), Uint32)
  1260. for chunk in cutlass.range_constexpr(4):
  1261. q = cute.make_rmem_tensor((8,), Float32)
  1262. for j in cutlass.range_constexpr(8):
  1263. q[j] = values[chunk * 8 + j] * inv_scale
  1264. packed64 = cvt_e4m3x8_f32(q[0], q[1], q[2], q[3], q[4], q[5], q[6], q[7])
  1265. for j in cutlass.range_constexpr(8):
  1266. containers[chunk * 8 + j] = Uint32(
  1267. (packed64 >> Uint64(j * 8)) & Uint64(0xFF)
  1268. )
  1269. return containers
  1270. @dsl_user_op
  1271. def fp6_block_ue8m0_exact(
  1272. max_abs: Float32,
  1273. global_scale_val: Float32,
  1274. fmt_max: Float32,
  1275. *,
  1276. loc=None,
  1277. ip=None,
  1278. ) -> Uint32:
  1279. """Exact UE8M0 block-scale byte matching ``_ue8m0_scale_from_block_max``.
  1280. Computes ``ceil(log2((max_abs * gs) / fmt_max)) + 127`` with a true ``div.rn``
  1281. (same operand order as the Torch reference) and IEEE-754 bit extraction
  1282. (``exponent_field + (mantissa != 0)``) instead of ``lg2.approx``/``rcp.approx``.
  1283. The approximate path rounds the ceil the wrong way near powers of two, flipping
  1284. the exponent by one; that desync breaks power-of-two scale/code compensation for
  1285. small block elements (they underflow to zero in only one path).
  1286. """
  1287. return Uint32(
  1288. llvm.inline_asm(
  1289. T.i32(),
  1290. [
  1291. Float32(max_abs).ir_value(loc=loc, ip=ip),
  1292. Float32(global_scale_val).ir_value(loc=loc, ip=ip),
  1293. Float32(fmt_max).ir_value(loc=loc, ip=ip),
  1294. ],
  1295. """
  1296. {
  1297. .reg .pred p_zero, p_neg, p_ovf, p_mant;
  1298. .reg .f32 t, ratio;
  1299. .reg .b32 bits, expf, mant, result;
  1300. mul.f32 t, $1, $2;
  1301. div.rn.f32 ratio, t, $3;
  1302. setp.le.f32 p_zero, ratio, 0f00000000;
  1303. mov.b32 bits, ratio;
  1304. and.b32 expf, bits, 0x7F800000;
  1305. shr.u32 expf, expf, 23;
  1306. and.b32 mant, bits, 0x007FFFFF;
  1307. setp.ne.u32 p_mant, mant, 0;
  1308. selp.s32 result, 1, 0, p_mant;
  1309. add.s32 result, result, expf;
  1310. setp.lt.s32 p_neg, result, 0;
  1311. setp.gt.s32 p_ovf, result, 255;
  1312. selp.s32 result, 0, result, p_neg;
  1313. selp.s32 result, 255, result, p_ovf;
  1314. selp.s32 $0, 0, result, p_zero;
  1315. }
  1316. """,
  1317. "=r,f,f,f",
  1318. has_side_effects=False,
  1319. is_align_stack=False,
  1320. asm_dialect=llvm.AsmDialect.AD_ATT,
  1321. )
  1322. )
  1323. @dsl_user_op
  1324. def ue8m0_output_scale_exact(
  1325. ue8m0_val: Uint32,
  1326. global_scale_val: Float32,
  1327. *,
  1328. loc=None,
  1329. ip=None,
  1330. ) -> Float32:
  1331. """Exact ``gs * 2^(127-ue)`` (== reference ``gs / 2^(ue-127)``).
  1332. ``ue8m0_to_output_scale`` forms ``2^(127-ue)`` with ``ex2.approx``, which is
  1333. not bit-exact for integer exponents (~1e-7 relative error). That perturbation
  1334. flips the bf16/FP6 rounding for the few values sitting on a quantization
  1335. boundary. Here the power of two is built directly from IEEE-754 bits
  1336. (exponent field ``254-ue``, zero mantissa), so multiplying ``gs`` by it is
  1337. exact and matches the Torch reference's scaled value bit-for-bit.
  1338. """
  1339. return Float32(
  1340. llvm.inline_asm(
  1341. T.f32(),
  1342. [
  1343. Uint32(ue8m0_val).ir_value(loc=loc, ip=ip),
  1344. Float32(global_scale_val).ir_value(loc=loc, ip=ip),
  1345. ],
  1346. """
  1347. {
  1348. .reg .pred p_zero;
  1349. .reg .b32 ef, pw;
  1350. .reg .f32 powf, result;
  1351. setp.eq.u32 p_zero, $1, 0;
  1352. sub.s32 ef, 254, $1;
  1353. shl.b32 pw, ef, 23;
  1354. mov.b32 powf, pw;
  1355. mul.f32 result, powf, $2;
  1356. selp.f32 $0, 0f00000000, result, p_zero;
  1357. }
  1358. """,
  1359. "=f,r,f",
  1360. has_side_effects=False,
  1361. is_align_stack=False,
  1362. asm_dialect=llvm.AsmDialect.AD_ATT,
  1363. )
  1364. )
  1365. @cute.jit
  1366. def quantize_block_fp6_e3m2_fast(
  1367. values: cute.Tensor,
  1368. max_abs: Float32,
  1369. global_scale_val: Float32,
  1370. ) -> Tuple[Uint64, Uint64, Uint64, Uint8]:
  1371. """Fast MX-FP6 E3M2 block quantize (32 elements) with UE8M0 scale byte."""
  1372. scale_byte = Uint8(0)
  1373. lo = Uint64(0)
  1374. mid = Uint64(0)
  1375. hi = Uint64(0)
  1376. if global_scale_val != Float32(0.0):
  1377. scale_u32 = fp6_block_ue8m0_exact(
  1378. max_abs, global_scale_val, Float32(FLOAT6_E3M2_MAX)
  1379. )
  1380. scale_byte = Uint8(scale_u32 & Uint32(0xFF))
  1381. inv_scale = ue8m0_output_scale_exact(scale_u32, global_scale_val)
  1382. if inv_scale != Float32(0.0):
  1383. containers = _quantize_to_containers_e3m2(values, inv_scale)
  1384. lo, mid, hi = pack_32_byte_containers_to_24bytes(containers)
  1385. return lo, mid, hi, scale_byte
  1386. @cute.jit
  1387. def quantize_block_fp6_e2m3_fast(
  1388. values: cute.Tensor,
  1389. max_abs: Float32,
  1390. global_scale_val: Float32,
  1391. ) -> Tuple[Uint64, Uint64, Uint64, Uint8]:
  1392. """Fast MX-FP6 E2M3 block quantize (32 elements) with UE8M0 scale byte."""
  1393. scale_byte = Uint8(0)
  1394. lo = Uint64(0)
  1395. mid = Uint64(0)
  1396. hi = Uint64(0)
  1397. if global_scale_val != Float32(0.0):
  1398. scale_u32 = fp6_block_ue8m0_exact(
  1399. max_abs, global_scale_val, Float32(FLOAT6_E2M3_MAX)
  1400. )
  1401. scale_byte = Uint8(scale_u32 & Uint32(0xFF))
  1402. inv_scale = ue8m0_output_scale_exact(scale_u32, global_scale_val)
  1403. if inv_scale != Float32(0.0):
  1404. containers = _quantize_to_containers_e2m3(values, inv_scale)
  1405. lo, mid, hi = pack_32_byte_containers_to_24bytes(containers)
  1406. return lo, mid, hi, scale_byte
  1407. @cute.jit
  1408. def quantize_block_fp6_e3m2_bytes(
  1409. values: cute.Tensor,
  1410. max_abs: Float32,
  1411. global_scale_val: Float32,
  1412. ) -> Tuple[Uint64, Uint64, Uint64, Uint64, Uint8]:
  1413. """E3M2 block quantize (32 elems) -> four u64 byte-container lanes + scale.
  1414. Identical quantization math to :func:`quantize_block_fp6_e3m2_fast`; only
  1415. the output packing differs (1 byte per code instead of 3:4), so the GEMM's
  1416. per-call activation expansion is unnecessary."""
  1417. scale_byte = Uint8(0)
  1418. q0 = Uint64(0)
  1419. q1 = Uint64(0)
  1420. q2 = Uint64(0)
  1421. q3 = Uint64(0)
  1422. if global_scale_val != Float32(0.0):
  1423. scale_u32 = fp6_block_ue8m0_exact(
  1424. max_abs, global_scale_val, Float32(FLOAT6_E3M2_MAX)
  1425. )
  1426. scale_byte = Uint8(scale_u32 & Uint32(0xFF))
  1427. inv_scale = ue8m0_output_scale_exact(scale_u32, global_scale_val)
  1428. if inv_scale != Float32(0.0):
  1429. containers = _quantize_to_containers_e3m2(values, inv_scale)
  1430. q0, q1, q2, q3 = pack_32_byte_containers_to_4xu64(containers)
  1431. return q0, q1, q2, q3, scale_byte
  1432. @cute.jit
  1433. def quantize_block_fp6_e2m3_bytes(
  1434. values: cute.Tensor,
  1435. max_abs: Float32,
  1436. global_scale_val: Float32,
  1437. ) -> Tuple[Uint64, Uint64, Uint64, Uint64, Uint8]:
  1438. """E2M3 block quantize (32 elems) -> four u64 byte-container lanes + scale.
  1439. See :func:`quantize_block_fp6_e3m2_bytes`."""
  1440. scale_byte = Uint8(0)
  1441. q0 = Uint64(0)
  1442. q1 = Uint64(0)
  1443. q2 = Uint64(0)
  1444. q3 = Uint64(0)
  1445. if global_scale_val != Float32(0.0):
  1446. scale_u32 = fp6_block_ue8m0_exact(
  1447. max_abs, global_scale_val, Float32(FLOAT6_E2M3_MAX)
  1448. )
  1449. scale_byte = Uint8(scale_u32 & Uint32(0xFF))
  1450. inv_scale = ue8m0_output_scale_exact(scale_u32, global_scale_val)
  1451. if inv_scale != Float32(0.0):
  1452. containers = _quantize_to_containers_e2m3(values, inv_scale)
  1453. q0, q1, q2, q3 = pack_32_byte_containers_to_4xu64(containers)
  1454. return q0, q1, q2, q3, scale_byte
  1455. @cute.jit
  1456. def quantize_block_fp8_e4m3_bytes(
  1457. values: cute.Tensor,
  1458. max_abs: Float32,
  1459. global_scale_val: Float32,
  1460. ) -> Tuple[Uint64, Uint64, Uint64, Uint64, Uint8]:
  1461. """E4M3 block quantize (32 elems) -> four u64 byte-container lanes + scale.
  1462. W6A8 activation path: same UE8M0 amax-ceil scale and byte-container layout
  1463. as :func:`quantize_block_fp6_e2m3_bytes`, but the bytes hold real E4M3 codes
  1464. (``FLOAT8_E4M3_MAX``) for the ``e4m3.e2m3`` dense/MoE MMA."""
  1465. scale_byte = Uint8(0)
  1466. q0 = Uint64(0)
  1467. q1 = Uint64(0)
  1468. q2 = Uint64(0)
  1469. q3 = Uint64(0)
  1470. if global_scale_val != Float32(0.0):
  1471. scale_u32 = fp6_block_ue8m0_exact(
  1472. max_abs, global_scale_val, Float32(FLOAT8_E4M3_MAX)
  1473. )
  1474. scale_byte = Uint8(scale_u32 & Uint32(0xFF))
  1475. inv_scale = ue8m0_output_scale_exact(scale_u32, global_scale_val)
  1476. if inv_scale != Float32(0.0):
  1477. containers = _quantize_to_containers_e4m3(values, inv_scale)
  1478. q0, q1, q2, q3 = pack_32_byte_containers_to_4xu64(containers)
  1479. return q0, q1, q2, q3, scale_byte
  1480. @cute.jit
  1481. def quantize_block_fp6_e3m2_containers(
  1482. values: cute.Tensor,
  1483. max_abs: Float32,
  1484. global_scale_val: Float32,
  1485. ) -> Tuple[cute.Tensor, Uint8]:
  1486. """E3M2 block quantize (32 elems) -> 32 byte-containers + UE8M0 scale byte.
  1487. Like :func:`quantize_block_fp6_e3m2_fast` but skips the 3:4 pack and returns
  1488. the 32 expanded byte-containers (one FP6 code in bits[5:0] of each ``Uint8``).
  1489. Feeds the byte-container smem/``mxf8f6f4`` MMA path (the FP6 analogue of the
  1490. dense load-boundary expansion, but produced in-kernel by the route-pack)."""
  1491. out = cute.make_rmem_tensor((32,), Uint8)
  1492. for i in cutlass.range_constexpr(32):
  1493. out[i] = Uint8(0)
  1494. scale_byte = Uint8(0)
  1495. if global_scale_val != Float32(0.0):
  1496. scale_u32 = fp6_block_ue8m0_exact(
  1497. max_abs, global_scale_val, Float32(FLOAT6_E3M2_MAX)
  1498. )
  1499. scale_byte = Uint8(scale_u32 & Uint32(0xFF))
  1500. inv_scale = ue8m0_output_scale_exact(scale_u32, global_scale_val)
  1501. if inv_scale != Float32(0.0):
  1502. containers = _quantize_to_containers_e3m2(values, inv_scale)
  1503. for i in cutlass.range_constexpr(32):
  1504. out[i] = Uint8(containers[i] & Uint32(0xFF))
  1505. return out, scale_byte
  1506. @cute.jit
  1507. def quantize_block_fp6_e2m3_containers(
  1508. values: cute.Tensor,
  1509. max_abs: Float32,
  1510. global_scale_val: Float32,
  1511. ) -> Tuple[cute.Tensor, Uint8]:
  1512. """E2M3 block quantize (32 elems) -> 32 byte-containers + UE8M0 scale byte.
  1513. See :func:`quantize_block_fp6_e3m2_containers`."""
  1514. out = cute.make_rmem_tensor((32,), Uint8)
  1515. for i in cutlass.range_constexpr(32):
  1516. out[i] = Uint8(0)
  1517. scale_byte = Uint8(0)
  1518. if global_scale_val != Float32(0.0):
  1519. scale_u32 = fp6_block_ue8m0_exact(
  1520. max_abs, global_scale_val, Float32(FLOAT6_E2M3_MAX)
  1521. )
  1522. scale_byte = Uint8(scale_u32 & Uint32(0xFF))
  1523. inv_scale = ue8m0_output_scale_exact(scale_u32, global_scale_val)
  1524. if inv_scale != Float32(0.0):
  1525. containers = _quantize_to_containers_e2m3(values, inv_scale)
  1526. for i in cutlass.range_constexpr(32):
  1527. out[i] = Uint8(containers[i] & Uint32(0xFF))
  1528. return out, scale_byte
  1529. @cute.jit
  1530. def quantize_block_fp8_e4m3_containers(
  1531. values: cute.Tensor,
  1532. max_abs: Float32,
  1533. global_scale_val: Float32,
  1534. ) -> Tuple[cute.Tensor, Uint8]:
  1535. """FP8 E4M3 block quantize (32 elems) -> 32 bytes + UE8M0 scale byte.
  1536. The W6A8 activation hop: identical structure to
  1537. :func:`quantize_block_fp6_e2m3_containers` (same UE8M0 amax-ceil scale,
  1538. same byte-container output consumed by the ``mxf8f6f4`` MMA), but the
  1539. bytes hold real E4M3 codes and the scale uses ``FLOAT8_E4M3_MAX``."""
  1540. out = cute.make_rmem_tensor((32,), Uint8)
  1541. for i in cutlass.range_constexpr(32):
  1542. out[i] = Uint8(0)
  1543. scale_byte = Uint8(0)
  1544. if global_scale_val != Float32(0.0):
  1545. scale_u32 = fp6_block_ue8m0_exact(
  1546. max_abs, global_scale_val, Float32(FLOAT8_E4M3_MAX)
  1547. )
  1548. scale_byte = Uint8(scale_u32 & Uint32(0xFF))
  1549. inv_scale = ue8m0_output_scale_exact(scale_u32, global_scale_val)
  1550. if inv_scale != Float32(0.0):
  1551. containers = _quantize_to_containers_e4m3(values, inv_scale)
  1552. for i in cutlass.range_constexpr(32):
  1553. out[i] = Uint8(containers[i] & Uint32(0xFF))
  1554. return out, scale_byte
  1555. @cute.jit
  1556. def quantize_block_fp6_e3m2(
  1557. values: cute.Tensor,
  1558. max_abs: Float32,
  1559. global_scale_val: Float32,
  1560. ) -> Tuple[Uint64, Uint64, Uint64, Uint8]:
  1561. """MX-FP6 E3M2 block quantize (32 elements) with UE8M0 scale byte."""
  1562. return quantize_block_fp6_e3m2_fast(values, max_abs, global_scale_val)
  1563. @cute.jit
  1564. def quantize_block_fp6_e2m3(
  1565. values: cute.Tensor,
  1566. max_abs: Float32,
  1567. global_scale_val: Float32,
  1568. ) -> Tuple[Uint64, Uint64, Uint64, Uint8]:
  1569. """MX-FP6 E2M3 block quantize (32 elements) with UE8M0 scale byte."""
  1570. return quantize_block_fp6_e2m3_fast(values, max_abs, global_scale_val)
  1571. @cute.jit
  1572. def silu_mul_32(
  1573. gate: cute.Tensor,
  1574. up: cute.Tensor,
  1575. ) -> cute.Tensor:
  1576. """Fused SiLU(gate) * up for 32 float32 element pairs."""
  1577. out = cute.make_rmem_tensor((32,), Float32)
  1578. for i in cutlass.range_constexpr(32):
  1579. g = gate[i]
  1580. sigmoid_g = cute.arch.rcp_approx(
  1581. Float32(1.0) + cute.math.exp(-g, fastmath=False)
  1582. )
  1583. out[i] = g * sigmoid_g * up[i]
  1584. return out
  1585. @cute.jit
  1586. def relu2_32(x: cute.Tensor) -> cute.Tensor:
  1587. """Compute ReLU^2 for 32 float32 values."""
  1588. out = cute.make_rmem_tensor((32,), Float32)
  1589. for i in cutlass.range_constexpr(32):
  1590. v = fmax_f32(x[i], Float32(0.0))
  1591. out[i] = v * v
  1592. return out
  1593. @cute.jit
  1594. def silu_mul_quantize_block_fp6_e3m2(
  1595. gate: cute.Tensor,
  1596. up: cute.Tensor,
  1597. global_scale_val: Float32,
  1598. ) -> Tuple[Uint64, Uint64, Uint64, Uint8]:
  1599. """Fused SiLU(gate)*up + MX-FP6 E3M2 quantize for 32 element pairs."""
  1600. activated = silu_mul_32(gate, up)
  1601. block_max = max_abs_32(activated)
  1602. return quantize_block_fp6_e3m2_fast(activated, block_max, global_scale_val)
  1603. @cute.jit
  1604. def silu_mul_quantize_block_fp6_e2m3(
  1605. gate: cute.Tensor,
  1606. up: cute.Tensor,
  1607. global_scale_val: Float32,
  1608. ) -> Tuple[Uint64, Uint64, Uint64, Uint8]:
  1609. """Fused SiLU(gate)*up + MX-FP6 E2M3 quantize for 32 element pairs."""
  1610. activated = silu_mul_32(gate, up)
  1611. block_max = max_abs_32(activated)
  1612. return quantize_block_fp6_e2m3_fast(activated, block_max, global_scale_val)
  1613. @cute.jit
  1614. def relu2_quantize_block_fp6_e3m2(
  1615. x: cute.Tensor,
  1616. global_scale_val: Float32,
  1617. ) -> Tuple[Uint64, Uint64, Uint64, Uint8]:
  1618. """Fuse ReLU^2 and MX-FP6 E3M2 quantization for 32 float32 values."""
  1619. activated = relu2_32(x)
  1620. block_max = max_abs_32(activated)
  1621. return quantize_block_fp6_e3m2_fast(activated, block_max, global_scale_val)
  1622. @cute.jit
  1623. def relu2_quantize_block_fp6_e2m3(
  1624. x: cute.Tensor,
  1625. global_scale_val: Float32,
  1626. ) -> Tuple[Uint64, Uint64, Uint64, Uint8]:
  1627. """Fuse ReLU^2 and MX-FP6 E2M3 quantization for 32 float32 values."""
  1628. activated = relu2_32(x)
  1629. block_max = max_abs_32(activated)
  1630. return quantize_block_fp6_e2m3_fast(activated, block_max, global_scale_val)