+1 (726) 227-4060

PyTorch 1.x to 2.x Migration: A Field Checklist

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-smi for 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, torchaudio and torchtext are version-locked to torch; 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.load defaults to weights_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 passing weights_only=False for trusted checkpoints you control, or better, by re-saving state dicts only and using torch.serialization.add_safe_globals for 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() before scheduler.step(), and do not call the scheduler in the inner loop if it is an epoch scheduler.
  • torch.cuda.amp moved to torch.amp. torch.cuda.amp.autocast(...) still works but is deprecated; use torch.autocast("cuda", dtype=...) and torch.amp.GradScaler("cuda"). If you are moving to BF16, drop the scaler entirely (see Choosing precision in 2026).
  • torch.nn.functional.scaled_dot_product_attention replaced 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 dim behaviours and dtype promotion tightened in a few places (torch.range removed, torch.symeig/torch.lstsq/torch.eig removed in favour of torch.linalg). Run the test suite and grep for the torch.linalg deprecations listed in each release note.
  • torch.utils.data workers and fork. Recent releases warn on fork start method with CUDA initialized; set multiprocessing_context="spawn" or initialize CUDA after the loader.
  • TorchScript is in maintenance mode. torch.jit.script still exists and still works, but new features do not target it and it is not the path to torch.compile. Plan to replace it with torch.export for deployment artifacts.
  • Distributed: FSDP1 → FSDP2. FullyShardedDataParallel still works but fully_shard is 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 with datasets.
  • 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) and torch.compile. Most Apex usage can be deleted outright.
  • torch.distributed.launch is deprecated in favour of torchrun.
  • Legacy torchvision transforms still work, but torchvision.transforms.v2 handles bounding boxes and masks and is the maintained API.

4. The migration sequence

  1. 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.
  2. Upgrade the environment per section 1. Get imports working and the test suite running before changing behaviour.
  3. Fix errors, then warnings. Treat DeprecationWarning and UserWarning from torch as a to-do list; run with -W error::DeprecationWarning in CI once the list is empty.
  4. Reproduce the baseline. Golden outputs should match within floating-point tolerance (1e-5 FP32, 1e-2 BF16). Training loss curves should overlay for the first few hundred steps. A divergence here is a real behavioural change; find it before moving on.
  5. 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 check and 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.