Repository navigation
fix(ernie_image): honour a partial denoise window on the Heun scheduler - #10006
Pfannkuchensack merged 4 commits into
Conversation
FlowMatchHeunDiscreteScheduler.set_timesteps takes only a step count, so the driver refused every partial denoise instead of rebuilding the schedule. Build the expanded Heun layout from the requested window, shifted the way set_timesteps shifts it, matching what the FLUX driver installs.
|
Gentle check-in on this one — it has been sitting for about a day and a half without review. The Heun partial-denoise change reuses the schedule helper the FLUX driver already uses, and the updated The branch is based on |
|
Checking in on this one — it has been a couple of days since the last note and The fix itself is unaffected by that drift: the Heun partial-denoise path in |
|
Tested on an RTX 4090 with ERNIE-Image Turbo (1024², seed 7, shift 4), calling
Two points:
|
ruff format --check flagged the signature: it fits in the 120-column limit. Signed-off-by: Muneeb Anjum <horizonbymuneeb@users.noreply.github.com>
|
Thanks — that is a genuinely useful run, and you are right on both points. 1. 2. Full-window Heun output change. You are right and the description was wrong. I have corrected it rather than argued with it: a new "Full-window Heun output changes" section states the sigma tail change ( On the substance: aligning Heun with the Euler/FLUX schedule is what makes a partial window well-defined, since the window is a slice of the same linspace the Euler path already uses. Building it from diffusers' own Your table is also the first real-hardware evidence for the case that motivated this: img2img Heun at strength 0.5 went from |
Summary
ERNIE-Image offers Heun alongside Euler in its scheduler set, but every partial denoise on Heun failed before the first step:
denoise()handed Euler the requested window throughset_timesteps(sigmas=...)and, for a scheduler whoseset_timestepstakes only a step count, refused anything that did not run 1.0 → 0.0. So img2img, inpaint and Canvas passes with strength < 1 failed for a scheduler the model loader advertises.After: the Heun branch rebuilds the scheduler's expanded two-evaluation layout from the requested window, applying the same flow shift
set_timestepsapplies, which is the layout the FLUX driver already installs throughset_heun_timesteps_from_sigmas. A pass now opens at thedenoising_startsigma and stops at thedenoising_endsigma.Full-window Heun output changes
A full-window Heun pass is not bit-identical to before. Previously
set_timesteps(N)built diffusers' own Heun schedule; now txt2img also goes through theget_schedulewindow, so the sigmas differ. Atshift=4, 8 steps, the tail goes from… 0.41, 0.016 → 0to… 0.57, 0.36 → 0: the last interval becomes a first-order jump from 0.36. Measured mean absolute pixel difference againstmainis 12.5 at 8 steps and 2.8 at 20 steps.That aligns Heun with the Euler/FLUX schedule the rest of the model already uses, and it is what makes the partial window well-defined, but existing Heun seeds no longer reproduce. Verified on an RTX 4090 with ERNIE-Image Turbo (1024², seed 7,
shift=4): Euler is bit-identical, Heun changes as described, and the partial-window img2img Heun case that raised#10003now returns a clean result.Related Issues / Discussions
Closes #10003
QA Instructions
Reproduced on a stub transformer (
[1, 128, 2, 2]latents,steps=8), which is the same shape the existingtests/backend/ernie_image/test_ernie_denoise.pyharness uses. Before,eulerpartial → ok,heunpartial → theValueErrorabove,heunfull → ok. After the change all three run.tests/backend/ernie_image/test_ernie_denoise.py:test_heun_rejects_a_partial_denoise_rangeasserted the old refusal; it is replaced bytest_heun_honours_a_partial_denoise_range(first sigma 0.5, terminal 0.0, layout length2N-1timesteps, progress does not overrun).test_heun_keeps_the_schedulers_own_shift_on_a_partial_rangepins both window ends undershift=3: start 0.5 → 0.75 and terminal 0.25 → 0.5. Adenoising_end=1.0case alone cannot catch a missing shift, because the shift maps 0 to 0.The test file was run against the real
denoise()path with the two heavy imports the module pulls in stubbed (the pipeline-state dataclass and the inpaint extension, neither of which the scheduler path touches), sinceuv syncdoes not build in this sparse checkout:15 passed. Mutation check: making the new shift helper an identity turns bothtest_heun_keeps_the_schedulers_own_shift_on_a_partial_rangecases red (0.5 != 0.75), so the shift coverage is not vacuous.ruff checkandruff format --checkare clean on both changed files.Review
No material findings: the change reuses the scheduler layout helper the FLUX driver already uses and keeps the ERNIE-specific shift in one private helper next to its only caller. The remaining risk is a schedule built from a window that is not a plain linspace subset:
get_scheduleis the only producer and it slices a linspace, so the shifted window matches whatset_timestepswould have produced for those end sigmas.Checklist
What's Newcopy (if doing a release after this PR)