필사 모드: Python 3.15's abi3t — The Problem a Stable ABI Solves for Free-Threaded Builds, and What It Costs
English- Introduction — What's Left After the GIL Comes Off
- Why One .so Can't Serve Both Builds
- The Wheel Matrix — The Numbers PEP 803 Actually Cites
- What abi3t Does
- The Cost — abi3t Is Narrower Than abi3
- The Most Important Misconception — Loadable Isn't the Same as Thread-Safe
- No Ride for 3.14 — And When Not to Bother
- So When Is Phase III
- Closing
- References
Introduction — What's Left After the GIL Comes Off
The big-picture story of free-threaded Python looks settled already. With PEP 779 finalized on June 16, 2025, free-threaded builds left the experimental stage starting with Python 3.14. In the PEP's own terms, this is Phase II — officially supported, but still an opt-in build. And Phase III is the phase where free-threading becomes the default build.
So the natural question is "when does it become the default." And interpreter performance alone isn't what's blocking that answer.
Looking at PEP 779's Phase II criteria, there's a performance item — it set "15% as a hard performance target" for single-threaded overhead, and at the time the PEP was written the actual number was around 10%. For memory, the target was 20% on a pyperformance geometric-mean basis. Everything up to here is an interpreter-internals problem.
But the same PEP's Open Issues section lists the stable ABI — not as a settled requirement, but as an open question. And this open question turned out to be the real bottleneck holding the ecosystem back, ahead of any performance number. This post is about how PEP 803 fills that gap, and what it costs.
Why One .so Can't Serve Both Builds
Root cause first. A free-threaded build doesn't just remove the GIL — it changes the shape of objects themselves. PEP 793 puts it precisely — the regular build and the free-threading build have different memory layouts for Python objects. That's because the change in reference-counting scheme added fields to the object header.
So what breaks when the layout differs? Python objects that are statically allocated at compile time break. That covers the structs C extensions embed directly in their source code. PEP 793 points to module definitions as the most common snag — the most common obstacle keeping one compiled library file from working on both the regular and the free-threaded build is PyModuleDef itself. PyModuleDef contains a PyModuleDef_Base inside it, which in turn contains an object header. If the header size differs between builds, a .so with that struct baked in statically is only valid for one of them.
So the ABI split. cp314 and cp314t are different ABIs, and extensions have to be built separately for each.
The Wheel Matrix — The Numbers PEP 803 Actually Cites
This is where you need to recall why the stable ABI (abi3) exists in the first place. abi3 is the mechanism that lets you avoid rebuilding for every Python minor version — it folds away the version axis. Build once, and it keeps loading on 3.x and above.
The problem is that this simply didn't work on free-threaded builds. In PEP 803's own words — the stable ABI cannot be used on free-threaded builds, and if both the Py_LIMITED_API and Py_GIL_DISABLED macros are defined, the extension fails to build.
In other words, the moment you go free-threading, the version axis you had folded away unfolds again. So what happens to the wheel count? PEP 803's Motivation section cites real maintainers' numbers instead of abstract worries.
- cryptography — shipped 48 wheel files in a recent release. Maintainer Alex Gaynor's words: "As long as we have O(1) builds, that's ok. What we can't/won't do is O(n) where we need new builds for every Python release."
- SciPy — uploaded 60 version-specific wheels in its last release to support 4 CPython versions. (Ralf Gommers, chair of the SciPy Steering Council)
- pydantic-core — shipped 112 wheels in its latest release, and that number is set to grow further. Maintainer David Hewitt's warning cuts to the point: "if free-threading does not adopt a stable ABI, all the benefits above will be lost when free-threading becomes the default."
That last sentence connects directly to Phase III. The moment free-threading becomes the default, if there's no stable ABI, all the gains earned from abi3 up to that point evaporate at once. So the Steering Council took the position that a stable ABI for free-threading needed to be ready and defined in time for Python 3.15. PEP 803 is the answer to that demand.
What abi3t Does
PEP 803 — "abi3t": Stable ABI for Free-Threaded Builds. Authored by Petr Viktorin and Nathan Goldbaum, it was Accepted on March 30, 2026, and targets Python 3.15.
The core move isn't fixing abi3 — it's adding one more variant. Extensions compile by defining Py_TARGET_ABI3T instead of (more precisely, in addition to) Py_LIMITED_API.
/* Before including Python.h */
#define Py_TARGET_ABI3T 0x30f0000 /* 3.15 */
/* Or as a compiler flag */
/* -DPy_TARGET_ABI3T=0x30f0000 */
And here's the practically most important part. An extension compiled with abi3t isn't exclusive to free-threaded builds. In the PEP's own Abstract wording — in practice, abi3t 3.15 is compatible with abi3 3.15. The PEP recommends that extension authors explicitly compile for both ABIs at once and signal that compatibility with the wheel tag abi3.abi3t.
To sum it up:
Today (through 3.14)
Python version × threading mode × platform → wheel explosion
cp313-*, cp313t-*, cp314-*, cp314t-*, ... repeated per platform
After abi3t (3.15+)
One binary covers both the GIL build and the free-threaded build for 3.15 and above
myproject-1.0-cp315-abi3.abi3t-macosx_11_0_arm64.whl
→ The version axis and the threading-mode axis fold at the same time. Only the platform axis is left.
The filename tag changes too — on Unix-like systems, .abi3t.so is used instead of .abi3.so.
A runtime safeguard ships alongside this. On a free-threaded build, PyModuleDef_Init() detects an extension using the non-free-threading stable ABI and, when it's loaded, emits a guidance message and raises an exception. It chose to fail loudly rather than break silently.
The Cost — abi3t Is Narrower Than abi3
This is where things get honest. abi3t isn't a free upgrade — it's a stricter ABI. That's because the only way to absorb the layout difference is to "not expose the layout at all."
Structs become opaque. Compile with Py_TARGET_ABI3T and PyObject, PyVarObject, PyModuleDef_Base, and PyModuleDef become incomplete types. You can't access fields directly, you can't know their size or alignment, and you can't embed them inside another struct.
As a result, the following macros disappear — PyObject_HEAD, _PyObject_EXTRA_INIT, PyObject_HEAD_INIT, PyObject_VAR_HEAD, Py_SET_TYPE().
Anyone who's written a C extension will do a double-take seeing PyObject_HEAD on that list — the standard idiom for defining a custom type is invalidated wholesale.
/* The old way — not possible under abi3t */
typedef struct {
PyObject_HEAD /* Forbidden — size is unknown */
int my_data;
} CustomObject;
/* The abi3t way — drop the base, define only the "extra fields" */
typedef struct {
int my_data;
} CustomObjectData;
static PyType_Spec CustomType_spec = {
.basicsize = -sizeof(CustomObjectData), /* Negative basicsize (PEP 697) */
/* ... */
};
To access the data you have to use PyObject_GetTypeData(), and there's a trap here — the second argument must be the defining class, not Py_TYPE(obj). Once subclassing gets involved, the two diverge. So instance methods need to receive defining_class via the METH_METHOD flag and the PyCMethod signature, or use a type token.
The module entry point has to switch too. Instead of the PyInit_* hook, you need PEP 793's PyModExport_* hook — a natural consequence of not being able to statically allocate PyModuleDef. PyModule_GetDef() and PyType_GetModuleByDef() also need to be replaced with the token-based PyModule_GetToken() / PyType_GetModuleByToken().
And some things simply can't be ported at all. Variable-size types — types using Py_tp_itemsize or tp_itemsize — cannot be ported to abi3t 3.15. There's no workaround; it just doesn't work.
The prerequisites aren't trivial either. To target abi3t you already need to support abi3, you need to be using multi-phase initialization (PyModuleDef_Slot), and your module needs to be either isolated or loaded only once per process.
Build-time conditionals need cleanup too. PY_VERSION_HEX, PY_MAJOR_VERSION, and PY_MINOR_VERSION reflect the build-time version, so they need to be replaced with the runtime value Py_Version, and Py_GIL_DISABLED is always defined under abi3t, so it's useless as a branch condition.
The Most Important Misconception — Loadable Isn't the Same as Thread-Safe
If you take away only one line from this post, it should be this one. It's a sentence from the PEP 803 text itself.
"Implementing this PEP will make it possible to build extensions that can be successfully loaded on free-threaded Python, but not necessarily ones that are thread-safe without a GIL."
abi3t solves a distribution problem, not a concurrency problem. Your extension building cleanly with abi3t and loading fine on a free-threaded interpreter doesn't suddenly make code that touched global state without a lock safe. Actual thread-safe APIs are an area the PEP explicitly defers to future work.
This distinction shows up in runtime behavior too. A free-threaded build can turn the GIL back on — via the PYTHON_GIL environment variable or the -X gil option. And according to the official documentation, importing a C extension that isn't explicitly marked as supporting free-threading can automatically re-enable the GIL, with a warning printed when that happens.
In other words, "it loads," "it runs without the GIL," and "it's faster without the GIL" are three separate gates. abi3t only makes it possible to mass-produce passage through the first one.
No Ride for 3.14 — And When Not to Bother
There's no backport for abi3t. In the PEP's wording, an extension targeting abi3t is backward-compatible with earlier CPython releases neither at the source level (API) nor at the compiled level (ABI). That's because it has to avoid PyModuleDef and use the new PyModExport hook. The PEP explicitly states it does not provide a way to target free-threaded 3.14 or earlier (cp314-abi3t), and defers that problem to external tooling.
In practice, this means: if you still need to support 3.10 through 3.14, the existing wheel matrix for that range stays exactly as it is. The relief abi3t gives you is the kind that starts at 3.15 and accumulates year over year, not the kind that cuts your CI time in half next year.
And not everyone needs to switch. The 3.15 What's New doc makes this clear — the stable ABI doesn't expose all of CPython's functionality, and extensions that can't move to abi3t can keep building the existing stable ABI (abi3) and the version-specific free-threading ABI (cp315t) separately, as before.
To sum it up, here's the split.
Where abi3t pays off
- The wheels you ship are already in the dozens, and grow linearly with every new Python version.
- You already support
abi3and use multi-phase initialization — you already meet more than half the prerequisites. - You genuinely plan to support free-threaded users.
Where it's overkill or impossible
- It's a pure-Python package. As the free-threading guide puts it, pure Python code works without modification by design — there's nothing to do.
- It uses variable-size types, or needs APIs outside the stable ABI — it was never a candidate to begin with.
- The extension is internal-only and supports a single Python version — there's no axis to fold, so all you're left with is migration cost.
So When Is Phase III
Numbers first, honestly. Per the official documentation, single-threaded overhead on the pyperformance benchmark suite averages about 1% on macOS aarch64 and about 8% on x86-64 Linux. The docs attach the caveat that this varies by workload and hardware.
As for the trajectory — LWN summarized a talk by CPython core developer and Steering Council member Thomas Wouters at PyCon US 2026 (mid-May, Long Beach). In 3.13, single-threaded workloads were 20-40% slower; in 3.14, that dropped to 0-10%. Wouters himself said he was "personally shocked" at reaching 0% slowdown on ARM, and that on Linux with GCC it's usually around 5%, and can go as high as 10%.
On the ecosystem side, per LWN, more than 50% of the top 360 PyPI binary wheels support free-threading, largely thanks to work by Meta and Quansight. (This "top 360" population matches the same criterion hugovk's tracker uses for "the top 360 most-downloaded packages with extensions.")
And yet Phase III has no PEP and no schedule. PEP 779 makes this explicit — the entry criteria for Phase II and the decision on Phase III are entirely different matters, and the latter will revolve around community support. Wouters's own prediction has to be quoted together with his caveat to be fair. He said he believes a free-threaded version will become the default Python eventually, but nailed down that "I'm not saying this with any authority whatsoever." His personal guess is after 3.16 (October 2027) and before 3.20 (October 2031) — which, even taken at its most optimistic, is at least a year out, with a four-year range.
The remaining homework is clear too. Wouters said more performance and scalability improvements are still needed on both the interpreter and third-party package side, and noted that sharing objects across threads is still a performance problem. That's an area abi3t doesn't touch at all.
For a sense of timing — per PEP 790, Python 3.15 hit feature freeze at beta 1 on May 7, 2026; rc1 is August 4, 2026; rc2 is September 1; and the final release is October 1, 2026. As of this writing (mid-July), we're in the beta window, and the official docs are built against 3.15.0b3. In other words, abi3t is a finalized design, but not yet a shipped thing.
Closing
The free-threading story usually starts with "the GIL goes away" and ends with a benchmark graph. But after passing Phase II in 3.14, the place that actually needed progress wasn't the interpreter — it was the distribution pipeline. The problem of the version axis that abi3 had folded away unfolding again in the face of free-threading — cryptography's 48, SciPy's 60, pydantic-core's 112-wheel matrices doubling in size.
PEP 803's abi3t folds that axis back down. One binary serves both the GIL build and the free-threaded build for 3.15 and above, tagged abi3.abi3t. The cost is a narrower ABI — an opaque PyObject, a missing PyObject_HEAD, negative basicsize, the PyModExport hook, and variable-size types that can't be ported at all.
And you need to know exactly where the boundary of what this solves lies. abi3t only makes your extension loadable on free-threaded Python — it does not make it thread-safe. That's still your job, and the PEP says so itself.
If you're waiting on Phase III, here's the summary — performance is already near the target (1-8% depending on architecture), the ecosystem has cleared half of the top 360, and now the ABI hole is being filled at 3.15 too. And yet the switch to a default build doesn't even have a PEP. What's left isn't technology — it's consensus. And consensus moves slower than benchmarks.
References
- PEP 803 – "abi3t": Stable ABI for Free-Threaded Builds (Petr Viktorin, Nathan Goldbaum)
- PEP 793 – PyModExport: A new entry point for C extension modules
- PEP 779 – Criteria for supported status for free-threaded Python
- PEP 790 – Python 3.15 Release Schedule
- Migrating to Stable ABI for free threading (abi3t) — Python 3.15 docs
- Python support for free threading — official HOWTO
- What's New In Python 3.15
- Free-threaded Python: past, present, and future — LWN (a write-up of Thomas Wouters's PyCon US 2026 talk)
- Python Free-Threading Guide — package compatibility tracking
- free-threaded wheels tracker (hugovk)
현재 단락 (1/88)
The big-picture story of free-threaded Python looks settled already. With [PEP 779](https://peps.pyt...