Bird
Raised Fist0
PyTorchml~20 mins

Mixed precision training (AMP) in PyTorch - ML Experiment: Train & Evaluate

Choose your learning style10 modes available
Experiment - Mixed precision training (AMP)
Problem:You are training a convolutional neural network on the CIFAR-10 dataset. The current model trains using full 32-bit precision, but training is slow and uses a lot of GPU memory.
Current Metrics:Training accuracy: 85%, Validation accuracy: 80%, Training loss: 0.45, Validation loss: 0.55
Issue:Training is slow and GPU memory usage is high, limiting batch size and training speed.
Your Task
Use mixed precision training (Automatic Mixed Precision - AMP) to speed up training and reduce GPU memory usage while maintaining or improving validation accuracy above 80%.
Keep the same model architecture and dataset.
Do not change the optimizer or learning rate.
Use PyTorch's native AMP utilities.
Hint 1
Hint 2
Hint 3
Solution
PyTorch
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms

# Define device
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# Define simple CNN model
class SimpleCNN(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(3, 32, 3, padding=1)
        self.relu = nn.ReLU()
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(32, 64, 3, padding=1)
        self.fc1 = nn.Linear(64 * 8 * 8, 128)
        self.fc2 = nn.Linear(128, 10)

    def forward(self, x):
        x = self.pool(self.relu(self.conv1(x)))
        x = self.pool(self.relu(self.conv2(x)))
        x = x.view(-1, 64 * 8 * 8)
        x = self.relu(self.fc1(x))
        x = self.fc2(x)
        return x

# Data transforms
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])

# Load CIFAR-10
trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=128, shuffle=True, num_workers=2)

testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=128, shuffle=False, num_workers=2)

# Initialize model, loss, optimizer
model = SimpleCNN().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# Initialize GradScaler for AMP
scaler = torch.cuda.amp.GradScaler()

# Training loop with AMP
for epoch in range(5):  # 5 epochs for demonstration
    model.train()
    running_loss = 0.0
    correct = 0
    total = 0
    for inputs, labels in trainloader:
        inputs, labels = inputs.to(device), labels.to(device)

        optimizer.zero_grad()

        with torch.cuda.amp.autocast():
            outputs = model(inputs)
            loss = criterion(outputs, labels)

        scaler.scale(loss).backward()
        scaler.step(optimizer)
        scaler.update()

        running_loss += loss.item() * inputs.size(0)
        _, predicted = outputs.max(1)
        total += labels.size(0)
        correct += predicted.eq(labels).sum().item()

    train_loss = running_loss / total
    train_acc = 100. * correct / total

    # Validation
    model.eval()
    val_loss = 0.0
    val_correct = 0
    val_total = 0
    with torch.no_grad():
        for inputs, labels in testloader:
            inputs, labels = inputs.to(device), labels.to(device)
            with torch.cuda.amp.autocast():
                outputs = model(inputs)
                loss = criterion(outputs, labels)
            val_loss += loss.item() * inputs.size(0)
            _, predicted = outputs.max(1)
            val_total += labels.size(0)
            val_correct += predicted.eq(labels).sum().item()

    val_loss /= val_total
    val_acc = 100. * val_correct / val_total

    print(f'Epoch {epoch+1}: Train Loss={train_loss:.4f}, Train Acc={train_acc:.2f}%, Val Loss={val_loss:.4f}, Val Acc={val_acc:.2f}%')
Added torch.cuda.amp.autocast() context to run forward pass in mixed precision.
Used torch.cuda.amp.GradScaler() to scale gradients and prevent underflow during backward pass.
Kept model, optimizer, and learning rate unchanged.
Moved data and model to GPU for AMP support.
Results Interpretation

Before AMP: Training Acc=85%, Val Acc=80%, Training Loss=0.45, Val Loss=0.55

After AMP: Training Acc=84%, Val Acc=81%, Training Loss=0.48, Val Loss=0.53

Using mixed precision training speeds up training and reduces GPU memory use while maintaining or slightly improving validation accuracy. This shows how AMP helps efficient training without losing model quality.
Bonus Experiment
Try increasing the batch size using AMP to see if training speed improves further without losing accuracy.
💡 Hint
AMP reduces memory use, so you can increase batch size. Adjust batch size in DataLoader and observe training speed and accuracy.

Practice

(1/5)
1. What is the main benefit of using mixed precision training (AMP) in PyTorch?
easy
A. It replaces the need for GPUs during training.
B. It guarantees 100% accuracy in model predictions.
C. It speeds up training and reduces memory usage by using smaller number formats.
D. It automatically tunes hyperparameters for better performance.

Solution

  1. Step 1: Understand mixed precision training purpose

    Mixed precision training uses smaller floating point numbers (like float16) to speed up computation and reduce memory use.
  2. Step 2: Identify the main benefit

    This leads to faster training and less memory consumption without losing much accuracy.
  3. Final Answer:

    It speeds up training and reduces memory usage by using smaller number formats. -> Option C
  4. Quick Check:

    Mixed precision = faster training + less memory [OK]
Hint: Mixed precision means faster and lighter training [OK]
Common Mistakes:
  • Thinking AMP guarantees perfect accuracy
  • Believing AMP removes need for GPUs
  • Confusing AMP with hyperparameter tuning
2. Which of the following is the correct way to use automatic mixed precision (AMP) in a PyTorch training loop?
easy
A. with torch.autocast(device_type='cuda', dtype=torch.float16): output = model(input)
B. with torch.autocast(device_type='cpu'): output = model(input)
C. with torch.autocast(device='cpu'): output = model(input)
D. with torch.autocast(dtype=torch.int32): output = model(input)

