Pair the diffusion warmup presets with a scheduler that uses them - #8593
Conversation
FAMILY_TRAIN_DEFAULTS recommends lr_warmup_steps=20 for the big flow-matching
DiTs, with a comment saying the ramp keeps the first adapter updates from
overshooting. DiffusionLoraConfig.lr_scheduler defaults to "constant", and
diffusers' get_scheduler returns before it ever reads num_warmup_steps for that
schedule:
if name == SchedulerType.CONSTANT:
return schedule_func(optimizer, last_epoch=last_epoch)
so the advertised ramp never happens. Carry "constant_with_warmup" alongside
each warmup preset, which is already an accepted value in _LR_SCHEDULERS.
Six families are affected, not the four the report names: ltx-2 and minimax-h3
set the same preset. sdxl, z-image and krea-2 set no warmup and are untouched.
Closes unslothai#8269
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dd125cca69
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| "learning_rate": 1e-4, | ||
| "resolution": 512, | ||
| "lr_warmup_steps": 20, | ||
| "lr_scheduler": "constant_with_warmup", |
There was a problem hiding this comment.
Wire the scheduler presets into the Train UI
When any of these families is trained through the Studio UI, this new default is discarded: DiffusionTrainableFamily.defaults does not declare the scheduler or warmup fields, mergeFamilies() copies only rank/LR/resolution, and the panel initializes and sends lrScheduler="constant" with zero warmup. Consequently the primary training flow still performs no ramp for all six affected families despite /diffusion/info advertising one; the frontend needs to preserve and seed both new defaults.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Correct, and fixed in 47d586c. The panel narrowed the reported defaults to rank/lr/resolution in mergeFamilies(), initialised lrScheduler to "constant" with zero warmup, and then sent both fields explicitly on Start, so the pairing never reached a run started from the UI.
One detail differs from the report: DiffusionTrainableFamily.defaults is an untyped dict, so the values did reach the client. The blockers were the TypeScript defaults shape in features/images/api.ts, which enumerates its fields and had no scheduler or warmup, and mergeFamilies() itself.
The fix carries the pair through both merge arms, seeds it in the family re-seed effect, and resets it to constant/0 for a family with no warmup preset, so flux.1's 20 steps do not ride along into SDXL. Normalisation lives in diffusion-train-lr-schedule.ts: half a pair is dropped rather than seeded, since a warmup count under constant is exactly the inert state the backend pairing exists to prevent, and a scheduler the panel's Select cannot show (cosine_with_restarts, polynomial) is dropped rather than left rendering blank. The reported family owns the ramp outright, with no static-preset fallback, so a backend that drops a warmup preset drops the ramp here too. The LR schedule Select now marks the settings dirty like every other control, or the re-seed would replace a hand-picked schedule on the next family switch.
studio/frontend/tests/diffusion-train-warmup-preset.test.ts covers it: 8 tests, 3 of which fail against the previous panel. Full frontend suite 2015 passed, studio/backend/tests/test_diffusion_train_extras.py 29 passed.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 47d586cefc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| setLrScheduler(family.defaults.lrScheduler ?? "constant"); | ||
| setLrWarmupSteps(family.defaults.lrWarmupSteps ?? 0); |
There was a problem hiding this comment.
Track LR schedule edits separately from other settings
After the user edits any numeric control—even Steps, rank, resolution, or seed—settingsDirty.current stays true, so this entire family-seeding block is skipped on subsequent family changes. As a result, switching from a warmed-up DiT to SDXL carries constant_with_warmup and 20 steps into SDXL, while switching in the opposite direction can retain constant and omit the new ramp; the LR schedule/warmup pair needs its own dirty state so unrelated edits do not suppress model-specific defaults.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Confirmed against a running Studio, and fixed in 2116951. settingsDirty is one flag over every numeric control, Steps and Seed included, so gating the ramp on it was wrong. Reproduced on the panel before the fix: open Train on SDXL, type a step count, switch to FLUX.1-dev, and the LR schedule stays Constant with the Warmup steps field hidden. That is the ramp this change exists to seed, silently not running.
The pair now has its own lrScheduleDirty ref, set by the LR schedule Select and by the Warmup steps input, and its re-seed sits outside the settingsDirty block. It gets a separate flag rather than joining the existing one because it is the only setting whose control disappears: under plain constant the Warmup steps field is not rendered, so a value carried past a family change is invisible rather than merely stale.
Same three cases read off the live panel after the fix:
SDXL -> edit Steps -> FLUX.1-dev Constant + warmup 20 (was: Constant, field hidden)
FLUX.1-dev / Qwen-Image, untouched Constant + warmup 20
SDXL / Z-Image, untouched Constant hidden
FLUX.1-dev -> pick Cosine -> SDXL Cosine decay 20 (user pick survives)
Two assertions added to diffusion-train-warmup-preset.test.ts, both failing against 47d586c. Frontend suite 2016 passed.
|
@codex review |
|
Codex Review: Didn't find any major issues. Another round soon, please! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
Follow-up to my own change in 2116951, pushed as f457166. Making "Warmup steps" visible by default put it in reach of qwen-image's own preset is 5e-5, so switching after touching the ramp trained it at double the recommended rate with nothing on the panel to say so. Before this PR the field was hidden under the default
One added test, failing against 2116951. Frontend suite 2017 passed. |
|
@codex review |
|
Codex Review: Didn't find any major issues. Breezy! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
# Conflicts: # studio/backend/core/training/diffusion_train_common.py
|
Merged main in (773d3d1). #8464 landed the same backend fix about an hour ago, which put this branch in conflict, so the shape of this PR has changed. The conflict was in What is left is the part #8464 did not do. It fixed the advertisement; the Train panel still discards it. On main right now, Two of the three tests added here are now covered by Net diff against main is now 5 files: the frontend wiring, its tests, and that one backend test. Frontend suite 3489 passed, |
|
@codex review |
|
Codex Review: Didn't find any major issues. Bravo. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |

