Files

10 KiB
Raw Permalink Blame History

fastvideo/pipelines/basic/magi_human/ — daVinci-MagiHuman

Generated: 2026-05-07

Single-stream joint audio-visual generative pipeline. 4 variants × 2 input modes (T2V / TI2V) = 8 entrypoints. The DiT denoises video and audio latents in one unified token sequence — no cross-attention, channel-major token packing.

If you are touching this pipeline, read the parity invariants and cross-refs sections below before editing any file in the manifest.

Manifest

File Role
magi_human_pipeline.py Composed pipeline class. load_modules lazy-loads four shared upstream components (Wan 2.2 VAE, T5-Gemma, Stable Audio VAE, scheduler).
pipeline_configs.py Per-variant PipelineConfig dataclasses (base, distill, sr_540p, sr_1080p).
presets.py Preset registry per variant — entry point that fastvideo/registry.py imports.
__init__.py SPDX header only; no public exports beyond what presets.py registers.
stages/audio_decoding.py Decodes audio latents through the lazy Stable Audio VAE wrapper.
stages/denoising.py Joint AV denoising loop (32-step FlowUniPC with CFG=2 base, 8-step CFG=1 distill).
stages/latent_preparation.py Channel-major video token packing + audio interleave + reference-image masking. Carries the channel-major packing invariant — see Parity Invariants.
stages/reference_image.py TI2V reference-image conditioning.
stages/sr_denoising.py SR DiT denoising loop with cfg-trick guidance tensor. SR-1080p uses block-sparse video→video local-window attention on 32 of 40 SR DiT layers via a 3-block SDPA accumulator.
stages/sr_latent_preparation.py Trilinear-up of base latent + ZeroSNR noise + audio mix for the SR pass.
stages/__init__.py Re-exports stage classes.
JOURNAL.md 14-wave port-state journal; root cause writeups for the parity invariants below.
AGENTS.md This file.

External coordinates of related files (read these too if you change wiring):

Path Role
fastvideo/models/dits/magi_human.py DiT architecture port.
fastvideo/configs/models/dits/magi_human.py MagiHumanVideoConfig arch dataclass.
fastvideo/models/encoders/t5gemma.py T5-Gemma 9B UL2 text encoder port.
fastvideo/configs/models/encoders/t5gemma.py T5GemmaEncoderConfig.
fastvideo/models/vaes/sa_audio.py Lazy OobleckVAE wrapper (shared with pipelines/basic/stable_audio/). Not new — pre-existed on main.
fastvideo/models/vaes/oobleck.py OobleckVAE itself (pre-existing).
fastvideo/models/loader/component_loader.py sr_transformer module type alias added here.
scripts/checkpoint_conversion/convert_magi_human_to_diffusers.py Reference → FastVideo state-dict converter. Carries the _FP32_KEEP_SUFFIXES invariant — see Parity Invariants.
scripts/checkpoint_conversion/push_magi_human_to_hf.py Push converted weights to umbrella HF repo.
tests/local_tests/magi_human/ 14-test parity battery (skipped in CI; GPU-gated).
tests/local_tests/helpers/magi_human_upstream.py Upstream daVinci-MagiHuman reference loader.
examples/inference/basic/basic_magi_human*.py 8 user-facing example scripts (one per variant × mode).
fastvideo/tests/ssim/test_magi_human_similarity.py CI-eligible SSIM regression.

Parity Invariants

These are load-bearing. Each one was a multi-wave bug hunt in the original port. Breaking any of them re-introduces a known production-only regression. See the matching lesson under .agents/lessons/.

1. Channel-major video token packing

Video tokens must be packed (C pT pH pW) — channel-major.

# CORRECT
einops.rearrange(x, "b c (T pT) (H pH) (W pW) -> b (T H W) (C pT pH pW)", ...)
# WRONG — produced pure noise in production E2E despite passing pipeline parity
einops.rearrange(x, "b c (T pT) (H pH) (W pW) -> b (T H W) (pT pH pW C)", ...)

