[WideEP] Update NCCL to 2.30.7 to enable DeepEPv2 in the vllm/vllm-openai image (#45321)
Signed-off-by: Tyler Michael Smith <tlrmchlsmth@gmail.com> Signed-off-by: Tyler Michael Smith <tyler@vllm.ai> Signed-off-by: Tyler Michael Smith <tyler@tylermsmith.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Ilya Markov <ilmarkov@users.noreply.github.com> Co-authored-by: OpenAI Codex <codex@openai.com> Co-authored-by: Codex <noreply@openai.com>
Tyler Michael Smith committed
Jul 24, 2026 at 16:00 UTC
5d8e90a96616c4fe339ff0b0c2a2d470f6eb24bf
13 files changed
+338
-78
.buildkite/test_areas/misc.yaml
+1
-1
@@ -212,7 +212,7 @@ steps:
212
- vllm/multimodal
213
- examples/
214
commands:
215
- - pip install tensorizer # for tensorizer test
215
+ - pip install --no-deps tensorizer # for tensorizer test
216
# for basic
217
- python3 basic/offline_inference/chat.py
218
- python3 basic/offline_inference/generate.py --model facebook/opt-125m
.buildkite/test_areas/model_runner_v2.yaml
+1
-1
@@ -41,7 +41,7 @@ steps:
41
commands:
42
- set -x
43
- export VLLM_USE_V2_MODEL_RUNNER=1
44
- - pip install tensorizer # for tensorizer test
44
+ - pip install --no-deps tensorizer # for tensorizer test
45
- python3 basic/offline_inference/chat.py # for basic
46
- python3 basic/offline_inference/generate.py --model facebook/opt-125m
47
#- python3 basic/offline_inference/generate.py --model meta-llama/Llama-2-13b-chat-hf --cpu-offload-gb 10 # TODO
docker/Dockerfile
+25
-8
@@ -25,6 +25,10 @@
25
ARG CUDA_VERSION=13.0.3
26
ARG PYTHON_VERSION=3.12
27
ARG UBUNTU_VERSION=22.04
28
+# DeepEPv2 requires NCCL >= 2.30.4 (GIN backend).
29
+# This version is only used for CUDA 13+ builds; CUDA 12 falls back to
30
+# the default NCCL version shipped with the base image.
31
+ARG NCCL_VERSION=2.30.7
32
33
# By parameterizing the base images, we allow third-party to use their own
34
# base images. One use case is hermetic builds with base images stored in
@@ -477,10 +481,17 @@ WORKDIR /workspace
481
# Build DeepEP wheels
482
COPY tools/ep_kernels/install_python_libraries.sh /tmp/install_python_libraries.sh
483
# Defaults moved here from tools/ep_kernels/install_python_libraries.sh for centralized version management
480
-ARG DEEPEP_COMMIT_HASH=73b6ea4
484
+ARG DEEPEP_COMMIT_HASH=d4f41e4e93
485
ARG NVSHMEM_VER
486
+ARG NCCL_VERSION
487
RUN --mount=type=cache,target=/opt/uv/cache \
488
mkdir -p /tmp/ep_kernels_workspace/dist && \
489
+ CUDA_MAJOR=$(echo $CUDA_VERSION | cut -d. -f1) && \
490
+ if [ "$CUDA_MAJOR" -ge 13 ] && [ -n "$NCCL_VERSION" ]; then \
491
+ echo "nvidia-nccl-cu${CUDA_MAJOR}==${NCCL_VERSION}" \
492
+ > /tmp/nccl-override.txt && \
493
+ export UV_OVERRIDE=/tmp/nccl-override.txt; \
494
+ fi && \
495
export TORCH_CUDA_ARCH_LIST='9.0a 10.0a' && \
496
/tmp/install_python_libraries.sh \
497
--workspace /tmp/ep_kernels_workspace \
@@ -644,6 +655,7 @@ FROM ${FINAL_BASE_IMAGE} AS vllm-base
655
656
ARG CUDA_VERSION
657
ARG PYTHON_VERSION
658
+ARG NCCL_VERSION
659
ARG DEADSNAKES_MIRROR_URL
660
ARG DEADSNAKES_GPGKEY_URL
661
ARG GET_PIP_URL
@@ -696,7 +708,6 @@ RUN apt-get update -y \
708
# Install CUDA development tools for runtime JIT compilation
709
# (FlashInfer, DeepGEMM, EP kernels all require compilation at runtime)
710
RUN CUDA_VERSION_DASH=$(echo $CUDA_VERSION | cut -d. -f1,2 | tr '.' '-') && \
699
- CUDA_VERSION_SHORT=$(echo $CUDA_VERSION | cut -d. -f1,2) && \
711
apt-get update -y && \
712
apt-get install -y --no-install-recommends --allow-change-held-packages \
713
cuda-nvcc-${CUDA_VERSION_DASH} \
@@ -709,12 +720,6 @@ RUN CUDA_VERSION_DASH=$(echo $CUDA_VERSION | cut -d. -f1,2 | tr '.' '-') && \
720
libnuma-dev \
721
# numactl CLI for NUMA binding at runtime
722
numactl && \
712
- # Fixes nccl_allocator requiring nccl.h at runtime
713
- # https://github.com/vllm-project/vllm/blob/1336a1ea244fa8bfd7e72751cabbdb5b68a0c11a/vllm/distributed/device_communicators/pynccl_allocator.py#L22
714
- # NCCL packages don't use the cuda-MAJOR-MINOR naming convention,
715
- # so we pin the version to match our CUDA version
716
- NCCL_VER=$(apt-cache madison libnccl-dev | grep "+cuda${CUDA_VERSION_SHORT}" | head -1 | awk -F'|' '{gsub(/^ +| +$/, "", $2); print $2}') && \
717
- apt-get install -y --no-install-recommends --allow-change-held-packages libnccl-dev=${NCCL_VER} libnccl2=${NCCL_VER} && \
723
rm -rf /var/lib/apt/lists/*
724
725
# Install uv for faster pip installs
@@ -734,6 +739,18 @@ RUN mkdir -p "${UV_PYTHON_INSTALL_DIR}" "${UV_CACHE_DIR}" \
739
&& chgrp -R 0 /opt/uv \
740
&& chmod -R g+rwX,a+rX /opt/uv
741
742
+# DeepEPv2 GIN requires NCCL >= 2.30.4 at both compile and runtime. torch pins
743
+# an older version as a transitive dep; this override forces uv to use our
744
+# pinned version whenever nvidia-nccl-cu* is resolved. Empty on CUDA 12 (no-op).
745
+RUN CUDA_MAJOR=$(echo $CUDA_VERSION | cut -d. -f1) && \
746
+ if [ "$CUDA_MAJOR" -ge 13 ]; then \
747
+ echo "nvidia-nccl-cu${CUDA_MAJOR}==${NCCL_VERSION}" \
748
+ > /etc/uv-overrides.txt; \
749
+ else \
750
+ touch /etc/uv-overrides.txt; \
751
+ fi
752
+ENV UV_OVERRIDE=/etc/uv-overrides.txt
753
+
754
# ----------------------------------------------------------------------
755
# Non-root support (opt-in)
756
# ----------------------------------------------------------------------
docker/versions.json
+4
-1
@@ -10,6 +10,9 @@
10
"UBUNTU_VERSION": {
11
"default": "22.04"
12
},
13
+ "NCCL_VERSION": {
14
+ "default": "2.30.7"
15
+ },
16
"BUILD_BASE_IMAGE": {
17
"default": "nvidia/cuda:13.0.3-devel-ubuntu22.04"
18
},
@@ -56,7 +59,7 @@
59
"default": "cuda"
60
},
61
"DEEPEP_COMMIT_HASH": {
59
- "default": "73b6ea4"
62
+ "default": "d4f41e4e93"
63
},
64
"GIT_REPO_CHECK": {
65
"default": "0"
docs/serving/expert_parallel_deployment.md
+5
@@ -12,6 +12,11 @@ Before using EP, you need to install the necessary dependencies. We are actively
12
2. **Install DeepGEMM library**: Follow the [official instructions](https://github.com/deepseek-ai/DeepGEMM#installation).
13
3. **For disaggregated serving**: Install `gdrcopy` by running the [`install_gdrcopy.sh`](../../tools/install_gdrcopy.sh) script (e.g., `install_gdrcopy.sh "${GDRCOPY_OS_VERSION}" "12.8" "x64"`). You can find available OS versions [here](https://developer.download.nvidia.com/compute/redist/gdrcopy/CUDA%2012.8/).
14
15
+!!! note "NCCL version (CUDA 13+)"
16
+ The `deepep_v2` backend requires NCCL >= 2.30.4. PyTorch ships an older
17
+ NCCL, so you must upgrade it before building or running DeepEP. See the
18
+ [EP kernels guide](../../tools/ep_kernels) for instructions.
19
+
20
### Backend Selection Guide
21
22
vLLM provides multiple communication backends for EP. Use `--all2all-backend` to select one:
tests/distributed/test_mnnvl_alltoall.py
+9
-3
@@ -73,7 +73,10 @@ def _spawn_workers(worker_fn, world_size, *, dp_size=None):
73
err_queue.close()
74
err_queue.join_thread()
75
if errors:
76
- pytest.fail("Worker(s) failed:\n" + "\n---\n".join(errors))
76
+ combined = "\n---\n".join(errors)
77
+ if "NCCL GIN" in combined:
78
+ pytest.skip("NCCL GIN not available on this system")
79
+ pytest.fail("Worker(s) failed:\n" + combined)
80
81
82
def _run_worker(rank, world_size, port, worker_fn, dp_size, dp_port, err_queue):
@@ -916,8 +919,11 @@ def _deepep_v2_lifecycle_worker(rank, world_size):
919
DeepEPV2All2AllManager,
920
)
921
919
- cpu_group = get_ep_group().cpu_group
920
- manager = DeepEPV2All2AllManager(cpu_group)
922
+ ep_group = get_ep_group()
923
+ manager = DeepEPV2All2AllManager(
924
+ ep_group.cpu_group,
925
+ device_group=ep_group.device_group,
926
+ )
927
928
assert manager.rank == rank
929
assert manager.world_size == world_size
tests/kernels/moe/parallel_utils.py
+36
-12
@@ -36,6 +36,10 @@ if has_deep_ep_v2():
36
P = ParamSpec("P")
37
38
39
+class GINNotAvailableError(RuntimeError):
40
+ pass
41
+
42
+
43
@dataclasses.dataclass
44
class ProcessGroupInfo:
45
world_size: int
@@ -96,19 +100,27 @@ def parallel_launch(
100
**kwargs: P.kwargs,
101
) -> None:
102
assert not kwargs
99
- spawn(
100
- _worker_parallel_launch,
101
- args=(
102
- world_size,
103
- world_size,
104
- 0,
105
- f"tcp://{os.getenv('LOCALHOST', 'localhost')}:{get_open_port()}",
106
- worker,
103
+ try:
104
+ spawn(
105
+ _worker_parallel_launch,
106
+ args=(
107
+ world_size,
108
+ world_size,
109
+ 0,
110
+ f"tcp://{os.getenv('LOCALHOST', 'localhost')}:{get_open_port()}",
111
+ worker,
112
+ )
113
+ + args,
114
+ nprocs=world_size,
115
+ join=True,
116
)
108
- + args,
109
- nprocs=world_size,
110
- join=True,
111
- )
117
+ except Exception as exc:
118
+ # pytest.skip cannot propagate directly through torch.multiprocessing.
119
+ if "GINNotAvailableError" in str(exc):
120
+ import pytest
121
+
122
+ pytest.skip("NCCL GIN not available (no IBGDA-capable hardware)")
123
+ raise
124
125
126
## DeepEP specific utils
@@ -225,6 +237,18 @@ def make_deepep_v2_a2a(
237
):
238
import deep_ep
239
240
+ from vllm.utils.nccl import query_nccl_gin_type
241
+
242
+ # ElasticBuffer can segfault when GIN is unavailable. Initialize the
243
+ # lazy communicator and reject unsupported systems before entering DeepEP.
244
+ probe = torch.zeros(1, device=pgi.device)
245
+ torch.distributed.all_reduce(probe, group=pg)
246
+ gin_type = query_nccl_gin_type(pg)
247
+ if gin_type is None:
248
+ raise RuntimeError("Failed to determine NCCL GIN support")
249
+ if gin_type == 0:
250
+ raise GINNotAvailableError("NCCL GIN not available")
251
+
252
buffer = deep_ep.ElasticBuffer(
253
group=pg,
254
num_max_tokens_per_rank=v2_args.max_tokens_per_rank,
tests/kernels/moe/test_deepep_v2_moe.py
+101
-50
@@ -14,6 +14,7 @@ from torch.distributed import ProcessGroup
14
from tests.kernels.moe.utils import make_dummy_moe_config, make_test_weights
15
from tests.kernels.utils import torch_experts
16
from vllm.config import VllmConfig, set_current_vllm_config
17
+from vllm.forward_context import set_forward_context
18
from vllm.model_executor.layers.fused_moe import TritonExperts
19
from vllm.model_executor.layers.fused_moe.activation import MoEActivation
20
from vllm.model_executor.layers.fused_moe.config import (
@@ -36,6 +37,14 @@ requires_deep_ep_v2 = pytest.mark.skipif(
37
)
38
39
40
+def assert_fp8_close(actual: torch.Tensor, expected: torch.Tensor) -> None:
41
+ close = torch.isclose(actual, expected, atol=2e-1, rtol=2e-1)
42
+ close_fraction = close.float().mean().item()
43
+ assert close_fraction > 0.99, (
44
+ f"Only {close_fraction:.1%} of FP8 outputs are within tolerance"
45
+ )
46
+
47
+
48
@dataclasses.dataclass
49
class TestConfig:
50
dtype: torch.dtype
@@ -50,6 +59,7 @@ class TestConfig:
59
class TestTensors:
60
rank_tokens: torch.Tensor
61
rank_token_scales: torch.Tensor | None
62
+ intermediate_scales: torch.Tensor | None
63
topk: torch.Tensor
64
topk_weights: torch.Tensor
65
config: TestConfig
@@ -63,6 +73,12 @@ class TestTensors:
73
rank_tokens = (
74
torch.randn((config.m, config.k), device="cuda", dtype=token_dtype) / 10
75
)
76
+ if config.dtype == torch.float8_e4m3fn:
77
+ rank_token_scales = torch.tensor(1 / 448, device="cuda")
78
+ intermediate_scales = torch.tensor(8 / 448, device="cuda")
79
+ else:
80
+ rank_token_scales = None
81
+ intermediate_scales = None
82
83
topk = torch.stack(
84
[
@@ -73,7 +89,8 @@ class TestTensors:
89
topk_weights = torch.randn(topk.shape, dtype=torch.float32, device="cuda")
90
return TestTensors(
91
rank_tokens=rank_tokens,
76
- rank_token_scales=None,
92
+ rank_token_scales=rank_token_scales,
93
+ intermediate_scales=intermediate_scales,
94
topk=topk,
95
topk_weights=topk_weights,
96
config=config,
@@ -124,7 +141,6 @@ def make_modular_kernel(
141
mk = FusedMoEKernel(
142
prepare_finalize=a2a,
143
fused_experts=fused_experts,
127
- inplace=False,
144
)
145
return mk
146
@@ -162,6 +178,7 @@ def deepep_v2_moe_impl(
178
w2_scale=w2_scale,
179
per_act_token_quant=per_act_token_quant,
180
a1_scale=test_tensors.rank_token_scales,
181
+ a2_scale=test_tensors.intermediate_scales,
182
)
183
184
hidden_size = test_tensors.rank_tokens.size(1)
@@ -231,6 +248,8 @@ def _deep_ep_v2_moe(
248
test_tensors.topk,
249
w1_scale=w1_scale,
250
w2_scale=w2_scale,
251
+ a1_scale=test_tensors.rank_token_scales,
252
+ a2_scale=test_tensors.intermediate_scales,
253
quant_dtype=q_dtype,
254
per_act_token_quant=per_act_token_quant,
255
)
@@ -262,12 +281,15 @@ def _deep_ep_v2_moe(
281
per_act_token_quant,
282
)
283
265
- torch.testing.assert_close(
266
- torch_combined,
267
- deepep_combined,
268
- atol=6e-2,
269
- rtol=6e-2,
270
- )
284
+ if is_quantized:
285
+ assert_fp8_close(torch_combined, deepep_combined)
286
+ else:
287
+ torch.testing.assert_close(
288
+ torch_combined,
289
+ deepep_combined,
290
+ atol=6e-2,
291
+ rtol=6e-2,
292
+ )
293
294
295
MNKs = [
@@ -355,17 +377,29 @@ def _deep_ep_v2_moe_cudagraph(
377
num_local_experts = config.num_experts // pgi.world_size
378
hidden_size = config.k
379
358
- # Create FP8 weights directly, then dequantize for bf16 reference.
359
- w1_fp8 = torch.randn(
360
- (config.num_experts, 2 * config.n, config.k),
361
- device="cuda",
362
- dtype=torch.bfloat16,
363
- ).to(torch.float8_e4m3fn)
364
- w2_fp8 = torch.randn(
365
- (config.num_experts, config.k, config.n),
366
- device="cuda",
367
- dtype=torch.bfloat16,
368
- ).to(torch.float8_e4m3fn)
380
+ # All ranks must use the same global weights before taking their EP slice.
381
+ w1_bf16 = (
382
+ torch.randn(
383
+ (config.num_experts, 2 * config.n, config.k),
384
+ device="cuda",
385
+ dtype=torch.bfloat16,
386
+ )
387
+ / 15
388
+ )
389
+ w2_bf16 = (
390
+ torch.randn(
391
+ (config.num_experts, config.k, config.n),
392
+ device="cuda",
393
+ dtype=torch.bfloat16,
394
+ )
395
+ / 15
396
+ )
397
+ torch.distributed.broadcast(w1_bf16, src=0, group=pg)
398
+ torch.distributed.broadcast(w2_bf16, src=0, group=pg)
399
+
400
+ # Round-trip through FP8 before constructing the reference and kernel weights.
401
+ w1_fp8 = w1_bf16.to(torch.float8_e4m3fn)
402
+ w2_fp8 = w2_bf16.to(torch.float8_e4m3fn)
403
w1_ref = w1_fp8.to(torch.bfloat16)
404
w2_ref = w2_fp8.to(torch.bfloat16)
405
@@ -385,21 +419,8 @@ def _deep_ep_v2_moe_cudagraph(
419
backend="nccl",
420
)
421
initialize_model_parallel(tensor_model_parallel_size=1)
388
- # Reference MoE using dequantized bf16 weights
389
- torch_combined = torch_experts(
390
- test_tensors.rank_tokens,
391
- w1_ref,
392
- w2_ref,
393
- test_tensors.topk_weights,
394
- test_tensors.topk,
395
- )
396
-
397
- # Use the production pipeline: make_fused_moe_layer creates
398
- # a FusedMoE layer, quantizes weights, runs
399
- # process_weights_after_loading (TrtLLM W31 swap + BlockMajorK
400
- # shuffle), and selects the kernel.
401
- # Quantize weights using production helper, EP-slice, then
402
- # convert to TrtLLM format.
422
+ # Mirror production weight processing: quantize, EP-slice, then
423
+ # convert to the TrtLLM BlockMajorK format.
424
from tests.kernels.moe.test_moe_layer import _quantize_fp8_halves
425
from vllm.model_executor.layers.fused_moe.experts.trtllm_fp8_moe import (
426
TrtLlmFp8ExpertsModular,
@@ -411,14 +432,32 @@ def _deep_ep_v2_moe_cudagraph(
432
433
block_shape = [128, 128]
434
qw = _quantize_fp8_halves(w1_ref, w2_ref, block_shape)
435
+ assert qw.w13_weight_scale is not None
436
+ assert qw.w2_weight_scale is not None
437
+
438
+ # Reference MoE using the same blockwise FP8 quantization scheme as
439
+ # the production kernel. torch_experts quantizes activations before
440
+ # both GEMMs and dequantizes the operands for the reference matmuls.
441
+ reference_topk_weights = test_tensors.topk_weights.to(torch.bfloat16).to(
442
+ torch.float32
443
+ )
444
+ torch_combined = torch_experts(
445
+ test_tensors.rank_tokens,
446
+ qw.w13_weight,
447
+ qw.w2_weight,
448
+ reference_topk_weights,
449
+ test_tensors.topk,
450
+ w1_scale=qw.w13_weight_scale,
451
+ w2_scale=qw.w2_weight_scale,
452
+ quant_dtype=torch.float8_e4m3fn,
453
+ block_shape=block_shape,
454
+ )
455
456
# EP-slice before format conversion
457
e_start = num_local_experts * pgi.rank
458
e_end = e_start + num_local_experts
459
w1_ep = qw.w13_weight[e_start:e_end]
460
w2_ep = qw.w2_weight[e_start:e_end]
420
- assert qw.w13_weight_scale is not None
421
- assert qw.w2_weight_scale is not None
461
w1_scale_ep = qw.w13_weight_scale[e_start:e_end]
462
w2_scale_ep = qw.w2_weight_scale[e_start:e_end]
463
@@ -452,11 +491,23 @@ def _deep_ep_v2_moe_cudagraph(
491
w2_scale=w2_scale_ep,
492
)
493
moe_config = make_dummy_moe_config(
455
- num_experts=num_local_experts,
494
+ num_experts=config.num_experts,
495
+ num_local_experts=num_local_experts,
496
experts_per_token=config.topk,
497
hidden_dim=hidden_size,
498
intermediate_size=config.n,
499
)
500
+ moe_parallel_config = dataclasses.replace(
501
+ moe_config.moe_parallel_config,
502
+ ep_size=pgi.world_size,
503
+ ep_rank=pgi.rank,
504
+ use_ep=True,
505
+ all2all_backend="deepep_v2",
506
+ )
507
+ moe_config = dataclasses.replace(
508
+ moe_config,
509
+ moe_parallel_config=moe_parallel_config,
510
+ )
511
fused_experts = TrtLlmFp8ExpertsModular(
512
moe_config=moe_config,
513
quant_config=quant_config,
@@ -480,21 +531,21 @@ def _deep_ep_v2_moe_cudagraph(
531
mk_kernel = FusedMoEKernel(
532
prepare_finalize=a2a,
533
fused_experts=fused_experts,
483
- inplace=False,
534
)
535
486
- for _ in range(3):
487
- out = mk_kernel.apply(
488
- hidden_states=test_tensors.rank_tokens,
489
- w1=w1_ep,
490
- w2=w2_ep,
491
- topk_weights=test_tensors.topk_weights,
492
- topk_ids=test_tensors.topk,
493
- activation=MoEActivation.SILU,
494
- global_num_experts=config.num_experts,
495
- expert_map=None,
496
- apply_router_weight_on_input=False,
497
- )
536
+ with set_forward_context(None, vllm_cfg):
537
+ for _ in range(3):
538
+ out = mk_kernel.apply(
539
+ hidden_states=test_tensors.rank_tokens,
540
+ w1=w1_ep,
541
+ w2=w2_ep,
542
+ topk_weights=test_tensors.topk_weights,
543
+ topk_ids=test_tensors.topk,
544
+ activation=MoEActivation.SILU,
545
+ global_num_experts=config.num_experts,
546
+ expert_map=None,
547
+ apply_router_weight_on_input=False,
548
+ )
549
550
torch.testing.assert_close(
551
torch_combined,
tools/ep_kernels/README.md
+32
@@ -11,6 +11,38 @@ Step 2 is necessary for multi-node deployment.
11
12
All scripts accept a positional argument as workspace path for staging the build, defaulting to `$(pwd)/ep_kernels_workspace`.
13
14
+## NCCL version requirement (CUDA 13+)
15
+
16
+DeepEPv2 uses the NCCL GIN (GPU-Initiated Networking) backend, which requires
17
+NCCL >= 2.30.4 at both compile time and runtime. PyTorch 2.11 pins
18
+`nvidia-nccl-cu13==2.28.9` as a transitive dependency, so you need to
19
+override it.
20
+
21
+**With uv** (recommended):
22
+
23
+```bash
24
+# Create an override file
25
+echo "nvidia-nccl-cu13>=2.30.4" > /tmp/nccl-override.txt
26
+export UV_OVERRIDE=/tmp/nccl-override.txt
27
+
28
+# All subsequent uv pip install commands will respect the override
29
+uv pip install vllm
30
+```
31
+
32
+**With pip**:
33
+
34
+```bash
35
+pip install vllm
36
+pip install "nvidia-nccl-cu13>=2.30.4" --no-deps
37
+```
38
+
39
+The override / reinstall must happen before building DeepEP (for GIN device
40
+headers) and must remain in place at runtime. You can verify with:
41
+
42
+```bash
43
+python -c "from vllm.utils.import_utils import has_deep_ep_v2; print(has_deep_ep_v2())"
44
+```
45
+
46
## Usage
47
48
```bash
vllm/distributed/device_communicators/all2all.py
+27
@@ -978,6 +978,7 @@ class DeepEPV2All2AllManager(All2AllManagerBase):
978
self._device_group = device_group
979
self.handle_cache = Cache()
980
self._num_sms: int | None = None
981
+ self._gin_checked = False
982
983
def _make_all2all_kwargs(
984
self,
@@ -1000,11 +1001,37 @@ class DeepEPV2All2AllManager(All2AllManagerBase):
1001
explicitly_destroy=True,
1002
)
1003
1004
+ def _check_gin_support(self, group) -> None:
1005
+ from vllm.utils.nccl import query_nccl_gin_type
1006
+
1007
+ # ProcessGroupNCCL creates communicators lazily. Initialize this exact
1008
+ # group before querying so a null comm pointer is not mistaken for
1009
+ # missing GIN support.
1010
+ probe = torch.zeros(1, device="cuda")
1011
+ torch.distributed.all_reduce(probe, group=group)
1012
+
1013
+ gin_type = query_nccl_gin_type(group)
1014
+ if gin_type is None:
1015
+ raise RuntimeError(
1016
+ "DeepEPv2 communicator properties query failed; "
1017
+ "networking capability could not be determined."
1018
+ )
1019
+ if gin_type == 0:
1020
+ raise RuntimeError(
1021
+ "DeepEPv2 requires NCCL GIN (GPU-Initiated Networking). "
1022
+ "This usually means IBGDA-capable InfiniBand NICs or drivers "
1023
+ "are not available. See tools/ep_kernels/README.md for "
1024
+ "requirements."
1025
+ )
1026
+
1027
def get_handle(self, kwargs):
1028
import deep_ep # type: ignore[import-not-found]
1029
1030
num_experts = kwargs.pop("num_experts", 256)
1031
buffer_kwargs = self._make_all2all_kwargs(**kwargs)
1032
+ if not self._gin_checked:
1033
+ self._check_gin_support(buffer_kwargs["group"])
1034
+ self._gin_checked = True
1035
logger.debug("DeepEP v2 all2all args %s", buffer_kwargs)
1036
handle: deep_ep.ElasticBuffer = self.handle_cache.get_or_create(
1037
buffer_kwargs, deep_ep.ElasticBuffer
vllm/distributed/device_communicators/pynccl_allocator.py
+6
-2
@@ -14,7 +14,7 @@ from vllm import envs
14
from vllm.distributed.device_communicators.pynccl import PyNcclCommunicator
15
from vllm.logger import init_logger
16
from vllm.platforms import current_platform
17
-from vllm.utils.nccl import find_nccl_include_paths
17
+from vllm.utils.nccl import find_nccl_include_paths, find_nccl_library_paths
18
19
logger = init_logger(__name__)
20
@@ -74,11 +74,15 @@ def compile_nccl_allocator():
74
out_dir = tempfile.gettempdir()
75
nccl_allocator_libname = "nccl_allocator"
76
nccl_include_paths = find_nccl_include_paths()
77
+ ldflags = ["-l:libnccl.so.2"]
78
+ nccl_lib_paths = find_nccl_library_paths()
79
+ if nccl_lib_paths:
80
+ ldflags = [f"-L{p}" for p in nccl_lib_paths] + ldflags
81
load_inline(
82
name=nccl_allocator_libname,
83
cpp_sources=nccl_allocator_source,
84
with_cuda=True,
81
- extra_ldflags=["-lnccl"],
85
+ extra_ldflags=ldflags,
86
verbose=envs.VLLM_LOGGING_LEVEL == "DEBUG",
87
is_python_module=False,
88
build_directory=out_dir,
vllm/distributed/device_communicators/pynccl_wrapper.py
+29
@@ -51,6 +51,26 @@ class ncclUniqueId(ctypes.Structure):
51
_fields_ = [("internal", ctypes.c_byte * 128)]
52
53
54
+# NCCL 2.30+ ncclCommProperties_t. Only fields through ginType are read;
55
+# trailing fields keep the layout aligned with NCCL's versioned structure.
56
+class ncclCommProperties(ctypes.Structure):
57
+ _fields_ = [
58
+ ("size", ctypes.c_size_t),
59
+ ("magic", ctypes.c_uint),
60
+ ("version", ctypes.c_uint),
61
+ ("rank", ctypes.c_int),
62
+ ("nRanks", ctypes.c_int),
63
+ ("cudaDev", ctypes.c_int),
64
+ ("nvmlDev", ctypes.c_int),
65
+ ("deviceApiSupport", ctypes.c_bool),
66
+ ("multimemSupport", ctypes.c_bool),
67
+ ("ginType", ctypes.c_int),
68
+ ("nLsaTeams", ctypes.c_int),
69
+ ("hostRmaSupport", ctypes.c_bool),
70
+ ("railedGinType", ctypes.c_int),
71
+ ]
72
+
73
+
74
cudaStream_t = ctypes.c_void_p
75
buffer_type = ctypes.c_void_p
76
@@ -317,6 +337,12 @@ class NCCLLibrary:
337
# ncclResult_t ncclCommWindowDeregister(
338
# ncclComm_t comm, ncclWindow_t win);
339
Function("ncclCommWindowDeregister", ncclResult_t, [ncclComm_t, ncclWindow_t]),
340
+ # Query runtime properties of a specific initialized communicator.
341
+ Function(
342
+ "ncclCommQueryProperties",
343
+ ncclResult_t,
344
+ [ncclComm_t, ctypes.POINTER(ncclCommProperties)],
345
+ ),
346
]
347
348
# class attribute to store the mapping from the path to the library
@@ -375,6 +401,9 @@ class NCCLLibrary:
401
# Having an exception here on ROCm platform is
402
# not allowed during graph capturing
403
continue
404
+ elif func.name == "ncclCommQueryProperties":
405
+ # Optional on NCCL versions older than 2.29.
406
+ continue
407
raise
408
NCCLLibrary.path_to_dict_mapping[so_file] = _funcs
409
self._funcs = NCCLLibrary.path_to_dict_mapping[so_file]
vllm/utils/nccl.py
+62
@@ -3,6 +3,7 @@
3
4
from __future__ import annotations
5
6
+import ctypes
7
import importlib.util
8
import os
9
@@ -62,3 +63,64 @@ def find_nccl_include_paths() -> list[str] | None:
63
out.append(p)
64
seen.add(p)
65
return out or None
66
+
67
+
68
+def find_nccl_library_paths() -> list[str] | None:
69
+ """Return possible library paths containing `libnccl.so`.
70
+
71
+ Looks inside the `nvidia-nccl-cuXX` pip package.
72
+ """
73
+ paths: list[str] = []
74
+ try:
75
+ spec = importlib.util.find_spec("nvidia.nccl")
76
+ if spec and (locs := getattr(spec, "submodule_search_locations", None)):
77
+ for loc in locs:
78
+ lib_dir = os.path.join(loc, "lib")
79
+ if os.path.isdir(lib_dir):
80
+ paths.append(lib_dir)
81
+ except Exception as e:
82
+ logger.debug("Failed to find nccl library path from nvidia.nccl package: %s", e)
83
+ return paths or None
84
+
85
+
86
+def query_nccl_gin_type(group: torch.distributed.ProcessGroup) -> int | None:
87
+ """Return the GIN type for an initialized group, or ``None`` on failure."""
88
+ from vllm.distributed.device_communicators.pynccl_wrapper import (
89
+ NCCLLibrary,
90
+ ncclCommProperties,
91
+ )
92
+
93
+ try:
94
+ backend = group._get_backend(torch.device("cuda"))
95
+ # GIN is a property of this initialized communicator, not just the
96
+ # NCCL version. ncclCommQueryProperties requires its ncclComm_t.
97
+ comm_ptr = backend._comm_ptr()
98
+ if comm_ptr == 0:
99
+ return None
100
+ except Exception:
101
+ logger.warning(
102
+ "Failed to extract NCCL comm pointer from process group",
103
+ exc_info=True,
104
+ )
105
+ return None
106
+
107
+ try:
108
+ nccl = NCCLLibrary()
109
+ query_fn = nccl._funcs.get("ncclCommQueryProperties")
110
+ if query_fn is None:
111
+ return None
112
+
113
+ props = ncclCommProperties()
114
+ ctypes.memset(ctypes.addressof(props), 0, ctypes.sizeof(props))
115
+ props.size = ctypes.sizeof(props)
116
+ props.magic = 0xCAFEBEEF
117
+ props.version = nccl.ncclGetRawVersion()
118
+ result = query_fn(ctypes.c_void_p(comm_ptr), ctypes.byref(props))
119
+ except Exception:
120
+ logger.warning("Failed to query NCCL communicator properties", exc_info=True)
121
+ return None
122
+
123
+ if result != 0:
124
+ logger.warning("ncclCommQueryProperties returned error %d", result)
125
+ return None
126
+ return props.ginType