A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 4, 08:45 UTC
[ROCm][FEAT] AITER Fused Allreduce + RMSNorm (#37646)
Signed-off-by: vllmellm <vllm.ellm@embeddedllm.com> Signed-off-by: Rita Brugarolas Brufau <rita.brugarolasbrufau@amd.com> Signed-off-by: junkang1991 <junkangchow@gmail.com> Co-authored-by: Rita Brugarolas <Rita.BrugarolasBrufau@amd.com> Co-authored-by: junkang1991 <junkangchow@gmail.com> Co-authored-by: Luka Govedič <ProExpertProg@users.noreply.github.com> Co-authored-by: TJian <tunjian.tan@embeddedllm.com>
vllmellm committed
May 1, 2026 at 23:07 UTC
529c671e8075d265a48b72e0eaaeb5e30d2f1630
9 files changed
+454
-20
.buildkite/test-amd.yaml
+1
@@ -1108,6 +1108,7 @@ steps:
1108
- export VLLM_TEST_CLEAN_GPU_MEMORY=1
1109
- VLLM_TEST_CLEAN_GPU_MEMORY=1 pytest -v -s tests/compile/passes/distributed/test_async_tp.py
1110
- pytest -v -s tests/compile/passes/distributed/test_sequence_parallelism.py
1111
+ - pytest -v -s tests/compile/passes/distributed/test_tp2_ar_rms.py::test_tp2_ar_rms_fusions
1112
1113
#----------------------------------------------------------- mi300 · cuda ------------------------------------------------------------#
1114
tests/compile/fusions_e2e/test_tp2_ar_rms.py
+19
-3
@@ -19,6 +19,8 @@ from .models import (
19
FLASHINFER_ATTN,
20
FLASHINFER_MLA_ATTN,
21
FLASHMLA_SPARSE_ATTN,
22
+ ROCM_AITER_UNIFIED_ATTN,
23
+ ROCM_ATTN,
24
TRITON_ATTN,
25
deepseek_coder_v2_lite_fp8,
26
deepseek_r1_fp4,
@@ -34,7 +36,9 @@ from .models import (
36
qwen3_a3b_fp8,
37
)
38
37
-pytestmark = pytest.mark.skipif(not current_platform.is_cuda(), reason="Only test CUDA")
39
+pytestmark = pytest.mark.skipif(
40
+ not current_platform.is_cuda_alike(), reason="Only test CUDA/ROCm"
41
+)
42
43
44
@multi_gpu_test(num_gpus=2)
@@ -55,6 +59,7 @@ pytestmark = pytest.mark.skipif(not current_platform.is_cuda(), reason="Only tes
59
@pytest.mark.parametrize("n_layers", [4])
60
@pytest.mark.parametrize("custom_ops", custom_ops_combos("quant_fp8", "rms_norm"))
61
@pytest.mark.parametrize("inductor_graph_partition", INDUCTOR_GRAPH_PARTITION)
62
+@pytest.mark.skipif(not current_platform.is_cuda(), reason="Only test CUDA")
63
def test_tp2_ar_rms_fp8_fusions(
64
model_name: str,
65
matches_fn: Callable[[int], Matches],
@@ -124,6 +129,7 @@ def test_tp2_ar_rms_fp8_fusions(
129
@pytest.mark.parametrize("custom_ops", custom_ops_combos("rms_norm"))
130
@pytest.mark.parametrize("inductor_graph_partition", INDUCTOR_GRAPH_PARTITION)
131
@pytest.mark.skipif(not is_blackwell(), reason="Blackwell required for fp4")
132
+@pytest.mark.skipif(not current_platform.is_cuda(), reason="Only test CUDA")
133
def test_tp2_ar_rms_fp4_fusions(
134
model_name: str,
135
matches_fn: Callable[[int], Matches],
@@ -176,10 +182,19 @@ def test_tp2_ar_rms_fp4_fusions(
182
"model_name, matches_fn, model_kwargs, hf_overrides",
183
[llama3_8b, qwen3_a3b, gpt_oss_20b],
184
)
179
-@pytest.mark.parametrize("attn_backend", [TRITON_ATTN])
185
+@pytest.mark.parametrize(
186
+ "attn_backend",
187
+ [
188
+ TRITON_ATTN,
189
+ FLASHINFER_ATTN,
190
+ ROCM_ATTN,
191
+ ROCM_AITER_UNIFIED_ATTN,
192
+ ],
193
+)
194
@pytest.mark.parametrize("n_layers", [4])
181
-@pytest.mark.parametrize("custom_ops", custom_ops_combos("rms_norm"))
195
+@pytest.mark.parametrize("custom_ops", tuple(custom_ops_combos("rms_norm")))
196
@pytest.mark.parametrize("inductor_graph_partition", INDUCTOR_GRAPH_PARTITION)
197
+@pytest.mark.skipif(not current_platform.is_cuda_alike(), reason="Only test CUDA/ROCm")
198
def test_tp2_ar_rms_fusions(
199
model_name: str,
200
matches_fn: Callable[[int], Matches],
@@ -221,4 +236,5 @@ def test_tp2_ar_rms_fusions(
236
compilation_config,
237
matches_check,
238
tp_size=2,
239
+ use_aiter=current_platform.is_rocm(),
240
)
tests/compile/passes/distributed/test_fusion_all_reduce.py
+77
-12
@@ -8,8 +8,12 @@ import torch
8
import vllm.envs as envs
9
from tests.compile.backend import TestBackend
10
from tests.utils import TestFP8Layer, has_module_attribute, multi_gpu_test
11
+from vllm._aiter_ops import IS_AITER_FOUND, rocm_aiter_ops
12
from vllm._custom_ops import cutlass_scaled_fp4_mm, scaled_fp4_quant
12
-from vllm.compilation.passes.fusion.allreduce_rms_fusion import AllReduceFusionPass
13
+from vllm.compilation.passes.fusion.allreduce_rms_fusion import (
14
+ AllReduceFusionPass,
15
+ RocmAiterAllReduceFusionPass,
16
+)
17
from vllm.compilation.passes.utility.fix_functionalization import (
18
FixFunctionalizationPass,
19
)
@@ -42,13 +46,19 @@ DEVICE_TYPE = current_platform.device_type
46
47
class TestAllReduceRMSNormModel(torch.nn.Module):
48
def __init__(
45
- self, hidden_size=16, token_num=16, eps=1e-6, dtype: torch.dtype = torch.float16
49
+ self,
50
+ hidden_size=16,
51
+ token_num=16,
52
+ eps=1e-6,
53
+ dtype: torch.dtype = torch.float16,
54
+ use_aiter: bool = False,
55
):
56
super().__init__()
57
self.hidden_size = hidden_size
58
self.eps = eps
59
self.norm = [RMSNorm(hidden_size, eps) for i in range(4)]
60
self.w = [torch.rand(hidden_size, hidden_size) for _ in range(3)]
61
+ self.use_aiter = use_aiter
62
63
def forward(self, x):
64
# avoid having graph input be an arg to a pattern directly
@@ -76,6 +86,8 @@ class TestAllReduceRMSNormModel(torch.nn.Module):
86
return [torch.ops.vllm.all_reduce.default]
87
88
def ops_in_model_after(self):
89
+ if self.use_aiter:
90
+ return [rocm_aiter_ops.get_fused_allreduce_rmsnorm_op()]
91
return [torch.ops.vllm.flashinfer_trtllm_fused_allreduce_norm.default]
92
93
@@ -194,12 +206,36 @@ class TestAllReduceFusedAddRMSNormStaticQuantFP4Model(torch.nn.Module):
206
207
@multi_gpu_test(num_gpus=2)
208
@pytest.mark.parametrize(
197
- "test_model, enable_quant_fp8_custom_op",
209
+ "test_model, enable_quant_fp8_custom_op, use_aiter",
210
[
199
- (TestAllReduceRMSNormModel, False),
200
- (TestAllReduceRMSNormStaticQuantFP8Model, True),
201
- (TestAllReduceRMSNormStaticQuantFP8Model, False),
202
- (TestAllReduceFusedAddRMSNormStaticQuantFP4Model, False),
211
+ (TestAllReduceRMSNormModel, False, IS_AITER_FOUND),
212
+ pytest.param(
213
+ TestAllReduceRMSNormStaticQuantFP8Model,
214
+ True,
215
+ False,
216
+ marks=pytest.mark.skipif(
217
+ current_platform.is_rocm(),
218
+ reason="Not supported on ROCm platform",
219
+ ),
220
+ ),
221
+ pytest.param(
222
+ TestAllReduceRMSNormStaticQuantFP8Model,
223
+ False,
224
+ False,
225
+ marks=pytest.mark.skipif(
226
+ current_platform.is_rocm(),
227
+ reason="Not supported on ROCm platform",
228
+ ),
229
+ ),
230
+ pytest.param(
231
+ TestAllReduceFusedAddRMSNormStaticQuantFP4Model,
232
+ False,
233
+ False,
234
+ marks=pytest.mark.skipif(
235
+ current_platform.is_rocm(),
236
+ reason="Not supported on ROCm platform",
237
+ ),
238
+ ),
239
],
240
)
241
@pytest.mark.parametrize("batch_size", [8])
@@ -210,9 +246,18 @@ class TestAllReduceFusedAddRMSNormStaticQuantFP4Model(torch.nn.Module):
246
@pytest.mark.parametrize("flashinfer_allreduce_backend", ["trtllm", "mnnvl"])
247
@pytest.mark.skipif(envs.VLLM_TARGET_DEVICE not in ["cuda"], reason="Only test on CUDA")
248
@pytest.mark.skipif(
213
- not find_spec("flashinfer")
214
- or not has_module_attribute("flashinfer.comm", "allreduce_fusion")
215
- or not has_module_attribute("flashinfer.comm", "create_allreduce_fusion_workspace"),
249
+ current_platform.is_rocm() and not IS_AITER_FOUND,
250
+ reason="aiter is not found",
251
+)
252
+@pytest.mark.skipif(
253
+ current_platform.is_cuda()
254
+ and (
255
+ not find_spec("flashinfer")
256
+ or not has_module_attribute("flashinfer.comm", "allreduce_fusion")
257
+ or not has_module_attribute(
258
+ "flashinfer.comm", "create_allreduce_fusion_workspace"
259
+ )
260
+ ),
261
reason="flashinfer is not found or flashinfer "
262
"is not compiled with allreduce_fusion",
263
)
@@ -225,7 +270,14 @@ def test_all_reduce_fusion_pass_replace(
270
enable_rms_norm_custom_op,
271
enable_quant_fp8_custom_op,
272
flashinfer_allreduce_backend,
273
+ use_aiter: bool,
274
+ monkeypatch: pytest.MonkeyPatch,
275
):
276
+ if use_aiter:
277
+ with monkeypatch.context() as m:
278
+ m.setenv("VLLM_ROCM_USE_AITER", str(use_aiter))
279
+ rocm_aiter_ops.refresh_env_variables()
280
+
281
num_processes = 2
282
if (
283
test_model == TestAllReduceFusedAddRMSNormStaticQuantFP4Model
@@ -249,6 +301,8 @@ def test_all_reduce_fusion_pass_replace(
301
enable_rms_norm_custom_op,
302
enable_quant_fp8_custom_op,
303
flashinfer_allreduce_backend,
304
+ use_aiter,
305
+ monkeypatch,
306
),
307
nprocs=nprocs,
308
)
@@ -267,6 +321,8 @@ def all_reduce_fusion_pass_on_test_model(
321
enable_rms_norm_custom_op,
322
enable_quant_fp8_custom_op,
323
flashinfer_allreduce_backend,
324
+ use_aiter: bool,
325
+ monkeypatch: pytest.MonkeyPatch,
326
):
327
set_random_seed(0)
328
@@ -313,7 +369,11 @@ def all_reduce_fusion_pass_on_test_model(
369
)
370
with set_current_vllm_config(vllm_config):
371
initialize_model_parallel(tensor_model_parallel_size=world_size)
316
- all_reduce_fusion_pass = AllReduceFusionPass(vllm_config)
372
+ all_reduce_fusion_pass = (
373
+ RocmAiterAllReduceFusionPass(vllm_config)
374
+ if use_aiter
375
+ else AllReduceFusionPass(vllm_config)
376
+ )
377
noop_pass = NoOpEliminationPass(vllm_config)
378
func_pass = FixFunctionalizationPass(vllm_config)
379
cleanup_pass = PostCleanupPass(vllm_config)
@@ -323,7 +383,12 @@ def all_reduce_fusion_pass_on_test_model(
383
)
384
385
token_num = batch_size * seq_len
326
- model = test_model_cls(hidden_size, token_num, dtype=dtype)
386
+ if test_model_cls is TestAllReduceRMSNormModel:
387
+ model = test_model_cls(
388
+ hidden_size, token_num, dtype=dtype, use_aiter=use_aiter
389
+ )
390
+ else:
391
+ model = test_model_cls(hidden_size, token_num, dtype=dtype)
392
393
hidden_states = torch.randn((token_num, hidden_size), requires_grad=False)
394
vllm/_aiter_ops.py
+115
@@ -2,9 +2,12 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
import functools
4
from collections.abc import Callable
5
+from contextlib import contextmanager
6
+from typing import Protocol
7
8
import torch
9
from torch._ops import OpOverload
10
+from torch.distributed import ProcessGroup
11
12
import vllm.envs as envs
13
from vllm.platforms import current_platform
@@ -39,6 +42,27 @@ def is_aiter_found() -> bool:
42
IS_AITER_FOUND = is_aiter_found()
43
44
45
+class AiterCustomAllreduceProto(Protocol):
46
+ max_size: int
47
+ world_size: int
48
+ fully_connected: bool
49
+
50
+ @contextmanager
51
+ def capture(self): ...
52
+ def close(self) -> None: ...
53
+ def fused_ar_rms(
54
+ self,
55
+ inp: torch.Tensor,
56
+ res_inp: torch.Tensor,
57
+ *,
58
+ w: torch.Tensor,
59
+ eps: float,
60
+ registered: bool = False,
61
+ use_1stage: bool = False,
62
+ ) -> tuple[torch.Tensor, torch.Tensor]: ...
63
+ def should_custom_ar(self, inp: torch.Tensor) -> bool: ...
64
+
65
+
66
def is_aiter_found_and_supported() -> bool:
67
"""Check if AITER library is available and platform supports it.
68
@@ -750,6 +774,55 @@ def _rocm_aiter_rmsnorm_fused_dynamic_quant_fake(
774
return out, y_scale
775
776
777
+def _rocm_aiter_fused_allreduce_rmsnorm_impl(
778
+ input_: torch.Tensor,
779
+ residual: torch.Tensor,
780
+ weight: torch.Tensor,
781
+ epsilon: float,
782
+) -> tuple[torch.Tensor, torch.Tensor]:
783
+ aiter_ar = rocm_aiter_ops.get_aiter_allreduce()
784
+ assert aiter_ar is not None, "aiter allreduce must be initialized"
785
+
786
+ total_bytes = input_.numel() * input_.element_size()
787
+ hidden_dim = input_.shape[-1]
788
+ token_num = input_.shape[0]
789
+ hidden_ok = hidden_dim in (512, 1024, 2048, 4096, 7168)
790
+ token_ok = token_num <= 80
791
+ world_size = aiter_ar.world_size
792
+ full_nvlink = aiter_ar.fully_connected
793
+
794
+ if world_size == 2:
795
+ size_ok = True
796
+ elif full_nvlink and world_size <= 4:
797
+ size_ok = total_bytes < 256 * 1024
798
+ elif full_nvlink and world_size <= 8:
799
+ size_ok = total_bytes < 128 * 1024
800
+ else:
801
+ size_ok = False
802
+
803
+ use_1stage = hidden_ok and token_ok and size_ok
804
+
805
+ result = aiter_ar.fused_ar_rms(
806
+ input_,
807
+ residual,
808
+ w=weight,
809
+ eps=epsilon,
810
+ registered=torch.cuda.is_current_stream_capturing(),
811
+ use_1stage=use_1stage,
812
+ )
813
+ assert result is not None
814
+ return result[0], result[1]
815
+
816
+
817
+def _rocm_aiter_fused_allreduce_rmsnorm_fake(
818
+ input_: torch.Tensor,
819
+ residual: torch.Tensor,
820
+ weight: torch.Tensor,
821
+ epsilon: float,
822
+) -> tuple[torch.Tensor, torch.Tensor]:
823
+ return torch.empty_like(input_), torch.empty_like(residual)
824
+
825
+
826
def _rocm_aiter_per_tensor_quant_impl(
827
x: torch.Tensor,
828
quant_dtype: torch.dtype,
@@ -1188,6 +1261,9 @@ class rocm_aiter_ops:
1261
# TODO: Consolidate under _LINEAR_ENABLED
1262
_TRITON_UNQUANT_GEMM = envs.VLLM_ROCM_USE_AITER_TRITON_GEMM
1263
1264
+ _ALL_REDUCE_MAX_SIZE: int = 8192 * 1024 * 8 * 2
1265
+ _CUSTOM_ALL_REDUCE: AiterCustomAllreduceProto | None = None
1266
+
1267
@classmethod
1268
def refresh_env_variables(cls):
1269
"""
@@ -1362,6 +1438,35 @@ class rocm_aiter_ops:
1438
1439
return cls.is_linear_enabled() and on_gfx950()
1440
1441
+ @classmethod
1442
+ def initialize_aiter_allreduce(
1443
+ cls, group: ProcessGroup, device: torch.device
1444
+ ) -> None:
1445
+ try:
1446
+ from aiter.dist.device_communicators.custom_all_reduce import (
1447
+ CustomAllreduce as AiterCustomAllreduce,
1448
+ )
1449
+
1450
+ cls._CUSTOM_ALL_REDUCE = AiterCustomAllreduce(group, device)
1451
+ except Exception:
1452
+ cls._CUSTOM_ALL_REDUCE = None
1453
+
1454
+ @classmethod
1455
+ def get_aiter_allreduce(cls) -> AiterCustomAllreduceProto | None:
1456
+ return cls._CUSTOM_ALL_REDUCE
1457
+
1458
+ @classmethod
1459
+ def destroy_aiter_allreduce(cls) -> None:
1460
+ if cls._CUSTOM_ALL_REDUCE is not None:
1461
+ cls._CUSTOM_ALL_REDUCE.close()
1462
+ cls._CUSTOM_ALL_REDUCE = None
1463
+
1464
+ @classmethod
1465
+ def get_aiter_allreduce_max_size(cls) -> int | None:
1466
+ # effective max input size (based on upstream aiter version: v0.1.10.post3)
1467
+ # https://github.com/ROCm/aiter/blob/6a0e7b26ccf33164785531212cc2ec2cde0b9243/aiter/dist/device_communicators/custom_all_reduce.py#L272-L273
1468
+ return int(cls._ALL_REDUCE_MAX_SIZE / 2)
1469
+
1470
@staticmethod
1471
@if_aiter_supported
1472
def register_ops_once() -> None:
@@ -1552,6 +1657,12 @@ class rocm_aiter_ops:
1657
fake_impl=_triton_rotary_embedding_fake,
1658
)
1659
1660
+ direct_register_custom_op(
1661
+ op_name="rocm_aiter_fused_allreduce_rmsnorm",
1662
+ op_func=_rocm_aiter_fused_allreduce_rmsnorm_impl,
1663
+ fake_impl=_rocm_aiter_fused_allreduce_rmsnorm_fake,
1664
+ )
1665
+
1666
direct_register_custom_op(
1667
op_name="fused_mla_dual_rms_norm",
1668
op_func=_fused_mla_dual_rms_norm_impl,
@@ -1605,6 +1716,10 @@ class rocm_aiter_ops:
1716
def get_triton_rotary_embedding_op() -> OpOverload:
1717
return torch.ops.vllm.rocm_aiter_triton_rotary_embedding.default
1718
1719
+ @staticmethod
1720
+ def get_fused_allreduce_rmsnorm_op() -> OpOverload:
1721
+ return torch.ops.vllm.rocm_aiter_fused_allreduce_rmsnorm.default
1722
+
1723
@staticmethod
1724
def get_fused_mla_dual_rms_norm_op() -> OpOverload:
1725
return torch.ops.vllm.fused_mla_dual_rms_norm.default
vllm/compilation/passes/fusion/act_quant_fusion.py
+1
@@ -190,6 +190,7 @@ class SiluMulBlockQuantPattern(ActivationQuantPattern):
190
is_scale_transposed: bool = False,
191
is_e8m0: bool = False,
192
is_tma_aligned: bool = False,
193
+ match_aiter: bool = False,
194
) -> None:
195
super().__init__(quant_key)
196
self.quant_matcher = MatcherQuantFP8(
vllm/compilation/passes/fusion/allreduce_rms_fusion.py
+209
-1
@@ -12,12 +12,14 @@ from torch._higher_order_ops.auto_functionalize import auto_functionalized
12
from torch._inductor.pattern_matcher import PatternMatcherPass
13
14
import vllm.ir.ops
15
+from vllm._aiter_ops import rocm_aiter_ops
16
from vllm.compilation.passes.fusion.rms_quant_fusion import (
17
_rms_input_weight_dtype_match,
18
)
19
from vllm.config import VllmConfig
20
from vllm.config.utils import Range
21
from vllm.distributed import get_tp_group, tensor_model_parallel_all_reduce
22
+from vllm.distributed.device_communicators.custom_all_reduce import CustomAllreduce
23
from vllm.distributed.parallel_state import (
24
get_tensor_model_parallel_rank,
25
get_tensor_model_parallel_world_size,
@@ -32,7 +34,12 @@ from vllm.utils.torch_utils import (
34
)
35
36
from ..inductor_pass import enable_fake_mode
35
-from ..vllm_inductor_pass import VllmInductorPass, VllmPatternMatcherPass
37
+from ..vllm_inductor_pass import (
38
+ VllmFusionPatternMatcherPass,
39
+ VllmInductorPass,
40
+ VllmPatternMatcherPass,
41
+ VllmPatternReplacement,
42
+)
43
from .matcher_utils import MatcherFusedAddRMSNorm, MatcherQuantFP8
44
45
FP8_DTYPE = current_platform.fp8_dtype()
@@ -889,3 +896,204 @@ class AllReduceFusionPass(VllmPatternMatcherPass):
896
return
897
with contextlib.suppress(Exception):
898
destroy_fi_ar_workspace()
899
+
900
+
901
+# TODO: make BasePattern to inherit from VllmPatternReplacement
902
+class AiterAllreduceFusedRMSNormPattern(BasePattern, VllmPatternReplacement):
903
+ def __init__(
904
+ self,
905
+ epsilon: float,
906
+ dtype: torch.dtype,
907
+ device: str | None,
908
+ use_aiter_rmsnorm: bool = True,
909
+ ) -> None:
910
+ super().__init__(dtype, device)
911
+ self.dtype = dtype
912
+ self.epsilon = epsilon
913
+ self.FUSED_AR_RMSNORM_OP = rocm_aiter_ops.get_fused_allreduce_rmsnorm_op()
914
+
915
+ def get_inputs(self) -> list[torch.Tensor]:
916
+ return [self.empty(5, 16), self.empty(16)]
917
+
918
+ @property
919
+ def pattern(self):
920
+ def _pattern(
921
+ input: torch.Tensor, weight: torch.Tensor
922
+ ) -> tuple[torch.Tensor, torch.Tensor]:
923
+ allreduce_output = tensor_model_parallel_all_reduce(input)
924
+ rms = vllm.ir.ops.rms_norm(allreduce_output, weight, self.epsilon)
925
+
926
+ return rms, allreduce_output
927
+
928
+ return _pattern
929
+
930
+ @property
931
+ def replacement(self):
932
+ def _replacement(
933
+ input: torch.Tensor, weight: torch.Tensor
934
+ ) -> tuple[torch.Tensor, torch.Tensor]:
935
+ residual = torch.empty_like(input)
936
+ allreduce = self.FUSED_AR_RMSNORM_OP(
937
+ input_=input,
938
+ residual=residual,
939
+ weight=weight,
940
+ epsilon=self.epsilon,
941
+ )
942
+ return allreduce[0], allreduce[1]
943
+
944
+ return _replacement
945
+
946
+
947
+class AiterAllreduceFusedAddRMSNormPattern(BasePattern, VllmPatternReplacement):
948
+ def __init__(
949
+ self,
950
+ epsilon: float,
951
+ dtype: torch.dtype,
952
+ device: str | None,
953
+ use_aiter_rmsnorm: bool = True,
954
+ ) -> None:
955
+ super().__init__(dtype, device)
956
+ self.epsilon = epsilon
957
+ self.dtype = dtype
958
+ self.rmsnorm_matcher = MatcherFusedAddRMSNorm(
959
+ epsilon, match_rocm_aiter=use_aiter_rmsnorm
960
+ )
961
+ self.FUSED_AR_RMSNORM_OP = rocm_aiter_ops.get_fused_allreduce_rmsnorm_op()
962
+
963
+ def get_inputs(self) -> list[torch.Tensor]:
964
+ input, residual, weight = self.rmsnorm_matcher.inputs()
965
+
966
+ return [residual, input.to(self.dtype), weight]
967
+
968
+ @property
969
+ def pattern(self):
970
+ def _pattern(
971
+ residual: torch.Tensor, input: torch.Tensor, weight: torch.Tensor
972
+ ) -> tuple[torch.Tensor, torch.Tensor]:
973
+ allreduce_output = tensor_model_parallel_all_reduce(input)
974
+ rms, residual = self.rmsnorm_matcher(allreduce_output, weight, residual)
975
+
976
+ return rms, residual
977
+
978
+ return _pattern
979
+
980
+ @property
981
+ def replacement(self):
982
+ def _replacement(
983
+ residual: torch.Tensor, input: torch.Tensor, weight: torch.Tensor
984
+ ) -> tuple[torch.Tensor, torch.Tensor]:
985
+ allreduce = self.FUSED_AR_RMSNORM_OP(
986
+ input_=input,
987
+ residual=residual,
988
+ weight=weight,
989
+ epsilon=self.epsilon,
990
+ )
991
+ return allreduce[0], allreduce[1]
992
+
993
+ return _replacement
994
+
995
+
996
+class RocmAiterAllReduceFusionPass(VllmFusionPatternMatcherPass):
997
+ def __init__(self, config: VllmConfig) -> None:
998
+ super().__init__(config, "rocm_aiter_allreduce_fusion_pass")
999
+ self.disabled = True
1000
+ self.tp_size = get_tensor_model_parallel_world_size()
1001
+ if self.tp_size <= 1:
1002
+ logger.warning_once("AllReduce fusion pass is disabled for tp_size <= 1.")
1003
+ return
1004
+
1005
+ if config.model_config is None:
1006
+ logger.warning_once(
1007
+ "AllReduce fusion pass is disabled for missing model_config."
1008
+ )
1009
+ return
1010
+
1011
+ device_comm = get_tp_group().device_communicator
1012
+ if device_comm is None:
1013
+ logger.warning_once("Device communicator is required.")
1014
+ return
1015
+
1016
+ ca_comm = getattr(device_comm, "ca_comm", None)
1017
+ if ca_comm is None:
1018
+ logger.warning_once("Custom Allreduce is required.")
1019
+ return
1020
+ self.ca_comm = ca_comm
1021
+
1022
+ assert isinstance(ca_comm, CustomAllreduce)
1023
+
1024
+ group = get_tp_group().cpu_group
1025
+ rocm_aiter_ops.initialize_aiter_allreduce(group, self.device)
1026
+ hidden_dim = config.model_config.get_hidden_size()
1027
+ element_size = torch.tensor([], dtype=self.model_dtype).element_size()
1028
+ max_size = rocm_aiter_ops.get_aiter_allreduce_max_size()
1029
+ if max_size is None:
1030
+ logger.warning("AITER allreduce fusion must be initialized")
1031
+ return
1032
+
1033
+ # Aiter's fused_allreduce_rmsnorm kernel dispatches on hidden_dim.
1034
+ # Before aiter v0.1.12 the launcher was template-specialized on HIDDEN_DIM
1035
+ # and silently no-op'd for sizes outside {512, 1024, 2048, 4096}. From v0.1.12
1036
+ # hidden_dim is a runtime argument. Detect the older API via the missing
1037
+ # `_pool` attribute and skip fusion for unsupported sizes.
1038
+ # Ref (old kernel): https://github.com/ROCm/aiter/blob/6a0e7b26ccf33164785531212cc2ec2cde0b9243/csrc/include/custom_all_reduce.cuh#L2590
1039
+ aiter_ar = rocm_aiter_ops.get_aiter_allreduce()
1040
+ _AITER_OLD_FUSED_AR_RMS_HIDDEN = (512, 1024, 2048, 4096)
1041
+ if (
1042
+ aiter_ar is not None
1043
+ and not hasattr(aiter_ar, "_pool")
1044
+ and hidden_dim not in _AITER_OLD_FUSED_AR_RMS_HIDDEN
1045
+ ):
1046
+ logger.warning_once(
1047
+ "AITER allreduce-rmsnorm fusion disabled: aiter<0.1.12 "
1048
+ "only supports hidden_dim in %s; got %d. Upgrade aiter to "
1049
+ ">=0.1.12 to enable fusion for this model.",
1050
+ _AITER_OLD_FUSED_AR_RMS_HIDDEN,
1051
+ hidden_dim,
1052
+ )
1053
+ # Tear down aiter's custom-allreduce so its IPC handles don't
1054
+ # race with vllm's ca_comm on the unfused fallback path.
1055
+ with contextlib.suppress(Exception):
1056
+ rocm_aiter_ops.destroy_aiter_allreduce()
1057
+ return
1058
+
1059
+ max_token_num = max_size // (hidden_dim * element_size)
1060
+ self.max_token_num = min(
1061
+ max_token_num,
1062
+ config.scheduler_config.max_num_batched_tokens,
1063
+ )
1064
+
1065
+ for epsilon in [1e-5, 1e-6]:
1066
+ self.register(
1067
+ AiterAllreduceFusedRMSNormPattern(
1068
+ epsilon,
1069
+ self.model_dtype,
1070
+ self.device,
1071
+ )
1072
+ )
1073
+ self.register(
1074
+ AiterAllreduceFusedAddRMSNormPattern(
1075
+ epsilon,
1076
+ self.model_dtype,
1077
+ self.device,
1078
+ )
1079
+ )
1080
+
1081
+ # WARNING: This is a hack to clear the pattern matcher cache
1082
+ # and allow multiple values of epsilon.
1083
+ torch._inductor.pattern_matcher._seen_patterns.clear()
1084
+
1085
+ self.disabled = False
1086
+
1087
+ self.dump_patterns(config, self.pm_pass)
1088
+
1089
+ def is_applicable_for_range(self, compile_range: Range) -> bool:
1090
+ if self.disabled:
1091
+ logger.warning_once("AllReduce fusion pass is disabled.")
1092
+ return False
1093
+ return bool(compile_range.end <= self.max_token_num)
1094
+
1095
+ def __del__(self) -> None:
1096
+ if getattr(self, "disabled", True):
1097
+ return
1098
+ with contextlib.suppress(Exception):
1099
+ rocm_aiter_ops.destroy_aiter_allreduce()
vllm/compilation/passes/pass_manager.py
+7
-1
@@ -18,6 +18,9 @@ from .ir.lowering_pass import VllmIRLoweringPass
18
from .vllm_inductor_pass import VllmInductorPass, VllmPatternMatcherPass
19
20
if rocm_aiter_ops.is_enabled():
21
+ from .fusion.allreduce_rms_fusion import (
22
+ RocmAiterAllReduceFusionPass,
23
+ )
24
from .fusion.rocm_aiter_fusion import (
25
MLADualRMSNormFusionPass,
26
RocmAiterRMSNormQuantFusionPass,
@@ -137,7 +140,10 @@ class PostGradPassManager(CustomGraphPass): # type: ignore[misc]
140
self.passes += [AsyncTPPass(config)]
141
142
if self.pass_config.fuse_allreduce_rms:
140
- self.passes += [AllReduceFusionPass(config)]
143
+ if rocm_aiter_ops.is_enabled():
144
+ self.passes += [RocmAiterAllReduceFusionPass(config)]
145
+ else:
146
+ self.passes += [AllReduceFusionPass(config)]
147
148
if self.pass_config.fuse_minimax_qk_norm:
149
self.passes += [MiniMaxQKNormPass(config)]
vllm/config/vllm.py
+16
-2
@@ -121,6 +121,15 @@ def enable_allreduce_rms_fusion(cfg: "VllmConfig") -> bool:
121
from vllm.platforms import current_platform
122
from vllm.utils.flashinfer import has_flashinfer
123
124
+ if current_platform.is_rocm():
125
+ from vllm._aiter_ops import rocm_aiter_ops
126
+
127
+ return (
128
+ rocm_aiter_ops.is_enabled()
129
+ and rocm_aiter_ops.is_rmsnorm_enabled()
130
+ and cfg.parallel_config.tensor_parallel_size > 1
131
+ )
132
+
133
return (
134
cfg.parallel_config.tensor_parallel_size > 1
135
and current_platform.is_cuda()
@@ -1604,10 +1613,15 @@ class VllmConfig:
1613
if compile_range_end is not None:
1614
computed_compile_ranges_endpoints.append(compile_range_end)
1615
1607
- # Add the compile ranges for flashinfer
1616
+ # Add the compile ranges for flashinfer/aiter.
1617
if compilation_config.pass_config.fuse_allreduce_rms:
1618
tp_size = self.parallel_config.tensor_parallel_size
1610
- max_size = compilation_config.pass_config.flashinfer_max_size(tp_size)
1619
+ from vllm._aiter_ops import rocm_aiter_ops
1620
+
1621
+ if rocm_aiter_ops.is_enabled():
1622
+ max_size = rocm_aiter_ops.get_aiter_allreduce_max_size()
1623
+ else:
1624
+ max_size = compilation_config.pass_config.flashinfer_max_size(tp_size)
1625
if max_size is not None:
1626
assert isinstance(self.model_config.dtype, torch.dtype)
1627
max_token_num = max_size // (
vllm/distributed/parallel_state.py
+9
-1
@@ -472,6 +472,7 @@ class GroupCoordinator:
472
# only cuda uses this function,
473
# so we don't abstract it into the base class
474
maybe_ca_context = nullcontext()
475
+ maybe_aiter_context = nullcontext()
476
from vllm.distributed.device_communicators.cuda_communicator import (
477
CudaCommunicator,
478
)
@@ -482,13 +483,20 @@ class GroupCoordinator:
483
if ca_comm is not None:
484
maybe_ca_context = ca_comm.capture() # type: ignore
485
486
+ from vllm._aiter_ops import rocm_aiter_ops
487
+
488
+ if rocm_aiter_ops.is_enabled():
489
+ aiter_ar = rocm_aiter_ops.get_aiter_allreduce()
490
+ if aiter_ar is not None:
491
+ maybe_aiter_context = aiter_ar.capture() # type: ignore
492
+
493
# ensure all initialization operations complete before attempting to
494
# capture the graph on another stream
495
curr_stream = torch.cuda.current_stream()
496
if curr_stream != stream:
497
stream.wait_stream(curr_stream)
498
491
- with torch.cuda.stream(stream), maybe_ca_context:
499
+ with torch.cuda.stream(stream), maybe_ca_context, maybe_aiter_context:
500
yield graph_capture_context
501
502
def all_reduce(self, input_: torch.Tensor) -> torch.Tensor: