185 lines
5.8 KiB
Python
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__]))
|