Guard LRRangeTest and OneCycle schedulers against zero step sizes - #8166
Conversation
LRRangeTest divides the step index by self.step_size and OneCycle divides cycle_first_step_size by total_size (first + second step size), both taken unvalidated from user config. A zero step size raises a bare ZeroDivisionError instead of a clear configuration error. Reject a non-positive step size at construction with a ValueError, mirroring the existing warmup_num_steps guards (deepspeedai#8126, deepspeedai#8142, deepspeedai#8151). Valid configs are unaffected. Signed-off-by: Ehsan Barkhordar <realbarkhordar@gmail.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1673e819f8
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| cycle_second_step_size) if cycle_second_step_size is not None else cycle_first_step_size | ||
|
|
||
| self.total_size = cycle_first_step_size + cycle_second_step_size | ||
| if self.total_size <= 0: |
There was a problem hiding this comment.
Reject zero first cycle step independently
When cycle_first_step_size=0 and cycle_second_step_size is positive, this sum-only check passes because total_size > 0, but self.step_ratio becomes 0. A get_lr() call before the first step() then enters _get_scale_factor() with x == 0 and evaluates x / self.step_ratio, raising the same ZeroDivisionError this guard is meant to prevent; DeepSpeed already exercises pre-training get_lr() via TestGetLrBeforeTrain. Please reject a zero first-step size separately, or explicitly handle a zero-length warm-up half.
Useful? React with 👍 / 👎.
|
Hi @ebarkhordar, I found the comment from Codex bot is reasonable. Can you address it? I didn't see any other issue. Let's merge this after the issue has been addressed. |
A sum-only check on cycle_first_step_size + cycle_second_step_size lets cycle_first_step_size=0 through whenever the second half is positive, and step_ratio is then 0. _get_scale_factor divides x by step_ratio, and x is 0 at every cycle boundary including the first get_lr() before any step(), so the ZeroDivisionError the guard was meant to prevent still fires. Validate the two halves separately instead: the first must be positive, the second non-negative. A zero second half is left working, since step_ratio is then 1.0 and x stays below it, and a test pins that. Signed-off-by: Ehsan Barkhordar <realbarkhordar@gmail.com>
|
@tohtana Thanks for looking. The bot's note is right and it reproduces. With Pushed 1e6da81. The two halves are now validated separately, first positive and second non-negative, which replaces the sum check. A zero second half is left working: How I checked it: clean The workflows on this PR are still waiting for approval, so that evidence is from my container and not from this repo's CI. |
|
Correcting my last line: five of the six workflows here are held for approval, but The The same job, running the same
|
tohtana
left a comment
There was a problem hiding this comment.
Thank you for the update, @ebarkhordar!
This looks good to me. The CI also looks working now.
…i#8171) ## What When `warmup_max_lr` is left unspecified, `WarmupLR` inherits the optimizer's learning rate (added in deepspeedai#7360). The fallback computed: ```python warmup_max_lr = [group['lr'] for group in self.optimizer.param_groups][0] ``` The trailing `[0]` reduces the per-group list to group 0's scalar. `_format_param` then broadcasts that scalar back to every group (`[value] * len(param_groups)`). So on an optimizer with multiple parameter groups that have distinct base LRs, every group warms up to group 0's lr and the other groups' configured LRs are silently discarded. ## Fix Drop the trailing `[0]` so `_format_param` receives the full per-group list and each group warms up to its own base lr. This mirrors deepspeedai#7969, which fixed the same multi-group collapse in the sibling `WarmupCosineLR`. ## Verification Reproduced and verified on a CPU-only container against this branch (real `import deepspeed`, module resolved from the checkout). With two param groups at lr 0.1 and 0.2 and `warmup_max_lr` omitted: - before: `max_lrs == [0.1, 0.1]` (group 1 collapsed to group 0) - after: `max_lrs == [0.1, 0.2]` Added `test_warmup_lr_inherits_per_group_lr_when_max_unspecified` in `tests/unit/runtime/test_lr_schedulers.py`, mirroring the existing `test_warmup_cosine_lr_initializes_all_param_groups`. It fails on master (`assert [0.1, 0.1] == [0.1, 0.2]`) and passes with this change. `WarmupDecayLR` defaults `warmup_max_lr=0.001`, so this path only changes behavior when the value is left unspecified. Ran the repo's formatting hooks (yapf, flake8, codespell, license, end-of-file) on the changed files; all pass. Note: this is a small follow-on in the same file as my open deepspeedai#8166 (a different scheduler class), kept to a one-line change plus one test. Signed-off-by: Ehsan Barkhordar <realbarkhordar@gmail.com> Co-authored-by: Masahiro Tanaka <81312776+tohtana@users.noreply.github.com>
|
Thanks for the review and the merge, @tohtana. The |
Problem
Two learning-rate schedulers in
deepspeed/runtime/lr_schedules.pydivide by a step-size value taken directly from user config, with no validation, so a0step size crashes with a bareZeroDivisionErrorinstead of a clear configuration error:LRRangeTestdivides the step index byself.step_sizein_continuous_interval/_staircase_interval. Withlr_range_test_step_size=0the firststep()raisesZeroDivisionError.OneCyclecomputesself.step_ratio = cycle_first_step_size / self.total_sizein_initialize_cycle, wheretotal_size = cycle_first_step_size + cycle_second_step_size. When both halves are0, the constructor raisesZeroDivisionError.Repro (CPU-only):
The sibling
WarmupLR/WarmupCosineLRconstructors already reject invalidwarmup_num_stepsthis way (#8126, #8142, #8151); these two schedulers were skipped.Fix
Validate at construction, before the division:
LRRangeTest.__init__: reject a non-positivelr_range_test_step_sizewith aValueError, mirroring the existingwarmup_num_stepsguard exactly.OneCycle._initialize_cycle: reject a non-positivetotal_size(cycle_first_step_size + cycle_second_step_size) with aValueError.No behavior change for valid configs: the guards only fire when the value is
<= 0, which previously crashed (or, for a negativeOneCycletotal, produced a meaningless schedule).Testing
Added CPU-only regression tests next to the existing scheduler-validation tests. They raise
ZeroDivisionError(OneCycle) or silently accept the misconfig (LRRangeTest) on current master, and pass with this change:yapf, flake8, codespell clean via
pre-commit run. DCO signed off.