Repository navigation
Fix FLUX scheduler integration - #9724
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
1e8ea28 to
300fd9a
Compare
Pfannkuchensack
left a comment
There was a problem hiding this comment.
Thanks for this. I reviewed it at 300fd9a (base 3289f163af) and found no blockers. The fix holds up both on CPU and on the GPU.
Verification
CPU:
- Ruff check/format and
git diff --checkare clean. tests/backend/flux+test_denoise_noise_inputs.py: 165 passed, 5 xfailed.- Adjacent FLUX/FLUX.2 invocation tests: 67 passed.
- Running the real
denoise()with Euler, Heun and the default scheduler against independent fp64 Euler/Heun references (full, partial start/end, CFG, soft-mask inpainting) gives max error ≤ 3.7e-7.
GPU (RTX 4090, FLUX.1 dev int8, 1024², 20 steps, seed 42, same DB, base vs PR):
| Case | flux_denoise time, base → PR |
Image, base vs PR |
|---|---|---|
| Euler txt2img (mean of 3 warm runs) | 21.4 s → 20.6 s | bit-identical |
| Euler img2img (start 0.5) | 6.3 s → 5.3 s | bit-identical |
Euler denoising_end=0.6 |
18.5 s → 17.2 s | base ran on to σ=0; PR correctly stops at σ≈0.4 |
| Heun txt2img | 40.0 s → 39.8 s | different (now on the shifted schedule); quality comparable |
| Heun img2img (start 0.5) | 9.3 s → 9.2 s | base is visibly washed out / blurry; PR is sharp |
| LCM (8 steps) | 8.3 s → 8.3 s | differs, but LCM differs run-to-run anyway (#9791) |
Euler and Heun were bit-reproducible within each variant. The saved Euler step is the old wasted evaluation at σ=0, so the speedup costs no quality.
Findings (all non-blocking)
-
Heun progress is still a staircase.
denoise.pyreportsorder=2, butstep/total_stepsare already user-facing, andcalc_percentage()halves both fororder == 2.- Observed over the socket at 20 steps:
0.0, 0.1, 0.1, 0.2, 0.2, … 0.9, 1.0. With an odd step count it hits 100 % one step early. - Base stopped at 90 %, so this is an improvement, but reporting
order=1would make it linear. - The new tests currently pin
[2, 2, 1].
- Observed over the socket at 20 steps:
-
The LCM exclusion is untested.
FlowMatchLCMScheduler.set_timestepsacceptssigmas, so thenot is_lcmcheck carries weight. If it is removed, the whole suite still passes, becausetest_lcm_preview_uses_the_model_evaluation_sample_and_sigmaderives its expectations fromscheduler.timesteps. Asserting the literal LCM schedule (e.g. model timesteps[1.0, 0.001]for 2 steps) would catch that. -
Other test gaps (mutation-checked):
- A Heun preview taken one evaluation late passes, because previews are only checked with a constant-velocity model. Checking
callback.latentsin the sample-dependent Heun test would catch it. - Indexing CFG by
step_indexinstead ofuser_steppasses, because no test uses a non-uniformcfg_scalewith Heun. Expectedtimestep_indexper evaluation for 3 steps:[0, 0, 1, 1, 2]. - The Heun case of
test_flux_img2img_preblend_matches_scheduler_first_sigmacallsset_heun_timesteps_from_sigmasdirectly, so it can't fail. Routing Heun back tonum_inference_stepsindenoise()leaves it green. The real coverage istest_heun_partial_start_and_end_use_custom_sigmas.
- A Heun preview taken one evaluation late passes, because previews are only checked with a constant-velocity model. Checking
-
set_heun_timesteps_from_sigmasresets private Diffusers state by hand (_step_index,_begin_index,dt,prev_derivative,sample).- The reset is complete for 0.41.0, and the independent Heun-recurrence test would catch layout drift.
- A smaller surface: call
scheduler.set_timesteps(num_inference_steps=len(sigmas), device=device)first, then overwrite onlytimesteps/sigmas. - Nit: the comment says "Diffusers 0.40.0", but the pin is 0.41.0 (the Heun/Euler/LCM sources are byte-identical).
-
Two non-obvious comments were lost when the internal Euler loop was removed:
- why the negative CFG pass must include the Kontext
img_cond_seqtokens (otherwise CFG artifacts); - the ControlNet-residual-sum peak-memory TODO.
Both are worth keeping on the surviving loop.
- why the negative CFG pass must include the Kontext
-
Nits:
- The
inspect.signature+hasattr(scheduler, "sigmas")branch looks generic but assumes Euler's "append one terminal" layout. An explicitisinstance(..., FlowMatchEulerDiscreteScheduler)that raises for unknown schedulers would be clearer, and would match theisinstanceHeun dispatch rather than thehasattr(scheduler, "state_in_first_order")duck-typing. _FakeHeunSchedulerno longer models a reachable path.- The two Euler tests overlap heavily.
torch.equalon float schedules relies on the fixture values rounding to whole numbers.
- The
Worth mentioning in the PR description
- FLUX Euler with
denoising_end < 1now returns partially denoised latents. Base silently denoised to 0, so saved workflows that relied on that will look noisier. This is correct, but visible. The webv2 UI always sendsdenoising_end: 1. - Heun images change for the same seed, because base ignored the shifted, clipped schedule.
Out of scope (filed separately, not caused by this PR)
- #9790: chaining two FLUX Denoise nodes with
add_noise=falsecrashes (noise=None→pack()). This is why I could only testdenoising_endsingle-stage. - #9791: LCM is not seed-reproducible (unseeded noise in
scheduler.step()). - #9792: FLUX.2 and Z-Image have the same schedule/
denoising_end/Heun-progress problems this PR fixes for FLUX.1.set_heun_timesteps_from_sigmascould be reused there.
f398708 to
73b780e
Compare
Preserve Euler endpoints and custom Heun schedules, and calculate previews from matching samples and sigmas. Remove the duplicate Euler loop and add CPU regression coverage for schedules, previews, CFG, and inpainting.
Correct Heun progress, scheduler reset, and explicit scheduler dispatch. Add regression coverage for LCM, previews, and CFG.
94cccbe to
7358fb0
Compare
Summary
Fix FLUX denoising so Euler uses Diffusers by default, respects InvokeAI's full and partial sigma schedules, and previews use a matching sample and sigma. Apply InvokeAI's custom schedule to Heun while preserving its first and second order evaluations. Keep LCM's scheduler-defined schedule.
Related Issues / Discussions
QA Instructions
git diff --checkpassed.Review
No material review findings remain.
Compatibility / Rollout
LCM continues to generate its own schedule. For partial denoising, its exact start and terminal sigmas can differ from InvokeAI's clipped schedule.
Checklist
What's Newcopy (if doing a release after this PR)