
编写自定义 Launcher 插件以 Hydra example_launcher_plugin 为例的完整实战指南【免费下载链接】hydraHydra is a framework for elegantly configuring complex applications项目地址: https://gitcode.com/GitHub_Trending/hyd/hydra导读本文以 Hydra 官方示例仓库中的 example_launcher_plugin 为主线深入讲解如何为 Hydra 编写一个自定义 Launcher启动器插件从Launcher抽象接口的职责划分到插件配置的注册方式再到setup()与launch()的完整实现、multirun 执行流程以及插件的打包分发与测试验证。读完本文你将掌握在 Hydra 中实现自定义批量作业启动逻辑如本地循环启动、调度器提交等的完整技术路径并能直接对照仓库源码进行二次开发。一、Launcher 在 Hydra 中的角色Hydra 的核心能力之一是multirun多任务批量运行通过--multirun或-m开关配合 sweep 参数如dbpostgresql,mysql一次命令行即可批量启动多个 Job。而“如何启动这些 Job、在哪里启动、如何组织输出目录”正是由Launcher 插件决定的。在 Hydra 的插件体系hydra/plugins/launcher.py中Launcher是一个继承自Plugin的抽象基类仅定义了两个抽象方法setup(*, hydra_context, task_function, config)在启动前把执行所需的上下文注入到 launcher 实例中launch(job_overrides, initial_job_idx)接收一个“作业参数批”Sequence[Sequence[str]]真正执行批量作业并返回Sequence[JobReturn]。官方内置的BasicLauncher见 hydra/_internal/core_plugins/basic_launcher.py就是一个最简单的实现在本进程内逐个调用run_job。而本示例插件ExampleLauncher做的事情与它本质相同——把dbpostgresql,mysql展开成 2 个作业并逐一运行——但它刻意把“插件如何组织代码、如何配置、如何打包、如何测试”的完整流程展示出来是学习自定义 Launcher 的最佳起点。二、示例工程结构总览插件完整位于 examples/plugins/example_launcher_plugin 目录下其结构分为四块职责清晰example_launcher_plugin/ ├── example/ # 一个使用该插件的最小 Hydra 应用 │ ├── my_app.py │ └── conf/ │ ├── config.yaml # 通过 defaults 覆盖使用 example launcher │ └── db/ │ ├── mysql.yaml │ └── postgresql.yaml ├── hydra_plugins/ │ └── example_launcher_plugin/ │ ├── __init__.py │ ├── example_launcher.py # Launcher 核心实现 │ └── py.typed ├── tests/ │ └── test_example_launcher_plugin.py ├── setup.py # 插件打包分发 ├── MANIFEST.in # 打包包含的数据文件 └── README.md # 本插件说明文档关键约定Hydra 通过hydra_plugins命名空间包自动发现插件对应 hydra/core/plugins.py 中的插件发现机制因此插件主体必须放在名为hydra_plugins的顶层包下。三、插件配置两种注册方式与一个命名空间3.1 配置的存放位置Launcher 插件的配置应放在hydra/launcher配置组下这样用户便可通过hydra/launchername覆盖默认 launcher。README 中给出了配置文件的示意# hydra/launcher/example.yaml _target_: hydra_plugins.example_launcher_plugin.example_launcher.ExampleLauncher foo: 10 bar: abcde3.2 本示例的实际做法ConfigStore 注册需要特别说明在这个示例插件中配置文件并没有以独立.yaml文件打包hydra_plugins/example_launcher_plugin/目录下只有.py与py.typed而是采用ConfigStore结构化配置注册的方式在 example_launcher.py 中完成dataclass class LauncherConfig: _target_: str ( hydra_plugins.example_launcher_plugin.example_launcher.ExampleLauncher ) foo: int 10 bar: str abcde ConfigStore.instance().store( grouphydra/launcher, nameexample, nodeLauncherConfig )这里借助 Hydra 的ConfigStore实现见 hydra/core/config_store.py以 dataclass 结构化的方式把ExampleLauncher注册到hydra/launcher组、名字为example并携带默认参数foo: int 10、bar: str abcde。这两种方式打包.yaml配置文件 /ConfigStore结构化注册等价都是把配置暴露到hydra/launcher命名空间下前者适合纯 yaml 配置后者能享受类型检查与 IDE 补全。两种方式产出的最终配置效果一致用户选择hydra/launcherexample后Hydra 会按_target_实例化ExampleLauncher并把foo、bar作为构造参数传入。3.3 应用侧如何选用插件示例应用 conf/config.yaml 通过 defaults 列表完成两件事defaults: - db: mysql - override hydra/launcher: example选择db: mysql为任务提供数据库配置使用override hydra/launcher: example把 Hydra 默认的本地启动器替换为本插件实现的ExampleLauncher。应用本体 my_app.py 只是最普通的 Hydra 入口负责打印合并后的配置import hydra from omegaconf import OmegaConf, DictConfig hydra.main(config_pathconf, config_nameconfig) def my_app(cfg: DictConfig) - None: print(OmegaConf.to_yaml(cfg)) if __name__ __main__: my_app()而db配置组提供了两个选项postgresql.yaml 与 mysql.yaml正是 multirun 扫描的对象# db/mysql.yaml driver: mysql user: omry pass: secret # db/postgresql.yaml driver: postgresql user: postgres_user pass: drowssap timeout: 10四、核心实现ExampleLauncher 源码逐段解析插件核心 example_launcher.py 的ExampleLauncher继承自hydra.plugins.launcher.Launcher其生命周期分为初始化 → setup → launch三个阶段。4.1 初始化接收插件配置参数class ExampleLauncher(Launcher): def __init__(self, foo: str, bar: str) - None: self.config: Optional[DictConfig] None self.task_function: Optional[TaskFunction] None self.hydra_context: Optional[HydraContext] None # foo and var are coming from the the plugins configuration self.foo foo self.bar bar__init__只接收foo、bar两个参数——它们正是配置节点中foo: 10、bar: abcdeLauncherConfig的默认值注入进来的。三个以Optional声明的成员config、task_function、hydra_context留待setup()填充。4.2 setup注入执行上下文def setup( self, *, hydra_context: HydraContext, task_function: TaskFunction, config: DictConfig, ) - None: self.config config self.hydra_context hydra_context self.task_function task_functionsetup()由 Hydra 在启动 multirun 前调用把已合并的完整配置、用户任务函数、以及**HydraContext含配置加载器等工具**注入实例。这也是Launcher抽象接口hydra/plugins/launcher.py要求实现的第一个方法任何自定义 Launcher 都必须按该签名实现。4.3 launch批量作业的执行循环launch()是插件的“主战场”接收job_overrides一个“列表的列表”每个内层列表代表一个作业的完整参数与initial_job_idx批次起始序号供 sweeper 分多批执行时使用返回Sequence[JobReturn]。其执行流程可拆解为如下步骤① 环境与日志准备setup_globals() assert self.config is not None assert self.hydra_context is not None assert self.task_function is not None configure_log(self.config.hydra.hydra_logging, self.config.hydra.verbose) sweep_dir Path(str(self.config.hydra.sweep.dir)) sweep_dir.mkdir(parentsTrue, exist_okTrue)setup_globals()重置进程级全局状态configure_log(...)按配置为 Hydra 自身配置日志从hydra.sweep.dir读取 sweep 输出根目录形如multirun/2019-10-22/19-45-05并创建。② 输出提示信息log.info( fExample Launcher(foo{self.foo}, bar{self.bar}) is launching {len(job_overrides)} jobs locally ) log.info(fSweep output dir : {sweep_dir})这两行正是 README 示例输出中Example Launcher(foo10, barabcde) is launching 2 jobs locally与Sweep output dir : ...的来源直观证明了foo/bar从配置成功传入。③ 逐作业循环生成配置 → 填充 job 元数据 → 运行for idx, overrides in enumerate(job_overrides): idx initial_job_idx idx lst .join(filter_overrides(overrides)) log.info(f\t#{idx} : {lst}) sweep_config self.hydra_context.config_loader.load_sweep_config( self.config, list(overrides) ) with open_dict(sweep_config): # 作业 id 通常来自底层调度器如 SLURM_JOB_ID此处仅作演示 sweep_config.hydra.job.id fjob_id_for_{idx} sweep_config.hydra.job.num idx这一小段是 launcher 最核心的“每作业处理”逻辑filter_overrides(overrides)过滤掉 Hydra 内部覆盖项后拼成可读字符串用于日志输出config_loader.load_sweep_config(config, overrides)为当前作业生成合并后的 sweep 配置即把dbpostgresql等覆盖项应用进去使用open_dict(sweep_config)临时放开只读限制写入hydra.job.id与hydra.job.num。注释明确指出真实场景中作业 id 通常由底层调度器如 SLURM 的SLURM_JOB_ID提供且应在远端进程中填充而不是在此处硬编码。④ 跨进程时的 Singleton 状态传递重要设计# 如果 launcher 在不同进程中执行代码必须在新的进程中恢复 singleton 状态 state Singleton.get_state() # 本进程执行 launcher Singleton.set_state(state) # 子进程执行 task_functionHydra 大量使用单例如ConfigStore、Plugins当 launcher 把作业提交到子进程或远端节点执行时需要把当前进程的 singleton 状态序列化后随任务参数一起传递并在子进程执行前恢复。示例中的这两行代码演示了这一机制对于本地进程内串行执行的 launcher 而言get_state/set_state是连续调用主要起到示范作用。⑤ 运行作业ret run_job( hydra_contextself.hydra_context, task_functionself.task_function, configsweep_config, job_dir_keyhydra.sweep.dir, job_subdir_keyhydra.sweep.subdir, ) runs.append(ret) # run_job 会把日志系统配置为 Job 模式同进程串行执行后需要恢复 configure_log(self.config.hydra.hydra_logging, self.config.hydra.verbose)run_job来自 hydra/core/utils.py是执行单个任务函数的标准入口负责设置作业工作目录hydra.sweep.subdir形如0、1、运行task_function、收集返回结果。作业完成后再次调用configure_log把日志系统恢复为 Hydra 自身模式——这一行对于在同一进程内串行调用run_job的 launcher 必不可少否则后续作业的日志配置会互相污染。最后返回runs其下标与输入job_overrides一一对应。4.4 性能提示插件导入的注意事项源码开头有一段 IMPORTANT 注释对插件开发是硬性约束如果插件导入了任何需要较长时间加载的模块请在launch()内延迟lazy导入或者把重型依赖放入以_开头的文件如_core.py中——Hydra 在插件发现阶段不会扫描这类文件、也不会导入它们。原因是已安装插件在Hydra 初始化阶段就会被导入慢速导入会拖慢所有Hydra 应用的启动速度。这一点在设计真实插件时务必遵守。五、运行示例与输出解析按照 README 的演示进入 example 目录后执行python example/my_app.py --multirun dbpostgresql,mysql由于 config.yaml 已通过override hydra/launcher: example选用了本插件--multirun会触发ExampleLauncher把dbpostgresql,mysql展开为 2 个作业。README 记录的输出为$ python example/my_app.py --multirun dbpostgresql,mysql [2019-10-22 19:45:05,060] - Example Launcher(foo10, barabcde) is launching 2 jobs locally [2019-10-22 19:45:05,060] - Sweep output dir : multirun/2019-10-22/19-45-05 [2019-10-22 19:45:05,060] - #0 : dbpostgresql db: driver: postgresql pass: drowssap timeout: 10 user: postgres_user [2019-10-22 19:45:05,135] - #1 : dbmysql db: driver: mysql pass: secret user: omry逐行对照源码可以清晰看到第 1 行来自launch()中log.info(fExample Launcher(foo{self.foo}, bar{self.bar}) ...)foo10、barabcde即LauncherConfig默认值第 2 行来自Sweep output dir : {sweep_dir}目录按日期时间组织为multirun/日期/时间#0/#1行来自循环中log.info(f\t#{idx} : {lst})lst是filter_overrides过滤后的参数串两个db:配置块分别是load_sweep_config应用dbpostgresql、dbmysql后任务函数OmegaConf.to_yaml(cfg)打印出的结果与 postgresql.yaml、mysql.yaml 内容一一对应注意 postgresql 额外带有timeout: 10mysql 没有印证了 sweep 配置是按选项分别合并的。六、插件打包与分发插件通过 setup.py 打包其中的若干细节是 Hydra 插件分发的标准做法from setuptools import find_namespace_packages, setup setup( namehydra-example-launcher, version1.0.0, packagesfind_namespace_packages(include[hydra_plugins.*]), python_requires3.10, install_requires[ # 建议固定 hydra-core 主版本避免新主版本引入破坏性变更 # 例如: hydra-core1.0.*, hydra-core, ], include_package_dataTrue, )find_namespace_packages(include[hydra_plugins.*])只打包hydra_plugins命名空间下的包确保 Hydra 插件发现机制能命中install_requires依赖hydra-core注释建议考虑固定主版本如hydra-core1.0.*以防 Hydra 新主版本的 API 变更破坏插件include_package_dataTrue配合 MANIFEST.in 把数据文件一并打进包global-exclude *.pyc global-exclude __pycache__ recursive-include hydra_plugins/* *.yaml py.typed这条规则显式包含插件目录下的*.yaml与py.typed——*.yaml是提供给 Hydra 发现的配置文件若采用 yaml 文件方式注册时必需py.typed是类型标注声明。setup.py 中的注释也强调配置文件要能在运行时被发现除了打进包还需加入搜索路径。七、测试复用 Hydra 官方 Launcher 测试套件插件测试 test_example_launcher_plugin.py 展示了如何零成本获得高质量覆盖def test_discovery() - None: # 验证该插件可被插件子系统作为 Launcher 发现 assert ExampleLauncher.__name__ in [ x.__name__ for x in Plugins.instance().discover(Launcher) ] mark.parametrize(launcher_name, overrides, [(example, [])]) class TestExampleLauncher(LauncherTestSuite): 注意应由该 launcher 提供 hydra/launcher/example.yaml mark.parametrize( task_launcher_cfg, extra_flags, [({}, [-m, hydra/launcherexample])], ) class TestExampleLauncherIntegration(IntegrationTestSuite): 通过集成测试套件运行该 launcher三个层次对应三类保障test_discovery断言插件能被Plugins.discover(Launcher)发现验证命名空间包与插件注册正确LauncherTestSuite来自 hydra/test_utils/launcher_common_tests.py对 launcher 的通用行为如JobReturn结果、作业目录等做参数化测试任何自定义 launcher 都应继承它IntegrationTestSuite以-m hydra/launcherexample方式驱动完整应用跑集成测试验证 launcher 与 Hydra 主流程的兼容性。八、自定义 Launcher 的通用检查清单基于对ExampleLauncher的剖析可以把编写任何自定义 Launcher 的要点归纳如下继承hydra.plugins.launcher.Launcher实现setup()与launch()两个抽象方法签名见 hydra/plugins/launcher.py将配置注册到hydra/launcher组可用打包.yaml文件也可用ConfigStore结构化注册参数将注入__init__在launch()中调用setup_globals()与configure_log()→ 准备 sweep 输出目录 → 用config_loader.load_sweep_config()为每个作业生成配置 → 填充hydra.job.id/num→ 用run_job()执行若作业跨进程/远端执行序列化并恢复Singleton状态作业 id 在远端进程填充同进程串行执行时每个作业后恢复日志配置注意导入性能重依赖延迟导入或放入_前缀文件打包时使用find_namespace_packages(include[hydra_plugins.*])并确保配置文件与py.typed被打入测试时复用LauncherTestSuite与IntegrationTestSuite覆盖通用行为与端到端流程。掌握以上流程你就能够把示例中的本地循环启动器替换为基于subprocess、concurrent.futures或各类调度器 API 的真实分布式/并行 Launcher 实现。【免费下载链接】hydraHydra is a framework for elegantly configuring complex applications项目地址: https://gitcode.com/GitHub_Trending/hyd/hydra创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考