205 lines
6.0 KiB
Python
205 lines
6.0 KiB
Python
# flake8: noqa
|
|
# isort: skip_file
|
|
|
|
import os
|
|
|
|
os.environ["RAY_TRAIN_V2_ENABLED"] = "1"
|
|
|
|
# __quickstart_start__
|
|
import random
|
|
import tempfile
|
|
import uuid
|
|
|
|
import ray.train
|
|
import ray.train.torch
|
|
import ray.tune
|
|
from ray.tune.integration.ray_train import TuneReportCallback
|
|
|
|
|
|
# [1] Define your Ray Train worker code.
|
|
def train_fn_per_worker(train_loop_config: dict):
|
|
# Unpack train worker hyperparameters.
|
|
# Train feeds in the `train_loop_config` defined below.
|
|
lr = train_loop_config["lr"]
|
|
|
|
# training code here...
|
|
print(
|
|
ray.train.get_context().get_world_size(),
|
|
ray.train.get_context().get_world_rank(),
|
|
train_loop_config,
|
|
)
|
|
# model = ray.train.torch.prepare_model(...) # Wrap model in DDP.
|
|
with tempfile.TemporaryDirectory() as temp_checkpoint_dir:
|
|
ray.train.report(
|
|
{"loss": random.random()},
|
|
checkpoint=ray.train.Checkpoint.from_directory(temp_checkpoint_dir),
|
|
)
|
|
|
|
|
|
# [2] Define a function that launches the Ray Train run.
|
|
def train_driver_fn(config: dict):
|
|
# Unpack run-level hyperparameters.
|
|
# Tune feeds in hyperparameters defined in the `param_space` below.
|
|
num_workers = config["num_workers"]
|
|
|
|
trainer = ray.train.torch.TorchTrainer(
|
|
train_fn_per_worker,
|
|
train_loop_config=config["train_loop_config"],
|
|
scaling_config=ray.train.ScalingConfig(
|
|
num_workers=num_workers,
|
|
# Uncomment to use GPUs.
|
|
# use_gpu=True,
|
|
),
|
|
run_config=ray.train.RunConfig(
|
|
# [3] Assign unique names to each run.
|
|
# Recommendation: use the trial id as part of the run name.
|
|
name=f"train-trial_id={ray.tune.get_context().get_trial_id()}",
|
|
# [4] (Optional) Pass in a `TuneReportCallback` to propagate
|
|
# reported results to the Tuner.
|
|
callbacks=[TuneReportCallback()],
|
|
# (If multi-node, configure S3 / NFS as the storage path.)
|
|
# storage_path="s3://...",
|
|
),
|
|
)
|
|
trainer.fit()
|
|
|
|
|
|
# Launch a single Train run.
|
|
# Note that you can only create a TuneReportCallback in a Ray Tune session.
|
|
# train_driver_fn({"num_workers": 4, "train_loop_config": {"lr": 1e-3}})
|
|
|
|
|
|
# Launch a sweep of hyperparameters with Ray Tune.
|
|
tuner = ray.tune.Tuner(
|
|
train_driver_fn,
|
|
param_space={
|
|
"num_workers": ray.tune.choice([2, 4]),
|
|
"train_loop_config": {
|
|
"lr": ray.tune.grid_search([1e-3, 3e-4]),
|
|
"batch_size": ray.tune.grid_search([32, 64]),
|
|
},
|
|
},
|
|
run_config=ray.tune.RunConfig(
|
|
name=f"tune_train_example-{uuid.uuid4().hex[:6]}",
|
|
# (If multi-node, configure S3 / NFS as the storage path.)
|
|
# storage_path="s3://...",
|
|
),
|
|
# [5] (Optional) Set the maximum number of concurrent trials
|
|
# in order to prevent too many Train driver processes from
|
|
# being launched at once.
|
|
tune_config=ray.tune.TuneConfig(max_concurrent_trials=2),
|
|
)
|
|
results = tuner.fit()
|
|
|
|
print(results.get_best_result(metric="loss", mode="min"))
|
|
# __quickstart_end__
|
|
|
|
# __max_concurrent_trials_start__
|
|
# For a fixed size cluster, calculate this based on the limiting resource (ex: GPUs).
|
|
total_cluster_gpus = 8
|
|
num_gpu_workers_per_trial = 4
|
|
max_concurrent_trials = total_cluster_gpus // num_gpu_workers_per_trial
|
|
|
|
|
|
def train_driver_fn(config: dict):
|
|
trainer = ray.train.torch.TorchTrainer(
|
|
train_fn_per_worker,
|
|
scaling_config=ray.train.ScalingConfig(
|
|
num_workers=num_gpu_workers_per_trial, use_gpu=True
|
|
),
|
|
)
|
|
trainer.fit()
|
|
|
|
|
|
tuner = ray.tune.Tuner(
|
|
train_driver_fn,
|
|
tune_config=ray.tune.TuneConfig(max_concurrent_trials=max_concurrent_trials),
|
|
)
|
|
# __max_concurrent_trials_end__
|
|
|
|
# __trainable_resources_start__
|
|
# Cluster setup:
|
|
# head_node:
|
|
# resources:
|
|
# CPU: 16.0
|
|
# worker_node_cpu:
|
|
# resources:
|
|
# CPU: 32.0
|
|
# TRAIN_DRIVER_RESOURCE: 1.0
|
|
# worker_node_gpu:
|
|
# resources:
|
|
# GPU: 4.0
|
|
|
|
import ray.tune
|
|
|
|
|
|
def train_driver_fn(config):
|
|
# trainer = TorchTrainer(...)
|
|
...
|
|
|
|
|
|
tuner = ray.tune.Tuner(
|
|
ray.tune.with_resources(
|
|
train_driver_fn,
|
|
# Note: 0.01 is an arbitrary value to schedule the actor
|
|
# onto the `worker_node_cpu` node type.
|
|
{"TRAIN_DRIVER_RESOURCE": 0.01},
|
|
),
|
|
)
|
|
# __trainable_resources_end__
|
|
|
|
|
|
# __fault_tolerance_start__
|
|
import tempfile
|
|
|
|
import ray.tune
|
|
import ray.train
|
|
import ray.train.torch
|
|
|
|
|
|
def train_fn_per_worker(train_loop_config: dict):
|
|
# [1] Train worker restoration logic.
|
|
checkpoint = ray.train.get_checkpoint()
|
|
if checkpoint:
|
|
with checkpoint.as_directory() as temp_checkpoint_dir:
|
|
# model.load_state_dict(torch.load(...))
|
|
...
|
|
|
|
with tempfile.TemporaryDirectory() as temp_checkpoint_dir:
|
|
# torch.save(...)
|
|
ray.train.report(
|
|
{"loss": 0.1},
|
|
checkpoint=ray.train.Checkpoint.from_directory(temp_checkpoint_dir),
|
|
)
|
|
|
|
|
|
def train_fn_driver(config: dict):
|
|
trainer = ray.train.torch.TorchTrainer(
|
|
train_fn_per_worker,
|
|
run_config=ray.train.RunConfig(
|
|
# [2] Train driver restoration is automatic, as long as
|
|
# the (storage_path, name) remains the same across trial restarts.
|
|
# The easiest way to do this is to attach the trial ID in the name.
|
|
# **Do not include any timestamps or random values in the name.**
|
|
name=f"train-trial_id={ray.tune.get_context().get_trial_id()}",
|
|
# [3] Enable worker-level fault tolerance to gracefully handle
|
|
# Train worker failures.
|
|
failure_config=ray.train.FailureConfig(max_failures=3),
|
|
# (If multi-node, configure S3 / NFS as the storage path.)
|
|
# storage_path="s3://...",
|
|
),
|
|
)
|
|
trainer.fit()
|
|
|
|
|
|
tuner = ray.tune.Tuner(
|
|
train_fn_driver,
|
|
run_config=ray.tune.RunConfig(
|
|
# [4] Enable trial-level fault tolerance to gracefully handle
|
|
# Train driver process failures.
|
|
failure_config=ray.tune.FailureConfig(max_failures=3)
|
|
),
|
|
)
|
|
tuner.fit()
|
|
# __fault_tolerance_end__
|