Skip to content

MIT 6.7960 L03: Optimization Overview — SGD, Adam, LR Schedules & Scaling Rules

Aug 30, 2026 1 min
TL;DR From SGD to Adam: pick the right optimizer and scale LR with batch size using scaling rules
Table of Contents
  1. Optimizer Genealogy: From SGD to Adam
  2. Scaling Rules: How to Adjust LR for Large Batches
  3. Learning Rate Schedules: Warmup → Cosine → Decay
  4. Practical Decision Tree: Choosing Optimizer & Hyperparameters for a New Project
  5. Complete Runnable PyTorch Example: Optimizer Comparison Experiment
  6. Common Pitfalls & Avoidance Guide
  7. References

🌏 中文版

MIT 6.7960 Fall 2024 OCW Lecture 7 Scaling Rules for Optimization is taught by Jeremy Bernstein. This lecture doesn't just list optimizers — it derives from gradient descent dynamics why large batches need large learning rates, why Adam fails in certain regimes, and how to use "scaling rules" to transfer hyperparameters from small-batch experiments to large-scale training. This article restructures the lecture highlights into a practical decision framework with runnable PyTorch code.

Optimizer Genealogy: From SGD to Adam

The main thread of deep learning optimizer evolution: how to stably converge to good solutions in high-dimensional non-convex landscapes with minimal hyperparameter tuning.

OptimizerCore IdeaBest ForDrawbacks
SGDPure gradient descent, optional momentumSmall models, convex problems, when theory guarantees matterSlow convergence on deep nets, needs careful LR tuning
SGD + MomentumAccumulate historical gradient direction, cross saddle pointsImage classification, ResNet-style architecturesStill needs manual LR, sensitive to ill-conditioning
Adam1st-order momentum + 2nd-order adaptive LRNLP, Transformers, rapid prototypingDiverges at large batch, weight decay must be decoupled
AdamWAdam + decoupled weight decayModern LLM/ViT training defaultSame as Adam, but regularization is correct
Lion / SophiaSign gradient / 2nd-order approximationLarge model pre-training experimentsNewer ecosystem, hyperparameter-sensitive

Key insight: Bernstein emphasizes in the lecture that optimizer choice matters less than "scaling rules" — the same optimizer fails if learning rate, batch size, and weight decay aren't scaled by the rules.

Scaling Rules: How to Adjust LR for Large Batches

The core formula derived in the lecture (Linear Scaling Rule):

lr_new = lr_base × (batch_size_new / batch_size_base)

Prerequisites:

  • Using SGD + Momentum or AdamW (adaptive optimizers approximately satisfy this)
  • Learning rate in the "stable regime" (too large → divergence, too small → slow convergence)
  • Warmup steps scale proportionally: warmup_steps_new = warmup_base × (batch_size_new / batch_size_base)

Why it works: Large-batch gradient variance drops ∝ 1/√B, signal-to-noise ratio improves, allowing larger step sizes. But beyond "critical batch size", returns diminish or diverge.

In practice, sqrt scaling (LR ∝ √B) is more stable for Transformers, per Kaplan et al. 2020 and Chinchilla experiments.

Learning Rate Schedules: Warmup → Cosine → Decay

Modern standard schedule (Warmup + Cosine Annealing):

import torch
from torch.optim.lr_scheduler import LambdaLR, CosineAnnealingLR, SequentialLR

def get_lr_scheduler(optimizer, warmup_steps, total_steps, min_lr_ratio=0.1):
    """Warmup + Cosine decay, returns a scheduler ready to step()"""
    def warmup_lambda(step):
        return min(1.0, step / warmup_steps)
    
    warmup_scheduler = LambdaLR(optimizer, warmup_lambda)
    cosine_scheduler = CosineAnnealingLR(
        optimizer, 
        T_max=total_steps - warmup_steps,
        eta_min=optimizer.param_groups[0]['lr'] * min_lr_ratio
    )
    return SequentialLR(optimizer, [warmup_scheduler, cosine_scheduler], [warmup_steps])

# Usage example
model = torch.nn.Linear(512, 10)
optimizer = torch.optim.AdamW(model.parameters(), lr=3e-4, weight_decay=0.1)
scheduler = get_lr_scheduler(optimizer, warmup_steps=2000, total_steps=100_000)

for step in range(100_000):
    loss = model(torch.randn(32, 512)).sum()
    loss.backward()
    optimizer.step()
    scheduler.step()
    optimizer.zero_grad()

