Compare commits
409
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ea7278c00 | ||
|
|
a5aa64ab6b | ||
|
|
0bcd15d5e2 | ||
|
|
0b0c692012 | ||
|
|
6c2a250fd0 | ||
|
|
0607bd78c7 | ||
|
|
c4bb13a650 | ||
|
|
d051603677 | ||
|
|
00a8e5bcb6 | ||
|
|
4117696a4f | ||
|
|
23cb8c0c2b | ||
|
|
ca7965ed28 | ||
|
|
61dffbe904 | ||
|
|
7ec6251133 | ||
|
|
b323cb787c | ||
|
|
e263249175 | ||
|
|
6c776a219b | ||
|
|
3f6893a098 | ||
|
|
d728a0dc5f | ||
|
|
d6ba5ced94 | ||
|
|
9139c0411b | ||
|
|
f571621ae7 | ||
|
|
96b7c0d223 | ||
|
|
74a52c027b | ||
|
|
2cd2018137 | ||
|
|
6627366805 | ||
|
|
7aa1f78e10 | ||
|
|
38096b94a3 | ||
|
|
4d05e9c020 | ||
|
|
62780cf53b | ||
|
|
40ff9af3fb | ||
|
|
93d921fe33 | ||
|
|
d26aa6b2d7 | ||
|
|
b9c397dd1a | ||
|
|
867f960bf0 | ||
|
|
1d7e7e2e35 | ||
|
|
0d93b16fdd | ||
|
|
7ba7dbbbd5 | ||
|
|
c7c2d77fae | ||
|
|
991fa391ff | ||
|
|
ecb15bf9e0 | ||
|
|
b24396561a | ||
|
|
0b80dd52db | ||
|
|
04878f0584 | ||
|
|
cd1ffc09c9 | ||
|
|
db4a60c5db | ||
|
|
9491c8638a | ||
|
|
6809a751fb | ||
|
|
8322b01815 | ||
|
|
9edc8adf5f | ||
|
|
e1f3904799 | ||
|
|
02f1ce11ae | ||
|
|
7f03e03dc6 | ||
|
|
e3b88bb12a | ||
|
|
cb66acd400 | ||
|
|
442e2d2e18 | ||
|
|
dd35763ad6 | ||
|
|
e90be598e5 | ||
|
|
ba5e81083c | ||
|
|
76ce9c7fd6 | ||
|
|
08d99c089e | ||
|
|
20751a21aa | ||
|
|
9dd2a837f4 | ||
|
|
93aab45ac2 | ||
|
|
017ce6602d | ||
|
|
a575055eec | ||
|
|
81f3fec7fd | ||
|
|
d265a454bf | ||
|
|
c100c66578 | ||
|
|
8760eb7a06 | ||
|
|
361f919c88 | ||
|
|
8b5377aab2 | ||
|
|
d995516da0 | ||
|
|
f47ad3f5b7 | ||
|
|
10bdf5e076 | ||
|
|
3e26db40b0 | ||
|
|
c73dd0ab55 | ||
|
|
430e52154e | ||
|
|
384eee8aef | ||
|
|
c4824c7764 | ||
|
|
9b0e57fe4b | ||
|
|
0100218594 | ||
|
|
39718cd54d | ||
|
|
37d06a832f | ||
|
|
8839ba8d4d | ||
|
|
61b91220c0 | ||
|
|
316f3876c2 | ||
|
|
614b59543c | ||
|
|
1c14afd559 | ||
|
|
9a3c45779c | ||
|
|
bfc9c01797 | ||
|
|
3a3ad3d209 | ||
|
|
aef4e9b3b1 | ||
|
|
a943220c11 | ||
|
|
556ac7088e | ||
|
|
e7456f1b75 | ||
|
|
c993d7393e | ||
|
|
7f83164233 | ||
|
|
4e52f47d1e | ||
|
|
e19913f6e9 | ||
|
|
2413a57651 | ||
|
|
7bb76b5ec9 | ||
|
|
0bd19a976b | ||
|
|
40b93784d2 | ||
|
|
33d3478bad | ||
|
|
3d8ac9d14b | ||
|
|
aaef49bfc6 | ||
|
|
cf6a00b9be | ||
|
|
1ae39562dd | ||
|
|
26064193e2 | ||
|
|
8446fc003e | ||
|
|
a28f2bab4b | ||
|
|
f82d8be4bf | ||
|
|
8e1775183e | ||
|
|
620bc36dc4 | ||
|
|
29ff16ec96 | ||
|
|
8f9d76a80d | ||
|
|
a4d9a75e2c | ||
|
|
b2db0c0a13 | ||
|
|
6aa7d8a278 | ||
|
|
ccc9014430 | ||
|
|
a159b63c67 | ||
|
|
ac48bb3cd1 | ||
|
|
3987b9ddcd | ||
|
|
c7da2f5d60 | ||
|
|
39ae1decc0 | ||
|
|
1aed667377 | ||
|
|
c1612ff397 | ||
|
|
a534ba20a0 | ||
|
|
e9bbaca07d | ||
|
|
9bfa585448 | ||
|
|
b2062556a9 | ||
|
|
c9c5585758 | ||
|
|
9212f4f218 | ||
|
|
6388db815b | ||
|
|
7a4285189f | ||
|
|
a837fe841a | ||
|
|
f9e3680f11 | ||
|
|
98f761ec45 | ||
|
|
c041318f2c | ||
|
|
604e0205a4 | ||
|
|
13213395b4 | ||
|
|
46afee5998 | ||
|
|
c488fa1211 | ||
|
|
d3cff517cd | ||
|
|
2f3d407406 | ||
|
|
bcffa4026e | ||
|
|
6d6a10be7a | ||
|
|
73dd105f3d | ||
|
|
56d4a6074f | ||
|
|
c4ad4227c0 | ||
|
|
a63ccce73d | ||
|
|
0462e1b0e7 | ||
|
|
907f2100ec | ||
|
|
e0a3db5651 | ||
|
|
fca45bc8e1 | ||
|
|
86d639c848 | ||
|
|
00338aa9ca | ||
|
|
8537dcd6de | ||
|
|
8208536cd1 | ||
|
|
0653f8f3af | ||
|
|
e0d702decb | ||
|
|
541ef014ee | ||
|
|
ffc1a7a58b | ||
|
|
9028953625 | ||
|
|
6eb95693a1 | ||
|
|
c3567eb468 | ||
|
|
15568f27db | ||
|
|
126a75ad63 | ||
|
|
a2bfc7cdb2 | ||
|
|
b963a24612 | ||
|
|
fb7be2fe2c | ||
|
|
ab00392664 | ||
|
|
9f1e7c19d2 | ||
|
|
e8b0e4c61e | ||
|
|
9145ffdc46 | ||
|
|
c3d07c870b | ||
|
|
e8812bef0b | ||
|
|
b9be2449dc | ||
|
|
bc7a804618 | ||
|
|
7b094c945b | ||
|
|
eeb3e8a597 | ||
|
|
05406c5d1b | ||
|
|
99d04a7f98 | ||
|
|
1b2b2a0161 | ||
|
|
98d65835b5 | ||
|
|
422585d08f | ||
|
|
e59a1ce16a | ||
|
|
d71acc0eb5 | ||
|
|
af2934dd6b | ||
|
|
1801512818 | ||
|
|
5ae05b032e | ||
|
|
7a592ff09a | ||
|
|
8b23984c79 | ||
|
|
bf18371afe | ||
|
|
69349dd2aa | ||
|
|
8d89f30d3f | ||
|
|
10546353da | ||
|
|
9fb74b9732 | ||
|
|
521dee0e82 | ||
|
|
229419208e | ||
|
|
65f3b946b9 | ||
|
|
191fcbf46c | ||
|
|
755a4e4470 | ||
|
|
9709b7513b | ||
|
|
32cd603515 | ||
|
|
d4bdd3621a | ||
|
|
e2f8322842 | ||
|
|
6966f9e0bc | ||
|
|
743f4ed5f9 | ||
|
|
1c04ace573 | ||
|
|
6cbff73687 | ||
|
|
dec8b10939 | ||
|
|
133a5278af | ||
|
|
da856274cc | ||
|
|
a253856147 | ||
|
|
6e25d94ebc | ||
|
|
cae8fa18dc | ||
|
|
821e5a0832 | ||
|
|
c1abc42782 | ||
|
|
ef15ea2391 | ||
|
|
1ea2517e22 | ||
|
|
0c63528c59 | ||
|
|
b063f8ca41 | ||
|
|
e7fff0173a | ||
|
|
d82abc271e | ||
|
|
970409962f | ||
|
|
055586703d | ||
|
|
5d89f86675 | ||
|
|
19a51a1fe6 | ||
|
|
d3232cea5a | ||
|
|
0c90c8c24d | ||
|
|
4c08ffce49 | ||
|
|
af4a77553c | ||
|
|
c096fda1eb | ||
|
|
8f47e85be0 | ||
|
|
90d3bd19eb | ||
|
|
afb4f7d3c5 | ||
|
|
02e1143f22 | ||
|
|
e2f4d1a7b5 | ||
|
|
f037351146 | ||
|
|
1ee11e08dc | ||
|
|
595f0ea60e | ||
|
|
d921832cd2 | ||
|
|
629697629a | ||
|
|
a25313beec | ||
|
|
dbde64385b | ||
|
|
9d909f5f04 | ||
|
|
76b0550c15 | ||
|
|
384c1e9493 | ||
|
|
b1dbcc93f6 | ||
|
|
b93833772e | ||
|
|
30b523edd6 | ||
|
|
6aab7f3832 | ||
|
|
9cd53fe5f8 | ||
|
|
6a32cf3a5e | ||
|
|
98be9b3da2 | ||
|
|
c53e85b767 | ||
|
|
40a8bd2d3b | ||
|
|
31aa115611 | ||
|
|
98ac10a528 | ||
|
|
a5a6d171e5 | ||
|
|
51ed1ea423 | ||
|
|
00ec3e7388 | ||
|
|
0d626ef2d1 | ||
|
|
b36d0ef085 | ||
|
|
10c8c5df4d | ||
|
|
31e26abec4 | ||
|
|
9e83ba630c | ||
|
|
fc02a9ce8e | ||
|
|
3d36160fc4 | ||
|
|
a11ec43de2 | ||
|
|
2656d6530c | ||
|
|
e3f54e7169 | ||
|
|
4ba5681307 | ||
|
|
8c23c86994 | ||
|
|
78d606a3a7 | ||
|
|
c4e108de78 | ||
|
|
16bf2eaf77 | ||
|
|
1d15d974fa | ||
|
|
5e57868b76 | ||
|
|
6be280c914 | ||
|
|
3ccdec9798 | ||
|
|
8658f774f3 | ||
|
|
4f3ad3f6df | ||
|
|
b454aa56c3 | ||
|
|
f9b8e30ff3 | ||
|
|
0356205b84 | ||
|
|
719a1879bd | ||
|
|
b57180bf97 | ||
|
|
7cebf5f82c | ||
|
|
dd0f4b6753 | ||
|
|
d303b4e03a | ||
|
|
31b719ae49 | ||
|
|
887aaf3d3e | ||
|
|
b1d89eba1f | ||
|
|
995a5fdf97 | ||
|
|
70a70b689e | ||
|
|
4d6ac89b43 | ||
|
|
4171cacd93 | ||
|
|
b2ade71467 | ||
|
|
82ed9fe58d | ||
|
|
3d8cc4f0a0 | ||
|
|
0557f7a7d9 | ||
|
|
dc66cd97ef | ||
|
|
6da206e196 | ||
|
|
87f98c9b8b | ||
|
|
e60601df7f | ||
|
|
eed9c4bfbf | ||
|
|
1dee77f4a4 | ||
|
|
b80148819c | ||
|
|
c3b971488e | ||
|
|
88e753f281 | ||
|
|
77832059cc | ||
|
|
633d393568 | ||
|
|
5854aec2ce | ||
|
|
30e45c2411 | ||
|
|
2a4fe697a6 | ||
|
|
921db7479d | ||
|
|
7f539424cb | ||
|
|
19a838f54f | ||
|
|
d922ab2cbc | ||
|
|
9ea77d37f3 | ||
|
|
2e35b0c6bd | ||
|
|
1c627a3f98 | ||
|
|
a931efe33a | ||
|
|
041e5e9029 | ||
|
|
efcc245c2e | ||
|
|
922e7e0813 | ||
|
|
c62a8514b0 | ||
|
|
3eb8081801 | ||
|
|
3505d09564 | ||
|
|
570607945c | ||
|
|
d3a821cdcf | ||
|
|
3f24578139 | ||
|
|
89fcf08378 | ||
|
|
c2930b2aa1 | ||
|
|
019239690b | ||
|
|
d6119c1f82 | ||
|
|
84214c80bb | ||
|
|
c2d7143c72 | ||
|
|
afdb6fbfa5 | ||
|
|
ba4c02d883 | ||
|
|
2c137931f3 | ||
|
|
36682797a0 | ||
|
|
0ef1357a77 | ||
|
|
a75d19786a | ||
|
|
be548a78ea | ||
|
|
6a610e2bc9 | ||
|
|
321d5112b4 | ||
|
|
ba75ad82db | ||
|
|
58caa5109f | ||
|
|
2f3ca8aaad | ||
|
|
3a67319cb6 | ||
|
|
266fa044b3 | ||
|
|
f3398db868 | ||
|
|
fda02036bc | ||
|
|
68179cd752 | ||
|
|
2dd5760291 | ||
|
|
af2ee9c78a | ||
|
|
1c80371b27 | ||
|
|
eef473225d | ||
|
|
44fb84ef6a | ||
|
|
e8597b7448 | ||
|
|
e2252c0a5e | ||
|
|
72cb427cd9 | ||
|
|
63030cf6ec | ||
|
|
773d44b875 | ||
|
|
1df513922f | ||
|
|
30c45620a2 | ||
|
|
6b2c731596 | ||
|
|
e6022c20b2 | ||
|
|
460f6e398e | ||
|
|
d2ffec5cce | ||
|
|
cb12e88713 | ||
|
|
0958c344b8 | ||
|
|
71dc27ea7b | ||
|
|
1263449d2a | ||
|
|
b4de5a9f1b | ||
|
|
8acd8e21f9 | ||
|
|
d45d82334f | ||
|
|
6392bd40a9 | ||
|
|
17f07bc313 | ||
|
|
325861fb99 | ||
|
|
c5088670c8 | ||
|
|
0403c6f47e | ||
|
|
a55b1cdde4 | ||
|
|
aec9a20a16 | ||
|
|
b4458e5bae | ||
|
|
a790153705 | ||
|
|
0df1445d0d | ||
|
|
038da6e02b | ||
|
|
3fb2fbe1a2 | ||
|
|
acea9d23e1 | ||
|
|
b7a448cf5b | ||
|
|
04e32991c6 | ||
|
|
424c643b6e | ||
|
|
de803cb250 | ||
|
|
effc1d3492 | ||
|
|
b1ddb1ba33 | ||
|
|
9ff65c83b6 | ||
|
|
15473f3c77 | ||
|
|
490641cf47 | ||
|
|
4ad6880ca6 | ||
|
|
9d0af28307 | ||
|
|
d6dfe95466 | ||
|
|
636d3b743e | ||
|
|
e3a5c6954f | ||
|
|
f633e30ebb |
@@ -1,94 +0,0 @@
|
||||
# Agent Infrastructure — Status Dashboard
|
||||
|
||||
Developer-maintained overview of all agent components and their maturity.
|
||||
Use this to understand what exists, how complete it is, and how much to trust it.
|
||||
|
||||
_Last synced: 2026-03-02_
|
||||
|
||||
> To resync this dashboard, use the workflow: `.agents/workflows/sync-dashboard.md`
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| Category | Total | ✅ Ready | 🟡 Draft | 🔴 Stub | Trust |
|
||||
|----------|-------|---------|---------|---------|-------|
|
||||
| Skills | 8 | 0 | 8 | 0 | Low — newly created, untested |
|
||||
| Workflows (SOPs) | 4 | 0 | 4 | 0 | Low — newly created, untested |
|
||||
| Memory files | 4 | 1 | 3 | 0 | Medium — codebase_map is solid |
|
||||
| Lessons | 0 | — | — | — | N/A — empty |
|
||||
| Exploration logs | 0 | — | — | — | N/A — empty |
|
||||
|
||||
---
|
||||
|
||||
## Skills (`.agents/skills/`)
|
||||
|
||||
| Skill | File | Status | Trust | Tested | Notes |
|
||||
|-------|------|--------|-------|--------|-------|
|
||||
| Launch Experiment | `launch-experiment.md` | 🟡 Draft | Low | ❌ | Needs dry-run validation |
|
||||
| Monitor Experiment | `monitor-experiment.md` | 🟡 Draft | Low | ❌ | Requires W&B API access to test |
|
||||
| Summarize Run | `summarize-run.md` | 🟡 Draft | Low | ❌ | Pattern from existing test infra |
|
||||
| Log Experiment | `log-experiment.md` | 🟡 Draft | Low | ❌ | Journal formatting only |
|
||||
| Evaluate Video Quality | `evaluate-video-quality.md` | 🟡 Draft | Low | ❌ | SSIM section most mature |
|
||||
| Index Related Work | `index-related-work.md` | 🟡 Draft | Low | ❌ | Schema defined, no entries yet |
|
||||
| Search Related Work | `search-related-work.md` | 🟡 Draft | Low | ❌ | Depends on indexed entries |
|
||||
| Skill Template | `SKILL_TEMPLATE.md` | ✅ Ready | High | ✅ | Meta-template, stable |
|
||||
|
||||
### Trust Level Definitions
|
||||
- **High**: Tested in production, validated against real experiments
|
||||
- **Medium**: Logic is sound, partially tested or based on existing patterns
|
||||
- **Low**: Newly written, not yet validated
|
||||
- **None**: Placeholder only
|
||||
|
||||
---
|
||||
|
||||
## Workflows / SOPs (`.agents/workflows/`)
|
||||
|
||||
| Workflow | File | Status | Trust | Tested | Notes |
|
||||
|----------|------|--------|-------|--------|-------|
|
||||
| Experiment Lifecycle | `experiment-lifecycle.md` | 🟡 Draft | Low | ❌ | End-to-end flow, untested |
|
||||
| Evaluation Development | `evaluation-development.md` | 🟡 Draft | Low | ❌ | Metric dev process |
|
||||
| Experiment Journaling | `experiment-journaling.md` | 🟡 Draft | Low | ❌ | Journaling cadence |
|
||||
| Lesson Capture | `lesson-capture.md` | 🟡 Draft | Low | ❌ | Post-experiment reflection |
|
||||
| Sync Dashboard | `sync-dashboard.md` | 🟡 Draft | Low | ❌ | This dashboard's updater |
|
||||
|
||||
---
|
||||
|
||||
## Memory (`.agents/memory/`)
|
||||
|
||||
| File | Status | Trust | Notes |
|
||||
|------|--------|-------|-------|
|
||||
| `codebase_map.md` | ✅ Ready | High | Synthesized from full repo research |
|
||||
| `experiment_journal.md` | 🟡 Draft | Medium | Schema defined, no entries yet |
|
||||
| `evaluation_registry.md` | 🟡 Draft | Medium | SSIM/loss metrics documented |
|
||||
| `related_work/README.md` | 🟡 Draft | Medium | Schema defined, no entries yet |
|
||||
|
||||
---
|
||||
|
||||
## Lessons (`.agents/lessons/`)
|
||||
|
||||
| File | Category | Severity | Notes |
|
||||
|------|----------|----------|-------|
|
||||
|
||||
_No lessons captured yet._
|
||||
|
||||
---
|
||||
|
||||
## Exploration Logs (`.agents/exploration/`)
|
||||
|
||||
| File | Status | Topic | Notes |
|
||||
|------|--------|-------|-------|
|
||||
|
||||
_No exploration logs yet._
|
||||
|
||||
---
|
||||
|
||||
## What to Do Next
|
||||
|
||||
1. **Validate skills**: Run a minimal training experiment using the
|
||||
`experiment-lifecycle` SOP to test `launch-experiment` → `monitor-experiment`
|
||||
→ `summarize-run` end-to-end.
|
||||
2. **Index first related work**: Use `index-related-work` to add at least one
|
||||
paper (e.g., the Self-Forcing paper used in the codebase).
|
||||
3. **Capture first lesson**: After the validation run, capture any findings.
|
||||
4. **Promote to Ready**: As each skill/SOP is tested, update its status here.
|
||||
@@ -1,46 +0,0 @@
|
||||
# Exploration Logs
|
||||
|
||||
This directory holds draft procedures and investigation notes for tasks that
|
||||
don't yet have a standardized skill or SOP. Each exploration should follow this
|
||||
template.
|
||||
|
||||
## When to Create an Exploration Log
|
||||
|
||||
- You are working on a task with no existing skill or workflow.
|
||||
- You are experimenting with a new metric, training technique, or tool.
|
||||
- You want to document findings before they are promoted to a standard.
|
||||
|
||||
## File Naming
|
||||
|
||||
`<topic-slug>.md` — e.g., `fvd-metric-investigation.md`
|
||||
|
||||
## Template
|
||||
|
||||
```markdown
|
||||
# Exploration Log: <Topic>
|
||||
|
||||
## Status: draft | under_review | promoted | abandoned
|
||||
|
||||
## Context
|
||||
<Why this exploration is needed — link to experiment or task if applicable.>
|
||||
|
||||
## Progress
|
||||
- [ ] Step 1: ...
|
||||
- [ ] Step 2: ...
|
||||
|
||||
## Findings
|
||||
<What you have learned so far.>
|
||||
|
||||
## Mistakes / Dead Ends
|
||||
<What didn't work and why — these become lessons.>
|
||||
|
||||
## Proposed Standardization
|
||||
<If this works, describe the skill/SOP/workflow to create.>
|
||||
```
|
||||
|
||||
## Lifecycle
|
||||
|
||||
1. **Create** during exploration mode.
|
||||
2. **Update** as you make progress.
|
||||
3. **Promote**: If findings are solid, create a skill in `.agents/skills/` or an SOP in `.agents/workflows/`.
|
||||
4. **Archive mistakes**: Move failures into `.agents/lessons/`.
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
date: 2026-05-07
|
||||
experiment: PR #1280 (daVinci-MagiHuman port), distill DiT parity bring-up
|
||||
category: porting
|
||||
severity: important
|
||||
---
|
||||
|
||||
# Conversion `--cast-bf16` Needs an FP32-Keep Suffix Allowlist
|
||||
|
||||
## What Happened
|
||||
|
||||
`scripts/checkpoint_conversion/convert_magi_human_to_diffusers.py --cast-bf16`
|
||||
produced a converted distill DiT checkpoint that loaded cleanly, ran end-to-
|
||||
end, and emitted reasonable output — but `test_magi_human_distill_parity`
|
||||
showed `diff_mean=0.114` against the upstream reference. The base DiT was
|
||||
bit-exact with the same conversion script. Only the distill variant
|
||||
regressed.
|
||||
|
||||
The error was small enough that visual quality looked normal, but large
|
||||
enough to fail bit-exact parity. The MagiHuman base + distill DiTs share
|
||||
most of their architecture, so a difference that affected only distill was
|
||||
counterintuitive.
|
||||
|
||||
## Root Cause
|
||||
|
||||
`--cast-bf16` was downcasting **all** fp32 tensors to bf16 indiscriminately.
|
||||
The base checkpoint and the FastVideo `final_linear` / adapter modules
|
||||
require eight specific tensors to remain in fp32:
|
||||
|
||||
- LayerNorm `gamma` / `beta` weights for the final residual exit
|
||||
- Adapter projection biases
|
||||
- A handful of scale parameters in the output projection chain
|
||||
|
||||
These tensors participate in chains where bf16 precision causes accumulation
|
||||
error large enough to drift the parity check. The base DiT happened to not
|
||||
hit those specific chains in the path the test exercised (different
|
||||
attention mask shape, different audio interleave); the distill variant did.
|
||||
|
||||
## Fix / Workaround
|
||||
|
||||
Added `_FP32_KEEP_SUFFIXES` allowlist to
|
||||
`convert_magi_human_to_diffusers.py` (commit `829f70d3`) and gated `--cast-
|
||||
bf16` on it. Tensors whose state-dict key ends with any allowlisted suffix
|
||||
keep their original fp32 dtype regardless of the flag.
|
||||
|
||||
Distill DiT parity went from `diff_mean=0.114` (silently wrong) to bit-exact
|
||||
in one commit.
|
||||
|
||||
## Prevention
|
||||
|
||||
1. **Treat `--cast-bf16` as opinionated, not blanket.** Any conversion
|
||||
script that supports a global dtype downcast flag MUST own an explicit
|
||||
allowlist of fp32-keep tensors, documented at the top of the file.
|
||||
|
||||
2. **The `add-model-conversion` skill** should enforce two checks for any
|
||||
converter that ships a `--cast-bf16`-style flag:
|
||||
- Run the parity test for **every** variant of the model (base, distill,
|
||||
SR, etc.), not just the headline variant. Different variants exercise
|
||||
different code paths.
|
||||
- Diff the converted checkpoint's dtype map against the upstream
|
||||
reference and assert the allowlist covers every fp32 tensor in the
|
||||
reference.
|
||||
|
||||
3. **For MagiHuman specifically**: if you add or rename DiT modules that
|
||||
touch `final_linear`, the adapter, or any LayerNorm in the residual exit
|
||||
path, **check that any fp32-required tensors are covered by
|
||||
`_FP32_KEEP_SUFFIXES`** in the conversion script and re-run
|
||||
`test_magi_human_distill_parity` (it's the canary).
|
||||
|
||||
4. The lesson generalizes beyond MagiHuman: any DiT that uses bf16 mixed
|
||||
precision but keeps specific tensors in fp32 (a common pattern with
|
||||
flash-attn-style backends) needs this allowlist for any conversion that
|
||||
downcasts.
|
||||
@@ -0,0 +1,77 @@
|
||||
---
|
||||
date: 2026-05-07
|
||||
experiment: PR #1280 (daVinci-MagiHuman port), DiT parity bring-up
|
||||
category: porting
|
||||
severity: important
|
||||
---
|
||||
|
||||
# DiT Dtype Boundary Alignment with Flash-Attn-Style Backends
|
||||
|
||||
## What Happened
|
||||
|
||||
DiT bit-exact parity for daVinci-MagiHuman against the upstream reference
|
||||
sat at `diff_max=0.5` after the architecture port was complete and weight
|
||||
loading was correct. The error grew with depth (later layers diverged more
|
||||
than earlier ones), suggesting an accumulating numerical drift rather than
|
||||
a structural mismatch. None of the obvious culprits (RoPE, GQA expansion,
|
||||
attention mask handling) accounted for the pattern.
|
||||
|
||||
## Root Cause
|
||||
|
||||
Four cumulative dtype-boundary mismatches, each individually small but
|
||||
together pushing parity from `diff_max=0.5` to bit-exact (`diff_max=0.0`):
|
||||
|
||||
1. **SDPA inputs were not cast to bf16.** Upstream's `flash_attn_with_cp`
|
||||
internally casts Q/K/V to bf16 at `dit_module.py:508` before the kernel.
|
||||
FastVideo was passing fp32 tensors through, getting numerically different
|
||||
intermediates even though the kernel accepts both.
|
||||
|
||||
2. **Post-attention output was kept in bf16 across the per-head gating
|
||||
multiply.** Upstream upcasts to fp32 before the gating, FastVideo did the
|
||||
gate in bf16 then upcast.
|
||||
|
||||
3. **A residual-stream cast at the block boundary.** FastVideo had a
|
||||
`.to(bf16)` then `.to(fp32)` at the start of each block. Upstream keeps
|
||||
the residual stream **continuously in fp32** across all 40 layers; only
|
||||
the inputs to specific kernels are temporarily downcast.
|
||||
|
||||
4. **Parity test scheduler used a double-shift.** A separate per-block fix
|
||||
(Wave 11 production migration) — single-shift schedule is what upstream
|
||||
uses; the parity test was double-shifting.
|
||||
|
||||
## Fix / Workaround
|
||||
|
||||
Four cumulative changes in `fastvideo/models/dits/magi_human.py` (commit
|
||||
`3a4816cb`), each with a comment at the call site explaining the upstream
|
||||
parity rationale:
|
||||
|
||||
- Cast SDPA inputs to bf16 right before the attention call.
|
||||
- Upcast attention output to fp32 before the per-head gate multiply.
|
||||
- Drop the residual-stream `.to(bf16)`/`.to(fp32)` wrapper at the block
|
||||
boundary; let the residual stay fp32 throughout.
|
||||
- Single-shift schedule in the parity test fixture (matches upstream Wave 11).
|
||||
|
||||
## Prevention
|
||||
|
||||
1. **For any DiT port with a flash-attn-style backend**, treat the dtype of
|
||||
the residual stream as a load-bearing invariant, not a performance knob.
|
||||
Document it in the model's per-pipeline AGENTS.md. MagiHuman's invariant:
|
||||
*residual stream stays fp32 across all blocks; only kernel inputs are
|
||||
temporarily bf16*.
|
||||
|
||||
2. **Use layer-by-layer activation hooks** when DiT parity is close-but-not-
|
||||
bit-exact and the gap grows with depth. The
|
||||
`fastvideo/hooks/activation_trace.py` infra exists exactly for this case
|
||||
(`add-model-trace` skill). In MagiHuman's case it would have localized the
|
||||
first divergence point in one pass.
|
||||
|
||||
3. **The `add-model-port-dit` skill** should explicitly call out:
|
||||
- SDPA input dtype must match the upstream kernel's internal cast.
|
||||
- Post-attention upcast happens **before** any per-head gate, not after.
|
||||
- Residual stream dtype across block boundaries is a parity invariant.
|
||||
These rules apply to any DiT port whose upstream uses a flash-attn-style
|
||||
backend (`flash_attn_with_cp`, `flex_flash_attn_func`, etc.).
|
||||
|
||||
4. **Add an "intermediate-layer parity" test** for new DiT ports — comparing
|
||||
activations at layer 5, 10, 20, 30 — not just the final output. A growing-
|
||||
with-depth pattern is otherwise indistinguishable from "almost right".
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
date: 2026-05-07
|
||||
experiment: PR #1280 (daVinci-MagiHuman port), Wave 14
|
||||
category: porting
|
||||
severity: critical
|
||||
---
|
||||
|
||||
# Silent Channel-Major Token-Packing Bugs
|
||||
|
||||
## What Happened
|
||||
|
||||
While porting daVinci-MagiHuman (`fastvideo/pipelines/basic/magi_human/`),
|
||||
the pipeline-parity test passed bit-exactly but the E2E user-visible output
|
||||
was **pure static noise**. Latent tensors compared identically against the
|
||||
upstream reference at every checkpointed boundary, yet decoded videos showed
|
||||
no recognizable content. The discrepancy reproduced on every variant
|
||||
(base / distill / SR-540p / SR-1080p) with the same noise profile.
|
||||
|
||||
## Root Cause
|
||||
|
||||
Video tokens were being packed **spatial-major** instead of **channel-major**:
|
||||
|
||||
```python
|
||||
# What we had (spatial-major, WRONG)
|
||||
einops.rearrange(x, "b c (T pT) (H pH) (W pW) -> b (T H W) (pT pH pW C)", ...)
|
||||
|
||||
# What upstream's UnfoldNd produces (channel-major, CORRECT)
|
||||
einops.rearrange(x, "b c (T pT) (H pH) (W pW) -> b (T H W) (C pT pH pW)", ...)
|
||||
```
|
||||
|
||||
A single-character einops reorder. The pipeline-parity test used FastVideo's
|
||||
own packer on **both** sides of the comparison, so the bug was invisible there
|
||||
— both sides agreed on the wrong layout. The DiT consumed those tokens
|
||||
without complaint because the channel dimension only matters at decode time,
|
||||
when the VAE's first conv expects channel-major input. By that point the test
|
||||
boundary was already passed.
|
||||
|
||||
The bug was load-bearing for any token-packed format that downstream feeds
|
||||
into a `UnfoldNd`-shaped consumer. Wave 14 of the port took multiple bug-hunt
|
||||
iterations and an Oracle consultation to localize.
|
||||
|
||||
## Fix / Workaround
|
||||
|
||||
Single-character einops change in `stages/latent_preparation.py:_img2tokens`
|
||||
(commit `6d190693` of the original PR). After the fix, all four variants
|
||||
produced expected E2E output and the pipeline-parity tests still passed
|
||||
because both sides of the parity check are now correct.
|
||||
|
||||
## Prevention
|
||||
|
||||
1. **Never use the FastVideo-side packer on both sides of a parity test.**
|
||||
At least one parity boundary must compare against an upstream tensor
|
||||
produced by the upstream packer. For MagiHuman this means a separate
|
||||
`_img2tokens` parity test that feeds upstream `UnfoldNd` output as the
|
||||
reference, not FastVideo's reformatted equivalent.
|
||||
|
||||
2. **Add an E2E hash check** alongside latent-parity. The mp4 SHA was the
|
||||
first signal that something was wrong; if it had been part of the standard
|
||||
parity battery, the bug would have surfaced in Wave 1, not Wave 14. See
|
||||
`fastvideo/tests/ssim/test_magi_human_similarity.py` for the CI version.
|
||||
|
||||
3. **For any new model port that involves explicit tensor reshaping into
|
||||
tokens**, document the expected packing order (`(C pT pH pW)` vs
|
||||
`(pT pH pW C)`) at the call site and assert the layout matches the
|
||||
downstream consumer's expectation.
|
||||
|
||||
4. The `add-model-port-dit` skill's parity gate should require an E2E hash
|
||||
check for any DiT that does video token packing, not just latent
|
||||
bit-exactness.
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
date: 2026-05-22
|
||||
experiment: PR #1386 DreamVerse app CI backend tests
|
||||
category: infrastructure
|
||||
severity: important
|
||||
---
|
||||
|
||||
# DreamVerse App CI Streaming Imports Need GPU
|
||||
|
||||
## What Happened
|
||||
|
||||
DreamVerse app CI backend pytest collection imports FastVideo streaming surfaces.
|
||||
When those tests run in a CPU-only Modal environment, collection can fail before
|
||||
any app assertions run with Triton reporting:
|
||||
|
||||
```text
|
||||
RuntimeError: 0 active drivers
|
||||
```
|
||||
|
||||
## Root Cause
|
||||
|
||||
Some streaming import paths can import `fastvideo_kernel` at module import time.
|
||||
Triton then probes for an active GPU driver during pytest collection. A CPU-only
|
||||
Modal container has no active driver, so the failure appears as an import-time
|
||||
collection error rather than a DreamVerse app behavior failure.
|
||||
|
||||
## Fix / Workaround
|
||||
|
||||
For PR #1386, use a surgical CI fix: allocate a GPU to
|
||||
`run_dreamverse_app_tests`. Do not refactor core streaming/kernel imports just to
|
||||
unstick this app CI path.
|
||||
|
||||
Keep `build_kernel=False` for this job. The DreamVerse app backend test imports
|
||||
streaming surfaces but does not need to rebuild or exercise custom kernels.
|
||||
|
||||
## Prevention
|
||||
|
||||
When adding or modifying DreamVerse app CI jobs that import FastVideo streaming
|
||||
modules, make the GPU requirement explicit if the import graph may touch
|
||||
`fastvideo_kernel`. Prefer small CI resource fixes for app test collection issues
|
||||
unless the product code genuinely requires lazy import cleanup.
|
||||
@@ -1,129 +0,0 @@
|
||||
# FastVideo-WorldModel — Codebase Map
|
||||
|
||||
High-level structural index for agent orientation. Updated 2026-03-08.
|
||||
|
||||
## Repository Layout
|
||||
|
||||
```
|
||||
FastVideo-WorldModel/
|
||||
├── fastvideo/ # Core Python package
|
||||
│ ├── models/ # Model implementations
|
||||
│ │ ├── dits/ # DiT transformers (wanvideo, ltx2, ...)
|
||||
│ │ ├── vaes/ # VAE models
|
||||
│ │ ├── encoders/ # Text/image encoders (T5, CLIP)
|
||||
│ │ ├── schedulers/ # Noise schedulers
|
||||
│ │ ├── upsamplers/ # Super-resolution models
|
||||
│ │ ├── audio/ # Audio models
|
||||
│ │ └── loader/ # Component loaders for HF repos
|
||||
│ ├── configs/ # Configuration system
|
||||
│ │ ├── models/ # Arch configs + param_names_mapping
|
||||
│ │ ├── pipelines/ # Pipeline wiring
|
||||
│ │ └── sample/ # Default sampling parameters
|
||||
│ ├── pipelines/ # End-to-end pipelines
|
||||
│ │ ├── basic/ # Per-model pipelines (wan/, ltx2/, ...)
|
||||
│ │ └── stages/ # Reusable pipeline stages
|
||||
│ ├── train/ # Refactored training framework (YAML-driven, preferred)
|
||||
│ │ ├── trainer.py # Main training loop coordinator
|
||||
│ │ ├── entrypoint/ # Training entrypoint (train.py) + checkpoint conversion
|
||||
│ │ ├── methods/ # Training algorithms (FineTune, DFSFT, DMD2, SelfForcing)
|
||||
│ │ │ ├── base.py # TrainingMethod ABC
|
||||
│ │ │ ├── fine_tuning/ # FineTuneMethod, DiffusionForcingSFTMethod
|
||||
│ │ │ └── distribution_matching/ # DMD2Method, SelfForcingMethod
|
||||
│ │ ├── models/ # Per-role model wrappers (ModelBase, CausalModelBase)
|
||||
│ │ │ └── wan/ # WanModel, WanCausalModel
|
||||
│ │ ├── callbacks/ # Composable hooks (grad_clip, ema, validation)
|
||||
│ │ └── utils/ # Config, builder, checkpoint, optimizer, tracking
|
||||
│ ├── training/ # Legacy training infrastructure (being phased out)
|
||||
│ │ ├── trackers.py # W&B tracker (BaseTracker → WandbTracker)
|
||||
│ │ ├── training_utils.py # Checkpointing, grad clipping, state dicts
|
||||
│ │ ├── training_pipeline.py # Base training pipeline
|
||||
│ │ ├── wan_training_pipeline.py # Wan T2V training
|
||||
│ │ ├── wan_i2v_training_pipeline.py # Wan I2V training
|
||||
│ │ ├── distillation_pipeline.py # Distillation base
|
||||
│ │ ├── wan_distillation_pipeline.py # Wan distillation
|
||||
│ │ ├── self_forcing_distillation_pipeline.py # Self-forcing distill
|
||||
│ │ ├── ltx2_training_pipeline.py # LTX-2 training
|
||||
│ │ └── matrixgame_training_pipeline.py # MatrixGame training
|
||||
│ ├── attention/ # Attention backends
|
||||
│ ├── distributed/ # Sequence/tensor parallel utilities
|
||||
│ ├── layers/ # Tensor-parallel layers
|
||||
│ ├── tests/ # Package-level tests
|
||||
│ │ ├── training/ # Training regression tests (W&B summary comparison)
|
||||
│ │ ├── ssim/ # SSIM visual regression tests
|
||||
│ │ ├── encoders/ # Encoder parity tests
|
||||
│ │ └── modal/ # Modal CI test runner
|
||||
│ └── registry.py # Unified config registry
|
||||
├── fastvideo-kernel/ # CUDA/custom kernels (separate build: ./build.sh)
|
||||
├── scripts/ # Utility scripts
|
||||
│ ├── distill/ # Distillation launch scripts
|
||||
│ ├── inference/ # Inference scripts
|
||||
│ ├── checkpoint_conversion/ # Weight conversion tools
|
||||
│ ├── finetune/ # Finetune scripts
|
||||
│ └── preprocess/ # Data preprocessing
|
||||
├── examples/ # Ready-to-run examples
|
||||
│ ├── training/ # Training examples (finetune/, consistency_finetune/)
|
||||
│ ├── distill/ # Distillation examples
|
||||
│ ├── inference/ # Inference examples
|
||||
│ └── dataset/ # Dataset examples
|
||||
├── docs/ # MkDocs documentation source
|
||||
│ ├── design/overview.md # Architecture overview
|
||||
│ ├── training/ # Training guides
|
||||
│ └── contributing/ # Contributor guides + coding_agents.md
|
||||
├── tests/ # Top-level tests (local_tests/)
|
||||
├── AGENTS.md # Agent coding guidelines
|
||||
└── .agents/ # Agent infrastructure (you are here)
|
||||
```
|
||||
|
||||
## Key Training Entrypoints
|
||||
|
||||
### New framework (`fastvideo/train/`) — preferred
|
||||
|
||||
| Method | Config Example | Launch Pattern |
|
||||
|--------|---------------|----------------|
|
||||
| FineTune (Wan) | `examples/train/finetune_wan2.1_t2v_1.3B_vsa_*.yaml` | `torchrun -m fastvideo.train.entrypoint.train --config <yaml>` |
|
||||
| DFSFT (Wan causal) | `examples/train/dfsft_wan_causal_t2v_1.3B.yaml` | `torchrun -m fastvideo.train.entrypoint.train --config <yaml>` |
|
||||
| DMD2 distillation | `examples/train/distill_wan2.1_t2v_1.3B_dmd2.yaml` | `torchrun -m fastvideo.train.entrypoint.train --config <yaml>` |
|
||||
| Self-Forcing | `examples/train/self_forcing_wan_causal_t2v_1.3B.yaml` | `torchrun -m fastvideo.train.entrypoint.train --config <yaml>` |
|
||||
|
||||
### Legacy pipelines (`fastvideo/training/`) — being phased out
|
||||
|
||||
| Pipeline | Entrypoint | Launch Pattern |
|
||||
|----------|-----------|----------------|
|
||||
| Wan T2V finetune | `fastvideo/training/wan_training_pipeline.py` | `torchrun --nproc_per_node N` |
|
||||
| Wan I2V finetune | `fastvideo/training/wan_i2v_training_pipeline.py` | `torchrun --nproc_per_node N` |
|
||||
| Wan distillation (DMD) | `fastvideo/training/wan_distillation_pipeline.py` | `torchrun --nproc_per_node N` |
|
||||
| Self-forcing distill | `fastvideo/training/wan_self_forcing_distillation_pipeline.py` | `torchrun --nproc_per_node N` |
|
||||
| LTX-2 finetune | `fastvideo/training/ltx2_training_pipeline.py` | `torchrun --nproc_per_node N` |
|
||||
| MatrixGame | `fastvideo/training/matrixgame_training_pipeline.py` | `torchrun --nproc_per_node N` |
|
||||
|
||||
## W&B Integration
|
||||
|
||||
- **Tracker classes**: `fastvideo/training/trackers.py`
|
||||
- `WandbTracker` — logs metrics, videos, timing
|
||||
- `SequentialTracker` — fan-out to multiple trackers
|
||||
- `DummyTracker` — no-op for offline/test
|
||||
- **Run summary location**: `<output_dir>/tracker/wandb/latest-run/files/wandb-summary.json`
|
||||
- **Reference summaries**: `fastvideo/tests/training/*/` (e.g., `a40_reference_wandb_summary.json`)
|
||||
- **Environment**: `WANDB_API_KEY`, `WANDB_BASE_URL`, `WANDB_MODE`
|
||||
|
||||
## Critical Environment Variables
|
||||
|
||||
| Variable | Purpose |
|
||||
|----------|---------|
|
||||
| `WANDB_API_KEY` | W&B authentication |
|
||||
| `WANDB_MODE` | `online` / `offline` |
|
||||
| `FASTVIDEO_ATTENTION_BACKEND` | `FLASH_ATTN` / `TORCH_SDPA` |
|
||||
| `TOKENIZERS_PARALLELISM` | Set `false` to avoid fork warnings |
|
||||
| `HF_HOME` | HuggingFace cache directory |
|
||||
|
||||
## Build & Test Commands
|
||||
|
||||
```bash
|
||||
uv pip install -e ".[dev]" # Editable install
|
||||
pre-commit run --all-files # Lint/format/spell
|
||||
pytest tests/ # Top-level tests
|
||||
pytest fastvideo/tests/ -v # Package tests
|
||||
pytest fastvideo/tests/training/Vanilla -srP # Training loss regression
|
||||
pytest fastvideo/tests/ssim/ -vs # SSIM visual regression
|
||||
cd fastvideo-kernel && ./build.sh # Build kernels
|
||||
```
|
||||
@@ -1,327 +0,0 @@
|
||||
# Evaluation Metrics Registry
|
||||
|
||||
Living catalog of all evaluation metrics for FastVideo-WorldModel video quality
|
||||
assessment. Each metric includes a detailed explanation, implementation status,
|
||||
usage instructions, and interpretation guide.
|
||||
|
||||
_Last updated: 2026-03-02_
|
||||
|
||||
---
|
||||
|
||||
## Metric Summary
|
||||
|
||||
| Metric | Category | Status | Location | Trust |
|
||||
|--------|----------|--------|----------|-------|
|
||||
| **FVD** | Distribution | ✅ Implemented | `benchmarks/fvd/` | High |
|
||||
| **SSIM** | Reference | ✅ Implemented | `fastvideo/tests/ssim/` | High |
|
||||
| **LPIPS** | Perceptual | ✅ Implemented | `scripts/lora_extraction/` | Medium |
|
||||
| **Loss trajectory** | Training signal | ✅ Implemented | W&B `train_loss` | Medium |
|
||||
| **Grad norm stability** | Training signal | ✅ Implemented | W&B `grad_norm` | Medium |
|
||||
| **GameWorld Score** | Multi-dim benchmark | 🟡 External | Matrix-Game repo | Low |
|
||||
| **Human preference** | Gold standard | 🔴 Manual | N/A | Highest |
|
||||
|
||||
---
|
||||
|
||||
## Implemented Metrics
|
||||
|
||||
### FVD — Fréchet Video Distance
|
||||
|
||||
**Category**: Distribution-level quality metric
|
||||
**Status**: ✅ Fully implemented in `benchmarks/fvd/`
|
||||
**Trust**: High — standard protocol, I3D feature extractor
|
||||
|
||||
#### What It Measures
|
||||
FVD measures the distance between the **distribution** of generated videos and
|
||||
a distribution of real/reference videos. It works by:
|
||||
1. Extracting spatiotemporal features from both real and generated video sets
|
||||
using a pretrained **I3D** (Inflated 3D ConvNet) model.
|
||||
2. Modeling each set of features as a multivariate Gaussian (mean + covariance).
|
||||
3. Computing the **Fréchet distance** between the two Gaussians.
|
||||
|
||||
Lower FVD = generated videos are more statistically similar to real videos.
|
||||
|
||||
#### Why It Matters
|
||||
- FVD is the **de facto standard** for benchmarking video generation models.
|
||||
- It captures both **visual quality** (are individual frames realistic?) and
|
||||
**temporal coherence** (do frames flow naturally?).
|
||||
- Matrix-Game 2.0, Open-Sora, and most video generation papers report FVD.
|
||||
|
||||
#### Limitations
|
||||
- Requires a **large sample set** (standard protocol uses 2048 videos) to
|
||||
produce stable statistics. Small sample sizes yield noisy results.
|
||||
- Measures **distributional similarity**, not per-video quality. A model could
|
||||
have low FVD by generating a diverse set of "roughly okay" videos.
|
||||
- The I3D model was trained on Kinetics-400 (human actions). It may be less
|
||||
sensitive to domain-specific artifacts in non-human-action videos (e.g.,
|
||||
driving, game environments).
|
||||
- Does not directly measure text-video alignment or action controllability.
|
||||
|
||||
#### How to Use
|
||||
|
||||
```python
|
||||
# Programmatic
|
||||
from benchmarks.fvd import compute_fvd_with_config, FVDConfig
|
||||
|
||||
config = FVDConfig.fvd2048_16f() # Standard: 2048 videos, 16 frames
|
||||
results = compute_fvd_with_config('data/real/', 'outputs/gen/', config)
|
||||
print(f"FVD: {results['fvd']:.2f}")
|
||||
```
|
||||
|
||||
```bash
|
||||
# CLI
|
||||
python -m benchmarks.fvd.cli \
|
||||
--real-path data/real/ \
|
||||
--gen-path outputs/gen/ \
|
||||
--protocol fvd2048_16f
|
||||
```
|
||||
|
||||
**Preset protocols**:
|
||||
| Protocol | Videos | Frames | Use Case |
|
||||
|----------|--------|--------|----------|
|
||||
| `fvd2048_16f` | 2048 | 16 | Standard benchmark (papers) |
|
||||
| `fvd2048_128f` | 2048 | 128 | Long video evaluation |
|
||||
| `quick_test` | 100 | 16 | Fast dev iteration |
|
||||
|
||||
**Feature extractors**: `i3d` (default, standard), `clip`, `videomae`
|
||||
|
||||
#### Interpretation
|
||||
| FVD Range | Interpretation |
|
||||
|-----------|---------------|
|
||||
| < 100 | Excellent — near-real quality |
|
||||
| 100–300 | Good — competitive with SOTA |
|
||||
| 300–600 | Fair — noticeable gap from real |
|
||||
| > 600 | Poor — significant quality issues |
|
||||
|
||||
> FVD values are dataset-dependent. Always compare against baselines evaluated
|
||||
> on the same real video distribution.
|
||||
|
||||
---
|
||||
|
||||
### SSIM — Structural Similarity Index
|
||||
|
||||
**Category**: Per-frame reference comparison
|
||||
**Status**: ✅ Implemented in `fastvideo/tests/ssim/`
|
||||
**Trust**: High — used in CI regression tests
|
||||
|
||||
#### What It Measures
|
||||
SSIM compares two images (or video frames) based on three components:
|
||||
1. **Luminance**: brightness similarity
|
||||
2. **Contrast**: dynamic range similarity
|
||||
3. **Structure**: spatial pattern similarity
|
||||
|
||||
The final score is a value in [0, 1] where 1.0 = identical.
|
||||
|
||||
#### Why It Matters
|
||||
- Used as a **regression guard** in CI: ensures model updates don't degrade
|
||||
visual output below a threshold.
|
||||
- More perceptually meaningful than raw pixel MSE.
|
||||
- Fast to compute — suitable for automated testing.
|
||||
|
||||
#### Limitations
|
||||
- Requires a **pixel-aligned reference** video. Cannot compare videos with
|
||||
different seeds, prompts, or angles.
|
||||
- Operates **per-frame** — does not capture temporal coherence.
|
||||
- Insensitive to some perceptual artifacts (color shifts, high-frequency noise).
|
||||
|
||||
#### How to Use
|
||||
|
||||
```bash
|
||||
pytest fastvideo/tests/ssim/ -vs
|
||||
```
|
||||
|
||||
#### Interpretation
|
||||
| SSIM Range | Quality |
|
||||
|------------|---------|
|
||||
| > 0.90 | Excellent — very close to reference |
|
||||
| 0.80–0.90 | Good — acceptable for most uses |
|
||||
| 0.70–0.80 | Fair — noticeable differences |
|
||||
| < 0.70 | Poor — significant divergence |
|
||||
|
||||
---
|
||||
|
||||
### LPIPS — Learned Perceptual Image Patch Similarity
|
||||
|
||||
**Category**: Per-frame perceptual distance
|
||||
**Status**: ✅ Implemented in `scripts/lora_extraction/lora_inference_comparison.py`
|
||||
**Trust**: Medium — available but only used for LoRA comparison currently
|
||||
|
||||
#### What It Measures
|
||||
LPIPS uses a pretrained neural network (AlexNet by default) to extract
|
||||
deep features from two images and computes the distance between them in
|
||||
feature space. Unlike SSIM, LPIPS correlates much more strongly with
|
||||
**human perceptual judgments**.
|
||||
|
||||
Lower LPIPS = more perceptually similar.
|
||||
|
||||
#### Why It Matters
|
||||
- Best available automated proxy for **human visual judgments** at the frame
|
||||
level.
|
||||
- Captures semantic and structural differences that SSIM misses (e.g., texture
|
||||
changes, minor recoloring).
|
||||
- Used for validating LoRA merge quality.
|
||||
|
||||
#### Limitations
|
||||
- Per-frame metric — no temporal awareness.
|
||||
- Requires reference video (paired comparison only).
|
||||
- Slightly slower than SSIM due to neural network forward pass.
|
||||
|
||||
#### How to Use
|
||||
|
||||
```bash
|
||||
python scripts/lora_extraction/lora_inference_comparison.py \
|
||||
--base merged_model \
|
||||
--ft path/to/finetuned \
|
||||
--adapter NONE \
|
||||
--output-dir results \
|
||||
--prompt "A cat" \
|
||||
--compute-lpips
|
||||
```
|
||||
|
||||
#### Interpretation
|
||||
| LPIPS Range | Quality |
|
||||
|-------------|---------|
|
||||
| < 0.10 | Excellent — nearly indistinguishable |
|
||||
| 0.10–0.20 | Good — minor perceptual differences |
|
||||
| 0.20–0.40 | Fair — noticeable differences |
|
||||
| > 0.40 | Poor — clearly different |
|
||||
|
||||
---
|
||||
|
||||
### Loss Trajectory
|
||||
|
||||
**Category**: Training signal proxy
|
||||
**Status**: ✅ Active (from W&B `train_loss`)
|
||||
**Trust**: Medium — proxy, not direct quality measure
|
||||
|
||||
#### What It Measures
|
||||
Tracks the training loss over time. A healthy training run shows:
|
||||
- **Decreasing loss** over the first hundreds of steps.
|
||||
- **Stable gradient norms** (no wild spikes).
|
||||
- **Consistent step times** (no infrastructure issues).
|
||||
|
||||
#### Why It Matters
|
||||
- Cheapest evaluation signal — available in real-time from W&B.
|
||||
- Critical for the **30-minute quality check** workflow.
|
||||
- At later training stages (when loss becomes meaningful), trajectory shape
|
||||
can predict final model quality.
|
||||
|
||||
#### Context: How This Evolves
|
||||
The team's experience shows evaluation signals change during a project:
|
||||
- **Early stage**: Loss may be flat or meaningless → focus on SSIM & visual
|
||||
inspection instead.
|
||||
- **Mid stage**: Loss starts decreasing → trajectory shape becomes useful.
|
||||
- **Late stage**: Loss is meaningful → can compare trajectories across runs.
|
||||
|
||||
This dynamic is a key insight from the team's workflow: don't over-rely on
|
||||
loss early; don't ignore it late.
|
||||
|
||||
---
|
||||
|
||||
### Grad Norm Stability
|
||||
|
||||
**Category**: Training health diagnostic
|
||||
**Status**: ✅ Active (from W&B `grad_norm`)
|
||||
**Trust**: Medium — diagnostic, not quality metric
|
||||
|
||||
#### What It Measures
|
||||
The magnitude of gradients during training. Stable grad norms indicate
|
||||
healthy optimization. Spikes or NaN values indicate training instability.
|
||||
|
||||
#### Alert Thresholds
|
||||
| Condition | Meaning |
|
||||
|-----------|---------|
|
||||
| Stable ~0.3–0.5 | Normal training |
|
||||
| Single spike > 3× average | Possible bad batch, monitor |
|
||||
| NaN or Inf | 🔴 Training has diverged — stop run |
|
||||
| Increasing trend | Learning rate may be too high |
|
||||
|
||||
---
|
||||
|
||||
## External Benchmarks
|
||||
|
||||
### GameWorld Score Benchmark (Matrix-Game)
|
||||
|
||||
**Category**: Multi-dimensional evaluation framework for interactive world models
|
||||
**Status**: 🟡 External — not implemented in-repo
|
||||
**Source**: [Matrix-Game 1.0 benchmark](https://github.com/SkyworkAI/Matrix-Game), used in [Matrix-Game 2.0 paper](https://arxiv.org/abs/2508.13009)
|
||||
|
||||
#### What It Measures
|
||||
A comprehensive benchmark examining **four critical capabilities**:
|
||||
|
||||
| Dimension | What It Evaluates | Example Signals |
|
||||
|-----------|-------------------|-----------------|
|
||||
| **Visual quality** | Frame-level realism, absence of artifacts | Color fidelity, sharpness, coherence |
|
||||
| **Temporal quality** | Smoothness across frames, motion consistency | Jitter, flickering, temporal aliasing |
|
||||
| **Action controllability** | Response to input actions (keyboard/mouse) | Action delay, correctness, smoothness |
|
||||
| **Physical rule understanding** | Adherence to physics (gravity, collision) | Object persistence, plausible motion |
|
||||
|
||||
#### Context from Matrix-Game 2.0
|
||||
- Evaluation uses **597-frame composite action sequences** over 32 Minecraft
|
||||
scenes and 16 wild scenes.
|
||||
- Action controllability assessment is **Minecraft-specific** — cannot be
|
||||
directly applied to wild/general scenes.
|
||||
- The paper notes that models that "collapse" to static frames can
|
||||
paradoxically score higher on consistency metrics — beware of this confound.
|
||||
|
||||
#### Relevance to FastVideo
|
||||
- Matrix-Game 2.0 is built on SkyReels-V2/Wan2.1 architecture — **same model
|
||||
family as FastVideo**.
|
||||
- Their distillation uses DMD-based Self-Forcing — **same technique** as our
|
||||
`self_forcing_distillation_pipeline.py`.
|
||||
- GameWorld Score dimensions are a useful framework for thinking about world
|
||||
model quality even outside gaming contexts.
|
||||
|
||||
---
|
||||
|
||||
## Human Preference Evaluation
|
||||
|
||||
**Category**: Gold-standard quality assessment
|
||||
**Status**: 🔴 Manual process — no automated implementation
|
||||
**Priority**: **Highest** — this is the most important evaluation signal
|
||||
**Trust**: Highest — but expensive
|
||||
|
||||
### What It Measures
|
||||
Human evaluators compare generated videos and rate them on dimensions like:
|
||||
- Overall quality and realism
|
||||
- Temporal coherence and smoothness
|
||||
- Prompt adherence / action correctness
|
||||
- Absence of artifacts
|
||||
|
||||
#### Why It's the Most Important Metric
|
||||
All automated metrics are **proxies** for human judgment. They can be gamed
|
||||
or may miss artifacts that humans easily notice. Human preference is the
|
||||
ultimate ground truth for video generation quality.
|
||||
|
||||
#### Cost & Practicality
|
||||
| Approach | Cost | Scale | When to Use |
|
||||
|----------|------|-------|-------------|
|
||||
| Internal team review | Low | ~10–50 videos | Every major checkpoint |
|
||||
| Crowdsource (MTurk, Scale) | Medium | 100+ videos | Pre-release validation |
|
||||
| A/B preference test | Medium | Pairs | Comparing two model versions |
|
||||
|
||||
#### Recommended Protocol
|
||||
1. Sample 10–20 videos from the model at a checkpoint.
|
||||
2. Include diverse prompts (easy + hard, short + long).
|
||||
3. Have 2–3 evaluators score each video 1–5 on: quality, coherence, fidelity.
|
||||
4. Record scores in the experiment journal.
|
||||
|
||||
---
|
||||
|
||||
## Metrics NOT Used
|
||||
|
||||
| Metric | Reason |
|
||||
|--------|--------|
|
||||
| ~~CLIP-Score~~ | Not used by the team. Measures text-image alignment using CLIP embeddings, but not well-suited for video temporal quality. |
|
||||
| Inception Score (IS) | Less informative than FVD for video; primarily an image metric. |
|
||||
| PSNR | Pixel-level metric; less perceptually meaningful than SSIM/LPIPS. |
|
||||
|
||||
---
|
||||
|
||||
## Adding a New Metric
|
||||
|
||||
Follow the SOP: `.agents/workflows/evaluation-development.md`
|
||||
|
||||
1. Prototype in `.agents/exploration/`
|
||||
2. Validate on known-good and known-bad samples
|
||||
3. Add to this registry
|
||||
4. Update the `evaluate-video-quality` skill
|
||||
@@ -1,21 +0,0 @@
|
||||
# Experiment Journal
|
||||
|
||||
Living log of all experiments. Each entry captures what was tried, the result,
|
||||
and any insights. Newest entries go at the top.
|
||||
|
||||
_No experiments logged yet. Use the `log-experiment` skill to add entries._
|
||||
|
||||
<!-- TEMPLATE — copy and fill for each new experiment:
|
||||
|
||||
## [YYYY-MM-DD] Experiment: <name>
|
||||
- **Hypothesis**: <what you expected to learn>
|
||||
- **Config**: model=..., lr=..., sp_size=..., gpus=..., script=...
|
||||
- **W&B run**: <run_id or URL>
|
||||
- **Duration**: <total wall time>
|
||||
- **Key metrics**: loss=..., step_time=..., grad_norm=...
|
||||
- **Checkpoint**: <path>
|
||||
- **Insight**: <what was learned>
|
||||
- **Status**: running | completed | failed | abandoned
|
||||
- **Related lessons**: `.agents/lessons/<filename>.md`
|
||||
|
||||
-->
|
||||
@@ -1,4 +0,0 @@
|
||||
{"name": "codebase-map", "description": "High-level structural index of the FastVideo-WorldModel repository", "path": "codebase-map/README.md", "status": "ready", "trust": "high"}
|
||||
{"name": "evaluation-registry", "description": "Catalog of all evaluation metrics with detailed explanations, implementation status, and usage guides", "path": "evaluation-registry/README.md", "status": "draft", "trust": "medium"}
|
||||
{"name": "experiment-journal", "description": "Living log of all experiments with hypotheses, configs, metrics, and insights", "path": "experiment-journal/README.md", "status": "draft", "trust": "medium"}
|
||||
{"name": "related-work", "description": "Index of related papers, repos, and blog posts with structured comparisons to FastVideo", "path": "related-work/README.md", "status": "draft", "trust": "low"}
|
||||
@@ -1,34 +0,0 @@
|
||||
# Related Work Index
|
||||
|
||||
Each file in this directory is a structured summary of a related paper, repo,
|
||||
or blog post relevant to FastVideo-WorldModel training.
|
||||
|
||||
## File Format
|
||||
|
||||
Each file is named `<slug>.md` and follows this structure:
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: <paper/repo title>
|
||||
source: <URL or citation>
|
||||
type: paper | repo | blog
|
||||
date_indexed: <ISO-8601>
|
||||
tags: [world-model, distillation, evaluation, reward-shaping, ...]
|
||||
---
|
||||
|
||||
## Summary
|
||||
<1-2 paragraph summary of the work.>
|
||||
|
||||
## Key Differences from FastVideo
|
||||
- <Bullet points comparing their approach to ours.>
|
||||
|
||||
## Actionable Insights
|
||||
- <What we could adopt or adapt.>
|
||||
```
|
||||
|
||||
## How to Add New Entries
|
||||
|
||||
Use the `index-related-work` skill, or manually create a file following the
|
||||
template above.
|
||||
|
||||
_No related work indexed yet._
|
||||
@@ -1,76 +0,0 @@
|
||||
# Agent Onboarding — FastVideo-WorldModel
|
||||
|
||||
Welcome, agent. This is the **master onboarding** guide. Follow the steps below,
|
||||
then check if a **domain-specific onboarding** exists for your task.
|
||||
|
||||
## Domain-Specific Onboarding
|
||||
|
||||
If your task falls into one of these areas, read the specialized guide **after**
|
||||
completing the general steps below:
|
||||
|
||||
| Domain | Guide | When to Use |
|
||||
|--------|-------|-------------|
|
||||
| **WorldModel Training** | `worldmodel-training/README.md` | Training, finetuning, distillation, experiment management |
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Understand the Codebase
|
||||
|
||||
Read these files to build your context:
|
||||
|
||||
| Priority | File | What you learn |
|
||||
|----------|------|----------------|
|
||||
| 1 | `AGENTS.md` | Coding guidelines, build/test commands, PR conventions |
|
||||
| 2 | `docs/design/overview.md` | Architecture: models, pipelines, configs, registry |
|
||||
| 3 | `fastvideo/train/` | Refactored training framework (YAML-driven, modular methods/models/callbacks) |
|
||||
| 4 | `docs/training/overview.md` | Training data flow and preprocessing |
|
||||
| 5 | `docs/training/finetune.md` | Training arguments, parallelism, LoRA, validation |
|
||||
| 6 | `docs/contributing/coding_agents.md` | How to add model pipelines with agent assistance |
|
||||
|
||||
## Step 2: Discover Available Resources
|
||||
|
||||
Read these two index files to see what skills and memory modules exist:
|
||||
|
||||
- **`.agents/skills/index.jsonl`** — catalog of all agent skills (name + description)
|
||||
- **`.agents/memory/index.jsonl`** — catalog of all memory modules (name + description)
|
||||
|
||||
Each entry has a `path` field pointing to the full content. Only load the
|
||||
full README.md for modules relevant to your current task.
|
||||
|
||||
## Step 3: Check for Existing Skills & SOPs
|
||||
|
||||
Before writing new code or procedures:
|
||||
|
||||
1. **Skills**: Read `.agents/skills/index.jsonl` — find a matching skill by description.
|
||||
2. **Workflows/SOPs**: Browse `.agents/workflows/` — step-by-step procedures for common tasks.
|
||||
3. **Lessons**: Browse `.agents/lessons/` — known pitfalls and their fixes.
|
||||
|
||||
If a skill or SOP exists for your task, **use it**. If not, you are in **exploration mode** — see Step 4.
|
||||
|
||||
## Step 4: Exploration Mode
|
||||
|
||||
If no existing skill/SOP covers your task:
|
||||
|
||||
1. Document your progress in `.agents/exploration/<topic>.md` using the template in `.agents/exploration/README.md`.
|
||||
2. At the end of your session, reflect:
|
||||
- **What worked** → propose a new skill or SOP in the exploration log.
|
||||
- **What failed** → create a lesson in `.agents/lessons/`.
|
||||
3. Flag the exploration log for human review.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```
|
||||
.agents/
|
||||
├── ONBOARDING.md ← you are here
|
||||
├── STATUS.md ← dashboard: completeness & trust of all components
|
||||
├── skills/ ← reusable agent skills
|
||||
├── workflows/ ← SOPs and procedures
|
||||
├── memory/ ← persistent context (folder per topic + index.jsonl)
|
||||
│ ├── index.jsonl
|
||||
│ ├── codebase-map/
|
||||
│ ├── experiment-journal/
|
||||
│ ├── evaluation-registry/
|
||||
│ └── related-work/
|
||||
├── lessons/ ← mistakes and fixes
|
||||
└── exploration/ ← draft procedures
|
||||
```
|
||||
@@ -1,302 +0,0 @@
|
||||
# WorldModel Training — Agent Onboarding
|
||||
|
||||
Specialized onboarding for agents working on FastVideo-WorldModel training,
|
||||
distillation, and evaluation. Read the master onboarding (`.agents/onboarding/README.md`)
|
||||
first, then come here.
|
||||
|
||||
---
|
||||
|
||||
## Domain Context
|
||||
|
||||
FastVideo-WorldModel trains **interactive world models** — video generation systems
|
||||
that respond to user actions (keyboard/mouse) in real-time. The architecture is
|
||||
based on **Wan2.1** (SkyReels-V2) DiT models with causal attention for
|
||||
auto-regressive streaming generation.
|
||||
|
||||
**Key techniques you will work with:**
|
||||
- Full finetuning and LoRA on Wan / LTX-2 / MatrixGame models
|
||||
- DMD-based distillation (few-step generation)
|
||||
- Self-Forcing distillation (causal streaming)
|
||||
- Diffusion-Forcing SFT (DFSFT) for causal models
|
||||
- VSA (Variable Sparsity Acceleration) for efficient training
|
||||
|
||||
---
|
||||
|
||||
## Training Code: Two Generations
|
||||
|
||||
### New modular framework: `fastvideo/train/` (preferred)
|
||||
|
||||
The refactored training code uses a **YAML-only config-driven** architecture
|
||||
with composable methods, per-role models, and a callback system. All new
|
||||
training work should use this framework.
|
||||
|
||||
### Legacy pipelines: `fastvideo/training/` (deprecated)
|
||||
|
||||
The old monolithic pipeline classes (`WanTrainingPipeline`,
|
||||
`DistillationPipeline`, etc.) still exist but are being phased out. The new
|
||||
framework imports select utilities from `fastvideo/training/` for backward
|
||||
compatibility (EMA, gradient clipping, checkpoint wrappers).
|
||||
|
||||
---
|
||||
|
||||
## Essential Reading (Training-Specific)
|
||||
|
||||
Read these **in order** before touching any training code:
|
||||
|
||||
| # | File | What You Learn |
|
||||
|---|------|----------------|
|
||||
| 1 | `docs/training/overview.md` | Training data flow: raw video → text embeddings + video latents → training |
|
||||
| 2 | `docs/training/finetune.md` | Training arguments, parallelism (SP/TP), LoRA, validation settings |
|
||||
| 3 | `docs/training/data_preprocess.md` | How to preprocess datasets into the expected format |
|
||||
| 4 | `docs/design/overview.md` | Architecture: models, pipelines, configs, registry |
|
||||
|
||||
---
|
||||
|
||||
## New Training Framework (`fastvideo/train/`)
|
||||
|
||||
### Architecture Overview
|
||||
|
||||
```
|
||||
fastvideo/train/
|
||||
├── __init__.py → exports Trainer
|
||||
├── trainer.py → main training loop coordinator
|
||||
├── entrypoint/
|
||||
│ ├── train.py → YAML-only training entrypoint
|
||||
│ └── dcp_to_diffusers.py → checkpoint conversion utility
|
||||
├── methods/ → training algorithms (TrainingMethod ABC)
|
||||
│ ├── base.py → TrainingMethod base class
|
||||
│ ├── fine_tuning/
|
||||
│ │ ├── finetune.py → FineTuneMethod (supervised finetuning)
|
||||
│ │ └── dfsft.py → DiffusionForcingSFTMethod (causal)
|
||||
│ ├── distribution_matching/
|
||||
│ │ ├── dmd2.py → DMD2Method (distribution matching distill)
|
||||
│ │ └── self_forcing.py → SelfForcingMethod (causal streaming)
|
||||
│ ├── knowledge_distillation/ → (stub, not yet implemented)
|
||||
│ └── consistency_model/ → (stub, not yet implemented)
|
||||
├── models/ → per-role model instances
|
||||
│ ├── base.py → ModelBase & CausalModelBase (ABC)
|
||||
│ └── wan/
|
||||
│ ├── wan.py → WanModel (non-causal)
|
||||
│ └── wan_causal.py → WanCausalModel (causal streaming)
|
||||
├── callbacks/ → training hooks & monitoring
|
||||
│ ├── callback.py → Callback base class + CallbackDict
|
||||
│ ├── grad_clip.py → GradNormClipCallback
|
||||
│ ├── ema.py → EMACallback (shadow weights)
|
||||
│ └── validation.py → ValidationCallback (sampling + eval)
|
||||
└── utils/ → configuration, building, checkpointing
|
||||
├── builder.py → build_from_config() (config → runtime)
|
||||
├── checkpoint.py → CheckpointManager (DCP-based)
|
||||
├── config.py → load_run_config() (YAML → RunConfig)
|
||||
├── training_config.py → TypedConfig dataclasses
|
||||
├── optimizer.py → build_optimizer_and_scheduler()
|
||||
├── instantiate.py → resolve_target() + instantiate()
|
||||
├── tracking.py → build_tracker() (W&B, etc.)
|
||||
├── dataloader.py → dataloader utilities
|
||||
├── module_state.py → apply_trainable()
|
||||
└── moduleloader.py → load_module_from_path()
|
||||
```
|
||||
|
||||
### Key Concepts
|
||||
|
||||
**TrainingMethod** (`methods/base.py`): Abstract base class for all training
|
||||
algorithms. Owns role models (student, teacher, critic), manages checkpoint
|
||||
state, and defines the training step interface.
|
||||
|
||||
**ModelBase** (`models/base.py`): Per-role model wrapper. Each role (student,
|
||||
teacher, critic) gets its own `ModelBase` instance owning a `transformer` and
|
||||
`noise_scheduler`. `CausalModelBase` extends this for streaming models.
|
||||
|
||||
**Callback system** (`callbacks/`): Composable hooks for gradient clipping,
|
||||
EMA, validation, etc. Configured via YAML, dispatched by `CallbackDict`.
|
||||
|
||||
**Config system** (`utils/config.py`, `utils/training_config.py`): YAML files
|
||||
are parsed into typed `RunConfig` dataclass trees. Models and methods use
|
||||
`_target_` fields for instantiation (similar to Hydra).
|
||||
|
||||
### Training Flow
|
||||
|
||||
```
|
||||
run_training_from_config(config_path)
|
||||
→ load_run_config() # YAML → RunConfig
|
||||
→ init_distributed() # TP/SP setup
|
||||
→ build_from_config() # instantiate models, method, dataloader
|
||||
→ Trainer.run() # main loop:
|
||||
├─ callbacks.on_train_start()
|
||||
├─ checkpoint_manager.maybe_resume()
|
||||
├─ for step in range(max_steps):
|
||||
│ ├─ method.single_train_step(batch)
|
||||
│ ├─ method.backward()
|
||||
│ ├─ callbacks.on_before_optimizer_step()
|
||||
│ ├─ method.optimizers_schedulers_step()
|
||||
│ ├─ tracker.log(metrics, step)
|
||||
│ ├─ callbacks.on_training_step_end()
|
||||
│ └─ checkpoint_manager.maybe_save(step)
|
||||
├─ callbacks.on_train_end()
|
||||
└─ checkpoint_manager.save_final()
|
||||
```
|
||||
|
||||
### Training Methods
|
||||
|
||||
| Method | Class | Use Case |
|
||||
|--------|-------|----------|
|
||||
| **FineTune** | `FineTuneMethod` | Single-role supervised finetuning |
|
||||
| **DFSFT** | `DiffusionForcingSFTMethod` | Diffusion-forcing SFT with inhomogeneous timesteps |
|
||||
| **DMD2** | `DMD2Method` | Multi-role distribution matching distillation (student + teacher + critic) |
|
||||
| **Self-Forcing** | `SelfForcingMethod` | Extends DMD2 for causal student rollouts |
|
||||
|
||||
### Launching Training (New Framework)
|
||||
|
||||
Training is launched via `torchrun` with a single YAML config:
|
||||
|
||||
```bash
|
||||
torchrun --nproc_per_node <N_GPUS> \
|
||||
-m fastvideo.train.entrypoint.train \
|
||||
--config examples/train/<config>.yaml
|
||||
```
|
||||
|
||||
### Example YAML Configs
|
||||
|
||||
| Config | Method | Description |
|
||||
|--------|--------|-------------|
|
||||
| `examples/train/finetune_wan2.1_t2v_1.3B_vsa_phase3.4_0.9sparsity.yaml` | FineTune | Wan 1.3B finetuning with VSA sparsity |
|
||||
| `examples/train/distill_wan2.1_t2v_1.3B_dmd2.yaml` | DMD2 | Wan 1.3B distillation (student + teacher + critic) |
|
||||
| `examples/train/dfsft_wan_causal_t2v_1.3B.yaml` | DFSFT | Causal Wan 1.3B diffusion-forcing SFT |
|
||||
| `examples/train/self_forcing_wan_causal_t2v_1.3B.yaml` | Self-Forcing | Causal streaming distillation |
|
||||
|
||||
### Checkpointing (New Framework)
|
||||
|
||||
**CheckpointManager** (`utils/checkpoint.py`) saves via `torch.distributed.checkpoint`:
|
||||
|
||||
```
|
||||
output_dir/
|
||||
└─ checkpoint-{step}/
|
||||
├─ dcp/ # DCP state dict
|
||||
├─ config.json # resolved training config
|
||||
└─ .fastvideo_metadata.json
|
||||
```
|
||||
|
||||
Checkpoint state includes: role model weights, per-role optimizers/schedulers,
|
||||
CUDA RNG state, and callback state (e.g., EMA shadow weights).
|
||||
|
||||
### Config Structure
|
||||
|
||||
A YAML config defines the full training pipeline:
|
||||
|
||||
```yaml
|
||||
models:
|
||||
student:
|
||||
_target_: fastvideo.train.models.wan.WanModel
|
||||
model_path: ...
|
||||
trainable: true
|
||||
teacher: # optional, for distillation
|
||||
_target_: fastvideo.train.models.wan.WanModel
|
||||
model_path: ...
|
||||
trainable: false
|
||||
|
||||
method:
|
||||
_target_: fastvideo.train.methods.fine_tuning.FineTuneMethod
|
||||
# method-specific params...
|
||||
|
||||
training:
|
||||
distributed: { num_gpus: 8, tp_size: 1, sp_size: 8 }
|
||||
data: { data_path: ..., batch_size: 1 }
|
||||
optimizer: { lr: 1e-5, lr_scheduler: constant_with_warmup }
|
||||
loop: { max_train_steps: 1000 }
|
||||
checkpoint: { output_dir: ./outputs }
|
||||
tracker: { trackers: [wandb], project_name: ... }
|
||||
|
||||
callbacks:
|
||||
grad_clip:
|
||||
_target_: fastvideo.train.callbacks.GradNormClipCallback
|
||||
max_grad_norm: 1.0
|
||||
validation:
|
||||
_target_: fastvideo.train.callbacks.ValidationCallback
|
||||
validation_steps: 100
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Legacy Training Pipelines (`fastvideo/training/`)
|
||||
|
||||
> **Note:** Use the new `fastvideo/train/` framework for new work. This section
|
||||
> is retained for reference on existing pipelines not yet migrated.
|
||||
|
||||
| Pipeline | Entrypoint | Use Case |
|
||||
|----------|-----------|----------|
|
||||
| Wan T2V finetune | `fastvideo/training/wan_training_pipeline.py` | Standard text-to-video finetune / LoRA |
|
||||
| Wan I2V finetune | `fastvideo/training/wan_i2v_training_pipeline.py` | Image-to-video (first frame conditioned) |
|
||||
| MatrixGame finetune | `fastvideo/training/matrixgame_training_pipeline.py` | Action-conditioned world model |
|
||||
| LTX-2 finetune | `fastvideo/training/ltx2_training_pipeline.py` | LTX-2 architecture finetuning |
|
||||
| Wan DMD distillation | `fastvideo/training/wan_distillation_pipeline.py` | Few-step distillation via DMD |
|
||||
| Self-Forcing distill | `fastvideo/training/wan_self_forcing_distillation_pipeline.py` | Causal streaming distillation |
|
||||
|
||||
---
|
||||
|
||||
## Key Infrastructure
|
||||
|
||||
### W&B Integration
|
||||
- **Tracker**: `fastvideo/training/trackers.py` — `WandbTracker` class
|
||||
- **New framework tracker**: `fastvideo/train/utils/tracking.py` — `build_tracker()`
|
||||
- **Env vars**: `WANDB_API_KEY`, `WANDB_BASE_URL`, `WANDB_MODE`
|
||||
|
||||
### Parallelism
|
||||
- **SP** (Sequence Parallel): splits video frames across GPUs — `sp_size: N`
|
||||
- **TP** (Tensor Parallel): splits model layers across GPUs — `tp_size: N`
|
||||
- Typical configs: SP=2–8, TP=1–2
|
||||
|
||||
---
|
||||
|
||||
## Evaluation (for training runs)
|
||||
|
||||
Read `.agents/memory/evaluation-registry/README.md` for the full metric catalog.
|
||||
|
||||
**Quick summary for training agents:**
|
||||
| Metric | When to Use | Trust |
|
||||
|--------|-------------|-------|
|
||||
| **Loss trajectory** | Every run, real-time from W&B | Medium |
|
||||
| **SSIM** | When comparing against reference outputs | High |
|
||||
| **FVD** | For benchmarking model quality (`benchmarks/fvd/`) | High |
|
||||
| **LPIPS** | LoRA merge validation | Medium |
|
||||
| **Human preference** | Major checkpoints | Highest |
|
||||
|
||||
---
|
||||
|
||||
## Common Workflows
|
||||
|
||||
| Task | Skill / SOP |
|
||||
|------|-------------|
|
||||
| Launch a training run | `.agents/skills/launch-experiment/SKILL.md` |
|
||||
| Monitor a running experiment | `.agents/skills/monitor-experiment/SKILL.md` |
|
||||
| Summarize final results | `.agents/skills/summarize-run/SKILL.md` |
|
||||
| Full experiment lifecycle | `.agents/workflows/experiment-lifecycle.md` |
|
||||
| Capture lessons from failures | `.agents/workflows/lesson-capture.md` |
|
||||
|
||||
---
|
||||
|
||||
## World Model–Specific Concepts
|
||||
|
||||
### Action Injection (MatrixGame)
|
||||
The MatrixGame pipeline adds **action modules** to each DiT block, enabling
|
||||
frame-level mouse/keyboard input conditioning. The action sequence is injected
|
||||
per-frame alongside the latent video tokens.
|
||||
|
||||
### Causal Architecture
|
||||
For streaming generation, the model uses **causal attention** (each frame only
|
||||
attends to previous frames). This enables auto-regressive chunk-by-chunk
|
||||
generation — critical for real-time interactive world models.
|
||||
|
||||
### Self-Forcing Distillation
|
||||
A **data-free** distillation method where the student model is trained to
|
||||
generate coherent video sequences by being forced to use its own previous
|
||||
outputs (rather than ground-truth) as context. This produces models robust to
|
||||
their own error accumulation during long auto-regressive generation.
|
||||
|
||||
### DMD Distillation (Distribution Matching Distillation)
|
||||
Reduces inference steps from ~50 to 3–4 by training a student model to match
|
||||
the output distribution of the teacher model. Uses a critic network to estimate
|
||||
distribution divergence.
|
||||
|
||||
### Diffusion-Forcing SFT (DFSFT)
|
||||
Supervised finetuning with **inhomogeneous timesteps** across chunks — each
|
||||
chunk in a causal sequence can have a different noise level, training the model
|
||||
to handle mixed-fidelity contexts.
|
||||
@@ -50,8 +50,6 @@ Each skill lives in its own directory under `.agents/skills/`:
|
||||
└── assets/ # Optional: templates, resources
|
||||
```
|
||||
|
||||
After creating a new skill, add an entry to `.agents/skills/index.jsonl`:
|
||||
|
||||
```json
|
||||
{"name": "<skill-name>", "description": "<description>", "path": "<skill-name>/SKILL.md", "status": "draft", "trust": "low"}
|
||||
```
|
||||
Skill discovery is directory-based; no hand-maintained registry entry is
|
||||
required. Run `.agents/scripts/sync-skills.sh` if a local Claude Code checkout
|
||||
needs refreshed `.claude/skills/` symlinks.
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
---
|
||||
name: add-model-01-prep
|
||||
description: Use at the start of a FastVideo model port to gather required inputs, inspect/download HF weights, clone and install the official reference repo in the current environment, create a local_tests README skeleton, and produce a handoff before conversion or implementation.
|
||||
---
|
||||
|
||||
# Add Model Prep
|
||||
|
||||
## Goal
|
||||
|
||||
Prepare external assets and the shared parity-test environment for a FastVideo
|
||||
model port. Stop before writing conversion scripts, model components, pipeline
|
||||
code, registry entries, or executable parity tests.
|
||||
|
||||
## Ask First
|
||||
|
||||
Ask once, then proceed if the HF token is already exported:
|
||||
|
||||
```text
|
||||
Before prep: (1) official reference repo or Diffusers pipeline URL, (2) HF repo
|
||||
id or local weights path and whether it has a root model_index.json, (3) target
|
||||
model_family, (4) workload types, (5) which token env var is exported:
|
||||
HF_TOKEN, HUGGINGFACE_HUB_TOKEN, or HF_API_KEY, (6) may I stage clone and
|
||||
weights under the FastVideo repo root, and (7) may I install official reference
|
||||
dependencies into the current FastVideo conda/env for parity tests?
|
||||
```
|
||||
|
||||
Useful optional inputs: `pipeline_class`, `reference_dir`, `hf_revision`,
|
||||
`official_revision`, `reuse_hints`, `download_scope`.
|
||||
|
||||
## Rules
|
||||
|
||||
- Follow `../add-model/shared/common_rules.md` for token/auth safety, state files,
|
||||
escape hatches, and skip/pass semantics.
|
||||
- Run from the FastVideo repo root.
|
||||
- Use repo-relative defaults: `<ReferenceDir>/`,
|
||||
`official_weights/<model_family>/`, `converted_weights/<model_family>/`.
|
||||
- Install official reference deps into the current FastVideo environment, not a
|
||||
new venv/conda env, so parity tests run both implementations with one shared
|
||||
numeric stack.
|
||||
- If the reference is a Diffusers class/package instead of a cloneable repo,
|
||||
record import path and version instead of cloning.
|
||||
- Prep may create only the local-test README and `PORT_STATUS.md` skeletons;
|
||||
executable `.py` parity tests belong to `../add-model-02-parity/SKILL.md`.
|
||||
|
||||
## Escape Hatches
|
||||
|
||||
Follow `../add-model/shared/common_rules.md`. Prep-specific ask cases include
|
||||
overwriting an existing clone or weight directory, installing untrusted/private
|
||||
deps, choosing between incompatible official references, large downloads outside
|
||||
the agreed scope, or missing gated-repo auth setup by env var name.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Verify the repo:
|
||||
|
||||
```bash
|
||||
git rev-parse --show-toplevel
|
||||
```
|
||||
|
||||
Expected markers: `fastvideo/`, `scripts/checkpoint_conversion/`,
|
||||
`scripts/huggingface/download_hf.py`, `fastvideo/registry.py`.
|
||||
|
||||
2. Inspect HF or local weight layout:
|
||||
|
||||
```bash
|
||||
python ".agents/skills/add-model-01-prep/scripts/inspect_hf_layout.py" \
|
||||
"Org/Model" \
|
||||
--revision "<revision>" \
|
||||
--json
|
||||
```
|
||||
|
||||
For a local path, replace `Org/Model` with `/path/to/weights`. Record
|
||||
`source_layout`, `needs_conversion`, `model_index_class`, and
|
||||
`components_seen`.
|
||||
|
||||
3. Download HF weights if needed:
|
||||
|
||||
```bash
|
||||
python ".agents/skills/add-model-01-prep/scripts/download_hf_weights.py" \
|
||||
"Org/Model" \
|
||||
"official_weights/<model_family>" \
|
||||
--revision "<revision>"
|
||||
```
|
||||
|
||||
For selected files, repeat `--file-name`. For partial snapshots, repeat
|
||||
`--allow-pattern` or `--ignore-pattern`. If the user provided a local path,
|
||||
record it instead of copying large weights by default.
|
||||
|
||||
4. Clone the official reference repo if applicable:
|
||||
|
||||
```bash
|
||||
python ".agents/skills/add-model-01-prep/scripts/clone_reference_repo.py" \
|
||||
"<official_repo_url>" \
|
||||
"<ReferenceDir>" \
|
||||
--branch "<tag-or-branch>" \
|
||||
--commit "<commit-sha>" \
|
||||
--update-gitignore
|
||||
```
|
||||
|
||||
Omit `--branch`, `--commit`, or `--update-gitignore` when not needed. The
|
||||
helper refuses to overwrite existing paths and prints remote/HEAD instead.
|
||||
|
||||
5. Keep prep assets ignored. Ensure `.gitignore` includes relevant entries:
|
||||
|
||||
```gitignore
|
||||
/<ReferenceDir>/
|
||||
/official_weights/
|
||||
/converted_weights/
|
||||
```
|
||||
|
||||
6. Follow the official repo's setup instructions in the current environment.
|
||||
Inspect dependency files and README install docs before installing anything:
|
||||
|
||||
- `README*`, install docs, or model-card instructions.
|
||||
- `requirements*.txt`, `pyproject.toml`, `setup.py`, `environment.yml`.
|
||||
|
||||
Use the current FastVideo conda/env. Do not create a new env even if upstream
|
||||
docs recommend one; translate the needed install commands into the active env.
|
||||
Prefer editable/no-deps first so the official source is importable without
|
||||
changing shared pins:
|
||||
|
||||
```bash
|
||||
uv pip install --no-deps -e ./<ReferenceDir>
|
||||
```
|
||||
|
||||
Then install only missing official deps needed for parity imports. Stop before
|
||||
installing requirements that would change FastVideo's core stack. If upstream
|
||||
requires private/non-PyPI deps, record that parity needs a local stub helper
|
||||
rather than pretending setup is complete.
|
||||
|
||||
7. Create the model-family local test skeleton and top-level port state file:
|
||||
|
||||
```bash
|
||||
mkdir -p tests/local_tests/<model_family>
|
||||
cp ".agents/skills/add-model-01-prep/templates/local_tests_readme.md" \
|
||||
tests/local_tests/<model_family>/README.md
|
||||
cp ".agents/skills/add-model-01-prep/templates/port_status.md" \
|
||||
tests/local_tests/<model_family>/PORT_STATUS.md
|
||||
```
|
||||
|
||||
Edit every placeholder in the README and `PORT_STATUS.md`. The README gives
|
||||
later review agents enough information to reproduce the shared environment and
|
||||
run/review parity work:
|
||||
|
||||
- official code URL or import path, local clone path, and commit/version;
|
||||
- HF URL or local weight path, revision, access notes, and token env var name
|
||||
only;
|
||||
- commands already run and any blocked official dependency installs;
|
||||
- shared-env install commands to re-run without changing core pins;
|
||||
- expected local parity test paths and pytest commands;
|
||||
- private-dependency stubs or known setup gaps;
|
||||
- PR/review notes explaining which parity tests are required before handoff.
|
||||
|
||||
Do not include raw tokens, absolute cache paths that are not repo-reproducible,
|
||||
or large generated outputs. If prep is blocked before imports work, still create
|
||||
the README with `official_env_status=blocked` and the exact blocker.
|
||||
|
||||
`PORT_STATUS.md` must follow `../add-model/contracts/port_state.md`. Record open
|
||||
questions and prep issues immediately, using stable IDs such as `Q001` and
|
||||
`I001`. Keep resolved questions/issues in the table with a resolution instead of
|
||||
deleting them.
|
||||
|
||||
## Handoff
|
||||
|
||||
End with the canonical prep handoff contract from
|
||||
`../add-model/contracts/prep_handoff.md` and update the shared state files before
|
||||
handoff.
|
||||
|
||||
## Helper Scripts
|
||||
|
||||
- `scripts/inspect_hf_layout.py`: classify HF/local layout.
|
||||
- `scripts/download_hf_weights.py`: download HF snapshot or selected files.
|
||||
- `scripts/clone_reference_repo.py`: clone reference repo safely.
|
||||
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Clone an official reference repo without overwriting existing paths."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Clone a reference repo for FastVideo parity tests.")
|
||||
parser.add_argument("repo_url", help="Official reference repository URL")
|
||||
parser.add_argument("target_dir", help="Directory to clone into")
|
||||
parser.add_argument("--branch", help="Branch or tag to clone")
|
||||
parser.add_argument("--commit", help="Commit SHA to check out after clone")
|
||||
parser.add_argument(
|
||||
"--update-gitignore",
|
||||
action="store_true",
|
||||
help="Add the target directory to .gitignore if missing",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--gitignore",
|
||||
default=".gitignore",
|
||||
help="Path to gitignore file when --update-gitignore is used",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def run(command: list[str], check: bool = True) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.run(
|
||||
command,
|
||||
check=check,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
)
|
||||
|
||||
|
||||
def print_existing_repo_info(target: Path) -> int:
|
||||
print(f"target_exists: {target}")
|
||||
if not (target / ".git").exists():
|
||||
print("error: target exists but is not a git repo", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
remote = run(["git", "-C", str(target), "remote", "-v"], check=False)
|
||||
head = run(["git", "-C", str(target), "rev-parse", "HEAD"], check=False)
|
||||
if remote.stdout:
|
||||
print("remote_v:")
|
||||
print(remote.stdout.rstrip())
|
||||
if head.stdout:
|
||||
print(f"head: {head.stdout.strip()}")
|
||||
print("not_overwritten: true")
|
||||
return 0
|
||||
|
||||
|
||||
def gitignore_entry_for(target: Path) -> str:
|
||||
root = Path.cwd().resolve()
|
||||
resolved = target.resolve()
|
||||
try:
|
||||
relative = resolved.relative_to(root)
|
||||
except ValueError as exc:
|
||||
raise ValueError("--update-gitignore requires target_dir to be under the current directory") from exc
|
||||
|
||||
text = relative.as_posix().rstrip("/")
|
||||
return "/" + text + "/"
|
||||
|
||||
|
||||
def update_gitignore(path: Path, target: Path) -> bool:
|
||||
entry = gitignore_entry_for(target)
|
||||
existing = path.read_text().splitlines() if path.exists() else []
|
||||
if entry in existing:
|
||||
return False
|
||||
|
||||
new_text = "\n".join(existing).rstrip("\n")
|
||||
if new_text:
|
||||
new_text += "\n"
|
||||
new_text += entry + "\n"
|
||||
path.write_text(new_text)
|
||||
return True
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
target = Path(args.target_dir)
|
||||
|
||||
if target.exists():
|
||||
return print_existing_repo_info(target)
|
||||
|
||||
command = ["git", "clone", "--depth", "1"]
|
||||
if args.branch:
|
||||
command.extend(["--branch", args.branch])
|
||||
command.extend([args.repo_url, str(target)])
|
||||
|
||||
try:
|
||||
run(command)
|
||||
if args.commit:
|
||||
run(["git", "-C", str(target), "fetch", "--depth", "1", "origin", args.commit])
|
||||
run(["git", "-C", str(target), "checkout", args.commit])
|
||||
except subprocess.CalledProcessError as exc:
|
||||
if exc.stdout:
|
||||
print(exc.stdout, end="")
|
||||
if exc.stderr:
|
||||
print(exc.stderr, end="", file=sys.stderr)
|
||||
return exc.returncode
|
||||
|
||||
head = run(["git", "-C", str(target), "rev-parse", "HEAD"])
|
||||
print(f"cloned: {target}")
|
||||
print(f"head: {head.stdout.strip()}")
|
||||
|
||||
if args.update_gitignore:
|
||||
changed = update_gitignore(Path(args.gitignore), target)
|
||||
print(f"gitignore_updated: {str(changed).lower()}")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Download HF weights using the standard FastVideo token env vars."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
HF_TOKEN_ENV_KEYS = ("HF_TOKEN", "HUGGINGFACE_HUB_TOKEN", "HF_API_KEY")
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Download a HF model snapshot or selected files into a local directory.")
|
||||
parser.add_argument("repo_id", help="HF repo id, for example Org/Model")
|
||||
parser.add_argument("local_dir", help="Destination directory")
|
||||
parser.add_argument("--repo-type", default="model", help="HF repo type (default: model)")
|
||||
parser.add_argument("--revision", help="HF branch, tag, or commit")
|
||||
parser.add_argument(
|
||||
"--file-name",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Download one file; may be repeated. If omitted, download full snapshot.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--allow-pattern",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Snapshot allow pattern; may be repeated. Ignored when --file-name is used.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ignore-pattern",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Snapshot ignore pattern; may be repeated. Ignored when --file-name is used.",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def resolve_token() -> tuple[str | None, str | None]:
|
||||
for key in HF_TOKEN_ENV_KEYS:
|
||||
value = os.environ.get(key)
|
||||
if value:
|
||||
return key, value
|
||||
return None, None
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
token_env, token = resolve_token()
|
||||
local_dir = Path(args.local_dir).expanduser()
|
||||
|
||||
if token_env:
|
||||
print(f"token_env: {token_env}")
|
||||
else:
|
||||
print("token_env: none", file=sys.stderr)
|
||||
|
||||
try:
|
||||
if local_dir.exists() and not local_dir.is_dir():
|
||||
print(
|
||||
f"error: destination exists and is not a directory: {local_dir}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
local_dir.mkdir(parents=True, exist_ok=True)
|
||||
if args.file_name:
|
||||
from huggingface_hub import hf_hub_download
|
||||
|
||||
for file_name in args.file_name:
|
||||
path = hf_hub_download(
|
||||
repo_id=args.repo_id,
|
||||
filename=file_name,
|
||||
repo_type=args.repo_type,
|
||||
revision=args.revision,
|
||||
local_dir=str(local_dir),
|
||||
token=token,
|
||||
)
|
||||
print(f"downloaded_file: {path}")
|
||||
else:
|
||||
from huggingface_hub import snapshot_download
|
||||
|
||||
path = snapshot_download(
|
||||
repo_id=args.repo_id,
|
||||
repo_type=args.repo_type,
|
||||
revision=args.revision,
|
||||
local_dir=str(local_dir),
|
||||
token=token,
|
||||
allow_patterns=args.allow_pattern or None,
|
||||
ignore_patterns=args.ignore_pattern or None,
|
||||
)
|
||||
print(f"downloaded_snapshot: {path}")
|
||||
except Exception as exc: # noqa: BLE001 - CLI should print concise failures.
|
||||
print(f"error: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print(f"local_dir: {local_dir.resolve()}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,260 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Inspect a Hugging Face repo or local weight directory layout."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
HF_TOKEN_ENV_KEYS = ("HF_TOKEN", "HUGGINGFACE_HUB_TOKEN", "HF_API_KEY")
|
||||
RAW_WEIGHT_SUFFIXES = (".safetensors", ".pt", ".pth", ".ckpt", ".bin")
|
||||
KNOWN_COMPONENTS = {
|
||||
"audio_vae",
|
||||
"conditioner",
|
||||
"feature_extractor",
|
||||
"image_encoder",
|
||||
"scheduler",
|
||||
"text_encoder",
|
||||
"text_encoder_2",
|
||||
"tokenizer",
|
||||
"tokenizer_2",
|
||||
"transformer",
|
||||
"transformer_2",
|
||||
"unet",
|
||||
"upsampler",
|
||||
"vae",
|
||||
"vocoder",
|
||||
}
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Classify a HF repo or local directory as Diffusers, raw, custom, or unknown.")
|
||||
parser.add_argument("source", help="HF repo id or local weights directory")
|
||||
parser.add_argument("--repo-type", default="model", help="HF repo type (default: model)")
|
||||
parser.add_argument("--revision", help="HF revision to inspect")
|
||||
parser.add_argument(
|
||||
"--max-local-files",
|
||||
type=int,
|
||||
default=20000,
|
||||
help="Maximum local files to scan recursively (default: 20000)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--sample-limit",
|
||||
type=int,
|
||||
default=80,
|
||||
help="Number of file paths to print in human output (default: 80)",
|
||||
)
|
||||
parser.add_argument("--json", action="store_true", help="Emit JSON only")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def resolve_token() -> tuple[str | None, str | None]:
|
||||
for key in HF_TOKEN_ENV_KEYS:
|
||||
value = os.environ.get(key)
|
||||
if value:
|
||||
return key, value
|
||||
return None, None
|
||||
|
||||
|
||||
def load_local_files(root: Path, max_files: int) -> tuple[list[str], bool]:
|
||||
files: list[str] = []
|
||||
truncated = False
|
||||
for path in root.rglob("*"):
|
||||
if not path.is_file():
|
||||
continue
|
||||
files.append(path.relative_to(root).as_posix())
|
||||
if len(files) >= max_files:
|
||||
truncated = True
|
||||
break
|
||||
return sorted(files), truncated
|
||||
|
||||
|
||||
def load_local_model_index(root: Path) -> tuple[dict[str, Any] | None, str | None]:
|
||||
index_path = root / "model_index.json"
|
||||
if not index_path.is_file():
|
||||
return None, None
|
||||
try:
|
||||
return json.loads(index_path.read_text()), None
|
||||
except Exception as exc: # noqa: BLE001 - surface malformed JSON clearly.
|
||||
return None, f"failed to parse local model_index.json: {exc}"
|
||||
|
||||
|
||||
def load_remote_files(
|
||||
repo_id: str,
|
||||
repo_type: str,
|
||||
revision: str | None,
|
||||
token: str | None,
|
||||
) -> list[str]:
|
||||
from huggingface_hub import list_repo_files
|
||||
|
||||
return sorted(list_repo_files(
|
||||
repo_id,
|
||||
repo_type=repo_type,
|
||||
revision=revision,
|
||||
token=token,
|
||||
))
|
||||
|
||||
|
||||
def load_remote_model_index(
|
||||
repo_id: str,
|
||||
repo_type: str,
|
||||
revision: str | None,
|
||||
token: str | None,
|
||||
) -> tuple[dict[str, Any] | None, str | None]:
|
||||
from huggingface_hub import hf_hub_download
|
||||
|
||||
try:
|
||||
path = hf_hub_download(
|
||||
repo_id=repo_id,
|
||||
filename="model_index.json",
|
||||
repo_type=repo_type,
|
||||
revision=revision,
|
||||
token=token,
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001 - missing/inaccessible file is data.
|
||||
return None, f"failed to download model_index.json: {exc}"
|
||||
|
||||
try:
|
||||
return json.loads(Path(path).read_text()), None
|
||||
except Exception as exc: # noqa: BLE001 - surface malformed JSON clearly.
|
||||
return None, f"failed to parse remote model_index.json: {exc}"
|
||||
|
||||
|
||||
def root_file_names(files: list[str]) -> set[str]:
|
||||
return {name for name in files if "/" not in name}
|
||||
|
||||
|
||||
def component_names(files: list[str], model_index: dict[str, Any] | None) -> list[str]:
|
||||
components: set[str] = set()
|
||||
for name in files:
|
||||
parts = name.split("/", 1)
|
||||
if len(parts) != 2:
|
||||
continue
|
||||
top, rest = parts
|
||||
if top in KNOWN_COMPONENTS or rest == "config.json":
|
||||
components.add(top)
|
||||
|
||||
if model_index:
|
||||
for key, value in model_index.items():
|
||||
if key.startswith("_"):
|
||||
continue
|
||||
if isinstance(value, list) and len(value) == 2:
|
||||
components.add(key)
|
||||
|
||||
return sorted(components)
|
||||
|
||||
|
||||
def classify_layout(
|
||||
files: list[str],
|
||||
model_index: dict[str, Any] | None,
|
||||
components: list[str],
|
||||
) -> tuple[str, str]:
|
||||
roots = root_file_names(files)
|
||||
raw_weight_files = [name for name in roots if name.endswith(RAW_WEIGHT_SUFFIXES)]
|
||||
has_model_index = "model_index.json" in roots or model_index is not None
|
||||
|
||||
if has_model_index and components:
|
||||
return "diffusers", "no"
|
||||
if has_model_index:
|
||||
return "custom", "unknown"
|
||||
if raw_weight_files:
|
||||
return "raw_official", "yes"
|
||||
if any(name.endswith(RAW_WEIGHT_SUFFIXES) for name in files):
|
||||
return "custom", "yes"
|
||||
return "unknown", "unknown"
|
||||
|
||||
|
||||
def build_result(args: argparse.Namespace) -> dict[str, Any]:
|
||||
token_env, token = resolve_token()
|
||||
source_path = Path(args.source).expanduser()
|
||||
is_local = source_path.exists()
|
||||
|
||||
if is_local:
|
||||
root = source_path.resolve()
|
||||
if not root.is_dir():
|
||||
raise ValueError(f"local source is not a directory: {root}")
|
||||
files, truncated = load_local_files(root, args.max_local_files)
|
||||
model_index, model_index_error = load_local_model_index(root)
|
||||
source_kind = "local"
|
||||
source = str(root)
|
||||
else:
|
||||
files = load_remote_files(args.source, args.repo_type, args.revision, token)
|
||||
truncated = False
|
||||
model_index, model_index_error = load_remote_model_index(
|
||||
args.source,
|
||||
args.repo_type,
|
||||
args.revision,
|
||||
token,
|
||||
)
|
||||
source_kind = "hf"
|
||||
source = args.source
|
||||
|
||||
components = component_names(files, model_index)
|
||||
source_layout, needs_conversion = classify_layout(files, model_index, components)
|
||||
|
||||
return {
|
||||
"source": source,
|
||||
"source_kind": source_kind,
|
||||
"repo_type": None if is_local else args.repo_type,
|
||||
"revision": args.revision,
|
||||
"token_env": token_env,
|
||||
"source_layout": source_layout,
|
||||
"needs_conversion": needs_conversion,
|
||||
"model_index_class": (model_index or {}).get("_class_name"),
|
||||
"model_index_diffusers_version": (model_index or {}).get("_diffusers_version"),
|
||||
"model_index_error": model_index_error,
|
||||
"components_seen": components,
|
||||
"file_count": len(files),
|
||||
"file_scan_truncated": truncated,
|
||||
"files_sample": files[:args.sample_limit],
|
||||
}
|
||||
|
||||
|
||||
def print_human(result: dict[str, Any]) -> None:
|
||||
for key in (
|
||||
"source",
|
||||
"source_kind",
|
||||
"repo_type",
|
||||
"revision",
|
||||
"token_env",
|
||||
"source_layout",
|
||||
"needs_conversion",
|
||||
"model_index_class",
|
||||
"model_index_diffusers_version",
|
||||
"model_index_error",
|
||||
"file_count",
|
||||
"file_scan_truncated",
|
||||
):
|
||||
value = result.get(key)
|
||||
if value is not None:
|
||||
print(f"{key}: {value}")
|
||||
|
||||
components = result["components_seen"]
|
||||
print("components_seen: " + (", ".join(components) if components else "none"))
|
||||
print("files_sample:")
|
||||
for name in result["files_sample"]:
|
||||
print(f" {name}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
try:
|
||||
result = build_result(args)
|
||||
except Exception as exc: # noqa: BLE001 - CLI should print concise failures.
|
||||
print(f"error: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
if args.json:
|
||||
print(json.dumps(result, indent=2, sort_keys=True))
|
||||
else:
|
||||
print_human(result)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,122 @@
|
||||
# <Model Family> Local Tests
|
||||
|
||||
Local-only parity and smoke tests for the `<model_family>` FastVideo port. These
|
||||
tests compare FastVideo against the official reference implementation and are
|
||||
not expected to run in CI unless explicitly promoted later.
|
||||
|
||||
Port progress, open questions, issues, and handoff notes live in
|
||||
`tests/local_tests/<model_family>/PORT_STATUS.md`.
|
||||
|
||||
## Reference Assets
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Model family | `<model_family>` |
|
||||
| Workload types | `<T2V/I2V/V2V/T2I/or compatibility shim with rationale>` |
|
||||
| Official reference | `<url or import path>` |
|
||||
| Local reference dir | `<ReferenceDir or none>` |
|
||||
| Official commit/version | `<sha, tag, package version, or unknown>` |
|
||||
| HF weights | `<HF repo id/url or local path>` |
|
||||
| HF revision | `<revision or default>` |
|
||||
| Local weights dir | `<official_weights/model_family or local path>` |
|
||||
| Source layout | `<diffusers/raw_official/monolithic/separate_components/mixed/custom/unknown>` |
|
||||
| Needs conversion | `<yes/no/unknown>` |
|
||||
|
||||
Do not write token values in this file. Use only the token env var name:
|
||||
`<HF_TOKEN or HUGGINGFACE_HUB_TOKEN or HF_API_KEY>`.
|
||||
|
||||
## Shared Environment Setup
|
||||
|
||||
Run from the FastVideo repo root in the same conda/env used for FastVideo.
|
||||
Do not create a separate upstream environment for parity tests.
|
||||
|
||||
```bash
|
||||
# Official reference source, if cloneable.
|
||||
python ".agents/skills/add-model-01-prep/scripts/clone_reference_repo.py" \
|
||||
"<official_repo_url>" \
|
||||
"<ReferenceDir>" \
|
||||
--commit "<commit-sha>" \
|
||||
--update-gitignore
|
||||
|
||||
# Editable install without changing shared core pins.
|
||||
uv pip install --no-deps -e ./<ReferenceDir>
|
||||
|
||||
# Additional official deps installed or required for imports:
|
||||
# <package list or none>
|
||||
```
|
||||
|
||||
Do not change core dependency versions (`torch`, `diffusers`, `transformers`,
|
||||
`flash-attn`, `triton`, CUDA packages) without explicit approval.
|
||||
|
||||
## Official Environment Status
|
||||
|
||||
```text
|
||||
dependency_changes: <none | installed no-deps editable | installed official deps in current env | blocked on user>
|
||||
official_env_status: <imports_ok | private_deps_need_stubs | blocked>
|
||||
private_dep_stubs: <none or tests/local_tests/helpers/<model_family>_upstream.py>
|
||||
blocked_on: <none or exact blocker>
|
||||
```
|
||||
|
||||
## Weight Setup
|
||||
|
||||
```bash
|
||||
python ".agents/skills/add-model-01-prep/scripts/download_hf_weights.py" \
|
||||
"<Org/Model>" \
|
||||
"official_weights/<model_family>" \
|
||||
--revision "<revision>"
|
||||
```
|
||||
|
||||
If weights are local-only, record the local path and do not copy large files into
|
||||
the repository.
|
||||
|
||||
## Prototype And Conversion Artifacts
|
||||
|
||||
State-dict key/shape dumps are generated after FastVideo native prototypes exist
|
||||
and are used to build the conversion mapping.
|
||||
|
||||
```text
|
||||
official_key_dumps:
|
||||
<component>: converted_weights/<model_family>/_mapping/<component>_official_keys.json
|
||||
fastvideo_key_dumps:
|
||||
<component>: converted_weights/<model_family>/_mapping/<component>_fastvideo_keys.json
|
||||
conversion_script: scripts/checkpoint_conversion/<model_family>_to_diffusers.py
|
||||
conversion_source_layout: <diffusers | separate_components | monolithic | mixed | custom>
|
||||
converted_weights_dir: converted_weights/<model_family>
|
||||
strict_load_status: <not_run | pass | pass_with_documented_exclusions | blocked>
|
||||
```
|
||||
|
||||
For monolithic official checkpoints, record the component prefix split here. For
|
||||
example, a single checkpoint may contain transformer, VAE/pretransform,
|
||||
conditioner, and scheduler/vocoder keys that the conversion script writes into
|
||||
separate FastVideo component subfolders.
|
||||
|
||||
## Expected Parity Tests
|
||||
|
||||
Planned local tests for this family:
|
||||
|
||||
| Component | Official files / args | Test | Concerns | Status |
|
||||
|---|---|---|---|---|
|
||||
| `<component>` | `<definition path; instantiation path + args>` | `tests/local_tests/<bucket>/test_<model_family>_<component>_parity.py` | `<prototype or setup concerns>` | `<planned/scaffold_skip/debug_red/non_skip_pass/blocked>` |
|
||||
| `pipeline` | `<official pipeline call>` | `tests/local_tests/pipelines/test_<model_family>_pipeline_parity.py` | `<pipeline concerns>` | `<planned/scaffold_skip/debug_red/non_skip_pass/blocked>` |
|
||||
|
||||
Include reused components in this table. Reuse is accepted only after the
|
||||
FastVideo component definition and official instantiation arguments have both
|
||||
been checked and the component parity test passes non-skip.
|
||||
|
||||
Run the relevant tests with:
|
||||
|
||||
```bash
|
||||
pytest tests/local_tests/<bucket>/test_<model_family>_<component>_parity.py -v -s
|
||||
pytest tests/local_tests/pipelines/test_<model_family>_pipeline_parity.py -v -s
|
||||
```
|
||||
|
||||
## Review Notes
|
||||
|
||||
- Required before handoff: non-skip PASS for each required component parity
|
||||
test, including reused components that own weights or numerical behavior.
|
||||
- Pipeline parity may start as a scaffold, but final handoff requires non-skip
|
||||
PASS or an explicit blocker accepted through the escape-hatch process.
|
||||
- User decisions and pause points are tracked as `E###` rows in
|
||||
`PORT_STATUS.md`; do not rely on chat history for escape-hatch context.
|
||||
- Review agents should verify this README's setup commands still match the PR,
|
||||
then run the listed parity tests or report the exact blocker.
|
||||
@@ -0,0 +1,69 @@
|
||||
# <Model Family> Port Status
|
||||
|
||||
## Summary
|
||||
|
||||
- model_family: `<model_family>`
|
||||
- workload_types: `<T2V/I2V/V2V/T2I/or compatibility shim with rationale>`
|
||||
- official_ref: `<url or import path>`
|
||||
- official_ref_dir: `<ReferenceDir or none>`
|
||||
- hf_weights_path: `<HF repo id/url or local path>`
|
||||
- local_weights_dir: `<official_weights/model_family or local path>`
|
||||
- source_layout: `<diffusers/raw_official/monolithic/separate_components/mixed/custom/unknown>`
|
||||
- local_tests_readme: `tests/local_tests/<model_family>/README.md`
|
||||
|
||||
## Current Phase
|
||||
|
||||
- phase: `prep`
|
||||
- status: `in_progress`
|
||||
- owner: `prep`
|
||||
- last_updated: `<YYYY-MM-DD>`
|
||||
|
||||
## Component Matrix
|
||||
|
||||
| Component | Type | Reuse/Port | Official Definition | Official Instantiation | FastVideo Target | Prototype | Conversion | Parity | Open Issues |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| `<component>` | `<dit/vae/encoder/generic>` | `<unknown/reuse/port>` | `<path + symbols>` | `<path + args>` | `<target files>` | `<not_started/in_progress/pass/blocked>` | `<not_started/pass/blocked>` | `<not_started/scaffold_skip/debug_red/non_skip_pass/blocked>` | `<none or IDs>` |
|
||||
|
||||
## Conversion State
|
||||
|
||||
- conversion_script: `scripts/checkpoint_conversion/<model_family>_to_diffusers.py`
|
||||
- converted_weights_dir: `converted_weights/<model_family>`
|
||||
- source_layout: `<diffusers/separate_components/monolithic/mixed/custom/unknown>`
|
||||
- strict_load_status: `not_run`
|
||||
- passthrough_components: `<none or list>`
|
||||
- retry_history: `<none>`
|
||||
|
||||
## Parity Commands
|
||||
|
||||
| Scope | Command | Last Result | Notes |
|
||||
|---|---|---|---|
|
||||
| component | `pytest tests/local_tests/<bucket>/test_<model_family>_<component>_parity.py -v -s` | `not_run` | `<notes>` |
|
||||
| pipeline | `pytest tests/local_tests/pipelines/test_<model_family>_pipeline_parity.py -v -s` | `not_run` | `<notes>` |
|
||||
|
||||
## Open Questions
|
||||
|
||||
| ID | Question | Owner | Needed By Phase | Status | Resolution |
|
||||
|---|---|---|---|---|---|
|
||||
| Q001 | `<question>` | `<owner>` | `<phase>` | `<open/resolved>` | `<resolution or blank>` |
|
||||
|
||||
## Issues And Blockers
|
||||
|
||||
| ID | Phase | Component | Severity | Issue | Evidence | Owner | Status | Resolution |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| I001 | `<phase>` | `<component or all>` | `<low/medium/high/blocker>` | `<issue>` | `<logs/paths/commands>` | `<owner>` | `<open/resolved>` | `<resolution or blank>` |
|
||||
|
||||
## Escape Hatches
|
||||
|
||||
| ID | Phase | Decision Type | Question | Recommended Option | Status | Resolution |
|
||||
|---|---|---|---|---|---|---|
|
||||
| E001 | `<phase>` | `<scope/dependency/auth/cost/destructive/ambiguity/blocker>` | `<one precise question>` | `<safe recommended option>` | `<open/resolved>` | `<resolution or blank>` |
|
||||
|
||||
## Decisions
|
||||
|
||||
| Date | Decision | Rationale | Impact |
|
||||
|---|---|---|---|
|
||||
| `<YYYY-MM-DD>` | `<decision>` | `<why>` | `<affected components/phases>` |
|
||||
|
||||
## Handoff Notes
|
||||
|
||||
- `<short notes for the next agent>`
|
||||
@@ -0,0 +1,234 @@
|
||||
---
|
||||
name: add-model-02-parity
|
||||
description: Use during /add-model after reference/architecture study to scaffold and later activate local FastVideo component parity tests. Emphasizes early test creation, official-reference loading, standardized FastVideo loading, and non-skip handoff gates.
|
||||
---
|
||||
|
||||
# Add Model Parity
|
||||
|
||||
## Goal
|
||||
|
||||
Create parity tests as early as possible in a FastVideo port. The first pass can
|
||||
land before conversion or component implementation as an executable scaffold;
|
||||
handoff is blocked until the same tests become non-skip PASS with real weights.
|
||||
|
||||
## When To Run
|
||||
|
||||
Follow `../add-model/shared/common_rules.md` for token/auth safety, state files,
|
||||
escape hatches, and skip/pass semantics.
|
||||
|
||||
Run immediately after `/add-model` Phase 1 has identified:
|
||||
|
||||
- official component classes and call signatures;
|
||||
- FastVideo target component buckets/classes/configs;
|
||||
- local reference clone or import path from `add-model-01-prep`;
|
||||
- local raw or Diffusers weight path;
|
||||
- `official_env_status=imports_ok`, or private deps that will be stubbed
|
||||
locally in tests;
|
||||
- `local_tests_readme` documenting setup and planned review/test commands;
|
||||
- expected component inputs and output tensors.
|
||||
|
||||
Do not wait for all FastVideo components to be implemented. Write the tests
|
||||
first, then let component-porting subagents make them pass.
|
||||
|
||||
## Outputs
|
||||
|
||||
- One component parity test per required component, including reused components:
|
||||
`tests/local_tests/<bucket>/test_<family>_<component>_parity.py`.
|
||||
- Optional helper for upstream private deps:
|
||||
`tests/local_tests/helpers/<family>_upstream.py`.
|
||||
- Pipeline parity is owned later by `../add-model-09-pipeline/SKILL.md` after all
|
||||
component parity tests pass non-skip.
|
||||
- A parity status block for the `/add-model` parity verification phase.
|
||||
|
||||
## Early Scaffold Rules
|
||||
|
||||
- A scaffold may skip while the FastVideo class, converted weights, or official
|
||||
import is missing.
|
||||
- A scaffold must already encode the real official load path, FastVideo load
|
||||
path, deterministic inputs, expected output extraction, and tolerance target.
|
||||
- Each parity test must declare its coverage scope in the file docstring or a
|
||||
module constant: `production_loader`, `implementation_subcomponent`, or `both`.
|
||||
Implementation/subcomponent parity may bypass production loaders deliberately,
|
||||
but final handoff still needs production-loader coverage somewhere before the
|
||||
pipeline depends on that component.
|
||||
- Official reference imports must run in the current FastVideo environment; do
|
||||
not create or assume a separate upstream venv/conda env.
|
||||
- A scaffold is not evidence of correctness. It becomes evidence only after a
|
||||
local non-skip PASS.
|
||||
- Prefer env-var path overrides with repo-relative defaults.
|
||||
- Keep tests local-only under `tests/local_tests/`; package/CI quality tests are
|
||||
added later.
|
||||
- Update shared state files as described in
|
||||
`../add-model/shared/common_rules.md` whenever adding or activating parity
|
||||
tests.
|
||||
|
||||
## Component Template
|
||||
|
||||
Copy `templates/component_parity_test.py` and fill every `TODO` marker. The
|
||||
template is distilled from:
|
||||
|
||||
- `tests/local_tests/transformers/test_ltx2.py`
|
||||
- `tests/local_tests/transformers/test_gamecraft_parity.py`
|
||||
- `tests/local_tests/encoders/test_ltx2_gemma_parity.py`
|
||||
- `tests/local_tests/vaes/test_oobleck_vae_parity.py`
|
||||
- `tests/local_tests/sd35/test_sd35_component_parity.py`
|
||||
|
||||
The template supports three states:
|
||||
|
||||
| State | Meaning |
|
||||
|---|---|
|
||||
| Scaffold skip | Test is committed early, but official import, FastVideo class, or weights are not available yet. |
|
||||
| Debug red | Both sides load and the test fails numerically. This is useful: porting can chase the first drift. |
|
||||
| Non-skip pass | Required before `/add-model` handoff. |
|
||||
|
||||
## Subagent Dispatch Pattern
|
||||
|
||||
After Phase 1, dispatch one parity subagent per component before or alongside
|
||||
component implementation:
|
||||
|
||||
```text
|
||||
Create a local parity test scaffold for <family> <component>.
|
||||
|
||||
Use the prep handoff:
|
||||
- official_ref_dir/import: <...>
|
||||
- local_weights_dir: <...>
|
||||
- source_layout: <...>
|
||||
- needs_conversion: <yes/no>
|
||||
- official_env_status: <imports_ok | private_deps_need_stubs>
|
||||
- local_tests_readme: tests/local_tests/<model_family>/README.md
|
||||
- port_state_file: tests/local_tests/<model_family>/PORT_STATUS.md
|
||||
- official_definition_files: <paths + classes/functions>
|
||||
- official_instantiation_files: <paths + factory/pipeline/config call sites + args>
|
||||
- concerns_or_unknowns: <known ambiguous inputs, outputs, deps, or args>
|
||||
|
||||
The complete per-component packet must match
|
||||
`../add-model/contracts/component_context.md`.
|
||||
|
||||
Read the official component call path and the planned FastVideo component API.
|
||||
Add tests/local_tests/<bucket>/test_<family>_<component>_parity.py based on
|
||||
add-model-02-parity/templates/component_parity_test.py.
|
||||
|
||||
The scaffold must load the official model with real weights when available,
|
||||
load the FastVideo model through the standardized config/class/loader path when
|
||||
available, create deterministic inputs, compare concrete outputs, and skip only
|
||||
when a dependency is genuinely missing. Do not make an unconditional skip or a
|
||||
shape-only test.
|
||||
```
|
||||
|
||||
## FastVideo Load Patterns
|
||||
|
||||
Pick the narrowest load path that matches the component:
|
||||
|
||||
| Component | Preferred FastVideo load path |
|
||||
|---|---|
|
||||
| DiT / transformer | Bucket config + model class, or `TransformerLoader` when testing converted Diffusers component dirs. |
|
||||
| VAE | VAE class `from_pretrained(...)` when implemented, or bucket config + class for local converted dirs. |
|
||||
| Text/image encoder | Bucket config + model class; pass HF subpaths from `local_weights_dir` or converted component dirs. |
|
||||
| Scheduler/conditioner | Native class/config plus exact official kwargs. |
|
||||
|
||||
For early scaffolds, an import of the planned FastVideo class may be inside a
|
||||
helper that calls `pytest.skip` if the class does not exist yet. Replace that
|
||||
skip with a real import once the component PR adds the class.
|
||||
|
||||
Direct class/config construction is allowed for implementation or subcomponent
|
||||
parity, such as connector-only encoder checks or official monolithic-checkpoint
|
||||
mapping tests. Label that scope explicitly and add separate production-loader
|
||||
coverage when converted component dirs are available.
|
||||
|
||||
## Official Load Patterns
|
||||
|
||||
- Clone/reference repo path: add its source dir to `sys.path` before imports.
|
||||
- HF/Diffusers reference: import only inside the test, not production code.
|
||||
- Private deps: add a helper under `tests/local_tests/helpers/` to install
|
||||
stubs before importing upstream modules; do not rely on an external upstream
|
||||
environment.
|
||||
- Gated HF repos: resolve `HF_TOKEN`, `HUGGINGFACE_HUB_TOKEN`, or `HF_API_KEY`
|
||||
under the token rules in `../add-model/shared/common_rules.md`.
|
||||
|
||||
## Non-Skip Activation Checklist
|
||||
|
||||
Before `/add-model` handoff, each scaffolded test must be activated:
|
||||
|
||||
```text
|
||||
[ ] Official side imports and loads real weights.
|
||||
[ ] FastVideo side imports and loads the converted or original weights.
|
||||
[ ] Test executes at least one real forward call on both sides.
|
||||
[ ] Test compares output tensors, not only shapes or state-dict keys.
|
||||
[ ] Local pytest output contains PASSED, not SKIPPED or XFAIL.
|
||||
[ ] Tolerance is justified for the component scope and kernel alignment.
|
||||
```
|
||||
|
||||
## Component Parity Details
|
||||
|
||||
Reference imports:
|
||||
|
||||
- Import from `official_ref_dir` or the recorded package/import path.
|
||||
- If upstream has private deps, add a helper under
|
||||
`tests/local_tests/helpers/<family>_upstream.py` that installs minimal stubs
|
||||
before importing upstream modules.
|
||||
- Common stubs: identity compile/op-registration decorators, CP world size set to
|
||||
1, identity scatter/gather, and test-friendly custom-op kernels.
|
||||
- Stub decorators that register `torch.ops.<ns>.<op>` must preserve the
|
||||
`torch.library` registration side-effect. Identity decorators alone are not
|
||||
enough.
|
||||
- Delete stub helpers and every `install_stubs()` call as soon as the real deps
|
||||
become required installs. No-op shims are dead code.
|
||||
|
||||
Kernel and wrapper pitfalls:
|
||||
|
||||
- If parity routes flash-attn GQA through SDPA, expand KV heads manually on the
|
||||
SDPA side with `repeat_interleave` along the head axis.
|
||||
- If upstream VAE `decode()` denormalizes internally but FastVideo/Diffusers
|
||||
expects pre-denormalized latents, apply `z = z * std + mean` only on the
|
||||
FastVideo side in the parity test.
|
||||
- Per-channel VAE `latents_mean` / `latents_std` must be reshaped explicitly,
|
||||
e.g. `.view(1, z_dim, 1, 1, 1)` for 5D video latents.
|
||||
|
||||
Tolerance guide:
|
||||
|
||||
| Scope | Start `atol` / `rtol` | Notes |
|
||||
|---|---|---|
|
||||
| Single block, same kernel | `1e-4` / `1e-4` | Tight default. |
|
||||
| Full DiT, aligned kernels | `1e-2` / `1e-2` | Cross-layer accumulation. |
|
||||
| Full DiT, cross-kernel bf16 | `0.1` / `0.1` | Also require abs-mean drift below 5% and per-modality diagnostics. |
|
||||
| VAE decode fp32 | `5e-2` / `5e-2` | After normalization alignment. |
|
||||
| Encoder wrapper around same HF class | `1e-3` / `1e-3` | Should be near-zero. |
|
||||
|
||||
Element-wise `assert_close` alone is not enough for deep full-DiT parity. Also
|
||||
log global abs-mean drift and per-modality summaries.
|
||||
|
||||
When a non-skip component parity run is numerically red after weight/input
|
||||
checks, invoke `../add-model-08-trace/SKILL.md` before adding bespoke forward
|
||||
hooks. Use `docs/contributing/activation_trace.md` to keep
|
||||
`FASTVIDEO_TRACE_LAYERS`, `FASTVIDEO_TRACE_STATS`, and `FASTVIDEO_TRACE_STEPS`
|
||||
identical across FastVideo and upstream traces.
|
||||
|
||||
Useful local commands:
|
||||
|
||||
```bash
|
||||
pytest tests/local_tests/<bucket>/test_<family>_*parity*.py -v -s
|
||||
pytest tests/local_tests -k "<family> and parity" -v -s
|
||||
```
|
||||
|
||||
## Escape Hatches
|
||||
|
||||
Follow `../add-model/shared/common_rules.md`. Parity-specific ask cases include
|
||||
private dependency approval, choosing between incompatible official references,
|
||||
accepting a shape-only substitute, or loosening required tolerances.
|
||||
|
||||
## Pipeline Parity
|
||||
|
||||
Pipeline parity is later than component parity because it needs stages, presets,
|
||||
registry wiring, converted weights, and green component parity. Record official
|
||||
pipeline call notes in `local_tests_readme`, but do not treat pipeline parity as
|
||||
owned by this skill.
|
||||
|
||||
Use `../add-model-09-pipeline/SKILL.md` and its
|
||||
`templates/pipeline_parity_test.py` for pipeline parity scaffolding and
|
||||
debugging. Compare denoised latents or decoded media, not just successful
|
||||
generation.
|
||||
|
||||
## Handoff Status Block
|
||||
|
||||
Return `../add-model/contracts/parity_status.md` to `/add-model` and update the
|
||||
shared state files before handoff.
|
||||
@@ -0,0 +1,188 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
"""Component parity scaffold for <FAMILY> <COMPONENT>.
|
||||
|
||||
This file is intended to be created early in a port. It may skip until the
|
||||
official reference, FastVideo class, and real weights are available, but it must
|
||||
never become an unconditional skip or shape-only test.
|
||||
|
||||
Fill every TODO before considering this test active.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
from torch.testing import assert_close
|
||||
|
||||
os.environ.setdefault("MASTER_ADDR", "localhost")
|
||||
os.environ.setdefault("MASTER_PORT", "29519")
|
||||
os.environ.setdefault("DISABLE_SP", "1")
|
||||
os.environ.setdefault("FASTVIDEO_ATTENTION_BACKEND", "TORCH_SDPA")
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[3]
|
||||
FAMILY = "<family>" # TODO: snake_case family name.
|
||||
COMPONENT = "<component>" # TODO: transformer | vae | encoder | conditioner | ...
|
||||
PARITY_SCOPE = "implementation_subcomponent" # TODO: production_loader | implementation_subcomponent | both
|
||||
OFFICIAL_MODULE = "<official.module>" # TODO: e.g. "ltx_core.model.transformer".
|
||||
OFFICIAL_CLASS = "<OfficialClass>" # TODO: official class/factory name.
|
||||
FASTVIDEO_CONFIG_MODULE = "fastvideo.configs.models.<bucket>" # TODO.
|
||||
FASTVIDEO_CONFIG_CLASS = "<FastVideoConfig>" # TODO.
|
||||
FASTVIDEO_MODEL_MODULE = "fastvideo.models.<bucket>.<module>" # TODO.
|
||||
FASTVIDEO_MODEL_CLASS = "<FastVideoModel>" # TODO.
|
||||
|
||||
OFFICIAL_REF_DIR = Path(os.getenv("<FAMILY_UPPER>_OFFICIAL_REF_DIR", REPO_ROOT / "<ReferenceDir>"))
|
||||
LOCAL_WEIGHTS_DIR = Path(os.getenv("<FAMILY_UPPER>_LOCAL_WEIGHTS_DIR", REPO_ROOT / "official_weights" / FAMILY))
|
||||
CONVERTED_WEIGHTS_DIR = Path(os.getenv("<FAMILY_UPPER>_CONVERTED_WEIGHTS_DIR",
|
||||
REPO_ROOT / "converted_weights" / FAMILY))
|
||||
|
||||
|
||||
def _resolve_hf_token() -> str | None:
|
||||
for key in ("HF_TOKEN", "HUGGINGFACE_HUB_TOKEN", "HF_API_KEY"):
|
||||
value = os.environ.get(key)
|
||||
if value:
|
||||
return value
|
||||
return None
|
||||
|
||||
|
||||
def _add_official_to_path() -> None:
|
||||
"""Add the official source path before importing upstream modules."""
|
||||
# TODO: adjust for the official repo layout. Common examples:
|
||||
# OFFICIAL_REF_DIR / "src"
|
||||
# OFFICIAL_REF_DIR / "packages" / "<pkg>" / "src"
|
||||
# OFFICIAL_REF_DIR
|
||||
official_src = OFFICIAL_REF_DIR / "src"
|
||||
if not official_src.exists():
|
||||
official_src = OFFICIAL_REF_DIR
|
||||
if official_src.exists() and str(official_src) not in sys.path:
|
||||
sys.path.insert(0, str(official_src))
|
||||
|
||||
|
||||
def _import_or_skip(module_name: str, attr_name: str | None = None):
|
||||
if "<" in module_name or (attr_name is not None and "<" in attr_name):
|
||||
pytest.skip(f"Template import placeholder not filled: {module_name}.{attr_name}")
|
||||
try:
|
||||
module = importlib.import_module(module_name)
|
||||
except Exception as exc: # noqa: BLE001 - local parity should skip missing refs.
|
||||
pytest.skip(f"Cannot import {module_name}: {exc}")
|
||||
if attr_name is None:
|
||||
return module
|
||||
try:
|
||||
return getattr(module, attr_name)
|
||||
except AttributeError:
|
||||
pytest.skip(f"{module_name} has no attribute {attr_name}")
|
||||
|
||||
|
||||
def _load_official_model(device: torch.device, dtype: torch.dtype) -> torch.nn.Module:
|
||||
"""Load the official component with real weights."""
|
||||
_add_official_to_path()
|
||||
if not OFFICIAL_REF_DIR.exists():
|
||||
pytest.skip(f"Official reference missing: {OFFICIAL_REF_DIR}")
|
||||
if not LOCAL_WEIGHTS_DIR.exists():
|
||||
pytest.skip(f"Local weights missing: {LOCAL_WEIGHTS_DIR}")
|
||||
|
||||
# TODO: import official class/factory and load real weights strictly.
|
||||
# Examples in-tree:
|
||||
# - LTX2: SingleGPUModelBuilder(...).build(device=device, dtype=dtype)
|
||||
# - GameCraft: torch.load(...)["module"] -> official_model.load_state_dict(...)
|
||||
# - Oobleck: create_model_from_config(config) + ckpt state_dict
|
||||
OfficialClass = _import_or_skip(OFFICIAL_MODULE, OFFICIAL_CLASS)
|
||||
model = OfficialClass() # TODO: pass official config kwargs.
|
||||
state_dict = {} # TODO: load official state dict from LOCAL_WEIGHTS_DIR.
|
||||
missing, unexpected = model.load_state_dict(state_dict, strict=True)
|
||||
assert not missing and not unexpected, (f"official load mismatch missing={missing[:5]} unexpected={unexpected[:5]}")
|
||||
return model.to(device=device, dtype=dtype).eval()
|
||||
|
||||
|
||||
def _load_fastvideo_model(device: torch.device, dtype: torch.dtype) -> torch.nn.Module:
|
||||
"""Load the FastVideo component with the same tensor content."""
|
||||
if not CONVERTED_WEIGHTS_DIR.exists() and not LOCAL_WEIGHTS_DIR.exists():
|
||||
pytest.skip(f"No FastVideo loadable weights: {CONVERTED_WEIGHTS_DIR} or {LOCAL_WEIGHTS_DIR}")
|
||||
|
||||
# TODO: replace with the bucket-specific FastVideo config/class/loader.
|
||||
# DiT examples:
|
||||
# from fastvideo.configs.models.dits import <Config>
|
||||
# from fastvideo.models.dits.<module> import <Model>
|
||||
# VAE examples:
|
||||
# from fastvideo.models.vaes.<module> import <VAE>
|
||||
# model = <VAE>.from_pretrained(...)
|
||||
FastVideoConfig = _import_or_skip(FASTVIDEO_CONFIG_MODULE, FASTVIDEO_CONFIG_CLASS)
|
||||
FastVideoModel = _import_or_skip(FASTVIDEO_MODEL_MODULE, FASTVIDEO_MODEL_CLASS)
|
||||
|
||||
config = FastVideoConfig()
|
||||
model = FastVideoModel(config=config)
|
||||
state_dict = {} # TODO: load converted or directly mapped state dict.
|
||||
missing, unexpected = model.load_state_dict(state_dict, strict=True)
|
||||
assert not missing and not unexpected, (
|
||||
f"FastVideo load mismatch missing={missing[:5]} unexpected={unexpected[:5]}")
|
||||
return model.to(device=device, dtype=dtype).eval()
|
||||
|
||||
|
||||
def _make_inputs(device: torch.device, dtype: torch.dtype) -> dict[str, torch.Tensor]:
|
||||
"""Create deterministic inputs matching the official component call."""
|
||||
torch.manual_seed(0)
|
||||
# TODO: replace with component-specific tensors and metadata.
|
||||
return {
|
||||
"hidden_states": torch.randn(1, 4, 16, device=device, dtype=dtype),
|
||||
"timestep": torch.tensor([10], device=device),
|
||||
}
|
||||
|
||||
|
||||
def _run_official(model: torch.nn.Module, inputs: dict[str, torch.Tensor]) -> torch.Tensor:
|
||||
"""Run official component and return the tensor to compare."""
|
||||
with torch.inference_mode():
|
||||
output = model(**inputs) # TODO: adapt official call signature.
|
||||
if isinstance(output, dict):
|
||||
sample = output.get("sample")
|
||||
output = sample if sample is not None else output.get("x")
|
||||
elif hasattr(output, "sample"):
|
||||
output = output.sample
|
||||
elif isinstance(output, tuple):
|
||||
output = output[0]
|
||||
assert torch.is_tensor(output), f"official output is not tensor: {type(output)}"
|
||||
return output.detach().float().cpu()
|
||||
|
||||
|
||||
def _run_fastvideo(model: torch.nn.Module, inputs: dict[str, torch.Tensor]) -> torch.Tensor:
|
||||
"""Run FastVideo component and return the tensor to compare."""
|
||||
with torch.inference_mode():
|
||||
output = model(**inputs) # TODO: adapt FastVideo call signature.
|
||||
if isinstance(output, dict):
|
||||
sample = output.get("sample")
|
||||
output = sample if sample is not None else output.get("x")
|
||||
elif hasattr(output, "sample"):
|
||||
output = output.sample
|
||||
elif isinstance(output, tuple):
|
||||
output = output[0]
|
||||
assert torch.is_tensor(output), f"FastVideo output is not tensor: {type(output)}"
|
||||
return output.detach().float().cpu()
|
||||
|
||||
|
||||
@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA required for this parity test.")
|
||||
def test_component_parity():
|
||||
"""Compare official and FastVideo outputs on identical inputs."""
|
||||
device = torch.device("cuda:0")
|
||||
dtype = torch.bfloat16
|
||||
|
||||
official = _load_official_model(device, dtype)
|
||||
fastvideo = _load_fastvideo_model(device, dtype)
|
||||
inputs = _make_inputs(device, dtype)
|
||||
|
||||
official_out = _run_official(official, inputs)
|
||||
fastvideo_out = _run_fastvideo(fastvideo, inputs)
|
||||
|
||||
assert official_out.shape == fastvideo_out.shape
|
||||
diff = (official_out - fastvideo_out).abs()
|
||||
print(f"official abs_mean={official_out.abs().mean().item():.6f} "
|
||||
f"fastvideo abs_mean={fastvideo_out.abs().mean().item():.6f} "
|
||||
f"diff_max={diff.max().item():.6f} diff_mean={diff.mean().item():.6f}")
|
||||
|
||||
# TODO: pick tolerance by scope:
|
||||
# - single block / same kernel: 1e-4
|
||||
# - full DiT aligned kernels: 1e-2
|
||||
# - full DiT cross-kernel bf16: 1e-1 + abs_mean drift check
|
||||
# - VAE decode fp32: 5e-2 after normalization alignment
|
||||
assert_close(fastvideo_out, official_out, atol=1e-4, rtol=1e-4)
|
||||
@@ -0,0 +1,114 @@
|
||||
---
|
||||
name: add-model-03-port-dit
|
||||
description: Use during /add-model Phase 4 or Phase 6 to prototype or parity-debug one FastVideo-native DiT/transformer component.
|
||||
---
|
||||
|
||||
# Add Model Port DiT
|
||||
|
||||
## Goal
|
||||
|
||||
Prototype or parity-debug one diffusion transformer in FastVideo-native code.
|
||||
This skill is for one component only; do not work on the VAE, encoders,
|
||||
pipeline, or unrelated conversion code unless the current component cannot load
|
||||
without a minimal fix there.
|
||||
|
||||
## Inputs
|
||||
|
||||
Follow `../add-model/shared/component_skill_common.md` and require the complete
|
||||
packet from `../add-model/contracts/component_context.md`.
|
||||
|
||||
DiT-specific packet fields:
|
||||
|
||||
- `component`: transformer or DiT name.
|
||||
- `parity_test`: `tests/local_tests/<bucket>/test_<family>_<component>_parity.py`.
|
||||
- `weights`: converted transformer dir or local official path.
|
||||
- `target_files`: `fastvideo/models/dits/<family>.py` and
|
||||
`fastvideo/configs/models/dits/<family>.py`.
|
||||
|
||||
## Modes
|
||||
|
||||
Use the common prototype and parity-debug modes from
|
||||
`../add-model/shared/component_skill_common.md`.
|
||||
|
||||
DiT-specific prototype concerns include ambiguous official flags, shape
|
||||
mismatches, missing FastVideo layer equivalents, and dedicated output heads.
|
||||
|
||||
## Reuse Proof
|
||||
|
||||
Apply the shared reuse proof. DiT-specific comparison must include attention
|
||||
algorithm, positional embeddings, RoPE/patching, timestep/guidance embeddings,
|
||||
scaling constants, dtype casts, state-dict names, and every output head.
|
||||
|
||||
## Existing FastVideo Patterns
|
||||
|
||||
- Base class: `fastvideo/models/dits/base.py::BaseDiT`.
|
||||
- Config bases: `DiTConfig` and `DiTArchConfig` in
|
||||
`fastvideo/configs/models/dits/base.py`.
|
||||
- Use the matching DiT config bucket. Wrong bucket inheritance can typecheck but
|
||||
fail during pipeline wiring.
|
||||
- Config export: add the config to
|
||||
`fastvideo/configs/models/dits/__init__.py`.
|
||||
- Registry discovery: set `EntryClass = <ClassName>` in the model file.
|
||||
- Loader path: `TransformerLoader` reads `transformer/config.json`, calls
|
||||
`dit_config.update_model_arch(config)`, resolves `_class_name` through
|
||||
`ModelRegistry`, and constructs the class with `config` and `hf_config`.
|
||||
- Reference examples: `stable_audio.py`, `wanvideo.py`, `sd3.py`, `longcat.py`,
|
||||
and `ltx2.py`.
|
||||
- Layer guidance: `fastvideo/layers/AGENTS.md`.
|
||||
|
||||
## Implementation Rules
|
||||
|
||||
- Use FastVideo-native layers by default: `ReplicatedLinear` for DiT hot-path
|
||||
linears, `DistributedAttention` for standard full-sequence attention, and
|
||||
`LocalAttention` for local/window attention or simple single-GPU parity paths.
|
||||
- Raw SDPA is acceptable for cross-modality flat streams when no FastVideo
|
||||
distributed equivalent exists; document the SP gap in the module docstring.
|
||||
- Mirror official tensor contracts exactly: latent packing, patch ordering,
|
||||
timestep embedding scale, RoPE/positional embedding, guidance embedding,
|
||||
cross-attention context order, output head order, and dtype casts.
|
||||
- Preserve all output heads that the official DiT emits. Do not silently drop
|
||||
audio, depth, pose, mask, or auxiliary heads.
|
||||
- Put architecture fields on `DiTArchConfig`; keep inference steps, CFG scales,
|
||||
FPS, flow shift, and sampling defaults out of the arch config.
|
||||
- Define `_fsdp_shard_conditions`, `_compile_conditions`,
|
||||
`param_names_mapping`, and `reverse_param_names_mapping` where needed.
|
||||
- Follow the production import boundary in
|
||||
`../add-model/shared/common_rules.md`.
|
||||
|
||||
## Prototype Checks
|
||||
|
||||
Follow the shared prototype success criteria. A useful one-off check is:
|
||||
|
||||
```bash
|
||||
python - <<'PY'
|
||||
# Import the target config/class, instantiate with random weights, and print
|
||||
# state_dict names/shapes for the conversion mapping.
|
||||
PY
|
||||
```
|
||||
|
||||
## Parity-Debug Loop
|
||||
|
||||
Run the shared parity-debug loop. The component test command is:
|
||||
|
||||
```bash
|
||||
pytest <parity_test> -v -s
|
||||
```
|
||||
|
||||
For numerical drift, use `../add-model-08-trace/SKILL.md` before writing bespoke
|
||||
hooks. Start with FastVideo's activation trace (`fastvideo/hooks/activation_trace.py`;
|
||||
`docs/contributing/activation_trace.md`) and a block-level regex such as
|
||||
`FASTVIDEO_TRACE_LAYERS="^block\.layers\.[0-9]+$"`. Only fall back to custom
|
||||
per-block hooks if the needed boundary or statistic is not exposed by
|
||||
`FASTVIDEO_TRACE_STATS`.
|
||||
|
||||
## Escape Hatches
|
||||
|
||||
Follow `../add-model/shared/common_rules.md` and the component-specific guidance
|
||||
in `../add-model/shared/component_skill_common.md`. DiT-specific ask cases include
|
||||
dropping an output head/modality, accepting an unsupported kernel/private op, or
|
||||
choosing between incompatible official transformer definitions.
|
||||
|
||||
## Handoff
|
||||
|
||||
Return `../add-model/contracts/component_skill_handoff.md` following the common
|
||||
handoff rules in `../add-model/shared/component_skill_common.md`.
|
||||
@@ -0,0 +1,100 @@
|
||||
---
|
||||
name: add-model-04-port-vae
|
||||
description: Use during /add-model Phase 4 or Phase 6 to prototype or parity-debug one FastVideo-native VAE component.
|
||||
---
|
||||
|
||||
# Add Model Port VAE
|
||||
|
||||
## Goal
|
||||
|
||||
Prototype or parity-debug one VAE or autoencoder in FastVideo-native code. This
|
||||
skill covers video, image, and audio VAEs.
|
||||
|
||||
## Inputs
|
||||
|
||||
Follow `../add-model/shared/component_skill_common.md` and require the complete
|
||||
packet from `../add-model/contracts/component_context.md`.
|
||||
|
||||
VAE-specific packet fields:
|
||||
|
||||
- `component`: VAE or autoencoder name.
|
||||
- `parity_test`: `tests/local_tests/vaes/test_<family>_<component>_parity.py`.
|
||||
- `weights`: converted VAE dir, HF subfolder, or local official path.
|
||||
- `target_files`: `fastvideo/models/vaes/<arch_or_family>.py` and
|
||||
`fastvideo/configs/models/vaes/<arch_or_family>.py`.
|
||||
|
||||
## Modes
|
||||
|
||||
Use the common prototype and parity-debug modes from
|
||||
`../add-model/shared/component_skill_common.md`.
|
||||
|
||||
VAE-specific prototype concerns include latent normalization, stochastic
|
||||
posterior behavior, tiling incompatibility, temporal/spatial/audio layout, and
|
||||
decode output containers.
|
||||
|
||||
## Reuse Proof
|
||||
|
||||
Apply the shared reuse proof. VAE-specific comparison must include latent layout,
|
||||
temporal/spatial/audio compression, scaling factor, mean/std normalization,
|
||||
posterior behavior, encode/decode output objects, tiling flags, and cropping.
|
||||
|
||||
## Existing FastVideo Patterns
|
||||
|
||||
- Shared tiling wrapper: `fastvideo/models/vaes/common.py::ParallelTiledVAE`.
|
||||
- Config bases: `VAEConfig` and `VAEArchConfig` in
|
||||
`fastvideo/configs/models/vaes/base.py`.
|
||||
- Use the matching VAE config bucket. Wrong bucket inheritance can typecheck but
|
||||
fail during pipeline wiring.
|
||||
- Config export: add the config to
|
||||
`fastvideo/configs/models/vaes/__init__.py`.
|
||||
- Registry discovery: set `EntryClass = <ClassName>` in the model file.
|
||||
- Loader path: VAE loaders resolve `_class_name` through `ModelRegistry` and
|
||||
load converted component weights from the VAE subdir.
|
||||
- Reference examples: `oobleck.py`, `autoencoder_kl.py`, `wanvae.py`,
|
||||
`ltx2vae.py`, and `gamecraftvae.py`.
|
||||
- Layer guidance: `fastvideo/layers/AGENTS.md`.
|
||||
|
||||
## Implementation Rules
|
||||
|
||||
- Name reusable VAE architectures by architecture (`oobleck.py`,
|
||||
`autoencoder_kl.py`); name family-specific VAEs by family.
|
||||
- Match official encode/decode contracts exactly: input layout, latent layout,
|
||||
temporal/spatial/audio compression, scaling factor, mean/std normalization,
|
||||
posterior sampling behavior, decode output object, and frame/sample cropping.
|
||||
- Compare deterministic outputs in parity: decode outputs, encode mean/mode, or
|
||||
round-trip tensors. Do not compare stochastic samples unless the RNG path is
|
||||
explicitly controlled.
|
||||
- Use FastVideo tiling only when it preserves official numerics for the tested
|
||||
shape; disable it in config for audio or unsupported dimensions.
|
||||
- Put architecture constants on `VAEArchConfig`; put `load_encoder`,
|
||||
`load_decoder`, tiling, dtype, and pretrained path fields on `VAEConfig`.
|
||||
- Follow the production import boundary in
|
||||
`../add-model/shared/common_rules.md`.
|
||||
|
||||
## Prototype Checks
|
||||
|
||||
Follow the shared prototype success criteria.
|
||||
|
||||
## Parity-Debug Loop
|
||||
|
||||
Run the shared parity-debug loop. The component test command is:
|
||||
|
||||
```bash
|
||||
pytest <parity_test> -v -s
|
||||
```
|
||||
|
||||
For numerical drift, check normalization, latent scaling, posterior mode vs
|
||||
sample, channel order, and temporal/spatial/audio cropping before changing
|
||||
layers.
|
||||
|
||||
## Escape Hatches
|
||||
|
||||
Follow `../add-model/shared/common_rules.md` and the component-specific guidance
|
||||
in `../add-model/shared/component_skill_common.md`. VAE-specific ask cases include
|
||||
dropping an encode/decode path, accepting an unsupported private op, or choosing
|
||||
between incompatible official VAE definitions.
|
||||
|
||||
## Handoff
|
||||
|
||||
Return `../add-model/contracts/component_skill_handoff.md` following the common
|
||||
handoff rules in `../add-model/shared/component_skill_common.md`.
|
||||
@@ -0,0 +1,119 @@
|
||||
---
|
||||
name: add-model-05-port-encoder
|
||||
description: Use during /add-model Phase 4 or Phase 6 to prototype or parity-debug one FastVideo-native text, image, audio, or compound encoder component.
|
||||
---
|
||||
|
||||
# Add Model Port Encoder
|
||||
|
||||
## Goal
|
||||
|
||||
Prototype or parity-debug one encoder or encoder-like conditioner in
|
||||
FastVideo-native code. Use this for text encoders, image encoders, audio
|
||||
encoders, and compound conditioners that fit the encoder config/loader bucket.
|
||||
|
||||
## Inputs
|
||||
|
||||
Follow `../add-model/shared/component_skill_common.md` and require the complete
|
||||
packet from `../add-model/contracts/component_context.md`.
|
||||
|
||||
Encoder-specific packet fields:
|
||||
|
||||
- `component`: encoder or encoder-like conditioner name.
|
||||
- `parity_test`: `tests/local_tests/encoders/test_<family>_<component>_parity.py`.
|
||||
- `weights`: converted encoder dir, HF subfolder, or external HF id.
|
||||
- `target_files`: `fastvideo/models/encoders/<arch_or_family>.py` and
|
||||
`fastvideo/configs/models/encoders/<arch_or_family>.py`.
|
||||
|
||||
## Modes
|
||||
|
||||
Use the common prototype and parity-debug modes from
|
||||
`../add-model/shared/component_skill_common.md`.
|
||||
|
||||
Encoder-specific prototype concerns include tokenizer kwargs, hidden-state
|
||||
extraction, output packing, connector order, and external/passthrough weight
|
||||
needs.
|
||||
|
||||
## Reuse Proof
|
||||
|
||||
Apply the shared reuse proof. Encoder-specific comparison must include tokenizer
|
||||
contracts, hidden-state extraction, masks, positional IDs, output packing,
|
||||
connector/projection ordering, passthrough paths, and returned dataclass shape.
|
||||
|
||||
## Existing FastVideo Patterns
|
||||
|
||||
- Base classes: `TextEncoder` and `ImageEncoder` in
|
||||
`fastvideo/models/encoders/base.py`.
|
||||
- Output type: `BaseEncoderOutput`.
|
||||
- Config bases: `TextEncoderConfig`, `ImageEncoderConfig`,
|
||||
`TextEncoderArchConfig`, and `ImageEncoderArchConfig` in
|
||||
`fastvideo/configs/models/encoders/base.py`.
|
||||
- Use the matching encoder config bucket. Wrong bucket inheritance can typecheck
|
||||
but fail during pipeline wiring.
|
||||
- Config export: add the config to
|
||||
`fastvideo/configs/models/encoders/__init__.py`.
|
||||
- Registry discovery: set `EntryClass = <ClassName>` or a list of class names in
|
||||
the model file.
|
||||
- Reference examples: native `t5.py`, `clip.py`, `siglip.py`, `llama.py`,
|
||||
`qwen2_5.py`, `gemma.py`, and compound `stable_audio_conditioner.py`.
|
||||
- Layer guidance: `fastvideo/layers/AGENTS.md`.
|
||||
|
||||
## Implementation Rules
|
||||
|
||||
- Reuse tokenizers and pure data utilities when needed, but do not add runtime
|
||||
third-party model-class imports as a placeholder for a component that owns
|
||||
weights or numerical behavior.
|
||||
- For LLM-style encoders, follow existing tensor-parallel patterns such as
|
||||
`QKVParallelLinear`, `MergedColumnParallelLinear`, `RowParallelLinear`,
|
||||
`VocabParallelEmbedding`, and `RMSNorm` when matching native examples.
|
||||
- Match official hidden-state extraction exactly: layer index, pooled output,
|
||||
attention mask dtype, padding side, truncation, special tokens, final norm,
|
||||
output_hidden_states, and returned tuple/dataclass shape.
|
||||
- For connector or conditioner modules, preserve sub-conditioner order and the
|
||||
exact packing of cross-attention tokens, masks, and global conditioning.
|
||||
- Put tokenizer kwargs and architecture constants on the arch config when they
|
||||
affect numerical behavior.
|
||||
- If an external HF encoder is explicitly accepted as a lazy wrapper, keep it
|
||||
isolated, document why it is not a native port, and still require parity for
|
||||
the wrapper's output contract.
|
||||
|
||||
Hybrid external-HF encoder checklist:
|
||||
|
||||
- Put external model folders in passthrough subfolders such as
|
||||
`text_encoder/<external_name>/`, or record a root `model_index.json` path field
|
||||
that the loader resolves to a local directory.
|
||||
- Keep external model parameters out of the FastVideo-owned state-dict surface
|
||||
when the external model is loaded lazily from its own HF files.
|
||||
- Convert and strict-check only the FastVideo-owned connector/projection weights;
|
||||
document external model weights as passthrough.
|
||||
- Add parity for the wrapper's final output contract and, when useful, a narrower
|
||||
connector-only parity test that labels its scope as
|
||||
`implementation_subcomponent`.
|
||||
- Verify the production loader resolves the same external path used by the
|
||||
pipeline, not just the direct class used in the parity test.
|
||||
|
||||
## Prototype Checks
|
||||
|
||||
Follow the shared prototype success criteria.
|
||||
|
||||
## Parity-Debug Loop
|
||||
|
||||
Run the shared parity-debug loop. The component test command is:
|
||||
|
||||
```bash
|
||||
pytest <parity_test> -v -s
|
||||
```
|
||||
|
||||
For numerical drift, check tokenization, masks, hidden-state selection,
|
||||
positional IDs, dtype/autocast, and output packing before changing layers.
|
||||
|
||||
## Escape Hatches
|
||||
|
||||
Follow `../add-model/shared/common_rules.md` and the component-specific guidance
|
||||
in `../add-model/shared/component_skill_common.md`. Encoder-specific ask cases
|
||||
include accepting private model-code execution, choosing between incompatible
|
||||
tokenizer/encoder references, or dropping a required conditioning stream.
|
||||
|
||||
## Handoff
|
||||
|
||||
Return `../add-model/contracts/component_skill_handoff.md` following the common
|
||||
handoff rules in `../add-model/shared/component_skill_common.md`.
|
||||
@@ -0,0 +1,111 @@
|
||||
---
|
||||
name: add-model-06-port-generic
|
||||
description: Use during /add-model Phase 4 or Phase 6 to prototype or parity-debug one non-DiT, non-VAE, non-encoder FastVideo component.
|
||||
---
|
||||
|
||||
# Add Model Port Generic
|
||||
|
||||
## Goal
|
||||
|
||||
Prototype or parity-debug one scheduler, conditioner, upsampler, vocoder,
|
||||
adapter, preprocessor, or unknown component in FastVideo-native code.
|
||||
|
||||
## Inputs
|
||||
|
||||
Follow `../add-model/shared/component_skill_common.md` and require the complete
|
||||
packet from `../add-model/contracts/component_context.md`.
|
||||
|
||||
Generic-component packet fields:
|
||||
|
||||
- `component`: component name.
|
||||
- `component_type`: scheduler, conditioner, upsampler, vocoder, adapter,
|
||||
preprocessor, or unknown.
|
||||
- `parity_test`: `tests/local_tests/<bucket>/test_<family>_<component>_parity.py`.
|
||||
- `weights`: converted component dir, HF subfolder, or none.
|
||||
- `target_files`: matching `fastvideo/models/` and `fastvideo/configs/models/`
|
||||
bucket files when applicable.
|
||||
|
||||
## Modes
|
||||
|
||||
Use the common prototype and parity-debug modes from
|
||||
`../add-model/shared/component_skill_common.md`.
|
||||
|
||||
Generic-component prototype concerns include stateless/stateful ambiguity,
|
||||
missing loader buckets, source prefixes, mutable scheduler state, and output
|
||||
container shape.
|
||||
|
||||
## Reuse Proof
|
||||
|
||||
Apply the shared reuse proof. Generic-component comparison must include mutable
|
||||
state, scaling constants, scheduler/conditioner semantics, output containers, and
|
||||
whether the component owns state or is stateless.
|
||||
|
||||
## Existing FastVideo Patterns
|
||||
|
||||
- Schedulers live under `fastvideo/models/schedulers/` and expose `EntryClass`.
|
||||
- Upsamplers use `fastvideo/models/upsamplers/` plus configs under
|
||||
`fastvideo/configs/models/upsamplers/`; see `hunyuan15.py`.
|
||||
- Vocoders and audio-specific modules can live under `fastvideo/models/audio/`
|
||||
with configs under `fastvideo/configs/models/audio/`; see `ltx2_audio_vae.py`.
|
||||
- Compound conditioners may fit the encoder bucket when the pipeline loader uses
|
||||
`ConditionerLoader`; see `stable_audio_conditioner.py`.
|
||||
- Registry discovery uses `EntryClass`; config bucket exports are required when
|
||||
pipeline configs import them by bucket.
|
||||
- Use the narrowest matching config bucket. Wrong bucket inheritance can typecheck
|
||||
but fail during pipeline wiring.
|
||||
- Layer guidance: `fastvideo/layers/AGENTS.md`.
|
||||
|
||||
## Bucket Decision
|
||||
|
||||
- If the component is a transformer/DiT, stop and use `add-model-03-port-dit`.
|
||||
- If the component is a VAE/autoencoder, stop and use `add-model-04-port-vae`.
|
||||
- If the component is a text/image/audio encoder or encoder-like conditioner,
|
||||
stop and use `add-model-05-port-encoder` unless the loader requires a different
|
||||
bucket.
|
||||
- Otherwise choose the narrowest existing bucket. Add a new bucket only when no
|
||||
existing loader/config shape can represent the component without misleading
|
||||
names or unsafe runtime behavior.
|
||||
|
||||
## Implementation Rules
|
||||
|
||||
- Match official behavior, not just shapes: constructor args, default values,
|
||||
runtime flags, RNG use, dtype/autocast, scaling constants, masks, and output
|
||||
containers all matter.
|
||||
- Keep the implementation minimal and native. Do not keep a runtime import of
|
||||
the official implementation as the production component.
|
||||
- For schedulers, compare timesteps, sigmas/noise levels, step outputs, shift
|
||||
handling, prediction type, and any mutable internal state.
|
||||
- For upsamplers, compare resize mode, align_corners, residual branches,
|
||||
causal padding, normalization, and exact target-shape behavior.
|
||||
- For vocoders/audio components, compare waveform shape, sample-rate contract,
|
||||
channel order, hop length, normalization, and dtype.
|
||||
- If private upstream deps are required only for tests, keep stubs under
|
||||
`tests/local_tests/helpers/` and do not import them from production code.
|
||||
|
||||
## Prototype Checks
|
||||
|
||||
Follow the shared prototype success criteria.
|
||||
|
||||
## Parity-Debug Loop
|
||||
|
||||
Run the shared parity-debug loop. The component test command is:
|
||||
|
||||
```bash
|
||||
pytest <parity_test> -v -s
|
||||
```
|
||||
|
||||
For numerical drift, add targeted intermediate comparisons in the test to
|
||||
identify the first divergent operation.
|
||||
|
||||
## Escape Hatches
|
||||
|
||||
Follow `../add-model/shared/common_rules.md` and the component-specific guidance
|
||||
in `../add-model/shared/component_skill_common.md`. Generic-component ask cases
|
||||
include creating a new loader bucket, accepting an unsupported private op,
|
||||
choosing between incompatible official definitions, or dropping a required
|
||||
component.
|
||||
|
||||
## Handoff
|
||||
|
||||
Return `../add-model/contracts/component_skill_handoff.md` following the common
|
||||
handoff rules in `../add-model/shared/component_skill_common.md`.
|
||||
@@ -0,0 +1,186 @@
|
||||
---
|
||||
name: add-model-07-conversion
|
||||
description: Use during /add-model Phase 5 to write and verify a FastVideo checkpoint conversion script after native component prototypes expose FastVideo state-dict keys/shapes.
|
||||
---
|
||||
|
||||
# Add Model Conversion
|
||||
|
||||
## Goal
|
||||
|
||||
Convert official weights into a FastVideo-loadable component layout after Phase 4
|
||||
native prototypes exist. The conversion script owns parameter mapping, component
|
||||
splitting, passthrough assets, config emission, and strict-load verification.
|
||||
|
||||
## Inputs
|
||||
|
||||
Follow `../add-model/shared/common_rules.md` for token/auth safety, state files,
|
||||
escape hatches, production boundaries, and skip/pass semantics.
|
||||
|
||||
Require the initial request from
|
||||
`../add-model/contracts/conversion_request.md`.
|
||||
|
||||
If the FastVideo key/shape dump is missing, return to `/add-model` Phase 4. Do
|
||||
not write a final mapping against an unimplemented component.
|
||||
|
||||
For Phase 6 retry requests from component skills, also require the retry shape
|
||||
from `../add-model/contracts/conversion_request.md`.
|
||||
|
||||
## Output
|
||||
|
||||
- `scripts/checkpoint_conversion/<family>_to_diffusers.py`.
|
||||
- `converted_weights/<family>/` with `model_index.json` and per-component
|
||||
subfolders.
|
||||
- Updated `tests/local_tests/<model_family>/README.md` with conversion command,
|
||||
source layout, output path, and strict-load status.
|
||||
- Updated `tests/local_tests/<model_family>/PORT_STATUS.md` with conversion
|
||||
state, retry history, open questions, and issues/blockers.
|
||||
|
||||
## Reference Scripts
|
||||
|
||||
- `scripts/checkpoint_conversion/convert_ltx2_weights.py`: component prefix
|
||||
splitting, metadata config extraction, passthrough Gemma/tokenizer assets, and
|
||||
optional component-only output.
|
||||
- `scripts/checkpoint_conversion/stable_audio_to_diffusers.py`: monolithic
|
||||
`model.safetensors` split into transformer/VAE/conditioner, plus copied
|
||||
passthrough subfolders. Use this shape for single-checkpoint official repos.
|
||||
- `scripts/checkpoint_conversion/convert_gamecraft_full.py`: separate official
|
||||
sources for transformer, VAE, text encoders, tokenizers, scheduler, and root
|
||||
`model_index.json`.
|
||||
- `scripts/checkpoint_conversion/longcat_to_fastvideo.py`: fused QKV/KV split,
|
||||
renamed native transformer weights, and copied existing Diffusers components.
|
||||
- `scripts/checkpoint_conversion/pt_to_safetensors.py`: simple `.pt` extraction
|
||||
helper for nested checkpoint dictionaries.
|
||||
|
||||
## Source Layout Decision
|
||||
|
||||
Choose exactly one primary layout:
|
||||
|
||||
| Layout | Conversion behavior |
|
||||
|---|---|
|
||||
| `diffusers` | Usually no tensor remap; verify configs/classes and copy or update `_class_name` only when needed. |
|
||||
| `raw_official` | Convert a raw official checkpoint file or directory. Choose explicit component ownership before writing output. |
|
||||
| `separate_components` | Convert/copy each component from its own file or directory. |
|
||||
| `monolithic` | Load one model checkpoint and split state dict by authoritative prefixes into component buckets. |
|
||||
| `mixed` | Convert some components and copy passthrough components such as tokenizers, text encoders, schedulers, or already-Diffusers VAE dirs. |
|
||||
| `custom` | Document why none of the above fits before writing conversion code. |
|
||||
|
||||
Monolithic checkpoints need explicit prefix ownership. For example, Stable Audio
|
||||
uses one `model.safetensors` with DiT, pretransform/VAE, and conditioner keys;
|
||||
the converter splits those keys into FastVideo component subfolders and writes
|
||||
per-component configs.
|
||||
|
||||
## Script Shape
|
||||
|
||||
Start from `templates/family_to_diffusers.py` or the closest reference script.
|
||||
Keep the script explicit and reviewable:
|
||||
|
||||
- `COMPONENT_SPECS` or `COMPONENT_PREFIXES` declares component ownership.
|
||||
- `PARAM_NAME_MAP` declares key renames.
|
||||
- `SKIP_PATTERNS` declares intentionally dropped training-only keys.
|
||||
- tensor split/fuse helpers are named by operation, e.g. `split_qkv`.
|
||||
- `build_component_configs(...)` writes loader-compatible config files. Most
|
||||
model components use `config.json`; schedulers use `scheduler_config.json`.
|
||||
- `build_model_index(...)` writes a root `model_index.json` matching the target
|
||||
FastVideo pipeline and component classes.
|
||||
- verification reports missing, unexpected, skipped, unchanged, renamed, and
|
||||
shape-mismatched keys.
|
||||
|
||||
`model_index.json` library tokens must match FastVideo loaders:
|
||||
|
||||
- standard native DiT/VAE/audio/vocoder/upsampler components loaded by existing
|
||||
Diffusers-style loaders usually use `"diffusers"` with a FastVideo
|
||||
`_class_name` in the component `config.json`;
|
||||
- text encoders, tokenizers, image encoders, processors, and feature extractors
|
||||
usually use `"transformers"`;
|
||||
- `conditioner` currently expects `"fastvideo"`;
|
||||
- use fully qualified `"fastvideo.<module>"` only when intentionally relying on
|
||||
the custom fastvideo-library escape path;
|
||||
- do not write bare `"fastvideo"` for transformer, VAE, or other loaders that
|
||||
expect `"diffusers"` unless the loader explicitly expects it.
|
||||
|
||||
## Mapping Rules
|
||||
|
||||
- Use Phase 4 key/shape dumps to derive mappings. Do not guess from official key
|
||||
names alone.
|
||||
- Preserve each component's official file paths, parity test path, and prototype
|
||||
concerns in comments or structured constants near the mapping that uses them.
|
||||
- Every official inference parameter should be mapped, copied through, or listed
|
||||
as intentionally skipped with a reason.
|
||||
- Every FastVideo prototype parameter should receive a tensor or be listed as an
|
||||
intentional external/passthrough parameter.
|
||||
- Shape matches are necessary but not sufficient; check semantic pairing for
|
||||
Q/K/V, gate/up/down, norm scale/bias, LoRA/base, and modality-specific heads.
|
||||
- If official and FastVideo fuse or split tensors differently, convert tensors in
|
||||
the script rather than changing production code to match checkpoint quirks.
|
||||
|
||||
## Verification
|
||||
|
||||
Run conversion locally, then verify before returning to Phase 6. For retry
|
||||
requests, update the mapping, rerun conversion, and refresh only the implicated
|
||||
converted component when safe; otherwise rerun the full conversion.
|
||||
|
||||
```bash
|
||||
python scripts/checkpoint_conversion/<family>_to_diffusers.py \
|
||||
--src <official_weights> \
|
||||
--revision <hf_revision> \
|
||||
--dst converted_weights/<model_family>
|
||||
```
|
||||
|
||||
Omit `--revision` for local sources or when prep recorded `default` / `none`.
|
||||
|
||||
Minimum output layout:
|
||||
|
||||
```text
|
||||
converted_weights/<family>/
|
||||
model_index.json
|
||||
transformer/config.json
|
||||
transformer/*.safetensors
|
||||
vae/config.json
|
||||
vae/*.safetensors
|
||||
scheduler/scheduler_config.json as needed
|
||||
text_encoder/... as needed
|
||||
```
|
||||
|
||||
Required checks:
|
||||
|
||||
- `model_index.json` exists and lists every required component.
|
||||
- Each converted component has the config filename its loader expects and
|
||||
safetensors weights when it owns weights. Scheduler dirs require
|
||||
`scheduler_config.json`; most other native model dirs use `config.json`.
|
||||
- Weight filenames may vary by loader: transformer and VAE loaders glob all
|
||||
`*.safetensors`; text encoders may load `*.safetensors`, `*.bin`, and
|
||||
sometimes `*.pt`; `conditioner` currently expects
|
||||
`diffusion_pytorch_model.safetensors`. Use the loader's actual accepted layout
|
||||
rather than assuming one global filename.
|
||||
- Passthrough components are copied or referenced deliberately.
|
||||
- Each emitted component config validates through the same path production
|
||||
loaders use. Instantiate the relevant config and call `update_model_arch(...)`
|
||||
or `update_model_config(...)` with the emitted JSON so unknown keys fail during
|
||||
conversion, not at pipeline load time.
|
||||
- Record production loader strictness for every stateful component. If the loader
|
||||
intentionally uses non-strict loading, add explicit missing/unexpected-key
|
||||
assertions in the parity test and document exactly which keys are allowed.
|
||||
- Each new FastVideo component strict-loads converted weights where its production
|
||||
loader is strict. If strict loading is impossible, record the exact allowed
|
||||
missing/unexpected keys and why they are not inference weights.
|
||||
- Retry fixes include the original component parity evidence and the new
|
||||
strict-load result in `local_tests_readme` so the component subagent can resume
|
||||
without rediscovering context.
|
||||
- `local_tests_readme` records the command, output directory, and strict-load
|
||||
result.
|
||||
|
||||
Do not chase numerical parity in this skill except to identify a conversion
|
||||
mapping bug. Long parity-debug loops belong to `/add-model` Phase 6.
|
||||
|
||||
## Escape Hatches
|
||||
|
||||
Follow `../add-model/shared/common_rules.md`. Conversion-specific ask cases
|
||||
include selecting between incompatible official checkpoints, publishing/uploading
|
||||
weights, overwriting an existing converted repo not created by this run,
|
||||
accepting non-strict missing inference weights, or dropping a component/output
|
||||
from scope.
|
||||
|
||||
## Handoff
|
||||
|
||||
Return `../add-model/contracts/conversion_handoff.md` and update the shared state
|
||||
files before handoff.
|
||||
@@ -0,0 +1,293 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
"""Convert <model_family> official weights to a FastVideo Diffusers-style tree.
|
||||
|
||||
This template supports both separate component sources and a monolithic pipeline
|
||||
checkpoint that must be split by component prefix. Replace every TODO before
|
||||
using it for a real port.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
from collections import OrderedDict
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import torch
|
||||
from safetensors import safe_open
|
||||
from safetensors.torch import load_file, save_file
|
||||
|
||||
try:
|
||||
from huggingface_hub import snapshot_download
|
||||
except ImportError: # pragma: no cover - optional local conversion dependency
|
||||
snapshot_download = None
|
||||
|
||||
# TODO: fill with authoritative component prefixes for monolithic checkpoints.
|
||||
# Example: {"model.model.": "transformer", "pretransform.model.": "vae"}
|
||||
COMPONENT_PREFIXES: dict[str, str] = {}
|
||||
|
||||
# TODO: fill with component-specific source paths for separate-component repos.
|
||||
# Example: {"transformer": "transformer/model.safetensors", "vae": "vae/"}
|
||||
SEPARATE_COMPONENT_PATHS: dict[str, str] = {}
|
||||
|
||||
# TODO: copy passthrough dirs that are already loadable by FastVideo/Diffusers.
|
||||
PASSTHROUGH_SUBFOLDERS: tuple[str, ...] = ("tokenizer", "scheduler")
|
||||
|
||||
# TODO: add regex renames derived from Phase 4 key/shape dumps.
|
||||
PARAM_NAME_MAP: dict[str, str] = {}
|
||||
|
||||
# TODO: include training-only or dynamically-computed keys that must not load.
|
||||
SKIP_PATTERNS: tuple[str, ...] = ()
|
||||
|
||||
|
||||
def _hf_token() -> str | None:
|
||||
return (os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACE_HUB_TOKEN") or os.environ.get("HF_API_KEY"))
|
||||
|
||||
|
||||
def resolve_src(src: str, revision: str | None) -> Path:
|
||||
if os.path.exists(src):
|
||||
return Path(src)
|
||||
if snapshot_download is None:
|
||||
raise RuntimeError("huggingface_hub is required when --src is a repo id")
|
||||
return Path(snapshot_download(repo_id=src, revision=revision, token=_hf_token()))
|
||||
|
||||
|
||||
def load_checkpoint(path: Path) -> dict[str, torch.Tensor]:
|
||||
if path.is_dir():
|
||||
weights: dict[str, torch.Tensor] = {}
|
||||
for shard in sorted(path.glob("*.safetensors")):
|
||||
weights.update(load_file(str(shard)))
|
||||
if weights:
|
||||
return weights
|
||||
raise FileNotFoundError(f"No safetensors found in {path}")
|
||||
|
||||
if path.suffix == ".safetensors":
|
||||
return load_file(str(path))
|
||||
|
||||
checkpoint = torch.load(path, map_location="cpu", weights_only=True)
|
||||
if isinstance(checkpoint, dict):
|
||||
for key in ("state_dict", "model_state_dict", "model", "module", "ema"):
|
||||
if key in checkpoint and isinstance(checkpoint[key], dict):
|
||||
return checkpoint[key]
|
||||
return checkpoint
|
||||
raise TypeError(f"Unsupported checkpoint type: {type(checkpoint)!r}")
|
||||
|
||||
|
||||
def should_skip_key(key: str) -> bool:
|
||||
return any(re.search(pattern, key) for pattern in SKIP_PATTERNS)
|
||||
|
||||
|
||||
def apply_mapping(key: str) -> str | None:
|
||||
if should_skip_key(key):
|
||||
return None
|
||||
for pattern, replacement in PARAM_NAME_MAP.items():
|
||||
if re.match(pattern, key):
|
||||
return re.sub(pattern, replacement, key)
|
||||
return key
|
||||
|
||||
|
||||
def split_monolithic(state: dict[str, torch.Tensor], ) -> dict[str, OrderedDict[str, torch.Tensor]]:
|
||||
components: dict[str, OrderedDict[str, torch.Tensor]] = {
|
||||
name: OrderedDict()
|
||||
for name in set(COMPONENT_PREFIXES.values())
|
||||
}
|
||||
intentionally_skipped: list[str] = []
|
||||
unowned: list[str] = []
|
||||
for key, value in state.items():
|
||||
if should_skip_key(key):
|
||||
intentionally_skipped.append(key)
|
||||
continue
|
||||
for prefix, component in COMPONENT_PREFIXES.items():
|
||||
if key.startswith(prefix):
|
||||
mapped = apply_mapping(key[len(prefix):])
|
||||
if mapped is not None:
|
||||
components[component][mapped] = value
|
||||
break
|
||||
else:
|
||||
unowned.append(key)
|
||||
if unowned:
|
||||
sample = ", ".join(unowned[:10])
|
||||
raise ValueError(f"Unowned monolithic keys: {len(unowned)}. "
|
||||
f"Add COMPONENT_PREFIXES or SKIP_PATTERNS entries. Sample: {sample}")
|
||||
if intentionally_skipped:
|
||||
print(f"Intentionally skipped {len(intentionally_skipped)} keys")
|
||||
return {name: weights for name, weights in components.items() if weights}
|
||||
|
||||
|
||||
def load_separate_components(src_dir: Path) -> dict[str, OrderedDict[str, torch.Tensor]]:
|
||||
components: dict[str, OrderedDict[str, torch.Tensor]] = {}
|
||||
for component, rel_path in SEPARATE_COMPONENT_PATHS.items():
|
||||
state = load_checkpoint(src_dir / rel_path)
|
||||
converted: OrderedDict[str, torch.Tensor] = OrderedDict()
|
||||
for key, value in state.items():
|
||||
mapped = apply_mapping(key)
|
||||
if mapped is not None:
|
||||
converted[mapped] = value
|
||||
components[component] = converted
|
||||
return components
|
||||
|
||||
|
||||
def build_component_configs(_src_dir: Path) -> dict[str, dict[str, Any]]:
|
||||
# TODO: emit config content accepted by FastVideo loaders. Most components use
|
||||
# config.json; schedulers use scheduler_config.json.
|
||||
return {
|
||||
"transformer": {
|
||||
"_class_name": "<FastVideoTransformerClass>"
|
||||
},
|
||||
"vae": {
|
||||
"_class_name": "<FastVideoVAEClass>"
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def config_filename(component: str) -> str:
|
||||
if component == "scheduler":
|
||||
return "scheduler_config.json"
|
||||
return "config.json"
|
||||
|
||||
|
||||
def source_label(src: str) -> str:
|
||||
if os.path.exists(src):
|
||||
return Path(src).name
|
||||
return src
|
||||
|
||||
|
||||
def build_model_index(
|
||||
src: str,
|
||||
revision: str | None,
|
||||
available_components: set[str],
|
||||
) -> dict[str, Any]:
|
||||
# TODO: match the target pipeline and every required component.
|
||||
index: dict[str, Any] = {
|
||||
"_class_name": "<FastVideoPipelineClass>",
|
||||
"_diffusers_version": "0.30.0",
|
||||
"_fastvideo_converted_from": source_label(src),
|
||||
# Existing transformer/VAE loaders expect "diffusers" even when
|
||||
# _class_name names a FastVideo-native class registered in FastVideo.
|
||||
"transformer": ["diffusers", "<FastVideoTransformerClass>"],
|
||||
"vae": ["diffusers", "<FastVideoVAEClass>"],
|
||||
}
|
||||
if revision:
|
||||
index["_fastvideo_converted_revision"] = revision
|
||||
return {key: value for key, value in index.items() if key.startswith("_") or key in available_components}
|
||||
|
||||
|
||||
def validate_component_configs(configs: dict[str, dict[str, Any]]) -> None:
|
||||
# TODO: instantiate each FastVideo config and call update_model_arch(...) or
|
||||
# update_model_config(...) with this JSON so unknown emitted keys fail here.
|
||||
placeholder_configs = [name for name, config in configs.items() if "<" in json.dumps(config)]
|
||||
if placeholder_configs:
|
||||
raise ValueError(f"Replace config placeholders for: {placeholder_configs}")
|
||||
|
||||
|
||||
def verify_conversion(
|
||||
dst_dir: Path,
|
||||
components: dict[str, OrderedDict[str, torch.Tensor]],
|
||||
) -> None:
|
||||
del dst_dir, components
|
||||
# TODO: load each emitted stateful component through its production loader and
|
||||
# assert strict load, or document exact allowed missing/unexpected keys.
|
||||
raise NotImplementedError("Implement production config validation and strict-load checks")
|
||||
|
||||
|
||||
def write_component(
|
||||
dst_dir: Path,
|
||||
name: str,
|
||||
state: dict[str, torch.Tensor],
|
||||
config: dict[str, Any] | None,
|
||||
) -> None:
|
||||
component_dir = dst_dir / name
|
||||
if component_dir.exists() and any(component_dir.iterdir()):
|
||||
shutil.rmtree(component_dir)
|
||||
component_dir.mkdir(parents=True, exist_ok=True)
|
||||
save_file(dict(state), str(component_dir / "diffusion_pytorch_model.safetensors"))
|
||||
if config is not None:
|
||||
config_path = component_dir / config_filename(name)
|
||||
with config_path.open("w", encoding="utf-8") as f:
|
||||
json.dump(config, f, indent=2)
|
||||
f.write("\n")
|
||||
print(f"Wrote {name}: {len(state)} tensors")
|
||||
|
||||
|
||||
def copy_passthrough(src_dir: Path, dst_dir: Path) -> list[str]:
|
||||
copied: list[str] = []
|
||||
for subfolder in PASSTHROUGH_SUBFOLDERS:
|
||||
src = src_dir / subfolder
|
||||
if not src.is_dir():
|
||||
continue
|
||||
dst = dst_dir / subfolder
|
||||
if dst.exists():
|
||||
shutil.rmtree(dst)
|
||||
shutil.copytree(src, dst)
|
||||
copied.append(subfolder)
|
||||
print(f"Copied {subfolder}/")
|
||||
return copied
|
||||
|
||||
|
||||
def default_monolithic_checkpoint(src_path: Path) -> Path:
|
||||
if src_path.is_file():
|
||||
return src_path
|
||||
return src_path / "model.safetensors"
|
||||
|
||||
|
||||
def convert(
|
||||
src: str,
|
||||
dst: str,
|
||||
layout: str,
|
||||
revision: str | None,
|
||||
) -> None:
|
||||
src_path = resolve_src(src, revision)
|
||||
dst_dir = Path(dst)
|
||||
dst_dir.mkdir(parents=True, exist_ok=True)
|
||||
model_index_path = dst_dir / "model_index.json"
|
||||
|
||||
if layout in {"monolithic", "raw_official"}:
|
||||
# TODO: replace model.safetensors with the official monolithic file name.
|
||||
components = split_monolithic(load_checkpoint(default_monolithic_checkpoint(src_path)))
|
||||
elif layout in {"separate_components", "mixed"}:
|
||||
if not src_path.is_dir():
|
||||
raise ValueError(f"{layout} layout requires a source directory: {src_path}")
|
||||
components = load_separate_components(src_path)
|
||||
else:
|
||||
raise ValueError(f"Unsupported template layout: {layout}")
|
||||
|
||||
copied = (copy_passthrough(src_path, dst_dir) if src_path.is_dir() else [])
|
||||
configs = build_component_configs(src_path if src_path.is_dir() else src_path.parent)
|
||||
validate_component_configs(configs)
|
||||
for name, state in components.items():
|
||||
write_component(dst_dir, name, state, configs.get(name))
|
||||
|
||||
available = set(components) | set(copied)
|
||||
with model_index_path.open("w", encoding="utf-8") as f:
|
||||
json.dump(build_model_index(src, revision, available), f, indent=2)
|
||||
f.write("\n")
|
||||
print(f"Wrote {dst_dir / 'model_index.json'}")
|
||||
verify_conversion(dst_dir, components)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--src", required=True, help="HF repo id, local dir, or checkpoint path")
|
||||
parser.add_argument("--revision", help="HF branch, tag, or commit for repo sources")
|
||||
parser.add_argument(
|
||||
"--dst",
|
||||
required=True,
|
||||
help="Output converted_weights/<model_family> directory",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--layout",
|
||||
choices=("raw_official", "monolithic", "separate_components", "mixed"),
|
||||
required=True,
|
||||
help="Official source layout",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
convert(args.src, args.dst, args.layout, args.revision)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,214 @@
|
||||
---
|
||||
name: add-model-08-trace
|
||||
description: Use during /add-model Phase 6 when component parity has failed and root cause requires layer-by-layer divergence analysis. Uses FastVideo activation trace first, falling back to custom hooks only for boundaries or stats the utility cannot observe.
|
||||
---
|
||||
|
||||
# Add-Model Trace
|
||||
|
||||
## Manual Invocation
|
||||
|
||||
Load this skill when `/add-model` Phase 6 component parity has failed and the
|
||||
root cause requires layer-by-layer divergence analysis. This skill is not
|
||||
auto-fired. The calling subagent (DiT, VAE, encoder, or generic port skill)
|
||||
loads it when its standard parity-debug loop hits a wall and cannot isolate
|
||||
the divergence from end-to-end tensor comparisons alone.
|
||||
|
||||
Do not load this skill for first-pass parity failures. Try weight-diff and
|
||||
end-to-end tensor comparison first. Load this skill only when those do not
|
||||
isolate the cause.
|
||||
|
||||
## Goal
|
||||
|
||||
Find the first numerical divergence point between FastVideo's port and the
|
||||
official reference, layer by layer, by instrumenting both sides at matching
|
||||
tensor boundaries. The investigation must leave zero source residue in
|
||||
production code when it closes.
|
||||
|
||||
## When To Run
|
||||
|
||||
After a component parity test FAILS at a bf16-noise-realistic tolerance AND
|
||||
the calling subagent's first-pass debug (weight-diff, end-to-end tensor
|
||||
compare) does not isolate the cause.
|
||||
|
||||
Required inputs before starting:
|
||||
|
||||
- A working FastVideo loader for the component under investigation.
|
||||
- A working official loader, typically via
|
||||
`tests/local_tests/helpers/<family>_upstream.py::load_upstream_<component>`.
|
||||
- Shared deterministic test inputs (same tensors on both sides).
|
||||
- The component parity test file path and its current failure output.
|
||||
|
||||
## Primary Path: FastVideo Activation Trace
|
||||
|
||||
Use FastVideo's first-class activation trace before writing custom hooks:
|
||||
`fastvideo/hooks/activation_trace.py`, documented in
|
||||
`docs/contributing/activation_trace.md`.
|
||||
|
||||
Pipeline runs attach trace to the transformer during pipeline initialization.
|
||||
Component-only parity harnesses may call `attach_activation_trace(model)` from
|
||||
local test/debug code; do not add trace calls to production model code.
|
||||
|
||||
Prefix the failing parity command with a tight layer regex:
|
||||
|
||||
```bash
|
||||
FASTVIDEO_TRACE_ACTIVATIONS=1 \
|
||||
FASTVIDEO_TRACE_LAYERS="^block\.layers\.[0-9]+$" \
|
||||
FASTVIDEO_TRACE_STATS="abs_mean,sum,max,shape" \
|
||||
FASTVIDEO_TRACE_STEPS="0" \
|
||||
FASTVIDEO_TRACE_OUTPUT="/tmp/opencode/fv_trace.jsonl" \
|
||||
pytest tests/local_tests -k "parity" -v -s
|
||||
```
|
||||
|
||||
Match the layer regex to the actual `model.named_modules()` names. Empty or
|
||||
broad regexes are expensive; prefer block-level names first, then narrow to
|
||||
submodules after the first divergent block is known.
|
||||
|
||||
## Trace Compare Contract
|
||||
|
||||
One JSONL file per side. FastVideo output should use `FASTVIDEO_TRACE_OUTPUT`;
|
||||
the upstream harness should emit the same JSONL shape:
|
||||
|
||||
```json
|
||||
{"module":"block.layers.0","tensor":"out","step":0,"abs_mean":0.0123,"sum":1.0,"max":0.5,"shape":[1,16,32]}
|
||||
```
|
||||
|
||||
Compare rows by `(module, step, tensor)`. The first row whose `shape`,
|
||||
`abs_mean`, or `max` diverges beyond the component tolerance is the first broken
|
||||
boundary. Keep `FASTVIDEO_TRACE_LAYERS`, `FASTVIDEO_TRACE_STATS`, and
|
||||
`FASTVIDEO_TRACE_STEPS` identical between sides; if row order differs, sort or
|
||||
normalize before diffing.
|
||||
|
||||
## Drill-Down Loop
|
||||
|
||||
**Initial run:** trace every top-level block (`^block\.layers\.[0-9]+$` or the
|
||||
family's equivalent). Identify the first block index where `abs_mean` or `max`
|
||||
drifts beyond tolerance while earlier blocks match.
|
||||
|
||||
**Drill run:** tighten `FASTVIDEO_TRACE_LAYERS` to submodules inside the first
|
||||
divergent block: attention output, MLP projections, norm outputs, modality
|
||||
adapters, or other named boundaries exposed by `named_modules()`.
|
||||
|
||||
**Iterate:** if the first divergent operation is a free function or tensor op not
|
||||
visible as an `nn.Module`, use the fallback instrumentation hierarchy below.
|
||||
|
||||
The loop ends when the first divergent submodule or operation is identified with
|
||||
a file:line citation in the official source.
|
||||
|
||||
## Fallback Instrumentation Hierarchy
|
||||
|
||||
Use these only when activation trace cannot observe the needed boundary or
|
||||
statistic.
|
||||
|
||||
### (1) Custom forward hooks
|
||||
|
||||
`module.register_forward_hook(...)` and `register_forward_pre_hook(...)`.
|
||||
Always within `try/finally` with `handle.remove()`. Zero source residue.
|
||||
|
||||
### (2) Runtime monkey-patch
|
||||
|
||||
`module.attr = wrapped_func` or `cls.method = wrapped_method`, restored via
|
||||
`try/finally` (save original first). Use for free functions and non-Module sites
|
||||
such as activation functions (`swiglu`, `apply_rotary_emb`).
|
||||
|
||||
### (3) Source edits in FastVideo's own code
|
||||
|
||||
Only when (1) and (2) are insufficient. Track all edits within a single named
|
||||
`git stash` boundary OR a temporary branch. Run `git diff` before closing the
|
||||
investigation to confirm cleanup.
|
||||
|
||||
### (4) Source edits in official repo source
|
||||
|
||||
Allowed only when hook and monkey-patch approaches cannot capture the site.
|
||||
For git-tracked or editable official clones, use `git diff` in the clone path to
|
||||
verify cleanup. For non-editable site-packages, back up the target file before
|
||||
editing and restore it before handoff.
|
||||
|
||||
## Hypothesis Toggles
|
||||
|
||||
Use env-var-gated monkey-patches to A/B test suspect implementations without
|
||||
source edits. Pattern: `<FAMILY>_DEBUG_PATCH_<HYPOTHESIS>=1`.
|
||||
|
||||
Example from the magi-human investigation:
|
||||
|
||||
```
|
||||
MAGI_DEBUG_PATCH_LINEAR=1
|
||||
```
|
||||
|
||||
This patched `PackedExpertLinear.forward` to mirror upstream's
|
||||
`_BF16ComputeLinear` explicit-cast pattern, isolating a dtype-cast difference
|
||||
as the root cause.
|
||||
|
||||
Document all toggles in the script docstring. Each toggle must:
|
||||
|
||||
- save the original before patching;
|
||||
- restore the original in a `try/finally` block;
|
||||
- print a `[debug] Patched <ClassName>.<method>` line to stdout when active.
|
||||
|
||||
## Cleanup Gate
|
||||
|
||||
The calling agent MUST report `[cleanup-gate] PASS` on all five items before
|
||||
handoff. Do not hand off with any item unresolved.
|
||||
|
||||
1. `git diff` in the FastVideo repo: empty. No stray prints, hooks, or
|
||||
monkey-patches in production code.
|
||||
2. `git diff` in the official-repo clone (if used): empty. For non-editable
|
||||
site-packages installs: `diff original.py original.py.trace-backup` is
|
||||
empty OR `pip install --force-reinstall <pkg>` succeeded and the installed
|
||||
file matches the original.
|
||||
3. `git stash list`: only the named investigation stash (or empty). No
|
||||
unnamed stashes left from this session.
|
||||
4. No new untracked files outside `/tmp/opencode/` (logs) and the existing
|
||||
debug script directory (`tests/local_tests/transformers/` or equivalent).
|
||||
5. `mypy` clean on any production files touched during the investigation.
|
||||
|
||||
## Escape Hatches
|
||||
|
||||
Escalate to the calling bucket skill when:
|
||||
|
||||
- A forward hook on an official module raises because of a custom `forward`
|
||||
signature or varlen handler args that the hook closure cannot satisfy. The
|
||||
bucket skill has component-specific knowledge to work around this.
|
||||
- The first divergent layer is `block[0]`, meaning the divergence is in the
|
||||
adapter, modality dispatcher, coordinate embedding, or packing step before
|
||||
any block runs. Check those sites first; the bug is not in attention or MLP.
|
||||
- Per-block drift is never zero anywhere across all blocks. This usually means
|
||||
the inputs are not bit-identical between sides. Verify with a state-dict
|
||||
compare (weight-diff script) AND confirm the input tensors are the same
|
||||
object or have identical values before the forward call.
|
||||
|
||||
## Handoff
|
||||
|
||||
Return to the calling subagent with:
|
||||
|
||||
- FastVideo trace JSONL path and upstream trace JSONL path.
|
||||
- Trace settings used: `FASTVIDEO_TRACE_LAYERS`, `FASTVIDEO_TRACE_STATS`, and
|
||||
`FASTVIDEO_TRACE_STEPS`.
|
||||
- The first divergent `(module, step, tensor)` row and observed drift.
|
||||
- The upstream file:line citation where the divergence originates.
|
||||
- Fallback hook/patch verdict if activation trace could not observe the boundary.
|
||||
- Hypothesis verdict if an A/B toggle was used, for example `PATCH_LINEAR=1`.
|
||||
- Cleanup-gate status: `[cleanup-gate] PASS` or a list of unresolved items.
|
||||
|
||||
The calling agent uses this to scope the production fix in the FastVideo
|
||||
component file.
|
||||
|
||||
## References
|
||||
|
||||
- `docs/contributing/activation_trace.md` for canonical activation-trace env vars,
|
||||
JSONL output, cost model, and troubleshooting.
|
||||
- `fastvideo/hooks/activation_trace.py` for the implementation and
|
||||
`attach_activation_trace(model)` entry point.
|
||||
- `templates/block_trace_debug.py` in this skill directory: fallback custom-hook
|
||||
template when activation trace cannot observe the needed boundary or stat.
|
||||
- `tests/local_tests/transformers/_debug_magi_human_block_parity.py` in the
|
||||
FastVideo3 repo: historical worked example for custom hook/patch debugging.
|
||||
- `add-model/SKILL.md` Phase 6: the calling context for this skill.
|
||||
- `add-model-03-port-dit/SKILL.md`, `add-model-04-port-vae/SKILL.md`,
|
||||
`add-model-05-port-encoder/SKILL.md`, `add-model-06-port-generic/SKILL.md`:
|
||||
bucket-specific debug language and component-specific escape-hatch knowledge.
|
||||
|
||||
## Changelog
|
||||
|
||||
| Date | Change |
|
||||
|---|---|
|
||||
| 2026-05-01 | Initial skill extracted from `_debug_magi_human_block_parity.py` pattern. |
|
||||
@@ -0,0 +1,334 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
"""Per-block divergence debugger template for FastVideo model ports.
|
||||
|
||||
Run directly (not a pytest test):
|
||||
python tests/local_tests/transformers/_debug_<family>_<component>_parity.py
|
||||
|
||||
Generalizes: tests/local_tests/transformers/_debug_magi_human_block_parity.py
|
||||
|
||||
Fill FAMILY, COMPONENT, and the two loader functions. Run once for the initial
|
||||
drift table, then set <FAMILY>_DEBUG_DRILL_LAYER=NN to drill into submodules.
|
||||
Add <FAMILY>_DEBUG_PATCH_<HYPOTHESIS>=1 to A/B test a suspect implementation.
|
||||
|
||||
CLEANUP: all hooks removed in try/finally; monkey-patches restored in
|
||||
try/finally; source edits tracked in a named git stash. Zero source residue.
|
||||
See add-model-08-trace/SKILL.md for the full cleanup gate checklist.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import gc
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import torch
|
||||
|
||||
FAMILY: str = "<family>" # e.g. "magi_human", "ltx2", "wan"
|
||||
COMPONENT: str = "<component>" # e.g. "dit", "vae", "encoder"
|
||||
DRILL_LAYER_ENV: str = "<FAMILY>_DEBUG_DRILL_LAYER"
|
||||
HYPOTHESIS_ENV: str = "<FAMILY>_DEBUG_PATCH_<HYPOTHESIS>"
|
||||
REL_THRESHOLD: float = 0.005 # 0.5% abs_mean drift flags a block as divergent
|
||||
LOG_DIR: Path = Path("/tmp/opencode")
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[3]
|
||||
sys.path.insert(0, str(REPO_ROOT))
|
||||
|
||||
|
||||
def load_official(device: torch.device) -> torch.nn.Module:
|
||||
"""Load the official upstream model. TODO: implement for your family.
|
||||
|
||||
Example (magi-human):
|
||||
from tests.local_tests.helpers.magi_human_upstream import install_stubs, load_upstream_dit
|
||||
install_stubs()
|
||||
return load_upstream_dit(base_shard_dir, device=device, dtype=None)
|
||||
"""
|
||||
raise NotImplementedError(f"Fill load_official() for {FAMILY}/{COMPONENT}.")
|
||||
|
||||
|
||||
def load_fastvideo(device: torch.device) -> torch.nn.Module:
|
||||
"""Load the FastVideo-native model. TODO: implement for your family.
|
||||
|
||||
Example (magi-human):
|
||||
from fastvideo.configs.models.dits.magi_human import MagiHumanVideoConfig
|
||||
from fastvideo.models.dits.magi_human import MagiHumanDiT
|
||||
from safetensors.torch import load_file; import glob
|
||||
fv = MagiHumanDiT(MagiHumanVideoConfig())
|
||||
state = {}
|
||||
for shard in sorted(glob.glob(str(transformer_dir / "*.safetensors"))): state.update(load_file(shard))
|
||||
fv.load_state_dict(state, strict=False); return fv.to(device).eval()
|
||||
"""
|
||||
raise NotImplementedError(f"Fill load_fastvideo() for {FAMILY}/{COMPONENT}.")
|
||||
|
||||
|
||||
def build_inputs(device: torch.device) -> dict[str, Any]:
|
||||
"""Return deterministic inputs shared by both sides. TODO: replace.
|
||||
|
||||
Both sides must receive the SAME tensors (clone before each forward call).
|
||||
Non-identical inputs cause non-zero drift everywhere.
|
||||
"""
|
||||
torch.manual_seed(0)
|
||||
return {"x": torch.randn(64, 1024, dtype=torch.bfloat16, device=device)}
|
||||
|
||||
|
||||
def _stat(name: str, t: torch.Tensor) -> dict:
|
||||
f = t.detach().float()
|
||||
return {
|
||||
"name": name,
|
||||
"shape": tuple(t.shape),
|
||||
"abs_mean": f.abs().mean().item(),
|
||||
"sum": f.sum().item(),
|
||||
"min": f.min().item(),
|
||||
"max": f.max().item(),
|
||||
}
|
||||
|
||||
|
||||
def _attach_block_hooks(
|
||||
model: torch.nn.Module,
|
||||
label: str,
|
||||
log: list[dict],
|
||||
tensors: dict[str, torch.Tensor] | None = None,
|
||||
drill_layer: int | None = None,
|
||||
) -> list[Any]:
|
||||
"""Return hook handles. Caller MUST remove them in try/finally."""
|
||||
handles: list[Any] = []
|
||||
|
||||
def _hook(name: str):
|
||||
|
||||
def fn(_module, _inputs, outputs):
|
||||
t = outputs[0] if isinstance(outputs, tuple) else outputs
|
||||
if not torch.is_tensor(t):
|
||||
return
|
||||
log.append({"side": label, **_stat(name, t)})
|
||||
if tensors is not None:
|
||||
tensors[name] = t.detach().float().cpu()
|
||||
|
||||
return fn
|
||||
|
||||
def _pre_hook(name: str):
|
||||
# Pre-hooks observe a free function's output by intercepting the next
|
||||
# module's input (useful when the activation is not an nn.Module).
|
||||
def fn(_module, inputs):
|
||||
t = inputs[0] if isinstance(inputs, tuple) else inputs
|
||||
if not torch.is_tensor(t):
|
||||
return
|
||||
key = f"{name}<in>"
|
||||
log.append({"side": label, **_stat(key, t)})
|
||||
if tensors is not None:
|
||||
tensors[key] = t.detach().float().cpu()
|
||||
|
||||
return fn
|
||||
|
||||
# TODO: adapt attribute paths to your model. Remove adapter block if absent.
|
||||
if hasattr(model, "adapter"):
|
||||
handles.append(model.adapter.register_forward_hook(_hook("adapter")))
|
||||
|
||||
# TODO: adapt model.block.layers to your block container.
|
||||
# Alternatives: model.transformer.layers, model.blocks, model.layers
|
||||
block_layers = model.block.layers # type: ignore[attr-defined]
|
||||
for i, layer in enumerate(block_layers):
|
||||
handles.append(layer.register_forward_hook(_hook(f"block[{i:02d}]")))
|
||||
if drill_layer is not None and i == drill_layer:
|
||||
tag = f"L{i:02d}"
|
||||
# TODO: adapt submodule names to your layer's attributes.
|
||||
# magi-human uses: attention, mlp.pre_norm, mlp.up_gate_proj,
|
||||
# mlp.down_proj (pre+post), mlp, attn_post_norm, mlp_post_norm.
|
||||
if hasattr(layer, "attention"):
|
||||
handles.append(layer.attention.register_forward_hook(_hook(f"{tag}.attention")))
|
||||
if hasattr(layer, "mlp"):
|
||||
mlp = layer.mlp
|
||||
if hasattr(mlp, "pre_norm"):
|
||||
handles.append(mlp.pre_norm.register_forward_hook(_hook(f"{tag}.mlp.pre_norm")))
|
||||
if hasattr(mlp, "up_gate_proj"):
|
||||
handles.append(mlp.up_gate_proj.register_forward_hook(_hook(f"{tag}.mlp.up_gate_proj")))
|
||||
if hasattr(mlp, "down_proj"):
|
||||
handles.append(mlp.down_proj.register_forward_pre_hook(_pre_hook(f"{tag}.mlp.down_proj")))
|
||||
handles.append(mlp.down_proj.register_forward_hook(_hook(f"{tag}.mlp.down_proj")))
|
||||
handles.append(mlp.register_forward_hook(_hook(f"{tag}.mlp")))
|
||||
if hasattr(layer, "attn_post_norm"):
|
||||
handles.append(layer.attn_post_norm.register_forward_hook(_hook(f"{tag}.attn_post_norm")))
|
||||
if hasattr(layer, "mlp_post_norm"):
|
||||
handles.append(layer.mlp_post_norm.register_forward_hook(_hook(f"{tag}.mlp_post_norm")))
|
||||
return handles
|
||||
|
||||
|
||||
def _apply_hypothesis_patch() -> bool:
|
||||
"""Apply an optional monkey-patch gated by HYPOTHESIS_ENV. TODO: implement.
|
||||
|
||||
Pattern: save original on the class, patch, restore in _restore_hypothesis_patch().
|
||||
"""
|
||||
if os.getenv(HYPOTHESIS_ENV) != "1":
|
||||
return False
|
||||
# TODO: import FastVideo class, save original, apply patch.
|
||||
print(f"[debug] Hypothesis patch {HYPOTHESIS_ENV}=1 applied.")
|
||||
return True
|
||||
|
||||
|
||||
def _restore_hypothesis_patch() -> None:
|
||||
if os.getenv(HYPOTHESIS_ENV) != "1":
|
||||
return
|
||||
# TODO: restore original, e.g.: _mod.TargetClass.method = _mod._ORIGINAL_METHOD
|
||||
|
||||
|
||||
def _write_log(entries: list[dict], path: Path) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(path, "w") as f:
|
||||
for e in entries:
|
||||
f.write(f"{e['name']} {e['shape']} "
|
||||
f"{e['abs_mean']:.8f} {e['sum']:.4f} "
|
||||
f"{e['min']:.6f} {e['max']:.6f}\n")
|
||||
|
||||
|
||||
def _sort_key(name: str, drill_layer: int) -> tuple:
|
||||
if name == "adapter":
|
||||
return (0, "")
|
||||
if name.startswith(f"L{drill_layer:02d}."):
|
||||
sub_order = {
|
||||
"attention": 0,
|
||||
"attn_post_norm": 1,
|
||||
"mlp.pre_norm": 2,
|
||||
"mlp.up_gate_proj": 3,
|
||||
"mlp.down_proj<in>": 4,
|
||||
"mlp.down_proj": 5,
|
||||
"mlp": 6,
|
||||
"mlp_post_norm": 7,
|
||||
}.get(name.split(".", 1)[1], 9)
|
||||
return (1, f"block[{drill_layer:02d}]", sub_order)
|
||||
if name.startswith("block["):
|
||||
return (1, name, 99)
|
||||
return (2, name, 0)
|
||||
|
||||
|
||||
def _print_table(by_name: dict[str, dict], drill_layer: int) -> int | None:
|
||||
hdr = (f"{'name':<18} {'up_shape':<22} {'up_absmean':>12} {'fv_absmean':>12} "
|
||||
f"{'absmean_diff':>14} {'rel%':>8} {'up_sum':>14} {'fv_sum':>14} {'sum_diff':>12}")
|
||||
print(f"\n{hdr}\n{'-' * len(hdr)}")
|
||||
first_div: int | None = None
|
||||
for name in sorted(by_name.keys(), key=lambda n: _sort_key(n, drill_layer)):
|
||||
d = by_name[name]
|
||||
up, fv = d.get("up"), d.get("fv")
|
||||
if up is None or fv is None:
|
||||
continue
|
||||
am_diff = abs(up["abs_mean"] - fv["abs_mean"])
|
||||
am_rel = am_diff / max(up["abs_mean"], 1e-9)
|
||||
sum_diff = abs(up["sum"] - fv["sum"])
|
||||
flag = ""
|
||||
if name.startswith("block[") and am_rel > REL_THRESHOLD:
|
||||
flag = " <<< DIVERGE"
|
||||
if first_div is None:
|
||||
first_div = int(name[len("block["):-1])
|
||||
print(f"{name:<18} {str(up['shape']):<22} {up['abs_mean']:>12.6f} "
|
||||
f"{fv['abs_mean']:>12.6f} {am_diff:>14.6f} {am_rel * 100:>7.3f}% "
|
||||
f"{up['sum']:>14.4f} {fv['sum']:>14.4f} {sum_diff:>12.4f}{flag}")
|
||||
return first_div
|
||||
|
||||
|
||||
def _print_elementwise(up_t: dict[str, torch.Tensor], fv_t: dict[str, torch.Tensor], drill_layer: int) -> None:
|
||||
common = set(up_t.keys()) & set(fv_t.keys())
|
||||
if not common:
|
||||
return
|
||||
hdr = f"{'name':<30} {'shape':<22} {'diff_max':>12} {'diff_mean':>12} {'diff_rel%':>10}"
|
||||
print(f"\nElement-wise diffs for drilled L{drill_layer:02d} submodules:\n{hdr}\n{'-' * len(hdr)}")
|
||||
for name in sorted(common):
|
||||
a, b = up_t[name], fv_t[name]
|
||||
if a.shape != b.shape:
|
||||
continue
|
||||
diff = (a - b).abs()
|
||||
rel = (diff.mean().item() / max(a.abs().mean().item(), 1e-9)) * 100
|
||||
print(f"{name:<30} {str(tuple(a.shape)):<22} "
|
||||
f"{diff.max().item():>12.6f} {diff.mean().item():>12.6f} {rel:>9.4f}%")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if not torch.cuda.is_available():
|
||||
print("Need CUDA. Skipping.")
|
||||
return
|
||||
|
||||
# TODO: add precondition checks (official clone present, weights available).
|
||||
|
||||
drill_layer = int(os.getenv(DRILL_LAYER_ENV, "0"))
|
||||
device = torch.device("cuda:0")
|
||||
patched = _apply_hypothesis_patch()
|
||||
try:
|
||||
inputs = build_inputs(device)
|
||||
|
||||
print("Loading official model...")
|
||||
official = load_official(device)
|
||||
up_log: list[dict] = []
|
||||
up_t: dict[str, torch.Tensor] = {}
|
||||
up_handles = _attach_block_hooks(official, "up", up_log, up_t, drill_layer)
|
||||
print("Running official forward (with hooks)...")
|
||||
try:
|
||||
with torch.inference_mode():
|
||||
# TODO: adapt forward call signature to your component.
|
||||
ref_out = official(**{k: v.clone() for k, v in inputs.items()})
|
||||
if isinstance(ref_out, dict):
|
||||
sample = ref_out.get("sample")
|
||||
ref_out = sample if sample is not None else ref_out.get("x")
|
||||
elif hasattr(ref_out, "sample"):
|
||||
ref_out = ref_out.sample
|
||||
elif isinstance(ref_out, tuple):
|
||||
ref_out = ref_out[0]
|
||||
assert torch.is_tensor(ref_out), f"official output is not tensor: {type(ref_out)}"
|
||||
ref_out = ref_out.detach().float().cpu()
|
||||
finally:
|
||||
for h in up_handles:
|
||||
h.remove()
|
||||
del official
|
||||
gc.collect()
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
print("Loading FastVideo model...")
|
||||
fv = load_fastvideo(device)
|
||||
fv_log: list[dict] = []
|
||||
fv_t: dict[str, torch.Tensor] = {}
|
||||
fv_handles = _attach_block_hooks(fv, "fv", fv_log, fv_t, drill_layer)
|
||||
print("Running FastVideo forward (with hooks)...")
|
||||
try:
|
||||
with torch.inference_mode():
|
||||
# TODO: adapt forward call signature to your component.
|
||||
fv_out = fv(**{k: v.clone() for k, v in inputs.items()})
|
||||
if isinstance(fv_out, dict):
|
||||
sample = fv_out.get("sample")
|
||||
fv_out = sample if sample is not None else fv_out.get("x")
|
||||
elif hasattr(fv_out, "sample"):
|
||||
fv_out = fv_out.sample
|
||||
elif isinstance(fv_out, tuple):
|
||||
fv_out = fv_out[0]
|
||||
assert torch.is_tensor(fv_out), f"FastVideo output is not tensor: {type(fv_out)}"
|
||||
fv_out = fv_out.detach().float().cpu()
|
||||
finally:
|
||||
for h in fv_handles:
|
||||
h.remove()
|
||||
finally:
|
||||
_restore_hypothesis_patch()
|
||||
|
||||
LOG_DIR.mkdir(parents=True, exist_ok=True)
|
||||
up_path = LOG_DIR / f"{FAMILY}_{COMPONENT}_up_layers.log"
|
||||
fv_path = LOG_DIR / f"{FAMILY}_{COMPONENT}_fv_layers.log"
|
||||
_write_log(up_log, up_path)
|
||||
_write_log(fv_log, fv_path)
|
||||
print(f"\nLogs: {up_path} {fv_path}\nDiff: diff {up_path} {fv_path}")
|
||||
|
||||
by_name: dict[str, dict] = {}
|
||||
for entry in up_log + fv_log:
|
||||
by_name.setdefault(entry["name"], {})[entry["side"]] = entry
|
||||
first_div = _print_table(by_name, drill_layer)
|
||||
|
||||
print()
|
||||
if first_div is not None:
|
||||
print(f"First block exceeding {REL_THRESHOLD * 100:.2f}% drift: block[{first_div:02d}]")
|
||||
print(f"Re-run with {DRILL_LAYER_ENV}={first_div} to drill submodules.")
|
||||
else:
|
||||
print(f"No block exceeded {REL_THRESHOLD * 100:.2f}% -- divergence is amortized or pre-block.")
|
||||
|
||||
diff = (ref_out - fv_out).abs()
|
||||
print(f"\nFinal ref_abs={ref_out.abs().mean():.6f} fv_abs={fv_out.abs().mean():.6f} "
|
||||
f"diff_max={diff.max():.6f} diff_mean={diff.mean():.6f}")
|
||||
_print_elementwise(up_t, fv_t, drill_layer)
|
||||
if patched:
|
||||
print(f"\n[debug] Hypothesis {HYPOTHESIS_ENV}=1 was active this run.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,255 @@
|
||||
---
|
||||
name: add-model-09-pipeline
|
||||
description: Use during /add-model Phase 7 after all required component parity tests pass to define FastVideo pipeline wiring, configs, presets, registry entries, examples, smoke tests, and pipeline parity tests.
|
||||
---
|
||||
|
||||
# Add Model Pipeline
|
||||
|
||||
## Goal
|
||||
|
||||
Implement and verify the end-to-end FastVideo pipeline after the native
|
||||
components and converted weights have passed non-skip component parity. This
|
||||
skill owns pipeline class/stage wiring, pipeline configs, presets, registry
|
||||
entries, examples, smoke tests, and pipeline parity-debug.
|
||||
|
||||
FastVideo has one pipeline architecture: stage-based composition through
|
||||
`ComposedPipelineBase`. Add or specialize stages only when existing stages cannot
|
||||
represent the official behavior safely.
|
||||
|
||||
## Hard Gate
|
||||
|
||||
Do not start pipeline work until every required component, including reused
|
||||
components, has a non-skip local parity PASS.
|
||||
|
||||
If any component row is missing, skipped, red, or blocked, return to `/add-model`
|
||||
Phase 6. Pipeline parity cannot distinguish stage wiring mistakes from broken
|
||||
component numerics when component parity is still unresolved.
|
||||
|
||||
## Inputs
|
||||
|
||||
Follow `../add-model/shared/common_rules.md` for token/auth safety, state files,
|
||||
escape hatches, production boundaries, and skip/pass semantics.
|
||||
|
||||
Require a complete packet matching
|
||||
`../add-model/contracts/pipeline_context.md`.
|
||||
|
||||
The packet must include:
|
||||
|
||||
- official pipeline files and official call/default sources;
|
||||
- workload types, input/output modalities, and output contract;
|
||||
- converted or source `model_index.json` path;
|
||||
- component parity rows, all `non_skip_pass`;
|
||||
- target FastVideo pipeline/config/preset/registry/example/test paths;
|
||||
- `local_tests_readme` and `port_state_file` paths.
|
||||
|
||||
## Outputs
|
||||
|
||||
- Pipeline package under `fastvideo/pipelines/basic/<family>/`.
|
||||
- Pipeline config under `fastvideo/configs/pipelines/<family>.py` or a documented
|
||||
family-local config file when that matches existing project style.
|
||||
- Presets under `fastvideo/pipelines/basic/<family>/presets.py`.
|
||||
- Registry updates in `fastvideo/registry.py`.
|
||||
- Basic example under `examples/inference/basic/basic_<family>*.py`.
|
||||
- Local smoke and parity tests under `tests/local_tests/pipelines/`.
|
||||
- Updated `tests/local_tests/<model_family>/README.md`.
|
||||
- Updated `tests/local_tests/<model_family>/PORT_STATUS.md`.
|
||||
- Handoff matching `../add-model/contracts/pipeline_handoff.md`.
|
||||
|
||||
## Mode: Pipeline Definition
|
||||
|
||||
Use this mode first.
|
||||
|
||||
1. Read the official pipeline call path before editing FastVideo code.
|
||||
2. Compare official defaults against the planned FastVideo config and presets:
|
||||
steps, CFG scales, secondary CFG, flow shift, schedulers, sigmas, seed/RNG,
|
||||
resolution, frames, FPS, duration, VAE scaling, decode slicing, negative
|
||||
prompt defaults, and output heads.
|
||||
3. Create or update the pipeline class with `_required_config_modules` matching
|
||||
the emitted `model_index.json` and `ComposedPipelineBase.load_modules`.
|
||||
Runtime pipeline resolution is exact: `model_index.json["_class_name"]` must
|
||||
match a registered `EntryClass.__name__`, or a wrapper/alias class in
|
||||
`EntryClass`. Registry detectors do not select the executable pipeline class.
|
||||
4. Add new public generation kwargs to `fastvideo/api/sampling_param.py` before
|
||||
examples or presets use them. `SamplingParam.update()` ignores unknown keys
|
||||
except for logging, and preset defaults apply only to declared fields. Add CLI
|
||||
args when the option should be available from command-line entrypoints.
|
||||
5. Put loader-time changes in `load_modules()` or earlier, not
|
||||
`initialize_pipeline()`. `ComposedPipelineBase.__init__` loads modules before
|
||||
`post_init()` calls `initialize_pipeline()`, so process-global flags, loader
|
||||
path rewrites, dtype overrides, and tokenizer path changes needed for loading
|
||||
cannot be introduced there.
|
||||
6. Use `self.get_module("transformer_2", None)` and similar optional accessors
|
||||
for truly optional modules. Do not hard-require optional modules by accident.
|
||||
7. Avoid mutating class-level `_required_config_modules` in custom code. If a
|
||||
pipeline needs dynamic modules, copy the list to an instance-owned value or
|
||||
pass `required_config_modules` explicitly so one pipeline instance cannot leak
|
||||
module requirements into another.
|
||||
8. Create the stage chain in official execution order. Prefer existing shared
|
||||
stages for standard text encoding, timestep preparation, latent preparation,
|
||||
denoising, and decoding.
|
||||
9. Add model-specific stages only for family-specific behavior that does not fit
|
||||
the shared stage contracts.
|
||||
10. Add pipeline config classes for wiring and runtime defaults. Do not duplicate
|
||||
component architecture fields unless a loader requires them in the subconfig.
|
||||
Family-local config files such as
|
||||
`fastvideo/pipelines/basic/<family>/pipeline_configs.py` are valid only when
|
||||
`fastvideo/registry.py` imports and registers the classes explicitly.
|
||||
11. Add `InferencePreset` objects with `model_family`, `name`, `version`,
|
||||
`defaults`, optional validation-only `stage_schemas`, and an `ALL_PRESETS`
|
||||
tuple. `stage_schemas` validates user-facing `stage_overrides` names; it does
|
||||
not drive `create_pipeline_stages()` execution.
|
||||
12. Register config classes and presets in `fastvideo/registry.py`: add
|
||||
`register_configs(...)`, import the family's `ALL_PRESETS`, and append it to
|
||||
`_register_presets()`. Detectors should cover HF paths and `_class_name`
|
||||
strings for config/preset lookup, but not as a replacement for exact pipeline
|
||||
class-name resolution.
|
||||
13. Add a basic example with a user-story docstring and normal file-path inputs
|
||||
for image, audio, or video references. Keep orchestration glue in the
|
||||
pipeline or a helper, not in the example.
|
||||
14. Add a separate smoke test
|
||||
`tests/local_tests/pipelines/test_<family>_pipeline_smoke.py` that proves
|
||||
imports, `EntryClass`, registry, presets, config defaults, and at least one
|
||||
real load/generate path when weights are local. Older local tests sometimes
|
||||
colocate smoke checks in parity files; new ports should use the separate file
|
||||
convention.
|
||||
15. Add or update pipeline parity test scaffolding with
|
||||
`templates/pipeline_parity_test.py`.
|
||||
16. Update `local_tests_readme` and `port_state_file` with commands, statuses,
|
||||
default sources, decisions, and blockers.
|
||||
|
||||
Production import boundaries are defined in
|
||||
`../add-model/shared/common_rules.md`.
|
||||
|
||||
## Mode: Pipeline Parity Debug
|
||||
|
||||
Run after pipeline definition and after smoke can execute far enough to load the
|
||||
pipeline. Loop until pipeline parity is a non-skip PASS or a precise blocker is
|
||||
returned.
|
||||
|
||||
Mandatory order:
|
||||
|
||||
```bash
|
||||
pytest tests/local_tests/pipelines/test_<family>_pipeline_smoke.py -v -s
|
||||
DISABLE_SP=1 pytest tests/local_tests/pipelines/test_<family>_pipeline_parity.py -v -s
|
||||
python examples/inference/basic/basic_<family>.py
|
||||
```
|
||||
|
||||
Pipeline parity must compare real outputs, not only successful generation:
|
||||
|
||||
- denoised latents when decode parity is expensive or nondeterministic;
|
||||
- decoded videos/images when visual output should be deterministic enough;
|
||||
- decoded waveform or audio features for audio pipelines;
|
||||
- separate video and audio targets for joint AV pipelines unless a validated
|
||||
joint metric exists.
|
||||
|
||||
Debug pipeline drift in this order:
|
||||
|
||||
1. Confirm both sides use the same component weights and component parity PASS
|
||||
results are still valid.
|
||||
2. Align official and FastVideo call arguments, presets, and default values.
|
||||
3. Align scheduler timesteps, sigmas/noise levels, prediction type, flow shift,
|
||||
guidance math, and secondary-guidance branches.
|
||||
4. Align RNG: initial latents/noise, generator device, seed, per-step noise, VAE
|
||||
sampling, and any official `+1 frame` or crop/slice behavior.
|
||||
5. Align conditioning: prompt templates, negative prompts, masks, image/audio
|
||||
preprocessing, modality packing, text truncation, and dtype/autocast.
|
||||
6. Align decode: latent scaling, per-channel mean/std, tiling flags, output
|
||||
channel order, sample rate, FPS, and final slicing.
|
||||
7. Add targeted stage-level diagnostics to identify the first divergent stage.
|
||||
|
||||
If stage diagnostics show the first bad stage is transformer/denoising or a
|
||||
mid-DiT block, enable activation trace before adding ad hoc pipeline prints; see
|
||||
`docs/contributing/activation_trace.md` and `../add-model-08-trace/SKILL.md`.
|
||||
Keep `FASTVIDEO_TRACE_LAYERS`, `FASTVIDEO_TRACE_STATS`, and
|
||||
`FASTVIDEO_TRACE_STEPS` identical across reruns so pipeline parity traces diff
|
||||
one-to-one.
|
||||
|
||||
If the first divergence belongs to component implementation, strict loading, or
|
||||
conversion mapping, stop pipeline edits and return `next_step=return_to_phase_6`
|
||||
with the exact failing evidence. Do not patch conversion from this skill.
|
||||
|
||||
## Stage And Variant Rules
|
||||
|
||||
- Canonical video T2V order: `InputValidationStage`, `TextEncodingStage`,
|
||||
`ConditioningStage`, `TimestepPreparationStage`, `LatentPreparationStage`,
|
||||
`DenoisingStage`, `DecodingStage`.
|
||||
- Canonical video I2V delta adds image loading/encoding and image VAE encoding in
|
||||
the official order, commonly: `TextEncodingStage`, `ImageEncodingStage`,
|
||||
`ConditioningStage`, `TimestepPreparationStage`, `LatentPreparationStage`,
|
||||
`ImageVAEEncodingStage`, `DenoisingStage`, `DecodingStage`.
|
||||
- Treat `ConditioningStage` as default-present for Wan-style pipelines, but still
|
||||
follow the reference if another family truly skips or replaces it.
|
||||
- T2V video pipelines usually use validation, text encoding, conditioning,
|
||||
timestep preparation, latent preparation, denoising, and decoding.
|
||||
- I2V adds image loading/encoding and image-latent preparation according to the
|
||||
official pipeline, not by assuming CLIP or Wan-specific branches.
|
||||
- Pick image, audio, and video encoders from the reference. Do not assume CLIP or
|
||||
any other common encoder unless the reference uses it.
|
||||
- Cross-attention class names are not prescribed; match the family style and
|
||||
preserve the official tensor contract.
|
||||
- `WorkloadType` currently has no `T2A`, `A2A`, or `AV` values. Until that enum
|
||||
is extended, audio-only pipelines may register with `WorkloadType.T2V` and
|
||||
preset `workload_type="t2v"` as a compatibility shim, but must document the
|
||||
rationale in code and `PORT_STATUS.md`.
|
||||
- Audio-only pipelines should not force real video semantics into presets. Use
|
||||
minimal video-shaped placeholders such as small `height`/`width` and
|
||||
`num_frames=1` only when shared `VideoGenerator`/validation paths require them,
|
||||
and document that the real output is audio.
|
||||
- Record modality-specific shape knobs and output contract in the pipeline
|
||||
handoff: video uses `height`, `width`, `num_frames`, and `fps`; audio uses
|
||||
`audio_seconds` and `sampling_rate`; joint AV records both plus whether output
|
||||
is muxed or paired files.
|
||||
- Use sibling pipeline classes/configs when required modules, HF repo layout,
|
||||
stage chains, or inputs differ materially.
|
||||
- Use one kwargs-driven pipeline class only when variants share weights,
|
||||
modules, stage chain, and safe call semantics.
|
||||
- Split later if components diverge, workload tags require separate discovery,
|
||||
signatures become unsafe, or stage branches become substantial.
|
||||
- If the DiT branches on `added_kv_proj_dim`, document the T2V/I2V split.
|
||||
- If the reference uses `transformer_2`, `boundary_ratio`, `guidance_scale_2`, or
|
||||
DMD step lists, keep those on config, presets, or stages deliberately.
|
||||
- Support every official output head in scope. If a head is out of scope, record
|
||||
explicit user approval in `PORT_STATUS.md`.
|
||||
|
||||
Pipeline verification order:
|
||||
|
||||
```bash
|
||||
pytest tests/local_tests/pipelines/test_<family>_pipeline_smoke.py -v -s
|
||||
DISABLE_SP=1 pytest -v -s tests/local_tests/pipelines/test_<family>_pipeline_parity.py
|
||||
python examples/inference/basic/basic_<family>.py
|
||||
```
|
||||
|
||||
Smoke tests prove loadability only. They are not a substitute for numerical
|
||||
component or pipeline parity.
|
||||
|
||||
## Escape Hatches
|
||||
|
||||
Follow `../add-model/shared/common_rules.md`. Pipeline-specific ask cases include
|
||||
dropping a public mode, modality, or output head; adding a new workload enum;
|
||||
changing official defaults for user-facing behavior; accepting a known pipeline
|
||||
parity blocker; running GPU-heavy quality work outside the agreed scope; or
|
||||
publishing/uploading generated references or converted weights.
|
||||
|
||||
## Handoff
|
||||
|
||||
Return `../add-model/contracts/pipeline_handoff.md` and update the shared state
|
||||
files before handoff.
|
||||
|
||||
Do not hand back a green pipeline if smoke or parity skipped locally. A skip is a
|
||||
setup gap, not a pass.
|
||||
|
||||
## References
|
||||
|
||||
- `fastvideo/pipelines/composed_pipeline_base.py` for module loading and stage
|
||||
execution.
|
||||
- `fastvideo/pipelines/basic/wan/` for standard video T2V/I2V/DMD variants.
|
||||
- `fastvideo/pipelines/basic/stable_audio/` for audio-specific stage composition.
|
||||
- `fastvideo/configs/pipelines/stable_audio.py` and
|
||||
`fastvideo/pipelines/basic/stable_audio/presets.py` for config/preset shape.
|
||||
- `fastvideo/registry.py` for `register_configs(...)` and preset registration.
|
||||
- `tests/local_tests/pipelines/test_gamecraft_pipeline_parity.py` for latent
|
||||
parity structure.
|
||||
- `tests/local_tests/pipelines/test_stable_audio_pipeline_parity.py` for audio
|
||||
parity structure.
|
||||
- `tests/local_tests/pipelines/test_stable_audio_pipeline_smoke.py` for no-GPU
|
||||
import/registry/preset preflight shape.
|
||||
@@ -0,0 +1,157 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
"""Pipeline parity scaffold for TODO_MODEL_FAMILY.
|
||||
|
||||
Copy this file to
|
||||
`tests/local_tests/pipelines/test_<family>_pipeline_parity.py` and replace every
|
||||
TODO before treating it as an executable scaffold.
|
||||
|
||||
The filled test should compare denoised latents, decoded media, audio waveform,
|
||||
or another concrete output from the official pipeline against FastVideo. A
|
||||
successful generation without tensor/media comparison is not parity.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
from torch.testing import assert_close
|
||||
|
||||
_REPO_ROOT = Path(__file__).resolve().parents[3]
|
||||
_MODEL_FAMILY = "TODO_MODEL_FAMILY"
|
||||
_OFFICIAL_REF_ENV = "TODO_OFFICIAL_REF_PATH"
|
||||
_OFFICIAL_REF_DEFAULT = _REPO_ROOT / "TODO_OFFICIAL_REF_DIR"
|
||||
_FASTVIDEO_MODEL_ENV = "TODO_FASTVIDEO_MODEL_PATH"
|
||||
_FASTVIDEO_MODEL_DEFAULT = _REPO_ROOT / "converted_weights" / _MODEL_FAMILY
|
||||
|
||||
|
||||
def _path_from_env(env_name: str, default: Path) -> Path:
|
||||
return Path(os.getenv(env_name, str(default))).expanduser()
|
||||
|
||||
|
||||
def _add_official_to_path() -> Path:
|
||||
official_path = _path_from_env(_OFFICIAL_REF_ENV, _OFFICIAL_REF_DEFAULT)
|
||||
if not official_path.exists():
|
||||
pytest.skip(f"Official reference not found at {official_path}")
|
||||
if str(official_path) not in sys.path:
|
||||
sys.path.insert(0, str(official_path))
|
||||
return official_path
|
||||
|
||||
|
||||
def _log_tensor_stats(label: str, tensor: torch.Tensor) -> None:
|
||||
value = tensor.detach().float()
|
||||
print(f"[{_MODEL_FAMILY} PIPELINE] {label}: shape={tuple(tensor.shape)} "
|
||||
f"dtype={tensor.dtype} device={tensor.device} "
|
||||
f"min={value.min().item():.6f} max={value.max().item():.6f} "
|
||||
f"mean={value.mean().item():.6f} std={value.std().item():.6f}")
|
||||
|
||||
|
||||
def _extract_tensor(output: Any, key: str) -> torch.Tensor:
|
||||
if isinstance(output, dict):
|
||||
value = output.get(key)
|
||||
else:
|
||||
value = getattr(output, key, None)
|
||||
if value is None:
|
||||
raise AssertionError(f"Pipeline output did not contain {key!r}")
|
||||
if not torch.is_tensor(value):
|
||||
try:
|
||||
import numpy as np
|
||||
value = torch.from_numpy(np.asarray(value))
|
||||
except Exception as exc: # pragma: no cover - scaffold guard
|
||||
raise AssertionError(f"Could not convert {key!r} to tensor") from exc
|
||||
return value.detach().float().cpu()
|
||||
|
||||
|
||||
def _run_official_pipeline(
|
||||
official_path: Path,
|
||||
params: dict[str, Any],
|
||||
device: torch.device,
|
||||
) -> Any:
|
||||
del official_path, params, device
|
||||
pytest.skip("TODO: import the official pipeline/factory, load official weights, "
|
||||
"run with params, and return the comparison target.")
|
||||
|
||||
|
||||
def _run_fastvideo_pipeline(model_path: Path, params: dict[str, Any]) -> Any:
|
||||
from fastvideo import VideoGenerator
|
||||
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
str(model_path),
|
||||
{
|
||||
"engine": {
|
||||
"num_gpus": 1,
|
||||
"use_fsdp_inference": False,
|
||||
"offload": {
|
||||
"dit": False,
|
||||
"vae": False,
|
||||
"text_encoder": False,
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
try:
|
||||
return generator.generate({
|
||||
"prompt": params["prompt"],
|
||||
"negative_prompt": params.get("negative_prompt"),
|
||||
"sampling": {
|
||||
"height": params.get("height"),
|
||||
"width": params.get("width"),
|
||||
"num_frames": params.get("num_frames"),
|
||||
"fps": params.get("fps"),
|
||||
"num_inference_steps": params["num_inference_steps"],
|
||||
"guidance_scale": params.get("guidance_scale"),
|
||||
"seed": params["seed"],
|
||||
},
|
||||
"output": {
|
||||
"output_path": f"outputs_{_MODEL_FAMILY}/pipeline_parity",
|
||||
"save_video": False,
|
||||
},
|
||||
})
|
||||
finally:
|
||||
generator.shutdown()
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not torch.cuda.is_available(),
|
||||
reason="TODO_MODEL_FAMILY pipeline parity requires CUDA.",
|
||||
)
|
||||
def test_todo_model_family_pipeline_official_parity() -> None:
|
||||
official_path = _add_official_to_path()
|
||||
fastvideo_model_path = _path_from_env(
|
||||
_FASTVIDEO_MODEL_ENV,
|
||||
_FASTVIDEO_MODEL_DEFAULT,
|
||||
)
|
||||
if not fastvideo_model_path.exists():
|
||||
pytest.skip(f"FastVideo model path not found at {fastvideo_model_path}")
|
||||
|
||||
device = torch.device("cuda:0")
|
||||
params = {
|
||||
"prompt": "TODO: stable parity prompt",
|
||||
"negative_prompt": "",
|
||||
"height": 64,
|
||||
"width": 64,
|
||||
"num_frames": 9,
|
||||
"fps": 8,
|
||||
"num_inference_steps": 4,
|
||||
"guidance_scale": 1.0,
|
||||
"seed": 0,
|
||||
}
|
||||
|
||||
official_output = _run_official_pipeline(official_path, params, device)
|
||||
fastvideo_output = _run_fastvideo_pipeline(fastvideo_model_path, params)
|
||||
|
||||
comparison_key = "TODO_COMPARISON_KEY"
|
||||
official_tensor = _extract_tensor(official_output, comparison_key)
|
||||
fastvideo_tensor = _extract_tensor(fastvideo_output, comparison_key)
|
||||
|
||||
_log_tensor_stats("official", official_tensor)
|
||||
_log_tensor_stats("fastvideo", fastvideo_tensor)
|
||||
assert official_tensor.shape == fastvideo_tensor.shape
|
||||
|
||||
diff = (official_tensor - fastvideo_tensor).abs()
|
||||
print(f"diff max={diff.max().item():.6f} "
|
||||
f"mean={diff.mean().item():.6f} median={diff.median().item():.6f}")
|
||||
assert_close(fastvideo_tensor, official_tensor, atol=1e-2, rtol=1e-2)
|
||||
@@ -0,0 +1,141 @@
|
||||
---
|
||||
name: add-model-10-pr-review
|
||||
description: Review rubric for FastVideo PRs that add or modify model families, variants, first-class components, checkpoint conversion, pipelines, parity coverage, or generated-media quality baselines. Use when reviewing a PR whose diff touches fastvideo/models/, fastvideo/pipelines/basic/, fastvideo/registry.py, scripts/checkpoint_conversion/, fastvideo/tests/ssim/, or related model-port surfaces. Pairs with review-pr-link as a project-scoped review pass; produces findings, not fixes.
|
||||
---
|
||||
|
||||
# Add-Model PR Review
|
||||
|
||||
Use this skill when a reviewed PR appears to add, port, or substantially modify
|
||||
a FastVideo model family, model variant, first-class model component,
|
||||
checkpoint conversion, model pipeline, or local parity coverage.
|
||||
|
||||
This is a review skill, not an implementation workflow. Do not run `/add-model`
|
||||
or start writing missing port code during review. Use the add-model skill stack
|
||||
as a rubric for findings.
|
||||
|
||||
## Trigger Paths
|
||||
|
||||
Trigger this skill if `git diff --name-only <base>...HEAD` includes any of:
|
||||
|
||||
- `fastvideo/models/dits/`, `fastvideo/configs/models/dits/`
|
||||
- `fastvideo/models/vaes/`, `fastvideo/configs/models/vaes/`
|
||||
- `fastvideo/models/encoders/`, `fastvideo/configs/models/encoders/`
|
||||
- `fastvideo/models/schedulers/`, `fastvideo/configs/models/schedulers/`
|
||||
- `fastvideo/models/upsamplers/`, `fastvideo/configs/models/upsamplers/`
|
||||
- `fastvideo/models/audio/`, `fastvideo/configs/models/audio/`
|
||||
- `fastvideo/pipelines/basic/`, `fastvideo/configs/pipelines/`
|
||||
- `fastvideo/registry.py`, `fastvideo/api/sampling_param.py`
|
||||
- `scripts/checkpoint_conversion/`
|
||||
- `examples/inference/basic/`
|
||||
- `tests/local_tests/`, especially component or pipeline parity tests
|
||||
- `fastvideo/tests/ssim/` or other quality-regression tests for generated media
|
||||
|
||||
Also trigger when the PR title/body claims a new model, model variant, VAE,
|
||||
encoder, scheduler, conditioner, pipeline, conversion script, or generated-media
|
||||
quality baseline even if the path list is incomplete.
|
||||
|
||||
## Review Inputs
|
||||
|
||||
Read these add-model references as review checklists:
|
||||
|
||||
- `../add-model/SKILL.md`: phase gates and final handoff requirements.
|
||||
- `../add-model/shared/common_rules.md`: token/auth safety, production import
|
||||
boundaries, state files, and skip/pass semantics.
|
||||
- `../add-model/contracts/final_handoff.md`: final evidence expected from a
|
||||
complete port.
|
||||
- `../add-model/contracts/component_context.md` and
|
||||
`../add-model/contracts/component_skill_handoff.md`: component evidence and
|
||||
parity-debug expectations.
|
||||
- `../add-model/contracts/conversion_request.md` and
|
||||
`../add-model/contracts/conversion_handoff.md`: conversion evidence,
|
||||
strict-load status, config validation, and retry context.
|
||||
- `../add-model/contracts/pipeline_context.md` and
|
||||
`../add-model/contracts/pipeline_handoff.md`: pipeline class/stage/config/
|
||||
preset/registry/example evidence.
|
||||
|
||||
Then read only the satellite skill(s) that match touched areas:
|
||||
|
||||
- DiT/transformer changes: `../add-model-03-port-dit/SKILL.md`.
|
||||
- VAE changes: `../add-model-04-port-vae/SKILL.md`.
|
||||
- Encoder/conditioner changes: `../add-model-05-port-encoder/SKILL.md`.
|
||||
- Scheduler/upsampler/vocoder/other components:
|
||||
`../add-model-06-port-generic/SKILL.md`.
|
||||
- Component parity tests: `../add-model-02-parity/SKILL.md`.
|
||||
- Checkpoint conversion: `../add-model-07-conversion/SKILL.md`.
|
||||
- Pipeline/config/presets/registry/examples:
|
||||
`../add-model-09-pipeline/SKILL.md`.
|
||||
- Prep/state docs: `../add-model-01-prep/SKILL.md`.
|
||||
|
||||
## Required Review Lanes
|
||||
|
||||
For a full model-family or model-variant PR, cover all lanes. For a
|
||||
component-only PR, cover the component, conversion/parity as applicable, and the
|
||||
documented downstream consumer.
|
||||
|
||||
1. Scope and source-of-truth lane:
|
||||
Verify the PR clearly identifies the official reference, weights/revision,
|
||||
supported variants, modalities, output heads, and any approved scope cuts.
|
||||
|
||||
2. Component lane:
|
||||
Verify each required component is FastVideo-native or has a documented and
|
||||
accepted lazy-wrapper exception. Check bucket/config inheritance, `EntryClass`,
|
||||
state-dict surface, reused-component evidence, and output heads.
|
||||
|
||||
3. Conversion lane:
|
||||
Verify mappings are derived from prototype key/shape dumps, source layout is
|
||||
supported, skipped keys are intentional, emitted configs validate through
|
||||
production paths, component strict-load status is recorded, `model_index.json`
|
||||
library tokens match loaders, and revisions are pinned when converting from
|
||||
HF.
|
||||
|
||||
4. Component parity lane:
|
||||
Verify local parity tests exist for every required component, including reused
|
||||
components. Scaffolds may skip in CI, but the PR must provide local non-skip
|
||||
PASS evidence or an explicit accepted blocker.
|
||||
|
||||
5. Pipeline lane:
|
||||
Verify stage order, required modules, `_class_name` / `EntryClass.__name__`
|
||||
resolution, config defaults, presets, `SamplingParam` fields, registry
|
||||
registration, examples, smoke tests, and pipeline parity.
|
||||
|
||||
6. Quality and evidence lane:
|
||||
Verify media quality regression is added or explicitly deferred, examples run,
|
||||
generated outputs are non-corrupt, `tests/local_tests/<family>/README.md` and
|
||||
`PORT_STATUS.md` are current, and final blockers are surfaced in the review.
|
||||
|
||||
## Findings To Prioritize
|
||||
|
||||
Prioritize review findings in this order:
|
||||
|
||||
- Missing or skipped required component parity without accepted blocker.
|
||||
- Pipeline parity/smoke/example missing or skipped for a pipeline PR.
|
||||
- Conversion emits unloadable or unvalidated configs/weights.
|
||||
- Wrong `model_index.json` `_class_name`, component library token, or registry
|
||||
class resolution.
|
||||
- Runtime diffusers/transformers model-class imports for components that own
|
||||
weights or numerical behavior.
|
||||
- Dropped modalities, output heads, variants, or conditioning streams without
|
||||
explicit approval.
|
||||
- Reused FastVideo component lacks exact definition/instantiation proof or
|
||||
non-skip parity.
|
||||
- Public generation kwargs/preset defaults missing from `SamplingParam`.
|
||||
- Tests only check shapes, importability, or successful generation without
|
||||
numerical/media comparison.
|
||||
- Tokens, credentials, reference clones, staged weights, or generated bulk assets
|
||||
committed to the PR.
|
||||
|
||||
## Output Format
|
||||
|
||||
Write normal code-review findings first, ordered by severity. Include file and
|
||||
line references from the PR diff when possible.
|
||||
|
||||
Use this phrasing for missing add-model evidence:
|
||||
|
||||
```text
|
||||
This PR does not satisfy the add-model <component|conversion|pipeline|final>
|
||||
gate because <specific required evidence> is missing. The risk is <runtime load,
|
||||
numerical parity, dropped output, registry resolution, etc.>.
|
||||
```
|
||||
|
||||
Keep the summary short. Mention which lanes were reviewed and which could not be
|
||||
verified because assets, GPU time, or external credentials were unavailable.
|
||||
@@ -0,0 +1,438 @@
|
||||
---
|
||||
name: add-model
|
||||
description: Manual /add-model workflow for implementing a FastVideo model or first-class component port after add-model-01-prep has staged reference code and weights. Organizes the port into numbered phases with conversion rules, component policies, parity gates, and handoff checks.
|
||||
---
|
||||
|
||||
# Add Model
|
||||
|
||||
## Manual Invocation
|
||||
|
||||
This skill is for explicit `/add-model` use only. Do not auto-start it from a
|
||||
casual model-port mention. The setup-only workflow is
|
||||
`../add-model-01-prep/SKILL.md`.
|
||||
|
||||
## Goal
|
||||
|
||||
Port a new FastVideo model family, model variant, or first-class reusable
|
||||
component so it can be loaded through FastVideo's native model, config, stage,
|
||||
registry, preset, and test infrastructure.
|
||||
|
||||
FastVideo has one pipeline architecture: stage-based composition via
|
||||
`ComposedPipelineBase`. Vary the stages and modules, not the architecture.
|
||||
|
||||
## Scope Shapes
|
||||
|
||||
Use this skill for either shape:
|
||||
|
||||
| Shape | Required output |
|
||||
|---|---|
|
||||
| Full model family or variant | Native components, conversion if needed, pipeline config/class, presets, registry, smoke test, local parity tests, example, quality regression. |
|
||||
| First-class component contribution | Native component class/config, bucket export, component parity test, and a documented downstream pipeline that will consume it. Skip pipeline/preset/registry rows only when the contribution is intentionally component-only. |
|
||||
|
||||
If upstream ships many variants, lock scope before coding. "Base model" means
|
||||
checkpoint variant, not a modality subset. If the base checkpoint produces
|
||||
audio, pose, depth, masks, or other output heads, either support those outputs
|
||||
or get explicit user agreement to drop them.
|
||||
|
||||
## Required Input
|
||||
|
||||
Start from an `add-model-01-prep` handoff, or equivalent fields matching
|
||||
`contracts/prep_handoff.md`.
|
||||
|
||||
Before Phase 0, read the shared rules and all relevant schemas:
|
||||
|
||||
- `shared/common_rules.md`
|
||||
- `contracts/prep_handoff.md`
|
||||
- `contracts/port_state.md`
|
||||
- `contracts/escape_hatch.md`
|
||||
- `contracts/component_context.md`
|
||||
- `contracts/parity_status.md`
|
||||
- `contracts/conversion_request.md`
|
||||
- `contracts/conversion_handoff.md`
|
||||
- `contracts/component_skill_handoff.md`
|
||||
- `contracts/pipeline_context.md`
|
||||
- `contracts/pipeline_handoff.md`
|
||||
- `contracts/final_handoff.md`
|
||||
|
||||
## Hard Rules
|
||||
|
||||
- Follow `shared/common_rules.md` for token/auth safety, state files, escape
|
||||
hatches, production import boundaries, and skip/pass semantics.
|
||||
- If the prep handoff is missing or ambiguous, stop and run
|
||||
`../add-model-01-prep/SKILL.md`.
|
||||
- If a needed component is not ported, do not ship the pipeline that needs it.
|
||||
- Wan is grandfathered for missing local parity; do not copy its missing-test
|
||||
precedent for new work.
|
||||
|
||||
## Escape Hatches
|
||||
|
||||
Follow `shared/common_rules.md` and `contracts/escape_hatch.md`. The main
|
||||
orchestrator should ask only when no phase skill can safely continue under the
|
||||
shared rules.
|
||||
|
||||
## Files Map
|
||||
|
||||
| Area | Paths |
|
||||
|---|---|
|
||||
| DiT | `fastvideo/models/dits/<family>.py`, `fastvideo/configs/models/dits/<family>.py`, bucket `__init__.py`. |
|
||||
| VAE | `fastvideo/models/vaes/<arch_or_family>.py`, `fastvideo/configs/models/vaes/<arch_or_family>.py`, bucket `__init__.py`. Name by shared arch when reusable (`oobleck.py`, `autoencoder_kl.py`), otherwise by family (`wanvae.py`). |
|
||||
| Encoder / conditioner / scheduler / upsampler | Native class/config in the matching `fastvideo/models/<bucket>/` and `fastvideo/configs/models/<bucket>/` bucket. |
|
||||
| Lazy loader wrapper | Optional `fastvideo/models/<bucket>/<family>_loader.py` or similar thin `nn.Module` wrapper when a component is fetched from an external HF repo and should be hidden from host-pipeline state-dict matching. |
|
||||
| Conversion | `scripts/checkpoint_conversion/<family>_to_diffusers.py` only when `needs_conversion=yes`. |
|
||||
| Pipeline | `fastvideo/pipelines/basic/<family>/<family>_pipeline.py` plus sibling files for variants whose components or required modules differ. |
|
||||
| Pipeline config | `fastvideo/configs/pipelines/<family>.py` or `fastvideo/pipelines/basic/<family>/pipeline_configs.py`. |
|
||||
| Stages | `fastvideo/pipelines/basic/<family>/stages/` only for model-specific stage subclasses. |
|
||||
| Presets / registry | `fastvideo/pipelines/basic/<family>/presets.py`, `fastvideo/registry.py`. |
|
||||
| Tests | Component parity under `tests/local_tests/<bucket>/`; pipeline smoke/parity under `tests/local_tests/pipelines/`; CI-backed quality tests under `fastvideo/tests/`. |
|
||||
| Example | `examples/inference/basic/basic_<family>*.py`, one per public mode/variant. |
|
||||
|
||||
## Phase 0: Scope And Handoff Gate
|
||||
|
||||
1. Validate every required handoff field.
|
||||
2. Resolve `needs_conversion=unknown` before component work:
|
||||
|
||||
```bash
|
||||
python ".agents/skills/add-model-01-prep/scripts/inspect_hf_layout.py" \
|
||||
"<hf-or-local-path>" \
|
||||
--json
|
||||
```
|
||||
|
||||
3. List first-PR scope across both axes:
|
||||
- Variant axis: base, distill, SR/refine, causal, DMD, I2V, V2V, etc.
|
||||
- Modality axis: video, image, audio, pose, depth, masks, text, etc.
|
||||
4. For component-only work, explicitly name the downstream full-pipeline PR or
|
||||
planned consumer.
|
||||
5. Confirm `official_env_status` is `imports_ok` or
|
||||
`private_deps_need_stubs`. If it is `blocked`, return to
|
||||
`../add-model-01-prep/SKILL.md` before parity scaffolding.
|
||||
6. Confirm `local_tests_readme` exists and records official setup, HF weights,
|
||||
dependency changes, and planned parity commands for reviewers.
|
||||
7. Confirm `port_state_file` exists, follows `contracts/port_state.md`, and has
|
||||
rows for open questions/issues found during prep.
|
||||
8. If there are multiple official implementations, choose the one whose
|
||||
architecture matches the published weights. A blessed library port can be a
|
||||
better parity reference than a highly configurable research repo; document
|
||||
the choice in tests.
|
||||
|
||||
## Phase 1: Reference And Architecture Study
|
||||
|
||||
Read the official pipeline call path before writing code.
|
||||
|
||||
Record:
|
||||
|
||||
- Required modules from `model_index.json` or equivalent: transformer, VAE,
|
||||
text encoders, tokenizers, scheduler, image encoders, audio VAE, vocoder,
|
||||
conditioners, upsamplers.
|
||||
- Input/output modalities and every dedicated DiT output head.
|
||||
- Text/image/audio encoding flow, latent shape, dtype, scaling, packing,
|
||||
scheduler/timestep math, guidance math, VAE normalization, and decode flow.
|
||||
- Whether the official code relies on private deps, custom ops, or special
|
||||
kernels that parity tests must stub.
|
||||
|
||||
Arch config rule:
|
||||
|
||||
- `ArchConfig` fields must match the emitted per-component config, especially
|
||||
`transformer/config.json`, one-to-one.
|
||||
- Pipeline knobs do not belong on the DiT arch config: inference steps, CFG
|
||||
scales, flow shift, FPS, VAE stride, text target length, data-proxy knobs,
|
||||
eval defaults, and sampling defaults go on `PipelineConfig`, presets, or
|
||||
stages.
|
||||
- If the HF repo is raw or has empty configs, synthesize
|
||||
`transformer/config.json` from the official Python model-config class, not
|
||||
from data/eval config classes.
|
||||
|
||||
## Phase 2: Early Parity Scaffolding
|
||||
|
||||
Create component parity tests before or alongside implementation. Use
|
||||
`../add-model-02-parity/SKILL.md` and its `templates/component_parity_test.py`.
|
||||
The official reference must import in the current FastVideo environment, or the
|
||||
prep handoff must identify private deps that will be stubbed locally for tests.
|
||||
Use `local_tests_readme` as the reviewer-facing source for setup commands and
|
||||
update its planned test table as parity scaffolds are added.
|
||||
|
||||
This phase is early by design:
|
||||
|
||||
- Official loading can be implemented from the reference study.
|
||||
- FastVideo loading can target planned standardized class/config/loader paths.
|
||||
- Tests may initially skip because the FastVideo class or converted weights do
|
||||
not exist yet.
|
||||
- The scaffold must still contain real official loading, deterministic inputs,
|
||||
output extraction, and concrete tensor comparisons. No unconditional skips,
|
||||
no shape-only tests.
|
||||
|
||||
Use subagents here: dispatch one parity-test subagent per required component,
|
||||
including components that may be reused. Their output becomes the red/skip
|
||||
target that porting or reuse-verification subagents make pass later.
|
||||
|
||||
## Phase 3: Reuse Gate And Component Dispatch
|
||||
|
||||
Build a component inventory before implementation:
|
||||
|
||||
| Field | Meaning |
|
||||
|---|---|
|
||||
| Component | transformer, VAE, text encoder, image encoder, scheduler, conditioner, upsampler, vocoder, etc. |
|
||||
| Official definition | Repo-relative source file, class/function name, and relevant line/range if known. |
|
||||
| Official instantiation | Repo-relative pipeline/config/factory call site plus constructor args and runtime flags. |
|
||||
| FastVideo target | Existing class to reuse or new bucket/file/config to add. |
|
||||
| Parity test | Required local test path, including reused components. |
|
||||
| Status | `reuse_pending`, `reuse_proven`, `port_pending`, `non_skip_pass`, or `blocked`. |
|
||||
|
||||
Reuse is allowed only from the checked-out FastVideo tree. Do not wait for or
|
||||
depend on an open PR adding a native class; add the native port directly in this
|
||||
PR if the current tree cannot be reused.
|
||||
|
||||
Reuse decision:
|
||||
|
||||
1. Record exact official definition and instantiation evidence for every
|
||||
component.
|
||||
2. If an existing FastVideo class and config match both definition and
|
||||
instantiation, pass that reused target to the bucket-specific skill in
|
||||
`mode=prototype` and require reuse evidence plus key/shape dumps.
|
||||
3. If either definition or instantiation differs, port the component directly as
|
||||
FastVideo-native code through the bucket-specific skill.
|
||||
4. Reused components still require non-skip component parity against the exact
|
||||
official instantiation used by the target pipeline.
|
||||
|
||||
Porting subagent dispatch:
|
||||
|
||||
- Dispatch one subagent per component after Phase 2 parity scaffolds exist.
|
||||
- Use `../add-model-03-port-dit/SKILL.md` for DiTs/transformers.
|
||||
- Use `../add-model-04-port-vae/SKILL.md` for VAEs.
|
||||
- Use `../add-model-05-port-encoder/SKILL.md` for text, image, audio, or compound
|
||||
encoders/conditioners that fit the encoder config bucket.
|
||||
- Use `../add-model-06-port-generic/SKILL.md` for schedulers, upsamplers,
|
||||
vocoders, adapters, preprocessors, or unknown components.
|
||||
- Each subagent owns one component only and must loop on that component's local
|
||||
parity test until it produces a non-skip PASS or returns a precise blocker.
|
||||
|
||||
Every component subagent must receive a complete packet matching
|
||||
`contracts/component_context.md`. If any required path is unknown, pass `unknown`
|
||||
plus the exact search already performed. Do not silently omit ambiguous official
|
||||
files or prototype concerns.
|
||||
|
||||
Bucket, layer, and attention rules live in the bucket-specific skills and
|
||||
`fastvideo/layers/AGENTS.md`.
|
||||
|
||||
## Phase 4: Native Component Prototype
|
||||
|
||||
Conversion needs a FastVideo state-dict surface. Use the Phase 3
|
||||
bucket-specific skill in `mode=prototype` for every required component, including
|
||||
reused components.
|
||||
|
||||
Prototype success criteria:
|
||||
|
||||
- the FastVideo-native or reused class/config can import and instantiate with the
|
||||
exact official architecture args;
|
||||
- official and FastVideo key/shape dumps exist for every stateful component;
|
||||
- `local_tests_readme` and `port_state_file` record prototype status and concerns;
|
||||
- the returned handoff matches `contracts/component_skill_handoff.md`.
|
||||
|
||||
Do not chase numerical parity in Phase 4. Prototype mode ends when conversion has
|
||||
the key/shape surface it needs, or when the component skill returns a precise
|
||||
blocker or escape hatch.
|
||||
|
||||
## Phase 5: Param Mapping And Weight Conversion
|
||||
|
||||
Use `../add-model-07-conversion/SKILL.md` after Phase 4 prototypes exist.
|
||||
Send a request matching `contracts/conversion_request.md`; consume the returned
|
||||
`contracts/conversion_handoff.md` update before Phase 6.
|
||||
|
||||
Use the prep handoff's `needs_conversion` value:
|
||||
|
||||
- `no`: verify the source already has the component layout FastVideo loaders can
|
||||
consume, then record any passthrough components.
|
||||
- `yes`: write `scripts/checkpoint_conversion/<family>_to_diffusers.py` and
|
||||
output `converted_weights/<family>/`.
|
||||
- `unknown`: return to Phase 0.
|
||||
|
||||
The conversion skill owns source-layout handling, mapping derivation, config and
|
||||
`model_index.json` emission, passthrough assets, strict-load verification, and
|
||||
Phase 6 retry requests. Component skills must not patch conversion scripts or
|
||||
converted weights ad hoc.
|
||||
|
||||
## Phase 6: Component Parity Debug
|
||||
|
||||
This is the expected expensive loop. Dispatch one subagent per required
|
||||
component, including reused components, using the bucket-specific skill in
|
||||
`mode=parity-debug`.
|
||||
|
||||
Each subagent gets:
|
||||
|
||||
- the complete component context packet from Phase 3/4;
|
||||
- updated conversion mapping notes and strict-load result from Phase 5;
|
||||
- any prototype concerns or unknowns that were not resolved before conversion.
|
||||
|
||||
The bucket-specific skills own parity-debug tactics. If a failure belongs to
|
||||
conversion, route it through `../add-model-07-conversion/SKILL.md` with a retry
|
||||
request matching `contracts/conversion_request.md`, then resume the component
|
||||
skill with the updated conversion handoff.
|
||||
|
||||
When a component failure narrows to layer-by-layer numerical drift, load
|
||||
`../add-model-08-trace/SKILL.md` before writing custom hooks. It uses
|
||||
`fastvideo/hooks/activation_trace.py`; canonical env vars and JSONL format are
|
||||
documented in `docs/contributing/activation_trace.md`.
|
||||
|
||||
Phase 6 ends only when every required component handoff reports
|
||||
`parity_status=non_skip_pass`, or when a precise blocker or escape hatch is
|
||||
recorded in `port_state_file`.
|
||||
|
||||
## Phase 7: Pipeline, Stages, And Variants
|
||||
|
||||
Do not start Phase 7 until every required component, reused or ported, has a
|
||||
non-skip local parity PASS from Phase 6. If any component parity test is still
|
||||
`scaffold_skip`, `debug_red`, `blocked`, or missing, resume Phase 6 first.
|
||||
|
||||
Use `../add-model-09-pipeline/SKILL.md` for pipeline definition and parity-debug.
|
||||
Send a complete packet matching `contracts/pipeline_context.md`; consume the
|
||||
returned `contracts/pipeline_handoff.md` before moving to quality regression or
|
||||
final handoff.
|
||||
|
||||
The pipeline skill owns:
|
||||
|
||||
- pipeline class, stage chain, and optional model-specific stages;
|
||||
- pipeline config, presets, registry updates, and examples;
|
||||
- official args/defaults/presets comparison before setting FastVideo defaults;
|
||||
- pipeline smoke and parity tests;
|
||||
- continuous pipeline parity-debug until non-skip PASS or precise blocker;
|
||||
- updates to `local_tests_readme` and `port_state_file`.
|
||||
|
||||
The pipeline handoff must explicitly cover stage order, variants, modality and
|
||||
output-head handling, config/preset/registry/example status, smoke/parity tests,
|
||||
and any return-to-Phase-6 evidence.
|
||||
|
||||
## Phase 8: PipelineConfig, Presets, Registry, Examples
|
||||
|
||||
This phase is implemented through `../add-model-09-pipeline/SKILL.md` after the
|
||||
Phase 7 component-parity gate passes. Accept the pipeline handoff only if it
|
||||
covers configs, presets, registry detection/exact class resolution, examples,
|
||||
new `SamplingParam` fields for public kwargs/defaults, and local smoke/parity
|
||||
status. Detailed rules live in `../add-model-09-pipeline/SKILL.md`.
|
||||
|
||||
## Phase 9: Parity Activation And Local Verification
|
||||
|
||||
Local parity is author-run, not CI-enforced. CI may only run package-level
|
||||
quality tests later. Before handoff, Phase 2 scaffolds must be activated into
|
||||
non-skip PASS results.
|
||||
|
||||
Order is mandatory:
|
||||
|
||||
1. Run conversion if needed.
|
||||
2. Run component parity for every required component, including reused ones.
|
||||
3. Run pipeline smoke.
|
||||
4. Run pipeline parity.
|
||||
5. Run the basic example.
|
||||
|
||||
If pipeline smoke or parity points back to component implementation,
|
||||
strict-load, or conversion mapping, return to Phase 6 or Phase 5 rather than
|
||||
patching around the issue in the pipeline.
|
||||
|
||||
Skip policy:
|
||||
|
||||
- Follow `shared/common_rules.md`: a committed local test may skip for absent
|
||||
clones/weights, but a local skip is not a verified pass.
|
||||
|
||||
Use the commands and tolerance guidance from `../add-model-02-parity/SKILL.md` for
|
||||
component checks and from `../add-model-09-pipeline/SKILL.md` for pipeline smoke,
|
||||
pipeline parity, and examples. Record exact commands, status, and blockers in
|
||||
`local_tests_readme` and `port_state_file`.
|
||||
|
||||
## Phase 10: Quality Regression
|
||||
|
||||
Video outputs:
|
||||
|
||||
- Add `fastvideo/tests/ssim/test_<family>_similarity.py` when output video
|
||||
quality must be preserved.
|
||||
- Seed references through `seed-ssim-references` after the test exists.
|
||||
|
||||
Audio outputs:
|
||||
|
||||
- SSIM does not apply. Use an audio-specific regression metric such as
|
||||
mel-spectrogram L1, multi-resolution STFT, CLAP cosine, or a project-approved
|
||||
learned metric.
|
||||
- Document the metric and hardware/runtime assumptions in the test.
|
||||
|
||||
Joint AV outputs:
|
||||
|
||||
- Keep video and audio regression checks separate unless there is a validated
|
||||
joint metric.
|
||||
|
||||
## Phase 11: Post-Parity Review And Handoff
|
||||
|
||||
After parity is green, run a hot-path review before handoff:
|
||||
|
||||
- Hoist constant tensor allocations out of sampler/denoising loops.
|
||||
- Replace per-step `randn_like` churn with preallocated buffers plus
|
||||
`.normal_()` when safe.
|
||||
- Move `torch.backends.*` flag changes to one-shot setup/load paths.
|
||||
- Delete `batch.extra` writes that nothing reads.
|
||||
- Derive magic constants from configs when possible.
|
||||
|
||||
Pre-handoff checklist:
|
||||
|
||||
```text
|
||||
[ ] Prep handoff is complete and committed nowhere with token values.
|
||||
[ ] Conversion was run if needed and output loads with real weights.
|
||||
[ ] Every required component, reused or newly ported, has a non-skip local parity PASS.
|
||||
[ ] `local_tests_readme` lists every component parity test, command, status, and blocker if any.
|
||||
[ ] `port_state_file` has every open question/issue either resolved or listed as an explicit blocker.
|
||||
[ ] Any `next_step=ask_user` has a matching `escape_hatch` block and `E###` row.
|
||||
[ ] Pipeline smoke has a non-skip local PASS.
|
||||
[ ] Pipeline parity has a non-skip local PASS against the official reference.
|
||||
[ ] Basic example runs and writes a non-corrupt output.
|
||||
[ ] Video SSIM or audio-specific quality regression is added or explicitly deferred.
|
||||
[ ] Runtime production code has no diffusers/transformers model-class imports.
|
||||
[ ] Production comments are WHY-focused; examples have user-story docstrings.
|
||||
[ ] Post-parity hot-path pass is complete.
|
||||
```
|
||||
|
||||
Ask before deleting any reference clone or staged weights created by
|
||||
`add-model-01-prep`. Leave `.gitignore` entries so future parity assets stay
|
||||
untracked. Never commit the clone, weights, `.env`, credentials, or anything
|
||||
matching `*secret*`.
|
||||
|
||||
## References
|
||||
|
||||
- `../add-model-01-prep/SKILL.md` for user-input collection, HF inspection,
|
||||
weight staging, reference cloning, and setup handoff.
|
||||
- `contracts/` for canonical handoff schemas used by prep, parity, conversion,
|
||||
component porting, escape hatches, and final handoff.
|
||||
- `../add-model-02-parity/SKILL.md` for early component parity scaffolds and
|
||||
activation templates.
|
||||
- `../add-model-07-conversion/SKILL.md` for Phase 5 mapping, conversion scripts,
|
||||
monolithic checkpoint splitting, and strict-load checks.
|
||||
- `../add-model-03-port-dit/SKILL.md`, `../add-model-04-port-vae/SKILL.md`,
|
||||
`../add-model-05-port-encoder/SKILL.md`, and
|
||||
`../add-model-06-port-generic/SKILL.md` for component subagent implementation
|
||||
and parity-debug loops.
|
||||
- `../add-model-09-pipeline/SKILL.md` for pipeline definition, config/preset/
|
||||
registry/example wiring, smoke tests, and pipeline parity-debug.
|
||||
- `fastvideo/layers/AGENTS.md` for native layer selection and state-dict surface
|
||||
guidance.
|
||||
- `docs/contributing/coding_agents.md` for narrative context.
|
||||
- `docs/design/overview.md` for pipeline/config/registry architecture.
|
||||
- `fastvideo/pipelines/basic/wan/` for standard T2V/I2V/DMD/Causal variants.
|
||||
- `fastvideo/pipelines/basic/ltx2/` for non-standard stages and audio/video
|
||||
patterns.
|
||||
- `tests/local_tests/pipelines/test_gamecraft_pipeline_parity.py` for pipeline
|
||||
parity shape.
|
||||
- `tests/local_tests/transformers/test_ltx2.py`,
|
||||
`tests/local_tests/vaes/test_ltx2_vae.py`, and
|
||||
`tests/local_tests/encoders/test_ltx2_gemma_parity.py` for component parity.
|
||||
- `scripts/checkpoint_conversion/convert_ltx2_weights.py` for modern conversion
|
||||
script shape.
|
||||
- `scripts/checkpoint_conversion/wan_to_diffusers.py` for legacy regex mapping
|
||||
reference only.
|
||||
|
||||
## Changelog
|
||||
|
||||
| Date | Change |
|
||||
|---|---|
|
||||
| 2026-04-24 | Initial FastVideo add-model workflow. |
|
||||
| 2026-04-30 | Split external setup into `add-model-01-prep`. |
|
||||
| 2026-04-30 | Rewrote as manual `/add-model` phase workflow and incorporated prior review decisions. |
|
||||
| 2026-04-30 | Extracted early parity scaffolding into `add-model-02-parity` and moved it before conversion/component implementation. |
|
||||
| 2026-04-30 | Added component reuse proof gate, bucket-specific porting skills, and parity PASS requirement for reused components. |
|
||||
| 2026-04-30 | Split prototype, conversion, and parity-debug phases; added conversion skill for monolithic and separate checkpoint layouts. |
|
||||
| 2026-04-30 | Extracted handoff schemas into `contracts/` for shared use across skills. |
|
||||
| 2026-04-30 | Added pipeline skill contract and Phase 7 component-parity gate. |
|
||||
| 2026-04-30 | Added escape-hatch contract for user decisions and `ask_user` handoffs. |
|
||||
@@ -0,0 +1,29 @@
|
||||
# Add Model Contracts
|
||||
|
||||
Canonical handoff schemas for the `/add-model` workflow. When a skill needs to
|
||||
send or receive structured context, use these files instead of inventing a local
|
||||
schema.
|
||||
|
||||
| Contract | Use |
|
||||
|---|---|
|
||||
| `prep_handoff.md` | `add-model-01-prep` output and `/add-model` Phase 0 input. |
|
||||
| `port_state.md` | Per-port `PORT_STATUS.md` file tracking progress, open questions, and issues. |
|
||||
| `escape_hatch.md` | Shared pause-and-ask schema for user decisions the workflow cannot safely choose. |
|
||||
| `component_context.md` | Per-component packet passed to parity, prototype, conversion, and parity-debug subagents. |
|
||||
| `parity_status.md` | `add-model-02-parity` scaffold/activation status returned to `/add-model`. |
|
||||
| `conversion_request.md` | Phase 5 conversion input and Phase 6 conversion retry request. |
|
||||
| `conversion_handoff.md` | `add-model-07-conversion` output back to `/add-model` and component subagents. |
|
||||
| `component_skill_handoff.md` | Component porting skill output in prototype or parity-debug mode. |
|
||||
| `pipeline_context.md` | Phase 7 packet passed to `add-model-09-pipeline` after component parity is green. |
|
||||
| `pipeline_handoff.md` | `add-model-09-pipeline` output back to `/add-model` after pipeline definition or parity-debug. |
|
||||
| `final_handoff.md` | Final `/add-model` pre-handoff checklist summary. |
|
||||
|
||||
Rules:
|
||||
|
||||
- Do not omit required fields. Use `unknown` plus the search already performed
|
||||
when the value is not known yet.
|
||||
- Do not include raw token values. Use env var names only.
|
||||
- Keep model-specific mapping details in conversion scripts and the local tests
|
||||
README/status notes, not in generic skill docs.
|
||||
- Use `next_step=ask_user` only with an `escape_hatch` block matching
|
||||
`escape_hatch.md`.
|
||||
@@ -0,0 +1,55 @@
|
||||
# Component Context Contract
|
||||
|
||||
Canonical per-component packet passed from `/add-model` to parity, prototype,
|
||||
conversion, and parity-debug subagents.
|
||||
|
||||
```text
|
||||
component_context:
|
||||
model_family: <snake_case>
|
||||
component: <name>
|
||||
component_type: <dit|vae|encoder|scheduler|conditioner|upsampler|vocoder|generic>
|
||||
mode: parity-scaffold | prototype | parity-debug
|
||||
official_ref_dir: <path or import path>
|
||||
official_definition_files:
|
||||
- path: <repo-relative or absolute path in official repo>
|
||||
symbols: <class/function names>
|
||||
notes: <layer graph, output contract, state-dict owner>
|
||||
official_instantiation_files:
|
||||
- path: <repo-relative or absolute path in official repo>
|
||||
symbols: <factory/pipeline/config names>
|
||||
args: <constructor args, config values, runtime flags>
|
||||
official_weight_source: <checkpoint file, subfolder, prefix, or passthrough source>
|
||||
fastvideo_target_files:
|
||||
- fastvideo/models/<bucket>/<file>.py
|
||||
- fastvideo/configs/models/<bucket>/<file>.py
|
||||
local_tests_readme: tests/local_tests/<model_family>/README.md
|
||||
port_state_file: tests/local_tests/<model_family>/PORT_STATUS.md
|
||||
parity_test: tests/local_tests/<bucket>/test_<family>_<component>_parity.py
|
||||
prototype_key_dumps:
|
||||
official: converted_weights/<family>/_mapping/<component>_official_keys.json | planned | unknown
|
||||
fastvideo: converted_weights/<family>/_mapping/<component>_fastvideo_keys.json | planned | unknown
|
||||
conversion:
|
||||
script: scripts/checkpoint_conversion/<family>_to_diffusers.py | not_created | not_needed | unknown
|
||||
converted_component_dir: converted_weights/<family>/<component> | not_created | not_needed | unknown
|
||||
model_index_library: <diffusers|transformers|fastvideo|fastvideo.*|unknown|none>
|
||||
config_file: <config.json|scheduler_config.json|none|unknown>
|
||||
mapping_notes: <key prefixes, split/fuse concerns, skipped keys, not_created, not_needed, or unknown>
|
||||
production_loader_strictness: <strict|non_strict_with_allowed_keys|stateless|unknown>
|
||||
strict_load: <not_run | pass | pass_with_documented_exclusions | blocked>
|
||||
concerns_or_unknowns:
|
||||
- <prototype mismatch, ambiguous arg, missing op, dtype concern, output head, etc.>
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- If any required path is unknown, pass `unknown` plus the exact search already
|
||||
performed.
|
||||
- Do not silently omit ambiguous official files, instantiation args, or prototype
|
||||
concerns.
|
||||
- For reused components, still fill every field and set `fastvideo_target_files`
|
||||
to the reused class/config.
|
||||
- In `mode=parity-scaffold`, prototype and conversion fields may be `planned`,
|
||||
`not_created`, `not_needed`, or `unknown`; do not invent paths or statuses that
|
||||
do not exist yet.
|
||||
- Update `port_state_file` when concerns, issues, conversion status, or parity
|
||||
status change.
|
||||
@@ -0,0 +1,41 @@
|
||||
# Component Skill Handoff Contract
|
||||
|
||||
Returned by `add-model-03-port-dit`, `add-model-04-port-vae`,
|
||||
`add-model-05-port-encoder`, and `add-model-06-port-generic`.
|
||||
|
||||
```text
|
||||
component: <name>
|
||||
mode: prototype | parity-debug
|
||||
files_changed: <model/config/export/test/readme paths>
|
||||
official_files_used: <definition files, instantiation files>
|
||||
prototype_key_dumps: <official path, fastvideo path, or none>
|
||||
port_state_file: tests/local_tests/<model_family>/PORT_STATUS.md
|
||||
concerns_or_unknowns: <remaining or newly discovered concerns>
|
||||
parity_test: <path>
|
||||
parity_status: scaffold_skip | debug_red | non_skip_pass | blocked
|
||||
production_loader_strictness: strict | non_strict_with_allowed_keys | stateless
|
||||
strict_load: pass | pass_with_documented_exclusions | blocked | not_run
|
||||
pytest_output: <command + short result>
|
||||
blocker: <none or exact missing dependency/weights/numeric mismatch>
|
||||
conversion_retry_request: <none or failing keys/shapes/prefixes/evidence for add-model-07-conversion>
|
||||
readme_updated: yes | no
|
||||
next_step: phase_5_conversion | phase_5_conversion_retry | phase_6_continue | ask_user | blocked
|
||||
escape_hatch: <none or block matching contracts/escape_hatch.md>
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- In `mode=prototype`, parity may be `scaffold_skip` or `blocked`; key dumps are
|
||||
the required artifact. Successful prototype handoff should use
|
||||
`next_step=phase_5_conversion`.
|
||||
- In `mode=parity-debug`, final success requires `parity_status=non_skip_pass`.
|
||||
- If conversion is implicated, return `conversion_retry_request` and do not edit
|
||||
conversion scripts or converted weights directly.
|
||||
- If production loading is non-strict, list allowed missing/unexpected keys in
|
||||
the parity test or handoff and mark `strict_load=pass_with_documented_exclusions`.
|
||||
- Update `port_state_file` before returning: component row, open questions,
|
||||
issues/blockers, decisions, and handoff notes.
|
||||
- Return an `escape_hatch` only for user decisions, not for normal component
|
||||
implementation or parity-debug failures.
|
||||
- Use `next_step=ask_user` only with an `escape_hatch` block and a matching
|
||||
`PORT_STATUS.md` row.
|
||||
@@ -0,0 +1,41 @@
|
||||
# Conversion Handoff Contract
|
||||
|
||||
Returned by `../add-model-07-conversion/SKILL.md` to `/add-model` and component
|
||||
parity-debug subagents.
|
||||
|
||||
```text
|
||||
conversion_script: scripts/checkpoint_conversion/<family>_to_diffusers.py
|
||||
source_layout: <diffusers|raw_official|separate_components|monolithic|mixed|custom>
|
||||
converted_weights_dir: converted_weights/<model_family>
|
||||
port_state_file: tests/local_tests/<model_family>/PORT_STATUS.md
|
||||
components_written: <list>
|
||||
passthrough_components: <list>
|
||||
strict_load: pass | pass_with_documented_exclusions | blocked
|
||||
component_context_updates:
|
||||
- component: <name>
|
||||
converted_component_dir: <path>
|
||||
model_index_library: <diffusers|transformers|fastvideo|fastvideo.*>
|
||||
config_file: <path or none>
|
||||
config_validation: pass | blocked | not_applicable
|
||||
mapping_notes: <prefixes, split/fuse ops, skipped keys>
|
||||
production_loader_strictness: strict | non_strict_with_allowed_keys | stateless
|
||||
strict_load: pass | pass_with_documented_exclusions | blocked | not_run
|
||||
retry_resolved: <yes | no | not_a_retry>
|
||||
concerns_or_unknowns: <remaining list>
|
||||
blocked_on: <none or exact blocker>
|
||||
next_step: phase_6_component_parity_debug | ask_user
|
||||
escape_hatch: <none or block matching contracts/escape_hatch.md>
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Include strict-load evidence for every stateful converted component.
|
||||
- If a component intentionally loads non-strictly, list the exact missing or
|
||||
unexpected keys and why they are safe.
|
||||
- Include the actual `model_index.json` library token, config filename, and config
|
||||
validation result for every emitted component.
|
||||
- Preserve retry evidence so the requesting component subagent can resume with
|
||||
updated context.
|
||||
- Keep `port_state_file` synchronized with `component_context_updates`.
|
||||
- Use `next_step=ask_user` only with an `escape_hatch` block and a matching
|
||||
`PORT_STATUS.md` row.
|
||||
@@ -0,0 +1,54 @@
|
||||
# Conversion Request Contract
|
||||
|
||||
Consumed by `../add-model-07-conversion/SKILL.md` in Phase 5 and during Phase 6
|
||||
conversion retries.
|
||||
|
||||
Initial conversion request:
|
||||
|
||||
```text
|
||||
model_family: <snake_case>
|
||||
source_layout: diffusers | raw_official | monolithic | separate_components | mixed | custom
|
||||
official_weights: <HF repo, local dir, or checkpoint file>
|
||||
hf_revision: <revision | default | none>
|
||||
converted_weights_dir: converted_weights/<model_family>
|
||||
local_tests_readme: tests/local_tests/<model_family>/README.md
|
||||
port_state_file: tests/local_tests/<model_family>/PORT_STATUS.md
|
||||
components:
|
||||
- name: <transformer|vae|text_encoder|conditioner|scheduler|...>
|
||||
component_type: <dit|vae|encoder|scheduler|conditioner|upsampler|vocoder|generic>
|
||||
official_definition_files: <paths + symbols>
|
||||
official_instantiation_files: <paths + call sites + args>
|
||||
official_weight_source: <checkpoint file, prefix, subfolder, or passthrough source>
|
||||
official_keys: <path to official key/shape dump>
|
||||
fastvideo_keys: <path to FastVideo prototype key/shape dump>
|
||||
fastvideo_class: <class name>
|
||||
model_index_library: <diffusers|transformers|fastvideo|fastvideo.*>
|
||||
config_filename: <config.json|scheduler_config.json|none>
|
||||
production_loader_strictness: <strict|non_strict_with_allowed_keys|stateless>
|
||||
source_prefix_or_path: <prefix or path>
|
||||
parity_test: <component parity test path>
|
||||
prototype_concerns_or_unknowns: <short list>
|
||||
```
|
||||
|
||||
Retry request from a component skill:
|
||||
|
||||
```text
|
||||
conversion_retry_request:
|
||||
component: <name>
|
||||
parity_test: <path>
|
||||
failing_keys: <official and FastVideo keys, if known>
|
||||
expected_actual_shapes: <expected vs actual shapes, if known>
|
||||
source_prefix_or_path: <prefix/path implicated by the failure>
|
||||
evidence: <strict-load error, first divergent tensor, parity log excerpt>
|
||||
suspected_fix: <rename | split | fuse | skip | component bucket | config | unknown>
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Phase 5 conversion requires Phase 4 official/FastVideo key dumps.
|
||||
- Component skills must use the retry request instead of editing conversion
|
||||
scripts or converted weights directly.
|
||||
- Conversion must update `port_state_file` with conversion status, retry history,
|
||||
strict-load status, new issues, and resolved issues.
|
||||
- Conversion must validate emitted config keys through the production config
|
||||
update path and record the config filename expected by each loader.
|
||||
@@ -0,0 +1,51 @@
|
||||
# Escape Hatch Contract
|
||||
|
||||
Canonical pause-and-ask schema for `/add-model` skills. Use this when the next
|
||||
action requires user input instead of autonomous debugging.
|
||||
|
||||
```text
|
||||
escape_hatch:
|
||||
needs_user_input: yes | no
|
||||
decision_type: scope | dependency | auth | cost | destructive | ambiguity | blocker
|
||||
question: <one precise question>
|
||||
recommended_option: <safe recommended choice>
|
||||
options:
|
||||
- <option + consequence>
|
||||
safe_default: <what the agent will do after approval, or none>
|
||||
blocked_until_answered: yes | no
|
||||
state_snapshot:
|
||||
phase: <phase or skill mode>
|
||||
files_changed:
|
||||
- <paths>
|
||||
command_or_test: <last relevant command, or not_run>
|
||||
evidence: <short logs, paths, error text, or blocker ID>
|
||||
```
|
||||
|
||||
Use `needs_user_input=no` when the handoff is green or the next step is already
|
||||
specified by the workflow.
|
||||
|
||||
Ask the user only for decisions the workflow cannot safely choose:
|
||||
|
||||
- product or PR scope changes, including dropping a modality, output head, or
|
||||
variant;
|
||||
- core dependency changes, version pin changes, or installing untrusted/private
|
||||
dependencies;
|
||||
- auth setup for gated repos, using env var names only and never token values;
|
||||
- large downloads, publishing weights, SSIM/reference uploads, or GPU-heavy work
|
||||
where cost/runtime approval is needed;
|
||||
- destructive file/git operations, overwriting existing clones/weights, or
|
||||
deleting staged assets;
|
||||
- ambiguous official sources of truth with incompatible behavior;
|
||||
- accepting a known blocker, loosening parity/quality tolerances, or shipping
|
||||
without required non-skip parity.
|
||||
|
||||
Do not ask for normal recoverable failures:
|
||||
|
||||
- missing imports, missing local paths, skipped tests, failing parity, conversion
|
||||
mapping errors, strict-load failures, format/lint failures, or implementation
|
||||
bugs covered by the skill workflow.
|
||||
|
||||
Before returning `next_step=ask_user`, update
|
||||
`tests/local_tests/<model_family>/PORT_STATUS.md` with the blocker/question ID,
|
||||
include the exact evidence, and provide one recommended option plus at most three
|
||||
alternatives.
|
||||
@@ -0,0 +1,38 @@
|
||||
# Final Handoff Contract
|
||||
|
||||
Completed by `/add-model` before handing work back to the user or opening a PR.
|
||||
|
||||
```text
|
||||
final_handoff:
|
||||
prep_handoff_complete: yes | no
|
||||
conversion_status: not_needed | pass | blocked
|
||||
components:
|
||||
- name: <component>
|
||||
reuse_or_port: reused | ported
|
||||
parity_test: <path>
|
||||
parity_status: non_skip_pass | blocked
|
||||
concerns_or_unknowns: <none or list>
|
||||
pipeline_smoke: pass | blocked | not_run
|
||||
pipeline_parity: pass | blocked | not_run
|
||||
example_status: pass | blocked | not_run
|
||||
quality_regression: added | deferred_with_reason | not_applicable
|
||||
local_tests_readme: tests/local_tests/<model_family>/README.md
|
||||
port_state_file: tests/local_tests/<model_family>/PORT_STATUS.md
|
||||
token_values_committed: no
|
||||
runtime_third_party_model_imports: none | listed_with_rationale
|
||||
blockers: <none or list>
|
||||
escape_hatch: <none or block matching contracts/escape_hatch.md>
|
||||
```
|
||||
|
||||
Required before handoff:
|
||||
|
||||
- Every required component, reused or ported, has non-skip local parity PASS.
|
||||
- Pipeline smoke and pipeline parity are non-skip PASS, or a blocker is explicit.
|
||||
- Basic example runs and writes a non-corrupt output.
|
||||
- `local_tests_readme` lists every component parity command/status/blocker.
|
||||
- `port_state_file` has no unresolved blocker that is omitted from the final
|
||||
response or PR notes.
|
||||
- No raw HF token values, credentials, `.env`, reference clone, or staged weight
|
||||
blobs are committed.
|
||||
- If final handoff is blocked on user input, include an `escape_hatch` block and
|
||||
matching `PORT_STATUS.md` row.
|
||||
@@ -0,0 +1,44 @@
|
||||
# Parity Status Contract
|
||||
|
||||
Returned by `../add-model-02-parity/SKILL.md` to `/add-model` and later updated by
|
||||
component parity-debug subagents.
|
||||
|
||||
```text
|
||||
component_parity:
|
||||
- component: <name>
|
||||
test: tests/local_tests/<bucket>/test_<family>_<component>_parity.py
|
||||
status: scaffold_skip | debug_red | non_skip_pass | blocked
|
||||
missing: <none | fastvideo_class | converted_weights | official_import | ...>
|
||||
coverage_scope: production_loader | implementation_subcomponent | both
|
||||
official_definition_files: <paths>
|
||||
official_instantiation_files: <paths>
|
||||
concerns_or_unknowns: <short list>
|
||||
pipeline_parity:
|
||||
test: <path or not-created>
|
||||
status: not_started | scaffold_skip | debug_red | non_skip_pass | blocked
|
||||
local_tests_readme: tests/local_tests/<model_family>/README.md
|
||||
port_state_file: tests/local_tests/<model_family>/PORT_STATUS.md
|
||||
notes: <short list>
|
||||
escape_hatch: <none or block matching contracts/escape_hatch.md>
|
||||
```
|
||||
|
||||
Status meanings:
|
||||
|
||||
- `scaffold_skip`: test is present but skips for a specific missing dependency,
|
||||
FastVideo class, or weights.
|
||||
- `debug_red`: both sides load and the test fails numerically.
|
||||
- `non_skip_pass`: required before final handoff for every required component,
|
||||
including reused components.
|
||||
- `blocked`: a precise missing dependency, weight, official call path, or
|
||||
component/conversion regression prevents local activation.
|
||||
|
||||
Coverage meanings:
|
||||
|
||||
- `production_loader`: FastVideo side loads through the same loader/path used by
|
||||
a pipeline.
|
||||
- `implementation_subcomponent`: FastVideo side constructs classes or remaps
|
||||
tensors directly to isolate implementation behavior.
|
||||
- `both`: the test covers both production loading and implementation behavior.
|
||||
|
||||
Use `escape_hatch` only when blocked status requires a user decision. Normal
|
||||
skips or red parity should be debugged by the workflow without asking.
|
||||
@@ -0,0 +1,82 @@
|
||||
# Pipeline Context Contract
|
||||
|
||||
Canonical packet passed from `/add-model` to `add-model-09-pipeline` for pipeline
|
||||
definition and pipeline parity-debug work.
|
||||
|
||||
```text
|
||||
pipeline_context:
|
||||
model_family: <snake_case>
|
||||
mode: pipeline-definition | pipeline-parity-debug
|
||||
workload_types:
|
||||
- <T2V|I2V|V2V|T2I|compatibility-shim-with-rationale>
|
||||
modalities:
|
||||
inputs: <text/image/video/audio/pose/depth/mask/etc.>
|
||||
outputs: <video/image/audio/joint-av/latents/etc.>
|
||||
official_ref_dir: <path or import path>
|
||||
official_pipeline_files:
|
||||
- path: <repo-relative or absolute path in official repo>
|
||||
symbols: <pipeline/factory/sample functions>
|
||||
notes: <stage order, mutable state, output contract>
|
||||
official_call:
|
||||
command_or_api: <official CLI, Python call, or package entrypoint>
|
||||
args_and_defaults: <height, width, frames, fps, duration, steps, CFG, scheduler, seeds, etc.>
|
||||
preset_source: <model card, config file, official script, or unknown>
|
||||
scheduler_and_rng: <timestep/sigma/noise/generator behavior>
|
||||
output_contract: <decoded media, denoised latents, waveform, dict keys, etc.>
|
||||
model_index:
|
||||
class_name: <FastVideo pipeline class name to emit in model_index.json>
|
||||
entry_class_names: <registered EntryClass.__name__ values that must include class_name>
|
||||
required_modules: <text_encoder, tokenizer, vae, transformer, scheduler, etc.>
|
||||
passthrough_modules: <tokenizer, scheduler, processor, external HF dirs, or none>
|
||||
sampling_param:
|
||||
new_fields: <none or list of public kwargs/preset defaults to add to SamplingParam>
|
||||
cli_fields: <none or list of fields that need CLI args>
|
||||
placeholder_fields: <none or video-shaped compatibility placeholders with rationale>
|
||||
components:
|
||||
- name: <component>
|
||||
component_type: <dit|vae|encoder|scheduler|conditioner|upsampler|vocoder|generic>
|
||||
parity_test: tests/local_tests/<bucket>/test_<family>_<component>_parity.py
|
||||
parity_status: non_skip_pass
|
||||
fastvideo_target_files: <model/config/export files>
|
||||
converted_component_dir: converted_weights/<family>/<component>
|
||||
conversion:
|
||||
converted_weights_dir: converted_weights/<family>
|
||||
source_layout: <diffusers|raw_official|monolithic|separate_components|mixed|custom>
|
||||
model_index_path: converted_weights/<family>/model_index.json
|
||||
fastvideo_targets:
|
||||
pipeline_files:
|
||||
- fastvideo/pipelines/basic/<family>/<family>_pipeline.py
|
||||
stage_files:
|
||||
- fastvideo/pipelines/basic/<family>/stages/<stage>.py
|
||||
pipeline_config_files:
|
||||
- fastvideo/configs/pipelines/<family>.py
|
||||
preset_file: fastvideo/pipelines/basic/<family>/presets.py
|
||||
registry_file: fastvideo/registry.py
|
||||
example_files:
|
||||
- examples/inference/basic/basic_<family>.py
|
||||
smoke_test: tests/local_tests/pipelines/test_<family>_pipeline_smoke.py
|
||||
parity_test: tests/local_tests/pipelines/test_<family>_pipeline_parity.py
|
||||
local_tests_readme: tests/local_tests/<model_family>/README.md
|
||||
port_state_file: tests/local_tests/<model_family>/PORT_STATUS.md
|
||||
concerns_or_unknowns:
|
||||
- <pipeline branch, unsupported workload, output head, preset ambiguity, etc.>
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Start only after every required component, reused or ported, has
|
||||
`parity_status=non_skip_pass`. If any row is missing or skipped, return to
|
||||
`/add-model` Phase 6.
|
||||
- Record official call arguments and default sources before writing FastVideo
|
||||
presets. Do not invent inference defaults from memory.
|
||||
- `model_index.class_name` must match a registered pipeline `EntryClass.__name__`;
|
||||
registry detectors are not sufficient for executable pipeline resolution.
|
||||
- Every public generation kwarg or preset default must be represented in
|
||||
`SamplingParam`, or documented as an intentional internal-only field.
|
||||
- `T2A`, `A2A`, and `AV` may be used only after `WorkloadType` supports them;
|
||||
otherwise record the compatibility shim and rationale explicitly.
|
||||
- Keep token values out of the packet. Use only token environment variable names.
|
||||
- If a target path is unknown, use `unknown` plus the exact search already
|
||||
performed.
|
||||
- Update `local_tests_readme` and `port_state_file` whenever pipeline smoke,
|
||||
parity, presets, registry, examples, or blockers change.
|
||||
@@ -0,0 +1,71 @@
|
||||
# Pipeline Handoff Contract
|
||||
|
||||
Returned by `add-model-09-pipeline` to `/add-model` after pipeline definition or
|
||||
pipeline parity-debug work.
|
||||
|
||||
```text
|
||||
pipeline_handoff:
|
||||
model_family: <snake_case>
|
||||
mode: pipeline-definition | pipeline-parity-debug
|
||||
files_changed:
|
||||
- <pipeline/config/preset/registry/stage/example/test/readme/status paths>
|
||||
official_files_used:
|
||||
- <definition/call/default source paths>
|
||||
required_config_modules:
|
||||
emitted: <list from pipeline class>
|
||||
model_index: <list from converted or source model_index.json>
|
||||
status: match | mismatch | blocked
|
||||
pipeline_class_resolution:
|
||||
model_index_class_name: <_class_name>
|
||||
entry_class_names: <registered EntryClass.__name__ values>
|
||||
status: exact_match | alias_added | blocked
|
||||
sampling_param:
|
||||
fields_added: <none or list>
|
||||
cli_fields_added: <none or list>
|
||||
unknown_kwargs_checked: yes | no | blocked
|
||||
stage_chain:
|
||||
- <stage names in execution order>
|
||||
pipeline_config:
|
||||
file: <path>
|
||||
classes: <class names>
|
||||
official_defaults_checked: yes | no | blocked
|
||||
presets:
|
||||
file: <path>
|
||||
names: <preset names>
|
||||
status: pass | blocked | not_run
|
||||
registry:
|
||||
status: pass | blocked | not_run
|
||||
detectors: <HF paths and model_index _class_name strings covered>
|
||||
smoke_test:
|
||||
path: tests/local_tests/pipelines/test_<family>_pipeline_smoke.py
|
||||
status: non_skip_pass | blocked | not_run
|
||||
pytest_output: <command + short result>
|
||||
pipeline_parity:
|
||||
path: tests/local_tests/pipelines/test_<family>_pipeline_parity.py
|
||||
status: scaffold_skip | debug_red | non_skip_pass | blocked
|
||||
pytest_output: <command + short result>
|
||||
comparison_target: <latents|decoded video|audio|joint outputs>
|
||||
example:
|
||||
path: examples/inference/basic/basic_<family>.py
|
||||
status: pass | blocked | not_run
|
||||
output: <path or none>
|
||||
readme_updated: yes | no
|
||||
port_state_updated: yes | no
|
||||
blockers: <none or exact blocker list>
|
||||
next_step: phase_10_quality_regression | return_to_phase_6 | ask_user
|
||||
escape_hatch: <none or block matching contracts/escape_hatch.md>
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- `pipeline-definition` may return with parity still `scaffold_skip` only if the
|
||||
exact missing dependency, weight, or call-path blocker is recorded.
|
||||
- Final `/add-model` handoff requires `smoke_test.status=non_skip_pass` and
|
||||
`pipeline_parity.status=non_skip_pass`, unless the user explicitly accepts a
|
||||
documented blocker.
|
||||
- If parity failure traces to a component, conversion, or strict-load issue,
|
||||
return `next_step=return_to_phase_6` and include the exact failing evidence.
|
||||
- Keep `local_tests_readme` and `port_state_file` synchronized with this
|
||||
handoff before returning.
|
||||
- Use `next_step=ask_user` only with an `escape_hatch` block and a matching
|
||||
`PORT_STATUS.md` row.
|
||||
@@ -0,0 +1,89 @@
|
||||
# Port State Contract
|
||||
|
||||
Canonical per-port state file created during prep and updated by every
|
||||
`/add-model` phase.
|
||||
|
||||
Path:
|
||||
|
||||
```text
|
||||
tests/local_tests/<model_family>/PORT_STATUS.md
|
||||
```
|
||||
|
||||
Purpose:
|
||||
|
||||
- Single source of truth for resumable port progress.
|
||||
- Tracks component status, conversion status, parity status, open questions,
|
||||
blockers, escape hatches, and issue history.
|
||||
- Lets review agents run the same setup/tests without reconstructing handoffs
|
||||
from conversation history.
|
||||
|
||||
Required sections:
|
||||
|
||||
```text
|
||||
# <Model Family> Port Status
|
||||
|
||||
## Summary
|
||||
- model_family:
|
||||
- workload_types:
|
||||
- official_ref:
|
||||
- official_ref_dir:
|
||||
- hf_weights_path:
|
||||
- local_weights_dir:
|
||||
- source_layout:
|
||||
- local_tests_readme:
|
||||
|
||||
## Current Phase
|
||||
- phase:
|
||||
- status: not_started | in_progress | blocked | complete
|
||||
- owner: orchestrator | prep | parity | conversion | component:<name> | pipeline
|
||||
- last_updated:
|
||||
|
||||
## Component Matrix
|
||||
| Component | Type | Reuse/Port | Official Definition | Official Instantiation | FastVideo Target | Prototype | Conversion | Parity | Open Issues |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
|
||||
## Conversion State
|
||||
- conversion_script:
|
||||
- converted_weights_dir:
|
||||
- source_layout:
|
||||
- strict_load_status:
|
||||
- passthrough_components:
|
||||
- retry_history:
|
||||
|
||||
## Parity Commands
|
||||
| Scope | Command | Last Result | Notes |
|
||||
|---|---|---|---|
|
||||
|
||||
## Open Questions
|
||||
| ID | Question | Owner | Needed By Phase | Status | Resolution |
|
||||
|---|---|---|---|---|---|
|
||||
|
||||
## Issues And Blockers
|
||||
| ID | Phase | Component | Severity | Issue | Evidence | Owner | Status | Resolution |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
|
||||
## Escape Hatches
|
||||
| ID | Phase | Decision Type | Question | Recommended Option | Status | Resolution |
|
||||
|---|---|---|---|---|---|---|
|
||||
|
||||
## Decisions
|
||||
| Date | Decision | Rationale | Impact |
|
||||
|---|---|---|---|
|
||||
|
||||
## Handoff Notes
|
||||
- <short notes for the next agent>
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Update this file whenever a phase starts, blocks, resolves an issue, or hands
|
||||
off to another skill.
|
||||
- Record open questions and issues immediately. Do not leave blockers only in
|
||||
chat history or subagent responses.
|
||||
- Use stable IDs: `Q001`, `Q002`, `I001`, `I002`, etc.
|
||||
- Use stable escape-hatch IDs: `E001`, `E002`, etc. Link them from handoff
|
||||
`escape_hatch.state_snapshot.evidence` when returning `next_step=ask_user`.
|
||||
- Do not include raw token values, machine-local cache internals, or large output
|
||||
dumps. Use repo-relative paths when possible.
|
||||
- If a question or issue is resolved, keep the row and fill `Resolution` instead
|
||||
of deleting it.
|
||||
@@ -0,0 +1,42 @@
|
||||
# Prep Handoff Contract
|
||||
|
||||
Produced by `../add-model-01-prep/SKILL.md` and consumed by `/add-model` Phase 0.
|
||||
|
||||
```text
|
||||
model_family: <snake_case>
|
||||
workload_types: <T2V/I2V/V2V/T2I/or compatibility shim with rationale>
|
||||
official_ref: <url or import path>
|
||||
official_ref_dir: <ReferenceDir or none>
|
||||
official_ref_commit: <sha or unknown>
|
||||
hf_weights_path: <HF id or local path>
|
||||
hf_revision: <revision or default>
|
||||
local_weights_dir: official_weights/<model_family> or <local path>
|
||||
source_layout: diffusers | raw_official | monolithic | separate_components | mixed | custom | unknown
|
||||
model_index_class: <_class_name or none>
|
||||
components_seen: <components>
|
||||
needs_conversion: yes | no | unknown
|
||||
hf_token_env: <env var name only>
|
||||
dependency_changes: none | installed no-deps editable | installed official deps in current env | blocked on user
|
||||
official_env_status: imports_ok | private_deps_need_stubs | blocked
|
||||
local_tests_readme: tests/local_tests/<model_family>/README.md
|
||||
port_state_file: tests/local_tests/<model_family>/PORT_STATUS.md
|
||||
gitignore_entries_added: <list>
|
||||
next_step: add-model | ask_user
|
||||
open_questions: <short list>
|
||||
escape_hatch: <none or block matching contracts/escape_hatch.md>
|
||||
```
|
||||
|
||||
Validation:
|
||||
|
||||
- `official_env_status` must be `imports_ok` or `private_deps_need_stubs` before
|
||||
component parity scaffolding.
|
||||
- `local_tests_readme` must exist and describe official setup, HF weights,
|
||||
dependency changes, planned parity commands, and review notes.
|
||||
- `port_state_file` must exist and follow `contracts/port_state.md`.
|
||||
- Prep does not go directly to conversion; `/add-model` must run component
|
||||
prototype/key-dump Phase 4 before Phase 5 conversion.
|
||||
- `T2A`, `A2A`, and `AV` may be used only after `WorkloadType` supports them;
|
||||
otherwise record the compatibility shim and rationale explicitly.
|
||||
- Never include HF token values.
|
||||
- Use `next_step=ask_user` only with an `escape_hatch` block and a matching
|
||||
`PORT_STATUS.md` row.
|
||||
@@ -0,0 +1,96 @@
|
||||
# Shared Add-Model Rules
|
||||
|
||||
These rules apply to every `add-model` related skill: prep, parity, conversion,
|
||||
component porting, pipeline, and the main `/add-model` orchestrator.
|
||||
|
||||
## Token And Auth Safety
|
||||
|
||||
- Never accept, print, echo, log, hard-code, or commit raw HF token values.
|
||||
- Refer only to token environment variable names: `HF_TOKEN`,
|
||||
`HUGGINGFACE_HUB_TOKEN`, or `HF_API_KEY`.
|
||||
- Scripts may read those environment variables but must not print their values.
|
||||
- Ask for auth setup only by env var name. Do not ask the user to paste a token.
|
||||
- Read scope is needed for gated repos during conversion/load. Write scope is
|
||||
needed for publishing converted weights or seeding generated references.
|
||||
|
||||
## Shared State Files
|
||||
|
||||
- `tests/local_tests/<model_family>/README.md` is the reviewer-facing setup and
|
||||
verification log. Keep it current with setup commands, dependency blockers,
|
||||
parity commands, conversion commands, and pass/blocker status.
|
||||
- `tests/local_tests/<model_family>/PORT_STATUS.md` is the per-port state file.
|
||||
It must follow `../contracts/port_state.md` and keep stable `Q###`, `I###`, and
|
||||
`E###` IDs.
|
||||
- Keep resolved questions/issues in `PORT_STATUS.md` with the resolution instead
|
||||
of deleting them.
|
||||
- Before returning a handoff, update both state files when the skill changed
|
||||
setup, tests, conversion, parity status, blockers, or decisions.
|
||||
- Do not include raw tokens, non-reproducible absolute cache paths, large
|
||||
generated outputs, `.env`, credentials, or anything matching `*secret*`.
|
||||
|
||||
## Escape Hatches
|
||||
|
||||
Continue autonomously for recoverable setup, implementation, conversion,
|
||||
strict-load, smoke, parity-debug, lint, or test failures. Stop and ask the user
|
||||
only when the next action requires a product, cost, safety, auth, dependency, or
|
||||
scope decision the workflow cannot safely choose.
|
||||
|
||||
Use `../contracts/escape_hatch.md` whenever returning `next_step=ask_user`.
|
||||
|
||||
Ask for user input only for:
|
||||
|
||||
- scope changes, such as dropping a modality, output head, variant, component, or
|
||||
public mode;
|
||||
- core dependency changes, untrusted/private dependency installs, or version pin
|
||||
changes;
|
||||
- auth setup for gated repos, using env var names only;
|
||||
- large downloads, publishing weights, SSIM/reference uploads, or GPU-heavy work
|
||||
where cost/runtime approval is needed;
|
||||
- destructive file/git operations, overwriting existing clones/weights, or
|
||||
deleting staged assets;
|
||||
- incompatible official sources of truth where no reference can be chosen from
|
||||
published weights and docs;
|
||||
- accepting a blocker, loosening tolerances, using shape-only substitutes, or
|
||||
shipping without required non-skip parity.
|
||||
|
||||
Do not ask for normal recoverable failures: missing imports, missing local paths,
|
||||
skipped tests, failing parity, conversion mapping bugs, strict-load errors,
|
||||
format/lint failures, smoke failures, registry import issues, example failures,
|
||||
or implementation bugs covered by the phase workflow.
|
||||
|
||||
Before asking:
|
||||
|
||||
- update `PORT_STATUS.md` with an `E###` escape-hatch row plus any linked `Q###`
|
||||
or `I###` row;
|
||||
- include exact evidence: command, path, short error text, parity or strict-load
|
||||
excerpt, or blocker ID;
|
||||
- provide one recommended option and at most three alternatives;
|
||||
- set the relevant handoff `next_step=ask_user` and include the `escape_hatch`
|
||||
block.
|
||||
|
||||
Skill-specific escape-hatch sections may add extra examples, but they must not
|
||||
weaken these shared rules.
|
||||
|
||||
## Production Boundary
|
||||
|
||||
- No runtime `from diffusers import <model class>` or
|
||||
`from transformers import <model class>` in `fastvideo/` production code.
|
||||
- Components that own weights or numerical behavior must be FastVideo-native
|
||||
unless the user explicitly accepts a documented lazy-wrapper exception.
|
||||
- Allowed third-party runtime exceptions are tokenizers and pure data utilities
|
||||
when they match existing project patterns.
|
||||
- Tests may import diffusers/transformers as parity references.
|
||||
- Production comments explain why, not what or provenance. Avoid narrative
|
||||
comments like `vendored from`, `matches upstream`, `REVIEW`, or session-history
|
||||
commentary.
|
||||
|
||||
## Verification Semantics
|
||||
|
||||
- A committed local test may skip when clones, weights, or private deps are absent
|
||||
so CI and other contributors are not blocked.
|
||||
- On the porter's machine, a skip is not a pass. Fix the missing import, weights,
|
||||
or path before claiming verification.
|
||||
- New ports require local non-skip parity for required components and pipeline
|
||||
parity when a pipeline is in scope.
|
||||
- Smoke tests prove loadability only. They are not a substitute for numerical
|
||||
component or pipeline parity.
|
||||
@@ -0,0 +1,117 @@
|
||||
# Component Skill Common Instructions
|
||||
|
||||
These instructions apply to `add-model-03-port-dit`, `add-model-04-port-vae`,
|
||||
`add-model-05-port-encoder`, and `add-model-06-port-generic`. Bucket-specific skills
|
||||
add target paths, implementation patterns, drift checks, and scope questions.
|
||||
|
||||
## Required Context
|
||||
|
||||
Require the complete packet from `../contracts/component_context.md`.
|
||||
|
||||
Do not start if the official definition files, official instantiation files, or
|
||||
parity test path are missing. Ask the `/add-model` orchestrator for the complete
|
||||
component context packet instead of rediscovering broad scope silently.
|
||||
|
||||
If the parity scaffold is missing, create it first with
|
||||
`../../add-model-02-parity/templates/component_parity_test.py`.
|
||||
|
||||
## Prototype Mode
|
||||
|
||||
Prototype mode runs before conversion:
|
||||
|
||||
- implement or prove reuse for the minimal native component, config, export, and
|
||||
`EntryClass` surface needed by the relevant loader;
|
||||
- instantiate with random weights using the exact official architecture args, or
|
||||
instantiate/document stateless components with no weights;
|
||||
- dump official and FastVideo `state_dict()` names/shapes for every stateful
|
||||
component so conversion can derive mappings from real surfaces;
|
||||
- return concerns discovered during prototype work, such as ambiguous official
|
||||
flags, shape mismatches, private ops, missing loader buckets, passthrough
|
||||
weights, or output heads;
|
||||
- update `local_tests_readme` with prototype status and key-dump paths;
|
||||
- update `port_state_file` with prototype status, open questions, issues, and
|
||||
handoff notes;
|
||||
- do not chase numerical parity and do not block on converted weights.
|
||||
|
||||
Prototype mode succeeds when the component imports, instantiates with official
|
||||
args, and required key/shape dumps exist. Converted weights and parity PASS are
|
||||
not required yet.
|
||||
|
||||
## Parity-Debug Mode
|
||||
|
||||
Parity-debug mode runs after conversion:
|
||||
|
||||
- strict-load converted weights through the same path the pipeline will use, or
|
||||
document that the component is stateless or an approved passthrough;
|
||||
- use `conversion_context` and `concerns_or_unknowns` to decide whether a failure
|
||||
belongs to mapping, loading, implementation, tokenization, normalization,
|
||||
scheduler semantics, or the parity test;
|
||||
- run only the component parity test first with `pytest <parity_test> -v -s`;
|
||||
- if it skips, fix the missing official import, FastVideo class, tokenizer,
|
||||
converted weights, or path;
|
||||
- if it fails numerically, add targeted intermediate comparisons to identify the
|
||||
first divergent operation or tensor;
|
||||
- update component implementation only when the failure is a component
|
||||
layer/config/forward/contract bug;
|
||||
- update `local_tests_readme` with the command, result, and blocker or PASS;
|
||||
- update `port_state_file` with parity status, resolved/new issues, open
|
||||
questions, and handoff notes;
|
||||
- keep iterating until the test is a non-skip PASS or return a precise blocker.
|
||||
|
||||
## Conversion Boundary
|
||||
|
||||
Component skills must not patch conversion scripts or converted weights ad hoc.
|
||||
If the first drift or strict-load failure points to wrong keys, missing tensors,
|
||||
shape mismatches, component prefixes, split/fuse logic, skipped-key policy, or
|
||||
config emission, return a conversion retry request for
|
||||
`../../add-model-07-conversion/SKILL.md` matching
|
||||
`../contracts/conversion_request.md`.
|
||||
Resume parity-debug only after conversion returns an updated handoff.
|
||||
|
||||
## Reuse Proof
|
||||
|
||||
When `fastvideo_target_files` point to existing FastVideo code instead of a new
|
||||
port:
|
||||
|
||||
- compare the official definition against the FastVideo target: graph/operation
|
||||
structure, parameter or state shapes, normalization, activation, positional or
|
||||
temporal behavior, scaling constants, dtype behavior, state-dict names, output
|
||||
containers, and output tensors;
|
||||
- compare the official instantiation against the FastVideo config and loader
|
||||
args: constructor args, config values, defaults, variant flags, optional
|
||||
submodules, checkpoint metadata, tokenizer/media paths, and loader path;
|
||||
- treat a matching class instantiated with different args as not reusable;
|
||||
- record reuse evidence in `local_tests_readme` and keep the reused component in
|
||||
`prototype_key_dumps` when it owns state so conversion and parity-debug use the
|
||||
same surface;
|
||||
- still run parity-debug to a non-skip PASS. If mismatch is found, return the
|
||||
concern so `/add-model` can switch the component to a native port.
|
||||
|
||||
## Handoff
|
||||
|
||||
Return `../contracts/component_skill_handoff.md`.
|
||||
|
||||
Mode-specific expectations:
|
||||
|
||||
- In `mode=prototype`, `parity_status` may be `scaffold_skip` or `blocked`; key
|
||||
dumps are the required artifact and successful prototype handoff should use
|
||||
`next_step=phase_5_conversion`.
|
||||
- In `mode=parity-debug`, final success requires
|
||||
`parity_status=non_skip_pass`.
|
||||
- If conversion is implicated, return `conversion_retry_request` and leave
|
||||
conversion edits to `add-model-07-conversion`.
|
||||
- If production loading is non-strict, list allowed missing/unexpected keys in
|
||||
the parity test or handoff and mark
|
||||
`strict_load=pass_with_documented_exclusions`.
|
||||
|
||||
## Escape Hatches
|
||||
|
||||
Follow `common_rules.md`. Do not ask for normal prototype or parity-debug
|
||||
failures such as missing imports, tokenizer/path issues, red parity,
|
||||
strict-load failures, key mismatches, shape mismatches, or implementation bugs.
|
||||
Return conversion retry requests or precise blockers as directed by the workflow.
|
||||
|
||||
Ask only when component work requires a scope or safety decision, such as
|
||||
dropping a required stream/output/path, changing core dependencies, accepting
|
||||
private model code or unsupported private ops, choosing between incompatible
|
||||
official definitions, creating a new loader bucket, or loosening required parity.
|
||||
@@ -0,0 +1,99 @@
|
||||
---
|
||||
name: ci-runner
|
||||
description: Work on FastVideo's Slurm-only, change-aware GPU CI lanes, static Buildkite graph, trusted ci-runner policy, lane scripts, and GB200 validation.
|
||||
---
|
||||
|
||||
# Slinky Slurm CI lanes
|
||||
|
||||
FastVideo's `ci-runner` Buildkite queue is the control plane for all active
|
||||
GPU CI. A private host-owned dispatcher leases GPUs from the Slinky Slurm tray
|
||||
and runs the immutable PR SHA inside an isolated Enroot container. Buildkite
|
||||
pipeline upload and Slurm submission occur on the login plane; every test
|
||||
payload executes on Slurm compute.
|
||||
|
||||
The files under `fastvideo/tests/modal/` and `.buildkite/scripts/pr_test.sh`
|
||||
are dormant rollback code. Never add an active Buildkite or slash-command
|
||||
route to them. `pr_test.sh` must continue to reject Buildkite invocations.
|
||||
|
||||
The private operator bundle is deliberately outside this repository because
|
||||
it contains site paths and credentials. See
|
||||
`docs/contributing/ci_architecture.md`; this skill covers the repository half
|
||||
and the coordination contract with that bundle.
|
||||
|
||||
## Invariants
|
||||
|
||||
- `.buildkite/pipeline.yml` contains exactly one static step for every active
|
||||
GPU lane. Each step pins a unique key and label, a 90-minute timeout, the
|
||||
trusted `/opt/fastvideo-ci-runner/run-ci` command (`run-unit` is the one
|
||||
compatibility wrapper), step-level internal `TEST_TYPE`, and
|
||||
`queue: "ci-runner"`.
|
||||
- Active CI contains no `pr_test.sh` command, Modal invocation, default queue,
|
||||
Buildkite plugin, `soft_fail`, or job-controlled artifact glob.
|
||||
- The six Fastcheck lanes use `:microscope:` labels. Full-Suite-only lanes use
|
||||
`:test_tube:` or `:bar_chart:` so direct reruns update the right aggregate.
|
||||
- SSIM and vanilla training request all four GPUs. Keep both in the
|
||||
`fastvideo/slinky/whole-tray` Buildkite concurrency group with a limit of one
|
||||
so the second job does not consume an agent or command timeout while waiting
|
||||
for the same tray.
|
||||
- `/test full` schedules all twenty lanes. `/merge`, `ready`, and new pushes to
|
||||
ready PRs use the trusted base-branch planner in
|
||||
`.github/scripts/plan_merge_ci.py`: automatic Fastcheck remains the universal
|
||||
six-lane baseline, and the merge build adds only path-relevant integration
|
||||
lanes. Unknown source/build paths fail closed to all fourteen additive lanes.
|
||||
The trusted uploader still normalizes and validates the complete static graph
|
||||
before Buildkite evaluates its plan conditions.
|
||||
- Focused merge builds may pass allowlisted golden-gate and SSIM test basenames.
|
||||
The private host validates the lane plan and basenames before staging them,
|
||||
and the in-container scripts validate them again. Direct `/test ssim`,
|
||||
explicit `/test full`, and the weekly main-branch schedule run the complete
|
||||
SSIM matrix.
|
||||
- The trusted uploader serves exactly three entry pipelines:
|
||||
`pr-fastcheck` for automatic PR builds, `ci` for slash-command/ready-label
|
||||
API builds, and `fastvideo-performance-lane` for the weekly schedule. Keep
|
||||
incoming GitHub webhook processing disabled on `ci` so it cannot duplicate
|
||||
`pr-fastcheck` on every PR update.
|
||||
- Test payloads live in `.buildkite/scripts/unit_test.sh` or executable
|
||||
`.buildkite/scripts/lanes/<lane>.sh`. Backend policy (GPU count, extras,
|
||||
secrets, kernel build, artifacts) stays in the agent-owned lane table.
|
||||
- Tests must preserve an inherited `MASTER_PORT`. Packed containers share the
|
||||
tray network namespace, so the private runner assigns a distinct port range
|
||||
per GPU lease and the SSIM scheduler assigns task offsets within its range.
|
||||
- The ARM64 runner image includes the pinned FA4 CuTe overlay validated on
|
||||
GB200. Keep SSIM at `FASTVIDEO_FA4=1` because its references were seeded with
|
||||
FA4; keep lanes with FA2 baselines at `FASTVIDEO_FA4=0`. A runner image change
|
||||
must revalidate both the FA4 import and an actual GB200 forward kernel.
|
||||
- `fastvideo/tests/ssim/ci_runner.py` is the active four-GPU SSIM scheduler.
|
||||
New SSIM files are discovered through `REQUIRED_GPUS` and
|
||||
`*_MODEL_TO_PARAMS`; do not wire them through the dormant Modal scheduler.
|
||||
- The host policy fail-closes unknown tuples. A repository-side lane change is
|
||||
inert until the operator updates the private lane table and uploader policy
|
||||
in the same rollout.
|
||||
|
||||
## Adding or changing a lane
|
||||
|
||||
1. Read the closest `AGENTS.md` and the domain-specific testing guide.
|
||||
2. Add or update the executable lane payload under `.buildkite/scripts/`.
|
||||
Keep it deterministic and free of host-specific paths or credential fetches.
|
||||
3. Add the static pipeline step and canonical `/test <name>` mapping. Keep the
|
||||
`<name>-ci` alias only when compatibility requires it.
|
||||
4. Add its source/test path ownership to `.github/scripts/plan_merge_ci.py`.
|
||||
Prefer the narrowest correctness-preserving lane set; leave unknown paths
|
||||
fail-closed. Extend `fastvideo/tests/contract/test_ci_test_collection.py`,
|
||||
`test_merge_ci_plan.py`, and focused CPU-only scheduler/policy tests.
|
||||
5. Coordinate the private lane row: GPU count (1-4), wall time, script, scope
|
||||
pairs, step key, command, HF cache/token, tracking mode, extras, attention
|
||||
backend policy, kernel policy, and artifact relay. Active training lanes
|
||||
keep W&B offline and do not stage a W&B credential.
|
||||
6. Update the trusted pipeline-uploader schema. A mismatch must reject the
|
||||
pipeline rather than silently skip a lane.
|
||||
7. Run `pre-commit run --files <changed paths>`, the planner's representative
|
||||
diff matrix, contract tests, private driver tests, and a real GB200 canary.
|
||||
Multi-GPU, hardware-reference, training, performance, and SSIM changes need
|
||||
their own target-hardware evidence.
|
||||
|
||||
## Rollback
|
||||
|
||||
Rollback the Slurm routing/configuration change or pause the `ci-runner` queue.
|
||||
Do not silently reactivate Modal. A manual Modal experiment requires the
|
||||
explicit local opt-in documented in `ci_architecture.md`; returning it to
|
||||
production CI needs a separate reviewed decision.
|
||||
@@ -44,7 +44,7 @@ pipeline PR.
|
||||
|-----------|----------|-------------|
|
||||
| PR number or URL | Yes | E.g. `1280` or `https://github.com/hao-ai-lab/FastVideo/pull/1280` |
|
||||
| Max desired PR size | No | Defaults to ~2,500 LOC of code per stack PR (excluding generated/journal files) |
|
||||
| Output dir | No | Defaults to `.agents/exploration/decompose-<pr-number>.md` |
|
||||
| Output directory | No | Defaults to `.agents/tmp/decompose-<pr-number>/` (gitignored) |
|
||||
|
||||
## Steps
|
||||
|
||||
@@ -54,8 +54,10 @@ pipeline PR.
|
||||
Always cross-check against the authoritative `git diff`:
|
||||
|
||||
```bash
|
||||
mkdir -p .agents/tmp/decompose-<N>
|
||||
git fetch origin pull/<N>/head:<feature-branch>
|
||||
git diff origin/main..origin/<feature-branch> --name-status > /tmp/pr-<N>-files.txt
|
||||
git diff origin/main..origin/<feature-branch> --name-status \
|
||||
> .agents/tmp/decompose-<N>/files.txt
|
||||
git diff origin/main..origin/<feature-branch> --stat
|
||||
```
|
||||
|
||||
@@ -68,7 +70,7 @@ Classify every changed file into one of four tiers:
|
||||
|
||||
| Tier | Description | Examples |
|
||||
|---|---|---|
|
||||
| **Tier 0 — Invisible** | Lint/style/CI configs that don't affect runtime | `.gitignore`, `pyproject.toml` (codespell only), `.agents/skills/index.jsonl` stubs |
|
||||
| **Tier 0 — Invisible** | Lint/style/CI configs that don't affect runtime | `.gitignore`, `pyproject.toml` (codespell only), agent documentation |
|
||||
| **Tier 1 — Dead code** | New files in their own dirs; aggregator one-liners | `fastvideo/models/dits/<new>/`, `fastvideo/pipelines/basic/<new>/`, `examples/inference/basic/basic_<new>*.py`, `tests/local_tests/<new>/`, `__init__.py` exports |
|
||||
| **Tier 2 — Cross-cutting infra** | Modifications to files used by every pipeline | See protected-paths list below |
|
||||
| **Tier 3 — Activation switch** | `register_configs(...)` calls + the example scripts that demo them | `fastvideo/registry.py` |
|
||||
@@ -82,7 +84,9 @@ fastvideo/configs/models/dits/__init__.py
|
||||
fastvideo/configs/models/encoders/__init__.py
|
||||
fastvideo/configs/models/vaes/__init__.py
|
||||
fastvideo/envs.py
|
||||
fastvideo/fastvideo_args.py
|
||||
fastvideo/api/schema.py
|
||||
fastvideo/api/resolution.py
|
||||
fastvideo/api/inference_resolution.py
|
||||
fastvideo/distributed/**
|
||||
fastvideo/layers/**
|
||||
fastvideo/attention/**
|
||||
@@ -235,9 +239,11 @@ git -C "$REPO" fetch origin main:main
|
||||
git -C "$REPO" fetch "origin/$SOURCE_BRANCH"
|
||||
git -C "$REPO" worktree add "$WORKTREE" origin/main
|
||||
|
||||
# Capture baseline for provenance
|
||||
mkdir -p "$REPO/.agents/exploration"
|
||||
cat > "$REPO/.agents/exploration/<feature>-baseline-${SOURCE_SHA:0:8}.txt" <<EOF
|
||||
# Capture baseline for provenance. Everything under .agents/tmp is transient
|
||||
# and ignored by git.
|
||||
OUTPUT_DIR="$REPO/.agents/tmp/decompose-$SOURCE_PR"
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
cat > "$OUTPUT_DIR/<feature>-baseline-${SOURCE_SHA:0:8}.txt" <<EOF
|
||||
Source PR: <repo>#$SOURCE_PR
|
||||
Source SHA: $SOURCE_SHA
|
||||
Authoritative file count: $(git -C "$REPO" diff origin/main..origin/$SOURCE_BRANCH --name-only | wc -l)
|
||||
@@ -272,12 +278,13 @@ Notes:
|
||||
|
||||
## Outputs
|
||||
|
||||
The skill produces:
|
||||
The skill produces all transient planning artifacts under
|
||||
`.agents/tmp/decompose-<pr>/`:
|
||||
|
||||
1. A markdown decomposition plan (`.agents/exploration/decompose-<pr>.md`)
|
||||
1. A markdown decomposition plan (`plan.md`)
|
||||
2. A proposed branch graph
|
||||
3. A worktree-bootstrap script
|
||||
4. Per-PR file allocation lists (under `/tmp/<feature>-stack/`)
|
||||
3. A worktree-bootstrap script (`bootstrap.sh`)
|
||||
4. Per-PR file allocation lists (under `stack/`)
|
||||
5. AGENTS.md scaffolds for any new pipeline packages
|
||||
6. Draft lesson files (placed alongside the PR that owns the code they concern)
|
||||
7. A finalized provenance table for the package AGENTS.md
|
||||
@@ -312,7 +319,7 @@ The skill should warn against:
|
||||
```
|
||||
User: split PR 1280
|
||||
Agent: [invokes decompose-pipeline-pr]
|
||||
→ produces .agents/exploration/decompose-1280.md with:
|
||||
→ produces .agents/tmp/decompose-1280/plan.md with:
|
||||
- tiered file table (56 files: 3 tier-0, 35 tier-1, 9 tier-2,
|
||||
9 tier-3)
|
||||
- branch graph (PR-A + PR-B + 8-PR stack)
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
---
|
||||
name: dreamverse-deploy
|
||||
description: Use when redeploying the migrated Dreamverse app backend and frontend on a chosen local GPU; tears down existing ports, launches services, and waits for readiness checks.
|
||||
---
|
||||
|
||||
# dreamverse-deploy — redeploy migrated Dreamverse on a chosen GPU
|
||||
|
||||
**Scope:** project (lives in this repo at `.agents/skills/dreamverse-deploy/`)
|
||||
|
||||
**When to use:** you want to (re)launch the migrated `apps/dreamverse/` backend
|
||||
and frontend on this dev node, pinned to a specific physical GPU. Tears down
|
||||
any existing deploy on the same ports first, then boots fresh and waits for
|
||||
both `/readyz` and the FE root to return 200.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Working tree containing `apps/dreamverse/`
|
||||
- `dreamverse-server` installed from this checkout; if missing, run
|
||||
`uv pip install -e ".[dreamverse]"`
|
||||
- Local conda env at `~/miniconda3/envs/fv-main/` with `flashinfer-python`,
|
||||
`cerebras-cloud-sdk`, `openai` installed (override the default path with
|
||||
`DREAMVERSE_PYTHON=/path/to/python`)
|
||||
- `~/.env` exporting `CEREBRAS_API_KEY`, `GROQ_API_KEY`, etc.
|
||||
- npm available in `$PATH` (or set `NPM=/path/to/npm`)
|
||||
- `gcc-13` + `g++-13` at `/usr/bin/` (workaround for nvcc gcc-15 rejection)
|
||||
- **Recommended:** native ffmpeg at `$HOME/opt/ffmpeg-native/bin/ffmpeg`, built
|
||||
via `bash apps/dreamverse/scripts/install_native_ffmpeg.sh`. The deploy
|
||||
detects that binary directly and exports it for the backend. The installer's
|
||||
generated `apps/dreamverse/scripts/ffmpeg-env.sh` is for manual launches.
|
||||
When the binary is missing, the deploy falls back to system ffmpeg with a
|
||||
warning. Set
|
||||
`DREAMVERSE_REQUIRE_NATIVE_FFMPEG=true` to make the missing binary a hard
|
||||
failure.
|
||||
|
||||
If any required prereq is missing, the script fails fast with a clear message.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Deploy on GPU 4 with the current web port. The legacy helper default remains
|
||||
# 5274, so pass 5299 explicitly. Torch compile and warmup are both off.
|
||||
./.agents/skills/dreamverse-deploy/scripts/dreamverse-deploy.sh 4 8009 5299
|
||||
|
||||
# Deploy on GPU 6 with custom ports
|
||||
./.agents/skills/dreamverse-deploy/scripts/dreamverse-deploy.sh 6 8089 5275
|
||||
|
||||
# Deploy on GPU 0 with warmup enabled
|
||||
./.agents/skills/dreamverse-deploy/scripts/dreamverse-deploy.sh --warmup 0 8009 5299
|
||||
|
||||
# Deploy with torch.compile enabled (max-autotune; first segment ~3-4min,
|
||||
# subsequent segments save ~3s — only worth it for benchmarking)
|
||||
./.agents/skills/dreamverse-deploy/scripts/dreamverse-deploy.sh --torch-compile 4 8009 5299
|
||||
|
||||
# Deploy with both warmup AND torch.compile enabled
|
||||
./.agents/skills/dreamverse-deploy/scripts/dreamverse-deploy.sh --warmup --torch-compile 4 8009 5299
|
||||
|
||||
# Flags can appear before, between, or after positional args
|
||||
./.agents/skills/dreamverse-deploy/scripts/dreamverse-deploy.sh 4 8089 5275 --warmup
|
||||
```
|
||||
|
||||
### Arguments
|
||||
|
||||
| Position | Name | Default | Notes |
|
||||
|---|---|---|---|
|
||||
| 1 | `GPU` | (required) | Physical GPU index, e.g. `4` |
|
||||
| 2 | `BACKEND_PORT` | `8009` | TCP port for the FastAPI server |
|
||||
| 3 | `FRONTEND_PORT` | `5274` | TCP port for the Next.js dev server |
|
||||
|
||||
### Flags
|
||||
|
||||
| Flag | Default | Notes |
|
||||
|---|---|---|
|
||||
| `--warmup` / `--no-warmup` | off | Run GPU warmup at boot (~minutes). Overrides `DREAMVERSE_WARMUP` |
|
||||
| `--torch-compile` / `--no-torch-compile` | off | Enable max-autotune `torch.compile`. First segment ~3-4min when on, ~45s when off. Overrides `DREAMVERSE_TORCH_COMPILE` |
|
||||
| `--nvenc` / `--no-nvenc` | off | Use `h264_nvenc` hardware encoder instead of `libx264` software. Eliminates ~1100ms/segment of CPU encoding cost (raises realtime ratio from ~0.78x → ≥1.0x, eliminating inter-segment buffer-drain stutter). Requires native ffmpeg built with `--enable-nvenc` (the install script's default since the NVENC update). Hard-fails up-front if the binary is missing or lacks NVENC. Overrides `DREAMVERSE_NVENC` |
|
||||
| `-h` / `--help` | — | Show usage |
|
||||
|
||||
Flags can appear in any position relative to the positional args. Explicit flag values always win over env-var defaults.
|
||||
|
||||
### Environment variables (used when no flag is given)
|
||||
|
||||
| Var | Default | Purpose |
|
||||
|---|---|---|
|
||||
| `DREAMVERSE_WARMUP` | `false` | Same as `--warmup`/`--no-warmup`. Flag takes precedence |
|
||||
| `DREAMVERSE_TORCH_COMPILE` | `false` | Same as `--torch-compile`/`--no-torch-compile`. Flag takes precedence |
|
||||
| `DREAMVERSE_NVENC` | `false` | Same as `--nvenc`/`--no-nvenc`. Flag takes precedence |
|
||||
| `DREAMVERSE_PYTHON` | `~/miniconda3/envs/fv-main/bin/python` | Conda environment used for the flashinfer prerequisite probe; `dreamverse-server` itself is resolved from `PATH` |
|
||||
| `DREAMVERSE_REPO_ROOT` | git rev-parse | Repo root override |
|
||||
| `DREAMVERSE_LOG_DIR` | `/tmp/opencode/dreamverse-deploy` | Directory for the per-GPU backend and per-port frontend logs |
|
||||
| `DREAMVERSE_REQUIRE_NATIVE_FFMPEG` | `false` | If `true`, fail when `$HOME/opt/ffmpeg-native/bin/ffmpeg` is absent |
|
||||
|
||||
## What it does
|
||||
|
||||
1. Validates prereqs.
|
||||
2. Kills any process on the target backend/frontend ports + waits for the
|
||||
target GPU to release memory (allows up to 30s for cleanup).
|
||||
3. Sources `~/.env`.
|
||||
4. Exports the env recipe required for boot:
|
||||
- `CUDA_VISIBLE_DEVICES=<gpu>`
|
||||
- `FASTVIDEO_ENABLE_DEVTOOLS=1`
|
||||
- `FASTVIDEO_ENABLE_STARTUP_WARMUP=<DREAMVERSE_WARMUP>`
|
||||
- `FASTVIDEO_GPU_COUNT=1`
|
||||
- `ENABLE_TORCH_COMPILE=<0|1 derived from DREAMVERSE_TORCH_COMPILE>`
|
||||
- `CC=/usr/bin/gcc-13 CXX=/usr/bin/g++-13 CUDAHOSTCXX=/usr/bin/g++-13`
|
||||
- `NVCC_PREPEND_FLAGS="-ccbin /usr/bin/gcc-13 -allow-unsupported-compiler"`
|
||||
- `FASTVIDEO_FFMPEG_BIN=$HOME/opt/ffmpeg-native/bin/ffmpeg` +
|
||||
`FASTVIDEO_VIDEO_CODEC=<libx264|h264_nvenc>` (when the native binary exists)
|
||||
5. Launches the installed `dreamverse-server` console command in a detached
|
||||
`setsid` session and captures its PID.
|
||||
6. Polls `/readyz` until 200. The budget is 5 minutes by default, 8 minutes
|
||||
with one startup optimization enabled, and 15 minutes with both warmup and
|
||||
`torch.compile` enabled.
|
||||
7. Launches the devtools frontend through npm in a detached session and
|
||||
captures its PID.
|
||||
8. Polls FE `/` until 200 (max 60s).
|
||||
9. Prints URLs, PIDs, and log paths.
|
||||
|
||||
## What it does NOT do
|
||||
|
||||
- Does not modify `~/.env` or the FastVideo `.venv`.
|
||||
- Does not push code or commit anything.
|
||||
- Does not run Playwright. Use the e2e wrapper separately:
|
||||
```bash
|
||||
cd apps/dreamverse/web
|
||||
PLAYWRIGHT_SKIP_WEBSERVER=1 BACKEND_HOST=127.0.0.1 BACKEND_PORT=8009 \
|
||||
PLAYWRIGHT_BASE_URL=http://127.0.0.1:5299 \
|
||||
NEXT_PUBLIC_INCLUDE_DEVTOOLS=1 \
|
||||
npm exec -- playwright test
|
||||
```
|
||||
The standard suite runs by default; the long-running two-segment
|
||||
audio-continuation spec is gated behind
|
||||
`PLAYWRIGHT_LONG_RUNNING=1` (see below).
|
||||
|
||||
## Long-running e2e (paired with `--warmup --torch-compile`)
|
||||
|
||||
[`apps/dreamverse/web/e2e/long-running-segments.spec.ts`](../../../apps/dreamverse/web/e2e/long-running-segments.spec.ts)
|
||||
drives a real two-segment session through the FE, captures every WS
|
||||
frame, and asserts segments 1 AND 2 both reach `media_segment_complete`
|
||||
with at least one binary fMP4 chunk per segment. It guards against the
|
||||
BrokenPipe regression previously caused by dropped LTX-2 audio continuation
|
||||
kwargs.
|
||||
Skipped by default. Enable with:
|
||||
|
||||
```bash
|
||||
./.agents/skills/dreamverse-deploy/scripts/dreamverse-deploy.sh \
|
||||
--warmup --torch-compile 4 8009 5299
|
||||
|
||||
cd apps/dreamverse/web
|
||||
PLAYWRIGHT_SKIP_WEBSERVER=1 \
|
||||
BACKEND_HOST=127.0.0.1 \
|
||||
BACKEND_PORT=8009 \
|
||||
PLAYWRIGHT_BASE_URL=http://127.0.0.1:5299 \
|
||||
NEXT_PUBLIC_INCLUDE_DEVTOOLS=1 \
|
||||
PLAYWRIGHT_LONG_RUNNING=1 \
|
||||
npm exec -- playwright test e2e/long-running-segments.spec.ts
|
||||
```
|
||||
|
||||
Expected runtime: ~7-9 minutes on a B200 (torch.compile max-autotune
|
||||
warm-up dominates the cold start; per-test timeout is 900s). The spec
|
||||
hard-fails on any WS `error`/`step_error` frame so the BrokenPipe
|
||||
regression surfaces with the actual ffmpeg/audio diagnostics rather
|
||||
than an opaque "test timed out".
|
||||
|
||||
## Teardown
|
||||
|
||||
Stop both services without redeploying:
|
||||
|
||||
```bash
|
||||
# Stop services on default ports (port-pattern based)
|
||||
./.agents/skills/dreamverse-deploy/scripts/dreamverse-deploy.sh --stop
|
||||
|
||||
# Stop AND nuke any process holding GPU N
|
||||
./.agents/skills/dreamverse-deploy/scripts/dreamverse-deploy.sh --stop 4
|
||||
```
|
||||
|
||||
The redeploy path (`<GPU>` mode) automatically nukes any process holding the
|
||||
target GPU before launching — including orphan `multiproc_executor` worker
|
||||
subprocesses left over from a parent backend that was killed without grace.
|
||||
This was the failure mode of an earlier naive port-only kill: parent dies,
|
||||
children survive, GPU stays full, next deploy OOMs.
|
||||
|
||||
## Notes
|
||||
|
||||
- The installed `dreamverse-server` console command enters
|
||||
`apps/dreamverse/dreamverse/server_entry.py`, which loads the current
|
||||
Dreamverse runtime from `apps/dreamverse/dreamverse/`.
|
||||
- The B200 / sm_100a NVCC flags are mandatory on this dev node because the
|
||||
conda toolchain ships gcc-15, which nvcc rejects. The script requires the
|
||||
configured gcc-13 and g++-13 binaries during preflight.
|
||||
|
||||
## Deployment boundary
|
||||
|
||||
This skill is for a local checkout on a directly attached GPU. For a container
|
||||
image, use `apps/dreamverse/docker/README.md`. For Modal, follow
|
||||
`apps/dreamverse/scripts/modal/README.md`; do not adapt this process-killing
|
||||
workflow to a remote deployment.
|
||||
@@ -0,0 +1,460 @@
|
||||
#!/usr/bin/env bash
|
||||
# See ../SKILL.md for full usage.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
is_pid_alive() {
|
||||
kill -0 "$1" 2>/dev/null
|
||||
}
|
||||
|
||||
terminate_pid() {
|
||||
local pid="$1"
|
||||
local label="${2:-pid=${pid}}"
|
||||
|
||||
[[ -n "${pid}" ]] && [[ "${pid}" != "$$" ]] || return 0
|
||||
is_pid_alive "${pid}" || return 0
|
||||
|
||||
kill "${pid}" 2>/dev/null || true
|
||||
for _ in $(seq 1 10); do
|
||||
is_pid_alive "${pid}" || return 0
|
||||
sleep 0.5
|
||||
done
|
||||
|
||||
if is_pid_alive "${pid}"; then
|
||||
kill -9 "${pid}" 2>/dev/null && echo " force-killed ${label}" || true
|
||||
fi
|
||||
}
|
||||
|
||||
terminate_pattern() {
|
||||
local pattern="$1"
|
||||
local pid
|
||||
|
||||
if ! command -v pgrep >/dev/null 2>&1; then
|
||||
pkill -TERM -f "${pattern}" 2>/dev/null || true
|
||||
sleep 2
|
||||
pkill -KILL -f "${pattern}" 2>/dev/null || true
|
||||
return 0
|
||||
fi
|
||||
|
||||
for pid in $(pgrep -f -- "${pattern}" 2>/dev/null || true); do
|
||||
terminate_pid "${pid}" "pattern='${pattern}' pid=${pid}"
|
||||
done
|
||||
}
|
||||
|
||||
list_port_pids() {
|
||||
local port="$1"
|
||||
|
||||
if command -v lsof >/dev/null 2>&1; then
|
||||
lsof -t -iTCP:"${port}" -sTCP:LISTEN 2>/dev/null || true
|
||||
return 0
|
||||
fi
|
||||
|
||||
ss -tlnp 2>/dev/null | awk -v port=":${port}" '
|
||||
$0 ~ port {
|
||||
while (match($0, /pid=[0-9]+/)) {
|
||||
print substr($0, RSTART + 4, RLENGTH - 4)
|
||||
$0 = substr($0, RSTART + RLENGTH)
|
||||
}
|
||||
}
|
||||
' || true
|
||||
}
|
||||
|
||||
if [[ "${1:-}" == "--stop" ]]; then
|
||||
for pat in 'apps/dreamverse/dreamverse/main.py' 'dreamverse-server --host 0.0.0.0 --port' 'next dev --port' 'next-server (v'; do
|
||||
terminate_pattern "${pat}"
|
||||
done
|
||||
if [[ -n "${2:-}" ]] && [[ "${2}" =~ ^[0-9]+$ ]]; then
|
||||
gpu_uuid="$(nvidia-smi --query-gpu=index,uuid --format=csv,noheader 2>/dev/null | awk -F', ' -v g="${2}" '$1==g {print $2}')"
|
||||
if [[ -n "${gpu_uuid}" ]]; then
|
||||
for pid in $(nvidia-smi --query-compute-apps=pid,gpu_uuid --format=csv,noheader 2>/dev/null \
|
||||
| awk -F', ' -v u="${gpu_uuid}" '$2==u {print $1}'); do
|
||||
terminate_pid "${pid}" "GPU${2} pid=${pid}"
|
||||
done
|
||||
fi
|
||||
fi
|
||||
sleep 2
|
||||
echo "stopped: ports may take a few seconds to free"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Args
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
usage() {
|
||||
cat <<USAGE
|
||||
Usage: $(basename "$0") [FLAGS] <GPU> [BACKEND_PORT] [FRONTEND_PORT]
|
||||
$(basename "$0") --stop [GPU]
|
||||
|
||||
Positional:
|
||||
GPU Physical GPU index (required), e.g. 4
|
||||
BACKEND_PORT default 8009
|
||||
FRONTEND_PORT default 5274
|
||||
|
||||
Flags (override env vars when both set):
|
||||
--warmup / --no-warmup run GPU warmup at boot (default off)
|
||||
--torch-compile / --no-torch-compile
|
||||
enable max-autotune torch.compile
|
||||
(default off — first segment ~3-4min
|
||||
when on, ~45s when off)
|
||||
--nvenc / --no-nvenc use h264_nvenc hardware encoder (default
|
||||
off — uses libx264 software encoder).
|
||||
Requires native ffmpeg built with NVENC.
|
||||
-h, --help show this help
|
||||
|
||||
Env overrides:
|
||||
DREAMVERSE_WARMUP 'true'|'false' (default false)
|
||||
DREAMVERSE_TORCH_COMPILE 'true'|'false' (default false)
|
||||
DREAMVERSE_NVENC 'true'|'false' (default false)
|
||||
DREAMVERSE_REPO_ROOT default: \$(git rev-parse --show-toplevel)
|
||||
DREAMVERSE_LOG_DIR default: /tmp/opencode/dreamverse-deploy
|
||||
DREAMVERSE_REQUIRE_NATIVE_FFMPEG 'true'|'false' (default false)
|
||||
USAGE
|
||||
}
|
||||
|
||||
WARMUP_OVERRIDE=""
|
||||
TORCH_COMPILE_OVERRIDE=""
|
||||
NVENC_OVERRIDE=""
|
||||
POSITIONAL=()
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-h|--help) usage; exit 0 ;;
|
||||
--warmup) WARMUP_OVERRIDE=true; shift ;;
|
||||
--no-warmup) WARMUP_OVERRIDE=false; shift ;;
|
||||
--torch-compile) TORCH_COMPILE_OVERRIDE=true; shift ;;
|
||||
--no-torch-compile) TORCH_COMPILE_OVERRIDE=false; shift ;;
|
||||
--nvenc) NVENC_OVERRIDE=true; shift ;;
|
||||
--no-nvenc) NVENC_OVERRIDE=false; shift ;;
|
||||
--) shift; while [[ $# -gt 0 ]]; do POSITIONAL+=("$1"); shift; done ;;
|
||||
-*) echo "error: unknown flag '$1'" >&2; usage >&2; exit 2 ;;
|
||||
*) POSITIONAL+=("$1"); shift ;;
|
||||
esac
|
||||
done
|
||||
set -- "${POSITIONAL[@]+"${POSITIONAL[@]}"}"
|
||||
|
||||
if [[ $# -lt 1 ]]; then
|
||||
usage >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
GPU="${1}"
|
||||
BACKEND_PORT="${2:-8009}"
|
||||
FRONTEND_PORT="${3:-5274}"
|
||||
|
||||
if ! [[ "${GPU}" =~ ^[0-9]+$ ]]; then
|
||||
echo "error: GPU must be a non-negative integer (got '${GPU}')" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
WARMUP="${WARMUP_OVERRIDE:-${DREAMVERSE_WARMUP:-false}}"
|
||||
case "${WARMUP}" in
|
||||
true|false) ;;
|
||||
*) echo "error: warmup must be 'true' or 'false' (got '${WARMUP}')" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
TORCH_COMPILE="${TORCH_COMPILE_OVERRIDE:-${DREAMVERSE_TORCH_COMPILE:-false}}"
|
||||
case "${TORCH_COMPILE}" in
|
||||
true|false) ;;
|
||||
*) echo "error: torch-compile must be 'true' or 'false' (got '${TORCH_COMPILE}')" >&2; exit 2 ;;
|
||||
esac
|
||||
TORCH_COMPILE_FLAG=$([[ "${TORCH_COMPILE}" == "true" ]] && echo 1 || echo 0)
|
||||
|
||||
NVENC="${NVENC_OVERRIDE:-${DREAMVERSE_NVENC:-false}}"
|
||||
case "${NVENC}" in
|
||||
true|false) ;;
|
||||
*) echo "error: nvenc must be 'true' or 'false' (got '${NVENC}')" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
REPO_ROOT="${DREAMVERSE_REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
||||
LOG_DIR="${DREAMVERSE_LOG_DIR:-/tmp/opencode/dreamverse-deploy}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Prereq checks
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
bail() { echo "error: $*" >&2; exit 3; }
|
||||
|
||||
[[ -d "${REPO_ROOT}/apps/dreamverse" ]] \
|
||||
|| bail "REPO_ROOT '${REPO_ROOT}' does not contain apps/dreamverse/. Are you on a migration branch?"
|
||||
DREAMVERSE_SERVER="$(command -v dreamverse-server 2>/dev/null || true)"
|
||||
[[ -n "${DREAMVERSE_SERVER}" ]] && [[ -x "${DREAMVERSE_SERVER}" ]] \
|
||||
|| bail "dreamverse-server not executable or not in PATH (run: uv pip install -e \".[dreamverse]\")"
|
||||
|
||||
CONDA_ENV_PYTHON="${DREAMVERSE_PYTHON:-${HOME}/miniconda3/envs/fv-main/bin/python}"
|
||||
[[ -x "${CONDA_ENV_PYTHON}" ]] \
|
||||
|| bail "conda env python missing at ${CONDA_ENV_PYTHON} (set DREAMVERSE_PYTHON to override)"
|
||||
"${CONDA_ENV_PYTHON}" -c 'import flashinfer' 2>/dev/null \
|
||||
|| bail "flashinfer-python not installed in ${CONDA_ENV_PYTHON} (run: ${CONDA_ENV_PYTHON} -m pip install flashinfer-python --no-build-isolation)"
|
||||
|
||||
NPM="${NPM:-npm}"
|
||||
NPM_REQUESTED="${NPM}"
|
||||
NPM="$(command -v "${NPM}" 2>/dev/null || true)"
|
||||
[[ -n "${NPM}" ]] && [[ -x "${NPM}" ]] || bail "npm not executable or not in PATH: ${NPM_REQUESTED} (set NPM to override)"
|
||||
|
||||
GCC13="$(command -v "${GCC13:-gcc-13}" 2>/dev/null || true)"
|
||||
GPP13="$(command -v "${GPP13:-g++-13}" 2>/dev/null || true)"
|
||||
[[ -n "${GCC13}" ]] && command -v "${GCC13}" >/dev/null 2>&1 \
|
||||
|| bail "gcc-13 not found or not executable (needed for nvcc workaround). Set GCC13 or install gcc-13 in PATH"
|
||||
[[ -n "${GPP13}" ]] && command -v "${GPP13}" >/dev/null 2>&1 \
|
||||
|| bail "g++-13 not found or not executable (needed for nvcc workaround). Set GPP13 or install g++-13 in PATH"
|
||||
|
||||
[[ -f "${HOME}/.env" ]] || echo "warn: ${HOME}/.env missing — provider API keys may be unset" >&2
|
||||
|
||||
NATIVE_FFMPEG_BIN="${HOME}/opt/ffmpeg-native/bin/ffmpeg"
|
||||
if [[ "${NVENC}" == "true" ]]; then
|
||||
NATIVE_VIDEO_CODEC=h264_nvenc
|
||||
else
|
||||
NATIVE_VIDEO_CODEC=libx264
|
||||
fi
|
||||
REQUIRE_NATIVE_FFMPEG="${DREAMVERSE_REQUIRE_NATIVE_FFMPEG:-false}"
|
||||
case "${REQUIRE_NATIVE_FFMPEG}" in
|
||||
true|false) ;;
|
||||
*) bail "DREAMVERSE_REQUIRE_NATIVE_FFMPEG must be 'true' or 'false' (got '${REQUIRE_NATIVE_FFMPEG}')" ;;
|
||||
esac
|
||||
if [[ -x "${NATIVE_FFMPEG_BIN}" ]]; then
|
||||
if [[ "${NVENC}" == "true" ]]; then
|
||||
encoder_list="$("${NATIVE_FFMPEG_BIN}" -hide_banner -encoders 2>/dev/null || true)"
|
||||
if [[ "${encoder_list}" != *h264_nvenc* ]]; then
|
||||
bail "--nvenc requested but ${NATIVE_FFMPEG_BIN} was not built with NVENC. Rebuild: bash apps/dreamverse/scripts/install_native_ffmpeg.sh (with ENABLE_NVENC=1, the default)"
|
||||
fi
|
||||
if ! "${NATIVE_FFMPEG_BIN}" -hide_banner -loglevel error -y \
|
||||
-f lavfi -i 'color=red:size=64x64:rate=24:duration=0.2' \
|
||||
-c:v h264_nvenc -f null - >/dev/null 2>&1; then
|
||||
bail "--nvenc requested but the GPU on this host has no NVENC silicon (probe failed: 'OpenEncodeSessionEx unsupported device'). Datacenter Blackwell (B200) and some H100 SKUs ship without NVENC; --nvenc only works on hosts with NVENC-capable GPUs (RTX 50-series, T4, A10, etc.)."
|
||||
fi
|
||||
fi
|
||||
echo " native ffmpeg: ${NATIVE_FFMPEG_BIN} (codec=${NATIVE_VIDEO_CODEC})"
|
||||
elif [[ "${REQUIRE_NATIVE_FFMPEG}" == "true" ]] || [[ "${NVENC}" == "true" ]]; then
|
||||
bail "${NATIVE_FFMPEG_BIN} missing (required by --nvenc or DREAMVERSE_REQUIRE_NATIVE_FFMPEG=true). Run: bash apps/dreamverse/scripts/install_native_ffmpeg.sh"
|
||||
else
|
||||
echo "warn: ${NATIVE_FFMPEG_BIN} missing — backend will fall back to system ffmpeg (\$(command -v ffmpeg))." >&2
|
||||
echo " Build native ffmpeg with: bash apps/dreamverse/scripts/install_native_ffmpeg.sh" >&2
|
||||
fi
|
||||
echo " python: ${CONDA_ENV_PYTHON}"
|
||||
|
||||
mkdir -p "${LOG_DIR}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Teardown anything on target ports
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
echo "[1/8] killing any existing deploy on ports ${BACKEND_PORT}/${FRONTEND_PORT} and GPU ${GPU}..."
|
||||
|
||||
kill_port_pid() {
|
||||
local port="$1"
|
||||
local pid
|
||||
|
||||
for pid in $(list_port_pids "${port}"); do
|
||||
terminate_pid "${pid}" "port=${port} pid=${pid}"
|
||||
done
|
||||
}
|
||||
|
||||
for pat in "dreamverse-server --host 0.0.0.0 --port ${BACKEND_PORT}" "next dev --port ${FRONTEND_PORT}" "NEXT_PUBLIC_INCLUDE_DEVTOOLS=1 next dev --port ${FRONTEND_PORT}"; do
|
||||
terminate_pattern "${pat}"
|
||||
done
|
||||
kill_port_pid "${BACKEND_PORT}"
|
||||
kill_port_pid "${FRONTEND_PORT}"
|
||||
|
||||
gpu_uuid="$(nvidia-smi --query-gpu=index,uuid --format=csv,noheader 2>/dev/null | awk -F', ' -v g="${GPU}" '$1==g {print $2}')"
|
||||
if [[ -n "${gpu_uuid}" ]]; then
|
||||
for pid in $(nvidia-smi --query-compute-apps=pid,gpu_uuid --format=csv,noheader 2>/dev/null \
|
||||
| awk -F', ' -v u="${gpu_uuid}" '$2==u {print $1}'); do
|
||||
if [[ -n "${pid}" ]] && [[ "${pid}" != "$$" ]]; then
|
||||
cmd="$(ps -p "${pid}" -o comm= 2>/dev/null || true)"
|
||||
terminate_pid "${pid}" "GPU${GPU} pid=${pid} (${cmd:-?})"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
for i in $(seq 1 30); do
|
||||
free_be=true
|
||||
free_fe=true
|
||||
ss -tln 2>/dev/null | grep -qE ":${BACKEND_PORT}\b" && free_be=false
|
||||
ss -tln 2>/dev/null | grep -qE ":${FRONTEND_PORT}\b" && free_fe=false
|
||||
gpu_mem="$(nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits 2>/dev/null | sed -n "$((GPU + 1))p" || echo 99999)"
|
||||
if "${free_be}" && "${free_fe}" && [[ "${gpu_mem}" -lt 1000 ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
gpu_mem="$(nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits 2>/dev/null | sed -n "$((GPU + 1))p" || echo 0)"
|
||||
echo " ports cleared; GPU${GPU} at ${gpu_mem} MiB"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Launch backend
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
echo "[2/8] launching backend on GPU ${GPU} port ${BACKEND_PORT} (warmup=${WARMUP} torch_compile=${TORCH_COMPILE} nvenc=${NVENC})..."
|
||||
|
||||
backend_log="${LOG_DIR}/backend-gpu${GPU}.log"
|
||||
: > "${backend_log}"
|
||||
|
||||
setsid bash -c "
|
||||
set -a
|
||||
if [[ -f \"${HOME}/.env\" ]]; then
|
||||
source \"${HOME}/.env\"
|
||||
fi
|
||||
set +a
|
||||
if [[ -x \"${NATIVE_FFMPEG_BIN}\" ]]; then
|
||||
export FASTVIDEO_FFMPEG_BIN=\"${NATIVE_FFMPEG_BIN}\"
|
||||
export FASTVIDEO_VIDEO_CODEC=\"${NATIVE_VIDEO_CODEC}\"
|
||||
fi
|
||||
export DREAMVERSE_PYTHON=\"${CONDA_ENV_PYTHON}\"
|
||||
export CUDA_VISIBLE_DEVICES=${GPU}
|
||||
export FASTVIDEO_ENABLE_DEVTOOLS=1
|
||||
export FASTVIDEO_ENABLE_STARTUP_WARMUP=${WARMUP}
|
||||
export FASTVIDEO_GPU_COUNT=1
|
||||
export ENABLE_TORCH_COMPILE=${TORCH_COMPILE_FLAG}
|
||||
export CC=${GCC13}
|
||||
export CXX=${GPP13}
|
||||
export CUDAHOSTCXX=${GPP13}
|
||||
export NVCC_PREPEND_FLAGS=\"-ccbin ${GCC13} -allow-unsupported-compiler\"
|
||||
cd \"${REPO_ROOT}\"
|
||||
exec \"${DREAMVERSE_SERVER}\" --host 0.0.0.0 --port ${BACKEND_PORT}
|
||||
" > "${backend_log}" 2>&1 < /dev/null &
|
||||
disown
|
||||
|
||||
# Wait briefly, then resolve actual python PID (the inner process, not the
|
||||
# wrapper bash).
|
||||
sleep 4
|
||||
backend_pid="$(pgrep -f "dreamverse-server --host 0.0.0.0 --port ${BACKEND_PORT}" | head -1 || true)"
|
||||
|
||||
if [[ -z "${backend_pid}" ]]; then
|
||||
echo "error: backend failed to spawn. Last 30 lines of log:" >&2
|
||||
tail -30 "${backend_log}" >&2
|
||||
exit 4
|
||||
fi
|
||||
|
||||
echo " backend pid=${backend_pid} log=${backend_log}"
|
||||
|
||||
# Poll /readyz. Deadline scales with warmup + torch.compile flags
|
||||
# because warmup runs two synthetic segments before /readyz=200, and
|
||||
# torch.compile max-autotune adds ~3-4min cold start to the first
|
||||
# segment. Empirical worst case (warmup=true, torch_compile=true):
|
||||
# ~7 min on B200; we budget 15 min for safety.
|
||||
if [[ "${WARMUP}" == "true" ]] && [[ "${TORCH_COMPILE}" == "true" ]]; then
|
||||
READYZ_BUDGET_SECONDS=900
|
||||
elif [[ "${WARMUP}" == "true" ]] || [[ "${TORCH_COMPILE}" == "true" ]]; then
|
||||
READYZ_BUDGET_SECONDS=480
|
||||
else
|
||||
READYZ_BUDGET_SECONDS=300
|
||||
fi
|
||||
READYZ_POLL_INTERVAL=6
|
||||
READYZ_MAX_ITERS=$(( READYZ_BUDGET_SECONDS / READYZ_POLL_INTERVAL ))
|
||||
|
||||
echo "[3/8] polling http://127.0.0.1:${BACKEND_PORT}/readyz (budget=${READYZ_BUDGET_SECONDS}s) ..."
|
||||
ready=0
|
||||
for i in $(seq 1 ${READYZ_MAX_ITERS}); do
|
||||
code="$(curl -s -o /dev/null -w '%{http_code}' --max-time 2 "http://127.0.0.1:${BACKEND_PORT}/readyz" 2>/dev/null || echo 000)"
|
||||
if [[ "${code}" == "200" ]]; then
|
||||
ready=1
|
||||
break
|
||||
fi
|
||||
if ! kill -0 "${backend_pid}" 2>/dev/null; then
|
||||
echo "error: backend pid ${backend_pid} died. Last 50 lines:" >&2
|
||||
tail -50 "${backend_log}" >&2
|
||||
exit 5
|
||||
fi
|
||||
sleep ${READYZ_POLL_INTERVAL}
|
||||
done
|
||||
|
||||
if [[ "${ready}" != "1" ]]; then
|
||||
echo "error: backend did not become /readyz=200 within ${READYZ_BUDGET_SECONDS}s. Last 50 lines:" >&2
|
||||
tail -50 "${backend_log}" >&2
|
||||
exit 5
|
||||
fi
|
||||
|
||||
echo "[4/8] backend /readyz OK"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Launch frontend
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
echo "[5/8] launching frontend on port ${FRONTEND_PORT}..."
|
||||
|
||||
frontend_log="${LOG_DIR}/frontend-port${FRONTEND_PORT}.log"
|
||||
: > "${frontend_log}"
|
||||
|
||||
# Resolve dev script: dev:devtools forces port 5274 + devtools env. If the
|
||||
# requested port differs, run `next dev --port` directly with devtools env.
|
||||
fe_cmd="run dev:devtools"
|
||||
if [[ "${FRONTEND_PORT}" != "5274" ]]; then
|
||||
fe_cmd="exec -- next dev --port ${FRONTEND_PORT}"
|
||||
fi
|
||||
|
||||
setsid bash -c "
|
||||
cd \"${REPO_ROOT}/apps/dreamverse/web\"
|
||||
export NEXT_PUBLIC_INCLUDE_DEVTOOLS=1
|
||||
export BACKEND_URL=http://127.0.0.1:${BACKEND_PORT}
|
||||
export BACKEND_HOST=127.0.0.1
|
||||
export BACKEND_PORT=${BACKEND_PORT}
|
||||
exec '${NPM}' ${fe_cmd}
|
||||
" > "${frontend_log}" 2>&1 < /dev/null &
|
||||
disown
|
||||
|
||||
sleep 4
|
||||
frontend_pid="$(pgrep -f "next dev --port ${FRONTEND_PORT}" | head -1 || true)"
|
||||
if [[ -z "${frontend_pid}" ]]; then
|
||||
echo "error: frontend failed to spawn. Last 30 lines:" >&2
|
||||
tail -30 "${frontend_log}" >&2
|
||||
exit 6
|
||||
fi
|
||||
|
||||
echo " frontend pid=${frontend_pid} log=${frontend_log}"
|
||||
|
||||
# Poll FE root
|
||||
echo "[6/8] polling http://127.0.0.1:${FRONTEND_PORT}/ ..."
|
||||
fe_ready=0
|
||||
for i in $(seq 1 30); do
|
||||
code="$(curl -s -o /dev/null -w '%{http_code}' --max-time 2 "http://127.0.0.1:${FRONTEND_PORT}/" 2>/dev/null || echo 000)"
|
||||
if [[ "${code}" == "200" ]]; then
|
||||
fe_ready=1
|
||||
break
|
||||
fi
|
||||
if ! kill -0 "${frontend_pid}" 2>/dev/null; then
|
||||
echo "error: frontend pid ${frontend_pid} died. Last 30 lines:" >&2
|
||||
tail -30 "${frontend_log}" >&2
|
||||
exit 7
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [[ "${fe_ready}" != "1" ]]; then
|
||||
echo "error: frontend did not respond 200 within 60s. Last 30 lines:" >&2
|
||||
tail -30 "${frontend_log}" >&2
|
||||
exit 7
|
||||
fi
|
||||
|
||||
echo "[7/8] frontend / OK"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Print summary
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
cwd="$(readlink "/proc/${backend_pid}/cwd" 2>/dev/null || echo unknown)"
|
||||
gpu_mem_now="$(nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits 2>/dev/null | sed -n "$((GPU + 1))p" || echo 0)"
|
||||
ffmpeg_in_use="$(tr '\0' '\n' < "/proc/${backend_pid}/environ" 2>/dev/null | sed -n 's/^FASTVIDEO_FFMPEG_BIN=//p' | head -1)"
|
||||
[[ -z "${ffmpeg_in_use}" ]] && ffmpeg_in_use="$(command -v ffmpeg 2>/dev/null || echo '<not found>') (system fallback)"
|
||||
|
||||
cat <<SUMMARY
|
||||
[8/8] redeploy OK
|
||||
|
||||
Frontend : http://localhost:${FRONTEND_PORT} (PID ${frontend_pid})
|
||||
Backend : http://localhost:${BACKEND_PORT} (PID ${backend_pid})
|
||||
cwd=${cwd}
|
||||
gpu=${GPU} mem=${gpu_mem_now} MiB
|
||||
ffmpeg=${ffmpeg_in_use}
|
||||
|
||||
Logs : ${backend_log}
|
||||
${frontend_log}
|
||||
|
||||
Stop : ./.agents/skills/dreamverse-deploy/scripts/dreamverse-deploy.sh --stop
|
||||
|
||||
E2E : cd apps/dreamverse/web && \\
|
||||
PLAYWRIGHT_SKIP_WEBSERVER=1 \\
|
||||
BACKEND_URL=http://127.0.0.1:${BACKEND_PORT} \\
|
||||
PLAYWRIGHT_BASE_URL=http://127.0.0.1:${FRONTEND_PORT} \\
|
||||
NEXT_PUBLIC_INCLUDE_DEVTOOLS=1 \\
|
||||
npm exec -- playwright test
|
||||
SUMMARY
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
name: env-var-conventions
|
||||
description: Add, read, rename, or remove an environment variable in FastVideo, or change the environment-variable policy. Use before touching fastvideo/envs.py, os.environ, os.getenv, or monkeypatch.setenv in fastvideo/, and when fastvideo/tests/contract/test_env_policy.py fails.
|
||||
---
|
||||
|
||||
# Environment Variable Conventions
|
||||
|
||||
## Purpose
|
||||
|
||||
FastVideo registers its environment variables as typed fields in
|
||||
`fastvideo/envs.py`. The policy that governs them is
|
||||
`docs/contributing/env_vars.md`, and the contract test
|
||||
`fastvideo/tests/contract/test_env_policy.py` enforces the policy in the unit
|
||||
CI lane. This skill routes an environment-variable change through that policy.
|
||||
The policy doc is the single source of the rules; read it instead of relying
|
||||
on a summary here.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Read `docs/contributing/env_vars.md` in full.
|
||||
- Decide whether the setting belongs in an environment variable or an argument
|
||||
(rule 5 in the policy doc). Settings that users change per deployment are
|
||||
arguments; add them as typed config fields in `fastvideo/api/schema.py`
|
||||
instead.
|
||||
|
||||
## Inputs
|
||||
|
||||
| Parameter | Required | Description |
|
||||
| ---------- | -------- | -------------------------------------------------------------- |
|
||||
| `change` | Yes | Add, read, rename, or remove a variable, or change the policy. |
|
||||
| `variable` | Yes | The variable name, with the `FASTVIDEO_` prefix. |
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Declare or edit the variable in `fastvideo/envs.py`.**
|
||||
- Pick the field type and category that the policy doc lists.
|
||||
- Write a description that states what the variable does and its units.
|
||||
- To rename, keep the old name in `deprecated_names`. To remove, add the
|
||||
name to `DEPRECATED_VARIABLES`. Update the uses in `examples/`,
|
||||
`scripts/`, `docs/`, `apps/`, and the tests.
|
||||
2. **Read the variable with `envs.NAME.get()` inside a function.**
|
||||
- In tests, change the value with `envs.NAME.override(value)`, and a variable
|
||||
outside the registry with `envs.override_external(name, value)`; the
|
||||
`env_overrides` fixture keeps either until the end of the test.
|
||||
- Name a variable that only tests read `FASTVIDEO_TEST_*`.
|
||||
- Do not call `os.environ`, `os.getenv`, or `monkeypatch.setenv` for a
|
||||
FastVideo variable.
|
||||
- To set a variable that another tool reads, call `envs.set_external`,
|
||||
`envs.setdefault_external`, or `envs.unset_external`.
|
||||
3. **Regenerate the table in the policy doc.**
|
||||
- Run `python fastvideo/tests/contract/test_env_policy.py`.
|
||||
4. **Run the contract test.**
|
||||
- Run `pytest fastvideo/tests/contract/test_env_policy.py`.
|
||||
- When the test reports a fixed known violation, delete or lower its entry
|
||||
in `KNOWN_VIOLATIONS`. Never add an entry to `KNOWN_VIOLATIONS`.
|
||||
5. **When the policy itself changes, update the policy doc and the contract
|
||||
test in the same pull request.**
|
||||
- The rules in `docs/contributing/env_vars.md`, the checks and allowlist in
|
||||
`fastvideo/tests/contract/test_env_policy.py`, and this skill must agree.
|
||||
|
||||
## Outputs
|
||||
|
||||
- A registry entry in `fastvideo/envs.py` and call sites that use
|
||||
`envs.NAME.get()`.
|
||||
- A regenerated table in `docs/contributing/env_vars.md`.
|
||||
- A passing `fastvideo/tests/contract/test_env_policy.py`.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
Add a FASTVIDEO_DEBUG_MY_STAGE switch that logs MyStage inputs.
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- `docs/contributing/env_vars.md`: the policy, the field types, and the
|
||||
violation kinds that the contract test reports.
|
||||
- `fastvideo/envs.py`: the registry.
|
||||
- `fastvideo/tests/contract/test_env_policy.py`: the contract test,
|
||||
`EXTERNAL_ALLOWLIST`, and `KNOWN_VIOLATIONS`.
|
||||
@@ -1,128 +0,0 @@
|
||||
---
|
||||
name: evaluate-video-quality
|
||||
description: Evaluate generated video quality using available metrics (SSIM, loss trajectory, caption consistency)
|
||||
---
|
||||
|
||||
# Evaluate Video Quality
|
||||
|
||||
## Purpose
|
||||
Assess the quality of videos generated by a training run. Combines multiple
|
||||
signals to give a holistic quality assessment. This skill is **evolving** —
|
||||
new metrics will be added as they are developed.
|
||||
|
||||
## Prerequisites
|
||||
- Generated videos available locally or via W&B artifacts.
|
||||
- For SSIM: reference videos from official implementations.
|
||||
- For caption consistency: LLM access (optional, stub for now).
|
||||
|
||||
## Inputs
|
||||
|
||||
| Parameter | Required | Description |
|
||||
|-----------|----------|-------------|
|
||||
| `video_paths` | Yes | List of paths to generated videos |
|
||||
| `reference_paths` | No | Paths to reference videos (for SSIM) |
|
||||
| `prompts` | No | Prompts used to generate videos (for caption check) |
|
||||
| `loss_summary` | No | Path to W&B summary JSON (for loss trajectory) |
|
||||
| `metrics` | No | Which metrics to run (default: all available) |
|
||||
|
||||
## Available Metrics
|
||||
|
||||
Check `.agents/memory/evaluation-registry/README.md` for the current catalog.
|
||||
|
||||
### SSIM (Active)
|
||||
|
||||
Leverages the existing infrastructure in `fastvideo/tests/ssim/`.
|
||||
|
||||
```bash
|
||||
pytest fastvideo/tests/ssim/ -vs --video-path <generated> --reference-path <reference>
|
||||
```
|
||||
|
||||
Or use the SSIM utility directly:
|
||||
|
||||
```python
|
||||
from fastvideo.tests.ssim.ssim_utils import compute_ssim
|
||||
score = compute_ssim(generated_video, reference_video)
|
||||
# score > 0.85 is typically "acceptable"
|
||||
```
|
||||
|
||||
**Interpretation**:
|
||||
| SSIM Range | Quality |
|
||||
|------------|---------|
|
||||
| > 0.90 | Excellent — very close to reference |
|
||||
| 0.80–0.90 | Good — acceptable for most uses |
|
||||
| 0.70–0.80 | Fair — noticeable differences |
|
||||
| < 0.70 | Poor — significant quality issues |
|
||||
|
||||
### Loss Trajectory (Active)
|
||||
|
||||
Analyze the loss curve shape from W&B summary:
|
||||
|
||||
```python
|
||||
import json
|
||||
with open(loss_summary_path) as f:
|
||||
summary = json.load(f)
|
||||
|
||||
final_loss = summary["train_loss"]
|
||||
runtime = summary["_runtime"]
|
||||
steps = summary["_step"]
|
||||
```
|
||||
|
||||
**Early-stage heuristics** (first 500 steps):
|
||||
- Loss should be decreasing (even slightly).
|
||||
- Grad norm should be stable (no wild oscillations).
|
||||
- If loss is flat or increasing, flag for review.
|
||||
|
||||
### Caption Consistency (Draft — Not Yet Calibrated)
|
||||
|
||||
Use an LLM to evaluate whether the video content matches the input prompt.
|
||||
|
||||
```
|
||||
Prompt: "A golden retriever playing in the snow"
|
||||
Video: <path>
|
||||
|
||||
Score the video on:
|
||||
1. Object presence (is there a golden retriever?)
|
||||
2. Action accuracy (is it playing?)
|
||||
3. Environment match (is there snow?)
|
||||
4. Overall coherence (does it look natural?)
|
||||
|
||||
Each 1-5, total /20.
|
||||
```
|
||||
|
||||
> ⚠️ This metric is in **draft** status. Results should not be treated as
|
||||
> ground truth until calibrated against human judgments.
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Identify available metrics** — Check `.agents/memory/evaluation-registry/README.md`.
|
||||
2. **Run each metric** — Collect scores.
|
||||
3. **Aggregate** — Produce a combined quality report.
|
||||
4. **Log** — Update the experiment journal with quality results.
|
||||
|
||||
## Outputs
|
||||
|
||||
```markdown
|
||||
## Video Quality Report: <experiment_name>
|
||||
|
||||
| Metric | Score | Threshold | Status |
|
||||
|--------|-------|-----------|--------|
|
||||
| SSIM (avg) | 0.87 | > 0.80 | ✅ Pass |
|
||||
| Loss trajectory | decreasing | decreasing | ✅ Pass |
|
||||
| Caption consistency | 16/20 | > 14/20 | ✅ Pass |
|
||||
|
||||
### Per-Video Scores
|
||||
| Video | SSIM | Caption |
|
||||
|-------|------|---------|
|
||||
| video_001.mp4 | 0.89 | 17/20 |
|
||||
| video_002.mp4 | 0.85 | 15/20 |
|
||||
```
|
||||
|
||||
## References
|
||||
- `fastvideo/tests/ssim/` — SSIM test infrastructure
|
||||
- `fastvideo/tests/training/Vanilla/test_training_loss.py` — loss comparison
|
||||
- `.agents/memory/evaluation-registry/README.md` — metric catalog
|
||||
|
||||
## Changelog
|
||||
| Date | Change |
|
||||
|------|--------|
|
||||
| 2026-03-02 | Initial version with SSIM, loss trajectory, caption consistency stub |
|
||||
@@ -1,94 +0,0 @@
|
||||
---
|
||||
name: index-related-work
|
||||
description: Ingest a paper or repository into the related work index
|
||||
---
|
||||
|
||||
# Index Related Work
|
||||
|
||||
## Purpose
|
||||
Create a structured summary of a related paper, repository, or blog post and
|
||||
add it to `.agents/memory/related-work/` for future reference. This builds the
|
||||
agent's knowledge base for making informed decisions about training, evaluation,
|
||||
and architecture choices.
|
||||
|
||||
## Prerequisites
|
||||
- Access to the paper/repo (URL, PDF, or local clone).
|
||||
|
||||
## Inputs
|
||||
|
||||
| Parameter | Required | Description |
|
||||
|-----------|----------|-------------|
|
||||
| `source` | Yes | URL, citation, or local path |
|
||||
| `type` | Yes | `paper`, `repo`, or `blog` |
|
||||
| `tags` | No | List of tags (default: inferred from content) |
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Extract key information
|
||||
|
||||
For **papers**: Read abstract, method section, experimental setup, and results.
|
||||
For **repos**: Read README, key source files, and training scripts.
|
||||
For **blogs**: Read the full post.
|
||||
|
||||
Focus on:
|
||||
- What problem does it solve?
|
||||
- What architecture/technique is used?
|
||||
- How does it relate to FastVideo's approach?
|
||||
|
||||
### 2. Create the index entry
|
||||
|
||||
Write to `.agents/memory/related-work/<slug>.md`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: <title>
|
||||
source: <URL or citation>
|
||||
type: paper | repo | blog
|
||||
date_indexed: <ISO-8601>
|
||||
tags: [world-model, distillation, evaluation, ...]
|
||||
---
|
||||
|
||||
## Summary
|
||||
<1-2 paragraph summary.>
|
||||
|
||||
## Key Differences from FastVideo
|
||||
- <comparison points>
|
||||
|
||||
## Actionable Insights
|
||||
- <what we could adopt or adapt>
|
||||
```
|
||||
|
||||
### 3. Update the catalog
|
||||
|
||||
If `.agents/memory/related-work/_catalog.md` exists, append the new entry.
|
||||
If not, create it:
|
||||
|
||||
```markdown
|
||||
# Related Work Catalog
|
||||
|
||||
| Slug | Title | Type | Tags | Date |
|
||||
|------|-------|------|------|------|
|
||||
| <slug> | <title> | <type> | <tags> | <date> |
|
||||
```
|
||||
|
||||
## Outputs
|
||||
- New file in `.agents/memory/related-work/<slug>.md`.
|
||||
- Updated catalog.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
Index the Self-Forcing paper:
|
||||
|
||||
source: https://arxiv.org/abs/2406.xxxxx
|
||||
type: paper
|
||||
tags: [world-model, self-forcing, distillation]
|
||||
```
|
||||
|
||||
## References
|
||||
- `.agents/memory/related-work/README.md` — schema documentation
|
||||
|
||||
## Changelog
|
||||
| Date | Change |
|
||||
|------|--------|
|
||||
| 2026-03-02 | Initial version |
|
||||
@@ -1,10 +0,0 @@
|
||||
{"name": "launch-experiment", "description": "Generate and execute a training launch command for FastVideo models", "path": "launch-experiment/SKILL.md", "status": "draft", "trust": "low"}
|
||||
{"name": "monitor-experiment", "description": "Poll a running W&B training run for progress and emit structured alerts", "path": "monitor-experiment/SKILL.md", "status": "draft", "trust": "low"}
|
||||
{"name": "summarize-run", "description": "Extract a W&B run summary into a structured experiment report", "path": "summarize-run/SKILL.md", "status": "draft", "trust": "low"}
|
||||
{"name": "log-experiment", "description": "Append or update an experiment entry in the experiment journal", "path": "log-experiment/SKILL.md", "status": "draft", "trust": "low"}
|
||||
{"name": "evaluate-video-quality", "description": "Evaluate generated video quality using available metrics (SSIM, loss trajectory, caption consistency)", "path": "evaluate-video-quality/SKILL.md", "status": "draft", "trust": "low"}
|
||||
{"name": "index-related-work", "description": "Ingest a paper or repository into the related work index", "path": "index-related-work/SKILL.md", "status": "draft", "trust": "low"}
|
||||
{"name": "search-related-work", "description": "Query the related work index for relevant papers, repos, or comparisons", "path": "search-related-work/SKILL.md", "status": "draft", "trust": "low"}
|
||||
{"name": "seed-ssim-references", "description": "Run a new or updated fastvideo/tests/ssim/ test on Modal, pull generated videos, and upload them to FastVideo/ssim-reference-videos so the test has a regression baseline", "path": "seed-ssim-references/SKILL.md", "status": "draft", "trust": "low"}
|
||||
{"name": "reseed-ssim-references", "description": "Re-seed (overwrite) HF reference videos for an existing fastvideo/tests/ssim/ test and a single model id on Modal L40S. Always backs up current refs first, regenerates on Modal, pauses for the user to eyeball before-vs-after, then uploads with --force scoped to --model-id. Sister skill to seed-ssim-references; use when intentional code change has invalidated existing refs", "path": "reseed-ssim-references/SKILL.md", "status": "draft", "trust": "low"}
|
||||
{"name": "decompose-pipeline-pr", "description": "Decompose an oversized FastVideo pipeline PR into a stack of independently-reviewable PRs. Tiers the diff by blast radius (invisible / dead code / cross-cutting infra / activation), produces a branch graph and worktree bootstrap, drafts the AGENTS.md manifest, flags missing tests on cross-cutting infra changes, and extracts lessons from the PR body. Worked example: PR #1280 daVinci-MagiHuman (9.8k LOC) decomposed into 10 stacked PRs.", "path": "decompose-pipeline-pr/SKILL.md", "status": "tested", "trust": "medium"}
|
||||
@@ -1,127 +0,0 @@
|
||||
---
|
||||
name: launch-experiment
|
||||
description: Generate and execute a training launch command for FastVideo models
|
||||
---
|
||||
|
||||
# Launch Experiment
|
||||
|
||||
## Purpose
|
||||
Construct a fully-specified `torchrun` training command for a FastVideo model
|
||||
given a target pipeline, dataset, and hyperparameter overrides. This skill
|
||||
automates the boilerplate of setting environment variables, picking the right
|
||||
entrypoint, and applying defaults from the closest example script.
|
||||
|
||||
## Prerequisites
|
||||
- The repo is cloned and `fastvideo` is installed (`uv pip install -e ".[dev]"`).
|
||||
- Dataset is preprocessed (see `docs/training/data_preprocess.md`).
|
||||
- `WANDB_API_KEY` is set in the environment (or `WANDB_MODE=offline` for local).
|
||||
- GPU resources are available (multi-GPU requires NCCL).
|
||||
|
||||
## Inputs
|
||||
|
||||
| Parameter | Required | Description |
|
||||
|-----------|----------|-------------|
|
||||
| `pipeline` | Yes | Training pipeline type: `finetune`, `distill-dmd`, `self-forcing`, `lora`, `consistency` |
|
||||
| `model` | Yes | Model family: `wan-t2v-1.3B`, `wan-i2v-14B`, `ltx2`, `matrixgame` |
|
||||
| `data_path` | Yes | Path to preprocessed dataset (parquet) |
|
||||
| `num_gpus` | Yes | Number of GPUs |
|
||||
| `overrides` | No | Dict of hyperparameter overrides (any CLI arg) |
|
||||
| `output_dir` | No | Output directory (default: `outputs/<model>_<pipeline>`) |
|
||||
| `run_name` | No | W&B run name (default: auto-generated) |
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Identify the training entrypoint
|
||||
|
||||
| Pipeline | Entrypoint |
|
||||
|----------|-----------|
|
||||
| `finetune` (Wan T2V) | `fastvideo/training/wan_training_pipeline.py` |
|
||||
| `finetune` (Wan I2V) | `fastvideo/training/wan_i2v_training_pipeline.py` |
|
||||
| `finetune` (LTX-2) | `fastvideo/training/ltx2_training_pipeline.py` |
|
||||
| `finetune` (MatrixGame) | `fastvideo/training/matrixgame_training_pipeline.py` |
|
||||
| `distill-dmd` | `fastvideo/training/wan_distillation_pipeline.py` |
|
||||
| `self-forcing` | `fastvideo/training/wan_self_forcing_distillation_pipeline.py` |
|
||||
|
||||
### 2. Resolve default hyperparameters
|
||||
|
||||
Find the closest example script in `examples/training/` for the model:
|
||||
|
||||
| Model | Example Script Directory |
|
||||
|-------|-------------------------|
|
||||
| `wan-t2v-1.3B` | `examples/training/finetune/wan_t2v_1.3B/crush_smol/` |
|
||||
| `wan-i2v-14B` | `examples/training/finetune/wan_i2v_14B_480p/crush_smol/` |
|
||||
| `ltx2` | `examples/training/finetune/ltx2/` |
|
||||
| `matrixgame` | `examples/training/finetune/MatrixGame2.0/` |
|
||||
| `distill-dmd` | `scripts/distill/v1_distill_dmd_wan.sh` |
|
||||
|
||||
Read the script to extract default values for:
|
||||
- `--learning_rate`, `--train_batch_size`, `--sp_size`, `--tp_size`
|
||||
- `--num_latent_t`, `--num_height`, `--num_width`, `--num_frames`
|
||||
- `--gradient_accumulation_steps`, `--max_train_steps`
|
||||
- `--mixed_precision`, `--weight_decay`, `--max_grad_norm`
|
||||
- `--validation_steps`, `--validation_sampling_steps`
|
||||
|
||||
### 3. Set environment variables
|
||||
|
||||
```bash
|
||||
export WANDB_API_KEY="${WANDB_API_KEY}"
|
||||
export WANDB_BASE_URL="https://api.wandb.ai"
|
||||
export FASTVIDEO_ATTENTION_BACKEND=FLASH_ATTN
|
||||
export TOKENIZERS_PARALLELISM=false
|
||||
export TRITON_CACHE_DIR=/tmp/triton_cache
|
||||
```
|
||||
|
||||
### 4. Construct the torchrun command
|
||||
|
||||
```bash
|
||||
torchrun --nnodes 1 --nproc_per_node <num_gpus> \
|
||||
<entrypoint> \
|
||||
--pretrained_model_name_or_path <model_hf_id> \
|
||||
--data_path "<data_path>" \
|
||||
--output_dir "<output_dir>" \
|
||||
--wandb_run_name "<run_name>" \
|
||||
--tracker_project_name "<project_name>" \
|
||||
--log_validation \
|
||||
<...all hyperparameters...>
|
||||
```
|
||||
|
||||
### 5. Log to experiment journal
|
||||
|
||||
After launching, append an entry to `.agents/memory/experiment-journal/README.md`:
|
||||
|
||||
```markdown
|
||||
## [YYYY-MM-DD] Experiment: <run_name>
|
||||
- **Hypothesis**: <user-provided or auto-generated>
|
||||
- **Config**: model=<model>, lr=<lr>, sp_size=<sp>, gpus=<n>, script=<entrypoint>
|
||||
- **W&B run**: <pending — will be updated by monitor skill>
|
||||
- **Status**: running
|
||||
```
|
||||
|
||||
## Outputs
|
||||
- A ready-to-execute shell command.
|
||||
- An experiment journal entry.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
Launch a Wan T2V 1.3B finetune on 4 GPUs with lr=5e-5 and max_train_steps=1000:
|
||||
|
||||
pipeline: finetune
|
||||
model: wan-t2v-1.3B
|
||||
data_path: data/crush_smol_preprocessed/
|
||||
num_gpus: 4
|
||||
overrides:
|
||||
learning_rate: 5e-5
|
||||
max_train_steps: 1000
|
||||
```
|
||||
|
||||
## References
|
||||
- `examples/training/finetune/wan_t2v_1.3B/crush_smol/finetune_t2v.sh`
|
||||
- `scripts/distill/v1_distill_dmd_wan.sh`
|
||||
- `docs/training/finetune.md` (training arguments table)
|
||||
- `fastvideo/training/trackers.py` (tracker initialization)
|
||||
|
||||
## Changelog
|
||||
| Date | Change |
|
||||
|------|--------|
|
||||
| 2026-03-02 | Initial version |
|
||||
@@ -1,87 +0,0 @@
|
||||
---
|
||||
name: log-experiment
|
||||
description: Append or update an experiment entry in the experiment journal
|
||||
---
|
||||
|
||||
# Log Experiment
|
||||
|
||||
## Purpose
|
||||
Create or update an entry in `.agents/memory/experiment-journal/README.md` to maintain
|
||||
a living record of all experiments and their outcomes.
|
||||
|
||||
## Prerequisites
|
||||
- `.agents/memory/experiment-journal/README.md` exists.
|
||||
|
||||
## Inputs
|
||||
|
||||
| Parameter | Required | Description |
|
||||
|-----------|----------|-------------|
|
||||
| `name` | Yes | Experiment name / identifier |
|
||||
| `hypothesis` | No | What you expected to learn |
|
||||
| `config` | Yes | Key config: model, lr, sp_size, gpus, script |
|
||||
| `wandb_run` | No | W&B run ID or URL |
|
||||
| `duration` | No | Total wall time |
|
||||
| `metrics` | No | Key metrics dict (loss, step_time, grad_norm) |
|
||||
| `checkpoint` | No | Path to checkpoint |
|
||||
| `insight` | No | What was learned |
|
||||
| `status` | Yes | `running`, `completed`, `failed`, `abandoned` |
|
||||
| `lessons` | No | Paths to related lesson files |
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Check for existing entry
|
||||
|
||||
Search `.agents/memory/experiment-journal/README.md` for an entry with the same name.
|
||||
If found, update it instead of creating a duplicate.
|
||||
|
||||
### 2. Format the entry
|
||||
|
||||
```markdown
|
||||
## [YYYY-MM-DD] Experiment: <name>
|
||||
- **Hypothesis**: <hypothesis or "N/A">
|
||||
- **Config**: model=<model>, lr=<lr>, sp_size=<sp>, gpus=<n>, script=<script>
|
||||
- **W&B run**: <wandb_run or "pending">
|
||||
- **Duration**: <duration or "in progress">
|
||||
- **Key metrics**: loss=<loss>, step_time=<step_time>, grad_norm=<grad_norm>
|
||||
- **Checkpoint**: <checkpoint or "N/A">
|
||||
- **Insight**: <insight or "pending">
|
||||
- **Status**: <status>
|
||||
- **Related lessons**: <lessons or "none">
|
||||
```
|
||||
|
||||
### 3. Insert at the top of the journal
|
||||
|
||||
New entries go at the top of the file (after the header), so the most recent
|
||||
experiments are always visible first.
|
||||
|
||||
### 4. Warn on duplicates
|
||||
|
||||
If a similar experiment name exists with `status: completed`, warn that this
|
||||
may be a repeat. If it's `status: running`, assume this is an update.
|
||||
|
||||
## Outputs
|
||||
- Updated `.agents/memory/experiment-journal/README.md`.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
Log a completed experiment:
|
||||
|
||||
name: wan-t2v-finetune-lr5e5-sp4
|
||||
config: model=wan-t2v-1.3B, lr=5e-5, sp_size=4, gpus=4
|
||||
wandb_run: fastvideo/training/run_abc123
|
||||
duration: 2h 15m
|
||||
metrics: {loss: 0.065, step_time: 2.3, grad_norm: 0.35}
|
||||
checkpoint: outputs/wan_finetune/checkpoint-1000
|
||||
insight: LR 5e-5 converges 30% faster than 1e-5 with no quality loss
|
||||
status: completed
|
||||
```
|
||||
|
||||
## References
|
||||
- `.agents/memory/experiment-journal/README.md` — journal file
|
||||
- `.agents/workflows/experiment-lifecycle.md` — when to log
|
||||
|
||||
## Changelog
|
||||
| Date | Change |
|
||||
|------|--------|
|
||||
| 2026-03-02 | Initial version |
|
||||
@@ -1,134 +0,0 @@
|
||||
---
|
||||
name: monitor-experiment
|
||||
description: Poll a running W&B training run for progress and emit structured alerts
|
||||
---
|
||||
|
||||
# Monitor Experiment
|
||||
|
||||
## Purpose
|
||||
Continuously (or on-demand) check a running experiment's W&B metrics and emit
|
||||
alerts for anomalies. Supports the "30-minute quality check" paradigm: after
|
||||
the first 30 minutes of a long training run, produce a checkpoint quality
|
||||
report before committing more resources.
|
||||
|
||||
## Prerequisites
|
||||
- `WANDB_API_KEY` is set in the environment.
|
||||
- The experiment is actively logging to W&B (not in `WANDB_MODE=offline`).
|
||||
- For offline mode: read from local `wandb-summary.json` instead.
|
||||
|
||||
## Inputs
|
||||
|
||||
| Parameter | Required | Description |
|
||||
|-----------|----------|-------------|
|
||||
| `run_id` | Yes* | W&B run ID (e.g., `entity/project/run_id`) |
|
||||
| `output_dir` | Yes* | Local output directory (for offline mode fallback) |
|
||||
| `poll_interval` | No | Seconds between polls (default: 60) |
|
||||
| `alert_on` | No | List of alert conditions to enable (default: all) |
|
||||
|
||||
\* One of `run_id` or `output_dir` is required.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Connect to the run
|
||||
|
||||
**Online mode** (preferred):
|
||||
|
||||
```python
|
||||
import wandb
|
||||
api = wandb.Api()
|
||||
run = api.run("<run_id>")
|
||||
```
|
||||
|
||||
**Offline fallback**:
|
||||
|
||||
```python
|
||||
import json
|
||||
summary_path = f"{output_dir}/tracker/wandb/latest-run/files/wandb-summary.json"
|
||||
with open(summary_path) as f:
|
||||
summary = json.load(f)
|
||||
```
|
||||
|
||||
### 2. Track key metrics
|
||||
|
||||
| Metric | W&B Key | Description |
|
||||
|--------|---------|-------------|
|
||||
| Training loss | `train_loss` | Primary training loss |
|
||||
| Gradient norm | `grad_norm` | Gradient magnitude |
|
||||
| Step time | `step_time` | Wall-clock seconds per step |
|
||||
| Learning rate | `learning_rate` | Current LR |
|
||||
| Avg step time | `avg_step_time` | Running average step time |
|
||||
| Validation videos | `validation_videos_*` | Generated validation samples |
|
||||
|
||||
### 3. Evaluate alert conditions
|
||||
|
||||
| Alert | Condition | Severity |
|
||||
|-------|-----------|----------|
|
||||
| **Loss spike** | `current_loss > 3 × rolling_avg_loss` | 🔴 Critical |
|
||||
| **NaN/Inf gradient** | `grad_norm` is NaN or Inf | 🔴 Critical |
|
||||
| **Step time regression** | `step_time > 2 × baseline_step_time` | 🟡 Warning |
|
||||
| **No progress** | No new W&B logs for > 10 minutes | 🟡 Warning |
|
||||
| **Loss plateau** | Loss change < 1% over last 100 steps | 🟢 Info |
|
||||
|
||||
### 4. Emit structured status
|
||||
|
||||
Output format (agent-consumable):
|
||||
|
||||
```json
|
||||
{
|
||||
"run_id": "...",
|
||||
"step": 500,
|
||||
"metrics": {
|
||||
"train_loss": 0.078,
|
||||
"grad_norm": 0.41,
|
||||
"step_time": 2.5,
|
||||
"learning_rate": 1e-6
|
||||
},
|
||||
"alerts": [
|
||||
{"type": "loss_spike", "severity": "critical", "message": "Loss jumped to 0.45 (avg: 0.08)"}
|
||||
],
|
||||
"status": "running"
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 30-Minute Quality Check
|
||||
|
||||
After the first 30 minutes of wall-clock time:
|
||||
1. Summarize the loss curve shape (decreasing? at what rate?).
|
||||
2. Check if validation videos have been generated.
|
||||
3. Report step count, loss at start vs. current, and estimated time to completion.
|
||||
4. Produce a go/no-go recommendation.
|
||||
|
||||
```markdown
|
||||
## 30-Minute Check: <run_name>
|
||||
- **Steps completed**: 150
|
||||
- **Loss**: 0.12 → 0.08 (↓ 33%)
|
||||
- **Grad norm**: stable at ~0.4
|
||||
- **Step time**: 2.5s/step (consistent)
|
||||
- **Validation videos**: 5 generated at step 100
|
||||
- **Recommendation**: ✅ Continue — loss is decreasing normally
|
||||
```
|
||||
|
||||
## Outputs
|
||||
- Structured JSON status updates.
|
||||
- Alert messages for anomalous conditions.
|
||||
- 30-minute checkpoint quality report.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
Monitor W&B run "fastvideo/Wan_distillation/abc123":
|
||||
|
||||
run_id: fastvideo/Wan_distillation/abc123
|
||||
poll_interval: 120
|
||||
alert_on: [loss_spike, nan_gradient, step_time_regression]
|
||||
```
|
||||
|
||||
## References
|
||||
- `fastvideo/training/trackers.py` — `WandbTracker` implementation
|
||||
- `fastvideo/tests/training/Vanilla/test_training_loss.py` — how summaries are compared
|
||||
- `fastvideo/tests/training/Vanilla/a40_reference_wandb_summary.json` — reference summary format
|
||||
|
||||
## Changelog
|
||||
| Date | Change |
|
||||
|------|--------|
|
||||
| 2026-03-02 | Initial version |
|
||||
@@ -0,0 +1,642 @@
|
||||
---
|
||||
name: reseed-performance-baseline
|
||||
description: Re-seed the HF performance-tracking baseline for an intentional runtime, dependency, environment-caused benchmark shift, or reviewed v2 calibration using one or more reviewed normalized performance JSONs. Use when performance CI fails because metrics such as latency, throughput, component time, or peak memory changed for an accepted reason and the rolling median baseline in FastVideo/performance-tracking must be advanced, or when a new v2 exact comparable identity needs its first approved baseline. The workflow backs up existing history under /tmp, validates all source JSONs for the same legacy (model_id, gpu_type) target or the same v2 exact identity, rejects internally inconsistent source batches, uploads one success=true baseline record per accepted source JSON, and offers to clean local temp state after a successful upload.
|
||||
---
|
||||
|
||||
# Re-seed Performance Baseline
|
||||
|
||||
## Purpose
|
||||
|
||||
Replace or advance the rolling performance baseline in the HF dataset
|
||||
`FastVideo/performance-tracking`. Legacy targets are scoped by
|
||||
`(model_id, gpu_type)`. V2 targets are scoped by exact comparable identity:
|
||||
`workload_id`, `variant_id`, `benchmark_version`, `hardware_profile_id`,
|
||||
`software_profile_id`, and `recipe_fingerprint`.
|
||||
|
||||
Performance comparison uses the median of up to the last 5 successful,
|
||||
baseline-eligible records for the same target. Failed or calibration-only
|
||||
records are useful audit history, but they do not move the future baseline
|
||||
because `compare_baseline.py` loads records with `successful_only=True` and
|
||||
`baseline_eligible_only=True`.
|
||||
|
||||
This skill now reseeds from a reviewed batch of one or more source performance
|
||||
JSONs. It uploads one new `success=true` record per accepted source JSON; it
|
||||
does not blindly replicate one measurement into 3 or 5 records. The effective
|
||||
reseed size is therefore dynamic and equals the number of provided, validated,
|
||||
internally consistent source JSONs.
|
||||
|
||||
For baseline shifts with existing history, if the operator provides fewer than
|
||||
3 records, call out that the last-5 rolling median may not move immediately. If
|
||||
the operator provides 3 consistent shifted records, the rolling median usually
|
||||
moves immediately. If the operator provides 5 consistent shifted records, the
|
||||
last-5 window is effectively reset to the new runtime profile. For the first
|
||||
approved v2 baseline of a new exact identity, one reviewed calibration seed is
|
||||
enough for the next comparable run to leave `CALIBRATION_NEEDED`.
|
||||
|
||||
These records are intentional operator-approved baseline resets, not ordinary
|
||||
independent main-branch persistence. Mark them clearly with provenance fields
|
||||
so the HF history remains auditable.
|
||||
|
||||
Use this skill when a performance test fails for an intentional and reviewed
|
||||
reason, such as a torch/runtime/container upgrade that legitimately increases
|
||||
peak memory or changes timings. This is the performance equivalent of
|
||||
`reseed-ssim-references`: backup first, scope tightly, require explicit human
|
||||
approval, then upload reviewed accepted baseline records.
|
||||
|
||||
## When to use
|
||||
|
||||
- A PR or main run failed the rolling performance comparison by more than the
|
||||
allowed regression threshold, and maintainers agree the shift is caused by
|
||||
an intentional runtime, dependency, hardware image, or benchmark environment
|
||||
change rather than a FastVideo logic regression.
|
||||
- One or more shifted source result JSONs have been reviewed and accepted, and
|
||||
the operator wants to use those exact reviewed results to advance the rolling
|
||||
baseline.
|
||||
- The source batch is internally consistent: no provided source JSON regresses
|
||||
against the batch median by more than the configured tolerance.
|
||||
|
||||
## When not to use
|
||||
|
||||
- The benchmark failure might be a real code regression. Fix or investigate
|
||||
the code path first.
|
||||
- The fixed benchmark thresholds in
|
||||
`.buildkite/performance-benchmarks/tests/*.json` are too low. Those are a
|
||||
separate gate from the rolling HF baseline and may need a code review change.
|
||||
- There is no clear source run, commit, and rationale. Baseline history is a
|
||||
production signal; do not edit it without provenance.
|
||||
- The provided source JSONs disagree materially with each other. Rerun or
|
||||
investigate instead of uploading a noisy reseed batch.
|
||||
|
||||
## Inputs
|
||||
|
||||
| Parameter | Required | Description |
|
||||
|-----------|----------|-------------|
|
||||
| `model_id` | Legacy required; v2 inferred | Benchmark id, e.g. `wan-t2v-1.3b-2gpu`. This maps to the HF subdirectory after `sanitize(model_id)`. For v2 records, use the `model_id` from each source artifact only as the upload directory; comparison is by exact identity. |
|
||||
| `gpu_type` | Legacy required; v2 inferred | Exact GPU device string from the performance record, e.g. the L40S device name emitted by CI. V2 hardware matching uses `hardware_profile_id`; preserve `gpu_type` as display metadata. |
|
||||
| `source_results` | Yes | One or more local paths or Buildkite artifact URLs for accepted shifted performance JSONs. Prefer normalized `normalized_perf_*.json` artifacts emitted by `compare_baseline.py`. Accept `source_result` as an alias only for a single JSON. |
|
||||
| `max_intra_batch_regression` | No | Maximum allowed regression of any source JSON against the source batch median. Default: `0.05` (5%). |
|
||||
| `intent_rationale` | Yes | One-line explanation for why the baseline shift is legitimate. This is written into provenance and should be reused in the PR. |
|
||||
|
||||
Hardcoded defaults:
|
||||
|
||||
- HF repo: `FastVideo/performance-tracking` (`HF_REPO_ID` override is
|
||||
supported by the code, but use the default unless the user explicitly asks).
|
||||
- Local sync root: `/tmp/perf-tracking` (`PERFORMANCE_TRACKING_ROOT` override
|
||||
is supported).
|
||||
- Prepared-record staging root: `/tmp/performance_reseed_prepared`
|
||||
(`PERFORMANCE_RESEED_STAGING_ROOT` override is supported). Keep it separate
|
||||
and non-nested from the sync root.
|
||||
- Backup root: `/tmp/performance_reseed_backup`.
|
||||
- Download scratch root for source artifact URLs: `/tmp/performance_reseed_source`.
|
||||
- Baseline window: last 5 `success=true`, `baseline_eligible=true` records
|
||||
for the same legacy `(model_id, gpu_type)` target or the same v2 exact
|
||||
comparable identity.
|
||||
- Reseed count: dynamic. Upload exactly one accepted seed record per validated
|
||||
source JSON.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Validate the target and source results
|
||||
|
||||
Normalize `source_results` to a list. If the user passes a single
|
||||
`source_result`, treat it as a one-element `source_results` list and report
|
||||
that a single record may not move the last-5 median immediately.
|
||||
|
||||
If any source result is a Buildkite artifact URL, download it first into a
|
||||
local scratch directory under `/tmp/performance_reseed_source/` and use the
|
||||
downloaded JSON path for the rest of the workflow. If the agent cannot access
|
||||
the artifact because Buildkite authentication is missing, ask the user to
|
||||
download the artifact manually and provide the local path.
|
||||
|
||||
Prefer the normalized Buildkite artifact emitted by `compare_baseline.py`:
|
||||
|
||||
```text
|
||||
perf_reports/results/normalized_perf_*.json
|
||||
```
|
||||
|
||||
That file is already in the HF tracking schema. Load each normalized JSON
|
||||
directly:
|
||||
|
||||
```python
|
||||
import json
|
||||
|
||||
with open(source_result, encoding="utf-8") as f:
|
||||
record = json.load(f)
|
||||
```
|
||||
|
||||
Classify the source batch before continuing:
|
||||
|
||||
- **Legacy source records** have no v2 exact identity fields. Stop if any
|
||||
normalized record's `model_id` or `gpu_type` does not match the requested
|
||||
`model_id` and `gpu_type`.
|
||||
- **V2 source records** have exact identity fields. Stop unless every source
|
||||
record has all six comparable identity fields and they are identical across
|
||||
the batch: `workload_id`, `variant_id`, `benchmark_version`,
|
||||
`hardware_profile_id`, `software_profile_id`, and `recipe_fingerprint`.
|
||||
Do not fall back to legacy `(model_id, gpu_type)` matching for v2 records.
|
||||
|
||||
The source records may have `success: false` when they came from failed
|
||||
rolling baseline comparisons. That is expected; only the reviewed reseed
|
||||
records become new `success: true` baseline records after explicit approval.
|
||||
For a first v2 baseline seed, the source records must instead be successful
|
||||
scheduled-main full-suite `CALIBRATION_NEEDED` normalized artifacts. Reject PR,
|
||||
local, direct-run, non-main-branch, or non-full-suite calibration artifacts as
|
||||
seed sources.
|
||||
|
||||
Sort validated source records by their original `timestamp` ascending before
|
||||
preparing the seed records. If a source timestamp is missing or unparsable,
|
||||
preserve input order for those records and print a warning. This makes the
|
||||
fresh reseed timestamps deterministic and makes it clear which records enter
|
||||
the last-5 window when more than 5 source JSONs are provided.
|
||||
|
||||
Check that `HF_API_KEY` is exported. The sync path may be public, but the
|
||||
upload path requires write access.
|
||||
|
||||
### 1a. Check source batch consistency
|
||||
|
||||
Before syncing or preparing uploads, reject source batches that are internally
|
||||
inconsistent. Use the same metric direction as `compare_baseline.py`:
|
||||
|
||||
- Lower is better: `latency`, `memory`, `text_encoder_time_s`, `dit_time_s`,
|
||||
`vae_decode_time_s`.
|
||||
- Higher is better: `throughput`.
|
||||
|
||||
For each metric with at least two non-null source values:
|
||||
|
||||
1. Compute the source batch median.
|
||||
2. For lower-is-better metrics, compute `(source_value - batch_median) / batch_median`.
|
||||
3. For `throughput`, compute `(batch_median - source_value) / batch_median`.
|
||||
4. Stop if any source record regresses against the batch median by more than
|
||||
`max_intra_batch_regression`.
|
||||
|
||||
Default `max_intra_batch_regression` to `0.05`. Print a table with per-source values, batch median, and
|
||||
worst intra-batch regression.
|
||||
|
||||
This check prevents uploading a mixed batch where one JSON is materially
|
||||
slower or faster than the others. If the batch fails this check, ask the user
|
||||
to provide a cleaner batch or explicitly investigate the variance. Do not
|
||||
silently drop outliers unless the user gives a concrete reviewed reason and a
|
||||
new source list.
|
||||
|
||||
### 1b. How to obtain source results from CI
|
||||
|
||||
The performance CI exports normalized source results for failed rolling
|
||||
baseline comparisons when `compare_baseline.py` ran. The preferred artifacts
|
||||
come from:
|
||||
|
||||
```text
|
||||
perf_reports/results/normalized_perf_*.json
|
||||
```
|
||||
|
||||
The normal operator flow is:
|
||||
|
||||
1. Open the failed Buildkite performance job or several reruns of the same
|
||||
benchmark after the accepted environment shift.
|
||||
2. Download the `normalized_perf_*.json` artifacts for the target benchmark.
|
||||
3. Pass all reviewed local paths or artifact URLs as `source_results`.
|
||||
|
||||
Do not scrape the Markdown performance summary to reconstruct JSON. The
|
||||
normalized JSON artifacts are the only supported source of truth for reseed
|
||||
metrics and provenance. Raw `fastvideo/tests/performance/results/perf_*.json`
|
||||
artifacts are not accepted by this skill. If no normalized JSON artifact is
|
||||
present, that run is not a valid source for baseline reseeding.
|
||||
|
||||
### 2. Sync and back up existing HF records under /tmp
|
||||
|
||||
Use `fastvideo/performance/hf_store.py` helpers directly. Do **not** use
|
||||
`compare_baseline.py` as a sync shortcut; on full main runs it can persist
|
||||
records, while this step must only fetch and back up existing history.
|
||||
|
||||
The sync command pattern is:
|
||||
|
||||
```bash
|
||||
export PERFORMANCE_TRACKING_ROOT="${PERFORMANCE_TRACKING_ROOT:-/tmp/perf-tracking}"
|
||||
export HF_REPO_ID="${HF_REPO_ID:-FastVideo/performance-tracking}"
|
||||
python -c 'from fastvideo.performance.hf_store import sync_from_hf; import os; sync_from_hf(os.environ["PERFORMANCE_TRACKING_ROOT"], strict=True)'
|
||||
```
|
||||
|
||||
For legacy records, back up the sanitized model directory under `/tmp`:
|
||||
|
||||
```bash
|
||||
SHORT_COMMIT=$(git rev-parse --short=12 HEAD)
|
||||
TIMESTAMP=$(date -u +%Y%m%d_%H%M%S)
|
||||
MODEL_SAFE=$(python - <<'PY'
|
||||
from fastvideo.performance.hf_store import sanitize
|
||||
print(sanitize("<model_id>"))
|
||||
PY
|
||||
)
|
||||
BACKUP_DIR="/tmp/performance_reseed_backup/${TIMESTAMP}_${SHORT_COMMIT}_${MODEL_SAFE}"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
cp -R "${PERFORMANCE_TRACKING_ROOT}/${MODEL_SAFE}" "$BACKUP_DIR/" 2>/dev/null || true
|
||||
```
|
||||
|
||||
For v2 records, back up the full local tracking root after sync. Exact identity
|
||||
lookup scans across model directories, so a benchmark rename may have relevant
|
||||
history outside the current source artifact's `model_id` directory:
|
||||
|
||||
```bash
|
||||
BACKUP_DIR="/tmp/performance_reseed_backup/${TIMESTAMP}_${SHORT_COMMIT}_v2_exact_identity"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
cp -R "${PERFORMANCE_TRACKING_ROOT}" "$BACKUP_DIR/tracking-root"
|
||||
```
|
||||
|
||||
Write provenance next to the backup:
|
||||
|
||||
```bash
|
||||
cat > "$BACKUP_DIR/PROVENANCE.txt" <<EOF
|
||||
model_id: <model_id>
|
||||
gpu_type: <gpu_type>
|
||||
source_results:
|
||||
- <source_result_1>
|
||||
- <source_result_2>
|
||||
reseed_record_count: <len(source_results)>
|
||||
max_intra_batch_regression: <threshold>
|
||||
head_commit: $(git rev-parse HEAD)
|
||||
timestamp_utc: $(date -u +%FT%TZ)
|
||||
reason: <intent_rationale>
|
||||
EOF
|
||||
```
|
||||
|
||||
If the backup has no prior records, this is not a destructive reseed; it is a
|
||||
first baseline seed. Continue, but report that baseline history was empty.
|
||||
|
||||
### 3. Compute old baseline and candidate shift
|
||||
|
||||
Load the last 5 successful baseline records for the target.
|
||||
|
||||
For legacy targets:
|
||||
|
||||
```python
|
||||
from fastvideo.performance.hf_store import load_records_for_model
|
||||
|
||||
records = load_records_for_model(
|
||||
"/tmp/perf-tracking",
|
||||
"<model_id>",
|
||||
"<gpu_type>",
|
||||
last_n=5,
|
||||
successful_only=True,
|
||||
baseline_eligible_only=True,
|
||||
)
|
||||
```
|
||||
|
||||
For v2 exact-identity targets:
|
||||
|
||||
```python
|
||||
from fastvideo.performance.hf_store import load_records_for_identity
|
||||
|
||||
records = load_records_for_identity(
|
||||
"/tmp/perf-tracking",
|
||||
{
|
||||
"workload_id": "<workload_id>",
|
||||
"variant_id": "<variant_id>",
|
||||
"benchmark_version": "<benchmark_version>",
|
||||
"hardware_profile_id": "<hardware_profile_id>",
|
||||
"software_profile_id": "<software_profile_id>",
|
||||
"recipe_fingerprint": "<recipe_fingerprint>",
|
||||
},
|
||||
last_n=5,
|
||||
successful_only=True,
|
||||
baseline_eligible_only=True,
|
||||
)
|
||||
```
|
||||
|
||||
Print a small table showing old medians, source batch medians, candidate
|
||||
medians after appending the proposed seed records, and source batch spread for:
|
||||
|
||||
- `latency`
|
||||
- `throughput`
|
||||
- `memory`
|
||||
- `text_encoder_time_s`
|
||||
- `dit_time_s`
|
||||
- `vae_decode_time_s`
|
||||
|
||||
Also print how many successful old records exist. Make clear:
|
||||
|
||||
- 1 seed record usually does not move an existing last-5 median by itself, but
|
||||
it is enough to establish the first v2 baseline for a new exact identity.
|
||||
- 3 consistent seed records usually move the last-5 median immediately.
|
||||
- 5 consistent seed records effectively reset the last-5 window.
|
||||
- The records are intentional approved baseline resets and must be labeled
|
||||
that way.
|
||||
|
||||
### 4. Confirm intent
|
||||
|
||||
Require an explicit confirmation phrase before preparing the upload:
|
||||
|
||||
> About to RE-SEED performance baseline for `<target description>`.
|
||||
> This will upload `<N>` new `success=true` records to
|
||||
> `FastVideo/performance-tracking/<sanitize(model_id)>/` or the source
|
||||
> artifact's v2 model directory, one per accepted source JSON.
|
||||
>
|
||||
> Reason: `<intent_rationale>`
|
||||
> Source results: `<source_results>`
|
||||
> Reseed record count: `<N>`
|
||||
> Max intra-batch regression: `<threshold>`
|
||||
> Note: these records come from a reviewed source batch and are intended to
|
||||
> move the rolling median to the accepted runtime profile. They are not
|
||||
> ordinary main-branch persistence.
|
||||
> HEAD: `<git rev-parse --short=12 HEAD>`
|
||||
> Backup: `<BACKUP_DIR>`
|
||||
>
|
||||
> Reply `confirm performance reseed` to proceed, anything else to abort.
|
||||
|
||||
Do not continue unless the user types exactly `confirm performance reseed`.
|
||||
|
||||
### 5. Create the accepted seed records
|
||||
|
||||
Create one seed record from each normalized source result.
|
||||
|
||||
For first v2 baseline seeds, use the scoped utility. It validates exact
|
||||
identity, requires successful scheduled-main full-suite `CALIBRATION_NEEDED`
|
||||
source artifacts, preserves the normalized v2 identity and metadata fields,
|
||||
and writes seed records with `success=true`, `baseline_eligible=true`, and
|
||||
`comparison_status=PASS`:
|
||||
|
||||
```bash
|
||||
python fastvideo/tests/performance/seed_baseline.py \
|
||||
--source-result <normalized_perf_1.json> \
|
||||
--source-result <normalized_perf_2.json> \
|
||||
--intent-rationale "<intent_rationale>" \
|
||||
--max-intra-batch-regression 0.05 \
|
||||
--tracking-root "${PERFORMANCE_TRACKING_ROOT}" \
|
||||
--staging-root "${PERFORMANCE_RESEED_STAGING_ROOT:-/tmp/performance_reseed_prepared}"
|
||||
```
|
||||
|
||||
The utility is prepare-only and intentionally has no upload option. Upload the
|
||||
scoped records only after the separate confirmation in step 6.
|
||||
|
||||
The utility validates against an isolated fresh HF snapshot and leaves
|
||||
`PERFORMANCE_TRACKING_ROOT` untouched; that argument only proves the staging
|
||||
root is separate from the operator's tracking mirror. Before writing, it stops
|
||||
if the exact identity already has a successful baseline-eligible record or if
|
||||
the workload/variant/version already trusts another recipe. It atomically
|
||||
reserves the exact identity and writes a digest-protected upload manifest bound
|
||||
to the current HF endpoint, repository id, and repository type. Keep the
|
||||
prepared records, manifest, source files, and reservation unchanged until the
|
||||
operation is uploaded or explicitly cleaned up.
|
||||
|
||||
If the prepared seed records look correct, upload only those scoped records in
|
||||
step 7. Do not rerun the utility with a different source list after approval.
|
||||
|
||||
For legacy reseeds or accepted v2 baseline shifts from regression artifacts,
|
||||
create one seed record from each normalized source result. Do not copy the
|
||||
source JSON wholesale.
|
||||
|
||||
Infer the baseline field allowlist from all existing HF records for the target
|
||||
after syncing, including both `success=true` and `success=false` records. For
|
||||
legacy targets the target is `(model_id, gpu_type)`. For v2 baseline-shift
|
||||
reseeds the target is the exact comparable identity. Use the union of
|
||||
non-provenance keys present in those target records, preserving only fields
|
||||
that also exist in the normalized source record or are explicitly set by the
|
||||
reseed workflow. Always include `model_id`, `timestamp`, `success`,
|
||||
`baseline_eligible`, and `comparison_status` because the upload path and
|
||||
baseline loader depend on them. Always set `timestamp` to a fresh reseed
|
||||
timestamp, `success` to `true`, `baseline_eligible` to `true`, and
|
||||
`comparison_status` to `PASS`. Do not include unrelated source-only fields
|
||||
that are absent from existing HF records.
|
||||
|
||||
Exclude existing provenance or operator metadata from the inferred baseline
|
||||
field allowlist. At minimum, exclude keys prefixed with `baseline_reseed` and
|
||||
any fields known to be local-only audit metadata.
|
||||
|
||||
If there are no previous HF records for the target, fall back to this default
|
||||
baseline field list:
|
||||
|
||||
- `model_id`
|
||||
- `timestamp`
|
||||
- `commit_sha`
|
||||
- `gpu_type`
|
||||
- `latency`
|
||||
- `throughput`
|
||||
- `memory`
|
||||
- `text_encoder_time_s`
|
||||
- `dit_time_s`
|
||||
- `vae_decode_time_s`
|
||||
- `success`
|
||||
- `baseline_eligible`
|
||||
- `comparison_status`
|
||||
|
||||
For v2 baseline-shift reseeds with no previous HF records for the exact
|
||||
identity, also preserve:
|
||||
|
||||
- `workload_id`
|
||||
- `variant_id`
|
||||
- `benchmark_version`
|
||||
- `recipe_fingerprint`
|
||||
- `hardware_profile_id`
|
||||
- `software_profile_id`
|
||||
- `recipe`
|
||||
- `hardware_profile`
|
||||
- `software_profile`
|
||||
- `software_comparison_profile`
|
||||
|
||||
Do not upload extra fields from the source artifact.
|
||||
|
||||
Optional provenance fields are allowed and useful:
|
||||
|
||||
- `baseline_reseed: true`
|
||||
- `baseline_reseed_reason`
|
||||
- `baseline_reseed_source_result`
|
||||
- `baseline_reseed_source_timestamp`
|
||||
- `baseline_reseed_source_success`
|
||||
- `baseline_reseed_batch_size`
|
||||
- `baseline_reseed_batch_index`
|
||||
- `baseline_reseed_operator`
|
||||
- `baseline_reseed_max_intra_batch_regression`
|
||||
|
||||
The v2 calibration seed utility writes analogous first-seed provenance:
|
||||
|
||||
- `baseline_seed: true`
|
||||
- `baseline_seed_reason`
|
||||
- `baseline_seed_source_result`
|
||||
- `baseline_seed_source_status`
|
||||
- `baseline_seed_source_timestamp`
|
||||
- `baseline_seed_source_success`
|
||||
- `baseline_seed_source_run_source`
|
||||
- `baseline_seed_source_branch`
|
||||
- `baseline_seed_source_test_scope`
|
||||
- `baseline_seed_source_pr_number`
|
||||
- `baseline_seed_batch_size`
|
||||
- `baseline_seed_batch_index`
|
||||
- `baseline_seed_operator`
|
||||
|
||||
Use a fresh reseed timestamp for each seed record, not the original source
|
||||
result timestamp. This is required because
|
||||
`load_records_for_model(..., last_n=5)` keeps the last records after loading
|
||||
the model directory; stale filenames/timestamps may not enter the last-5
|
||||
window and therefore may not move the median. Preserve the original source
|
||||
timestamp in `baseline_reseed_source_timestamp`.
|
||||
|
||||
Use the existing filename convention from `_write_tracking_record()`:
|
||||
`<sanitize(timestamp)>_<sanitize(commit_sha)>.json` under the sanitized model
|
||||
directory, but include a deterministic suffix such as `_reseed_01`,
|
||||
`_reseed_02`, and so on before `.json` so multiple records from the same
|
||||
batch do not overwrite each other.
|
||||
|
||||
If a source record already exists on HF with `success=false`, do not edit it
|
||||
in place unless the user explicitly asked for an audit-preserving correction.
|
||||
Prefer uploading new accepted seed records so failed history remains visible.
|
||||
|
||||
### 6. Pause before upload
|
||||
|
||||
Print:
|
||||
|
||||
- Backup directory path under `/tmp`.
|
||||
- Prepared local record paths under `PERFORMANCE_RESEED_STAGING_ROOT`.
|
||||
- Prepared upload-manifest path under the identity reservation.
|
||||
- HF paths that will receive the new records.
|
||||
- Old rolling medians.
|
||||
- Source batch medians, source batch spread, reseed count, and candidate
|
||||
medians.
|
||||
- Rationale.
|
||||
|
||||
Ask the user to reply exactly `upload`. Anything else aborts and leaves the
|
||||
prepared records plus backup on disk.
|
||||
|
||||
### 7. Upload only the scoped records
|
||||
|
||||
For a first v2 calibration seed, use the manifest uploader after the user
|
||||
replies exactly `upload`:
|
||||
|
||||
```bash
|
||||
python -c 'from fastvideo.tests.performance.seed_baseline import upload_prepared_seed_manifest; print(upload_prepared_seed_manifest("<prepared_manifest>"))'
|
||||
```
|
||||
|
||||
The uploader verifies the source and prepared-record digests, pins and scans
|
||||
the current HF revision, rechecks exact-identity and recipe-cohort conflicts,
|
||||
and writes the entire batch in one commit whose `parent_commit` must still be
|
||||
current. A concurrent Hub update makes the commit fail. Do not retry
|
||||
automatically: preserve staging, refresh/review remote state, and request a new
|
||||
explicit `upload` after the conflict is understood. Each record goes to:
|
||||
|
||||
```text
|
||||
FastVideo/performance-tracking/<sanitize(model_id)>/<record_filename>.json
|
||||
```
|
||||
|
||||
Never call `upload_record()` once per first-seed record: that can partially
|
||||
land the batch and has no compare-and-swap guard.
|
||||
|
||||
For a legacy reseed or an accepted v2 baseline shift, the first-seed manifest
|
||||
validator does not apply because an eligible baseline already exists. Upload
|
||||
only the individually reviewed records prepared in step 5 with the shared
|
||||
`upload_record(local_path, record, strict=True)` helper. Stop on the first
|
||||
failure and report exactly which records reached HF; do not silently rerun or
|
||||
replicate the remainder.
|
||||
|
||||
Never bulk upload the tracking or staging root, and never modify another
|
||||
model's directory in the same operation.
|
||||
|
||||
### 8. Report outcome and offer cleanup
|
||||
|
||||
Report:
|
||||
|
||||
- Uploaded HF paths.
|
||||
- Backup directory under `/tmp`.
|
||||
- Local tracking root, usually `/tmp/perf-tracking`.
|
||||
- Old baseline window count and medians.
|
||||
- Source batch medians, source batch spread, reseed count, and candidate
|
||||
medians.
|
||||
- Expected effect based on reseed count.
|
||||
- Any separate threshold changes still needed in
|
||||
`.buildkite/performance-benchmarks/tests/*.json`.
|
||||
|
||||
Include the `intent_rationale` in the PR or follow-up comment so reviewers can
|
||||
distinguish an accepted baseline shift from a hidden regression.
|
||||
|
||||
After the upload is verified, ask whether the user wants to clear temporary
|
||||
local state. Explain what each directory is for:
|
||||
|
||||
- `PERFORMANCE_TRACKING_ROOT`, usually `/tmp/perf-tracking`: read-only local
|
||||
synced mirror used for operator review and reporting. First-v2 preparation
|
||||
independently proves remote state from a fresh temporary HF snapshot.
|
||||
- `PERFORMANCE_RESEED_STAGING_ROOT`, usually
|
||||
`/tmp/performance_reseed_prepared`: prepared local seed records used for the
|
||||
scoped upload, plus the identity reservation and digest manifest. Keeping
|
||||
this separate prevents aborted preparations from appearing in later
|
||||
baseline reads.
|
||||
- `/tmp/performance_reseed_backup/<...>`: local backup of the target model's
|
||||
pre-reseed HF history plus `PROVENANCE.txt`, kept so a bad reseed can be
|
||||
audited or corrected.
|
||||
- `/tmp/performance_reseed_source/<...>` when used: downloaded source JSON
|
||||
artifacts from Buildkite URLs.
|
||||
|
||||
Ask:
|
||||
|
||||
> Reseed succeeded. Do you want me to delete the local temp tracking mirror,
|
||||
> this reseed's prepared staging records, source downloads, and reseed backup
|
||||
> under `/tmp`? These files are local safety/audit artifacts only; HF already
|
||||
> has the uploaded records.
|
||||
>
|
||||
> Reply `cleanup reseed temp` to delete them, anything else to keep them.
|
||||
|
||||
Do not delete anything unless the user replies exactly
|
||||
`cleanup reseed temp`. If cleanup is requested, remove only the specific
|
||||
directories and prepared record paths created for this reseed. Do not remove
|
||||
the shared staging root when it contains other records. Remove this operation's
|
||||
identity reservation only with its prepared records and manifest, and never
|
||||
remove unrelated `/tmp` contents.
|
||||
|
||||
## Failure modes and handling
|
||||
|
||||
- **`HF_API_KEY` unset.** Stop before upload. Do not create an untracked
|
||||
process that appears to have reseeded but never reached HF.
|
||||
- **Source result does not match target.** Stop. The wrong benchmark or GPU
|
||||
would poison a separate baseline.
|
||||
- **Source batch is internally inconsistent.** Stop if any source regresses
|
||||
against the source batch median by more than `max_intra_batch_regression`.
|
||||
Ask for cleaner sources or a reviewed explanation before continuing.
|
||||
- **Too few source records to move the median.** Continue only after making
|
||||
clear that one or two records may not immediately move an existing last-5
|
||||
median. This warning does not block a first v2 calibration seed for an exact
|
||||
identity with no eligible baseline yet.
|
||||
- **The source results are noisy or suspicious.** Stop. Reseeding amplifies
|
||||
those measurements into the baseline, so they must be reviewed first.
|
||||
- **HF sync fails.** Stop for destructive reseeds. A stale or empty sync can
|
||||
make the old baseline look missing.
|
||||
- **The exact v2 identity already has an eligible baseline.** Stop. The
|
||||
`CALIBRATION_NEEDED` artifact is stale; use the reviewed baseline-shift path
|
||||
instead of the first-seed utility.
|
||||
- **The workload/variant/version trusts another recipe.** Stop. The source is
|
||||
stale relative to the current recipe cohort and must not bypass
|
||||
`RECIPE_MISMATCH` by creating a second trusted recipe.
|
||||
- **The staging root already has a prepared seed for the exact identity.**
|
||||
Stop and reuse, upload, or explicitly clean that preparation. Do not prepare
|
||||
another copy of the same measurement.
|
||||
- **The conditional Hub commit loses its parent race.** Stop without retrying.
|
||||
Keep the preparation, refresh and review the new remote state, then request
|
||||
a new explicit `upload` only if the seed is still valid.
|
||||
- **Candidate still violates fixed thresholds.** Report that this skill only
|
||||
handles the rolling HF baseline; update benchmark JSON thresholds in code
|
||||
review if maintainers accept the new absolute limit.
|
||||
- **The user aborts at either confirmation.** Leave the backup and prepared
|
||||
records on disk. Nothing should be uploaded.
|
||||
- **The user declines cleanup.** Keep `/tmp/perf-tracking`, the prepared seed
|
||||
records under `/tmp/performance_reseed_prepared`, the source download
|
||||
directory if any, and `/tmp/performance_reseed_backup/<...>` in place for
|
||||
audit/debugging.
|
||||
- **A bad seed was uploaded.** Use the backup and HF history to identify the
|
||||
uploaded file, then remove or supersede it with an explicitly reviewed
|
||||
corrective record. Do not silently rewrite unrelated history.
|
||||
|
||||
## References
|
||||
|
||||
- `.agents/skills/reseed-ssim-references/SKILL.md` — safety pattern for
|
||||
intentional baseline replacement.
|
||||
- `fastvideo/tests/performance/compare_baseline.py` — normalization, rolling
|
||||
median comparison, and persistence rules.
|
||||
- `fastvideo/performance/hf_store.py` — HF sync and record loading helpers.
|
||||
- `fastvideo/tests/performance/seed_baseline.py` — first-seed preparation,
|
||||
staging reservation, manifest validation, and conditional batch upload.
|
||||
- `fastvideo/tests/performance/test_inference_performance.py` — source result
|
||||
JSON schema.
|
||||
- `.buildkite/performance-benchmarks/tests/*.json` — fixed absolute benchmark
|
||||
thresholds, separate from rolling baseline comparisons.
|
||||
|
||||
## Changelog
|
||||
|
||||
| Date | Change |
|
||||
|------|--------|
|
||||
| 2026-05-03 | Initial version. Sister workflow to `reseed-ssim-references`, scoped to one performance `(model_id, gpu_type)` baseline seed with backup, confirmation, provenance, and `success=true` upload. |
|
||||
| 2026-05-03 | Previous policy: replicate one approved shifted source result into 3 success records by default, or 5 only when explicitly requested. Add provenance marker for replicated-source reseeds. Superseded by the 2026-05-08 dynamic multi-source policy. |
|
||||
| 2026-05-08 | Replace fixed 3/5 replication with dynamic multi-source reseeding: upload one seed record per reviewed source JSON, validate intra-batch consistency, move backup/source scratch under `/tmp`, and ask whether to clean temp state after successful upload. |
|
||||
| 2026-07-13 | Keep first-v2-seed preparation outside the canonical mirror, reserve staging identities atomically, reject stale or replayed calibration seeds, and upload reviewed manifests with a single parent-guarded Hub commit. |
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: reseed-ssim-references
|
||||
description: Re-seed HF reference videos for a single existing SSIM test on Modal L40S. Always backs up current refs locally first, regenerates on Modal, pauses for the user to eyeball before-vs-after quality, then overwrites the targeted `<model_id>` subtree on `FastVideo/ssim-reference-videos` with `--force`. Use when an intentional code change (model port fix, attention backend swap, kernel upgrade, hyperparameter change) has invalidated existing refs and they need to be regenerated. Pairs with `seed-ssim-references`, which is for first-time seeding only.
|
||||
description: Re-seed HF reference videos for a single existing SSIM test on Modal L40S. Always backs up current refs locally first, regenerates on Modal, pauses for the user to eyeball before-vs-after quality, then overwrites the targeted model subtree on `FastVideo/ssim-reference-videos` with `--force`. Use when an intentional code change (model port fix, attention backend swap, kernel upgrade, hyperparameter change) has invalidated existing refs and they need to be regenerated. Pairs with `seed-ssim-references`, which is for first-time seeding only.
|
||||
---
|
||||
|
||||
# Re-seed SSIM Reference Videos
|
||||
@@ -13,7 +13,7 @@ on HF — the old refs are overwritten — so the skill always:
|
||||
|
||||
1. Confirms intent with a one-liner the user has to type.
|
||||
2. Downloads the existing refs as a local, timestamped backup.
|
||||
3. Regenerates on Modal L40S (same code path that CI uses).
|
||||
3. Regenerates through the manual legacy Modal L40S maintenance path.
|
||||
4. Pauses for a side-by-side eyeball of backup vs new mp4s.
|
||||
5. Uploads with `--force`, scoped to the single `--model-id`.
|
||||
6. Reminds the user to keep the backup until the PR lands.
|
||||
@@ -51,12 +51,13 @@ harder to recover from than failing closed.
|
||||
|
||||
Hardcoded:
|
||||
|
||||
- Modal GPU: **L40S** (matches CI; re-seeding from another SKU produces refs
|
||||
that L40S CI cannot match).
|
||||
- Modal GPU: **L40S**. This is a manual reference-maintenance target, not the
|
||||
active Slurm CI compute path; changing the SKU also changes the historical
|
||||
`L40S_reference_videos` contract.
|
||||
- Quality tier: **`default`**. `full_quality` is a separate, deliberate
|
||||
operation.
|
||||
- HF repo: `FastVideo/ssim-reference-videos` (override via
|
||||
`FASTVIDEO_SSIM_REFERENCE_HF_REPO`).
|
||||
`FASTVIDEO_TEST_SSIM_REFERENCE_HF_REPO`).
|
||||
- Device folder: `L40S_reference_videos`.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
---
|
||||
name: search-related-work
|
||||
description: Query the related work index for relevant papers, repos, or comparisons
|
||||
---
|
||||
|
||||
# Search Related Work
|
||||
|
||||
## Purpose
|
||||
Search through `.agents/memory/related-work/` to find indexed papers, repos,
|
||||
or blog posts relevant to a query. Use this when you need to understand how
|
||||
other work compares to FastVideo's approach, or when looking for techniques
|
||||
to adopt.
|
||||
|
||||
## Prerequisites
|
||||
- The related work index has entries (`.agents/memory/related-work/*.md`).
|
||||
|
||||
## Inputs
|
||||
|
||||
| Parameter | Required | Description |
|
||||
|-----------|----------|-------------|
|
||||
| `query` | Yes | Natural language query |
|
||||
| `tags` | No | Filter by tags (e.g., `[distillation, evaluation]`) |
|
||||
| `type` | No | Filter by type (`paper`, `repo`, `blog`) |
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Search the index
|
||||
|
||||
Use grep-based search through `.agents/memory/related-work/`:
|
||||
|
||||
```bash
|
||||
# Search by content
|
||||
grep -rl "<query>" .agents/memory/related-work/
|
||||
|
||||
# Search by tags (in frontmatter)
|
||||
grep -l "tags:.*<tag>" .agents/memory/related-work/*.md
|
||||
```
|
||||
|
||||
### 2. Rank results
|
||||
|
||||
For each matching file:
|
||||
1. Read the file.
|
||||
2. Score relevance to the query based on:
|
||||
- Title match
|
||||
- Tag match
|
||||
- Content match (summary, differences, insights)
|
||||
3. Return top results.
|
||||
|
||||
### 3. Format output
|
||||
|
||||
```markdown
|
||||
## Related Work Search: "<query>"
|
||||
|
||||
### 1. <Title> (relevance: high)
|
||||
- **Source**: <URL>
|
||||
- **Tags**: <tags>
|
||||
- **Key insight**: <most relevant excerpt>
|
||||
- **File**: `.agents/memory/related-work/<slug>.md`
|
||||
|
||||
### 2. <Title> (relevance: medium)
|
||||
...
|
||||
```
|
||||
|
||||
## Outputs
|
||||
- Ranked list of relevant related work entries with excerpts.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
Search for work related to video quality evaluation metrics:
|
||||
|
||||
query: "video generation quality evaluation metrics"
|
||||
tags: [evaluation]
|
||||
```
|
||||
|
||||
## References
|
||||
- `.agents/memory/related-work/README.md` — index schema
|
||||
|
||||
## Changelog
|
||||
| Date | Change |
|
||||
|------|--------|
|
||||
| 2026-03-02 | Initial version |
|
||||
@@ -35,7 +35,8 @@ The skill is run **manually**, once per new test. Before invoking it, the user
|
||||
has already sanity-tested the new test locally — it launches `VideoGenerator`
|
||||
and writes an artefact without crashing (the missing-reference assertion at
|
||||
the end is expected). The skill does not re-test locally; it goes straight
|
||||
to Modal L40S (which is what CI uses).
|
||||
to the manual legacy Modal L40S reference-maintenance target. Active CI runs
|
||||
on the Slinky Slurm cluster and only consumes the resulting references.
|
||||
|
||||
## When to use
|
||||
|
||||
@@ -61,7 +62,8 @@ Prompt the user for it if they didn't supply it.
|
||||
|
||||
Everything else is fixed:
|
||||
|
||||
- Modal runner GPU: **L40S** (hardcoded in `fastvideo/tests/modal/ssim_test.py`).
|
||||
- Modal maintenance GPU: **L40S** (hardcoded in
|
||||
`fastvideo/tests/modal/ssim_test.py`; this is not the active CI compute path).
|
||||
- Device folder: `L40S_reference_videos`.
|
||||
- Quality tier: `default` (the tier CI runs). The `full_quality` tier is not
|
||||
seeded by this skill.
|
||||
@@ -150,10 +152,12 @@ modal run fastvideo/tests/modal/ssim_test.py \
|
||||
Env prefix rationale (parity with CI; see `.buildkite/pipeline.yml:1-3` and
|
||||
`.buildkite/scripts/pr_test.sh:62-83`):
|
||||
- `IMAGE_VERSION=py3.12-latest`: pins the Modal image tag to the same one CI
|
||||
uses. Without this, `ssim_test.py:17` falls back to `latest`, which on
|
||||
GHCR is built from `Dockerfile.python3.10` — different Python, torch, and
|
||||
flash-attn wheel than CI's `py3.12-latest` (`infra-build-image.yml:51-67`,
|
||||
`_template-build-image.yml:65-101`).
|
||||
uses. The published `py3.12-latest` and `latest` tags point at Python 3.12 /
|
||||
CUDA 12.6.3 / cu126; `py3.12-cuda12.6.3-latest` is the explicit alias for the
|
||||
same image. CUDA 13 / cu130 is available under the explicit
|
||||
`py3.12-cuda13.0.0-latest` tag. This tag policy comes from
|
||||
`infra-build-image.yml`; the unparameterized `docker/Dockerfile` build itself
|
||||
still defaults to CUDA 13 / cu130.
|
||||
- `BUILDKITE_REPO`/`BUILDKITE_COMMIT`/`BUILDKITE_PULL_REQUEST`: mirror what
|
||||
Buildkite exports. `ssim_test.py:38-46` bakes these into the image's
|
||||
`.env(...)` block; mismatched values can perturb in-container code paths
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
---
|
||||
name: summarize-run
|
||||
description: Extract a W&B run summary into a structured experiment report
|
||||
---
|
||||
|
||||
# Summarize Run
|
||||
|
||||
## Purpose
|
||||
After a training run completes (or at any checkpoint), extract key metrics from
|
||||
the W&B run summary and produce a structured markdown report. Supports both
|
||||
online (W&B API) and offline (local `wandb-summary.json`) modes.
|
||||
|
||||
## Prerequisites
|
||||
- Run has completed or reached a checkpoint with a saved summary.
|
||||
- For online: `WANDB_API_KEY` set in environment.
|
||||
- For offline: access to `<output_dir>/tracker/wandb/latest-run/files/wandb-summary.json`.
|
||||
|
||||
## Inputs
|
||||
|
||||
| Parameter | Required | Description |
|
||||
|-----------|----------|-------------|
|
||||
| `run_id` | Yes* | W&B run ID for online access |
|
||||
| `output_dir` | Yes* | Local output dir for offline access |
|
||||
| `reference_run` | No | Path to reference `wandb-summary.json` for comparison |
|
||||
| `experiment_name` | No | Name for the journal entry (default: from W&B) |
|
||||
|
||||
\* One of `run_id` or `output_dir` is required.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Load run summary
|
||||
|
||||
**Online**:
|
||||
|
||||
```python
|
||||
import wandb
|
||||
api = wandb.Api()
|
||||
run = api.run("<run_id>")
|
||||
summary = dict(run.summary)
|
||||
config = dict(run.config)
|
||||
```
|
||||
|
||||
**Offline** (existing codebase pattern from `fastvideo/tests/training/`):
|
||||
|
||||
```python
|
||||
import json
|
||||
summary_path = f"{output_dir}/tracker/wandb/latest-run/files/wandb-summary.json"
|
||||
with open(summary_path) as f:
|
||||
summary = json.load(f)
|
||||
```
|
||||
|
||||
### 2. Extract key fields
|
||||
|
||||
| Field | Source | Description |
|
||||
|-------|--------|-------------|
|
||||
| `train_loss` | `summary["train_loss"]` | Final training loss |
|
||||
| `avg_step_time` | `summary["avg_step_time"]` | Average seconds per step |
|
||||
| `step_time` | `summary["step_time"]` | Last step time |
|
||||
| `grad_norm` | `summary["grad_norm"]` | Final gradient norm |
|
||||
| `learning_rate` | `summary["learning_rate"]` | Final LR |
|
||||
| `_step` | `summary["_step"]` | Total steps completed |
|
||||
| `_runtime` | `summary["_runtime"]` | Total wall-clock seconds |
|
||||
| `validation_videos_*` | `summary[key]` | Validation video artifacts |
|
||||
|
||||
### 3. Compare against reference (optional)
|
||||
|
||||
Follow the pattern in `fastvideo/tests/training/Vanilla/test_training_loss.py`:
|
||||
|
||||
```python
|
||||
# Fields to compare
|
||||
compare_fields = ["train_loss", "grad_norm", "avg_step_time"]
|
||||
tolerance = 0.05 # 5% relative tolerance
|
||||
|
||||
for field in compare_fields:
|
||||
ref_val = reference_summary[field]
|
||||
cur_val = summary[field]
|
||||
diff_pct = abs(cur_val - ref_val) / abs(ref_val) * 100
|
||||
status = "✅" if diff_pct < tolerance * 100 else "⚠️"
|
||||
print(f"{status} {field}: {cur_val:.4f} (ref: {ref_val:.4f}, diff: {diff_pct:.1f}%)")
|
||||
```
|
||||
|
||||
### 4. Generate report
|
||||
|
||||
```markdown
|
||||
# Run Summary: <experiment_name>
|
||||
|
||||
| Metric | Value | Reference | Diff |
|
||||
|--------|-------|-----------|------|
|
||||
| Train Loss | 0.0788 | 0.0800 | -1.5% ✅ |
|
||||
| Avg Step Time | 2.81s | 2.80s | +0.4% ✅ |
|
||||
| Grad Norm | 0.408 | 0.410 | -0.5% ✅ |
|
||||
| Total Steps | 500 | — | — |
|
||||
| Wall Time | 23m 30s | — | — |
|
||||
|
||||
## Configuration
|
||||
- Model: Wan-AI/Wan2.1-T2V-1.3B-Diffusers
|
||||
- Learning Rate: 1e-6
|
||||
- Batch Size: 1
|
||||
- GPUs: 8 × (SP=1, TP=1)
|
||||
- Mixed Precision: bf16
|
||||
|
||||
## Validation Videos
|
||||
<list of validation video paths if available>
|
||||
|
||||
## Notes
|
||||
<any observations or anomalies>
|
||||
```
|
||||
|
||||
### 5. Update experiment journal
|
||||
|
||||
Append or update the experiment's entry in `.agents/memory/experiment-journal/README.md`
|
||||
with the final metrics and status.
|
||||
|
||||
## Outputs
|
||||
- Structured markdown report.
|
||||
- Updated experiment journal entry.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
Summarize the run in output directory "outputs/wan_finetune":
|
||||
|
||||
output_dir: outputs/wan_finetune
|
||||
reference_run: fastvideo/tests/training/Vanilla/a40_reference_wandb_summary.json
|
||||
experiment_name: wan-t2v-finetune-lr1e6
|
||||
```
|
||||
|
||||
## References
|
||||
- `fastvideo/tests/training/Vanilla/test_training_loss.py` — reference comparison pattern
|
||||
- `fastvideo/tests/training/Vanilla/a40_reference_wandb_summary.json` — example summary
|
||||
- `fastvideo/tests/training/lora/test_lora_training.py` — LoRA summary comparison
|
||||
- `fastvideo/training/trackers.py` — tracker summary generation
|
||||
|
||||
## Changelog
|
||||
| Date | Change |
|
||||
|------|--------|
|
||||
| 2026-03-02 | Initial version |
|
||||
@@ -1,93 +0,0 @@
|
||||
---
|
||||
description: How to develop, validate, and register a new evaluation metric
|
||||
---
|
||||
|
||||
# Evaluation Development SOP
|
||||
|
||||
Standard procedure for adding new video quality evaluation metrics to
|
||||
the FastVideo agent toolkit.
|
||||
|
||||
## When to use
|
||||
|
||||
- You need a metric that does not exist in
|
||||
`.agents/memory/evaluation-registry/README.md`.
|
||||
- An existing metric needs significant changes to its methodology.
|
||||
- You are exploring a new evaluation approach.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Research
|
||||
|
||||
- Search `.agents/memory/related-work/` for existing evaluation
|
||||
approaches.
|
||||
- Check `.agents/memory/evaluation-registry/README.md` for current
|
||||
metrics and their limitations.
|
||||
- Review literature: FVD, CLIP-Score, human preference, etc.
|
||||
|
||||
### 2. Prototype
|
||||
|
||||
- Write a standalone script in `.agents/exploration/<metric-name>.md`.
|
||||
- Keep it simple: one script, minimal dependencies.
|
||||
- Test on a few known-good and known-bad video samples.
|
||||
|
||||
### 3. Validate
|
||||
|
||||
- **Known-good test**: metric should score high on reference-quality
|
||||
videos.
|
||||
- **Known-bad test**: metric should score low on degraded or unrelated
|
||||
videos.
|
||||
- **Sensitivity test**: small quality differences should produce
|
||||
meaningful score differences.
|
||||
- Document thresholds and their justification.
|
||||
|
||||
### 4. Register
|
||||
|
||||
Update `.agents/memory/evaluation-registry/README.md`:
|
||||
|
||||
- Add the metric with status `Active`.
|
||||
- Document location, thresholds, and trust level.
|
||||
|
||||
### 5. Integrate
|
||||
|
||||
Update `.agents/skills/evaluate-video-quality/SKILL.md`:
|
||||
|
||||
- Add the new metric as a section.
|
||||
- Include code examples and interpretation guide.
|
||||
|
||||
### 6. Document
|
||||
|
||||
- Move the exploration log content into the skill.
|
||||
- Clean up the exploration file or mark it as `promoted`.
|
||||
- If anything went wrong during development, create a lesson.
|
||||
|
||||
## Where the metrics live
|
||||
|
||||
The eval suite is `fastvideo/eval/`. New metrics register themselves
|
||||
via `@register("<group>.<name>")` and are auto-discovered when
|
||||
`fastvideo.eval.metrics` is imported.
|
||||
|
||||
- **Native metrics** (SSIM, PSNR, LPIPS, optical flow, VLM): add a
|
||||
file under the appropriate group dir
|
||||
(`fastvideo/eval/metrics/common/`, `optical_flow/`, `videoscore2/`,
|
||||
`physics_iq/`).
|
||||
- **Metrics that wrap upstream research code**: follow the vbench
|
||||
pattern in `fastvideo/eval/metrics/vbench/`. The contract is:
|
||||
- Upstream lives as a git submodule under
|
||||
`fastvideo/third_party/eval/<bench>/`, pinned to a SHA in repo-root
|
||||
`.gitmodules`.
|
||||
- The metric package's `__init__.py` inserts the submodule path on
|
||||
`sys.path` and installs runtime compat shims (attribute-level
|
||||
monkey-patches) for any modern-dep drift. Do not modify upstream
|
||||
files on disk, and do not ship a `setup.sh`.
|
||||
- See `fastvideo/eval/README.md` for the worked vbench example.
|
||||
- Full porting guide:
|
||||
[`docs/contributing/eval-metrics.md`](../../docs/contributing/eval-metrics.md).
|
||||
|
||||
## Out of scope of the initial eval port
|
||||
|
||||
The following land in follow-up PRs:
|
||||
|
||||
- **MIND** metrics (depends on a separate `vipe` submodule).
|
||||
- **VBench-2.0** sibling package.
|
||||
- Native conversion of **FVD** under `fastvideo/eval/metrics/fvd/`.
|
||||
- The training-time `EvalCallback`.
|
||||
@@ -1,47 +0,0 @@
|
||||
---
|
||||
description: When and how to log experiments in the experiment journal
|
||||
---
|
||||
|
||||
# Experiment Journaling SOP
|
||||
|
||||
Ensures every experiment is properly recorded with context and outcomes.
|
||||
|
||||
## When to Log
|
||||
|
||||
**Always.** Every experiment — even quick tests — should be journaled.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Before Launch — Create Draft Entry
|
||||
|
||||
Use the `log-experiment` skill with `status: running`:
|
||||
- Include hypothesis and config.
|
||||
- Leave metrics, duration, and insight blank.
|
||||
|
||||
### 2. After 30-Minute Check — Update with Initial Metrics
|
||||
|
||||
Update the entry with:
|
||||
- Current loss and its trajectory direction.
|
||||
- Step time.
|
||||
- Number of validation videos generated.
|
||||
- Preliminary go/no-go assessment.
|
||||
|
||||
### 3. On Completion — Fill Final Entry
|
||||
|
||||
Update the entry with `status: completed`:
|
||||
- Final loss, grad norm, avg step time.
|
||||
- Total duration and steps.
|
||||
- Checkpoint path.
|
||||
- Key insight.
|
||||
|
||||
### 4. On Failure — Document Failure Mode
|
||||
|
||||
Update the entry with `status: failed`:
|
||||
- What went wrong (OOM, NaN, crash, etc.).
|
||||
- At what step the failure occurred.
|
||||
- Create a lesson in `.agents/lessons/` for non-trivial failures.
|
||||
|
||||
### 5. Cross-Reference
|
||||
|
||||
- Link related lessons: `**Related lessons**: .agents/lessons/<filename>.md`
|
||||
- Link related experiments: if this is a follow-up, reference the prior entry.
|
||||
@@ -1,87 +0,0 @@
|
||||
---
|
||||
description: End-to-end experiment lifecycle from hypothesis to lessons learned
|
||||
---
|
||||
|
||||
# Experiment Lifecycle SOP
|
||||
|
||||
Standard operating procedure for running ML training experiments on
|
||||
FastVideo-WorldModel. Every experiment should follow this flow.
|
||||
|
||||
## Overview
|
||||
|
||||
```
|
||||
Plan → Launch → Monitor → Summarize → Journal → Reflect
|
||||
```
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Plan the Experiment
|
||||
|
||||
Before launching:
|
||||
- [ ] Define a clear **hypothesis** (what you expect to learn).
|
||||
- [ ] Select the **model** and **pipeline** type (finetune, distill, lora, etc.).
|
||||
- [ ] Prepare the **dataset** (preprocessed into parquet format).
|
||||
- [ ] Review existing experiments in `.agents/memory/experiment-journal/README.md` for related work.
|
||||
- [ ] Check `.agents/lessons/` for known pitfalls with this configuration.
|
||||
- [ ] Document the plan in the experiment journal as a draft entry.
|
||||
|
||||
### 2. Launch the Experiment
|
||||
|
||||
Use the `launch-experiment` skill:
|
||||
- Provide: pipeline, model, data_path, num_gpus, and any hyperparameter overrides.
|
||||
- The skill generates the `torchrun` command and creates a journal entry.
|
||||
- Verify the command looks correct before executing.
|
||||
|
||||
Reference: `.agents/skills/launch-experiment.md`
|
||||
|
||||
### 3. Monitor the Experiment
|
||||
|
||||
Use the `monitor-experiment` skill:
|
||||
- Provide the W&B run ID (or output_dir for offline).
|
||||
- Monitor alerts: loss spikes, NaN gradients, step time regressions.
|
||||
- At the **30-minute mark**: perform the quality check.
|
||||
- Is loss decreasing?
|
||||
- Are validation videos reasonable?
|
||||
- Is step time consistent?
|
||||
- **Decision point**: Continue or abort based on the 30-min check.
|
||||
|
||||
Reference: `.agents/skills/monitor-experiment.md`
|
||||
|
||||
### 4. Summarize the Run
|
||||
|
||||
After completion (or at any checkpoint), use the `summarize-run` skill:
|
||||
- Extract final metrics from W&B summary.
|
||||
- Compare against reference runs if available.
|
||||
- Generate a structured report.
|
||||
|
||||
Reference: `.agents/skills/summarize-run.md`
|
||||
|
||||
### 5. Update the Experiment Journal
|
||||
|
||||
Use the `log-experiment` skill to update the journal entry:
|
||||
- Fill in final metrics, duration, checkpoint paths.
|
||||
- Record the key insight learned.
|
||||
- Set status to `completed`, `failed`, or `abandoned`.
|
||||
|
||||
Reference: `.agents/skills/log-experiment.md`
|
||||
|
||||
### 6. Reflect and Capture Lessons
|
||||
|
||||
After every experiment:
|
||||
- **What went right?** → Note in the journal insight field.
|
||||
- **What went wrong?** → Create a lesson in `.agents/lessons/`:
|
||||
- Use the template in `.agents/lessons/README.md`.
|
||||
- Cross-reference the experiment journal entry.
|
||||
- **What was surprising?** → Consider creating an exploration log if this
|
||||
warrants further investigation.
|
||||
|
||||
Reference: `.agents/workflows/lesson-capture.md`
|
||||
|
||||
## Validation Criteria
|
||||
|
||||
This SOP is validated when an agent can:
|
||||
1. Follow steps 1–6 end-to-end for a minimal training run
|
||||
(e.g., `examples/training/finetune/wan_t2v_1.3B/crush_smol/finetune_t2v.sh`
|
||||
with `--max_train_steps 5`).
|
||||
2. Produce a complete experiment journal entry.
|
||||
3. Generate a run summary report.
|
||||
@@ -1,71 +0,0 @@
|
||||
---
|
||||
description: Post-experiment reflection to capture lessons learned
|
||||
---
|
||||
|
||||
# Lesson Capture SOP
|
||||
|
||||
Systematic procedure for turning experiment outcomes into persistent knowledge.
|
||||
|
||||
## When to Use
|
||||
|
||||
After **every** completed or failed experiment. Even successful experiments
|
||||
can yield lessons (e.g., "LR 5e-5 works better than 1e-5 for LoRA").
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Review the Experiment
|
||||
|
||||
Read the experiment journal entry. Ask:
|
||||
- Did anything go wrong?
|
||||
- Was anything surprising?
|
||||
- Did anything take longer than expected?
|
||||
- Was a workaround needed?
|
||||
|
||||
### 2. Decide: Lesson or Not?
|
||||
|
||||
| Situation | Action |
|
||||
|-----------|--------|
|
||||
| Something broke | Create a lesson (category: `infrastructure` or `data`) |
|
||||
| Hyperparameter choice mattered | Create a lesson (category: `hyperparameter`) |
|
||||
| Porting issue found | Create a lesson (category: `porting`) |
|
||||
| Evaluation metric was misleading | Create a lesson (category: `evaluation`) |
|
||||
| Everything went smoothly | No lesson needed, but note in the journal insight |
|
||||
|
||||
### 3. Create the Lesson File
|
||||
|
||||
In `.agents/lessons/`, create `<YYYY-MM-DD>_<short-slug>.md`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
date: <ISO-8601>
|
||||
experiment: <journal entry reference>
|
||||
category: hyperparameter | data | infrastructure | evaluation | porting
|
||||
severity: critical | important | minor
|
||||
---
|
||||
|
||||
# <Short Descriptive Title>
|
||||
|
||||
## What Happened
|
||||
<description>
|
||||
|
||||
## Root Cause
|
||||
<analysis>
|
||||
|
||||
## Fix / Workaround
|
||||
<resolution>
|
||||
|
||||
## Prevention
|
||||
<how to avoid in future>
|
||||
```
|
||||
|
||||
### 4. Cross-Reference
|
||||
|
||||
- Update the experiment journal entry with a link to the lesson file.
|
||||
- If a similar lesson already exists, add a reference or update it.
|
||||
|
||||
### 5. Periodic Pattern Review
|
||||
|
||||
Every ~10 lessons, scan for patterns:
|
||||
- Multiple lessons in the same category → consider a new skill or SOP.
|
||||
- Repeated mistakes → strengthen the relevant SOP with a checklist item.
|
||||
- Infrastructure issues → propose a codebase fix.
|
||||
@@ -1,67 +0,0 @@
|
||||
---
|
||||
description: Synchronize the STATUS.md dashboard by scanning .agents/ directories
|
||||
---
|
||||
|
||||
# Sync Dashboard
|
||||
|
||||
Updates `.agents/STATUS.md` by scanning the skills, workflows, memory, lessons,
|
||||
and exploration directories to reflect what actually exists on disk.
|
||||
|
||||
## When to Use
|
||||
|
||||
- After adding, removing, or renaming any file in `.agents/`.
|
||||
- Periodically (e.g., at end of each conversation session).
|
||||
- When the dashboard feels out of date.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Scan directories
|
||||
|
||||
List all files in each directory:
|
||||
|
||||
```bash
|
||||
echo "=== Skills ==="
|
||||
ls -1 .agents/skills/*.md 2>/dev/null | grep -v SKILL_TEMPLATE
|
||||
|
||||
echo "=== Workflows ==="
|
||||
ls -1 .agents/workflows/*.md 2>/dev/null
|
||||
|
||||
echo "=== Memory ==="
|
||||
ls -1 .agents/memory/*.md 2>/dev/null
|
||||
ls -1 .agents/memory/related-work/*.md 2>/dev/null | grep -v README
|
||||
|
||||
echo "=== Lessons ==="
|
||||
ls -1 .agents/lessons/*.md 2>/dev/null | grep -v README
|
||||
|
||||
echo "=== Exploration ==="
|
||||
ls -1 .agents/exploration/*.md 2>/dev/null | grep -v README
|
||||
```
|
||||
|
||||
### 2. Compare with STATUS.md
|
||||
|
||||
For each file found:
|
||||
- If it's in STATUS.md → leave it (preserve status/trust/tested fields).
|
||||
- If it's NOT in STATUS.md → add it with status `🔴 Stub`, trust `None`, tested `❌`.
|
||||
|
||||
For each entry in STATUS.md:
|
||||
- If the file no longer exists → mark it as `❌ Removed` or delete the row.
|
||||
|
||||
### 3. Update counts
|
||||
|
||||
Recalculate the summary table at the top:
|
||||
- Count files per category.
|
||||
- Count by status (Ready, Draft, Stub).
|
||||
|
||||
### 4. Update timestamp
|
||||
|
||||
Set `_Last synced: <current date>_` at the top of STATUS.md.
|
||||
|
||||
### 5. Review
|
||||
|
||||
Read through the updated STATUS.md for accuracy. Flag anything that looks wrong.
|
||||
|
||||
## Notes
|
||||
|
||||
- Do NOT change trust levels during sync — those are set manually after testing.
|
||||
- Do NOT change status during sync — status changes require actual validation.
|
||||
- This workflow only handles structural sync (file existence), not content review.
|
||||
@@ -1,5 +1,9 @@
|
||||
{
|
||||
"benchmark_id": "wan-t2v-1.3b-2gpu",
|
||||
"config_schema_version": 2,
|
||||
"workload_id": "wan-t2v",
|
||||
"variant_id": "1.3b-sp2",
|
||||
"benchmark_version": 3,
|
||||
"description": "Wan2.1 T2V 1.3B inference performance",
|
||||
"model": {
|
||||
"model_path": "Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
|
||||
@@ -36,7 +40,10 @@
|
||||
"thresholds": {
|
||||
"L40S": {
|
||||
"max_generation_time_s": 34.0,
|
||||
"max_peak_memory_mb": 11000.0
|
||||
"max_peak_memory_mb": 11000.0,
|
||||
"max_text_encoder_time_s": 5.0,
|
||||
"max_dit_time_s": 10.0,
|
||||
"max_vae_decode_time_s": 10.0
|
||||
},
|
||||
"default": {
|
||||
"max_generation_time_s": 120.0,
|
||||
|
||||
+462
-419
@@ -1,6 +1,9 @@
|
||||
env:
|
||||
IMAGE_VERSION: "py3.12-latest"
|
||||
BUILDKITE_CLEAN_CHECKOUT: true
|
||||
# Slurm workers clone the immutable commit and initialize submodules inside
|
||||
# their isolated container. The Buildkite login-plane checkout is a no-op.
|
||||
BUILDKITE_GIT_SUBMODULES: false
|
||||
|
||||
notify:
|
||||
- github_commit_status:
|
||||
@@ -8,431 +11,471 @@ notify:
|
||||
if: build.env("TEST_SCOPE") == "fastcheck" || build.env("TEST_SCOPE") == null
|
||||
- github_commit_status:
|
||||
context: "full-suite-passed"
|
||||
if: build.env("TEST_SCOPE") == "full"
|
||||
if: build.env("TEST_SCOPE") == "full" || build.env("TEST_SCOPE") == "merge"
|
||||
- github_commit_status:
|
||||
context: "direct-test-completed"
|
||||
if: build.env("TEST_SCOPE") == "direct"
|
||||
- github_commit_status:
|
||||
context: "scheduled-ssim-passed"
|
||||
if: build.env("TEST_SCOPE") == "scheduled"
|
||||
|
||||
# This is the complete active GPU CI surface. Every command is a trusted host
|
||||
# dispatcher, and every test payload executes inside the Slinky Slurm tray.
|
||||
# fastvideo/tests/modal remains available only for an explicit manual rollback;
|
||||
# no active pipeline or slash-command route invokes it.
|
||||
steps:
|
||||
# ============================================================
|
||||
# Direct test: triggered by /test <name> slash command.
|
||||
# Labels match fastcheck/full-suite counterparts so the GitHub
|
||||
# check status overwrites the original failed check.
|
||||
# Only ONE step executes per build (gated by TEST_TYPE).
|
||||
# ============================================================
|
||||
- label: ":microscope: Encoder Tests"
|
||||
key: "encoder"
|
||||
if: |
|
||||
build.env("TEST_SCOPE") == "full" ||
|
||||
(build.env("TEST_SCOPE") == "merge" &&
|
||||
build.env("MERGE_TEST_PLAN") =~ /,encoder,/) ||
|
||||
build.env("TEST_SCOPE") == "fastcheck" ||
|
||||
build.env("TEST_SCOPE") == null ||
|
||||
(build.env("TEST_SCOPE") == "direct" &&
|
||||
(build.env("TEST_TYPE") == "encoder" || build.env("TEST_TYPE") == "encoder_ci"))
|
||||
command: "/opt/fastvideo-ci-runner/run-ci"
|
||||
timeout_in_minutes: 90
|
||||
env:
|
||||
TEST_TYPE: "encoder_ci"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "ci-runner"
|
||||
|
||||
# --- Fastcheck-scope direct tests ---
|
||||
- label: ":microscope: Encoder Tests"
|
||||
if: build.env("TEST_SCOPE") == "direct" && build.env("TEST_TYPE") == "encoder"
|
||||
command: "timeout 90m .buildkite/scripts/pr_test.sh"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "default"
|
||||
- label: ":microscope: VAE Tests"
|
||||
if: build.env("TEST_SCOPE") == "direct" && build.env("TEST_TYPE") == "vae"
|
||||
command: "timeout 90m .buildkite/scripts/pr_test.sh"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "default"
|
||||
- label: ":microscope: Transformer Tests"
|
||||
if: build.env("TEST_SCOPE") == "direct" && build.env("TEST_TYPE") == "transformer"
|
||||
command: "timeout 90m .buildkite/scripts/pr_test.sh"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "default"
|
||||
- label: ":microscope: Kernel Tests"
|
||||
if: build.env("TEST_SCOPE") == "direct" && build.env("TEST_TYPE") == "kernel_tests"
|
||||
command: "timeout 90m .buildkite/scripts/pr_test.sh"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "default"
|
||||
- label: ":microscope: Unit Tests"
|
||||
if: build.env("TEST_SCOPE") == "direct" && build.env("TEST_TYPE") == "unit_test"
|
||||
command: "timeout 90m .buildkite/scripts/pr_test.sh"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "default"
|
||||
- label: ":microscope: VAE Tests"
|
||||
key: "vae"
|
||||
if: |
|
||||
build.env("TEST_SCOPE") == "full" ||
|
||||
(build.env("TEST_SCOPE") == "merge" &&
|
||||
build.env("MERGE_TEST_PLAN") =~ /,vae,/) ||
|
||||
build.env("TEST_SCOPE") == "fastcheck" ||
|
||||
build.env("TEST_SCOPE") == null ||
|
||||
(build.env("TEST_SCOPE") == "direct" &&
|
||||
(build.env("TEST_TYPE") == "vae" || build.env("TEST_TYPE") == "vae_ci"))
|
||||
command: "/opt/fastvideo-ci-runner/run-ci"
|
||||
timeout_in_minutes: 90
|
||||
env:
|
||||
TEST_TYPE: "vae_ci"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "ci-runner"
|
||||
|
||||
# --- Full-suite-scope direct tests ---
|
||||
- label: ":bar_chart: SSIM Tests"
|
||||
if: build.env("TEST_SCOPE") == "direct" && build.env("TEST_TYPE") == "ssim"
|
||||
command: "timeout 90m .buildkite/scripts/pr_test.sh"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
- exit_status: 1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "default"
|
||||
- label: ":test_tube: LoRA Inference Tests"
|
||||
if: build.env("TEST_SCOPE") == "direct" && build.env("TEST_TYPE") == "inference_lora"
|
||||
command: "timeout 90m .buildkite/scripts/pr_test.sh"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "default"
|
||||
- label: ":test_tube: Training Tests"
|
||||
if: build.env("TEST_SCOPE") == "direct" && build.env("TEST_TYPE") == "training"
|
||||
command: "timeout 90m .buildkite/scripts/pr_test.sh"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "default"
|
||||
- label: ":test_tube: Distillation DMD Tests"
|
||||
if: build.env("TEST_SCOPE") == "direct" && build.env("TEST_TYPE") == "distillation_dmd"
|
||||
command: "timeout 90m .buildkite/scripts/pr_test.sh"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "default"
|
||||
- label: ":test_tube: Self-Forcing Tests"
|
||||
if: build.env("TEST_SCOPE") == "direct" && build.env("TEST_TYPE") == "self_forcing"
|
||||
command: "timeout 90m .buildkite/scripts/pr_test.sh"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "default"
|
||||
- label: ":test_tube: LoRA Training Tests"
|
||||
if: build.env("TEST_SCOPE") == "direct" && build.env("TEST_TYPE") == "training_lora"
|
||||
command: "timeout 90m .buildkite/scripts/pr_test.sh"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
- exit_status: 1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "default"
|
||||
- label: ":test_tube: Training Tests VSA"
|
||||
if: build.env("TEST_SCOPE") == "direct" && build.env("TEST_TYPE") == "training_vsa"
|
||||
command: "timeout 90m .buildkite/scripts/pr_test.sh"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
- exit_status: 1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "default"
|
||||
- label: ":test_tube: Inference Tests VMoBA"
|
||||
if: build.env("TEST_SCOPE") == "direct" && build.env("TEST_TYPE") == "inference_vmoba"
|
||||
command: "timeout 90m .buildkite/scripts/pr_test.sh"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "default"
|
||||
- label: ":test_tube: Performance Tests"
|
||||
if: build.env("TEST_SCOPE") == "direct" && build.env("TEST_TYPE") == "performance"
|
||||
command: "timeout 90m .buildkite/scripts/pr_test.sh"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "default"
|
||||
- label: ":test_tube: API Server Tests"
|
||||
if: build.env("TEST_SCOPE") == "direct" && build.env("TEST_TYPE") == "api_server"
|
||||
command: "timeout 90m .buildkite/scripts/pr_test.sh"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "default"
|
||||
- label: ":microscope: Transformer Tests"
|
||||
key: "transformer"
|
||||
if: |
|
||||
build.env("TEST_SCOPE") == "full" ||
|
||||
(build.env("TEST_SCOPE") == "merge" &&
|
||||
build.env("MERGE_TEST_PLAN") =~ /,transformer,/) ||
|
||||
build.env("TEST_SCOPE") == "fastcheck" ||
|
||||
build.env("TEST_SCOPE") == null ||
|
||||
(build.env("TEST_SCOPE") == "direct" &&
|
||||
(build.env("TEST_TYPE") == "transformer" || build.env("TEST_TYPE") == "transformer_ci"))
|
||||
command: "/opt/fastvideo-ci-runner/run-ci"
|
||||
timeout_in_minutes: 90
|
||||
env:
|
||||
TEST_TYPE: "transformer_ci"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "ci-runner"
|
||||
|
||||
# ============================================================
|
||||
# Fastcheck: Runs on every PR (~10-15 min parallel)
|
||||
# Core component validation: encoders, VAEs, transformers,
|
||||
# CUDA kernels, and unit tests.
|
||||
# ============================================================
|
||||
- label: "Trigger Fastcheck"
|
||||
if: build.env("TEST_SCOPE") == "fastcheck" || build.env("TEST_SCOPE") == null
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
plugins:
|
||||
- monorepo-diff#v1.4.0:
|
||||
diff: 'git fetch origin "${BUILDKITE_PULL_REQUEST_BASE_BRANCH:-main}" && git diff --name-only "origin/${BUILDKITE_PULL_REQUEST_BASE_BRANCH:-main}...HEAD"'
|
||||
watch:
|
||||
- path:
|
||||
- "fastvideo/models/encoders/**"
|
||||
- "fastvideo/models/loader/**"
|
||||
- "fastvideo/tests/encoders/**"
|
||||
- "pyproject.toml"
|
||||
- "docker/Dockerfile.python3.12"
|
||||
config:
|
||||
command: "timeout 20m .buildkite/scripts/pr_test.sh"
|
||||
label: ":microscope: Encoder Tests"
|
||||
env:
|
||||
- TEST_TYPE=encoder
|
||||
agents:
|
||||
queue: "default"
|
||||
- path:
|
||||
- "fastvideo/models/vaes/**"
|
||||
- "fastvideo/models/loader/**"
|
||||
- "fastvideo/tests/vaes/**"
|
||||
- "pyproject.toml"
|
||||
- "docker/Dockerfile.python3.12"
|
||||
config:
|
||||
command: "timeout 20m .buildkite/scripts/pr_test.sh"
|
||||
label: ":microscope: VAE Tests"
|
||||
env:
|
||||
- TEST_TYPE=vae
|
||||
agents:
|
||||
queue: "default"
|
||||
- path:
|
||||
- "fastvideo/models/dits/**"
|
||||
- "fastvideo/models/loader/**"
|
||||
- "fastvideo/tests/transformers/**"
|
||||
- "fastvideo/layers/**"
|
||||
- "fastvideo/attention/**"
|
||||
- "pyproject.toml"
|
||||
- "docker/Dockerfile.python3.12"
|
||||
config:
|
||||
command: "timeout 15m .buildkite/scripts/pr_test.sh"
|
||||
label: ":microscope: Transformer Tests"
|
||||
env:
|
||||
- TEST_TYPE=transformer
|
||||
agents:
|
||||
queue: "default"
|
||||
- path:
|
||||
- "fastvideo-kernel/**"
|
||||
- "pyproject.toml"
|
||||
- "docker/Dockerfile.python3.12"
|
||||
config:
|
||||
command: "timeout 15m .buildkite/scripts/pr_test.sh"
|
||||
label: ":microscope: Kernel Tests"
|
||||
env:
|
||||
- TEST_TYPE=kernel_tests
|
||||
agents:
|
||||
queue: "default"
|
||||
- path:
|
||||
- "fastvideo/**"
|
||||
- ".buildkite/**"
|
||||
- ".github/**"
|
||||
- "pyproject.toml"
|
||||
- "docker/Dockerfile.python3.12"
|
||||
config:
|
||||
command: "timeout 15m .buildkite/scripts/pr_test.sh"
|
||||
label: ":microscope: Unit Tests"
|
||||
env:
|
||||
- TEST_TYPE=unit_test
|
||||
agents:
|
||||
queue: "default"
|
||||
- label: ":microscope: Kernel Tests"
|
||||
key: "kernel-tests"
|
||||
if: |
|
||||
build.env("TEST_SCOPE") == "full" ||
|
||||
(build.env("TEST_SCOPE") == "merge" &&
|
||||
build.env("MERGE_TEST_PLAN") =~ /,kernel-tests,/) ||
|
||||
build.env("TEST_SCOPE") == "fastcheck" ||
|
||||
build.env("TEST_SCOPE") == null ||
|
||||
(build.env("TEST_SCOPE") == "direct" &&
|
||||
(build.env("TEST_TYPE") == "kernel_tests" || build.env("TEST_TYPE") == "kernel_tests_ci"))
|
||||
command: "/opt/fastvideo-ci-runner/run-ci"
|
||||
timeout_in_minutes: 90
|
||||
env:
|
||||
TEST_TYPE: "kernel_tests_ci"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "ci-runner"
|
||||
|
||||
# ============================================================
|
||||
# Full Suite: Runs when TEST_SCOPE=full
|
||||
# Triggered by adding the 'ready' label (via ci-trigger-full-suite.yml)
|
||||
# or on-demand via /test full slash command.
|
||||
# Includes integration tests, SSIM regression, training pipelines,
|
||||
# and performance benchmarks.
|
||||
# ============================================================
|
||||
- label: "Trigger Full Suite"
|
||||
if: build.env("TEST_SCOPE") == "full"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
plugins:
|
||||
- monorepo-diff#v1.4.0:
|
||||
diff: 'git fetch origin "${BUILDKITE_PULL_REQUEST_BASE_BRANCH:-main}" && git diff --name-only "origin/${BUILDKITE_PULL_REQUEST_BASE_BRANCH:-main}...HEAD"'
|
||||
watch:
|
||||
- path:
|
||||
- "fastvideo/**/*.py"
|
||||
- "pyproject.toml"
|
||||
- "docker/Dockerfile.python3.12"
|
||||
config:
|
||||
command: "timeout 90m .buildkite/scripts/pr_test.sh"
|
||||
label: ":bar_chart: SSIM Tests"
|
||||
env:
|
||||
- TEST_TYPE=ssim
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "default"
|
||||
- path:
|
||||
- "fastvideo/tests/lora/**"
|
||||
- "fastvideo/models/loader/**"
|
||||
- "fastvideo/tests/transformers/**"
|
||||
- "fastvideo/pipelines/**"
|
||||
- "fastvideo/layers/lora/**"
|
||||
- "pyproject.toml"
|
||||
- "docker/Dockerfile.python3.12"
|
||||
config:
|
||||
command: "timeout 20m .buildkite/scripts/pr_test.sh"
|
||||
label: ":test_tube: LoRA Inference Tests"
|
||||
env:
|
||||
- TEST_TYPE=inference_lora
|
||||
agents:
|
||||
queue: "default"
|
||||
- path:
|
||||
- "fastvideo/**"
|
||||
- "pyproject.toml"
|
||||
- "docker/Dockerfile.python3.12"
|
||||
config:
|
||||
command: "timeout 15m .buildkite/scripts/pr_test.sh"
|
||||
label: ":test_tube: Training Tests"
|
||||
env:
|
||||
- TEST_TYPE=training
|
||||
agents:
|
||||
queue: "default"
|
||||
- path:
|
||||
- "fastvideo/training/*distillation_pipeline.py"
|
||||
- "pyproject.toml"
|
||||
- "docker/Dockerfile.python3.12"
|
||||
config:
|
||||
command: "timeout 15m .buildkite/scripts/pr_test.sh"
|
||||
label: ":test_tube: Distillation DMD Tests"
|
||||
env:
|
||||
- TEST_TYPE=distillation_dmd
|
||||
agents:
|
||||
queue: "default"
|
||||
- path:
|
||||
- "fastvideo/training/*self_forcing_distillation_pipeline.py"
|
||||
- "fastvideo/tests/training/self-forcing/**"
|
||||
- "pyproject.toml"
|
||||
- "docker/Dockerfile.python3.12"
|
||||
config:
|
||||
command: "timeout 15m .buildkite/scripts/pr_test.sh"
|
||||
label: ":test_tube: Self-Forcing Tests"
|
||||
env:
|
||||
- TEST_TYPE=self_forcing
|
||||
agents:
|
||||
queue: "default"
|
||||
- path:
|
||||
- "fastvideo/**"
|
||||
- "pyproject.toml"
|
||||
- "docker/Dockerfile.python3.12"
|
||||
config:
|
||||
command: "timeout 15m .buildkite/scripts/pr_test.sh"
|
||||
label: ":test_tube: LoRA Training Tests"
|
||||
env:
|
||||
- TEST_TYPE=training_lora
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "default"
|
||||
- path:
|
||||
- "fastvideo/**"
|
||||
- "fastvideo-kernel/**"
|
||||
- "pyproject.toml"
|
||||
- "docker/Dockerfile.python3.12"
|
||||
config:
|
||||
command: "timeout 15m .buildkite/scripts/pr_test.sh"
|
||||
label: ":test_tube: Training Tests VSA"
|
||||
env:
|
||||
- TEST_TYPE=training_vsa
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "default"
|
||||
- path:
|
||||
- "fastvideo-kernel/**"
|
||||
- "fastvideo/attention/backends/vmoba.py"
|
||||
- "pyproject.toml"
|
||||
- "docker/Dockerfile.python3.12"
|
||||
config:
|
||||
command: "timeout 15m .buildkite/scripts/pr_test.sh"
|
||||
label: ":test_tube: Inference Tests VMoBA"
|
||||
env:
|
||||
- TEST_TYPE=inference_vmoba
|
||||
agents:
|
||||
queue: "default"
|
||||
- path:
|
||||
- "fastvideo/models/dits/**"
|
||||
- "fastvideo/pipelines/**"
|
||||
- "fastvideo/attention/**"
|
||||
- "fastvideo/layers/**"
|
||||
- "fastvideo/worker/**"
|
||||
- "fastvideo/entrypoints/**"
|
||||
- "fastvideo/tests/performance/**"
|
||||
- ".buildkite/performance-benchmarks/**"
|
||||
- "pyproject.toml"
|
||||
- "docker/Dockerfile.python3.12"
|
||||
config:
|
||||
command: "timeout 30m .buildkite/scripts/pr_test.sh"
|
||||
label: ":test_tube: Performance Tests"
|
||||
env:
|
||||
- TEST_TYPE=performance
|
||||
agents:
|
||||
queue: "default"
|
||||
- path:
|
||||
- "fastvideo/entrypoints/openai/**"
|
||||
- "fastvideo/entrypoints/cli/serve.py"
|
||||
- "fastvideo/tests/entrypoints/test_openai_api_integration.py"
|
||||
- "pyproject.toml"
|
||||
- "docker/Dockerfile.python3.12"
|
||||
config:
|
||||
command: "timeout 30m .buildkite/scripts/pr_test.sh"
|
||||
label: ":test_tube: API Server Tests"
|
||||
env:
|
||||
- TEST_TYPE=api_server
|
||||
agents:
|
||||
queue: "default"
|
||||
- label: ":microscope: Unit Tests"
|
||||
key: "unit"
|
||||
if: |
|
||||
build.env("TEST_SCOPE") == "full" ||
|
||||
(build.env("TEST_SCOPE") == "merge" &&
|
||||
build.env("MERGE_TEST_PLAN") =~ /,unit,/) ||
|
||||
build.env("TEST_SCOPE") == "fastcheck" ||
|
||||
build.env("TEST_SCOPE") == null ||
|
||||
(build.env("TEST_SCOPE") == "direct" &&
|
||||
(build.env("TEST_TYPE") == "unit_test" || build.env("TEST_TYPE") == "unit_test_ci"))
|
||||
command: "/opt/fastvideo-ci-runner/run-unit"
|
||||
timeout_in_minutes: 90
|
||||
env:
|
||||
TEST_TYPE: "unit_test_ci"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "ci-runner"
|
||||
|
||||
- label: ":microscope: DreamVerse App Tests"
|
||||
key: "dreamverse"
|
||||
if: |
|
||||
build.env("TEST_SCOPE") == "full" ||
|
||||
(build.env("TEST_SCOPE") == "merge" &&
|
||||
build.env("MERGE_TEST_PLAN") =~ /,dreamverse,/) ||
|
||||
build.env("TEST_SCOPE") == "fastcheck" ||
|
||||
build.env("TEST_SCOPE") == null ||
|
||||
(build.env("TEST_SCOPE") == "direct" &&
|
||||
(build.env("TEST_TYPE") == "dreamverse_app" || build.env("TEST_TYPE") == "dreamverse_app_ci"))
|
||||
command: "/opt/fastvideo-ci-runner/run-ci"
|
||||
timeout_in_minutes: 90
|
||||
env:
|
||||
TEST_TYPE: "dreamverse_app_ci"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "ci-runner"
|
||||
|
||||
- label: ":test_tube: Golden-Gate Tests"
|
||||
key: "golden-gate"
|
||||
if: |
|
||||
build.env("TEST_SCOPE") == "full" ||
|
||||
(build.env("TEST_SCOPE") == "merge" &&
|
||||
build.env("MERGE_TEST_PLAN") =~ /,golden-gate,/) ||
|
||||
(build.env("TEST_SCOPE") == "direct" &&
|
||||
(build.env("TEST_TYPE") == "golden_gate" || build.env("TEST_TYPE") == "golden_gate_ci"))
|
||||
command: "/opt/fastvideo-ci-runner/run-ci"
|
||||
timeout_in_minutes: 90
|
||||
env:
|
||||
TEST_TYPE: "golden_gate_ci"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "ci-runner"
|
||||
|
||||
- label: ":bar_chart: SSIM Tests"
|
||||
key: "ssim"
|
||||
depends_on: "golden-gate"
|
||||
if: |
|
||||
build.env("TEST_SCOPE") == "full" ||
|
||||
build.env("TEST_SCOPE") == "scheduled" ||
|
||||
(build.env("TEST_SCOPE") == "merge" &&
|
||||
build.env("MERGE_TEST_PLAN") =~ /,ssim,/) ||
|
||||
(build.env("TEST_SCOPE") == "direct" &&
|
||||
(build.env("TEST_TYPE") == "ssim" || build.env("TEST_TYPE") == "ssim_ci"))
|
||||
command: "/opt/fastvideo-ci-runner/run-ci"
|
||||
timeout_in_minutes: 90
|
||||
concurrency: 1
|
||||
concurrency_group: "fastvideo/slinky/whole-tray"
|
||||
env:
|
||||
TEST_TYPE: "ssim_ci"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
- exit_status: 1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "ci-runner"
|
||||
|
||||
- label: ":test_tube: LoRA Inference Tests"
|
||||
key: "lora-inference"
|
||||
depends_on: "golden-gate"
|
||||
if: |
|
||||
build.env("TEST_SCOPE") == "full" ||
|
||||
(build.env("TEST_SCOPE") == "merge" &&
|
||||
build.env("MERGE_TEST_PLAN") =~ /,lora-inference,/) ||
|
||||
(build.env("TEST_SCOPE") == "direct" &&
|
||||
(build.env("TEST_TYPE") == "inference_lora" || build.env("TEST_TYPE") == "inference_lora_ci"))
|
||||
command: "/opt/fastvideo-ci-runner/run-ci"
|
||||
timeout_in_minutes: 90
|
||||
env:
|
||||
TEST_TYPE: "inference_lora_ci"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "ci-runner"
|
||||
|
||||
- label: ":test_tube: LoRA Extraction Tests"
|
||||
key: "lora-extraction"
|
||||
depends_on: "golden-gate"
|
||||
if: |
|
||||
build.env("TEST_SCOPE") == "full" ||
|
||||
(build.env("TEST_SCOPE") == "merge" &&
|
||||
build.env("MERGE_TEST_PLAN") =~ /,lora-extraction,/) ||
|
||||
(build.env("TEST_SCOPE") == "direct" &&
|
||||
(build.env("TEST_TYPE") == "lora_extraction" || build.env("TEST_TYPE") == "lora_extraction_ci"))
|
||||
command: "/opt/fastvideo-ci-runner/run-ci"
|
||||
timeout_in_minutes: 90
|
||||
env:
|
||||
TEST_TYPE: "lora_extraction_ci"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "ci-runner"
|
||||
|
||||
- label: ":test_tube: Training Tests"
|
||||
key: "training"
|
||||
depends_on: "golden-gate"
|
||||
if: |
|
||||
build.env("TEST_SCOPE") == "full" ||
|
||||
(build.env("TEST_SCOPE") == "merge" &&
|
||||
build.env("MERGE_TEST_PLAN") =~ /,training,/) ||
|
||||
(build.env("TEST_SCOPE") == "direct" &&
|
||||
(build.env("TEST_TYPE") == "training" || build.env("TEST_TYPE") == "training_ci"))
|
||||
command: "/opt/fastvideo-ci-runner/run-ci"
|
||||
timeout_in_minutes: 90
|
||||
concurrency: 1
|
||||
concurrency_group: "fastvideo/slinky/whole-tray"
|
||||
env:
|
||||
TEST_TYPE: "training_ci"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "ci-runner"
|
||||
|
||||
- label: ":test_tube: Distillation DMD Tests"
|
||||
key: "distillation"
|
||||
depends_on: "golden-gate"
|
||||
if: |
|
||||
build.env("TEST_SCOPE") == "full" ||
|
||||
(build.env("TEST_SCOPE") == "merge" &&
|
||||
build.env("MERGE_TEST_PLAN") =~ /,distillation,/) ||
|
||||
(build.env("TEST_SCOPE") == "direct" &&
|
||||
(build.env("TEST_TYPE") == "distillation_dmd" || build.env("TEST_TYPE") == "distillation_dmd_ci"))
|
||||
command: "/opt/fastvideo-ci-runner/run-ci"
|
||||
timeout_in_minutes: 90
|
||||
env:
|
||||
TEST_TYPE: "distillation_dmd_ci"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "ci-runner"
|
||||
|
||||
- label: ":test_tube: Self-Forcing Tests"
|
||||
key: "self-forcing"
|
||||
depends_on: "golden-gate"
|
||||
if: |
|
||||
build.env("TEST_SCOPE") == "full" ||
|
||||
(build.env("TEST_SCOPE") == "merge" &&
|
||||
build.env("MERGE_TEST_PLAN") =~ /,self-forcing,/) ||
|
||||
(build.env("TEST_SCOPE") == "direct" &&
|
||||
(build.env("TEST_TYPE") == "self_forcing" || build.env("TEST_TYPE") == "self_forcing_ci"))
|
||||
command: "/opt/fastvideo-ci-runner/run-ci"
|
||||
timeout_in_minutes: 90
|
||||
env:
|
||||
TEST_TYPE: "self_forcing_ci"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "ci-runner"
|
||||
|
||||
- label: ":test_tube: LoRA Training Tests"
|
||||
key: "lora-training"
|
||||
depends_on: "golden-gate"
|
||||
if: |
|
||||
build.env("TEST_SCOPE") == "full" ||
|
||||
(build.env("TEST_SCOPE") == "merge" &&
|
||||
build.env("MERGE_TEST_PLAN") =~ /,lora-training,/) ||
|
||||
(build.env("TEST_SCOPE") == "direct" &&
|
||||
(build.env("TEST_TYPE") == "training_lora" || build.env("TEST_TYPE") == "training_lora_ci"))
|
||||
command: "/opt/fastvideo-ci-runner/run-ci"
|
||||
timeout_in_minutes: 90
|
||||
env:
|
||||
TEST_TYPE: "training_lora_ci"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
- exit_status: 1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "ci-runner"
|
||||
|
||||
- label: ":test_tube: Training Tests VSA"
|
||||
key: "training-vsa"
|
||||
depends_on: "golden-gate"
|
||||
if: |
|
||||
build.env("TEST_SCOPE") == "full" ||
|
||||
(build.env("TEST_SCOPE") == "merge" &&
|
||||
build.env("MERGE_TEST_PLAN") =~ /,training-vsa,/) ||
|
||||
(build.env("TEST_SCOPE") == "direct" &&
|
||||
(build.env("TEST_TYPE") == "training_vsa" || build.env("TEST_TYPE") == "training_vsa_ci"))
|
||||
command: "/opt/fastvideo-ci-runner/run-ci"
|
||||
timeout_in_minutes: 90
|
||||
env:
|
||||
TEST_TYPE: "training_vsa_ci"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
- exit_status: 1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "ci-runner"
|
||||
|
||||
- label: ":test_tube: Inference Tests VMoBA"
|
||||
key: "inference-vmoba"
|
||||
depends_on: "golden-gate"
|
||||
if: |
|
||||
build.env("TEST_SCOPE") == "full" ||
|
||||
(build.env("TEST_SCOPE") == "merge" &&
|
||||
build.env("MERGE_TEST_PLAN") =~ /,inference-vmoba,/) ||
|
||||
(build.env("TEST_SCOPE") == "direct" &&
|
||||
(build.env("TEST_TYPE") == "inference_vmoba" || build.env("TEST_TYPE") == "inference_vmoba_ci"))
|
||||
command: "/opt/fastvideo-ci-runner/run-ci"
|
||||
timeout_in_minutes: 90
|
||||
env:
|
||||
TEST_TYPE: "inference_vmoba_ci"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "ci-runner"
|
||||
|
||||
- label: ":test_tube: Performance Tests"
|
||||
key: "performance"
|
||||
depends_on: "golden-gate"
|
||||
if: |
|
||||
build.env("TEST_SCOPE") == "full" ||
|
||||
(build.env("TEST_SCOPE") == "merge" &&
|
||||
build.env("MERGE_TEST_PLAN") =~ /,performance,/) ||
|
||||
(build.env("TEST_SCOPE") == "direct" &&
|
||||
(build.env("TEST_TYPE") == "performance" || build.env("TEST_TYPE") == "performance_ci"))
|
||||
command: "/opt/fastvideo-ci-runner/run-ci"
|
||||
timeout_in_minutes: 90
|
||||
env:
|
||||
TEST_TYPE: "performance_ci"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "ci-runner"
|
||||
|
||||
- label: ":test_tube: API Server Tests"
|
||||
key: "api-server"
|
||||
depends_on: "golden-gate"
|
||||
if: |
|
||||
build.env("TEST_SCOPE") == "full" ||
|
||||
(build.env("TEST_SCOPE") == "merge" &&
|
||||
build.env("MERGE_TEST_PLAN") =~ /,api-server,/) ||
|
||||
(build.env("TEST_SCOPE") == "direct" &&
|
||||
(build.env("TEST_TYPE") == "api_server" || build.env("TEST_TYPE") == "api_server_ci"))
|
||||
command: "/opt/fastvideo-ci-runner/run-ci"
|
||||
timeout_in_minutes: 90
|
||||
env:
|
||||
TEST_TYPE: "api_server_ci"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "ci-runner"
|
||||
|
||||
- label: ":test_tube: Train Framework Tests"
|
||||
key: "train-framework"
|
||||
depends_on: "golden-gate"
|
||||
if: |
|
||||
build.env("TEST_SCOPE") == "full" ||
|
||||
(build.env("TEST_SCOPE") == "merge" &&
|
||||
build.env("MERGE_TEST_PLAN") =~ /,train-framework,/) ||
|
||||
(build.env("TEST_SCOPE") == "direct" &&
|
||||
(build.env("TEST_TYPE") == "train_framework" || build.env("TEST_TYPE") == "train_framework_ci"))
|
||||
command: "/opt/fastvideo-ci-runner/run-ci"
|
||||
timeout_in_minutes: 90
|
||||
env:
|
||||
TEST_TYPE: "train_framework_ci"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "ci-runner"
|
||||
|
||||
- label: ":test_tube: Eval Metrics Tests"
|
||||
key: "eval"
|
||||
depends_on: "golden-gate"
|
||||
if: |
|
||||
build.env("TEST_SCOPE") == "full" ||
|
||||
(build.env("TEST_SCOPE") == "merge" &&
|
||||
build.env("MERGE_TEST_PLAN") =~ /,eval,/) ||
|
||||
(build.env("TEST_SCOPE") == "direct" &&
|
||||
(build.env("TEST_TYPE") == "eval" || build.env("TEST_TYPE") == "eval_ci"))
|
||||
command: "/opt/fastvideo-ci-runner/run-ci"
|
||||
timeout_in_minutes: 90
|
||||
env:
|
||||
TEST_TYPE: "eval_ci"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "ci-runner"
|
||||
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Canonical Slurm CI selection for the OpenAI-compatible API lane.
|
||||
set -euo pipefail
|
||||
|
||||
exec pytest ./fastvideo/tests/entrypoints/test_openai_api_integration.py -vs
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Canonical Slurm CI selection for the distillation-DMD lane.
|
||||
set -euo pipefail
|
||||
|
||||
exec pytest ./fastvideo/tests/training/distill/test_distill_dmd.py -vs
|
||||
Executable
+87
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env bash
|
||||
# DreamVerse needs a GPU for import-time device resolution, but it does not
|
||||
# build or exercise fastvideo-kernel. A checksummed Node archive is installed
|
||||
# in the disposable Slurm container because the shared CI image is
|
||||
# Python/CUDA focused.
|
||||
set -euo pipefail
|
||||
|
||||
node_version=v22.23.2
|
||||
case $(uname -m) in
|
||||
aarch64 | arm64)
|
||||
node_arch=arm64
|
||||
node_archive_sha256=013b59cfd2819703a6f4a14ab891fc46fc2a4e3f5bcd92de3fb4929b43e35b30
|
||||
;;
|
||||
x86_64 | amd64)
|
||||
node_arch=x64
|
||||
node_archive_sha256=b294a556e639d64338823920e5866c21c02741742d2e1529ee1a225c1ec9252a
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported architecture for DreamVerse Node runtime: $(uname -m)" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
node_archive="node-${node_version}-linux-${node_arch}.tar.gz"
|
||||
node_runtime_root=$(mktemp -d -t fastvideo-node.XXXXXX)
|
||||
node_archive_path="${node_runtime_root}/${node_archive}"
|
||||
node_install_dir="${node_runtime_root}/${node_archive%.tar.gz}"
|
||||
curl --proto '=https' --tlsv1.2 --retry 5 --retry-all-errors \
|
||||
--location --fail --silent --show-error \
|
||||
"https://nodejs.org/dist/${node_version}/${node_archive}" \
|
||||
--output "$node_archive_path"
|
||||
printf '%s %s\n' "$node_archive_sha256" "$node_archive_path" | sha256sum --check --status
|
||||
tar -xzf "$node_archive_path" -C "$node_runtime_root"
|
||||
export PATH="${node_install_dir}/bin:${PATH}"
|
||||
node --version
|
||||
npm --version
|
||||
|
||||
export PYTHONPATH="$(pwd)/apps/dreamverse${PYTHONPATH:+:$PYTHONPATH}"
|
||||
pytest apps/dreamverse/dreamverse/tests -q
|
||||
|
||||
cd apps/dreamverse/web
|
||||
npm ci
|
||||
npm run typecheck
|
||||
npm test
|
||||
machine_arch=$(uname -m)
|
||||
if [[ $machine_arch =~ ^(aarch64|arm64)$ ]]; then
|
||||
npx playwright install --with-deps chromium firefox
|
||||
else
|
||||
npx playwright install --with-deps chromium webkit firefox
|
||||
fi
|
||||
|
||||
master_port=${MASTER_PORT:-7959}
|
||||
BACKEND_PORT=${BACKEND_PORT:-$((master_port + 50))}
|
||||
python -m uvicorn dreamverse.mock_server:app --host 127.0.0.1 --port "$BACKEND_PORT" &
|
||||
mock_server_pid=$!
|
||||
cleanup() {
|
||||
kill "$mock_server_pid" 2>/dev/null || true
|
||||
wait "$mock_server_pid" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
for _ in {1..30}; do
|
||||
curl -fsS "http://127.0.0.1:$BACKEND_PORT/healthz" && break
|
||||
sleep 1
|
||||
done
|
||||
curl -fsS "http://127.0.0.1:$BACKEND_PORT/healthz"
|
||||
|
||||
if [[ $machine_arch =~ ^(aarch64|arm64)$ ]]; then
|
||||
# Playwright WebKit traps before opening a page on Linux ARM64, and its
|
||||
# bundled Chromium lacks the H.264/AAC codecs used by the fMP4 assertions.
|
||||
# Firefox covers every flow, including streaming. Chromium and its mobile
|
||||
# profile still cover all codec-independent UI behavior on GB200.
|
||||
BACKEND_HOST=127.0.0.1 BACKEND_PORT="$BACKEND_PORT" CI=1 \
|
||||
npm run e2e -- --project=firefox
|
||||
BACKEND_HOST=127.0.0.1 BACKEND_PORT="$BACKEND_PORT" CI=1 \
|
||||
npm run e2e -- \
|
||||
--project=chromium \
|
||||
--project=mobile-chromium \
|
||||
--grep-invert='streams, plays, and surfaces a downloadable clip|starts a new project and switches back to the prior session|saved projects persist across a page reload'
|
||||
else
|
||||
BACKEND_HOST=127.0.0.1 BACKEND_PORT="$BACKEND_PORT" CI=1 \
|
||||
npm run e2e -- \
|
||||
--project=chromium \
|
||||
--project=webkit \
|
||||
--project=firefox \
|
||||
--project=mobile-safari \
|
||||
--project=mobile-chromium
|
||||
fi
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Canonical Slurm CI selection for the encoder lane.
|
||||
set -euo pipefail
|
||||
|
||||
exec pytest ./fastvideo/tests/encoders -vs
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Canonical Slurm CI selection for the evaluation lane.
|
||||
set -euo pipefail
|
||||
|
||||
exec pytest ./fastvideo/tests/eval -vs
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
# Canonical Slurm CI selection for the golden-gate lane. Environment (HF_HOME
|
||||
# and authentication) is the runner's responsibility.
|
||||
set -euo pipefail
|
||||
|
||||
golden_root=./fastvideo/tests/golden_gate
|
||||
selected=${FASTVIDEO_GOLDEN_TEST_FILES-}
|
||||
if [ -z "$selected" ]; then
|
||||
if [ "${TEST_SCOPE:-}" = merge ]; then
|
||||
echo "Missing FASTVIDEO_GOLDEN_TEST_FILES for merge scope" >&2
|
||||
exit 2
|
||||
fi
|
||||
selected=all
|
||||
fi
|
||||
if [ "$selected" = all ]; then
|
||||
exec pytest "$golden_root" -xvs
|
||||
fi
|
||||
|
||||
[[ $selected =~ ^test_[a-z0-9_]+\.py(,test_[a-z0-9_]+\.py)*$ ]] || {
|
||||
echo "Invalid FASTVIDEO_GOLDEN_TEST_FILES selection" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
IFS=, read -r -a golden_files <<< "$selected"
|
||||
golden_paths=()
|
||||
for golden_file in "${golden_files[@]}"; do
|
||||
golden_path="$golden_root/$golden_file"
|
||||
[ -f "$golden_path" ] || {
|
||||
echo "Selected golden test does not exist: $golden_file" >&2
|
||||
exit 2
|
||||
}
|
||||
golden_paths+=("$golden_path")
|
||||
done
|
||||
|
||||
exec pytest "${golden_paths[@]}" -xvs
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Canonical Slurm CI selection for the LoRA-inference lane.
|
||||
set -euo pipefail
|
||||
|
||||
exec pytest ./fastvideo/tests/inference/lora/test_lora_inference_similarity.py -vs
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Canonical Slurm CI selection for the VMoBA-inference lane.
|
||||
set -euo pipefail
|
||||
|
||||
exec python fastvideo/tests/inference/vmoba/test_vmoba_inference.py
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Canonical Slurm CI selection for the custom-kernel lane.
|
||||
set -euo pipefail
|
||||
|
||||
exec pytest fastvideo-kernel/tests/ -vs
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Canonical Slurm CI selection for the LoRA-extraction lane.
|
||||
set -euo pipefail
|
||||
|
||||
exec pytest ./fastvideo/tests/lora_extraction/test_lora_extraction.py -vs
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env bash
|
||||
# Canonical Slurm performance lane. Reports are written outside the checkout
|
||||
# so the trusted host driver can upload them after untrusted code exits.
|
||||
set -uo pipefail
|
||||
|
||||
export PERFORMANCE_TRACKING_ROOT=/tmp/perf-tracking
|
||||
export PERF_REPORTS_DIR=/workspace/artifacts/performance
|
||||
mkdir -p "$PERF_REPORTS_DIR"
|
||||
|
||||
if [[ ${BUILDKITE_PULL_REQUEST:-false} =~ ^[1-9][0-9]*$ ]]; then
|
||||
export PERF_RUN_SOURCE=pr
|
||||
export PERF_UPLOAD_POLICY=pass
|
||||
elif [ "${BUILDKITE_BRANCH:-}" = main ] \
|
||||
&& { [ "${BUILDKITE_SOURCE:-}" = schedule ] || [ "${TEST_SCOPE:-}" = full ]; }; then
|
||||
export PERF_RUN_SOURCE=scheduled_main
|
||||
export PERF_UPLOAD_POLICY=always
|
||||
elif [ "${TEST_SCOPE:-}" = direct ]; then
|
||||
export PERF_RUN_SOURCE=unknown
|
||||
export PERF_UPLOAD_POLICY=pass
|
||||
else
|
||||
export PERF_RUN_SOURCE=unknown
|
||||
export PERF_UPLOAD_POLICY=never
|
||||
fi
|
||||
|
||||
nvidia-smi \
|
||||
--query-gpu=index,timestamp,clocks.sm,clocks.max.sm,power.draw,power.limit,temperature.gpu \
|
||||
--format=csv -l 10 > "$PERF_REPORTS_DIR/gpu_telemetry.csv" 2>/dev/null &
|
||||
telemetry_pid=$!
|
||||
cleanup() {
|
||||
kill "$telemetry_pid" 2>/dev/null || true
|
||||
wait "$telemetry_pid" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
pytest ./fastvideo/tests/performance -vs
|
||||
pytest_rc=$?
|
||||
compare_rc=0
|
||||
if [ "$pytest_rc" -eq 0 ] || [ "$PERF_UPLOAD_POLICY" = always ]; then
|
||||
PERF_PYTEST_RC=$pytest_rc python ./fastvideo/tests/performance/compare_baseline.py
|
||||
compare_rc=$?
|
||||
fi
|
||||
python ./fastvideo/tests/performance/dashboard.py || true
|
||||
cp -f fastvideo/tests/performance/results/*.json "$PERF_REPORTS_DIR/" 2>/dev/null || true
|
||||
|
||||
echo "--- GPU telemetry (clocks.sm vs clocks.max.sm reveals capped hosts) ---"
|
||||
cat "$PERF_REPORTS_DIR/gpu_telemetry.csv" || true
|
||||
|
||||
final_rc=$pytest_rc
|
||||
if [ "$final_rc" -eq 0 ]; then
|
||||
final_rc=$compare_rc
|
||||
fi
|
||||
exit "$final_rc"
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Canonical Slurm CI selection for the self-forcing lane.
|
||||
set -euo pipefail
|
||||
|
||||
export WANDB_MODE=offline
|
||||
exec pytest ./fastvideo/tests/training/self-forcing/test_self_forcing.py -vs
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env bash
|
||||
# Canonical four-GPU SSIM lane for the Slinky Slurm worker.
|
||||
set -euo pipefail
|
||||
|
||||
args=()
|
||||
if [ "${FASTVIDEO_SSIM_BOOTSTRAP_MODE:-0}" = 1 ]; then
|
||||
args+=(--bootstrap-mode)
|
||||
fi
|
||||
selected=${FASTVIDEO_SSIM_TEST_FILES-}
|
||||
if [ -z "$selected" ]; then
|
||||
if [ "${TEST_SCOPE:-}" = merge ]; then
|
||||
echo "Missing FASTVIDEO_SSIM_TEST_FILES for merge scope" >&2
|
||||
exit 2
|
||||
fi
|
||||
selected=all
|
||||
fi
|
||||
if [ "$selected" != all ]; then
|
||||
[[ $selected =~ ^test_[a-z0-9_]+\.py(,test_[a-z0-9_]+\.py)*$ ]] || {
|
||||
echo "Invalid FASTVIDEO_SSIM_TEST_FILES selection" >&2
|
||||
exit 2
|
||||
}
|
||||
IFS=, read -r -a ssim_files <<< "$selected"
|
||||
for ssim_file in "${ssim_files[@]}"; do
|
||||
args+=(--test-file "$ssim_file")
|
||||
done
|
||||
fi
|
||||
|
||||
# MoGe's utils3d dependency builds glcontext from source on ARM64. The current
|
||||
# runner image predates the baked-in X11 headers below, so keep this guarded
|
||||
# bootstrap until every deployed image digest contains libx11-dev.
|
||||
if [ ! -f /usr/include/X11/Xlib.h ]; then
|
||||
apt-get -o Acquire::Retries=5 update
|
||||
apt-get -o Acquire::Retries=5 install -y --no-install-recommends libx11-dev
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
fi
|
||||
|
||||
uv pip install git+https://github.com/microsoft/MoGe.git
|
||||
uv pip install k_diffusion einops_exts alias_free_torch torchsde
|
||||
|
||||
exec python fastvideo/tests/ssim/ci_runner.py "${args[@]}"
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Canonical Slurm CI selection for the modular training-framework lane.
|
||||
set -euo pipefail
|
||||
|
||||
exec pytest ./fastvideo/tests/train/models ./fastvideo/tests/train/methods -vs
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Canonical Slurm CI selection for the legacy vanilla-training lane.
|
||||
set -euo pipefail
|
||||
|
||||
export WANDB_MODE=offline
|
||||
exec pytest ./fastvideo/tests/training/Vanilla -srP
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Canonical Slurm CI selection for the legacy LoRA-training lane.
|
||||
set -euo pipefail
|
||||
|
||||
export WANDB_MODE=offline
|
||||
exec pytest ./fastvideo/tests/training/lora/test_lora_training.py -srP
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Canonical Slurm CI selection for the legacy VSA-training lane.
|
||||
set -euo pipefail
|
||||
|
||||
export WANDB_MODE=offline
|
||||
exec pytest ./fastvideo/tests/training/VSA -srP
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# Canonical Slurm CI selection for the transformer lane.
|
||||
set -euo pipefail
|
||||
|
||||
# The existing block reference records an absent FASTVIDEO_FA4 (FA2). Keep
|
||||
# that reference identity; the component lane also selects FA2 explicitly.
|
||||
env -u FASTVIDEO_FA4 pytest ./fastvideo/tests/golden_gate/test_wan_t2v.py -xvs
|
||||
pytest ./fastvideo/tests/golden_gate/test_wan_causal.py -xvs
|
||||
exec pytest ./fastvideo/tests/transformers -vs
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Canonical Slurm CI selection for the VAE lane.
|
||||
set -euo pipefail
|
||||
|
||||
pytest ./fastvideo/tests/golden_gate/test_wan_vae.py -xvs
|
||||
exec pytest ./fastvideo/tests/vaes -vs
|
||||
@@ -1,6 +1,19 @@
|
||||
#!/bin/bash
|
||||
set -uo pipefail
|
||||
|
||||
# DORMANT ROLLBACK ONLY. Active CI is Slurm-only and pipeline.yml never calls
|
||||
# this launcher. Refuse every Buildkite invocation even if a stale step or
|
||||
# operator typo reaches this file; local rollback experiments require an
|
||||
# explicit opt-in.
|
||||
if [ -n "${BUILDKITE:-}" ]; then
|
||||
echo "Legacy Modal CI is disabled; use the Slinky Slurm runner." >&2
|
||||
exit 2
|
||||
fi
|
||||
if [ "${FASTVIDEO_ENABLE_LEGACY_MODAL_CI:-0}" != 1 ]; then
|
||||
echo "Legacy Modal CI is dormant. Set FASTVIDEO_ENABLE_LEGACY_MODAL_CI=1 only for a manual rollback test." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
|
||||
}
|
||||
@@ -76,10 +89,27 @@ EFFECTIVE_PR=${BUILDKITE_PULL_REQUEST:-false}
|
||||
if [ "$EFFECTIVE_PR" = "false" ] && [ -n "${PR_NUMBER:-}" ]; then
|
||||
EFFECTIVE_PR=$PR_NUMBER
|
||||
fi
|
||||
MODAL_ENV="BUILDKITE_REPO=$BUILDKITE_REPO BUILDKITE_COMMIT=$BUILDKITE_COMMIT BUILDKITE_PULL_REQUEST=$EFFECTIVE_PR BUILDKITE_BRANCH=${BUILDKITE_BRANCH:-} TEST_SCOPE=${TEST_SCOPE:-} IMAGE_VERSION=$IMAGE_VERSION"
|
||||
MODAL_ENV="BUILDKITE_REPO=$BUILDKITE_REPO BUILDKITE_COMMIT=$BUILDKITE_COMMIT BUILDKITE_PULL_REQUEST=$EFFECTIVE_PR BUILDKITE_BRANCH=${BUILDKITE_BRANCH:-} BUILDKITE_SOURCE=${BUILDKITE_SOURCE:-} TEST_SCOPE=${TEST_SCOPE:-} BUILDKITE_BUILD_URL=${BUILDKITE_BUILD_URL:-} BUILDKITE_BUILD_ID=${BUILDKITE_BUILD_ID:-} BUILDKITE_JOB_ID=${BUILDKITE_JOB_ID:-} IMAGE_VERSION=$IMAGE_VERSION"
|
||||
|
||||
POST_RUN_HOOK=""
|
||||
|
||||
is_truthy() {
|
||||
case "${1:-}" in
|
||||
1|true|TRUE|yes|YES|on|ON) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
ssim_bootstrap_args() {
|
||||
local title="${PR_TITLE:-}"
|
||||
local message="${BUILDKITE_MESSAGE:-}"
|
||||
if is_truthy "${FASTVIDEO_SSIM_BOOTSTRAP_MODE:-}" \
|
||||
|| [[ "$title" == *"[new-model]"* ]] \
|
||||
|| [[ "$message" == *"[new-model]"* ]]; then
|
||||
printf ' --bootstrap-mode'
|
||||
fi
|
||||
}
|
||||
|
||||
upload_performance_artifacts() {
|
||||
SHORT_SHA=${BUILDKITE_COMMIT:0:7}
|
||||
LOCAL_DIR="downloaded_reports"
|
||||
@@ -121,6 +151,19 @@ upload_performance_artifacts() {
|
||||
fi
|
||||
}
|
||||
|
||||
_upload_normalized_perf_results() {
|
||||
local found=0
|
||||
while IFS= read -r -d '' target; do
|
||||
found=1
|
||||
log "Found normalized performance result: $target. Uploading to Buildkite..."
|
||||
buildkite-agent artifact upload "$target"
|
||||
done < <(find "$LOCAL_DIR" -path "*/results/normalized_perf_*.json" -print0)
|
||||
|
||||
if [ "$found" -eq 0 ]; then
|
||||
log "No normalized performance result artifacts found. This is expected when the rolling performance comparison did not run."
|
||||
fi
|
||||
}
|
||||
|
||||
_cleanup_modal_volume() {
|
||||
log "Cleaning up perf_reports/ from Modal Volume..."
|
||||
if modal volume rm hf-model-weights "perf_reports/" --recursive; then
|
||||
@@ -139,6 +182,7 @@ upload_performance_artifacts() {
|
||||
_download_reports || { _cleanup_local; return 1; }
|
||||
_upload_dashboard
|
||||
_upload_perf_summary
|
||||
_upload_normalized_perf_results
|
||||
_cleanup_modal_volume
|
||||
_cleanup_local
|
||||
}
|
||||
@@ -156,9 +200,18 @@ case "$TEST_TYPE" in
|
||||
log "Running transformer tests..."
|
||||
MODAL_COMMAND="$MODAL_ENV HF_API_KEY=$HF_API_KEY python3 -m modal run $MODAL_TEST_FILE::run_transformer_tests"
|
||||
;;
|
||||
"golden_gate")
|
||||
log "Running golden-gate tests..."
|
||||
MODAL_COMMAND="$MODAL_ENV HF_API_KEY=$HF_API_KEY python3 -m modal run $MODAL_TEST_FILE::run_golden_gate_tests"
|
||||
;;
|
||||
"ssim")
|
||||
log "Running SSIM tests..."
|
||||
MODAL_COMMAND="$MODAL_ENV HF_API_KEY=$HF_API_KEY python3 -m modal run $MODAL_SSIM_TEST_FILE::run_ssim_tests"
|
||||
SSIM_BOOTSTRAP_ARGS=$(ssim_bootstrap_args)
|
||||
if [ -n "$SSIM_BOOTSTRAP_ARGS" ]; then
|
||||
log "SSIM bootstrap mode enabled for new-model reference draft generation"
|
||||
fi
|
||||
MODAL_COMMAND="$MODAL_ENV HF_API_KEY=$HF_API_KEY python3 -m modal run "
|
||||
MODAL_COMMAND+="$MODAL_SSIM_TEST_FILE::run_ssim_tests$SSIM_BOOTSTRAP_ARGS"
|
||||
;;
|
||||
"training")
|
||||
log "Running training tests..."
|
||||
@@ -197,6 +250,18 @@ case "$TEST_TYPE" in
|
||||
log "Running unit tests..."
|
||||
MODAL_COMMAND="$MODAL_ENV python3 -m modal run $MODAL_TEST_FILE::run_unit_test"
|
||||
;;
|
||||
"dreamverse_app")
|
||||
log "Running DreamVerse app tests..."
|
||||
MODAL_COMMAND="$MODAL_ENV python3 -m modal run $MODAL_TEST_FILE::run_dreamverse_app_tests"
|
||||
;;
|
||||
"train_framework")
|
||||
log "Running fastvideo.train framework tests..."
|
||||
MODAL_COMMAND="$MODAL_ENV HF_API_KEY=$HF_API_KEY python3 -m modal run $MODAL_TEST_FILE::run_train_framework_tests"
|
||||
;;
|
||||
"eval")
|
||||
log "Running eval metric tests..."
|
||||
MODAL_COMMAND="$MODAL_ENV HF_API_KEY=$HF_API_KEY python3 -m modal run $MODAL_TEST_FILE::run_eval_tests"
|
||||
;;
|
||||
"lora_extraction")
|
||||
log "Running LoRA extraction tests..."
|
||||
MODAL_COMMAND="$MODAL_ENV HF_API_KEY=$HF_API_KEY python3 -m modal run $MODAL_TEST_FILE::run_lora_extraction_tests"
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
exec pytest \
|
||||
./fastvideo/tests/api/ \
|
||||
./fastvideo/tests/contract/ \
|
||||
./fastvideo/tests/dataset/ \
|
||||
./fastvideo/tests/workflow/ \
|
||||
./fastvideo/tests/entrypoints/ \
|
||||
./fastvideo/tests/loader/ \
|
||||
./fastvideo/tests/pipelines/ \
|
||||
./fastvideo/tests/platforms/ \
|
||||
./fastvideo/tests/train/ \
|
||||
./fastvideo/tests/stages/ \
|
||||
./fastvideo/tests/ops/ \
|
||||
./fastvideo/tests/worker/ \
|
||||
./fastvideo/tests/training/test_trackers.py \
|
||||
./fastvideo/tests/attention/test_sdpa_metadata_mask_contract.py \
|
||||
./fastvideo/tests/attention/test_vsa_h3_tile_grad_safety.py \
|
||||
./fastvideo/tests/modal/test_kernel_build_cache.py \
|
||||
./fastvideo/tests/modal/test_pr_test.py \
|
||||
./fastvideo/tests/modal/test_ssim_test.py \
|
||||
--ignore=./fastvideo/tests/entrypoints/test_openai_api_integration.py \
|
||||
--ignore=./fastvideo/tests/train/models \
|
||||
--ignore=./fastvideo/tests/train/methods \
|
||||
-vs
|
||||
@@ -0,0 +1,31 @@
|
||||
# Build-context excludes: keep the context small and the `COPY . .` layer cache
|
||||
# stable. Docker uploads everything here to the daemon and bakes it into a layer;
|
||||
# without this, the 6.5 GB host .venv alone is shipped + cached on every build.
|
||||
#
|
||||
# IMPORTANT: do NOT ignore .git — fastvideo-kernel's build runs
|
||||
# `git submodule update --init --recursive`, which needs the repo metadata.
|
||||
|
||||
# Virtualenvs — the image builds its own /opt/venv
|
||||
.venv/
|
||||
venv/
|
||||
env/
|
||||
|
||||
# Python caches & build/test/lint artifacts
|
||||
**/__pycache__/
|
||||
*.py[cod]
|
||||
*.egg-info/
|
||||
.eggs/
|
||||
.pytest_cache/
|
||||
.mypy_cache/
|
||||
.ruff_cache/
|
||||
.cache/
|
||||
|
||||
# Local run outputs / logs (not needed in the image)
|
||||
outputs/
|
||||
wandb/
|
||||
*.log
|
||||
|
||||
# Editor / OS cruft
|
||||
.DS_Store
|
||||
.idea/
|
||||
.vscode/
|
||||
@@ -8,10 +8,10 @@ PR TITLE: Must start with a type tag, e.g.:
|
||||
MERGE WORKFLOW:
|
||||
1. Ensure pre-commit passes and you have at least 1 approval
|
||||
2. Comment /merge (or add the "ready" label) to enter the Merge Queue
|
||||
3. Full Test Suite runs automatically on a staging branch → auto-merge on success
|
||||
3. A path-aware merge gate runs only relevant integration tests → auto-merge on success
|
||||
|
||||
ON-DEMAND TESTING (write access required):
|
||||
/test full — Full Test Suite /test ssim — SSIM regression
|
||||
/test full — Explicit all-lane run /test ssim — Full SSIM regression
|
||||
/test training — Training pipeline /test encoder — Encoder tests
|
||||
/test transformer — Transformer tests /test vae — VAE tests
|
||||
/test kernel — CUDA kernel tests /test unit — Unit tests
|
||||
|
||||
+4
-14
@@ -186,13 +186,13 @@ pull_request_rules:
|
||||
label:
|
||||
add: ["scope: docs"]
|
||||
|
||||
- name: "label scope: ui"
|
||||
- name: "label scope: studio"
|
||||
conditions:
|
||||
- files~=^ui/
|
||||
- files~=^apps/fastvideo_studio/
|
||||
- -closed
|
||||
actions:
|
||||
label:
|
||||
add: ["scope: ui"]
|
||||
add: ["scope: studio"]
|
||||
|
||||
- name: "label scope: model"
|
||||
conditions:
|
||||
@@ -272,7 +272,7 @@ pull_request_rules:
|
||||
remove: [needs-rebase]
|
||||
|
||||
# ============================================================
|
||||
# Auto-merge and auto-rebase
|
||||
# Auto-merge
|
||||
# ============================================================
|
||||
|
||||
- name: auto-merge when ready and all checks pass
|
||||
@@ -290,16 +290,6 @@ pull_request_rules:
|
||||
merge:
|
||||
method: squash
|
||||
|
||||
- name: auto-update when ready
|
||||
conditions:
|
||||
- label=ready
|
||||
- "#approved-reviews-by>=1"
|
||||
- -conflict
|
||||
- -closed
|
||||
- -draft
|
||||
actions:
|
||||
update: {}
|
||||
|
||||
# ============================================================
|
||||
# PR title format help
|
||||
# ============================================================
|
||||
|
||||
Executable
+133
@@ -0,0 +1,133 @@
|
||||
#!/usr/bin/env bash
|
||||
# Gate the path-aware Buildkite merge plan on the cheap GitHub checks.
|
||||
#
|
||||
# Polls the workflow runs for the PR head commit and only exits 0 once the
|
||||
# watched cheap workflows (pre-commit, docs build) have succeeded, so the
|
||||
# 'ready' label cannot burn path-selected GPU lanes on a head that a cheap
|
||||
# check has already doomed.
|
||||
#
|
||||
# Semantics:
|
||||
# - watched run completed with a bad conclusion -> exit 1 (fail CLOSED:
|
||||
# no merge gate; the next push re-arms via the 'synchronize' trigger)
|
||||
# - watched run cancelled -> still pending: the docs
|
||||
# workflow's repo-global 'pages' concurrency group cancels runs superseded
|
||||
# by unrelated pushes, so 'cancelled' is not a verdict on this PR
|
||||
# - watched runs pending -> poll until done
|
||||
# - docs run absent -> not applicable after a
|
||||
# short grace period ('Deploy Documentation' is path-filtered on PRs)
|
||||
# - pre-commit run absent -> keep polling: pre-commit
|
||||
# is never path-filtered, so its absence is always anomalous
|
||||
# - 'ready' label removed while waiting -> exit 1 (fail CLOSED:
|
||||
# un-labeling is a deliberate maintainer action)
|
||||
# - GitHub API unreachable or timeout -> exit 0 (fail OPEN,
|
||||
# loud warning: never brick CI on a GitHub outage)
|
||||
#
|
||||
# Required env: PR_SHA (PR head commit), PR_NUMBER, GITHUB_REPOSITORY, GH_TOKEN.
|
||||
set -euo pipefail
|
||||
|
||||
: "${PR_SHA:?PR_SHA (PR head commit) is required}"
|
||||
: "${PR_NUMBER:?PR_NUMBER (pull request number) is required}"
|
||||
: "${GITHUB_REPOSITORY:?GITHUB_REPOSITORY is required}"
|
||||
|
||||
# Workflow-level `name:` values that must be green before the merge gate
|
||||
# may start. "Deploy Documentation" is path-filtered on PRs, so its run may
|
||||
# legitimately never exist; pre-commit always runs, so it must appear.
|
||||
WATCHED_NAMES='["pre-commit", "Deploy Documentation"]'
|
||||
WATCHED_REGEX='^(pre-commit|Deploy Documentation)$'
|
||||
POLL_SECS="${POLL_SECS:-20}"
|
||||
GRACE_SECS="${GRACE_SECS:-60}"
|
||||
MAX_WAIT_SECS="${MAX_WAIT_SECS:-1500}"
|
||||
|
||||
# Bound each API call so a hung connection hits the 3-strike fail-open path
|
||||
# instead of pinning the loop until the job timeout (which would fail closed
|
||||
# on exactly the GitHub-outage case this script is meant to survive).
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
gh_api() { timeout 30 gh api "$@"; }
|
||||
else
|
||||
gh_api() { gh api "$@"; } # macOS dev boxes; CI always has coreutils timeout
|
||||
fi
|
||||
|
||||
# The workflow checked the label before starting the gate, but the wait can
|
||||
# last ~25 min: re-check once before any exit 0 and fail closed if 'ready'
|
||||
# was removed in the meantime. An API error here proceeds (the label was
|
||||
# present when the gate started; never brick CI on an outage).
|
||||
recheck_ready_label() {
|
||||
local pr_json
|
||||
if pr_json=$(gh_api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" 2>/dev/null); then
|
||||
if ! jq -e '[.labels[]?.name] | index("ready")' <<<"$pr_json" >/dev/null 2>&1; then
|
||||
echo "::error::PR #${PR_NUMBER} no longer has the 'ready' label —" \
|
||||
"NOT triggering the Buildkite merge gate. Re-add the label to re-arm."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "::warning::Could not re-check the 'ready' label on PR #${PR_NUMBER}; proceeding (it was present when the gate started)."
|
||||
fi
|
||||
}
|
||||
|
||||
start=$(date +%s)
|
||||
api_fails=0
|
||||
missing=""
|
||||
|
||||
while true; do
|
||||
elapsed=$(( $(date +%s) - start ))
|
||||
|
||||
if runs_json=$(gh_api "repos/${GITHUB_REPOSITORY}/actions/runs?head_sha=${PR_SHA}&per_page=100" 2>/dev/null) \
|
||||
&& state=$(jq --arg re "$WATCHED_REGEX" '
|
||||
[.workflow_runs[]? | select(.name // "" | test($re))]
|
||||
| group_by(.name) | map(max_by(.id))
|
||||
| map({name, status, conclusion})' <<<"$runs_json" 2>/dev/null); then
|
||||
api_fails=0
|
||||
echo "t+${elapsed}s watched checks: $(jq -c . <<<"$state")"
|
||||
|
||||
failed=$(jq -r '[.[] | select(.status == "completed"
|
||||
and (.conclusion | IN("success", "skipped", "neutral", "cancelled") | not))]
|
||||
| map(.name) | join(", ")' <<<"$state")
|
||||
if [ -n "$failed" ]; then
|
||||
echo "::error::Cheap check(s) failed on ${PR_SHA}: ${failed}." \
|
||||
"NOT triggering the Buildkite merge gate. Push a fix (the 'ready'" \
|
||||
"label re-arms on every push), or re-run the failed check and then" \
|
||||
"re-run this workflow."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 'cancelled' counts as pending: wait for a re-run to reach a real verdict
|
||||
# (bounded by MAX_WAIT, then the fail-open below).
|
||||
pending=$(jq '[.[] | select(.status != "completed" or .conclusion == "cancelled")] | length' <<<"$state")
|
||||
missing=$(jq -r --argjson watched "$WATCHED_NAMES" '($watched - map(.name)) | join(", ")' <<<"$state")
|
||||
if [ "$pending" -eq 0 ]; then
|
||||
if [ -z "$missing" ]; then
|
||||
recheck_ready_label
|
||||
echo "All watched cheap checks are green — merge gate may proceed."
|
||||
exit 0
|
||||
fi
|
||||
case "$missing" in
|
||||
*pre-commit*)
|
||||
echo "pre-commit run not found for ${PR_SHA} yet; waiting (pre-commit is never path-filtered, so its absence is anomalous)."
|
||||
;;
|
||||
*)
|
||||
if [ "$elapsed" -ge "$GRACE_SECS" ]; then
|
||||
recheck_ready_label
|
||||
echo "::warning::Watched run(s) never appeared for ${PR_SHA}: ${missing} (path-filtered, likely not applicable). Proceeding on the checks that did run."
|
||||
exit 0
|
||||
fi
|
||||
echo "Waiting up to ${GRACE_SECS}s grace for path-filtered run(s) to appear: ${missing}."
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
else
|
||||
api_fails=$(( api_fails + 1 ))
|
||||
echo "::warning::GitHub API error querying workflow runs for ${PR_SHA} (attempt ${api_fails}/3)."
|
||||
if [ "$api_fails" -ge 3 ]; then
|
||||
recheck_ready_label
|
||||
echo "::warning::FAILING OPEN: cannot query GitHub check status — triggering the merge gate WITHOUT the cheap-check gate."
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$elapsed" -ge "$MAX_WAIT_SECS" ]; then
|
||||
recheck_ready_label
|
||||
echo "::warning::FAILING OPEN: watched checks still pending after $(( MAX_WAIT_SECS / 60 )) min${missing:+ (never appeared: ${missing})} — triggering the merge gate anyway."
|
||||
exit 0
|
||||
fi
|
||||
sleep "$POLL_SECS"
|
||||
done
|
||||
@@ -0,0 +1,581 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Select the additive GPU integration lanes needed by a PR diff.
|
||||
|
||||
Fastcheck is the universal six-lane baseline and is intentionally not repeated
|
||||
here. This planner selects only the more expensive merge-gate lanes. Unknown
|
||||
source/build paths fail closed to the complete integration set, while explicit
|
||||
documentation and repository-metadata paths require no additional GPU work.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import fnmatch
|
||||
import re
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import TextIO
|
||||
|
||||
MERGE_LANES = (
|
||||
"golden-gate",
|
||||
"ssim",
|
||||
"lora-inference",
|
||||
"lora-extraction",
|
||||
"training",
|
||||
"distillation",
|
||||
"self-forcing",
|
||||
"lora-training",
|
||||
"training-vsa",
|
||||
"inference-vmoba",
|
||||
"performance",
|
||||
"api-server",
|
||||
"train-framework",
|
||||
"eval",
|
||||
)
|
||||
|
||||
LANE_SCRIPT_TO_KEY = {
|
||||
"api_server.sh": "api-server",
|
||||
"distillation_dmd.sh": "distillation",
|
||||
"eval.sh": "eval",
|
||||
"golden_gate.sh": "golden-gate",
|
||||
"inference_lora.sh": "lora-inference",
|
||||
"inference_vmoba.sh": "inference-vmoba",
|
||||
"lora_extraction.sh": "lora-extraction",
|
||||
"performance.sh": "performance",
|
||||
"self_forcing.sh": "self-forcing",
|
||||
"ssim.sh": "ssim",
|
||||
"train_framework.sh": "train-framework",
|
||||
"training.sh": "training",
|
||||
"training_lora.sh": "lora-training",
|
||||
"training_vsa.sh": "training-vsa",
|
||||
}
|
||||
|
||||
FASTCHECK_LANE_SCRIPTS = {
|
||||
"dreamverse.sh",
|
||||
"encoder.sh",
|
||||
"kernel_tests.sh",
|
||||
"transformer.sh",
|
||||
"vae.sh",
|
||||
}
|
||||
|
||||
LEGACY_TRAINING_LANES = (
|
||||
"training",
|
||||
"distillation",
|
||||
"self-forcing",
|
||||
"lora-training",
|
||||
"training-vsa",
|
||||
)
|
||||
|
||||
ALL_TRAINING_LANES = (*LEGACY_TRAINING_LANES, "train-framework")
|
||||
|
||||
SSIM_SMOKE_TESTS = (
|
||||
"test_flux_t2i_similarity.py",
|
||||
"test_wan_t2v_similarity.py",
|
||||
)
|
||||
|
||||
SAFE_PATTERNS = (
|
||||
"*.md",
|
||||
"*.rst",
|
||||
".agents/**",
|
||||
".claude/**",
|
||||
".codex/**",
|
||||
".github/ISSUE_TEMPLATE/**",
|
||||
".github/PULL_REQUEST_TEMPLATE.md",
|
||||
".github/dependabot.yml",
|
||||
".github/mergify.yml",
|
||||
".github/scripts/**",
|
||||
".github/workflows/**",
|
||||
".buildkite/scripts/pre_commit.sh",
|
||||
".git-blame-ignore-revs",
|
||||
".gitattributes",
|
||||
".gitignore",
|
||||
".pre-commit-config.yaml",
|
||||
"AGENTS.md",
|
||||
"CITATION.cff",
|
||||
"CODE_OF_CONDUCT.md",
|
||||
"CONTRIBUTING.md",
|
||||
"LICENSE",
|
||||
"NOTICE",
|
||||
"__init__.py",
|
||||
"collect_env.py",
|
||||
"SECURITY.md",
|
||||
"assets/**",
|
||||
"comfyui/**",
|
||||
"docs/**",
|
||||
"examples/**",
|
||||
"mkdocs.yml",
|
||||
"requirements-mkdocs.in",
|
||||
"requirements-mkdocs.txt",
|
||||
"scripts/**",
|
||||
"tests/__init__.py",
|
||||
"tests/local_tests/**",
|
||||
)
|
||||
|
||||
ALL_IMPACT_PATTERNS = (
|
||||
".buildkite/pipeline.yml",
|
||||
"docker/**",
|
||||
"pyproject.toml",
|
||||
"requirements*.txt",
|
||||
"setup.cfg",
|
||||
"setup.py",
|
||||
"uv.lock",
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FamilyCoverage:
|
||||
pattern: re.Pattern[str]
|
||||
golden_tests: tuple[str, ...]
|
||||
ssim_tests: tuple[str, ...]
|
||||
|
||||
|
||||
FAMILY_COVERAGE = (
|
||||
FamilyCoverage(
|
||||
re.compile(r"(^|[/_.-])dreamx(_world)?([/_.-]|$)"),
|
||||
("test_dreamx.py", ),
|
||||
("test_dreamx_world_similarity.py", ),
|
||||
),
|
||||
FamilyCoverage(
|
||||
re.compile(r"(^|[/_.-])flux[_-]?2([/_.-]|$)"),
|
||||
("test_flux2_klein.py", ),
|
||||
("test_flux2_similarity.py", ),
|
||||
),
|
||||
FamilyCoverage(
|
||||
re.compile(r"(^|[/_.-])flux(?![_-]?2)([/_.-]|$)"),
|
||||
("test_flux.py", ),
|
||||
("test_flux_t2i_similarity.py", ),
|
||||
),
|
||||
FamilyCoverage(
|
||||
re.compile(r"(^|[/_.-])(hunyuan)?gamecraft([/_.-]|$)"),
|
||||
("test_gamecraft.py", ),
|
||||
("test_gamecraft_similarity.py", ),
|
||||
),
|
||||
FamilyCoverage(
|
||||
re.compile(r"(^|[/_.-])gen3c([/_.-]|$)"),
|
||||
("test_gen3c.py", ),
|
||||
("test_gen3c_similarity.py", ),
|
||||
),
|
||||
FamilyCoverage(
|
||||
re.compile(r"(^|[/_.-])glm[_-]?image([/_.-]|$)"),
|
||||
("test_glm_image.py", ),
|
||||
("test_glm_image_similarity.py", ),
|
||||
),
|
||||
FamilyCoverage(
|
||||
re.compile(r"(^|[/_.-])kandinsky[_-]?5([/_.-]|$)"),
|
||||
("test_kandinsky5.py", ),
|
||||
("test_kandinsky5_similarity.py", ),
|
||||
),
|
||||
FamilyCoverage(
|
||||
re.compile(r"(^|[/_.-])lingbot([a-z0-9_-]*)([/_.-]|$)"),
|
||||
("test_lingbot.py", ),
|
||||
("test_lingbot_similarity.py", ),
|
||||
),
|
||||
FamilyCoverage(
|
||||
re.compile(r"(^|[/_.-])longcat([/_.-]|$)"),
|
||||
("test_longcat.py", ),
|
||||
("test_longcat_similarity.py", ),
|
||||
),
|
||||
FamilyCoverage(
|
||||
re.compile(r"(^|[/_.-])ltx[_-]?2([/_.-]|$)"),
|
||||
("test_ltx2.py", ),
|
||||
("test_ltx2_similarity.py", ),
|
||||
),
|
||||
FamilyCoverage(
|
||||
re.compile(r"(^|[/_.-])matrixgame[_-]?2([/_.-]|$)"),
|
||||
("test_matrixgame.py", ),
|
||||
("test_matrixgame2_similarity.py", ),
|
||||
),
|
||||
FamilyCoverage(
|
||||
re.compile(r"(^|[/_.-])matrixgame[_-]?3([/_.-]|$)"),
|
||||
("test_matrixgame.py", ),
|
||||
("test_matrixgame3_similarity.py", ),
|
||||
),
|
||||
FamilyCoverage(
|
||||
re.compile(r"(^|[/_.-])minimax[_-]?h3([/_.-]|$)"),
|
||||
("test_minimax_h3_t2v.py", ),
|
||||
("test_minimax_h3_similarity.py", ),
|
||||
),
|
||||
FamilyCoverage(
|
||||
re.compile(r"(^|[/_.-])sd[_-]?3([._-]?5)?([/_.-]|$)"),
|
||||
("test_sd35.py", ),
|
||||
("test_sd35_similarity.py", ),
|
||||
),
|
||||
FamilyCoverage(
|
||||
re.compile(r"(^|[/_.-])stable[_-]?audio([/_.-]|$)"),
|
||||
("test_stable_audio.py", ),
|
||||
("test_stable_audio_similarity.py", ),
|
||||
),
|
||||
FamilyCoverage(
|
||||
re.compile(r"(^|[/_.-])turbo(diffusion)?([/_.-]|$)"),
|
||||
(),
|
||||
("test_turbodiffusion_similarity.py", ),
|
||||
),
|
||||
FamilyCoverage(
|
||||
re.compile(r"(^|[/_.-])wan(video|vae)?([/_.-]|$)"),
|
||||
("test_wan_t2v.py", "test_wan_vae.py", "test_wan_causal.py", "test_wan_denoising.py"),
|
||||
(
|
||||
"test_causal_similarity.py",
|
||||
"test_wan_i2v_similarity.py",
|
||||
"test_wan_t2v_similarity.py",
|
||||
),
|
||||
),
|
||||
FamilyCoverage(
|
||||
re.compile(r"(^|[/_.-])z[_-]?image([/_.-]|$)"),
|
||||
("test_zimage.py", ),
|
||||
("test_zimage_similarity.py", ),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class MergePlan:
|
||||
lanes: set[str] = field(default_factory=set)
|
||||
golden_tests: set[str] = field(default_factory=set)
|
||||
ssim_tests: set[str] = field(default_factory=set)
|
||||
golden_all: bool = False
|
||||
ssim_all: bool = False
|
||||
reasons: list[str] = field(default_factory=list)
|
||||
|
||||
def add_lanes(self, *lanes: str, reason: str) -> None:
|
||||
unknown = set(lanes) - set(MERGE_LANES)
|
||||
if unknown:
|
||||
raise ValueError(f"Unknown merge lanes: {sorted(unknown)}")
|
||||
self.lanes.update(lanes)
|
||||
self.reasons.append(reason)
|
||||
|
||||
def add_golden(self, tests: tuple[str, ...], reason: str) -> None:
|
||||
self.add_lanes("golden-gate", reason=reason)
|
||||
self.golden_tests.update(tests)
|
||||
|
||||
def add_ssim(self, tests: tuple[str, ...], reason: str) -> None:
|
||||
self.add_lanes("ssim", reason=reason)
|
||||
self.ssim_tests.update(tests)
|
||||
|
||||
def require_all(self, reason: str) -> None:
|
||||
self.lanes.update(MERGE_LANES)
|
||||
self.golden_all = True
|
||||
self.ssim_all = True
|
||||
self.reasons.append(reason)
|
||||
|
||||
def ordered_lanes(self) -> tuple[str, ...]:
|
||||
return tuple(lane for lane in MERGE_LANES if lane in self.lanes)
|
||||
|
||||
def encoded_lanes(self) -> str:
|
||||
lanes = self.ordered_lanes()
|
||||
return "," + ",".join(lanes or ("none", )) + ","
|
||||
|
||||
def encoded_golden_tests(self) -> str:
|
||||
if "golden-gate" not in self.lanes:
|
||||
return "none"
|
||||
if self.golden_all or not self.golden_tests:
|
||||
return "all"
|
||||
return ",".join(sorted(self.golden_tests))
|
||||
|
||||
def encoded_ssim_tests(self) -> str:
|
||||
if "ssim" not in self.lanes:
|
||||
return "none"
|
||||
if self.ssim_all or not self.ssim_tests:
|
||||
return "all"
|
||||
return ",".join(sorted(self.ssim_tests))
|
||||
|
||||
|
||||
def _matches_any(path: str, patterns: tuple[str, ...]) -> bool:
|
||||
return any(fnmatch.fnmatchcase(path, pattern) for pattern in patterns)
|
||||
|
||||
|
||||
def _family_coverage(path: str) -> tuple[set[str], set[str]]:
|
||||
normalized = path.lower()
|
||||
golden: set[str] = set()
|
||||
ssim: set[str] = set()
|
||||
for family in FAMILY_COVERAGE:
|
||||
if family.pattern.search(normalized):
|
||||
golden.update(family.golden_tests)
|
||||
ssim.update(family.ssim_tests)
|
||||
# Select the component actually touched, including compatibility paths.
|
||||
# Family configs/pipeline wiring can affect all four Wan gates.
|
||||
if re.search(r"(^|[/_.-])wan(video|vae)?([/_.-]|$)", normalized):
|
||||
if (normalized.endswith(("/wan/vae.py", "/wan/vae_config.py", "/vaes/wanvae.py"))
|
||||
or normalized.endswith("/wan/stages/conditioning.py")):
|
||||
golden = {"test_wan_vae.py"}
|
||||
elif normalized.endswith(("/wan/causal_transformer.py", "/dits/causal_wanvideo.py",
|
||||
"/wan/stages/causal_denoising.py")):
|
||||
golden = {"test_wan_causal.py"}
|
||||
elif (normalized == "fastvideo/models/dits/wanvideo.py"
|
||||
or normalized.endswith(("/wan/transformer.py", "/wan/stages/denoising.py", "/wan/stages/dmd.py"))):
|
||||
golden = {"test_wan_t2v.py", "test_wan_denoising.py"}
|
||||
return golden, ssim
|
||||
|
||||
|
||||
def _select_output_coverage(plan: MergePlan, path: str) -> None:
|
||||
golden, ssim = _family_coverage(path)
|
||||
if golden:
|
||||
plan.add_golden(tuple(sorted(golden)), reason=f"model-family golden coverage: {path}")
|
||||
else:
|
||||
plan.golden_all = True
|
||||
plan.add_lanes("golden-gate", reason=f"shared output golden coverage: {path}")
|
||||
if ssim:
|
||||
plan.add_ssim(tuple(sorted(ssim)), reason=f"model-family SSIM coverage: {path}")
|
||||
else:
|
||||
plan.add_ssim(SSIM_SMOKE_TESTS, reason=f"shared output SSIM smoke coverage: {path}")
|
||||
|
||||
|
||||
def classify_paths(paths: list[str]) -> MergePlan:
|
||||
plan = MergePlan()
|
||||
normalized_paths: list[str] = []
|
||||
for raw_path in paths:
|
||||
path = raw_path.strip()
|
||||
while path.startswith("./"):
|
||||
path = path[2:]
|
||||
if path:
|
||||
normalized_paths.append(path)
|
||||
normalized_paths = sorted(set(normalized_paths))
|
||||
if not normalized_paths:
|
||||
plan.require_all("changed-file list was empty; failing closed")
|
||||
return plan
|
||||
|
||||
for path in normalized_paths:
|
||||
if path == "__FASTVIDEO_CI_PLAN_ALL__":
|
||||
plan.require_all("changed-file API failed; failing closed")
|
||||
continue
|
||||
|
||||
if path in {"requirements-mkdocs.in", "requirements-mkdocs.txt"}:
|
||||
plan.reasons.append(f"documentation dependencies need no GPU integration: {path}")
|
||||
continue
|
||||
|
||||
if _matches_any(path, ALL_IMPACT_PATTERNS):
|
||||
plan.require_all(f"cross-cutting build/runtime surface: {path}")
|
||||
continue
|
||||
|
||||
lane_script_prefix = ".buildkite/scripts/lanes/"
|
||||
if path.startswith(lane_script_prefix):
|
||||
script_name = Path(path).name
|
||||
lane = LANE_SCRIPT_TO_KEY.get(script_name)
|
||||
if lane is None:
|
||||
if script_name in FASTCHECK_LANE_SCRIPTS:
|
||||
plan.reasons.append(f"covered by automatic Fastcheck lane: {path}")
|
||||
else:
|
||||
plan.require_all(f"unknown lane script: {path}")
|
||||
elif lane == "golden-gate":
|
||||
plan.golden_all = True
|
||||
plan.add_lanes(lane, reason=f"golden lane implementation: {path}")
|
||||
elif lane == "ssim":
|
||||
plan.ssim_all = True
|
||||
plan.add_lanes(lane, reason=f"SSIM lane implementation: {path}")
|
||||
else:
|
||||
plan.add_lanes(lane, reason=f"lane implementation: {path}")
|
||||
continue
|
||||
|
||||
if path.startswith("fastvideo/tests/golden_gate/"):
|
||||
name = Path(path).name
|
||||
if name.startswith("test_") and name.endswith(".py"):
|
||||
plan.add_golden((name, ), reason=f"changed golden test: {path}")
|
||||
elif name in {"AGENTS.md", "README.md"}:
|
||||
plan.reasons.append(f"golden documentation only: {path}")
|
||||
else:
|
||||
plan.golden_all = True
|
||||
plan.add_lanes("golden-gate", reason=f"shared golden harness/reference: {path}")
|
||||
continue
|
||||
|
||||
if path.startswith("fastvideo/tests/ssim/"):
|
||||
name = Path(path).name
|
||||
if name.startswith("test_") and name.endswith(".py"):
|
||||
plan.add_ssim((name, ), reason=f"changed SSIM test: {path}")
|
||||
elif path.endswith((".py", ".json", ".pt", ".png", ".mp4")):
|
||||
plan.ssim_all = True
|
||||
plan.add_lanes("ssim", reason=f"shared SSIM harness/reference: {path}")
|
||||
continue
|
||||
|
||||
if path.startswith("fastvideo/tests/performance/") or path.startswith(".buildkite/performance-benchmarks/"):
|
||||
plan.add_lanes("performance", reason=f"performance coverage: {path}")
|
||||
continue
|
||||
if path.startswith(("fastvideo/performance/", "fastvideo/performance_dashboard/",
|
||||
"apps/performance_dashboard/")):
|
||||
plan.add_lanes("performance", reason=f"performance implementation: {path}")
|
||||
continue
|
||||
if path.startswith("fastvideo/benchmarks/"):
|
||||
if "/mlx_" in path or Path(path).name.startswith("mlx_"):
|
||||
plan.reasons.append(f"covered by the path-filtered macOS MLX workflow: {path}")
|
||||
else:
|
||||
plan.add_lanes("performance", reason=f"benchmark implementation: {path}")
|
||||
continue
|
||||
if path.startswith("fastvideo/tests/eval/") or path.startswith("fastvideo/eval/"):
|
||||
plan.add_lanes("eval", reason=f"evaluation coverage: {path}")
|
||||
continue
|
||||
if path.startswith("fastvideo/third_party/eval/"):
|
||||
plan.add_lanes("eval", reason=f"vendored evaluation implementation: {path}")
|
||||
continue
|
||||
if path.startswith("fastvideo/tests/lora_extraction/") or path.startswith("scripts/lora_extraction/"):
|
||||
plan.add_lanes("lora-extraction", reason=f"LoRA extraction coverage: {path}")
|
||||
continue
|
||||
if path.startswith("fastvideo/tests/inference/lora/"):
|
||||
plan.add_lanes("lora-inference", reason=f"LoRA inference coverage: {path}")
|
||||
continue
|
||||
if path.startswith("fastvideo/tests/inference/vmoba/"):
|
||||
plan.add_lanes("inference-vmoba", reason=f"VMoBA inference coverage: {path}")
|
||||
continue
|
||||
if path.startswith(("fastvideo/dataset/", "fastvideo/workflow/", "fastvideo/pipelines/preprocess/",
|
||||
"fastvideo/pipelines/training/")):
|
||||
plan.add_lanes(*ALL_TRAINING_LANES, reason=f"shared data/training input surface: {path}")
|
||||
continue
|
||||
if path.startswith("fastvideo/tests/train/") or path.startswith("fastvideo/train/"):
|
||||
plan.add_lanes("train-framework", reason=f"modular training coverage: {path}")
|
||||
continue
|
||||
|
||||
if path.startswith("fastvideo/tests/training/"):
|
||||
lowered = path.lower()
|
||||
if "/vanilla/" in lowered:
|
||||
plan.add_lanes("training", reason=f"vanilla training coverage: {path}")
|
||||
elif "/distill/" in lowered:
|
||||
plan.add_lanes("distillation", reason=f"distillation coverage: {path}")
|
||||
elif "/self-forcing/" in lowered:
|
||||
plan.add_lanes("self-forcing", reason=f"self-forcing coverage: {path}")
|
||||
elif "/lora/" in lowered:
|
||||
plan.add_lanes("lora-training", reason=f"LoRA training coverage: {path}")
|
||||
elif "/vsa/" in lowered:
|
||||
plan.add_lanes("training-vsa", reason=f"VSA training coverage: {path}")
|
||||
else:
|
||||
plan.add_lanes(*LEGACY_TRAINING_LANES, reason=f"shared legacy training coverage: {path}")
|
||||
continue
|
||||
|
||||
if path.startswith("fastvideo/training/"):
|
||||
lowered = path.lower()
|
||||
if "self_forcing" in lowered:
|
||||
plan.add_lanes("self-forcing", reason=f"self-forcing implementation: {path}")
|
||||
elif "distill" in lowered:
|
||||
plan.add_lanes("distillation", reason=f"distillation implementation: {path}")
|
||||
elif "lora" in lowered:
|
||||
plan.add_lanes("lora-training", reason=f"LoRA training implementation: {path}")
|
||||
else:
|
||||
plan.add_lanes(*LEGACY_TRAINING_LANES, reason=f"shared legacy training implementation: {path}")
|
||||
continue
|
||||
|
||||
lowered = path.lower()
|
||||
if "vmoba" in lowered and path.startswith(("fastvideo/", ".buildkite/")):
|
||||
plan.add_lanes("inference-vmoba", reason=f"VMoBA implementation: {path}")
|
||||
plan.add_golden(("test_wan_t2v.py", ), reason=f"VMoBA end-to-end coverage: {path}")
|
||||
continue
|
||||
if "lora" in lowered and path.startswith("fastvideo/"):
|
||||
plan.add_lanes(
|
||||
"lora-inference",
|
||||
"lora-extraction",
|
||||
"lora-training",
|
||||
reason=f"shared LoRA implementation: {path}",
|
||||
)
|
||||
_select_output_coverage(plan, path)
|
||||
continue
|
||||
|
||||
if path.startswith("fastvideo/entrypoints/") or path.startswith("fastvideo/api/"):
|
||||
plan.add_lanes("api-server", reason=f"API/entrypoint integration: {path}")
|
||||
if "openai" not in lowered and "/cli/" not in lowered:
|
||||
_select_output_coverage(plan, path)
|
||||
continue
|
||||
if path.startswith("fastvideo/worker/"):
|
||||
plan.add_lanes("api-server", reason=f"worker/API integration: {path}")
|
||||
_select_output_coverage(plan, path)
|
||||
continue
|
||||
if path.startswith("fastvideo/distributed/"):
|
||||
plan.add_lanes(
|
||||
"training",
|
||||
"train-framework",
|
||||
reason=f"distributed runtime integration: {path}",
|
||||
)
|
||||
_select_output_coverage(plan, path)
|
||||
continue
|
||||
if path.startswith(("fastvideo/hooks/", "fastvideo/platforms/", "fastvideo/third_party/")):
|
||||
_select_output_coverage(plan, path)
|
||||
continue
|
||||
if path.startswith(("fastvideo/models/", "fastvideo/pipelines/", "fastvideo/configs/",
|
||||
"fastvideo/layers/", "fastvideo/attention/")):
|
||||
_select_output_coverage(plan, path)
|
||||
continue
|
||||
if path in {
|
||||
"fastvideo/forward_context.py",
|
||||
"fastvideo/image_processor.py",
|
||||
"fastvideo/registry.py",
|
||||
"fastvideo/utils.py",
|
||||
}:
|
||||
_select_output_coverage(plan, path)
|
||||
continue
|
||||
if path.startswith("fastvideo/mlx_runtime/"):
|
||||
plan.reasons.append(f"covered by the path-filtered macOS MLX workflow: {path}")
|
||||
continue
|
||||
if path.startswith("fastvideo/logging_utils/") or path in {
|
||||
"fastvideo/__init__.py",
|
||||
"fastvideo/envs.py",
|
||||
"fastvideo/logger.py",
|
||||
"fastvideo/profiler.py",
|
||||
"fastvideo/version.py",
|
||||
}:
|
||||
plan.reasons.append(f"covered by automatic Fastcheck: {path}")
|
||||
continue
|
||||
if path.startswith(("fastvideo-kernel/", "csrc/")):
|
||||
plan.add_golden(("test_wan_t2v.py", ), reason=f"kernel integration smoke: {path}")
|
||||
plan.add_ssim(("test_wan_t2v_similarity.py", ), reason=f"kernel numerical smoke: {path}")
|
||||
continue
|
||||
|
||||
if path.startswith("apps/dreamverse/"):
|
||||
# DreamVerse is already one of the six automatic Fastcheck lanes.
|
||||
plan.reasons.append(f"covered by automatic DreamVerse Fastcheck: {path}")
|
||||
continue
|
||||
if path.startswith("fastvideo/tests/"):
|
||||
# The automatic unit/component Fastcheck lanes own the remaining
|
||||
# package tests. Domain-specific expensive test roots were handled
|
||||
# above.
|
||||
plan.reasons.append(f"covered by automatic Fastcheck: {path}")
|
||||
continue
|
||||
if path in {".buildkite/scripts/unit_test.sh", ".buildkite/scripts/pr_test.sh"}:
|
||||
plan.reasons.append(f"covered by automatic unit Fastcheck: {path}")
|
||||
continue
|
||||
if _matches_any(path, SAFE_PATTERNS):
|
||||
plan.reasons.append(f"no additional GPU integration needed: {path}")
|
||||
continue
|
||||
|
||||
plan.require_all(f"unclassified path; failing closed: {path}")
|
||||
|
||||
return plan
|
||||
|
||||
|
||||
def _write_github_output(output: TextIO, plan: MergePlan) -> None:
|
||||
output.write(f"merge_test_plan={plan.encoded_lanes()}\n")
|
||||
output.write(f"merge_golden_tests={plan.encoded_golden_tests()}\n")
|
||||
output.write(f"merge_ssim_tests={plan.encoded_ssim_tests()}\n")
|
||||
output.write(f"merge_plan_label={','.join(plan.ordered_lanes()) or 'none'}\n")
|
||||
|
||||
|
||||
def _write_summary(output: TextIO, plan: MergePlan) -> None:
|
||||
output.write("## Change-aware merge test plan\n\n")
|
||||
output.write("| Selection | Value |\n|---|---|\n")
|
||||
output.write(f"| Additional Slurm lanes | `{','.join(plan.ordered_lanes()) or 'none'}` |\n")
|
||||
output.write(f"| Golden tests | `{plan.encoded_golden_tests()}` |\n")
|
||||
output.write(f"| SSIM tests | `{plan.encoded_ssim_tests()}` |\n\n")
|
||||
output.write("Fastcheck remains the universal six-lane baseline.\n")
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--paths-file", type=Path, required=True)
|
||||
parser.add_argument("--github-output", type=Path)
|
||||
parser.add_argument("--summary-file", type=Path)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
paths = args.paths_file.read_text(encoding="utf-8").splitlines()
|
||||
plan = classify_paths(paths)
|
||||
print(f"MERGE_TEST_PLAN={plan.encoded_lanes()}")
|
||||
print(f"MERGE_GOLDEN_TESTS={plan.encoded_golden_tests()}")
|
||||
print(f"MERGE_SSIM_TESTS={plan.encoded_ssim_tests()}")
|
||||
for reason in plan.reasons:
|
||||
print(f"- {reason}")
|
||||
if args.github_output:
|
||||
with args.github_output.open("a", encoding="utf-8") as output:
|
||||
_write_github_output(output, plan)
|
||||
if args.summary_file:
|
||||
with args.summary_file.open("a", encoding="utf-8") as output:
|
||||
_write_summary(output, plan)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Executable
+122
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env bash
|
||||
# Self-test for gate_full_suite.sh using a mocked `gh`. No network, runs on
|
||||
# any dev box: bash .github/scripts/test_gate_full_suite.sh
|
||||
set -u
|
||||
here=$(cd "$(dirname "$0")" && pwd)
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
|
||||
# Mock gh. Asserts the exact endpoint (including head_sha) it is called
|
||||
# with — an endpoint typo in the gate script fails the test rather than
|
||||
# silently serving canned data. On the runs endpoint it serves
|
||||
# $MOCK_DIR/response_<call#>.json, sticking on the highest existing file,
|
||||
# and exits 1 if none exist (simulates a GitHub API outage). On the pulls
|
||||
# endpoint it serves $MOCK_DIR/pr.json, defaulting to a 'ready'-labeled PR.
|
||||
cat > "$tmp/gh" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
if [ "${1:-}" != "api" ]; then
|
||||
echo "unexpected gh invocation: $*" >> "$MOCK_DIR/endpoint_error"
|
||||
exit 2
|
||||
fi
|
||||
case "${2:-}" in
|
||||
"repos/o/r/actions/runs?head_sha=deadbeef&per_page=100")
|
||||
n=$(( $(cat "$MOCK_DIR/count" 2>/dev/null || echo 0) + 1 ))
|
||||
echo "$n" > "$MOCK_DIR/count"
|
||||
while [ "$n" -gt 0 ]; do
|
||||
if [ -f "$MOCK_DIR/response_$n.json" ]; then
|
||||
cat "$MOCK_DIR/response_$n.json"
|
||||
exit 0
|
||||
fi
|
||||
n=$(( n - 1 ))
|
||||
done
|
||||
echo "api outage" >&2
|
||||
exit 1
|
||||
;;
|
||||
"repos/o/r/pulls/42")
|
||||
if [ -f "$MOCK_DIR/pr.json" ]; then
|
||||
cat "$MOCK_DIR/pr.json"
|
||||
else
|
||||
echo '{"labels": [{"name": "ready"}]}'
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "unexpected gh endpoint: $2" >> "$MOCK_DIR/endpoint_error"
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
EOF
|
||||
chmod +x "$tmp/gh"
|
||||
|
||||
PC_OK='{"name": "pre-commit", "id": 1, "status": "completed", "conclusion": "success"}'
|
||||
PC_BAD='{"name": "pre-commit", "id": 1, "status": "completed", "conclusion": "failure"}'
|
||||
PC_PENDING='{"name": "pre-commit", "id": 1, "status": "in_progress", "conclusion": null}'
|
||||
DOCS_OK='{"name": "Deploy Documentation", "id": 2, "status": "completed", "conclusion": "success"}'
|
||||
DOCS_BAD='{"name": "Deploy Documentation", "id": 2, "status": "completed", "conclusion": "failure"}'
|
||||
DOCS_CANCELLED='{"name": "Deploy Documentation", "id": 2, "status": "completed", "conclusion": "cancelled"}'
|
||||
OTHER='{"name": "Trigger Merge Gate", "id": 3, "status": "in_progress", "conclusion": null}'
|
||||
NULL_NAME='{"name": null, "id": 4, "status": "completed", "conclusion": "failure"}'
|
||||
PC_OK_RERUN='{"name": "pre-commit", "id": 5, "status": "completed", "conclusion": "success"}'
|
||||
|
||||
fails=0
|
||||
want_log="" # optional: expect() also greps out.log for this regex, then resets
|
||||
pr_json="" # optional: served for the pulls (label re-check) endpoint, then resets
|
||||
raw_body="" # optional: serve responses verbatim instead of wrapping in workflow_runs
|
||||
expect() { # <name> <expected-exit> <response json>...
|
||||
local name=$1 want=$2 dir i=1
|
||||
shift 2
|
||||
dir=$(mktemp -d "$tmp/test_XXXXXX")
|
||||
for body in "$@"; do
|
||||
if [ -n "$raw_body" ]; then
|
||||
printf '%s' "$body" > "$dir/response_$i.json"
|
||||
else
|
||||
printf '{"workflow_runs": [%s]}' "$body" > "$dir/response_$i.json"
|
||||
fi
|
||||
i=$(( i + 1 ))
|
||||
done
|
||||
[ -n "$pr_json" ] && printf '%s' "$pr_json" > "$dir/pr.json"
|
||||
( export PATH="$tmp:$PATH" MOCK_DIR="$dir" PR_SHA=deadbeef PR_NUMBER=42 \
|
||||
GITHUB_REPOSITORY=o/r POLL_SECS=0 GRACE_SECS=1 MAX_WAIT_SECS=3
|
||||
bash "$here/gate_full_suite.sh" > "$dir/out.log" 2>&1 )
|
||||
local rc=$?
|
||||
if [ "$rc" -ne "$want" ]; then
|
||||
echo "FAIL: $name (exit $rc, want $want)"
|
||||
cat "$dir/out.log"
|
||||
fails=1
|
||||
elif [ -f "$dir/endpoint_error" ]; then
|
||||
echo "FAIL: $name (mock gh got an unexpected call)"
|
||||
cat "$dir/endpoint_error"
|
||||
fails=1
|
||||
elif [ -n "$want_log" ] && ! grep -Eq "$want_log" "$dir/out.log"; then
|
||||
echo "FAIL: $name (log does not match: $want_log)"
|
||||
cat "$dir/out.log"
|
||||
fails=1
|
||||
else
|
||||
echo "ok: $name"
|
||||
fi
|
||||
want_log="" pr_json="" raw_body=""
|
||||
}
|
||||
|
||||
expect "both green -> proceed" 0 "$PC_OK, $DOCS_OK, $OTHER, $NULL_NAME"
|
||||
expect "docs build failed -> blocked" 1 "$PC_OK, $DOCS_BAD"
|
||||
expect "pre-commit failed -> blocked" 1 "$PC_BAD"
|
||||
expect "pending then green -> proceed" 0 "$PC_PENDING" "$PC_OK, $DOCS_OK"
|
||||
want_log="never appeared.*Deploy Documentation"
|
||||
expect "docs run absent (path-filtered) -> proceed after grace" 0 "$PC_OK"
|
||||
expect "API outage -> fail open" 0
|
||||
want_log="FAILING OPEN"
|
||||
expect "pending past MAX_WAIT -> fail open" 0 "$PC_PENDING"
|
||||
want_log="FAILING OPEN"
|
||||
expect "unrelated runs only -> no grace, fail open at MAX_WAIT" 0 "$OTHER"
|
||||
expect "cancelled docs then green -> proceed" 0 \
|
||||
"$PC_OK, $DOCS_CANCELLED" "$PC_OK, $DOCS_OK"
|
||||
want_log="FAILING OPEN"
|
||||
expect "cancelled docs forever -> fail open at MAX_WAIT" 0 "$PC_OK, $DOCS_CANCELLED"
|
||||
want_log="FAILING OPEN"
|
||||
expect "pre-commit absent -> no grace, fail open at MAX_WAIT" 0 "$DOCS_OK"
|
||||
expect "duplicate run names -> latest wins" 0 "$PC_BAD, $PC_OK_RERUN, $DOCS_OK"
|
||||
raw_body=1
|
||||
expect "garbage response body -> fail open" 0 "this is not json"
|
||||
pr_json='{"labels": [{"name": "other"}]}'
|
||||
expect "ready label removed mid-gate -> blocked" 1 "$PC_OK, $DOCS_OK"
|
||||
|
||||
exit "$fails"
|
||||
@@ -12,10 +12,42 @@ on:
|
||||
tag_suffix:
|
||||
required: true
|
||||
type: string
|
||||
image_name:
|
||||
required: false
|
||||
type: string
|
||||
default: fastvideo-dev
|
||||
build_args:
|
||||
required: false
|
||||
type: string
|
||||
default: ''
|
||||
include_latest_tags:
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
mark_as_latest:
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
runner:
|
||||
required: false
|
||||
type: string
|
||||
default: ubuntu-latest
|
||||
architecture:
|
||||
required: false
|
||||
type: string
|
||||
default: amd64
|
||||
push_by_digest:
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
digest_artifact_name:
|
||||
required: false
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ inputs.runner }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
@@ -23,6 +55,10 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
# The Docker context intentionally includes .git so the kernel build can
|
||||
# initialize its pinned submodules. Do not copy the checkout token with it.
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Free up disk space
|
||||
run: |
|
||||
@@ -61,17 +97,26 @@ jobs:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Normalize image reference
|
||||
id: image
|
||||
env:
|
||||
IMAGE: ghcr.io/${{ github.repository }}/${{ inputs.image_name }}
|
||||
run: echo "name=${IMAGE,,}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Prepare tags
|
||||
id: prepare-tags
|
||||
run: |
|
||||
SHORT_SHA=$(echo ${{ github.sha }} | cut -c1-7)
|
||||
|
||||
TAGS="type=raw,value=${{ inputs.tag_suffix }}-latest"
|
||||
TAGS="${TAGS}\ntype=raw,value=${{ inputs.tag_suffix }}-sha-${SHORT_SHA}"
|
||||
|
||||
# Set Python 3.10 as the default image
|
||||
if [[ "${{ inputs.python_version }}" == "3.10" ]]; then
|
||||
TAGS="type=raw,value=${{ inputs.tag_suffix }}-sha-${SHORT_SHA}"
|
||||
|
||||
if [[ "${{ inputs.include_latest_tags }}" == "true" ]]; then
|
||||
TAGS="type=raw,value=${{ inputs.tag_suffix }}-latest\n${TAGS}"
|
||||
fi
|
||||
|
||||
# Tag the designated default variant as the global `latest` image
|
||||
if [[ "${{ inputs.include_latest_tags }}" == "true" && "${{ inputs.mark_as_latest }}" == "true" ]]; then
|
||||
TAGS="${TAGS}\ntype=raw,value=latest"
|
||||
fi
|
||||
|
||||
@@ -85,22 +130,72 @@ jobs:
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}/fastvideo-dev
|
||||
images: ${{ steps.image.outputs.name }}
|
||||
tags: ${{ steps.prepare-tags.outputs.tags }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
if: ${{ !inputs.push_by_digest }}
|
||||
id: build-push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ${{ inputs.dockerfile_path }}
|
||||
platforms: linux/${{ inputs.architecture }}
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
build-args: ${{ inputs.build_args }}
|
||||
cache-from: type=gha,scope=${{ inputs.image_name }}-${{ inputs.tag_suffix }}-${{ inputs.architecture }}
|
||||
cache-to: type=gha,mode=max,scope=${{ inputs.image_name }}-${{ inputs.tag_suffix }}-${{ inputs.architecture }}
|
||||
|
||||
# Multi-architecture callers publish immutable manifests by digest here,
|
||||
# then create the shared user-facing tags in a single downstream job.
|
||||
# This prevents architecture jobs from racing to replace the same tag.
|
||||
- name: Build and push Docker image by digest
|
||||
if: ${{ inputs.push_by_digest }}
|
||||
id: build-push-digest
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ${{ inputs.dockerfile_path }}
|
||||
platforms: linux/${{ inputs.architecture }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: ${{ inputs.build_args }}
|
||||
outputs: type=image,name=${{ steps.image.outputs.name }},push-by-digest=true,name-canonical=true,push=true
|
||||
cache-from: type=gha,scope=${{ inputs.image_name }}-${{ inputs.tag_suffix }}-${{ inputs.architecture }}
|
||||
cache-to: type=gha,mode=max,scope=${{ inputs.image_name }}-${{ inputs.tag_suffix }}-${{ inputs.architecture }}
|
||||
|
||||
- name: Export digest
|
||||
if: ${{ inputs.push_by_digest }}
|
||||
env:
|
||||
DIGEST: ${{ steps.build-push-digest.outputs.digest }}
|
||||
run: |
|
||||
if [[ -z "${{ inputs.digest_artifact_name }}" ]]; then
|
||||
echo "digest_artifact_name is required when push_by_digest is true" >&2
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p /tmp/digests
|
||||
touch "/tmp/digests/${DIGEST#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
if: ${{ inputs.push_by_digest }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ inputs.digest_artifact_name }}
|
||||
path: /tmp/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
- name: Success message
|
||||
if: ${{ !inputs.push_by_digest }}
|
||||
run: |
|
||||
echo "✅ Python ${{ inputs.python_version }} image successfully built and pushed to ghcr.io/${{ github.repository }}/fastvideo-dev:${{ inputs.tag_suffix }}-latest"
|
||||
echo "To run tests with this image, manually trigger the 'Run Tests' workflow."
|
||||
echo "✅ Python ${{ inputs.python_version }} image successfully built and pushed to ${{ steps.image.outputs.name }}:${{ inputs.tag_suffix }}-sha-${GITHUB_SHA::7}"
|
||||
echo "Digest: ${{ steps.build-push.outputs.digest }}"
|
||||
echo "To run tests with this image, manually trigger the 'Run Tests' workflow."
|
||||
|
||||
- name: Digest success message
|
||||
if: ${{ inputs.push_by_digest }}
|
||||
env:
|
||||
DIGEST: ${{ steps.build-push-digest.outputs.digest }}
|
||||
run: |
|
||||
echo "✅ Python ${{ inputs.python_version }} linux/${{ inputs.architecture }} image pushed as ${DIGEST}"
|
||||
|
||||
@@ -26,29 +26,48 @@ jobs:
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
const bkStatuses = data.statuses.filter(
|
||||
s => s.context.startsWith('buildkite/ci/')
|
||||
);
|
||||
|
||||
const FASTCHECK_PREFIX = 'buildkite/ci/microscope-';
|
||||
// Buildkite derives the GitHub context prefix from the label emoji.
|
||||
// Keep hard Full Suite lanes in test-tube/bar-chart namespaces and
|
||||
// Fastcheck lanes in microscope so targeted reruns cannot clear the
|
||||
// wrong aggregate status. Automatic PR jobs use pr-fastcheck while
|
||||
// slash-command and Full Suite jobs use ci; normalize the suffix
|
||||
// and keep the newest status for each logical lane.
|
||||
const FASTCHECK_PREFIXES = [
|
||||
'buildkite/pr-fastcheck/microscope-',
|
||||
'buildkite/ci/microscope-',
|
||||
];
|
||||
const FULL_SUITE_PREFIXES = [
|
||||
'buildkite/ci/test-tube-',
|
||||
'buildkite/ci/bar-chart-',
|
||||
];
|
||||
|
||||
const fastcheck = bkStatuses.filter(
|
||||
s => s.context.startsWith(FASTCHECK_PREFIX)
|
||||
);
|
||||
const fullSuite = bkStatuses.filter(
|
||||
s => FULL_SUITE_PREFIXES.some(p => s.context.startsWith(p))
|
||||
);
|
||||
function newestByLane(prefixes) {
|
||||
const statuses = new Map();
|
||||
for (const status of data.statuses) {
|
||||
const prefix = prefixes.find(p => status.context.startsWith(p));
|
||||
if (!prefix) continue;
|
||||
const lane = status.context.slice(prefix.length);
|
||||
const previous = statuses.get(lane);
|
||||
if (!previous || Date.parse(status.updated_at) > Date.parse(previous.updated_at)) {
|
||||
statuses.set(lane, status);
|
||||
}
|
||||
}
|
||||
return statuses;
|
||||
}
|
||||
|
||||
if (
|
||||
fastcheck.length > 0
|
||||
&& fastcheck.every(s => s.state === 'success')
|
||||
) {
|
||||
const fastcheck = newestByLane(FASTCHECK_PREFIXES);
|
||||
const fullSuiteOnly = newestByLane(FULL_SUITE_PREFIXES);
|
||||
const fastcheckPassed =
|
||||
fastcheck.size === 6
|
||||
&& [...fastcheck.values()].every(s => s.state === 'success');
|
||||
const fullSuitePassed =
|
||||
fastcheckPassed
|
||||
&& fullSuiteOnly.size === 14
|
||||
&& [...fullSuiteOnly.values()].every(s => s.state === 'success');
|
||||
|
||||
if (fastcheckPassed) {
|
||||
core.info(
|
||||
`All ${fastcheck.length} fastcheck tests passed — updating fastcheck-passed`
|
||||
`All ${fastcheck.size} fastcheck tests passed — updating fastcheck-passed`
|
||||
);
|
||||
await github.rest.repos.createCommitStatus({
|
||||
owner: context.repo.owner,
|
||||
@@ -56,17 +75,13 @@ jobs:
|
||||
sha,
|
||||
state: 'success',
|
||||
context: 'fastcheck-passed',
|
||||
description:
|
||||
`All ${fastcheck.length} fastcheck tests passed`,
|
||||
description: `All ${fastcheck.size} fastcheck tests passed`,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
fullSuite.length > 0
|
||||
&& fullSuite.every(s => s.state === 'success')
|
||||
) {
|
||||
if (fullSuitePassed) {
|
||||
core.info(
|
||||
`All ${fullSuite.length} full suite tests passed — updating full-suite-passed`
|
||||
'All 20 full suite tests passed — updating full-suite-passed'
|
||||
);
|
||||
await github.rest.repos.createCommitStatus({
|
||||
owner: context.repo.owner,
|
||||
@@ -74,7 +89,6 @@ jobs:
|
||||
sha,
|
||||
state: 'success',
|
||||
context: 'full-suite-passed',
|
||||
description:
|
||||
`All ${fullSuite.length} full suite tests passed`,
|
||||
description: 'All 20 full suite tests passed',
|
||||
});
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user