166 lines
5.2 KiB
Python
166 lines
5.2 KiB
Python
import os
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
import ray
|
|
from ray.air.constants import TRAINING_ITERATION
|
|
from ray.air.execution import FixedResourceManager
|
|
from ray.train import ScalingConfig
|
|
from ray.train._internal.storage import StorageContext
|
|
from ray.train.tests.util import mock_storage_context
|
|
from ray.tune import CheckpointConfig, Trainable, register_trainable
|
|
from ray.tune.execution.tune_controller import TuneController
|
|
from ray.tune.experiment import Trial
|
|
|
|
STORAGE = mock_storage_context()
|
|
|
|
|
|
@pytest.fixture(scope="function")
|
|
def ray_start_4_cpus_2_gpus_extra():
|
|
address_info = ray.init(num_cpus=4, num_gpus=2, resources={"a": 2})
|
|
yield address_info
|
|
ray.shutdown()
|
|
|
|
|
|
# TODO: [V2] Delete the `data_parallel` variant once V1 is fully removed.
|
|
@pytest.mark.parametrize("trainable_type", ["class", "function", "data_parallel"])
|
|
@pytest.mark.parametrize("patch_iter", [False, True])
|
|
def test_checkpoint_freq_dir_name(
|
|
ray_start_4_cpus_2_gpus_extra, trainable_type, patch_iter, tmp_path
|
|
):
|
|
"""Test that trial checkpoint IDs are correctly set across trainable types.
|
|
|
|
This includes a current workaround to set checkpoint IDs according to reported
|
|
metrics.
|
|
"""
|
|
|
|
def num_checkpoints(trial):
|
|
return sum(
|
|
item.startswith("checkpoint_")
|
|
for item in os.listdir(trial.storage.trial_fs_path)
|
|
)
|
|
|
|
def last_checkpoint_dir(trial):
|
|
return max(
|
|
item
|
|
for item in os.listdir(trial.storage.trial_fs_path)
|
|
if item.startswith("checkpoint_")
|
|
)
|
|
|
|
checkpoint_config = None
|
|
|
|
if trainable_type == "class":
|
|
|
|
class MyTrainable(Trainable):
|
|
def step(self):
|
|
# `training_iteration` is increased after the report, so we
|
|
# +1 here.
|
|
return {"step": self.iteration + 1}
|
|
|
|
def save_checkpoint(self, checkpoint_dir):
|
|
return {"test": self.iteration}
|
|
|
|
def load_checkpoint(self, checkpoint_dir):
|
|
pass
|
|
|
|
register_trainable("test_checkpoint_freq", MyTrainable)
|
|
checkpoint_config = CheckpointConfig(checkpoint_frequency=3)
|
|
|
|
elif trainable_type in {"function", "data_parallel"}:
|
|
|
|
def train_fn(config):
|
|
for step in range(1, 10):
|
|
if step > 0 and step % 3 == 0:
|
|
with tempfile.TemporaryDirectory() as checkpoint_dir:
|
|
(Path(checkpoint_dir) / "data.ckpt").write_text(str(step))
|
|
ray.tune.report(
|
|
{"step": step},
|
|
checkpoint=ray.tune.Checkpoint.from_directory(
|
|
checkpoint_dir
|
|
),
|
|
)
|
|
else:
|
|
ray.tune.report({"step": step})
|
|
|
|
if trainable_type == "function":
|
|
register_trainable("test_checkpoint_freq", train_fn)
|
|
elif trainable_type == "data_parallel":
|
|
from ray.train.data_parallel_trainer import DataParallelTrainer
|
|
|
|
trainer = DataParallelTrainer(
|
|
train_loop_per_worker=train_fn,
|
|
scaling_config=ScalingConfig(num_workers=1),
|
|
)
|
|
register_trainable("test_checkpoint_freq", trainer.as_trainable())
|
|
|
|
else:
|
|
raise RuntimeError("Invalid trainable type")
|
|
|
|
if patch_iter:
|
|
|
|
class CustomStorageContext(StorageContext):
|
|
def _update_checkpoint_index(self, metrics):
|
|
# Todo: Support auto-fille metrics for function trainables
|
|
self.current_checkpoint_index = metrics.get(
|
|
"step", self.current_checkpoint_index + 1
|
|
)
|
|
|
|
storage = mock_storage_context(
|
|
storage_context_cls=CustomStorageContext,
|
|
storage_path=tmp_path,
|
|
)
|
|
else:
|
|
storage = mock_storage_context(storage_path=tmp_path)
|
|
|
|
trial = Trial(
|
|
"test_checkpoint_freq",
|
|
checkpoint_config=checkpoint_config,
|
|
storage=storage,
|
|
)
|
|
runner = TuneController(
|
|
resource_manager_factory=lambda: FixedResourceManager(),
|
|
storage=STORAGE,
|
|
checkpoint_period=0,
|
|
)
|
|
runner.add_trial(trial)
|
|
|
|
while not trial.is_saving:
|
|
runner.step()
|
|
runner.step()
|
|
assert trial.last_result[TRAINING_ITERATION] == 3
|
|
assert num_checkpoints(trial) == 1
|
|
|
|
if patch_iter:
|
|
assert last_checkpoint_dir(trial) == "checkpoint_000003"
|
|
else:
|
|
assert last_checkpoint_dir(trial) == "checkpoint_000000"
|
|
|
|
while not trial.is_saving:
|
|
runner.step()
|
|
runner.step()
|
|
assert trial.last_result[TRAINING_ITERATION] == 6
|
|
assert num_checkpoints(trial) == 2
|
|
|
|
if patch_iter:
|
|
assert last_checkpoint_dir(trial) == "checkpoint_000006"
|
|
else:
|
|
assert last_checkpoint_dir(trial) == "checkpoint_000001"
|
|
|
|
while not trial.is_saving:
|
|
runner.step()
|
|
runner.step()
|
|
assert trial.last_result[TRAINING_ITERATION] == 9
|
|
assert num_checkpoints(trial) == 3
|
|
|
|
if patch_iter:
|
|
assert last_checkpoint_dir(trial) == "checkpoint_000009"
|
|
else:
|
|
assert last_checkpoint_dir(trial) == "checkpoint_000002"
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(pytest.main(["-v", __file__]))
|