| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | """Test that MessageQueue uses the local node's IP for binding, |
| 4 | not a remote master_addr. This validates the fix for cross-node |
| 5 | data-parallel where each DP group leader must bind to its own IP. |
| 6 | |
| 7 | The bug: multiproc_executor used `parallel_config.master_addr` as |
| 8 | `connect_ip` for every DP group's MessageQueue. For DP groups whose |
| 9 | leader is NOT on the master node, binding to master_addr fails with |
| 10 | "Cannot assign requested address". |
| 11 | |
| 12 | The fix: use `get_ip()` (local node IP) instead of `master_addr`. |
| 13 | """ |
| 14 | |
| 15 | import pytest |
| 16 | import zmq |
| 17 | |
| 18 | from vllm.distributed.device_communicators.shm_broadcast import MessageQueue |
| 19 | from vllm.utils.network_utils import get_ip |
| 20 | |
| 21 | |
| 22 | def test_mq_bind_with_local_ip(): |
| 23 | """MessageQueue with remote readers should successfully bind |
| 24 | when connect_ip is the local node's IP.""" |
| 25 | # n_reader=2, n_local_reader=1 means 1 remote reader, |
| 26 | # which triggers the remote ZMQ socket bind. |
| 27 | mq = MessageQueue( |
| 28 | n_reader=2, |
| 29 | n_local_reader=1, |
| 30 | connect_ip=get_ip(), |
| 31 | ) |
| 32 | handle = mq.export_handle() |
| 33 | assert handle.remote_subscribe_addr is not None |
| 34 | # The bound address should contain our local IP |
| 35 | local_ip = get_ip() |
| 36 | assert ( |
| 37 | local_ip in handle.remote_subscribe_addr |
| 38 | or f"[{local_ip}]" in handle.remote_subscribe_addr |
| 39 | ) |
| 40 | del mq |
| 41 | |
| 42 | |
| 43 | def test_mq_bind_with_non_local_ip_fails(): |
| 44 | """MessageQueue should fail to bind when connect_ip is a |
| 45 | non-local IP address (simulating the bug where master_addr |
| 46 | from a different node was used).""" |
| 47 | # Use a non-local IP that we definitely can't bind to. |
| 48 | # 198.51.100.1 is from TEST-NET-2 (RFC 5737), never locally assigned. |
| 49 | non_local_ip = "198.51.100.1" |
| 50 | with pytest.raises(zmq.error.ZMQError, match="Cannot assign requested address"): |
| 51 | MessageQueue( |
| 52 | n_reader=2, |
| 53 | n_local_reader=1, |
| 54 | connect_ip=non_local_ip, |
| 55 | ) |
| 56 | |
| 57 | |
| 58 | def test_mq_bind_defaults_to_local_ip(): |
| 59 | """When connect_ip is None, MessageQueue should auto-detect |
| 60 | the local IP and bind successfully.""" |
| 61 | mq = MessageQueue( |
| 62 | n_reader=2, |
| 63 | n_local_reader=1, |
| 64 | connect_ip=None, # should fallback to get_ip() |
| 65 | ) |
| 66 | handle = mq.export_handle() |
| 67 | assert handle.remote_subscribe_addr is not None |
| 68 | del mq |
| 69 | |
| 70 | |
| 71 | if __name__ == "__main__": |
| 72 | test_mq_bind_with_local_ip() |
| 73 | print("PASSED: test_mq_bind_with_local_ip") |
| 74 | test_mq_bind_with_non_local_ip_fails() |
| 75 | print("PASSED: test_mq_bind_with_non_local_ip_fails") |
| 76 | test_mq_bind_defaults_to_local_ip() |
| 77 | print("PASSED: test_mq_bind_defaults_to_local_ip") |
| 78 | print("\nAll tests passed!") |