运行基本 Tune 实验#

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

并行运行独立的 Tune Trial#

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

步骤 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: 接下来,定义要运行的 trial 空间。这里,我们定义了一个简单的从 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: (可选)配置分配给每个 trial 的资源。Tune 使用此资源分配来控制并行性。例如,如果每个 trial 配置为使用 4 个 CPU,而集群只有 32 个 CPU,那么 Tune 将把并发 trial 的数量限制为 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 运行 trial。Tune 将报告实验状态,实验完成后,你可以检查结果。Tune 可以自动重试失败的 trial 以及整个实验;请参阅如何定义 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).

最终结果对象包含已完成 trial 的元数据

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 进行并行 trial 执行有何不同。实际上,上面的示例可以类似地重写为

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 task 相比,Tune 提供以下附加功能

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

  • 为细粒度容错进行 trial 检查点。

  • 多 worker trial 的组调度。

简而言之,如果你需要状态追踪或支持更高级的机器学习工作负载,请考虑使用 Tune。