Files
hao-ai-lab-FastVideo/fastvideo/tests/train/utils/test_moduleloader_attention_backend.py
Davids048andClaude Fable 5.1 0bcd15d5e2 [refactor]: delete FastVideoArgs, TrainingArgs, the flattening, and argparse
ResolvedGeneratorConfig is the only runtime config. It answers only its
typed fields, pipeline_config, mode, training_mode, and inference_mode;
the transitional flat-name fallback is gone.

Deleted: fastvideo/fastvideo_args.py (FastVideoArgs, TrainingArgs, their
argparse, prepare/set/get_current_fastvideo_args), the flat-name fallback,
generator_config_to_fastvideo_args and the other flattening helpers,
PipelineConfig/model-config/PreprocessConfig/SamplingParam add_cli_args,
configs/utils.py, and the tests of the deleted code.

Materialization builds the model's PipelineConfig directly from the typed
values through an explicit table of typed path to PipelineConfig mirror
attribute (PIPELINE_CONFIG_MIRRORS); PipelineConfig.from_source replaces
from_kwargs. flat_field metadata stays only on the fields behind the 24
from_pretrained keywords; a pipeline.experimental key that names a mirror
attribute, and pipeline.preset / preset_version / components.vae_weights,
are rejected during resolution. The training root keeps the DiT on the
device through a resolution step instead of a from_pretrained override.

The goldens snapshot the end-state objects (resolved config, PipelineConfig,
decisions, request); the cli category is gone. Against the 3f6893a0
goldens, every field matches except disable_autocast, boundary_ratio,
ltx2_vae_tiling (DESIGN section 9), and the fields that no longer exist.
The schema parity inventory classifies the typed GeneratorConfig paths.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012M91wnVFmPEvJ9h39r5BH7
2026-10-04 04:45:02 +00:00

190 lines
7.5 KiB
Python

# SPDX-License-Identifier: Apache-2.0
"""Verify construction-scoped attention backends and resolved configs in the training loader."""
from __future__ import annotations
import torch
import pytest
from fastvideo.api.schema import ExecutionMode
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,
TrainingConfig,
)
# A registered model path, so resolution finds its PipelineConfig class without a download.
_MODEL_PATH = "Wan-AI/Wan2.1-T2V-1.3B-Diffusers"
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=_MODEL_PATH,
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=_MODEL_PATH,
module_type="transformer",
training_config=training_config,
attention_backend="ATTN_QAT_TRAIN",
)
assert _active_component_attention_backend_scope() is None
def _wan_training_config() -> TrainingConfig:
"""Training config with the Wan model definition and a two-GPU sequence-parallel layout."""
return TrainingConfig(
distributed=DistributedConfig(num_gpus=2, sp_size=2, hsdp_shard_dim=2),
pipeline_config=PipelineConfig.from_source(_MODEL_PATH),
model_path=_MODEL_PATH,
vsa_sparsity=0.5,
)
def test_load_module_passes_distillation_config_and_teacher_flag(monkeypatch, tmp_path) -> None:
"""The loader receives the transformer overrides as typed values and the teacher/critic flag as a keyword."""
training_config = _wan_training_config()
captured: dict = {}
monkeypatch.setattr(moduleloader, "maybe_download_model", lambda path: str(tmp_path))
monkeypatch.setattr(
moduleloader,
"verify_model_config_and_directory",
lambda path: {"transformer": ("diffusers", "WanTransformer3DModel")},
)
def _fake_load_module(**kwargs):
captured.update(kwargs)
return torch.nn.Linear(1, 1)
monkeypatch.setattr(moduleloader.PipelineComponentLoader, "load_module", _fake_load_module)
moduleloader.load_module_from_path(
model_path=_MODEL_PATH,
module_type="transformer",
training_config=training_config,
disable_custom_init_weights=True,
override_transformer_cls_name="CausalWanTransformer3DModel",
transformer_override_safetensor="/weights/student.safetensors",
)
resolved_config = captured["resolved_config"]
assert captured["loading_teacher_critic_model"] is True
assert resolved_config.mode is ExecutionMode.DISTILLATION
assert resolved_config.training_mode
assert resolved_config.model_path == _MODEL_PATH
assert resolved_config.pipeline.components.override_transformer_cls_name == "CausalWanTransformer3DModel"
assert resolved_config.pipeline.components.transformer_weights == "/weights/student.safetensors"
assert resolved_config.engine.num_gpus == 2
assert resolved_config.engine.parallelism.sp_size == 2
assert resolved_config.engine.parallelism.hsdp_shard_dim == 2
assert resolved_config.engine.precision.dit == "fp32"
assert not any((resolved_config.engine.offload.dit, resolved_config.engine.offload.dit_layerwise,
resolved_config.engine.offload.text_encoder, resolved_config.engine.offload.vae))
assert type(resolved_config.pipeline_config) is type(training_config.pipeline_config)
assert resolved_config.pipeline_config is not training_config.pipeline_config
def test_vae_load_keeps_checkpoint_vae_config(monkeypatch, tmp_path) -> None:
"""The VAE config that the loader filled from the checkpoint becomes the training pipeline config's VAE config."""
training_config = _wan_training_config()
monkeypatch.setattr(moduleloader, "maybe_download_model", lambda path: str(tmp_path))
monkeypatch.setattr(
moduleloader,
"verify_model_config_and_directory",
lambda path: {"vae": ("diffusers", "AutoencoderKLWan")},
)
def _fill_vae_config(**kwargs):
kwargs["resolved_config"].pipeline_config.vae_config.arch_config.z_dim = 48
return torch.nn.Linear(1, 1)
monkeypatch.setattr(moduleloader.PipelineComponentLoader, "load_module", _fill_vae_config)
moduleloader.load_module_from_path(
model_path=_MODEL_PATH,
module_type="vae",
training_config=training_config,
)
assert training_config.pipeline_config.vae_config.arch_config.z_dim == 48
def test_inference_resolved_config_offloads_only_the_dit() -> None:
"""The inference config keeps encoders and VAE on the device and carries the training VSA sparsity."""
resolved_config = moduleloader.build_inference_resolved_config(_wan_training_config(), model_path=_MODEL_PATH)
assert resolved_config.mode is ExecutionMode.INFERENCE
assert resolved_config.inference_mode
assert resolved_config.engine.offload.dit is True
assert resolved_config.engine.offload.text_encoder is False
assert resolved_config.engine.offload.vae is False
assert resolved_config.engine.attention.vsa_sparsity == 0.5
assert resolved_config.engine.precision.dit == "fp32"