[AMD] Fuse RMSNorm + FP8 per-token quant for GLM-4.7-FP8 (#21403)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jacob0226
2026-04-10 22:45:31 -07:00
committed by GitHub
co-authored by Claude Opus 4.6
parent aeeff58cd4
commit 7e4e1dcd7a
3 changed files with 149 additions and 13 deletions
+76 -8
View File
@@ -80,14 +80,68 @@ _is_gfx95_supported = is_gfx95_supported()
_is_npu = is_npu()
_use_ag_after_qlora = envs.SGLANG_USE_AG_AFTER_QLORA.get()
if _use_aiter and _is_gfx95_supported:
from aiter.ops.triton.fused_fp8_quant import fused_rms_fp8_group_quant
if _use_aiter:
from aiter.ops.rmsnorm import add_rmsnorm_quant as _aiter_add_rmsnorm_quant
from aiter.ops.rmsnorm import rmsnorm_quant as _aiter_rmsnorm_quant
from sglang.srt.layers.quantization.rocm_mxfp4_utils import fused_rms_mxfp4_quant
from sglang.srt.layers.quantization.fp8_kernel import fp8_dtype as _aiter_fp8_dtype
if _is_gfx95_supported:
from aiter.ops.triton.fused_fp8_quant import fused_rms_fp8_group_quant
from sglang.srt.layers.quantization.rocm_mxfp4_utils import (
fused_rms_mxfp4_quant,
)
elif _is_npu:
from sglang.srt.hardware_backend.npu.cmo import prepare_weight_cache
def _fused_rmsnorm_fp8_per_token_quant(
hidden_states: torch.Tensor,
weight: torch.Tensor,
epsilon: float,
residual: Optional[torch.Tensor] = None,
):
"""Fused (optional residual-add +) RMSNorm + FP8 per-token quantization.
Only used with the aiter (ROCm) backend.
Args:
residual: if provided, computes hidden_states + residual before RMSNorm
and returns updated residual_out as second element.
Returns:
If residual is None: (out_fp8, scale)
If residual provided: ((out_fp8, scale), residual_out)
"""
M, N = hidden_states.shape
out_fp8 = torch.empty((M, N), dtype=_aiter_fp8_dtype, device=hidden_states.device)
scale = torch.empty(M, dtype=torch.float32, device=hidden_states.device)
if residual is not None:
residual_out = torch.empty_like(hidden_states)
_aiter_add_rmsnorm_quant(
out_fp8,
hidden_states,
residual,
residual_out,
scale,
weight,
epsilon,
0, # group_size=0 → per-token
)
return (out_fp8, scale.unsqueeze(1)), residual_out
else:
_aiter_rmsnorm_quant(
out_fp8,
hidden_states,
scale,
weight,
epsilon,
0, # group_size=0 → per-token
)
return (out_fp8, scale.unsqueeze(1))
# TODO: According to the discussion in https://github.com/flashinfer-ai/flashinfer/issues/1223#issuecomment-3047256465
# We set the max token num to 128 for allreduce fusion with min-latency case(use_oneshot=True).
FUSE_ALLREDUCE_MAX_BATCH_SIZE = 2048
@@ -147,7 +201,6 @@ class ScatterMode(Enum):
class AttentionInputs:
def __init__(
self,
hidden_states: torch.Tensor,
@@ -311,8 +364,8 @@ class LayerScatterModes:
if context.is_layer_sparse:
return (
ScatterMode.SCATTERED
# Token dispatch/combine will be handled outside of LayerCommunicator for these modes.
if (
# Token dispatch/combine will be handled outside of LayerCommunicator for these modes.
not get_moe_a2a_backend().is_none()
or should_use_flashinfer_cutlass_moe_fp4_allgather()
)
@@ -484,7 +537,7 @@ class LayerCommunicator:
None,
None,
)
elif _use_aiter and _is_gfx95_supported and ("fp8" in quant_format):
elif _use_aiter and _is_gfx95_supported and (quant_format == "fp8"):
# aiter (ROCm gfx95) fused RMSNorm + FP8 group quant.
# When NSA is active, also preserve the unquantized bf16
# output as a 3-tuple (fp8, scale, bf16) so the NSA
@@ -509,10 +562,16 @@ class LayerCommunicator:
_unq_bf16,
)
elif _use_aiter and (quant_format == "fp8_per_token"):
hidden_states = _fused_rmsnorm_fp8_per_token_quant(
hidden_states,
self.input_layernorm.weight.data,
self.input_layernorm.variance_epsilon,
)
else:
hidden_states = self.input_layernorm(hidden_states)
else:
if _use_aiter and _is_gfx95_supported and ("mxfp4" in quant_format):
hidden_states, *_, residual = fused_rms_mxfp4_quant(
hidden_states,
@@ -523,7 +582,7 @@ class LayerCommunicator:
None,
residual,
)
elif _use_aiter and _is_gfx95_supported and ("fp8" in quant_format):
elif _use_aiter and _is_gfx95_supported and (quant_format == "fp8"):
# aiter (ROCm gfx95) fused RMSNorm + FP8 group quant
# with residual addition. When NSA is active, pack
# the unquantized bf16 as a 3-tuple (fp8, scale, bf16).
@@ -548,6 +607,15 @@ class LayerCommunicator:
hidden_states[1],
_unq_bf16,
)
elif _use_aiter and (quant_format == "fp8_per_token"):
if post_residual_addition is not None:
residual = residual + post_residual_addition
hidden_states, residual = _fused_rmsnorm_fp8_per_token_quant(
hidden_states,
self.input_layernorm.weight.data,
self.input_layernorm.variance_epsilon,
residual=residual,
)
else:
hidden_states, residual = self.input_layernorm(
hidden_states,
@@ -3,7 +3,7 @@ from __future__ import annotations
import logging
from enum import Enum
from functools import lru_cache
from typing import TYPE_CHECKING, Callable, List, Optional, Tuple
from typing import TYPE_CHECKING, Callable, List, Optional, Tuple, Union
import torch
@@ -1608,7 +1608,7 @@ def can_auto_enable_marlin_fp8() -> bool:
def apply_fp8_ptpc_linear(
input: torch.Tensor,
input: Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]],
weight: torch.Tensor,
weight_scale: torch.Tensor,
input_scale: Optional[torch.Tensor] = None,
@@ -1619,6 +1619,19 @@ def apply_fp8_ptpc_linear(
pad_output: Optional[bool] = None,
compressed_tensor_quant: bool = False,
) -> torch.Tensor:
"""FP8 per-token per-channel linear. Only used with the aiter (ROCm) backend."""
# Handle pre-quantized (fp8_tensor, scale) tuple from fused RMSNorm+Quant
if isinstance(input, tuple):
q_input, x_scale = input
q_input = q_input.view(-1, q_input.shape[-1])
output_shape = [*q_input.shape[:-1], weight.shape[0]]
output = aiter.gemm_a8w8_bpreshuffle(
q_input, weight, x_scale, weight_scale, None, torch.bfloat16
)
if bias is not None:
output = output + bias
return output.view(*output_shape)
# View input as 2D matrix for fp8 methods
input_2d = input.view(-1, input.shape[-1])
+58 -3
View File
@@ -289,7 +289,9 @@ class Glm4MoeAttention(nn.Module):
hidden_states: torch.Tensor,
forward_batch: ForwardBatch,
):
if hidden_states.shape[0] == 0:
# hidden_states can be a (fp8_tensor, scale) tuple from fused RMSNorm+Quant
hs = hidden_states[0] if isinstance(hidden_states, tuple) else hidden_states
if hs.shape[0] == 0:
return hidden_states, forward_batch, None
qkv, _ = self.qkv_proj(hidden_states)
@@ -865,6 +867,51 @@ class Glm4MoeDecoderLayer(nn.Module):
),
)
# Detect if QKV uses aiter FP8 per-token quant so we can fuse
# RMSNorm + FP8 quant into a single kernel in prepare_attn
self.attn_quant_format = ""
self._detect_attn_quant_format()
def _detect_fp8_per_token_quant(self, linear_layer, label: str) -> str:
"""Check if a linear layer uses aiter FP8 per-token quantization."""
from sglang.srt.utils import get_bool_env_var, is_hip
if not (get_bool_env_var("SGLANG_USE_AITER") and is_hip()):
return ""
if not hasattr(linear_layer, "quant_method"):
return ""
scheme = getattr(linear_layer, "scheme", None) or getattr(
linear_layer.quant_method, "scheme", None
)
if scheme is not None:
from compressed_tensors.quantization import QuantizationStrategy
from sglang.srt.layers.quantization.compressed_tensors.schemes.compressed_tensors_w8a8_fp8 import (
CompressedTensorsW8A8Fp8,
)
if (
isinstance(scheme, CompressedTensorsW8A8Fp8)
and scheme.strategy == QuantizationStrategy.CHANNEL
):
logger.info(
"layer_%d Fused RMSNorm+Quant %s: ENABLED (fp8_per_token)",
self.layer_id,
label,
)
return "fp8_per_token"
logger.info(
"layer_%d Fused RMSNorm+Quant %s: skipped",
self.layer_id,
label,
)
return ""
def _detect_attn_quant_format(self):
self.attn_quant_format = self._detect_fp8_per_token_quant(
self.self_attn.qkv_proj, "attn"
)
def _is_layer_sparse(self, layer_id: int, is_nextn: bool) -> bool:
return is_nextn or (
self.config.n_routed_experts is not None
@@ -880,7 +927,10 @@ class Glm4MoeDecoderLayer(nn.Module):
) -> torch.Tensor:
hidden_states, residual = self.layer_communicator.prepare_attn(
hidden_states, residual, forward_batch
hidden_states,
residual,
forward_batch,
quant_format=self.attn_quant_format,
)
hidden_states = self.self_attn(
@@ -927,7 +977,12 @@ class Glm4MoeDecoderLayer(nn.Module):
tbo_subbatch_index: Optional[int] = None,
):
state.hidden_states_after_comm_pre_attn, state.residual_after_input_ln = (
self.layer_communicator.prepare_attn(hidden_states, residual, forward_batch)
self.layer_communicator.prepare_attn(
hidden_states,
residual,
forward_batch,
quant_format=self.attn_quant_format,
)
)
state.update(
dict(