Repository navigation
[BUG]: Missing Implementation for Loading after_scheduler Parameters #5119
Copy link
Copy link
Closed
Labels
bugSomething isn't workingSomething isn't working
Description
🐛 Describe the bug
The current implementation of WarmupScheduler does not include the functionality to load the after_scheduler part of the parameters. This omission leads to a scenario where the learning rate cannot be restored to its saved state after loading from a checkpoint.
The relevant piece of code in question is as follows:
class WarmupScheduler(_LRScheduler):
"""Starts with a linear warmup lr schedule until it reaches N epochs then applies
the specific scheduler (For example: ReduceLROnPlateau).
Args:
optimizer (:class:`torch.optim.Optimizer`): Wrapped optimizer.
warmup_epochs (int): Number of epochs to linearly warmup lr until starting applying the scheduler.
after_scheduler (:class:`torch.optim.lr_scheduler`): After target_epoch, use this scheduler.
last_epoch (int, optional): The index of last epoch, defaults to -1. When last_epoch=-1,
the schedule is started from the beginning or When last_epoch=-1, sets initial lr as lr.
"""
def __init__(self, optimizer, warmup_epochs, after_scheduler, last_epoch=-1):
self.warmup_epochs = int(warmup_epochs)
self.after_scheduler = after_scheduler
self.finished = False
super().__init__(optimizer, last_epoch)
def state_dict(self):
state_dict = {key: value for key, value in self.__dict__.items() if key not in "optimizer"}
if isinstance(state_dict["after_scheduler"], _LRScheduler):
state_dict["after_scheduler_type"] = type(state_dict["after_scheduler"]).__name__
state_dict["after_scheduler_dict"] = state_dict["after_scheduler"].state_dict()
del state_dict["after_scheduler"]
else:
raise NotImplementedError()
return state_dict
def get_lr(self):
if self.last_epoch >= self.warmup_epochs:
if not self.finished:
self.after_scheduler.base_lrs = self.base_lrs
self.finished = True
return self.after_scheduler.get_lr()
return [(self.last_epoch + 1) / self.warmup_epochs * lr for lr in self.base_lrs]
def step(self, epoch=None):
if self.finished:
if epoch is None:
self.after_scheduler.step(None)
self._last_lr = self.after_scheduler.get_last_lr()
else:
self.after_scheduler.step(epoch - self.warmup_epochs)
self._last_lr = self.after_scheduler.get_last_lr()
else:
return super().step(epoch)
Environment
No response
Reacted by airlsyn and Wenhao Chen
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't working
Activity
JazyJiang commentedon Sep 24, 2026
Thanks for the report! This was fixed by #5369 (merged 2024-02-06):
WarmupSchedulernow derives fromTwoStageScheduler, whosestate_dict()/load_state_dict()save and restore theafter_schedulerstate.We verified it on current
main: aWarmupScheduler(warmup_epochs=3)wrappingCosineAnnealingLR(T_max=20), stepped 7 times, saved, then restored into a fresh optimizer + scheduler, gives the same learning rate as the original after 3 more steps (0.731498 vs 0.731498). The same check against the code before #5369 diverges (0.731498 vs 0.860513).Note that the optimizer state needs to be restored together with the scheduler, as with plain PyTorch schedulers, since
CosineAnnealingLRupdates the learning rate from the optimizer's current value.Closing as fixed. Please reopen if you still see this on the latest release.