运行基础的 Tune 实验#

使用 Tune 最常见也是最简单的方式是将其作为并行实验运行器。如果你能用 Python 函数定义实验,你就可以使用 Tune 在集群中运行成百上千个独立的实验实例。Tune 会管理实验的执行、状态报告和容错。

并行运行独立的 Tune 实验#

作为一个通用示例,我们考虑使用 Tune 作为简单的网格搜索来执行 N 个独立的模型训练实验。每个实验可以根据传入的配置字典执行不同的代码。

步骤 1: 首先,我们定义我们想要运行变体的模型训练函数。该函数接受一个配置字典作为参数,并返回一个简单的字典输出。关于记录 Tune 结果的更多信息,请参阅 如何配置 Tune 中的日志记录?

from ray import tune
import ray
import os

NUM_MODELS = 100

def train_model(config):
    score = config["model_id"]

    # Import model libraries, etc...
    # Load data and train model code here...

    # Return final stats. You can also return intermediate progress
    # using ray.tune.report() if needed.
    # To return your model, you could write it to storage and return its
    # URI in this dict, or return it as a Tune Checkpoint:
    # https://docs.rayai.org.cn/en/latest/tune/tutorials/tune-checkpoints.html
    return {"score": score, "other_data": ...}

步骤 2: 接下来,定义要运行的实验空间。这里,我们定义了一个从 0..NUM_MODELS 的简单网格搜索,它将生成要传递给每个模型函数的配置字典。关于 Tune 在定义空间方面提供的功能,请参阅 使用 Tune 搜索空间

# Define trial parameters as a single grid sweep.
trial_space = {
    # This is an example parameter. You could replace it with filesystem paths,
    # model types, or even full nested Python dicts of model configurations, etc.,
    # that enumerate the set of trials to run.
    "model_id": tune.grid_search([
        "model_{}".format(i)
        for i in range(NUM_MODELS)
    ])
}

步骤 3: 可选地,配置每个实验分配的资源。Tune 使用此资源分配来控制并行性。例如,如果每个实验都配置为使用 4 个 CPU,而集群只有 32 个 CPU,那么 Tune 将把并发实验的数量限制为 8,以避免使集群过载。有关更多信息,请参阅 Ray Tune 并行性和资源指南

# Can customize resources per trial, here we set 1 CPU each.
train_model = tune.with_resources(train_model, {"cpu": 1})

步骤 4: 使用 Tune 运行实验。Tune 会报告实验状态,实验完成后,你可以检查结果。Tune 可以自动重试失败的实验,以及整个实验;请参阅 如何为 Ray Tune 实验定义停止条件

# Start a Tune run and print the best result.
tuner = tune.Tuner(train_model, param_space=trial_space)
results = tuner.fit()

# Access individual results.
print(results[0])
print(results[1])
print(results[2])

步骤 5: 检查结果。它们看起来会是这样的。Tune 会定期向标准输出打印一个状态摘要,显示正在进行的实验状态,直到它完成。

== Status ==
Current time: 2022-09-21 10:19:34 (running for 00:00:04.54)
Memory usage on this node: 6.9/31.1 GiB
Using FIFO scheduling algorithm.
Resources requested: 0/8 CPUs, 0/0 GPUs, 0.0/16.13 GiB heap, 0.0/8.06 GiB objects
Result logdir: /home/ubuntu/ray_results/train_model_2022-09-21_10-19-26
Number of trials: 100/100 (100 TERMINATED)
+-------------------------+------------+----------------------+------------+--------+------------------+
| Trial name              | status     | loc                  | model_id   |   iter |   total time (s) |
|-------------------------+------------+----------------------+------------+--------+------------------|
| train_model_8d627_00000 | TERMINATED | 192.168.1.67:2381731 | model_0    |      1 |      8.46386e-05 |
| train_model_8d627_00001 | TERMINATED | 192.168.1.67:2381761 | model_1    |      1 |      0.000126362 |
| train_model_8d627_00002 | TERMINATED | 192.168.1.67:2381763 | model_2    |      1 |      0.000112772 |
...
| train_model_8d627_00097 | TERMINATED | 192.168.1.67:2381731 | model_97   |      1 |      5.57899e-05 |
| train_model_8d627_00098 | TERMINATED | 192.168.1.67:2381767 | model_98   |      1 |      6.05583e-05 |
| train_model_8d627_00099 | TERMINATED | 192.168.1.67:2381763 | model_99   |      1 |      6.69956e-05 |
+-------------------------+------------+----------------------+------------+--------+------------------+

2022-09-21 10:19:35,159     INFO tune.py:762 -- Total run time: 5.06 seconds (4.46 seconds for the tuning loop).

最终的结果对象包含已完成的实验元数据。

Result(metrics={'score': 'model_0', 'other_data': Ellipsis, 'done': True, 'trial_id': '8d627_00000', 'experiment_tag': '0_model_id=model_0'}, error=None, log_dir=PosixPath('/home/ubuntu/ray_results/train_model_2022-09-21_10-19-26/train_model_8d627_00000_0_model_id=model_0_2022-09-21_10-19-30'))
Result(metrics={'score': 'model_1', 'other_data': Ellipsis, 'done': True, 'trial_id': '8d627_00001', 'experiment_tag': '1_model_id=model_1'}, error=None, log_dir=PosixPath('/home/ubuntu/ray_results/train_model_2022-09-21_10-19-26/train_model_8d627_00001_1_model_id=model_1_2022-09-21_10-19-31'))
Result(metrics={'score': 'model_2', 'other_data': Ellipsis, 'done': True, 'trial_id': '8d627_00002', 'experiment_tag': '2_model_id=model_2'}, error=None, log_dir=PosixPath('/home/ubuntu/ray_results/train_model_2022-09-21_10-19-26/train_model_8d627_00002_2_model_id=model_2_2022-09-21_10-19-31'))

Tune 与使用 Ray Core(ray.remote)相比如何?#

你可能想知道 Tune 与仅仅使用 Tasks 来并行执行实验有什么区别。事实上,上面的示例可以类似地重写为

remote_train = ray.remote(train_model)
futures = [remote_train.remote({"model_id": i}) for i in range(NUM_MODELS)]
print("Submitting tasks...")
results = ray.get(futures)
print("Trial results", results)

与使用 Ray Tasks 相比,Tune 提供了以下附加功能:

  • 状态报告和跟踪,包括与常用监控工具的集成和回调。

  • 实验的检查点,用于细粒度的容错。

  • 多工作节点实验的联合调度。

总之,如果你需要状态跟踪或对更高级的 ML 工作负载的支持,请考虑使用 Tune。