[ROCm][Ray][CI] Keep assigned GPU visible for weight transfer (#47000)
Signed-off-by: Andreas Karatzas <akaratza@amd.com>
Andreas Karatzas committed
Jun 30, 2026 at 01:59 UTC
ba22cb6765b6bb5b1a3abc3b20db3817cec1665c
1 file changed
+59
-14
tests/distributed/test_weight_transfer.py
+59
-14
@@ -29,9 +29,43 @@ from vllm.distributed.weight_transfer.nccl_engine import (
29
NCCLWeightTransferInitInfo,
30
NCCLWeightTransferUpdateInfo,
31
)
32
+from vllm.platforms import current_platform
33
from vllm.utils.network_utils import get_open_port
34
35
36
+def _weight_transfer_ray_env_vars() -> dict[str, str]:
37
+ if not current_platform.is_rocm():
38
+ return {}
39
+
40
+ return {
41
+ "RAY_EXPERIMENTAL_NOSET_CUDA_VISIBLE_DEVICES": "1",
42
+ "RAY_EXPERIMENTAL_NOSET_HIP_VISIBLE_DEVICES": "1",
43
+ "RAY_EXPERIMENTAL_NOSET_ROCR_VISIBLE_DEVICES": "1",
44
+ }
45
+
46
+
47
+def _init_ray_for_weight_transfer() -> None:
48
+ if ray.is_initialized():
49
+ return
50
+ ray.init(
51
+ ignore_reinit_error=True,
52
+ runtime_env={"env_vars": _weight_transfer_ray_env_vars()},
53
+ )
54
+
55
+
56
+def _get_ray_assigned_device() -> torch.device:
57
+ gpu_ids = ray.get_gpu_ids()
58
+ if not gpu_ids:
59
+ return torch.device("cuda:0")
60
+ return torch.device(f"cuda:{int(gpu_ids[0])}")
61
+
62
+
63
+def _set_ray_assigned_device() -> torch.device:
64
+ device = _get_ray_assigned_device()
65
+ torch.accelerator.set_device(device)
66
+ return device
67
+
68
+
69
def create_mock_parallel_config(
70
rank: int = 0,
71
world_size: int = 1,
@@ -321,6 +355,8 @@ def trainer_broadcast_tensor(
355
"""Trainer task that broadcasts a tensor via NCCL."""
356
import torch
357
358
+ device = _set_ray_assigned_device()
359
+
360
from vllm.distributed.device_communicators.pynccl import PyNcclCommunicator
361
from vllm.distributed.utils import StatelessProcessGroup
362
@@ -331,12 +367,11 @@ def trainer_broadcast_tensor(
367
rank=0,
368
world_size=world_size,
369
)
334
- # Ray sets CUDA_VISIBLE_DEVICES, so device 0 is the assigned GPU
335
- comm = PyNcclCommunicator(pg, device=0)
370
+ comm = PyNcclCommunicator(pg, device=device.index)
371
372
# Create and broadcast the tensor
373
dtype = getattr(torch, tensor_dtype)
339
- tensor_to_send = torch.ones(tensor_shape, dtype=dtype, device="cuda:0")
374
+ tensor_to_send = torch.ones(tensor_shape, dtype=dtype, device=device)
375
comm.broadcast(tensor_to_send, src=0, stream=torch.cuda.current_stream())
376
torch.accelerator.synchronize()
377
@@ -356,6 +391,8 @@ def inference_receive_tensor(
391
392
import torch
393
394
+ _set_ray_assigned_device()
395
+
396
from vllm.config.parallel import ParallelConfig
397
from vllm.config.weight_transfer import WeightTransferConfig
398
from vllm.distributed.weight_transfer.nccl_engine import (
@@ -435,7 +472,7 @@ def test_nccl_weight_transfer_between_processes():
472
This test verifies that the NCCLWeightTransferEngine can receive
473
tensors broadcast by a trainer process via NCCL.
474
"""
438
- ray.init(ignore_reinit_error=True)
475
+ _init_ray_for_weight_transfer()
476
477
master_address = "127.0.0.1"
478
master_port = get_open_port()
@@ -473,6 +510,8 @@ def trainer_broadcast_sparse_tensor(
510
"""Trainer task that broadcasts sparse patches via NCCL."""
511
import torch
512
513
+ device = _set_ray_assigned_device()
514
+
515
from vllm.distributed.device_communicators.pynccl import PyNcclCommunicator
516
from vllm.distributed.utils import StatelessProcessGroup
517
from vllm.distributed.weight_transfer.base import SparseWeightPatch
@@ -487,12 +526,12 @@ def trainer_broadcast_sparse_tensor(
526
rank=0,
527
world_size=world_size,
528
)
490
- comm = PyNcclCommunicator(pg, device=0)
529
+ comm = PyNcclCommunicator(pg, device=device.index)
530
531
patch = SparseWeightPatch(
532
name="test.weight",
494
- indices=torch.tensor([1, 7, 25], dtype=torch.int32, device="cuda:0"),
495
- values=torch.tensor([10.0, 20.0, 30.0], dtype=torch.float32, device="cuda:0"),
533
+ indices=torch.tensor([1, 7, 25], dtype=torch.int32, device=device),
534
+ values=torch.tensor([10.0, 20.0, 30.0], dtype=torch.float32, device=device),
535
)
536
NCCLWeightTransferEngine.trainer_send_sparse_weights(
537
iter([patch]),
@@ -513,6 +552,8 @@ def inference_receive_sparse_tensor(
552
553
import torch
554
555
+ device = _set_ray_assigned_device()
556
+
557
from vllm.config.parallel import ParallelConfig
558
from vllm.config.weight_transfer import WeightTransferConfig
559
from vllm.distributed.weight_transfer.nccl_engine import (
@@ -540,7 +581,7 @@ def inference_receive_sparse_tensor(
581
)
582
)
583
543
- target = torch.zeros(30, dtype=torch.float32, device="cuda")
584
+ target = torch.zeros(30, dtype=torch.float32, device=device)
585
586
def apply_sparse_patches(patches: list[SparseWeightPatch]):
587
for patch in patches:
@@ -556,9 +597,9 @@ def inference_receive_sparse_tensor(
597
engine.receive_sparse_weights(update_info, apply_sparse_patches)
598
torch.accelerator.synchronize()
599
559
- expected = torch.zeros(30, dtype=torch.float32, device="cuda")
600
+ expected = torch.zeros(30, dtype=torch.float32, device=device)
601
expected[[1, 7, 25]] = torch.tensor(
561
- [10.0, 20.0, 30.0], dtype=torch.float32, device="cuda"
602
+ [10.0, 20.0, 30.0], dtype=torch.float32, device=device
603
)
604
success = torch.equal(target, expected)
605
engine.shutdown()
@@ -574,7 +615,7 @@ def inference_receive_sparse_tensor(
615
)
616
def test_nccl_sparse_weight_transfer_between_processes():
617
"""Test NCCL sparse weight transfer from trainer to inference process."""
577
- ray.init(ignore_reinit_error=True)
618
+ _init_ray_for_weight_transfer()
619
620
master_address = "127.0.0.1"
621
master_port = get_open_port()
@@ -933,16 +974,18 @@ class TrainerActor:
974
"""Trainer actor that creates and holds CUDA IPC handles."""
975
976
def __init__(self, tensor_shape: list[int], tensor_dtype: str):
977
+ device = _set_ray_assigned_device()
978
+
979
# Create tensor on GPU and keep it alive
980
dtype = getattr(torch, tensor_dtype)
938
- self.tensor = torch.ones(tensor_shape, dtype=dtype, device="cuda:0")
981
+ self.tensor = torch.ones(tensor_shape, dtype=dtype, device=device)
982
self.tensor.fill_(42.0) # Fill with 42 to verify correct transfer
983
984
# Create IPC handle (tensor must stay alive for IPC to work)
985
# reduce_tensor returns (rebuild_func, args); we only send args
986
# since the receiver imports rebuild_cuda_tensor directly.
987
_, ipc_args = reduce_tensor(self.tensor)
945
- gpu_uuid = get_physical_gpu_id(0)
988
+ gpu_uuid = get_physical_gpu_id(device.index)
989
990
torch.accelerator.synchronize()
991
@@ -974,6 +1017,8 @@ def inference_receive_ipc_tensor(
1017
1018
import torch
1019
1020
+ _set_ray_assigned_device()
1021
+
1022
from vllm.config.parallel import ParallelConfig
1023
from vllm.config.weight_transfer import WeightTransferConfig
1024
from vllm.distributed.weight_transfer.ipc_engine import (
@@ -1072,7 +1117,7 @@ def test_ipc_weight_transfer_between_processes(mode: str):
1117
from ray.util.placement_group import placement_group
1118
from ray.util.scheduling_strategies import PlacementGroupSchedulingStrategy
1119
1075
- ray.init(ignore_reinit_error=True)
1120
+ _init_ray_for_weight_transfer()
1121
1122
# Create a placement group to ensure both processes are on the same GPU
1123
# Use fractional GPUs so both tasks can share the same GPU bundle