Upstream UnfoldNd packs channel-major. Mismatching this passes the FastVideo self-parity test (both sides use FastVideo's packer) but breaks against the official reference, which only surfaces in production E2E. Lives in stages/latent_preparation.py — search for _img2tokens.

→ .agents/lessons/2026-05-07_silent-channel-major-packing-bugs.md

2. DiT dtype boundary discipline

Across DiT block boundaries, the residual stream stays fp32. SDPA inputs are cast to bf16 inside the attention call. Post-attention output is upcast to fp32 before the per-head gating multiply. There is no block-boundary cast.

These four rules are cumulative — relaxing any one re-introduces measurable parity drift (worst case diff_max=0.5, best case diff_max≈1e-3 which is still not bit-exact).

→ .agents/lessons/2026-05-07_dit-dtype-boundary-with-flash-attn.md

3. Conversion _FP32_KEEP_SUFFIXES allowlist

scripts/checkpoint_conversion/convert_magi_human_to_diffusers.py runs with --cast-bf16 by default. The allowlist of fp32-keep suffixes prevents a specific set of 8 tensors from being downcast. The base checkpoint and the FastVideo final_linear/adapter modules require these in fp32. The distill DiT was the canary: its parity went from diff_mean=0.114 (silently wrong) to bit-exact when the allowlist was fixed.

If you add or rename DiT modules, check that any fp32-required tensors are covered by _FP32_KEEP_SUFFIXES and re-run test_magi_human_distill_parity.

→ .agents/lessons/2026-05-07_conversion-cast-bf16-suffix-allowlist.md (lands in 7/8)

4. Umbrella HF repo layout

User code is a single string per variant:

VideoGenerator.from_pretrained("FastVideo/MagiHuman-Diffusers/base")

fastvideo/utils.py:maybe_download_model recognises the 3-segment org/repo/subfolder form and downloads only that subtree (not the full ~75 GB repo). The umbrella repo:

FastVideo/MagiHuman-Diffusers/
├── base/{model_index.json, transformer/, scheduler/}
├── distill/{model_index.json, transformer/, scheduler/}
├── sr_540p/{model_index.json, transformer/, sr_transformer/, scheduler/}
└── sr_1080p/{model_index.json, transformer/, sr_transformer/, scheduler/}

Note: vae/, text_encoder/, audio_vae/ subfolders are deliberately absent. Those four shared components are lazy-loaded by MagiHumanPipeline.load_modules from their canonical upstream repos (Wan-AI/Wan2.2-TI2V-5B, google/t5gemma-9b-9b-ul2, stabilityai/stable-audio-open-1.0). This relies on fastvideo/utils.py:verify_model_config_and_directory honoring model_index.json declarations.

Cross-Refs (If you change X, re-run Y)

If you touch... Re-run at minimum
stages/latent_preparation.py (any token packing) test_magi_human_pipeline_parity (base T2V) and one of the SR-540p/1080p tests and examples/inference/basic/basic_magi_human.py (E2E mp4 hash)
stages/denoising.py or stages/sr_denoising.py All four pipeline-parity tests ({base, ti2v, sr540p, sr1080p}_pipeline_parity)
fastvideo/models/dits/magi_human.py (any layer) test_magi_human_parity and test_magi_human_distill_parity (DiT-level) before the pipeline tests
Anything dtype-related in the DiT All DiT parity tests + verify the residual-stream dtype invariant manually with a layer-by-layer hook trace (see fastvideo/hooks/activation_trace.py)
scripts/checkpoint_conversion/convert_magi_human_to_diffusers.py Convert the distill checkpoint and run test_magi_human_distill_parity (it's the canary for _FP32_KEEP_SUFFIXES)
fastvideo/utils.py:maybe_download_model (3-segment detector) Smoke-load every existing 2-segment HF id in registry.py plus one MagiHuman variant
magi_human_pipeline.py:load_modules test_magi_human_pipeline_smoke + one full E2E examples/inference/basic/basic_magi_human.py
presets.py or pipeline_configs.py Both DiT parity tests (preset wiring leaks into module construction)

Run Book

# Setup once
export HF_TOKEN=hf_...    # any of HF_TOKEN / HUGGINGFACE_HUB_TOKEN / HF_API_KEY works
# Accept terms at:
#   - https://huggingface.co/GAIR/daVinci-MagiHuman
#   - https://huggingface.co/google/t5gemma-9b-9b-ul2
#   - https://huggingface.co/stabilityai/stable-audio-open-1.0
#   - https://huggingface.co/Wan-AI/Wan2.2-TI2V-5B

# Full parity battery (~233s on a single H100, GPU-gated)
pytest tests/local_tests/magi_human/ -v -s

# E2E mp4 generation (any variant)
python examples/inference/basic/basic_magi_human.py

# CI-eligible SSIM regression
pytest fastvideo/tests/ssim/test_magi_human_similarity.py -v -s

The base T2V mp4 hash should be dcf5f2bf6534c7c0d91e7353e42b23db — stable across all 43 commits of the original port.

Open Questions

  • OQ-7 — Wan VAE decode shows a max diff of ~8e-4 against the diffusers reference (not bit-exact). Root cause is a known fp32 op-order drift in the Wan 2.2 VAE itself, not MagiHuman wiring. Tracked in JOURNAL.md. Documented here so the test author knows the tolerance is intentional, not a regression.

Provenance

This pipeline was decomposed from a single 9,812-line PR (#1280, will/magi @ 4e1603634d27c8e1b5c4cc5d9387f046547f5c49) into a stack of focused PRs:

Step PR Branch Scope
Prereq A #1293 will/activation-trace Generic activation-tracing infra
Prereq B #1294 will/loader-infra Loader umbrella-repo + optional component dirs
1/8 #1295 will/magi-01-housekeeping gitignore, codespell, skills index
2/8 #1296 will/magi-02-t5gemma T5-Gemma encoder + parity test
3/8 #1297 will/magi-03-dit DiT + parity tests
4/8 #1298 will/magi-04a-stages Pipeline stages + sr_transformer alias
5/8 #1299 will/magi-04b-orchestrator Pipeline orchestrator + parity battery
6/8 this PR will/magi-04c-provenance This AGENTS.md, JOURNAL.md, lessons, parent AGENTS.md hook
7/8 (next) will/magi-05-conversion Checkpoint conversion + 3rd lesson
8/8 (last) will/magi-06-activate Registry + examples + SSIM + codebase-map

Provenance section will be finalized in 8/8 with the actual PR numbers for 7/8 and 8/8.