Closes #8269.
The bug
FAMILY_TRAIN_DEFAULTSrecommendslr_warmup_steps: 20for the big flow-matching DiTs, and says why right above the table:DiffusionLoraConfig.lr_schedulerdefaults to"constant", and diffusers'get_schedulerreturns before it ever readsnum_warmup_stepsfor that schedule:The trainers pass the value in good faith (
diffusion_dit_trainer.py:2121,diffusion_h3_trainer.py:694), so nothing errors and nothing warns. The advertised ramp simply never happens.One correction to the report
It names four families. There are six:
ltx-2andminimax-h3carry the samelr_warmup_steps: 20preset and are affected identically.Fix
Carry the scheduler alongside the ramp.
constant_with_warmupis already an accepted value in_LR_SCHEDULERS, and diffusers does readnum_warmup_stepsfor it, so the preset now does what the comment says. Families that set no warmup keepconstantand are not touched.Why it survived
test_flow_families_carry_warmup_presetsalready existed and passes today: it asserts the warmup number is present and positive, but never that anything consumes it. Three tests added beside it, pinning the pairing rather than the value:lr_warmup_stepsmust name a scheduler that consumes warmup, and that scheduler must be one_LR_SCHEDULERSaccepts/training/diffusion/infoadvertises must be constructible as-is and survive into a builtDiffusionLoraConfigpytest studio/backend/tests/test_diffusion_train_extras.pygives 2 failed / 27 passed againstmainand 29 passed with this change.Across every
tests/*diffusion*file: 37 failed / 2077 passed on a clean tree and 37 failed / 2080 passed with this change, so the three added tests are the only difference. Those 37 are pre-existing and need a GPU (nf4 is not a CPU fallback);tests/test_mcp_server.pyalso fails to collect on a clean tree here for a missingfastmcp.This changes training behaviour for those six families, in that the ramp now actually runs. That is the stated intent of the preset, so I read it as the fix rather than a new default, but if you would rather the presets be dropped than honoured, that is a one-line alternative and I am happy to switch.