Files
wehub-resource-sync 94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:38:16 +08:00

274 lines
9.2 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.
# ==============================================================================
# Define a enum class for FP4 formats, including MXFP4, NVFP4 and future formats
from enum import Enum
import torch
class FP4KVCacheRecipe(Enum):
MXFP4 = 1 # KVFP4: block-wise scaling
NVFP4 = 2 # two-level scaling: global FP32 + block FP8 E4M3
E2M1_MAX = 6.0
MAX_BLOCK_SCALE_FP8 = 448.0 # Maximum FP8 E4M3 value
# Put constants directly on CUDA if available
_device = "cuda" if torch.cuda.is_available() else "cpu"
# E2M1 format: 1 sign bit + 2 exponent bits + 1 mantissa bit = 4 bits
# 16 possible values: 0x0-0xF
# Negative values: 0x8-0xF (sign bit = 1)
# Positive values: 0x0-0x7 (sign bit = 0)
E2M1_VALUES = torch.tensor(
[
0,
0.5,
1,
1.5,
2,
3,
4,
6, # 0x0-0x7: positive values
-0,
-0.5,
-1,
-1.5,
-2,
-3,
-4,
-6,
], # 0x8-0xF: negative values
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 BlockFP4KVQuantizeUtil:
"""Block-wise FP4 (E2M1) quantization for KV cache.
Similar to MXFP4 but uses block_size=16 (MXFP4 spec defines block_size=32).
Each block of 16 elements shares one uint8 exponent-only scale factor.
"""
@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)
class NVFP4KVQuantizeUtil:
"""Utility class for NVFP4 quantization and dequantization with two-level scaling
(global FP32 + block FP8 E4M3).
Quantize formula: x_fp4 * block_scale * global_scale = x_bf16
- Quantize: ``nvfp4_kv_quantize`` (SM100+), fallback ``fp4_quantize`` (SM90)
- Dequantize: ``nvfp4_kv_dequantize`` (SM100+)
"""
@staticmethod
def quantize(
tensor: torch.Tensor, global_scale: torch.Tensor
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
"""Quantize BF16/FP16 tensor to NVFP4 format.
Requires SM90+. Uses ``nvfp4_kv_quantize`` on SM100+ (native PTX),
falls back to ``fp4_quantize`` on SM90.
Args:
tensor: Input tensor of shape [B, M, N]
global_scale: Global scale factor (float32 scalar or 1-element tensor)
Returns:
(fp4_data, block_scales, global_scale):
fp4_data: shape [B, M, N/2], dtype uint8
block_scales: shape [B, M, N/16], dtype float8_e4m3fn
global_scale: passthrough
"""
from sglang.srt.utils import is_sm90_supported, is_sm100_supported
assert is_sm90_supported(), "NVFP4 KV cache quantize requires SM90+ GPU"
b, m, n = tensor.shape
tensor_2d = tensor.reshape(b * m, n)
if isinstance(global_scale, (int, float)):
global_scale = torch.tensor(
[global_scale], dtype=torch.float32, device=tensor.device
)
elif global_scale.dim() == 0:
global_scale = global_scale.unsqueeze(0)
if is_sm100_supported():
from flashinfer import nvfp4_kv_quantize
# nvfp4_kv_quantize takes global_scale directly (not inverted)
fp4_2d, scales_2d = nvfp4_kv_quantize(tensor_2d, global_scale)
else:
# SM90: fp4_quantize takes inverted global_scale
from flashinfer import fp4_quantize
global_scale_inv = 1.0 / global_scale
fp4_2d, scales_2d = fp4_quantize(
tensor_2d,
global_scale_inv,
sf_vec_size=16,
sf_use_ue8m0=False,
is_sf_swizzled_layout=False,
is_sf_8x4_layout=False,
enable_pdl=None,
)
fp4_data = fp4_2d.view(b, m, fp4_2d.shape[-1])
block_scales = scales_2d.view(b, m, scales_2d.shape[-1]).view(
torch.float8_e4m3fn
)
return fp4_data, block_scales, global_scale
@staticmethod
def dequantize(
quant_tensor: torch.Tensor,
block_scales: torch.Tensor,
global_scale: torch.Tensor,
dtype: torch.dtype = torch.bfloat16,
) -> torch.Tensor:
"""Dequantize NVFP4 tensor to BF16/FP16.
Uses ``nvfp4_kv_dequantize`` on SM100+, falls back to pure PyTorch
E2M1 LUT on SM90.
Args:
quant_tensor: Packed FP4 data of shape [B, M, N/2] (uint8)
block_scales: Per-block FP8 E4M3 scales of shape [B, M, N/16]
global_scale: Global scale factor (float32)
dtype: Output dtype (bfloat16 or float16)
Returns:
Dequantized tensor of shape [B, M, N]
"""
from sglang.srt.utils import is_sm100_supported
b, m, n_half = quant_tensor.shape
if isinstance(global_scale, (int, float)):
global_scale = torch.tensor(
[global_scale], dtype=torch.float32, device=quant_tensor.device
)
elif global_scale.dim() == 0:
global_scale = global_scale.unsqueeze(0)
if is_sm100_supported():
from flashinfer import nvfp4_kv_dequantize
quant_2d = quant_tensor.view(torch.uint8).reshape(b * m, n_half)
scales_2d = block_scales.view(torch.uint8).reshape(b * m, -1)
output_2d = nvfp4_kv_dequantize(
quant_2d, scales_2d, global_scale, output_dtype=dtype
)
return output_2d.reshape(b, m, -1)
else:
# Pure PyTorch fallback for SM90
n = n_half * 2
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
float_vals = E2M1_VALUES[fp4_vals.long()]
reshaped = float_vals.view(b, m * n // 16, 16)
block_scales_float = block_scales.float().unsqueeze(-1)
scaled = reshaped * block_scales_float
return (scaled.view(b, m, n) * global_scale).to(dtype)