import pytest import ray from ray.air.execution.resources.fixed import FixedResourceManager from ray.air.execution.resources.request import ResourceRequest from ray.util.scheduling_strategies import PlacementGroupSchedulingStrategy REQUEST_2_CPU = ResourceRequest([{"CPU": 2}]) REQUEST_4_CPU = ResourceRequest([{"CPU": 4}]) REQUEST_1_2_CPU = ResourceRequest([{"CPU": 1}, {"CPU": 2}]) REQUEST_0_2_CPU = ResourceRequest([{"CPU": 0}, {"CPU": 2}]) @pytest.fixture(scope="module") def ray_start_4_cpus(): address_info = ray.init(num_cpus=4) yield address_info # The code after the yield will run as teardown code. ray.shutdown() def test_acquire_return_resources(ray_start_4_cpus): manager = FixedResourceManager(total_resources={"CPU": 4}) assert not manager.has_resources_ready(REQUEST_2_CPU) assert not manager.has_resources_ready(REQUEST_4_CPU) manager.request_resources(REQUEST_2_CPU) manager.request_resources(REQUEST_4_CPU) assert manager.has_resources_ready(REQUEST_4_CPU) ready_2 = manager.acquire_resources(REQUEST_2_CPU) assert manager.has_resources_ready(REQUEST_2_CPU) assert not manager.has_resources_ready(REQUEST_4_CPU) manager.free_resources(ready_2) assert manager.has_resources_ready(REQUEST_4_CPU) def test_numerical_error(ray_start_4_cpus): """Make sure we don't run into numerical errors when using fractional resources. Legacy test: test_trial_runner::TrialRunnerTest::testResourceNumericalError """ manager = FixedResourceManager( total_resources={"CPU": 0.99, "GPU": 0.99, "a": 0.99} ) resource_request = ResourceRequest([{"CPU": 0.33, "GPU": 0.33, "a": 0.33}]) for i in range(3): manager.request_resources(resource_request) assert manager.acquire_resources( resource_request=resource_request ), manager._available_resources assert manager._available_resources["CPU"] == 0 assert manager._available_resources["GPU"] == 0 assert manager._available_resources["a"] == 0 def test_bind_two_bundles(ray_start_4_cpus): """Test that binding two remote objects to a ready resource works. - Request resources with 2 bundles (1 CPU and 2 CPUs) - Bind two remote tasks to these bundles, execute - Assert that resource allocation returns the correct resources: 1 CPU and 2 CPUs """ manager = FixedResourceManager() manager.request_resources(REQUEST_1_2_CPU) assert manager.has_resources_ready(REQUEST_1_2_CPU) @ray.remote def get_assigned_resources(): return ray.get_runtime_context().get_assigned_resources() acq = manager.acquire_resources(REQUEST_1_2_CPU) [av1] = acq.annotate_remote_entities([get_assigned_resources]) res1 = ray.get(av1.remote()) assert sum(v for k, v in res1.items() if k.startswith("CPU")) == 1 [av1, av2] = acq.annotate_remote_entities( [get_assigned_resources, get_assigned_resources] ) res1, res2 = ray.get([av1.remote(), av2.remote()]) assert sum(v for k, v in res1.items() if k.startswith("CPU")) == 1 assert sum(v for k, v in res2.items() if k.startswith("CPU")) == 2 def test_bind_empty_head_bundle(ray_start_4_cpus): """Test that binding two remote objects to a ready resource works with empty head. - Request resources with 2 bundles (0 CPU and 2 CPUs) - Bind two remote tasks to these bundles, execute - Assert that resource allocation returns the correct resources: 0 CPU and 2 CPUs """ manager = FixedResourceManager() assert REQUEST_0_2_CPU.head_bundle_is_empty manager.request_resources(REQUEST_0_2_CPU) ray.wait(manager.get_resource_futures(), num_returns=1) assert manager.has_resources_ready(REQUEST_0_2_CPU) @ray.remote def get_assigned_resources(): return ray.get_runtime_context().get_assigned_resources() acq = manager.acquire_resources(REQUEST_0_2_CPU) [av1] = acq.annotate_remote_entities([get_assigned_resources]) res1 = ray.get(av1.remote()) assert sum(v for k, v in res1.items() if k.startswith("CPU")) == 0 [av1, av2] = acq.annotate_remote_entities( [get_assigned_resources, get_assigned_resources] ) res1, res2 = ray.get([av1.remote(), av2.remote()]) assert sum(v for k, v in res1.items() if k.startswith("CPU")) == 0 assert sum(v for k, v in res2.items() if k.startswith("CPU")) == 2 @pytest.mark.parametrize("strategy", ["STRICT_PACK", "PACK", "SPREAD", "STRICT_SPREAD"]) def test_strategy(ray_start_4_cpus, strategy): """The fixed resoure manager does not support STRICT placement strategies.""" manager = FixedResourceManager() req = ResourceRequest([{"CPU": 2}], strategy=strategy) if strategy.startswith("STRICT_"): with pytest.raises(RuntimeError): manager.request_resources(req) else: manager.request_resources(req) @pytest.mark.parametrize("strategy", ["STRICT_PACK", "PACK", "SPREAD", "STRICT_SPREAD"]) def test_strategy_nested(ray_start_4_cpus, strategy): """The fixed resoure manager does not support STRICT_SPREAD within a PG.""" @ray.remote def nested_test(): manager = FixedResourceManager() req = ResourceRequest([{"CPU": 2}], strategy=strategy) if strategy == "STRICT_SPREAD": with pytest.raises(RuntimeError): manager.request_resources(req) else: manager.request_resources(req) pg = ray.util.placement_group([{"CPU": 2}]) ray.wait([pg.ready()]) try: ray.get( nested_test.options( scheduling_strategy=PlacementGroupSchedulingStrategy( placement_group=pg, placement_group_capture_child_tasks=True ) ).remote() ) finally: ray.util.remove_placement_group(pg) if __name__ == "__main__": import sys sys.exit(pytest.main(["-v", __file__]))