[Distributed] Add opt-in FlashInfer PCIe IPC all-reduce backend (#53576)
Signed-off-by: Zihua Wu <13583761+lucifer1004@users.noreply.github.com> Co-authored-by: Yongye Zhu <zyy1102000@gmail.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Gabriel Wu committed
Sep 1, 2026 at 11:34 UTC
446c769482dca3278f9d581d72f9b1a05c0690e0
7 files changed
+450
-4
tests/distributed/test_comm_ops.py
+1
@@ -396,6 +396,7 @@ def test_flashinfer_all_reduce_precedes_nccl(monkeypatch: pytest.MonkeyPatch) ->
396
fi_ar_comm.all_reduce.return_value = output
397
communicator = CudaCommunicator.__new__(CudaCommunicator)
398
communicator.fi_ar_comm = fi_ar_comm
399
+ communicator.fi_pcie_ipc_ar_comm = None
400
communicator.pynccl_comm = Mock(world_size=8)
401
communicator.qr_comm = None
402
nccl_selector = Mock(return_value=True)
tests/distributed/test_flashinfer_pcie_ipc_all_reduce.py
new
+146
@@ -0,0 +1,146 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
+from types import SimpleNamespace
5
+from unittest.mock import Mock
6
+
7
+import pytest
8
+import torch
9
+
10
+from vllm.distributed.device_communicators import cuda_communicator
11
+from vllm.distributed.device_communicators import (
12
+ flashinfer_pcie_ipc_all_reduce as pcie_ipc,
13
+)
14
+from vllm.distributed.parallel_state import GroupCoordinator
15
+
16
+
17
+def _uninitialized_comm() -> pcie_ipc.FlashInferPcieIpcAllReduce:
18
+ comm = pcie_ipc.FlashInferPcieIpcAllReduce.__new__(
19
+ pcie_ipc.FlashInferPcieIpcAllReduce
20
+ )
21
+ comm.disabled = False
22
+ comm.group = object()
23
+ comm.tune_group = object()
24
+ comm.device = torch.device("cuda:0")
25
+ comm.world_size = 4
26
+ comm.rank = 0
27
+ comm.workspace = None
28
+ comm.hidden_dim = 0
29
+ comm.dtype = None
30
+ return comm
31
+
32
+
33
+def test_setup_uses_exact_graph_capacity(monkeypatch, tmp_path):
34
+ workspace = Mock()
35
+ factory = Mock(return_value=workspace)
36
+ monkeypatch.setattr(
37
+ pcie_ipc,
38
+ "flashinfer_comm",
39
+ SimpleNamespace(PcieIpcAllReduceWorkspace=factory),
40
+ )
41
+ monkeypatch.setattr(torch.accelerator, "synchronize", Mock())
42
+
43
+ comm = _uninitialized_comm()
44
+ cache = tmp_path / "tp4.json"
45
+ comm.setup(
46
+ hidden_dim=4096,
47
+ dtype=torch.bfloat16,
48
+ capture_sizes=[32, 8, 16, 32],
49
+ tune_cache=cache,
50
+ )
51
+
52
+ factory.assert_called_once_with(
53
+ group=comm.group,
54
+ max_numel=32 * 4096,
55
+ dtype=torch.bfloat16,
56
+ tune_batches=(8, 16, 32),
57
+ tune_cache=str(cache),
58
+ )
59
+ workspace.tune.assert_called_once_with(
60
+ [4096], dtype=torch.bfloat16, tune_group=comm.tune_group
61
+ )
62
+ workspace.prepare.assert_called_once_with(
63
+ [(8, 4096), (16, 4096), (32, 4096)], dtype=torch.bfloat16
64
+ )
65
+ assert workspace.rebind_stream.call_count == 2
66
+
67
+
68
+@pytest.mark.parametrize(
69
+ ("shape", "dtype", "contiguous", "expected"),
70
+ [
71
+ ((16, 4096), torch.bfloat16, True, True),
72
+ ((16, 2048), torch.bfloat16, True, False),
73
+ ((16, 4096), torch.float16, True, False),
74
+ ((16, 4096), torch.bfloat16, False, False),
75
+ ],
76
+)
77
+def test_should_use_only_prepared_hidden_shape(shape, dtype, contiguous, expected):
78
+ comm = _uninitialized_comm()
79
+ comm.hidden_dim = 4096
80
+ comm.dtype = torch.bfloat16
81
+ comm.workspace = Mock()
82
+ comm.workspace.supports.return_value = True
83
+ inp = SimpleNamespace(
84
+ is_cuda=True,
85
+ is_contiguous=lambda: contiguous,
86
+ dim=lambda: len(shape),
87
+ shape=shape,
88
+ dtype=dtype,
89
+ )
90
+
91
+ assert comm.should_use(inp) is expected
92
+
93
+
94
+def test_capture_rebinds_workspace_even_on_error(monkeypatch):
95
+ comm = _uninitialized_comm()
96
+ comm.workspace = Mock()
97
+ synchronize = Mock()
98
+ monkeypatch.setattr(torch.accelerator, "synchronize", synchronize)
99
+
100
+ with pytest.raises(RuntimeError, match="capture failed"), comm.capture():
101
+ raise RuntimeError("capture failed")
102
+
103
+ assert synchronize.call_count == 2
104
+ assert comm.workspace.rebind_stream.call_count == 2
105
+
106
+
107
+def test_cuda_dispatch_prefers_pcie_ipc_before_existing_flashinfer(monkeypatch):
108
+ communicator = cuda_communicator.CudaCommunicator.__new__(
109
+ cuda_communicator.CudaCommunicator
110
+ )
111
+ communicator.pynccl_comm = Mock(world_size=4)
112
+ communicator.qr_comm = None
113
+ communicator.fi_pcie_ipc_ar_comm = Mock()
114
+ communicator.fi_pcie_ipc_ar_comm.should_use.return_value = True
115
+ expected = object()
116
+ communicator.fi_pcie_ipc_ar_comm.all_reduce.return_value = expected
117
+ communicator.fi_ar_comm = Mock()
118
+ communicator.aiter_ar_comm = None
119
+ communicator.ca_comm = None
120
+ communicator.symm_mem_comm = None
121
+ monkeypatch.setattr(
122
+ cuda_communicator, "should_nccl_symm_mem_allreduce", lambda *_: False
123
+ )
124
+
125
+ assert communicator.all_reduce(object()) is expected
126
+ communicator.fi_ar_comm.should_use_fi_ar.assert_not_called()
127
+
128
+
129
+def test_group_destroy_releases_communicator_before_process_groups(monkeypatch):
130
+ events = []
131
+ coordinator = GroupCoordinator.__new__(GroupCoordinator)
132
+ coordinator.device_communicator = SimpleNamespace(
133
+ destroy=lambda: events.append("communicator")
134
+ )
135
+ coordinator.device_group = object()
136
+ coordinator.cpu_group = object()
137
+ coordinator.mq_broadcaster = None
138
+ monkeypatch.setattr(
139
+ torch.distributed,
140
+ "destroy_process_group",
141
+ lambda _group: events.append("process_group"),
142
+ )
143
+
144
+ coordinator.destroy()
145
+
146
+ assert events == ["communicator", "process_group", "process_group"]
vllm/distributed/device_communicators/cuda_communicator.py
+33
@@ -52,6 +52,7 @@ class CudaCommunicator(DeviceCommunicatorBase):
52
use_custom_allreduce = False
53
use_torch_symm_mem = False
54
use_flashinfer_allreduce = False
55
+ use_flashinfer_pcie_ipc_allreduce = False
56
use_aiter_allreduce = False
57
else:
58
from vllm.distributed.parallel_state import _ENABLE_CUSTOM_ALL_REDUCE
@@ -62,6 +63,10 @@ class CudaCommunicator(DeviceCommunicatorBase):
63
use_flashinfer_allreduce = (
64
envs.VLLM_ALLREDUCE_USE_FLASHINFER and not envs.VLLM_BATCH_INVARIANT
65
)
66
+ use_flashinfer_pcie_ipc_allreduce = (
67
+ envs.VLLM_ALLREDUCE_USE_FLASHINFER_PCIE_IPC
68
+ and not envs.VLLM_BATCH_INVARIANT
69
+ )
70
use_aiter_allreduce = use_custom_allreduce and bool(
71
rocm_aiter_ops.is_custom_all_reduce_enabled()
72
)
@@ -69,6 +74,7 @@ class CudaCommunicator(DeviceCommunicatorBase):
74
self.use_custom_allreduce = use_custom_allreduce
75
self.use_torch_symm_mem = use_torch_symm_mem
76
self.use_flashinfer_allreduce = use_flashinfer_allreduce
77
+ self.use_flashinfer_pcie_ipc_allreduce = use_flashinfer_pcie_ipc_allreduce
78
self.use_aiter_allreduce = use_aiter_allreduce
79
80
# lazy import to avoid documentation build error
@@ -78,6 +84,9 @@ class CudaCommunicator(DeviceCommunicatorBase):
84
from vllm.distributed.device_communicators.flashinfer_all_reduce import (
85
FlashInferAllReduce,
86
)
87
+ from vllm.distributed.device_communicators.flashinfer_pcie_ipc_all_reduce import ( # noqa: E501
88
+ FlashInferPcieIpcAllReduce,
89
+ )
90
from vllm.distributed.device_communicators.pynccl import PyNcclCommunicator
91
from vllm.distributed.device_communicators.quick_all_reduce import (
92
QuickAllReduce,
@@ -97,6 +106,7 @@ class CudaCommunicator(DeviceCommunicatorBase):
106
self.qr_comm: QuickAllReduce | None = None
107
self.symm_mem_comm: SymmMemCommunicator | None = None
108
self.fi_ar_comm: FlashInferAllReduce | None = None
109
+ self.fi_pcie_ipc_ar_comm: FlashInferPcieIpcAllReduce | None = None
110
self.aiter_ar_comm: AiterCustomAllreduce | None = None
111
112
if use_torch_symm_mem and current_platform.is_cuda():
@@ -111,6 +121,17 @@ class CudaCommunicator(DeviceCommunicatorBase):
121
device=self.device,
122
)
123
124
+ if (
125
+ self.use_flashinfer_pcie_ipc_allreduce
126
+ and self.world_size > 1
127
+ and self.device_group is not None
128
+ ):
129
+ self.fi_pcie_ipc_ar_comm = FlashInferPcieIpcAllReduce(
130
+ group=self.device_group,
131
+ tune_group=self.cpu_group,
132
+ device=self.device,
133
+ )
134
+
135
if self.use_aiter_allreduce and self.world_size > 1:
136
self.aiter_ar_comm = AiterCustomAllreduce(
137
group=self.cpu_group,
@@ -220,6 +241,7 @@ class CudaCommunicator(DeviceCommunicatorBase):
241
depends on the input tensor.
242
"""
243
all_potential_ar_backends = [
244
+ "FLASHINFER_PCIE_IPC",
245
"FLASHINFER",
246
"NCCL_SYMM_MEM",
247
"QUICK_REDUCE",
@@ -229,6 +251,11 @@ class CudaCommunicator(DeviceCommunicatorBase):
251
"PYNCCL",
252
]
253
enabled_ar_backends: list[str] = []
254
+ if (
255
+ self.fi_pcie_ipc_ar_comm is not None
256
+ and not self.fi_pcie_ipc_ar_comm.disabled
257
+ ):
258
+ enabled_ar_backends.append("FLASHINFER_PCIE_IPC")
259
if self.fi_ar_comm is not None and not self.fi_ar_comm.disabled:
260
enabled_ar_backends.append("FLASHINFER")
261
# Mirror the static preconditions of `should_nccl_symm_mem_allreduce`:
@@ -302,6 +329,9 @@ class CudaCommunicator(DeviceCommunicatorBase):
329
out = qr_comm.quick_all_reduce(input_)
330
assert out is not None
331
return out
332
+ fi_pcie_ipc_ar_comm = self.fi_pcie_ipc_ar_comm
333
+ if fi_pcie_ipc_ar_comm is not None and fi_pcie_ipc_ar_comm.should_use(input_):
334
+ return fi_pcie_ipc_ar_comm.all_reduce(input_)
335
if use_fi_ar:
336
assert fi_ar_comm is not None
337
out = fi_ar_comm.all_reduce(input_)
@@ -594,6 +624,9 @@ class CudaCommunicator(DeviceCommunicatorBase):
624
if self.fi_ar_comm is not None:
625
self.fi_ar_comm.destroy()
626
self.fi_ar_comm = None
627
+ if self.fi_pcie_ipc_ar_comm is not None:
628
+ self.fi_pcie_ipc_ar_comm.destroy()
629
+ self.fi_pcie_ipc_ar_comm = None
630
if self.all2all_manager is not None:
631
self.all2all_manager.destroy()
632
self.all2all_manager = None # type: ignore[assignment]
vllm/distributed/device_communicators/flashinfer_pcie_ipc_all_reduce.py
new
+239
@@ -0,0 +1,239 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+"""FlashInfer PCIe CUDA-IPC all-reduce integration.
4
+
5
+This backend targets small tensor-parallel all-reduces on a single PCIe-only
6
+node. Its workspace has a stricter lifetime and stream contract than the
7
+existing FlashInfer MNNVL/TRT-LLM all-reduce implementation, so it intentionally
8
+lives behind a separate wrapper and an opt-in environment variable.
9
+"""
10
+
11
+from collections.abc import Sequence
12
+from contextlib import contextmanager
13
+from pathlib import Path
14
+from typing import TYPE_CHECKING, Any
15
+
16
+import torch
17
+import torch.distributed as dist
18
+from torch.distributed import ProcessGroup
19
+
20
+from vllm.distributed.parallel_state import in_the_same_node_as
21
+from vllm.logger import init_logger
22
+from vllm.platforms import current_platform
23
+
24
+if TYPE_CHECKING:
25
+ from vllm.v1.worker.gpu_worker import Worker
26
+
27
+logger = init_logger(__name__)
28
+
29
+_SUPPORTED_WORLD_SIZES = (2, 4, 8)
30
+_SUPPORTED_DTYPES = (torch.bfloat16, torch.float16)
31
+
32
+try:
33
+ import flashinfer.comm as flashinfer_comm
34
+
35
+ _pcie_ipc_available = hasattr(flashinfer_comm, "PcieIpcAllReduceWorkspace")
36
+except ImportError:
37
+ flashinfer_comm = None # type: ignore[assignment]
38
+ _pcie_ipc_available = False
39
+
40
+
41
+class FlashInferPcieIpcAllReduce:
42
+ """vLLM lifecycle wrapper for FlashInfer's PCIe IPC all-reduce."""
43
+
44
+ def __init__(
45
+ self,
46
+ group: ProcessGroup,
47
+ tune_group: ProcessGroup,
48
+ device: int | str | torch.device,
49
+ ) -> None:
50
+ self.disabled = True
51
+ self.group = group
52
+ self.tune_group = tune_group
53
+ self.device = torch.device(device)
54
+ self.world_size = dist.get_world_size(group)
55
+ self.rank = dist.get_rank(group)
56
+ self.workspace: Any | None = None
57
+ self.hidden_dim = 0
58
+ self.dtype: torch.dtype | None = None
59
+
60
+ if not _pcie_ipc_available:
61
+ logger.warning_once(
62
+ "FlashInfer PCIe IPC all-reduce was requested but this "
63
+ "FlashInfer build does not provide PcieIpcAllReduceWorkspace; "
64
+ "falling back to another all-reduce backend."
65
+ )
66
+ return
67
+ if not current_platform.is_cuda():
68
+ logger.warning_once(
69
+ "FlashInfer PCIe IPC all-reduce requires the CUDA platform."
70
+ )
71
+ return
72
+ if self.world_size not in _SUPPORTED_WORLD_SIZES:
73
+ logger.warning_once(
74
+ "FlashInfer PCIe IPC all-reduce does not support world_size=%d; "
75
+ "supported sizes are %s.",
76
+ self.world_size,
77
+ _SUPPORTED_WORLD_SIZES,
78
+ )
79
+ return
80
+ if not all(in_the_same_node_as(tune_group, source_rank=0)):
81
+ logger.warning_once(
82
+ "FlashInfer PCIe IPC all-reduce requires every rank in the TP "
83
+ "group to be on one node."
84
+ )
85
+ return
86
+
87
+ # Setup is deferred until kernel_warmup, where the model hidden size and
88
+ # exact CUDA Graph buckets are known. Until then dispatch falls through.
89
+ self.disabled = False
90
+
91
+ @property
92
+ def initialized(self) -> bool:
93
+ return not self.disabled and self.workspace is not None
94
+
95
+ def setup(
96
+ self,
97
+ *,
98
+ hidden_dim: int,
99
+ dtype: torch.dtype,
100
+ capture_sizes: Sequence[int],
101
+ tune_cache: Path,
102
+ ) -> None:
103
+ """Allocate, tune, and prepare the exact graph-capture shapes."""
104
+ if self.disabled or self.workspace is not None:
105
+ return
106
+ if dtype not in _SUPPORTED_DTYPES:
107
+ logger.warning_once(
108
+ "FlashInfer PCIe IPC all-reduce does not support dtype=%s; "
109
+ "falling back to another all-reduce backend.",
110
+ dtype,
111
+ )
112
+ self.disabled = True
113
+ return
114
+
115
+ batches = tuple(sorted({int(size) for size in capture_sizes if size > 0}))
116
+ if not batches:
117
+ logger.warning_once(
118
+ "FlashInfer PCIe IPC all-reduce has no CUDA Graph capture sizes "
119
+ "to prepare; falling back to another all-reduce backend."
120
+ )
121
+ self.disabled = True
122
+ return
123
+
124
+ self.hidden_dim = int(hidden_dim)
125
+ self.dtype = dtype
126
+ max_numel = batches[-1] * self.hidden_dim
127
+ workspace = flashinfer_comm.PcieIpcAllReduceWorkspace(
128
+ group=self.group,
129
+ max_numel=max_numel,
130
+ dtype=dtype,
131
+ tune_batches=batches,
132
+ tune_cache=str(tune_cache),
133
+ )
134
+ self.workspace = workspace
135
+
136
+ # tune() reuses a complete persisted cache and profiles only cache
137
+ # misses. It is deliberately mandatory: FlashInfer's seed policy was
138
+ # substantially slower than NCCL for the target TP4 decode workload.
139
+ torch.accelerator.synchronize(self.device)
140
+ workspace.rebind_stream()
141
+ workspace.tune(
142
+ [self.hidden_dim],
143
+ dtype=dtype,
144
+ tune_group=self.tune_group,
145
+ )
146
+ workspace.prepare([(batch, self.hidden_dim) for batch in batches], dtype=dtype)
147
+ torch.accelerator.synchronize(self.device)
148
+ workspace.rebind_stream()
149
+ logger.info_once(
150
+ "Initialized FlashInfer PCIe IPC all-reduce for TP%d, hidden_dim=%d, "
151
+ "dtype=%s, max_tokens=%d.",
152
+ self.world_size,
153
+ self.hidden_dim,
154
+ dtype,
155
+ batches[-1],
156
+ )
157
+
158
+ def should_use(self, inp: torch.Tensor) -> bool:
159
+ workspace = self.workspace
160
+ return bool(
161
+ not self.disabled
162
+ and workspace is not None
163
+ and inp.is_cuda
164
+ and inp.is_contiguous()
165
+ and inp.dim() == 2
166
+ and inp.shape[1] == self.hidden_dim
167
+ and inp.dtype == self.dtype
168
+ and workspace.supports(inp)
169
+ )
170
+
171
+ def all_reduce(self, inp: torch.Tensor) -> torch.Tensor:
172
+ assert self.workspace is not None
173
+ return self.workspace.all_reduce(inp)
174
+
175
+ @contextmanager
176
+ def capture(self):
177
+ """Move the single-stream workspace to and from the capture stream."""
178
+ workspace = self.workspace
179
+ if workspace is None:
180
+ yield
181
+ return
182
+
183
+ # The caller orders graph capture against normal execution. A device
184
+ # sync makes that assertion explicit before relaxing FlashInfer's
185
+ # single-stream guard. Captured launches themselves are exempt from the
186
+ # guard, while capture warmups execute and bind to the capture stream.
187
+ torch.accelerator.synchronize(self.device)
188
+ workspace.rebind_stream()
189
+ try:
190
+ yield
191
+ finally:
192
+ torch.accelerator.synchronize(self.device)
193
+ workspace.rebind_stream()
194
+
195
+ def destroy(self) -> None:
196
+ workspace = self.workspace
197
+ if workspace is not None:
198
+ workspace.destroy()
199
+ self.workspace = None
200
+
201
+
202
+def warmup_flashinfer_pcie_ipc_allreduce(worker: "Worker") -> None:
203
+ """Initialize the TP PCIe IPC backend immediately before graph capture."""
204
+ from vllm.distributed.device_communicators.cuda_communicator import (
205
+ CudaCommunicator,
206
+ )
207
+ from vllm.distributed.parallel_state import get_tp_group
208
+ from vllm.model_executor.warmup.flashinfer_autotune_cache import (
209
+ resolve_flashinfer_autotune_file,
210
+ )
211
+
212
+ tp_group = get_tp_group()
213
+ communicator = tp_group.device_communicator
214
+ if not isinstance(communicator, CudaCommunicator):
215
+ return
216
+ pcie_comm = communicator.fi_pcie_ipc_ar_comm
217
+ if pcie_comm is None or pcie_comm.disabled:
218
+ return
219
+ if worker.vllm_config.parallel_config.use_ubatching:
220
+ logger.warning_once(
221
+ "FlashInfer PCIe IPC all-reduce does not yet support DBO or "
222
+ "multi-ubatch execution; falling back to another backend."
223
+ )
224
+ pcie_comm.disabled = True
225
+ return
226
+
227
+ capture_sizes = worker.vllm_config.compilation_config.cudagraph_capture_sizes
228
+ if not capture_sizes:
229
+ return
230
+
231
+ base_cache = resolve_flashinfer_autotune_file(worker.model_runner)
232
+ ranks = "-".join(str(rank) for rank in tp_group.ranks)
233
+ tune_cache = base_cache.with_name(f"pcie_ipc_allreduce_tp_{ranks}.json")
234
+ pcie_comm.setup(
235
+ hidden_dim=worker.model_config.get_hidden_size(),
236
+ dtype=worker.model_config.dtype,
237
+ capture_sizes=capture_sizes,
238
+ tune_cache=tune_cache,
239
+ )
vllm/distributed/parallel_state.py
+17
-4
@@ -29,7 +29,7 @@ import pickle
29
import weakref
30
from collections import deque, namedtuple
31
from collections.abc import Callable
32
-from contextlib import contextmanager, nullcontext
32
+from contextlib import AbstractContextManager, contextmanager, nullcontext
33
from dataclasses import dataclass
34
from datetime import timedelta
35
from multiprocessing import shared_memory
@@ -666,6 +666,7 @@ class GroupCoordinator:
666
# only cuda uses this function,
667
# so we don't abstract it into the base class
668
maybe_ca_context = nullcontext()
669
+ maybe_fi_pcie_ipc_context: AbstractContextManager[Any] = nullcontext()
670
maybe_aiter_context = nullcontext()
671
from vllm.distributed.device_communicators.cuda_communicator import (
672
CudaCommunicator,
@@ -682,6 +683,10 @@ class GroupCoordinator:
683
ca_comm = self.device_communicator.ca_comm
684
if ca_comm is not None:
685
maybe_ca_context = ca_comm.capture() # type: ignore
686
+ if isinstance(self.device_communicator, CudaCommunicator):
687
+ fi_pcie_ipc_ar_comm = self.device_communicator.fi_pcie_ipc_ar_comm
688
+ if fi_pcie_ipc_ar_comm is not None:
689
+ maybe_fi_pcie_ipc_context = fi_pcie_ipc_ar_comm.capture()
690
691
from vllm._aiter_ops import rocm_aiter_ops
692
@@ -696,7 +701,12 @@ class GroupCoordinator:
701
if curr_stream != stream:
702
stream.wait_stream(curr_stream)
703
699
- with torch.cuda.stream(stream), maybe_ca_context, maybe_aiter_context:
704
+ with (
705
+ torch.cuda.stream(stream),
706
+ maybe_ca_context,
707
+ maybe_fi_pcie_ipc_context,
708
+ maybe_aiter_context,
709
+ ):
710
yield graph_capture_context
711
712
def all_reduce(self, input_: torch.Tensor) -> torch.Tensor:
@@ -1360,14 +1370,17 @@ class GroupCoordinator:
1370
return self.device_communicator.recv(size, dtype, src)
1371
1372
def destroy(self):
1373
+ # Device communicators can own collective workspaces whose teardown
1374
+ # uses these process groups (for example FlashInfer PCIe IPC barriers).
1375
+ # Release them before destroying the groups they depend on.
1376
+ if self.device_communicator is not None:
1377
+ self.device_communicator.destroy()
1378
if hasattr(self, "device_group"):
1379
torch.distributed.destroy_process_group(self.device_group)
1380
del self.device_group
1381
if hasattr(self, "cpu_group"):
1382
torch.distributed.destroy_process_group(self.cpu_group)
1383
del self.cpu_group
1369
- if self.device_communicator is not None:
1370
- self.device_communicator.destroy()
1384
if self.mq_broadcaster is not None:
1385
self.mq_broadcaster = None
1386
vllm/envs.py
+7
@@ -264,6 +264,7 @@ if TYPE_CHECKING:
264
VLLM_ROCM_FP8_MFMA_PAGE_ATTN: bool = False
265
VLLM_ALLREDUCE_USE_SYMM_MEM: bool = True
266
VLLM_ALLREDUCE_USE_FLASHINFER: bool = True
267
+ VLLM_ALLREDUCE_USE_FLASHINFER_PCIE_IPC: bool = False
268
VLLM_TUNED_CONFIG_FOLDER: str | None = None
269
VLLM_ENABLE_STARTUP_PLAN: bool = False
270
VLLM_GPT_OSS_SYSTEM_TOOL_MCP_LABELS: set[str] = set()
@@ -1864,6 +1865,12 @@ environment_variables: dict[str, Callable[[], Any]] = {
1865
"VLLM_ALLREDUCE_USE_FLASHINFER": lambda: bool(
1866
int(os.getenv("VLLM_ALLREDUCE_USE_FLASHINFER", "1"))
1867
),
1868
+ # Whether to use FlashInfer's single-node PCIe CUDA-IPC all-reduce.
1869
+ # This backend has a strict single-stream contract and is opt-in while its
1870
+ # integration is being qualified.
1871
+ "VLLM_ALLREDUCE_USE_FLASHINFER_PCIE_IPC": lambda: bool(
1872
+ int(os.getenv("VLLM_ALLREDUCE_USE_FLASHINFER_PCIE_IPC", "0"))
1873
+ ),
1874
# Experimental: use this to enable MCP tool calling for non harmony models
1875
"VLLM_USE_EXPERIMENTAL_PARSER_CONTEXT": lambda: bool(
1876
int(os.getenv("VLLM_USE_EXPERIMENTAL_PARSER_CONTEXT", "0"))
vllm/model_executor/warmup/kernel_warmup.py
+7
@@ -13,6 +13,9 @@ from typing import TYPE_CHECKING
13
import torch
14
15
import vllm.envs as envs
16
+from vllm.distributed.device_communicators.flashinfer_pcie_ipc_all_reduce import (
17
+ warmup_flashinfer_pcie_ipc_allreduce,
18
+)
19
from vllm.logger import init_logger
20
from vllm.model_executor.warmup.b12x_warmup import b12x_warmup
21
from vllm.model_executor.warmup.cutedsl_warmup import cutedsl_warmup
@@ -182,6 +185,10 @@ def kernel_warmup(worker: "Worker", *, process_local_only: bool = False):
185
186
minimax_m3_msa_warmup(worker)
187
188
+ # Allocate the exact decode-sized workspace, autotune cache misses, and
189
+ # resolve every CUDA Graph bucket before capture begins.
190
+ warmup_flashinfer_pcie_ipc_allreduce(worker)
191
+
192
enable_flashinfer_autotune = (
193
worker.vllm_config.kernel_config.enable_flashinfer_autotune
194
)