Skip to content

Commit 361cc4c

Browse files
[feat]: Activate MagiHuman pipeline (registry + examples + SSIM regression)
The activation switch. After this PR merges, MagiHuman is publicly loadable via: from fastvideo import VideoGenerator gen = VideoGenerator.from_pretrained('FastVideo/MagiHuman-Diffusers/base') Files: - fastvideo/registry.py: 153 lines of register_configs/register_presets calls covering all 4 variants x 2 modes (8 total entrypoints). - examples/inference/basic/basic_magi_human{_,_ti2v,_distill,_distill_ti2v, _sr540p,_sr540p_ti2v,_sr1080p,_sr1080p_ti2v}.py: 8 user-facing scripts. - fastvideo/tests/ssim/test_magi_human_similarity.py: CI-eligible SSIM regression test against the umbrella HF repo. - .agents/memory/codebase-map/models/magi_human.md: codebase-map entry (first per-model entry under the models/ subdir; sets the convention). - fastvideo/pipelines/basic/magi_human/AGENTS.md: provenance section finalized with all 8 PR numbers + the will/magi source SHA. Verification: - Existing tests on main: should still pass (no shared component changes in this PR; the umbrella loader infra landed in PR-B / loader-infra). - pre-commit run --all-files: clean. - E2E smoke after activation: any of the 8 examples emits expected mp4 with hash dcf5f2bf6534c7c0d91e7353e42b23db on the base T2V variant. Magi-Stack: 8/8
1 parent 547fcd9 commit 361cc4c

12 files changed

