Prhub

#2272 refactor: move the FSDP backend out of experimental

原始 PR 作者 Zhichenzzz 合并时间 2026-08-08 11:08 文件变更 50 提交数 2 评论 0 代码增减 +67 / -77

执行摘要

FSDP 后端迁出 experimental,升级为正式后端模块

PR body 明确说明:FSDP 后端已从 experimental 毕业,应成为与 megatron_utilssglang_utilstraining_utils 平级的正式后端模块;且 experimental/ 下已无其他内容,可以整体移除。迁移后包减少一层嵌套,配合把跨包相对导入改为绝对导入,使包位置无关,并与仓库现有代码风格一致。

值得快速浏览:这是大型仓库中“大范围纯移动”重构的工程样板,重点观察三点:一是相对导入转绝对导入使包位置无关的做法;二是源码移动与 docker 补丁、CODEOWNERS 等基础设施的联动检查清单;三是用全量 fast 测试证明“零行为变化”的验证方式。无需精读逻辑实现。

讨论亮点

Review 讨论极少,仅有一条。fzyzcjy 给出 APPROVED,并附言 “LGTM if it is pure move”,即认可的前提是确认这是纯移动而非行为变更。PR 的实际改动(67 增 / 77 删、仅 import 与路径引用变化、全量 fast 测试通过)证实了这一点,无未解决的疑虑。

实现拆解

  1. 目录迁移:将 miles/backends/experimental/fsdp_utils/ 整体 git mv 为 miles/backends/fsdp_utils/,删除已空的 experimental/ 目录;共涉及 50 个文件,涵盖源码、测试、构建脚本与基础设施配置。
  2. 导入改写:包内 7 处跨包相对导入改为绝对导入(如 from ...training_utils.parallel 改为 from miles.backends.training_utils.parallel),包内相对导入(...)保持不变;代表文件为 actor.pyparallel.pymodels/qwen3_moe.py。这样包位置独立,未来再次移动时无需调整点号数量。
  3. 构建与基础设施联动docker/Dockerfile.rocm 中创建并拷贝 sglang_attn_bridge 的目标路径、docker/amd_patch/latest/miles.patchdocker/npu_patch/miles.patch 的 patch 目标路径均从 experimental/fsdp_utils 更新为 fsdp_utils,避免 ROCm/NPU 构建失效;.github/CODEOWNERS 原地更新条目路径,保持其在 /miles/backends/ 之后的位置,使更具体规则仍生效。
  4. 外部引用点同步miles/utils/arguments.pyload_fsdp_argsvalidate_hybrid_shard_args 的动态导入、miles/ray/train/actor_factory.py 中的 FSDP actor 引用均切换到新路径。setup.py 使用 find_packages(include=["miles*"]),CI 工作流未按旧路径过滤,均无需改动。
  5. 验证与未纳入pre-commit run --all-files 通过;tests/fast/backends/ 447 项通过;tests/fast/ 4547 项通过,唯一失败 test_tracking_backend_end_to_end_local_ray 在未修改的 main 上同样失败,与本 PR 无关。docs/developer/experimental-features.mddocs/developer/architecture.md 仍描述旧位置,刻意留作 code-only 后续跟进。
文件 模块 状态 重要度
miles/backends/fsdp_utils/actor.py FSDP 后端 renamed 6.65
miles/backends/fsdp_utils/parallel.py FSDP 后端 renamed 5.79
miles/utils/arguments.py 参数解析 modified 5.11
tests/fast/backends/test_fsdp_hf_compat.py 单元测试 modified 5.52
docker/Dockerfile.rocm 构建脚本 modified 4.5

关键符号

FSDPTrainRayActor build_fsdp_meshes load_fsdp_args validate_hybrid_shard_args

关键源码片段

miles/backends/fsdp_utils/actor.py rename-or-move

FSDP 后端核心 actor,搬迁后集中展示了跨包相对导入转绝对导入的改造方式,是验证纯移动的主样本。

# miles/backends/fsdp_utils/actor.py
# 搬迁自 miles/backends/experimental/fsdp_utils/actor.py。
# 关键变化:包层级从 experimental/fsdp_utils 变为 fsdp_utils,
# 原本指向 training_utils 的跨包相对导入(...training_utils)改写成绝对导入,
# 使包位置无关、与仓库代码风格一致;包内相对导入(. / ..)保持不变。
import logging
import os
import random
from argparse import Namespace
from contextlib import ExitStack
from typing import TYPE_CHECKINGimport ray
import torch
import torch.distributed as dist
from tqdm import tqdm# 原 : from miles.backends.experimental.fsdp_utils.adaptations import routing_replay
from miles.backends.fsdp_utils.adaptations import routing_replay
# 以下原先通过 ...training_utils 相对导入,现改为绝对导入
from miles.backends.training_utils.ci_utils import check_grad_norm
from miles.backends.training_utils.data import DataIterator, get_batch, get_data_iterator, get_rollout_data
from miles.backends.training_utils.log_utils import (
    aggregate_forward_results,
    aggregate_train_losses,
    log_rollout_data,
    log_train_step,
)
from miles.backends.training_utils.loss import compute_advantages_and_returns, get_log_probs_and_entropy, loss_function
from miles.backends.training_utils.parallel import get_parallel_state, set_parallel_state
from miles.ray.train_actor import TrainRayActor
from miles.utils import train_dump_utils, train_metric_utils
# 原 : from ....utils.profile_utils import TrainProfiler
from miles.utils.profile_utils import TrainProfiler
from miles.utils.timer import Timer, inverse_timer, timer
from miles.utils.tracking_utils.tracking import init_tracking# 包内相对导入保持不变,因为模块间相对位置没有变
from . import checkpoint
from .adaptations.class_patches import apply_class_patches, apply_model_instance_patches
from .adaptations.packing import apply_packing
from .adaptations.post_load_fixups import apply_post_load_fixups
from .adaptations.precision import apply_fp32_master, precision_forward_context, resolve_precision_policy
from .lr_scheduler import get_lr_scheduler
from .parallel import create_fsdp_parallel_state
from .update_weight_utils import UpdateWeightFromDistributed, UpdateWeightFromTensorlogger = logging.getLogger(__name__)
​
​
class FSDPTrainRayActor(TrainRayActor):
    """纯 HF+FSDP 训练的简化 TrainRayActor。    rank 0 初始化原生 HF 模型(其余 rank 在 meta 上),外包 FSDP2,并提供 train / save /
    update_weights 钩子;权重同步采用 rank 0 汇总完整 state_dict 后逐张量广播。
    本次重构仅改变模块路径,类行为没有变化。
    """
