Files
2026-07-13 13:17:40 +08:00

159 lines
4.7 KiB
Python

"""Tests for get_bundle_indices_sorted_by_node."""
import sys
from unittest.mock import MagicMock, patch
import pytest
import ray
from ray._private.test_utils import placement_group_assert_no_leak
from ray.llm._internal.serve.utils.pg_utils import get_bundle_indices_sorted_by_node
from ray.serve._private.utils import get_head_node_id
# Realistic 56-char hex node IDs
NODE_A = "ab7c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c" # driver node
NODE_B = "1234567890abcdef1234567890abcdef1234567890abcdef12345678"
NODE_C = "fedcba0987654321fedcba0987654321fedcba0987654321fedcba09"
def _assert_grouped_by_node(result, bundles_to_node_id):
seen_nodes = set()
prev_node = None
for idx in result:
node = bundles_to_node_id[idx]
if node != prev_node:
assert (
node not in seen_nodes
), f"Node {node} appeared non-contiguously in {result}"
seen_nodes.add(node)
prev_node = node
@pytest.mark.parametrize(
"bundles_to_node_id,expected_sorted_bundle_indices",
[
pytest.param(
{0: NODE_C, 1: NODE_A, 2: NODE_B, 3: NODE_C, 4: NODE_A, 5: NODE_B},
[1, 4, 2, 5, 0, 3],
),
pytest.param(
{0: NODE_B, 1: NODE_B, 2: NODE_A, 3: NODE_A},
[2, 3, 0, 1],
),
pytest.param(
# Driver has no bundles
{0: NODE_C, 1: NODE_B, 2: NODE_C, 3: NODE_B},
[1, 3, 0, 2],
),
pytest.param(
{0: NODE_A, 1: NODE_A, 2: NODE_A},
[0, 1, 2],
),
pytest.param(
{0: NODE_A},
[0],
),
pytest.param(
{},
[],
),
],
)
def test_sort_bundle_indices_by_node(
bundles_to_node_id, expected_sorted_bundle_indices
):
mock_pg = MagicMock()
with (
patch(
"ray.llm._internal.serve.utils.pg_utils.placement_group_table",
return_value={"bundles_to_node_id": bundles_to_node_id},
),
patch(
"ray.llm._internal.serve.utils.pg_utils.get_head_node_id",
return_value=NODE_A,
),
):
result = get_bundle_indices_sorted_by_node(mock_pg)
assert result == expected_sorted_bundle_indices
@pytest.mark.parametrize(
"bundles_to_node_id,driver_node_id,expected_sorted_bundle_indices",
[
# Explicit driver node orders that node's bundles first, even though the
# head node (NODE_A) also owns bundles.
pytest.param(
{0: NODE_A, 1: NODE_B, 2: NODE_A, 3: NODE_B},
NODE_B,
[1, 3, 0, 2],
),
# Head node (NODE_A) owns no bundles; explicit driver still wins over
# lexicographic fallback (which would otherwise pick NODE_B first).
pytest.param(
{0: NODE_B, 1: NODE_C, 2: NODE_B, 3: NODE_C},
NODE_C,
[1, 3, 0, 2],
),
],
)
def test_explicit_driver_node_ordered_first(
bundles_to_node_id, driver_node_id, expected_sorted_bundle_indices
):
mock_pg = MagicMock()
with (
patch(
"ray.llm._internal.serve.utils.pg_utils.placement_group_table",
return_value={"bundles_to_node_id": bundles_to_node_id},
),
patch(
"ray.llm._internal.serve.utils.pg_utils.get_head_node_id",
return_value=NODE_A,
),
):
result = get_bundle_indices_sorted_by_node(
mock_pg, driver_node_id=driver_node_id
)
assert result == expected_sorted_bundle_indices
# Integration test: verifies the full path through a real cluster, though
# bundle ordering across nodes is non-deterministic and may already be sorted.
@pytest.mark.parametrize("strategy", ["SPREAD", "PACK"])
def test_sort_bundles(ray_start_cluster, strategy):
cluster = ray_start_cluster
for _ in range(2):
cluster.add_node(num_cpus=2)
ray.init(address=cluster.address)
cluster.wait_for_nodes()
driver_node_id = get_head_node_id()
pg = ray.util.placement_group(
name=f"test_sort_bundles_{strategy}",
strategy=strategy,
bundles=[{"CPU": 1}] * 4,
)
ray.get(pg.ready())
result = get_bundle_indices_sorted_by_node(pg)
table = ray.util.placement_group_table(pg)
bundles_to_node_id = table["bundles_to_node_id"]
assert sorted(result) == list(range(4))
_assert_grouped_by_node(result, bundles_to_node_id)
driver_bundles = [i for i in result if bundles_to_node_id[i] == driver_node_id]
if driver_bundles:
assert result[: len(driver_bundles)] == driver_bundles
placement_group_assert_no_leak([pg])
if __name__ == "__main__":
sys.exit(pytest.main(["-v", __file__]))