from __future__ import annotations import torch import triton import triton.language as tl @triton.jit def _select_group_value(group, v0, v1, v2, v3): return tl.where( group == 0, v0, tl.where(group == 1, v1, tl.where(group == 2, v2, v3)), ) @triton.jit def _ceil_ue8m0_exp(x): bits = x.to(tl.int32, bitcast=True) exp = (bits >> 23) & 0xFF mantissa = bits & 0x7FFFFF exp += mantissa != 0 return tl.minimum(tl.maximum(exp, 1), 254) @triton.jit def _fp4_e2m1_code(x): ax = tl.minimum(tl.abs(x), 6.0) idx = (ax > 0.25).to(tl.uint8) idx += (ax > 0.75).to(tl.uint8) idx += (ax > 1.25).to(tl.uint8) idx += (ax > 1.75).to(tl.uint8) idx += (ax > 2.5).to(tl.uint8) idx += (ax > 3.5).to(tl.uint8) idx += (ax > 5.0).to(tl.uint8) sign = ((x < 0) & (idx != 0)).to(tl.uint8) return idx | (sign << 3) @triton.jit def _quantize_fp4_indexer_kernel( x, x_fp4, x_sf, BLOCK_N: tl.constexpr, GROUP_N: tl.constexpr, ): token_id = tl.program_id(0) offs = tl.arange(0, BLOCK_N) values = tl.load(x + token_id * BLOCK_N + offs).to(tl.float32) abs_values = tl.abs(values) amax0 = tl.max(tl.where(offs < GROUP_N, abs_values, 0.0), axis=0) amax1 = tl.max( tl.where((GROUP_N <= offs) & (offs < 2 * GROUP_N), abs_values, 0.0), axis=0, ) amax2 = tl.max( tl.where((2 * GROUP_N <= offs) & (offs < 3 * GROUP_N), abs_values, 0.0), axis=0, ) amax3 = tl.max(tl.where(3 * GROUP_N <= offs, abs_values, 0.0), axis=0) sf0 = tl.maximum(amax0 / 6.0, 1.0e-4) sf1 = tl.maximum(amax1 / 6.0, 1.0e-4) sf2 = tl.maximum(amax2 / 6.0, 1.0e-4) sf3 = tl.maximum(amax3 / 6.0, 1.0e-4) exp0 = _ceil_ue8m0_exp(sf0) exp1 = _ceil_ue8m0_exp(sf1) exp2 = _ceil_ue8m0_exp(sf2) exp3 = _ceil_ue8m0_exp(sf3) packed_sf = exp0 | (exp1 << 8) | (exp2 << 16) | (exp3 << 24) tl.store(x_sf + token_id, packed_sf) pair_offsets = tl.arange(0, BLOCK_N // 2) offs0 = pair_offsets * 2 offs1 = offs0 + 1 group0 = offs0 // GROUP_N group1 = offs1 // GROUP_N scale_exp0 = _select_group_value(group0, exp0, exp1, exp2, exp3) scale_exp1 = _select_group_value(group1, exp0, exp1, exp2, exp3) scale0 = (scale_exp0 << 23).to(tl.float32, bitcast=True) scale1 = (scale_exp1 << 23).to(tl.float32, bitcast=True) v0 = tl.load(x + token_id * BLOCK_N + offs0).to(tl.float32) / scale0 v1 = tl.load(x + token_id * BLOCK_N + offs1).to(tl.float32) / scale1 code0 = _fp4_e2m1_code(v0) code1 = _fp4_e2m1_code(v1) packed = (code0 & 0x0F) | ((code1 & 0x0F) << 4) tl.store(x_fp4 + token_id * (BLOCK_N // 2) + pair_offsets, packed) @triton.jit def _store_fp4_index_k_cache_kernel( k_fp4, k_sf, cache, loc, page_size: tl.constexpr, cache_stride: tl.constexpr, BLOCK: tl.constexpr, ): token_id = tl.program_id(0) offsets = tl.arange(0, BLOCK) cache_loc = tl.load(loc + token_id) page = cache_loc // page_size page_offset = cache_loc - page * page_size k = tl.load(k_fp4 + token_id * BLOCK + offsets) tl.store(cache + page * cache_stride + page_offset * BLOCK + offsets, k) sf = tl.load(k_sf + token_id) sf_offsets = tl.arange(0, 4) sf_bytes = (sf >> (sf_offsets * 8)) & 0xFF tl.store( cache + page * cache_stride + page_size * BLOCK + page_offset * 4 + sf_offsets, sf_bytes, ) def quantize_fp4_indexer_tensor(x: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: assert x.shape[-1] == 128 x = x.contiguous().view(-1, x.shape[-1]) x_fp4 = torch.empty((x.shape[0], 64), device=x.device, dtype=torch.int8) x_sf = torch.empty((x.shape[0],), device=x.device, dtype=torch.int32) if x.shape[0] > 0: _quantize_fp4_indexer_kernel[(x.shape[0],)]( x, x_fp4, x_sf, BLOCK_N=128, GROUP_N=32, ) return x_fp4, x_sf def store_fp4_index_k_cache( input: torch.Tensor, cache: torch.Tensor, loc: torch.Tensor, *, page_size: int, ) -> None: assert input.shape[-1] == 128 k_fp4, k_sf = quantize_fp4_indexer_tensor(input.contiguous()) n_tokens = input.numel() // input.shape[-1] assert k_fp4.shape == (n_tokens, 64) assert k_sf.shape == (n_tokens,) assert cache.shape[1] == page_size * (64 + 4) if n_tokens == 0: return _store_fp4_index_k_cache_kernel[(n_tokens,)]( k_fp4.view(torch.uint8), k_sf, cache, loc, page_size, cache.stride(0), BLOCK=64, )