[ROCm][AITER] Directly Implement AITER Custom All-reduce in CudaCommunicator (#46065)
Signed-off-by: BadrBasowid <badr.basowid@gmail.com>
BadrBasowid committed
Jul 6, 2026 at 20:16 UTC
740f379faefd1ddeb47c85ebd600af3ccae77b7a
12 files changed
+499
-148
.buildkite/test-amd.yaml
+5
-1
@@ -2782,15 +2782,19 @@ steps:
2782
- vllm/envs.py
2783
- examples/offline_inference/data_parallel.py
2784
- tests/distributed/test_context_parallel.py
2785
+ - tests/distributed/test_rocm_aiter_custom_ar.py
2786
- tests/distributed/test_rocm_quick_reduce.py
2787
- tests/distributed/test_quick_all_reduce.py
2788
+ - tests/v1/e2e/general/test_rocm_aiter_custom_ar.py
2789
- tests/v1/distributed/test_dbo.py
2790
- tests/utils.py
2791
commands:
2792
- pytest -v -s tests/distributed/test_context_parallel.py
2791
- - pytest -v -s tests/v1/distributed/test_dbo.py
2793
+ - pytest -v -s tests/distributed/test_rocm_aiter_custom_ar.py
2794
+ - pytest -v -s tests/v1/e2e/general/test_rocm_aiter_custom_ar.py
2795
- pytest -v -s tests/distributed/test_rocm_quick_reduce.py
2796
- pytest -v -s tests/distributed/test_quick_all_reduce.py
2797
+ - pytest -v -s tests/v1/distributed/test_dbo.py
2798
2799
#-------------------------------------------------------- mi355 · entrypoints --------------------------------------------------------#
2800
tests/compile/fusions_e2e/conftest.py
+1
@@ -79,6 +79,7 @@ def run_e2e_fusion_test(monkeypatch, caplog_mp_spawn):
79
):
80
monkeypatch.setenv("VLLM_USE_DEEP_GEMM", "1" if use_deepgemm else "0")
81
monkeypatch.setenv("VLLM_ROCM_USE_AITER", "1" if use_aiter else "0")
82
+ monkeypatch.setenv("VLLM_ROCM_USE_AITER_CUSTOM_AR", "1" if use_aiter else "0")
83
from vllm._aiter_ops import rocm_aiter_ops
84
85
rocm_aiter_ops.refresh_env_variables()
tests/compile/passes/distributed/test_fusion_all_reduce.py
+9
-1
@@ -30,6 +30,9 @@ from vllm.config import (
30
set_current_vllm_config,
31
)
32
from vllm.distributed import tensor_model_parallel_all_reduce
33
+from vllm.distributed.device_communicators.aiter_custom_all_reduce import (
34
+ AiterCustomAllreduce,
35
+)
36
from vllm.distributed.parallel_state import (
37
init_distributed_environment,
38
initialize_model_parallel,
@@ -504,8 +507,12 @@ def all_reduce_fusion_pass_on_test_model(
507
"MASTER_ADDR": "localhost",
508
"MASTER_PORT": "12345",
509
"VLLM_FLASHINFER_ALLREDUCE_BACKEND": flashinfer_allreduce_backend,
510
+ "VLLM_ROCM_USE_AITER": str(int(use_aiter)),
511
+ "VLLM_ROCM_USE_AITER_CUSTOM_AR": str(int(use_aiter)),
512
}
513
)
514
+ if use_aiter:
515
+ rocm_aiter_ops.refresh_env_variables()
516
517
init_distributed_environment()
518
@@ -616,7 +623,7 @@ def test_rocm_aiter_all_reduce_rmsnorm_group_quant_fp8_fusion_pass_replace(
623
m.setenv("VLLM_ROCM_USE_AITER", "1")
624
rocm_aiter_ops.refresh_env_variables()
625
619
- if not rocm_aiter_ops.has_fused_allreduce_rmsnorm_quant_per_group():
626
+ if not AiterCustomAllreduce.build_supports_per_group_quant():
627
pytest.skip(
628
"aiter build is missing 'fused_ar_rms_per_group_quant' (needs "
629
"ROCm/aiter PR #2823); the new patterns aren't registered."
@@ -671,6 +678,7 @@ def rocm_aiter_group_quant_fusion_pass_on_test_model(
678
"MASTER_ADDR": "localhost",
679
"MASTER_PORT": "12345",
680
"VLLM_ROCM_USE_AITER": "1",
681
+ "VLLM_ROCM_USE_AITER_CUSTOM_AR": "1",
682
}
683
)
684
rocm_aiter_ops.refresh_env_variables()
tests/distributed/test_rocm_aiter_custom_ar.py
new
+134
@@ -0,0 +1,134 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
+import pytest
5
+import ray
6
+import torch
7
+import torch.distributed as dist
8
+
9
+from vllm._aiter_ops import is_aiter_found, rocm_aiter_ops
10
+from vllm.distributed.communication_op import tensor_model_parallel_all_reduce # noqa
11
+from vllm.distributed.parallel_state import get_tp_group, graph_capture
12
+from vllm.envs import disable_envs_cache
13
+from vllm.platforms import current_platform
14
+
15
+from ..utils import (
16
+ assert_rocm_custom_allreduce_backend_state,
17
+ ensure_model_parallel_initialized,
18
+ init_test_distributed_environment,
19
+ multi_gpu_test,
20
+ multi_process_parallel,
21
+)
22
+
23
+pytestmark = pytest.mark.skipif(
24
+ not current_platform.is_rocm(),
25
+ reason="ROCm-only AITER custom allreduce tests",
26
+)
27
+
28
+test_cases = [
29
+ ((2, 7168), torch.float16),
30
+ ((2, 7168), torch.bfloat16),
31
+ ((128, 8192), torch.float16),
32
+ ((128, 8192), torch.bfloat16),
33
+]
34
+
35
+
36
+def _configure_aiter_custom_ar_env(monkeypatch: pytest.MonkeyPatch) -> None:
37
+ monkeypatch.delenv("CUDA_VISIBLE_DEVICES", raising=False)
38
+ monkeypatch.delenv("HIP_VISIBLE_DEVICES", raising=False)
39
+ monkeypatch.setenv("VLLM_ROCM_USE_AITER", "1")
40
+ monkeypatch.setenv("VLLM_ROCM_USE_AITER_CUSTOM_AR", "1")
41
+ monkeypatch.setenv("VLLM_ROCM_QUICK_REDUCE_QUANTIZATION", "NONE")
42
+ disable_envs_cache()
43
+ rocm_aiter_ops.refresh_env_variables()
44
+
45
+
46
+def _assert_aiter_handles_input(inp: torch.Tensor) -> None:
47
+ aiter_ar_comm = get_tp_group().device_communicator.aiter_ar_comm
48
+ assert aiter_ar_comm is not None
49
+ assert aiter_ar_comm.should_custom_ar(inp), (
50
+ f"AITER CustomAllreduce does not support input shape {inp.shape}."
51
+ )
52
+
53
+
54
+@ray.remote(num_gpus=1, max_calls=1)
55
+def graph_allreduce(
56
+ monkeypatch: pytest.MonkeyPatch,
57
+ tp_size,
58
+ pp_size,
59
+ rank,
60
+ distributed_init_port,
61
+) -> None:
62
+ with monkeypatch.context() as m:
63
+ _configure_aiter_custom_ar_env(m)
64
+
65
+ device = torch.device(f"cuda:{rank}")
66
+ torch.accelerator.set_device_index(device)
67
+ init_test_distributed_environment(tp_size, pp_size, rank, distributed_init_port)
68
+ ensure_model_parallel_initialized(tp_size, pp_size)
69
+ assert_rocm_custom_allreduce_backend_state(True, "NONE")
70
+ group = get_tp_group().device_group
71
+
72
+ # A small all_reduce for warmup.
73
+ # this is needed because device communicators might be created lazily
74
+ # (e.g. NCCL). This will ensure that the communicator is initialized
75
+ # before any communication happens, so that this group can be used for
76
+ # graph capture immediately.
77
+ data = torch.zeros(1)
78
+ data = data.to(device=device)
79
+ dist.all_reduce(data, group=group)
80
+ torch.accelerator.synchronize()
81
+ del data
82
+
83
+ for shape, dtype in test_cases:
84
+ with graph_capture(device=device) as graph_capture_context:
85
+ inp = torch.ones(shape, dtype=dtype, device=device)
86
+ _assert_aiter_handles_input(inp)
87
+ expected = inp * tp_size
88
+
89
+ torch.accelerator.synchronize()
90
+ graph = torch.cuda.CUDAGraph()
91
+ with torch.cuda.graph(graph, stream=graph_capture_context.stream):
92
+ out = tensor_model_parallel_all_reduce(inp)
93
+
94
+ graph.replay()
95
+ torch.testing.assert_close(out, expected)
96
+
97
+
98
+@ray.remote(num_gpus=1, max_calls=1)
99
+def eager_allreduce(
100
+ monkeypatch: pytest.MonkeyPatch,
101
+ tp_size,
102
+ pp_size,
103
+ rank,
104
+ distributed_init_port,
105
+) -> None:
106
+ with monkeypatch.context() as m:
107
+ _configure_aiter_custom_ar_env(m)
108
+
109
+ device = torch.device(f"cuda:{rank}")
110
+ torch.accelerator.set_device_index(device)
111
+ init_test_distributed_environment(tp_size, pp_size, rank, distributed_init_port)
112
+ ensure_model_parallel_initialized(tp_size, pp_size)
113
+ assert_rocm_custom_allreduce_backend_state(True, "NONE")
114
+
115
+ for shape, dtype in test_cases:
116
+ inp = torch.ones(shape, dtype=dtype, device=device)
117
+ _assert_aiter_handles_input(inp)
118
+ expected = inp * tp_size
119
+ out = tensor_model_parallel_all_reduce(inp)
120
+ torch.testing.assert_close(out, expected)
121
+
122
+
123
+@pytest.mark.skipif(not is_aiter_found(), reason="AITER is not installed")
124
+@multi_gpu_test(num_gpus=2)
125
+@pytest.mark.parametrize("tp_size", [2])
126
+@pytest.mark.parametrize("pipeline_parallel_size", [1])
127
+@pytest.mark.parametrize("test_target", [eager_allreduce, graph_allreduce])
128
+def test_rocm_aiter_custom_allreduce(
129
+ monkeypatch: pytest.MonkeyPatch,
130
+ tp_size,
131
+ pipeline_parallel_size,
132
+ test_target,
133
+):
134
+ multi_process_parallel(monkeypatch, tp_size, pipeline_parallel_size, test_target)
tests/utils.py
+40
@@ -1459,6 +1459,46 @@ def multi_process_parallel(
1459
ray.shutdown()
1460
1461
1462
+def assert_rocm_custom_allreduce_backend_state(
1463
+ use_aiter_custom_ar: bool,
1464
+ quick_reduce_quantization: str,
1465
+) -> None:
1466
+ from vllm.distributed.parallel_state import get_tp_group
1467
+
1468
+ device_communicator = get_tp_group().device_communicator
1469
+ aiter_ar_comm = device_communicator.aiter_ar_comm
1470
+ if use_aiter_custom_ar:
1471
+ assert aiter_ar_comm is not None, "AITER CustomAllreduce was not initialized."
1472
+ assert not aiter_ar_comm.disabled, "AITER CustomAllreduce is disabled."
1473
+ assert device_communicator.ca_comm is None, (
1474
+ "vLLM CustomAllreduce should not be initialized when AITER CA is used."
1475
+ )
1476
+ else:
1477
+ assert aiter_ar_comm is None, (
1478
+ "AITER CustomAllreduce should not be initialized when disabled."
1479
+ )
1480
+ assert device_communicator.ca_comm is not None, (
1481
+ "vLLM CustomAllreduce should be initialized when AITER CA is disabled."
1482
+ )
1483
+
1484
+ qr_comm = device_communicator.qr_comm
1485
+ assert qr_comm is not None, "QuickReduce communicator was not initialized."
1486
+ if quick_reduce_quantization == "NONE":
1487
+ assert qr_comm.disabled, "QuickReduce should be disabled."
1488
+ else:
1489
+ assert not qr_comm.disabled, "QuickReduce should be enabled."
1490
+
1491
+
1492
+def assert_rocm_custom_allreduce_backend_state_on_worker(
1493
+ _worker,
1494
+ use_aiter_custom_ar: bool,
1495
+ quick_reduce_quantization: str,
1496
+) -> None:
1497
+ assert_rocm_custom_allreduce_backend_state(
1498
+ use_aiter_custom_ar, quick_reduce_quantization
1499
+ )
1500
+
1501
+
1502
@contextmanager
1503
def error_on_warning(category: type[Warning] = Warning):
1504
"""
tests/v1/e2e/general/test_rocm_aiter_custom_ar.py
new
+118
@@ -0,0 +1,118 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
+import pytest
5
+
6
+from vllm._aiter_ops import is_aiter_found, rocm_aiter_ops
7
+from vllm.config import CompilationConfig, CompilationMode, CUDAGraphMode
8
+from vllm.envs import disable_envs_cache
9
+from vllm.platforms import current_platform
10
+
11
+from ....conftest import VllmRunner
12
+from ....utils import (
13
+ assert_rocm_custom_allreduce_backend_state_on_worker,
14
+ multi_gpu_test,
15
+)
16
+
17
+PROMPTS = ["Hello, my name is", "The capital of France is"]
18
+
19
+
20
+def _run_generation(
21
+ vllm_runner: type[VllmRunner],
22
+ monkeypatch: pytest.MonkeyPatch,
23
+ compilation_config: CompilationConfig,
24
+ *,
25
+ model: str,
26
+ max_tokens: int,
27
+ use_aiter_custom_ar: bool,
28
+ quick_reduce_quantization: str,
29
+) -> list[tuple[list[int], str]]:
30
+ with monkeypatch.context() as m:
31
+ m.setenv("VLLM_ALLOW_INSECURE_SERIALIZATION", "1")
32
+ m.setenv("VLLM_ROCM_USE_AITER", "1")
33
+ m.setenv(
34
+ "VLLM_ROCM_USE_AITER_CUSTOM_AR",
35
+ "1" if use_aiter_custom_ar else "0",
36
+ )
37
+ m.setenv("VLLM_ROCM_QUICK_REDUCE_QUANTIZATION", quick_reduce_quantization)
38
+ disable_envs_cache()
39
+ rocm_aiter_ops.refresh_env_variables()
40
+
41
+ with vllm_runner(
42
+ model,
43
+ dtype="half",
44
+ tensor_parallel_size=2,
45
+ compilation_config=compilation_config,
46
+ max_model_len=256,
47
+ max_num_seqs=len(PROMPTS),
48
+ gpu_memory_utilization=0.7,
49
+ ) as llm:
50
+ llm.get_llm().collective_rpc(
51
+ assert_rocm_custom_allreduce_backend_state_on_worker,
52
+ args=(use_aiter_custom_ar, quick_reduce_quantization),
53
+ )
54
+
55
+ return llm.generate_greedy(PROMPTS, max_tokens)
56
+
57
+
58
+@pytest.mark.skipif(not current_platform.is_rocm(), reason="ROCm-only")
59
+@pytest.mark.skipif(not is_aiter_found(), reason="AITER is not installed")
60
+@multi_gpu_test(num_gpus=2)
61
+@pytest.mark.parametrize(
62
+ "quick_reduce_quantization",
63
+ [
64
+ pytest.param("FP", id="quick-reduce-on"),
65
+ pytest.param("NONE", id="quick-reduce-off"),
66
+ ],
67
+)
68
+@pytest.mark.parametrize(
69
+ "cudagraph_mode",
70
+ [
71
+ pytest.param(CUDAGraphMode.NONE, id="cudagraph-none"),
72
+ pytest.param(CUDAGraphMode.FULL, id="cudagraph-full"),
73
+ ],
74
+)
75
+@pytest.mark.parametrize(
76
+ "model,max_tokens",
77
+ [
78
+ pytest.param("facebook/opt-125m", 8, id="opt-125m"),
79
+ ],
80
+)
81
+def test_rocm_aiter_custom_ar_e2e(
82
+ vllm_runner: type[VllmRunner],
83
+ monkeypatch: pytest.MonkeyPatch,
84
+ cudagraph_mode: CUDAGraphMode,
85
+ quick_reduce_quantization: str,
86
+ model: str,
87
+ max_tokens: int,
88
+):
89
+ compilation_mode = (
90
+ CompilationMode.NONE
91
+ if cudagraph_mode == CUDAGraphMode.NONE
92
+ else CompilationMode.VLLM_COMPILE
93
+ )
94
+ compilation_config = CompilationConfig(
95
+ mode=compilation_mode,
96
+ cudagraph_mode=cudagraph_mode,
97
+ )
98
+
99
+ baseline_generations = _run_generation(
100
+ vllm_runner,
101
+ monkeypatch,
102
+ compilation_config,
103
+ model=model,
104
+ max_tokens=max_tokens,
105
+ use_aiter_custom_ar=False,
106
+ quick_reduce_quantization=quick_reduce_quantization,
107
+ )
108
+ aiter_custom_ar_generations = _run_generation(
109
+ vllm_runner,
110
+ monkeypatch,
111
+ compilation_config,
112
+ model=model,
113
+ max_tokens=max_tokens,
114
+ use_aiter_custom_ar=True,
115
+ quick_reduce_quantization=quick_reduce_quantization,
116
+ )
117
+
118
+ assert aiter_custom_ar_generations == baseline_generations
vllm/_aiter_ops.py
+34
-95
@@ -2,12 +2,9 @@
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
5
6
import torch
7
from torch._ops import OpOverload
10
-from torch.distributed import ProcessGroup
8
9
import vllm.envs as envs
10
from vllm.platforms import current_platform
@@ -52,42 +49,6 @@ def is_aiter_found() -> bool:
49
IS_AITER_FOUND = is_aiter_found()
50
51
55
-class AiterCustomAllreduceProto(Protocol):
56
- max_size: int
57
- world_size: int
58
- fully_connected: bool
59
-
60
- @contextmanager
61
- def capture(self): ...
62
- def close(self) -> None: ...
63
- def fused_ar_rms(
64
- self,
65
- inp: torch.Tensor,
66
- res_inp: torch.Tensor,
67
- *,
68
- w: torch.Tensor,
69
- eps: float,
70
- registered: bool = False,
71
- use_1stage: bool = False,
72
- ) -> tuple[torch.Tensor, torch.Tensor]: ...
73
- def fused_ar_rms_per_group_quant(
74
- self,
75
- inp: torch.Tensor,
76
- res_inp: torch.Tensor,
77
- *,
78
- w: torch.Tensor,
79
- eps: float,
80
- group_size: int = 128,
81
- registered: bool = False,
82
- use_1stage: bool = False,
83
- emit_bf16: bool = False,
84
- ) -> (
85
- tuple[torch.Tensor, torch.Tensor, torch.Tensor]
86
- | tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]
87
- ): ...
88
- def should_custom_ar(self, inp: torch.Tensor) -> bool: ...
89
-
90
-
52
def is_aiter_found_and_supported() -> bool:
53
"""Check if AITER library is available and platform supports it.
54
@@ -830,6 +791,7 @@ def _rocm_aiter_fused_allreduce_rmsnorm_impl(
791
) -> tuple[torch.Tensor, torch.Tensor]:
792
aiter_ar = rocm_aiter_ops.get_aiter_allreduce()
793
assert aiter_ar is not None, "aiter allreduce must be initialized"
794
+ ca = aiter_ar.aiter_ca
795
796
total_bytes = input_.numel() * input_.element_size()
797
hidden_dim = input_.shape[-1]
@@ -840,8 +802,8 @@ def _rocm_aiter_fused_allreduce_rmsnorm_impl(
802
else:
803
hidden_ok = False
804
token_ok = token_num <= 80
843
- world_size = aiter_ar.world_size
844
- full_nvlink = aiter_ar.fully_connected
805
+ world_size = ca.world_size
806
+ full_nvlink = ca.fully_connected
807
808
if world_size == 2:
809
size_ok = True
@@ -854,12 +816,11 @@ def _rocm_aiter_fused_allreduce_rmsnorm_impl(
816
817
use_1stage = hidden_ok and token_ok and size_ok
818
857
- result = aiter_ar.fused_ar_rms(
819
+ result = ca.custom_fused_ar_rms(
820
input_,
821
residual,
860
- w=weight,
861
- eps=epsilon,
862
- registered=torch.cuda.is_current_stream_capturing(),
822
+ weight,
823
+ epsilon,
824
use_1stage=use_1stage,
825
)
826
assert result is not None
@@ -890,6 +851,7 @@ def _rocm_aiter_fused_allreduce_rmsnorm_quant_per_group_impl(
851
"""
852
aiter_ar = rocm_aiter_ops.get_aiter_allreduce()
853
assert aiter_ar is not None, "aiter allreduce must be initialized"
854
+ ca = aiter_ar.aiter_ca
855
856
total_bytes = input_.numel() * input_.element_size()
857
hidden_dim = input_.shape[-1]
@@ -900,8 +862,8 @@ def _rocm_aiter_fused_allreduce_rmsnorm_quant_per_group_impl(
862
else:
863
hidden_ok = False
864
token_ok = token_num <= 80
903
- world_size = aiter_ar.world_size
904
- full_nvlink = aiter_ar.fully_connected
865
+ world_size = ca.world_size
866
+ full_nvlink = ca.fully_connected
867
868
if world_size == 2:
869
size_ok = True
@@ -914,7 +876,7 @@ def _rocm_aiter_fused_allreduce_rmsnorm_quant_per_group_impl(
876
877
use_1stage = hidden_ok and token_ok and size_ok
878
917
- result = aiter_ar.fused_ar_rms_per_group_quant(
879
+ result = ca.fused_ar_rms_per_group_quant(
880
input_,
881
residual,
882
w=weight,
@@ -962,6 +924,7 @@ def _rocm_aiter_fused_allreduce_rmsnorm_quant_per_group_with_bf16_norm_impl(
924
"""
925
aiter_ar = rocm_aiter_ops.get_aiter_allreduce()
926
assert aiter_ar is not None, "aiter allreduce must be initialized"
927
+ ca = aiter_ar.aiter_ca
928
929
total_bytes = input_.numel() * input_.element_size()
930
hidden_dim = input_.shape[-1]
@@ -972,8 +935,8 @@ def _rocm_aiter_fused_allreduce_rmsnorm_quant_per_group_with_bf16_norm_impl(
935
else:
936
hidden_ok = False
937
token_ok = token_num <= 80
975
- world_size = aiter_ar.world_size
976
- full_nvlink = aiter_ar.fully_connected
938
+ world_size = ca.world_size
939
+ full_nvlink = ca.fully_connected
940
941
if world_size == 2:
942
size_ok = True
@@ -986,7 +949,7 @@ def _rocm_aiter_fused_allreduce_rmsnorm_quant_per_group_with_bf16_norm_impl(
949
950
use_1stage = hidden_ok and token_ok and size_ok
951
989
- result = aiter_ar.fused_ar_rms_per_group_quant(
952
+ result = ca.fused_ar_rms_per_group_quant(
953
input_,
954
residual,
955
w=weight,
@@ -1577,6 +1540,7 @@ class rocm_aiter_ops:
1540
1541
# Check if the env variable is set
1542
_AITER_ENABLED = envs.VLLM_ROCM_USE_AITER
1543
+ _CUSTOM_ALL_REDUCE_ENABLED = envs.VLLM_ROCM_USE_AITER_CUSTOM_AR
1544
_LINEAR_ENABLED = envs.VLLM_ROCM_USE_AITER_LINEAR
1545
_FMOE_ENABLED = envs.VLLM_ROCM_USE_AITER_MOE
1546
_MLA_ENABLED = envs.VLLM_ROCM_USE_AITER_MLA
@@ -1598,9 +1562,6 @@ class rocm_aiter_ops:
1562
# num_shared_experts / shared_expert_scoring_func args (7-arg form).
1563
_TOPK_SOFTMAX_FUSED_SIGMOID: bool | None = None
1564
1601
- _ALL_REDUCE_MAX_SIZE: int = 8192 * 1024 * 8 * 2
1602
- _CUSTOM_ALL_REDUCE: AiterCustomAllreduceProto | None = None
1603
-
1565
@classmethod
1566
def refresh_env_variables(cls):
1567
"""
@@ -1611,6 +1572,7 @@ class rocm_aiter_ops:
1572
you can call this function to reload the env variables.
1573
"""
1574
cls._AITER_ENABLED = envs.VLLM_ROCM_USE_AITER
1575
+ cls._CUSTOM_ALL_REDUCE_ENABLED = envs.VLLM_ROCM_USE_AITER_CUSTOM_AR
1576
cls._LINEAR_ENABLED = envs.VLLM_ROCM_USE_AITER_LINEAR
1577
cls._FMOE_ENABLED = envs.VLLM_ROCM_USE_AITER_MOE
1578
cls._MLA_ENABLED = envs.VLLM_ROCM_USE_AITER_MLA
@@ -1770,6 +1732,11 @@ class rocm_aiter_ops:
1732
def is_mha_enabled(cls) -> bool:
1733
return cls._AITER_ENABLED and cls._MHA_ENABLED
1734
1735
+ @classmethod
1736
+ @if_aiter_supported
1737
+ def is_custom_all_reduce_enabled(cls) -> bool:
1738
+ return cls._AITER_ENABLED and cls._CUSTOM_ALL_REDUCE_ENABLED
1739
+
1740
@classmethod
1741
@if_aiter_supported
1742
def is_shuffle_kv_cache_enabled(cls) -> bool:
@@ -1824,33 +1791,20 @@ class rocm_aiter_ops:
1791
return cls.is_linear_enabled() and on_gfx950()
1792
1793
@classmethod
1827
- def initialize_aiter_allreduce(
1828
- cls, group: ProcessGroup, device: torch.device
1829
- ) -> None:
1830
- try:
1831
- from aiter.dist.device_communicators.custom_all_reduce import (
1832
- CustomAllreduce as AiterCustomAllreduce,
1833
- )
1834
-
1835
- cls._CUSTOM_ALL_REDUCE = AiterCustomAllreduce(group, device)
1836
- except Exception:
1837
- cls._CUSTOM_ALL_REDUCE = None
1838
-
1839
- @classmethod
1840
- def get_aiter_allreduce(cls) -> AiterCustomAllreduceProto | None:
1841
- return cls._CUSTOM_ALL_REDUCE
1842
-
1843
- @classmethod
1844
- def destroy_aiter_allreduce(cls) -> None:
1845
- if cls._CUSTOM_ALL_REDUCE is not None:
1846
- cls._CUSTOM_ALL_REDUCE.close()
1847
- cls._CUSTOM_ALL_REDUCE = None
1794
+ def get_aiter_allreduce(cls):
1795
+ """Return the TP device communicator's AITER custom-allreduce if it has
1796
+ one, return None otherwise
1797
+ """
1798
+ from vllm.distributed.device_communicators.aiter_custom_all_reduce import (
1799
+ AiterCustomAllreduce,
1800
+ )
1801
+ from vllm.distributed.parallel_state import get_tp_group
1802
1849
- @classmethod
1850
- def get_aiter_allreduce_max_size(cls) -> int | None:
1851
- # effective max input size (based on upstream aiter version: v0.1.10.post3)
1852
- # https://github.com/ROCm/aiter/blob/6a0e7b26ccf33164785531212cc2ec2cde0b9243/aiter/dist/device_communicators/custom_all_reduce.py#L272-L273
1853
- return int(cls._ALL_REDUCE_MAX_SIZE / 2)
1803
+ device_comm = get_tp_group().device_communicator
1804
+ aiter_ar_comm = getattr(device_comm, "aiter_ar_comm", None)
1805
+ return (
1806
+ aiter_ar_comm if isinstance(aiter_ar_comm, AiterCustomAllreduce) else None
1807
+ )
1808
1809
@classmethod
1810
@if_aiter_supported
@@ -2165,21 +2119,6 @@ class rocm_aiter_ops:
2119
def get_fused_allreduce_rmsnorm_quant_per_group_with_bf16_norm_op() -> OpOverload: # noqa: E501
2120
return torch.ops.vllm.rocm_aiter_fused_allreduce_rmsnorm_quant_per_group_with_bf16_norm.default # noqa: E501
2121
2168
- # TODO(frida-andersson): drop once vLLM pins AITER >= 0.1.14 (ROCm/aiter#2823).
2169
- @classmethod
2170
- def has_fused_allreduce_rmsnorm_quant_per_group(cls) -> bool:
2171
- """True if the running AITER build exposes the per-group AR+RMS+quant
2172
- kernel (added in ROCm/aiter PR #2823).
2173
-
2174
- The pattern registration in ``RocmAiterAllReduceFusionPass`` keys off
2175
- this so vLLM degrades to the AR+RMS-only fusion when run against an
2176
- older aiter that lacks the per-group launcher.
2177
- """
2178
- aiter_ar = cls.get_aiter_allreduce()
2179
- return aiter_ar is not None and hasattr(
2180
- aiter_ar, "fused_ar_rms_per_group_quant"
2181
- )
2182
-
2122
@staticmethod
2123
def get_fused_mla_dual_rms_norm_op() -> OpOverload:
2124
return torch.ops.vllm.fused_mla_dual_rms_norm.default
vllm/compilation/passes/fusion/allreduce_rms_fusion.py
+10
-39
@@ -19,7 +19,6 @@ from vllm.compilation.passes.fusion.rms_quant_fusion import (
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
22
from vllm.distributed.parallel_state import (
23
get_tensor_model_parallel_rank,
24
get_tensor_model_parallel_world_size,
@@ -1473,39 +1472,23 @@ class RocmAiterAllReduceFusionPass(VllmFusionPatternMatcherPass):
1472
)
1473
return
1474
1476
- device_comm = get_tp_group().device_communicator
1477
- if device_comm is None:
1478
- logger.warning_once("Device communicator is required.")
1479
- return
1480
-
1481
- ca_comm = getattr(device_comm, "ca_comm", None)
1475
+ ca_comm = rocm_aiter_ops.get_aiter_allreduce()
1476
if ca_comm is None:
1483
- logger.warning_once("Custom Allreduce is required.")
1477
+ logger.warning_once(
1478
+ "AITER allreduce fusions are disabled "
1479
+ "because AITER Custom All Reduce is not enabled. "
1480
+ "Set VLLM_ROCM_USE_AITER_CUSTOM_AR=1 "
1481
+ "to enable it."
1482
+ )
1483
return
1484
self.ca_comm = ca_comm
1485
1487
- assert isinstance(ca_comm, CustomAllreduce)
1488
-
1489
- group = get_tp_group().cpu_group
1490
- rocm_aiter_ops.initialize_aiter_allreduce(group, self.device)
1486
hidden_dim = config.model_config.get_hidden_size()
1487
element_size = torch.tensor([], dtype=self.model_dtype).element_size()
1493
- max_size = rocm_aiter_ops.get_aiter_allreduce_max_size()
1494
- if max_size is None:
1495
- logger.warning("AITER allreduce fusion must be initialized")
1496
- return
1497
-
1498
- # Aiter's fused_allreduce_rmsnorm kernel dispatches on hidden_dim.
1499
- # Before aiter v0.1.12 the launcher was template-specialized on HIDDEN_DIM
1500
- # and silently no-op'd for sizes outside {512, 1024, 2048, 4096}. From v0.1.12
1501
- # hidden_dim is a runtime argument. Detect the older API via the missing
1502
- # `_pool` attribute and skip fusion for unsupported sizes.
1503
- # Ref (old kernel): https://github.com/ROCm/aiter/blob/6a0e7b26ccf33164785531212cc2ec2cde0b9243/csrc/include/custom_all_reduce.cuh#L2590
1504
- aiter_ar = rocm_aiter_ops.get_aiter_allreduce()
1488
+ max_size = ca_comm.effective_max_size()
1489
_AITER_OLD_FUSED_AR_RMS_HIDDEN = (512, 1024, 2048, 4096)
1490
if (
1507
- aiter_ar is not None
1508
- and not hasattr(aiter_ar, "_pool")
1491
+ not ca_comm.supports_dynamic_hidden_dim
1492
and hidden_dim not in _AITER_OLD_FUSED_AR_RMS_HIDDEN
1493
):
1494
logger.warning_once(
@@ -1515,10 +1498,6 @@ class RocmAiterAllReduceFusionPass(VllmFusionPatternMatcherPass):
1498
_AITER_OLD_FUSED_AR_RMS_HIDDEN,
1499
hidden_dim,
1500
)
1518
- # Tear down aiter's custom-allreduce so its IPC handles don't
1519
- # race with vllm's ca_comm on the unfused fallback path.
1520
- with contextlib.suppress(Exception):
1521
- rocm_aiter_ops.destroy_aiter_allreduce()
1501
return
1502
1503
max_token_num = max_size // (hidden_dim * element_size)
@@ -1532,9 +1511,7 @@ class RocmAiterAllReduceFusionPass(VllmFusionPatternMatcherPass):
1511
# fall back to the AR+RMS-only fusion paired with PR #41825's
1512
# standalone RMS+quant fusion -- still correct, just leaves the
1513
# post-AR quant as a standalone kernel.
1535
- supports_per_group_quant = (
1536
- rocm_aiter_ops.has_fused_allreduce_rmsnorm_quant_per_group()
1537
- )
1514
+ supports_per_group_quant = ca_comm.supports_per_group_quant
1515
if not supports_per_group_quant:
1516
logger.warning_once(
1517
"AITER AR+RMS+per-group-FP8-quant fusion disabled: aiter "
@@ -1609,9 +1586,3 @@ class RocmAiterAllReduceFusionPass(VllmFusionPatternMatcherPass):
1586
logger.debug(
1587
"%s Replaced %s patterns", self.__class__.__name__, self.matched_count
1588
)
1612
-
1613
- def __del__(self) -> None:
1614
- if getattr(self, "disabled", True):
1615
- return
1616
- with contextlib.suppress(Exception):
1617
- rocm_aiter_ops.destroy_aiter_allreduce()
vllm/config/vllm.py
+7
-2
@@ -1850,8 +1850,13 @@ class VllmConfig:
1850
tp_size = self.parallel_config.tensor_parallel_size
1851
from vllm._aiter_ops import rocm_aiter_ops
1852
1853
- if rocm_aiter_ops.is_enabled():
1854
- max_size = rocm_aiter_ops.get_aiter_allreduce_max_size()
1853
+ max_size: int | None = None
1854
+ if rocm_aiter_ops.is_custom_all_reduce_enabled():
1855
+ from vllm.distributed.device_communicators.aiter_custom_all_reduce import ( # noqa: E501
1856
+ AiterCustomAllreduce,
1857
+ )
1858
+
1859
+ max_size = AiterCustomAllreduce.effective_max_size()
1860
else:
1861
max_size = compilation_config.pass_config.flashinfer_max_size(tp_size)
1862
if max_size is not None and self.model_config is not None:
vllm/distributed/device_communicators/aiter_custom_all_reduce.py
new
+95
@@ -0,0 +1,95 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+"""vLLM-owned wrapper over AITER's ``CustomAllreduce``.
4
+
5
+vLLM's ``CudaCommunicator`` stores one of these as ``aiter_ar_comm`` (when
6
+``VLLM_ROCM_USE_AITER_CUSTOM_AR`` is set) so the plain allreduce and
7
+the fused allreduce+RMSNorm path share a single AITER instance with its IPC buffers.
8
+
9
+"""
10
+
11
+import torch
12
+from torch.distributed import ProcessGroup
13
+
14
+from vllm.logger import init_logger
15
+
16
+logger = init_logger(__name__)
17
+
18
+
19
+class AiterCustomAllreduce:
20
+ # Default IPC buffer size for AITER's CustomAllreduce.
21
+ MAX_SIZE: int = 8192 * 1024 * 8 * 2
22
+
23
+ @classmethod
24
+ def effective_max_size(cls) -> int:
25
+ """
26
+ Max input byte size eligible for AITER custom allreduce.
27
+ """
28
+ return cls.MAX_SIZE // 2
29
+
30
+ def __init__(
31
+ self,
32
+ group: ProcessGroup,
33
+ device: int | str | torch.device,
34
+ max_size: int | None = None,
35
+ ):
36
+ from aiter.dist.device_communicators.custom_all_reduce import (
37
+ CustomAllreduce as _AiterCustomAllreduce,
38
+ )
39
+
40
+ if max_size is None:
41
+ max_size = self.MAX_SIZE
42
+
43
+ self._impl = _AiterCustomAllreduce(group, device, max_size=max_size)
44
+
45
+ @property
46
+ def aiter_ca(self):
47
+ return self._impl
48
+
49
+ @property
50
+ def disabled(self) -> bool:
51
+ return self._impl.disabled
52
+
53
+ def should_custom_ar(self, inp: torch.Tensor) -> bool:
54
+ return self._impl.should_custom_ar(inp)
55
+
56
+ def custom_all_reduce(self, inp: torch.Tensor) -> torch.Tensor | None:
57
+ return self._impl.custom_all_reduce(inp)
58
+
59
+ def capture(self):
60
+ return self._impl.capture()
61
+
62
+ def close(self) -> None:
63
+ self._impl.close()
64
+
65
+ @property
66
+ def supports_dynamic_hidden_dim(self) -> bool:
67
+ """Aiter's fused_allreduce_rmsnorm kernel dispatches on hidden_dim.
68
+ Before aiter v0.1.12 the launcher was template-specialized on HIDDEN_DIM
69
+ and silently no-op'd for sizes outside {512, 1024, 2048, 4096}. From v0.1.12
70
+ hidden_dim is a runtime argument. Older builds are detected via
71
+ AiterCustomAllreduce.supports_dynamic_hidden_dim; This function is used to
72
+ skip fusion for unsupported sizes on them.
73
+ Ref (old kernel): https://github.com/ROCm/aiter/blob/6a0e7b26ccf33164785531212cc2ec2cde0b9243/csrc/include/custom_all_reduce.cuh#L2590
74
+ """
75
+ return hasattr(self._impl, "_pool")
76
+
77
+ @staticmethod
78
+ def build_supports_per_group_quant() -> bool:
79
+ """True if the running AITER build exposes the per-group AR+RMS+quant
80
+ kernel (added in ROCm/aiter PR #2823).
81
+
82
+ The pattern registration in ``RocmAiterAllReduceFusionPass`` keys off
83
+ this so vLLM degrades to the AR+RMS-only fusion when run against an
84
+ older aiter that lacks the per-group launcher.
85
+ """
86
+ from aiter.dist.device_communicators.custom_all_reduce import (
87
+ CustomAllreduce as _AiterCustomAllreduce,
88
+ )
89
+
90
+ return hasattr(_AiterCustomAllreduce, "fused_ar_rms_per_group_quant")
91
+
92
+ # TODO(frida-andersson): drop once vLLM pins AITER >= 0.1.14 (ROCm/aiter#2823).
93
+ @property
94
+ def supports_per_group_quant(self) -> bool:
95
+ return self.build_supports_per_group_quant()
vllm/distributed/device_communicators/cuda_communicator.py
+40
-10
@@ -6,6 +6,7 @@ import torch
6
from torch.distributed import ProcessGroup
7
8
import vllm.envs as envs
9
+from vllm._aiter_ops import rocm_aiter_ops
10
from vllm.distributed.device_communicators.all_reduce_utils import (
11
NCCL_SYMM_MEM_ALL_REDUCE_CONFIG,
12
should_nccl_symm_mem_ag_rs,
@@ -19,6 +20,7 @@ from vllm.logger import init_logger
20
from vllm.platforms import current_platform
21
22
from ..utils import StatelessProcessGroup
23
+from .aiter_custom_all_reduce import AiterCustomAllreduce
24
from .base_device_communicator import DeviceCommunicatorBase
25
26
logger = init_logger(__name__)
@@ -48,16 +50,21 @@ class CudaCommunicator(DeviceCommunicatorBase):
50
use_custom_allreduce = False
51
use_torch_symm_mem = False
52
use_flashinfer_allreduce = False
53
+ use_aiter_allreduce = False
54
else:
55
from vllm.distributed.parallel_state import _ENABLE_CUSTOM_ALL_REDUCE
56
57
use_custom_allreduce = _ENABLE_CUSTOM_ALL_REDUCE
58
use_torch_symm_mem = envs.VLLM_ALLREDUCE_USE_SYMM_MEM
59
use_flashinfer_allreduce = envs.VLLM_ALLREDUCE_USE_FLASHINFER
60
+ use_aiter_allreduce = use_custom_allreduce and bool(
61
+ rocm_aiter_ops.is_custom_all_reduce_enabled()
62
+ )
63
64
self.use_custom_allreduce = use_custom_allreduce
65
self.use_torch_symm_mem = use_torch_symm_mem
66
self.use_flashinfer_allreduce = use_flashinfer_allreduce
67
+ self.use_aiter_allreduce = use_aiter_allreduce
68
69
# lazy import to avoid documentation build error
70
from vllm.distributed.device_communicators.custom_all_reduce import (
@@ -85,6 +92,7 @@ class CudaCommunicator(DeviceCommunicatorBase):
92
self.qr_comm: QuickAllReduce | None = None
93
self.symm_mem_comm: SymmMemCommunicator | None = None
94
self.fi_ar_comm: FlashInferAllReduce | None = None
95
+ self.aiter_ar_comm: AiterCustomAllreduce | None = None
96
97
if use_torch_symm_mem and current_platform.is_cuda():
98
self.symm_mem_comm = SymmMemCommunicator(
@@ -98,7 +106,13 @@ class CudaCommunicator(DeviceCommunicatorBase):
106
device=self.device,
107
)
108
101
- if use_custom_allreduce and self.world_size > 1:
109
+ if self.use_aiter_allreduce and self.world_size > 1:
110
+ self.aiter_ar_comm = AiterCustomAllreduce(
111
+ group=self.cpu_group,
112
+ device=self.device,
113
+ )
114
+
115
+ if use_custom_allreduce and self.aiter_ar_comm is None and self.world_size > 1:
116
# Initialize a custom fast all-reduce implementation.
117
self.ca_comm = CustomAllreduce(
118
group=self.cpu_group,
@@ -108,13 +122,14 @@ class CudaCommunicator(DeviceCommunicatorBase):
122
),
123
)
124
111
- if current_platform.is_rocm():
112
- # Initialize a custom quick all-reduce implementation for AMD.
113
- # Quick reduce is designed as a complement to custom allreduce.
114
- # Based on quickreduce (https://github.com/mk1-project/quickreduce).
115
- # If it's a rocm, 'use_custom_allreduce==True' means it must
116
- # currently be an MI300 series.
117
- self.qr_comm = QuickAllReduce(group=self.cpu_group, device=self.device)
125
+ if use_custom_allreduce and self.world_size > 1 and current_platform.is_rocm():
126
+ # Initialize a custom quick all-reduce implementation for AMD.
127
+ # Quick reduce is designed as a complement to custom allreduce
128
+ # (vLLM's or AITER's), so it is initialized for either backend.
129
+ # Based on quickreduce (https://github.com/mk1-project/quickreduce).
130
+ # On ROCm, 'use_custom_allreduce==True' means it must currently be
131
+ # an MI300 series.
132
+ self.qr_comm = QuickAllReduce(group=self.cpu_group, device=self.device)
133
134
if self.world_size > 1:
135
self._log_all_reduce_backend_selection()
@@ -203,6 +218,7 @@ class CudaCommunicator(DeviceCommunicatorBase):
218
"NCCL_SYMM_MEM",
219
"QUICK_REDUCE",
220
"FLASHINFER",
221
+ "AITER_CUSTOM",
222
"CUSTOM",
223
"SYMM_MEM",
224
"PYNCCL",
@@ -236,6 +252,8 @@ class CudaCommunicator(DeviceCommunicatorBase):
252
enabled_ar_backends.append("QUICK_REDUCE")
253
if self.fi_ar_comm is not None and not self.fi_ar_comm.disabled:
254
enabled_ar_backends.append("FLASHINFER")
255
+ if self.aiter_ar_comm is not None and not self.aiter_ar_comm.disabled:
256
+ enabled_ar_backends.append("AITER_CUSTOM")
257
if self.ca_comm is not None and not self.ca_comm.disabled:
258
enabled_ar_backends.append("CUSTOM")
259
if self.symm_mem_comm is not None and not self.symm_mem_comm.disabled:
@@ -261,8 +279,8 @@ class CudaCommunicator(DeviceCommunicatorBase):
279
out = torch.ops.vllm.all_reduce_symmetric_with_copy(input_)
280
if out is not None:
281
return out
264
- # always try quick reduce first, then flashinfer, then custom allreduce,
265
- # and then pynccl. (quick reduce just for ROCM MI3*)
282
+ # always try quick reduce first, then flashinfer, then the AITER or vLLM
283
+ # custom allreduce, and then pynccl. (quick reduce just for ROCM MI3*)
284
qr_comm = self.qr_comm
285
if (
286
qr_comm is not None
@@ -281,6 +299,15 @@ class CudaCommunicator(DeviceCommunicatorBase):
299
out = fi_ar_comm.all_reduce(input_)
300
assert out is not None
301
return out
302
+ aiter_ar_comm = self.aiter_ar_comm
303
+ if (
304
+ aiter_ar_comm is not None
305
+ and not aiter_ar_comm.disabled
306
+ and aiter_ar_comm.should_custom_ar(input_)
307
+ ):
308
+ out = aiter_ar_comm.custom_all_reduce(input_)
309
+ assert out is not None
310
+ return out
311
ca_comm = self.ca_comm
312
if (
313
ca_comm is not None
@@ -509,6 +536,9 @@ class CudaCommunicator(DeviceCommunicatorBase):
536
self.pynccl_comm = None
537
if self.ca_comm is not None:
538
self.ca_comm = None
539
+ if self.aiter_ar_comm is not None:
540
+ self.aiter_ar_comm.close()
541
+ self.aiter_ar_comm = None
542
if self.fi_ar_comm is not None:
543
self.fi_ar_comm.destroy()
544
self.fi_ar_comm = None
vllm/envs.py
+6
@@ -119,6 +119,7 @@ if TYPE_CHECKING:
119
VLLM_USE_OINK_OPS: bool = False
120
VLLM_MXFP8_EMULATION_DEQUANT_AT_LOAD: bool = True
121
VLLM_ROCM_USE_AITER: bool = False
122
+ VLLM_ROCM_USE_AITER_CUSTOM_AR: bool = True
123
VLLM_ROCM_USE_AITER_PAGED_ATTN: bool = False
124
VLLM_ROCM_USE_AITER_LINEAR: bool = True
125
VLLM_ROCM_USE_AITER_LINEAR_HIPBMM: bool = False
@@ -1146,6 +1147,11 @@ environment_variables: dict[str, Callable[[], Any]] = {
1147
"VLLM_ROCM_USE_AITER": lambda: (
1148
os.getenv("VLLM_ROCM_USE_AITER", "False").lower() in ("true", "1")
1149
),
1150
+ # Use AITER's CustomAllreduce as the custom-allreduce backend inside vLLM's
1151
+ # CudaCommunicator on ROCm.
1152
+ "VLLM_ROCM_USE_AITER_CUSTOM_AR": lambda: (
1153
+ os.getenv("VLLM_ROCM_USE_AITER_CUSTOM_AR", "True").lower() in ("true", "1")
1154
+ ),
1155
# Whether to use aiter paged attention.
1156
# By default is disabled.
1157
"VLLM_ROCM_USE_AITER_PAGED_ATTN": lambda: (