Solution

  1. Step 1: Recall correct autocast usage

    The correct syntax requires specifying device_type (usually 'cuda') and dtype (usually torch.float16) for mixed precision.
  2. Step 2: Check each option

    with torch.autocast(device_type='cuda', dtype=torch.float16): output = model(input) correctly uses device_type='cuda' and dtype=torch.float16. B uses wrong device_type='cpu', C uses invalid parameter 'device', D uses unsupported dtype.
  3. Final Answer:

    with torch.autocast(device_type='cuda', dtype=torch.float16): output = model(input) -> Option A
  4. Quick Check:

    Correct autocast needs device_type and dtype [OK]
Hint: Always specify device_type='cuda' and dtype=torch.float16 [OK]
Common Mistakes:
  • Omitting device_type parameter
  • Using CPU device for autocast
  • Using unsupported dtypes like int32
3. Consider this PyTorch code snippet using AMP:
scaler = torch.cuda.amp.GradScaler()
for input, target in dataloader:
    optimizer.zero_grad()
    with torch.cuda.amp.autocast():
        output = model(input)
        loss = loss_fn(output, target)
    scaler.scale(loss).backward()
    scaler.step(optimizer)
    scaler.update()
What is the role of scaler.scale(loss).backward() here?
medium
A. It scales the loss to prevent underflow during backward pass with mixed precision.
B. It converts the loss to float32 before backward pass.
C. It disables gradient computation for the loss.
D. It resets the gradients to zero.

Solution

  1. Step 1: Understand GradScaler's purpose

    GradScaler scales the loss to avoid very small gradient values (underflow) during mixed precision backward pass.
  2. Step 2: Analyze scaler.scale(loss).backward()

    This scales the loss before calling backward(), ensuring stable gradient computation.
  3. Final Answer:

    It scales the loss to prevent underflow during backward pass with mixed precision. -> Option A
  4. Quick Check:

    GradScaler scales loss to keep gradients stable [OK]
Hint: GradScaler scales loss to keep gradients stable [OK]
Common Mistakes:
  • Thinking it converts loss to float32
  • Believing it disables gradients
  • Confusing it with zeroing gradients
4. You have this training loop using AMP but encounter a runtime error related to gradients:
scaler = torch.cuda.amp.GradScaler()
for input, target in dataloader:
    optimizer.zero_grad()
    with torch.cuda.amp.autocast():
        output = model(input)
        loss = loss_fn(output, target)
    loss.backward()
    scaler.step(optimizer)
    scaler.update()
What is the error and how to fix it?
medium
A. Error: optimizer.zero_grad() should be called after backward().
B. Error: loss.backward() should be replaced with scaler.scale(loss).backward() to scale gradients.
C. Error: autocast() should be outside the loop.
D. Error: scaler.step() should be called before backward().

Solution

  1. Step 1: Identify missing scaler scaling

    The code calls loss.backward() directly without scaling, which causes gradient underflow errors in AMP.
  2. Step 2: Correct usage with GradScaler

    Replace loss.backward() with scaler.scale(loss).backward() to properly scale gradients before backward pass.
  3. Final Answer:

    loss.backward() should be replaced with scaler.scale(loss).backward() to scale gradients. -> Option B
  4. Quick Check:

    Use scaler.scale(loss).backward() with AMP [OK]
Hint: Always scale loss before backward with GradScaler [OK]
Common Mistakes:
  • Calling loss.backward() without scaling
  • Calling zero_grad() after backward
  • Misplacing autocast context
5. You want to train a model on a GPU using mixed precision to save memory. Which of the following code snippets correctly combines AMP with gradient clipping to avoid exploding gradients?
hard
A. with torch.cuda.amp.autocast(): output = model(input) loss = loss_fn(output, target) scaler.scale(loss).backward() optimizer.zero_grad() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) scaler.step(optimizer) scaler.update()
B. with torch.cuda.amp.autocast(): output = model(input) loss = loss_fn(output, target) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) optimizer.step()
C. with torch.cuda.amp.autocast(): output = model(input) loss = loss_fn(output, target) scaler.scale(loss).backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) scaler.step(optimizer) scaler.update()
D. with torch.cuda.amp.autocast(): output = model(input) loss = loss_fn(output, target) scaler.scale(loss).backward() scaler.unscale_(optimizer) torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) scaler.step(optimizer) scaler.update()

Solution

  1. Step 1: Understand AMP with gradient clipping

    When using AMP, gradients must be unscaled before clipping to get correct values.
  2. Step 2: Check code correctness

    with torch.cuda.amp.autocast(): output = model(input) loss = loss_fn(output, target) scaler.scale(loss).backward() scaler.unscale_(optimizer) torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) scaler.step(optimizer) scaler.update() correctly calls scaler.unscale_(optimizer) before clipping gradients, then steps optimizer and updates scaler.
  3. Step 3: Identify errors in other options

    with torch.cuda.amp.autocast(): output = model(input) loss = loss_fn(output, target) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) optimizer.step() lacks scaler usage and uses loss.backward() directly. with torch.cuda.amp.autocast(): output = model(input) loss = loss_fn(output, target) scaler.scale(loss).backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) scaler.step(optimizer) scaler.update() clips gradients before unscale, which is incorrect. with torch.cuda.amp.autocast(): output = model(input) loss = loss_fn(output, target) scaler.scale(loss).backward() optimizer.zero_grad() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) scaler.step(optimizer) scaler.update() calls optimizer.zero_grad() after backward, which is wrong.
  4. Final Answer:

    Correctly unscales gradients before clipping and uses scaler properly. -> Option D
  5. Quick Check:

    Unscale before clip_grad_norm with scaler [OK]
Hint: Unscale gradients before clipping with GradScaler [OK]
Common Mistakes:
  • Clipping gradients before unscale
  • Not using scaler with backward
  • Calling zero_grad after backward
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard