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

185 lines
5.8 KiB
Python

from dataclasses import astuple, dataclass
import pytest
import ray
from ray.data._internal.util import _autodetect_parallelism
from ray.data.context import DataContext
from ray.tests.conftest import * # noqa
from ray.util.scheduling_strategies import PlacementGroupSchedulingStrategy
@dataclass
class TestCase:
avail_cpus: int
target_max_block_size: int
data_size: int
expected_parallelism: int
MiB = 1024 * 1024
GiB = 1024 * MiB
TEST_CASES = [
TestCase(
avail_cpus=4,
target_max_block_size=DataContext.get_current().target_max_block_size,
data_size=1024,
expected_parallelism=8, # avail_cpus has precedence
),
TestCase(
avail_cpus=4,
target_max_block_size=DataContext.get_current().target_max_block_size,
data_size=10 * MiB,
expected_parallelism=10, # MIN_BLOCK_SIZE has precedence
),
TestCase(
avail_cpus=4,
target_max_block_size=DataContext.get_current().target_max_block_size,
data_size=20 * MiB,
expected_parallelism=20, # MIN_BLOCK_SIZE has precedence
),
TestCase(
avail_cpus=4,
target_max_block_size=DataContext.get_current().target_max_block_size,
data_size=100 * MiB,
expected_parallelism=100, # MIN_BLOCK_SIZE has precedence
),
TestCase(
avail_cpus=4,
target_max_block_size=DataContext.get_current().target_max_block_size,
data_size=1 * GiB,
expected_parallelism=200, # MIN_PARALLELISM has precedence
),
TestCase(
avail_cpus=4,
target_max_block_size=DataContext.get_current().target_max_block_size,
data_size=10 * GiB,
expected_parallelism=200, # MIN_PARALLELISM has precedence
),
TestCase(
avail_cpus=150,
target_max_block_size=DataContext.get_current().target_max_block_size,
data_size=10 * GiB,
expected_parallelism=300, # avail_cpus has precedence
),
TestCase(
avail_cpus=400,
target_max_block_size=DataContext.get_current().target_max_block_size,
data_size=10 * GiB,
expected_parallelism=800, # avail_cpus has precedence
),
TestCase(
avail_cpus=400,
target_max_block_size=DataContext.get_current().target_max_block_size,
data_size=1 * MiB,
expected_parallelism=800, # avail_cpus has precedence
),
TestCase(
avail_cpus=4,
target_max_block_size=DataContext.get_current().target_max_block_size,
data_size=1000 * GiB,
expected_parallelism=8000, # MAX_BLOCK_SIZE has precedence
),
TestCase(
avail_cpus=4,
target_max_block_size=DataContext.get_current().target_max_block_size,
data_size=10000 * GiB,
expected_parallelism=80000, # MAX_BLOCK_SIZE has precedence
),
TestCase(
avail_cpus=4,
target_max_block_size=512 * MiB,
data_size=1000 * GiB,
expected_parallelism=2000, # passed max_block_size has precedence
),
TestCase(
avail_cpus=4,
target_max_block_size=512 * MiB,
data_size=10000 * GiB,
expected_parallelism=20000, # passed max_block_size has precedence
),
]
@pytest.mark.parametrize(
"avail_cpus,target_max_block_size,data_size,expected",
[astuple(test) for test in TEST_CASES],
)
def test_autodetect_parallelism(
shutdown_only, avail_cpus, target_max_block_size, data_size, expected
):
class MockReader:
def estimate_inmemory_data_size(self):
return data_size
result, _, _ = _autodetect_parallelism(
parallelism=-1,
target_max_block_size=target_max_block_size,
ctx=DataContext.get_current(),
datasource_or_legacy_reader=MockReader(),
avail_cpus=avail_cpus,
)
assert result == expected, (result, expected)
def test_auto_parallelism_basic(shutdown_only):
ray.init(num_cpus=8)
context = DataContext.get_current()
context.read_op_min_num_blocks = 1
# Datasource bound.
ds = ray.data.range_tensor(5, shape=(100,), override_num_blocks=-1)
assert ds._logical_plan.initial_num_blocks() == 5, ds
# CPU bound. TODO(ekl) we should fix range datasource to respect parallelism more
# properly, currently it can go a little over.
ds = ray.data.range_tensor(10000, shape=(100,), override_num_blocks=-1)
assert ds._logical_plan.initial_num_blocks() == 16, ds
# Block size bound.
ds = ray.data.range_tensor(100000000, shape=(100,), override_num_blocks=-1)
assert ds._logical_plan.initial_num_blocks() >= 590, ds
assert ds._logical_plan.initial_num_blocks() <= 600, ds
def test_auto_parallelism_placement_group(shutdown_only):
ray.init(num_cpus=16, num_gpus=8)
@ray.remote
def run():
context = DataContext.get_current()
context.min_parallelism = 1
ds = ray.data.range_tensor(2000, shape=(100,), override_num_blocks=-1)
return ds._logical_plan.initial_num_blocks()
# 1/16 * 4 * 16 = 4
pg = ray.util.placement_group([{"CPU": 1}])
num_blocks = ray.get(
run.options(
scheduling_strategy=PlacementGroupSchedulingStrategy(placement_group=pg)
).remote()
)
assert num_blocks == 4, num_blocks
# 2/16 * 4 * 16 = 8
pg = ray.util.placement_group([{"CPU": 2}])
num_blocks = ray.get(
run.options(
scheduling_strategy=PlacementGroupSchedulingStrategy(placement_group=pg)
).remote()
)
assert num_blocks == 8, num_blocks
# 1/8 * 4 * 16 = 8
pg = ray.util.placement_group([{"CPU": 1, "GPU": 1}])
num_blocks = ray.get(
run.options(
scheduling_strategy=PlacementGroupSchedulingStrategy(placement_group=pg)
).remote()
)
assert num_blocks == 8, num_blocks
if __name__ == "__main__":
import sys
sys.exit(pytest.main(["-v", __file__]))