|
|
@@ -0,0 +1,5524 @@
|
|
|
+"""
|
|
|
+Copyright (c) 2025 by FlashInfer team.
|
|
|
+
|
|
|
+Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
+you may not use this file except in compliance with the License.
|
|
|
+You may obtain a copy of the License at
|
|
|
+
|
|
|
+ http://www.apache.org/licenses/LICENSE-2.0
|
|
|
+
|
|
|
+Unless required by applicable law or agreed to in writing, software
|
|
|
+distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
+See the License for the specific language governing permissions and
|
|
|
+limitations under the License.
|
|
|
+
|
|
|
+Common utilities for FP4 quantization kernels.
|
|
|
+
|
|
|
+This module contains shared PTX intrinsics, helper functions, and reduction
|
|
|
+utilities used by the fused frontend kernels.
|
|
|
+"""
|
|
|
+
|
|
|
+import functools
|
|
|
+import math
|
|
|
+import operator
|
|
|
+from typing import Callable, Tuple
|
|
|
+
|
|
|
+import cutlass
|
|
|
+import cutlass.cute as cute
|
|
|
+import torch
|
|
|
+import torch.nn.functional as F
|
|
|
+from cutlass import Float32, Int32, Int64, Uint8, Uint16, Uint32, Uint64
|
|
|
+from cutlass.cutlass_dsl import T, dsl_user_op
|
|
|
+from cutlass._mlir import ir
|
|
|
+from cutlass._mlir.dialects import llvm
|
|
|
+
|
|
|
+
|
|
|
+# =============================================================================
|
|
|
+# Constants
|
|
|
+# =============================================================================
|
|
|
+
|
|
|
+FLOAT4_E2M1_MAX = 6.0 # Maximum value representable in FP4 E2M1
|
|
|
+FLOAT8_E4M3_MAX = 448.0 # Maximum value representable in FP8 E4M3
|
|
|
+SF_VEC_SIZE = 16 # Elements per scale factor block
|
|
|
+COPY_BITS = 128 # 128-bit vectorized loads
|
|
|
+_FP4_MAG_LUT = (0.0, 0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0)
|
|
|
+
|
|
|
+
|
|
|
+def align_up(value: int, alignment: int) -> int:
|
|
|
+ return ((value + alignment - 1) // alignment) * alignment
|
|
|
+
|
|
|
+
|
|
|
+def make_swizzle_indices(rows_padded: int, cols_padded: int, device: torch.device) -> torch.Tensor:
|
|
|
+ """Pre-compute swizzle gather indices for FP8 block-scale factors."""
|
|
|
+ numel = rows_padded * cols_padded
|
|
|
+ idx = torch.arange(numel, device=device)
|
|
|
+ idx_5d = idx.reshape(rows_padded // 128, 4, 32, cols_padded // 4, 4)
|
|
|
+ return idx_5d.permute(0, 3, 2, 1, 4).contiguous().reshape(-1)
|
|
|
+
|
|
|
+
|
|
|
+def swizzle_block_scale(scale: torch.Tensor) -> torch.Tensor:
|
|
|
+ if scale.ndim == 2:
|
|
|
+ scale = scale.unsqueeze(0)
|
|
|
+ squeeze_batch = True
|
|
|
+ elif scale.ndim == 3:
|
|
|
+ squeeze_batch = False
|
|
|
+ else:
|
|
|
+ raise ValueError(f"scale must be 2D or 3D, got {tuple(scale.shape)}")
|
|
|
+
|
|
|
+ batch, rows, cols = scale.shape
|
|
|
+ rows_padded = align_up(rows, 128)
|
|
|
+ cols_padded = align_up(cols, 4)
|
|
|
+
|
|
|
+ padded = torch.zeros((batch, rows_padded, cols_padded), dtype=scale.dtype, device=scale.device)
|
|
|
+ padded[:, :rows, :cols] = scale
|
|
|
+ swizzled = padded.reshape(batch, rows_padded // 128, 4, 32, cols_padded // 4, 4)
|
|
|
+ swizzled = swizzled.permute(0, 1, 4, 3, 2, 5).contiguous()
|
|
|
+ swizzled = swizzled.reshape(batch, rows_padded, cols_padded)
|
|
|
+ return swizzled[0] if squeeze_batch else swizzled
|
|
|
+
|
|
|
+
|
|
|
+def as_grouped_scale_view(scale_storage: torch.Tensor, rows: int, cols: int) -> torch.Tensor:
|
|
|
+ batch = scale_storage.shape[0]
|
|
|
+ rows_padded = align_up(rows, 128)
|
|
|
+ cols_padded = align_up(cols // SF_VEC_SIZE, 4)
|
|
|
+ sf = scale_storage.view(torch.float8_e4m3fn)
|
|
|
+ sf = sf.view(batch, rows_padded // 128, cols_padded // 4, 32, 4, 4)
|
|
|
+ return sf.permute(3, 4, 1, 5, 2, 0)
|
|
|
+
|
|
|
+
|
|
|
+def as_grouped_scale_view_mx(scale_storage: torch.Tensor, rows: int, cols: int) -> torch.Tensor:
|
|
|
+ """Grouped UE8M0 scale view for vec32 (MXFP8 w4a8) block scales."""
|
|
|
+ batch = scale_storage.shape[0]
|
|
|
+ rows_padded = align_up(rows, 128)
|
|
|
+ cols_padded = align_up(cols // 32, 4)
|
|
|
+ sf = scale_storage.view(torch.float8_e8m0fnu)
|
|
|
+ sf = sf.view(batch, rows_padded // 128, cols_padded // 4, 32, 4, 4)
|
|
|
+ return sf.permute(3, 4, 1, 5, 2, 0)
|
|
|
+
|
|
|
+
|
|
|
+def _fp4_quantize_values(x: torch.Tensor) -> torch.Tensor:
|
|
|
+ sign = torch.sign(x)
|
|
|
+ x = torch.abs(x.clone())
|
|
|
+ x[(x >= 0.0) & (x <= 0.25)] = 0.0
|
|
|
+ x[(x > 0.25) & (x < 0.75)] = 0.5
|
|
|
+ x[(x >= 0.75) & (x <= 1.25)] = 1.0
|
|
|
+ x[(x > 1.25) & (x < 1.75)] = 1.5
|
|
|
+ x[(x >= 1.75) & (x <= 2.5)] = 2.0
|
|
|
+ x[(x > 2.5) & (x < 3.5)] = 3.0
|
|
|
+ x[(x >= 3.5) & (x <= 5.0)] = 4.0
|
|
|
+ x[x > 5.0] = 6.0
|
|
|
+ return x * sign
|
|
|
+
|
|
|
+
|
|
|
+def fp4_quantize_values_torch(x: torch.Tensor) -> torch.Tensor:
|
|
|
+ """Pure-Torch FP4 E2M1 quantization with kernel-matching tie-breaking."""
|
|
|
+ return _fp4_quantize_values(x)
|
|
|
+
|
|
|
+
|
|
|
+def _fp4_encode_nibbles(values: torch.Tensor) -> torch.Tensor:
|
|
|
+ mags = values.abs()
|
|
|
+ idx = torch.zeros_like(values, dtype=torch.uint8)
|
|
|
+ for code, mag in enumerate(_FP4_MAG_LUT):
|
|
|
+ idx = torch.where(mags == mag, torch.full_like(idx, code), idx)
|
|
|
+ sign_bit = (values < 0).to(torch.uint8) << 3
|
|
|
+ return idx | sign_bit
|
|
|
+
|
|
|
+
|
|
|
+def pack_grouped_fp4_values(values: torch.Tensor) -> torch.Tensor:
|
|
|
+ """Pack grouped FP4 values `[G, R, C]` into uint8 `[R, C/2, G]`."""
|
|
|
+ nibbles = _fp4_encode_nibbles(values)
|
|
|
+ packed = nibbles.view(values.shape[0], values.shape[1], values.shape[2] // 2, 2)
|
|
|
+ packed = packed[..., 0] | (packed[..., 1] << 4)
|
|
|
+ return packed.permute(1, 2, 0).contiguous()
|
|
|
+
|
|
|
+
|
|
|
+def quantize_grouped_nvfp4_torch(
|
|
|
+ input_tensor: torch.Tensor,
|
|
|
+ row_counts: torch.Tensor,
|
|
|
+ global_scale: torch.Tensor,
|
|
|
+) -> tuple[torch.Tensor, torch.Tensor]:
|
|
|
+ """Pure-Torch grouped NVFP4 quantization with reciprocal global scales."""
|
|
|
+ num_groups, rows, cols = input_tensor.shape
|
|
|
+ if cols % SF_VEC_SIZE != 0:
|
|
|
+ raise ValueError(f"cols must be divisible by {SF_VEC_SIZE}, got {cols}")
|
|
|
+ if global_scale.numel() == 1:
|
|
|
+ global_scale = global_scale.expand(num_groups).contiguous()
|
|
|
+
|
|
|
+ quantized = torch.zeros((num_groups, rows, cols), dtype=torch.float32, device=input_tensor.device)
|
|
|
+ scales = torch.zeros((num_groups, rows, cols // SF_VEC_SIZE), dtype=torch.float32, device=input_tensor.device)
|
|
|
+ for group_idx in range(num_groups):
|
|
|
+ valid_rows = int(row_counts[group_idx].item())
|
|
|
+ if valid_rows == 0:
|
|
|
+ continue
|
|
|
+ x = input_tensor[group_idx, :valid_rows].float()
|
|
|
+ sliced = x.view(valid_rows, cols // SF_VEC_SIZE, SF_VEC_SIZE)
|
|
|
+ block_max = sliced.abs().amax(dim=-1, keepdim=True).to(torch.float32)
|
|
|
+ scale = (global_scale[group_idx] * (block_max / FLOAT4_E2M1_MAX)).to(torch.float8_e4m3fn).to(torch.float32)
|
|
|
+ output_scale = 1.0 / (scale * (1.0 / global_scale[group_idx]))
|
|
|
+ clipped = torch.clamp(sliced * output_scale, -FLOAT4_E2M1_MAX, FLOAT4_E2M1_MAX).view(valid_rows, cols)
|
|
|
+ quantized[group_idx, :valid_rows] = _fp4_quantize_values(clipped)
|
|
|
+ scales[group_idx, :valid_rows] = scale.squeeze(-1)
|
|
|
+
|
|
|
+ packed = pack_grouped_fp4_values(quantized)
|
|
|
+ swizzled = swizzle_block_scale(scales.to(torch.float8_e4m3fn))
|
|
|
+ scale_view = as_grouped_scale_view(swizzled.view(torch.uint8), rows, cols)
|
|
|
+ return packed, scale_view
|
|
|
+
|
|
|
+
|
|
|
+def silu_mul_quantize_grouped_nvfp4_torch(
|
|
|
+ input_tensor: torch.Tensor,
|
|
|
+ row_counts: torch.Tensor,
|
|
|
+ global_scale: torch.Tensor,
|
|
|
+) -> tuple[torch.Tensor, torch.Tensor]:
|
|
|
+ cols = input_tensor.shape[-1] // 2
|
|
|
+ left = input_tensor[..., :cols].float()
|
|
|
+ right = input_tensor[..., cols:].float()
|
|
|
+ activated = (F.silu(left) * right).to(input_tensor.dtype).to(torch.float32)
|
|
|
+ return quantize_grouped_nvfp4_torch(activated, row_counts, global_scale)
|
|
|
+
|
|
|
+
|
|
|
+def relu2_quantize_grouped_nvfp4_torch(
|
|
|
+ input_tensor: torch.Tensor,
|
|
|
+ row_counts: torch.Tensor,
|
|
|
+ global_scale: torch.Tensor,
|
|
|
+) -> tuple[torch.Tensor, torch.Tensor]:
|
|
|
+ """Pure-Torch grouped NVFP4 quantization for ReLU^2 FC1 outputs."""
|
|
|
+ activated = torch.square(F.relu(input_tensor.float()))
|
|
|
+ activated = activated.to(input_tensor.dtype).to(torch.float32)
|
|
|
+ return quantize_grouped_nvfp4_torch(activated, row_counts, global_scale)
|
|
|
+
|
|
|
+
|
|
|
+MX_SF_VEC_SIZE = 32 # Elements per UE8M0 scale block (MXFP8 w4a8 activations)
|
|
|
+_INV_FLOAT8_E4M3_MAX = 1.0 / FLOAT8_E4M3_MAX
|
|
|
+
|
|
|
+
|
|
|
+def pow2_ceil_ue8m0_torch(scale: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
|
|
|
+ """Bit-exact Torch replica of the ``pow2_ceil_ue8m0`` device intrinsic.
|
|
|
+
|
|
|
+ Rounds positive fp32 scales UP to a power of two by bumping the exponent
|
|
|
+ whenever the mantissa is nonzero. Returns ``(rounded_fp32, ue8m0_u8)``;
|
|
|
+ a zero scale maps to (0.0, byte 0).
|
|
|
+ """
|
|
|
+ bits = scale.to(torch.float32).contiguous().view(torch.int32)
|
|
|
+ mant = bits & 0x007FFFFF
|
|
|
+ bumped = torch.where(mant != 0, (bits + 0x00800000) & 0x7F800000, bits)
|
|
|
+ rounded = bumped.view(torch.float32)
|
|
|
+ byte = ((bumped >> 23) & 0xFF).to(torch.uint8)
|
|
|
+ return rounded, byte
|
|
|
+
|
|
|
+
|
|
|
+def _ue8m0_output_scale_torch(byte: torch.Tensor) -> torch.Tensor:
|
|
|
+ """Torch replica of ``ue8m0_to_output_scale``: 2^(127-byte), 0 for byte 0."""
|
|
|
+ inv_bits = (254 - byte.to(torch.int32)).clamp(min=0) << 23
|
|
|
+ inv = inv_bits.view(torch.float32)
|
|
|
+ return torch.where(byte == 0, torch.zeros_like(inv), inv)
|
|
|
+
|
|
|
+
|
|
|
+def quant_dequant_mxfp8_torch(x: torch.Tensor) -> torch.Tensor:
|
|
|
+ """Per-32-block MXFP8 quantize-dequantize roundtrip (oracle helper).
|
|
|
+
|
|
|
+ Matches the in-kernel ``quantize_block_fp8_mx`` numerics bit-for-bit:
|
|
|
+ UE8M0 ceil block scale, E4M3 RN-saturating payload.
|
|
|
+ """
|
|
|
+ orig_shape = x.shape
|
|
|
+ cols = orig_shape[-1]
|
|
|
+ if cols % MX_SF_VEC_SIZE != 0:
|
|
|
+ raise ValueError(f"last dim must be divisible by {MX_SF_VEC_SIZE}, got {cols}")
|
|
|
+ blocked = x.to(torch.float32).reshape(-1, cols // MX_SF_VEC_SIZE, MX_SF_VEC_SIZE)
|
|
|
+ block_max = blocked.abs().amax(dim=-1, keepdim=True)
|
|
|
+ rounded, byte = pow2_ceil_ue8m0_torch(block_max * _INV_FLOAT8_E4M3_MAX)
|
|
|
+ inv = _ue8m0_output_scale_torch(byte)
|
|
|
+ payload = (
|
|
|
+ (blocked * inv)
|
|
|
+ .clamp(-FLOAT8_E4M3_MAX, FLOAT8_E4M3_MAX)
|
|
|
+ .to(torch.float8_e4m3fn)
|
|
|
+ .to(torch.float32)
|
|
|
+ )
|
|
|
+ return (payload * rounded).reshape(orig_shape)
|
|
|
+
|
|
|
+
|
|
|
+def quantize_grouped_mxfp8_torch(
|
|
|
+ input_tensor: torch.Tensor,
|
|
|
+ row_counts: torch.Tensor,
|
|
|
+) -> tuple[torch.Tensor, torch.Tensor]:
|
|
|
+ """Pure-Torch grouped MXFP8 quantization (no global scale).
|
|
|
+
|
|
|
+ Mirrors ``quantize_grouped_nvfp4_torch`` for the w4a8 activation path:
|
|
|
+ returns ``(payload, scale_view)`` where ``payload`` is E4M3 bytes in
|
|
|
+ ``[R, C, G]`` order and ``scale_view`` is the swizzled UE8M0 grouped
|
|
|
+ view (vec32 columns).
|
|
|
+ """
|
|
|
+ num_groups, rows, cols = input_tensor.shape
|
|
|
+ if cols % MX_SF_VEC_SIZE != 0:
|
|
|
+ raise ValueError(f"cols must be divisible by {MX_SF_VEC_SIZE}, got {cols}")
|
|
|
+
|
|
|
+ payload = torch.zeros((num_groups, rows, cols), dtype=torch.uint8, device=input_tensor.device)
|
|
|
+ scales = torch.zeros(
|
|
|
+ (num_groups, rows, cols // MX_SF_VEC_SIZE), dtype=torch.uint8, device=input_tensor.device
|
|
|
+ )
|
|
|
+ for group_idx in range(num_groups):
|
|
|
+ valid_rows = int(row_counts[group_idx].item())
|
|
|
+ if valid_rows == 0:
|
|
|
+ continue
|
|
|
+ x = input_tensor[group_idx, :valid_rows].float()
|
|
|
+ blocked = x.view(valid_rows, cols // MX_SF_VEC_SIZE, MX_SF_VEC_SIZE)
|
|
|
+ block_max = blocked.abs().amax(dim=-1, keepdim=True)
|
|
|
+ rounded, byte = pow2_ceil_ue8m0_torch(block_max * _INV_FLOAT8_E4M3_MAX)
|
|
|
+ inv = _ue8m0_output_scale_torch(byte)
|
|
|
+ q = (
|
|
|
+ (blocked * inv)
|
|
|
+ .clamp(-FLOAT8_E4M3_MAX, FLOAT8_E4M3_MAX)
|
|
|
+ .to(torch.float8_e4m3fn)
|
|
|
+ .view(torch.uint8)
|
|
|
+ .view(valid_rows, cols)
|
|
|
+ )
|
|
|
+ payload[group_idx, :valid_rows] = q
|
|
|
+ scales[group_idx, :valid_rows] = byte.squeeze(-1)
|
|
|
+
|
|
|
+ payload_view = payload.permute(1, 2, 0).contiguous()
|
|
|
+ swizzled = swizzle_block_scale(scales)
|
|
|
+ scale_view = as_grouped_scale_view_mx(swizzled, rows, cols)
|
|
|
+ return payload_view, scale_view
|
|
|
+
|
|
|
+
|
|
|
+def silu_mul_quantize_grouped_mxfp8_torch(
|
|
|
+ input_tensor: torch.Tensor,
|
|
|
+ row_counts: torch.Tensor,
|
|
|
+) -> tuple[torch.Tensor, torch.Tensor]:
|
|
|
+ """SiLU(left)*right then grouped MXFP8 quantization (w4a8 FC2 input)."""
|
|
|
+ cols = input_tensor.shape[-1] // 2
|
|
|
+ left = input_tensor[..., :cols].float()
|
|
|
+ right = input_tensor[..., cols:].float()
|
|
|
+ activated = (F.silu(left) * right).to(input_tensor.dtype).to(torch.float32)
|
|
|
+ return quantize_grouped_mxfp8_torch(activated, row_counts)
|
|
|
+
|
|
|
+
|
|
|
+def relu2_quantize_grouped_mxfp8_torch(
|
|
|
+ input_tensor: torch.Tensor,
|
|
|
+ row_counts: torch.Tensor,
|
|
|
+) -> tuple[torch.Tensor, torch.Tensor]:
|
|
|
+ """ReLU^2 then grouped MXFP8 quantization (w4a8 FC2 input, non-gated)."""
|
|
|
+ activated = torch.square(F.relu(input_tensor.float()))
|
|
|
+ activated = activated.to(input_tensor.dtype).to(torch.float32)
|
|
|
+ return quantize_grouped_mxfp8_torch(activated, row_counts)
|
|
|
+
|
|
|
+
|
|
|
+# =============================================================================
|
|
|
+# Architecture Detection
|
|
|
+# =============================================================================
|
|
|
+
|
|
|
+
|
|
|
+@functools.lru_cache(maxsize=16)
|
|
|
+def get_sm_version(device: int | torch.device | str | None = None) -> int:
|
|
|
+ """Get the SM version of a CUDA device.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ device: CUDA device to query. Can be an int (device index), torch.device,
|
|
|
+ device string (e.g., 'cuda:0'), or None to use current device.
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ SM version as an integer (e.g., 120 on the current target).
|
|
|
+ """
|
|
|
+ if not torch.cuda.is_available():
|
|
|
+ return 80
|
|
|
+ if device is None:
|
|
|
+ device = torch.cuda.current_device()
|
|
|
+ props = torch.cuda.get_device_properties(device)
|
|
|
+ return props.major * 10 + props.minor
|
|
|
+
|
|
|
+
|
|
|
+# =============================================================================
|
|
|
+# PTX Intrinsics - Cluster Operations
|
|
|
+# =============================================================================
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def set_block_rank(
|
|
|
+ smem_ptr: cute.Pointer, peer_cta_rank_in_cluster: Int32, *, loc=None, ip=None
|
|
|
+) -> Int32:
|
|
|
+ """Map smem pointer to address at another CTA rank in the cluster."""
|
|
|
+ smem_ptr_i32 = smem_ptr.toint(loc=loc, ip=ip).ir_value()
|
|
|
+ return Int32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [smem_ptr_i32, peer_cta_rank_in_cluster.ir_value()],
|
|
|
+ "mapa.shared::cluster.u32 $0, $1, $2;",
|
|
|
+ "=r,r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def store_shared_remote(
|
|
|
+ val: Float32,
|
|
|
+ smem_ptr: cute.Pointer,
|
|
|
+ mbar_ptr: cute.Pointer,
|
|
|
+ peer_cta_rank_in_cluster: Int32,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+) -> None:
|
|
|
+ """Store Float32 value to shared memory on a remote CTA in the cluster."""
|
|
|
+ remote_smem_ptr_i32 = set_block_rank(
|
|
|
+ smem_ptr, peer_cta_rank_in_cluster, loc=loc, ip=ip
|
|
|
+ ).ir_value()
|
|
|
+ remote_mbar_ptr_i32 = set_block_rank(
|
|
|
+ mbar_ptr, peer_cta_rank_in_cluster, loc=loc, ip=ip
|
|
|
+ ).ir_value()
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [remote_smem_ptr_i32, val.ir_value(loc=loc, ip=ip), remote_mbar_ptr_i32],
|
|
|
+ "st.async.shared::cluster.mbarrier::complete_tx::bytes.f32 [$0], $1, [$2];",
|
|
|
+ "r,f,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def elem_pointer(x: cute.Tensor, coord, *, loc=None, ip=None) -> cute.Pointer:
|
|
|
+ """Get pointer to element at coordinate in tensor."""
|
|
|
+ return x.iterator + cute.crd2idx(coord, x.layout, loc=loc, ip=ip)
|
|
|
+
|
|
|
+
|
|
|
+# =============================================================================
|
|
|
+# PTX Intrinsics - 128-bit Vectorized Global Loads/Stores
|
|
|
+# =============================================================================
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def ld_global_v4_u32(
|
|
|
+ base_ptr: Int64, *, loc=None, ip=None
|
|
|
+) -> Tuple[Uint32, Uint32, Uint32, Uint32]:
|
|
|
+ """Load 128 bits (4 x uint32) from global memory."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.i32(), T.i32(), T.i32(), T.i32()]),
|
|
|
+ [Int64(base_ptr).ir_value(loc=loc, ip=ip)],
|
|
|
+ "ld.global.v4.u32 {$0, $1, $2, $3}, [$4];",
|
|
|
+ "=r,=r,=r,=r,l",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+ v0 = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
|
|
|
+ v1 = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
|
|
|
+ v2 = llvm.extractvalue(T.i32(), result, [2], loc=loc, ip=ip)
|
|
|
+ v3 = llvm.extractvalue(T.i32(), result, [3], loc=loc, ip=ip)
|
|
|
+
|
|
|
+ return Uint32(v0), Uint32(v1), Uint32(v2), Uint32(v3)
|
|
|
+
|
|
|
+
|
|
|
+# =============================================================================
|
|
|
+# PTX Intrinsics - Non-Coherent Global Loads
|
|
|
+# =============================================================================
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def ld_global_nc_u32(base_ptr: Int64, *, loc=None, ip=None) -> Uint32:
|
|
|
+ """Load 32 bits from global memory via non-coherent cache (.nc)."""
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [Int64(base_ptr).ir_value(loc=loc, ip=ip)],
|
|
|
+ "ld.global.nc.u32 $0, [$1];",
|
|
|
+ "=r,l",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def ld_global_b16(base_ptr: Int64, *, loc=None, ip=None) -> Uint32:
|
|
|
+ """Load one 16-bit global-memory payload into a 32-bit register.
|
|
|
+
|
|
|
+ PTX permits a bit-typed load into a wider bit register. This is the form
|
|
|
+ emitted by FlashInfer for the DSV4 RoPE gather and avoids rounding the
|
|
|
+ address down to a word followed by a runtime halfword select.
|
|
|
+ """
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [Int64(base_ptr).ir_value(loc=loc, ip=ip)],
|
|
|
+ "ld.global.b16 $0, [$1];",
|
|
|
+ "=r,l",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def prefetch_global_l2(base_ptr: Int64, *, loc=None, ip=None) -> None:
|
|
|
+ """Prefetch a global memory line into L2."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [Int64(base_ptr).ir_value(loc=loc, ip=ip)],
|
|
|
+ "prefetch.global.L2 [$0];",
|
|
|
+ "l",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def ld_global_nc_v2_u32(
|
|
|
+ base_ptr: Int64, *, loc=None, ip=None
|
|
|
+) -> Tuple[Uint32, Uint32]:
|
|
|
+ """Load 64 bits (2 x uint32) from global memory via non-coherent cache (.nc)."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.i32(), T.i32()]),
|
|
|
+ [Int64(base_ptr).ir_value(loc=loc, ip=ip)],
|
|
|
+ "ld.global.nc.v2.u32 {$0, $1}, [$2];",
|
|
|
+ "=r,=r,l",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ return (
|
|
|
+ Uint32(llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)),
|
|
|
+ Uint32(llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)),
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def ld_global_nc_v4_u32(
|
|
|
+ base_ptr: Int64, *, loc=None, ip=None
|
|
|
+) -> Tuple[Uint32, Uint32, Uint32, Uint32]:
|
|
|
+ """Load 128 bits (4 x uint32) from global memory via non-coherent cache (.nc)."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.i32(), T.i32(), T.i32(), T.i32()]),
|
|
|
+ [Int64(base_ptr).ir_value(loc=loc, ip=ip)],
|
|
|
+ "ld.global.nc.v4.u32 {$0, $1, $2, $3}, [$4];",
|
|
|
+ "=r,=r,=r,=r,l",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ return (
|
|
|
+ Uint32(llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)),
|
|
|
+ Uint32(llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)),
|
|
|
+ Uint32(llvm.extractvalue(T.i32(), result, [2], loc=loc, ip=ip)),
|
|
|
+ Uint32(llvm.extractvalue(T.i32(), result, [3], loc=loc, ip=ip)),
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def st_global_u64(base_ptr: Int64, value: Uint64, *, loc=None, ip=None):
|
|
|
+ """Store 64 bits to global memory."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int64(base_ptr).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint64(value).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "st.global.u64 [$0], $1;",
|
|
|
+ "l,l",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def st_global_u8(base_ptr: Int64, value: Uint8, *, loc=None, ip=None):
|
|
|
+ """Store 8 bits to global memory."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int64(base_ptr).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint8(value).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "st.global.u8 [$0], $1;",
|
|
|
+ "l,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def st_global_f32(base_ptr: Int64, value: Float32, *, loc=None, ip=None):
|
|
|
+ """Store 32-bit float to global memory."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int64(base_ptr).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(value).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "st.global.f32 [$0], $1;",
|
|
|
+ "l,f",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def st_global_v2_f32(base_ptr: Int64, v0: Float32, v1: Float32, *, loc=None, ip=None):
|
|
|
+ """Store 64 bits (2 x float32) to global memory."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int64(base_ptr).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(v0).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(v1).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "st.global.v2.f32 [$0], {$1, $2};",
|
|
|
+ "l,f,f",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def st_global_v4_f32(
|
|
|
+ base_ptr: Int64,
|
|
|
+ v0: Float32,
|
|
|
+ v1: Float32,
|
|
|
+ v2: Float32,
|
|
|
+ v3: Float32,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+):
|
|
|
+ """Store 128 bits (4 x float32) to global memory."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int64(base_ptr).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(v0).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(v1).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(v2).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(v3).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "st.global.v4.f32 [$0], {$1, $2, $3, $4};",
|
|
|
+ "l,f,f,f,f",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def ld_global_v4_f32(
|
|
|
+ base_ptr: Int64,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+) -> Tuple[Float32, Float32, Float32, Float32]:
|
|
|
+ """Load 128 bits (4 x float32) from global memory."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
|
|
|
+ [Int64(base_ptr).ir_value(loc=loc, ip=ip)],
|
|
|
+ "ld.global.v4.f32 {$0, $1, $2, $3}, [$4];",
|
|
|
+ "=f,=f,=f,=f,l",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ r0 = llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)
|
|
|
+ r1 = llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)
|
|
|
+ r2 = llvm.extractvalue(T.f32(), result, [2], loc=loc, ip=ip)
|
|
|
+ r3 = llvm.extractvalue(T.f32(), result, [3], loc=loc, ip=ip)
|
|
|
+ return Float32(r0), Float32(r1), Float32(r2), Float32(r3)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def st_global_v4_u32(
|
|
|
+ base_ptr: Int64,
|
|
|
+ v0: Uint32,
|
|
|
+ v1: Uint32,
|
|
|
+ v2: Uint32,
|
|
|
+ v3: Uint32,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+):
|
|
|
+ """Store 128 bits (4 x uint32) to global memory."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int64(base_ptr).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(v0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(v1).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(v2).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(v3).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "st.global.v4.u32 [$0], {$1, $2, $3, $4};",
|
|
|
+ "l,r,r,r,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def smem_ptr_to_addr(ptr: cute.Pointer, *, loc=None, ip=None) -> Int32:
|
|
|
+ """Convert a generic/smem pointer to a shared-memory u32 address for PTX."""
|
|
|
+ generic_addr = llvm.ptrtoint(T.i64(), ptr.llvm_ptr, loc=loc, ip=ip)
|
|
|
+ return Int32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [generic_addr],
|
|
|
+ "cvta.to.shared.u32 $0, $1;",
|
|
|
+ "=r,l",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def shared_ptr_to_u32(ptr: cute.Pointer, *, loc=None, ip=None) -> Int32:
|
|
|
+ """Convert an address-space-3 shared-memory pointer to a u32 address."""
|
|
|
+ return Int32(llvm.ptrtoint(T.i32(), ptr.llvm_ptr, loc=loc, ip=ip))
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def ldmatrix_m8n8x4_b16(smem_addr: Int32, *, loc=None, ip=None) -> Tuple[Uint32, Uint32, Uint32, Uint32]:
|
|
|
+ """Issue `ldmatrix.sync.aligned.m8n8.x4.shared.b16` from a shared-memory byte address."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.i32(), T.i32(), T.i32(), T.i32()]),
|
|
|
+ [Int32(smem_addr).ir_value(loc=loc, ip=ip)],
|
|
|
+ "ldmatrix.sync.aligned.m8n8.x4.shared.b16 {$0, $1, $2, $3}, [$4];",
|
|
|
+ "=r,=r,=r,=r,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ r0 = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
|
|
|
+ r1 = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
|
|
|
+ r2 = llvm.extractvalue(T.i32(), result, [2], loc=loc, ip=ip)
|
|
|
+ r3 = llvm.extractvalue(T.i32(), result, [3], loc=loc, ip=ip)
|
|
|
+ return Uint32(r0), Uint32(r1), Uint32(r2), Uint32(r3)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def ld_shared_u16_offset(
|
|
|
+ smem_addr: Int32,
|
|
|
+ byte_offset: int,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+) -> Uint16:
|
|
|
+ """Load a shared halfword at a compile-time byte displacement.
|
|
|
+
|
|
|
+ Keeping the result as i16 lets NVVM select a PTX ``%rs`` operand and
|
|
|
+ avoids a redundant ``cvt.u16.u32`` before packed FP8 decode. Embedding the
|
|
|
+ displacement also avoids materializing one address register per unrolled
|
|
|
+ K step.
|
|
|
+ """
|
|
|
+ offset = int(byte_offset)
|
|
|
+ address = f"[$1+{offset}]" if offset else "[$1]"
|
|
|
+ return Uint16(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i16(),
|
|
|
+ [Int32(smem_addr).ir_value(loc=loc, ip=ip)],
|
|
|
+ f"ld.shared.u16 $0, {address};",
|
|
|
+ "=h,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def ld_shared_u8_offset(
|
|
|
+ smem_addr: Int32,
|
|
|
+ byte_offset: int,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+) -> Uint32:
|
|
|
+ """Load one shared byte at a compile-time displacement, zero-extended.
|
|
|
+
|
|
|
+ This is the native ``ld.shared.u8`` form used for UE8M0 scale tables. A
|
|
|
+ common row base plus an immediate displacement keeps the unrolled scale
|
|
|
+ cache free of word-alignment, shift, and mask instructions.
|
|
|
+ """
|
|
|
+ offset = int(byte_offset)
|
|
|
+ address = f"[$1+{offset}]" if offset else "[$1]"
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [Int32(smem_addr).ir_value(loc=loc, ip=ip)],
|
|
|
+ f"ld.shared.u8 $0, {address};",
|
|
|
+ "=r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def ldmatrix_m8n8x2_b16(smem_addr: Int32, *, loc=None, ip=None) -> Tuple[Uint32, Uint32]:
|
|
|
+ """Issue `ldmatrix.sync.aligned.m8n8.x2.shared.b16` from a shared-memory byte address."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.i32(), T.i32()]),
|
|
|
+ [Int32(smem_addr).ir_value(loc=loc, ip=ip)],
|
|
|
+ "ldmatrix.sync.aligned.m8n8.x2.shared.b16 {$0, $1}, [$2];",
|
|
|
+ "=r,=r,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ r0 = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
|
|
|
+ r1 = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
|
|
|
+ return Uint32(r0), Uint32(r1)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def ldmatrix_m8n8x4_left_half_b16(smem_addr: Int32, *, loc=None, ip=None) -> Tuple[Uint32, Uint32]:
|
|
|
+ """Issue `ldmatrix.sync.aligned.m8n8.x4.shared.b16` and return the left half fragment pair."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.i32(), T.i32()]),
|
|
|
+ [Int32(smem_addr).ir_value(loc=loc, ip=ip)],
|
|
|
+ "ldmatrix.sync.aligned.m8n8.x4.shared.b16 {$0, _, $1, _}, [$2];",
|
|
|
+ "=r,=r,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ r0 = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
|
|
|
+ r1 = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
|
|
|
+ return Uint32(r0), Uint32(r1)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def ldmatrix_m8n8x4_right_half_b16(smem_addr: Int32, *, loc=None, ip=None) -> Tuple[Uint32, Uint32]:
|
|
|
+ """Issue `ldmatrix.sync.aligned.m8n8.x4.shared.b16` and return the right half fragment pair."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.i32(), T.i32()]),
|
|
|
+ [Int32(smem_addr).ir_value(loc=loc, ip=ip)],
|
|
|
+ "ldmatrix.sync.aligned.m8n8.x4.shared.b16 {_, $0, _, $1}, [$2];",
|
|
|
+ "=r,=r,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ r0 = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
|
|
|
+ r1 = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
|
|
|
+ return Uint32(r0), Uint32(r1)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def ldmatrix_m8n8x4_trans_b16(
|
|
|
+ smem_addr: Int32, *, loc=None, ip=None
|
|
|
+) -> Tuple[Uint32, Uint32, Uint32, Uint32]:
|
|
|
+ """Issue `ldmatrix.sync.aligned.trans.m8n8.x4.shared.b16` from a shared-memory byte address."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.i32(), T.i32(), T.i32(), T.i32()]),
|
|
|
+ [Int32(smem_addr).ir_value(loc=loc, ip=ip)],
|
|
|
+ "ldmatrix.sync.aligned.trans.m8n8.x4.shared.b16 {$0, $1, $2, $3}, [$4];",
|
|
|
+ "=r,=r,=r,=r,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ r0 = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
|
|
|
+ r1 = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
|
|
|
+ r2 = llvm.extractvalue(T.i32(), result, [2], loc=loc, ip=ip)
|
|
|
+ r3 = llvm.extractvalue(T.i32(), result, [3], loc=loc, ip=ip)
|
|
|
+ return Uint32(r0), Uint32(r1), Uint32(r2), Uint32(r3)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def ldmatrix_m8n8x4_trans_left_half_b16(smem_addr: Int32, *, loc=None, ip=None) -> Tuple[Uint32, Uint32]:
|
|
|
+ """Issue transposed `ldmatrix` and return the left half fragment pair."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.i32(), T.i32()]),
|
|
|
+ [Int32(smem_addr).ir_value(loc=loc, ip=ip)],
|
|
|
+ "ldmatrix.sync.aligned.trans.m8n8.x4.shared.b16 {$0, $1, _, _}, [$2];",
|
|
|
+ "=r,=r,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ r0 = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
|
|
|
+ r1 = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
|
|
|
+ return Uint32(r0), Uint32(r1)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def ldmatrix_m8n8x4_trans_right_half_b16(smem_addr: Int32, *, loc=None, ip=None) -> Tuple[Uint32, Uint32]:
|
|
|
+ """Issue transposed `ldmatrix` and return the right half fragment pair."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.i32(), T.i32()]),
|
|
|
+ [Int32(smem_addr).ir_value(loc=loc, ip=ip)],
|
|
|
+ "ldmatrix.sync.aligned.trans.m8n8.x4.shared.b16 {_, _, $0, $1}, [$2];",
|
|
|
+ "=r,=r,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ r0 = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
|
|
|
+ r1 = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
|
|
|
+ return Uint32(r0), Uint32(r1)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def ld_shared_v4_u32(smem_addr: Int32, *, loc=None, ip=None) -> Tuple[Uint32, Uint32, Uint32, Uint32]:
|
|
|
+ """Load 128 bits (4 x uint32) from shared memory. smem_addr is a u32 shared-memory address."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.i32(), T.i32(), T.i32(), T.i32()]),
|
|
|
+ [Int32(smem_addr).ir_value(loc=loc, ip=ip)],
|
|
|
+ "ld.shared.v4.u32 {$0, $1, $2, $3}, [$4];",
|
|
|
+ "=r,=r,=r,=r,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ r0 = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
|
|
|
+ r1 = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
|
|
|
+ r2 = llvm.extractvalue(T.i32(), result, [2], loc=loc, ip=ip)
|
|
|
+ r3 = llvm.extractvalue(T.i32(), result, [3], loc=loc, ip=ip)
|
|
|
+ return Uint32(r0), Uint32(r1), Uint32(r2), Uint32(r3)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def ld_shared_v2_u32(smem_addr: Int32, *, loc=None, ip=None) -> Tuple[Uint32, Uint32]:
|
|
|
+ """Load 64 bits (2 x uint32) from shared memory."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.i32(), T.i32()]),
|
|
|
+ [Int32(smem_addr).ir_value(loc=loc, ip=ip)],
|
|
|
+ "ld.shared.v2.u32 {$0, $1}, [$2];",
|
|
|
+ "=r,=r,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ r0 = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
|
|
|
+ r1 = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
|
|
|
+ return Uint32(r0), Uint32(r1)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def st_shared_u64(smem_addr: Int32, value: Uint64, *, loc=None, ip=None):
|
|
|
+ """Store 64 bits to shared memory. smem_addr is a u32 shared-memory address."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int32(smem_addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint64(value).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "st.shared.u64 [$0], $1;",
|
|
|
+ "r,l",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def st_shared_v4_u32(
|
|
|
+ smem_addr: Int32,
|
|
|
+ v0: Uint32,
|
|
|
+ v1: Uint32,
|
|
|
+ v2: Uint32,
|
|
|
+ v3: Uint32,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+):
|
|
|
+ """Store 128 bits (4 x uint32) to shared memory. smem_addr is a u32 shared-memory address."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int32(smem_addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(v0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(v1).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(v2).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(v3).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "st.shared.v4.u32 [$0], {$1, $2, $3, $4};",
|
|
|
+ "r,r,r,r,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def cp_async_bulk_g2s_mbar(
|
|
|
+ smem_dst_u32: Int32,
|
|
|
+ gmem_src_i64: Int64,
|
|
|
+ nbytes: Int32,
|
|
|
+ mbar_u32: Int32,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+) -> None:
|
|
|
+ """Emit cp.async.bulk.shared::cta.global.mbarrier::complete_tx::bytes.
|
|
|
+
|
|
|
+ CTA-scope bulk g2s gather completing on a CTA-scope mbarrier. Mirrors
|
|
|
+ FlashInfer cp_async_bulk_g2s (sparse_mla_sm120/arch/cp_async.cuh:64-72)
|
|
|
+ exactly:
|
|
|
+ [$0]=smem dst (u32 shared addr), [$1]=gmem src (i64 generic),
|
|
|
+ $2=bytes (u32), [$3]=mbar (u32 shared addr).
|
|
|
+ Used by the IO warp of the DSV4/GLM sparse-MLA decode port to gather a
|
|
|
+ whole token row (e.g. 656B or 576B+8B) in one transaction.
|
|
|
+ """
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int32(smem_dst_u32).ir_value(loc=loc, ip=ip),
|
|
|
+ Int64(gmem_src_i64).ir_value(loc=loc, ip=ip),
|
|
|
+ Int32(nbytes).ir_value(loc=loc, ip=ip),
|
|
|
+ Int32(mbar_u32).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "cp.async.bulk.shared::cta.global.mbarrier::complete_tx::bytes"
|
|
|
+ " [$0], [$1], $2, [$3];",
|
|
|
+ "r,l,r,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def create_l2_evict_first_policy(*, loc=None, ip=None) -> Uint64:
|
|
|
+ """Create an L2 evict-first policy for one-pass streaming cache rows."""
|
|
|
+ return Uint64(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i64(),
|
|
|
+ [],
|
|
|
+ "createpolicy.fractional.L2::evict_first.b64 $0, 1.0;",
|
|
|
+ "=l",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def cp_async_bulk_g2s_mbar_l2hint(
|
|
|
+ smem_dst_u32: Int32,
|
|
|
+ gmem_src_i64: Int64,
|
|
|
+ nbytes: Int32,
|
|
|
+ mbar_u32: Int32,
|
|
|
+ cache_policy: Uint64,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+) -> None:
|
|
|
+ """Bulk global-to-shared copy with an explicit L2 cache policy."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int32(smem_dst_u32).ir_value(loc=loc, ip=ip),
|
|
|
+ Int64(gmem_src_i64).ir_value(loc=loc, ip=ip),
|
|
|
+ Int32(nbytes).ir_value(loc=loc, ip=ip),
|
|
|
+ Int32(mbar_u32).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint64(cache_policy).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "cp.async.bulk.shared::cta.global.mbarrier::complete_tx::bytes.L2::cache_hint"
|
|
|
+ " [$0], [$1], $2, [$3], $4;",
|
|
|
+ "r,l,r,r,l",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def st_shared_u8(smem_addr: Int32, value: Uint8, *, loc=None, ip=None):
|
|
|
+ """Store 8 bits to shared memory. smem_addr is a u32 shared-memory address."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int32(smem_addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint8(value).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "st.shared.u8 [$0], $1;",
|
|
|
+ "r,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def st_shared_u16(smem_addr: Int32, value: Uint32, *, loc=None, ip=None):
|
|
|
+ """Store the low 16 bits of a register to shared memory."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int32(smem_addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(value).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "st.shared.u16 [$0], $1;",
|
|
|
+ "r,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def quantize_scaled_store_shared_v2_e4m3(
|
|
|
+ smem_addr: Int32,
|
|
|
+ scaled_value0: Float32,
|
|
|
+ scaled_value1: Float32,
|
|
|
+ inv_scale: Float32,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+):
|
|
|
+ """Scale, quantize, and vector-store two contiguous E4M3 bytes.
|
|
|
+
|
|
|
+ ``scaled_value*`` already include the per-candidate V scale used by the
|
|
|
+ preceding atomic-max pass. Keeping that multiplication in ordinary SSA
|
|
|
+ lets LLVM retain/reuse the products across the barrier, as FlashInfer does.
|
|
|
+ The E4M3 results never widen through ``Uint32`` and are written with the
|
|
|
+ same half-register ``st.shared.v2.b8`` form.
|
|
|
+ """
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int32(smem_addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(scaled_value0).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(scaled_value1).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(inv_scale).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b16 q0, q1;
|
|
|
+ .reg .f32 zero, tmp;
|
|
|
+ mov.f32 zero, 0f00000000;
|
|
|
+ mul.f32 tmp, $1, $3;
|
|
|
+ cvt.rn.satfinite.e4m3x2.f32 q0, zero, tmp;
|
|
|
+ mul.f32 tmp, $2, $3;
|
|
|
+ cvt.rn.satfinite.e4m3x2.f32 q1, zero, tmp;
|
|
|
+ st.shared.v2.b8 [$0], {q0, q1};
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "r,f,f,f",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def cp_async_u32_shared_global(smem_addr: Int32, gmem_addr: Int64, *, loc=None, ip=None):
|
|
|
+ """4-byte `cp.async.ca.shared.global` copy."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int32(smem_addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Int64(gmem_addr).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "cp.async.ca.shared.global [$0], [$1], 4;",
|
|
|
+ "r,l",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def cp_async_u64_shared_global(smem_addr: Int32, gmem_addr: Int64, *, loc=None, ip=None):
|
|
|
+ """8-byte `cp.async.ca.shared.global` copy."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int32(smem_addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Int64(gmem_addr).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "cp.async.ca.shared.global [$0], [$1], 8;",
|
|
|
+ "r,l",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def cp_async4_shared_global(smem_addr: Int32, gmem_addr: Int64, *, loc=None, ip=None):
|
|
|
+ """16-byte `cp.async.cg.shared.global` copy."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int32(smem_addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Int64(gmem_addr).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "cp.async.cg.shared.global [$0], [$1], 16;",
|
|
|
+ "r,l",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def cp_async4_shared_global_pred(
|
|
|
+ smem_addr: Int32, gmem_addr: Int64, pred: Int32, *, loc=None, ip=None
|
|
|
+):
|
|
|
+ """Predicated 16-byte `cp.async.cg.shared.global` copy."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int32(pred).ir_value(loc=loc, ip=ip),
|
|
|
+ Int32(smem_addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Int64(gmem_addr).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "{ .reg .pred p; setp.ne.b32 p, $0, 0; @p cp.async.cg.shared.global [$1], [$2], 16; }",
|
|
|
+ "r,r,l",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def cp_async4_ca_shared_global_pred(
|
|
|
+ smem_addr: Int32, gmem_addr: Int64, pred: Int32, *, loc=None, ip=None
|
|
|
+):
|
|
|
+ """Predicated 16-byte `cp.async.ca.shared.global` copy."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int32(pred).ir_value(loc=loc, ip=ip),
|
|
|
+ Int32(smem_addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Int64(gmem_addr).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "{ .reg .pred p; setp.ne.b32 p, $0, 0; @p cp.async.ca.shared.global [$1], [$2], 16; }",
|
|
|
+ "r,r,l",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def get_ptr_as_int64(tensor: cute.Tensor, offset, *, loc=None, ip=None) -> Int64:
|
|
|
+ """Get the memory address of tensor[offset] as Int64."""
|
|
|
+ elem_ptr = tensor.iterator + offset
|
|
|
+ ptr_int = llvm.ptrtoint(T.i64(), elem_ptr.llvm_ptr, loc=loc, ip=ip)
|
|
|
+ return Int64(ptr_int)
|
|
|
+
|
|
|
+
|
|
|
+# =============================================================================
|
|
|
+# PTX Intrinsics - Global Atomics
|
|
|
+# =============================================================================
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def atomic_add_global_i32(addr: Int64, val: Int32, *, loc=None, ip=None) -> Int32:
|
|
|
+ """Global memory int32 atomic add. Returns old value."""
|
|
|
+ return Int32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [
|
|
|
+ Int64(addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Int32(val).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "atom.global.add.s32 $0, [$1], $2;",
|
|
|
+ "=r,l,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def red_add_global_release_i32(addr: Int64, val: Int32, *, loc=None, ip=None):
|
|
|
+ """No-return global int32 add with a GPU-scope release fence."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int64(addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Int32(val).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "fence.acq_rel.gpu;\n"
|
|
|
+ "red.relaxed.gpu.global.add.s32 [$0], $1;",
|
|
|
+ "l,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def red_add_global_f32(addr: Int64, val: Float32, *, loc=None, ip=None):
|
|
|
+ """No-return global fp32 add reduction (relaxed device scope)."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int64(addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(val).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "red.global.add.f32 [$0], $1;",
|
|
|
+ "l,f",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def cvt_bf16x2_to_f16x2(packed: Uint32, *, loc=None, ip=None) -> Uint32:
|
|
|
+ """Convert a u32 holding two bf16 (lo, hi) into an f16x2 u32 (lo, hi)."""
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [Uint32(packed).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b16 blo, bhi, hlo, hhi;
|
|
|
+ .reg .f32 flo, fhi;
|
|
|
+ mov.b32 {blo, bhi}, $1;
|
|
|
+ cvt.f32.bf16 flo, blo;
|
|
|
+ cvt.f32.bf16 fhi, bhi;
|
|
|
+ cvt.rn.f16.f32 hlo, flo;
|
|
|
+ cvt.rn.f16.f32 hhi, fhi;
|
|
|
+ mov.b32 $0, {hlo, hhi};
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def red_add_global_bf16x2(addr: Int64, packed: Uint32, *, loc=None, ip=None):
|
|
|
+ """No-return global atomic add of a packed bf16x2 value (2 contiguous bf16).
|
|
|
+
|
|
|
+ Used by the W4A16 TC-decode FC2 epilogue to fold the per-route partial
|
|
|
+ outputs into the per-token output without a separate top-k-sum launch.
|
|
|
+ The address must be 4-byte aligned and cover two consecutive bf16 lanes.
|
|
|
+ """
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int64(addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(packed).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "red.relaxed.gpu.global.add.noftz.bf16x2 [$0], $1;",
|
|
|
+ "l,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def red_max_global_f32_nonnegative(addr: Int64, val: Float32, *, loc=None, ip=None):
|
|
|
+ """No-return global max reduction for a non-negative fp32 value.
|
|
|
+
|
|
|
+ Non-negative IEEE-754 float ordering matches unsigned integer bit ordering,
|
|
|
+ so this emits a no-return u32 max reduction on the value's bit pattern.
|
|
|
+ Callers must initialize the destination with a non-negative fp32 value.
|
|
|
+ """
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int64(addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(val).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "{ .reg .u32 vi; mov.b32 vi, $1; red.relaxed.gpu.global.max.u32 [$0], vi; }",
|
|
|
+ "l,f",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def atomic_cas_global_i32(addr: Int64, compare: Int32, value: Int32, *, loc=None, ip=None) -> Int32:
|
|
|
+ """Global memory int32 atomic compare-and-swap. Returns old value."""
|
|
|
+ return Int32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [
|
|
|
+ Int64(addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Int32(compare).ir_value(loc=loc, ip=ip),
|
|
|
+ Int32(value).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "atom.global.cas.b32 $0, [$1], $2, $3;",
|
|
|
+ "=r,l,r,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def atomic_add_shared_i32(addr: Int32, val: Int32, *, loc=None, ip=None) -> Int32:
|
|
|
+ """Shared-memory int32 atomic add (CTA-scope). Returns old value.
|
|
|
+
|
|
|
+ Uses ``atom.shared.add.s32`` with a 32-bit shared-memory address
|
|
|
+ (the native address width for smem on NVIDIA GPUs).
|
|
|
+ """
|
|
|
+ return Int32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [
|
|
|
+ Int32(addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Int32(val).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "atom.shared.add.s32 $0, [$1], $2;",
|
|
|
+ "=r,r,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def atomic_max_shared_f32(smem_addr: Int32, val: Float32, *, loc=None, ip=None):
|
|
|
+ """No-return shared atomic max for a NON-NEGATIVE fp32 ``val``.
|
|
|
+
|
|
|
+ Relies on the IEEE-754 ordering of non-negative floats matching the signed
|
|
|
+ int ordering of their bit patterns, so the smem max can be done with a
|
|
|
+ single s32 reduction. All callers consume the reduced shared slot after a
|
|
|
+ barrier; none needs the old or resulting value in the issuing lane.
|
|
|
+ """
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int32(smem_addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(val).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "{.reg .s32 vi; mov.b32 vi, $1; red.shared.max.s32 [$0], vi;}",
|
|
|
+ "r,f",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def atomic_max_shared_f32_offset(
|
|
|
+ smem_addr: Int32,
|
|
|
+ byte_offset: int,
|
|
|
+ val: Float32,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+):
|
|
|
+ """No-return nonnegative FP32 shared max at a constant displacement."""
|
|
|
+ offset = int(byte_offset)
|
|
|
+ address = f"[$0+{offset}]" if offset else "[$0]"
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int32(smem_addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(val).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ f"{{.reg .s32 vi; mov.b32 vi, $1; red.shared.max.s32 {address}, vi;}}",
|
|
|
+ "r,f",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def red_add_shared_i32(addr: Int32, val: Int32, *, loc=None, ip=None):
|
|
|
+ """No-return shared-memory int32 add.
|
|
|
+
|
|
|
+ Use this when the old value is not needed. NVCC lowers equivalent C++
|
|
|
+ histogram increments to cheaper no-return shared atomics.
|
|
|
+ """
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int32(addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Int32(val).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "red.shared.add.u32 [$0], $1;",
|
|
|
+ "r,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def ld_shared_i32(addr: Int32, *, loc=None, ip=None) -> Int32:
|
|
|
+ """Volatile-style load int32 from shared memory at a 32-bit byte address."""
|
|
|
+ return Int32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [Int32(addr).ir_value(loc=loc, ip=ip)],
|
|
|
+ "ld.shared.s32 $0, [$1];",
|
|
|
+ "=r,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def ld_shared_i32_relaxed(addr: Int32, *, loc=None, ip=None) -> Int32:
|
|
|
+ """Load int32 from shared memory at a 32-bit byte address."""
|
|
|
+ return Int32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [Int32(addr).ir_value(loc=loc, ip=ip)],
|
|
|
+ "ld.shared.s32 $0, [$1];",
|
|
|
+ "=r,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def ld_shared_u32(addr: Int32, *, loc=None, ip=None) -> Uint32:
|
|
|
+ """Load uint32 from shared memory at a 32-bit byte address."""
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [Int32(addr).ir_value(loc=loc, ip=ip)],
|
|
|
+ "ld.shared.u32 $0, [$1];",
|
|
|
+ "=r,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def st_shared_i32(addr: Int32, val: Int32, *, loc=None, ip=None):
|
|
|
+ """Store int32 to shared memory at a 32-bit byte address."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int32(addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Int32(val).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "st.shared.s32 [$0], $1;",
|
|
|
+ "r,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def st_shared_u32(addr: Int32, val: Uint32, *, loc=None, ip=None):
|
|
|
+ """Store uint32 to shared memory at a 32-bit byte address."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int32(addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(val).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "st.shared.u32 [$0], $1;",
|
|
|
+ "r,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def ld_shared_f32(addr: Int32, *, loc=None, ip=None) -> Float32:
|
|
|
+ """Load float32 from shared memory at a 32-bit byte address."""
|
|
|
+ return Float32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.f32(),
|
|
|
+ [Int32(addr).ir_value(loc=loc, ip=ip)],
|
|
|
+ "ld.shared.f32 $0, [$1];",
|
|
|
+ "=f,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def ld_shared_f32_offset(
|
|
|
+ addr: Int32,
|
|
|
+ byte_offset: int,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+) -> Float32:
|
|
|
+ """Load shared FP32 using a compile-time displacement."""
|
|
|
+ offset = int(byte_offset)
|
|
|
+ address = f"[$1+{offset}]" if offset else "[$1]"
|
|
|
+ return Float32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.f32(),
|
|
|
+ [Int32(addr).ir_value(loc=loc, ip=ip)],
|
|
|
+ f"ld.shared.f32 $0, {address};",
|
|
|
+ "=f,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def ld_shared_v4_f32(
|
|
|
+ addr: Int32, *, loc=None, ip=None
|
|
|
+) -> Tuple[Float32, Float32, Float32, Float32]:
|
|
|
+ """Load 128 bits (4 x float32) from shared memory."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
|
|
|
+ [Int32(addr).ir_value(loc=loc, ip=ip)],
|
|
|
+ "ld.shared.v4.f32 {$0, $1, $2, $3}, [$4];",
|
|
|
+ "=f,=f,=f,=f,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ r0 = llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)
|
|
|
+ r1 = llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)
|
|
|
+ r2 = llvm.extractvalue(T.f32(), result, [2], loc=loc, ip=ip)
|
|
|
+ r3 = llvm.extractvalue(T.f32(), result, [3], loc=loc, ip=ip)
|
|
|
+ return Float32(r0), Float32(r1), Float32(r2), Float32(r3)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def ld_shared_bf16_to_f32(addr: Int32, *, loc=None, ip=None) -> Float32:
|
|
|
+ """Load a BF16 from shared memory at a 32-bit byte address and convert to FP32."""
|
|
|
+ return Float32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.f32(),
|
|
|
+ [Int32(addr).ir_value(loc=loc, ip=ip)],
|
|
|
+ "{.reg .b16 tmp; ld.shared.b16 tmp, [$1]; cvt.f32.bf16 $0, tmp;}",
|
|
|
+ "=f,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def st_shared_f32(addr: Int32, val: Float32, *, loc=None, ip=None):
|
|
|
+ """Store float32 to shared memory at a 32-bit byte address."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int32(addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(val).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "st.shared.f32 [$0], $1;",
|
|
|
+ "r,f",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def st_shared_f32_offset(
|
|
|
+ addr: Int32,
|
|
|
+ byte_offset: int,
|
|
|
+ val: Float32,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+):
|
|
|
+ """Store shared FP32 using a compile-time displacement."""
|
|
|
+ offset = int(byte_offset)
|
|
|
+ address = f"[$0+{offset}]" if offset else "[$0]"
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int32(addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(val).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ f"st.shared.f32 {address}, $1;",
|
|
|
+ "r,f",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def st_shared_v4_f32(
|
|
|
+ addr: Int32,
|
|
|
+ v0: Float32,
|
|
|
+ v1: Float32,
|
|
|
+ v2: Float32,
|
|
|
+ v3: Float32,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+):
|
|
|
+ """Store 128 bits (4 x float32) to shared memory."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int32(addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(v0).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(v1).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(v2).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(v3).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "st.shared.v4.f32 [$0], {$1, $2, $3, $4};",
|
|
|
+ "r,f,f,f,f",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def st_shared_bf16_from_f32(addr: Int32, val: Float32, *, loc=None, ip=None):
|
|
|
+ """Convert float32 to BF16 and store one 16-bit value to shared memory."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int32(addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(val).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "{ .reg .b16 tmp; cvt.rn.bf16.f32 tmp, $1; st.shared.b16 [$0], tmp; }",
|
|
|
+ "r,f",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def st_shared_f16_from_f32(addr: Int32, val: Float32, *, loc=None, ip=None):
|
|
|
+ """Convert float32 to FP16 and store one 16-bit value to shared memory."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int32(addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(val).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "{ .reg .b16 tmp; cvt.rn.f16.f32 tmp, $1; st.shared.b16 [$0], tmp; }",
|
|
|
+ "r,f",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def st_global_i32(addr: Int64, val: Int32, *, loc=None, ip=None):
|
|
|
+ """Store int32 to global memory."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int64(addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Int32(val).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "st.global.s32 [$0], $1;",
|
|
|
+ "l,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+# =============================================================================
|
|
|
+# PTX Intrinsics - Global Memory Barriers
|
|
|
+# =============================================================================
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def ld_global_acquire_i32(addr: Int64, *, loc=None, ip=None) -> Int32:
|
|
|
+ """Load int32 from global memory with acquire semantics."""
|
|
|
+ return Int32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [Int64(addr).ir_value(loc=loc, ip=ip)],
|
|
|
+ "ld.global.acquire.gpu.s32 $0, [$1];",
|
|
|
+ "=r,l",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def st_global_release_i32(addr: Int64, val: Int32, *, loc=None, ip=None):
|
|
|
+ """Store int32 to global memory with release semantics."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int64(addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Int32(val).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "st.global.release.gpu.s32 [$0], $1;",
|
|
|
+ "l,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def spin_wait_global_eq_i32(addr: Int64, expected: Int32, *, loc=None, ip=None):
|
|
|
+ """Spin-wait until *addr != expected (acquire semantics on load)."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int64(addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Int32(expected).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "{\n"
|
|
|
+ ".reg .pred %p0;\n"
|
|
|
+ ".reg .s32 %val;\n"
|
|
|
+ "spin_loop:\n"
|
|
|
+ " ld.global.acquire.gpu.s32 %val, [$0];\n"
|
|
|
+ " setp.eq.s32 %p0, %val, $1;\n"
|
|
|
+ " @%p0 bra spin_loop;\n"
|
|
|
+ "}",
|
|
|
+ "l,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def spin_wait_global_ge_i32(addr: Int64, target: Int32, *, loc=None, ip=None):
|
|
|
+ """Spin-wait until *addr >= target using acquire loads."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int64(addr).ir_value(loc=loc, ip=ip),
|
|
|
+ Int32(target).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "{\n"
|
|
|
+ ".reg .pred %p0;\n"
|
|
|
+ ".reg .s32 %val;\n"
|
|
|
+ "spin_loop_ge:\n"
|
|
|
+ " ld.global.acquire.gpu.s32 %val, [$0];\n"
|
|
|
+ " setp.lt.s32 %p0, %val, $1;\n"
|
|
|
+ " @%p0 bra spin_loop_ge;\n"
|
|
|
+ "}",
|
|
|
+ "l,r",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def threadfence(*, loc=None, ip=None):
|
|
|
+ """Emit membar.gl — threadfence across global memory."""
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [],
|
|
|
+ "membar.gl;",
|
|
|
+ "",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+# =============================================================================
|
|
|
+# PTX Intrinsics - Scatter Atomics
|
|
|
+# =============================================================================
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def scatter_add_bf16x2(addr: Int64, val0_f32, val1_f32, *, loc=None, ip=None):
|
|
|
+ """BF16x2 atomic reduction add to global memory.
|
|
|
+
|
|
|
+ Packs two f32 values into bf16x2 via cvt.rn.satfinite, then does
|
|
|
+ red.relaxed.gpu.global.add.noftz.bf16x2. Workaround for LLVM NVPTX
|
|
|
+ bug that decomposes vector<2xbf16> into .v2.bf16x2 which ptxas rejects.
|
|
|
+ """
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int64(addr).ir_value(loc=loc, ip=ip),
|
|
|
+ val0_f32.ir_value(loc=loc, ip=ip),
|
|
|
+ val1_f32.ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "{ .reg .b32 packed; cvt.rn.satfinite.bf16x2.f32 packed, $2, $1; red.relaxed.gpu.global.add.noftz.bf16x2 [$0], packed; }",
|
|
|
+ "l,f,f",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def scatter_add_bf16(addr: Int64, val_f32, *, loc=None, ip=None):
|
|
|
+ """BF16 atomic reduction add to global memory.
|
|
|
+
|
|
|
+ Converts the f32 input to bf16 inside PTX and atomically accumulates it into
|
|
|
+ one bf16 output lane. This is intended for opt-in approximate reductions.
|
|
|
+ """
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int64(addr).ir_value(loc=loc, ip=ip),
|
|
|
+ val_f32.ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "{ .reg .b16 packed; cvt.rn.satfinite.bf16.f32 packed, $1; red.relaxed.gpu.global.add.noftz.bf16 [$0], packed; }",
|
|
|
+ "l,f",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def scatter_add_v4_bf16x2(addr: Int64, v0, v1, v2, v3, v4, v5, v6, v7, *, loc=None, ip=None):
|
|
|
+ """Vectorized BF16x2 atomic reduction: 8 bf16 values (16 bytes) in one go.
|
|
|
+
|
|
|
+ red.global.add.noftz.v4.bf16x2 [addr], {packed0, packed1, packed2, packed3}
|
|
|
+ Each packed reg contains 2 bf16 values converted from f32 pairs.
|
|
|
+ """
|
|
|
+ llvm.inline_asm(
|
|
|
+ None,
|
|
|
+ [
|
|
|
+ Int64(addr).ir_value(loc=loc, ip=ip),
|
|
|
+ v0.ir_value(loc=loc, ip=ip), v1.ir_value(loc=loc, ip=ip),
|
|
|
+ v2.ir_value(loc=loc, ip=ip), v3.ir_value(loc=loc, ip=ip),
|
|
|
+ v4.ir_value(loc=loc, ip=ip), v5.ir_value(loc=loc, ip=ip),
|
|
|
+ v6.ir_value(loc=loc, ip=ip), v7.ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "{ .reg .b32 p0,p1,p2,p3;"
|
|
|
+ " cvt.rn.satfinite.bf16x2.f32 p0, $2, $1;"
|
|
|
+ " cvt.rn.satfinite.bf16x2.f32 p1, $4, $3;"
|
|
|
+ " cvt.rn.satfinite.bf16x2.f32 p2, $6, $5;"
|
|
|
+ " cvt.rn.satfinite.bf16x2.f32 p3, $8, $7;"
|
|
|
+ " red.global.add.noftz.v4.bf16x2 [$0], {p0, p1, p2, p3}; }",
|
|
|
+ "l,f,f,f,f,f,f,f,f",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+# =============================================================================
|
|
|
+# PTX Intrinsics - Math Operations
|
|
|
+# =============================================================================
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def rcp_approx_ftz(a: Float32, *, loc=None, ip=None) -> Float32:
|
|
|
+ """Fast reciprocal using PTX rcp.approx.ftz.f32."""
|
|
|
+ return Float32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.f32(),
|
|
|
+ [Float32(a).ir_value(loc=loc, ip=ip)],
|
|
|
+ "rcp.approx.ftz.f32 $0, $1;",
|
|
|
+ "=f,f",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def fmin_f32(a: Float32, b: Float32, *, loc=None, ip=None) -> Float32:
|
|
|
+ """Compute min of two float32 values using PTX min.f32."""
|
|
|
+ return Float32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.f32(),
|
|
|
+ [Float32(a).ir_value(loc=loc, ip=ip), Float32(b).ir_value(loc=loc, ip=ip)],
|
|
|
+ "min.f32 $0, $1, $2;",
|
|
|
+ "=f,f,f",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def fmax_f32(a: Float32, b: Float32, *, loc=None, ip=None) -> Float32:
|
|
|
+ """Compute max of two float32 values using PTX max.f32."""
|
|
|
+ return Float32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.f32(),
|
|
|
+ [Float32(a).ir_value(loc=loc, ip=ip), Float32(b).ir_value(loc=loc, ip=ip)],
|
|
|
+ "max.f32 $0, $1, $2;",
|
|
|
+ "=f,f,f",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def fabs_f32(a: Float32, *, loc=None, ip=None) -> Float32:
|
|
|
+ """Compute absolute value of float32 using PTX abs.f32."""
|
|
|
+ return Float32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.f32(),
|
|
|
+ [Float32(a).ir_value(loc=loc, ip=ip)],
|
|
|
+ "abs.f32 $0, $1;",
|
|
|
+ "=f,f",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+# =============================================================================
|
|
|
+# Half2 SIMD Intrinsics
|
|
|
+# =============================================================================
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def half2_mul(a: Uint32, b: Uint32, *, loc=None, ip=None) -> Uint32:
|
|
|
+ """Multiply two Half2 values element-wise: (a.x*b.x, a.y*b.y)."""
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [Uint32(a).ir_value(loc=loc, ip=ip), Uint32(b).ir_value(loc=loc, ip=ip)],
|
|
|
+ "mul.f16x2 $0, $1, $2;",
|
|
|
+ "=r,r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def broadcast_f32_to_half2(x: Float32, *, loc=None, ip=None) -> Uint32:
|
|
|
+ """Pack one float32 value into both lanes of an f16x2 register."""
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [Float32(x).ir_value(loc=loc, ip=ip)],
|
|
|
+ "cvt.rn.f16x2.f32 $0, $1, $1;",
|
|
|
+ "=r,f",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def hadd2(a: Uint32, b: Uint32, *, loc=None, ip=None) -> Uint32:
|
|
|
+ """Add two Half2 values element-wise: (a.x+b.x, a.y+b.y)."""
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [Uint32(a).ir_value(loc=loc, ip=ip), Uint32(b).ir_value(loc=loc, ip=ip)],
|
|
|
+ "add.f16x2 $0, $1, $2;",
|
|
|
+ "=r,r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def habs2(x: Uint32, *, loc=None, ip=None) -> Uint32:
|
|
|
+ """Half2 absolute value - clears sign bits of both fp16 values."""
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [Uint32(x).ir_value(loc=loc, ip=ip)],
|
|
|
+ "and.b32 $0, $1, 0x7FFF7FFF;",
|
|
|
+ "=r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def hmax2(a: Uint32, b: Uint32, *, loc=None, ip=None) -> Uint32:
|
|
|
+ """Half2 max - element-wise max of 2 fp16 pairs."""
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [Uint32(a).ir_value(loc=loc, ip=ip), Uint32(b).ir_value(loc=loc, ip=ip)],
|
|
|
+ "max.f16x2 $0, $1, $2;",
|
|
|
+ "=r,r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def hmax_to_f32(x: Uint32, *, loc=None, ip=None) -> Float32:
|
|
|
+ """Extract max of 2 fp16 values in half2 as float32."""
|
|
|
+ return Float32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.f32(),
|
|
|
+ [Uint32(x).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b16 h0, h1;
|
|
|
+ .reg .f32 f0, f1;
|
|
|
+ mov.b32 {h0, h1}, $1;
|
|
|
+ cvt.f32.f16 f0, h0;
|
|
|
+ cvt.f32.f16 f1, h1;
|
|
|
+ max.f32 $0, f0, f1;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=f,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def half2_to_float2_scaled(
|
|
|
+ h2: Uint32, scale: Float32, *, loc=None, ip=None
|
|
|
+) -> Tuple[Float32, Float32]:
|
|
|
+ """Convert half2 to float2 AND multiply by scale."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.f32(), T.f32()]),
|
|
|
+ [Uint32(h2).ir_value(loc=loc, ip=ip), Float32(scale).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b16 h0, h1;
|
|
|
+ .reg .f32 f0, f1;
|
|
|
+ mov.b32 {h0, h1}, $2;
|
|
|
+ cvt.f32.f16 f0, h0;
|
|
|
+ cvt.f32.f16 f1, h1;
|
|
|
+ mul.f32 $0, f0, $3;
|
|
|
+ mul.f32 $1, f1, $3;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=f,=f,r,f",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+ f0 = llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)
|
|
|
+ f1 = llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)
|
|
|
+
|
|
|
+ return Float32(f0), Float32(f1)
|
|
|
+
|
|
|
+
|
|
|
+# =============================================================================
|
|
|
+# BFloat2 SIMD Intrinsics
|
|
|
+# =============================================================================
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def bfloat2_mul(a: Uint32, b: Uint32, *, loc=None, ip=None) -> Uint32:
|
|
|
+ """Multiply two BFloat2 values element-wise: (a.x*b.x, a.y*b.y)."""
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [Uint32(a).ir_value(loc=loc, ip=ip), Uint32(b).ir_value(loc=loc, ip=ip)],
|
|
|
+ "mul.bf16x2 $0, $1, $2;",
|
|
|
+ "=r,r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def bfloat2_broadcast_lane(x: Uint32, lane: Int32, *, loc=None, ip=None) -> Uint32:
|
|
|
+ """Duplicate one BF16 lane from a packed bf16x2 register into both lanes."""
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [
|
|
|
+ Uint32(x).ir_value(loc=loc, ip=ip),
|
|
|
+ Int32(lane).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .pred p;
|
|
|
+ .reg .b32 lo, hi, val, shifted;
|
|
|
+ and.b32 lo, $1, 0x0000ffff;
|
|
|
+ shr.u32 hi, $1, 16;
|
|
|
+ setp.eq.s32 p, $2, 0;
|
|
|
+ @p mov.b32 val, lo;
|
|
|
+ @!p mov.b32 val, hi;
|
|
|
+ shl.b32 shifted, val, 16;
|
|
|
+ or.b32 $0, val, shifted;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=r,r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def broadcast_f32_to_bfloat2(x: Float32, *, loc=None, ip=None) -> Uint32:
|
|
|
+ """Pack one float32 value into both lanes of a bf16x2 register."""
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [Float32(x).ir_value(loc=loc, ip=ip)],
|
|
|
+ "cvt.rn.satfinite.bf16x2.f32 $0, $1, $1;",
|
|
|
+ "=r,f",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def pack_f32x2_to_bfloat2(x0: Float32, x1: Float32, *, loc=None, ip=None) -> Uint32:
|
|
|
+ """Pack 2 float32 values into one bf16x2 register."""
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [Float32(x0).ir_value(loc=loc, ip=ip), Float32(x1).ir_value(loc=loc, ip=ip)],
|
|
|
+ "cvt.rn.satfinite.bf16x2.f32 $0, $2, $1;",
|
|
|
+ "=r,f,f",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def cvt_bf16x2_to_e4m3x2(src: Uint32, *, loc=None, ip=None) -> Uint32:
|
|
|
+ """Convert packed bf16x2 to packed e4m3x2 in the low 16 bits of a u32."""
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [Uint32(src).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b16 out, zero;
|
|
|
+ cvt.rn.satfinite.e4m3x2.bf16x2 out, $1;
|
|
|
+ mov.u16 zero, 0;
|
|
|
+ mov.b32 $0, {out, zero};
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def cvt_bf16x2x2_to_e4m3x4(lo: Uint32, hi: Uint32, *, loc=None, ip=None) -> Uint32:
|
|
|
+ """Convert two bf16x2 values and pack the e4m3x2 results into one u32."""
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [Uint32(lo).ir_value(loc=loc, ip=ip), Uint32(hi).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b16 out_lo, out_hi;
|
|
|
+ cvt.rn.satfinite.e4m3x2.bf16x2 out_lo, $1;
|
|
|
+ cvt.rn.satfinite.e4m3x2.bf16x2 out_hi, $2;
|
|
|
+ mov.b32 $0, {out_lo, out_hi};
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=r,r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def bfloat2_add(a: Uint32, b: Uint32, *, loc=None, ip=None) -> Uint32:
|
|
|
+ """Add two BFloat2 values element-wise: (a.x+b.x, a.y+b.y)."""
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [Uint32(a).ir_value(loc=loc, ip=ip), Uint32(b).ir_value(loc=loc, ip=ip)],
|
|
|
+ "add.bf16x2 $0, $1, $2;",
|
|
|
+ "=r,r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def fp8x4_e4m3_to_bfloat2x2(packed: Uint32, *, loc=None, ip=None) -> Tuple[Uint32, Uint32]:
|
|
|
+ """Widen 4 packed E4M3 bytes into 2 packed bf16x2 registers exactly."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.i32(), T.i32()]),
|
|
|
+ [Uint32(packed).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b16 low;
|
|
|
+ .reg .b32 q, out0, out1, sign0, sign1, mant0, mant1, tmp, bias, bias_f32;
|
|
|
+ prmt.b32 q, $2, 0, 0x1302;
|
|
|
+
|
|
|
+ and.b32 sign0, q, 0x80008000;
|
|
|
+ shr.u32 tmp, q, 4;
|
|
|
+ and.b32 mant0, tmp, 0x07F007F0;
|
|
|
+ or.b32 out0, mant0, sign0;
|
|
|
+
|
|
|
+ shl.b32 tmp, q, 8;
|
|
|
+ and.b32 sign1, tmp, 0x80008000;
|
|
|
+ shl.b32 tmp, q, 4;
|
|
|
+ and.b32 mant1, tmp, 0x07F007F0;
|
|
|
+ or.b32 out1, mant1, sign1;
|
|
|
+
|
|
|
+ mov.b32 bias_f32, 0x7B800000;
|
|
|
+ cvt.rn.bf16.f32 low, bias_f32;
|
|
|
+ mov.b32 bias, {low, low};
|
|
|
+
|
|
|
+ mul.bf16x2 $0, out0, bias;
|
|
|
+ mul.bf16x2 $1, out1, bias;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=r,=r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ lo = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
|
|
|
+ hi = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
|
|
|
+ return Uint32(lo), Uint32(hi)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def packed_dequant_e2m1x4_to_bfloat2x2(
|
|
|
+ packed: Uint32, *, loc=None, ip=None
|
|
|
+) -> Tuple[Uint32, Uint32]:
|
|
|
+ """FE2M1 -> BF16 register dequant for one packed 4-value fragment."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.i32(), T.i32()]),
|
|
|
+ [Uint32(packed).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b32 q, out1, out2, tmp;
|
|
|
+
|
|
|
+ and.b32 out1, $2, 0x80008000;
|
|
|
+ and.b32 tmp, $2, 0x70007000;
|
|
|
+ shr.u32 tmp, tmp, 6;
|
|
|
+ or.b32 out1, out1, tmp;
|
|
|
+
|
|
|
+ shl.b32 q, $2, 4;
|
|
|
+ and.b32 out2, q, 0x80008000;
|
|
|
+ and.b32 tmp, q, 0x70007000;
|
|
|
+ shr.u32 tmp, tmp, 6;
|
|
|
+ or.b32 out2, out2, tmp;
|
|
|
+
|
|
|
+ mov.b32 $0, out2;
|
|
|
+ mov.b32 $1, out1;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=r,=r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ lo = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
|
|
|
+ hi = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
|
|
|
+ return Uint32(lo), Uint32(hi)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def packed_dequant_e2m1x4_to_half2x2(
|
|
|
+ packed: Uint32, *, loc=None, ip=None
|
|
|
+) -> Tuple[Uint32, Uint32]:
|
|
|
+ """FE2M1 -> FP16 register dequant for one packed 4-value fragment."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.i32(), T.i32()]),
|
|
|
+ [Uint32(packed).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b32 q, out1, out2, tmp;
|
|
|
+
|
|
|
+ and.b32 out1, $2, 0x80008000;
|
|
|
+ and.b32 tmp, $2, 0x70007000;
|
|
|
+ shr.u32 tmp, tmp, 3;
|
|
|
+ or.b32 out1, out1, tmp;
|
|
|
+
|
|
|
+ shl.b32 q, $2, 4;
|
|
|
+ and.b32 out2, q, 0x80008000;
|
|
|
+ and.b32 tmp, q, 0x70007000;
|
|
|
+ shr.u32 tmp, tmp, 3;
|
|
|
+ or.b32 out2, out2, tmp;
|
|
|
+
|
|
|
+ mov.b32 $0, out2;
|
|
|
+ mov.b32 $1, out1;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=r,=r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ lo = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
|
|
|
+ hi = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
|
|
|
+ return Uint32(lo), Uint32(hi)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def packed_dequant_e4m3x4_to_bfloat2x2(
|
|
|
+ packed: Uint32, *, loc=None, ip=None
|
|
|
+) -> Tuple[Uint32, Uint32]:
|
|
|
+ """FE4M3 scale dequant for one packed 4-value BF16 fragment."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.i32(), T.i32()]),
|
|
|
+ [Uint32(packed).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b32 q, out1, out2, tmp;
|
|
|
+
|
|
|
+ and.b32 tmp, $2, 0x80008000;
|
|
|
+ shr.u32 out1, tmp, 1;
|
|
|
+ and.b32 tmp, $2, 0x7F007F00;
|
|
|
+ shr.u32 tmp, tmp, 4;
|
|
|
+ or.b32 out1, out1, tmp;
|
|
|
+
|
|
|
+ shl.b32 q, $2, 8;
|
|
|
+ and.b32 tmp, q, 0x80008000;
|
|
|
+ shr.u32 out2, tmp, 1;
|
|
|
+ and.b32 tmp, q, 0x7F007F00;
|
|
|
+ shr.u32 tmp, tmp, 4;
|
|
|
+ or.b32 out2, out2, tmp;
|
|
|
+
|
|
|
+ mov.b32 $0, out2;
|
|
|
+ mov.b32 $1, out1;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=r,=r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ lo = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
|
|
|
+ hi = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
|
|
|
+ return Uint32(lo), Uint32(hi)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def packed_dequant_e4m3x4_to_half2x2(
|
|
|
+ packed: Uint32, *, loc=None, ip=None
|
|
|
+) -> Tuple[Uint32, Uint32]:
|
|
|
+ """FE4M3 scale dequant for one packed 4-value FP16 fragment."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.i32(), T.i32()]),
|
|
|
+ [Uint32(packed).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b32 q, out1, out2;
|
|
|
+
|
|
|
+ and.b32 out1, $2, 0xFF00FF00;
|
|
|
+ shr.u32 out1, out1, 1;
|
|
|
+
|
|
|
+ shl.b32 q, $2, 8;
|
|
|
+ and.b32 out2, q, 0xFF00FF00;
|
|
|
+ shr.u32 out2, out2, 1;
|
|
|
+
|
|
|
+ mov.b32 $0, out2;
|
|
|
+ mov.b32 $1, out1;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=r,=r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ lo = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
|
|
|
+ hi = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
|
|
|
+ return Uint32(lo), Uint32(hi)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def packed_dequant_e8m0x4_to_bfloat2x2(
|
|
|
+ packed: Uint32, *, loc=None, ip=None
|
|
|
+) -> Tuple[Uint32, Uint32]:
|
|
|
+ """E8M0 compute-scale dequant for one packed 4-value BF16 fragment.
|
|
|
+
|
|
|
+ The W4A16 FP4 unpack path represents E2M1 values scaled by 2^-126. Match
|
|
|
+ the existing NVFP4 split by materializing E8M0 scales multiplied by 2^7 in
|
|
|
+ the MMA input and applying the remaining compensation in the kernel
|
|
|
+ epilogue.
|
|
|
+ """
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.i32(), T.i32()]),
|
|
|
+ [Uint32(packed).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .u32 b0, b1, b2, b3;
|
|
|
+ .reg .u32 h0, h1, h2, h3;
|
|
|
+ .reg .u32 t0, t1;
|
|
|
+
|
|
|
+ and.b32 b0, $2, 0x000000ff;
|
|
|
+ shr.u32 b1, $2, 8;
|
|
|
+ and.b32 b1, b1, 0x000000ff;
|
|
|
+ shr.u32 b2, $2, 16;
|
|
|
+ and.b32 b2, b2, 0x000000ff;
|
|
|
+ shr.u32 b3, $2, 24;
|
|
|
+
|
|
|
+ add.u32 h0, b0, 7;
|
|
|
+ add.u32 h1, b1, 7;
|
|
|
+ add.u32 h2, b2, 7;
|
|
|
+ add.u32 h3, b3, 7;
|
|
|
+ shl.b32 h0, h0, 7;
|
|
|
+ shl.b32 h1, h1, 7;
|
|
|
+ shl.b32 h2, h2, 7;
|
|
|
+ shl.b32 h3, h3, 7;
|
|
|
+
|
|
|
+ shl.b32 t0, h2, 16;
|
|
|
+ or.b32 $0, h0, t0;
|
|
|
+ shl.b32 t1, h3, 16;
|
|
|
+ or.b32 $1, h1, t1;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=r,=r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ lo = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
|
|
|
+ hi = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
|
|
|
+ return Uint32(lo), Uint32(hi)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def packed_dequant_e8m0x4_to_half2x2(
|
|
|
+ packed: Uint32, *, loc=None, ip=None
|
|
|
+) -> Tuple[Uint32, Uint32]:
|
|
|
+ """E8M0 compute-scale dequant for one packed 4-value FP16 fragment."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.i32(), T.i32()]),
|
|
|
+ [Uint32(packed).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .pred p0, p1, p2, p3;
|
|
|
+ .reg .u32 b0, b1, b2, b3;
|
|
|
+ .reg .s32 e0, e1, e2, e3;
|
|
|
+ .reg .f32 ef0, ef1, ef2, ef3;
|
|
|
+ .reg .f32 f0, f1, f2, f3;
|
|
|
+ .reg .b16 h0, h1, h2, h3;
|
|
|
+
|
|
|
+ and.b32 b0, $2, 0x000000ff;
|
|
|
+ shr.u32 b1, $2, 8;
|
|
|
+ and.b32 b1, b1, 0x000000ff;
|
|
|
+ shr.u32 b2, $2, 16;
|
|
|
+ and.b32 b2, b2, 0x000000ff;
|
|
|
+ shr.u32 b3, $2, 24;
|
|
|
+
|
|
|
+ setp.eq.u32 p0, b0, 0;
|
|
|
+ setp.eq.u32 p1, b1, 0;
|
|
|
+ setp.eq.u32 p2, b2, 0;
|
|
|
+ setp.eq.u32 p3, b3, 0;
|
|
|
+
|
|
|
+ cvt.s32.u32 e0, b0;
|
|
|
+ cvt.s32.u32 e1, b1;
|
|
|
+ cvt.s32.u32 e2, b2;
|
|
|
+ cvt.s32.u32 e3, b3;
|
|
|
+ sub.s32 e0, e0, 120;
|
|
|
+ sub.s32 e1, e1, 120;
|
|
|
+ sub.s32 e2, e2, 120;
|
|
|
+ sub.s32 e3, e3, 120;
|
|
|
+
|
|
|
+ cvt.rn.f32.s32 ef0, e0;
|
|
|
+ cvt.rn.f32.s32 ef1, e1;
|
|
|
+ cvt.rn.f32.s32 ef2, e2;
|
|
|
+ cvt.rn.f32.s32 ef3, e3;
|
|
|
+ ex2.approx.f32 f0, ef0;
|
|
|
+ ex2.approx.f32 f1, ef1;
|
|
|
+ ex2.approx.f32 f2, ef2;
|
|
|
+ ex2.approx.f32 f3, ef3;
|
|
|
+ selp.f32 f0, 0f00000000, f0, p0;
|
|
|
+ selp.f32 f1, 0f00000000, f1, p1;
|
|
|
+ selp.f32 f2, 0f00000000, f2, p2;
|
|
|
+ selp.f32 f3, 0f00000000, f3, p3;
|
|
|
+
|
|
|
+ cvt.rn.f16.f32 h0, f0;
|
|
|
+ cvt.rn.f16.f32 h1, f1;
|
|
|
+ cvt.rn.f16.f32 h2, f2;
|
|
|
+ cvt.rn.f16.f32 h3, f3;
|
|
|
+
|
|
|
+ setp.eq.u32 p0, b0, 255;
|
|
|
+ setp.eq.u32 p1, b1, 255;
|
|
|
+ setp.eq.u32 p2, b2, 255;
|
|
|
+ setp.eq.u32 p3, b3, 255;
|
|
|
+ selp.b16 h0, 0x7e00, h0, p0;
|
|
|
+ selp.b16 h1, 0x7e00, h1, p1;
|
|
|
+ selp.b16 h2, 0x7e00, h2, p2;
|
|
|
+ selp.b16 h3, 0x7e00, h3, p3;
|
|
|
+
|
|
|
+ mov.b32 $0, {h0, h2};
|
|
|
+ mov.b32 $1, {h1, h3};
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=r,=r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ lo = llvm.extractvalue(T.i32(), result, [0], loc=loc, ip=ip)
|
|
|
+ hi = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
|
|
|
+ return Uint32(lo), Uint32(hi)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def bf16_mma_m16n8k16_f32(
|
|
|
+ d0: Float32,
|
|
|
+ d1: Float32,
|
|
|
+ d2: Float32,
|
|
|
+ d3: Float32,
|
|
|
+ a0: Uint32,
|
|
|
+ a1: Uint32,
|
|
|
+ a2: Uint32,
|
|
|
+ a3: Uint32,
|
|
|
+ b0: Uint32,
|
|
|
+ b1: Uint32,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+) -> Tuple[Float32, Float32, Float32, Float32]:
|
|
|
+ """Warp MMA helper for `mma.sync.aligned.m16n8k16.row.col.f32.bf16.bf16.f32`."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
|
|
|
+ [
|
|
|
+ Uint32(a0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a1).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a2).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a3).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(b0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(b1).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d0).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d1).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d2).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d3).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ """
|
|
|
+ mma.sync.aligned.m16n8k16.row.col.f32.bf16.bf16.f32
|
|
|
+ {$0, $1, $2, $3},
|
|
|
+ {$4, $5, $6, $7},
|
|
|
+ {$8, $9},
|
|
|
+ {$10, $11, $12, $13};
|
|
|
+ """,
|
|
|
+ "=f,=f,=f,=f,r,r,r,r,r,r,f,f,f,f",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ r0 = llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)
|
|
|
+ r1 = llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)
|
|
|
+ r2 = llvm.extractvalue(T.f32(), result, [2], loc=loc, ip=ip)
|
|
|
+ r3 = llvm.extractvalue(T.f32(), result, [3], loc=loc, ip=ip)
|
|
|
+ return Float32(r0), Float32(r1), Float32(r2), Float32(r3)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def f32_to_raw_bits(x: Float32, *, loc=None, ip=None) -> Uint32:
|
|
|
+ """Return the raw IEEE-754 bits of one float32 value."""
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [Float32(x).ir_value(loc=loc, ip=ip)],
|
|
|
+ "mov.b32 $0, $1;",
|
|
|
+ "=r,f",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def f32_to_tf32_bits(x: Float32, *, loc=None, ip=None) -> Uint32:
|
|
|
+ """Round one float32 value to TF32 format and return its 32-bit operand bits."""
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [Float32(x).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b32 tmp;
|
|
|
+ cvt.rna.tf32.f32 tmp, $1;
|
|
|
+ mov.b32 $0, tmp;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=r,f",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def tf32_mma_m16n8k8_f32(
|
|
|
+ d0: Float32,
|
|
|
+ d1: Float32,
|
|
|
+ d2: Float32,
|
|
|
+ d3: Float32,
|
|
|
+ a0: Uint32,
|
|
|
+ a1: Uint32,
|
|
|
+ a2: Uint32,
|
|
|
+ a3: Uint32,
|
|
|
+ b0: Uint32,
|
|
|
+ b1: Uint32,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+) -> Tuple[Float32, Float32, Float32, Float32]:
|
|
|
+ """Warp MMA helper for `mma.sync.aligned.m16n8k8.row.col.f32.tf32.tf32.f32`."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
|
|
|
+ [
|
|
|
+ Uint32(a0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a1).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a2).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a3).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(b0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(b1).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d0).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d1).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d2).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d3).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ """
|
|
|
+ mma.sync.aligned.m16n8k8.row.col.f32.tf32.tf32.f32
|
|
|
+ {$0, $1, $2, $3},
|
|
|
+ {$4, $5, $6, $7},
|
|
|
+ {$8, $9},
|
|
|
+ {$10, $11, $12, $13};
|
|
|
+ """,
|
|
|
+ "=f,=f,=f,=f,r,r,r,r,r,r,f,f,f,f",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ r0 = llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)
|
|
|
+ r1 = llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)
|
|
|
+ r2 = llvm.extractvalue(T.f32(), result, [2], loc=loc, ip=ip)
|
|
|
+ r3 = llvm.extractvalue(T.f32(), result, [3], loc=loc, ip=ip)
|
|
|
+ return Float32(r0), Float32(r1), Float32(r2), Float32(r3)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def f16_mma_m16n8k16_f32(
|
|
|
+ d0: Float32,
|
|
|
+ d1: Float32,
|
|
|
+ d2: Float32,
|
|
|
+ d3: Float32,
|
|
|
+ a0: Uint32,
|
|
|
+ a1: Uint32,
|
|
|
+ a2: Uint32,
|
|
|
+ a3: Uint32,
|
|
|
+ b0: Uint32,
|
|
|
+ b1: Uint32,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+) -> Tuple[Float32, Float32, Float32, Float32]:
|
|
|
+ """Warp MMA helper for `mma.sync.aligned.m16n8k16.row.col.f32.f16.f16.f32`."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
|
|
|
+ [
|
|
|
+ Uint32(a0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a1).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a2).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a3).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(b0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(b1).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d0).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d1).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d2).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d3).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ """
|
|
|
+ mma.sync.aligned.m16n8k16.row.col.f32.f16.f16.f32
|
|
|
+ {$0, $1, $2, $3},
|
|
|
+ {$4, $5, $6, $7},
|
|
|
+ {$8, $9},
|
|
|
+ {$10, $11, $12, $13};
|
|
|
+ """,
|
|
|
+ "=f,=f,=f,=f,r,r,r,r,r,r,f,f,f,f",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ r0 = llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)
|
|
|
+ r1 = llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)
|
|
|
+ r2 = llvm.extractvalue(T.f32(), result, [2], loc=loc, ip=ip)
|
|
|
+ r3 = llvm.extractvalue(T.f32(), result, [3], loc=loc, ip=ip)
|
|
|
+ return Float32(r0), Float32(r1), Float32(r2), Float32(r3)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def bf16_mma_rhs_fragments_as_mma_a_m16n8k16_f32(
|
|
|
+ d0: Float32,
|
|
|
+ d1: Float32,
|
|
|
+ d2: Float32,
|
|
|
+ d3: Float32,
|
|
|
+ b0_0: Uint32,
|
|
|
+ b1_0: Uint32,
|
|
|
+ b0_1: Uint32,
|
|
|
+ b1_1: Uint32,
|
|
|
+ a0: Uint32,
|
|
|
+ a1: Uint32,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+) -> Tuple[Float32, Float32, Float32, Float32]:
|
|
|
+ """BF16 MMA form used by the routed m-block-size-8 path.
|
|
|
+
|
|
|
+ The dequantized RHS fragments feed the hardware A operand, while the
|
|
|
+ routed activation fragment loaded with `ldmatrix.x2` feeds hardware B.
|
|
|
+ """
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
|
|
|
+ [
|
|
|
+ Uint32(b0_0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(b1_0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(b0_1).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(b1_1).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a1).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d0).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d1).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d2).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d3).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ """
|
|
|
+ mma.sync.aligned.m16n8k16.row.col.f32.bf16.bf16.f32
|
|
|
+ {$0, $1, $2, $3},
|
|
|
+ {$4, $5, $6, $7},
|
|
|
+ {$8, $9},
|
|
|
+ {$10, $11, $12, $13};
|
|
|
+ """,
|
|
|
+ "=f,=f,=f,=f,r,r,r,r,r,r,f,f,f,f",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ r0 = llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)
|
|
|
+ r1 = llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)
|
|
|
+ r2 = llvm.extractvalue(T.f32(), result, [2], loc=loc, ip=ip)
|
|
|
+ r3 = llvm.extractvalue(T.f32(), result, [3], loc=loc, ip=ip)
|
|
|
+ return Float32(r0), Float32(r1), Float32(r2), Float32(r3)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def f16_mma_rhs_fragments_as_mma_a_m16n8k16_f32(
|
|
|
+ d0: Float32,
|
|
|
+ d1: Float32,
|
|
|
+ d2: Float32,
|
|
|
+ d3: Float32,
|
|
|
+ b0_0: Uint32,
|
|
|
+ b1_0: Uint32,
|
|
|
+ b0_1: Uint32,
|
|
|
+ b1_1: Uint32,
|
|
|
+ a0: Uint32,
|
|
|
+ a1: Uint32,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+) -> Tuple[Float32, Float32, Float32, Float32]:
|
|
|
+ """FP16 MMA form used by the routed m-block-size-8 path."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
|
|
|
+ [
|
|
|
+ Uint32(b0_0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(b1_0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(b0_1).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(b1_1).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a1).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d0).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d1).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d2).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d3).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ """
|
|
|
+ mma.sync.aligned.m16n8k16.row.col.f32.f16.f16.f32
|
|
|
+ {$0, $1, $2, $3},
|
|
|
+ {$4, $5, $6, $7},
|
|
|
+ {$8, $9},
|
|
|
+ {$10, $11, $12, $13};
|
|
|
+ """,
|
|
|
+ "=f,=f,=f,=f,r,r,r,r,r,r,f,f,f,f",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ r0 = llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)
|
|
|
+ r1 = llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)
|
|
|
+ r2 = llvm.extractvalue(T.f32(), result, [2], loc=loc, ip=ip)
|
|
|
+ r3 = llvm.extractvalue(T.f32(), result, [3], loc=loc, ip=ip)
|
|
|
+ return Float32(r0), Float32(r1), Float32(r2), Float32(r3)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def bf16_rowsum_m16k16_f32(
|
|
|
+ d0: Float32,
|
|
|
+ d1: Float32,
|
|
|
+ a0: Uint32,
|
|
|
+ a1: Uint32,
|
|
|
+ a2: Uint32,
|
|
|
+ a3: Uint32,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+) -> Tuple[Float32, Float32]:
|
|
|
+ """Row-sum helper matching FlashInfer's m16k16 BF16 rowsum MMA."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.f32(), T.f32()]),
|
|
|
+ [
|
|
|
+ Uint32(a0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a1).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a2).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a3).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(1065369472).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(1065369472).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d0).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d1).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ mma.sync.aligned.m16n8k16.row.col.f32.bf16.bf16.f32
|
|
|
+ {$0, _, $1, _},
|
|
|
+ {$2, $3, $4, $5},
|
|
|
+ {$6, $7},
|
|
|
+ {$0, 0., $1, 0.};
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=f,=f,r,r,r,r,r,r,0,1",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ r0 = llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)
|
|
|
+ r1 = llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)
|
|
|
+ return Float32(r0), Float32(r1)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def bf16_mma_m16n16k16_f32(
|
|
|
+ d0: Float32,
|
|
|
+ d1: Float32,
|
|
|
+ d2: Float32,
|
|
|
+ d3: Float32,
|
|
|
+ d4: Float32,
|
|
|
+ d5: Float32,
|
|
|
+ d6: Float32,
|
|
|
+ d7: Float32,
|
|
|
+ a0: Uint32,
|
|
|
+ a1: Uint32,
|
|
|
+ a2: Uint32,
|
|
|
+ a3: Uint32,
|
|
|
+ b0: Uint32,
|
|
|
+ b1: Uint32,
|
|
|
+ b2: Uint32,
|
|
|
+ b3: Uint32,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+) -> Tuple[Float32, Float32, Float32, Float32, Float32, Float32, Float32, Float32]:
|
|
|
+ """Warp MMA helper matching FlashInfer's `m16n16k16` BF16/BF16->F32 wrapper."""
|
|
|
+ result0 = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
|
|
|
+ [
|
|
|
+ Uint32(a0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a1).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a2).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a3).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(b0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(b1).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d0).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d1).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d2).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d3).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ """
|
|
|
+ mma.sync.aligned.m16n8k16.row.col.f32.bf16.bf16.f32
|
|
|
+ {$0, $1, $2, $3},
|
|
|
+ {$4, $5, $6, $7},
|
|
|
+ {$8, $9},
|
|
|
+ {$10, $11, $12, $13};
|
|
|
+ """,
|
|
|
+ "=f,=f,=f,=f,r,r,r,r,r,r,f,f,f,f",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ result1 = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
|
|
|
+ [
|
|
|
+ Uint32(a0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a1).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a2).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a3).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(b2).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(b3).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d4).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d5).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d6).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d7).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ """
|
|
|
+ mma.sync.aligned.m16n8k16.row.col.f32.bf16.bf16.f32
|
|
|
+ {$0, $1, $2, $3},
|
|
|
+ {$4, $5, $6, $7},
|
|
|
+ {$8, $9},
|
|
|
+ {$10, $11, $12, $13};
|
|
|
+ """,
|
|
|
+ "=f,=f,=f,=f,r,r,r,r,r,r,f,f,f,f",
|
|
|
+ has_side_effects=True,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ r0 = llvm.extractvalue(T.f32(), result0, [0], loc=loc, ip=ip)
|
|
|
+ r1 = llvm.extractvalue(T.f32(), result0, [1], loc=loc, ip=ip)
|
|
|
+ r2 = llvm.extractvalue(T.f32(), result0, [2], loc=loc, ip=ip)
|
|
|
+ r3 = llvm.extractvalue(T.f32(), result0, [3], loc=loc, ip=ip)
|
|
|
+ r4 = llvm.extractvalue(T.f32(), result1, [0], loc=loc, ip=ip)
|
|
|
+ r5 = llvm.extractvalue(T.f32(), result1, [1], loc=loc, ip=ip)
|
|
|
+ r6 = llvm.extractvalue(T.f32(), result1, [2], loc=loc, ip=ip)
|
|
|
+ r7 = llvm.extractvalue(T.f32(), result1, [3], loc=loc, ip=ip)
|
|
|
+ return (
|
|
|
+ Float32(r0),
|
|
|
+ Float32(r1),
|
|
|
+ Float32(r2),
|
|
|
+ Float32(r3),
|
|
|
+ Float32(r4),
|
|
|
+ Float32(r5),
|
|
|
+ Float32(r6),
|
|
|
+ Float32(r7),
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def mxfp8_mma_m16n8k32_f32_e4m3(
|
|
|
+ d0: Float32,
|
|
|
+ d1: Float32,
|
|
|
+ d2: Float32,
|
|
|
+ d3: Float32,
|
|
|
+ a0: Uint32,
|
|
|
+ a1: Uint32,
|
|
|
+ a2: Uint32,
|
|
|
+ a3: Uint32,
|
|
|
+ b0: Uint32,
|
|
|
+ b1: Uint32,
|
|
|
+ sfa: Uint32,
|
|
|
+ sfb: Uint32,
|
|
|
+ bid_a: int = 0,
|
|
|
+ tid_a: int = 0,
|
|
|
+ bid_b: int = 0,
|
|
|
+ tid_b: int = 0,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+) -> Tuple[Float32, Float32, Float32, Float32]:
|
|
|
+ """Warp MMA helper for SM120 MXFP8 block-scaled `m16n8k32` E4M3/E4M3."""
|
|
|
+ i16_ty = cutlass._mlir.ir.IntegerType.get_signless(16)
|
|
|
+ bid_a_i16 = cutlass._mlir.ir.Operation.create(
|
|
|
+ "llvm.mlir.constant",
|
|
|
+ results=[i16_ty],
|
|
|
+ attributes={"value": cutlass._mlir.ir.IntegerAttr.get(i16_ty, int(bid_a))},
|
|
|
+ ).result
|
|
|
+ tid_a_i16 = cutlass._mlir.ir.Operation.create(
|
|
|
+ "llvm.mlir.constant",
|
|
|
+ results=[i16_ty],
|
|
|
+ attributes={"value": cutlass._mlir.ir.IntegerAttr.get(i16_ty, int(tid_a))},
|
|
|
+ ).result
|
|
|
+ bid_b_i16 = cutlass._mlir.ir.Operation.create(
|
|
|
+ "llvm.mlir.constant",
|
|
|
+ results=[i16_ty],
|
|
|
+ attributes={"value": cutlass._mlir.ir.IntegerAttr.get(i16_ty, int(bid_b))},
|
|
|
+ ).result
|
|
|
+ tid_b_i16 = cutlass._mlir.ir.Operation.create(
|
|
|
+ "llvm.mlir.constant",
|
|
|
+ results=[i16_ty],
|
|
|
+ attributes={"value": cutlass._mlir.ir.IntegerAttr.get(i16_ty, int(tid_b))},
|
|
|
+ ).result
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
|
|
|
+ [
|
|
|
+ Uint32(a0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a1).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a2).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a3).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(b0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(b1).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(sfa).ir_value(loc=loc, ip=ip),
|
|
|
+ bid_a_i16,
|
|
|
+ tid_a_i16,
|
|
|
+ Uint32(sfb).ir_value(loc=loc, ip=ip),
|
|
|
+ bid_b_i16,
|
|
|
+ tid_b_i16,
|
|
|
+ Float32(d0).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d1).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d2).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d3).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ """
|
|
|
+ mma.sync.aligned.kind::mxf8f6f4.block_scale.scale_vec::1X.m16n8k32.row.col.f32.e4m3.e4m3.f32.ue8m0
|
|
|
+ {$0, $1, $2, $3},
|
|
|
+ {$4, $5, $6, $7},
|
|
|
+ {$8, $9},
|
|
|
+ {$0, $1, $2, $3},
|
|
|
+ {$10},
|
|
|
+ {$11, $12},
|
|
|
+ {$13},
|
|
|
+ {$14, $15};
|
|
|
+ """,
|
|
|
+ "=f,=f,=f,=f,r,r,r,r,r,r,r,h,h,r,h,h,0,1,2,3",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ r0 = llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)
|
|
|
+ r1 = llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)
|
|
|
+ r2 = llvm.extractvalue(T.f32(), result, [2], loc=loc, ip=ip)
|
|
|
+ r3 = llvm.extractvalue(T.f32(), result, [3], loc=loc, ip=ip)
|
|
|
+ return Float32(r0), Float32(r1), Float32(r2), Float32(r3)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def mxfp8_mma_m16n8k32_f32_e2m1(
|
|
|
+ d0: Float32,
|
|
|
+ d1: Float32,
|
|
|
+ d2: Float32,
|
|
|
+ d3: Float32,
|
|
|
+ a0: Uint32,
|
|
|
+ a1: Uint32,
|
|
|
+ a2: Uint32,
|
|
|
+ a3: Uint32,
|
|
|
+ b0: Uint32,
|
|
|
+ b1: Uint32,
|
|
|
+ sfa: Uint32,
|
|
|
+ sfb: Uint32,
|
|
|
+ bid_a: int = 0,
|
|
|
+ tid_a: int = 0,
|
|
|
+ bid_b: int = 0,
|
|
|
+ tid_b: int = 0,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+) -> Tuple[Float32, Float32, Float32, Float32]:
|
|
|
+ """SM120 block-scaled QMMA with E4M3 A and E2M1 B containers."""
|
|
|
+ asm = f"""
|
|
|
+ mma.sync.aligned.kind::mxf8f6f4.block_scale.scale_vec::1X.m16n8k32.row.col.f32.e4m3.e2m1.f32.ue8m0
|
|
|
+ {{$0, $1, $2, $3}},
|
|
|
+ {{$4, $5, $6, $7}},
|
|
|
+ {{$8, $9}},
|
|
|
+ {{$0, $1, $2, $3}},
|
|
|
+ {{$10}},
|
|
|
+ {{{int(bid_a)}, {int(tid_a)}}},
|
|
|
+ {{$11}},
|
|
|
+ {{{int(bid_b)}, {int(tid_b)}}};
|
|
|
+ """
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
|
|
|
+ [
|
|
|
+ Uint32(a0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a1).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a2).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a3).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(b0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(b1).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(sfa).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(sfb).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d0).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d1).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d2).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d3).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ asm,
|
|
|
+ "=f,=f,=f,=f,r,r,r,r,r,r,r,r,0,1,2,3",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ return tuple(
|
|
|
+ Float32(llvm.extractvalue(T.f32(), result, [i], loc=loc, ip=ip))
|
|
|
+ for i in range(4)
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def mma_m16n8k32_f32_e4m3(
|
|
|
+ d0: Float32,
|
|
|
+ d1: Float32,
|
|
|
+ d2: Float32,
|
|
|
+ d3: Float32,
|
|
|
+ a0: Uint32,
|
|
|
+ a1: Uint32,
|
|
|
+ a2: Uint32,
|
|
|
+ a3: Uint32,
|
|
|
+ b0: Uint32,
|
|
|
+ b1: Uint32,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+) -> Tuple[Float32, Float32, Float32, Float32]:
|
|
|
+ """Plain (non-block-scaled) SM120 FP8 E4M3 warp MMA `m16n8k32`.
|
|
|
+
|
|
|
+ d{0..3} are the accumulator IN-OUT (C on input, D on output)."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
|
|
|
+ [
|
|
|
+ Uint32(a0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a1).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a2).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(a3).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(b0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(b1).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d0).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d1).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d2).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(d3).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ """
|
|
|
+ mma.sync.aligned.m16n8k32.row.col.f32.e4m3.e4m3.f32
|
|
|
+ {$0, $1, $2, $3},
|
|
|
+ {$4, $5, $6, $7},
|
|
|
+ {$8, $9},
|
|
|
+ {$0, $1, $2, $3};
|
|
|
+ """,
|
|
|
+ "=f,=f,=f,=f,r,r,r,r,r,r,0,1,2,3",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ r0 = llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)
|
|
|
+ r1 = llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)
|
|
|
+ r2 = llvm.extractvalue(T.f32(), result, [2], loc=loc, ip=ip)
|
|
|
+ r3 = llvm.extractvalue(T.f32(), result, [3], loc=loc, ip=ip)
|
|
|
+ return Float32(r0), Float32(r1), Float32(r2), Float32(r3)
|
|
|
+
|
|
|
+
|
|
|
+# ``mma_m16n8k16_f32_bf16`` was proven during the SM120 sparse-MLA port to be
|
|
|
+# bit-identical (same signature, same emitted
|
|
|
+# ``mma.sync.aligned.m16n8k16.row.col.f32.bf16.bf16.f32``, same constraint
|
|
|
+# string and operand grouping) to the pre-existing ``bf16_mma_m16n8k16_f32``
|
|
|
+# above. Expose it under the port's name as a thin alias so the sparse-MLA
|
|
|
+# kernels can import it without duplicating the inline asm.
|
|
|
+mma_m16n8k16_f32_bf16 = bf16_mma_m16n8k16_f32
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def byte_perm(a: Uint32, b: Uint32, selector: Int32, *, loc=None, ip=None) -> Uint32:
|
|
|
+ """PTX byte permutation helper."""
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [
|
|
|
+ Uint32(a).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(b).ir_value(loc=loc, ip=ip),
|
|
|
+ Int32(selector).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ "prmt.b32 $0, $1, $2, $3;",
|
|
|
+ "=r,r,r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def frag_layout_swizzle_16b_to_8b(x: Uint32) -> Uint32:
|
|
|
+ tmp = cute.arch.shuffle_sync_bfly(x, offset=1)
|
|
|
+ x = byte_perm(x, tmp, Int32(0x5410 if cute.arch.lane_idx() & 0x1 == 0 else 0x3276))
|
|
|
+ tmp = cute.arch.shuffle_sync_bfly(x, offset=2)
|
|
|
+ x = byte_perm(x, tmp, Int32(0x5410 if cute.arch.lane_idx() & 0x2 == 0 else 0x3276))
|
|
|
+ return x
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def frag_layout_swizzle_16b_to_8b_trans(x: Uint32) -> Uint32:
|
|
|
+ tmp = cute.arch.shuffle_sync_bfly(x, offset=4)
|
|
|
+ x = byte_perm(x, tmp, Int32(0x6420 if cute.arch.lane_idx() & 0x4 == 0 else 0x3175))
|
|
|
+ tmp = cute.arch.shuffle_sync_bfly(x, offset=8)
|
|
|
+ x = byte_perm(x, tmp, Int32(0x5410 if cute.arch.lane_idx() & 0x8 == 0 else 0x3276))
|
|
|
+ tmp = cute.arch.shuffle_sync_bfly(x, offset=16)
|
|
|
+ x = byte_perm(x, tmp, Int32(0x5410 if cute.arch.lane_idx() & 0x10 == 0 else 0x3276))
|
|
|
+ return x
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def bfloat2_habs2(x: Uint32, *, loc=None, ip=None) -> Uint32:
|
|
|
+ """BFloat16x2 absolute value - clears sign bits of both bf16 values."""
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [Uint32(x).ir_value(loc=loc, ip=ip)],
|
|
|
+ "and.b32 $0, $1, 0x7FFF7FFF;",
|
|
|
+ "=r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def bfloat2_hmax2(a: Uint32, b: Uint32, *, loc=None, ip=None) -> Uint32:
|
|
|
+ """BFloat16x2 max - element-wise max of 2 bf16 pairs."""
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [Uint32(a).ir_value(loc=loc, ip=ip), Uint32(b).ir_value(loc=loc, ip=ip)],
|
|
|
+ "max.bf16x2 $0, $1, $2;",
|
|
|
+ "=r,r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def bfloat2_hmax_to_f32(x: Uint32, *, loc=None, ip=None) -> Float32:
|
|
|
+ """Extract max of 2 bf16 values in bfloat2 as float32."""
|
|
|
+ return Float32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.f32(),
|
|
|
+ [Uint32(x).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b32 lo, hi;
|
|
|
+ .reg .f32 f0, f1;
|
|
|
+ and.b32 lo, $1, 0xFFFF;
|
|
|
+ shr.b32 hi, $1, 16;
|
|
|
+ shl.b32 lo, lo, 16;
|
|
|
+ shl.b32 hi, hi, 16;
|
|
|
+ mov.b32 f0, lo;
|
|
|
+ mov.b32 f1, hi;
|
|
|
+ max.f32 $0, f0, f1;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=f,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def bfloat2_to_float2_scaled(
|
|
|
+ bf2: Uint32, scale: Float32, *, loc=None, ip=None
|
|
|
+) -> Tuple[Float32, Float32]:
|
|
|
+ """Convert bfloat16x2 to float2 AND multiply by scale."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.f32(), T.f32()]),
|
|
|
+ [Uint32(bf2).ir_value(loc=loc, ip=ip), Float32(scale).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b32 lo, hi;
|
|
|
+ .reg .f32 f0, f1;
|
|
|
+ and.b32 lo, $2, 0xFFFF;
|
|
|
+ shr.b32 hi, $2, 16;
|
|
|
+ shl.b32 lo, lo, 16;
|
|
|
+ shl.b32 hi, hi, 16;
|
|
|
+ mov.b32 f0, lo;
|
|
|
+ mov.b32 f1, hi;
|
|
|
+ mul.f32 $0, f0, $3;
|
|
|
+ mul.f32 $1, f1, $3;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=f,=f,r,f",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+
|
|
|
+ f0 = llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)
|
|
|
+ f1 = llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)
|
|
|
+
|
|
|
+ return Float32(f0), Float32(f1)
|
|
|
+
|
|
|
+
|
|
|
+# =============================================================================
|
|
|
+# FP8 E4M3 Intrinsics
|
|
|
+# =============================================================================
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def cvt_f32_to_e4m3(a: Float32, *, loc=None, ip=None) -> Uint32:
|
|
|
+ """Convert float32 to E4M3 using native cvt.rn.satfinite.e4m3x2.f32."""
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [Float32(a).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b16 fp8_pair;
|
|
|
+ .reg .f32 zero;
|
|
|
+ mov.f32 zero, 0f00000000;
|
|
|
+ cvt.rn.satfinite.e4m3x2.f32 fp8_pair, zero, $1;
|
|
|
+ cvt.u32.u16 $0, fp8_pair;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=r,f",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def cvt_f32x4_to_e4m3x4(
|
|
|
+ v0: Float32,
|
|
|
+ v1: Float32,
|
|
|
+ v2: Float32,
|
|
|
+ v3: Float32,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+) -> Uint32:
|
|
|
+ """Convert 4 float32 values to 4 packed E4M3 bytes (v0 in the low byte)."""
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [
|
|
|
+ Float32(v0).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(v1).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(v2).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(v3).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b16 lo, hi;
|
|
|
+ cvt.rn.satfinite.e4m3x2.f32 lo, $2, $1;
|
|
|
+ cvt.rn.satfinite.e4m3x2.f32 hi, $4, $3;
|
|
|
+ mov.b32 $0, {lo, hi};
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=r,f,f,f,f",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def nvfp4_scale_from_amax(block_amax: Float32, global_scale: Float32, *, loc=None, ip=None) -> Float32:
|
|
|
+ """Compute block_amax * reciprocal_global_scale / 6 with CUDA tensor-scalar semantics."""
|
|
|
+ return Float32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.f32(),
|
|
|
+ [
|
|
|
+ Float32(block_amax).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(global_scale).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .f64 amax_d, gs_d, q_d, six_d;
|
|
|
+ cvt.f64.f32 amax_d, $1;
|
|
|
+ cvt.f64.f32 gs_d, $2;
|
|
|
+ mov.f64 six_d, 0d4018000000000000;
|
|
|
+ mul.rn.f64 q_d, amax_d, gs_d;
|
|
|
+ div.rn.f64 q_d, q_d, six_d;
|
|
|
+ cvt.rn.f32.f64 $0, q_d;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=f,f,f",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def fp8_e4m3_to_f32(fp8_val: Uint32, *, loc=None, ip=None) -> Float32:
|
|
|
+ """Convert FP8 E4M3 to float32."""
|
|
|
+ return Float32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.f32(),
|
|
|
+ [Uint32(fp8_val).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .pred p_zero, p_neg;
|
|
|
+ .reg .u32 sign_u, exp_u, mant_u;
|
|
|
+ .reg .s32 exp_s;
|
|
|
+ .reg .f32 exp_f, mant_f, fp8_float, fp8_neg;
|
|
|
+
|
|
|
+ setp.eq.u32 p_zero, $1, 0;
|
|
|
+ and.b32 sign_u, $1, 0x80;
|
|
|
+ and.b32 mant_u, $1, 7;
|
|
|
+ shr.b32 exp_u, $1, 3;
|
|
|
+ and.b32 exp_u, exp_u, 15;
|
|
|
+ sub.s32 exp_s, exp_u, 7;
|
|
|
+ cvt.rn.f32.s32 exp_f, exp_s;
|
|
|
+ ex2.approx.f32 exp_f, exp_f;
|
|
|
+ cvt.rn.f32.u32 mant_f, mant_u;
|
|
|
+ fma.rn.f32 mant_f, mant_f, 0f3E000000, 0f3F800000;
|
|
|
+ mul.f32 fp8_float, exp_f, mant_f;
|
|
|
+ neg.f32 fp8_neg, fp8_float;
|
|
|
+ setp.ne.u32 p_neg, sign_u, 0;
|
|
|
+ selp.f32 fp8_float, fp8_neg, fp8_float, p_neg;
|
|
|
+ selp.f32 $0, 0f00000000, fp8_float, p_zero;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=f,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def fp8_e4m3_to_f32_and_rcp(fp8_val: Uint32, *, loc=None, ip=None) -> Float32:
|
|
|
+ """Convert FP8 E4M3 to float32 AND compute reciprocal."""
|
|
|
+ return Float32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.f32(),
|
|
|
+ [Uint32(fp8_val).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .pred p_zero;
|
|
|
+ .reg .u32 exp_u, mant_u;
|
|
|
+ .reg .s32 exp_s;
|
|
|
+ .reg .f32 exp_f, mant_f, fp8_float, result;
|
|
|
+
|
|
|
+ setp.eq.u32 p_zero, $1, 0;
|
|
|
+ and.b32 mant_u, $1, 7;
|
|
|
+ shr.b32 exp_u, $1, 3;
|
|
|
+ and.b32 exp_u, exp_u, 15;
|
|
|
+ sub.s32 exp_s, exp_u, 7;
|
|
|
+ cvt.rn.f32.s32 exp_f, exp_s;
|
|
|
+ ex2.approx.f32 exp_f, exp_f;
|
|
|
+ cvt.rn.f32.u32 mant_f, mant_u;
|
|
|
+ fma.rn.f32 mant_f, mant_f, 0f3E000000, 0f3F800000;
|
|
|
+ mul.f32 fp8_float, exp_f, mant_f;
|
|
|
+ rcp.approx.ftz.f32 result, fp8_float;
|
|
|
+ selp.f32 $0, 0f00000000, result, p_zero;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=f,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+# =============================================================================
|
|
|
+# E4M3 -> F16/F32 Native Conversion Intrinsics
|
|
|
+# =============================================================================
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def cvt_e4m3x2_to_f16x2_pair(
|
|
|
+ packed_u32: Uint32, *, loc=None, ip=None
|
|
|
+) -> Tuple[Uint32, Uint32]:
|
|
|
+ """Decode 4 e4m3 bytes (packed u32) into 2 x f16x2 pairs."""
|
|
|
+ res = llvm.inline_asm(
|
|
|
+ ir.Type.parse("!llvm.struct<(i32, i32)>"),
|
|
|
+ [Uint32(packed_u32).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b16 b16_01, b16_23;
|
|
|
+ .reg .b32 hi32;
|
|
|
+ cvt.u16.u32 b16_01, $2;
|
|
|
+ shr.b32 hi32, $2, 16;
|
|
|
+ cvt.u16.u32 b16_23, hi32;
|
|
|
+ cvt.rn.f16x2.e4m3x2 $0, b16_01;
|
|
|
+ cvt.rn.f16x2.e4m3x2 $1, b16_23;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=r,=r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ return (
|
|
|
+ Uint32(llvm.extractvalue(T.i32(), res, [0], loc=loc, ip=ip)),
|
|
|
+ Uint32(llvm.extractvalue(T.i32(), res, [1], loc=loc, ip=ip)),
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def f16x2_to_f32x2(
|
|
|
+ packed_h2: Uint32, *, loc=None, ip=None
|
|
|
+) -> Tuple[Float32, Float32]:
|
|
|
+ """Unpack f16x2 (u32) to two f32 values."""
|
|
|
+ res = llvm.inline_asm(
|
|
|
+ ir.Type.parse("!llvm.struct<(f32, f32)>"),
|
|
|
+ [Uint32(packed_h2).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b16 lo, hi;
|
|
|
+ mov.b32 {lo, hi}, $2;
|
|
|
+ cvt.f32.f16 $0, lo;
|
|
|
+ cvt.f32.f16 $1, hi;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=f,=f,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ return (
|
|
|
+ Float32(llvm.extractvalue(T.f32(), res, [0], loc=loc, ip=ip)),
|
|
|
+ Float32(llvm.extractvalue(T.f32(), res, [1], loc=loc, ip=ip)),
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def cvt_e4m3x4_to_f32x4(
|
|
|
+ packed_u32: Uint32,
|
|
|
+) -> Tuple[Float32, Float32, Float32, Float32]:
|
|
|
+ """Decode 4 e4m3 bytes (packed u32) to 4 x f32 via hw-native e4m3->f16->f32."""
|
|
|
+ h01, h23 = cvt_e4m3x2_to_f16x2_pair(packed_u32)
|
|
|
+ f0, f1 = f16x2_to_f32x2(h01)
|
|
|
+ f2, f3 = f16x2_to_f32x2(h23)
|
|
|
+ return f0, f1, f2, f3
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def cvt_e4m3_to_f32_via_f16(
|
|
|
+ fp8_val: Uint32, *, loc=None, ip=None
|
|
|
+) -> Float32:
|
|
|
+ """Convert single E4M3 byte to f32 via hw-native cvt.rn.f16x2.e4m3x2."""
|
|
|
+ return Float32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.f32(),
|
|
|
+ [Uint32(fp8_val).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b16 fp8_pair;
|
|
|
+ .reg .b32 h2;
|
|
|
+ .reg .b16 lo, hi;
|
|
|
+ cvt.u16.u32 fp8_pair, $1;
|
|
|
+ cvt.rn.f16x2.e4m3x2 h2, fp8_pair;
|
|
|
+ mov.b32 {lo, hi}, h2;
|
|
|
+ cvt.f32.f16 $0, lo;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=f,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def cvt_w4a16_packed_e4m3_scale_to_f32(
|
|
|
+ packed_byte: Uint32, *, loc=None, ip=None
|
|
|
+) -> Float32:
|
|
|
+ """Scalar mirror of packed_dequant_e4m3x4_to_bfloat2x2 for W4A16 scales."""
|
|
|
+ return Float32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.f32(),
|
|
|
+ [Uint32(packed_byte).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b32 bits, tmp;
|
|
|
+ .reg .b16 bf;
|
|
|
+ and.b32 bits, $1, 0x80;
|
|
|
+ shl.b32 bits, bits, 7;
|
|
|
+ and.b32 tmp, $1, 0x7f;
|
|
|
+ shl.b32 tmp, tmp, 4;
|
|
|
+ or.b32 bits, bits, tmp;
|
|
|
+ cvt.u16.u32 bf, bits;
|
|
|
+ cvt.f32.bf16 $0, bf;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=f,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def dequant_kv_e4m3_pair_to_bf16x2(
|
|
|
+ p0: Uint16, p1: Uint16, scale_f: Float32, *, loc=None, ip=None
|
|
|
+) -> Tuple[Uint32, Uint32]:
|
|
|
+ """FP8 -> BF16 K dequant for the BF16-QK m16n8k16 B operand.
|
|
|
+
|
|
|
+ Byte-for-byte mirror of FlashInfer's sparse_mla prefill BF16-QK inline
|
|
|
+ sequence (prefill_kernel.cuh:240-251): two ``cvt.rn.f16x2.e4m3x2`` decode
|
|
|
+ the two e4m3 byte-pairs ``p0``/``p1`` (each a u16 holding two consecutive
|
|
|
+ e4m3 K-dims), multiply the four resulting f16 lanes by the per-(token,blk)
|
|
|
+ UE8M0 ``scale_f`` in f32, then two ``cvt.rn.bf16x2.f32`` pack them back to
|
|
|
+ the bf16x2 ``b0``/``b1`` MMA operands. NON-saturating cvt (matches the
|
|
|
+ reference; the K magnitudes are bounded by the e4m3 range so satfinite is a
|
|
|
+ no-op, and the ref emits plain ``cvt.rn.bf16x2.f32``)."""
|
|
|
+ res = llvm.inline_asm(
|
|
|
+ ir.Type.parse("!llvm.struct<(i32, i32)>"),
|
|
|
+ [
|
|
|
+ Uint16(p0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint16(p1).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(scale_f).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b32 h2_0, h2_1;
|
|
|
+ .reg .b16 l0, h0, l1, h1;
|
|
|
+ .reg .f32 fk0, fk1, fk2, fk3;
|
|
|
+ cvt.rn.f16x2.e4m3x2 h2_0, $2;
|
|
|
+ cvt.rn.f16x2.e4m3x2 h2_1, $3;
|
|
|
+ mov.b32 {l0, h0}, h2_0;
|
|
|
+ mov.b32 {l1, h1}, h2_1;
|
|
|
+ cvt.f32.f16 fk0, l0;
|
|
|
+ cvt.f32.f16 fk1, h0;
|
|
|
+ cvt.f32.f16 fk2, l1;
|
|
|
+ cvt.f32.f16 fk3, h1;
|
|
|
+ mul.f32 fk0, fk0, $4;
|
|
|
+ mul.f32 fk1, fk1, $4;
|
|
|
+ mul.f32 fk2, fk2, $4;
|
|
|
+ mul.f32 fk3, fk3, $4;
|
|
|
+ cvt.rn.bf16x2.f32 $0, fk1, fk0;
|
|
|
+ cvt.rn.bf16x2.f32 $1, fk3, fk2;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=r,=r,h,h,f",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ return (
|
|
|
+ Uint32(llvm.extractvalue(T.i32(), res, [0], loc=loc, ip=ip)),
|
|
|
+ Uint32(llvm.extractvalue(T.i32(), res, [1], loc=loc, ip=ip)),
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def cvt_e8m0_to_f32(e8m0_val: Uint32, *, loc=None, ip=None) -> Float32:
|
|
|
+ """Convert a single E8M0 scale byte to its true f32 value 2**(byte-127).
|
|
|
+
|
|
|
+ Byte 0 maps to 0.0. Unlike packed_dequant_e8m0x4_* (which fold a 2**7 bias
|
|
|
+ into the MMA input), this returns the unbiased scale so callers can apply it
|
|
|
+ directly in an f32 accumulator.
|
|
|
+ """
|
|
|
+ return Float32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.f32(),
|
|
|
+ [Uint32(e8m0_val).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .pred p0;
|
|
|
+ .reg .u32 b0;
|
|
|
+ .reg .s32 e0;
|
|
|
+ .reg .f32 ef0;
|
|
|
+ and.b32 b0, $1, 0x000000ff;
|
|
|
+ setp.eq.u32 p0, b0, 0;
|
|
|
+ cvt.s32.u32 e0, b0;
|
|
|
+ sub.s32 e0, e0, 127;
|
|
|
+ cvt.rn.f32.s32 ef0, e0;
|
|
|
+ ex2.approx.f32 $0, ef0;
|
|
|
+ selp.f32 $0, 0f00000000, $0, p0;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=f,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def cvt_e8m0x4_to_f32x4(
|
|
|
+ packed: Uint32, *, loc=None, ip=None
|
|
|
+) -> Tuple[Float32, Float32, Float32, Float32]:
|
|
|
+ """Decode 4 E8M0 scale bytes (packed u32) to 4 x true f32 (2**(byte-127))."""
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.f32(), T.f32(), T.f32(), T.f32()]),
|
|
|
+ [Uint32(packed).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .pred p0, p1, p2, p3;
|
|
|
+ .reg .u32 b0, b1, b2, b3;
|
|
|
+ .reg .s32 e0, e1, e2, e3;
|
|
|
+ .reg .f32 ef0, ef1, ef2, ef3;
|
|
|
+ and.b32 b0, $4, 0x000000ff;
|
|
|
+ shr.u32 b1, $4, 8;
|
|
|
+ and.b32 b1, b1, 0x000000ff;
|
|
|
+ shr.u32 b2, $4, 16;
|
|
|
+ and.b32 b2, b2, 0x000000ff;
|
|
|
+ shr.u32 b3, $4, 24;
|
|
|
+ setp.eq.u32 p0, b0, 0;
|
|
|
+ setp.eq.u32 p1, b1, 0;
|
|
|
+ setp.eq.u32 p2, b2, 0;
|
|
|
+ setp.eq.u32 p3, b3, 0;
|
|
|
+ cvt.s32.u32 e0, b0;
|
|
|
+ cvt.s32.u32 e1, b1;
|
|
|
+ cvt.s32.u32 e2, b2;
|
|
|
+ cvt.s32.u32 e3, b3;
|
|
|
+ sub.s32 e0, e0, 127;
|
|
|
+ sub.s32 e1, e1, 127;
|
|
|
+ sub.s32 e2, e2, 127;
|
|
|
+ sub.s32 e3, e3, 127;
|
|
|
+ cvt.rn.f32.s32 ef0, e0;
|
|
|
+ cvt.rn.f32.s32 ef1, e1;
|
|
|
+ cvt.rn.f32.s32 ef2, e2;
|
|
|
+ cvt.rn.f32.s32 ef3, e3;
|
|
|
+ ex2.approx.f32 $0, ef0;
|
|
|
+ ex2.approx.f32 $1, ef1;
|
|
|
+ ex2.approx.f32 $2, ef2;
|
|
|
+ ex2.approx.f32 $3, ef3;
|
|
|
+ selp.f32 $0, 0f00000000, $0, p0;
|
|
|
+ selp.f32 $1, 0f00000000, $1, p1;
|
|
|
+ selp.f32 $2, 0f00000000, $2, p2;
|
|
|
+ selp.f32 $3, 0f00000000, $3, p3;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=f,=f,=f,=f,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ return (
|
|
|
+ Float32(llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)),
|
|
|
+ Float32(llvm.extractvalue(T.f32(), result, [1], loc=loc, ip=ip)),
|
|
|
+ Float32(llvm.extractvalue(T.f32(), result, [2], loc=loc, ip=ip)),
|
|
|
+ Float32(llvm.extractvalue(T.f32(), result, [3], loc=loc, ip=ip)),
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+# =============================================================================
|
|
|
+# FP4 (E2M1) Decode Intrinsics
|
|
|
+# =============================================================================
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def fp4_decode_4bytes(
|
|
|
+ packed_u32: Uint32, *, loc=None, ip=None
|
|
|
+) -> Tuple[Uint32, Uint32, Uint32, Uint32]:
|
|
|
+ """Decode 4 FP4 bytes (packed u32) into 4 x f16x2 pairs."""
|
|
|
+ res = llvm.inline_asm(
|
|
|
+ ir.Type.parse("!llvm.struct<(i32, i32, i32, i32)>"),
|
|
|
+ [Uint32(packed_u32).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b8 byte0, byte1, byte2, byte3;
|
|
|
+ mov.b32 {byte0, byte1, byte2, byte3}, $4;
|
|
|
+ cvt.rn.f16x2.e2m1x2 $0, byte0;
|
|
|
+ cvt.rn.f16x2.e2m1x2 $1, byte1;
|
|
|
+ cvt.rn.f16x2.e2m1x2 $2, byte2;
|
|
|
+ cvt.rn.f16x2.e2m1x2 $3, byte3;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=r,=r,=r,=r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ return (
|
|
|
+ Uint32(llvm.extractvalue(T.i32(), res, [0], loc=loc, ip=ip)),
|
|
|
+ Uint32(llvm.extractvalue(T.i32(), res, [1], loc=loc, ip=ip)),
|
|
|
+ Uint32(llvm.extractvalue(T.i32(), res, [2], loc=loc, ip=ip)),
|
|
|
+ Uint32(llvm.extractvalue(T.i32(), res, [3], loc=loc, ip=ip)),
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def fp4_decode_2(byte_val: Uint32, *, loc=None, ip=None) -> Uint32:
|
|
|
+ """Decode 1 FP4 byte into 1 f16x2 pair."""
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [Uint32(byte_val).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b8 b0;
|
|
|
+ cvt.u8.u32 b0, $1;
|
|
|
+ cvt.rn.f16x2.e2m1x2 $0, b0;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def e2m1x8_to_qmma_e2m1x8(
|
|
|
+ packed_u32: Uint32, *, loc=None, ip=None
|
|
|
+) -> Tuple[Uint32, Uint32]:
|
|
|
+ """Spread eight packed FP4 nibbles into QMMA E2M1 byte containers.
|
|
|
+
|
|
|
+ The SM120 f8f6f4 instruction expects one E2M1 value per byte with the
|
|
|
+ original nibble in bits 5:2. This six-instruction spread is cheaper than
|
|
|
+ losslessly relabeling every value as E4M3 when no residual multiplier is
|
|
|
+ required.
|
|
|
+ """
|
|
|
+ res = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.i32(), T.i32()]),
|
|
|
+ [Uint32(packed_u32).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b32 s, t;
|
|
|
+ shl.b32 s, $2, 2;
|
|
|
+ shr.u32 t, $2, 2;
|
|
|
+ prmt.b32 $0, s, t, 0x5140;
|
|
|
+ prmt.b32 $1, s, t, 0x7362;
|
|
|
+ and.b32 $0, $0, 0x3C3C3C3C;
|
|
|
+ and.b32 $1, $1, 0x3C3C3C3C;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=r,=r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ lo = llvm.extractvalue(T.i32(), res, [0], loc=loc, ip=ip)
|
|
|
+ hi = llvm.extractvalue(T.i32(), res, [1], loc=loc, ip=ip)
|
|
|
+ return Uint32(lo), Uint32(hi)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def e2m1x8_to_e4m3x8(
|
|
|
+ packed_u32: Uint32, *, loc=None, ip=None
|
|
|
+) -> Tuple[Uint32, Uint32]:
|
|
|
+ """Expand 8 packed E2M1 nibbles into 8 E4M3 bytes, losslessly.
|
|
|
+
|
|
|
+ Every E2M1 value is exactly representable in E4M3, so this is a pure
|
|
|
+ relabeling done with two prmt magnitude-LUT lookups plus a sign pass.
|
|
|
+ Nibble i of the input becomes byte i of the (lo, hi) output pair.
|
|
|
+ """
|
|
|
+ res = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.i32(), T.i32()]),
|
|
|
+ [Uint32(packed_u32).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b32 clo, chi, cslo, cshi, mlo, mhi, slo, shi;
|
|
|
+ .reg .b32 tbl_a, tbl_b, tbl_s, zero;
|
|
|
+ // E4M3 encodings of {0, .5, 1, 1.5, 2, 3, 4, 6}.
|
|
|
+ mov.b32 tbl_a, 0x3C383000;
|
|
|
+ mov.b32 tbl_b, 0x4C484440;
|
|
|
+ mov.b32 tbl_s, 0x00008000;
|
|
|
+ mov.b32 zero, 0;
|
|
|
+ // Magnitude codes (low 3 bits of each nibble) as prmt selectors.
|
|
|
+ and.b32 clo, $2, 0x00007777;
|
|
|
+ shr.u32 chi, $2, 16;
|
|
|
+ and.b32 chi, chi, 0x00007777;
|
|
|
+ prmt.b32 mlo, tbl_a, tbl_b, clo;
|
|
|
+ prmt.b32 mhi, tbl_a, tbl_b, chi;
|
|
|
+ // Sign bit (nibble bit 3) -> byte bit 7 via a 2-entry LUT.
|
|
|
+ shr.u32 cslo, $2, 3;
|
|
|
+ and.b32 cslo, cslo, 0x00001111;
|
|
|
+ shr.u32 cshi, $2, 19;
|
|
|
+ and.b32 cshi, cshi, 0x00001111;
|
|
|
+ prmt.b32 slo, tbl_s, zero, cslo;
|
|
|
+ prmt.b32 shi, tbl_s, zero, cshi;
|
|
|
+ or.b32 $0, mlo, slo;
|
|
|
+ or.b32 $1, mhi, shi;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=r,=r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ lo = llvm.extractvalue(T.i32(), res, [0], loc=loc, ip=ip)
|
|
|
+ hi = llvm.extractvalue(T.i32(), res, [1], loc=loc, ip=ip)
|
|
|
+ return Uint32(lo), Uint32(hi)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def e2m1x8_mul_residual_to_e4m3x8(
|
|
|
+ packed_u32: Uint32, residual_h2: Uint32, *, loc=None, ip=None
|
|
|
+) -> Tuple[Uint32, Uint32]:
|
|
|
+ """Expand 8 E2M1 nibbles to E4M3 bytes with a residual multiplier.
|
|
|
+
|
|
|
+ Decodes to f16 pairs, multiplies by ``residual_h2`` (the same residual
|
|
|
+ broadcast to both f16 lanes), and rounds to E4M3. Used by the w4a8
|
|
|
+ NVFP4 path where the per-K/16 e4m3 scale is decomposed into a shared
|
|
|
+ UE8M0 hardware exponent and this in-register residual.
|
|
|
+ """
|
|
|
+ res = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.i32(), T.i32()]),
|
|
|
+ [
|
|
|
+ Uint32(packed_u32).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(residual_h2).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b8 b0, b1, b2, b3;
|
|
|
+ .reg .b32 h0, h1, h2, h3;
|
|
|
+ .reg .b16 e0, e1, e2, e3;
|
|
|
+ mov.b32 {b0, b1, b2, b3}, $2;
|
|
|
+ cvt.rn.f16x2.e2m1x2 h0, b0;
|
|
|
+ cvt.rn.f16x2.e2m1x2 h1, b1;
|
|
|
+ cvt.rn.f16x2.e2m1x2 h2, b2;
|
|
|
+ cvt.rn.f16x2.e2m1x2 h3, b3;
|
|
|
+ mul.f16x2 h0, h0, $3;
|
|
|
+ mul.f16x2 h1, h1, $3;
|
|
|
+ mul.f16x2 h2, h2, $3;
|
|
|
+ mul.f16x2 h3, h3, $3;
|
|
|
+ cvt.rn.satfinite.e4m3x2.f16x2 e0, h0;
|
|
|
+ cvt.rn.satfinite.e4m3x2.f16x2 e1, h1;
|
|
|
+ cvt.rn.satfinite.e4m3x2.f16x2 e2, h2;
|
|
|
+ cvt.rn.satfinite.e4m3x2.f16x2 e3, h3;
|
|
|
+ mov.b32 $0, {e0, e1};
|
|
|
+ mov.b32 $1, {e2, e3};
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=r,=r,r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ lo = llvm.extractvalue(T.i32(), res, [0], loc=loc, ip=ip)
|
|
|
+ hi = llvm.extractvalue(T.i32(), res, [1], loc=loc, ip=ip)
|
|
|
+ return Uint32(lo), Uint32(hi)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def cvt_fp32x2_to_e2m1x2(v0: Float32, v1: Float32, *, loc=None, ip=None) -> Uint32:
|
|
|
+ """Convert 2 x f32 to 1 x FP4 (E2M1) byte."""
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [
|
|
|
+ Float32(v0).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(v1).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b8 b;
|
|
|
+ cvt.rn.satfinite.e2m1x2.f32 b, $2, $1;
|
|
|
+ cvt.u32.u8 $0, b;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=r,f,f",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def quant_dequant_2(
|
|
|
+ v0: Float32, v1: Float32, sf_f32: Float32, eff_scale: Float32
|
|
|
+) -> Tuple[Float32, Float32]:
|
|
|
+ """Quantize-dequantize pair roundtrip through FP4."""
|
|
|
+ inv_scale = Float32(1.0) / eff_scale
|
|
|
+ fp4_byte = cvt_fp32x2_to_e2m1x2(v0 * inv_scale, v1 * inv_scale)
|
|
|
+ h2 = fp4_decode_2(fp4_byte)
|
|
|
+ f0, f1 = f16x2_to_f32x2(h2)
|
|
|
+ return f0 * sf_f32, f1 * sf_f32
|
|
|
+
|
|
|
+
|
|
|
+# =============================================================================
|
|
|
+# Half2 FMA Dot Product Intrinsics
|
|
|
+# =============================================================================
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def hfma2_4(
|
|
|
+ w0: Uint32, x0: Uint32,
|
|
|
+ w1: Uint32, x1: Uint32,
|
|
|
+ w2: Uint32, x2: Uint32,
|
|
|
+ w3: Uint32, x3: Uint32,
|
|
|
+ *, loc=None, ip=None
|
|
|
+) -> Tuple[Float32, Float32]:
|
|
|
+ """4-element half2 FMA dot product returning (lo, hi) f32."""
|
|
|
+ res = llvm.inline_asm(
|
|
|
+ ir.Type.parse("!llvm.struct<(f32, f32)>"),
|
|
|
+ [
|
|
|
+ Uint32(w0).ir_value(loc=loc, ip=ip), Uint32(x0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(w1).ir_value(loc=loc, ip=ip), Uint32(x1).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(w2).ir_value(loc=loc, ip=ip), Uint32(x2).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(w3).ir_value(loc=loc, ip=ip), Uint32(x3).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .f16x2 acc;
|
|
|
+ .reg .b16 lo, hi;
|
|
|
+ mov.b32 acc, 0;
|
|
|
+ fma.rn.f16x2 acc, $2, $3, acc;
|
|
|
+ fma.rn.f16x2 acc, $4, $5, acc;
|
|
|
+ fma.rn.f16x2 acc, $6, $7, acc;
|
|
|
+ fma.rn.f16x2 acc, $8, $9, acc;
|
|
|
+ mov.b32 {lo, hi}, acc;
|
|
|
+ cvt.f32.f16 $0, lo;
|
|
|
+ cvt.f32.f16 $1, hi;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=f,=f,r,r,r,r,r,r,r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ return (
|
|
|
+ Float32(llvm.extractvalue(T.f32(), res, [0], loc=loc, ip=ip)),
|
|
|
+ Float32(llvm.extractvalue(T.f32(), res, [1], loc=loc, ip=ip)),
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def hfma2_4_sum(
|
|
|
+ w0: Uint32, x0: Uint32,
|
|
|
+ w1: Uint32, x1: Uint32,
|
|
|
+ w2: Uint32, x2: Uint32,
|
|
|
+ w3: Uint32, x3: Uint32,
|
|
|
+ *, loc=None, ip=None
|
|
|
+) -> Float32:
|
|
|
+ """4-element half2 FMA dot product returning lane sum."""
|
|
|
+ return Float32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.f32(),
|
|
|
+ [
|
|
|
+ Uint32(w0).ir_value(loc=loc, ip=ip), Uint32(x0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(w1).ir_value(loc=loc, ip=ip), Uint32(x1).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(w2).ir_value(loc=loc, ip=ip), Uint32(x2).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(w3).ir_value(loc=loc, ip=ip), Uint32(x3).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .f16x2 acc;
|
|
|
+ .reg .b16 lo, hi;
|
|
|
+ .reg .f32 flo, fhi;
|
|
|
+ mov.b32 acc, 0;
|
|
|
+ fma.rn.f16x2 acc, $1, $2, acc;
|
|
|
+ fma.rn.f16x2 acc, $3, $4, acc;
|
|
|
+ fma.rn.f16x2 acc, $5, $6, acc;
|
|
|
+ fma.rn.f16x2 acc, $7, $8, acc;
|
|
|
+ mov.b32 {lo, hi}, acc;
|
|
|
+ cvt.f32.f16 flo, lo;
|
|
|
+ cvt.f32.f16 fhi, hi;
|
|
|
+ add.f32 $0, flo, fhi;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=f,r,r,r,r,r,r,r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def hfma2_8(
|
|
|
+ w0: Uint32, x0: Uint32,
|
|
|
+ w1: Uint32, x1: Uint32,
|
|
|
+ w2: Uint32, x2: Uint32,
|
|
|
+ w3: Uint32, x3: Uint32,
|
|
|
+ w4: Uint32, x4: Uint32,
|
|
|
+ w5: Uint32, x5: Uint32,
|
|
|
+ w6: Uint32, x6: Uint32,
|
|
|
+ w7: Uint32, x7: Uint32,
|
|
|
+ *, loc=None, ip=None
|
|
|
+) -> Tuple[Float32, Float32]:
|
|
|
+ """8-element half2 FMA dot product returning (lo, hi) f32."""
|
|
|
+ res = llvm.inline_asm(
|
|
|
+ ir.Type.parse("!llvm.struct<(f32, f32)>"),
|
|
|
+ [
|
|
|
+ Uint32(w0).ir_value(loc=loc, ip=ip), Uint32(x0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(w1).ir_value(loc=loc, ip=ip), Uint32(x1).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(w2).ir_value(loc=loc, ip=ip), Uint32(x2).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(w3).ir_value(loc=loc, ip=ip), Uint32(x3).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(w4).ir_value(loc=loc, ip=ip), Uint32(x4).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(w5).ir_value(loc=loc, ip=ip), Uint32(x5).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(w6).ir_value(loc=loc, ip=ip), Uint32(x6).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(w7).ir_value(loc=loc, ip=ip), Uint32(x7).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .f16x2 acc;
|
|
|
+ .reg .b16 lo, hi;
|
|
|
+ mov.b32 acc, 0;
|
|
|
+ fma.rn.f16x2 acc, $2, $3, acc;
|
|
|
+ fma.rn.f16x2 acc, $4, $5, acc;
|
|
|
+ fma.rn.f16x2 acc, $6, $7, acc;
|
|
|
+ fma.rn.f16x2 acc, $8, $9, acc;
|
|
|
+ fma.rn.f16x2 acc, $10, $11, acc;
|
|
|
+ fma.rn.f16x2 acc, $12, $13, acc;
|
|
|
+ fma.rn.f16x2 acc, $14, $15, acc;
|
|
|
+ fma.rn.f16x2 acc, $16, $17, acc;
|
|
|
+ mov.b32 {lo, hi}, acc;
|
|
|
+ cvt.f32.f16 $0, lo;
|
|
|
+ cvt.f32.f16 $1, hi;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=f,=f,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ return (
|
|
|
+ Float32(llvm.extractvalue(T.f32(), res, [0], loc=loc, ip=ip)),
|
|
|
+ Float32(llvm.extractvalue(T.f32(), res, [1], loc=loc, ip=ip)),
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def hfma2_8_sum(
|
|
|
+ w0: Uint32, x0: Uint32,
|
|
|
+ w1: Uint32, x1: Uint32,
|
|
|
+ w2: Uint32, x2: Uint32,
|
|
|
+ w3: Uint32, x3: Uint32,
|
|
|
+ w4: Uint32, x4: Uint32,
|
|
|
+ w5: Uint32, x5: Uint32,
|
|
|
+ w6: Uint32, x6: Uint32,
|
|
|
+ w7: Uint32, x7: Uint32,
|
|
|
+ *, loc=None, ip=None
|
|
|
+) -> Float32:
|
|
|
+ """8-element half2 FMA dot product returning lane sum."""
|
|
|
+ return Float32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.f32(),
|
|
|
+ [
|
|
|
+ Uint32(w0).ir_value(loc=loc, ip=ip), Uint32(x0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(w1).ir_value(loc=loc, ip=ip), Uint32(x1).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(w2).ir_value(loc=loc, ip=ip), Uint32(x2).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(w3).ir_value(loc=loc, ip=ip), Uint32(x3).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(w4).ir_value(loc=loc, ip=ip), Uint32(x4).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(w5).ir_value(loc=loc, ip=ip), Uint32(x5).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(w6).ir_value(loc=loc, ip=ip), Uint32(x6).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(w7).ir_value(loc=loc, ip=ip), Uint32(x7).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .f16x2 acc;
|
|
|
+ .reg .b16 lo, hi;
|
|
|
+ .reg .f32 flo, fhi;
|
|
|
+ mov.b32 acc, 0;
|
|
|
+ fma.rn.f16x2 acc, $1, $2, acc;
|
|
|
+ fma.rn.f16x2 acc, $3, $4, acc;
|
|
|
+ fma.rn.f16x2 acc, $5, $6, acc;
|
|
|
+ fma.rn.f16x2 acc, $7, $8, acc;
|
|
|
+ fma.rn.f16x2 acc, $9, $10, acc;
|
|
|
+ fma.rn.f16x2 acc, $11, $12, acc;
|
|
|
+ fma.rn.f16x2 acc, $13, $14, acc;
|
|
|
+ fma.rn.f16x2 acc, $15, $16, acc;
|
|
|
+ mov.b32 {lo, hi}, acc;
|
|
|
+ cvt.f32.f16 flo, lo;
|
|
|
+ cvt.f32.f16 fhi, hi;
|
|
|
+ add.f32 $0, flo, fhi;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=f,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+# =============================================================================
|
|
|
+# FP4 Dot Product Intrinsics
|
|
|
+# =============================================================================
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def fp4_dot4_sum(
|
|
|
+ u_packed: Uint32,
|
|
|
+ x0: Uint32,
|
|
|
+ x1: Uint32,
|
|
|
+ x2: Uint32,
|
|
|
+ x3: Uint32,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+) -> Float32:
|
|
|
+ """FP4 dot product: decode 4 FP4 bytes and dot with 4 x f16x2 inputs."""
|
|
|
+ return Float32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.f32(),
|
|
|
+ [
|
|
|
+ Uint32(u_packed).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(x0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(x1).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(x2).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(x3).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b8 b0, b1, b2, b3;
|
|
|
+ .reg .b32 h0, h1, h2, h3;
|
|
|
+ .reg .f16x2 acc;
|
|
|
+ .reg .b16 lo, hi;
|
|
|
+ .reg .f32 flo, fhi;
|
|
|
+ mov.b32 {b0, b1, b2, b3}, $1;
|
|
|
+ cvt.rn.f16x2.e2m1x2 h0, b0;
|
|
|
+ cvt.rn.f16x2.e2m1x2 h1, b1;
|
|
|
+ cvt.rn.f16x2.e2m1x2 h2, b2;
|
|
|
+ cvt.rn.f16x2.e2m1x2 h3, b3;
|
|
|
+ mov.b32 acc, 0;
|
|
|
+ fma.rn.f16x2 acc, h0, $2, acc;
|
|
|
+ fma.rn.f16x2 acc, h1, $3, acc;
|
|
|
+ fma.rn.f16x2 acc, h2, $4, acc;
|
|
|
+ fma.rn.f16x2 acc, h3, $5, acc;
|
|
|
+ mov.b32 {lo, hi}, acc;
|
|
|
+ cvt.f32.f16 flo, lo;
|
|
|
+ cvt.f32.f16 fhi, hi;
|
|
|
+ add.f32 $0, flo, fhi;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=f,r,r,r,r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def fp4_dot8_dual_sum(
|
|
|
+ up_a: Uint32,
|
|
|
+ up_b: Uint32,
|
|
|
+ gate_a: Uint32,
|
|
|
+ gate_b: Uint32,
|
|
|
+ x0: Uint32,
|
|
|
+ x1: Uint32,
|
|
|
+ x2: Uint32,
|
|
|
+ x3: Uint32,
|
|
|
+ x4: Uint32,
|
|
|
+ x5: Uint32,
|
|
|
+ x6: Uint32,
|
|
|
+ x7: Uint32,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+) -> Tuple[Float32, Float32]:
|
|
|
+ """Fused dual FP4 dot product (up + gate) over the SAME 8 shared f16x2
|
|
|
+ activation words. Decodes both weight rows and runs two INDEPENDENT
|
|
|
+ f16x2 accumulator chains in a single inline-asm block, then reduces each
|
|
|
+ to f32. Bit-identical to running fp4_dot8_sum twice (same per-chain fma
|
|
|
+ order, same lo+hi f32 reduction) but emits one asm block so ptxas can
|
|
|
+ interleave the two independent fma chains for 2-way ILP and halves the
|
|
|
+ per-pair epilogue (mov/cvt/add) op count on the issue-bound decode loop."""
|
|
|
+ res = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.f32(), T.f32()]),
|
|
|
+ [
|
|
|
+ Uint32(up_a).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(up_b).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(gate_a).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(gate_b).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(x0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(x1).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(x2).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(x3).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(x4).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(x5).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(x6).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(x7).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b8 ua0, ua1, ua2, ua3, ub0, ub1, ub2, ub3;
|
|
|
+ .reg .b8 ga0, ga1, ga2, ga3, gb0, gb1, gb2, gb3;
|
|
|
+ .reg .b32 uh0, uh1, uh2, uh3, uh4, uh5, uh6, uh7;
|
|
|
+ .reg .b32 gh0, gh1, gh2, gh3, gh4, gh5, gh6, gh7;
|
|
|
+ .reg .f16x2 uacc, gacc;
|
|
|
+ .reg .b16 ulo, uhi, glo, ghi;
|
|
|
+ .reg .f32 uflo, ufhi, gflo, gfhi;
|
|
|
+ mov.b32 {ua0, ua1, ua2, ua3}, $2;
|
|
|
+ mov.b32 {ub0, ub1, ub2, ub3}, $3;
|
|
|
+ mov.b32 {ga0, ga1, ga2, ga3}, $4;
|
|
|
+ mov.b32 {gb0, gb1, gb2, gb3}, $5;
|
|
|
+ cvt.rn.f16x2.e2m1x2 uh0, ua0;
|
|
|
+ cvt.rn.f16x2.e2m1x2 uh1, ua1;
|
|
|
+ cvt.rn.f16x2.e2m1x2 uh2, ua2;
|
|
|
+ cvt.rn.f16x2.e2m1x2 uh3, ua3;
|
|
|
+ cvt.rn.f16x2.e2m1x2 uh4, ub0;
|
|
|
+ cvt.rn.f16x2.e2m1x2 uh5, ub1;
|
|
|
+ cvt.rn.f16x2.e2m1x2 uh6, ub2;
|
|
|
+ cvt.rn.f16x2.e2m1x2 uh7, ub3;
|
|
|
+ cvt.rn.f16x2.e2m1x2 gh0, ga0;
|
|
|
+ cvt.rn.f16x2.e2m1x2 gh1, ga1;
|
|
|
+ cvt.rn.f16x2.e2m1x2 gh2, ga2;
|
|
|
+ cvt.rn.f16x2.e2m1x2 gh3, ga3;
|
|
|
+ cvt.rn.f16x2.e2m1x2 gh4, gb0;
|
|
|
+ cvt.rn.f16x2.e2m1x2 gh5, gb1;
|
|
|
+ cvt.rn.f16x2.e2m1x2 gh6, gb2;
|
|
|
+ cvt.rn.f16x2.e2m1x2 gh7, gb3;
|
|
|
+ mov.b32 uacc, 0;
|
|
|
+ mov.b32 gacc, 0;
|
|
|
+ fma.rn.f16x2 uacc, uh0, $6, uacc;
|
|
|
+ fma.rn.f16x2 gacc, gh0, $6, gacc;
|
|
|
+ fma.rn.f16x2 uacc, uh1, $7, uacc;
|
|
|
+ fma.rn.f16x2 gacc, gh1, $7, gacc;
|
|
|
+ fma.rn.f16x2 uacc, uh2, $8, uacc;
|
|
|
+ fma.rn.f16x2 gacc, gh2, $8, gacc;
|
|
|
+ fma.rn.f16x2 uacc, uh3, $9, uacc;
|
|
|
+ fma.rn.f16x2 gacc, gh3, $9, gacc;
|
|
|
+ fma.rn.f16x2 uacc, uh4, $10, uacc;
|
|
|
+ fma.rn.f16x2 gacc, gh4, $10, gacc;
|
|
|
+ fma.rn.f16x2 uacc, uh5, $11, uacc;
|
|
|
+ fma.rn.f16x2 gacc, gh5, $11, gacc;
|
|
|
+ fma.rn.f16x2 uacc, uh6, $12, uacc;
|
|
|
+ fma.rn.f16x2 gacc, gh6, $12, gacc;
|
|
|
+ fma.rn.f16x2 uacc, uh7, $13, uacc;
|
|
|
+ fma.rn.f16x2 gacc, gh7, $13, gacc;
|
|
|
+ mov.b32 {ulo, uhi}, uacc;
|
|
|
+ mov.b32 {glo, ghi}, gacc;
|
|
|
+ cvt.f32.f16 uflo, ulo;
|
|
|
+ cvt.f32.f16 ufhi, uhi;
|
|
|
+ cvt.f32.f16 gflo, glo;
|
|
|
+ cvt.f32.f16 gfhi, ghi;
|
|
|
+ add.f32 $0, uflo, ufhi;
|
|
|
+ add.f32 $1, gflo, gfhi;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=f,=f,r,r,r,r,r,r,r,r,r,r,r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ up = Float32(llvm.extractvalue(T.f32(), res, [0], loc=loc, ip=ip))
|
|
|
+ gate = Float32(llvm.extractvalue(T.f32(), res, [1], loc=loc, ip=ip))
|
|
|
+ return up, gate
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def fp4_dot8_sum(
|
|
|
+ u_a: Uint32,
|
|
|
+ u_b: Uint32,
|
|
|
+ x0: Uint32,
|
|
|
+ x1: Uint32,
|
|
|
+ x2: Uint32,
|
|
|
+ x3: Uint32,
|
|
|
+ x4: Uint32,
|
|
|
+ x5: Uint32,
|
|
|
+ x6: Uint32,
|
|
|
+ x7: Uint32,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+) -> Float32:
|
|
|
+ """FP4 dot product: decode 8 FP4 bytes (2 x u32) and dot with 8 x f16x2 inputs."""
|
|
|
+ return Float32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.f32(),
|
|
|
+ [
|
|
|
+ Uint32(u_a).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(u_b).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(x0).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(x1).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(x2).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(x3).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(x4).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(x5).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(x6).ir_value(loc=loc, ip=ip),
|
|
|
+ Uint32(x7).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b8 a0, a1, a2, a3;
|
|
|
+ .reg .b8 b0, b1, b2, b3;
|
|
|
+ .reg .b32 h0, h1, h2, h3, h4, h5, h6, h7;
|
|
|
+ .reg .f16x2 acc;
|
|
|
+ .reg .b16 lo, hi;
|
|
|
+ .reg .f32 flo, fhi;
|
|
|
+ mov.b32 {a0, a1, a2, a3}, $1;
|
|
|
+ mov.b32 {b0, b1, b2, b3}, $2;
|
|
|
+ cvt.rn.f16x2.e2m1x2 h0, a0;
|
|
|
+ cvt.rn.f16x2.e2m1x2 h1, a1;
|
|
|
+ cvt.rn.f16x2.e2m1x2 h2, a2;
|
|
|
+ cvt.rn.f16x2.e2m1x2 h3, a3;
|
|
|
+ cvt.rn.f16x2.e2m1x2 h4, b0;
|
|
|
+ cvt.rn.f16x2.e2m1x2 h5, b1;
|
|
|
+ cvt.rn.f16x2.e2m1x2 h6, b2;
|
|
|
+ cvt.rn.f16x2.e2m1x2 h7, b3;
|
|
|
+ mov.b32 acc, 0;
|
|
|
+ fma.rn.f16x2 acc, h0, $3, acc;
|
|
|
+ fma.rn.f16x2 acc, h1, $4, acc;
|
|
|
+ fma.rn.f16x2 acc, h2, $5, acc;
|
|
|
+ fma.rn.f16x2 acc, h3, $6, acc;
|
|
|
+ fma.rn.f16x2 acc, h4, $7, acc;
|
|
|
+ fma.rn.f16x2 acc, h5, $8, acc;
|
|
|
+ fma.rn.f16x2 acc, h6, $9, acc;
|
|
|
+ fma.rn.f16x2 acc, h7, $10, acc;
|
|
|
+ mov.b32 {lo, hi}, acc;
|
|
|
+ cvt.f32.f16 flo, lo;
|
|
|
+ cvt.f32.f16 fhi, hi;
|
|
|
+ add.f32 $0, flo, fhi;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=f,r,r,r,r,r,r,r,r,r,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def _f16x2_dot_sum_f32acc(a_h2: Uint32, b_h2: Uint32) -> Float32:
|
|
|
+ a0, a1 = f16x2_to_f32x2(a_h2)
|
|
|
+ b0, b1 = f16x2_to_f32x2(b_h2)
|
|
|
+ return a0 * b0 + a1 * b1
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def fp4_dot4_sum_f32acc(
|
|
|
+ u_packed: Uint32,
|
|
|
+ x0: Uint32,
|
|
|
+ x1: Uint32,
|
|
|
+ x2: Uint32,
|
|
|
+ x3: Uint32,
|
|
|
+) -> Float32:
|
|
|
+ """FP4 dot product using f32 accumulation after FP4 decode."""
|
|
|
+ h0, h1, h2, h3 = fp4_decode_4bytes(u_packed)
|
|
|
+ return (
|
|
|
+ _f16x2_dot_sum_f32acc(h0, x0)
|
|
|
+ + _f16x2_dot_sum_f32acc(h1, x1)
|
|
|
+ + _f16x2_dot_sum_f32acc(h2, x2)
|
|
|
+ + _f16x2_dot_sum_f32acc(h3, x3)
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def fp4_dot8_sum_f32acc(
|
|
|
+ u_a: Uint32,
|
|
|
+ u_b: Uint32,
|
|
|
+ x0: Uint32,
|
|
|
+ x1: Uint32,
|
|
|
+ x2: Uint32,
|
|
|
+ x3: Uint32,
|
|
|
+ x4: Uint32,
|
|
|
+ x5: Uint32,
|
|
|
+ x6: Uint32,
|
|
|
+ x7: Uint32,
|
|
|
+) -> Float32:
|
|
|
+ """FP4 dot product using f32 accumulation after FP4 decode."""
|
|
|
+ h0, h1, h2, h3 = fp4_decode_4bytes(u_a)
|
|
|
+ h4, h5, h6, h7 = fp4_decode_4bytes(u_b)
|
|
|
+ return (
|
|
|
+ _f16x2_dot_sum_f32acc(h0, x0)
|
|
|
+ + _f16x2_dot_sum_f32acc(h1, x1)
|
|
|
+ + _f16x2_dot_sum_f32acc(h2, x2)
|
|
|
+ + _f16x2_dot_sum_f32acc(h3, x3)
|
|
|
+ + _f16x2_dot_sum_f32acc(h4, x4)
|
|
|
+ + _f16x2_dot_sum_f32acc(h5, x5)
|
|
|
+ + _f16x2_dot_sum_f32acc(h6, x6)
|
|
|
+ + _f16x2_dot_sum_f32acc(h7, x7)
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+# =============================================================================
|
|
|
+# Pack Intrinsics
|
|
|
+# =============================================================================
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def pack_f32x2_to_f16x2(
|
|
|
+ a: Float32, b: Float32, *, loc=None, ip=None
|
|
|
+) -> Uint32:
|
|
|
+ """Pack two f32 values into one f16x2 u32."""
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [
|
|
|
+ Float32(a).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(b).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b16 lo, hi;
|
|
|
+ cvt.rn.f16.f32 lo, $1;
|
|
|
+ cvt.rn.f16.f32 hi, $2;
|
|
|
+ mov.b32 $0, {lo, hi};
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=r,f,f",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+# =============================================================================
|
|
|
+# UE8M0 Intrinsics (for MXFP4)
|
|
|
+# =============================================================================
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def pow2_ceil_ue8m0(
|
|
|
+ scale: Float32,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+) -> Tuple[Float32, Uint32]:
|
|
|
+ """Round a positive FP32 ``scale`` UP to a power of two, bit-exactly.
|
|
|
+
|
|
|
+ Returns ``(rounded_fp32, ue8m0_byte)`` where:
|
|
|
+ * ``rounded_fp32`` == ``__uint_as_float(power_of_2_round(__float_as_uint(scale)))``
|
|
|
+ * ``ue8m0_byte`` == ``(__float_as_uint(rounded_fp32) >> 23) & 0xFF``
|
|
|
+
|
|
|
+ Bit-exact replica of FlashInfer's fp8_quant.cuh rounding +
|
|
|
+ scale_convert.cuh fp32_to_ue8m0. Integer ops only (no lg2.approx), so unlike
|
|
|
+ ``cvt_f32_to_ue8m0`` it matches the FlashInfer reference bit-for-bit.
|
|
|
+ """
|
|
|
+ result = llvm.inline_asm(
|
|
|
+ llvm.StructType.get_literal([T.f32(), T.i32()]),
|
|
|
+ [Float32(scale).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .pred p_mant;
|
|
|
+ .reg .b32 bits, mant;
|
|
|
+
|
|
|
+ // bits = __float_as_uint(scale)
|
|
|
+ mov.b32 bits, $2;
|
|
|
+ // mant = bits & 0x007FFFFF (mantissa field)
|
|
|
+ and.b32 mant, bits, 8388607;
|
|
|
+ setp.ne.u32 p_mant, mant, 0;
|
|
|
+ // if (mant) bits = (bits + 0x00800000) & 0x7F800000
|
|
|
+ @p_mant add.u32 bits, bits, 8388608;
|
|
|
+ @p_mant and.b32 bits, bits, 2139095040;
|
|
|
+ // rounded fp32 scale = __uint_as_float(bits)
|
|
|
+ mov.b32 $0, bits;
|
|
|
+ // ue8m0 = (bits >> 23) & 0xFF
|
|
|
+ bfe.u32 $1, bits, 23, 8;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=f,=r,f",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ loc=loc,
|
|
|
+ ip=ip,
|
|
|
+ )
|
|
|
+ rounded = llvm.extractvalue(T.f32(), result, [0], loc=loc, ip=ip)
|
|
|
+ ue8m0 = llvm.extractvalue(T.i32(), result, [1], loc=loc, ip=ip)
|
|
|
+ return Float32(rounded), Uint32(ue8m0)
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def cvt_f32_to_ue8m0(max_val: Float32, *, loc=None, ip=None) -> Uint32:
|
|
|
+ """
|
|
|
+ Convert float32 max value to UE8M0 scale factor.
|
|
|
+
|
|
|
+ UE8M0 is unsigned 8-bit exponent-only format:
|
|
|
+ - value = 2^(ue8m0 - 127)
|
|
|
+ - ue8m0 = ceil(log2(max_val)) + 127
|
|
|
+
|
|
|
+ Uses lg2.approx.f32 for fast log2 approximation.
|
|
|
+ Uses cvt.rpi (round towards positive infinity, i.e., ceiling).
|
|
|
+ Returns value clamped to [0, 255].
|
|
|
+ """
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [Float32(max_val).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .pred p_zero, p_neg, p_ovf;
|
|
|
+ .reg .f32 log2_val;
|
|
|
+ .reg .s32 exp_int, result;
|
|
|
+
|
|
|
+ // Check for zero/negative
|
|
|
+ setp.le.f32 p_zero, $1, 0f00000000;
|
|
|
+
|
|
|
+ // Compute ceil(log2(max_val)) using cvt.rpi (round towards +inf)
|
|
|
+ lg2.approx.f32 log2_val, $1;
|
|
|
+ cvt.rpi.s32.f32 exp_int, log2_val;
|
|
|
+
|
|
|
+ // Add bias and clamp to [0, 255]
|
|
|
+ add.s32 result, exp_int, 127;
|
|
|
+ setp.lt.s32 p_neg, result, 0;
|
|
|
+ setp.gt.s32 p_ovf, result, 255;
|
|
|
+ selp.s32 result, 0, result, p_neg;
|
|
|
+ selp.s32 result, 255, result, p_ovf;
|
|
|
+ selp.s32 $0, 0, result, p_zero;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=r,f",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def ue8m0_to_output_scale(ue8m0_val: Uint32, *, loc=None, ip=None) -> Float32:
|
|
|
+ """
|
|
|
+ Convert UE8M0 to output_scale for MXFP4 quantization.
|
|
|
+
|
|
|
+ UE8M0 value = 2^(ue8m0 - 127)
|
|
|
+ Returns 1 / 2^(ue8m0 - 127) = 2^(127 - ue8m0)
|
|
|
+ """
|
|
|
+ return Float32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.f32(),
|
|
|
+ [Uint32(ue8m0_val).ir_value(loc=loc, ip=ip)],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .pred p_zero;
|
|
|
+ .reg .s32 neg_exp;
|
|
|
+ .reg .f32 neg_exp_f, result;
|
|
|
+
|
|
|
+ // Check for zero
|
|
|
+ setp.eq.u32 p_zero, $1, 0;
|
|
|
+
|
|
|
+ // Compute 2^(127 - ue8m0) = 1 / 2^(ue8m0 - 127)
|
|
|
+ sub.s32 neg_exp, 127, $1;
|
|
|
+ cvt.rn.f32.s32 neg_exp_f, neg_exp;
|
|
|
+ ex2.approx.f32 result, neg_exp_f;
|
|
|
+ selp.f32 $0, 0f00000000, result, p_zero;
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=f,r",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+# =============================================================================
|
|
|
+# E2M1 Conversion
|
|
|
+# =============================================================================
|
|
|
+
|
|
|
+
|
|
|
+@dsl_user_op
|
|
|
+def cvt_e2m1x8_f32(
|
|
|
+ v0: Float32,
|
|
|
+ v1: Float32,
|
|
|
+ v2: Float32,
|
|
|
+ v3: Float32,
|
|
|
+ v4: Float32,
|
|
|
+ v5: Float32,
|
|
|
+ v6: Float32,
|
|
|
+ v7: Float32,
|
|
|
+ *,
|
|
|
+ loc=None,
|
|
|
+ ip=None,
|
|
|
+) -> Uint32:
|
|
|
+ """Convert eight float32 values to eight E2M1 (4-bit) values packed into uint32."""
|
|
|
+ return Uint32(
|
|
|
+ llvm.inline_asm(
|
|
|
+ T.i32(),
|
|
|
+ [
|
|
|
+ Float32(v0).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(v1).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(v2).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(v3).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(v4).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(v5).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(v6).ir_value(loc=loc, ip=ip),
|
|
|
+ Float32(v7).ir_value(loc=loc, ip=ip),
|
|
|
+ ],
|
|
|
+ """
|
|
|
+ {
|
|
|
+ .reg .b8 byte0, byte1, byte2, byte3;
|
|
|
+ cvt.rn.satfinite.e2m1x2.f32 byte0, $2, $1;
|
|
|
+ cvt.rn.satfinite.e2m1x2.f32 byte1, $4, $3;
|
|
|
+ cvt.rn.satfinite.e2m1x2.f32 byte2, $6, $5;
|
|
|
+ cvt.rn.satfinite.e2m1x2.f32 byte3, $8, $7;
|
|
|
+ mov.b32 $0, {byte0, byte1, byte2, byte3};
|
|
|
+ }
|
|
|
+ """,
|
|
|
+ "=r,f,f,f,f,f,f,f,f",
|
|
|
+ has_side_effects=False,
|
|
|
+ is_align_stack=False,
|
|
|
+ asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+# =============================================================================
|
|
|
+# Warp, Block, and Cluster Reduction Utilities
|
|
|
+# =============================================================================
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def warp_reduce(val, op, width: cutlass.Constexpr[int] = 32):
|
|
|
+ """Reduce across threads in a warp using butterfly shuffle."""
|
|
|
+ if cutlass.const_expr(isinstance(val, cute.TensorSSA)):
|
|
|
+ res = cute.make_rmem_tensor(val.shape, val.dtype)
|
|
|
+ res.store(val)
|
|
|
+ for i in cutlass.range_constexpr(cute.size(val.shape)):
|
|
|
+ res[i] = warp_reduce(res[i], op, width)
|
|
|
+ return res.load()
|
|
|
+ else:
|
|
|
+ for i in cutlass.range_constexpr(int(math.log2(width))):
|
|
|
+ val = op(val, cute.arch.shuffle_sync_bfly(val, offset=1 << i))
|
|
|
+ return val
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def block_reduce(
|
|
|
+ val: Float32,
|
|
|
+ op: Callable,
|
|
|
+ reduction_buffer: cute.Tensor,
|
|
|
+ init_val: Float32,
|
|
|
+) -> Float32:
|
|
|
+ """Block reduction across multiple warps using shared memory."""
|
|
|
+ lane_idx = cute.arch.lane_idx()
|
|
|
+ warp_idx = cute.arch.warp_idx()
|
|
|
+ warps_per_row = cute.size(reduction_buffer.shape[1])
|
|
|
+ row_idx = warp_idx // warps_per_row
|
|
|
+ col_idx = warp_idx % warps_per_row
|
|
|
+
|
|
|
+ if lane_idx == 0:
|
|
|
+ reduction_buffer[row_idx, col_idx] = val
|
|
|
+ cute.arch.barrier()
|
|
|
+
|
|
|
+ block_reduce_val = init_val
|
|
|
+ if lane_idx < warps_per_row:
|
|
|
+ block_reduce_val = reduction_buffer[row_idx, lane_idx]
|
|
|
+ return warp_reduce(block_reduce_val, op)
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def cluster_reduce(
|
|
|
+ val: Float32,
|
|
|
+ op: Callable,
|
|
|
+ reduction_buffer: cute.Tensor,
|
|
|
+ mbar_ptr: cute.Pointer,
|
|
|
+ cluster_n: cutlass.Constexpr[int],
|
|
|
+ init_val: Float32,
|
|
|
+) -> Float32:
|
|
|
+ """Cluster reduction across multiple CTAs using mbarrier."""
|
|
|
+ cta_rank_in_cluster = cute.arch.block_idx_in_cluster()
|
|
|
+ lane_idx = cute.arch.lane_idx()
|
|
|
+ warp_idx = cute.arch.warp_idx()
|
|
|
+
|
|
|
+ rows_per_block = reduction_buffer.shape[0]
|
|
|
+ warps_per_row = reduction_buffer.shape[1][0]
|
|
|
+
|
|
|
+ row_idx = warp_idx // warps_per_row
|
|
|
+ col_idx = warp_idx % warps_per_row
|
|
|
+
|
|
|
+ # Warp 0 sets up mbarrier transaction count
|
|
|
+ if warp_idx == 0:
|
|
|
+ with cute.arch.elect_one():
|
|
|
+ num_warps = rows_per_block * warps_per_row
|
|
|
+ expected_bytes = num_warps * cluster_n * 4
|
|
|
+ cute.arch.mbarrier_arrive_and_expect_tx(mbar_ptr, expected_bytes)
|
|
|
+
|
|
|
+ # Each lane < cluster_n writes to a different CTA's shared memory
|
|
|
+ if lane_idx < cluster_n:
|
|
|
+ store_shared_remote(
|
|
|
+ val,
|
|
|
+ elem_pointer(reduction_buffer, (row_idx, (col_idx, cta_rank_in_cluster))),
|
|
|
+ mbar_ptr,
|
|
|
+ peer_cta_rank_in_cluster=lane_idx,
|
|
|
+ )
|
|
|
+
|
|
|
+ # Wait for all cluster writes
|
|
|
+ cute.arch.mbarrier_wait(mbar_ptr, phase=0)
|
|
|
+
|
|
|
+ # Reduce across all values
|
|
|
+ num_total = warps_per_row * cluster_n
|
|
|
+ num_iter = cute.ceil_div(num_total, 32)
|
|
|
+
|
|
|
+ block_reduce_val = init_val
|
|
|
+ for i in cutlass.range_constexpr(num_iter):
|
|
|
+ idx = lane_idx + i * 32
|
|
|
+ if idx < num_total:
|
|
|
+ block_reduce_val = op(block_reduce_val, reduction_buffer[row_idx, idx])
|
|
|
+
|
|
|
+ return warp_reduce(block_reduce_val, op)
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def row_reduce(
|
|
|
+ x: cute.TensorSSA,
|
|
|
+ op: cute.ReductionOp,
|
|
|
+ threads_per_row: cutlass.Constexpr[int],
|
|
|
+ reduction_buffer: cute.Tensor,
|
|
|
+ mbar_ptr,
|
|
|
+ cluster_n: cutlass.Constexpr[int],
|
|
|
+ init_val: Float32,
|
|
|
+):
|
|
|
+ """Row reduction with optional cluster support."""
|
|
|
+ local_val = x.reduce(op, init_val=init_val, reduction_profile=0)
|
|
|
+
|
|
|
+ warp_op = {
|
|
|
+ cute.ReductionOp.ADD: operator.add,
|
|
|
+ cute.ReductionOp.MAX: cute.arch.fmax,
|
|
|
+ }[op]
|
|
|
+ warp_width = min(threads_per_row, 32)
|
|
|
+ warp_val = warp_reduce(local_val, warp_op, width=warp_width)
|
|
|
+
|
|
|
+ warps_per_row = max(threads_per_row // 32, 1)
|
|
|
+
|
|
|
+ if cutlass.const_expr(warps_per_row > 1 or cluster_n > 1):
|
|
|
+ if cutlass.const_expr(cluster_n == 1):
|
|
|
+ return block_reduce(warp_val, warp_op, reduction_buffer, init_val)
|
|
|
+ else:
|
|
|
+ return cluster_reduce(
|
|
|
+ warp_val, warp_op, reduction_buffer, mbar_ptr, cluster_n, init_val
|
|
|
+ )
|
|
|
+ else:
|
|
|
+ return warp_val
|
|
|
+
|
|
|
+
|
|
|
+# =============================================================================
|
|
|
+# Predicate Utility
|
|
|
+# =============================================================================
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def predicate_k(tXcX: cute.Tensor, limit: int) -> cute.Tensor:
|
|
|
+ """Create predicate tensor for bounds checking."""
|
|
|
+ tXpX = cute.make_rmem_tensor(
|
|
|
+ cute.make_layout(
|
|
|
+ (
|
|
|
+ cute.size(tXcX, mode=[0, 1]),
|
|
|
+ cute.size(tXcX, mode=[1]),
|
|
|
+ cute.size(tXcX, mode=[2]),
|
|
|
+ ),
|
|
|
+ stride=(cute.size(tXcX, mode=[2]), 0, 1),
|
|
|
+ ),
|
|
|
+ cutlass.Boolean,
|
|
|
+ )
|
|
|
+ for rest_v in cutlass.range_constexpr(tXpX.shape[0]):
|
|
|
+ for rest_k in cutlass.range_constexpr(tXpX.shape[2]):
|
|
|
+ tXpX[rest_v, 0, rest_k] = cute.elem_less(
|
|
|
+ tXcX[(0, rest_v), 0, rest_k][1], limit
|
|
|
+ )
|
|
|
+ return tXpX
|
|
|
+
|
|
|
+
|
|
|
+# =============================================================================
|
|
|
+# Helper Functions for SF Block Processing (block_size=16)
|
|
|
+# =============================================================================
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def load_8_half2(
|
|
|
+ mX: cute.Tensor, mW: cute.Tensor, row_offset: Int32, col_offset: Int32, H: int
|
|
|
+):
|
|
|
+ """Load 16 elements (8 half2 pairs) of X and W from global memory.
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ x_h2: rmem_tensor of shape (8,) containing X as half2
|
|
|
+ w_h2: rmem_tensor of shape (8,) containing W as half2
|
|
|
+ """
|
|
|
+ x_h2 = cute.make_rmem_tensor((8,), Uint32)
|
|
|
+ w_h2 = cute.make_rmem_tensor((8,), Uint32)
|
|
|
+
|
|
|
+ # Load X (2 x 128-bit loads = 16 elements)
|
|
|
+ x_ptr0 = get_ptr_as_int64(mX, row_offset * H + col_offset)
|
|
|
+ x_ptr1 = get_ptr_as_int64(mX, row_offset * H + col_offset + Int32(8))
|
|
|
+ x_h2[0], x_h2[1], x_h2[2], x_h2[3] = ld_global_v4_u32(x_ptr0)
|
|
|
+ x_h2[4], x_h2[5], x_h2[6], x_h2[7] = ld_global_v4_u32(x_ptr1)
|
|
|
+
|
|
|
+ # Load W (2 x 128-bit loads = 16 elements)
|
|
|
+ w_ptr0 = get_ptr_as_int64(mW, col_offset)
|
|
|
+ w_ptr1 = get_ptr_as_int64(mW, col_offset + Int32(8))
|
|
|
+ w_h2[0], w_h2[1], w_h2[2], w_h2[3] = ld_global_v4_u32(w_ptr0)
|
|
|
+ w_h2[4], w_h2[5], w_h2[6], w_h2[7] = ld_global_v4_u32(w_ptr1)
|
|
|
+
|
|
|
+ return x_h2, w_h2
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def half2_mul_8(x_h2: cute.Tensor, w_h2: cute.Tensor) -> cute.Tensor:
|
|
|
+ """Multiply 8 half2 pairs element-wise."""
|
|
|
+ xw_h2 = cute.make_rmem_tensor((8,), Uint32)
|
|
|
+ for i in cutlass.range_constexpr(8):
|
|
|
+ xw_h2[i] = half2_mul(x_h2[i], w_h2[i])
|
|
|
+ return xw_h2
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def bfloat2_mul_8(x_h2: cute.Tensor, w_h2: cute.Tensor) -> cute.Tensor:
|
|
|
+ """Multiply 8 bfloat2 pairs element-wise."""
|
|
|
+ xw_h2 = cute.make_rmem_tensor((8,), Uint32)
|
|
|
+ for i in cutlass.range_constexpr(8):
|
|
|
+ xw_h2[i] = bfloat2_mul(x_h2[i], w_h2[i])
|
|
|
+ return xw_h2
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def half2_max_abs_8(xw_h2: cute.Tensor) -> Uint32:
|
|
|
+ """Compute max absolute value across 8 half2 values using tree reduction."""
|
|
|
+ # Compute abs for all 8 values
|
|
|
+ abs_h2 = cute.make_rmem_tensor((8,), Uint32)
|
|
|
+ for i in cutlass.range_constexpr(8):
|
|
|
+ abs_h2[i] = habs2(xw_h2[i])
|
|
|
+
|
|
|
+ # Tree reduction: 8 -> 4 -> 2 -> 1
|
|
|
+ max_01 = hmax2(abs_h2[0], abs_h2[1])
|
|
|
+ max_23 = hmax2(abs_h2[2], abs_h2[3])
|
|
|
+ max_45 = hmax2(abs_h2[4], abs_h2[5])
|
|
|
+ max_67 = hmax2(abs_h2[6], abs_h2[7])
|
|
|
+ max_0123 = hmax2(max_01, max_23)
|
|
|
+ max_4567 = hmax2(max_45, max_67)
|
|
|
+ return hmax2(max_0123, max_4567)
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def bfloat2_max_abs_8(xw_h2: cute.Tensor) -> Uint32:
|
|
|
+ """Compute max absolute value across 8 bfloat2 values using tree reduction."""
|
|
|
+ # Compute abs for all 8 values
|
|
|
+ abs_h2 = cute.make_rmem_tensor((8,), Uint32)
|
|
|
+ for i in cutlass.range_constexpr(8):
|
|
|
+ abs_h2[i] = bfloat2_habs2(xw_h2[i])
|
|
|
+
|
|
|
+ # Tree reduction: 8 -> 4 -> 2 -> 1
|
|
|
+ max_01 = bfloat2_hmax2(abs_h2[0], abs_h2[1])
|
|
|
+ max_23 = bfloat2_hmax2(abs_h2[2], abs_h2[3])
|
|
|
+ max_45 = bfloat2_hmax2(abs_h2[4], abs_h2[5])
|
|
|
+ max_67 = bfloat2_hmax2(abs_h2[6], abs_h2[7])
|
|
|
+ max_0123 = bfloat2_hmax2(max_01, max_23)
|
|
|
+ max_4567 = bfloat2_hmax2(max_45, max_67)
|
|
|
+ return bfloat2_hmax2(max_0123, max_4567)
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def half2_to_float16(xw_h2: cute.Tensor, scale: Float32) -> cute.Tensor:
|
|
|
+ """Convert 8 half2 to 16 float32 with scaling."""
|
|
|
+ y_f32 = cute.make_rmem_tensor((16,), Float32)
|
|
|
+ for i in cutlass.range_constexpr(8):
|
|
|
+ y_f32[i * 2], y_f32[i * 2 + 1] = half2_to_float2_scaled(xw_h2[i], scale)
|
|
|
+ return y_f32
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def bfloat2_to_float16(xw_h2: cute.Tensor, scale: Float32) -> cute.Tensor:
|
|
|
+ """Convert 8 bfloat2 to 16 float32 with scaling."""
|
|
|
+ y_f32 = cute.make_rmem_tensor((16,), Float32)
|
|
|
+ for i in cutlass.range_constexpr(8):
|
|
|
+ y_f32[i * 2], y_f32[i * 2 + 1] = bfloat2_to_float2_scaled(xw_h2[i], scale)
|
|
|
+ return y_f32
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def quantize_and_pack_16(y_f32: cute.Tensor, value_scale: Float32) -> Uint64:
|
|
|
+ """Quantize 16 float32 values to FP4 and pack into uint64."""
|
|
|
+ t025 = value_scale * Float32(0.25)
|
|
|
+ t075 = value_scale * Float32(0.75)
|
|
|
+ t125 = value_scale * Float32(1.25)
|
|
|
+ t175 = value_scale * Float32(1.75)
|
|
|
+ t250 = value_scale * Float32(2.5)
|
|
|
+ t350 = value_scale * Float32(3.5)
|
|
|
+ t500 = value_scale * Float32(5.0)
|
|
|
+ packed = Uint64(0)
|
|
|
+ for i in cutlass.range_constexpr(16):
|
|
|
+ q = y_f32[i]
|
|
|
+ mag = fabs_f32(q)
|
|
|
+ nibble = Uint8(0)
|
|
|
+ if mag > t025 and mag < t075:
|
|
|
+ nibble = Uint8(1)
|
|
|
+ elif mag >= t075 and mag <= t125:
|
|
|
+ nibble = Uint8(2)
|
|
|
+ elif mag > t125 and mag < t175:
|
|
|
+ nibble = Uint8(3)
|
|
|
+ elif mag >= t175 and mag <= t250:
|
|
|
+ nibble = Uint8(4)
|
|
|
+ elif mag > t250 and mag < t350:
|
|
|
+ nibble = Uint8(5)
|
|
|
+ elif mag >= t350 and mag <= t500:
|
|
|
+ nibble = Uint8(6)
|
|
|
+ elif mag > t500:
|
|
|
+ nibble = Uint8(7)
|
|
|
+ if nibble != Uint8(0) and q < Float32(0.0):
|
|
|
+ nibble = nibble | Uint8(0x8)
|
|
|
+ packed = packed | (Uint64(nibble) << Uint64(i * 4))
|
|
|
+ return packed
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def quantize_and_pack_16_fast(y_f32: cute.Tensor, inv_scale: Float32) -> Uint64:
|
|
|
+ """Fast approximate FP4 quantize/pack for 16 float32 values."""
|
|
|
+ q = cute.make_rmem_tensor((16,), Float32)
|
|
|
+ for i in cutlass.range_constexpr(16):
|
|
|
+ q[i] = y_f32[i] * inv_scale
|
|
|
+
|
|
|
+ packed_lo = cvt_e2m1x8_f32(q[0], q[1], q[2], q[3], q[4], q[5], q[6], q[7])
|
|
|
+ packed_hi = cvt_e2m1x8_f32(q[8], q[9], q[10], q[11], q[12], q[13], q[14], q[15])
|
|
|
+ return (Uint64(packed_hi) << Uint64(32)) | Uint64(packed_lo)
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def quantize_block_fp4(
|
|
|
+ values: cute.Tensor, max_abs: Float32, global_scale_val: Float32,
|
|
|
+) -> Tuple[Uint64, cutlass.Uint8]:
|
|
|
+ """Quantize 16 float32 values to packed FP4 + e4m3 scale byte.
|
|
|
+
|
|
|
+ Given 16 values and their pre-computed max_abs, derives the NVFP4 block
|
|
|
+ scale, quantizes to FP4, and packs into a uint64. Returns
|
|
|
+ (packed_fp4_u64, scale_byte). The caller handles storage writes.
|
|
|
+ """
|
|
|
+ scale_float = max_abs * global_scale_val / Float32(FLOAT4_E2M1_MAX)
|
|
|
+ scale_float = fmin_f32(scale_float, Float32(FLOAT8_E4M3_MAX))
|
|
|
+ scale_u32 = cvt_f32_to_e4m3(scale_float)
|
|
|
+ scale_byte = cutlass.Uint8(scale_u32 & Uint32(0xFF))
|
|
|
+ quantized_scale = fp8_e4m3_to_f32(scale_u32)
|
|
|
+ packed64 = Uint64(0)
|
|
|
+ if quantized_scale != Float32(0.0) and global_scale_val != Float32(0.0):
|
|
|
+ packed64 = quantize_and_pack_16(values, quantized_scale / global_scale_val)
|
|
|
+ return packed64, scale_byte
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def quantize_block_fp4_fast(
|
|
|
+ values: cute.Tensor, max_abs: Float32, global_scale_val: Float32,
|
|
|
+) -> Tuple[Uint64, cutlass.Uint8]:
|
|
|
+ """Fast approximate FP4 block quantization using reciprocal/vector path."""
|
|
|
+ scale_u32 = Uint32(0)
|
|
|
+ scale_byte = cutlass.Uint8(0)
|
|
|
+ packed64 = Uint64(0)
|
|
|
+ if global_scale_val != Float32(0.0):
|
|
|
+ fp4_max_rcp = rcp_approx_ftz(Float32(FLOAT4_E2M1_MAX))
|
|
|
+ scale_float = global_scale_val * (max_abs * fp4_max_rcp)
|
|
|
+ scale_float = fmin_f32(scale_float, Float32(FLOAT8_E4M3_MAX))
|
|
|
+ scale_u32 = cvt_f32_to_e4m3(scale_float)
|
|
|
+ scale_byte = cutlass.Uint8(scale_u32 & Uint32(0xFF))
|
|
|
+ inv_quantized_scale = fp8_e4m3_to_f32_and_rcp(scale_u32)
|
|
|
+ if inv_quantized_scale != Float32(0.0):
|
|
|
+ packed64 = quantize_and_pack_16_fast(values, inv_quantized_scale * global_scale_val)
|
|
|
+ return packed64, scale_byte
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def max_abs_16(values: cute.Tensor) -> Float32:
|
|
|
+ """Compute the maximum absolute value of 16 float32 values."""
|
|
|
+ result = fabs_f32(values[0])
|
|
|
+ for i in cutlass.range_constexpr(1, 16):
|
|
|
+ result = fmax_f32(result, fabs_f32(values[i]))
|
|
|
+ return result
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def silu_mul_16(
|
|
|
+ gate: cute.Tensor, up: cute.Tensor,
|
|
|
+) -> cute.Tensor:
|
|
|
+ """Fused SiLU(gate) * up for 16 float32 element pairs.
|
|
|
+
|
|
|
+ Used in the CompactMoEKernel epilogue to fuse the activation function
|
|
|
+ between GEMM1 and GEMM2, avoiding the gmem round-trip.
|
|
|
+ """
|
|
|
+ out = cute.make_rmem_tensor((16,), Float32)
|
|
|
+ for i in cutlass.range_constexpr(16):
|
|
|
+ g = gate[i]
|
|
|
+ sigmoid_g = cute.arch.rcp_approx(
|
|
|
+ Float32(1.0) + cute.math.exp(-g, fastmath=False)
|
|
|
+ )
|
|
|
+ out[i] = g * sigmoid_g * up[i]
|
|
|
+ return out
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def silu_mul_quantize_block_fp4(
|
|
|
+ gate: cute.Tensor, up: cute.Tensor, global_scale_val: Float32,
|
|
|
+) -> Tuple[Uint64, cutlass.Uint8]:
|
|
|
+ """Fused SiLU(gate)*up + FP4 quantize for 16 element pairs.
|
|
|
+
|
|
|
+ Combines silu_mul_16, max_abs_16, and quantize_block_fp4 into a single
|
|
|
+ helper for the CompactMoEKernel epilogue.
|
|
|
+ """
|
|
|
+ activated = silu_mul_16(gate, up)
|
|
|
+ block_max = max_abs_16(activated)
|
|
|
+ return quantize_block_fp4(activated, block_max, global_scale_val)
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def relu2_16(x: cute.Tensor) -> cute.Tensor:
|
|
|
+ """Compute ReLU^2 for 16 float32 values."""
|
|
|
+ out = cute.make_rmem_tensor((16,), Float32)
|
|
|
+ for i in cutlass.range_constexpr(16):
|
|
|
+ v = fmax_f32(x[i], Float32(0.0))
|
|
|
+ out[i] = v * v
|
|
|
+ return out
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def relu2_quantize_block_fp4(
|
|
|
+ x: cute.Tensor, global_scale_val: Float32,
|
|
|
+) -> Tuple[Uint64, cutlass.Uint8]:
|
|
|
+ """Fuse ReLU^2 and FP4 quantization for 16 float32 values."""
|
|
|
+ activated = relu2_16(x)
|
|
|
+ block_max = max_abs_16(activated)
|
|
|
+ return quantize_block_fp4(activated, block_max, global_scale_val)
|
|
|
+
|
|
|
+
|
|
|
+# =============================================================================
|
|
|
+# MXFP8 (w4a8) Block Quantization: 32-wide UE8M0 + E4M3
|
|
|
+# =============================================================================
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def max_abs_32(values: cute.Tensor) -> Float32:
|
|
|
+ """Compute the maximum absolute value of 32 float32 values."""
|
|
|
+ result = fabs_f32(values[0])
|
|
|
+ for i in cutlass.range_constexpr(1, 32):
|
|
|
+ result = fmax_f32(result, fabs_f32(values[i]))
|
|
|
+ return result
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def quantize_block_fp8_mx(
|
|
|
+ values: cute.Tensor, max_abs: Float32,
|
|
|
+) -> Tuple[cute.Tensor, Uint32]:
|
|
|
+ """Quantize 32 float32 values to E4M3 payload bytes + UE8M0 scale byte.
|
|
|
+
|
|
|
+ The scale is ``pow2_ceil(max_abs / 448)`` so the scaled payload never
|
|
|
+ exceeds the E4M3 range; the payload carries its own exponent, so the
|
|
|
+ power-of-two block scale handles range only. No global scale is
|
|
|
+ involved (the MX scheme is self-ranging). Returns
|
|
|
+ ``(payload_u32x8, ue8m0_byte)`` with value ``4*i + j`` in byte ``j`` of
|
|
|
+ ``payload[i]``. An all-zero block yields scale byte 0 (decoded as a
|
|
|
+ zero output scale) and a zero payload.
|
|
|
+ """
|
|
|
+ scale_f = max_abs * Float32(1.0 / FLOAT8_E4M3_MAX)
|
|
|
+ _, scale_byte = pow2_ceil_ue8m0(scale_f)
|
|
|
+ inv_scale = ue8m0_to_output_scale(scale_byte)
|
|
|
+ payload = cute.make_rmem_tensor((8,), Uint32)
|
|
|
+ for i in cutlass.range_constexpr(8):
|
|
|
+ payload[i] = cvt_f32x4_to_e4m3x4(
|
|
|
+ values[i * 4 + 0] * inv_scale,
|
|
|
+ values[i * 4 + 1] * inv_scale,
|
|
|
+ values[i * 4 + 2] * inv_scale,
|
|
|
+ values[i * 4 + 3] * inv_scale,
|
|
|
+ )
|
|
|
+ return payload, scale_byte
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def silu_mul_32(
|
|
|
+ gate: cute.Tensor, up: cute.Tensor,
|
|
|
+) -> cute.Tensor:
|
|
|
+ """Fused SiLU(gate) * up for 32 float32 element pairs."""
|
|
|
+ out = cute.make_rmem_tensor((32,), Float32)
|
|
|
+ for i in cutlass.range_constexpr(32):
|
|
|
+ g = gate[i]
|
|
|
+ sigmoid_g = cute.arch.rcp_approx(
|
|
|
+ Float32(1.0) + cute.math.exp(-g, fastmath=False)
|
|
|
+ )
|
|
|
+ out[i] = g * sigmoid_g * up[i]
|
|
|
+ return out
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def silu_mul_quantize_block_fp8_mx(
|
|
|
+ gate: cute.Tensor, up: cute.Tensor,
|
|
|
+) -> Tuple[cute.Tensor, Uint32]:
|
|
|
+ """Fused SiLU(gate)*up + MXFP8 quantize for 32 element pairs."""
|
|
|
+ activated = silu_mul_32(gate, up)
|
|
|
+ block_max = max_abs_32(activated)
|
|
|
+ return quantize_block_fp8_mx(activated, block_max)
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def relu2_32(x: cute.Tensor) -> cute.Tensor:
|
|
|
+ """Compute ReLU^2 for 32 float32 values."""
|
|
|
+ out = cute.make_rmem_tensor((32,), Float32)
|
|
|
+ for i in cutlass.range_constexpr(32):
|
|
|
+ v = fmax_f32(x[i], Float32(0.0))
|
|
|
+ out[i] = v * v
|
|
|
+ return out
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def relu2_quantize_block_fp8_mx(
|
|
|
+ x: cute.Tensor,
|
|
|
+) -> Tuple[cute.Tensor, Uint32]:
|
|
|
+ """Fuse ReLU^2 and MXFP8 quantization for 32 float32 values."""
|
|
|
+ activated = relu2_32(x)
|
|
|
+ block_max = max_abs_32(activated)
|
|
|
+ return quantize_block_fp8_mx(activated, block_max)
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def mx_scale_from_amax32(amax: Float32) -> Tuple[Float32, Float32]:
|
|
|
+ """Per-32 MXFP8 block scale from a precomputed amax.
|
|
|
+
|
|
|
+ Returns ``(scale, inv_scale)`` with ``scale = pow2_ceil(amax/448)`` —
|
|
|
+ identical numerics to ``quantize_block_fp8_mx`` (zero amax yields
|
|
|
+ (0, 0), silencing the block).
|
|
|
+ """
|
|
|
+ rounded, byte = pow2_ceil_ue8m0(amax * Float32(1.0 / FLOAT8_E4M3_MAX))
|
|
|
+ inv_scale = ue8m0_to_output_scale(byte)
|
|
|
+ return rounded, inv_scale
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def quant_dequant_e4m3_2(
|
|
|
+ v0: Float32, v1: Float32, inv_scale: Float32, scale: Float32,
|
|
|
+) -> Tuple[Float32, Float32]:
|
|
|
+ """Quantize-dequantize a pair through E4M3 with a power-of-two block scale.
|
|
|
+
|
|
|
+ Bit-matches the w4a8 prefill activation quantizer
|
|
|
+ (``quantize_block_fp8_mx``): RN-saturating E4M3 of ``v*inv_scale``,
|
|
|
+ decoded back and rescaled. Used by the micro decode kernel's a8_mx mode
|
|
|
+ so decode numerics track the prefill recipe.
|
|
|
+ """
|
|
|
+ q0 = fp8_e4m3_to_f32(cvt_f32_to_e4m3(v0 * inv_scale)) * scale
|
|
|
+ q1 = fp8_e4m3_to_f32(cvt_f32_to_e4m3(v1 * inv_scale)) * scale
|
|
|
+ return q0, q1
|
|
|
+
|
|
|
+
|
|
|
+# =============================================================================
|
|
|
+# Helper Functions for Float32 SF Block Processing
|
|
|
+# =============================================================================
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def load_f32_16_from_smem(
|
|
|
+ sH: cute.Tensor, row_idx: Int32, col_offset: Int32
|
|
|
+) -> cute.Tensor:
|
|
|
+ """Load 16 Float32 values from shared memory."""
|
|
|
+ h_f32 = cute.make_rmem_tensor((16,), Float32)
|
|
|
+ for i in cutlass.range_constexpr(16):
|
|
|
+ h_f32[i] = Float32(sH[row_idx, col_offset + i])
|
|
|
+ return h_f32
|
|
|
+
|
|
|
+
|
|
|
+@cute.jit
|
|
|
+def compute_y_and_max_abs_f32(
|
|
|
+ h_f32: cute.Tensor, w_f32: cute.Tensor, rstd: Float32
|
|
|
+) -> Tuple[cute.Tensor, Float32]:
|
|
|
+ """Compute y = h * rstd * w and max_abs for 16 Float32 values."""
|
|
|
+ y_f32 = cute.make_rmem_tensor((16,), Float32)
|
|
|
+
|
|
|
+ # Compute y and track max_abs
|
|
|
+ y_f32[0] = h_f32[0] * rstd * w_f32[0]
|
|
|
+ max_abs = fabs_f32(y_f32[0])
|
|
|
+
|
|
|
+ for i in cutlass.range_constexpr(1, 16):
|
|
|
+ y_f32[i] = h_f32[i] * rstd * w_f32[i]
|
|
|
+ max_abs = fmax_f32(max_abs, fabs_f32(y_f32[i]))
|
|
|
+
|
|
|
+ return y_f32, max_abs
|