CPU / GPU / MPS
PyTorch device management: CPU, CUDA, MPS, XPU. Moving tensors and models, mixed precision, and the patterns for portable code.
PyTorch — devices
EXAMPLE
import torch
# ===== Detect available device =====
device = (
'cuda' if torch.cuda.is_available()
else 'mps' if torch.backends.mps.is_available() # Apple Silicon
else 'cpu'
)
print('device:', device)
# Multi-GPU index:
device = 'cuda:0' # first GPU
n_gpus = torch.cuda.device_count()
# ===== Create directly on device =====
x = torch.randn(3, 3, device=device)
# Faster than creating on CPU then moving.
# ===== Move tensors =====
x_cpu = torch.zeros(3, 3)
x_gpu = x_cpu.to(device)
x_gpu = x_cpu.to(device, non_blocking=True)
x_gpu = x_cpu.cuda() # legacy; .to is preferred
# ===== Move modules =====
model = torch.nn.Linear(4, 3).to(device)
# Every Variable / Parameter must be on the SAME device as input tensors.
# Common error message: 'expected all tensors to be on the same device'.
# ===== DataLoader + pinned memory =====
from torch.utils.data import DataLoader, TensorDataset
ds = TensorDataset(torch.randn(100, 4), torch.randint(0, 3, (100,)))
loader = DataLoader(ds, batch_size=8, pin_memory=True, num_workers=2)
for X, y in loader:
X = X.to(device, non_blocking=True)
y = y.to(device, non_blocking=True)
# train ...
# pin_memory=True + non_blocking=True together let the GPU copy in parallel with compute.
# ===== Mixed precision (autocast) =====
from torch.amp import autocast, GradScaler
scaler = GradScaler(device='cuda')
for X, y in loader:
X = X.to(device, non_blocking=True); y = y.to(device, non_blocking=True)
optim.zero_grad(set_to_none=True)
with autocast(device_type='cuda', dtype=torch.float16):
out = model(X)
loss = loss_fn(out, y)
scaler.scale(loss).backward()
scaler.step(optim)
scaler.update()
# ===== Apple Silicon (MPS) =====
# Same patterns; device = 'mps'.
# Not every op is implemented — fall back to CPU may be needed for some models.
# PyTorch sets PYTORCH_ENABLE_MPS_FALLBACK=1 to auto-fall-back.
# ===== Querying GPU info =====
torch.cuda.is_available()
torch.cuda.device_count()
torch.cuda.get_device_name(0)
torch.cuda.memory_allocated() # bytes
torch.cuda.max_memory_allocated()
torch.cuda.empty_cache() # release cached allocator memory
# ===== DistributedDataParallel (multi-GPU) =====
# Use torch.distributed.init_process_group + DDP wrapper.
# Run via torchrun:
# torchrun --nproc_per_node=4 train.py
# ===== Patterns to internalise =====
# - Detect device once at the top of main; thread it through
# - .to(device, non_blocking=True) + pin_memory=True is the perf trinity
# - autocast + GradScaler for mixed precision
# - Set seeds AND torch.backends.cudnn.deterministic for repro
# ===== Pitfalls =====
# - Model on GPU, input on CPU -> device mismatch error
# - Forgetting non_blocking + pin_memory -> CPU/GPU stalls
# - GradScaler with fp32 model -> no help (fp16 only)
# - torch.cuda calls without CUDA -> errors on Mac / pure CPU
Why it matters
Device management decides whether PyTorch flies or crawls. Detect device once, create tensors with device=, .to() into modules + tensors consistently, pin memory + non_blocking + autocast for GPU perf. The same code runs on CPU, CUDA, MPS, XPU with this discipline.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import torch
dev = 'cuda' if torch.cuda.is_available() else ('mps' if torch.backends.mps.is_available() else 'cpu')
x = torch.randn(1024, 1024, device=dev)
Try it Yourself »
Discussion
Loading…