背景
在 ICES 实习过程中,Wandb 是我常用的实验跟踪和可视化工具,而 Hydra 则是我进行配置管理的利器。我经常使用 WandB 的 Sweep 功能扫描超参数。然而,WandB Sweeper对Hydra的支持比较差,无法原生支持向Hydra的参数传递。
NOTEOmegaConf 是一个灵活的配置库,支持YAML和字典的混合语法。而Hydra 是一个用于多配置应用程序管理的框架。
当时,我希望通过 Wandb Sweeps 来动态调整 Hydra 管理的配置参数。
Hydra 本身支持通过命令行覆盖参数(例如 python train.py ++model.lr=0.01)或者追加新参数(例如 python train.py +model.new_param=true)。但我发现 WandB Sweeper 并没有提供一个直接、优雅的方式来生成这种 ++ 或 + 前缀的参数指令,以实现对 Hydra 配置的管理。
在查阅了 Hydra 的文档后,我最初的解决方案是在 sweep.yaml 中手动拼接参数,利用 ${args_no_hyphens} 并为需要覆盖的参数手动添加 ++ 前缀,如下所示:
# sweep.yaml (早期 workaround)
command:
- ${env}
- python
- ${program}
- ${args_no_hyphens}
# 手动指定需要覆盖的参数,并加上 ++
- ++experiment=contras-cat # experiment 是 Hydra 配置中的一个组
- ++trainer=gpu # trainer 也是
method: bayes
metric:
goal: maximize
name: test/F1@5
parameters:
# Sweep 需要优化的参数
++model.contrastive_weight:
distribution: uniform
max: 20
min: 0.5
program: train.py这种方法虽然可行,但显得有些“hacky”,并且容易出错,特别是当需要调整的参数变多时。我意识到这是一个普遍的需求,尤其是对于同时使用 WandB 和 Hydra 的用户。在 Issue #1856 中可以看出,社区中确有用户需要更原生的方式来处理这种情况。
我的解决方案
引入 args_append_hydra 和 args_override_hydra
基于这个痛点和社区的需求,我想着这是一个提交自己 first good commit 的好机会。我的目标是提供一种更简洁、更符合直觉的方式,让 WandB Sweeper 能够直接生成符合 Hydra 传参语法的参数指令。
经过研究,我向 Wandb 提交了 Pull Request #9657,引入了两个新的魔法变量:
${args_append_hydra}:这个变量会将sweep.yaml中parameters部分定义的所有参数,转换为 Hydra 追加参数的格式(即+key=value)。这对于在 Sweep 时探索全新的参数组合非常有用。${args_override_hydra}:这个变量会将parameters部分定义的参数转换为 Hydra 覆盖参数的格式(即++key=value)。这适用于你想要调整或优化配置文件中已存在的参数值。
这两个变量的工作方式与 Wandb 已有的 ${args}、${args_json} 等类似,都是在执行命令时,将 parameters 中的参数动态地格式化并插入到命令行中。
如何使用?
假设你的 Hydra 基础配置文件 (config.yaml) 如下:
# config.yaml
a: 1
b: 2
c: 3
model:
lr: 0.01
optimizer: adam新参数的追加
如果你想添加一个新的参数 d,可以使用 ${args_append_hydra}。
# sweep_append.yaml
command:
- ${env}
- python
- ${program}
- ${args_append_hydra} # 使用追加模式
method: grid
metric:
goal: maximize
name: validation_accuracy
parameters:
# Sweep 会追加并优化的参数
model.lr:
distribution: uniform
max: 0.005
min: 0.001
d:
distribution: uniform
max: 20
min: 0.5
program: train.pyWandb Agent 在运行时,会生成类似以下的命令:
python train.py +model.lr=0.001 +d=10.0
python train.py +model.lr=0.0032 +d=5.6
python train.py +model.lr=0.0042 +d=7.2
python train.py +model.lr=0.0039 +d=7.8最终 Hydra 加载的配置会是原始配置与追加参数的合并结果。
已有参数的覆盖
如果你只想在 Sweep 时调整 config.yaml 中已经存在的参数 c 和 model.lr,可以使用 ${args_override_hydra}。
# sweep_override.yaml
command:
- ${env}
- python
- ${program}
- ${args_override_hydra} # 使用覆盖模式
method: random
metric:
goal: minimize
name: training_loss
parameters:
# 覆盖 config.yaml 中的 c
c:
distribution: int_uniform
min: 5
max: 15
# 覆盖 config.yaml 中的 model.lr
model.lr:
distribution: uniform
min: 0.0001
max: 0.01
program: train.pyWandb Agent 会生成类似以下的命令:
python train.py ++c=8 ++model.lr=0.0054
python train.py ++c=12 ++model.lr=0.0012
# ... 等等这里的参数有 ++ 前缀,符合 Hydra 覆盖现有参数的语法。
NOTE后来发现其实这玩意也可以用来追加不存在的参数…
行为对照
为了更清晰地理解这两个新变量与原有变量的区别,可以参考下表:
| 指令 | 目标语法 | 主要作用 | 典型用例 |
|---|---|---|---|
${args} | --key=value | 传递标准命令行参数 | 适用于 argparse 等标准库 |
${args_no_hyphens} | key=value | 传递无前缀参数 | 某些特定脚本或 Hydra 覆盖模式的基础 |
${args_json} | '{"key":...}' | 传递 JSON 字符串 | 需要复杂结构化输入的脚本 |
${args_override_hydra} | ++key=value | 覆盖 Hydra 参数 | 优化 Hydra 配置中已知参数的范围 |
${args_append_hydra} | +key=value | 追加 Hydra 参数 | 在 Sweep 中探索新的 Hydra 参数组合 |
贡献历程与收获
这是我第一次向像 Wandb 这样的大型开源项目提交实质性的代码(之前只提过修改错别字的 PR 😄)。从发现问题、思考解决方案、编写代码和测试用例,到最终提交 PR (#9657) 并与维护者交流,整个过程让我受益匪浅。
虽然这个功能的实现本身并不复杂,主要是理解 Wandb Agent 如何处理参数以及 Hydra 的命令行接口,但能够将自己遇到的痛点转化为对社区有用的功能,这种感觉非常棒。
最后,特别感谢 Wandb 团队的帮助以及合并!
