Skip to content

Commit 815da1f

Browse files
committed
Merge remote-tracking branch 'origin/main' into svi
# Conflicts: # fastvideo/pipelines/stages/__init__.py
2 parents 332f918 + 72cb427 commit 815da1f

504 files changed

Lines changed: 88288 additions & 1341 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/STATUS.md

Lines changed: 0 additions & 94 deletions
This file was deleted.
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
---
2+
date: 2026-05-07
3+
experiment: PR #1280 (daVinci-MagiHuman port), distill DiT parity bring-up
4+
category: porting
5+
severity: important
6+
---
7+
8+
# Conversion `--cast-bf16` Needs an FP32-Keep Suffix Allowlist
9+
10+
## What Happened
11+
12+
`scripts/checkpoint_conversion/convert_magi_human_to_diffusers.py --cast-bf16`
13+
produced a converted distill DiT checkpoint that loaded cleanly, ran end-to-
14+
end, and emitted reasonable output — but `test_magi_human_distill_parity`
15+
showed `diff_mean=0.114` against the upstream reference. The base DiT was
16+
bit-exact with the same conversion script. Only the distill variant
17+
regressed.
18+
19+
The error was small enough that visual quality looked normal, but large
20+
enough to fail bit-exact parity. The MagiHuman base + distill DiTs share
21+
most of their architecture, so a difference that affected only distill was
22+
counterintuitive.
23+
24+
## Root Cause
25+
26+
`--cast-bf16` was downcasting **all** fp32 tensors to bf16 indiscriminately.
27+
The base checkpoint and the FastVideo `final_linear` / adapter modules
28+
require eight specific tensors to remain in fp32:
29+
30+
- LayerNorm `gamma` / `beta` weights for the final residual exit
31+
- Adapter projection biases
32+
- A handful of scale parameters in the output projection chain
33+
34+
These tensors participate in chains where bf16 precision causes accumulation
35+
error large enough to drift the parity check. The base DiT happened to not
36+
hit those specific chains in the path the test exercised (different
37+
attention mask shape, different audio interleave); the distill variant did.
38+
39+
## Fix / Workaround
40+
41+
Added `_FP32_KEEP_SUFFIXES` allowlist to
42+
`convert_magi_human_to_diffusers.py` (commit `829f70d3`) and gated `--cast-
43+
bf16` on it. Tensors whose state-dict key ends with any allowlisted suffix
44+
keep their original fp32 dtype regardless of the flag.
45+
46+
Distill DiT parity went from `diff_mean=0.114` (silently wrong) to bit-exact
47+
in one commit.
48+
49+
## Prevention
50+
51+
1. **Treat `--cast-bf16` as opinionated, not blanket.** Any conversion
52+
script that supports a global dtype downcast flag MUST own an explicit
53+
allowlist of fp32-keep tensors, documented at the top of the file.
54+
55+
2. **The `add-model-conversion` skill** should enforce two checks for any
56+
converter that ships a `--cast-bf16`-style flag:
57+
- Run the parity test for **every** variant of the model (base, distill,
58+
SR, etc.), not just the headline variant. Different variants exercise
59+
different code paths.
60+
- Diff the converted checkpoint's dtype map against the upstream
61+
reference and assert the allowlist covers every fp32 tensor in the
62+
reference.
63+
64+
3. **For MagiHuman specifically**: if you add or rename DiT modules that
65+
touch `final_linear`, the adapter, or any LayerNorm in the residual exit
66+
path, **check that any fp32-required tensors are covered by
67+
`_FP32_KEEP_SUFFIXES`** in the conversion script and re-run
68+
`test_magi_human_distill_parity` (it's the canary).
69+
70+
4. The lesson generalizes beyond MagiHuman: any DiT that uses bf16 mixed
71+
precision but keeps specific tensors in fp32 (a common pattern with
72+
flash-attn-style backends) needs this allowlist for any conversion that
73+
downcasts.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
---
2+
date: 2026-05-07
3+
experiment: PR #1280 (daVinci-MagiHuman port), DiT parity bring-up
4+
category: porting
5+
severity: important
6+
---
7+
8+
# DiT Dtype Boundary Alignment with Flash-Attn-Style Backends
9+
10+
## What Happened
11+
12+
DiT bit-exact parity for daVinci-MagiHuman against the upstream reference
13+
sat at `diff_max=0.5` after the architecture port was complete and weight
14+
loading was correct. The error grew with depth (later layers diverged more
15+
than earlier ones), suggesting an accumulating numerical drift rather than
16+
a structural mismatch. None of the obvious culprits (RoPE, GQA expansion,
17+
attention mask handling) accounted for the pattern.
18+
19+
## Root Cause
20+
21+
Four cumulative dtype-boundary mismatches, each individually small but
22+
together pushing parity from `diff_max=0.5` to bit-exact (`diff_max=0.0`):
23+
24+
1. **SDPA inputs were not cast to bf16.** Upstream's `flash_attn_with_cp`
25+
internally casts Q/K/V to bf16 at `dit_module.py:508` before the kernel.
26+
FastVideo was passing fp32 tensors through, getting numerically different
27+
intermediates even though the kernel accepts both.
28+
29+
2. **Post-attention output was kept in bf16 across the per-head gating
30+
multiply.** Upstream upcasts to fp32 before the gating, FastVideo did the
31+
gate in bf16 then upcast.
32+
33+
3. **A residual-stream cast at the block boundary.** FastVideo had a
34+
`.to(bf16)` then `.to(fp32)` at the start of each block. Upstream keeps
35+
the residual stream **continuously in fp32** across all 40 layers; only
36+
the inputs to specific kernels are temporarily downcast.
37+
38+
4. **Parity test scheduler used a double-shift.** A separate per-block fix
39+
(Wave 11 production migration) — single-shift schedule is what upstream
40+
uses; the parity test was double-shifting.
41+
42+
## Fix / Workaround
43+
44+
Four cumulative changes in `fastvideo/models/dits/magi_human.py` (commit
45+
`3a4816cb`), each with a comment at the call site explaining the upstream
46+
parity rationale:
47+
48+
- Cast SDPA inputs to bf16 right before the attention call.
49+
- Upcast attention output to fp32 before the per-head gate multiply.
50+
- Drop the residual-stream `.to(bf16)`/`.to(fp32)` wrapper at the block
51+
boundary; let the residual stay fp32 throughout.
52+
- Single-shift schedule in the parity test fixture (matches upstream Wave 11).
53+
54+
## Prevention
55+
56+
1. **For any DiT port with a flash-attn-style backend**, treat the dtype of
57+
the residual stream as a load-bearing invariant, not a performance knob.
58+
Document it in the model's per-pipeline AGENTS.md. MagiHuman's invariant:
59+
*residual stream stays fp32 across all blocks; only kernel inputs are
60+
temporarily bf16*.
61+
62+
2. **Use layer-by-layer activation hooks** when DiT parity is close-but-not-
63+
bit-exact and the gap grows with depth. The
64+
`fastvideo/hooks/activation_trace.py` infra exists exactly for this case
65+
(`add-model-trace` skill). In MagiHuman's case it would have localized the
66+
first divergence point in one pass.
67+
68+
3. **The `add-model-port-dit` skill** should explicitly call out:
69+
- SDPA input dtype must match the upstream kernel's internal cast.
70+
- Post-attention upcast happens **before** any per-head gate, not after.
71+
- Residual stream dtype across block boundaries is a parity invariant.
72+
These rules apply to any DiT port whose upstream uses a flash-attn-style
73+
backend (`flash_attn_with_cp`, `flex_flash_attn_func`, etc.).
74+
75+
4. **Add an "intermediate-layer parity" test** for new DiT ports — comparing
76+
activations at layer 5, 10, 20, 30 — not just the final output. A growing-
77+
with-depth pattern is otherwise indistinguishable from "almost right".
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---
2+
date: 2026-05-07
3+
experiment: PR #1280 (daVinci-MagiHuman port), Wave 14
4+
category: porting
5+
severity: critical
6+
---
7+
8+
# Silent Channel-Major Token-Packing Bugs
9+
10+
## What Happened
11+
12+
While porting daVinci-MagiHuman (`fastvideo/pipelines/basic/magi_human/`),
13+
the pipeline-parity test passed bit-exactly but the E2E user-visible output
14+
was **pure static noise**. Latent tensors compared identically against the
15+
upstream reference at every checkpointed boundary, yet decoded videos showed
16+
no recognizable content. The discrepancy reproduced on every variant
17+
(base / distill / SR-540p / SR-1080p) with the same noise profile.
18+
19+
## Root Cause
20+
21+
Video tokens were being packed **spatial-major** instead of **channel-major**:
22+
23+
```python
24+
# What we had (spatial-major, WRONG)
25+
einops.rearrange(x, "b c (T pT) (H pH) (W pW) -> b (T H W) (pT pH pW C)", ...)
26+
27+
# What upstream's UnfoldNd produces (channel-major, CORRECT)
28+
einops.rearrange(x, "b c (T pT) (H pH) (W pW) -> b (T H W) (C pT pH pW)", ...)
29+
```
30+
31+
A single-character einops reorder. The pipeline-parity test used FastVideo's
32+
own packer on **both** sides of the comparison, so the bug was invisible there
33+
— both sides agreed on the wrong layout. The DiT consumed those tokens
34+
without complaint because the channel dimension only matters at decode time,
35+
when the VAE's first conv expects channel-major input. By that point the test
36+
boundary was already passed.
37+
38+
The bug was load-bearing for any token-packed format that downstream feeds
39+
into a `UnfoldNd`-shaped consumer. Wave 14 of the port took multiple bug-hunt
40+
iterations and an Oracle consultation to localize.
41+
42+
## Fix / Workaround
43+
44+
Single-character einops change in `stages/latent_preparation.py:_img2tokens`
45+
(commit `6d190693` of the original PR). After the fix, all four variants
46+
produced expected E2E output and the pipeline-parity tests still passed
47+
because both sides of the parity check are now correct.
48+
49+
## Prevention
50+
51+
1. **Never use the FastVideo-side packer on both sides of a parity test.**
52+
At least one parity boundary must compare against an upstream tensor
53+
produced by the upstream packer. For MagiHuman this means a separate
54+
`_img2tokens` parity test that feeds upstream `UnfoldNd` output as the
55+
reference, not FastVideo's reformatted equivalent.
56+
57+
2. **Add an E2E hash check** alongside latent-parity. The mp4 SHA was the
58+
first signal that something was wrong; if it had been part of the standard
59+
parity battery, the bug would have surfaced in Wave 1, not Wave 14. See
60+
`fastvideo/tests/ssim/test_magi_human_similarity.py` for the CI version.
61+
62+
3. **For any new model port that involves explicit tensor reshaping into
63+
tokens**, document the expected packing order (`(C pT pH pW)` vs
64+
`(pT pH pW C)`) at the call site and assert the layout matches the
65+
downstream consumer's expectation.
66+
67+
4. The `add-model-port-dit` skill's parity gate should require an E2E hash
68+
check for any DiT that does video token packing, not just latent
69+
bit-exactness.

.agents/memory/codebase-map/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ FastVideo-WorldModel/
3131
│ │ │ └── distribution_matching/ # DMD2Method, SelfForcingMethod
3232
│ │ ├── models/ # Per-role model wrappers (ModelBase, CausalModelBase)
3333
│ │ │ ├── wan/ # WanModel, WanCausalModel
34-
│ │ │ └── matrixgame/ # MatrixGameModel, MatrixGameCausalModel
34+
│ │ │ └── matrixgame2/ # MatrixGame2Model, MatrixGame2CausalModel
3535
│ │ ├── callbacks/ # Composable hooks (grad_clip, ema, validation)
3636
│ │ └── utils/ # Config, builder, checkpoint, optimizer, tracking
3737
│ ├── training/ # Legacy training infrastructure (being phased out)
@@ -44,7 +44,7 @@ FastVideo-WorldModel/
4444
│ │ ├── wan_distillation_pipeline.py # Wan distillation
4545
│ │ ├── self_forcing_distillation_pipeline.py # Self-forcing distill
4646
│ │ ├── ltx2_training_pipeline.py # LTX-2 training
47-
│ │ └── matrixgame_training_pipeline.py # MatrixGame training
47+
│ │ └── matrixgame2_training_pipeline.py # Matrix-Game 2.0 training
4848
│ ├── attention/ # Attention backends
4949
│ ├── distributed/ # Sequence/tensor parallel utilities
5050
│ ├── layers/ # Tensor-parallel layers
@@ -95,7 +95,7 @@ FastVideo-WorldModel/
9595
| Wan distillation (DMD) | `fastvideo/training/wan_distillation_pipeline.py` | `torchrun --nproc_per_node N` |
9696
| Self-forcing distill | `fastvideo/training/wan_self_forcing_distillation_pipeline.py` | `torchrun --nproc_per_node N` |
9797
| LTX-2 finetune | `fastvideo/training/ltx2_training_pipeline.py` | `torchrun --nproc_per_node N` |
98-
| MatrixGame | `fastvideo/training/matrixgame_training_pipeline.py` | `torchrun --nproc_per_node N` |
98+
| Matrix-Game 2.0 | `fastvideo/training/matrixgame2_training_pipeline.py` | `torchrun --nproc_per_node N` |
9999

100100
## W&B Integration
101101

0 commit comments

Comments
 (0)