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()