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>
168 lines
5.5 KiB
Python
168 lines
5.5 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
"""Verify construction-scoped attention backends in the training loader."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import torch
|
|
import pytest
|
|
|
|
from fastvideo.attention.selector import _active_component_attention_backend_scope
|
|
from fastvideo.configs.pipelines.base import PipelineConfig
|
|
from fastvideo.platforms import AttentionBackendEnum
|
|
from fastvideo.train.utils import moduleloader
|
|
from fastvideo.train.utils.training_config import (
|
|
DistributedConfig,
|
|
ModelTrainingConfig,
|
|
TrainingConfig,
|
|
)
|
|
|
|
|
|
def test_load_transformer_scopes_attention_backend(monkeypatch, tmp_path) -> None:
|
|
"""Apply one backend while accepting trailing modular-manifest metadata."""
|
|
training_config = TrainingConfig(
|
|
distributed=DistributedConfig(hsdp_shard_dim=1),
|
|
pipeline_config=PipelineConfig(),
|
|
)
|
|
captured: list[tuple[AttentionBackendEnum | None, str | None]] = []
|
|
|
|
monkeypatch.setattr(moduleloader, "maybe_download_model", lambda path: str(tmp_path))
|
|
monkeypatch.setattr(
|
|
moduleloader,
|
|
"verify_model_config_and_directory",
|
|
lambda path: {"transformer": ("diffusers", "FakeTransformer", {
|
|
"subfolder": "transformer"
|
|
})},
|
|
)
|
|
|
|
def _fake_load_module(**kwargs):
|
|
del kwargs
|
|
scope = _active_component_attention_backend_scope()
|
|
captured.append((scope.backend, scope.component) if scope else (None, None))
|
|
return torch.nn.Linear(1, 1)
|
|
|
|
monkeypatch.setattr(
|
|
moduleloader.PipelineComponentLoader,
|
|
"load_module",
|
|
_fake_load_module,
|
|
)
|
|
|
|
result = moduleloader.load_module_from_path(
|
|
model_path="fake/model",
|
|
module_type="transformer",
|
|
training_config=training_config,
|
|
attention_backend="ATTN_QAT_TRAIN",
|
|
)
|
|
|
|
assert isinstance(result, torch.nn.Module)
|
|
assert captured == [(AttentionBackendEnum.ATTN_QAT_TRAIN, "transformer")]
|
|
assert _active_component_attention_backend_scope() is None
|
|
|
|
|
|
def test_load_transformer_restores_backend_when_loading_fails(
|
|
monkeypatch,
|
|
tmp_path,
|
|
) -> None:
|
|
training_config = TrainingConfig(
|
|
distributed=DistributedConfig(hsdp_shard_dim=1),
|
|
pipeline_config=PipelineConfig(),
|
|
)
|
|
monkeypatch.setattr(moduleloader, "maybe_download_model", lambda path: str(tmp_path))
|
|
monkeypatch.setattr(
|
|
moduleloader,
|
|
"verify_model_config_and_directory",
|
|
lambda path: {"transformer": ("diffusers", "FakeTransformer")},
|
|
)
|
|
|
|
def _raise_during_load(**kwargs):
|
|
del kwargs
|
|
scope = _active_component_attention_backend_scope()
|
|
assert scope is not None and scope.backend is AttentionBackendEnum.ATTN_QAT_TRAIN
|
|
raise RuntimeError("load failed")
|
|
|
|
monkeypatch.setattr(
|
|
moduleloader.PipelineComponentLoader,
|
|
"load_module",
|
|
_raise_during_load,
|
|
)
|
|
|
|
with pytest.raises(RuntimeError, match="load failed"):
|
|
moduleloader.load_module_from_path(
|
|
model_path="fake/model",
|
|
module_type="transformer",
|
|
training_config=training_config,
|
|
attention_backend="ATTN_QAT_TRAIN",
|
|
)
|
|
assert _active_component_attention_backend_scope() is None
|
|
|
|
|
|
def test_training_args_propagate_compile_settings() -> None:
|
|
training_config = TrainingConfig(
|
|
distributed=DistributedConfig(hsdp_shard_dim=1),
|
|
model=ModelTrainingConfig(
|
|
enable_torch_compile=True,
|
|
torch_compile_kwargs={"dynamic": False},
|
|
),
|
|
pipeline_config=PipelineConfig(),
|
|
)
|
|
|
|
args = moduleloader._make_training_args(training_config, model_path="fake/model")
|
|
|
|
assert args.enable_torch_compile is True
|
|
assert args.torch_compile_kwargs == {"dynamic": False}
|
|
|
|
|
|
def test_load_transformer_forwards_pre_fsdp_transform(monkeypatch, tmp_path) -> None:
|
|
training_config = TrainingConfig(
|
|
distributed=DistributedConfig(hsdp_shard_dim=1),
|
|
pipeline_config=PipelineConfig(),
|
|
)
|
|
|
|
def transform(module):
|
|
return module
|
|
|
|
captured = None
|
|
|
|
monkeypatch.setattr(moduleloader, "maybe_download_model", lambda path: str(tmp_path))
|
|
monkeypatch.setattr(
|
|
moduleloader,
|
|
"verify_model_config_and_directory",
|
|
lambda path: {"transformer": ("diffusers", "FakeTransformer")},
|
|
)
|
|
|
|
def _fake_load_module(**kwargs):
|
|
nonlocal captured
|
|
captured = getattr(kwargs["fastvideo_args"], "_pre_fsdp_transform", None)
|
|
return torch.nn.Linear(1, 1)
|
|
|
|
monkeypatch.setattr(moduleloader.PipelineComponentLoader, "load_module", _fake_load_module)
|
|
|
|
moduleloader.load_module_from_path(
|
|
model_path="fake/model",
|
|
module_type="transformer",
|
|
training_config=training_config,
|
|
pre_fsdp_transform=transform,
|
|
)
|
|
|
|
assert captured is transform
|
|
|
|
|
|
def test_pre_fsdp_transform_rejects_non_transformer(monkeypatch, tmp_path) -> None:
|
|
training_config = TrainingConfig(
|
|
distributed=DistributedConfig(hsdp_shard_dim=1),
|
|
pipeline_config=PipelineConfig(),
|
|
)
|
|
monkeypatch.setattr(moduleloader, "maybe_download_model", lambda path: str(tmp_path))
|
|
monkeypatch.setattr(
|
|
moduleloader,
|
|
"verify_model_config_and_directory",
|
|
lambda path: {"vae": ("diffusers", "FakeVAE")},
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="only be set when loading a transformer"):
|
|
moduleloader.load_module_from_path(
|
|
model_path="fake/model",
|
|
module_type="vae",
|
|
training_config=training_config,
|
|
pre_fsdp_transform=lambda module: module,
|
|
)
|