Files
hao-ai-lab-FastVideo/fastvideo/train/utils/moduleloader.py
T
Zhang PeiyuanandClaude Opus 5.5 c520878fd2 [perf]: QAT/QAD add safe regional compile for modular training (#1718)
Adds opt-in regional torch.compile to the YAML-driven modular Wan trainer (training.model.enable_torch_compile / torch_compile_kwargs) with FA4 and Wan modulation forward/backward as opaque custom ops, compiled repeated DiT blocks after FSDP setup, and activation checkpointing applied before FSDP for trainable Wan transformers.

Merged with main's #1716 (WanModel resolves the checkpointing type in __init__; the PR's pre-FSDP wrap uses it and the post-load wrap is gone) and #1757 (pre_fsdp_model_transform runs before this PR's pre_fsdp_transform). Maintainer review (Swarm order #776): AdaLN skip names are canonicalized through checkpoint-wrapper prefixes, test_inference_regional_compile.py joins the unit lane, the Wan modulation custom-op test moved to the transformer lane, regional_compile is classified in the schema inventory, and a 1/2-GPU training smoke covers fully_shard(CheckpointWrapper) in eager and regional-compile modes. Validated on GB200: the FA4 CuTe forward/backward parity and fullgraph-backward tests and all four smoke cases pass.

Also exempts MMAudio recipes, whose student builds its pipeline config at init, from the fine-tuning recipe construction check that #1648 left failing on main.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 05:24:24 -07:00

181 lines
7.2 KiB
Python

# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations
import os
from collections.abc import Callable
from contextlib import nullcontext
from typing import Any, TYPE_CHECKING
import torch
from fastvideo.attention.selector import (
_component_attention_backend_scope,
coerce_attn_backend,
)
from fastvideo.configs.pipelines.base import PipelineConfig
from fastvideo.fastvideo_args import ExecutionMode, TrainingArgs
from fastvideo.models.loader.component_loader import (
PipelineComponentLoader, )
from fastvideo.utils import (
maybe_download_model,
verify_model_config_and_directory,
)
from fastvideo.platforms import AttentionBackendEnum
if TYPE_CHECKING:
from fastvideo.train.utils.training_config import (
TrainingConfig, )
# ------------------------------------------------------------------
# TrainingArgs builders (only place that creates FastVideoArgs)
# ------------------------------------------------------------------
def _make_training_args(
tc: TrainingConfig,
*,
model_path: str,
) -> TrainingArgs:
"""Build a TrainingArgs for PipelineComponentLoader."""
pipeline_config = tc.pipeline_config or PipelineConfig()
# Propagate dit_precision from TrainingConfig to PipelineConfig
# so that TransformerLoader.load() picks up the correct
# default_dtype (e.g. fp32 master weights for training).
if tc.dit_precision and tc.dit_precision != pipeline_config.dit_precision:
pipeline_config.dit_precision = tc.dit_precision
return TrainingArgs(
model_path=model_path,
mode=ExecutionMode.DISTILLATION,
inference_mode=False,
pipeline_config=pipeline_config,
num_gpus=tc.distributed.num_gpus,
tp_size=tc.distributed.tp_size,
sp_size=tc.distributed.sp_size,
hsdp_replicate_dim=tc.distributed.hsdp_replicate_dim,
hsdp_shard_dim=tc.distributed.hsdp_shard_dim,
pin_cpu_memory=tc.distributed.pin_cpu_memory,
dit_cpu_offload=False,
dit_layerwise_offload=False,
vae_cpu_offload=False,
text_encoder_cpu_offload=False,
image_encoder_cpu_offload=False,
use_fsdp_inference=False,
enable_torch_compile=tc.model.enable_torch_compile,
# Modular stack opts into regional fullgraph compile; the legacy
# stack keeps whole-model torch.compile semantics (default False).
regional_compile=True,
torch_compile_kwargs=tc.model.torch_compile_kwargs,
)
def make_inference_args(
tc: TrainingConfig,
*,
model_path: str,
) -> TrainingArgs:
"""Build a TrainingArgs for inference (validation / pipelines)."""
args = _make_training_args(tc, model_path=model_path)
args.inference_mode = True
args.mode = ExecutionMode.INFERENCE
args.dit_cpu_offload = True
args.VSA_sparsity = tc.vsa_sparsity
return args
# ------------------------------------------------------------------
# Module loading
# ------------------------------------------------------------------
def load_module_from_path(
*,
model_path: str,
module_type: str,
training_config: TrainingConfig,
disable_custom_init_weights: bool = False,
override_transformer_cls_name: str | None = None,
transformer_override_safetensor: str | None = None,
attention_backend: AttentionBackendEnum | str | None = None,
pre_fsdp_model_transform: Callable[[torch.nn.Module], None] | None = None,
pre_fsdp_transform: Callable[[torch.nn.Module], torch.nn.Module] | None = None,
) -> torch.nn.Module:
"""Load one pipeline component with its role-scoped attention policy.
Accepts a ``TrainingConfig`` and internally builds the
``TrainingArgs`` needed by ``PipelineComponentLoader``.
Diffusers component entries retain provider and architecture as their
first two fields and can append modular loading metadata. Attention layers
bind their backend during construction, so the requested backend remains
scoped to this load call.
"""
fastvideo_args: Any = _make_training_args(training_config, model_path=model_path)
local_model_path = maybe_download_model(model_path)
config = verify_model_config_and_directory(local_model_path)
if module_type not in config:
raise ValueError(f"Module {module_type!r} not found in "
f"config at {local_model_path}")
module_info = config[module_type]
if module_info is None:
raise ValueError(f"Module {module_type!r} has null value in "
f"config at {local_model_path}")
# Trailing modular-manifest metadata does not change component dispatch;
# the provider and architecture remain the first two fields.
transformers_or_diffusers, _architecture = module_info[:2]
component_path = os.path.join(local_model_path, module_type)
# fastvideo_args is freshly built above and never escapes this function,
# so overrides are plain assignments — nothing to save or restore.
if override_transformer_cls_name is not None:
fastvideo_args.override_transformer_cls_name = str(override_transformer_cls_name)
if transformer_override_safetensor:
fastvideo_args.init_weights_from_safetensors = str(transformer_override_safetensor)
if pre_fsdp_model_transform is not None:
if module_type not in {"transformer", "transformer_ref"}:
raise ValueError("pre_fsdp_model_transform can only be used for transformer components")
fastvideo_args._pre_fsdp_model_transform = pre_fsdp_model_transform
if pre_fsdp_transform is not None:
if module_type != "transformer":
raise ValueError("pre_fsdp_transform can only be set when loading "
f"a transformer, got module_type={module_type!r}")
fastvideo_args._pre_fsdp_transform = pre_fsdp_transform
if attention_backend is not None and module_type not in {
"transformer",
"transformer_ref",
}:
raise ValueError("attention_backend can only be set when loading "
f"a transformer, got module_type={module_type!r}")
resolved_attention_backend = coerce_attn_backend(attention_backend)
# Per-role request delivered as a construction scope: process-local,
# exception-safe, and part of the selector's cache key (no global
# mutation, no cache flushes between roles).
attention_context = (nullcontext() if resolved_attention_backend is None else _component_attention_backend_scope(
resolved_attention_backend, component=module_type))
if disable_custom_init_weights:
fastvideo_args._loading_teacher_critic_model = True
# Attention implementations are bound while transformer layers are
# constructed. Scope the override to this one role so student,
# teacher, and critic can use independent backends in one process.
with attention_context:
module = PipelineComponentLoader.load_module(
module_name=module_type,
component_model_path=component_path,
transformers_or_diffusers=(transformers_or_diffusers),
fastvideo_args=fastvideo_args,
)
if not isinstance(module, torch.nn.Module):
raise TypeError(f"Loaded {module_type!r} is not a "
f"torch.nn.Module: {type(module)}")
return module