flashrt-smallm-gemm / benchmarks /benchmark_nvfp4_w4a4_decode_matvec.py
liangsu9988's picture
Uploaded using `kernel-builder`.
fcc5ab1 verified
Raw
History Blame
6.16 kB
import torch
from kernels.benchmark import Benchmark
_original_allclose = torch.allclose
def _bf16_max_ulp(input: torch.Tensor, other: torch.Tensor) -> int:
got_bits = input.detach().cpu().view(torch.int16).to(torch.int32) & 0xFFFF
exp_bits = other.detach().cpu().view(torch.int16).to(torch.int32) & 0xFFFF
got_ordered = torch.where((got_bits & 0x8000) != 0, 0x8000 - (got_bits & 0x7FFF), got_bits)
exp_ordered = torch.where((exp_bits & 0x8000) != 0, 0x8000 - (exp_bits & 0x7FFF), exp_bits)
return int((got_ordered - exp_ordered).abs().max().item())
def _flashrt_allclose(input, other, rtol=1e-05, atol=1e-08, equal_nan=False):
if input.dtype == torch.bfloat16 and other.dtype == torch.bfloat16:
return _bf16_max_ulp(input, other) <= 5
return _original_allclose(input, other, rtol=rtol, atol=atol, equal_nan=equal_nan)
torch.allclose = _flashrt_allclose
DECODE_SHAPES = [
("k4096_n1024", 4096, 1024),
("k4096_n4096", 4096, 4096),
("k4096_n12288", 4096, 12288),
("k12288_n1024", 12288, 1024),
("k12288_n4096", 12288, 4096),
("k12288_n12288", 12288, 12288),
]
def _swizzled_bytes(rows: int, cols: int) -> int:
n_blocks = cols // 16
return ((rows + 127) // 128) * ((n_blocks + 3) // 4) * 512
def _swizzle_constant_scale(rows: int, cols: int, value: int) -> torch.Tensor:
return torch.full((_swizzled_bytes(rows, cols),), value, dtype=torch.uint8)
def _reference_swizzle(scales: torch.Tensor) -> torch.Tensor:
rows, n_blocks = scales.shape
n_col_super = (n_blocks + 3) // 4
src = scales.cpu()
out = torch.zeros(
((rows + 127) // 128) * n_col_super * 512,
dtype=torch.uint8,
)
for row in range(rows):
rb = row // 128
ri = row % 128
for block in range(n_blocks):
cb = block // 4
ci = block % 4
super_idx = rb * n_col_super + cb
inner_off = (ri % 32) * 16 + (ri // 32) * 4 + ci
out[super_idx * 512 + inner_off] = src[row, block]
return out
def _ue4m3_to_float(byte: int) -> float:
sign = -1.0 if (byte & 0x80) else 1.0
exp = (byte >> 3) & 0x0F
mant = byte & 0x07
if exp == 0:
return sign * (mant / 8.0) * (2.0 ** -6)
if exp == 15 and mant == 7:
return 0.0
return sign * (1.0 + mant / 8.0) * (2.0 ** (exp - 7))
def _ue4m3_lut() -> torch.Tensor:
return torch.tensor([_ue4m3_to_float(i) for i in range(256)], dtype=torch.float32)
def _fp4_codebook() -> torch.Tensor:
return torch.tensor(
[
0.0,
0.5,
1.0,
1.5,
2.0,
3.0,
4.0,
6.0,
-0.0,
-0.5,
-1.0,
-1.5,
-2.0,
-3.0,
-4.0,
-6.0,
],
dtype=torch.float32,
)
def _unpack_fp4(packed: torch.Tensor) -> torch.Tensor:
codebook = _fp4_codebook().to(packed.device)
lo = packed & 0x0F
hi = packed >> 4
out = torch.empty(
(packed.shape[0], packed.shape[1] * 2),
device=packed.device,
dtype=torch.float32,
)
out[:, 0::2] = codebook[lo.long()]
out[:, 1::2] = codebook[hi.long()]
return out
def _reference_smallm(
a_packed: torch.Tensor,
b_packed: torch.Tensor,
sfa_linear: torch.Tensor,
sfb_linear: torch.Tensor,
K: int,
alpha: float,
chunk_rows: int = 256,
) -> torch.Tensor:
device = b_packed.device
N = b_packed.shape[0]
lut = _ue4m3_lut().to(device)
a = _unpack_fp4(a_packed.reshape(1, -1)).reshape(K)
a_scale = lut[sfa_linear.reshape(-1).to(device).long()].repeat_interleave(16)
a = a * a_scale
sfb_linear = sfb_linear.to(device)
out = torch.empty((N,), device=device, dtype=torch.bfloat16)
for start in range(0, N, chunk_rows):
end = min(start + chunk_rows, N)
b = _unpack_fp4(b_packed[start:end])
b_scale = lut[sfb_linear[start:end].long()].repeat_interleave(16, dim=1)
expected = (b * b_scale * a.reshape(1, K)).sum(dim=1) * alpha
out[start:end] = expected.to(torch.bfloat16)
return out
class Nvfp4W4A4DecodeMatvecBenchmark(Benchmark):
seed = 23
def _setup_shape(self, K: int, N: int) -> None:
torch.manual_seed(600 + K + N)
if torch.cuda.is_available():
torch.cuda.manual_seed_all(600 + K + N)
self.K = K
self.N = N
self.alpha = 0.5
self.a_packed = torch.randint(
0, 256, (K // 2,), device=self.device, dtype=torch.uint8
)
self.b_packed = torch.randint(
0, 256, (N, K // 2), device=self.device, dtype=torch.uint8
)
self.sfa_linear = torch.randint(0, 0x78, (1, K // 16), dtype=torch.uint8)
self.sfb_linear = torch.randint(0, 0x78, (N, K // 16), dtype=torch.uint8)
self.sfa = _reference_swizzle(self.sfa_linear).to(self.device)
self.sfb = _reference_swizzle(self.sfb_linear).to(self.device)
self.out = torch.empty((N,), device=self.device, dtype=torch.bfloat16)
def _benchmark(self) -> None:
self.kernel.nvfp4_w4a4_decode_matvec_bf16out(
self.a_packed,
self.b_packed,
self.sfa,
self.sfb,
alpha=self.alpha,
out=self.out,
)
def _reference(self) -> torch.Tensor:
return _reference_smallm(
self.a_packed,
self.b_packed,
self.sfa_linear,
self.sfb_linear,
self.K,
self.alpha,
)
def _register_shapes() -> None:
for label, K, N in DECODE_SHAPES:
def setup(self, K=K, N=N) -> None:
self._setup_shape(K, N)
def benchmark(self) -> None:
self._benchmark()
def verify(self) -> torch.Tensor:
return self._reference()
setattr(Nvfp4W4A4DecodeMatvecBenchmark, f"setup_{label}", setup)
setattr(Nvfp4W4A4DecodeMatvecBenchmark, f"benchmark_{label}", benchmark)
setattr(Nvfp4W4A4DecodeMatvecBenchmark, f"verify_{label}", verify)
_register_shapes()