Lines changed: 668 additions & 5 deletions
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# MagiHuman — Codebase Map Entry
2+
3+
**Family:** daVinci-MagiHuman (joint audio-visual generative model)
4+
**Reference:** [GAIR-NLP/daVinci-MagiHuman](https://github.com/GAIR-NLP/daVinci-MagiHuman)
5+
**Architecture:** 15B-param single-stream DiT, 40 layers, hidden=5120,
6+
head_dim=128, GQA num_query_groups=8. Joint AV denoising in a unified token
7+
sequence; **no cross-attention**.
8+
9+
## Variant Matrix
10+
11+
| Variant | T2V | TI2V | DiT | Steps | CFG | Resolution |
12+
|---|---|---|---|---|---|---|
13+
| `base` | yes | yes | base | 32 | 2 | 480x256 |
14+
| `distill` | yes | yes | distill (DMD-2) | 8 | 1 (no CFG) | 480x256 |
15+
| `sr_540p` | yes | yes | base + sr_540p | 32 + 5 | 2 + cfg-trick | 896x512 |
16+
| `sr_1080p` | yes | yes | base + sr_1080p | 32 + 5 | 2 + cfg-trick | 1920x1056 |
17+
18+
SR-1080p uses block-sparse video→video local-window attention on 32 of 40
19+
SR DiT layers (`frame_receptive_field=11`), implemented as a 3-block SDPA
20+
accumulator that mirrors upstream `flex_flash_attn_func`.
21+
22+
## File Locations
23+
24+
| Role | Path |
25+
|---|---|
26+
| Pipeline class | `fastvideo/pipelines/basic/magi_human/magi_human_pipeline.py` |
27+
| Pipeline package AGENTS.md | `fastvideo/pipelines/basic/magi_human/AGENTS.md` |
28+
| Stages | `fastvideo/pipelines/basic/magi_human/stages/*.py` |
29+
| DiT | `fastvideo/models/dits/magi_human.py` |
30+
| Text encoder (T5-Gemma) | `fastvideo/models/encoders/t5gemma.py` |
31+
| Audio VAE wrapper | `fastvideo/models/vaes/sa_audio.py` (shared with `stable_audio` pipeline) |
32+
| Conversion script | `scripts/checkpoint_conversion/convert_magi_human_to_diffusers.py` |
33+
| Examples | `examples/inference/basic/basic_magi_human*.py` (8 files, one per variant × mode) |
34+
| SSIM regression | `fastvideo/tests/ssim/test_magi_human_similarity.py` |
35+
| Local parity battery | `tests/local_tests/magi_human/` (14 tests, GPU-gated) |
36+
| Port journal | `fastvideo/pipelines/basic/magi_human/JOURNAL.md` |
37+
38+
## Canonical HF Repo
39+
40+
[FastVideo/MagiHuman-Diffusers](https://huggingface.co/FastVideo/MagiHuman-Diffusers)
41+
— umbrella repo with sibling subfolders per variant.
42+
43+
```python
44+
from fastvideo import VideoGenerator
45+
gen = VideoGenerator.from_pretrained("FastVideo/MagiHuman-Diffusers/base")
46+
gen.generate_video(prompt="...", output_path="out.mp4", save_video=True)
47+
```
48+
49+
Four shared upstream components are lazy-loaded by `MagiHumanPipeline.load_modules`:
50+
51+
| Component | Upstream repo |
52+
|---|---|
53+
| Wan 2.2 VAE | `Wan-AI/Wan2.2-TI2V-5B` |
54+
| T5-Gemma 9B UL2 | `google/t5gemma-9b-9b-ul2` |
55+
| Stable Audio VAE | `stabilityai/stable-audio-open-1.0` |
56+
| MagiHuman DiT weights | `GAIR/daVinci-MagiHuman` (gated) |
57+
58+
## Parity Invariants
59+
60+
Three load-bearing invariants. See `fastvideo/pipelines/basic/magi_human/AGENTS.md`
61+
for the full discussion.
62+
63+
1. **Channel-major video token packing** (`stages/latent_preparation.py`)
64+
2. **DiT dtype boundary**: residual stream stays fp32 across blocks
65+
3. **Conversion `_FP32_KEEP_SUFFIXES`** (`scripts/checkpoint_conversion/convert_magi_human_to_diffusers.py`)
66+
67+
## Lessons
68+
69+
- `.agents/lessons/2026-05-07_silent-channel-major-packing-bugs.md`
70+
- `.agents/lessons/2026-05-07_dit-dtype-boundary-with-flash-attn.md`
71+
- `.agents/lessons/2026-05-07_conversion-cast-bf16-suffix-allowlist.md`
72+
73+
## Provenance
74+
75+
Decomposed from PR [#1280](https://github.com/hao-ai-lab/FastVideo/pull/1280)
76+
(`will/magi` @ `4e1603634d27c8e1b5c4cc5d9387f046547f5c49`). See the package
77+
AGENTS.md for the full PR-stack table.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
"""Minimal user-runnable example for the daVinci-MagiHuman base AV pipeline.
3+
4+
Produces an mp4 with both video (Wan 2.2 TI2V-5B VAE) and audio (Stable
5+
Audio Open 1.0 VAE, first-class FastVideo port in
6+
`fastvideo/models/vaes/oobleck.py`) muxed together via PyAV.
7+
8+
Prerequisites (one-off):
9+
10+
# Accept terms of use on the gated HF repos with your HF_TOKEN:
11+
# - https://huggingface.co/google/t5gemma-9b-9b-ul2
12+
# - https://huggingface.co/stabilityai/stable-audio-open-1.0
13+
# All four cross-variant shared components (Wan 2.2 VAE, T5-Gemma
14+
# encoder + tokenizer, Stable Audio VAE) are lazy-loaded from their
15+
# canonical upstream HF repos on first build, so a single ~25 GB
16+
# cache is shared across every MagiHuman variant.
17+
18+
The umbrella HF repo `FastVideo/MagiHuman-Diffusers` holds all four
19+
variants (base / distill / sr_540p / sr_1080p) under sibling subfolders
20+
and FastVideo will download just the requested subfolder. Local
21+
conversion via `scripts/checkpoint_conversion/convert_magi_human_to_diffusers.py`
22+
is also supported.
23+
"""
24+
from fastvideo import VideoGenerator
25+
26+
27+
PROMPT = (
28+
"A warm afternoon scene: a person sits on a park bench reading a book, "
29+
"surrounded by softly swaying trees."
30+
)
31+
32+
33+
def main() -> None:
34+
generator = VideoGenerator.from_pretrained(
35+
"FastVideo/MagiHuman-Diffusers/base",
36+
num_gpus=1,
37+
)
38+
output_path = "outputs_video/magi_human_basic/output_magi_human.mp4"
39+
generator.generate_video(
40+
prompt=PROMPT,
41+
output_path=output_path,
42+
save_video=True,
43+
# Defaults pulled from the registered preset (magi_human_base):
44+
# height=256, width=448, fps=25, num_inference_steps=32, seed=42.
45+
# Override here only if you have a specific QA scenario.
46+
)
47+
generator.shutdown()
48+
49+
50+
if __name__ == "__main__":
51+
main()
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
"""Minimal user-runnable example for the daVinci-MagiHuman DMD-2 distilled
3+
text-to-AV pipeline.
4+
5+
Same arch as the base model (`basic_magi_human.py`) but with DMD-2 distilled
6+
weights: 8 denoising steps, no classifier-free guidance. ~4x faster than
7+
base at the same 256x480 resolution. Mirrors upstream
8+
`daVinci-MagiHuman/example/distill/run_T2V.sh`.
9+
10+
Prerequisites (one-off):
11+
12+
# 1) Accept terms on the gated HF repos with your HF_TOKEN:
13+
# - https://huggingface.co/google/t5gemma-9b-9b-ul2
14+
# - https://huggingface.co/stabilityai/stable-audio-open-1.0
15+
# Cross-variant shared components (Wan 2.2 VAE + T5-Gemma + Stable
16+
# Audio VAE) are lazy-loaded from their canonical upstream HF repos
17+
# and shared with the base variant cache.
18+
# 2) Convert the distill subfolder of GAIR/daVinci-MagiHuman:
19+
python scripts/checkpoint_conversion/convert_magi_human_to_diffusers.py \\
20+
--source GAIR/daVinci-MagiHuman \\
21+
--subfolder distill \\
22+
--output converted_weights/magi_human_distill \\
23+
--cast-bf16
24+
# `--cast-bf16` is recommended (61 GB fp32 -> 30 GB bf16); the FV pipeline
25+
# loads bf16 anyway, and the conversion keeps norms / RoPE bands fp32.
26+
"""
27+
from fastvideo import VideoGenerator
28+
29+
30+
PROMPT = (
31+
"A warm afternoon scene: a person sits on a park bench reading a book, "
32+
"surrounded by softly swaying trees."
33+
)
34+
35+
36+
def main() -> None:
37+
generator = VideoGenerator.from_pretrained(
38+
"FastVideo/MagiHuman-Diffusers/distill",
39+
num_gpus=1,
40+
)
41+
output_path = "outputs_video/magi_human_basic/output_magi_human_distill.mp4"
42+
generator.generate_video(
43+
prompt=PROMPT,
44+
output_path=output_path,
45+
save_video=True,
46+
# Defaults pulled from the registered preset (magi_human_distill):
47+
# height=256, width=480, fps=25, num_inference_steps=8, cfg=1, seed=42.
48+
)
49+
generator.shutdown()
50+
51+
52+
if __name__ == "__main__":
53+
main()
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
"""Minimal daVinci-MagiHuman DMD-2 distilled text+image-to-AV example."""
3+
from fastvideo import VideoGenerator
4+
from fastvideo.pipelines.basic.magi_human.pipeline_configs import (
5+
MagiHumanDistillI2VConfig,
6+
)
7+
8+
9+
PROMPT = (
10+
"A cheerful saxophonist performs a short line with expressive facial "
11+
"motion, natural head movement, and synchronized audio in a small jazz club."
12+
)
13+
IMAGE_PATH = "assets/images/saxophonist.jpg"
14+
15+
16+
def main() -> None:
17+
generator = VideoGenerator.from_pretrained(
18+
"FastVideo/MagiHuman-Diffusers/distill",
19+
num_gpus=1,
20+
workload_type="i2v",
21+
override_pipeline_cls_name="MagiHumanI2VPipeline",
22+
pipeline_config=MagiHumanDistillI2VConfig(),
23+
)
24+
generator.generate_video(
25+
prompt=PROMPT,
26+
image_path=IMAGE_PATH,
27+
output_path="outputs_video/magi_human_distill_ti2v/output_magi_human_distill_ti2v.mp4",
28+
save_video=True,
29+
)
30+
generator.shutdown()
31+
32+
33+
if __name__ == "__main__":
34+
main()
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
"""Run daVinci-MagiHuman SR-1080p text-to-AV in FastVideo.
3+
4+
Build the converted repo on large local storage, then symlink it into the
5+
workspace:
6+
7+
python scripts/checkpoint_conversion/convert_magi_human_to_diffusers.py \
8+
--source GAIR/daVinci-MagiHuman \
9+
--subfolder base \
10+
--sr-source GAIR/daVinci-MagiHuman \
11+
--sr-subfolder 1080p_sr \
12+
--output /raid/william5lin_converted_weights/magi_human_sr_1080p \
13+
--cast-bf16
14+
ln -s /raid/william5lin_converted_weights/magi_human_sr_1080p \
15+
converted_weights/magi_human_sr_1080p
16+
"""
17+
from fastvideo import VideoGenerator
18+
from fastvideo.pipelines.basic.magi_human.pipeline_configs import (
19+
MagiHumanSR1080pConfig,
20+
)
21+
22+
23+
PROMPT = (
24+
"A warm afternoon scene: a person sits on a park bench reading a book, "
25+
"surrounded by softly swaying trees."
26+
)
27+
28+
29+
def main() -> None:
30+
generator = VideoGenerator.from_pretrained(
31+
"FastVideo/MagiHuman-Diffusers/sr_1080p",
32+
num_gpus=1,
33+
override_pipeline_cls_name="MagiHumanSR1080pPipeline",
34+
pipeline_config=MagiHumanSR1080pConfig(),
35+
)
36+
generator.generate_video(
37+
prompt=PROMPT,
38+
output_path="outputs_video/magi_human_sr1080p/output_magi_human_sr1080p.mp4",
39+
save_video=True,
40+
)
41+
generator.shutdown()
42+
43+
44+
if __name__ == "__main__":
45+
main()
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
"""Run daVinci-MagiHuman SR-1080p text+image-to-AV in FastVideo."""
3+
from fastvideo import VideoGenerator
4+
from fastvideo.pipelines.basic.magi_human.pipeline_configs import (
5+
MagiHumanSR1080pI2VConfig,
6+
)
7+
8+
9+
PROMPT = (
10+
"A cheerful saxophonist performs a short line with expressive facial "
11+
"motion, natural head movement, and synchronized audio in a small jazz club."
12+
)
13+
IMAGE_PATH = "assets/images/saxophonist.jpg"
14+
15+
16+
def main() -> None:
17+
generator = VideoGenerator.from_pretrained(
18+
"FastVideo/MagiHuman-Diffusers/sr_1080p",
19+
num_gpus=1,
20+
workload_type="i2v",
21+
override_pipeline_cls_name="MagiHumanSR1080pI2VPipeline",
22+
pipeline_config=MagiHumanSR1080pI2VConfig(),
23+
)
24+
generator.generate_video(
25+
prompt=PROMPT,
26+
image_path=IMAGE_PATH,
27+
output_path="outputs_video/magi_human_sr1080p_ti2v/output_magi_human_sr1080p_ti2v.mp4",
28+
save_video=True,
29+
)
30+
generator.shutdown()
31+
32+
33+
if __name__ == "__main__":
34+
main()
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
"""Run daVinci-MagiHuman SR-540p text-to-AV in FastVideo.
3+
4+
The converted repo must contain both ``transformer/`` (base DiT) and
5+
``sr_transformer/`` (540p SR DiT). Build it with:
6+
7+
python scripts/checkpoint_conversion/convert_magi_human_to_diffusers.py \
8+
--source GAIR/daVinci-MagiHuman \
9+
--subfolder base \
10+
--sr-source GAIR/daVinci-MagiHuman \
11+
--sr-subfolder 540p_sr \
12+
--output converted_weights/magi_human_sr_540p
13+
"""
14+
from fastvideo import VideoGenerator
15+
16+
17+
PROMPT = (
18+
"A warm afternoon scene: a person sits on a park bench reading a book, "
19+
"surrounded by softly swaying trees."
20+
)
21+
22+
23+
def main() -> None:
24+
generator = VideoGenerator.from_pretrained(
25+
"FastVideo/MagiHuman-Diffusers/sr_540p",
26+
num_gpus=1,
27+
)
28+
generator.generate_video(
29+
prompt=PROMPT,
30+
output_path="outputs_video/magi_human_sr540p/output_magi_human_sr540p.mp4",
31+
save_video=True,
32+
)
33+
generator.shutdown()
34+
35+
36+
if __name__ == "__main__":
37+
main()
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
"""Run daVinci-MagiHuman SR-540p text+image-to-AV in FastVideo."""
3+
from fastvideo import VideoGenerator
4+
from fastvideo.pipelines.basic.magi_human.pipeline_configs import (
5+
MagiHumanSR540pI2VConfig,
6+
)
7+
8+
9+
PROMPT = (
10+
"A cheerful saxophonist performs a short line with expressive facial "
11+
"motion, natural head movement, and synchronized audio in a small jazz club."
12+
)
13+
IMAGE_PATH = "assets/images/saxophonist.jpg"
14+
15+
16+
def main() -> None:
17+
generator = VideoGenerator.from_pretrained(
18+
"FastVideo/MagiHuman-Diffusers/sr_540p",
19+
num_gpus=1,
20+
workload_type="i2v",
21+
override_pipeline_cls_name="MagiHumanSRI2VPipeline",
22+
pipeline_config=MagiHumanSR540pI2VConfig(),
23+
)
24+
generator.generate_video(
25+
prompt=PROMPT,
26+
image_path=IMAGE_PATH,
27+
output_path="outputs_video/magi_human_sr540p_ti2v/output_magi_human_sr540p_ti2v.mp4",
28+
save_video=True,
29+
)
30+
generator.shutdown()
31+
32+
33+
if __name__ == "__main__":
34+
main()

0 commit comments

Comments
 (0)