Flower 运行时自动安装 App 依赖:pyproject.toml 声明、uv sync 隔离环境与 SuperNode/SuperExec 启用方式
【免费下载链接】flowerFlower: A Friendly Federated AI Framework项目地址: https://gitcode.com/GitHub_Trending/flo/flower
本文讲解 Flower 框架的运行时依赖安装(runtime dependency installation)机制:Flower 如何读取 Flower App 在pyproject.toml中声明的 Python 依赖,用uv sync在隔离的运行环境中自动安装这些依赖,并在 App 执行完毕后清理环境。读完本文,你将掌握如何在 SuperLink、SuperNode 以及 process 隔离模式下启用或关闭该机制,并能结合源码理解$FLWR_HOME/runtime-envs隔离环境从创建、激活到删除的完整生命周期。
机制定位:不同 App 需要不同依赖时,运行时自动补齐
Flower App 在pyproject.toml中声明自己的 Python 包依赖。Flower 可以读取这份元数据,在 App 被执行之前自动为其安装依赖(来源文档:how-to-install-app-dependencies-at-runtime.rst)。
这个机制解决的核心问题是宿主环境的依赖冲突与预装成本:
- 当不同 App 依赖不同的 Python 包(甚至不同版本)时,运行时安装可以避免"把所有 App 的依赖都塞进同一个宿主环境";
- 当运行 Flower 的机器没有预装全部 App 依赖时,可以在 App 启动时按需补齐。
对于生产部署,文档同时给出了反向策略:如果启动时间、网络访问条件或镜像内容需要精确控制,仍然可以选择预装依赖 + 关闭运行时安装。
三种组件的默认行为不同,这是部署前必须知道的关键差异:
| 组件 | 运行时依赖安装默认状态 | 控制方式 |
|---|---|---|
| SuperLink | 默认开启 | 传--disable-runtime-dependency-installation或设置环境变量FLWR_DISABLE_RUNTIME_DEPENDENCY_INSTALLATION=1来关闭 |
| SuperNode | 默认关闭 | 启动时传--allow-runtime-dependency-installation开启 |
| SuperGrid | 自动依赖安装已启用 | 但不改变接入 SuperGrid 的 SuperNode 的默认值,SuperNode 运维者仍需单独决定是否开启 |
SuperLink 侧的默认值逻辑可以在源码中直接验证:flower_superlink.py 中的_runtime_dependency_install_default()返回os.getenv(FLWR_DISABLE_RUNTIME_DEPENDENCY_INSTALLATION) != "1",即"默认开启、仅当环境变量显式设为1时关闭"。环境变量名定义在 constant.py。同时源码中保留了向后兼容:如果用户仍传入旧的--allow-runtime-dependency-installation,SuperLink 会打印弃用警告(flower_superlink.py),提示该行为现已默认开启、可用--disable-runtime-dependency-installation关闭。
声明 App 依赖:[project].dependencies
要让运行时安装生效,第一步是在 Flower App 的pyproject.toml中把依赖写进[project].dependencies段:
[project] name = "my-flower-app" version = "1.0.0" dependencies = [ "flwr>=2.0.0", "torch==2.12.0", "torchvision==0.27.0", ]这里有两个源码可佐证的行为细节:
flwr依赖会被跳过。Flower 读取这份列表做运行时安装时,会剔除flwr项,以免 App 声明的flwr版本要求把正在运行的 Flower 进程所依赖的安装给替换掉。实现见 dependency_installer.py 的_exclude_flwr_dependencies():它从 PEP 508 风格的依赖串中提取包名(按下划线/连字符归一化)并与flwr比对;被跳过时会在日志中打一条 WARNING 说明原因。- 列表必须是合法的依赖列表。
_get_project_dependencies()通过get_project_config读取pyproject.toml的[project].dependencies,如果该字段不是列表会直接抛出RuntimeError,因此依赖声明格式错误会导致启动失败而不是被静默忽略。
实践建议(文档原文 tip):保持pyproject.toml与 App 实际需要的依赖同步。凡是没有写进[project].dependencies的包,运行时自动安装不会替你装上——它只是忠实执行uv sync对这份声明的解析结果。
在 SuperNode 上启用:--allow-runtime-dependency-installation
SuperNode 负责运行ClientApp代码。要让 SuperNode 为接收到的 App 安装其声明的依赖,启动时需要显式传入--allow-runtime-dependency-installation。以下示例展示一个 SuperNode 连接本地 SuperLink 做原型验证的启动方式:
$ flower-supernode \ --superlink 127.0.0.1:9092 \ --insecure \ --allow-runtime-dependency-installation该命令行参数不是各组件各自手写的,而是由公共的参数解析函数统一注入:args.py 中的add_args_runtime_dependency_install()为 CLI 注册--allow-runtime-dependency-installation(store_true),在 SuperLink 场景下还会额外注册与其互斥的--disable-runtime-dependency-installation(store_false),二者共同写入同一个runtime_dependency_install属性。App 进程侧的通用参数解析(add_args_flwr_app_common)也调用了同一函数,保证flwr-*app命令支持该标志。
何时选择它:当 SuperNode 宿主机允许在运行时安装 Python 包时使用;如果宿主机无法访问包索引,或你希望对已安装的包做更严格的管控,文档给出的替代方案是在 SuperNode 环境里预装 ClientApp 的依赖,而不是依赖运行时安装。
process 隔离模式:在flower-superexec上开启
当以--isolation=process运行 SuperLink 或 SuperNode 时,App 实际运行在独立启动的 SuperExec 进程托管的子进程中,此时传给flower-superlink/flower-supernode的运行时依赖安装标志不会传递到 App 进程。这种模式下需要在flower-superexec上单独开启:
$ flower-superexec \ --runtime-api-address <runtime-api-address> \ --plugin-type <choice-of-plugin> \ --allow-runtime-dependency-installation这条规则在源码调用链中有对应证据:SuperLink 在 subprocess 隔离模式下自动拉起 SuperExec 时,会依据自身的runtime_dependency_install开关决定是否在 SuperExec 命令中追加--allow-runtime-dependency-installation(见 flower_superlink.py 的_get_superexec_command());而 SuperExec 侧的SubprocessExecutor只有在执行规格标记了runtime_dependency_install时,才会在启动 App 子进程的参数列表中追加该标志(subprocess_executor.py)。这也解释了为什么 process 模式下"谁启动 SuperExec,谁就该负责把这个开关传给 SuperExec"。
安装流程源码解析:六步机制如何落地
文档对安装流程的官方描述共六步:读取[project].dependencies→ 在$FLWR_HOME/runtime-envs下创建隔离运行环境 → 在 App 项目目录用当前 Python 可执行文件调用uv sync→ 将声明的依赖装入该环境 → 将该环境激活到当前 App 进程 → App 执行完毕后删除环境。这六步全部收敛在 dependency_installer.py 的install_app_dependencies()中(L62-L167),下面逐步对照源码说明其实现要点。
1. 前置检查:uv必须可用
_ensure_uv_available()(L264-L275)会先执行sys.executable -m uv --version探测;若失败则抛出RuntimeError,要求先安装uv。也就是说运行时依赖安装以宿主 Python 环境中可通过-m uv调用uv为前提,这是使用该机制时的硬性依赖。
2. 读取依赖并剔除flwr
_get_project_dependencies()解析pyproject.toml,_exclude_flwr_dependencies()过滤掉flwr(前文已述)。若过滤后列表为空,日志会记录"未发现非 flwr 依赖,继续创建隔离环境"——即即使没有需要安装的第三方包,仍会创建并激活一个隔离环境,保证每次运行的环境边界一致。
3. 创建按启动隔离的环境目录
环境目录由_create_runtime_env_dir()(L192-L208)生成,落在get_flwr_home() / "runtime-envs" / <env_name>下(_RUNTIME_ENV_DIR = "runtime-envs"),对应文档所说的$FLWR_HOME/runtime-envs。目录命名策略保证"每次运行各用一个环境":
- 有
run_id时直接用str(run_id)作为目录名; - 否则用
项目路径 SHA-256 前 12 位 - 启动标识 SHA-256 前 12 位 - 随机 8 位 nonce拼接,确保不同 App、不同启动之间目录不冲突。
从源码结构看,这一"每运行一环境"的设计正是文档所述"并发运行的 App 不会互相修改对方 Python 包"的实现基础。
4. 执行uv sync并控制安装范围
实际执行的命令(L127-L141)为:
<当前Python> -m uv sync \ --python <当前Python> \ --no-install-project \ --no-install-package flwr \ --inexact参数含义与流程的对应关系:
--python sys.executable:锁定使用当前正在运行的 Python 解释器创建环境,保证 App 运行时的解释器版本与 Flower 进程一致;--no-install-project:不安装 App 项目本身(App 代码来自 Flower App Bundle,见下文);--no-install-package flwr:命令行层面再次排除flwr,与 Python 层的_exclude_flwr_dependencies()形成双重保险;- 通过环境变量
UV_PROJECT_ENVIRONMENT指向本次运行专属的环境目录,让uv sync把包装进该隔离环境而非宿主环境; - 若上层提供了依赖索引 URL(
--index-url),会追加到命令中;在开源版中该解析钩子当前返回None(_resolve_index_url_from_ee() 标注了对应解析器在flwr.ee侧尚未就绪),默认走标准索引。
uv sync的输出会流式转发到 Flower 日志,并逐行解析+ package前缀行记录实际安装到的包;命令失败时抛出RuntimeDependencyInstallationError,App 启动随之失败。
5. 激活环境:改写sys.path、PATH与VIRTUAL_ENV
_activate_runtime_env()(L211-L230)在当前进程内完成激活:把新环境的site-packages(按 Linux/macOS 的lib/pythonX.Y/site-packages与 Windows 的Lib/site-packages查找)插入sys.path最前端,把环境的bin/Scripts目录前置进PATH,并设置VIRTUAL_ENV。之后 App 代码加载到的就是这次运行时安装的依赖版本。
6. 退出时清理环境
_register_runtime_env_cleanup()(L245-L251)在创建环境后立即向 Flower 的退出处理器注册cleanup_app_runtime_environment(),App 执行完成后用shutil.rmtree(..., ignore_errors=True)尽力删除整个环境目录——即文档第六步"删除环境"。这是 best-effort 清理:删除失败不会导致报错,但正常路径下每次运行结束都会回收磁盘。
两点边界说明(来自文档与源码的一致结论):
- App 代码本身不经过这套安装流程。App 代码来自 Flower App Bundle(FAB),运行时依赖安装只负责装该 App 声明的包依赖;
- 并发安全由"每运行独立环境 + 进程内
sys.path前置激活"共同保证。
关闭机制与运维选型
关闭运行时安装的方式按组件区分:
- SuperLink:启动参数加
--disable-runtime-dependency-installation,或在其启动前设置环境变量FLWR_DISABLE_RUNTIME_DEPENDENCY_INSTALLATION=1。源码中两者等价——环境变量检查位于_runtime_dependency_install_default()(flower_superlink.py),命令行标志通过 args.py 中store_false写入同一属性; - SuperNode / SuperExec:不传
--allow-runtime-dependency-installation即为关闭(App 进程级默认值RUNTIME_DEPENDENCY_INSTALL = False,见 constant.py)。
选型上的判断标准可以直接沿用文档的表述:
| 场景 | 推荐做法 |
|---|---|
| 多个 App 依赖不同/冲突的第三方包 | 开启运行时安装,让每次运行各用隔离环境 |
| 宿主机没有预装全部 App 依赖 | 开启运行时安装(需保证可访问包索引) |
| 生产环境:启动时间敏感、网络受限、要求镜像内容精确可控 | 预装依赖到 Flower 运行环境,并关闭运行时安装 |
| SuperNode 宿主机禁止运行时装包 | 在 SuperNode 环境预装 ClientApp 依赖,不开启该标志 |
相关测试用例可作进一步验证入口:dependency_installer_test.py 覆盖了runtime-envs目录命名、安装失败抛错、索引 URL 解析等关键路径;flower_superlink_test.py 则验证了环境变量与命令行标志对默认开关的组合行为。若你的 SuperNode 需要接入 SuperGrid,接入方式的默认值与本机制的关系可参考 how-to-connect-supernodes-to-supergrid.rst。
【免费下载链接】flowerFlower: A Friendly Federated AI Framework项目地址: https://gitcode.com/GitHub_Trending/flo/flower
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考