Hydra 实验特性解析:使用--experimental-rerun从历史配置重新运行任务
【免费下载链接】hydraHydra is a framework for elegantly configuring complex applications项目地址: https://gitcode.com/GitHub_Trending/hyd/hydra
本文围绕 Hydra 的实验性重跑(Re-run)能力展开,讲解如何借助PickleJobInfoCallback回调把完整任务配置持久化到输出目录,再通过--experimental-rerun命令行选项从上次运行留下的config.pickle中恢复并重新执行同一个任务。读完本文,你将掌握这套"保存配置 → 重跑任务"工作流的具体配置方法、命令行用法、底层实现原理,以及它在多进程调度、超时重试等场景中的适用边界与限制。
:::caution 这是一个实验性(experimental)特性,其行为、命令参数与 API 在未来版本中可能发生变化。使用前请先通读本文,确认该特性支持的范围符合你的需求。 :::
为什么需要"从配置重跑"一个 Hydra 任务
Hydra 的核心能力是从配置文件中合成(compose)出完整配置,并把任务函数与配置绑定在一起执行。但在某些场景下,你希望在任务已经执行完之后,用与之前完全相同的配置再执行一次:
- 某个分布式任务中途失败或超时,希望原样重试,而不必手动重新拼写所有命令行覆盖参数;
- 需要复现一次实验的精确配置,用于排查问题或对比结果;
- 想把某次历史任务的配置作为"基线"传给后续流程做离线分析。
Hydra 给出的实验性答案是:先把最终合成好的完整配置对象(DictConfig)以 pickle 形式保存下来,之后直接用--experimental-rerun指向该 pickle 文件,即可绕过正常配置组合流程、直接把这份配置原样传入任务函数。
官方在 website/docs/experimental/rerun.md 中提供了完整的演示流程,仓库中也配套了可直接运行的示例应用(examples/experimental/rerun/),下文将以该示例为主线逐步展开。
第一步:用PickleJobInfoCallback保存任务配置
重跑的前提是"上次运行留下了可供恢复的配置"。Hydra 提供了实验性的回调hydra.experimental.callbacks.PickleJobInfoCallback,它会在任务生命周期中把配置与返回值序列化到输出目录下。
配置回调
在应用的config.yaml中,通过 Hydra 标准的hydra.callbacks配置块注册该回调:
foo: bar hydra: callbacks: save_job_info: _target_: hydra.experimental.callbacks.PickleJobInfoCallback job: chdir: false_target_指向回调类的完整限定名,Hydra 会按需实例化并注册它;hydra.job.chdir: false表示任务执行时不切换工作目录,让输出目录与日志行为更可控(这是示例应用特意设置的,具体取舍见下文"注意事项")。
该回调的源码位于 hydra/experimental/callbacks.py,它继承自 hydra/experimental/callback.py 中的Callback基类,实现了两个钩子方法:
on_job_start:在任务代码运行前触发,将合成好的完整配置config以 pickle 协议 4 保存到${output_dir}/.hydra/config.pickle,并打印Saving job configs in ...日志;on_job_end:在任务代码运行后触发,把JobReturn(包含任务返回值或异常信息)保存到${output_dir}/.hydra/job_return.pickle。
其中output_dir取自config.hydra.runtime.output_dir,output_subdir取自config.hydra.output_subdir(默认即.hydra),因此保存路径形如<run目录>/.hydra/config.pickle。config.pickle正是后续重跑所需的输入文件。
编写示例应用
示例主程序位于 examples/experimental/rerun/my_app.py:
# examples/experimental/rerun/my_app.py import logging from omegaconf import DictConfig import hydra from hydra.core.hydra_config import HydraConfig from hydra.utils import target_whitelist log = logging.getLogger(__name__) @hydra.main(config_path=".", config_name="config") def my_app(cfg: DictConfig) -> None: log.info(f"Output_dir={HydraConfig.get().runtime.output_dir}") log.info(f"cfg.foo={cfg.foo}") if __name__ == "__main__": with target_whitelist("hydra.experimental.callbacks.*"): my_app()两个值得注意的细节:
HydraConfig.get().runtime.output_dir用于在任务内读取本次运行的输出目录;- 主入口处用
target_whitelist("hydra.experimental.callbacks.*")包裹调用,这是因为PickleJobInfoCallback属于hydra.experimental命名空间,Hydra 默认禁止实例化该命名空间下的目标,需要通过白名单显式放行。
第二步:正常运行一次,观察保存产物
运行示例应用:
$ python my_app.py [2022-03-16 14:51:30,905][hydra.experimental.pickle_job_info_callback][INFO] - Saving job configs in /Users/jieru/workspace/hydra/examples/experimental/outputs/2022-03-16/14-51-30/.hydra/config.pickle [2022-03-16 14:51:30,906][__main__][INFO] - Output_dir=/Users/jieru/workspace/hydra/examples/experimental/outputs/2022-03-16/14-51-30 [2022-03-16 14:51:30,906][__main__][INFO] - cfg.foo=bar [2022-03-16 14:51:30,906][hydra.experimental.pickle_job_info_callback][INFO] - Saving job_return in /Users/jieru/workspace/hydra/examples/experimental/outputs/2022-03-16/14-51-30/.hydra/job_return.pickle运行后输出目录(本例为outputs/2022-03-16/14-51-30/)下的.hydra子目录中会生成两个文件:
config.pickle:序列化后的完整合成配置,这是重跑所用的核心文件;job_return.pickle:本次任务的JobReturn对象,可用于事后分析任务结果。
仓库中的测试 tests/test_callbacks.py 里的test_experimental_rerun也验证了这一点:正常运行后断言config.pickle与my_app.log均存在,并读取日志确认任务确实执行([JOB] Running my_app)。对应测试应用位于 tests/test_apps/app_with_pickle_job_info_callback/,其config.yaml同样注册了PickleJobInfoCallback。
第三步:使用--experimental-rerun重新运行
保存好config.pickle后,即可用它重跑任务:
$ OUTPUT_DIR=/Users/jieru/workspace/hydra/examples/experimental/outputs/2022-03-16/14-51-30/.hydra/ $ python my_app.py --experimental-rerun $OUTPUT_DIR/config.pickle /Users/jieru/workspace/hydra/hydra/main.py:23: UserWarning: Experimental rerun CLI option. warnings.warn(msg, UserWarning) [2022-03-16 14:59:21,666][__main__][INFO] - Output_dir=/Users/jieru/workspace/hydra/examples/experimental/outputs/2022-03-16/14-51-30 [2022-03-16 14:59:21,666][__main__][INFO] - cfg.foo=bar对比两次运行可以看到:
- 任务函数再次执行,
cfg.foo仍是bar,配置内容与首次运行完全一致; Output_dir与首次运行完全相同(仍指向14-51-30),因为重跑直接恢复了历史配置,并不会生成新的时间戳输出目录;- 控制台输出了一次
UserWarning: Experimental rerun CLI option.,提醒你这属于实验性入口。
同时你还会发现my_app.log被第二次运行的日志所更新,而PickleJobInfoCallback的保存日志(Saving job configs ...)没有再次出现——因为重跑模式下回调并不会被再次调用。下文将解释为什么会这样。
--experimental-rerun参数定义位置
该命令行选项由 Hydra 的参数解析器统一注册,定义在 hydra/_internal/utils.py:
parser.add_argument( "--experimental-rerun", help="Rerun a job from a previous config pickle", )即:该选项接收一个参数,值为历史配置 pickle 文件的路径。它与--config-path、--config-name、--config-dir等同级存在,属于 Hydra 的全局 CLI 选项。
底层实现:重跑时到底发生了什么
要理解重跑的行为边界,需要看@hydra.main装饰器的实现,位于 hydra/main.py:
def decorated_main(cfg_passthrough: Optional[DictConfig] = None) -> Any: if cfg_passthrough is not None: return task_function(cfg_passthrough) else: args_parser = get_args_parser() args = args_parser.parse_intermixed_args() if args.experimental_rerun is not None: cfg = _get_rerun_conf(args.experimental_rerun, args.overrides) task_function(cfg) _flush_loggers() else: _run_hydra(...)当检测到--experimental-rerun时,会调用_get_rerun_conf恢复配置,然后直接执行task_function(cfg),完全绕过正常的_run_hydra流程。_get_rerun_conf(hydra/main.py)的核心逻辑如下:
- 发出
UserWarning,提示"实验性重跑选项,其他命令行参数将被忽略"; - 检查 pickle 文件是否存在,不存在则抛出
ValueError; - 若检测到命令行覆盖参数(overrides),再次发出警告
Config overrides are not supported as of now; pickle.load反序列化出上次保存的完整配置对象;- 依据配置中的
hydra.job_logging与hydra.verbose重新配置日志; - 通过
HydraConfig.instance().set_config(config)恢复全局 Hydra 运行期配置(因此任务内的HydraConfig.get()可以正常工作); copy.deepcopy该配置,删除其中的hydra键,返回纯任务配置(task_cfg)传给任务函数。
这段实现同时印证了官方文档中的几个关键结论:
- 重跑是"配置直通":恢复的
cfg对象以cfg_passthrough的形式直接进入任务函数,hydra.main的其他职责(切换工作目录、调用回调、组织输出目录等)都不会执行——这与装饰器入口处cfg_passthrough is not None的分支逻辑一致; - 回调不会再次触发:
PickleJobInfoCallback的钩子由run_job流程驱动,而重跑绕过了该流程,所以第二次运行不会再生成新的config.pickle,my_app.log则因日志按恢复的job_logging配置重新初始化而被第二次写入; - 覆盖参数无效:
--experimental-rerun与其他 CLI 覆盖参数同时出现时,覆盖会被忽略(并伴随警告),因为重跑所需的配置完全来自 pickle 文件。
重跑工作的适用边界与重要限制
官方文档在"Important Notes"中明确了该实验特性的支持范围,结合源码可以归纳如下:
- 仅支持单次运行(single run):
--experimental-rerun只针对单个任务实例生效,不支持多任务(multirun)场景的重跑。 - 不能与其他命令行选项或覆盖参数混用:
--experimental-rerun一旦指定,其余命令行选项与覆盖会被直接忽略(并给出警告)。仓库测试 tests/test_callbacks.py 对两种情况做了参数化验证:无覆盖时出现Experimental rerun CLI option警告;携带+x=1覆盖时出现Config overrides are not supported as of now警告,同时验证重跑后日志文件被重新创建、任务确实再次执行。 - 重跑只恢复
cfg对象本身,不恢复完整的hydra.main行为:除日志按恢复配置重建外,切换工作目录、执行回调等均不会发生。这也是示例config.yaml中特意设置hydra.job.chdir: false的原因之一——避免首跑时的工作目录语义与重跑时不一致造成困惑。 - 配置是"尽力保留、惰性解析":
PickleJobInfoCallback序列化的是合成后但尚未解析全部插值的配置对象。对于foo: bar这类静态值,重跑结果与原运行完全一致;但对于在运行期才会解析的惰性插值,Hydra 无法保证两次运行行为一致。例如下面的配置:
time_now: ${now:%H-%M-%S}@hydra.main(config_path=".", config_name="config") def my_app(cfg: DictConfig) -> None: val = cfg.time_now # 其余业务逻辑cfg.time_now每次运行时都会解析成不同的时间戳——因为该插值是在任务函数内访问cfg.time_now的那一刻才求值的,pickle 保存的只是未解析的表达式本身。因此,若你的应用在运行期依赖这类动态值,重跑并不能保证完全可复现,请谨慎评估使用场景。
快速验证:跑一遍仓库自带的测试
仓库已为这一特性提供了自动化测试,可用于本地快速验证行为是否符合预期:
- tests/test_examples/test_experimental.py 中的
test_rerun:以examples/experimental/rerun/my_app.py为对象,通过覆盖hydra.run.dir等方式控制输出目录,验证首跑时cfg.foo=bar正常输出; - tests/test_callbacks.py 中的
test_experimental_rerun:走完整的"先跑 → 删除日志 → 用config.pickle重跑 → 断言日志重新生成"闭环,同时校验两种警告信息的输出。
这些测试既是对特性行为的确认,也可以作为你集成该特性时编写自身回归测试的参考模板。
小结
Hydra 的--experimental-rerun提供了一条轻量的实验性路径:通过PickleJobInfoCallback把合成配置落盘为config.pickle,再以单个 CLI 参数完成历史任务的精确重放。它的实现本质是"配置直通"——恢复后的cfg直接进入任务函数,hydra.main的其余编排逻辑(工作目录切换、回调等)均被跳过,覆盖参数也会被忽略;同时由于配置采用惰性解析,包含now等运行时插值的字段无法保证跨运行一致。
如果你的需求是"用完全相同的配置再跑一次任务",并且任务配置以静态值为主,这个实验特性开箱即用;若涉及多任务重放、动态插值强依赖或完整的生命周期回调,则需要等待该特性后续演进,或结合 Hydra 的 Launcher、Sweeper 与自定义回调自行编排重跑流程。
【免费下载链接】hydraHydra is a framework for elegantly configuring complex applications项目地址: https://gitcode.com/GitHub_Trending/hyd/hydra
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考