已关闭
[RFC]: MindSpeed-MM 生成类模型异构FSDP2统一框架 #576
chenpeizhe创建于 8月7日关闭于 8月29日
8月7日 添加了label:rfc
8月7日 修改了issue 的描述
8月7日 修改了issue 的描述
8月7日 修改了issue 的描述
8月11日 关联了pull request:feat: support fsdp model container
yeqm
8月21日 评论:
8月21日 评论:
👋 您好,欢迎向 MindSpeed MM 提交 Issue!
我们已收到您的反馈,感谢你对开源社区的支持。🎉
📅 处理时效: 维护团队将在 24 小时内 查看并回复您的问题(工作日)。
🚨 紧急联系: 如果您的问题非常紧急,可通过微信联系我们。
请确保 Issue 描述清晰,包含复现步骤和日志,这将帮助我们更快定位问题。谢谢!


8月29日 关闭了 issue
8月29日 添加了label:resolved
状态(Status): Draft
作者(Authors): @chenpeizhe
创建日期(Created): 2026-08-07
更新日期(Updated): 2026-08-07
相关 Issue/PR: #2
#https://gitcode.com/Ascend/MindSpeed-MM/pull/2920
#https://gitcode.com/Ascend/MindSpeed-MM/pull/2921
#https://gitcode.com/Ascend/MindSpeed-MM/pull/2922
#https://gitcode.com/Ascend/MindSpeed-MM/pull/2924
#https://gitcode.com/Ascend/MindSpeed-MM/pull/2926
#https://gitcode.com/Ascend/MindSpeed-MM/pull/2928
1. 概述
1.1 简介
支持MindSpeed-MM社区FSDP2框架生成类模型统一框架,提供高效迁移途径及高效灵活训练范式
1.2 动机
当前MindSpeed-MM社区无FSDP2统一生成模型训练框架,迁移模型工作量大,相关特性可复用性差,同时不支持异构模型统一训练
1.3 目标
构建MindSpeed-MM社区FSDP2框架生成类模型统一框架,形成统一的训练流程,高度集成可复用,支持异构模型统一训练和个性化配置
2. 用例分析
共44个文件变更
+5986
-727
*
3. 方案设计
3.1 总体方案
1. 总体架构流程图
flowchart TD A["torchrun trainer.py + yaml"] --> B["配置解析<br/>Arguments 聚合 6 个 dataclass"] B --> C["Trainer.__init__"] C --> D["initialize<br/>插件注册 / 分布式 / parallel_state"] D --> E["① 模型构建<br/>ModelContainerHub.build"] E --> F["② features 装配<br/>recompute / activation offload"] F --> G["③ FSDP2 包裹<br/>ParallelApplier"] G --> H["④ meta 物化 + 权重加载"] H --> I["optimizer / dataloader<br/>checkpointer / scheduler"] I --> J["TrainEngine.train 训练循环"]各环节代码位置:
trainer.py:489→params/argument.py:33(parallel / model / data / training / tools / features 六节)trainer.py:141:import_plugin触发@model_register注册 →init_process_group→init_parallel_statetrainer.py:181→model_container_hub.py:61(详见第 2 节)apply_features.py:92pre_fully_shard_apply:recompute 替换 forward → activation offload hookstorch_parallelize.py:61→fully_shard_parallel.py:43(详见第 5 节)trainer.py:208:FSDP 包裹之后才to_empty+ 权重加载train_engine.py:226两个关键时序约束:
fully_shard执行,FSDP hook 会被包到 checkpoint 外层而失效;to_empty物化并加载权重。1.1 整体训练逻辑分层视图
框架分六层,每层只认相邻层的契约;TrainEngine 对模型的全部假设只有三条(
model(**batch)返回.loss、梯度同步委托、checkpoint 走state_dict),这是 trainer.py 对接入新生成模型零改动的原因:flowchart TD L1["① yaml 配置层<br/>parallel / model.models / features / training"] L2["② Trainer 基建层(模型无关)<br/>分布式 / optimizer / dataloader / checkpointer"] L3["③ 组件构建层 ModelContainerHub<br/>逐子模型 meta_init / setup_weights / freeze"] L4["④ 组件管理层 ModelContainer<br/>FSDP opt-out / checkpoint 策略 / train() 保护<br/>forward 委托"] L5["⑤ 数据流编排层 GenerativePipeline<br/>prepare_conditions → noising<br/>→ predict → compute_loss"] L6["⑥ 组件实例层<br/>ae / text_encoder / predictor<br/>(各自独立 FSDP 单元)"] L1 --> L2 --> L3 --> L4 --> L5 --> L6 L2 -.->|"model(**batch) → .loss<br/>train_engine.py:152"| L4分层职责说明:
1.2 配置下发链路
yaml 各节经
Arguments聚合、校验后分发到各执行器,配置面与执行器一一对应:flowchart TD Y["yaml 六节<br/>parallel / model / data<br/>training / tools / features"] --> A["ConfigManager<br/>Arguments 聚合 + 校验"] A --> V1["ParallelArguments.model_post_init<br/>fsdp=auto 解析 / tp=1 强制"] A --> V2["compute_distributed_training<br/>跨节联动校验"] V1 --> P1["parallel.fsdp_plan<br/>apply_modules / ignored_modules<br/>混精 / prefetch / cpu_offload"] V1 --> P2["parallel 其他<br/>ring_attention_size<br/>ulysses_parallel_size / ep_plan"] A --> M["model.models.{ae,text_encoder,predictor}<br/>meta_init / dtype / freeze<br/>from_pretrained / load_format"] A --> F["features<br/>recompute_plan / activation_offload_plan"] A --> T["training / tools<br/>lr / iters / load / save<br/>profile / memory_profile"] P1 --> EX1["ParallelApplier<br/>FSDP2 包裹 + sub mesh"] P2 --> EX2["parallel_state<br/>mesh / CP 组构建"] M --> EX3["ModelContainerHub.build<br/>子模型构建 + 权重加载"] F --> EX4["FeaturesApplier<br/>recompute / activation offload"] T --> EX5["TrainEngine / Checkpointer<br/>训练循环 / 存取恢复"]关键校验点:
fully_shard_parallel_size: auto的解析、tp=1 强制及跨节联动校验都在配置解析期完成(params/parallel_args.py),非法配置在训练开始前即报错。2. 模型构建与权重初始化流程视图
2.1 构建路径总览
Trainer.get_foundation_model(trainer.py:171)按 yaml 是否含model.models分两条路:单模型走ModelHub.build(modelhub.py:97);Wan2.2 容器走ModelContainerHub.build(model_container_hub.py:61),后者对每个子模型独立执行下图流程:flowchart TD S["遍历 yaml model.models<br/>ae / text_encoder / predictor"] --> P1["pop freeze / meta_init<br/>显式传参 meta_init=子模型值<br/>model_container_hub.py"] P1 --> Q1{"meta_init ?"} Q1 -- "是" --> M1["init_empty_weights 上下文<br/>cls._from_config 建 meta 模型<br/>modelhub.py:40"] Q1 -- "否" --> M2["cls.from_pretrained<br/>直接加载真权重<br/>modelhub.py:48"] M1 --> Q2{"存在 training.load<br/>训练 ckpt ?"} M2 --> Q2 Q2 -- "有" --> W3["跳过 setup_weights<br/>权重由 checkpointer 恢复"] Q2 -- "无" --> W1["setup_weights<br/>按 load_format 加载"] W1 --> Q3{"load_format 嗅探<br/>auto 时按目录内容分流<br/>resolve_load_format (convert.py)"} Q3 -- "diffusers" --> L2["load_diffusers_weights<br/>model_type 选择键映射 pipeline"] Q3 -- "hf" --> L3["load_hf_weights"] Q3 -- "dcp" --> Q5{"源目录是 DCP ?"} Q5 -- "是 / 有效缓存已有" --> L1["load_dcp_weights"] Q5 -- "否(diffusers/hf)" --> CV["在线转换为 DCP<br/>convert_*_to_dcp<br/>写入 conversion_dir"] CV --> L1 L1 & L2 & L3 --> Z["setup_module_weights<br/>meta_init_utils.py:39<br/>加载成功则跳过随机初始化"] W3 --> Z Z --> F1["_apply_freeze<br/>冻结: eval + requires_grad_(False)"] F1 --> Q4{"非 meta 且冻结 ?"} Q4 -- "是" --> T1["立即 .to(NPU)<br/>_ms_mm_skip_to_device 可豁免"] Q4 -- "否" --> T2["留在 meta / CPU<br/>等 FSDP 包裹后统一物化"] T1 & T2 --> E["container_cls(built_models)<br/>组装 Wan2_2ModelContainer"]2.2 三个子模型的构建方式对照
wan_video_vae→WanVideoVAEBridgeWanVideoVAE(DiffusersAEModel + BaseModel)__init__内直接AutoencoderKLWan.from_pretrained;setup_weights为显式 no-op(权重已在构造内加载,防止二次加载);构建后即.to(NPU)UMT5→UMT5Bridgemodels/wan2_2/text_encoder.py的build_text_encoder)→BaseModel.setup_weights默认实现物化加载(hf 直载),特有逻辑经钩子注入:post_load重绑shared.weight↔encoder.embed_tokens.weightwan2_2_predictor→WanDiTFSDP2BridgeWanDiTFSDP2WithSetup(WanDiTFSDP2 + BaseModel)→BaseModel.setup_weights默认实现:经resolve_load_format嗅探后统一走load_checkpoint_with_conversion,diffusers 键映射由类属性weight_transform_model_type = "wan2_2"选择注册在WEIGHT_TRANSFORM_PIPELINES的Wan22DiffusersTransformPipeline(models/wan2_2/diffusers_ckpt_trans.py,表格驱动);post_load做 dtype 重 cast;加载失败直接抛异常,不静默回退随机初始化2.3 权重加载统一管线与在线 DCP 转换
模型侧的
setup_weights只保留特有逻辑,模型无关的加载编排全部收敛到 checkpoint 层三个函数:resolve_checkpoint_source(config)(convert.py):裁决权重来源(checkpoint_path优先于from_pretrained)、提取声明格式(load_format/checkpoint_format/hub_backend)、解析转换缓存目录(checkpoint_conversion_dir→dcp_cache_dir→dcp_conversion_dir→checkpoint_path,按此优先级);resolve_load_format(path, declared)(convert.py):归一化声明格式(auto/hf/diffusers/dcp,含别名),auto时按目录内容嗅探(DCP → diffusers → hf);load_checkpoint_with_conversion(model, path, fmt, model_type=...)(convert.py):统一加载入口。hf 直载;diffusers 经load_diffusers_weights加载,键映射 pipeline 由model_type(WEIGHT_TRANSFORM_PIPELINES注册键,如"wan2_2")选择,公共函数不绑定具体模型,新模型注册自己的 pipeline 并传入model_type即可复用。为什么权重加载是独立阶段(setup_weights),而不是折进
from_pretrained/_from_config:ModelHub.build的init_empty_weights()上下文内执行_from_config,权重物化必须在退出上下文之后(先materialize_meta_params再加载),无法折进_from_config;training.load门控在调用点:ModelContainerHub只在training_args.load is None时调用setup_weights(DCP 断点恢复会覆盖权重),而两个抽象 classmethod 的签名只收ModelArguments,拿不到 training 配置;from_pretrained/_from_config保持"结构构造"语义,权重加载是两阶段生命周期(结构 → 物化+加载)的第二阶段。非 meta 的子模型(如 ae)可以不走该阶段、在构造内直载,两条路径并存不冲突。setup_weights的默认实现目前放在BaseModel上(base_model.py);若希望保持BaseModel极简(仅两个抽象 classmethod),可演进为生成模型专用抽象类GenerativeBaseModel(BaseModel)承载该默认实现,调用点契约不变。在线 DCP 转换:当声明
load_format: dcp而源目录不是 DCP 时,框架先把源权重(diffusers 或 hf)加载进模型、再以 DCP iteration 0 落盘到转换缓存目录,随后从缓存目录加载;二次运行时检测到缓存目录已是 DCP,直接复用,不再重复转换。配置示例:predictor: model_id: wan2_2_predictor from_pretrained: /home/weights/Wan2.2-T2V-A14B-Diffusers/transformer_2 # diffusers 源权重 load_format: dcp # 声明走 DCP(触发在线转换) checkpoint_conversion_dir: /path/to/dcp_cache/transformer_2 # 转换缓存目录转换产物保存位置:
<checkpoint_conversion_dir>/iter_0000000/(DCP 分片)+<checkpoint_conversion_dir>/latest_checkpointed_iteration.txt(tracker 文件)。若不配置checkpoint_conversion_dir,可改用checkpoint_path指向缓存目录(此时它不再作为权重来源)。缓存有效性判断与容错(dcp_checkpointer.py):
looks_like_dcp_checkpoint_dir只在目录含有真实 DCP 内容时返回 True:tracker 文件、.distcp分片、或内含.metadata的iter_*/release子目录。被中断的转换留下的空壳iter_*目录不会被误判为可复用缓存,下次运行会自动重新转换(save 直接写入已存在的目录,无需手工清理);DistributedCheckpointer.load在 tracker 文件缺失时不再默认iteration=-1,而是自动发现唯一含.metadata的iter_*(或release)子目录进行加载;无有效候选或多个候选时抛出明确错误(提示 checkpoint 不完整或存在歧义),避免iter_-000001/.metadata之类的隐晦报错。2.4 设计要点
ModelContainerHub.build逐子模型向ModelHub.build显式传meta_init参数(可选关键字,缺省回落training_args.init_model_with_meta_device,单模型路径行为不变),使 ae 走真权重直载、text_encoder / predictor 走 meta 初始化,互不干扰;setup_weights只做"权重内容"加载;meta 参数的"实体化"(to_empty)推迟到 FSDP 包裹之后(trainer.py:208),保证 DTensor 分片布局先建立。trainer 的初始化阶段(init_model_weights,随机初始化用)会跳过setup_weights已成功加载(_weights_loaded=True)的子模型,只对随机初始化兜底的模型执行——否则已加载的权重可能被重初始化逻辑破坏(如带 padding_idx 的 Embedding 会被 empty 张量替换);modelhub._apply_configured_dtype在模型配置显式声明dtype时按配置 cast(如 wan2.2 各子模型的dtype: bf16),未配置时维持原.float()行为,无需模型侧再重写float();freeze: true→eval()+ 全参数requires_grad_(False)(model_container_hub.py:42);容器train()只切 predictor(container:185),checkpoint 只存 predictor(基类checkpoint_submodels = ("predictor",));WanFlowMatchScheduler(pipeline 持有)的 sigma 网格与 DiffSynth v1.x 一致(shift=3.0、sigma_min=0.003/1.002),与官方scheduler_config.json(flow_shift: 3.0)同口径;它不等同于 DiffSynth v2 Wan 模板(shift=5)和 diffusersFlowMatchEulerDiscreteScheduler(端点约定不同,实测 sigma 最大差 ~0.006)。对账脚本必须显式对齐该网格,禁止按上游模板"升级"(详见models/wan2_2/diffusion.py文件头注释)。3. UML 用例视图
系统边界内为框架提供的核心能力(用例),系统边界外为使用者(训练工程师)。每个用例标注其实现代码位置。
flowchart LR actor(["训练工程师"]) subgraph boundary["MindSpeed-MM FSDP2 训练框架"] UC1["UC1 配置异构子模型初始化<br/>各子模型独立 meta_init / dtype /<br/>freeze / from_pretrained / load_format"] UC2["UC2 自由选择 FSDP2 切分模块<br/>fsdp_plan.apply_modules /<br/>ignored_modules 模式配置"] UC3["UC3 配置显存优化策略<br/>recompute / activation_offload /<br/>cpu_offload"] UC4["UC4 配置混合精度与预取<br/>param_dtype / reduce_dtype /<br/>num_to_forward/backward_prefetch"] UC5["UC5 执行训练与断点恢复<br/>training.load / save (dcp)"] UC6["UC6 性能与显存剖析<br/>tools.profile / tools.memory_profile"] SYS1(("ModelContainerHub.build<br/>model_container_hub.py:61")) SYS2(("fully_shard_parallel_modules<br/>fully_shard_parallel.py:43")) SYS3(("FeaturesApplier<br/>apply_features.py:17")) SYS4(("set_modules_to_prefetch<br/>fully_shard_parallel.py:457")) SYS5(("TrainEngine + Checkpointer<br/>train_engine.py:226")) SYS6(("Profiling tools<br/>trainer.py get_dataloader 段")) end actor --> UC1 actor --> UC2 actor --> UC3 actor --> UC4 actor --> UC5 actor --> UC6 UC1 -.include.-> SYS1 UC2 -.include.-> SYS2 UC3 -.include.-> SYS3 UC4 -.include.-> SYS4 UC5 -.include.-> SYS5 UC6 -.include.-> SYS63.1 用例说明
model.models.{ae,text_encoder,predictor}各自的meta_init/dtype/freeze/from_pretrained/load_formatModelContainerHub.build逐子模型委托ModelHub.build(meta_init=...),经model_register取类构建;权重经BaseModel.setup_weights默认实现加载parallel.fsdp_plan.apply_modules/ignored_modules,支持predictor.blocks.{*}通配named_modules()模式匹配 → ignored 参数级排除 → 逐个fully_shard→ 子模型残余参数兜底 → 容器根级 opt-outfeatures.recompute_plan/activation_offload_plan/parallel.fsdp_plan.cpu_offloadFeaturesApplier.pre_fully_shard_apply:recompute 替换模块 forward 为 checkpoint 包装;activation offload 用 saved_tensors_hooks 异步搬激活到 CPUparam_dtype: bf16/reduce_dtype: fp32/num_to_*_prefetchMixedPrecisionPolicy+CPUOffloadPolicy装配进每个fully_shard调用;prefetch 按子模型 owner 过滤(_filter_prefetch_layers_by_owner,fully_shard_parallel.py:383),禁止跨子模型预取training.load/save_format: dcpTrainEngine构造时按需load();Wan2_2ModelContainer.state_dict()只导出 predictor 权重,冻结子模型永远从from_pretrained重载tools.profile.enable/tools.memory_profile.enablestart_step/end_step采集 timeline 与 memory snapshot4. 模型结构视图(类关系)
classDiagram class ModelContainer { +pipeline ; 纯编排器(非 nn.Module),默认 None +get_sub_models() +state_dict() ; 按 checkpoint_submodels 保存 +load_state_dict() ; 按子模型前缀分发,返回 _IncompatibleKeys +fully_shard() bool ; 返回 True,opt-out 根级包裹 +forward() ; pipeline 非空时委托,否则抛 NotImplementedError +set_is_last_backward() +set_requires_all_reduce() } class Wan2_2ModelContainer { +ae: WanVideoVAE +text_encoder: _SimpleTextEncoder +predictor: WanDiTFSDP2 +pipeline: WanT2VPipeline ; 数据流编排委托给它 +checkpoint_submodels = ("predictor",) ; 只存 predictor +train() ; 只切 predictor 训练态 } ModelContainer <|-- Wan2_2ModelContainer class GenerativePipelineBase { <<纯编排器,非 nn.Module>> +prepare_conditions(container, batch) +noising(container, conditions, batch) +predict(container, inputs) +compute_loss(...) +forward(container, batch) ; 编排四个阶段 } class FlowMatchT2VPipeline { +scheduler ; flow-match 调度器由 pipeline 持有 位置: models/wan2_2/pipeline_wan2_2.py(与 WanT2VPipeline 同文件合并) 组件角色约定: ae / text_encoder / predictor } class WanT2VPipeline { +task t2v/i2v 分支 +_prepare_i2v_condition() +scheduler = WanFlowMatchScheduler } GenerativePipelineBase <|-- FlowMatchT2VPipeline FlowMatchT2VPipeline <|-- WanT2VPipeline Wan2_2ModelContainer o-- WanT2VPipeline class WanVideoVAE { +forward = encode ; no_grad 下运行 } class _SimpleTextEncoder { +model: UMT5EncoderModel ; no_grad 下运行 +forward = encode ; 保证触发 FSDP2 hooks 位置: models/wan2_2/text_encoder.py } class WanDiTFSDP2 { +patch_embedding: Conv3d +text_embedding: Sequential +time_embedding: Sequential +time_projection: Sequential +blocks: ModuleList~WanAttentionBlock x 40~ +head: Head -_freqs ; RoPE 频率每 forward 预计算一次,40 层共享 } Wan2_2ModelContainer *-- WanVideoVAE Wan2_2ModelContainer *-- _SimpleTextEncoder Wan2_2ModelContainer *-- WanDiTFSDP2 class WanAttentionBlock { +norm1: WanLayerNorm +self_attn: WanSelfAttention +norm3: WanLayerNorm 或 Identity +cross_attn: WanCrossAttention +norm2: WanLayerNorm +ffn: Sequential(Linear, GELU, Linear) +modulation: Parameter(1, 6, dim) ; adaLN 6 组 shift/scale/gate } class WanSelfAttention { +q / k / v / o: Linear +norm_q / norm_k: WanRMSNorm +forward() QK-norm → RoPE → CP(Ulysses/Ring) → flash_attention → gather → o } class WanCrossAttention { +forward() 全文本序列 k_lens=None,无 RoPE / CP } class Head { +norm: WanLayerNorm +head: Linear +modulation: Parameter } WanDiTFSDP2 *-- WanAttentionBlock WanDiTFSDP2 *-- Head WanAttentionBlock *-- WanSelfAttention WanSelfAttention <|-- WanCrossAttention结构要点:
ModelContainer.__init__用setattr而非ModuleDict挂子模型(model_container.py:19),保证named_modules()路径为predictor.blocks.0扁平形式,与apply_modules模式直接对应;Wan2_2ModelContainer.train()(wan2_2_model_container.py)只调用predictor.train(mode),防止训练框架递归model.train()激活冻结 UMT5 中的 dropout;wan2_2_model_container.py:wan_video_vae→WanVideoVAEBridge、UMT5→UMT5Bridge、wan2_2_predictor→WanDiTFSDP2Bridge、wan2_2_container→Wan2_2ModelContainer,靠training.plugin列表经import_plugin动态加载。5. FSDP2 切分代码交互图
5.1 构建期:包裹流程
sequenceDiagram participant T as Trainer.get_model participant P as ParallelApplier<br/>(torch_parallelize.py:61) participant F as fully_shard_parallel_modules<br/>(fully_shard_parallel.py:43) participant M as module_name_match participant C as Wan2_2ModelContainer T->>P: model (Container, meta device) P->>F: apply_fsdp_modules F->>F: get_ignored_modules (:293)<br/>"ae" → ignored_params 全集 F->>F: 后序遍历 named_modules (:208)<br/>保证子模块先于父模块包裹 loop 每个 apply_modules 模式 F->>M: "text_encoder" → _SimpleTextEncoder 整体<br/>"predictor.blocks.{*}" → 40 x WanAttentionBlock<br/>"predictor.head" → Head F->>F: fully_shard(module, **config)<br/>mp_policy bf16/fp32 + reshard_after_forward<br/>+ CPUOffloadPolicy + ignored_params end F->>F: 残余参数兜底 (:172-181)<br/>predictor 整体再包一层 FSDP<br/>(patch/text/time_embedding 等未命中参数) F->>C: container.fully_shard() → True F-->>P: return(跳过根级包裹) P->>P: set_modules_to_prefetch (:457)<br/>_filter_prefetch_layers_by_owner (:383)<br/>禁止跨子模型预取三层模块选择语义:
apply_modules:命中的模块成为独立 FSDP 单元;ignored_modules:参数级排除,ae完全不进 FSDP 也不受 mp_policy 管理;ModelContainer.fully_shard()返回 True,model_container.py:38)。5.2 运行期:一个 train_step
sequenceDiagram participant E as TrainEngine.train_step<br/>(train_engine.py:130) participant C as Wan2_2ModelContainer<br/>forward 委托 participant PL as WanT2VPipeline<br/>(pipeline_wan2_2.py) participant V as ae (VAE) participant X as text_encoder (UMT5) participant D as WanDiTFSDP2 (predictor) participant S as WanFlowMatchScheduler<br/>(pipeline 持有) E->>E: configure_hsdp_gradient_sync<br/>非末 micro-step 关闭 FSDP 梯度同步 E->>C: model(**batch) C->>PL: pipeline.forward(container, **batch) Note over PL,V: prepare_conditions,torch.no_grad() PL->>V: ae(video) → latents Note over PL,X: 显式走 forward()<br/>以触发 FSDP2 unshard/reshard hooks PL->>X: text_encoder(prompt_ids, mask) → prompt Note over PL,S: noising PL->>S: q_sample(latents) → noised_latents, noise, timesteps Note over PL,D: predict(带梯度) PL->>D: predictor(noised_latents, t, prompt) loop 40 x WanAttentionBlock(各自独立 FSDP 单元) D->>D: unshard → x += gate * self_attn(norm1(x))<br/>→ x += cross_attn(norm3(x))<br/>→ x += gate * ffn(norm2(x)) → reshard end D-->>PL: model_output Note over PL,S: compute_loss PL->>S: training_losses → MSE x 时间步权重 PL-->>E: Wan2_2ModelOutput(loss) E->>E: loss.backward() → clip_grad_norm<br/>→ optimizer.step → lr_scheduler.steppipeline 是纯编排器(非 nn.Module),
named_modules()路径、FSDP 模式匹配、owner prefetch 过滤均不受影响;组件调用仍经各子模型forward(),FSDP2 pre/post hooks 照常触发。5.3 子模型独立切分(fsdp_plan.sub_plans)【本地暂存,未上库】
设计摘要(恢复时参考):
sub_plans允许 ModelContainer 的每个子模型独立选择是否切分、独立配置切分数(如 vae 不切、text_encoder 切 4 卡、DiT 全组 auto),通过按子模型 owner 解析有效 plan +ParallelState.get_fsdp_sub_mesh(2D(replica, shard)子 mesh)实现;配套的一次性恢复钩子解决 torch FSDP2 lazy init 对 root 模块置空post_forward_mesh_info导致冻结 root 子模型参数永不 reshard 的问题(恢复后 text_encoder 稳态显存从全量降为 1/shard_size)。语义上"参数组 ≠ 数据组",每卡数据与全局 batch 划分不变,冻结子模型与全局切分数值逐比特一致。6. 容器与框架的契约小结
支撑"冻结异构子模型 + 单 predictor 训练"形态的框架契约:
fully_shard()返回 Trueforward委托 pipelinepipeline.forward(self, **batch),子类也可自行重写train()只训 predictorstate_dict()只存 predictorcheckpoint_submodels = ("predictor",)setup_weights默认实现post_load钩子与weight_transform_model_type类属性异构 meta init 的核心机关在
ModelContainerHub.build:逐个子模型向ModelHub.build显式传meta_init,使同一容器内 ae 可直接from_pretrained加载真权重,而 text_encoder / predictor 走 meta 初始化 +BaseModel.setup_weights(按 dcp / diffusers / hf 格式嗅探加载)。新增一个生成模型的最小工作集:子模型构建类继承
BaseModel(实现from_pretrained/_from_config,可选post_load钩子)+ 一个ModelContainer子类(声明checkpoint_submodels、可选重写train())+ 一个 pipeline(flow-match T2V 家族直接复用FlowMatchT2VPipeline或按GenerativePipelineBase特化)+ yamlmodel.models与training.plugin。TrainEngine/Trainer 零改动。7. 关键修改逻辑(演进记录)
本节记录框架从"能跑通"到"可上库"的关键修改及其动机,便于理解当前架构形态的来由。
7.1 统一生成模型 pipeline(阶段 A/B)
Wan2_2ModelContainer曾一身兼两职(组件管理 + 数据流编排),每接入一个生成模型都要重写整套 forward 编排。pipelines/generative_pipeline.py的GenerativePipelineBase四阶段接口 +FlowMatchT2VPipeline通用 flow-match 数据流 + wan 特化WanT2VPipeline),容器forward默认委托pipeline.forward(self, **batch);容器从 342 行瘦到 170 行,只保留组件生命周期管理。pipeline 是纯编排器(非 nn.Module),FSDP/checkpoint/TrainEngine 全部无感。注:FlowMatchT2VPipeline与WanT2VPipeline已合并为单一文件models/wan2_2/pipeline_wan2_2.py(通用类与 wan 特化类同文件,通用基类GenerativePipelineBase仍在pipelines/下供后续模型复用)。7.2 权重加载统一(阶段 A + checkpoint 整改)
BaseModel.setup_weights默认实现(resolve_checkpoint_source → resolve_load_format → load_checkpoint_with_conversion → setup_module_weights),模型侧只留post_load钩子与weight_transform_model_type类属性;diffusers 键映射为表格驱动——checkpoint 层提供通用DiffusersKeyMapTransformPipeline与参数化convert_diffusers_key,映射表由各模型插件自带并注册进WEIGHT_TRANSFORM_PIPELINES(wan2_2 的表与 pipeline 在models/wan2_2/diffusers_ckpt_trans.py,checkpoint 层无模型知识,新增模型仅需自带表格 + 一行注册);load_diffusers_weights变为薄封装且model_type可选;convert_hf_to_dcp/convert_diffusers_to_dcp合并为_convert_to_dcp薄封装;ModelHub.build增加显式meta_init参数(替代全局标志改写,单模型路径行为不变)。looks_like_dcp_checkpoint_dir要求真实 DCP 内容(空壳iter_*不误判复用);DistributedCheckpointer.load无 tracker 时自动发现唯一有效iter_*或抛出明确错误(修复iter_-000001隐晦报错)。7.3 子模型分组切分(阶段 D1,fsdp_plan.sub_plans)【已暂存,见 7.7】
FSDPPlanConfig.sub_plans+ParallelState.get_fsdp_sub_mesh2D 子 mesh + 按 owner 解析有效 plan,一期仅冻结子模型),但按分阶段上库计划已从代码中回退,完整实现与测试备份于backups/20260728_feature_staging/(详见 7.7 节)。7.4 CP 正确性修复
ring_context_parallel.compute_mask曾惰性导入from mindspeed.utils import batch_index(该模块硬依赖 megatron),改为模块内 vendored_batch_index(与上游逐行等价的 bisect 实现,等价性单测固化)。All tensor operands to scatter/gather must have the same size;改为先 ring gather 再 ulysses 还原。ring 还原同时改用与 zigzag 切分配对的load_balanced_gather_forward_split_backward(修复普通 gather 导致 token 顺序 0,3,1,2 错乱的隐性数值 bug,ring-only 场景同样受益)。7.5 meta 初始化健壮性
meta_init: true后,trainer 初始化阶段对已加载权重的子模型仍无条件调init_model_weights→_SimpleTextEncoder无init_weights直接报错;且对含 padding_idx Embedding 的模型,该阶段的 empty 替换会销毁已加载权重。_weights_loaded != True的子模型执行init_model_weights(随机初始化路径);_SimpleTextEncoder补守卫版init_weights(loaded 时 no-op,否则委托 transformers 初始化)。7.6 数据管线瘦身
fsdp/data/新增约 4200 行中 ~40% 是 wan2.2 永不触达的拷贝(其他模型的 transform/processor/reader、conversation 模板等)。conversation.py全文件、MaskGenerator+ 19 个无用 transform、Opensora/CogVideoX/Rewardprocessor、AvVideo/read_video_av、无用 utils/常量);保留 wan2.2 实际使用项与 Wan 家族VACEVideoProcessor;diffusion 推理代码与 i2v 链路按规划保留。fsdp 数据管线作为统一依赖独立演进,不回并老路径。wan2.2 新增的 5 个数据模块单独收进fsdp/data/data_utils/generative_data_utils/(constants.py/data_transform.py/transform_pipeline.py/video_processor.py/video_reader.py),与既有utils.py、func_utils/分离,引用路径已同步更新。7.7 特性暂存说明(fsdp_subplan / cross_submodel_prefetch / condition_mbs)
以下三个特性已完成开发与验证,但按分阶段上库计划暂未包含在当前代码中,完整实现、测试与验证记录已备份至
backups/20260728_feature_staging/(MANIFEST.md 含逐项文件清单与恢复方式):FSDPPlanConfig.sub_plans按子模型 owner 覆盖shard_size/reshard/cpu_offload/enabled;ParallelState.get_fsdp_sub_mesh(2D(replica, shard)子 mesh);一期仅冻结子模型。配套冻结 root reshard 恢复:解决 torch FSDP2 lazy init 对 root 模块置空post_forward_mesh_info导致冻结 root 参数永不 reshard 的问题(text_encoder 稳态显存 ~11.2GB → ~2.8GB/shard4)。training.condition_mbs控制每 N 个 micro-step 对 VAE/TE 做一次批量条件编码并缓存逐槽消费(编码开销摊为 1/N、TE all-gather 频率降为 1/N;CP 整除时各 rank 只编码自己的槽位再交换,消除 CP 组内冗余编码)。验证记录(备份时):组编码与逐样本编码逐比特一致(含真实 UMT5 bf16、变长 padding mask);预取幂等且后续前向零新增 all-gather;冻结 root reshard 恢复循环终态 SHARDED;全量
ut_fsdp回归通过。8. 规划中的能力(未实施,设计已评审)
8.1 encoder-dp:条件编码冗余消除与批量化(condition_mbs)【本地暂存,见 7.7】
问题:① 每 micro-step 单独做 VAE/TE 编码,单次计算小、kernel 稀疏、free 大;② CP=N 时 CP 组内 N 卡对同一样本冗余编码 N 次。
方案状态【本地暂存,未上库】:
training.condition_mbs(条件阶段批处理 + 条件缓存)与fsdp_plan.cross_submodel_prefetch(跨子模型边界预取)两个方案均已完成开发与验证,但按分阶段上库计划暂未包含在当前代码中,完整实现与测试备份于backups/20260728_feature_staging/。设计摘要:每 N 个 micro-step 对 VAE/TE 做一次批量条件编码并缓存逐槽消费(编码开销摊为 1/N、TE all-gather 频率降为 1/N、CP 整除时各 rank 只编码自己槽位再交换);pipeline 在 VAE/TE 计算期间对下一个子模型发起异步 unshard,掩盖边界 all-gather。验证记录:组编码与逐样本编码逐比特一致(含真实 UMT5 bf16、变长 padding mask);预取幂等且后续前向零新增 all-gather。8.2 HSDP:可训练子模块的分组切分(阶段 D2)
问题:D1 的 sub_plans 仅支持冻结子模型;可训练子模块用非全局
shard_size时,副本组间梯度会发散。(注:D1 实现当前在backups/20260728_feature_staging/暂存,未上库。)方案(尚未实施):D1 的 2D 子 mesh
(dp_sub_replicate, dp_sub_shard)本身就是 HSDP 形态——fully_shard拿 2D mesh 自动做"组内 shard + 副本组间梯度 all-reduce",梯度全局平均与全局 FSDP 数学等价:flowchart LR subgraph G0["副本组 0(rank 0-3)"] A1["shard 0"] --- A2["shard 1"] --- A3["shard 2"] --- A4["shard 3"] end subgraph G1["副本组 1(rank 4-7)"] B1["shard 0"] --- B2["shard 1"] --- B3["shard 2"] --- B4["shard 3"] end A1 <-."反向: all-reduce(副本组间)".-> B1 A2 <-.-> B2 A3 <-.-> B3 A4 <-.-> B4 Note1["前向: 组内 all-gather 重建全参数<br/>反向: 组内 reduce-scatter + 组间 grad all-reduce"]待办项(实施时):①
_validate_fsdp_sub_plans放开trainable: true(改显式声明校验);②clip_grad_norm按 mesh 分组归约(现逻辑对全局组单一 all-reduce,HSDP 副本会重复计数范数 → 裁剪系数错误,需按grad.device_mesh分组在各自 shard 维组归约);③ 约束文档化(可训练子模块不得有 forward 中更新的 buffer);④ 验证口径:前向逐比特、梯度/收敛容差(归约顺序差异属预期)、副本组权重不发散、clip 范数与全量参考一致、DCP 异构 mesh 回归。*4. 测试设计
提供相关模型构建、权重加载、切分、训练等测试用例
5. 缺点和风险
说明潜在风险(Breaking Change、性能回退、复杂度提升、引入的安全问题)、负面影响(对现有功能/用户的冲击)、实现成本(代码量/维护成本/人力投入)、是否有API或版本兼容性、旧版本迁移方案问题等,给出应对措施。
6. 现有技术
参考其他项目/社区的类似设计,说明借鉴与差异。
7. 未解决问题
待社区讨论/决策的开放问题,如硬件适配范围、参数默认值等(需在RFC通过前解决)。
附录
欢迎加入社区,感谢您对社区的贡献 🎉!