[feat] Fix FLUX.1-dev port: native RoPE, parity tests, SSIM reference - #1321
Conversation
Merge Protections🔴 1 of 1 protections blocking · waiting on 👀 reviews
🔴 PR merge requirementsWaiting for
This rule is failing.
|
|
This PR has merge conflicts with the base branch. Please rebase: git fetch origin main
git rebase origin/main
# Resolve any conflicts, then:
git push --force-with-lease |
There was a problem hiding this comment.
Code Review
This pull request adds support for the FLUX.1-dev text-to-image model, implementing the FluxTransformer2DModel, a dedicated pipeline, and corresponding configurations. The changes also update rotary embeddings for Flux-style layouts and include extensive parity and similarity tests. Feedback points out a bug in the timestep scaling for the forward context and suggests safer validation for ControlNet sample inputs to avoid potential runtime errors.
| get_forward_context() | ||
| forward_context = nullcontext() | ||
| except AssertionError: | ||
| ts0 = int(timestep[0].item()) if timestep.numel() > 0 else 0 |
There was a problem hiding this comment.
The fallback logic for ts0 incorrectly assumes that timestep contains raw integer values (0-1000). However, in the FLUX pipeline, timestep is passed as a scaled float in the range [0, 1]. This results in ts0 being either 0 or 1, which is incorrect for the forward context. It should be scaled back to the 0-1000 range to maintain consistency with how the context is set in the denoising stage.
| ts0 = int(timestep[0].item()) if timestep.numel() > 0 else 0 | |
| ts0 = int(timestep[0].item() * 1000) if timestep.numel() > 0 else 0 |
| image_rotary_emb=image_rotary_emb, | ||
| joint_attention_kwargs=jkwargs, | ||
| ) | ||
| if controlnet_block_samples is not None: |
There was a problem hiding this comment.
| image_rotary_emb=image_rotary_emb, | ||
| joint_attention_kwargs=jkwargs, | ||
| ) | ||
| if controlnet_single_block_samples is not None: |
There was a problem hiding this comment.
There was a problem hiding this comment.
Pull request overview
This PR ports and validates FLUX.1-dev support in FastVideo by adding a native Flux transformer + pipeline implementation, wiring model discovery/CLI sampling parameters, and introducing local + SSIM/parity tests to document and verify correctness against Diffusers.
Changes:
- Added a FastVideo-native FLUX transformer (
FluxTransformer2DModel) with native RoPE support and a composed FLUX T2I pipeline with dedicated stages (conditioning/latents/denoise/decode). - Registered FLUX configs/sampling params in the central registry and extended sampling/batch plumbing to support FLUX-style embedded guidance plus optional “true CFG”.
- Added parity + SSIM tests and local-test documentation, and committed at least one seeded SSIM reference image (TORCH_SDPA).
Reviewed changes
Copilot reviewed 22 out of 25 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/local_tests/pipelines/test_flux_dev_pipeline_smoke.py | Adds a short CUDA-only end-to-end FLUX smoke run. |
| tests/local_tests/pipelines/test_flux_dev_pipeline_parity.py | Adds an end-to-end “sanity/parity” run vs Diffusers (shape/range/finite checks). |
| tests/local_tests/flux/test_flux_dev_component_loaders.py | Adds loader smoke tests for FLUX components from Diffusers-layout weights. |
| tests/local_tests/flux/README.md | Documents how to run FLUX local tests (currently has some stale status lines). |
| tests/local_tests/flux/PORT_STATUS.md | Tracks port status/evidence (currently has stale SSIM reference status). |
| fastvideo/tests/utils.py | Extends SSIM utility to treat images as single-frame clips. |
| fastvideo/tests/transformers/test_flux.py | Adds DiT forward-pass parity test vs Diffusers. |
| fastvideo/tests/ssim/test_flux_t2i_similarity.py | Adds SSIM regression test for FLUX T2I (needs backend/ref alignment). |
| fastvideo/tests/ssim/inference_similarity_utils.py | Generalizes similarity utils from video-only to media (video/image). |
| fastvideo/registry.py | Registers FLUX pipeline/sampling config and adds an early-return guard in config registration. |
| fastvideo/pipelines/stages/flux_stages.py | Implements FLUX-specific pipeline stages (packed latents, dynamic mu shifting, embedded/true CFG). |
| fastvideo/pipelines/pipeline_batch_info.py | Adds use_embedded_guidance + true_cfg_scale and updates CFG gating logic. |
| fastvideo/pipelines/basic/flux/flux_pipeline.py | Adds composed FLUX pipeline wiring the new stages. |
| fastvideo/models/dits/flux.py | Adds the FLUX transformer implementation using native RoPE helpers. |
| fastvideo/layers/rotary_embedding.py | Adds sequence_dim support for RoPE application and aligns 1D RoPE with Diffusers calling conventions. |
| fastvideo/configs/sample/flux.py | Adds FLUX-specific sampling defaults. |
| fastvideo/configs/sample/base.py | Adds CLI-exposed sampling params for embedded guidance / true CFG. |
| fastvideo/configs/pipelines/flux.py | Adds FLUX pipeline config (CLIP+T5 layout, tokenizer constraints, precisions). |
| fastvideo/configs/models/dits/flux.py | Adds FLUX DiT config/arch defaults. |
| fastvideo/configs/models/dits/init.py | Exports FluxDiTConfig. |
| examples/inference/basic/basic_flux_dev.py | Adds a simple FLUX.1-dev T2I example script. |
| .gitignore | Updates ignore rules to allow committed SSIM reference media (mp4/png). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| reason="FLUX T2I SSIM test requires CUDA", | ||
| ) | ||
| @pytest.mark.parametrize("prompt", TEST_PROMPTS) | ||
| @pytest.mark.parametrize("attention_backend_name", ["TORCH_SDPA", "FLASH_ATTN"]) |
| pytest fastvideo/tests/ssim/test_flux_t2i_similarity.py -vs | ||
| ``` | ||
|
|
||
| Status: reference images not yet committed — see PORT_STATUS.md. |
| | Item | Status | | ||
| |---|---| | ||
| | SSIM test | written (fastvideo/tests/ssim/test_flux_t2i_similarity.py) | | ||
| | Reference images committed | not yet — pending SSIM seeding run | | ||
|
|
| get_forward_context() | ||
| forward_context = nullcontext() | ||
| except AssertionError: | ||
| ts0 = int(timestep[0].item()) if timestep.numel() > 0 else 0 |
|
|
||
|
|
||
| def test_flux_dev_pipeline_short_run_finite_output( | ||
| monkeypatch: pytest.MonkeyPatch) -> None: |
| pytest tests/local_tests/pipelines/test_flux_dev_pipeline_parity.py -vs | ||
| ``` | ||
|
|
||
| Status: requires weights — pending local run (see PORT_STATUS.md). |
| ``` | ||
|
|
||
| Status: requires weights — not run in CI. | ||
| Pass evidence: pending (see PORT_STATUS.md). |
d7c0249 to
aabf179
Compare
add-model-02-parity:
- Use .item() before f-string formatting so the snippet portably works on
older PyTorch versions that lack `__format__` on 0-dim tensors.
- Switch p99 to torch.quantile (cleaner, no off-by-one risk for small N
vs. kthvalue(int(0.99 * N))).
- Document why both changes matter.
seed-ssim-references:
- .png artefact: correct trigger is `workload_type.value.endswith("2i")`
(per `VideoGenerator._is_image_workload`), not "num_frames=1". On
current main the SSIM helpers (`_find_reference_video`,
`output_video_name`) hardcode .mp4; consuming .png references requires
the `media_extension` parameter introduced in PR hao-ai-lab#1321.
- T2I gotcha: add `mkdir -p` before `cp` so the destination is created.
- .gitignore: clarify that `reference_videos_cli.py upload` uses
`huggingface_hub.upload_folder` (filesystem upload, not git), so
`git add -f` only matters for local commits. Note that the
`fastvideo/tests/ssim/reference_videos/**` catch-all near `.gitignore:94`
currently overrides the earlier `*.mp4` negation, so every reference
file requires `git add -f` regardless of extension.
Three concrete, generalizable improvements derived from PR hao-ai-lab#1321 (FLUX.1-dev port fixes) that the next model-port will hit: 1. add-model-02-parity — Tolerance guide stopped at "Full DiT, cross-kernel bf16: 0.1". FLUX (57 layers) needed atol=0.5 — observed max=0.5, mean=0.04, median=0 on A40. Added a "Very deep DiT (50+ layers), bf16" row and a "Calibrating atol > 0.1" subsection that requires diagnostic prints (max/mean/median/p99) so reviewers can verify the calibration without rerunning. Also distinguishes the healthy bf16-tail signature (median≈0, mean<<atol) from a real bug signature (mean_diff>>0.1). 2. add-model-09-pipeline — Step 4 listed two surfaces (sampling_param.py + CLI args) for adding new generation kwargs. The current architecture has four: sampling_param.py, api/schema.py SamplingConfig, the schema_parity_inventory YAML (moved + expected_dests), and the test_parser.py roundtrip dict snapshot. Missing any one fails CI with a different error; we hit three separate failures during PR hao-ai-lab#1321 before getting it green. Expanded Step 4 to enumerate all four with the failure mode for each. 3. seed-ssim-references — Skill documented .mp4 and .pt artefacts. T2I tests that reuse run_text_to_video_similarity_test produce .png when num_frames=1; reference_videos_cli.py copy-local silently skips PNG (walks .mp4/.pt only, reports "0 copied files"). Added .png as a third artefact type and a step-5 gotcha with the manual cp workaround plus the .gitignore negation pitfall. Each change is a small in-place edit to an existing skill file. No new skills added, no skill removed. Verifiable against the FLUX port: every failure mode called out here has a corresponding commit in PR hao-ai-lab#1321 (c82dd31 for atol, d1a63c8 for schema parity inventory, aabf179 for test_parser snapshot, 5b5fdcf for SSIM PNG copy).
add-model-02-parity:
- Use .item() before f-string formatting so the snippet portably works on
older PyTorch versions that lack `__format__` on 0-dim tensors.
- Switch p99 to torch.quantile (cleaner, no off-by-one risk for small N
vs. kthvalue(int(0.99 * N))).
- Document why both changes matter.
seed-ssim-references:
- .png artefact: correct trigger is `workload_type.value.endswith("2i")`
(per `VideoGenerator._is_image_workload`), not "num_frames=1". On
current main the SSIM helpers (`_find_reference_video`,
`output_video_name`) hardcode .mp4; consuming .png references requires
the `media_extension` parameter introduced in PR hao-ai-lab#1321.
- T2I gotcha: add `mkdir -p` before `cp` so the destination is created.
- .gitignore: clarify that `reference_videos_cli.py upload` uses
`huggingface_hub.upload_folder` (filesystem upload, not git), so
`git add -f` only matters for local commits. Note that the
`fastvideo/tests/ssim/reference_videos/**` catch-all near `.gitignore:94`
currently overrides the earlier `*.mp4` negation, so every reference
file requires `git add -f` regardless of extension.
|
This PR has merge conflicts with the base branch. Please rebase: git fetch origin main
git rebase origin/main
# Resolve any conflicts, then:
git push --force-with-lease |
103c97a to
697a4b3
Compare
|
This PR has merge conflicts with the base branch. Please rebase: git fetch origin main
git rebase origin/main
# Resolve any conflicts, then:
git push --force-with-lease |
|
Hi @Mister-Raggs — automated review from Gob, one of @SolitaryThinker's AI reviewers. Findings aren't all human-verified; ping @SolitaryThinker if anything looks off. TL;DRThe DiT port, pipeline stages, native RoPE refactor, and parity tests are well-structured; both prior gemini-bot findings (timestep scaling, ControlNet input guard) are addressed at the current HEAD. However, the headline SSIM regression test imports Verdict: ship-with-fixes
Pre-merge gate (not a code finding): PR is Findings (formatted for upload)[S0] SSIM regression test imports
|
697a4b3 to
41a9da0
Compare
|
Hi @Mister-Raggs — differential re-review from Gob, one of @SolitaryThinker's AI reviewers. The prior review (verdict: ship-with-fixes) is at #1321 (comment). Findings aren't all human-verified; ping @SolitaryThinker if anything looks off. TL;DRAll 5 prior findings (1× S0, 2× S1, 2× S2-persistent) plus the pre-merge rebase landed cleanly across the 5 fix commits. No new public-surface concerns. Tier moves up from Verdict: approve-with-followup (was ship-with-fixes)Prior findings status (697a4b3 → 41a9da0)
Tally: 6/6 ✅ What changed
Extras worth calling out
Remaining itemsNone blocking. The 5 prior S3 discussion items (FluxDenoisingStage two-pass cost docstring; DiT parity — Gob (@SolitaryThinker's AI reviewer). Full re-review with quoted code archived locally. |
|
This PR has merge conflicts with the base branch. Please rebase: git fetch origin main
git rebase origin/main
# Resolve any conflicts, then:
git push --force-with-lease |
… layer Add sequence_dim param to apply_rotary_emb (supports [B,S,H,D] FLUX layout alongside existing [B,H,S,D] default). Add freqs_dtype and repeat_interleave_real aliases to get_1d_rotary_pos_embed for Diffusers-compatible call sites. Remove diffusers.models.embeddings import from fastvideo/models/dits/flux.py.
… config - Export FluxDiTConfig from configs/models/dits/__init__.py (consistent with all other DiT families) - Align loader smoke test to use T5LargeConfig matching FluxPipelineConfig - Add tests/local_tests/flux/README.md with setup and test commands - Add tests/local_tests/flux/PORT_STATUS.md tracking component/pipeline/ quality status and known blockers
Both fields were declared in SamplingParam but had no --use-embedded-guidance or --true-cfg-scale CLI arguments, making them inaccessible from the command line without editing the example script.
Compares FastVideo FluxPipeline decoded image against Diffusers FluxPipeline under identical prompt/seed/steps (4 steps, 256x256, seed=42, atol=5e-2). Requires official_weights/FLUX.1-dev and CUDA. Updates README and PORT_STATUS to reflect test exists, pending run.
…ot feasible across noise seeds
…l error (median=0, mean=0.04)
…56, 8 steps, seed=0)
CI fixes: - Add use_embedded_guidance to SamplingConfig in fastvideo/api/schema.py (test_inventory_targets_exist_in_typed_schema walked request.sampling but field was missing from the dataclass) - Add true_cfg_scale and use_embedded_guidance to expected_dests in inference_schema_parity_inventory.yaml generate section (test_cli_dest_inventory_matches_live_parsers found both dests on the live parser but not in the inventory) Gemini/Copilot review fixes: - flux.py: scale ts0 by *1000 when timestep is float — FLUX passes timestep in [0,1] so the old int(item()) produced ts0=0 for every non-zero step - flux.py: use truthiness guard on controlnet samples instead of is not None to prevent ZeroDivisionError on empty-list input - test_flux_t2i_similarity.py: restrict SSIM parametrize to TORCH_SDPA; FLASH_ATTN reference not yet seeded (pending Will confirmation on coverage) - docs: update README.md and PORT_STATUS.md to reflect PASS evidence and committed SSIM references (TORCH_SDPA, A40, 2026-05-11) - test_flux_dev_pipeline_smoke.py: fix function signature indent (PEP 8)
SamplingConfig gained use_embedded_guidance in the rebase; the hardcoded config_to_dict snapshot in test_load_run_config_supports_yaml_roundtrip was missing the new field, causing a dict-equality assertion failure.
… rename fastvideo/api/flux.py still imported SamplingParam from fastvideo.configs.sample.base (old path removed on main), causing a ModuleNotFoundError at import time. Updated to fastvideo.api.sampling_param.
…p dead RoPE kwarg, refresh PORT_STATUS Addresses the 2026-06-21 review of the FLUX.1-dev port: - test_flux_t2i_similarity.py imported FluxSamplingParam from the removed fastvideo.configs.sample.flux; point it at fastvideo.api.flux so the SSIM gate can collect. - FluxTimestepPreparationStage silently fell back to the base schedule when the scheduler lacked `mu` or had use_dynamic_shifting=False, skipping FLUX's resolution-dependent shift. Emit a warning on both fallback paths. - repeat_interleave_real was added to get_1d_rotary_pos_embed but never read (expansion is driven by use_real); drop the dead kwarg and its FLUX call-site argument. No behavior change — use_real=True already yields the interleaved expansion FLUX needs. - PORT_STATUS.md: both "Known Blockers" were stale (SSIM TORCH_SDPA reference is committed; FLUX registers unconditionally with no early-return guard). Move them to a Resolved section and bump the date.
…d_video Both run_text_to_video_similarity_test and run_image_to_video_similarity_test define output_media_name but passed the never-assigned output_video_name to _remove_stale_generated_video, a NameError that crashed the FLUX SSIM test before generation. Leftover from the video->media rename. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
c0e715f to
efdbca4
Compare
|
/test full |
Summary
diffusersimports influx.pywith FastVideo-nativeapply_rotary_emb/get_1d_rotary_pos_embed; addedsequence_dimparam for FLUX[B,S,H,D]layout and fixed a CUDA device mismatch bug inget_1d_rotary_pos_embed--use-embedded-guidanceand--true-cfg-scaletoSamplingParamfor FLUX-style guidancetests/local_tests/flux/Test plan
All tests run on A40 (2026-05-11):
tests/local_tests/flux/test_flux_dev_component_loaders.pytests/local_tests/pipelines/test_flux_dev_pipeline_smoke.pytests/local_tests/pipelines/test_flux_dev_pipeline_parity.pyfastvideo/tests/transformers/test_flux.pyfastvideo/tests/ssim/test_flux_t2i_similarity.py(TORCH_SDPA)Notes
atol=0.5— calibrated to observed bfloat16 accumulation over 57 transformer layers (median diff = 0, mean = 0.04; real bugs produce mean >> 0.1).