miles/backends/fsdp_utils/parallel.py rename-or-move

展示 FSDP 并行状态构建逻辑搬迁后函数体零变化、仅 import 路径变化的典型样例。

# miles/backends/fsdp_utils/parallel.py
# 搬迁后唯一的代码变化是 import:
# 原 : from ...training_utils.parallel import ParallelState
# 现 : from miles.backends.training_utils.parallel import ParallelState
# 函数体与搬迁前完全一致,证明这是纯移动。
import logging
from argparse import Namespaceimport torch.distributed as dist
from torch.distributed.device_mesh import DeviceMesh, init_device_mesh# 跨包相对导入改为绝对导入
from miles.backends.training_utils.parallel import ParallelState
from miles.utils.distributed_utils import get_gloo_group
from miles.utils.ft_utils.process_group_utils import GroupInfologger = logging.getLogger(__name__)
​
​
def build_fsdp_meshes(
    device_type: str,
    world_size: int,
    context_parallel_size: int,
    dp_replicate_size: int,
) -> dict[str, DeviceMesh]:
    """构建数据并行/上下文并行视图与 FSDP2 分片 mesh。"""
    data_parallel_size = world_size // context_parallel_size
​
    # 先建立 (dp, cp) 二维 mesh,再从 dp 维派生 FSDP 分片 mesh
    dp_cp_mesh = init_device_mesh(
        device_type,
        mesh_shape=(data_parallel_size, context_parallel_size),
        mesh_dim_names=("dp", "cp"),
    )
    dp_mesh = dp_cp_mesh["dp"]
    fsdp_mesh = dp_mesh
    if dp_replicate_size > 1:
        # 需要副本时,把 dp 维拆成 (dp_replicate, dp_shard) 两个维度
        fsdp_mesh = dp_mesh._unflatten(
            0,
            (dp_replicate_size, data_parallel_size // dp_replicate_size),
            ("dp_replicate", "dp_shard"),
        )
​
    return {
        "dp_cp": dp_cp_mesh,
        "dp": dp_mesh,
        "cp": dp_cp_mesh["cp"],
        "fsdp": fsdp_mesh,
    }

评论区精华

纯移动的确认 设计

fzyzcjy 在唯一一条 review 中写道 “LGTM if it is pure move”,即认可该重构,前提是确认它只是路径移动而非行为变化。

结论:PR 的验证结果(67 增 / 77 删、只改 import 与路径引用、tests/fast 4547 项通过)证实了纯移动,review 通过并已合入。 · 已解决

风险与影响

  • 回归风险(低):纯目录移动不改变逻辑,但 50 个文件的 7 处跨包导入与外部引用点(arguments.pyactor_factory.py)若有遗漏会直接引发 ImportError;已通过 tests/fast 4547 项覆盖,但 fast 测试不含真实 GPU 训练路径。
  • 构建风险(中)Dockerfile.rocmamd_patchnpu_patch 均为路径硬编码,若不同步会破坏 ROCm/NPU 镜像构建或 patch 应用;本 PR 已同步,但当前 CI 中 MI300X runner 已被禁用(见 PR#2230),短期无法自动验证。
  • 兼容性风险(低):任何仍引用 miles.backends.experimental 的外部脚本或插件会失效;experimental 路径本身不承诺稳定,且仓库内引用已全部更新。
  • 文档风险(低):两处 developer 文档仍指向旧路径,可能误导后续阅读者,已明确留作 follow-up。
  • 用户/开发者:运行时行为无变化;内部开发者的 FSDP 后端 import 路径统一为 miles.backends.fsdp_utils,后续基于该后端的 PR 需使用新路径。
  • 系统结构experimental/ 目录消失,后端模块布局与 megatron_utils 等对齐,降低包嵌套深度和 import 复杂度。
  • 团队协作:FSDP 后端正式化后,相关改动(如 R3 路由重放、GDN packing)将落在稳定路径上;文档同步工作被显式留出,便于后续单独 review。
纯移动重构(回归风险低) docker/ 补丁路径引用同步 外部 imports 需全量核对 docs 仍指向旧路径

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论