Repository navigation
Conversation
FlowMatchLCMScheduler.step re-noises the sample every step, but these loops called it without a generator, so the noise came from the global torch RNG and the same seed gave a different image on each run. Pass a generator seeded from the node's seed, as the ERNIE-Image path does. Fixes invoke-ai#9791 Signed-off-by: GokayAI <60583610+gokay-ai@users.noreply.github.com>
|
Verified on GPU (RTX 4090): same seed, three runs each, invocation cache disabled. On main, LCM results differed between runs by a mean of 12.5/255 (FLUX.1 dev), 51.8/255 (FLUX.2 Klein 4B) and 8.7/255 (Z-Image Turbo). With this PR, repeated runs are bit-identical and different seeds still give different images. Euler and Heun outputs are unchanged with the generator passed. A few changes before merging:
Otherwise this looks good to me. |
Address review on invoke-ai#9837: - Add invocation-level tests that drive the real _run_diffusion for FLUX.1, FLUX.2 and Z-Image and assert the step generator is a CPU generator seeded from seed ^ 0xFFFFFFFF. Dropping generator= from either FLUX node, or the Z-Image step_generator, now fails them. - Move the Z-Image step generator and its comment above the scheduler stepping comments, so the tqdm comment sits next to the tqdm call. - Reword the backend comments: Euler and Heun do not use the step noise here (s_churn is 0), rather than not consulting the generator. Signed-off-by: GokayAI <60583610+gokay-ai@users.noreply.github.com>
|
Thanks for checking this on GPU. All three are addressed in 175ce72:
|
|
Heads-up on merge order: this PR and #9836 both change the same import line in |
Summary
With the LCM scheduler, FLUX.1, FLUX.2 and Z-Image give a different image each run for the same seed.
FlowMatchLCMScheduler.step()re-noises the sample every step, and these three loops called it without agenerator, so that noise came from the global torch RNG.The loops now pass a CPU generator seeded from the node's
seed, the same way the ERNIE-Image path already does (seed ^ 0xFFFFFFFF, so the step noise isn't correlated with the initial noise). The backenddenoise()functions for FLUX.1 and FLUX.2 take an optionalgeneratorthat defaults toNone, so other callers are unchanged. Euler and Heun accept the argument too and only use it whens_churn > 0, which is 0 on these paths, so their output doesn't change.Related Issues / Discussions
Fixes #9791
QA Instructions
New tests:
tests/backend/flux/test_denoise.py::test_lcm_step_noise_follows_the_generator(FLUX.1)tests/app/invocations/test_denoise_noise_inputs.py::test_flux2_lcm_step_noise_follows_the_generator(FLUX.2)Each runs the real LCM scheduler twice with the same seeded generator, advancing the global RNG in between, and checks the outputs match. Different seeds must give different outputs, so the test can't pass by ignoring the generator. Without a generator, the same FLUX.2 setup differs between runs by up to ~1.43. The dummy scheduler in
test_flux2_lcm_scheduler_setup_passes_munow acceptsgenerator, like the real Diffusers schedulers do.pytest tests/backend/flux tests/backend/flux2 tests/backend/ernie_image tests/app/invocations -k "flux or z_image or denoise": 995 passed, 5 skipped, 5 xfailed.ruff check/ruff format --checkpass on the changed files.The Z-Image loop lives inside the invocation, so there's no unit test for it; it gets the same change. I tested on CPU with mocks only and didn't run real LCM generations on a GPU.
Merge Plan
N/A
Checklist
What's Newcopy (if doing a release after this PR)