ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Hydra 插件开发实战指南:从插件发现机制到自定义 Launcher

Hydra 插件开发实战指南:从插件发现机制到自定义 Launcher Hydra 插件开发实战指南从插件发现机制到自定义 Launcher【免费下载链接】hydraHydra is a framework for elegantly configuring complex applications项目地址: https://gitcode.com/GitHub_Trending/hyd/hydraHydra 通过插件系统将 Launcher启动器、Sweeper搜索器、Config Source配置源、Completion 与 Search Path 等能力开放给开发者扩展。本文以 Hydra 1.1 版本的插件开发文档为主体结合本仓库中 插件扫描实现 与 官方示例插件 的源码完整讲解插件的目录规范、发现机制、起步流程、打包嵌入与测试方法读完即可上手开发一个可被 Hydra 自动发现的自定义插件。开发前必须遵守的两条硬性规则在动手写任何代码之前先记住插件能够被 Hydra 发现的根本前提。违反任意一条插件都将无法被加载插件必须位于顶层命名空间模块hydra_plugins之下。插件既可以是一个独立的 Python 包也可以是既有 Python 包的一部分但两种情况下都必须放在hydra_plugins这个顶层模块中。放在mylib.hydra_plugins这样的嵌套位置是不会被发现的因为 Hydra 只从顶层hydra_plugins模块开始递归扫描。不要在hydra_plugins目录里放置__init__.py。hydra_plugins必须是真正的命名空间包namespace package如果加上__init__.py可能破坏其他已安装的 Hydra 插件的正常发现。这一约束在源码层面有对应强制校验插件实例化逻辑 中任何插件类都必须通过is_in_toplevel_plugins_module检查即类名必须以hydra_plugins.或hydra._internal.core_plugins.开头否则直接抛出RuntimeError(Invalid plugin ... not the hydra_plugins package)。插件发现机制Hydra 启动时究竟发生了什么插件发现过程在每次 Hydra 启动时都会执行。其完整逻辑位于 plugins.py 的_scan_all_plugins方法核心流程如下将内建插件模块hydra._internal.core_plugins与外部插件顶层模块hydra_plugins作为扫描入口plugins.py。若环境中未安装任何插件import hydra_plugins会抛ImportError此时被静默忽略。通过pkgutil.walk_packages递归遍历hydra_plugins下所有子模块逐一执行import。对每个成功导入的模块用inspect.getmembers遍历模块成员凡是继承自Plugin且不是抽象类的具体类_is_concrete_plugin_type见 plugins.py都被收集为候选插件。收集到的插件按类型注册进plugin_type_to_subclass_list其中继承ConfigSource的类还会额外注册到SourcesRegistryplugins.py。导入失败的模块不会导致 Hydra 崩溃只会输出UserWarning提示插件与当前 Hydra 版本不兼容或存在 bug建议卸载或升级plugins.py。Hydra 支持的具体插件类型PLUGIN_TYPES见 plugins.py包括插件类型职责Plugin所有插件的抽象基类ConfigSource自定义配置来源文件系统之外如数据库、对象存储CompletionPlugin为不同 Shell 提供命令行补全Launcher自定义任务的启动与调度方式Sweeper定义超参搜索/扫描策略SearchPathPlugin扩展配置搜索路径用_前缀保护重模块导入开销即启动开销这是发现机制中最容易被忽略、却对性能影响最大的细节任何位于hydra_plugins下的模块都会被导入并扫描因此导入缓慢的模块会拖慢所有 Hydra 应用的启动——因为插件发现发生在每次 Hydra 初始化时。解决办法是用单下划线_前缀命名文件以_但不是__开头的模块会被扫描器跳过。例如_my_plugin_lib.py不会被导入扫描而my_plugin_lib.py会被导入。对应的实现是 plugins.py 中的这段判断模块名以_开头且不以__开头时直接continue。官方示例插件也把这一建议写进了代码注释example_launcher.py如果插件引入了任何导入耗时超过零点几秒的库应当惰性导入典型做法是在launch()方法内部再 import或者把重依赖放进_前缀的文件中。起步基于示例插件开始你的第一个插件官方推荐的开发方式是直接复制一个现成的示例插件作为骨架而不是从零搭建。仓库 examples/plugins 下提供了六种类型的官方示例插件example_configsource_plugin自定义配置源example_generic_plugin通用插件骨架example_launcher_plugin自定义 Launcherexample_registered_plugin手动注册插件的示例example_searchpath_plugin搜索路径插件example_sweeper_plugin自定义 Sweeper完整起步流程如下复制示例插件子树到一个独立工程目录例如以example_launcher_plugin为基础。编辑setup.py并重命名插件模块例如把hydra_plugins.example_xyz_plugin改成hydra_plugins.my_xyz_plugin。注意包名name与命名空间包结构packages都要同步修改。安装插件在插件目录内执行pip install -e .。可编辑安装能让后续代码修改即时生效是插件迭代开发的标准姿势。验证插件被发现运行示例应用并加上--info plugins参数$ python example/my_app.py --info plugins Installed Hydra Plugins *********************** ... Launcher: --------- MyLauncher ...运行示例应用确认插件确实在起作用例如 Launcher 真的被调用、日志按预期输出。可选嵌入已有应用若想将插件并入现有应用/库把hydra_plugins目录移入你的包中并确保它以命名空间包形式打进最终发行包典型做法是在setup.py中from setuptools import find_namespace_packages, setup setup( ..., packagesfind_namespace_packages(include[hydra_plugins.*]), )find_namespace_packages(include[hydra_plugins.*])会以命名空间包方式收集所有hydra_plugins子包同时避免引入__init__.py。可参考官方示例的完整写法 setup.py。持续开发确保官方推荐测试见下文测试一节与你新增的测试全部通过。源码级剖析一个 Launcher 插件是怎么写出来的以仓库自带的 example_launcher_plugin 为例看一个完整插件的组成部分。其目录结构为example_launcher_plugin/ ├── example/ │ ├── conf/ │ │ ├── config.yaml │ │ └── db/ # mysql.yaml / postgresql.yaml │ └── my_app.py # 示例应用 ├── hydra_plugins/ │ └── example_launcher_plugin/ │ ├── __init__.py # 注意这是插件子模块内的空 inithydra_plugins 顶层没有 │ ├── example_launcher.py │ └── py.typed ├── tests/ │ └── test_example_launcher_plugin.py ├── MANIFEST.in ├── README.md └── setup.py插件类与配置LauncherConfig 与 ConfigStore插件类的实现位于 example_launcher.py。整个文件做三件事第一定义插件的结构化配置并用 ConfigStore 注册。插件通过一个 dataclass 声明自己的可配置项_target_指向插件类本身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 )ConfigStore.instance().store(grouphydra/launcher, nameexample, ...)相当于为 Hydra 注册了一个名为example的 launcher 配置节点因此应用配置里才能写override hydra/launcher: example来启用它。第二实现Launcher抽象基类的两个核心方法——setup()和launch()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 和 bar 来自插件的配置LauncherConfig self.foo foo self.bar bar def setup(self, *, hydra_context, task_function, config) - None: ... def launch(self, job_overrides, initial_job_idx) - Sequence[JobReturn]: ...launch()的入参job_overrides是一个List[List[str]]每个内层 list 对应一次 job 的命令行覆盖参数返回值是按输入顺序排列的JobReturn数组对应run_job的返回值。从launch()的实现可以看到 Hydra 任务运行的关键调用链用self.hydra_context.config_loader.load_sweep_config(self.config, overrides)为每次 job 合成独立配置通过Singleton.get_state()/Singleton.set_state()在可能的子进程间传递单例状态调用run_job(...)真正执行任务函数并传入job_dir_keyhydra.sweep.dir与job_subdir_keyhydra.sweep.subdir控制输出目录。示例中还演示了跨进程 Launcher 必须注意的两点子进程中要恢复 Singleton 状态同进程内串行调用run_job后要重新configure_log还原 Hydra 自身的日志配置。第三把重依赖放进_前缀文件或延迟导入避免拖慢所有 Hydra 应用的启动见上文插件发现机制。示例应用与配置联动示例应用 my_app.py 就是一个普通的hydra.main应用关键在于其配置 config.yamldefaults: - db: mysql - override hydra/launcher: exampleoverride hydra/launcher: example将启动器从默认的 basic_launcher 切换为插件提供的example。运行 multirun 时输出会显示自定义 Launcher 接管了任务调度摘录自 README.md$ 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 : dbpostgresqlLauncher 类构造函数收到的foo10, barabcde正是来自LauncherConfig的默认值——这印证了配置注册与类实例化之间的对应关系。测试复用官方的 Launcher/Sweeper 测试套件插件开发文档明确要求确保官方推荐测试通过。示例插件 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 运行官方 Launcher 测试套件。注意本 Launcher 应提供 hydra/launcher/example.yaml。 mark.parametrize(task_launcher_cfg, extra_flags, [({}, [-m, hydra/launcherexample])]) class TestExampleLauncherIntegration(IntegrationTestSuite): 用官方集成测试套件跑通该 Launcher。其中test_discovery直接调用Plugins.instance().discover(Launcher)验证插件被扫描并注册为 Launcher 类型而LauncherTestSuite与IntegrationTestSuite分别位于 hydra/test_utils/launcher_common_tests.py前者定义于该文件第 25 行后者定义于第 432 行是 Hydra 面向所有 Launcher 插件开放的通用验证套件覆盖单元级与端到端两级测试。Sweeper 插件同样有对应的launcher_common_tests/ sweeper 公共测试基础设施可供复用。小结插件开发的核心心智模型可以概括为三句话位置决定一切插件必须放在顶层命名空间包hydra_plugins下、且不能有顶层__init__.py否则永远不会被发现。发现机制即性能约束每次 Hydra 启动都会导入并扫描hydra_plugins下所有模块重依赖要用_前缀文件隔离或惰性导入避免拖慢所有应用的启动。复制示例胜过从零开始从 examples/plugins 中选择最接近的示例插件作为骨架借助ConfigStore注册插件配置、用find_namespace_packages(include[hydra_plugins.*])打包并复用官方LauncherTestSuite/IntegrationTestSuite测试套件即可在最短路径上交付一个质量达标的 Hydra 插件。【免费下载链接】hydraHydra is a framework for elegantly configuring complex applications项目地址: https://gitcode.com/GitHub_Trending/hyd/hydra创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表