Skip to content

Commit 18eaab4

Browse files
gbyu-amdzejunchen-zejun
authored andcommitted
enable ar+norm+quant fusion (#1494)
* enable ar+norm+quant fusion * fix fusion with online quant
1 parent f584f48 commit 18eaab4

2 files changed

Lines changed: 120 additions & 8 deletions

File tree

atom/model_ops/layernorm.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ def __init__(
219219
x_pad_to_multiple: int = 0,
220220
fused_allreduce: bool = False,
221221
fused_quant: bool = False,
222+
fused_quant_emit_bf16: bool = False,
222223
quant_config: Optional[QuantizationConfig] = None,
223224
prefix: str = "",
224225
) -> None:
@@ -229,6 +230,12 @@ def __init__(
229230
self.x_pad_to_multiple = x_pad_to_multiple
230231
self.fused_allreduce = fused_allreduce
231232
self.use_fused_quant = fused_quant
233+
# When the fused AllReduce+RMSNorm+quant path is active AND a downstream
234+
# consumer also needs the unquantized (bf16) normed activation (e.g. the
235+
# GLM-5.2 DSA indexer, whose wk/weights_proj GEMMs run in BF16), the
236+
# kernel additionally emits that bf16 mirror. forward() then returns
237+
# ((fp8, scale, bf16), residual) instead of ((fp8, scale), residual).
238+
self.fused_quant_emit_bf16 = fused_quant_emit_bf16
232239
self.tp_size = get_tensor_model_parallel_world_size()
233240
self.quant_config = quant_config
234241
self.prefix = prefix
@@ -335,6 +342,44 @@ def forward(
335342
assert (
336343
residual is not None
337344
), "fused_allreduce_rmsnorm requires residual input!"
345+
if self.use_fused_quant and self.quant_type.value in (
346+
_QV_PER_1X128,
347+
_QV_PER_TOKEN,
348+
):
349+
# Combined AllReduce + RMSNorm + FP8 quant: the downstream GEMM
350+
# (e.g. fused_qkv_a_proj) consumes the (fp8, scale) output
351+
# directly, dropping a standalone per-token/per-group quant kernel
352+
# from the hot path. `_aiter_transpose_scale` (resolved at init)
353+
# selects the scale layout for that GEMM. The fused kernel does
354+
# not support non-contiguous input.
355+
if self.fused_quant_emit_bf16:
356+
# Also emit the pre-quant bf16 normed activation for a
357+
# co-consumer that runs in BF16 (GLM-5.2 DSA indexer).
358+
x, residual, x_scale, x_bf16 = (
359+
tensor_model_parallel_fused_allreduce_rmsnorm_quant(
360+
x.contiguous(),
361+
residual,
362+
self.weight,
363+
self.eps,
364+
quant_type=self.quant_type,
365+
group_size=128,
366+
transpose_scale=self._aiter_transpose_scale,
367+
emit_bf16=True,
368+
)
369+
)
370+
return (x, x_scale, x_bf16), residual
371+
x, residual, x_scale = (
372+
tensor_model_parallel_fused_allreduce_rmsnorm_quant(
373+
x.contiguous(),
374+
residual,
375+
self.weight,
376+
self.eps,
377+
quant_type=self.quant_type,
378+
group_size=128,
379+
transpose_scale=self._aiter_transpose_scale,
380+
)
381+
)
382+
return (x, x_scale), residual
338383
# tensor_model_parallel_fused_allreduce_rmsnorm does not support non-contiguous input
339384
x, residual = tensor_model_parallel_fused_allreduce_rmsnorm(
340385
x.contiguous(),

atom/models/deepseek_v2.py

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
make_layers,
8282
maybe_prefix,
8383
)
84+
from atom.quant_spec import should_skip_online_quant
8485
from atom.utils import envs
8586
from atom.utils.custom_register import direct_register_custom_op
8687

@@ -1949,13 +1950,23 @@ def __init__(
19491950
# qkv_a_proj + reduce + RMSNorm + quant fusion remains gated by
19501951
# use_triton_gemm() in forward(), because that path depends on Triton GEMM.
19511952
self.prefix = prefix
1952-
self.quant_dtype = layer_quant_dtype
1953-
self.qknorm_quant_type = layer_quant_type_value
1953+
# Online-aware scheme for the fused q/kv norm+quant feeding q_b_proj:
1954+
# use the online target (applied after __init__), else the static config.
1955+
# layer_quant_dtype/type stay static for the fp4x2 weight-loading branch.
1956+
eff_dtype, eff_type = layer_quant_dtype, layer_quant_type
1957+
if quant_config is not None and quant_config.online_quant:
1958+
online_cfg = quant_config.get_layer_quant_config(
1959+
f"{prefix}.{q_a_proj_name}", use_online_quant=True
1960+
)
1961+
if not should_skip_online_quant(eff_type, eff_dtype, online_cfg):
1962+
eff_dtype, eff_type = online_cfg.quant_dtype, online_cfg.quant_type
1963+
self.quant_dtype = eff_dtype
1964+
self.qknorm_quant_type = None if eff_type is None else eff_type.value
19541965
self.fuse_qknorm_quant = False
19551966
# always fuse qknorm
19561967
self.fuse_qknorm = ENABLE_DS_QKNORM_FUSION
19571968
if quant_config is not None and ENABLE_DS_QKNORM_QUANT_FUSION:
1958-
if layer_quant_dtype in (dtypes.fp8, dtypes.fp4x2):
1969+
if eff_dtype in (dtypes.fp8, dtypes.fp4x2):
19591970
self.fuse_qknorm_quant = True
19601971

19611972
def forward(
@@ -1964,8 +1975,16 @@ def forward(
19641975
hidden_states: torch.Tensor,
19651976
) -> torch.Tensor:
19661977
hidden_states_scale = None
1978+
# When input_layernorm fused AR+RMSNorm+quant, hidden_states is a tuple.
1979+
# A 3-tuple (fp8, scale, bf16) additionally carries the unquantized bf16
1980+
# normed activation for the v32 indexer (see RMSNorm.fused_quant_emit_bf16);
1981+
# a 2-tuple (fp8, scale) is the plain fused-quant output.
1982+
indexer_hidden = None
19671983
if isinstance(hidden_states, tuple):
1968-
hidden_states, hidden_states_scale = hidden_states
1984+
if len(hidden_states) == 3:
1985+
hidden_states, hidden_states_scale, indexer_hidden = hidden_states
1986+
else:
1987+
hidden_states, hidden_states_scale = hidden_states
19691988

19701989
if self.q_lora_rank is not None:
19711990
if self.fuse_qknorm_quant and use_triton_gemm():
@@ -2042,8 +2061,11 @@ def forward(
20422061
kv_c_normed = self.kv_a_layernorm(kv_c)
20432062
hidden_states_or_q_c_scale = None
20442063
if self.is_v32 and self.indexer is not None and not self.skip_topk:
2064+
# The indexer's wk/weights_proj GEMMs run in BF16. When input_layernorm
2065+
# fused the quant it emits a bf16 mirror (indexer_hidden); otherwise
2066+
# hidden_states is already the bf16 normed activation.
20452067
self.indexer(
2046-
hidden_states,
2068+
indexer_hidden if indexer_hidden is not None else hidden_states,
20472069
hidden_states_or_q_c,
20482070
hidden_states_or_q_c_scale,
20492071
positions,
@@ -2158,12 +2180,57 @@ def __init__(
21582180
reduce_results=not self.fuse_ar_input_norm,
21592181
prefix=f"{prefix}.mlp",
21602182
)
2183+
# Fuse activation quant into AR+RMSNorm when fused_qkv_a_proj is
2184+
# per-1x128/per-token FP8, so the GEMM consumes the (fp8, scale) directly.
2185+
# Use the online-quant target (get_layer_quant_config(..., online)), not
2186+
# the static params_dtype: online quant flips the qkv proj to fp8 only in
2187+
# process_weights_after_loading, which runs after this __init__.
2188+
qkv_proj = getattr(self.self_attn, "fused_qkv_a_proj", None)
2189+
eff_quant_type = None if qkv_proj is None else qkv_proj.quant_type
2190+
eff_quant_dtype = None if qkv_proj is None else qkv_proj.params_dtype
2191+
if (
2192+
qkv_proj is not None
2193+
and quant_config is not None
2194+
and quant_config.online_quant
2195+
):
2196+
online_cfg = quant_config.get_layer_quant_config(
2197+
qkv_proj.prefix, use_online_quant=True
2198+
)
2199+
if not should_skip_online_quant(
2200+
eff_quant_type, eff_quant_dtype, online_cfg
2201+
):
2202+
eff_quant_type = online_cfg.quant_type
2203+
eff_quant_dtype = online_cfg.quant_dtype
2204+
input_norm_fused_quant = (
2205+
qkv_proj is not None
2206+
and eff_quant_dtype == dtypes.fp8
2207+
and eff_quant_type.value
2208+
in (QuantType.per_1x128.value, QuantType.per_Token.value)
2209+
)
2210+
fused_allreduce = (
2211+
self.fuse_ar_input_norm and self.layer_idx > 0 and not is_mtp_block
2212+
)
2213+
# GLM-5.2 (v32) DSA: the indexer's wk/weights_proj GEMMs run in BF16 and
2214+
# consume the same normed activation, so the fused kernel must also emit
2215+
# the pre-quant bf16 mirror alongside the fp8 for fused_qkv_a_proj.
2216+
emit_bf16_for_indexer = (
2217+
getattr(self.self_attn, "is_v32", False)
2218+
and getattr(self.self_attn, "indexer", None) is not None
2219+
)
21612220
self.input_layernorm = RMSNorm(
21622221
config.hidden_size,
21632222
eps=config.rms_norm_eps,
2164-
fused_allreduce=self.fuse_ar_input_norm
2165-
and self.layer_idx > 0
2166-
and not is_mtp_block,
2223+
fused_allreduce=fused_allreduce,
2224+
fused_quant=fused_allreduce and input_norm_fused_quant,
2225+
fused_quant_emit_bf16=(
2226+
fused_allreduce and input_norm_fused_quant and emit_bf16_for_indexer
2227+
),
2228+
quant_config=quant_config,
2229+
prefix=(
2230+
f"{prefix}.self_attn.fused_qkv_a_proj"
2231+
if qkv_proj is not None
2232+
else f"{prefix}.self_attn.q_a_proj"
2233+
),
21672234
)
21682235
self.post_attention_layernorm = RMSNorm(
21692236
config.hidden_size,

0 commit comments

Comments
 (0)