# PR #2272 完整报告

- 仓库：`radixark/miles`
- 标题：refactor: move the FSDP backend out of experimental
- 合并时间：2026-08-08 11:08
- 原文链接：http://prhub.com.cn/radixark/miles/pull/2272

---

# 执行摘要

- 一句话：FSDP 后端迁出 experimental，升级为正式后端模块
- 推荐动作：值得快速浏览：这是大型仓库中“大范围纯移动”重构的工程样板，重点观察三点：一是相对导入转绝对导入使包位置无关的做法；二是源码移动与 docker 补丁、CODEOWNERS 等基础设施的联动检查清单；三是用全量 fast 测试证明“零行为变化”的验证方式。无需精读逻辑实现。

# 功能与动机

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

# 实现拆解

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.py`、`parallel.py`、`models/qwen3_moe.py`。这样包位置独立，未来再次移动时无需调整点号数量。
3. **构建与基础设施联动**：`docker/Dockerfile.rocm` 中创建并拷贝 `sglang_attn_bridge` 的目标路径、`docker/amd_patch/latest/miles.patch` 与 `docker/npu_patch/miles.patch` 的 patch 目标路径均从 `experimental/fsdp_utils` 更新为 `fsdp_utils`，避免 ROCm/NPU 构建失效；`.github/CODEOWNERS` 原地更新条目路径，保持其在 `/miles/backends/` 之后的位置，使更具体规则仍生效。
4. **外部引用点同步**：`miles/utils/arguments.py` 中 `load_fsdp_args` 与 `validate_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.md` 与 `docs/developer/architecture.md` 仍描述旧位置，刻意留作 code-only 后续跟进。

关键文件：
- `miles/backends/fsdp_utils/actor.py`（模块 FSDP 后端；类别 source；类型 rename-or-move；符号 FSDPTrainRayActor）: FSDP 后端核心 actor，搬迁后集中展示了跨包相对导入转绝对导入的改造方式，是验证纯移动的主样本。
- `miles/backends/fsdp_utils/parallel.py`（模块 FSDP 后端；类别 source；类型 rename-or-move；符号 build_fsdp_meshes）: 展示 FSDP 并行状态构建逻辑搬迁后函数体零变化、仅 import 路径变化的典型样例。
- `miles/utils/arguments.py`（模块 参数解析；类别 source；类型 dependency-wiring；符号 load_fsdp_args, validate_hybrid_shard_args）: FSDP 后端参数入口的外部引用点，搬迁后动态导入路径同步更新，是外部依赖检查的关键样本。
- `tests/fast/backends/test_fsdp_hf_compat.py`（模块 单元测试；类别 test；类型 test-coverage）: 测试文件中所有 experimental 路径 import 与 logger 名同步更新，是测试配套随源码迁移的完整样例。
- `docker/Dockerfile.rocm`（模块 构建脚本；类别 infra；类型 configuration）: ROCm 构建脚本中创建并拷贝 sglang_attn_bridge 的路径依赖旧目录，须同步更新，否则 ROCm 镜像构建失败。

关键符号：FSDPTrainRayActor, build_fsdp_meshes, load_fsdp_args, validate_hybrid_shard_args

## 关键源码片段

### `miles/backends/fsdp_utils/actor.py`

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

```python
# 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_CHECKING

import 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, UpdateWeightFromTensor

logger = 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`

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

```python
# 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 Namespace

import 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 GroupInfo

logger = 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,
    }

```

# 评论区精华

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

- 纯移动的确认 (design): PR 的验证结果（67 增 / 77 删、只改 import 与路径引用、tests/fast 4547 项通过）证实了纯移动，review 通过并已合入。

# 风险与影响

- 风险：
 - **回归风险（低）**：纯目录移动不改变逻辑，但 50 个文件的 7 处跨包导入与外部引用点（`arguments.py`、`actor_factory.py`）若有遗漏会直接引发 ImportError；已通过 `tests/fast` 4547 项覆盖，但 fast 测试不含真实 GPU 训练路径。
 - **构建风险（中）**：`Dockerfile.rocm`、`amd_patch`、`npu_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 仍指向旧路径

# 关联脉络

- PR #2235 feat(fsdp): support rollout routing replay (R3) for fsdp backend: 直接改动 miles/backends/experimental/fsdp_utils/ 下 routing_replay、replay_routers、actor 等文件，本 PR 将这些文件迁至新路径，后续 R3 演进需基于 miles/backends/fsdp_utils。
- PR #2213 fix(fsdp): apply the GDN packing patch to dense qwen3_5 and fix patch: 同样改动 experimental/fsdp_utils 下的 qwen3_5 模型与适配文件，迁移后旧路径失效，属于同一条 FSDP 后端成熟化演进线。
- PR #2230 fix(ci): disable MI300X runner jobs: 其中包含 tests/e2e/fsdp/test_qwen3_4B_fsdp_hybrid_shard_r2s2.py 的禁用，反映 FSDP 后端 CI 覆盖正在调整，与本 PR 同属 FSDP 后端稳定化进程。