简介PyMARL是基于PyTorch实现的深度多智能体强化学习研究框架面向从事多智能体协作算法研究的高校师生与工程开发者。它集成了QMIX、COMA、VDN、IQL、QTRAN等主流价值分解与策略学习算法并配套SMAC星际争霸II多智能体环境支持可用于在统一基准下比较不同算法在复杂协作任务中的表现。资源包共88个文件以32个Python源码、28个pyc编译文件、11个yaml配置、4个shell脚本及Dockerfile、README等为主整体约125KB目录涵盖learners、controllers、modules、runners、config、envs等核心模块结构清晰便于二次开发。已有159人学习下载。借助该框架读者可快速复现经典算法、搭建训练与评估流程并在此基础上扩展新算法或调整实验配置是入门与进阶多智能体强化学习研究的实用起点。1. 从 PyMARL 说起为什么星际争霸 II 是多智能体强化学习的最佳试验场如果你正在找一套能直接跑通 QMIX、COMA、VDN、IQL、QTRAN 这五个算法的 PyTorch 代码并且希望它自带 SMACStarCraft Multi-Agent Challenge环境支持那 PyMARL 这个框架大概率就是你绕不开的起点。它把星际争霸 II 里的微观操作任务抽象成标准的多智能体协作问题每个单位是一个智能体共享一个全局奖励但每个智能体只能看到局部观测。这种「局部观测 全局协作」的设定恰好是 CTDE集中训练、分散执行范式的经典落地场景。我最初接触它是因为想复现 QMIX 在 SMAC 上的基线表现结果发现网上很多代码要么环境配置写得太简略要么算法实现和原论文有出入。PyMARL 的价值在于它把五个算法的训练循环、经验回放、参数共享机制统一到了一套接口里你换算法只需要改一个注册名。这篇文章会从环境搭建讲到算法切换再到训练调参和排错把我在 Ubuntu 和 WSL 上踩过的坑一并说清楚。适合已经懂 PyTorch 基础、想切入多智能体协作方向的研究生和工程师。2. 把 PyMARL 跑起来环境、依赖与 SMAC 地图的完整配置2.1 为什么选 PyMARL 而不是自己从零搭多智能体强化学习的代码量其实不大但坑特别分散。你自己写 QMIX要处理 GRU 隐状态在 episode 边界处的重置、要保证 target network 的更新频率和论文一致、还要让每个 agent 的局部观测正确拼接成全局状态。这些细节任何一个写错训练曲线就是一条平线你还很难定位是算法问题还是实现问题。PyMARL 把这些公共部分抽成了 runner、learner、controller 三层。runner 负责和环境交互、收集 episodelearner 负责从 replay buffer 采样并计算 TD 误差controller 负责把观测转成动作。五个算法的差异只体现在 learner 里的 Q 值混合方式或策略梯度计算方式上。这种结构让你在切换 QMIX 和 VDN 时只需要改--config里的算法名其他代码一行不动。常见做法是直接用它的main.py入口配合sacred做实验管理。我一般会先把 SMAC 环境跑通确认能正常 reset 和 step再动算法部分。2.2 环境搭建Ubuntu 与 WSL 下的依赖安装星际争霸 II 的 Linux 版本是 SMAC 的硬依赖。你需要先下载暴雪官方提供的 SC2 Linux 包然后设置SC2PATH环境变量。这一步在 WSL 下也能跑但要注意 WSL2 的图形界面支持需要额外配置不过 SMAC 默认是无头模式不需要渲染所以纯命令行就能训练。# 创建 conda 环境Python 版本建议 3.8 或 3.9 conda create -n pymarl python3.9 -y conda activate pymarl # 安装 PyTorch根据你的 CUDA 版本选择这里以 CUDA 11.8 为例 pip install torch2.0.1 torchvision0.15.2 torchaudio2.0.2 --index-url https://download.pytorch.org/whl/cu118 # 安装 PyMARL 的核心依赖 pip install sacred numpy scipy tensorboardx pyyaml # 下载 SC2 Linux 包并解压假设放到 ~/StarCraftII export SC2PATH~/StarCraftII这段命令的逻辑是先隔离 Python 环境避免和系统里的其他 PyTorch 版本冲突然后装带 CUDA 支持的 PyTorch因为 SMAC 的观测维度不高但 QMIX 的 GRU 和混合网络在 GPU 上训练会快很多最后设置SC2PATHSMAC 启动时会去这个路径找SC2_x64可执行文件。参数说明SC2PATH必须指向包含Versions和Maps文件夹的根目录。如果你下载的是SC2.4.10.zip解压后目录结构应该是StarCraftII/Versions/Base75689/SC2_x64。SMAC 的地图文件需要额外放到StarCraftII/Maps/SMAC_Maps下否则会报Map not found。注意WSL 下如果遇到GLIBC版本问题建议用 Ubuntu 20.04 或 22.04 的镜像18.04 的 glibc 太旧SC2 跑不起来。2.3 SMAC 地图加载与最小验证脚本环境配好后别急着跑训练。先写一个最小脚本验证 SMAC 能不能正常交互。这一步能帮你排除掉 80% 的环境问题。from smac.env import StarCraft2Env import numpy as np # 选择一张简单的地图2s3z 表示 2 个 stalker 对 3 个 zealot env StarCraft2Env(map_name2s3z) env_info env.get_env_info() n_actions env_info[n_actions] n_agents env_info[n_agents] obs_shape env_info[obs_shape] state_shape env_info[state_shape] print(f动作数: {n_actions}, 智能体数: {n_agents}) print(f局部观测维度: {obs_shape}, 全局状态维度: {state_shape}) # 跑一个 episode随机策略 env.reset() terminated False episode_reward 0 while not terminated: actions [np.random.randint(n_actions) for _ in range(n_agents)] reward, terminated, _ env.step(actions) episode_reward reward print(f随机策略总奖励: {episode_reward}) env.close()逻辑说明get_env_info()返回的n_agents是当前地图的智能体数量obs_shape是每个智能体的局部观测维度state_shape是全局状态维度。QMIX 和 VDN 需要全局状态来训练混合网络COMA 需要全局状态计算反事实基线IQL 只用局部观测。所以先确认这些维度后面配网络结构时不会抓瞎。参数说明map_name可以换成3m、8m、2s_vs_1sc等。2s3z和3m适合快速验证8m和MMM2适合看算法上限。随机策略的奖励通常在 -20 到 20 之间波动如果一直是 -20 且 episode 长度固定说明 SC2 可能没正常加载检查SC2PATH和地图文件。3. 五个算法的切换逻辑QMIX、COMA、VDN、IQL、QTRAN 到底改哪里3.1 算法注册机制与配置文件结构PyMARL 用sacred做配置管理算法通过--config参数指定。每个算法对应src/config/algs/下的一个 yaml 文件。比如qmix.yaml里定义了mixer类型、hypernet_layers层数、lr学习率等。你运行python src/main.py --configqmix --env-configsc2 with env_args.map_name2s3z时sacred 会把这两个 yaml 合并成完整的实验配置。五个算法的核心差异在src/learners/目录下。q_learner.py是 QMIX、VDN、IQL、QTRAN 共用的基类coma_learner.py是 COMA 单独的实现。QMIX 和 VDN 的区别在src/modules/mixers/里qmix.py用超网络生成混合权重vdn.py直接求和。QTRAN 在qtran.py里实现了它的分解约束。我一般会先看q_learner.py的train方法理解 TD 误差怎么算再看 mixer 怎么把每个 agent 的 Q 值合成全局 Q。这样切换算法时你知道哪些参数需要跟着调。3.2 QMIX 与 VDN 的混合网络参数怎么调QMIX 的混合网络是它的核心。它用一个超网络根据全局状态生成每个 agent Q 值的权重并且强制权重非负保证单调性约束。VDN 就是简单求和没有超网络。在 SMAC 的2s3z上QMIX 通常比 VDN 收敛更快但在8m这种同质智能体地图上两者差距不大。# qmix.yaml 关键参数 mixer: qmix hypernet_layers: 2 hypernet_embed: 64 lr: 0.0005 optim_alpha: 0.99 optim_eps: 0.00001hypernet_layers控制超网络的层数2 层在大多数 SMAC 地图上够用。hypernet_embed是超网络的隐层维度64 是原论文的设定调到 128 可能过拟合。lr是学习率QMIX 对学习率比较敏感0.0005 是原论文的值如果你发现训练初期 Q 值爆炸先降到 0.0001 试试。VDN 的配置更简单把mixer改成vdn就行没有超网络参数。但 VDN 的lr可以稍微大一点0.001 也能跑。3.3 COMA 的反事实基线实现细节COMA 是 actor-critic 结构和 QMIX 那套 value-based 方法完全不同。它的核心是反事实基线对每个 agent固定其他 agent 的动作计算当前动作的优势。这个计算在coma_learner.py里通过counterfactual_baseline实现。# coma_learner.py 中的关键片段 def _compute_counterfactual_baseline(self, batch, agent_id): # 复制当前 batch把 agent_id 的动作替换成所有可能动作 # 然后计算每个动作的 Q 值加权求和得到基线 ...逻辑说明COMA 需要为每个 agent 单独计算基线所以训练速度比 QMIX 慢很多。在2s3z上COMA 的每步训练时间大约是 QMIX 的 3 到 5 倍。如果你的 GPU 显存不够先把batch_size从 32 降到 16。参数说明COMA 的critic_lr和actor_lr通常设成一样的0.0005。entropy_coef控制探索0.01 是常用值调大到 0.05 会增加探索但可能不收敛。3.4 IQL 与 QTRAN 的适用边界IQL 是最简单的独立 Q 学习每个 agent 单独学自己的 Q 函数不考虑协作。它在 SMAC 上表现通常最差但训练最快适合做 baseline。QTRAN 试图在保证个体 Q 和全局 Q 一致性的同时放松 QMIX 的单调性约束。但 QTRAN 在 SMAC 上的实际表现不稳定有些地图上不如 QMIX有些地图上略好。我的经验是如果你要做新算法对比QMIX 和 VDN 是必跑的 baselineCOMA 用来展示 actor-critic 的效果IQL 用来展示不协作的下限QTRAN 可选。在MMM2这种复杂地图上QTRAN 的调参成本很高不建议新手一上来就碰。4. 训练调参与排错从 loss 曲线看 QMIX 到底有没有在学4.1 关键超参数与默认值对照PyMARL 的默认参数在src/config/default.yaml里但不同算法需要覆盖。下面这张表是我在2s3z和8m上验证过的常用配置。参数QMIXVDNCOMAIQL说明lr0.00050.0010.00050.0005学习率batch_size32321632采样批次buffer_size5000500050005000经验回放容量target_update_interval200200200200目标网络更新步数epsilon_start1.01.0-1.0探索起始值epsilon_finish0.050.05-0.05探索终止值epsilon_anneal_time5000050000-50000探索衰减步数target_update_interval对 QMIX 影响很大。设成 200 是原论文的值如果你发现训练不稳定可以降到 100。epsilon_anneal_time控制探索衰减速度50000 步在2s3z上大约对应 200 个 episode如果地图更难可以加到 100000。4.2 用 TensorBoard 看三个核心指标PyMARL 默认把日志写到results/下用 TensorBoard 打开。我重点看三个曲线test_return_mean、td_loss、grad_norm。test_return_mean是测试集的平均回报这是最终指标。如果它一直不涨先看td_loss。QMIX 的 TD loss 在训练初期会快速下降然后在一个区间震荡。如果 loss 一直不降可能是学习率太大或者 target network 更新太慢。grad_norm是梯度范数。如果它突然变得很大说明梯度爆炸需要加梯度裁剪。PyMARL 默认在q_learner.py里做了clip_grad_norm_(self.params, 10)但有些地图上 10 还是太大可以改成 5。注意test_return_mean在2s3z上QMIX 通常 200 个 episode 内能到 18 以上VDN 稍慢COMA 需要 500 个 episode 左右。如果你的曲线在 0 附近震荡超过 300 个 episode大概率是环境配置或网络结构有问题。4.3 常见报错与排查路径报错一ModuleNotFoundError: No module named smac原因SMAC 没有正确安装。PyMARL 依赖的是smac包不是pysc2。 解决pip install githttps://github.com/oxwhirl/smac.git注意要装到当前 conda 环境里。报错二SC2PATH is not set原因环境变量没导出或者导出后没生效。 解决export SC2PATH/path/to/StarCraftII然后echo $SC2PATH确认。WSL 下要写进.bashrc并source。报错三Map not found: 2s3z原因SMAC 地图文件没放到正确位置。 解决确认StarCraftII/Maps/SMAC_Maps/2s3z.SC2Map存在。如果下载的 SMAC 包里没有地图去 GitHub 的smac/maps目录下拷贝。报错四训练 loss 为 NaN原因学习率太大或者 Q 值溢出。 解决把lr降到 0.0001加grad_norm裁剪到 5检查hypernet_embed是不是设得太大。报错五GPU 显存不足原因batch_size太大或者 COMA 的反事实计算占用显存。 解决batch_size降到 16COMA 可以降到 8。如果还不够把hypernet_embed从 64 降到 32。5. 从单地图到多地图迁移训练与算法对比的实验设计5.1 用同一套超参跑通三张地图很多人只在2s3z上跑通就结束了但 SMAC 的价值在于不同地图对算法的偏好不同。我一般会选2s3z、8m、MMM2三张地图做对比。2s3z是异质智能体8m是同质智能体MMM2是混合且需要长程协作。# 跑 QMIX 在 2s3z 上 python src/main.py --configqmix --env-configsc2 with env_args.map_name2s3z # 跑 QMIX 在 8m 上只改 map_name python src/main.py --configqmix --env-configsc2 with env_args.map_name8m # 跑 VDN 在 MMM2 上 python src/main.py --configvdn --env-configsc2 with env_args.map_nameMMM2逻辑说明--config指定算法--env-config指定环境with后面跟的是覆盖参数。sacred 会自动把map_name注入到env_args里。你不需要改任何代码只需要换命令行参数。参数说明MMM2的 episode 长度比2s3z长很多buffer_size建议加到 10000epsilon_anneal_time加到 100000。8m的智能体数量是 8batch_size可以保持 32但target_update_interval建议降到 100。5.2 算法对比的公平性控制做算法对比时最容易犯的错误是给不同算法调不同的超参然后得出「QMIX 比 VDN 好」的结论。公平的做法是固定batch_size、buffer_size、epsilon_anneal_time只调学习率和网络结构相关的参数。我一般会跑 3 个 seed取均值和标准差。PyMARL 支持--seed参数但要注意sacred的 seed 设置和 PyTorch 的 seed 要同步。在main.py里加torch.manual_seed(args.seed)和np.random.seed(args.seed)。注意SMAC 的环境本身有随机性即使 seed 相同两次运行的结果也可能有细微差异。所以对比时至少跑 3 个 seed看置信区间有没有重叠。5.3 从训练曲线判断算法是否「真的学到了」QMIX 在2s3z上的典型曲线是前 50 个 episode 回报在 -10 到 0 之间然后快速上升到 15 左右最后在 18 到 20 之间震荡。如果曲线在 10 左右就平了可能是epsilon_finish设得太高探索不够。VDN 的曲线更平滑但上限通常比 QMIX 低 1 到 2 个点。COMA 的曲线波动更大因为策略梯度的方差高。IQL 的曲线经常在 5 到 10 之间震荡很难突破。如果你发现 QMIX 的test_return_mean在 15 左右就上不去了先检查hypernet_layers是不是 2hypernet_embed是不是 64。这两个参数对 QMIX 的上限影响很大。另外target_update_interval设成 200 是原论文的值但在2s3z上100 可能更好。6. 一个容易被忽略的技巧用 GRU 隐状态重置解决 episode 边界问题QMIX 和 VDN 的 agent 网络通常用 GRU 处理局部观测序列。但 GRU 的隐状态在 episode 结束时必须重置否则上一个 episode 的信息会泄漏到下一个 episode。PyMARL 在episode_buffer.py里通过mask机制处理这个问题但如果你自己改网络结构很容易漏掉。具体做法是在forward里接收hidden_state和maskmask 为 0 的位置把隐状态置零。PyMARL 的RNNAgent已经实现了这个逻辑但如果你换成 LSTM 或者自定义 RNN要确保 mask 正确传递。class RNNAgent(nn.Module): def __init__(self, input_shape, args): super(RNNAgent, self).__init__() self.args args self.fc1 nn.Linear(input_shape, args.rnn_hidden_dim) self.rnn nn.GRUCell(args.rnn_hidden_dim, args.rnn_hidden_dim) self.fc2 nn.Linear(args.rnn_hidden_dim, args.n_actions) def forward(self, inputs, hidden_state): x F.relu(self.fc1(inputs)) h_in hidden_state.reshape(-1, self.args.rnn_hidden_dim) h self.rnn(x, h_in) q self.fc2(h) return q, h逻辑说明hidden_state的 shape 是(batch_size * n_agents, rnn_hidden_dim)。在 episode 边界episode_buffer会把mask为 0 的样本的hidden_state置零然后传给forward。这样 GRU 就不会跨 episode 记忆。参数说明rnn_hidden_dim默认是 64在2s3z上够用。如果地图更复杂比如MMM2可以加到 128。但要注意加大会增加显存占用和训练时间。我自己的习惯是每次改完网络结构先跑一个2s3z的短实验看test_return_mean能不能在 100 个 episode 内到 15。如果到不了先检查 GRU 的 mask 有没有正确传递。这个坑我踩过两次一次是忘了传 mask一次是 mask 的 shape 不对结果训练曲线完全平。希望帮到你。本文还有配套的精品资源点击获取