PyTorch 2.0 shipped in March 2023 and the 2.x line has had a dozen releases since. Plenty of production code is still pinned to 1.12 or 1.13, usually because the last upgrade attempt broke something and nobody had time to find out what. This is the checklist we use when we take a 1.x codebase to a current 2.x release: the API changes that actually bite, the dependency matrix to get right first, the ecosystem pieces that no longer exist, and how to add torch.compile without risking a regression. It pairs with our version migration and upgrade service.
1. Fix the toolchain before the code
Most "PyTorch upgrade" failures are CUDA and packaging failures. Resolve these first, in a fresh environment, before touching a line of model code.
- CUDA and driver. Current 2.x wheels are built against CUDA 12.x; 1.x-era environments are often on CUDA 11.x with a driver too old for 12. Check
nvidia-smifor the driver's maximum supported CUDA version and upgrade the driver first. The PyTorch wheel bundles its own CUDA runtime, so the system toolkit only matters for custom extensions. - Python. Current PyTorch requires Python 3.10 or later. 1.x environments on 3.7-3.9 need a Python upgrade, which drags every other dependency with it.
- Companion libraries.
torchvision,torchaudioandtorchtextare version-locked totorch; install them from the same index in one command. Third-party CUDA extensions (flash-attention, apex, custom ops) must be rebuilt or replaced. - Pin everything. Produce a lock file for the new environment before you start; you will be bisecting against it.
2. API deltas that actually bite
Most 1.x code runs unchanged on 2.x. The changes that reliably cause trouble:
torch.loaddefaults toweights_only=True(since 2.6). Checkpoints that pickled arbitrary Python objects (custom classes, argparse namespaces, numpy scalars in older formats) now fail to load. Fix by passingweights_only=Falsefor trusted checkpoints you control, or better, by re-saving state dicts only and usingtorch.serialization.add_safe_globalsfor the handful of types you genuinely need.- Optimizer and scheduler ordering. A warning in 1.x, now an error path in some patterns: call
optimizer.step()beforescheduler.step(), and do not call the scheduler in the inner loop if it is an epoch scheduler. torch.cuda.ampmoved totorch.amp.torch.cuda.amp.autocast(...)still works but is deprecated; usetorch.autocast("cuda", dtype=...)andtorch.amp.GradScaler("cuda"). If you are moving to BF16, drop the scaler entirely (see Choosing precision in 2026).torch.nn.functional.scaled_dot_product_attentionreplaced hand-written attention. Not a breaking change, but your custom attention is probably slower than the built-in and blocks fusion; swap it in.- Default
dimbehaviours and dtype promotion tightened in a few places (torch.rangeremoved,torch.symeig/torch.lstsq/torch.eigremoved in favour oftorch.linalg). Run the test suite and grep for thetorch.linalgdeprecations listed in each release note. torch.utils.dataworkers andfork. Recent releases warn onforkstart method with CUDA initialized; setmultiprocessing_context="spawn"or initialize CUDA after the loader.- TorchScript is in maintenance mode.
torch.jit.scriptstill exists and still works, but new features do not target it and it is not the path totorch.compile. Plan to replace it withtorch.exportfor deployment artifacts. - Distributed: FSDP1 → FSDP2.
FullyShardedDataParallelstill works butfully_shardis the supported design. New distributed code should use it; see Distributed training with FSDP2.
3. Replacing dead ecosystem pieces
Several libraries a 1.x codebase may depend on are gone or unmaintained:
- TorchText was deprecated in 2024 and its repository is no longer developed. Replace tokenization and vocab handling with Hugging Face
tokenizers/transformers, and dataset loading withdatasets. - TorchServe was archived in August 2025. Replace with Triton, LitServe, ONNX Runtime or vLLM; our TorchServe migration guide has the decision tree.
- Apex AMP and fused layers are superseded by native
torch.autocast, fused optimizers (fused=True) andtorch.compile. Most Apex usage can be deleted outright. torch.distributed.launchis deprecated in favour oftorchrun.- Legacy
torchvisiontransforms still work, buttorchvision.transforms.v2handles bounding boxes and masks and is the maintained API.
4. The migration sequence
- Baseline. On the old version, record evaluation metrics, a golden set of model outputs on fixed inputs, and per-step training loss for the first N steps with a fixed seed. These are your regression oracle.
- Upgrade the environment per section 1. Get imports working and the test suite running before changing behaviour.
- Fix errors, then warnings. Treat
DeprecationWarningandUserWarningfromtorchas a to-do list; run with-W error::DeprecationWarningin CI once the list is empty. - Reproduce the baseline. Golden outputs should match within floating-point tolerance (
1e-5FP32,1e-2BF16). Training loss curves should overlay for the first few hundred steps. A divergence here is a real behavioural change; find it before moving on. - Only then, modernize. Autocast, SDPA, fused optimizer,
torch.compile.
5. Adding torch.compile opportunistically
Do not compile everything on day one. Compile the model only, in inference first, behind a flag:
import os, torch
model = build_model().eval()
if os.environ.get("TORCH_COMPILE", "1") == "1":
model = torch.compile(model)
Compare golden outputs against eager; they should match to BF16/FP32 tolerance. Then check torch._dynamo.explain(model)(example) for graph breaks and fix the cheap ones. Move to training only after inference is clean, and keep the flag so a production incident can be diagnosed against eager in one restart. Our torch.compile in practice tutorial covers measurement and graph breaks in depth.
6. Regression-testing strategy
- Golden-output tests for every model, run in CI against a pinned checkpoint, with tolerance asserts.
- A short training smoke test (50-100 steps, fixed seed) that asserts loss decreases and matches a stored curve within tolerance.
- A serialization round-trip test: save with the new version, load, compare; and load every checkpoint format you have in production.
- A dependency-drift guard:
pip checkand a lock-file diff in CI so an unpinned transitive upgrade cannot reintroduce the problem.
7. After the upgrade
Stay current. Upgrading one minor version every quarter is a small task; upgrading five at once is a project. Subscribe to the PyTorch release notes, run the test suite against release candidates, and keep the regression oracle from step 4 as a permanent fixture.
If your team would rather hand this off, our PyTorch version migration and upgrades service does exactly this, including the toolchain work and regression harness. Contact us with your current version and we will scope it.