- Published on
Python 3.15의 abi3t — free-threaded 빌드용 stable ABI가 푸는 문제와 그 대가
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 들어가며 — GIL을 뗀 다음에 남은 문제
- 왜 하나의 .so가 두 빌드를 못 섬기나
- 휠 매트릭스 — PEP 803이 실제로 인용한 숫자들
- abi3t가 하는 일
- 대가 — abi3t는 abi3보다 좁다
- 가장 중요한 오해 — 로드되는 것과 스레드 안전한 것은 다르다
- 3.14는 못 탄다 — 그리고 언제 안 쓰나
- 그래서 Phase III는 언제인가
- 마치며
- 참고 자료
들어가며 — GIL을 뗀 다음에 남은 문제
free-threaded 파이썬의 큰 이야기는 이미 정리된 것처럼 보입니다. PEP 779가 2025년 6월 16일에 확정되면서 free-threaded 빌드는 Python 3.14부터 실험 단계를 벗어났습니다. PEP의 용어로 이게 Phase II입니다 — 공식 지원되지만 여전히 선택적인 빌드. 그리고 Phase III는 free-threaded를 기본 빌드로 만드는 단계입니다.
그래서 자연스러운 질문은 "언제 기본이 되나"입니다. 그리고 그 답을 막고 있는 게 인터프리터 성능만은 아닙니다.
PEP 779의 Phase II 기준을 보면 성능 항목이 있습니다 — 단일 스레드 오버헤드에 대해 "15% as a hard performance target"을 걸었고, PEP 작성 시점 기준으로 실제 수치는 10% 언저리였습니다. 메모리는 pyperformance 기하평균 기준 20%가 목표였습니다. 여기까지는 인터프리터 내부 문제입니다.
그런데 같은 PEP의 Open Issues 항목에 stable ABI가 올라가 있습니다. 확정된 요구사항이 아니라 열린 질문으로요. 그리고 이 열린 질문이, 성능 숫자보다 먼저 생태계를 붙잡고 있던 실질적 병목이었습니다. 이 글은 그 구멍이 PEP 803으로 어떻게 메워지는지, 그리고 그 대가가 무엇인지에 관한 이야기입니다.
왜 하나의 .so가 두 빌드를 못 섬기나
먼저 근본 원인부터. free-threaded 빌드는 단순히 GIL을 빼는 게 아니라 객체의 모양 자체를 바꿉니다. PEP 793의 표현이 정확합니다 — 일반 빌드와 free-threading 빌드는 파이썬 객체의 메모리 레이아웃이 다릅니다. 참조 카운팅 방식이 바뀌면서 객체 헤더에 필드가 붙었기 때문입니다.
레이아웃이 다르면 무엇이 깨지느냐. 컴파일 시점에 정적으로 할당된 파이썬 객체가 깨집니다. C 확장이 소스 코드에 박아 두는 구조체들이 여기 해당합니다. PEP 793은 모듈 정의가 그중 가장 흔한 걸림돌이라고 짚습니다 — 하나의 컴파일된 라이브러리 파일을 일반 빌드와 free-threaded 빌드 양쪽에서 쓰지 못하게 만드는 가장 흔한 장애물이 바로 PyModuleDef라는 겁니다. PyModuleDef는 내부에 PyModuleDef_Base를 갖고, 그건 다시 객체 헤더를 갖습니다. 헤더 크기가 빌드마다 다르면, 그 구조체를 정적으로 박은 .so는 한쪽 빌드에서만 유효합니다.
그래서 ABI가 갈라졌습니다. cp314와 cp314t는 서로 다른 ABI이고, 확장은 각각 따로 빌드해야 합니다.
휠 매트릭스 — PEP 803이 실제로 인용한 숫자들
여기서 stable ABI(abi3)의 존재 이유를 떠올려야 합니다. abi3는 "파이썬 마이너 버전마다 다시 빌드하지 않아도 되게" 해 주는 장치입니다. 버전 축을 접어 버리는 거죠. 한 번 빌드하면 3.x 이상에서 계속 로드됩니다.
문제는 free-threaded 빌드에서 이게 아예 안 됐다는 겁니다. PEP 803의 문장 그대로 — stable ABI는 free-threaded 빌드에서 사용할 수 없고, Py_LIMITED_API와 Py_GIL_DISABLED 매크로가 둘 다 정의되면 확장은 빌드에 실패합니다.
즉 free-threading으로 가는 순간, 접어 두었던 버전 축이 다시 펴집니다. 그러면 휠 개수가 어떻게 되느냐 — PEP 803의 Motivation은 추상적인 우려 대신 실제 메인테이너들의 숫자를 인용합니다.
- cryptography — 최근 릴리스에서 휠 파일 48개를 배포했습니다. 메인테이너 Alex Gaynor의 말: "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 — 마지막 릴리스에서 CPython 4개 버전을 지원하려고 버전별 휠 60개를 업로드했습니다. (SciPy 스티어링 카운슬 의장 Ralf Gommers)
- pydantic-core — 최신 릴리스에서 휠 112개를 배포했고, 이 숫자는 더 늘어날 예정입니다. 메인테이너 David Hewitt의 경고가 핵심입니다: "if free-threading does not adopt a stable ABI, all the benefits above will be lost when free-threading becomes the default."
이 마지막 문장이 Phase III와 직결됩니다. free-threading이 기본이 되는 순간, stable ABI가 없으면 그동안 abi3로 얻은 이득이 통째로 날아갑니다. 그래서 스티어링 카운슬은 free-threading용 stable ABI가 Python 3.15에 맞춰 준비되고 정의되어야 한다는 입장을 냈습니다. PEP 803은 그 요구에 대한 답입니다.
abi3t가 하는 일
PEP 803 — "abi3t": Stable ABI for Free-Threaded Builds. Petr Viktorin과 Nathan Goldbaum이 저자이고, 2026년 3월 30일에 Accepted됐으며, Python 3.15를 겨냥합니다.
핵심은 abi3를 고치는 게 아니라 변종을 하나 더 만드는 것입니다. 확장은 Py_LIMITED_API 대신(정확히는 그와 별개로) Py_TARGET_ABI3T를 정의해 컴파일합니다.
/* Python.h 를 include 하기 전에 */
#define Py_TARGET_ABI3T 0x30f0000 /* 3.15 */
/* 또는 컴파일러 플래그로 */
/* -DPy_TARGET_ABI3T=0x30f0000 */
그리고 여기가 실무적으로 가장 중요한 부분입니다. abi3t로 컴파일한 확장은 free-threaded 빌드 전용이 아닙니다. PEP의 Abstract 문장 그대로 — 실무적으로 abi3t 3.15는 abi3 3.15와 호환됩니다. PEP는 확장 저자가 두 ABI를 한 번에 명시적으로 컴파일하고, 휠 태그 abi3.abi3t로 그 호환성을 알리기를 권합니다.
정리하면 이렇게 됩니다.
지금 (3.14까지)
파이썬 버전 × 스레딩 모드 × 플랫폼 → 휠 폭발
cp313-*, cp313t-*, cp314-*, cp314t-*, ... 플랫폼마다 반복
abi3t 이후 (3.15+)
하나의 바이너리가 3.15 이상의 GIL 빌드와 free-threaded 빌드를 모두 커버
myproject-1.0-cp315-abi3.abi3t-macosx_11_0_arm64.whl
→ 버전 축과 스레딩 모드 축이 동시에 접힘. 남는 건 플랫폼 축뿐.
파일명 태그도 바뀝니다 — 유닉스 계열에서는 .abi3.so 대신 .abi3t.so를 씁니다.
런타임 안전장치도 같이 들어옵니다. free-threaded 빌드에서 PyModuleDef_Init()은 free-threading용이 아닌 stable ABI를 쓰는 확장을 감지해서, 로드될 때 안내 메시지를 내보내고 예외를 던집니다. 조용히 깨지는 대신 시끄럽게 거부하는 쪽을 택한 겁니다.
대가 — abi3t는 abi3보다 좁다
여기서부터가 정직해질 부분입니다. abi3t는 공짜 업그레이드가 아니라 더 엄격한 ABI입니다. 레이아웃 차이를 흡수하는 유일한 방법이 "레이아웃을 아예 안 보여주는 것"이기 때문입니다.
구조체가 불투명해집니다. Py_TARGET_ABI3T로 컴파일하면 PyObject, PyVarObject, PyModuleDef_Base, PyModuleDef가 불완전 타입이 됩니다. 필드에 직접 접근할 수 없고, 크기와 정렬도 알 수 없고, 다른 구조체에 끼워 넣을 수 없습니다.
따라서 다음 매크로들이 사라집니다 — PyObject_HEAD, _PyObject_EXTRA_INIT, PyObject_HEAD_INIT, PyObject_VAR_HEAD, Py_SET_TYPE().
C 확장을 써 본 사람이라면 이 목록에서 PyObject_HEAD를 보고 멈칫할 겁니다. 커스텀 타입을 정의하는 표준 관용구가 통째로 무효가 되니까요.
/* 기존 방식 — abi3t 에서는 불가능 */
typedef struct {
PyObject_HEAD /* 크기를 알 수 없으므로 금지 */
int my_data;
} CustomObject;
/* abi3t 방식 — 베이스를 빼고 "추가 필드"만 정의 */
typedef struct {
int my_data;
} CustomObjectData;
static PyType_Spec CustomType_spec = {
.basicsize = -sizeof(CustomObjectData), /* 음수 basicsize (PEP 697) */
/* ... */
};
데이터에 접근하려면 PyObject_GetTypeData()를 써야 하는데, 여기에 함정이 하나 있습니다 — 두 번째 인자는 정의한 클래스여야 하고 Py_TYPE(obj)가 아닙니다. 서브클래싱이 걸리면 둘이 달라지니까요. 그래서 인스턴스 메서드에서는 METH_METHOD 플래그와 PyCMethod 시그니처로 defining_class를 받아 오거나, 타입 토큰을 써야 합니다.
모듈 진입점도 갈아타야 합니다. PyInit_* 훅 대신 PEP 793의 PyModExport_* 훅을 써야 합니다. PyModuleDef를 정적으로 할당할 수 없으니 당연한 귀결입니다. PyModule_GetDef()와 PyType_GetModuleByDef()도 모듈 토큰 기반의 PyModule_GetToken() / PyType_GetModuleByToken()으로 바꿔야 합니다.
그리고 아예 포팅이 안 되는 것도 있습니다. 가변 크기 타입 — Py_tp_itemsize나 tp_itemsize를 쓰는 타입 — 은 abi3t 3.15로 포팅할 수 없습니다. 우회로가 아니라 그냥 안 됩니다.
전제 조건도 만만치 않습니다. abi3t를 목표로 하려면 이미 abi3를 지원하고 있어야 하고, 다단계 초기화(PyModuleDef_Slot)를 쓰고 있어야 하고, 모듈이 격리되어 있거나 프로세스당 한 번만 로드되어야 합니다.
빌드 타임 조건부도 정리해야 합니다. PY_VERSION_HEX, PY_MAJOR_VERSION, PY_MINOR_VERSION은 빌드 시점 버전을 반영하므로 런타임 값인 Py_Version으로 바꿔야 하고, Py_GIL_DISABLED는 abi3t에서 항상 정의되므로 분기 조건으로 쓸모가 없습니다.
가장 중요한 오해 — 로드되는 것과 스레드 안전한 것은 다르다
이 글에서 딱 한 줄만 가져간다면 이것이어야 합니다. PEP 803 본문의 문장입니다.
"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는 배포 문제를 푸는 것이지 동시성 문제를 푸는 게 아닙니다. 당신의 확장이 abi3t로 깔끔하게 빌드되고 free-threaded 인터프리터에 잘 로드된다고 해서, 전역 상태에 락 없이 접근하던 코드가 갑자기 안전해지지 않습니다. 실제 스레드 안전 API는 PEP가 명시적으로 후속 작업으로 미뤄 둔 영역입니다.
이 구분은 런타임 동작에도 드러납니다. free-threaded 빌드는 GIL을 다시 켤 수 있습니다 — 환경변수 PYTHON_GIL이나 -X gil 옵션으로요. 그리고 공식 문서에 따르면, free-threading을 지원한다고 명시적으로 표시되지 않은 C 확장을 임포트하면 GIL이 자동으로 다시 켜질 수 있고, 이때 경고가 출력됩니다.
즉 "로드된다"와 "GIL 없이 돈다"와 "GIL 없이 빨라진다"는 서로 다른 세 개의 관문입니다. abi3t는 첫 번째 관문을 대량 생산 가능하게 만들 뿐입니다.
3.14는 못 탄다 — 그리고 언제 안 쓰나
abi3t에는 백포트가 없습니다. PEP의 표현으로, abi3t를 겨냥한 확장은 이전 CPython 릴리스와 소스 수준(API)으로도 컴파일된 형태(ABI)로도 하위 호환되지 않습니다. PyModuleDef를 피하고 새 PyModExport 훅을 써야 하기 때문입니다. PEP는 free-threaded 3.14 이하를 겨냥하는 방법(cp314-abi3t)을 제공하지 않는다고 명시하고, 그 문제는 외부 도구로 미룹니다.
실무적으로 이 말은 이런 뜻입니다. 3.10~3.14를 계속 지원해야 한다면 그 구간의 기존 휠 매트릭스는 그대로 남습니다. abi3t가 주는 구제는 3.15부터 시작해서 해가 갈수록 누적되는 종류이지, 내년에 CI 시간이 반토막 나는 종류가 아닙니다.
그리고 모두가 갈아탈 필요는 없습니다. 3.15 What's New가 이걸 분명히 합니다 — stable ABI는 CPython의 모든 기능을 제공하지는 않으며, abi3t로 전환할 수 없는 확장은 기존 stable ABI(abi3)와 free-threading용 버전별 ABI(cp315t)를 따로 빌드하는 방식을 계속 쓰면 됩니다.
정리하면 이렇게 나뉩니다.
abi3t가 값을 하는 경우
- 배포하는 휠이 이미 수십 개대이고, 파이썬 버전이 늘 때마다 선형으로 증가한다.
- 이미
abi3를 지원하고 있고 다단계 초기화를 쓰고 있다 — 전제 조건을 이미 절반 이상 만족한다. - free-threaded 사용자를 실제로 지원할 계획이 있다.
과잉이거나 불가능한 경우
- 순수 파이썬 패키지다. free-threading 가이드의 표현대로 순수 파이썬 코드는 설계상 수정 없이 동작합니다 — 할 일이 없습니다.
- 가변 크기 타입을 쓰거나, stable ABI 밖의 API가 필요하다 — 애초에 대상이 아닙니다.
- 확장이 사내 전용이고 파이썬 버전을 하나만 지원한다 — 접을 축이 없으니 마이그레이션 비용만 남습니다.
그래서 Phase III는 언제인가
숫자부터 정직하게. 단일 스레드 오버헤드는 공식 문서 기준으로 pyperformance 벤치마크 스위트에서 평균 macOS aarch64에서 약 1%, x86-64 리눅스에서 약 8% 수준입니다. 워크로드와 하드웨어에 따라 달라진다는 단서가 문서에 함께 붙어 있습니다.
궤적을 보면 이렇습니다. PyCon US 2026(5월 중순, 롱비치)에서 CPython 코어 개발자이자 스티어링 카운슬 멤버인 Thomas Wouters가 한 발표를 LWN이 정리했습니다. 3.13에서는 단일 스레드 워크로드가 2040% 느려졌고, 3.14에서는 010%로 줄었습니다. Wouters 본인도 ARM에서 0% 슬로다운에 도달한 것에는 "personally shocked"였다고 말했고, 리눅스의 GCC에서는 보통 5% 정도이며 10%까지 갈 수 있다고 했습니다.
생태계 쪽은 LWN 기준으로 PyPI 상위 360개 바이너리 휠 중 50% 이상이 free-threaded를 지원합니다. 주로 Meta와 Quansight의 작업 덕분입니다. (이 "상위 360개"라는 모수는 hugovk의 트래커가 추적하는 "확장을 가진 다운로드 상위 360개 패키지"와 같은 기준입니다.)
그럼에도 Phase III에는 PEP도 없고 일정도 없습니다. PEP 779가 이 점을 명확히 해 뒀습니다 — Phase II 진입 요건과 Phase III 결정은 완전히 다른 문제이고, 후자는 커뮤니티의 지지를 중심으로 돌아갈 것이라고요. Wouters의 예측도 반드시 그의 단서와 함께 인용해야 공정합니다. 그는 free-threaded 버전이 언젠가 기본 파이썬이 될 거라고 믿는다면서도, "I'm not saying this with any authority whatsoever"라고 못을 박았습니다. 그의 개인적 짐작은 3.16(2027년 10월) 이후, 3.20(2031년 10월) 이전입니다. 즉 가장 낙관적으로 봐도 최소 1년 이상 남았고, 범위가 4년입니다.
남은 숙제도 분명합니다. Wouters는 인터프리터와 서드파티 패키지 양쪽에서 성능·확장성 개선이 더 필요하다고 했고, 스레드 간 객체 공유는 여전히 성능상 문제라고 짚었습니다. 이건 abi3t가 건드리는 영역이 전혀 아닙니다.
일정 감각을 위해 덧붙이면, PEP 790 기준으로 Python 3.15는 2026년 5월 7일 beta 1에서 기능 동결됐고, rc1은 2026년 8월 4일, rc2는 9월 1일, 최종 릴리스는 2026년 10월 1일입니다. 이 글을 쓰는 지금(7월 중순)은 베타 구간이고, 공식 문서도 3.15.0b3 기준으로 빌드되어 있습니다. 즉 abi3t는 확정된 설계이지만 아직 출시된 물건은 아닙니다.
마치며
free-threading 이야기는 보통 "GIL이 사라진다"에서 시작해서 벤치마크 그래프로 끝납니다. 하지만 3.14에서 Phase II를 통과한 뒤 실제로 진척이 필요했던 곳은 인터프리터가 아니라 배포 파이프라인이었습니다. abi3가 접어 두었던 버전 축이 free-threading 앞에서 다시 펴지는 문제 — cryptography의 48개, SciPy의 60개, pydantic-core의 112개짜리 휠 매트릭스가 두 배로 가는 문제 말입니다.
PEP 803의 abi3t는 그 축을 다시 접습니다. 하나의 바이너리가 3.15 이상의 GIL 빌드와 free-threaded 빌드를 모두 섬기고, 태그는 abi3.abi3t입니다. 대가는 더 좁은 ABI입니다 — 불투명해진 PyObject, 사라진 PyObject_HEAD, 음수 basicsize, PyModExport 훅, 포팅 불가능한 가변 크기 타입.
그리고 이게 푸는 문제의 경계를 정확히 알아야 합니다. abi3t는 당신의 확장을 free-threaded 파이썬에 로드되게 만들 뿐, 스레드 안전하게 만들지 않습니다. 그건 여전히 당신 몫이고, PEP가 스스로 그렇게 적어 뒀습니다.
Phase III를 기다리는 입장이라면 이렇게 정리하면 됩니다 — 성능은 이미 목표 근처(아키텍처에 따라 1~8%)까지 왔고, 생태계는 상위 360개 중 절반을 넘겼고, 이제 ABI 구멍도 3.15에서 메워집니다. 그런데도 기본 빌드 전환에는 PEP조차 없습니다. 남은 건 기술이 아니라 합의라는 뜻입니다. 그리고 합의는 벤치마크보다 느리게 움직입니다.
참고 자료
- 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 문서
- Python support for free threading — 공식 HOWTO
- What's New In Python 3.15
- Free-threaded Python: past, present, and future — LWN (Thomas Wouters의 PyCon US 2026 발표 정리)
- Python Free-Threading Guide — 패키지 호환성 추적
- free-threaded wheels 트래커 (hugovk)