为何Stable Baselines 3中PPO+BipedalWalker-v3的多进程训练反而更慢?
问题背景
之前我用Stable Baselines 3的多进程示例,在num_cpu=4的配置下训练A3C+CartPole-v1时,多进程耗时约为单进程的1/3.6,性能表现很不错。但当我把算法换成PPO、环境换成BipedalWalker-v3后,发现多进程模式下的训练性能反而更差了,想搞清楚哪里操作失误,以及为什么多进程训练会变慢。
我的测试代码如下:
import gym import time from stable_baselines3 import PPO from stable_baselines3 import A2C from stable_baselines3.common.env_util import make_vec_env from stable_baselines3.common.evaluation import evaluate_policy env_name = "BipedalWalker-v3" num_cpu = 4 n_timesteps = 10000 env = make_vec_env(env_name, n_envs=num_cpu) model = PPO('MlpPolicy', env, verbose=0) start_time = time.time() model.learn(n_timesteps) total_time_multi = time.time() - start_time print(f"Took {total_time_multi:.2f}s for multiprocessed version - {n_timesteps / total_time_multi:.2f} FPS") single_process_model = PPO('MlpPolicy', env_name, verbose=0) start_time = time.time() single_process_model.learn(n_timesteps) total_time_single = time.time() - start_time print(f"Took {total_time_single:.2f}s for single process version - {n_timesteps / total_time_single:.2f} FPS") print("Multiprocessed training is {:.2f}x faster!".format(total_time_single / total_time_multi))
运行输出结果:
Took 16.39s for multiprocessed version - 610.18 FPS Took 14.19s for single process version - 704.80 FPS Multiprocessed training is 0.87x faster!
问题原因分析
这种情况其实很常见,主要是算法特性、环境复杂度、超参数匹配度这几个因素共同作用的结果:
1. 算法本质差异:PPO是同步算法,A3C是异步算法
A3C天生为多进程设计,每个worker独立收集数据并异步更新主模型参数,进程间的通信开销非常小。而PPO是同步算法,它需要收集够一整批数据(由batch_size控制)才会执行一次策略更新。当你用4个环境时,必须等所有worker都收集到足够的数据后,才能汇总进行更新——如果某个环境的step耗时更长(比如BipedalWalker的物理模拟计算量远大于CartPole),就会出现明显的等待开销,拖慢整体速度。
2. 复杂环境放大了进程通信开销
BipedalWalker-v3是连续动作空间的环境,每个step的物理模拟、状态计算量比CartPole-v1大得多。多进程模式下,每个环境运行在独立进程中,观测、动作、奖励等数据需要通过进程间通信(IPC)传递给主进程,这种数据传输的开销在复杂环境下会被显著放大。而单进程模式下没有这种通信成本,CPU可以集中处理环境计算和模型更新,自然效率更高。
3. 训练步数太少,多进程的初始化成本占比过高
你设置的n_timesteps=10000实在太小了。PPO在多进程模式下,进程初始化、环境启动这些一次性的开销,在总训练时间里占了很大比例,还没等多进程的并行优势发挥出来,训练就结束了。对比来看,单进程模式没有这些额外的初始化开销,所以显得更快。
4. CPU核心资源竞争
如果你的CPU刚好是4核,多进程模式下,4个环境进程加上主进程的模型更新计算,会抢占CPU资源,导致上下文切换频繁,反而降低了整体效率。而单进程模式下,CPU可以更高效地调度计算资源,没有进程间的竞争。
调整建议
针对这些问题,你可以尝试以下优化:
- 增加总训练步数:把
n_timesteps调到至少100000甚至1e6,这样多进程的初始化和同步开销占比会大幅降低,并行收集数据的优势才能体现出来。 - 匹配PPO的超参数:调整
n_steps参数,让n_envs * n_steps等于或接近batch_size的整数倍。比如默认batch_size=2048,你可以设置n_steps=512,这样4个环境每次收集4*512=2048步数据,刚好凑成一个完整的batch,减少数据拼接和等待的开销。 - 减少进程数量:如果你的CPU核心数不多(比如4核),试试
num_cpu=2,降低进程间的竞争和通信开销,可能反而比4进程更快。 - 优化向量环境的启动方式:在
make_vec_env中指定start_method='fork'(仅Linux/macOS支持),它比默认的spawn启动方式开销更小;或者尝试DummyVecEnv(单进程多线程),虽然它适合IO密集型环境,但对于BipedalWalker这种计算密集型环境,也可以测试下效果。
内容的提问来源于stack exchange,提问作者Danilov Vladimir

