Skip to content

Fix lr_warmup_fraction silently rejected by default lr_warmup_steps=100#2243

Closed
alvinttang wants to merge 1 commit into
Lightning-AI:mainfrom
alvinttang:fix/lr-warmup-fraction-default-conflict
Closed

Fix lr_warmup_fraction silently rejected by default lr_warmup_steps=100#2243
alvinttang wants to merge 1 commit into
Lightning-AI:mainfrom
alvinttang:fix/lr-warmup-fraction-default-conflict

Conversation

@alvinttang

Copy link
Copy Markdown
Contributor

Summary

Fixes the bug surfaced in #2116 (and #2212 discussions, tutorial issues): TrainArgs.lr_warmup_fraction is silently unusable because lr_warmup_steps: int | None = 100 (truthy default) collides with lr_warmup_fraction in __post_init__'s mutually-exclusive validator (if self.lr_warmup_fraction and self.lr_warmup_steps: raise). User passing only --train.lr_warmup_fraction 0.1 immediately hits ValueError, forcing --train.lr_warmup_steps 0 as a workaround.

Classic mutually-exclusive-defaults anti-pattern.

Fix

litgpt/args.py (+9/-2):

  • Change lr_warmup_steps default from 100 to None.
  • In __post_init__, after the conflict check, materialize the default: 0 when lr_warmup_fraction is truthy, else 100. Preserves prior behaviour for all existing call sites and lr_warmup_fraction=0 semantics.

Test

tests/test_args.py (+18 LOC, TDD RED→GREEN):

  • test_lr_warmup_fraction_works_without_overriding_default_steps — was RED before (raised ValueError), now GREEN; asserts warmup_iters() == 30 for fraction-only input.
  • test_lr_warmup_conflict_only_when_both_explicitly_set — confirms validation still raises when both are explicitly set.

pytest tests/test_args.py 3/3 pass. Broader test_args.py + test_config.py + test_utils.py + test_types.py → 265 passed, 6 skipped (pre-existing missing-deps failures unrelated).

Risk notes

  • All five finetune scripts (adapter, adapter_v2, lora, lora_legacy, full) explicitly pass lr_warmup_steps=100 to their per-script TrainArgs(...) — unchanged behaviour.
  • pretrain.py passes lr_warmup_steps=2000 explicitly — unchanged.
  • Default TrainArgs() (no args) → lr_warmup_steps == 100, identical to before.
  • Finetune scripts list lr_warmup_fraction in the unsupported set, so the user-observable behaviour change is in pretraining only — exactly where the bug manifests.

…mup_steps

The default value of `lr_warmup_steps` was 100, which was treated as user-set
by `__post_init__`'s mutually-exclusive validation. As a result, passing
`--train.lr_warmup_fraction 0.1` (e.g. for pretraining) raised:

    ValueError: Can't provide both `--train.lr_warmup_fraction` and
    `--train.lr_warmup_steps`. Choose one.

This forced users to also pass `--train.lr_warmup_steps 0` explicitly. The bug
was reported in issue Lightning-AI#2116 (comment): "lr_warmup_fraction can be set, but is
not used. Only lr_warmup_steps is used."

Fix: change the default of `lr_warmup_steps` to `None` and resolve it inside
`__post_init__`:

* if the user provides `lr_warmup_fraction`, default `lr_warmup_steps` to 0
  (so it does not conflict and is ignored downstream)
* otherwise, default to 100 (preserving prior behavior)

The mutually-exclusive validation still raises when both are provided
explicitly. All existing semantics (truthy fraction wins in `warmup_iters`,
`lr_warmup_fraction=0` means disabled) are preserved.

Tests added to `tests/test_args.py`:

* `test_lr_warmup_fraction_works_without_overriding_default_steps`
* `test_lr_warmup_conflict_only_when_both_explicitly_set`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant