Skip to content

Commit 886790b

Browse files
authored
Validate warmup_type in WarmupCosineLR like WarmupLR (#8151)
## Problem `WarmupLR.__init__` validates `warmup_type` and falls back to `log` with a warning for unknown values, and `WarmupDecayLR` inherits that behavior. `WarmupCosineLR` documents the same `{'log', 'linear'}` contract but stores `warmup_type` unvalidated, and `get_lr_ratio()`'s warmup branch only assigns `ratio` for the two known types. Any other value (e.g. a typo like `'Linear'` or `'cosine'`) crashes on the first `step()`: ``` File "deepspeed/runtime/lr_schedules.py", line 850, in get_lr_ratio ratio = self.warmup_min_ratio + ratio * ratio_delta UnboundLocalError: cannot access local variable 'ratio' where it is not associated with a value ``` a confusing crash deep inside the scheduler instead of the documented warn-and-default behavior its sibling classes give. ## Fix Normalize `warmup_type` in `WarmupCosineLR.__init__` exactly as `WarmupLR.__init__` already does (same warning text, same fallback to `log`). Behavior for valid `log`/`linear` values is unchanged. ## Testing Added `test_warmup_cosine_lr_unknown_warmup_type_falls_back_to_log`, which fails with the `UnboundLocalError` above on current master and passes with this change; it asserts an unknown `warmup_type` produces the same lr-ratio trajectory as an explicit `log` scheduler through warmup and into cosine decay. ``` pytest tests/unit/runtime/test_lr_schedulers.py -k "warmup_cosine or reject_invalid" 9 passed, 45 deselected ``` yapf/flake8 clean on both touched files. Follows up on the recent scheduler hardening in #8126 and #8142, which did not cover `warmup_type`. --------- Signed-off-by: Sohum Trivedi <trivsohum@gmail.com>
1 parent 0c36f6d commit 886790b

2 files changed

Lines changed: 55 additions & 0 deletions

File tree

deepspeed/runtime/lr_schedules.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -822,6 +822,11 @@ def __init__(self,
822822
self.last_batch_iteration = last_batch_iteration
823823
self.cos_min_ratio = cos_min_ratio
824824

825+
# Currently only support linear and log function
826+
if warmup_type not in {WARMUP_LOG_RATE, WARMUP_LINEAR_RATE}:
827+
logger.warning(f"Using unknown warmup_type: {warmup_type}. The increasing function "
828+
f"is set to default (log)")
829+
warmup_type = WARMUP_LOG_RATE
825830
self.warmup_type = warmup_type
826831
self.warmup_min_ratio = warmup_min_ratio
827832
self.warmup_num_steps = max(2, warmup_num_steps)

tests/unit/runtime/test_lr_schedulers.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,3 +577,53 @@ def test_warmup_schedulers_reject_invalid_warmup_num_steps(scheduler_cls, bad_wa
577577

578578
with pytest.raises(ValueError):
579579
scheduler_cls(**kwargs)
580+
581+
582+
def test_warmup_cosine_lr_unknown_warmup_type_falls_back_to_log():
583+
# WarmupLR warns and falls back to the log warmup curve for an unrecognized
584+
# warmup_type; WarmupCosineLR must do the same instead of crashing with an
585+
# UnboundLocalError in get_lr_ratio on the first step.
586+
param = torch.nn.Parameter(torch.zeros(1))
587+
optimizer = torch.optim.Adam([param], lr=0.001)
588+
589+
scheduler = WarmupCosineLR(optimizer=optimizer,
590+
total_num_steps=100,
591+
warmup_num_steps=10,
592+
warmup_type="not_a_warmup_type")
593+
594+
assert scheduler.warmup_type == WARMUP_LOG_RATE
595+
596+
param_ref = torch.nn.Parameter(torch.zeros(1))
597+
optimizer_ref = torch.optim.Adam([param_ref], lr=0.001)
598+
scheduler_ref = WarmupCosineLR(optimizer=optimizer_ref,
599+
total_num_steps=100,
600+
warmup_num_steps=10,
601+
warmup_type=WARMUP_LOG_RATE)
602+
603+
for step in range(15):
604+
scheduler.step(step)
605+
scheduler_ref.step(step)
606+
assert scheduler.get_lr_ratio() == pytest.approx(scheduler_ref.get_lr_ratio())
607+
608+
609+
def test_warmup_cosine_lr_linear_warmup_type_produces_linear_ratios():
610+
# No other test exercises WarmupCosineLR with warmup_type=WARMUP_LINEAR_RATE,
611+
# so a regression that silently routed 'linear' through the log curve would
612+
# keep the suite green. Pin the per-step warmup ratios: with
613+
# warmup_min_ratio=0.0 the linear curve is step / warmup_num_steps, which
614+
# clearly diverges from the log curve (e.g. 0.1 vs ~0.301 at step 1).
615+
param = torch.nn.Parameter(torch.zeros(1))
616+
optimizer = torch.optim.Adam([param], lr=0.001)
617+
warmup_num_steps = 10
618+
619+
scheduler = WarmupCosineLR(optimizer=optimizer,
620+
total_num_steps=100,
621+
warmup_num_steps=warmup_num_steps,
622+
warmup_min_ratio=0.0,
623+
warmup_type=WARMUP_LINEAR_RATE)
624+
625+
assert scheduler.warmup_type == WARMUP_LINEAR_RATE
626+
627+
for step in range(warmup_num_steps):
628+
scheduler.step(step)
629+
assert scheduler.get_lr_ratio() == pytest.approx(step / warmup_num_steps)

0 commit comments

Comments
 (0)