Ray Tune 并行度与资源分配完全指南:从 per-trial 资源到并发控制

发布时间:2026/9/20 11:50:25
Ray Tune 并行度与资源分配完全指南:从 per-trial 资源到并发控制 人工智能分布式训练强化学习任务调度模型推理服务【免费下载链接】rayRay is an AI compute engine. Ray consists of a core distributed runtime and a set of AI Libraries for accelerating ML workloads.项目地址https://gitcode.com/gh_mirrors/ra/ray点击查看免费下载本文基于 Ray 开源仓库中的 tune-resources.rst 教程系统讲解 Ray Tune 中如何控制实验并行度与资源分配包括每个 Trial试验的 CPU/GPU 资源请求、tune.with_resources的三种写法、PlacementGroupFactory 的底层机制、GPU 场景下的CUDA_VISIBLE_DEVICES处理、以及与 Ray Train 结合做分布式训练时的并发限制。读完本文你将能精确计算集群能同时跑多少个 Trial并避免资源设置不当导致的死锁与挂起。一、理解 Tune 的并行模型per-trial 资源 × 集群资源Ray Tune 的并行度Parallelism由两个因素共同决定每个 Trial 请求的资源默认值为{cpu: 1, gpu: 0}即每个 Trial 默认占用 1 个 CPU、0 个 GPUTune 可用的集群总资源可通过ray.cluster_resources()查询。因此在默认配置下Tune 会自动并行运行N个 Trial其中N等于你机器上的 CPU核心数量。例如一台 4 核机器上运行 10 次采样Tune 会同时启动 4 个 Trial# 如果你的机器有 4 个 CPU下面的代码会同时运行 4 个 Trial tuner tune.Tuner( trainable, tune_configtune.TuneConfig(num_samples10) ) results tuner.fit()这个默认行为意味着并行度的本质是每个 Trial 的资源需求与集群资源总量之间的除法。想提升并行度要么减小每个 Trial 的资源请求要么扩展集群资源。二、用tune.with_resources覆盖每个 Trial 的资源请求要覆盖默认的 per-trial 资源可以使用tune.with_resources包装 trainable。它支持两种资源描述形式字典dict如{cpu: 2}PlacementGroupFactory对象用于更复杂的资源布局详见第三节。无论使用哪种形式Ray Tune 都会为每个 Trial 尝试启动一个Placement Group放置组并根据 Placement Group 是否成功创建来决定能否调度该 Trial。2.1 通过字典指定 CPU 数量# 如果你的机器有 4 个 CPU下面的代码会同时运行 2 个 Trial trainable_with_resources tune.with_resources(trainable, {cpu: 2}) tuner tune.Tuner( trainable_with_resources, tune_configtune.TuneConfig(num_samples10) ) results tuner.fit() # 如果你的机器有 4 个 CPU下面的代码会同时运行 1 个 Trial trainable_with_resources tune.with_resources(trainable, {cpu: 4}) tuner tune.Tuner( trainable_with_resources, tune_configtune.TuneConfig(num_samples10) ) results tuner.fit()2.2 小数资源让并行度超过 CPU 数量资源请求支持小数分数值例如{cpu: 0.5}。当每个 Trial 只请求 0.5 个 CPU 时4 核机器可以同时运行 8 个 Trial——这非常适合轻量级、以 I/O 或等待为主的 trainable# 小数资源同样被支持例如 {cpu: 0.5} # 如果你的机器有 4 个 CPU下面的代码会同时运行 8 个 Trial trainable_with_resources tune.with_resources(trainable, {cpu: 0.5}) tuner tune.Tuner( trainable_with_resources, tune_configtune.TuneConfig(num_samples10) ) results tuner.fit()2.3 用 lambda 动态决定资源资源请求也可以是一个接收 config 字典、返回资源请求的 lambda 函数。这样可以根据每次采样到的超参配置动态决定资源分配例如只有当config[use_gpu]为 True 时才请求 GPU# 通过 lambda 函数实现自定义资源分配 # 例如根据 config 中的设置决定是否为 Trial 分配 GPU trainable_with_resources tune.with_resources(trainable, resourceslambda config: {gpu: 1} if config[use_gpu] else {gpu: 0}) tuner tune.Tuner( trainable_with_resources, tune_configtune.TuneConfig(num_samples10) ) results tuner.fit()2.4 内存与自定义资源除 CPU/GPU 外资源字典还可以指定memory以字节为单位的内存需求自定义资源custom resources任意名称的资源类型用于匹配集群中通过resources配置声明的自定义资源例如 GPU 节点上标记的TRAIN_DRIVER_RESOURCE。2.5 源码级原理with_resources到底做了什么从源码看tune.with_resources的实现位于 python/ray/tune/trainable/util.py#L150-L250其核心逻辑如下只接受函数 trainable或继承自tune.Trainable的类否则抛出ValueError将字典通过resource_dict_to_pg_factory转换成PlacementGroupFactorylambda 函数则原样保留对函数 trainable直接在其上挂载_resources属性方法对象会被包一层普通函数后再挂载对类 trainable动态生成一个子类ResourceTrainable并覆写default_resource_request类方法返回对应的 Placement Group 工厂。而 resource_dict_to_pg_factory 会做字段归一化把cpu/CPU、gpu/GPU、memory从字典中拆出其余键视为自定义资源最终构造一个只含单个 bundle 的PlacementGroupFactory([bundle])。这也解释了为什么每个 Trial 本质上对应一个 Placement Group。注意字典形式的资源最终会被转换成单 bundle的 Placement Group。如果你的训练逻辑需要多个 bundle例如 driver 与 worker 分离部署就必须直接使用PlacementGroupFactory。三、PlacementGroupFactory为分布式/多 Worker 训练请求资源当你的 trainable 会启动更多远端 Worker例如使用 Ray 原生的多 Actor 编程或使用 Modin 这类基于 Ray 的库时单个 bundle 的字典形式无法表达一组资源的布局此时必须传入PlacementGroupFactory对象来请求这些资源例如from ray.tune.execution.placement_groups import PlacementGroupFactory # 一个 bundle 给 driver两个 bundle 给 worker placement_group_factory PlacementGroupFactory( [ {CPU: 1, GPU: 0}, # driver bundle {CPU: 2, GPU: 1}, # worker bundle 1 {CPU: 2, GPU: 1}, # worker bundle 2 ] ) trainable_with_resources tune.with_resources(trainable, placement_group_factory)PlacementGroupFactory定义在 python/ray/tune/execution/placement_groups.py#L9-L10本质上是ResourceRequest的封装持有创建 Placement Group 所需的全部参数。详细信息可查阅该类文档。重要警告如果你使用 Modin 等基于 Ray 的其他库而 trainable 又启动了远端 Worker资源设置不当可能导致死锁使整个集群挂起。原因是 Tune 只会为 trainable 的主进程请求资源远端 Worker 的资源如果没有通过 PlacementGroupFactory 显式声明就可能互相等待、谁也调度不出来。使用边界务必牢记通过tune.with_resources指定的资源只用于 Tune 调度 Trial不会自动在目标函数trainable内部强制执行。你必须自己确保 trainable 有足够资源运行——例如为 scikit-learn 模型相应设置n_jobs否则可能出现调度上去了、实际跑不动或资源超卖的情况。四、GPU 使用指南CUDA_VISIBLE_DEVICES与wait_for_gpu4.1 如何让 Trial 用上 GPU要在 Tune 中利用 GPU必须在tune.with_resources(trainable, resources_per_trial)中设置gpu键。设置后Ray Tune 会自动为每个 Trial 设置CUDA_VISIBLE_DEVICES环境变量将可见 GPU 限定为分配给该 Trial 的卡# 如果你有 8 块 GPU下面的代码会同时运行 8 个 Trial trainable_with_gpu tune.with_resources(trainable, {gpu: 1}) tuner tune.Tuner( trainable_with_gpu, tune_configtune.TuneConfig(num_samples10) ) results tuner.fit() # 如果你的机器有 4 个 CPU 和 1 块 GPU下面的代码会同时运行 1 个 Trial trainable_with_cpu_gpu tune.with_resources(trainable, {cpu: 2, gpu: 1}) tuner tune.Tuner( trainable_with_cpu_gpu, tune_configtune.TuneConfig(num_samples10) ) results tuner.fit()第二个例子很好地体现了资源除法4 个 CPU 除以每个 Trial 的 2 CPU 2 个并发槽位但 1 块 GPU 除以每个 Trial 的 1 GPU 1 个并发槽位受限于稀缺资源 GPU最终只能同时跑 1 个 Trial。仓库中的 tune_mnist_keras.py 是一个完整可运行的真实示例它用tune.with_resources(train_mnist, resources{cpu: 2, gpu: 0})包装 Keras MNIST 训练函数配合AsyncHyperBandScheduler与num_samples10做超参搜索。警告如果没有设置gpuTune 会把CUDA_VISIBLE_DEVICES环境变量设置为空字符串从而禁止该 Trial 访问任何 GPU。因此凡是可能在 GPU 机器上运行的 trainable都应显式声明 GPU 需求哪怕声明为 0 并做好降级处理否则训练会静默地退化为纯 CPU 运行甚至直接报错。4.2 故障排查tune.utils.wait_for_gpu实践中偶尔会遇到这样的问题新 Trial 启动时报GPU 显存不足OOM原因往往是上一个 Trial 没有足够快地清理 GPU 状态。为此 Tune 提供了tune.utils.wait_for_gpu工具函数用于在训练开始前阻塞等待指定 GPU 的显存释放。其实现位于 python/ray/tune/utils/util.py#L456-L555核心参数包括参数默认值说明gpu_idNone要检查的 GPU id 或 uuid为None时取ray.get_gpu_ids()返回的第一个 GPUtarget_util0.01显存利用率阈值低于该值视为已释放设为0表示阻塞到 GPU 完全空闲retry20最多检查次数每次间隔delay_s秒delay_s5两次检查之间的等待秒数典型用法是在 trainable 开头先等待 GPU 释放再开始训练import ray from ray import tune def tune_func(config): tune.utils.wait_for_gpu() # 等待 GPU 显存释放 train() # 再开始真正的训练 tuner tune.Tuner( tune.with_resources(tune_func, resources{gpu: 1}), tune_configtune.TuneConfig(num_samples10) ) tuner.fit()注意wait_for_gpu依赖gputil第三方库使用前需pip install gputil若未检测到 GPU 或 GPU id 不在 GPUtil 列表中会抛出RuntimeError/ValueError。五、在 Tune 中运行分布式训练Tune × Ray Train要调优分布式训练任务可以把 Ray Tune 与 Ray Train 结合Ray Tune 负责并行运行多个 Trial每个 Trial 内部通过 Ray Train 启动一轮分布式训练例如多个 GPU Worker 的 DDP 训练。核心要点是Tune 的每个 Trial 只是driver 进程真正做计算的是 Ray Train 启动的WorkerRay Train 通过自己的ScalingConfig如num_workers、use_gpu请求资源而不是由 Tune 的 per-trial 资源决定因此 Ray Train 的 Worker 资源需求不计入Tune 的调度计算如果 GPU 等资源紧张多个 Trial 的 Train 运行会互相竞争。完整的 Tune 调优 Ray Train 指南见 hyperparameter-optimization.rst可运行的完整示例代码见 train_tune_interop.py。其典型结构为def train_driver_fn(config: dict): trainer ray.train.torch.TorchTrainer( train_fn_per_worker, train_loop_configconfig[train_loop_config], scaling_configray.train.ScalingConfig( num_workersconfig[num_workers], # use_gpuTrue, ), ) trainer.fit() 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]), }, }, tune_configray.tune.TuneConfig(max_concurrent_trials2), ) results tuner.fit()一个值得注意的进阶技巧是Ray Train 的 driver 进程默认作为 1 个 CPU 的 Tune 函数运行可能被调度到易被抢占的节点如 Spot 实例上。此时可通过自定义资源把 driver 钉到安全节点——例如在 CPU Worker 节点上声明TRAIN_DRIVER_RESOURCE: 1.0再用with_resources请求它示例见 train_tune_interop.py#L120-L149。六、限制并发TuneConfig(max_concurrent_trials...)Tune 提供了max_concurrent_trials参数用于硬性限制同时运行的 Trial 数量上限定义在 TuneConfig 中from ray.tune import TuneConfig config TuneConfig( # ... num_samples100, max_concurrent_trials10, )关于该参数需要注意以下几点取值为None或0时表示不限制必须是非负数它的实现方式是给search_alg包一层ConcurrencyLimiter因此如果search_alg本身已经是ConcurrencyLimiter同时设置max_concurrent_trials会抛出异常实际并行度可能小于max_concurrent_trials真正决定并发数的是集群一次能容纳多少个 Trial。例如每个 Trial 需要 16 块 GPU、集群共有 32 块 GPU即使设置max_concurrent_trials10Tuner也只能同时运行 2 个 Trial。6.1 结合 Ray Train 时为什么要设置它在与 Ray Train 联用时max_concurrent_trials尤其关键。因为 Ray Train 运行只有在其全部 Worker 资源能同时满足时才会启动而 Tune 的 Trial 进程默认只占 1 CPU却可以立即启动于是会出现Trial 进程已经跑起来、内部 Train 却一直在等资源的尴尬局面——产生大量刷屏日志且 Trial 进程数量可能失控。一个实用的计算方法是基于限制性资源通常是 GPU反推并发上限见 train_tune_interop.py#L97-L118# 对于固定大小的集群根据限制性资源例如 GPU计算并发数 total_cluster_gpus 8 num_gpu_workers_per_trial 4 max_concurrent_trials total_cluster_gpus // num_gpu_workers_per_trial例如 128 CPU 8 GPU 的集群中每个 Trial 的 Train 需要 4 个 GPU Worker则最多只有 2 个 Train 运行能同时获得全部资源。此时设置max_concurrent_trials2就能避免多余的 Trial 进程空转等待。七、常见陷阱与最佳实践小结场景推荐做法依据默认行为不设置资源时每个 Trial 占 1 CPU并发数 CPU 数tune-resources.rst轻量 trainable使用{cpu: 0.5}等小数资源提升并行度原教程需要 GPU显式设置{gpu: 1}否则CUDA_VISIBLE_DEVICES为空、无法访问 GPU原教程 源码行为新 Trial GPU OOM训练前调用tune.utils.wait_for_gpu()python/ray/tune/utils/util.pytrainable 内部启动远端 Worker使用PlacementGroupFactory声明全部 bundle否则可能死锁挂起集群原教程与 Modin 等 Ray 库联用同样需要显式声明资源原教程资源不生效的错觉记住资源只用于调度不强制限制 trainable 内部行为如n_jobs需自行设置原教程 note多 Trial 抢 GPU用TuneConfig(max_concurrent_trialsN)限制并发N 由限制性资源除法得出TuneConfig 源码集群资源不足Tune 会持续尝试创建 Placement Group若使用 Ray Cluster Launcher 会触发自动伸缩autoscaling原教程最后的核心心法Tune 的并行度 集群可用资源 ÷ 每个 Trial 的资源请求。任何关于为什么 Trial 数不对的疑问都可以通过ray.cluster_resources()查看实际资源、用tune.with_resources校准 per-trial 请求、再用max_concurrent_trials设置安全上限这三步来定位和解决。赞分享人工智能分布式训练强化学习任务调度模型推理服务【免费下载链接】rayRay is an AI compute engine. Ray consists of a core distributed runtime and a set of AI Libraries for accelerating ML workloads.项目地址https://gitcode.com/gh_mirrors/ra/ray点击查看免费下载相关推荐突破资源瓶颈Prefect并发控制与资源配额完全指南突破资源瓶颈Prefect并发控制与资源配额完全指南 在数据处理和自动化任务中你是否经常遇到数据库连接耗尽、API请求被限流或服务器资源被过度占用的问题作工作流自动化流程编排任务调度数据工程后端GitLab CI/CD并行构建终极指南作业并发与资源分配控制GitLab CI/CD并行构建终极指南作业并发与资源分配控制 GitLab CI/CD作为现代软件开发的核心工具其强大的并行构建能力让团队能够显著提升交付运维云原生Ray Serve 资源分配指南为副本配置 CPU、GPU、加速器与并行度Ray Serve 资源分配指南为副本配置 CPU、GPU、加速器与并行度 本文基于 Ray 开源仓库当前仓库根目录 resource allocation人工智能分布式训练强化学习任务调度模型推理服务创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考