Files
sglang/test/registered/quant/test_fused_rms_fp8_group_quant.py
Bingxu Chen 2ea02f0642 [AMD CI] Migrate and Add More Testcases (#17116)
Co-authored-by: yctseng0211 <yctseng@amd.com>
2026-01-19 08:07:39 -08:00

151 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import itertools
import unittest
import torch
import torch.nn.functional as F
from sglang.test.ci.ci_register import register_amd_ci
from sglang.test.test_utils import CustomTestCase
register_amd_ci(est_time=10, suite="stage-a-test-1-amd")
def _fp8_available() -> bool:
# requirement1) GPU2) ROCm3) torch support float8_e4m3fn
if not torch.cuda.is_available():
return False
if getattr(torch.version, "hip", None) is None:
return False
return hasattr(torch, "float8_e4m3fn")
def _rmsnorm(x, weight, eps=1e-6):
# row-wise RMSNorm
row_norm = (x * x).sum(dim=-1)
norm = torch.rsqrt(row_norm / x.shape[1] + eps)
return x * norm[:, None] * weight[None, :]
def _per_token_fp8_group_quant(x, dtype_quant, group_size=128):
"""per token、group-size quant, return (quantized, scale)。"""
DTYPE_MAX = torch.finfo(dtype_quant).max
M, N = x.shape
pad = (group_size - (N % group_size)) % group_size
if pad:
x_reshape = F.pad(x, (0, pad, 0, 0), "constant", 0)
else:
x_reshape = x
G = (N + group_size - 1) // group_size
x_reshape = x_reshape.view(M, G, group_size).to(torch.float32)
x_max = torch.max(torch.abs(x_reshape), dim=-1, keepdim=True)[0].clamp_min_(1e-10)
x_scale = x_max / DTYPE_MAX
inv = 1.0 / x_scale
x_q = torch.clamp(x_reshape * inv, -DTYPE_MAX, DTYPE_MAX).to(dtype_quant)
x_q = x_q.view(M, G * group_size)
if pad:
x_q = x_q[:, :N]
x_scale = x_scale.squeeze(-1) # [M, G]
return x_q, x_scale
def _upcast_fp8_group(x_q, x_s, out_dtype=torch.float32, group_size=128):
"""unqaunt"""
M, N = x_q.shape
G = (N + group_size - 1) // group_size
pad = (group_size - (N % group_size)) % group_size
if pad:
x_q = F.pad(x_q, (0, pad, 0, 0), "constant", 0)
x_q = x_q.view(M, G, group_size).to(torch.float32)
x = x_q * x_s.view(M, G, 1)
x = x.view(M, G * group_size)[:, :N]
return x.to(out_dtype)
class TestFusedRMSFP8GroupQuant(CustomTestCase):
#
DTYPES = [torch.bfloat16, torch.float16]
# (M, N1, N2)
SHAPES = [(32, 128, 7168), (128, 7168, 7168)]
GROUP_SIZE = [128]
SEEDS = [0]
@classmethod
def setUpClass(cls):
if not _fp8_available():
raise unittest.SkipTest("Skip: ROCm/FP8 is not available")
torch.set_default_device("cuda")
def _run_ref(self, x1, w1, eps1, x2, w2, eps2, res1, dtype_quant, group_size):
s = x1 + (res1 if res1 is not None else 0)
y1 = _rmsnorm(s, w1, eps1)
y2 = _rmsnorm(x2, w2, eps2) if x2 is not None else None
y1_q, y1_s = _per_token_fp8_group_quant(y1, dtype_quant, group_size)
return (
(y1_q, y1_s),
y1.to(x1.dtype),
(y2.to(x1.dtype) if y2 is not None else None),
(s.to(x1.dtype) if res1 is not None else None),
)
def _case(self, M, N1, N2, group_size, dtype, seed):
torch.manual_seed(seed)
fp8 = torch.float8_e4m3fn
device = "cuda"
x1 = torch.randn(M, N1, dtype=dtype, device=device) / 10
x2 = torch.randn(M, N2, dtype=dtype, device=device) / 10
w1 = torch.ones(N1, dtype=torch.float32, device=device)
w2 = torch.ones(N2, dtype=torch.float32, device=device)
res1 = torch.randn(M, N1, dtype=dtype, device=device) / 10
# ref
(y1_q_ref, y1_s_ref), y1_ref, y2_ref, s_ref = self._run_ref(
x1, w1, 1e-6, x2, w2, 1e-6, res1, fp8, group_size
)
# be testedaiter fused op
from aiter.ops.triton.fused_fp8_quant import fused_rms_fp8_group_quant
(y1_q, y1_s), y1, y2, s = fused_rms_fp8_group_quant(
x1,
w1,
1e-6,
inp2=x2,
inp2_weight=w2,
inp2_epsilon=1e-6,
group_size=group_size,
dtype_quant=fp8,
res1=res1,
output_unquantized_inp1=True, # get unqaunt y1
)
torch.testing.assert_close(y1, y1_ref, atol=0.1, rtol=0.1)
torch.testing.assert_close(y2, y2_ref, atol=0.1, rtol=0.1)
torch.testing.assert_close(s, s_ref, atol=0.1, rtol=0.1)
# check unquant
y1_up_ref = _upcast_fp8_group(
y1_q_ref, y1_s_ref, out_dtype=torch.float32, group_size=group_size
)
y1_up = _upcast_fp8_group(
y1_q, y1_s, out_dtype=torch.float32, group_size=group_size
)
torch.testing.assert_close(y1_up, y1_up_ref, atol=0.1, rtol=0.1)
def test_fused_rms_fp8_group_quant(self):
for params in itertools.product(
self.SHAPES, self.GROUP_SIZE, self.DTYPES, self.SEEDS
):
(M, N1, N2), g, dtype, seed = params
with self.subTest(M=M, N1=N1, N2=N2, group_size=g, dtype=dtype, seed=seed):
self._case(M, N1, N2, g, dtype, seed)
if __name__ == "__main__":
unittest.main(verbosity=2)