Files
ray-project--ray/python/ray/tests/test_get_locations.py
T
2026-07-13 13:17:40 +08:00

254 lines
7.8 KiB
Python

import sys
import time
import numpy as np
import pandas as pd
import pytest
import ray
def test_uninitialized():
with pytest.raises(RuntimeError):
ray.experimental.get_object_locations([])
def test_get_locations_empty_list(ray_start_regular):
locations = ray.experimental.get_object_locations([])
assert len(locations) == 0
def test_get_locations(ray_start_regular):
node_id = ray.get_runtime_context().get_node_id()
sizes = [100, 1000]
obj_refs = [ray.put(np.zeros(s, dtype=np.uint8)) for s in sizes]
ray.wait(obj_refs)
locations = ray.experimental.get_object_locations(obj_refs)
assert len(locations) == 2
for idx, obj_ref in enumerate(obj_refs):
location = locations[obj_ref]
assert location["object_size"] > sizes[idx]
assert location["node_ids"] == [node_id]
def test_get_locations_inlined(ray_start_regular):
node_id = ray.get_runtime_context().get_node_id()
obj_refs = [ray.put("123")]
ray.wait(obj_refs)
locations = ray.experimental.get_object_locations(obj_refs)
for idx, obj_ref in enumerate(obj_refs):
location = locations[obj_ref]
assert location["node_ids"] == [node_id]
assert location["object_size"] > 0
def test_spilled_locations(ray_start_cluster_enabled):
cluster = ray_start_cluster_enabled
cluster.add_node(num_cpus=1, object_store_memory=75 * 1024 * 1024)
ray.init(cluster.address)
cluster.wait_for_nodes()
node_id = ray.get_runtime_context().get_node_id()
@ray.remote
def task():
arr = np.random.rand(5 * 1024 * 1024) # 40 MB
refs = []
refs.extend([ray.put(arr) for _ in range(2)])
ray.get(ray.put(arr))
ray.get(ray.put(arr))
return refs
object_refs = ray.get(task.remote())
ray.wait(object_refs)
locations = ray.experimental.get_object_locations(object_refs)
for obj_ref in object_refs:
location = locations[obj_ref]
assert location["node_ids"] == [node_id]
assert location["object_size"] > 0
def test_get_locations_multi_nodes(ray_start_cluster_enabled):
cluster = ray_start_cluster_enabled
# head node
cluster.add_node(num_cpus=1, object_store_memory=75 * 1024 * 1024)
ray.init(cluster.address)
# add 1 worker node
cluster.add_node(
num_cpus=0, resources={"custom": 1}, object_store_memory=75 * 1024 * 1024
)
cluster.wait_for_nodes()
all_node_ids = list(map(lambda node: node["NodeID"], ray.nodes()))
driver_node_id = ray.get_runtime_context().get_node_id()
all_node_ids.remove(driver_node_id)
worker_node_id = all_node_ids[0]
@ray.remote(num_cpus=0, resources={"custom": 1})
def create_object():
return np.random.rand(1 * 1024 * 1024)
@ray.remote
def task():
return [create_object.remote()]
object_refs = ray.get(task.remote())
ray.wait(object_refs)
locations = ray.experimental.get_object_locations(object_refs)
for obj_ref in object_refs:
location = locations[obj_ref]
assert set(location["node_ids"]) == {driver_node_id, worker_node_id}
assert location["object_size"] > 0
def test_location_pending(ray_start_cluster):
cluster = ray_start_cluster
cluster.add_node(num_cpus=1, object_store_memory=75 * 1024 * 1024)
ray.init(cluster.address)
cluster.wait_for_nodes()
@ray.remote
def task():
# sleep for 1 hour so the object will be pending
time.sleep(3600)
return 1
object_ref = task.remote()
locations = ray.experimental.get_object_locations([object_ref])
assert len(locations) == 1
location = locations[object_ref]
assert location["node_ids"] == []
assert location["object_size"] is None
local_locations = ray.experimental.get_local_object_locations([object_ref])
assert len(local_locations) == 1
local_location = local_locations[object_ref]
assert local_location["node_ids"] == []
assert local_location["object_size"] is None
# Tests for `get_local_object_locations`. We use matrix test:
# - callee can be regular ray task, or streaming generator;
# - caller can be in the same node (single node cluster), or different node.
#
# ... so we have 4 tests.
#
# Each test has the caller to produce Object(s) that consumes big memory but has a small
# sys.getsizeof. The caller then asserts the object size from the API
# `ray.experimental.get_local_object_locations` is > the actual memory consumed.
BIG_OBJ_SIZE = 3 * 1024 * 1024 # 3 MiB
class BigObject:
def __init__(self):
self.data = np.zeros((BIG_OBJ_SIZE,), dtype=np.uint8)
@ray.remote
def gen_big_object(block_size):
return pd.DataFrame([{"data": BigObject()} for _ in range(block_size)])
@ray.remote
def gen_big_objects(block_size, block_count):
for _ in range(block_count):
big_object = ray.get(gen_big_object.remote(block_size))
yield big_object
def assert_object_size_gt(obj_ref: ray.ObjectRef, size: int):
d = ray.experimental.get_local_object_locations([obj_ref])
assert d is not None
assert len(d) == 1
assert d[obj_ref]["object_size"] > size
def test_get_local_locations(ray_start_regular):
"""
caller and callee are in the same node.
callee is a regular ray task.
"""
obj_ref = gen_big_object.remote(3)
ray.wait([obj_ref])
# The dataframe consists of 3 MiB of NumPy NDArrays.
assert_object_size_gt(obj_ref, BIG_OBJ_SIZE)
def test_get_local_locations_generator(ray_start_regular):
"""
caller and callee are in the same node.
callee is a streaming generator.
"""
for obj_ref in gen_big_objects.remote(3, 10):
# No need to ray.wait, the object ref must have been ready before it's yielded.
# The dataframe consists of 3 MiB of NumPy NDArrays.
assert_object_size_gt(obj_ref, BIG_OBJ_SIZE)
def test_get_local_locations_multi_nodes(ray_start_cluster):
"""
caller and callee are in different nodes.
callee is a regular ray task.
"""
cluster = ray_start_cluster
# head node
head_node = cluster.add_node(num_cpus=1, object_store_memory=75 * 1024 * 1024)
head_node_id = head_node.node_id
ray.init(cluster.address)
# add 1 worker node
worker_node = cluster.add_node(num_cpus=1, object_store_memory=75 * 1024 * 1024)
worker_node_id = worker_node.node_id
cluster.wait_for_nodes()
@ray.remote
def caller():
obj_ref = gen_big_object.options(
label_selector={ray._raylet.RAY_NODE_ID_KEY: worker_node_id}
).remote(3)
ray.wait([obj_ref])
# The dataframe consists of 3 MiB of NumPy NDArrays.
assert_object_size_gt(obj_ref, BIG_OBJ_SIZE)
ray.get(
caller.options(
label_selector={ray._raylet.RAY_NODE_ID_KEY: head_node_id}
).remote()
)
def test_get_local_locations_generator_multi_nodes(ray_start_cluster):
"""
caller and callee are in different nodes.
callee is a streaming generator.
"""
cluster = ray_start_cluster
# head node
head_node = cluster.add_node(num_cpus=1, object_store_memory=75 * 1024 * 1024)
head_node_id = head_node.node_id
ray.init(cluster.address)
# add 1 worker node
worker_node = cluster.add_node(num_cpus=1, object_store_memory=75 * 1024 * 1024)
worker_node_id = worker_node.node_id
cluster.wait_for_nodes()
@ray.remote
def caller():
gen = gen_big_objects.options(
label_selector={ray._raylet.RAY_NODE_ID_KEY: worker_node_id}
).remote(3, 10)
for obj_ref in gen:
# No need to ray.wait, the object ref must have been ready before it's
# yielded.
assert_object_size_gt(obj_ref, BIG_OBJ_SIZE)
ray.get(
caller.options(
label_selector={ray._raylet.RAY_NODE_ID_KEY: head_node_id}
).remote()
)
if __name__ == "__main__":
sys.exit(pytest.main(["-sv", __file__]))