Guide · AI and GenAI
PyTorch 2.15 Explained: What Breaks When You Upgrade and How to Fix It
18 min readUpdated By Saket Bhatnagar
Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.
In short
- PyTorch 2.15, released on 28 October 2026, mostly breaks things around your code: the Python version, saved checkpoints, multi-GPU communication and strict test settings.
- It needs Python 3.11 or newer. On Python 3.10, pip cannot find 2.15 at all, and an unpinned install quietly gives you the last 2.14 release, 2.14.1 as of 31 October.
- torch.load with weights_only=True, the safe mode that refuses unknown Python objects, now refuses nested tensors (batches of different-length rows) unless you allow them by name.
- On multi-GPU jobs, the backend name nccl now picks a rewritten version of the code that moves tensors between GPUs. TORCH_DIST_USE_NCCL2=0 rolls back, but only in 2.15.
- Beginners can stop after the nested-tensor section. Engineers then get what the notes leave out: when the NCCL rollback switch is read, how the GPU is picked without device_id, and why 2.14 cannot read 2.15 checkpoints.
What goes wrong when you upgrade to a new PyTorch release?
Usually your model code keeps working, and the things around it break. The install step, old checkpoint files, multi-GPU start-up and your test settings are where an upgrade bites first.
PyTorch 2.15 is a good example. Its own release notes list more than twenty backwards-incompatible changes. Most are narrow, but four of them reach ordinary projects.
Picture a common setup. A small team trains a support-ticket classifier every night on one machine with two GPUs. CI runs on a Python 3.10 image, and pytest turns warnings into errors.
The model's weight files store nested tensors, and a separate evaluation job, still on PyTorch 2.14, reads the training job's checkpoints. The setup is made up, but each failure below was reproduced on PyTorch 2.15.0 or read in its release notes and source code.
Upgrade this project in one go and four things break in a row. CI cannot install 2.15, old checkpoints refuse to load, and the evaluation job cannot read new checkpoints. Tests that passed yesterday fail on warnings they never saw.
Which words do you need before reading the PyTorch 2.15 notes?
You need four terms for the first half. Each one is explained here once and used the same way below; the multi-GPU terms come in their own sections.
- Wheel: the ready-made package file pip downloads. Each wheel is built for one Python version and one operating system, so a missing wheel means no install.
- Checkpoint: a file that saves a model's weights, and sometimes the optimiser state, so you can load them later.
- weights_only=True: a torch.load setting that only rebuilds tensors and a short allowlist of safe Python objects. It exists because loading an untrusted file without it can run code.
- Nested tensor: one tensor that holds rows of different lengths, such as tickets of 40 and 300 tokens, without padding them to the same size.
What changed in PyTorch 2.15, and what breaks when you upgrade?
PyTorch 2.15 drops Python 3.10, tightens safe checkpoint loading and makes a rewritten NCCL backend the default. It also changes how some errors and warnings reach Python. Beginners need only the first three rows of the table; the engineer sections explain the rest.
The feature news is mostly about large GPU setups. The release blog highlights faster 4-bit decoding on new NVIDIA GPUs, FP8 on Apple Silicon and more AMD and Intel GPU support. This guide stays on what you must change.
| Change | Who it hits | What you see | What to do |
|---|---|---|---|
| Python 3.10 wheels gone (tested) | Anyone on Python 3.10 | pip: Could not find a version that satisfies the requirement torch==2.15.0 | Move to Python 3.11 or newer first |
| Default Linux wheel now CUDA 13.2; CUDA 12.6 and 13.0 wheels gone | Linux GPU users | pip install torch pulls the CUDA 13.2 build (tested: torch.version.cuda prints 13.2) | Check that your GPU driver supports CUDA 13.2 |
| Nested tensors off the weights_only allowlist (tested) | Anyone loading such checkpoints | UnpicklingError naming an Unsupported global | Allow the classes by name with safe_globals |
| nccl means the rewritten backend | Multi-GPU training | Communicators made at start-up, new class, changed methods | Pass device_id; roll back with TORCH_DIST_USE_NCCL2=0 if needed |
| Distributed checkpoint (DCP) metadata gains strides (tested) | Mixed 2.14 and 2.15 jobs | 2.14 load fails: too many values to unpack (expected 5) | Upgrade every reader before any writer |
| torch.ops warnings reach Python (tested) | Test suites run with warnings as errors | New UserWarning failures | Fix the cause or filter that warning |
| Complex-input ops raise TypeError (tested) | Code with except RuntimeError | Errors that used to be caught now escape | Catch (RuntimeError, TypeError) |
| MPS needs macOS 15 | Apple Silicon Macs on macOS 14 | Not tested by PyTorch; macOS 14 issues will not be fixed | Move to macOS 15 for GPU work on a Mac |
Which PyTorch 2.15 change hits your project first?
Drag to exploreAsk the questions in this order. Notice that the Python check comes first: nothing else matters until 2.15 installs.
Diagram connections:
- Planning the 2.15 upgrade to Python 3.10 anywhere?
- Python 3.10 anywhere? to Upgrade Python first (Yes)
- Python 3.10 anywhere? to Nested tensors in files? (No)
- Upgrade Python first to Nested tensors in files?
- Nested tensors in files? to Allowlist them by name (Yes)
- Nested tensors in files? to Multi-GPU with nccl? (No)
- Allowlist them by name to Multi-GPU with nccl?
- Multi-GPU with nccl? to Test the new backend (Yes)
- Multi-GPU with nccl? to Run tests and canary (No)
- Test the new backend to Run tests and canary
- Planning the 2.15 upgrade: Start with CI, then checkpoints, then GPUs
- Python 3.10 anywhere?: CI images, notebooks, servers
- Upgrade Python first: 2.15 has no 3.10 wheels
- Nested tensors in files?: Loaded with weights_only=True
- Allowlist them by name: safe_globals; 2.14 files also need torch._dynamo
- Multi-GPU with nccl?: init_process_group with backend nccl
- Test the new backend: Pass device_id; keep the rollback ready
- Run tests and canary: Warnings as errors, mixed-version jobs
Why does pip install torch==2.15.0 fail on Python 3.10?
It fails because PyTorch 2.15 publishes no wheel for Python 3.10. The package metadata requires Python 3.11 or newer, and the PyPI page lists wheels for Python 3.11 to 3.15 only.
In a Python 3.10 environment, a pinned install stops with this error from pip: Could not find a version that satisfies the requirement torch==2.15.0. The version list it prints ends at 2.14.1.
The quieter failure is the unpinned one. We ran pip install torch on Python 3.10 on 31 October 2026, and pip chose torch 2.14.1 without any warning. Your CI stays green while running the old version.
The release blog adds that 3.10 is no longer tested in CI and that building from source on 3.10 is not supported. Python 3.10 itself reached end of life on 1 October 2026, according to PEP 619.
Why do old checkpoints with nested tensors stop loading in 2.15?
They stop loading because PyTorch 2.15 removed nested tensors from the default weights_only allowlist. A file that loaded on 2.14 now fails until you name the nested-tensor classes yourself.
A checkpoint file is a pickle, Python's format for saving objects. Loading a pickle can call any function the file names. That is why weights_only=True only rebuilds objects on an approved list.
Think of the allowlist as the visitor list at a housing society gate. The file is a stream of visitors asking to enter. The default allowlist is the guard's permanent list.
safe_globals is a written note for today that names extra visitors. weights_only=False is opening the gate for everyone, which is fine only when you know who sent the file.
Where the picture breaks: a real guard can phone the resident. torch.load cannot ask anyone. It stops at the first unknown name and prints it, and allowing that name can reveal the next one.
What the failure looks like
We saved a jagged nested tensor (the torch.jagged layout) with 2.14.1 and loaded it on 2.15.0 with weights_only=True. The load failed with UnpicklingError and this line: Unsupported global: GLOBAL torch.nested._internal.nested_tensor._rebuild_njt was not an allowed global by default.
An older strided nested tensor failed the same way, naming torch._utils._rebuild_nested_tensor instead. Plain weight files with ordinary tensors still loaded with no change.
The fix, with the import the notes leave out
The release notes say to allow _rebuild_njt and NestedTensor with torch.serialization.safe_globals. Pause and predict: does that snippet work on its own in a fresh Python process?
For files saved by 2.14, it did not in our test. In a fresh process the load failed again, now naming torch._dynamo.decorators._DimRange. Adding import torch._dynamo before the load fixed it. Files saved by 2.15.0 loaded with the notes' snippet alone.
import torch
import torch._dynamo # needed for files saved by 2.14, which store _DimRange
from torch.nested._internal.nested_tensor import _rebuild_njt, NestedTensor
with torch.serialization.safe_globals([_rebuild_njt, NestedTensor]):
ckpt = torch.load("njt_2.14.pt", weights_only=True)
batch = ckpt["njt"]
print(type(batch).__name__, batch.is_nested) # NestedTensor TrueWhat just happened? torch._dynamo is the internal part of PyTorch behind torch.compile; the underscore marks it as internal. A jagged tensor saved by 2.14 also stores a small _DimRange object from it. Importing torch._dynamo adds that class to the default allowlist, through add_safe_globals in torch/_dynamo/__init__.py. The safe_globals block then allows only the two named classes, and only inside the with block.
This import detail is undocumented as of 31 October 2026, so pin your version and keep a test that loads one real checkpoint. Version 2.14.1 had a similar rule, but its error said it plainly: torch.nested and torch._dynamo must be imported to load nested jagged tensors.
For older strided nested tensors, allow torch._utils._rebuild_nested_tensor the same way. That one loaded on 2.15.0 without the extra import in our test.
The reverse direction breaks too. In our test, 2.14.1 could not load a jagged nested-tensor file saved by 2.15.0, even with weights_only=False. It failed with AttributeError: 'Tensor' object has no attribute '_dynamo_dynamic_range'.
What does the rewritten NCCL backend change for multi-GPU training?
NCCL is NVIDIA's library that copies tensors between GPUs. A process group is PyTorch's object that uses it, and each training process in the group is called a rank. Our nightly job uses both GPUs, so it runs two ranks, one per GPU.
In 2.15, backend="nccl" no longer means the old ProcessGroupNCCL. It means a rewritten implementation, ProcessGroupNCCL2. The old one is still available as nccl-legacy for this release only.
Version 2.14.1 already shipped the new backend, but only when you set TORCH_DIST_USE_NCCL2=1 or chose the nccl2 backend name. We confirmed both in the 2.14.1 package. Version 2.15 flips the default, so 0 now means roll back.
What behaves differently
- Communicators, the NCCL connections between GPUs, are created when you call init_process_group or new_group. The notes warn this uses GPU memory even for groups you never use.
- Each group is tied to one GPU, and torch.cuda.set_device is not used to pick it. The notes say to pass device_id; one process driving several GPUs needs nccl-legacy.
- isinstance checks against ProcessGroupNCCL return False. We confirmed ProcessGroupNCCL2 is not a subclass, and that it lacks legacy methods such as comm_split_count.
- An async all_gather future now gives a list of per-rank outputs, according to the release notes.
If you skip device_id, the 2.15.0 source picks the GPU itself. It uses LOCAL_RANK, which torchrun sets, modulo the number of visible GPUs. Without LOCAL_RANK it uses the global rank instead.
So the usual one-GPU-per-process torchrun job usually still lands on the right GPU. A launcher that maps ranks to GPUs differently does not. Passing device_id=torch.device("cuda", local_rank) removes the guesswork.
How the rollback switch is read
The release notes say to set TORCH_DIST_USE_NCCL2=0 on every rank before creating the group. The source shows why the timing matters.
How the name nccl becomes a backend in 2.15.0
Drag to exploreThe environment variable is read once per process, at the first lookup of nccl. Notice that nothing re-reads it afterwards.
Diagram connections:
- init_process_group(nccl) to First nccl lookup
- First nccl lookup to TORCH_DIST_USE_NCCL2 = 0?
- TORCH_DIST_USE_NCCL2 = 0? to ProcessGroupNCCL (Yes)
- TORCH_DIST_USE_NCCL2 = 0? to ProcessGroupNCCL2 (No or unset)
- ProcessGroupNCCL to Choice fixed for process
- ProcessGroupNCCL2 to Choice fixed for process
- init_process_group(nccl): Your training script, on every rank
- First nccl lookup: Registers the backend from a package entry point
- TORCH_DIST_USE_NCCL2 = 0?: Read only at this moment
- ProcessGroupNCCL: Legacy backend, removed in the next release
- ProcessGroupNCCL2: Rewritten backend, the 2.15 default
- Choice fixed for process: Changing the variable later has no effect
We checked this on 2.15.0 by inspecting the registered backend, which needs no GPU. Setting the variable before the first lookup selected the legacy creator. Setting it after the lookup changed nothing.
This read-once timing is undocumented as of 31 October 2026. Set the variable in the launcher or the job's environment, never with os.environ halfway through a script. The value must be exactly 0: in our test, false and off kept the new backend.
One more tested difference matters for CI machines without GPUs. Calling init_process_group("nccl") there used to raise ValueError. On 2.15.0 it raises RuntimeError: nccl2 requires at least one CUDA device.
Why can't PyTorch 2.14 read a 2.15 distributed checkpoint?
DCP, short for torch.distributed.checkpoint, writes one file per process plus a small metadata file. A 2.14 reader fails because 2.15 adds a strides field to that metadata file, and 2.14 does not know that field. Version 2.15 reads both formats, so only older readers break. Jagged nested-tensor files saved with torch.save follow the same one-way rule in our test.
We saved a small state dict with dcp.save on each version and loaded it with the other. Version 2.15.0 loaded the 2.14.1 checkpoint fine. Version 2.14.1 failed on the 2.15.0 checkpoint with a CheckpointException whose message includes too many values to unpack (expected 5).
Which DCP checkpoints load across versions
Drag to exploreCompatibility only flows forward. Notice the single failing arrow: a 2.14 reader cannot open a checkpoint written by 2.15.
Diagram connections:
- 2.14 training job to 2.15 evaluation job (Loads)
- 2.15 training job to 2.15 evaluation job (Loads)
- 2.14 training job to 2.14 evaluation job (Loads)
- 2.15 training job to 2.14 evaluation job (Fails: expected 5)
- 2.14 training job: Writes DCP metadata without strides
- 2.15 training job: Writes DCP metadata with strides
- 2.15 evaluation job: Reads both formats
- 2.14 evaluation job: Reads only the old format
The release notes add that there is no flag to write the old format. In our running example, upgrade the 2.14 evaluation job before the nightly training job writes its first 2.15 checkpoint.
Which smaller PyTorch 2.15 changes break tests and error handling?
Four smaller changes surface as test failures rather than install failures. Each one below was run on both versions on 31 October 2026.
Warnings from torch.ops now reach Python
C++ warnings raised inside operators called through torch.ops now go through Python's warnings module. In our 2.14.1 test, the same warning was only printed to the console, so warning filters never saw it.
import warnings
import torch
a, b = torch.ones(3), torch.ones(3)
out = torch.empty(2) # wrong shape, so the op resizes it and warns
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
torch.ops.aten.add.out(a, b, out=out)
print(torch.__version__, [w.category.__name__ for w in caught])
# 2.14.1: []
# 2.15.0: ['UserWarning']The comments show what each version printed. On 2.15.0 Python caught a UserWarning, and under a warnings-as-errors filter the same call raised it. Calling torch.add with out= warned in Python on both versions.
In the running example, pytest's warnings-as-errors setting turns this into failed tests. Fix the cause, or filter that one message, rather than turning the setting off.
Complex numbers now raise TypeError
Operations with no meaning for complex numbers now raise TypeError instead of RuntimeError. On CPU we saw this for sort, argmax, topk, maximum, max with dim, isposinf and view_as_real on a real tensor.
An except RuntimeError block no longer catches these errors. Operations that are merely unimplemented for complex, such as nansum and rad2deg, now raise NotImplementedError, which RuntimeError handlers still catch.
Two changes for notebooks and training loops
- importlib.reload(torch) now raises ImportError saying torch can only be initialised once per process. On 2.14.1 our reload raised a confusing RuntimeError about TORCH_LIBRARY. Restart the notebook kernel instead.
- Passing a set of parameters to an optimiser now gives a FutureWarning, because set order can change between runs. Wrap the parameters in list() instead.
How do you upgrade to PyTorch 2.15 safely?
Upgrade in this order: Python, the pinned install, tests, checkpoints, multi-GPU jobs, then error handling. Each step has a check you can run.
Move to Python 3.11 or newer
Run python --version in every CI image, notebook and server. Anything that prints 3.10 cannot install 2.15.
Pin and install in a fresh environment
Install torch==2.15.0, then run python -c "import torch; print(torch.__version__)". On Linux x86_64 the default PyPI wheel printed 2.15.0+cu132 in our test, meaning CUDA 13.2.
Run the tests with warnings as errors
New failures that name a UserWarning from an operator usually come from the torch.ops change. Fix each cause or filter that exact message.
Load one real checkpoint of each kind
Use weights_only=True. If you see Unsupported global, add the named class with safe_globals. For jagged nested tensors saved by 2.14, also import torch._dynamo first.
Upgrade checkpoint readers before writers
Every job that reads DCP checkpoints or nested-tensor files moves to 2.15 first. Only then let a 2.15 job write one. In our test, 2.14.1 could not read either kind written by 2.15.0.
Run a short multi-GPU job on the new backend
Pass device_id when you create the process group. If it hangs or fails, rerun with TORCH_DIST_USE_NCCL2=0 set on every rank, and report the bug.
Search for except RuntimeError
Wherever the guarded code may see complex tensors, catch (RuntimeError, TypeError) instead.
How do you explain a PyTorch 2.15 upgrade in a system design interview?
Frame it as a dependency upgrade on a shared ML platform: find what depends on the version, order the rollout by compatibility, and keep a way back. Interviewers listen for the order and the rollback, not the PyTorch trivia.
Inventory the dependants
List every image, job and notebook that imports torch, with its Python version. In our example that includes the CI image and the evaluation job.
Map the format changes
Say which files cross versions. DCP checkpoints and jagged nested-tensor files written by 2.15 could not be read by 2.14 in our test, so readers upgrade first.
Tighten loading, do not loosen it
Allow named classes with safe_globals instead of switching to weights_only=False. That keeps untrusted files from running code.
Canary the risky part
Run one multi-GPU job on the new NCCL backend first, a small trial called a canary. Watch for hangs and memory use before moving the nightly job.
Keep a rollback with an expiry
TORCH_DIST_USE_NCCL2=0 is the rollback, set before start-up. Note that it disappears in the next release, so open bugs now.
Questions people ask
- Should I upgrade to PyTorch 2.15 right away?
- Upgrade when you have run the checks in this guide, not on day one by default. Single-GPU projects on Python 3.11 or newer usually need only the checkpoint and test fixes. Multi-GPU teams should canary the new NCCL backend first. The rollback switch exists only in 2.15, so testing now is cheaper than testing on the next release.
- Can I use PyTorch 2.15 with Python 3.10?
- No. PyTorch 2.15 publishes no Python 3.10 wheels, and its metadata requires Python 3.11 or newer. A pinned pip install fails, and an unpinned one silently installs 2.14.1, the newest 2.14 release as of 31 October 2026. Stay on 2.14 while you move to Python 3.11 or newer, and pin the version so the fallback cannot hide.
- Is it safe to use weights_only=False to load old checkpoints?
- Only for files you created or fully trust. With weights_only=False, torch.load can run any function the file names, so a malicious checkpoint can run code on your machine. For your own nested-tensor files, prefer safe_globals with the exact classes named in the error, plus import torch._dynamo for jagged files saved by 2.14.
- Will TORCH_DIST_USE_NCCL2=0 keep working after PyTorch 2.15?
- No, according to the release notes as of 31 October 2026. They say nccl-legacy and TORCH_DIST_USE_NCCL2=0 will be removed in the next release. Use the switch to ship 2.15 safely, and report any problem with the new backend now so it can be fixed before the legacy path goes.
- Does PyTorch 2.15 still work on macOS 14?
- It may install, but it is not supported. The macOS wheels on PyPI are still tagged for macOS 14.0 or newer. The release blog says MPS, the Apple Silicon GPU backend, needs macOS 15. It adds that PyTorch may still run on macOS 14, but is not tested there and issues will not be fixed.
- Why does my notebook now fail when I reload torch?
- Because PyTorch 2.15 blocks importing torch twice in one process. importlib.reload(torch), or re-importing after a failed import, now raises ImportError saying torch can only be initialised once per process. On 2.14.1 the same reload raised a confusing RuntimeError. Restart the kernel instead of reloading torch.
Sources and further reading
- PyTorch 2.15 Release Blog, PyTorch Foundation, 28 October 2026
- PyTorch v2.15.0 release notes on GitHub, backwards-incompatible changes and deprecations
- torch 2.15.0 on PyPI: Requires-Python and wheel list
- PEP 619: Python 3.10 release schedule and end-of-life date
- torch/distributed/distributed_c10d.py at the v2.15.0 tag: NCCL backend selection and device choice