-
Notifications
You must be signed in to change notification settings - Fork 9k
[AMD] Add fused all-reduce RMSNorm per-token FP8/MXFP4 quant #29723
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
928a8e5
56b919a
7d8576d
de7acb7
fd23e99
0127720
76f037e
175a3d4
fbed1e3
d57379b
5a49833
1226eec
f316a92
781fca8
f063f3f
aaa6522
933baf8
fdffe85
cfaa0ff
1be831a
cff2fe5
74c58ab
4def648
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -86,6 +86,17 @@ | |
| _MODEL_PARALLEL_GROUP_TIMEOUT: Optional[timedelta] = None | ||
|
|
||
|
|
||
| def _should_use_1stage_mxfp4_ar(input_: torch.Tensor) -> bool: | ||
| hidden_size = input_.shape[-1] | ||
| tokens = input_.numel() // hidden_size | ||
| if hidden_size == 7168: | ||
| # CUDA-graph microbench: direct MXFP4 epilogue is faster through 56 | ||
| # tokens, while fallback wins from 64 tokens onward. | ||
| return tokens <= 56 | ||
| total_bytes = input_.numel() * input_.element_size() | ||
| return total_bytes <= 128 * 1024 | ||
|
mqhc2020 marked this conversation as resolved.
|
||
|
|
||
|
|
||
| def get_torch_distributed_pg_options(group_name=None): | ||
| if not _is_npu: | ||
| return None | ||
|
|
@@ -838,6 +849,41 @@ def fused_allreduce_rmsnorm( | |
| ) | ||
| return fused_outputs | ||
|
|
||
| def fused_allreduce_rmsnorm_mxfp4_quant( | ||
| self, | ||
| input_: torch.Tensor, | ||
| residual_inp_: torch.Tensor, | ||
| weight_: torch.Tensor, | ||
| eps: float, | ||
| emit_bf16: bool = False, | ||
| ): | ||
| """Attempt fused all-reduce + RMSNorm + MXFP4 quant via AITER custom AR.""" | ||
| if not (is_hip() and is_gfx95_supported()): | ||
| return None | ||
|
|
||
| ca_comm = self.ca_comm | ||
| if ca_comm is None or getattr(ca_comm, "disabled", True): | ||
| return None | ||
| if not hasattr(ca_comm, "custom_fused_ar_rms_mxfp4_quant"): | ||
| return None | ||
|
|
||
| if envs.SGLANG_USE_1STAGE_ALLREDUCE.is_set(): | ||
| use_1stage_ar = envs.SGLANG_USE_1STAGE_ALLREDUCE.get() | ||
| else: | ||
| use_1stage_ar = _should_use_1stage_mxfp4_ar(input_) | ||
|
|
||
| try: | ||
| return ca_comm.custom_fused_ar_rms_mxfp4_quant( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P0] The new quantized collectives omit the existing TC-piecewise CUDA-graph guard. AITER's custom communicator can return dummy zero outputs when its global capture state is active but the current stream is not capturing; this non- |
||
| input_, | ||
| residual_inp_, | ||
| weight_, | ||
| eps, | ||
| use_1stage_ar, | ||
| emit_bf16=emit_bf16, | ||
| ) | ||
| except Exception: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P0] An arbitrary collective runtime failure cannot safely become a per-rank local fallback. Once one or more ranks have entered the fused collective, independently returning |
||
| return None | ||
|
|
||
| def fused_allreduce_rmsnorm_quant_per_group( | ||
| self, | ||
| input_: torch.Tensor, | ||
|
|
@@ -909,6 +955,62 @@ def fused_allreduce_rmsnorm_quant_per_group( | |
| except Exception: | ||
| return None | ||
|
|
||
| def fused_allreduce_rmsnorm_quant_per_token( | ||
| self, | ||
| input_: torch.Tensor, | ||
| residual_inp_: torch.Tensor, | ||
| weight_: torch.Tensor, | ||
| eps: float, | ||
| ) -> Optional[Tuple[torch.Tensor, ...]]: | ||
| """Attempt fused all-reduce + RMSNorm + per-token FP8 quant in ONE kernel. | ||
|
|
||
| ROCm/aiter/gfx95-only entry point backed by the aiter custom-all-reduce | ||
| ``custom_fused_ar_rms_quant`` (``post_per_token_quant=True``). Returns | ||
| ``(fp8, residual_out, per_token_scale)`` with ``per_token_scale`` shaped | ||
| ``(M, 1)``, or ``None`` when the backend cannot service the request so | ||
| the caller can fall back to the ``fused_allreduce_rmsnorm`` + separate | ||
| per-token-quant path. | ||
|
|
||
| Unlike the per-group entry point this kernel does not emit a bf16 | ||
| sidecar, so GDN-style layers that need an unquantized normed output must | ||
| use the 2-kernel fallback. | ||
| """ | ||
| if not (is_hip() and is_gfx95_supported()): | ||
| return None | ||
|
|
||
| ca_comm = self.ca_comm | ||
| if ca_comm is None or getattr(ca_comm, "disabled", True): | ||
| return None | ||
| if not hasattr(ca_comm, "custom_fused_ar_rms_quant"): | ||
| return None | ||
|
|
||
| # Mirror the per-group eligibility gate so we fail fast without entering | ||
| # the HIP kernel dispatch. | ||
| K = input_.shape[-1] | ||
| if K > 16384: | ||
| return None | ||
| total_bytes = input_.numel() * input_.element_size() | ||
| if total_bytes == 0 or total_bytes > 8 * 1024 * 8192: | ||
| return None | ||
| if self.world_size == 6: | ||
| return None | ||
|
|
||
| if envs.SGLANG_USE_1STAGE_ALLREDUCE.is_set(): | ||
| use_1stage_ar = envs.SGLANG_USE_1STAGE_ALLREDUCE.get() | ||
| else: | ||
| use_1stage_ar = total_bytes <= 128 * 1024 | ||
|
|
||
| try: | ||
| return ca_comm.custom_fused_ar_rms_quant( | ||
| input_, | ||
| residual_inp_, | ||
| weight_, | ||
| eps, | ||
| use_1stage_ar, | ||
| ) | ||
| except Exception: | ||
| return None | ||
|
mqhc2020 marked this conversation as resolved.
|
||
|
|
||
| def _resolve_outplace_all_reduce_method( | ||
| self, | ||
| input_: torch.Tensor, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P0] This can select the 1-stage kernel above its documented 80-token hard limit. For example BF16
[128, 512]is exactly 128 KiB and returns true here despite having 128 tokens. Please requiretokens <= 80for every default 1-stage decision; keep the measured K=7168 cutoff as an additional restriction, not a replacement for the hard limit. Add boundary tests for 80/81 tokens.