Learning Rate Scheduling - PyTorch Basics
Implement Learning Rate Scheduling in Python with NumPy. medium exercise with examples, hints, and instant feedback. No setup or signup required.
Learning Rate Scheduling
What you'll build: lr_schedule(step, warmup_steps, total_steps, base_lr, min_lr), the learning rate at a given step under linear warmup followed by cosine decay.
Background
A fixed learning rate is rarely best. Early in training, Adam's second-moment estimates are noisy and large updates can destabilise the model, so the rate is warmed up from 0. Later, smaller steps let the model settle into a minimum, so the rate is decayed. Warmup + cosine decay is the standard schedule for transformers (GPT-3, LLaMA, ViT).
Formula
if step < warmup_steps: # linear warmup, 0 -> base_lr
lr = base_lr * step / warmup_steps
else: # cosine decay, base_lr -> min_lr
progress = (step - warmup_steps) / (total_steps - warmup_steps) # 0 -> 1
lr = min_lr + 0.5 * (base_lr - min_lr) * (1 + cos(π * progress))
At progress = 0 the cosine term is 1 (lr = base_lr), at 0.5 it is 0 (half way between the two), at 1 it is -1 (lr = min_lr).
Inputs
step: integer in[0, total_steps].warmup_steps: integer in[0, total_steps); 0 means no warmup.total_steps: integer, the step at which the schedule reachesmin_lr.base_lr: peak rate (default1e-3);min_lr: final rate (default1e-6).
Expected Return Format
A Python float.
Edge Cases and Conventions
step == warmup_stepsbelongs to the decay branch and returns exactlybase_lr, so the two pieces join continuously.step == 0returns0.0when there is warmup.
Constraints
- PyTorch track: return a plain float (use
math.cos). The function plugs intotorch.optim.lr_scheduler.LambdaLR, which the hidden tests use.
Follow-up
- With
warmup_steps = 0this equals PyTorch'sCosineAnnealingLR. Check it. - What does "warmup-stable-decay" (WSD) change, and why do some LLM runs prefer it?
Examples
Input: [lr_schedule(s, warmup_steps=100, total_steps=1000) for s in (0, 50, 100)] Output: [0.0, 0.0005, 0.001]
Linear warmup: half way through warmup the rate is half of base_lr.
Input: [lr_schedule(s, 100, 1000) for s in (550, 1000)] Output: [0.0005005, 1e-06]
Step 550 is half way through the decay, so lr = (base_lr + min_lr) / 2; the last step reaches min_lr.
Practice in Python
import numpy as np
def lr_schedule(step, warmup_steps, total_steps, base_lr=0.001, min_lr=1e-6):
"""
Learning rate with linear warmup and cosine decay.
Args:
step: Current training step (0 <= step <= total_steps)
warmup_steps: Number of warmup steps (0 for none)
total_steps: Step at which the rate reaches min_lr
base_lr: Peak learning rate (default 0.001)
min_lr: Final learning rate (default 1e-6)
Returns:
Learning rate (float) for the given step
"""
# Your code here
pass
The interactive editor loads with Python and NumPy. Run your implementation against 7 tests, or use the progressive hints.