PyTorch code often runs across laptops, CPU servers, NVIDIA GPUs, Apple Silicon Macs, and notebook runtimes with different accelerator support. Selecting a device at startup keeps the same training or inference code from hard-coding CUDA on machines that only have CPU or MPS available.
torch.device names the allocation target for tensors and modules. torch.cuda.is_available() and torch.backends.mps.is_available() let the program choose an accelerator only when the installed build and hardware can use it, then fall back to cpu.
A selected device only helps after model parameters and input tensors are moved to it. Keep the selection function close to the training or inference entry point, and check at least one parameter, input, and output device during the first run.
Related: How to enable CUDA in PyTorch
Related: How to enable MPS in PyTorch
Related: How to enable ROCm in PyTorch
import torch from torch import nn def select_device(): if torch.cuda.is_available(): return torch.device("cuda") if torch.backends.mps.is_available(): return torch.device("mps") return torch.device("cpu") device = select_device() model = nn.Linear(3, 2).to(device) inputs = torch.ones(4, 3, device=device) with torch.no_grad(): outputs = model(inputs) print(f"torch_version={torch.__version__}") print(f"cuda_available={torch.cuda.is_available()}") print(f"mps_available={torch.backends.mps.is_available()}") print(f"selected_device={device}") print(f"model_device={next(model.parameters()).device}") print(f"input_device={inputs.device}") print(f"output_device={outputs.device}")
$ python select_device_demo.py torch_version=2.12.1+cpu cuda_available=False mps_available=False selected_device=cpu model_device=cpu input_device=cpu output_device=cpu
The exact torch_version line changes with the active environment. The selected_device, model_device, input_device, and output_device lines should name the same device.
def select_device(): if torch.cuda.is_available(): return torch.device("cuda") if torch.backends.mps.is_available(): return torch.device("mps") return torch.device("cpu") device = select_device()
model = build_model().to(device) optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
Module.to(device) moves model parameters and buffers in place. Keeping the optimizer after the move avoids stale parameter references in accelerator-specific code paths.
for inputs, targets in train_loader: inputs = inputs.to(device) targets = targets.to(device) outputs = model(inputs) loss = loss_fn(outputs, targets)
Tensor.to(device) returns a tensor on the requested device. Assign the returned tensor before passing it into the model or loss function.
print(f"model_device={next(model.parameters()).device}") print(f"input_device={inputs.device}") print(f"output_device={outputs.device}")
Matching device values confirm that the model parameters, batch tensors, and output tensors are on the same backend.
$ rm select_device_demo.py