Video timestamps:

  • 0:00–12:00 SGD momentum & Nesterov acceleration derivation
  • 12:00–28:00 Adam/AdamW internals & weight decay decoupling
  • 28:00–42:00 Scaling rules derivation & critical batch size
  • 42:00–55:00 LR schedules in practice (warmup, cosine, constant, reduce-on-plateau)
  • 55:00–1:10:00 Experiments: convergence curves across batch sizes

Practical Decision Tree: Choosing Optimizer & Hyperparameters for a New Project

START: New model, new dataset

├─ Is it a Transformer / LLM / ViT large model?
│   ├─ Yes → AdamW (lr=3e-4, wd=0.1, β=(0.9,0.95)) + Warmup+Cosine
│   └─ No → Is it a CNN (ResNet/EfficientNet)?
│       ├─ Yes → SGD + Momentum (lr=0.1, momentum=0.9, wd=1e-4) + Cosine
│       └─ No → Try AdamW first (lr=1e-3, wd=0.01), watch loss curve

├─ Scaling up batch size?
│   ├─ Yes → Linear scale LR, proportional warmup increase, monitor grad norm
│   └─ No → Keep base config

└─ Observe first 1000 training steps:
    ├─ Loss explodes → LR ÷ 10, add gradient clipping (1.0)
    ├─ Loss oscillates → LR ÷ 3, extend warmup
    └─ Smooth descent → Keep going, log best checkpoint

Complete Runnable PyTorch Example: Optimizer Comparison Experiment

"""Optimizer comparison: SGD vs Adam vs AdamW convergence on MLP"""
import torch
import torch.nn as nn
import matplotlib.pyplot as plt

class MLP(nn.Module):
    def __init__(self, dim=256, depth=4):
        super().__init__()
        layers = []
        for _ in range(depth):
            layers += [nn.Linear(dim, dim), nn.ReLU()]
        layers.append(nn.Linear(dim, 10))
        self.net = nn.Sequential(*layers)
    
    def forward(self, x):
        return self.net(x)

def train_one_epoch(model, opt, scheduler, loader, device):
    model.train()
    total_loss = 0
    for x, y in loader:
        x, y = x.to(device), y.to(device)
        opt.zero_grad()
        loss = nn.functional.cross_entropy(model(x), y)
        loss.backward()
        torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
        opt.step()
        if scheduler:
            scheduler.step()
        total_loss += loss.item()
    return total_loss / len(loader)

# Synthetic data
torch.manual_seed(42)
train_data = torch.utils.data.TensorDataset(
    torch.randn(5000, 256), torch.randint(0, 10, (5000,))
)
loader = torch.utils.data.DataLoader(train_data, batch_size=128, shuffle=True)
device = 'cuda' if torch.cuda.is_available() else 'cpu'

# Three optimizer configs
configs = {
    'SGD+Momentum': dict(lr=0.1, momentum=0.9, weight_decay=1e-4, opt_fn=torch.optim.SGD),
    'Adam': dict(lr=3e-4, betas=(0.9, 0.999), weight_decay=0.1, opt_fn=torch.optim.Adam),
    'AdamW': dict(lr=3e-4, betas=(0.9, 0.95), weight_decay=0.1, opt_fn=torch.optim.AdamW),
}

results = {}
for name, cfg in configs.items():
    opt_fn = cfg.pop('opt_fn')
    model = MLP().to(device)
    opt = opt_fn(model.parameters(), **cfg)
    scheduler = get_lr_scheduler(opt, warmup_steps=50, total_steps=500)
    
    losses = []
    for epoch in range(20):
        loss = train_one_epoch(model, opt, scheduler, loader, device)
        losses.append(loss)
    results[name] = losses
    print(f"{name}: final loss = {losses[-1]:.4f}")

# Plot
plt.figure(figsize=(8, 5))
for name, losses in results.items():
    plt.plot(losses, label=name, marker='o')
plt.yscale('log')
plt.xlabel('Epoch')
plt.ylabel('Loss (log scale)')
plt.title('Optimizer Comparison on Synthetic MLP Task')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('optimizer_comparison.png', dpi=150)
print("Saved plot to optimizer_comparison.png")

Common Pitfalls & Avoidance Guide

SymptomLikely CauseFix
Loss becomes NaN in first stepsLR too large, no gradient clippingLR ÷ 10, add clip_grad_norm_(1.0)
Val loss flat, train loss dropsOverfitting, weight decay too smallIncrease wd, add dropout, early stopping
Large batch won't convergeLinear scaling breaksSwitch to sqrt scaling, extend warmup, check BN stats
AdamW weight decay ineffectiveUsed weight_decay param but optimizer is AdamUse torch.optim.AdamW (decoupled)

References