342 lines
11 KiB
Python
342 lines
11 KiB
Python
import sys
|
|
|
|
import pytest
|
|
|
|
import ray
|
|
from ray.util.scheduling_strategies import (
|
|
DoesNotExist,
|
|
Exists,
|
|
In,
|
|
NodeLabelSchedulingStrategy,
|
|
NotIn,
|
|
)
|
|
|
|
|
|
@ray.remote
|
|
class MyActor:
|
|
def __init__(self):
|
|
self.value = 0
|
|
|
|
def value(self):
|
|
return self.value
|
|
|
|
def get_node_id(self):
|
|
return ray.get_runtime_context().get_node_id()
|
|
|
|
|
|
@ray.remote
|
|
def get_node_id():
|
|
return ray.get_runtime_context().get_node_id()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"call_ray_start",
|
|
['ray start --head --labels={"gpu_type":"A100","region":"us"}'],
|
|
indirect=True,
|
|
)
|
|
def test_node_label_scheduling_basic(call_ray_start):
|
|
ray.init(address=call_ray_start)
|
|
actor = MyActor.options(
|
|
scheduling_strategy=NodeLabelSchedulingStrategy(
|
|
{"gpu_type": In("A100", "T100"), "region": Exists()}
|
|
)
|
|
).remote()
|
|
assert ray.get(actor.value.remote(), timeout=3) == 0
|
|
|
|
actor = MyActor.options(
|
|
scheduling_strategy=NodeLabelSchedulingStrategy({"gpu_type": NotIn("A100")})
|
|
).remote()
|
|
with pytest.raises(TimeoutError):
|
|
assert ray.get(actor.value.remote(), timeout=3) == 0
|
|
|
|
actor = MyActor.options(
|
|
scheduling_strategy=NodeLabelSchedulingStrategy(
|
|
hard={"gpu_type": DoesNotExist()},
|
|
soft={"gpu_type": In("A100")},
|
|
)
|
|
).remote()
|
|
with pytest.raises(TimeoutError):
|
|
assert ray.get(actor.value.remote(), timeout=3) == 0
|
|
|
|
actor = MyActor.options(
|
|
scheduling_strategy=NodeLabelSchedulingStrategy(
|
|
hard={"gpu_type": In("T100")},
|
|
)
|
|
).remote()
|
|
with pytest.raises(TimeoutError):
|
|
assert ray.get(actor.value.remote(), timeout=3) == 0
|
|
|
|
actor = MyActor.options(
|
|
scheduling_strategy=NodeLabelSchedulingStrategy(
|
|
hard={},
|
|
soft={"gpu_type": In("T100ssss")},
|
|
)
|
|
).remote()
|
|
assert ray.get(actor.value.remote(), timeout=3) == 0
|
|
|
|
|
|
def test_node_label_scheduling_in_cluster(ray_start_cluster):
|
|
cluster = ray_start_cluster
|
|
cluster.add_node(
|
|
resources={"worker1": 1},
|
|
num_cpus=3,
|
|
labels={"gpu_type": "A100", "azone": "azone-1"},
|
|
)
|
|
cluster.wait_for_nodes()
|
|
ray.init(address=cluster.address)
|
|
node_1 = ray.get(get_node_id.options(resources={"worker1": 1}).remote())
|
|
cluster.add_node(
|
|
resources={"worker2": 1},
|
|
num_cpus=3,
|
|
labels={"gpu_type": "T100", "azone": "azone-1"},
|
|
)
|
|
node_2 = ray.get(get_node_id.options(resources={"worker2": 1}).remote())
|
|
cluster.add_node(
|
|
resources={"worker3": 1},
|
|
num_cpus=3,
|
|
labels={"gpu_type": "T100", "azone": "azone-2"},
|
|
)
|
|
node_3 = ray.get(get_node_id.options(resources={"worker3": 1}).remote())
|
|
cluster.add_node(resources={"worker4": 1}, num_cpus=3)
|
|
node_4 = ray.get(get_node_id.options(resources={"worker4": 1}).remote())
|
|
cluster.wait_for_nodes()
|
|
|
|
actor = MyActor.options(
|
|
scheduling_strategy=NodeLabelSchedulingStrategy({"gpu_type": In("A100")})
|
|
).remote()
|
|
assert ray.get(actor.get_node_id.remote(), timeout=3) == node_1
|
|
|
|
actor = MyActor.options(
|
|
scheduling_strategy=NodeLabelSchedulingStrategy({"ray.io/node-id": In(node_4)})
|
|
).remote()
|
|
assert ray.get(actor.get_node_id.remote(), timeout=3) == node_4
|
|
|
|
actor = MyActor.options(
|
|
scheduling_strategy=NodeLabelSchedulingStrategy({"gpu_type": In("T100")})
|
|
).remote()
|
|
assert ray.get(actor.get_node_id.remote(), timeout=3) in (node_2, node_3)
|
|
|
|
actor = MyActor.options(
|
|
scheduling_strategy=NodeLabelSchedulingStrategy(
|
|
{"azone": In("azone-1", "azone-2")}
|
|
)
|
|
).remote()
|
|
assert ray.get(actor.get_node_id.remote(), timeout=3) in (node_1, node_2, node_3)
|
|
|
|
actor = MyActor.options(
|
|
scheduling_strategy=NodeLabelSchedulingStrategy(
|
|
{"gpu_type": In("T100"), "azone": In("azone-1")}
|
|
)
|
|
).remote()
|
|
assert ray.get(actor.get_node_id.remote(), timeout=3) in (node_2)
|
|
|
|
actor = MyActor.options(
|
|
scheduling_strategy=NodeLabelSchedulingStrategy(
|
|
{
|
|
"gpu_type": NotIn("A100"),
|
|
}
|
|
)
|
|
).remote()
|
|
assert ray.get(actor.get_node_id.remote(), timeout=3) in (node_2, node_3, node_4)
|
|
|
|
actor = MyActor.options(
|
|
scheduling_strategy=NodeLabelSchedulingStrategy(
|
|
{
|
|
"gpu_type": DoesNotExist(),
|
|
}
|
|
)
|
|
).remote()
|
|
assert ray.get(actor.get_node_id.remote(), timeout=3) in (node_4)
|
|
|
|
actor = MyActor.options(
|
|
scheduling_strategy=NodeLabelSchedulingStrategy(
|
|
{
|
|
"gpu_type": Exists(),
|
|
}
|
|
)
|
|
).remote()
|
|
assert ray.get(actor.get_node_id.remote(), timeout=3) in (node_1, node_2, node_3)
|
|
|
|
|
|
def test_node_label_scheduling_with_soft(ray_start_cluster):
|
|
cluster = ray_start_cluster
|
|
cluster.add_node(
|
|
resources={"worker1": 1},
|
|
num_cpus=3,
|
|
labels={"gpu_type": "A100", "azone": "azone-1"},
|
|
)
|
|
cluster.wait_for_nodes()
|
|
ray.init(address=cluster.address)
|
|
node_1 = ray.get(get_node_id.options(resources={"worker1": 1}).remote())
|
|
cluster.add_node(
|
|
resources={"worker2": 1},
|
|
num_cpus=3,
|
|
labels={"gpu_type": "T100", "azone": "azone-1"},
|
|
)
|
|
node_2 = ray.get(get_node_id.options(resources={"worker2": 1}).remote())
|
|
cluster.add_node(
|
|
resources={"worker3": 1},
|
|
num_cpus=3,
|
|
labels={"gpu_type": "T100", "azone": "azone-2"},
|
|
)
|
|
node_3 = ray.get(get_node_id.options(resources={"worker3": 1}).remote())
|
|
cluster.add_node(resources={"worker4": 1}, num_cpus=3)
|
|
node_4 = ray.get(get_node_id.options(resources={"worker4": 1}).remote())
|
|
cluster.wait_for_nodes()
|
|
|
|
# hard match and soft match
|
|
actor = MyActor.options(
|
|
scheduling_strategy=NodeLabelSchedulingStrategy(
|
|
hard={"azone": In("azone-1")}, soft={"gpu_type": In("T100")}
|
|
)
|
|
).remote()
|
|
assert ray.get(actor.get_node_id.remote(), timeout=3) == node_2
|
|
|
|
# hard match and soft don't match
|
|
actor = MyActor.options(
|
|
scheduling_strategy=NodeLabelSchedulingStrategy(
|
|
hard={"azone": In("azone-1")}, soft={"gpu_type": In("H100")}
|
|
)
|
|
).remote()
|
|
assert ray.get(actor.get_node_id.remote(), timeout=3) in (node_1, node_2)
|
|
|
|
# no hard and soft match
|
|
actor = MyActor.options(
|
|
scheduling_strategy=NodeLabelSchedulingStrategy(
|
|
hard={}, soft={"gpu_type": Exists()}
|
|
)
|
|
).remote()
|
|
assert ray.get(actor.get_node_id.remote(), timeout=3) in (node_1, node_2, node_3)
|
|
|
|
# no hard and soft don't match
|
|
actor = MyActor.options(
|
|
scheduling_strategy=NodeLabelSchedulingStrategy(
|
|
hard={}, soft={"gpu_type": In("H100")}
|
|
)
|
|
).remote()
|
|
assert ray.get(actor.get_node_id.remote(), timeout=3) in (
|
|
node_1,
|
|
node_2,
|
|
node_3,
|
|
node_4,
|
|
)
|
|
|
|
# hard don't match and soft match
|
|
actor = MyActor.options(
|
|
scheduling_strategy=NodeLabelSchedulingStrategy(
|
|
hard={"azone": In("azone-3")}, soft={"gpu_type": In("T100")}
|
|
)
|
|
).remote()
|
|
with pytest.raises(TimeoutError):
|
|
ray.get(actor.get_node_id.remote(), timeout=3)
|
|
|
|
|
|
def test_node_not_available(ray_start_cluster):
|
|
cluster = ray_start_cluster
|
|
cluster.add_node(resources={"worker1": 1}, num_cpus=1, labels={"gpu_type": "A100"})
|
|
cluster.wait_for_nodes()
|
|
ray.init(address=cluster.address)
|
|
node_1 = ray.get(get_node_id.options(resources={"worker1": 1}).remote())
|
|
cluster.add_node(resources={"worker2": 1}, num_cpus=1)
|
|
node_2 = ray.get(get_node_id.options(resources={"worker2": 1}).remote())
|
|
cluster.wait_for_nodes()
|
|
|
|
# Infeasible
|
|
actor = MyActor.options(
|
|
num_cpus=2,
|
|
scheduling_strategy=NodeLabelSchedulingStrategy(hard={"gpu_type": In("A100")}),
|
|
).remote()
|
|
with pytest.raises(TimeoutError):
|
|
ray.get(actor.get_node_id.remote(), timeout=3)
|
|
|
|
actor = MyActor.options(
|
|
num_cpus=1,
|
|
scheduling_strategy=NodeLabelSchedulingStrategy(hard={"gpu_type": In("A100")}),
|
|
).remote()
|
|
assert ray.get(actor.get_node_id.remote(), timeout=3) == node_1
|
|
|
|
# Soft match node is not available, sheduling to other available node.
|
|
actor_2 = MyActor.options(
|
|
num_cpus=1,
|
|
scheduling_strategy=NodeLabelSchedulingStrategy(
|
|
hard={}, soft={"gpu_type": In("A100")}
|
|
),
|
|
).remote()
|
|
assert ray.get(actor_2.get_node_id.remote(), timeout=3) == node_2
|
|
|
|
# No available nodes.
|
|
actor_3 = MyActor.options(
|
|
num_cpus=1,
|
|
scheduling_strategy=NodeLabelSchedulingStrategy(
|
|
hard={}, soft={"gpu_type": In("A100")}
|
|
),
|
|
).remote()
|
|
with pytest.raises(TimeoutError):
|
|
ray.get(actor_3.get_node_id.remote(), timeout=3)
|
|
|
|
# node_1 change to available
|
|
ray.kill(actor)
|
|
assert ray.get(actor_3.get_node_id.remote(), timeout=3) == node_1
|
|
|
|
|
|
def test_node_label_scheduling_invalid_paramter(call_ray_start):
|
|
ray.init(address=call_ray_start)
|
|
with pytest.raises(
|
|
ValueError, match="Type of value in position 0 for the In operator must be str"
|
|
):
|
|
MyActor.options(
|
|
scheduling_strategy=NodeLabelSchedulingStrategy({"gpu_type": In(123)})
|
|
)
|
|
|
|
with pytest.raises(
|
|
ValueError,
|
|
match="Type of value in position 0 for the NotIn operator must be str",
|
|
):
|
|
MyActor.options(
|
|
scheduling_strategy=NodeLabelSchedulingStrategy({"gpu_type": NotIn(123)})
|
|
)
|
|
|
|
with pytest.raises(
|
|
ValueError,
|
|
match="The variadic parameter of the In operator must be a non-empty tuple",
|
|
):
|
|
MyActor.options(
|
|
scheduling_strategy=NodeLabelSchedulingStrategy({"gpu_type": In()})
|
|
)
|
|
|
|
with pytest.raises(
|
|
ValueError,
|
|
match="The variadic parameter of the NotIn operator must be a non-empty tuple",
|
|
):
|
|
MyActor.options(
|
|
scheduling_strategy=NodeLabelSchedulingStrategy({"gpu_type": NotIn()})
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="The soft parameter must be a map"):
|
|
MyActor.options(
|
|
scheduling_strategy=NodeLabelSchedulingStrategy(hard=None, soft=["1"])
|
|
)
|
|
|
|
with pytest.raises(
|
|
ValueError, match="The map key of the hard parameter must be of type str"
|
|
):
|
|
MyActor.options(scheduling_strategy=NodeLabelSchedulingStrategy({111: "1111"}))
|
|
|
|
with pytest.raises(
|
|
ValueError, match="must be one of the `In`, `NotIn`, `Exists` or `DoesNotExist`"
|
|
):
|
|
MyActor.options(
|
|
scheduling_strategy=NodeLabelSchedulingStrategy({"gpu_type": "1111"})
|
|
)
|
|
|
|
with pytest.raises(
|
|
ValueError,
|
|
match="The `hard` and `soft` parameter "
|
|
"of NodeLabelSchedulingStrategy cannot both be empty.",
|
|
):
|
|
MyActor.options(scheduling_strategy=NodeLabelSchedulingStrategy(hard={}))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(pytest.main(["-sv", __file__]))
|