Next Monday Jul 20, 2026, PyTorch’s build backend switches from setuptools to
scikit-build-core 1.0, and setup.py is removed.
TL;DR: If you build with spin or pip install (regular or editable),
nothing changes. If you invoke python setup.py ... anywhere
(scripts, docs, muscle memory), switch to spin/pip. Prebuilt wheels and runtime
behavior are unaffected.
Why
Modern Python packaging orchestrates builds through standard interfaces
(PEP 517/518/621); the legacy python setup.py ... commands they replaced
have been deprecated for years. PyTorch is a hybrid: a substantial
CMake-built native core alongside a large pure-Python library, and while
CMake covers the native half down to the extension modules, wheels,
sdists, and package metadata are outside its scope. Until now, setup.py
bridged that gap with a decade of custom orchestration. The maintained,
standards-based backend purpose-built for exactly this hybrid is
scikit-build-core: it packages the Python side like any modern backend and
drives the CMake build, all from pyproject.toml. The switch puts PyTorch’s
build on the interfaces the rest of the ecosystem targets and makes
standards-compliant artifacts the default.
What you need to change
setup.py is removed, so any direct invocation of it stops working. The
replacements:
| Before | After |
|---|---|
python setup.py develop |
spin develop (or pip install -e . -v --no-build-isolation) |
python setup.py install |
spin install (or pip install . -v --no-build-isolation) |
python setup.py bdist_wheel |
python -m build --wheel --no-isolation |
python setup.py clean |
spin clean |
Either form works. Each spin command is a thin wrapper that runs exactly
the pip command shown beside it (using uv when available); the pip and
python -m build forms are the standard Python build interfaces and have
been the recommended way to build PyTorch for a while. The wrappers belong
to spin, the developer CLI we adopted last
year.
Build-adjacent developer tasks that used to hide behind setup.py
subcommands are moving there; spin with no arguments lists what is
available. Spin is part of requirements.txt, so a PyTorch dev environment
already has it; for a standalone, global install use uv tool install spin
and run it from the repository root.
What stays the same
- The spin and pip workflows are untouched, including editable
installs and all incremental-rebuild behavior (the CMake cache carries
over between builds exactly as before). - Every build-configuration environment variable keeps working:
USE_CUDA,USE_ROCM,DEBUG,REL_WITH_DEB_INFO,MAX_JOBS,
CMAKE_BUILD_TYPE,CC/CXX, and the rest. They now seed the CMake
cache directly and are documented incmake/EnvVarForwarding.cmakeand
pyproject.toml, instead of being interpreted by setup.py code. - ccache/sccache, cross-compilation, and CMake-cache-based workflows
carry over: the backend drives the same CMake build you know, without
setup.py in the middle. - Performance is neutral, by construction and by measurement. We
compared same-day CI binary builds (scikit-build-core vs setuptools):
matched build jobs show identical durations within noise, and all 52
wheel artifacts across every platform family are byte-comparable in size
(within 0.02%; the only content difference is license files now included
per PEP 639, and the setuptools-specifictop_level.txtgoing away).
Editable installs
scikit-build-core’s editable mode separates the two halves of a
development install cleanly: Python sources are imported straight from
your checkout (edit and rerun, as always), while compiled artifacts
(torch._C, the libtorch libraries) are imported from the wheel install
location instead of being scattered through the source tree.
Day to day this is invisible (import torch just works), but a few
things are worth knowing:
- Tooling that expects
.so/.pydfiles inside the package directory
(torch/lib,torch/_C*.so) needs updating; compiled artifacts no
longer land there. - Rebuilding after a C++ change works exactly as it does today: rerun
spin develop(orpip install -e . -v --no-build-isolation) for an
incremental, CMake-cached rebuild. - New and strictly opt-in: an editable install can also rebuild
automatically onimport torch. This capability is not battle-tested
yet, so it is off by default and the rerun workflow above remains the
recommended one. Opting in happens at install time, by setting the
variable on the install command (SKBUILD_EDITABLE_REBUILD=true spin develop); that install then rebuilds on the firstimport torchin a
process, until you reinstall without the variable. CONTRIBUTING.md has
the details.
The full development flow is documented in CONTRIBUTING.md.
If you build on top of PyTorch
Nothing changes. C++/CUDA extensions built with torch.utils.cpp_extension
keep working as-is: the setuptools-based helpers are not removed, and torch
keeps its runtime setuptools dependency. The one case that needs the
command replacements above is CI that builds PyTorch itself from source.
If you redistribute PyTorch
For conda-forge, Spack, distro, and other from-source packagers:
- Build via the standard PEP 517 interfaces:
python -m build --wheel --no-isolationorpip install. Anything that invokes setup.py
directly needs porting. - Build requirements now include
scikit-build-core>=1.0(see
[build-system]in pyproject.toml / requirements-build.txt). - Configuration via environment variables is unchanged, and CMake options
can also be passed explicitly through the standard config-settings
mechanism, right on the build command:
pip install . -Ccmake.define.FOO=BAR(long form
--config-settings=cmake.define.FOO=BAR) or
python -m build -Ccmake.define.FOO=BAR. See the
scikit-build-core configuration docs
for the full set of options.
Additional benefits
- Build products no longer land in the package directory: with setuptools,
any from-source build (regular or editable) copied compiled libraries
intotorch/itself; now the source tree stays clean apart from the
familiarbuild/directory. - The switch retires over 2,000 lines of bespoke build orchestration in
favor of declarative configuration in pyproject.toml. - Wheel metadata (version, dependencies, license files) now flows through
standard hooks instead of hand-rolled code; PEP 639 license inclusion,
for example, comes for free. - The migration lands on scikit-build-core 1.0, which gained several
capabilities specifically to support PyTorch’s needs: for example, the
[tool.scikit-build.env]table, added upstream so that PyTorch (and
projects like it) need no custom backend wrapper at all. Our thanks to
@henryiii and the scikit-build team. The idea has been in the air
for a while, too:
scikit-build came up on this forum back in 2021,
before scikit-build-core was even written.
Rollout and feedback
The change lands as two stacked PRs, #180247 (backend switch) and
#180248 (setup.py removal), tracked in RFC #157807, where the full
design discussion lives. CI is validated across all platforms and wheel
families, including content parity of the produced wheels against the
setuptools build.
One question we would like your input on: where should the build-variable
reference (USE_CUDA, MAX_JOBS, and friends) live long-term? Candidates are
CONTRIBUTING.md, the README, the CMake file that implements the forwarding
(cmake/EnvVarForwarding.cmake), pyproject.toml, or the rendered docs site;
tell us which you would find most discoverable.
If you maintain tooling that scripts setup.py, redistribute PyTorch, or
have an unusual build integration, now is the moment to check it;
comments welcome here or on the tracking issue #189758.