Files
sglang/python/sglang/srt/layers/quantization/kvfp4_tensor.py
Ho-Ren (Jack) Chuang 76196b3cbf feat: Add FP4 (E2M1) KV Cache Support with Quantization Utilities for MLA (#10078)
Signed-off-by: Ho-Ren (Jack) Chuang <horenchuang@bytedance.com>
Co-authored-by: Yichen Wang <yichen.wang@bytedance.com>
2025-11-01 22:24:58 -07:00

113 lines
3.8 KiB
Python

# Copyright 2025 SGLang 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.
# ==============================================================================
import torch
E2M1_MAX = 6.0
# Put constants directly on CUDA if available
_device = "cuda" if torch.cuda.is_available() else "cpu"
E2M1_VALUES = torch.tensor(
[0, 0.5, 1, 1.5, 2, 3, 4, 6], dtype=torch.float32, device=_device
)
E2M1_BOUNDS = torch.tensor(
[0.25, 0.75, 1.25, 1.75, 2.5, 3.5, 5], dtype=torch.float32, device=_device
)
class KVFP4QuantizeUtil:
"""Utility class for MXFP4 quantization and dequantization operations."""
@staticmethod
@torch.compile
def batched_quantize(tensor: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
"""
Quantize tensor to KVFP4 format
Args:
tensor: Input tensor of shape [B, M, N]
Returns:
quant_tensor: Quantized tensor of shape [B, M, N/2]
scale_factors: Scale factors of shape [B, M*N/16]
"""
b, m, n = tensor.shape
# Reshape to [B, M*N/16, 16] for block-wise quantization
reshaped = tensor.view(b, m * n // 16, 16)
# Compute scale factors per block
block_max = reshaped.abs().max(dim=-1, keepdim=True).values
scale_exp = torch.ceil(torch.log2(torch.clamp(block_max / E2M1_MAX, min=1e-10)))
scale_factors = (scale_exp + 127).squeeze(-1).to(torch.uint8)
# Apply scaling
scaled = reshaped / torch.exp2(scale_exp)
# Quantize to FP4
sign_bits = (scaled < 0).to(torch.uint8) << 3
abs_vals = scaled.abs()
# Pure tensor version (CUDA Graph safe)
magnitude_bits = torch.sum(abs_vals.unsqueeze(-1) >= E2M1_BOUNDS, dim=-1)
# Combine sign and magnitude
fp4_vals = sign_bits + magnitude_bits.to(torch.uint8)
# Pack two FP4 values into one uint8
fp4_reshaped = fp4_vals.view(b, m, n)
packed = (fp4_reshaped[..., 1::2] << 4) + fp4_reshaped[..., 0::2]
return packed, scale_factors
@staticmethod
@torch.compile
def batched_dequantize(
quant_tensor: torch.Tensor,
scale_factors: torch.Tensor,
dtype: torch.dtype = torch.bfloat16,
) -> torch.Tensor:
"""
Dequantize KVFP4 tensor
Args:
quant_tensor: Quantized tensor of shape [B, M, N/2]
scale_factors: Scale factors of shape [B, M*N/16]
dtype: Target dtype for output
Returns:
Dequantized tensor of shape [B, M, N]
"""
b, m, n_half = quant_tensor.shape
n = n_half * 2
# More efficient unpacking using bit operations
fp4_vals = torch.empty(b, m, n, dtype=torch.uint8, device=quant_tensor.device)
fp4_vals[..., 0::2] = quant_tensor & 0x0F
fp4_vals[..., 1::2] = (quant_tensor >> 4) & 0x0F
# Extract sign and magnitude
sign_mask = (fp4_vals & 0x08) != 0
magnitude_idx = fp4_vals & 0x07
# Convert to float values
float_vals = E2M1_VALUES[magnitude_idx.long()]
float_vals = torch.where(sign_mask, -float_vals, float_vals)
# Reshape for block-wise scaling
reshaped = float_vals.view(b, m * n // 16, 16)
# Apply scale factors
scale_exp = scale_factors.float() - 127
scaled = reshaped * torch.exp2(scale_exp.unsqueeze(-1))
return scaled.view(b, m, n).to(dtype)