Training with one floating-point dtype can leave accelerator throughput and memory savings unused. PyTorch automatic mixed precision keeps model parameters in their default precision while eligible forward operations use float16 or bfloat16.
The current torch.amp API separates two jobs. autocast chooses an operation-specific dtype for the forward pass and loss calculation, while GradScaler protects float16 gradients from underflow during the backward pass.
The smoke program selects CUDA float16 when a GPU is available and CPU bfloat16 otherwise. The CPU path proves the AMP control flow and parameter update without claiming accelerator performance; apply the same autocast and scaler placement to an existing training loop after its full-precision behavior is known.
Related: How to enable CUDA in PyTorch
Related: How to run a training loop in PyTorch
Related: How to debug NaN loss in PyTorch
import torch from torch import nn torch.manual_seed(7) device_type = "cuda" if torch.cuda.is_available() else "cpu" device = torch.device(device_type) amp_dtype = torch.float16 if device_type == "cuda" else torch.bfloat16 if device_type == "cpu": torch.backends.mkldnn.enabled = False
The CPU smoke path disables mkldnn for processors without native bfloat16 instructions; supported production CPUs can retain mkldnn acceleration.
model = nn.Sequential( nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1), ).to(device) optimizer = torch.optim.SGD(model.parameters(), lr=0.05) loss_fn = nn.MSELoss() scaler = torch.amp.GradScaler(device_type, enabled=(amp_dtype == torch.float16))
CPU bfloat16 training does not need gradient scaling, so the scaler remains disabled on the CPU path while preserving the same call shape.
inputs = torch.randn(16, 4, device=device) target = torch.randn(16, 1, device=device) before = model[0].weight.detach().clone()
optimizer.zero_grad(set_to_none=True) with torch.amp.autocast(device_type=device_type, dtype=amp_dtype): prediction = model(inputs) loss = loss_fn(prediction, target)
Backward operations stay outside the autocast context because they use the dtype chosen for their corresponding forward operations.
scaler.scale(loss).backward() gradients_finite = bool(torch.isfinite(model[0].weight.grad).all()) scaler.step(optimizer) scaler.update() weight_changed = not torch.equal(before, model[0].weight.detach())
print(f"device_type={device_type}") print(f"amp_dtype={amp_dtype}") print(f"scaler_enabled={scaler.is_enabled()}") print(f"prediction_dtype={prediction.dtype}") print(f"loss_dtype={loss.dtype}") print(f"gradients_finite={gradients_finite}") print(f"weight_changed={weight_changed}") print(f"loss_value={loss.detach().float().item():.6f}")
import torch from torch import nn torch.manual_seed(7) device_type = "cuda" if torch.cuda.is_available() else "cpu" device = torch.device(device_type) amp_dtype = torch.float16 if device_type == "cuda" else torch.bfloat16 if device_type == "cpu": torch.backends.mkldnn.enabled = False model = nn.Sequential( nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1), ).to(device) optimizer = torch.optim.SGD(model.parameters(), lr=0.05) loss_fn = nn.MSELoss() scaler = torch.amp.GradScaler(device_type, enabled=(amp_dtype == torch.float16)) inputs = torch.randn(16, 4, device=device) target = torch.randn(16, 1, device=device) before = model[0].weight.detach().clone() optimizer.zero_grad(set_to_none=True) with torch.amp.autocast(device_type=device_type, dtype=amp_dtype): prediction = model(inputs) loss = loss_fn(prediction, target) scaler.scale(loss).backward() gradients_finite = bool(torch.isfinite(model[0].weight.grad).all()) scaler.step(optimizer) scaler.update() weight_changed = not torch.equal(before, model[0].weight.detach()) print(f"device_type={device_type}") print(f"amp_dtype={amp_dtype}") print(f"scaler_enabled={scaler.is_enabled()}") print(f"prediction_dtype={prediction.dtype}") print(f"loss_dtype={loss.dtype}") print(f"gradients_finite={gradients_finite}") print(f"weight_changed={weight_changed}") print(f"loss_value={loss.detach().float().item():.6f}")
$ python amp_train_step.py device_type=cpu amp_dtype=torch.bfloat16 scaler_enabled=False prediction_dtype=torch.bfloat16 loss_dtype=torch.float32 gradients_finite=True weight_changed=True loss_value=0.648133
prediction_dtype=torch.bfloat16 confirms lower-precision forward execution on the CPU path. gradients_finite=True and weight_changed=True confirm that backward propagation produced finite gradients and the optimizer updated model parameters.