Hydra 输出与工作目录完全指南:从hydra.job.chdir到hydra.output_subdir
【免费下载链接】hydraHydra is a framework for elegantly configuring complex applications项目地址: https://gitcode.com/GitHub_Trending/hyd/hydra
导读
本篇技术指南聚焦 Hydra 框架的"输出/工作目录"机制:为什么每次运行都需要一个新的输出目录、输出目录里到底存放了什么、如何让 Python 应用自动切换到该目录,以及如何在chdir开启后仍能访问原始工作目录。文章以官方教程 website/docs/tutorials/basic/running_your_app/3_working_directory.md 为核心骨架,并结合仓库中hydra/core/utils.py、hydra/conf/hydra/output/default.yaml、示例应用 examples/tutorials/basic/running_your_hydra_app/3_working_directory 等源码与配置,帮助你在实际项目中正确使用、定制与迁移 Hydra 的目录管理能力。
一、为什么需要"每个运行一个输出目录"
在没有 Hydra 时,每次运行实验你都要手动指定一个新的输出目录(--output_dir 2019-09-25/15-16-17之类的命令行参数),既繁琐又容易忘记覆盖上一次的结果。Hydra 的核心思路是:
为每一次运行自动创建一个新的输出目录,并把你的代码执行环境"放"到这个输出目录中。
每次运行应用,Hydra 都会生成一个新的输出目录;默认情况下,该目录同时用于存放 Hydra 自身产生的输出(配置快照、日志等)。这意味着你无需操心目录命名,也不必担心不同运行之间互相覆盖文件。
从源码结构看,这个能力由三层机制协同实现:
- 默认目录模板:默认的
run.dir与sweep.dir模板定义在 hydra/conf/hydra/output/default.yaml; - 单次运行:
hydra.core.utils.run_job()负责计算输出目录、写入配置快照并(可选地)执行os.chdir(见 hydra/core/utils.py); - 批量运行(multirun):
BasicLauncher.launch()依据hydra.sweep.dir与hydra.sweep.subdir为每个 job 计算目录(见 hydra/_internal/core_plugins/basic_launcher.py)。
下面先用一个最小示例观察它的实际行为。
二、最小示例:查看工作目录与输出目录
官方示例应用位于 examples/tutorials/basic/running_your_hydra_app/3_working_directory/my_app.py,内容如下:
import os from omegaconf import DictConfig import hydra from hydra.core.hydra_config import HydraConfig @hydra.main() def my_app(_cfg: DictConfig) -> None: print(f"Working directory : {os.getcwd()}") print(f"Output directory : {HydraConfig.get().runtime.output_dir}") if __name__ == "__main__": my_app()运行两次,观察输出:
$ python my_app.py Working directory : /home/omry/dev/hydra Output directory : /home/omry/dev/hydra/outputs/2019-09-25/15-16-17 $ python my_app.py Working directory : /home/omry/dev/hydra Output directory : /home/omry/dev/hydra/outputs/2019-09-25/15-16-19关键观察点:
- 每次运行都会生成一个全新的输出目录,两次运行的时间戳目录不同(
15-16-17vs15-16-19),互不干扰; - 在默认配置下(Hydra < 1.2 或
chdir=False),当前工作目录不变,仍是你启动应用的目录; - 输出目录的完整路径可以通过Hydra Config 中的
hydra.runtime.output_dir获取——这是应用内访问输出目录的标准方式。关于如何访问 Hydra Config,可参考 website/docs/configure_hydra/Intro.md 中 "Accessing the Hydra config" 一节。
提示:上述示例同时存放在 website/docs/tutorials/basic/running_your_app/3_working_directory.md 与示例目录中,两者是同一份代码的文档与实体两面。
三、输出目录里到底有什么
查看其中一个输出目录:
$ tree outputs/2019-09-25/15-16-17 outputs/2019-09-25/15-16-17 ├── .hydra │ ├── config.yaml │ ├── hydra.yaml │ └── overrides.yaml └── my_app.log目录内容分两部分:
Hydra 输出子目录(默认名为.hydra):
| 文件 | 含义 |
|---|---|
config.yaml | 用户指定配置(合成后的完整配置)的 YAML 快照 |
hydra.yaml | Hydra 自身配置的 YAML 快照 |
overrides.yaml | 本次运行使用的命令行覆盖(overrides)列表 |
主输出目录:
| 文件 | 含义 |
|---|---|
my_app.log | 本次运行生成的日志文件 |
这套快照机制让每个输出目录自包含、可复现:只要保留outputs/...目录,事后就能精确得知"当时用了什么配置、覆盖了什么参数、Hydra 的配置是怎样的"。
从源码角度,这些文件由run_job()统一写入。在 hydra/core/utils.py 中可以看到实际写盘逻辑:
if config.hydra.output_subdir is not None: hydra_output = Path(config.hydra.runtime.output_dir) / Path( config.hydra.output_subdir ) _save_config(task_cfg, "config.yaml", hydra_output) _save_config(hydra_cfg, "hydra.yaml", hydra_output) _save_config(config.hydra.overrides.task, "overrides.yaml", hydra_output)其中_save_config()(hydra/core/utils.py)会先mkdir(parents=True, exist_ok=True)再以OmegaConf.to_yaml()序列化写入。注意config.hydra.output_subdir若为None,则整个.hydra目录都不会被创建——这正是下一节"禁用输出子目录"的机制入口。
四、让应用自动切换到输出目录:hydra.job.chdir
很多应用希望把输出文件(如数据库 dump、模型 checkpoint)直接写在输出目录里,而不是手动拼接绝对路径。Hydra 从 v1.2 起提供了hydra.job.chdir开关。
4.1 行为对比
hydra.job.chdir的默认值定义在 hydra/conf/init.py:
# Change current working dir to the output dir. chdir: bool = False即Hydra v1.2 起默认False,工作目录保持不变。将其设为True后,@hydra.main装饰器会在调用你的主函数之前执行os.chdir,把 Python 的工作目录切换到本次运行的输出目录。实测效果:
# check current working dir $ pwd /home/jasha/dev/hydra # for Hydra >= 1.2, working dir remains unchanged by default $ python my_app.py Working directory : /home/jasha/dev/hydra Output directory : /home/jasha/dev/hydra/outputs/2023-04-18/13-43-24 # working dir changed to output dir $ python my_app.py hydra.job.chdir=True Working directory : /home/jasha/dev/hydra/outputs/2023-04-18/13-43-17 Output directory : /home/jasha/dev/hydra/outputs/2023-04-18/13-43-17 # output dir and files are still created even if `chdir` is disabled: $ tree -a outputs/2023-04-18/13-43-24/ outputs/2023-04-18/13-43-24/ ├── .hydra │ ├── config.yaml │ ├── hydra.yaml │ └── overrides.yaml └── my_app.log三个要点:
chdir=True后,os.getcwd()与hydra.runtime.output_dir指向同一个目录,此时直接写相对路径文件即可落在输出目录内;- 即使不开启
chdir,输出目录和其中的文件照常创建(.hydra与my_app.log依旧存在),只是你的进程还在原目录; - 该开关既可以在命令行以
hydra.job.chdir=True传入,也可以写进配置文件的hydra.job节点。
4.2 源码实现
chdir的切换逻辑在 hydra/core/utils.py:
_chdir = hydra_cfg.hydra.job.chdir if _chdir: os.chdir(output_dir) ret.working_dir = output_dir else: ret.working_dir = os.getcwd()并且使用try/finally保证任务结束(包括异常中断)后恢复原始工作目录(hydra/core/utils.py)。仓库测试也对chdir=True的场景做了覆盖验证,例如 hydra/test_utils/launcher_common_tests.py 中通过overrides + ["hydra.job.chdir=True"]断言os.getcwd()指向预期目录。
需要留意:切换工作目录的行为会影响应用对相对路径的解析。如果你在代码里读取了"启动时所在目录"下的资源文件,请参考下一节的两个工具函数,否则在
chdir=True下会定位到错误位置。
五、访问原始工作目录:get_original_cwd()与to_absolute_path()
当hydra.job.chdir=True时,os.getcwd()已指向输出目录,但你依然可以拿到启动应用时所在的原始工作目录。官方示例 examples/tutorials/basic/running_your_hydra_app/3_working_directory/original_cwd.py 演示了这两种用法:
import os from omegaconf import DictConfig import hydra from hydra.utils import get_original_cwd, to_absolute_path @hydra.main() def my_app(_cfg: DictConfig) -> None: print(f"Current working directory : {os.getcwd()}") print(f"Orig working directory : {get_original_cwd()}") print(f"to_absolute_path('foo') : {to_absolute_path('foo')}") print(f"to_absolute_path('/foo') : {to_absolute_path('/foo')}") if __name__ == "__main__": my_app()运行结果:
$ python examples/tutorial/8_working_directory/original_cwd.py Current working directory : /Users/omry/dev/hydra/outputs/2019-10-23/10-53-03 Original working directory : /Users/omry/dev/hydra to_absolute_path('foo') : /Users/omry/dev/hydra/foo to_absolute_path('/foo') : /foo两个函数的语义(见 hydra/utils.py):
| 函数 | 行为 |
|---|---|
get_original_cwd() | 返回启动 Hydra 应用时的原始工作目录(即HydraConfig.get().runtime.cwd,见 hydra/utils.py)。未初始化 HydraConfig 时调用会抛出ValueError |
to_absolute_path(path) | 相对路径按原始工作目录为基准转为绝对路径;绝对路径原样返回(见 hydra/utils.py) |
典型用法:应用需要读取位于项目根目录下的数据文件或预训练模型时,用to_absolute_path("data/foo")而非裸的相对路径,这样无论chdir是否开启都能正确定位。
六、改变或禁用 Hydra 输出子目录:hydra.output_subdir
默认情况下 Hydra 输出子目录名为.hydra。你可以通过覆盖hydra.output_subdir来改变或禁用它的创建:
# 改变子目录名(例如改为 custom_hydra_output) python my_app.py hydra.output_subdir=custom_hydra_output # 禁用子目录创建(不生成 .hydra 目录) python my_app.py hydra.output_subdir=null结合上一节源码可以看到,run_job()在写快照前先判断config.hydra.output_subdir is not None(hydra/core/utils.py):为null时直接跳过整个.hydra目录的创建。
注意:禁用
.hydra后,本次运行将不再保留config.yaml/hydra.yaml/overrides.yaml快照,会损失可复现性,请按需使用。
七、定制输出目录命名:hydra.run.dir与hydra.sweep.dir
7.1 单次运行(run)目录模板
默认的单次运行模板定义在 hydra/conf/hydra/output/default.yaml:
run: dir: outputs/${now:%Y-%m-%d}/${now:%H-%M-%S}即outputs/日期/时分秒的树形结构。你可以在配置文件中覆盖hydra.run.dir来实现自己的命名策略,website/docs/configure_hydra/workdir.md 给出了三种常见模式:
按日期分组:
hydra: run: dir: ./outputs/${now:%Y-%m-%d}/${now:%H-%M-%S}按 job 名称分组:
hydra: run: dir: outputs/${hydra.job.name}/${now:%Y-%m-%d_%H-%M-%S}目录名中包含用户配置变量:
hydra: run: dir: outputs/${now:%Y-%m-%d_%H-%M-%S}/opt:${optimizer.type}模板中支持${now:...}(时间格式化)、${hydra.job.name}(任务名)以及用户自定义配置项插值,实现"目录名自描述实验内容"的效果。
7.2 批量运行(multirun)目录模板
multirun 场景下涉及两个键:
| 键 | 默认值 | 含义 |
|---|---|---|
hydra.sweep.dir | multirun/${now:%Y-%m-%d}/${now:%H-%M-%S} | 整个 sweep 的根目录 |
hydra.sweep.subdir | ${hydra.job.num} | 每个 job 的子目录(job 序号) |
默认配置同样位于 hydra/conf/hydra/output/default.yaml。在底层,BasicLauncher.launch()会为每个 job 调用run_job()并传入job_dir_key="hydra.sweep.dir"与job_subdir_key="hydra.sweep.subdir"(见 hydra/_internal/core_plugins/basic_launcher.py),而run_job()则在 hydra/core/utils.py 中先取sweep.dir,再延迟求值sweep.subdir(因为hydra.job.num这类值只在客户端执行时可用),最终拼成output_dir = os.path.join(dir, subdir)。
因此,multirun 的目录同样支持时间、job 名等插值:
hydra: sweep: dir: ${hydra.job.name} subdir: ${hydra.job.num}运行python my_app.py --multirun a=a1,a2,a3后将得到类似my_app/0、my_app/1、my_app/2的目录结构。
7.3 用hydra_override_dirname生成描述性目录名
如果希望子目录名直接体现本次 job 的命令行参数(如batch_size=32,learning_rate=0.1),可以使用内置的hydra_override_dirname解析器,它从命令行 override 推导目录名,通常与hydra.sweep.subdir配合使用(详见 website/docs/configure_hydra/workdir.md):
hydra: sweep: dir: multirun subdir: ${hydra_override_dirname:}运行:
python my_app.py --multirun batch_size=32 learning_rate=0.1,0.01会得到:
multirun ├── batch_size=32,learning_rate=0.01 └── batch_size=32,learning_rate=0.1解析器还支持在调用处定制分隔符与排除键,例如将随机种子排除在目录名之外:
hydra: sweep: dir: multirun subdir: '${hydra_override_dirname:{exclude_keys: [seed]}}/seed=${seed}'运行python my_app.py --multirun batch_size=32 learning_rate=0.1,0.01 seed=1,2将生成:
multirun ├── batch_size=32,learning_rate=0.01 │ ├── seed=1 │ └── seed=2 └── batch_size=32,learning_rate=0.1 ├── seed=1 └── seed=2也可自定义键值分隔符kv_sep、条目分隔符item_sep,甚至通过element_resolver挂载一个自定义 OmegaConf 解析器对每个元素做预处理(例如把/与\替换为_以保证目录名在不同平台可用)。相关示例配置与配套应用见仓库中的 examples/configure_hydra/job_override_dirname 目录。
八、配套资源与进一步阅读
- 本文核心教程: website/docs/tutorials/basic/running_your_app/3_working_directory.md
- 可运行的示例代码:examples/tutorials/basic/running_your_hydra_app/3_working_directory/my_app.py 与 original_cwd.py
- 工作目录定制模式详解:website/docs/configure_hydra/workdir.md
- 输出目录定制配套示例:examples/configure_hydra/workdir 与 examples/configure_hydra/job_override_dirname
- Hydra Config 访问方式(
HydraConfig.get()):website/docs/configure_hydra/Intro.md - 关键实现源码:hydra/core/utils.py(
run_job/_save_config)、hydra/utils.py(get_original_cwd/to_absolute_path)、hydra/conf/init.py(hydra.job.chdir默认值)、hydra/conf/hydra/output/default.yaml(默认目录模板) - 相关测试:hydra/test_utils/launcher_common_tests.py(
chdir行为验证)
使用建议:在 Hydra 1.2 及以上版本中,如果应用需要把产物写进输出目录,推荐显式开启hydra.job.chdir=True并使用get_original_cwd()/to_absolute_path()读取启动目录下的资源;同时保留默认的.hydra快照以维持可复现性。升级或迁移到新版本时,务必确认chdir默认值是否影响了你对相对路径的既有假设。
【免费下载链接】hydraHydra is a framework for elegantly configuring complex applications项目地址: https://gitcode.com/GitHub_Trending/hyd/hydra
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考