Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 817 – 휠 변형: 플랫폼 태그를 넘어서

Author:
Jonathan Dekhtiar <jonathan at dekhtiar.com>, Michał Górny <mgorny at quansight.com>, Konstantin Schütze <konstin at mailbox.org>, Ralf Gommers <ralf.gommers at gmail.com>, Andrey Talman <atalman at meta.com>, Charlie Marsh <charlie at astral.sh>, Michael Sarahan <msarahan at gmail.com>, Eli Uriegas <eliuriegas at meta.com>, Barry Warsaw <barry at python.org>, Donald Stufft <donald at stufft.io>, Andy R. Terrel <andy.terrel at gmail.com>
Discussions-To:
Discourse thread
Status:
Draft
Type:
Standards Track
Topic:
Packaging
Created:
10-Dec-2025
Post-History:
24-Jan-2026

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

초록

Python의 기존 휠 패키징 형식은 Platform compatibility tags를 사용하여 특정 휠이 지원하는 환경을 지정합니다. 이러한 태그는 GPU 가속의 사용 가능 여부와 같은 최신 하드웨어 구성 및 그 기능을 표현할 수 없습니다. 또한 서로 다른 종속성 ABI에 맞춰 빌드하는 경우와 같은 사용자 지정 패키지 변형을 제공하지 못합니다. 이러한 한계는 과학 컴퓨팅, 인공지능(AI), 머신 러닝(ML), 고성능 컴퓨팅(HPC) 커뮤니티에서 특히 큰 문제입니다.

이 PEP는 Binary distribution format의 확장인 “휠 변형”을 제안합니다. 이 확장은 패키지 유지 관리자가 동일한 패키지 버전에 대해 여러 빌드 변형을 선언할 수 있게 하면서, 설치 프로그램이 시스템 하드웨어 및 소프트웨어 특성에 따라 가장 적합한 변형을 자동으로 선택할 수 있도록 하는 메커니즘을 도입합니다. 더 구체적으로는 다음을 제안합니다.

  • 하드웨어 또는 소프트웨어 속성에 따라 휠을 구분할 수 있도록 하는 Wheel Variant라는 휠 형식의 발전입니다.
  • 설치 프로그램이 플랫폼 속성을 동적으로 감지하고 가장 적합한 휠을 선택할 수 있도록 하는 variant provider plugin 인터페이스입니다.

목표는 일반적인 설치 명령({tool} install <package>를)을 통해 가장 적합한 휠을 선택하고 최상의 사용자 경험을 제공하는 것입니다.

동기

2024 Python Developers Survey 는 Python 사용자 중 상당수가 과학 컴퓨팅 사용 사례를 보유하고 있음을 보여 줍니다. 여기에는 데이터 분석(응답자의 40%), 머신 러닝(30%), 데이터 엔지니어링(30%)이 포함됩니다. 이러한 분야를 위해 개발된 많은 소프트웨어 패키지는 현재 휠 형식으로는 적절하게 표현할 수 없는 다양한 하드웨어 기능에 의존하며, 이는 The limitations of platform compatibility tags 에서 강조한 바와 같습니다.

예를 들어 PyTorch와 같은 패키지는 특정 CUDA 또는 ROCm 버전에 맞춰 빌드해야 하지만, 현재는 해당 정보를 휠 태그에 포함할 수 없습니다. 매우 다른 하드웨어 구성을 대상으로 여러 휠을 빌드해야 하면 유지 관리자는 최적이 아닌 다양한 배포 전략을 사용하게 되며, 해당 패키지에 의존하려는 사용자와 다른 소프트웨어 작성자에게 마찰을 일으킵니다.

기존의 몇 가지 접근 방식은 Current workarounds and their drawbacks 에서 살펴봅니다. 여기에는 서로 다른 하드웨어 구성에 대해 별도의 패키지 색인을 유지 관리하는 방법, 가능한 모든 변형을 상당한 크기의 단일 휠에 묶는 방법, 별도의 패키지 이름(mypackage-gpu, mypackage-cpu, 등)을 사용하는 방법이 포함됩니다. 이러한 접근 방식은 각각 상당한 단점과 잠재적인 보안 영향을 지닙니다.

플랫폼 호환성 태그의 한계

현재의 휠 형식은 세 가지 플랫폼 태그를 통해 호환성을 인코딩합니다.

  1. Python tag: 최소 Python 버전을 인코딩하고 선택적으로 Python 배포판을 제한합니다(예: 모든 Python 3에 해당하는 py3, Python 3.13 이상에 해당하는 py313, CPython 3.13 이상에만 해당하는 cp313).
  2. ABI tag: 확장 모듈에 필요한 Python ABI를 인코딩합니다(예: 요구 사항이 없음을 나타내는 none, CPython 안정 ABI를 나타내는 abi3, CPython 3.13 ABI가 필요한 확장을 나타내는 cp313).
  3. Platform tag: 현재 운영 체제, 아키텍처 및 핵심 시스템 라이브러리를 인코딩합니다(예: 모든 플랫폼에 해당하는 any, glibc 2.34 이상을 사용하는 x86-64 Linux 시스템에 해당하는 manylinux_2_34_x86_64, macOS 14.0 이상을 사용하는 arm64 시스템에 해당하는 macosx_14_0_arm64).

이러한 태그는 Python 인터프리터, 운영 체제 및 광범위한 CPU 아키텍처의 가장 기본적인 속성만 표현할 수 있습니다. 비CPU 하드웨어 요구 사항이나 라이브러리 ABI 제약과 같이 더 상세한 내용은 표현할 수 없습니다.

이러한 유연성 부족으로 인해 많은 프로젝트는 PyTorch 팀이 제공하는 수동 설치 명령 선택기와 같이 최적이 아니지만 필요한 우회책을 찾게 되었습니다. 이러한 복잡성은 현재 태그 시스템의 근본적인 확장성 문제를 나타내며, 현재 시스템은 빌드 옵션의 조합적 복잡성을 처리할 만큼 확장 가능하지 않습니다.

현재의 우회책과 단점

런타임 CPU 디스패칭

NumPy와 같은 프로젝트는 현재 기준 CPU 대상을 위한 휠을 빌드하고 성능이 중요한 루틴에 런타임 디스패칭을 사용하는 방식을 택하고 있습니다. 이러한 해결책은 패키지 유지 관리자의 추가 노력을 필요로 하며, 일반적으로 일부 선택된 함수 외부의 코드가 컴파일러 최적화의 혜택을 받지 못하게 합니다.

비교를 위해 더 높은 CPU 기준을 대상으로 GROMACS_를 빌드하면 상당한 속도 향상이 제공되는 것으로 확인되었습니다.

A bar graph comparing GROMACS performance (in ns/day) with various targets. The first two bars are labeled "yum (2018.8)" and "generic (SSE2)", reach about 1.0 ns/day and are both marked as "SSE2". The next bar is labeled "ivybridge" ("AVX") and reaches almost 1.5 ns/day. Two following bars are labeled "haswell" and "broadwell" (both "AVX2") and exceed 1.5 ns/day slightly. The last two bars are labeled "skylake_avx512" and "cascadelake" (both "AVX512") and reach almost 2.0 ns/day.

Performance of GROMACS 2020.1 built for different generations of CPUs. Vertical axis shows performance expressed in ns/day, a GROMACS-specific measure of simulation speed (higher is better).

Intel Cascade Lake 마이크로아키텍처가 지원하는 AVX-512 명령어를 활용할 수 있는 아키텍처용으로 GROMACS를 컴파일하면 AVX2 명령어를 사용하는 경우보다 성능이 18% 추가로 향상되며, SSE2만 사용하는 일반적인 GROMACS 설치와 비교하면 약 70%의 속도 향상이 있습니다.

archspec: 마이크로아키텍처를 감지하고, 분류하고, 추론하기 위한 라이브러리

변형으로서의 별도 패키지 색인

PyTorchRAPIDS와 같은 프로젝트는 현재 사용자 지정 URL을 사용하는 별도 패키지 색인을 통해 “변형”을 근사하는 패키지를 배포하고 있습니다. 여기서는 PyTorch를 예로 사용하지만, 문제와 해결 방법, 사용자에게 미치는 영향은 다른 패키지에도 적용됩니다.

A grid-based selector for PyTorch versions. Individual rows provide the choice of PyTorch Build (stable or nightly), operating system (Linux, Mac, Windows), package (Pip, LibTorch, Source), language (Python, C++ / Java), and Compute Platform (CUDA 12.6, CUDA 12.8, CUDA 13.0, ROCM 6.4, CPU). Below these rows, the pip install command for the selected variant is provided, utilizing the --index-url parameter.

The PyTorch install selector (https://pytorch.org/get-started/locally/, captured 22-Aug-2025)

PyTorch는 가속기 유형별 색인 URL과 로컬 버전 세그먼트를 가속기 태그로 조합하여 사용합니다(예: +cu130, +rocm6.4 또는 +cpu). 사용자는 먼저 자신의 시스템에 맞는 올바른 색인 URL을 확인한 다음 PyTorch 전용 색인을 추가해야 합니다.

pip install torch --index-url https://download.pytorch.org/whl/cu129

도구는 PyTorch가 로컬 버전 세그먼트를 사용하는 방식에 대한 특별한 처리를 구현해야 합니다. 이러한 요구 사항은 일반적으로 패키지가 설치되는 방식을 깨뜨립니다. PyTorch 설치 문제는 사용자가 매우 흔하게 혼란을 겪는 지점입니다. 이를 수치로 나타내면, 2025-12-05 기준으로 uv의 이슈 추적기의 이슈 8136건 중 552건(6.8%)에 “torch”라는 용어가 포함되어 있습니다.

보안 위험: 이 접근 방식은 안타깝게도 공급망 공격으로 이어졌습니다. 자세한 내용은 PyTorch 블로그를 참조하십시오. 이는 해결하기 쉽지 않은 문제이며, PyTorch 팀은 모든 종속 항목의 완전한 미러를 만들어야 했습니다. 또한 이는 PEP 766의 핵심 동기 중 하나입니다.

구성의 복잡성으로 인해 프로젝트에서 원활한 패키지 업그레이드를 지원하지 않는 임시방편적인 설치 지침을 제공하는 경우가 많습니다.

변형으로서의 패키지 이름

XGBoost와 같은 패키지는 변형을 근사하기 위해 서로 다른 패키지 이름을 사용합니다.

pip install xgboost      # NVIDIA GPU variant
pip install xgboost-cpu  # CPU-only variant

다른 소프트웨어의 유지 관리자는 사용 가능한 변형 중 하나가 선택된다는 종속성을 표현할 수 없습니다. 특정 변형에 종속되거나, extras를 사용하여 여러 대체 종속성 집합을 제공하거나, 심지어 업스트림 변형에 맞는 여러 패키지 이름을 사용하여 자체 소프트웨어를 배포해야 합니다.

이러한 패키지는 대개 서로 겹치는 파일을 설치합니다. Python 패키징은 두 패키지가 상호 배타적임을 표현하는 기능을 지원하지 않으므로, 설치 도구는 두 패키지를 동일한 환경에 설치할 수 있으며, 나중에 설치된 패키지가 먼저 설치된 패키지의 파일을 덮어쓰게 됩니다. 이로 인해 런타임 오류가 발생하며, 패키지 업그레이드 순서에 따라 변형이 우연히 전환될 가능성도 생깁니다.

이 접근 방식의 추가적인 한계는 현재 여러 패키지 이름에 걸쳐 새 릴리스를 동시에 게시할 수 없다는 점입니다. PEP 694는 단일 패키지 내 여러 휠에 이러한 메커니즘을 추가할 것을 제안하지만, 이를 여러 패키지로 확장하는 것은 목표가 아닙니다.

보안 위험: 접미사가 붙은 변형 패키지가 확산되면 사용자는 다른 패키지에서도 이러한 접미사를 기대하게 되므로, 이름 선점이 훨씬 쉬워집니다. 예를 들어 사용자가 악성 numpy-cuda 패키지를 NumPy의 CUDA 변형이라고 믿게 만들 수 있습니다.

이 글을 작성하는 시점에 CuPy는 이미 서로 다른 이름의 cupy* 패키지 총 55개를 등록했으며, 그중 대부분은 실제로 사용된 적이 없고(Simple API를 사용해야만 표시됨), 나머지 패키지 중 상당수도 더 이상 업데이트되지 않습니다. 이는 문제의 규모와 이름 선점 위험에 대응하기 위해 투입된 노력을 분명히 보여 줍니다.

cupy
cupy-cuda70 cupy-cuda75 cupy-cuda80 cupy-cuda90 cupy-cuda91
cupy-cuda92 cupy-cuda100 cupy-cuda101 cupy-cuda102
cupy-cuda110 cupy-cuda111 cupy-cuda112 cupy-cuda113 cupy-cuda114
cupy-cuda115 cupy-cuda116 cupy-cuda117 cupy-cuda118 cupy-cuda119
cupy-cuda11x
cupy-cuda120 cupy-cuda121 cupy-cuda122 cupy-cuda123 cupy-cuda124
cupy-cuda125 cupy-cuda126 cupy-cuda127 cupy-cuda128 cupy-cuda129
cupy-cuda12x
cupy-cuda13x
cupy-rocm-4-0 cupy-rocm-4-1 cupy-rocm-4-2 cupy-rocm-4-3
cupy-rocm-4-4 cupy-rocm-4-5 cupy-rocm-5-0 cupy-rocm-5-1
cupy-rocm-5-2 cupy-rocm-5-3 cupy-rocm-5-4 cupy-rocm-5-5
cupy-rocm-5-6 cupy-rocm-5-7 cupy-rocm-5-8 cupy-rocm-5-9
cupy-rocm-6-0 cupy-rocm-6-1 cupy-rocm-6-2 cupy-rocm-6-3
cupy-rocm-7-0 cupy-rocm-7-1

변형으로서의 패키지 extras

JAX는 플러그인 기반 접근 방식을 사용합니다. 중앙 jax 패키지는 추가 플러그인을 설치하는 데 사용할 수 있는 여러 엑스트라를 제공하며, 예를 들어 jax[cuda12] 또는 jax[tpu]가 있습니다. 이는 결코 이상적이지 않습니다. pip install jax(추가 항목 없음)는 작동하지 않는 설치로 이어지며, 그 결과 Python 생태계에서 기본적으로 기대되는 동작인 의존성 연쇄가 제대로 작동하지 않습니다.

JAX에는 모든 사용 사례를 지원하기 위한 12개의 엑스트라가 포함되어 있으며, 이 중 상당수는 서로 겹치므로 사용자가 문서를 자세히 읽지 않으면 혼란을 일으킬 수 있습니다. 대부분은 기술적으로 상호 배타적이지만, 현재로서는 이를 패키지 메타데이터에 올바르게 표현할 수 없습니다.

Provides-Extra: minimum-jaxlib
Provides-Extra: cpu
Provides-Extra: ci
Provides-Extra: tpu
Provides-Extra: cuda
Provides-Extra: cuda12
Provides-Extra: cuda13
Provides-Extra: cuda12-local
Provides-Extra: cuda13-local
Provides-Extra: rocm
Provides-Extra: k8s
Provides-Extra: xprof

번들된 범용 패키지 - 모놀리식 빌드

가능한 모든 변형을 하나의 휠에 포함하는 것도 또 다른 선택지이지만, 이 경우 아티팩트가 지나치게 커져 대역폭을 낭비하고 특정 변형 하나만 필요한 사용자의 설치 시간이 길어집니다. 경우에 따라 이러한 아티팩트는 PyPI의 크기 제한을 초과하므로 PyPI에서 호스팅할 수 없습니다.

소스 배포 패키지를 통한 휠 변형 선택

FlashAttention은 PyPI에 휠을 전혀 게시하지 않고, 대신 플랫폼을 감지하고 업스트림 서버에서 적절한 휠을 다운로드한 다음 이를 설치 프로그램에 제공하는 사용자 지정 소스 배포 패키지를 게시합니다. 이 접근 방식은 최적의 변형을 자동으로 선택할 수 있지만, 바이너리 전용 설치가 작동하지 않게 하고, 소스 배포 패키지를 통한 느리고 오류가 발생하기 쉬운 빌드를 필요로 하며, 휠 파일 이름에 연결된 일반적인 캐싱 가정을 깨뜨립니다. 또한 소프트웨어가 실행될 버전과 일치하는 torch 패키지가 포함된 특별히 준비된 빌드 환경이 필요하며, 이를 위해 빌드 격리 없이 빌드해야 합니다. 프로젝트 측에서는 휠을 별도로 호스팅해야 합니다.

보안 위험: 일반적인 소스 빌드와 마찬가지로 이 모델은 설치 시 임의의 코드를 실행해야 합니다. 휠은 패키지 관리자의 통제 밖에서 전적으로 다운로드되므로 공격 표면이 서로 다른 두 개의 휠 다운로드 구현으로 확장되고, 적절한 출처 추적이 방지됩니다.

생태계의 분열

하드웨어 및 ABI 요구 사항을 충족하는 의존성 해결에 대한 표준화된 지원이 부족하여 생태계가 분열되었습니다.

  • 일관되지 않은 사용자 경험: 각 프로젝트가 서로 다른 설치 방법을 사용하므로 혼란이 발생하고 검색 가능성이 낮아집니다.
  • 개발 도구의 복잡성: 설치 프로그램, IDE 및 CI/CD 시스템은 비표준 설치 요구 사항을 처리하는 데 어려움을 겪습니다.
  • 취약성: 기존의 우회 방법은 흔히 오류가 발생하기 쉬우며, 과거에는 잘못된 아티팩트를 다운로드하는 등의 문제가 발생했습니다.

과학 컴퓨팅 및 AI/ML 워크플로에 미치는 영향

이러한 패키징의 한계는 성능 최적화가 중요한 과학 컴퓨팅 및 AI/ML 애플리케이션에 특히 큰 영향을 미칩니다.

현재 휠 형식에는 하드웨어 인식 기능이 없으므로 하드웨어 의존적 패키지를 사용할 때 최적이 아닌 경험이 발생합니다. 플러그인은 규모가 작고 범위가 명확한 패키지에 도움이 되지만, 사용자는 현재 일반적인 기본값이나 호환되지 않는 조합을 피하기 위해 올바른 변형(예: jax[cuda13])을 수동으로 식별해야 합니다. 명시적으로 재정의하지 않는 한 pip install jax가 사용자의 하드웨어에 맞는 패키지를 자동으로 선택하는 시스템이 필요합니다.

이러한 점에서 휠 변형은 올바른 방향으로 나아가는 분명한 단계입니다.

—Michael Hudgins, JAX 개발 인프라 책임자

이러한 문제는 패키지 작성자부터 모든 숙련도 수준의 최종 사용자에 이르기까지 학생, 과학자 및 엔지니어를 포함한 모든 사람에게 영향을 미칩니다.

모델을 실행하고 대규모 데이터세트를 처리하기 위한 컴퓨팅 자원에 접근하는 일은 10년 넘게 과학 컴퓨팅의 골칫거리였습니다. 오늘날에도 연구자와 데이터 과학자는 업무를 시작하기 전에 PyTorch와 같은 핵심 도구를 설치하는 데 몇 시간에서 며칠을 소비합니다. 이러한 복잡성은 일상 업무에서 Python을 사용하려는 사용자에게 진입 장벽으로 크게 작용합니다. WheelNext Wheel Variants 제안은 또 다른 새롭고 별도의 해결책을 만들지 않고, 더 넓은 패키징 생태계 안에서 지속되는 설치 및 컴퓨팅 자원 접근 문제를 해결할 방법을 제시합니다. 사용자 경험을 향상한다는 큰 그림에 집중합시다. 이는 실질적인 차이를 만들어 낼 것입니다.

—Leah Wasser, pyOpenSci의 전무이사 겸 창립자

이기종 컴퓨팅 환경

연구 기관과 클라우드 제공업체는 서로 다른 아키텍처(CPU, 하드웨어 가속기, ASIC 등)를 사용하는 이기종 컴퓨팅 클러스터를 관리합니다. 현재 시스템에서는 환경별 설치 절차가 필요하므로 재현 가능한 배포가 어렵습니다. 이러한 상황은 “과학 논문”을 재현하기 어렵게 만드는 데에도 영향을 줍니다. 이를 개선하는 데 주력하는 애플리케이션 작성자들도 패키징의 장애물로 어려움을 겪습니다.

과학자, 엔지니어 및 데이터 분석가를 위한 Python IDE인 Spyder의 패키지 관리자를 세 가지 주요 목표를 두고 개발해 왔습니다. 첫째, 터미널에서 난해한 명령을 입력하는 대신 GUI를 사용하여 사용자가 환경을 만들고 패키지를 설치할 수 있도록 함으로써 사용자의 작업을 더 쉽게 만드는 것입니다. 둘째, 연구 코드를 재현 가능하게 만들어 동료들과 코드 및 그 종속성을 공유할 수 있도록 하는 것입니다. 셋째, 사용자가 번거로움 없이 HPC 클러스터나 클라우드의 머신으로 코드를 옮겨 그곳에서 이용 가능한 방대한 컴퓨팅 자원을 활용할 수 있도록 하는 것입니다. 이 PEP에서 제안하는 개선 사항을 적용하면 모든 PyPI 사용자가 이를 실현할 수 있습니다. uv/pip를 기반으로 구축된 도구에서 적절한 GPU와 명령어 집합에 맞는 널리 사용되는 과학 라이브러리(PyTorch 및 CuPy 등)를 간단하고 투명하게 설치할 수 있기 때문입니다.

—Carlos Córdoba, Spyder IDE 수석 개발자

인공지능, 머신러닝 및 딥러닝

최근 현대 AI 워크플로의 발전은 점점 더 GPU 가속에 의존하고 있지만, 현재의 패키징 시스템은 배포를 복잡하게 만들며 전체 도구 스택의 오픈 소스 개발자(빌드 백엔드부터 설치 관리자, 그리고 패키지 유지 관리자까지)에게 상당한 부담을 줍니다.

PyTorch의 광범위한 휠 지원은 언제나 최고 수준이었으며, package selector를 통해 첫 출시 때부터 하드웨어 가속기를 지원했습니다. 이는 PyTorch가 사용자를 위해 별도의 설정 없이 바로 작동하게 만드는 늘 강력한 장점이었다고 생각합니다. 안타깝게도 이를 지원하는 인프라는 매우 복잡하고 유지 관리가 어려우며 비효율적입니다(우리와 사용자 및 패키지 저장소 모두에게 그렇습니다).

지원하는 하드웨어의 수가 다시 빠르게 증가하고 있는 상황에서, PyTorch가 처음 출시된 이후 사용자들이 기대해 온 형태인 pip install torch로 PyTorch 설치 지침을 제공할 수 있게 해 줄 휠 변형 작업을 적극적으로 지지합니다.

PyTorch 코어 유지 관리자

XGBoost_의 수석 유지 관리자는 휠 변형으로 해결될 것으로 예상하는 XGBoost의 여러 문제를 열거합니다.

  • 여러 SM [GPU 대상]에 대해 “팻 바이너리”를 사용하기 때문에 다운로드 크기가 큽니다. 현재 XGBoost는 11개의 서로 다른 SM을 대상으로 빌드합니다.
  • CPU 전용 패키지를 위한 별도의 패키징 이름이 필요합니다. 현재 별도의 xgboost-cpu라는 패키지를 제공하므로, 사용자는 별도의 requirements.txt 파일을 유지 관리해야 합니다. 예시는 xgboost#11632에서 확인할 수 있습니다.
  • 여러 CUDA 버전을 위한 복잡한 디스패치 로직입니다. XGBoost의 일부 기능에는 새로운 CUDA 버전(12.5 또는 12.8)이 필요하지만, XGBoost 휠은 12.0을 대상으로 합니다. 그 결과, 런타임에 CUDA 및 드라이버 버전을 감지하기 위해 상당히 복잡한 디스패치 로직을 유지 관리합니다. 이러한 디스패치 로직은 NVIDIA 프로바이더 플러그인과 같은 전용 소프트웨어에 구현하는 것이 가장 적절하며, 이를 통해 XGBoost 프로젝트는 핵심 사명에 집중할 수 있습니다.
  • 여러 OpenMP 런타임의 존재로 인한 정의되지 않은 동작입니다. XGBoost는 서로 다른 OpenMP 런타임이 설치된 다양한 시스템(또는 OpenMP 런타임이 전혀 없는 시스템)에 설치됩니다. 지금까지 XGBoost는 OpenMP 런타임의 복사본을 벤더링해 왔지만, 이는 점점 더 지속하기 어려워지고 있습니다. 시스템에 호환되지 않는 여러 OpenMP 런타임 버전이 존재하면 사용자는 충돌이나 멈춤과 같은 정의되지 않은 동작을 겪습니다. (이 문제는 MacOS에서 특히 심각했으며, 그 결과 XGBoost용 MacOS 휠은 더 이상 OpenMP를 번들로 포함하지 않습니다.)

XGBoost 의 수석 유지 관리자 Philip Hyunsu Cho

패키징의 복잡성으로 인해 개발자들은 소프트웨어의 실제 목표에 집중하지 못하고 있습니다:

전문 과학 데이터에 대한 사용자 지정 모델 훈련을 위한 프런트엔드로서 사용하기 쉽다는 점 덕분에 많은 사용자(35,000명 초과, 80개국 초과)를 확보한, 이미지 시퀀스에서 생물학적 움직임을 분석하기 위해 딥러닝을 사용하는 과학 소프트웨어 도구를 유지 관리하고 있습니다. 저희 사용자층은 질병 치료법을 발견하기 위해 하루 종일 뇌 수술과 분자 유전학 연구를 수행하는 과학자들입니다. 단지 데이터를 분석할 수 있도록 하드웨어 가속기 드라이버 호환성 매트릭스와 환경 관리자에 대해 배우고, 끊임없이 변화하는 Python 패키징 생태계를 따라가야 한다고 그들에게 기대하는 것은 전혀 합리적이지 않습니다.

이를 인식하여, 저희 팀은 연구비 수백만 달러 규모의 연구 결과 재현성을 뒷받침하는 현재의 도구가 모든 플랫폼과 호환되도록 하기 위해 의존성과 패키징 편법을 유지 관리하는 데 지나치게 많은 시간을 투입했습니다. 지난 몇 년 동안 저희는 납세자가 지원한 연구 자금 중 수백 시간을 들이고 250,000달러가 넘는 금액을 이 문제에 대한 엔지니어링 해결책 개발에 사용한 것으로 추정합니다. WheelNext가 있었다면 이 문제를 완전히 해결했을 것이며, 저희는 신경퇴행성 질환을 이해하고 치료하는 데 노력을 집중할 수 있었을 것입니다.

—SLEAP_의 저자이자 Salk Institute for Biological Studies의 수석 연구책임자인 Talmo Pereira, Ph.D.

개선 가능성은 다음과 같이 요약할 수 있습니다:

이 PEP는 점점 더 복잡하고 다양해지는 하드웨어 구성에 직면한 Python 생태계의 배포 문제를 개선하는 데 있어 중요한 진전입니다. 동일한 라이브러리에 대해 여러 배포 대상을 표준화된 방식으로 제공함으로써, 빠르게 성장하는 AI/ML 및 과학 컴퓨팅 분야를 지원하기 위해 개발자들이 추진해 온 번거롭고 시간이 많이 걸리는 여러 우회 방법을 통합하고 간소화할 것입니다.

NumPy 및 SciPy_의 저자이자 OpenTeams의 최고 AI 아키텍트인 Travis Oliphant

범위에 포함되지 않는 기능

이 PEP는 현대의 이기종 시스템 요구 사항을 충족하는 데 필요한 최소 범위를 제시합니다. 최소 범위를 벗어나는 측면은 도구 또는 향후 PEP를 통해 발전하도록 남겨 둡니다. 이러한 측면의 일부를 망라하지 않는 목록은 다음과 같습니다:

  • 변형을 결정론적으로 선택하거나 pylock.toml 파일에 변형을 포함하기 위한 정적 파일의 형식,
  • 설치 프로그램이 벤더링하거나 다시 구현하는 변형 제공자의 목록,
  • 설치 프로그램이 벤더링되지 않은 변형 제공자를 실행하도록 허용하기 위한 구체적인 옵트인 메커니즘 및 UX,
  • 빌드 백엔드가 PEP 517 메커니즘을 통해 변형을 출력하도록 지시하는 방법입니다.

선행 사례

이 문제는 Python 생태계에만 국한되지 않으며, 다양한 집단과 생태계가 바로 이 문제에 대해 여러 가지 해답을 제시해 왔습니다. 이 절에서는 여러 커뮤니티가 취한 다양한 접근 방식의 강점과 약점을 조명하는 데 중점을 둡니다.

Conda - conda-forge

Conda는 파일명 파싱 대신 집계된 메타데이터 색인을 사용하여 의존성을 해결하는 바이너리 전용 패키지 생태계입니다. packaging:specifications/simple-repository-api 문서와 달리, conda의 해결 방식은 전체 메타데이터를 포함하는 플랫폼별 repodata 색인 에 의존하므로, 파일 이름은 파싱 요구 사항이 없는 순수한 식별자 역할만 합니다.

변형 시스템: 2016-2017년에 conda-build는 이름/버전은 동일하지만 의존성이 다른 패키지를 구분하기 위해 변형을 도입했습니다.

pytorch-2.8.0-cpu_mkl_py313_he1d8d61_100.conda      # CPU + MKL variant
pytorch-2.8.0-cuda128_mkl_py313_hf206996_300.conda  # CUDA 12.8 + MKL variant
pytorch-2.8.0-cuda129_mkl_py313_he100a2c_300.conda  # CUDA 12.9 + MKL variant

해시(변형 메타데이터에서 계산됨)는 파일명 충돌을 방지하며, 실제 변형 선택은 솔버에서 표준 의존성 제약을 통해 이루어집니다. 특별한 메타데이터 파싱은 필요하지 않으며, 설치 프로그램은 다음과 같이 의존성을 단순히 해결합니다.

conda install pytorch mkl

뮤텍스 메타패키지: Python 메타데이터와 conda 메타데이터에는 “이 패키지는 저 패키지와 충돌합니다”와 같은 개념을 표현할 적절한 방법이 없습니다. 이를 강제하는 주된 메커니즘은 공통 패키지 이름을 공유하는 것이며, 한 번에 특정 이름을 가진 패키지는 하나만 존재할 수 있습니다. 뮤텍스 메타패키지는 이름은 같지만 빌드 문자열이 다른 패키지 집합입니다. 패키지는 서로 다른 의존성 라이브러리를 사용하는 관련 패키지로 인해 발생하는 문제를 피하기 위해 특정 뮤텍스 빌드(예: blas=*=openblasblas=*=mkl)에 의존합니다. 예를 들어 NumPy_가 OpenBLAS를 사용하고 SciPy_가 MKL를 사용하는 경우가 이에 해당합니다.

소프트웨어 변형 예시: BLAS, MPI, OpenMP, noarch 대 native

가상 패키지: 2019년에 도입됨. 가상 패키지는 시스템 감지 정보(CUDA 버전, glibc, CPU 기능)를 솔버 제약 조건으로 주입합니다. 빌드된 패키지는 __cuda >=12.8과 같은 의존성을 표현하며, 설치 프로그램은 설치 시 호환성을 확인합니다. 현재 가상 패키지에는 archspec(CPU 기능), OS/시스템 라이브러리 및 CUDA 드라이버 버전이 포함됩니다. 감지 로직은 도구별로 다릅니다(rattler, mamba).

Spack / Archspec

archspecSpack 패키지 관리자를 위해 개발된, CPU 마이크로아키텍처 변형을 감지하고 레이블을 지정하며 추론하는 라이브러리입니다.

변형 모델: CPU 마이크로아키텍처(예: haswell, skylake, zen2, armv8.1a)는 바이너리 호환성을 인코딩하는 유향 비순환 그래프(DAG)를 형성하며, 이를 통해 해결 과정에서 packageBpackageA에 의존한다는 것을 표현할 수 있습니다. 순서가 부분적인 이유는 (1) 서로 다른 ISA 계열은 비교할 수 없고, (2) 현대적인 설계에는 호환되지 않는 기능 집합이 있을 수 있기 때문입니다. cascadelake와 cannonlake는 둘 다 skylake에서 파생되었음에도 서로 비교할 수 없는데, 각각 고유한 AVX-512 확장을 갖기 때문입니다.

구현: 언어에 종속되지 않는 JSON 데이터베이스가 마이크로아키텍처 메타데이터(기능, 호환성 관계, 컴파일러별 최적화 플래그)를 저장합니다. 언어 바인딩은 감지(/proc/cpuinfo를 조회하고, 호환되는 기능 부분집합이 가장 큰 마이크로아키텍처와 일치시킴) 및 호환성 비교 연산자를 제공합니다.

패키지 관리자 통합: Spack은 대상 마이크로아키텍처를 패키지 출처 정보로 기록하고(spack install fftw target=broadwell), 컴파일러 플래그를 자동으로 선택하며, 마이크로아키텍처 인식 바이너리 캐싱을 활성화합니다. European Environment for Scientific Software Installations (EESSI)는 마이크로아키텍처별로 별도의 하위 디렉터리(예: x86_64, armv8.1a, haswell)에 최적화된 빌드를 배포합니다. 런타임 초기화는 정확히 일치하는 항목이 없을 때 archspec을 사용하여 가장 호환되는 빌드를 선택합니다.

Gentoo Linux

Gentoo Linux는 광범위한 패키지 사용자 지정을 지원하는 소스 우선 배포판입니다. 이는 주로 USE 플래그를 통해 이루어집니다. USE 플래그는 개별 패키지가 노출하는 불리언 플래그로, 활성화된 기능, 선택적 의존성 및 일부 빌드 매개변수를 세밀하게 조정할 수 있습니다(예: JPEG XL 이미지 형식 지원을 위한 jpegxl, AVX2 명령 집합 사용을 위한 cpu_flags_x86_avx2). 플래그는 개별적으로 전환할 수 있으며, 서로 다른 플래그 집합에 대해 별도의 바이너리 패키지를 빌드할 수 있습니다. 패키지 관리자는 구성이 일치하는 바이너리 패키지를 선택하거나 소스에서 빌드할 수 있습니다.

API 및 ABI 일치는 주로 슬로팅을 통해 수행됩니다. 슬롯은 일반적으로 주어진 패키지의 여러 버전 또는 변형을 함께 설치할 수 있도록 제공하는 데 사용됩니다(예: 서로 다른 메이저 GTK+ 또는 LLVM 버전, 또는 WebKitGTK의 GTK+3 및 GTK4 빌드). 반면 서브슬롯은 슬롯 내 버전을 그룹화하는 데 사용되며, 일반적으로 라이브러리 ABI 버전에 해당합니다. 그러면 패키지는 빌드 시 사용된 슬롯과 서브슬롯에 결합된 의존성을 선언할 수 있습니다. 또한 서로 다른 의존성 슬롯에 대해 별도의 바이너리 패키지를 빌드할 수 있습니다. 다른 슬롯 또는 서브슬롯에 속하는 의존성 버전을 설치할 때 패키지 관리자는 해당 의존성이 필요한 패키지를 새 슬롯에 대해 빌드된 바이너리 패키지로 교체하거나, 소스에서 다시 빌드할 수 있습니다.

일반적으로 슬롯 사용은 가능한 최신 버전으로 업그레이드하는 것이 바람직하다고 가정합니다. 더 세밀한 제어가 필요한 경우 슬롯을 USE 플래그와 함께 사용합니다. 예를 들어, llvm_slot_{major} 플래그를 사용하여 빌드 대상으로 삼을 LLVM 주 버전을 선택합니다.

개요 및 근거

휠 변형 용어집

변형 휠
동일한 배포 이름, 버전, 빌드 번호 및 플랫폼 호환성 태그를 공유하지만, 임의의 변형 속성 집합으로 명확하게 식별되는 휠입니다.
변형 네임스페이스
단일 제공자가 제공하는 관련 기능을 그룹화하는 데 사용하는 식별자입니다(예: nvidia, x86_64, arm 등).
변형 기능
네임스페이스 내의 특정 특성(키)입니다(예: version, avx512_bf16 등). 하나 이상의 값을 가질 수 있습니다.
변형 속성
단일 특정 기능과 해당 값을 설명하는 3-튜플(namespace :: feature-name :: feature-value)입니다. 기능에 여러 값이 있는 경우 각 값은 별도의 속성으로 표현됩니다.
변형 레이블
변형을 고유하게 식별하기 위해 휠 파일 이름에 추가하는 문자열입니다(최대 16자).
널 변형
변형 속성이 0개이고 예약된 레이블 null을 사용하는 특수한 변형입니다. 항상 지원되는 것으로 간주되지만 휠 변형 중 우선순위가 가장 낮으며, 비변형 휠보다 우선적으로 선택됩니다.
변형 제공자
특정 네임스페이스에 대해 지원되고 유효한 변형 속성을 제공하는 제공자이며, 일반적으로 시스템 감지를 구현하는 Python 패키지 형태입니다.
설치 시 제공자
휠 설치 중에 조회할 수 있는 플러그인으로 구현된 제공자입니다.
사전 제공자
지원되는 속성의 정적 목록을 제공하고 이를 휠 메타데이터에 삽입하는 제공자입니다. 이러한 목록은 pyproject.toml에 삽입하거나 빌드 시 조회하는 플러그인이 제공할 수 있습니다.

개요

휠 변형은 기존 휠 태그가 제공하는 것보다 빌드된 휠의 특성을 더 세밀하게 지정합니다. 개별 휠에는 Modified wheel filename에 설명된 대로 빌드 시 정의된 사람이 읽을 수 있는 레이블이 포함되며, Variant property system을 사용하여 특성이 지정됩니다. 속성은 네임스페이스, 기능 및 기능 값으로 이루어진 계층 구조로 구성됩니다. 설치할 휠을 평가할 때 설치 프로그램은 특정 휠의 변형 속성이 시스템과 호환되는지 판단하고, 호환되는 변형 속성의 우선순위에 따라 Variant ordering을 수행합니다. 이는 호환성을 판단하는 것에 더하여 수행됩니다. 변형 속성에 따른 순서는 태그에 따른 순서보다 우선합니다.

모든 변형 네임스페이스는 변형 제공자가 관리합니다. 변형 제공자에는 설치 시 제공자와 사전 제공자(ahead-of-time, AoT)의 두 종류가 있습니다. 설치 시 제공자는 휠을 설치하는 동안 플러그인을 조회하여 지원되는 속성 집합과 그 선호 순서를 결정해야 합니다. AoT 제공자의 경우 이 데이터는 정적이며 휠에 포함됩니다. 이 데이터는 휠 유지 관리자가 직접 제공하거나, 휠 빌드 시 AoT 플러그인에서 조회할 수 있습니다.

두 종류의 플러그인 모두 일반적으로 Provider plugin API를 구현하는 Python 패키지로 구현되지만, Providers에 설명된 대로 사용자 경험을 개선하기 위해 설치 프로그램에 벤더링되거나 재구현될 수도 있습니다. 플러그인 패키지는 격리된 환경 또는 비격리 환경에 설치될 수 있습니다. 특히 모든 플러그인은 PEP 517 백엔드의 get_requires_for_build_wheel() 훅에 의해 반환될 수 있으므로, 다른 빌드 의존성과 함께 설치됩니다. 따라서 플러그인 패키지가 의존성을 지나치게 좁게 고정하지 않는 것이 중요합니다. 그렇게 하면 동일한 환경에 서로 다른 패키지가 동시에 설치되지 못할 수 있기 때문입니다.

변형 지원을 관리하는 메타데이터는 pyproject.toml 파일에 정의되며, Metadata in source tree and wheels에서 살펴본 것처럼 휠의 variant.json 파일에 복사됩니다. 또한 Variant environment markers를 사용하여 변형의 일부 집합에만 해당하는 의존성을 정의할 수 있습니다.

수정된 휠 파일 이름

이 설계의 핵심 요구 사항 중 하나는 이 PEP보다 먼저 만들어진 설치 프로그램이 휠 변형 파일을 무시하도록 보장하는 것입니다. 이를 통해 하나의 색인에 변형 휠과 비변형 휠을 모두 게시할 수 있으며, 변형을 지원하지 않는 설치 프로그램은 전자를 안전하게 무시하고 후자로 대체할 수 있습니다.

변형 레이블 구성 요소는 파일 이름에서 변형 속성 집합으로의 고유한 매핑을 제공하고 변형을 사람이 읽을 수 있도록 식별하기 위한 두 가지 목적으로 파일 이름에 추가됩니다. 레이블은 서로 다른 파일 시스템에서 발생할 수 있는 문제를 피하기 위해 짧고 소문자로 유지됩니다. 기존 파일 이름 검증 알고리즘이 이를 거부하도록 파일 이름 끝에 -로 구분된 구성 요소로 추가됩니다.

  • 빌드 태그와 변형 레이블이 모두 있으면 파일 이름에 구성 요소가 너무 많이 포함됩니다. 예:
    numpy-2.3.2-1-cp313-cp313t-musllinux_1_2_x86_64-x86_64_v3.whl
                                                   ^^^^^^^^^^
    
  • 변형 레이블만 있으면 세 번째 위치의 Python 태그가 빌드 번호로 잘못 해석됩니다. 빌드 번호는 숫자로 시작해야 하고 당시에는 숫자로 시작하는 Python 태그가 없었으므로 파일 이름은 유효하지 않은 것으로 간주됩니다. 예:
    numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64-x86_64_v3.whl
                ^^^^^
    

이 동작은 여러 기존 도구에서 확인되었습니다: auditwheel, packaging, pdm, pip, poetry, 및 uv입니다.

변형 속성 시스템

변형 속성은 변형의 특성을 표현하는 데 사용됩니다. 플랫폼 호환성 태그와 달리, 변형 속성은 변형 메타데이터에 저장되므로 휠 파일 이름의 길이에 영향을 주지 않습니다. 변형 속성은 계층적 키-값 설계를 따르며, 키는 다시 네임스페이스와 기능 이름으로 나뉩니다. 네임스페이스는 단일 제공자가 정의한 기능을 그룹화하고, 여러 제공자가 같은 이름의 기능을 정의할 경우 충돌을 방지하는 데 사용됩니다. 이를 통해 각 네임스페이스를 독립적으로 관리하고 발전시킬 수 있습니다.

키는 소문자, 숫자 및 밑줄로 제한됩니다. 같은 이름이 서로 다르게 표기되는 것을 방지하기 위해 대문자는 허용되지 않습니다. 값에 허용되는 문자 집합은 버전과 유사한 값을 허용할 수 있도록 더 완화되어 있습니다.

변형 속성은 PEP 301의 Trove 분류자에서 영감을 받은 구조화된 3-튜플 형식으로 직렬화됩니다:

{namespace} :: {feature_name} :: {feature_value}

속성은 변형 휠의 호환성을 판단하고 설치할 최적의 변형을 선택하는 데 모두 사용됩니다. 제공자 플러그인은 어떤 변형 속성이 시스템과 호환되는지 나타내고, 이를 중요도 순으로 정렬합니다. 이 순서는 변형 휠 메타데이터에서 추가로 변경할 수 있습니다.

변형 기능은 하나의 변형 휠 안에 여러 값이 존재하도록 허용하는 것으로 선언할 수 있습니다. 이 경우 해당 값들은 논리적 OR로 매칭되므로, 휠이 지원되는 것으로 간주되려면 단 하나의 값만 시스템과 호환되면 됩니다. 반면 기능들은 논리적 AND로 처리되므로, 모든 기능이 호환되어야 합니다. 이를 통해 완전한 불리언 논리를 구현하지 않고도 변형 호환성을 지정하는 데 어느 정도 유연성을 제공합니다.

일반적으로 변형 기능은 단일 값이며, 최소 요구 사항 또는 상호 배타적인 요구 사항을 나타냅니다. 시스템은 여러 호환 가능한 값을 나타낼 수 있습니다. 예를 들어 기능이 최소 CUDA 런타임 버전을 선언하는 경우, 제공자는 현재 설치된 버전에 해당하거나 그보다 오래된 최소 버전을 요구하는 휠과의 호환성을 나타냅니다. 예를 들어 CUDA 12.8의 경우, 휠에서 사용되는 호환 가능한 최소 버전은 선호도가 높은 순서대로 다음과 같습니다:

nvidia :: cuda_version_lower_bound :: 12.8
nvidia :: cuda_version_lower_bound :: 12.7
nvidia :: cuda_version_lower_bound :: 12.6
...

마찬가지로 휠은 필요한 최소 CPU 버전을 나타낼 수 있으며, 제공자는 호환 가능한 모든 CPU 버전을 나타냅니다.

다중 값 기능은 하나의 패키지가 서로 호환되지 않는 여러 대상을 지원하는 “fat” 패키지에 유용합니다. 대표적인 예로 GPU가 있습니다. 이 경우 휠은 지원되는 여러 GPU를 선언하고, 제공자는 실제로 설치된 GPU를 나타냅니다(일반적으로 하나입니다). 두 목록 사이에 겹치는 항목이 있으면 휠이 호환됩니다.

널 변형

널 변형은 속성이 없는 변형 휠이지만, null 변형 레이블과 변형 메타데이터를 가진다는 점에서 비변형 휠과 구별됩니다. 전환 기간 동안 이는 제공된 변형을 지원하지 않는 시스템과 변형 휠을 전혀 지원하지 않는 시스템에 별도의 폴백을 제공할 수 있게 합니다.

예를 들어 선택적 GPU 지원을 제공하는 패키지는 세 종류의 휠을 게시할 수 있습니다:

  • 각각 단일 CUDA 버전과 이에 맞는 지원 GPU 집합을 대상으로 빌드되며, 제공자 플러그인이 시스템과 호환된다고 나타낼 때만 사용되는 여러 GPU 지원 휠입니다.
  • 제공자 플러그인이 호환 가능한 GPU가 설치되지 않았다고 나타낼 때 설치되는, GPU 변형보다 훨씬 작은 CPU 전용 널 변형입니다.
  • 변형을 지원하는 설치 프로그램이 없는 시스템에 설치되는 GPU+CPU 비변형 휠입니다.

널 변형을 게시하는 것은 선택 사항이며, 서로 다른 폴백이 사용자에게 이점을 제공하는 경우에만 의미가 있습니다. 하나를 게시하면, 휠 변형을 지원하는 설치 프로그램은 이를 비변형 휠보다 우선합니다. 게시하지 않으면 대신 비변형 휠로 폴백합니다. 설치 프로그램 플래그로 변형 지원을 명시적으로 비활성화한 경우에도 비변형 휠이 사용됩니다.

널 변형은 일반 변형과 명확히 구별되도록 예약된 null 레이블을 사용합니다.

설치 시 제공자 및 Ahead-of-Time 제공자

변형 휠 메타데이터는 해당 속성에 어떤 제공자가 사용되는지 지정합니다. 제공자는 두 가지 목적을 수행합니다.

  1. 설치 시: 어떤 변형 휠이 사용자 시스템과 호환되는지, 그리고 그중 어떤 것이 최선의 선택인지 결정하는 것,
  2. 빌드 시: 어떤 변형 속성이 휠을 빌드하는 데 유효한지 결정하는 것.

이 사양은 설치 시 제공자와 Ahead-of-Time 제공자라는 두 종류의 제공자를 제안합니다.

설치 시 제공자는 조회를 위해 설치하고 실행해야 하는 Python 패키지로 구현되거나, 도구에 벤더링되거나 재구현됩니다. 사용자 시스템을 조회하여 휠 호환성을 결정해야 할 때 사용되며, 예를 들어 GPU를 활용하거나 플랫폼 태그가 제공하는 범위를 넘어서는 CPU 명령어 집합을 요구하는 변형에 사용됩니다. 서드파티 패키지를 설치하면 Security implications 섹션에서 강조한 보안 위험이 발생하며, 제안된 완화책은 설치 프로그램 구현에 비용을 초래합니다.

Ahead-of-Time 제공자는 휠에 포함된 정적 메타데이터로 구현됩니다. 특정 변형 속성이 사용자 시스템과 항상 호환되는 경우에 사용되며, 이는 해당 속성을 사용하는 휠이 성공적으로 빌드되었다는 전제하에 성립합니다. 그러나 메타데이터는 어떤 속성이 선호되는지를 나타냅니다. 예를 들어 AoT 제공자는 서로 다른 BLAS / LAPACK 제공자를 대상으로 한 빌드 간 선택권을 제공하거나 패키지의 디버그 빌드를 제공하는 데 사용할 수 있습니다. 설치 프로그램 외부의 코드를 실행할 필요가 없으므로 설치 시 제공자가 겪는 문제를 일으키지 않으며, 더 자유롭게 사용할 수 있습니다.

AoT 제공자는 플러그인 패키지를 포함할 수 있습니다. 그러한 경우 이러한 패키지는 휠을 빌드할 때만 사용되며, 그 출력은 설치 시 사용되는 정적 메타데이터를 채우는 데 사용됩니다. 이를 통해 여러 패키지에서 일관된 속성 이름과 값을 더 쉽게 사용할 수 있습니다. 그렇지 않으면 패키지 관리자가 지원되는 속성을 pyproject.toml 파일에 직접 포함해야 합니다.

두 종류의 제공자 플러그인은 Python 패키지로 구현될 때 대략 동일한 API를 노출합니다. 그러나 AoT 제공자는 지원되는 유효한 변형 속성을 항상 모두 고려해야 하며, 사용자 시스템과 관계없이 지원되는 속성의 순서가 동일한 목록을 항상 반환해야 합니다. 모든 AoT 제공자는 기술적으로 설치 시 제공자로 사용할 수 있지만, 그 반대는 불가능합니다.

플러그인 안정성 및 버전 관리

이 사양으로 인해 휠을 설치하기 위해 제공자 패키지를 설치하고 실행해야 할 가능성이 생기므로, 이러한 패키지는 매우 오래된 패키지 버전을 포함하여 과거에 게시된 변형 휠에서도 계속 올바르게 작동하는 것이 권장됩니다. 이상적으로는 이전에 지원된 속성을 절대 제거하지 않아야 합니다.

호환성을 깨뜨리는 변경을 수행해야 하는 경우, 이를 위한 새 제공자 패키지를 도입하거나 기존 패키지에 새 플러그인 API 엔드포인트를 추가하는 것이 권장됩니다. 두 경우 모두 이전 휠을 계속 설치할 수 있도록 이전 엔드포인트를 최소 유지 관리 모드로 보존해야 할 수 있습니다. 이전 엔드포인트는 패키지를 빌드할 때 사용되는 get_all_configs() 후크에서 사용 중단 경고를 발생시킬 수 있습니다.

또 다른 방법은 시맨틱 버전 관리를 사용하여 호환성을 깨뜨리는 변경을 차단하는 것입니다. 그러나 이 방법은 패키지 작성자가 의존성에 상한을 안정적으로 설정하는 것에 의존합니다. 그렇지 않으면 이전 휠이 호환되지 않는 플러그인 버전을 사용하기 시작합니다. 이는 오늘날 사용되는 Python 빌드 백엔드에서도 이미 문제가 되고 있습니다.

플러그인을 벤더링하거나 재구현할 때 설치 도구는 플러그인의 현재 동작을 따라야 합니다. 특히 관련 제공자 버전 번호를 인식해야 하며, 해당 패키지가 설치 도구의 구현과 호환되지 않는 경우 외부 플러그인을 설치하는 방식으로 대체해야 할 수도 있습니다.

소스 트리와 휠의 메타데이터

변형은 소스 트리와 휠에 저장되는 몇 가지 새로운 메타데이터 부분을 도입합니다. 소스 트리에서는 다른 프로젝트 속성과 함께 pyproject.toml 파일에 저장되며, TOML 형식의 가독성과 엄격성의 이점을 활용합니다. 이후 동등한 JSON 구조로 변환되어 .dist-info 디렉터리에 별도의 파일로 저장됩니다. 기존 메타데이터 파일은 불필요한 비호환성을 방지하고 불편한 Core Metadata 형식으로 직렬화하지 않기 위해 변경되지 않습니다.

pyproject.toml의 메타데이터에는 다음이 포함됩니다:

  • 휠에서 사용할 수 있는 변형 제공자에 관한 정보
  • 선택적으로 기본 속성 순서를 재정의하는 목록
  • 플러그인을 사용하지 않는 사전 빌드 제공자를 위한 정적 속성 목록

휠 메타데이터에서는 플러그인에서 얻은 정적 속성 목록과 빌드된 휠의 변형 속성을 추가하여 위 항목을 보완합니다.

휠이 색인에 게시되면 모든 휠의 변형 메타데이터가 하나의 {name}-{version}-variants.json 파일로 결합됩니다. 이 파일을 사용하면 클라이언트가 각 휠에서 개별적으로 다운로드하거나 패키지 색인 서버가 제공하는 API에서 명시적인 변형 메타데이터 지원을 구현하지 않고도 변형 메타데이터를 효율적으로 가져올 수 있습니다.

ABI 의존성 변형 제공자

일부 패키지는 넓은 버전 범위에서 호환되지 않는 애플리케이션 바이너리 인터페이스(ABI)를 노출하는 확장 모듈을 제공합니다. 이 인터페이스를 사용하는 패키지는 빌드 시 사용된 버전에 휠을 고정해야 합니다. ABI가 자주 변경되면 고정 범위가 매우 좁아지므로, 사용자는 동일한 의존성의 서로 다른 버전에 고정될 수 있는 두 패키지를 설치해야 할 때 문제에 직면합니다. 서로 다른 의존성 버전에 대해 빌드된 변형을 제공하면 의존성 해결자가 설치 중인 모든 패키지와 호환되는 의존성 버전을 찾을 가능성이 높아집니다.

안타깝게도 이러한 변형 제공자는 사양에서 정의한 플러그인 API 내에서 구현할 수 없습니다. 견고한 구현에는 의존성 해결자와의 인터페이스가 필요하므로, 이 사용 사례를 지원하도록 API를 확장하여 결과적으로 상당한 복잡성을 추가하기보다는, 사양에서는 이 기능을 제공하려는 설치 도구가 구현할 수 있는 특별한 변형 네임스페이스로 abi_dependency를 예약합니다.

문제의 복잡성을 고려하여 이 확장은 전적으로 선택 사항으로 제공합니다. 따라서 이를 사용하는 모든 패키지는 변형이 적용되지 않은 휠도 제공해야 합니다.

패키징 도구를 위한 권장 구현 로직

색인에서 패키지 설치

A diagram showing installing a package including variant wheel building. It is split into three columns: Developer, Installer and Install-time providers. The diagram starts with Develop initiating package install. The subsequent steps involve installer, in order: resolver selects package version; determine if release has variant wheels. If there are no variant wheels, jump to installing package and report success. If the version has variant wheels, check user's variant preferences. In parallel, download JSON from index, then extract variant provider configuration. If it uses AoT providers only, converge to determine optimal variant immediately. If it requires install-time providers, the further path depends on whether non-vendored providers are included. If they are not, query install-time providers immediately and converge to determine optimal variant. If non-vendored providers are included, they are installed if not present in env and then queried. Querying providers involves an exchange of data with different providers (in the diagram, "provider 1" and "provider 2" are given as examples), each filtering and ordering supported configurations for the current environment. All the variant paths converge on determining optimal variant, which is following by installing package and reporting success.

A conceptual diagram of installing a wheel.

색인에서 패키지 버전을 설치하라는 요청을 받으면 제안되는 도구 동작은 다음과 같습니다:

  1. 원격 색인에서 원하는 패키지를 조회합니다.
  2. 버전 제약 조건을 충족하는 패키지 버전에 대해 평소와 같이 초기 일치 항목을 선택합니다(변형 메타데이터는 고려하지 않아도 됩니다).
  3. 플랫폼 호환성 태그를 기반으로 사용 가능한 휠을 필터링합니다.
  4. 남은 휠 중 변형 휠이 있는지 확인합니다. 그렇지 않다면 비변형 휠과 동일하게 진행합니다.
  5. 휠에 변형 레이블이 있는 경우 색인 수준 변형 메타데이터 파일 {name}-{version}-variants.json을 다운로드합니다. 이 파일이 없으면 모든 변형 휠이 호환되지 않는 것으로 간주하고 비변형 휠과 동일하게 진행합니다.
  6. 색인 수준 변형 메타데이터 파일을 사용하여 변형 레이블을 변형 속성 집합으로 매핑합니다. 휠 파일 이름에 있는 레이블 중 파일에서 누락된 것이 있으면 해당 휠이 호환되지 않는 것으로 간주합니다.
  7. 색인 수준 변형 메타데이터 파일에 지정된 제공자를 사용하여 지원되는 변형 속성의 정렬된 목록을 가져옵니다.
    • 활성화된 AoT 제공자의 경우 색인 수준 변형 메타데이터 파일의 정적 속성 데이터에서 가져옵니다.
    • 활성화된 설치 시 제공자의 경우:
      • 사용자가 정적 호환성 정보를 제공했다면 이를 사용합니다.
      • 그렇지 않고 제공자가 벤더링되었거나 재구현되었다면 구현별 방식으로 질의합니다.
      • 그렇지 않고 Python 제공자 패키지가 안전한 것으로 간주되면(설치 도구가 판단하거나 사용자가 명시적으로 선택한 경우) 격리된 환경에 설치하고 플러그인 API를 통해 질의합니다.
      • 위의 어느 경우에도 해당하지 않으면 제공자를 실행하지 않고 변형 속성을 호환되지 않는 것으로 간주하거나 설치에 실패합니다.
    • 비활성화된 제공자의 경우(예: 사용자가 활성화하지 않은 옵트인 제공자, 환경 마커를 통해 제외된 제공자) 네임스페이스의 모든 변형 속성이 호환되지 않는 것으로 간주합니다.
  8. 지원되는 속성 목록을 기반으로 변형을 필터링하고 정렬한 다음 가장 선호되는 변형을 선택합니다. 일치하는 변형 휠이 없으면 해당 규칙에 따라 비변형 휠을 사용합니다.
  9. 특정 버전의 여러 휠이 동일한 변형 레이블을 공유하는 경우 플랫폼 호환성 태그와 빌드 번호를 기준으로 정렬하고 가장 적합한 휠을 선택합니다.

로컬 휠 설치

로컬 휠 파일을 설치하라는 요청을 받으면 도구는 다음과 같이 동작하는 것이 제안됩니다.

  1. 파일 이름에 변형 레이블이 없으면 비변형 휠과 동일하게 진행합니다.
  2. 플랫폼 호환성 태그를 통해 휠의 호환성을 확인합니다.
  3. 휠 파일 내부의 *.dist-info/variant.json에서 변형 메타데이터를 읽습니다.
  4. Installing a package from an index인 경우와 같이 지원되는 변형 속성의 정렬된 목록을 가져옵니다.
  5. 지원되는 속성을 통해 휠의 호환성을 확인합니다.

변형 휠 빌드

변형 휠을 빌드하려면 빌드 백엔드가 변형 속성 목록과 변형 레이블을 받아야 합니다. 이를 수행하는 권장 방법은 빌드 백엔드 훅에 전달되는 config_settings 딕셔너리에서 백엔드가 정의한 키를 사용하는 것입니다.

변형 휠을 빌드할 때 빌드 백엔드는 다음과 같이 동작하는 것이 제안됩니다.

  1. pyproject.toml에서 변형 제공자 메타데이터를 읽습니다.
  2. 사용자가 정의한 변형 속성에 지정된 모든 네임스페이스에 메타데이터에서 대응하는 제공자가 있는지 확인합니다.
  3. get_requires_for_build_wheel()훅에서 다른 빌드 의존성과 함께 변형 제공자 플러그인 패키지를 반환합니다.
  4. build_wheel()훅에서 제공자 플러그인의 get_all_configs() 함수를 조회하여 유효한 모든 속성 키와 값을 가져옵니다. 이를 사용하여 지정된 속성이 올바른지 확인합니다.
  5. pyproject.toml의 변형 메타데이터를 JSON으로 변환하고, 변형 레이블에서 변형 속성으로의 매핑을 추가한 다음, 그 결과를 휠의 *.dist-info/variant.json 파일에 기록합니다.
  6. *.dist-info/variant.json 파일과 변형 레이블을 파일 이름에 포함하는 것을 제외하고 평소와 같이 휠을 빌드합니다.

색인에 변형 휠 게시

변형 휠은 일반 휠과 동일한 방식으로 색인에 업로드합니다. 모든 패키지 버전에 대한 색인 수준의 {name}-{version}-variants.json 파일을 게시하는 방법은 두 가지입니다. 사용자가 파일을 준비하여 업로드하거나, 색인이 자동으로 생성할 수 있습니다.

클라이언트가 이미 파일을 캐시했거나 기존 해시에 고정했을 수 있으므로, 파일을 게시한 후에는 변경하지 않아야 합니다. 따라서 색인이 파일 생성을 담당하는 경우, 릴리스 업로드가 완전히 끝날 때까지 파일 게시를 지연하는 메커니즘을 사용해야 합니다(예: PEP 694).

{name}-{version}-variants.json 파일을 생성하려면 다음과 같이 합니다.

  1. 특정 패키지 버전에 대한 첫 번째 변형 휠의 경우, 해당 휠의 *.dist-info/variant.json 파일에서 데이터를 복사합니다.
  2. 이후 휠의 경우 해당 휠의 *.dist-info/variant.json 파일에서 데이터를 가져와 기존 데이터에 병합합니다.
    • providers, static-propertiesvariants 딕셔너리의 서로 겹치지 않는 키는 함께 병합합니다.
    • 이러한 딕셔너리의 공통 키는 정확히 동일한 값을 가져야 합니다.
    • default-priorities.namespace 목록은 새 값이 이전 값으로 시작하는 경우 새 값으로 대체할 수 있습니다.
    • 이전 default-priorities.namespace 값에 없었던 경우 default-priorities.featuredefault-priorities.value 키를 추가할 수 있습니다.
    • 다른 키는 정확히 동일한 값을 가져야 합니다.

사용 사례 예시

PyTorch CPU/GPU 변형

2025년 10월 기준으로, PyTorch는 모든 릴리스에 대해 총 7개의 변형을 게시합니다. CPU 전용 변형 1개, 최소 CUDA 런타임 버전과 지원 GPU가 서로 다른 CUDA 변형 3개, ROCm 변형 2개, Linux XPU 변형 1개입니다.

설치된 런타임 버전과 GPU/​XPU를 조회하는 GPU/​XPU 플러그인을 사용하여 런타임을 사용할 수 없거나, 런타임이 너무 오래되었거나, 사용자의 GPU가 지원되지 않는 휠을 제외하고 남은 변형을 런타임 버전 순으로 정렬하면 이 설정을 개선할 수 있습니다. CPU 전용 버전은 항상 지원되는 null 변형으로 게시합니다.

GPU 런타임을 사용할 수 있고 지원되는 경우, 설치 프로그램은 지원되는 런타임 중 가장 최신 런타임에 해당하는 휠을 자동으로 선택합니다. 그렇지 않으면 CPU 전용 변형으로 대체합니다. 여러 가속기를 사용할 수 있고 지원되는 특수한 경우에는 PyTorch 패키지 유지 관리자가 기본적으로 어느 가속기를 우선할지 지정합니다.

최적화된 CPU 변형

휠 변형을 사용하면 현재 플랫폼 태그가 제공하는 범위를 넘어 특정 CPU 확장이 필요한 변형을 제공할 수 있습니다. 런타임 디스패칭이 실용적이지 않거나, 패키지가 기준선보다 높은 명령어를 사용하는 사전 빌드 구성 요소에 의존하거나, 명령어 집합의 가용성이 라이브러리 ABI 변경을 의미하거나, 단순히 코드베이스 전체에 적용되는 자동 벡터화와 같은 컴파일러 최적화의 이점을 얻으려는 경우에 특히 유용합니다.

예를 들어 x86-64 CPU 플러그인은 설치된 CPU의 기능을 감지하여 이를 적절한 x86-64 아키텍처 수준과 확장 명령어 집합에 매핑할 수 있습니다. 변형 휠은 어떤 수준 및/또는 명령어 집합이 필요한지를 나타냅니다. 설치 프로그램은 요구 사항을 충족하지 않는 변형을 걸러 내고 가장 잘 최적화된 변형을 선택합니다. 지원되는 경우, 비변형 휠을 아키텍처 기준선으로 나타내는 데 사용할 수 있습니다.

휠 변형을 사용하여 구현하면 필요한 명령어 집합을 세밀하게 표시할 수 있으며, 플러그인은 필요한 만큼 자주 업데이트할 수 있습니다. 특히 처음부터 사용 가능한 모든 명령어 집합을 포괄할 필요도 없고, 명령어 집합 지원 범위를 개선해야 할 때마다 설치 프로그램을 업데이트할 필요도 없습니다.

BLAS / LAPACK 변형

NumPySciPy같은 패키지는 서로 다른 BLAS / LAPACK 라이브러리를 사용하여 빌드할 수 있습니다. 사용자는 특정 하드웨어에서 성능을 향상하거나 라이선스 관련 고려 사항에 따라 특정 라이브러리를 선택할 수 있습니다. 또한 라이브러리마다 서로 다른 OpenMP 구현을 사용할 수 있지만, 스택 전체에서 일관된 구현을 사용하면 너무 많은 스레드를 생성하여 성능이 저하되는 것을 방지할 수 있습니다.

특정 플랫폼용으로 빌드된 모든 변형이 해당 플랫폼과 호환되므로 BLAS / LAPACK 변형은 설치 시 플러그인이 필요하지 않습니다. 따라서 미리 정의된 BLAS / LAPACK 라이브러리 이름 집합을 제공하는 선행 제공자(install-time = false인)를 사용할 수 있습니다. 패키지가 설치되면 일반적으로 기본 변형이 사용되지만, 사용자가 다른 변형을 명시적으로 선택할 수 있습니다.

디버그 패키지 변형

패키지는 일반 릴리스 빌드와 더불어 디버깅 또는 CI 목적으로 특수한 디버그 활성화 빌드를 제공할 수 있습니다. 이를 위해 디버그 빌드의 사용자 지정 속성을 정의하는 선택적 선행 제공자(install-time = false이고 optional = true인)를 사용할 수 있습니다. 제공자는 기본적으로 비활성화되어 있으므로 사용자는 일반적으로 릴리스 빌드를 제공하는 비변형 휠을 설치합니다. 그러나 선택적 제공자를 활성화하거나 변형을 명시적으로 선택하면 디버그 빌드를 쉽게 얻을 수 있습니다.

패키지 ABI 매칭

vLLM같은 패키지는 애플리케이션 바이너리 인터페이스(Application Binary Interface, ABI) 호환성을 유지하기 위해 해당 패키지가 빌드된 PyTorch 버전에 고정되어야 합니다. 이로 인해 패키지 버전의 고정이 불필요하게 엄격해지는 경우가 많으며, 서로 다른 PyTorch 버전을 요구하는 여러 패키지가 포함된 환경에서 만족스러운 해결책을 찾지 못하거나 소스 빌드를 사용하게 됩니다. 변형 휠을 사용하여 서로 다른 PyTorch 버전에 맞춰 빌드된 vLLM 변형을 게시할 수 있으므로, 업스트림에서 여러 버전을 동시에 쉽게 지원할 수 있습니다.

선택적 abi_dependency 확장을 사용하면 서로 다른 PyTorch 버전에 고정된 여러 vllm 변형을 빌드할 수 있습니다. 예를 들면 다음과 같습니다.

  • vllm-0.11.0-...-torch29.wheelabi_dependency :: torch :: 2.9
  • vllm-0.11.0-...-torch28.wheelabi_dependency :: torch :: 2.8
  • vllm-0.11.0-...-torch27.wheelabi_dependency :: torch :: 2.7

보안 관련 영향

이 제안은 변형 휠의 기능을 확인하기 위해 시스템 기능을 조회하는 플러그인 시스템을 도입합니다. 이 시스템에서는 패키지 색인 메타데이터에 플러그인을 제공하는 추가 Python 패키지를 지정할 수 있습니다. 특정 휠을 설치할 수 있는지 판단하거나 여러 변형 휠 중 가장 선호되는 변형을 선택해야 하는 설치 프로그램 및 기타 도구는 종속성을 해결하거나 휠을 처리하는 동안 이러한 패키지를 설치하고 그 안의 코드를 실행해야 할 수 있습니다.

이로 인해 악의적인 행위자가 임의의 코드 페이로드를 주입할 수 있는 새로운 두 지점을 도입하게 되어 공급망 공격 가능성이 높아집니다.

  1. 변형 제공자 플러그인 또는 해당 종속성 중 하나를 악성 코드와 함께 게시합니다.
  2. 기존 패키지 메타데이터에 악성 변형 제공자 플러그인을 도입합니다.

이러한 공격은 이미 패키지 의존성 수준에서 가능하지만, 일부 시나리오에서는 영향을 받는 도구가 상승된 권한으로 실행된다는 점을 강조해야 합니다. 예를 들어 다중 사용자 시스템에 패키지를 설치할 때가 그러하며, 설치된 패키지는 이후 일반 사용자 권한으로만 사용됩니다. 따라서 변형 제공자 플러그인은 상승된 권한으로 원격 코드 실행(Remote Code Execution) 취약점을 유발할 수 있습니다.

패키징 생태계에는 소스 배포판에서 패키지를 설치할 때 이미 이와 유사한 문제가 존재하며, 이 과정에서 빌드 백엔드와 기타 빌드 의존성이 설치되고 실행됩니다. 그러나 휠만을 대상으로 작동하는 다양한 도구와 소스 배포판 사용을 비활성화하는 도구별 옵션을 사용하는 사용자들은 의존성을 확인하거나 휠을 설치하거나 그 밖의 방식으로 처리하는 동안 시스템 외부의 코드가 실행되지 않는다는 가정에 의존해 왔습니다. 이 가정을 유지하기 위해 제안에서는 신뢰할 수 없는 제공자 플러그인 패키지를 사용자의 명시적인 동의 없이 절대 설치하지 않도록 명시적으로 요구합니다.

사양의 Providers 섹션에서는 보안과 사용자 경험을 모두 개선하기 위한 추가 제안을 제공합니다. 특히 가장 인기 있는 제공자 플러그인은 기본적으로 제공될 것으로 예상되며, 전담 유지 관리자 팀(처음에는 PEP 작성자 중 일부를 포함함)이 해당 플러그인의 보안 위험을 검사하고 안전하게 사용할 수 있는지 심사할 책임을 집니다. 설치 프로그램은 게시된 허용 목록을 사용하거나, 특정 제공자 플러그인 버전을 공급하거나, 이를 재구현하거나, 원하는 대로 Python 라이브러리로 사용할 수 있습니다.

이에 따라 대다수 패키지는 서로 경쟁하는 솔루션을 구현하기보다는 이러한 특정 플러그인에 집중하게 됩니다. 명시적인 옵트인을 요구하는 플러그인은 드물어야 하며, 주로 전문가 사용자에게 영향을 미쳐야 합니다. 이는 변형 사용을 기본적으로 안전하게 만드는 데 중요합니다. 또한 작업 흐름이 자주 중단되면 사용자는 모든 플러그인을 일괄적으로 허용하게 됩니다(보안 피로).

또한 사양에서는 정적 구성을 입력으로 사용하여 플러그인 실행을 완전히 건너뛸 수 있도록 허용합니다.

사양

이 PEP는 Binary distribution format 사양에 대한 확장 집합을 제안합니다. 이 확장을 통해 변형을 인식하는 도구가 설치할 수 있는 추가 휠 변형을 빌드하는 동시에, 이 사양을 구현하지 않는 프로그램에서는 해당 변형을 무시할 수 있습니다.

정의

이 문서에서 “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY” 및 “OPTIONAL”이라는 핵심 단어는 RFC 2119에 설명된 의미로 해석해야 합니다.

확장된 휠 파일 이름

원래 PEP 427에서 정의한 휠 파일 이름 템플릿은 다음과 같이 변경됩니다:

{distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}(-{variant label})?.whl
                                                                             +++++++++++++++++++

이 PEP에서 도입한 확장을 사용하는 휠은 변형 레이블 구성 요소를 반드시 포함해야 합니다. 레이블은 다음 규칙을 반드시 준수해야 합니다.

  • 대문자를 사용하지 않습니다(대소문자를 구분하는 파일 시스템과 구분하지 않는 파일 시스템 간의 문제를 방지하기 위함).
  • 길이는 1~16자입니다.
  • 0-9, a-z, . 또는 _ ASCII 문자만 사용합니다.

이는 다음 정규 표현식과 같습니다: ^[0-9a-z._]{1,16}$.

모든 레이블은 특정 변형 속성 집합에 고유하게 대응해야 하며, 단일 패키지 버전 내에서 동일한 레이블을 사용하는 모든 휠에 대해 그 속성 집합이 동일해야 합니다. 변형 레이블은 사람이 읽을 수 있는 문자열로 휠 빌드 시 지정해야 합니다. null 레이블은 null 변형을 위해 예약되며, 변형 속성이 빈 집합이어야 합니다.

이 사양을 구현하지 않는 설치 프로그램은 색인에서 설치할 때 변형 레이블이 있는 휠을 반드시 무시해야 하며, 그러한 레이블이 없는 휠을 사용할 수 있으면 해당 휠로 대체해야 합니다. 그러한 휠을 사용할 수 없다면 설치 프로그램은 적절한 진단 메시지를 출력해야 하며, 특히 그 결과 이전 패키지 버전을 선택하게 되는 경우에는 경고를 출력하고, 설치할 수 있는 패키지 버전이 없는 경우에는 명확한 오류를 출력해야 합니다.

예시:

  • 비변형 휠: numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl
  • 변형 레이블이 있는 휠: numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64-x86_64_v3.whl

변형 속성

모든 변형 휠은 0개 이상의 변형 속성으로 설명되어야 합니다. 속성이 정확히 0개인 변형 휠은 널 변형을 나타냅니다. 속성은 변형 휠을 빌드할 때 프로젝트의 빌드 백엔드에서 정의한 메커니즘을 사용하여 지정됩니다.

각 변형 속성은 다음 형식으로 직렬화되는 3-튜플로 설명됩니다.

{namespace} :: {feature_name} :: {feature_value}

네임스페이스는 0-9, a-z_ ASCII 문자만으로 구성되어야 합니다(^[a-z0-9_]+$). 이는 단일 변형 제공자에 해당해야 합니다.

기능 이름은 0-9, a-z_ ASCII 문자만으로 구성되어야 합니다(^[a-z0-9_]+$). 이는 해당 네임스페이스의 변형 제공자가 정의한 유효한 기능 이름에 해당해야 합니다.

기능 값은 0-9, a-z, _. ASCII 문자만으로 구성되어야 합니다(^[a-z0-9_.]+$). 이는 해당 기능에 대해 변형 제공자가 정의한 유효한 값에 해당해야 합니다.

제공자 플러그인이 기능을 “다중 값”으로 표시한 경우, 하나의 변형 휠은 동일한 네임스페이스와 기능 이름을 공유하는 여러 속성을 정의할 수 있습니다. 그렇지 않은 경우, 변형 휠 내에서 하나의 네임스페이스와 기능 이름 쌍에 해당하는 값은 둘 이상 존재해서는 안 됩니다.

변형 휠이 시스템과 호환되는 것으로 간주되려면, 해당 휠에 정의된 모든 기능이 호환되는 것으로 판정되어야 합니다. 기능이 호환되려면 해당 기능에 대응하는 값이 하나 이상 호환되어야 합니다.

예시:

# all of the following must be supported
x86_64 :: level :: v3
x86_64 :: avx512_bf16 :: on
nvidia :: cuda_version_lower_bound :: 12.8
# additionally, at least one of the following must be supported
nvidia :: sm_arch :: 120_real
nvidia :: sm_arch :: 110_real

제공자

변형 휠을 설치하거나 해결할 때 설치 프로그램은 변형 제공자에게 질의하여 주어진 휠의 속성이 시스템과 호환되는지 확인하고 Variant ordering을 통해 최상의 변형을 선택해야 합니다. 그러나 설치 프로그램은 확인을 생략하고 지정된 변형을 명시적으로 설치하는 옵션을 제공할 수 있습니다.

제공자는 설치 시점 제공자 또는 사전 제공자로 표시할 수 있습니다. 설치 시점 제공자의 경우, 설치 프로그램은 변형 속성 호환성을 확인하기 위해 제공자에게 질의하거나 사용자가 제공한 호환성 정보를 사용해야 합니다. 설치 프로그램은 특정 제공자를 벤더링하거나 다시 구현할 수 있습니다. 사용자가 제공하는 정보의 형식은 구현에 따라 정의됩니다.

사전 제공자의 경우, 대신 휠에 포함된 정적 메타데이터를 사용해야 합니다.

제공자는 선택 사항으로 표시할 수 있습니다. 제공자가 선택 사항으로 표시된 경우, 설치 프로그램은 기본적으로 해당 제공자에게 질의해서는 안 되며, 대신 해당 제공자의 속성이 호환되지 않는 것으로 간주해야 합니다. 설치 프로그램은 선택 사항인 제공자를 활성화하는 옵션을 제공하는 것이 좋습니다.

제공자는 Environment Markers에 대해 조건부로 지정할 수도 있습니다. 이 경우 설치 프로그램은 휠을 설치할 환경에 대해 마커를 확인해야 합니다. 설치 프로그램은 마커가 일치하지 않는 제공자를 사용해서는 안 되며, 대신 해당 제공자의 속성이 호환되지 않는 것으로 간주해야 합니다.

변형 제공자를 조회해야 하며 보안에 민감한 컨텍스트에서 실행되는 모든 도구는 신뢰할 수 있는 특정 제공자 패키지 버전을 확인할 수 없는 한 제공자 패키지를 설치하거나 실행해서는 안 됩니다. 이를 수행하는 데 사용되는 정확한 메커니즘은 구현에 따라 다릅니다. 그러나 설치 도구는 가장 일반적으로 사용되는 제공자를 명시적인 사용자 옵트인 없이도 안전하게 사용할 수 있도록 보장해야 합니다.

제공자 패키지를 설치할 때 도구는 격리된 가상 환경을 사용해야 합니다.

설치 시점 제공자 패키지는 공급망 공격을 방지하기 위한 조치를 취해야 하며, 예를 들어 모든 의존성을 벤더링해야 합니다.

도구 간에 일관된 경험을 제공하기 위해 변형 휠은 기본적으로 지원되어야 합니다. 도구는 변형이 아닌 휠만 사용하도록 하는 옵션을 제공할 수 있습니다.

변형 메타데이터

이 절에서는 패키지와 해당 휠의 제공자, 변형 및 속성에 대한 메타데이터 형식을 설명합니다. 이 형식은 약간씩 변형되어 다음 세 위치에서 사용됩니다.

  1. 소스 트리의 pyproject.toml 파일 내부
  2. 빌드된 휠에서 *.dist-info/variant.json 파일로
  3. 패키지 색인에서 {name}-{version}-variants.json 파일로

세 변형 메타데이터 파일은 모두 공통된 JSON 호환 구조를 공유합니다.

(root)
|
+- providers
|  +- {namespace}
|     +- enable-if     : str | None = None
|     +- install-time  : bool       = True
|     +- optional      : bool       = False
|     +- plugin-api    : str | None = None
|     +- requires      : list[str]  = []
|
+- default-priorities
|  +- namespace        : list[str]
|  +- feature
|     +- {namespace}   : list[str]  = []
|  +- property
|     +- {namespace}
|        +- {feature}  : list[str]  = []
|
+- static-properties
|  +- {namespace}
|     +- {feature}     : list[str]  = []
|
+- variants
  +- {variant_label}
     +- {namespace}
        +- {feature}   : list[str]  = []

최상위 객체는 포함하는 파일의 특정 지점을 루트로 하는 딕셔너리입니다. 개별 키는 하위 절에서 해당 키의 존재 요건과 함께 설명하는 하위 딕셔너리입니다. 도구는 PEP 업데이트의 향후 호환성을 위해 딕셔너리에서 알 수 없는 키를 반드시 무시해야 합니다. 그러나 사용자는 향후 발생할 수 있는 충돌을 방지하기 위해 지원되지 않는 키를 사용해서는 안 됩니다.

이 PEP의 부록에는 메타데이터 형식의 이해와 검증을 돕기 위한 JSON schema가 포함되어 있습니다. 이 스키마는 변형 메타데이터 사양이 개정될 때마다 업데이트됩니다. 스키마는 부록: 변형 메타데이터용 JSON 스키마에서 사용할 수 있습니다.

궁극적으로 변형 메타데이터 JSON 스키마는 packaging.python.org에서 제공되어야 합니다.

제공자 정보

providers는 딕셔너리이며, 키는 네임스페이스이고 값은 제공자 정보가 포함된 딕셔너리입니다. 이는 변형 제공자를 설치하고 사용하는 방법을 지정합니다. 패키지가 지원하는 모든 변형 네임스페이스에 대해 pyproject.toml에 제공자 정보 딕셔너리를 선언해야 합니다. 특정 휠에서 사용되지 않는 제공자의 데이터까지 포함하여, 이를 variant.json에 있는 그대로 복사해야 합니다.

제공자 정보의 사용법은 ProvidersProvider plugin API 절에서 설명합니다.

제공자 정보 딕셔너리에는 다음 키가 포함될 수 있습니다.

  • enable-if: str: 플러그인을 사용해야 하는 시점을 정의하는 environment marker입니다.
  • install-time: bool: 설치 시점 제공자인지 여부입니다. 기본값은 true입니다. false는 대신 AoT 제공자임을 의미합니다.
  • optional: bool: 프로바이더가 선택 사항인지 여부입니다. 기본값은 false로 설정됩니다. true이면 프로바이더가 선택 사항으로 간주됩니다.
  • plugin-api: str: 플러그인의 API 엔드포인트입니다. 지정된 경우 API endpoint섹션에 설명된 대로 객체 참조여야 합니다. 지정되지 않은 경우 정규화된 패키지 이름에서 모든 -문자를 _로 바꾼 후, requires의 첫 번째 의존성 지정자에서 패키지 이름을 사용합니다.
  • requires: list[str]: 프로바이더 플러그인을 설치하는 데 사용되는 0개 이상의 패키지 의존성 지정자 목록입니다. 의존성 지정자에 환경 마커가 포함된 경우, 플러그인이 설치되는 환경을 기준으로 평가되며 마커가 false로 평가되는 요구 사항은 제외됩니다. 이 경우 모든 가능한 환경에서 적어도 하나의 의존성이 남아 있어야 합니다. 또한 plugin-api가 지정되지 않은 경우 필터링 후 남은 첫 번째 의존성은 항상 동일한 API 엔드포인트로 평가되어야 합니다.

다음 예외를 제외한 모든 필드는 선택 사항입니다.

  1. install-time이 true이면 딕셔너리는 설치 시점 프로바이더를 설명하며, requires키가 반드시 존재하고 하나 이상의 의존성을 지정해야 합니다.
  2. install-time이 false이면 AoT 프로바이더를 설명하며, requires키는 선택 사항입니다. 이 경우:
    1. requires가 제공되고 비어 있지 않으면 프로바이더 딕셔너리는 static-properties를 채우기 위해 빌드 시 조회될 AoT 프로바이더 플러그인을 반드시 참조해야 합니다.
    2. 그렇지 않으면 static-propertiespyproject.toml에 지정해야 합니다.

기본 우선순위

default-priorities딕셔너리는 변형의 순서를 제어합니다. 정확한 알고리즘은 Variant ordering 섹션에 설명되어 있습니다.

여기에는 필수 키가 하나 있습니다.

  • namespace: list[str]: 휠 변형에서 사용되는 모든 네임스페이스를 우선순위가 높은 순서로 정렬한 목록입니다. 이 목록은 providers딕셔너리 키와 동일한 멤버를 가져야 합니다.

다음 선택적 키를 가질 수 있습니다.

  • feature: dict[str, list[str]]: 네임스페이스를 키로 사용하고, 해당 기능 이름을 우선순위 순서로 정렬한 목록을 값으로 사용하는 딕셔너리입니다. 각 목록의 값은 프로바이더 출력의 기본 순서를 재정의합니다. 이 값은 우선순위가 가장 높은 항목부터 가장 낮은 항목 순으로 나열됩니다. 목록에 없는 기능은 목록에 있는 기능보다 우선순위가 낮은 것으로 간주되며, 이들 사이의 상대적 우선순위는 플러그인에서 정의합니다.
  • property: dict[str, dict[str, list[str]]]: 네임스페이스를 최상위 키로, 기능 이름을 두 번째 수준의 키로, 해당 속성 값의 정렬된 목록을 두 번째 수준의 값으로 갖는 중첩 딕셔너리입니다. 목록에 있는 값은 프로바이더 출력의 기본 정렬 순서를 재정의합니다. 높은 우선순위부터 낮은 우선순위 순으로 나열됩니다. 목록에 없는 속성은 목록에 있는 속성보다 우선순위가 낮은 것으로 간주하며, 해당 속성 간의 상대적 우선순위는 플러그인 출력으로 정의됩니다.

정적 속성

static-properties 딕셔너리는 AoT 프로바이더가 지원하는 속성을 지정합니다. 네임스페이스를 최상위 키로, 기능 이름을 두 번째 수준의 키로, 기능 값의 정렬된 목록을 두 번째 수준의 값으로 갖는 중첩 딕셔너리입니다.

pyproject.toml 파일에서 이 딕셔너리에 있는 네임스페이스는 플러그인이 없는 모든 AoT 프로바이더(즉, install-timefalse이고 requires가 없거나 비어 있는 프로바이더)에 대응해야 합니다. 휠을 빌드할 때 빌드 백엔드는 AoT 프로바이더 플러그인(즉, install-timefalse이고 requires가 비어 있지 않은 플러그인)을 조회하여 지원되는 속성을 얻고 이를 딕셔너리에 포함해야 합니다. 따라서 variant.json*-variants.json의 딕셔너리는 모든 AoT 프로바이더(즉, install-timefalse인 모든 프로바이더)의 네임스페이스를 포함해야 합니다.

TOML 및 JSON 딕셔너리는 정렬되지 않으므로 static-properties 딕셔너리의 기능도 정렬되지 않습니다. 네임스페이스에 둘 이상의 기능이 지정된 경우 모든 기능의 순서는 default-priorities.feature.{namespace}에 지정해야 합니다. AoT 플러그인을 사용하여 static-properties를 채우는 경우 pyproject.toml의 목록에 이미 없는 기능을 해당 목록에 추가해야 합니다.

값 목록은 가장 선호되는 값부터 가장 덜 선호되는 값 순으로 정렬되며, 이는 Plugin interface에 정의된 get_supported_configs() 플러그인 API 호출이 반환하는 목록과 같습니다. default-priorities.property 딕셔너리를 사용하여 속성 순서를 재정의할 수 있습니다.

변형

variants 딕셔너리는 variant.json에서 휠이 빌드된 변형을 나타내는 데 사용되며, *-variants.json에서는 사용 가능한 모든 휠 변형을 나타내는 데 사용됩니다. 변형 레이블별로 모든 속성을 나열하는 3수준 딕셔너리입니다. 첫 번째 수준의 키는 변형 레이블이고, 두 번째 수준의 키는 네임스페이스이며, 세 번째 수준의 키는 기능 이름이고, 세 번째 수준의 값은 기능 값의 목록입니다.

pyproject.toml: 변형 프로젝트 수준 데이터 테이블

pyproject.toml 파일은 pyproject.toml specification에 정의된 표준 프로젝트 구성 파일입니다. 변형 메타데이터는 variant라는 최상위 테이블을 루트로 해야 합니다. variants 딕셔너리를 지정해서는 안 됩니다. 빌드 백엔드가 변형 휠을 빌드할 때 사용합니다.

예시 구조:

[variant.default-priorities]
# prefer CPU features over BLAS/LAPACK variants
namespace = ["x86_64", "aarch64", "blas_lapack"]

# prefer aarch64 version and x86_64 level features over other features
# (specific CPU extensions like "sse4.1")
feature.aarch64 = ["version"]
feature.x86_64 = ["level"]

# prefer x86-64-v3 and then older (even if CPU is newer)
property.x86_64.level = ["v3", "v2", "v1"]

[variant.providers.aarch64]
# example using different package based on Python version
requires = [
   "provider-variant-aarch64 >=0.0.1; python_version >= '3.12'",
   "legacy-provider-variant-aarch64 >=0.0.1; python_version < '3.12'",
]
# use only on aarch64/arm machines
enable-if = "platform_machine == 'aarch64' or 'arm' in platform_machine"
plugin-api = "provider_variant_aarch64.plugin:AArch64Plugin"

[variant.providers.x86_64]
requires = ["provider-variant-x86-64 >=0.0.1"]
# use only on x86_64 machines
enable-if = "platform_machine == 'x86_64' or platform_machine == 'AMD64'"
plugin-api = "provider_variant_x86_64.plugin:X8664Plugin"

[variant.providers.blas_lapack]
# plugin-api inferred from requires
requires = ["blas-lapack-variant-provider"]
# plugin used only when building package, properties will be inlined
# into variant.json
install-time = false

*.dist-info/variant.json: 패키징된 변형 메타데이터 파일

variant.json 파일은 빌드된 변형 휠의 *.dist-info/ 디렉터리에 있어야 합니다. 변형 메타데이터 딕셔너리를 최상위 객체로 하여 JSON으로 직렬화됩니다. pyproject.toml에 있는 모든 변형 메타데이터를 개별 키 섹션에 지정된 대로 복사하여 포함해야 합니다. 그에 더하여 다음을 포함해야 합니다.

  • 이 PEP의 부록에 제공된 스키마 파일에 해당하는 URL을 값으로 갖는 $schema 키 URL에는 형식의 버전이 포함되며, 이후 형식이 변경될 때마다 부록에 새 버전을 추가해야 합니다,
  • 휠이 제공하는 변형 하나만 정확히 나열하는 variants 객체

x86-64-v3용 예제 pyproject.toml 파일에서 빌드된 휠에 해당하는 variant.json 파일은 다음과 같은 형태입니다:

{
  // The schema URL will be replaced with the final URL on packaging.python.org
  "$schema": "https://variants-schema.wheelnext.dev/v0.0.3.json",
  "default-priorities": {
     "feature": {
        "aarch64": ["version"],
        "x86_64": ["level"]
     },
     "namespace": ["x86_64", "aarch64", "blas_lapack"],
     "property": {
        "x86_64": {
           "level": ["v3", "v2", "v1"]
        }
     }
  },
  "providers": {
     "aarch64": {
        "enable-if": "platform_machine == 'aarch64' or 'arm' in platform_machine",
        "plugin-api": "provider_variant_aarch64.plugin:AArch64Plugin",
        "requires": [
           "provider-variant-aarch64 >=0.0.1; python_version >= '3.12'",
           "legacy-provider-variant-aarch64 >=0.0.1; python_version < '3.12'"
        ]
     },
     "blas_lapack": {
        "install-time": false,
        "requires": ["blas-lapack-variant-provider"]
     },
     "x86_64": {
        "enable-if": "platform_machine == 'x86_64' or platform_machine == 'AMD64'",
        "plugin-api": "provider_variant_x86_64.plugin:X8664Plugin",
        "requires": ["provider-variant-x86-64 >=0.0.1"]
     }
  },
  "static-properties": {
     "blas_lapack": {
        "provider": ["accelerate", "openblas", "mkl"]
     },
  },
  "variants": {
     // always a single entry, expressing the variant properties of the wheel
     "x8664v3_openblas": {
        "blas_lapack": {
           "provider": ["openblas"]
        },
        "x86_64": {
           "level": ["v3"]
        }
     }
  }
}

{name}-{version}-variants.json: 색인 수준 변형 메타데이터 파일

변형 휠을 하나 이상 포함하는 모든 패키지 버전에 대해, 패키지 색인에서 호스팅하고 제공하는 해당 {name}-{version}-variants.json 파일이 반드시 존재해야 합니다. {name}{version} 자리 표시자는 패키지 이름과 버전에 해당하며, Binary Distribution Format 사양의 File name convention에 명시된 휠 파일과 동일한 규칙에 따라 정규화됩니다. 변형 휠 링크가 연결된 모든 색인 페이지에는 이 파일에 대한 링크가 반드시 있어야 합니다. 이 링크는 해시(선택 사항)를 포함하여 색인의 소스 배포 및 휠 링크와 동일한 단순 저장소 형식으로 표시됩니다.

이 파일은 앞에서 설명한 variant.json과 동일한 구조를 사용하지만, variants 객체에는 해당 패키지 버전에 대해 패키지 색인에서 사용할 수 있는 모든 변형이 반드시 나열되어야 합니다. 도구는 파일에 나열된 모든 변형에 대해 default-priorities, providersstatic-properties 섹션의 내용이 동일하도록 적용하는 것이 권장되지만, 상충하는 정보가 도입되지 않고 변형의 일부 집합 내에서 해결 결과가 변경되지 않는 한 신중한 병합도 가능합니다.

색인은 업로드된 휠 메타데이터에서 색인 수준 변형 메타데이터 파일을 자동으로 생성할 수 있습니다. 그런 경우 파일이 최종 상태가 될 때까지 게시하지 않아야 하며, 일단 게시한 후에는 클라이언트가 이를 캐시할 수 있으므로 변경하지 않아야 합니다.

파일이 자동으로 생성되지 않는 경우, 색인은 패키지 유지 관리자가 파일을 업로드할 수 있도록 허용해야 합니다. 변형 수준 메타데이터 파일이 업로드된 후에는 패키지 유지 관리자가 해당 버전에 대한 새 변형을 업로드하지 않아야 합니다.

앞의 예제에 나열된 변형 하나를 포함하여 두 개의 휠 변형이 있는 패키지에 해당하는 foo-1.2.3-variants.json 파일은 다음과 같은 형태입니다:

{
  // The schema URL will be replaced with the final URL on packaging.python.org
  "$schema": "https://variants-schema.wheelnext.dev/v0.0.3.json",
  "default-priorities": {
     // identical to above
  },
  "providers": {
     // identical to above
  },
  "static-properties": {
     // identical to above
  },
  "variants": {
     // all available wheel variants
     "x8664v3_openblas": {
        "blas_lapack": {
           "provider": ["openblas"]
        },
        "x86_64": {
           "level": ["v3"]
        }
     },
     "x8664v4_mkl": {
        "blas_lapack": {
           "provider": ["mkl"]
        },
        "x86_64": {
           "level": ["v4"]
        }
     }
 }
}

변형 순서 지정

여러 휠이 호환될 때 설치할 변형 휠을 결정하려면, 변형 휠을 해당 변형 속성에 따라 반드시 정렬해야 합니다.

순서를 지정할 때 변형 속성은 기능으로 그룹화되고, 기능은 네임스페이스로 그룹화됩니다. 순서는 다음 알고리즘과 동등해야 합니다:

  1. default-priorities.namespace 키의 값을 복사하여 네임스페이스의 순서 목록을 구성합니다.
  2. 각 네임스페이스에 대해:
    1. 기능 이름의 초기 순서 목록을 구성할 때 다음 값을 복사합니다. 해당 default-priorities.feature.{namespace} 키의 값을 사용합니다.
    2. 제공자로부터 지원되는 기능 이름을 순서대로 가져옵니다. 구성된 목록에 없는 모든 기능 이름을 목록 끝에 추가합니다.

    이 단계가 끝나면 각 네임스페이스에 대해 순서가 지정된 기능 이름 목록을 사용할 수 있습니다.

  3. 각 기능에 대해:
    1. 값의 초기 순서 목록을 구성할 때 다음 값을 복사합니다. 해당 default-priorities.property.{namespace}.{feature_name} 키의 값을 사용합니다.
    2. 지원되는 값을 제공자로부터 순서대로 가져옵니다. 다음의 경우: 구성된 목록에 없는 모든 값은 끝에 추가합니다.

    이 단계가 끝나면 모든 기능에 대해 순서가 지정된 속성 값 목록을 사용할 수 있습니다.

  4. 호환 가능한 변형 휠 중 하나 이상에 있는 모든 변형 속성에 대해, 해당 네임스페이스, 기능 이름 및 각 순서가 지정된 목록에서의 기능 값 인덱스로 구성된 3-튜플인 정렬 키를 생성합니다.
  5. 호환 가능한 각 변형 휠의 속성을 정렬 키에 따라 오름차순으로 정렬합니다.
  6. 변형 휠을 정렬하려면 해당 휠의 정렬된 속성을 비교합니다. 첫 번째 위치의 속성이 서로 다르면 해당 속성의 3-튜플이 더 작은 변형을 먼저 정렬합니다. 서로 같으면 두 번째 위치의 속성을 비교하고, 동률을 판별할 수 있거나 한 휠의 속성 목록이 소진될 때까지 이 과정을 계속합니다. 후자의 경우에는 속성이 더 많은 변형을 먼저 정렬합니다.

이 과정이 끝나면 변형 휠은 가장 선호되는 것부터 가장 덜 선호되는 것까지 정렬됩니다. 널 변형은 자연스럽게 다른 모든 변형 뒤에 정렬되며, 비변형 휠은 널 변형 뒤에 반드시 정렬되어야 합니다. 동일한 변형 집합을 가진 여러 휠과 여러 비변형 휠은 그다음 플랫폼 호환성 태그에 따라 반드시 정렬되어야 합니다.

또는 변형 휠의 정렬 알고리즘은 다음 의사 코드로 설명할 수 있습니다. 단순화를 위해 이 코드는 비변형 휠이나 태그를 고려하지 않습니다.

from typing import Self


def get_supported_feature_names(namespace: str) -> list[str]:
    """Get feature names from plugin's get_supported_configs()"""
    ...


def get_supported_feature_values(namespace: str, feature_name: str) -> list[str]:
    """Get feature values from plugin's get_supported_configs()"""
    ...


# default-priorities dict from variant metadata
default_priorities = {
    "namespace": [...],  # : list[str]
    "feature": {...},    # : dict[str, list[str]]
    "property": {...},   # : dict[str, dict[str, list[str]]]
}


# 1. Construct the ordered list of namespaces.
namespace_order = default_priorities["namespace"]
feature_order = {}
value_order = {}

for namespace in namespace_order:
    # 2. Construct the ordered lists of feature names.
    feature_order[namespace] = default_priorities["feature"].get(namespace, [])
    for feature_name in get_supported_feature_names(namespace):
        if feature_name not in feature_order[namespace]:
            feature_order[namespace].append(feature_name)

   value_order[namespace] = {}
   for feature_name in feature_order[namespace]:
        # 3. Construct the ordered lists of feature values.
        value_order[namespace][feature_name] = (
            default_priorities["property"].get(namespace, {}).get(feature_name, [])
        )
        for feature_value in get_supported_feature_values(namespace, feature_name):
            if feature_value not in value_order[namespace][feature_name]:
                value_order[namespace][feature_name].append(feature_value)


def property_key(prop: tuple[str, str, str]) -> tuple[int, int, int]:
    """Construct a sort key for variant property (akin to step 4.)"""
    namespace, feature_name, feature_value = prop
    return (
        namespace_order.index(namespace),
        feature_order[namespace].index(feature_name),
        value_order[namespace][feature_name].index(feature_value),
    )


class VariantWheel:
    """Example class exposing properties of a variant wheel"""
    properties: list[tuple[str, str, str]]

    def __lt__(self: Self, other: Self) -> bool:
        """Variant comparison function for sorting (akin to step 6.)"""
        for self_prop, other_prop in zip(self.properties, other.properties):
            if self_prop != other_prop:
                return property_key(self_prop) < property_key(other_prop)
        return len(self.properties) > len(other.properties)


# A list of variant wheels to sort.
wheels: list[VariantWheel] = [...]


for wheel in wheels:
    # 5. Order variant wheel properties by their sort keys.
    wheel.properties.sort(key=property_key)
# 6. Order variant wheels by comparing their sorted properties
# (see VariantWheel.__lt__())
wheels.sort()

pylock.toml과의 통합

다음 섹션이 pylock.toml Specification에 추가됩니다:

.. _pylock-packages-variants-json:

``[packages.variants-json]``
----------------------------

- **Type**: table
- **Required?**: no; requires that :ref:`pylock-packages-wheels` is used,
 mutually-exclusive with :ref:`pylock-packages-vcs`,
 :ref:`pylock-packages-directory`, and :ref:`pylock-packages-archive`.
- **Inspiration**: uv_
- The URL or path to the ``variants.json`` file.
- Only used if the project uses :ref:`wheel variants <wheel-variants>`.

.. _pylock-packages-variants-json-url:

``packages.variants-json.url``
''''''''''''''''''''''''''''''

See :ref:`pylock-packages-archive-url`.

.. _pylock-packages-variants-json-path:

``packages.variants-json.path``
'''''''''''''''''''''''''''''''

See :ref:`pylock-packages-archive-path`.

.. _pylock-packages-variants-json-hashes:

``packages.variants-json.hashes``
'''''''''''''''''''''''''''''''''

See :ref:`pylock-packages-archive-hashes`.

[packages.variants-json] 섹션이 있으면 설치 프로그램은 최적의 휠 파일을 선택하도록 변형을 해결해야 합니다.

제공자 플러그인 API

상위 수준 설계

모든 제공자 플러그인은 단일 네임스페이스 내에서 반드시 작동해야 합니다. 이 네임스페이스는 모든 플러그인 관련 작업의 고유 키로 사용됩니다. 플러그인이 정의하는 모든 속성은 플러그인의 네임스페이스 내에 속하며, 플러그인은 해당 네임스페이스 내에서 유효한 모든 기능 이름과 값을 정의합니다.

제공자 플러그인 작성자는 자신이 나타내는 프로젝트와 명확하게 연관 지을 수 있는 네임스페이스를 선택하고, 향후 이름 충돌을 일으킬 수 있는 다른 프로젝트를 가리키는 네임스페이스나 일반적인 용어는 피해야 합니다.

특정 패키지 버전에 대해 단일 색인에 게시된 모든 변형은 주어진 네임스페이스에 대해 반드시 동일한 제공자를 사용해야 합니다. 동일한 릴리스 버전에서 동일한 네임스페이스에 대해 둘 이상의 플러그인을 로드하려고 시도하면 치명적 오류가 발생해야 합니다. 유지 관리되지 않아 플러그인이 포크되는 경우처럼 서로 다른 패키지나 릴리스 버전에 걸쳐 동일한 네임스페이스에 대한 여러 플러그인이 존재할 수는 있지만, 단일 릴리스 버전 내에서는 상호 배타적입니다.

플러그인을 더 쉽게 검색하고 설치할 수 있도록 플러그인은 해당 플러그인을 사용하는 패키지가 게시되는 동일한 색인에 게시하는 것이 좋습니다. 특히 PyPI에 게시되는 패키지는 다른 색인에서 설치해야 하는 플러그인에 의존해서는 안 됩니다.

이 PEP의 일부로 예약된 네임스페이스를 제외하고, 플러그인용 설치 가능한 Python 패키지를 제공해야 합니다. 그러나 Providers 섹션에서 언급한 것처럼, 이러한 플러그인은 이를 필요로 하는 도구에서 다시 구현할 수도 있습니다. 후자의 경우 결과로 생성되는 재구현은 이 섹션에서 정의한 API를 따를 필요가 없습니다.

플러그인 패키지는 격리된 환경에서 실행될 수 있습니다. 플러그인은 설치된 패키지를 기반으로 결정을 내려서는 안 됩니다.

Python 패키지로 구현된 플러그인은 지정된 API 엔드포인트에서 두 종류의 객체를 노출합니다:

  1. 다음을 통해 액세스한 후 특정 값을 반환하는 속성:
    {API endpoint}.{attribute name}
    
  2. 다음을 통해 호출되는 호출 가능 객체:
    {API endpoint}.{callable name}({arguments}...)
    

이러한 객체는 모듈로 구현하거나, 클래스 메서드 또는 정적 메서드를 포함하는 클래스로 구현할 수 있습니다. 자세한 내용은 다음 섹션에서 제공합니다.

API 엔드포인트

플러그인 코드의 위치를 “API 엔드포인트”라고 하며, 다음 Entry points specification에 따른 객체 참조 표기법을 사용하여 표현합니다:

{import_path}(:{object_path})?

API 엔드포인트 사양은 다음 Python 의사 코드와 동등합니다:

import {import_path}

if "{object_path}":
    plugin = {import_path}.{object_path}
else:
    plugin = {import_path}

API 엔드포인트는 두 가지 컨텍스트에서 사용됩니다:

  1. variant 메타데이터의 plugin-api 키에서 명시적으로 또는 requires 키의 패키지 이름에서 추론하여 사용됩니다. 이는 휠을 빌드하고 설치할 때 플러그인을 사용하는 기본 방법입니다.
  2. variant_plugins 그룹에 설치된 엔트리 포인트의 값으로 사용됩니다. 해당 엔트리 포인트의 이름은 중요하지 않습니다. 이 방법은 선택 사항이지만 권장됩니다. 이를 통해 variant 관련 유틸리티가 사용자의 환경에 설치된 variant 플러그인을 검색할 수 있기 때문입니다. Variant 기능 구성 클래스

variant 기능 구성 클래스는 플러그인 API 함수에서 반환 값으로 사용됩니다.

이 클래스는 하나의 variant 기능과 가능한 값의 목록을 정의합니다. 컨텍스트에 따라 값의 순서가 중요할 수 있습니다. 이 클래스는 다음 프로토콜을 사용하여 정의합니다: 다음 프로토콜을 사용하여 정의합니다.

from abc import abstractmethod
from typing import Protocol


class VariantFeatureConfigType(Protocol):
    @property
    @abstractmethod
    def name(self) -> str:
        """Feature name"""
        raise NotImplementedError

    @property
    @abstractmethod
    def multi_value(self) -> bool:
        """Does this property allow multiple values per variant?"""
        raise NotImplementedError

    @property
    @abstractmethod
    def values(self) -> list[str]:
        """List of values, possibly ordered from most preferred to least"""
        raise NotImplementedError

“variant feature config”는 다음 속성 또는 어트리뷰트를 반드시 제공해야 합니다.

  • 단일 variant 휠 내에서 해당 기능이 여러 값을 가질 수 있는지 지정하는 multi_value: bool입니다.
  • multi_value: bool은 단일 변형 휠 내에서 해당 기능이 여러 대응 값을 가질 수 있는지를 지정합니다. 기능 값을 지정하는 values: list[str]입니다.
  • values: list[str]은 기능 값을 지정합니다. 순서가 중요한 컨텍스트에서는 값을 가장 선호하는 값부터 가장 선호하지 않는 값 순서로 정렬해야 합니다.

모든 기능은 플러그인의 네임스페이스 내에 있는 것으로 해석합니다.

플러그인 인터페이스

플러그인 인터페이스는 다음 프로토콜을 따라야 합니다.

from abc import abstractmethod
from typing import Protocol


class PluginType(Protocol):
    # Note: properties are used here for docstring purposes, these
    # must be actually implemented as attributes.

    @property
    @abstractmethod
    def namespace(self) -> str:
        """The provider namespace"""
        raise NotImplementedError

    @property
    def is_aot_plugin(self) -> bool:
        """Is this plugin valid for `install-time = false`?"""
        return False

    @classmethod
    @abstractmethod
    def get_all_configs(cls) -> list[VariantFeatureConfigType]:
        """Get all valid configs for the plugin"""
        raise NotImplementedError

    @classmethod
    @abstractmethod
    def get_supported_configs(cls) -> list[VariantFeatureConfigType]:
        """Get supported configs for the current system"""
        raise NotImplementedError

플러그인 인터페이스는 다음 속성을 정의해야 합니다.

  • 플러그인의 네임스페이스를 지정하는 namespace: str입니다.
  • 플러그인이 유효한 AoT 플러그인인지 나타내는 is_aot_plugin: bool입니다. 그러한 경우 get_supported_configs()는 항상 get_all_configs()와 동일한 값(순서 제외)을 반환해야 하며, 이 값은 플러그인이 실행 중인 플랫폼과 무관한 고정된 목록이어야 합니다. 지정되지 않은 경우 기본값은 False입니다.

플러그인 인터페이스는 다음 함수를 제공해야 합니다.

  • 플러그인의 네임스페이스 내에서 유효한 모든 변형 기능과 그에 허용된 모든 값을 설명하는 “변형 기능 구성” 목록을 반환하는 get_all_config() -> list[VariantFeatureConfigType]입니다. 여기서 목록의 순서는 중요하지 않습니다. 특정 플러그인 버전은 런타임 조건과 관계없이 항상 동일한 값(순서 제외)을 반환해야 합니다.
  • 이 특정 시스템과 호환되는 플러그인 네임스페이스 내의 변형 기능과 지원되는 해당 값을 설명하는 “변형 기능 구성” 목록을 반환하는 get_supported_configs() -> list[VariantFeatureConfigType]입니다. 변형 기능 및 값 목록은 가장 선호되는 것부터 가장 선호되지 않는 것 순으로 정렬해야 하며, 이는 Variant ordering에 영향을 미칩니다.

get_supported_configs()가 반환하는 값은 get_all_configs()가 반환하는 기능 이름 및 값의 부분집합이어야 합니다(순서 제외).

get_supported_configs()가 반환하는 값은 단일 설치 세션에서 여러 패키지에 걸쳐 캐시할 수 있습니다.

구현 예시

from dataclasses import dataclass


@dataclass
class VariantFeatureConfig:
    name: str
    values: list[str]
    multi_value: bool


# internal -- provided for illustrative purpose
_MAX_VERSION = 4
_ALL_GPUS = ["narf", "poit", "zort"]


def _get_current_version() -> int:
    """Returns currently installed runtime version"""
    ...  # implementation not provided


def _is_gpu_available(codename: str) -> bool:
    """Is specified GPU installed?"""
    ...  # implementation not provided


class MyPlugin:
    namespace = "example"

    # optional, defaults to False
    is_aot_plugin = False

    # all valid properties
    @staticmethod
    def get_all_configs() -> list[VariantFeatureConfig]:
        return [
            VariantFeatureConfig(
                # example :: gpu -- multi-valued, since the package
                # can target multiple GPUs
                name="gpu",
                # [narf, poit, zort]
                values=_ALL_GPUS,
                multi_value=True,
            ),
            VariantFeatureConfig(
                # example :: min_version -- single-valued, since
                # there is always one minimum
                name="min_version",
                # [1, 2, 3, 4] (order doesn't matter)
                values=[str(x) for x in range(1, _MAX_VERSION + 1)],
                multi_value=False,
            ),
        ]

    # properties compatible with the system
    @staticmethod
    def get_supported_configs() -> list[VariantFeatureConfig]:
        current_version = _get_current_version()
        if current_version is None:
            # no runtime found, system not supported at all
            return []

        return [
            VariantFeatureConfig(
                name="min_version",
                # [current, current - 1, ..., 1]
                values=[str(x) for x in range(current_version, 0, -1)],
                multi_value=False,
            ),
            VariantFeatureConfig(
                name="gpu",
                # this may be empty if no GPUs are supported --
                # 'example :: gpu feature' is not supported then;
                # but wheels with no GPU-specific code and only
                # 'example :: min_version' could still be installed
                values=[x for x in _ALL_GPUS if _is_gpu_available(x)],
                multi_value=True,
            ),
        ]

향후 확장

이 사양의 향후 버전과 서드파티 확장은 플러그인 인스턴스에 추가 속성 및 메서드를 도입할 수 있습니다. 구현은 추가 속성을 무시해야 합니다.

최상의 호환성을 위해 모든 비공개 속성에는 우발적인 충돌을 방지하도록 밑줄(_) 문자를 접두사로 사용해야 합니다.

빌드 백엔드

빌드 백엔드는 프런트엔드가 변형 휠을 지원하는지 여부를 판단할 수 없으므로, PEP 517PEP 660후크는 기본적으로 비변형 휠을 빌드해야 합니다. 빌드 백엔드는 변형 빌드를 요청하는 방법을 제공할 수 있습니다. 이 사양은 특정 구성 방식을 정의하지 않습니다.

변형 휠을 빌드할 때 빌드 백엔드는 변형 메타데이터의 정확성을 검증해야 하며, 부적합한 variant.json파일이 포함된 휠을 생성해서는 안 됩니다. 또한 사용자가 요청한 변형 속성이 유효한지 확인하기 위해 제공자를 조회해야 하지만, 이 검증을 건너뛰도록 허용하여 잠재적으로 알려지지 않은 속성이 포함된 변형 휠을 생성할 수도 있습니다.

변형 환경 마커

종속성 사양에 네 가지 새로운 environment markers가 도입됩니다.

  1. 휠 변형이 빌드된 모든 변형 속성의 네임스페이스 집합에 해당하는 variant_namespaces입니다.
  2. 휠 변형이 빌드된 모든 변형 속성의 namespace :: feature 쌍 집합에 해당하는 variant_features입니다.
  3. 휠 변형이 빌드된 모든 변형 속성의 namespace :: feature :: value 튜플 집합에 해당하는 variant_properties입니다.
  4. 휠이 빌드된 정확한 변형 레이블에 해당하는 variant_label입니다. 변형이 아닌 휠의 경우 빈 문자열입니다.

문자열 집합으로 평가되는 마커는 다음과 같이 in 또는 not in 연산자를 사용하여 매칭해야 합니다.

# satisfied by any "foo :: * :: *" property
dep1; "foo" in variant_namespaces
# satisfied by any "foo :: bar :: *" property
dep2; "foo :: bar" in variant_features
# satisfied only by "foo :: bar :: baz" property
dep3; "foo :: bar :: baz" in variant_properties

variant_label 마커는 일반 문자열입니다.

# satisfied by the variant "foobar"
dep4; variant_label == "foobar"
# satisfied by any wheel other other than the null variant
# (including the non-variant wheel)
dep5; variant_label != "null"
# satisfied by the non-variant wheel
dep6; variant_label == ""

구현은 기능과 속성을 매칭할 때 공백의 차이를 무시해야 합니다.

변형 마커 표현식은 현재 제공자 플러그인의 출력이 아니라 설치 중인 휠에 저장된 변형 속성을 기준으로 평가해야 합니다. 변형이 아닌 휠이 선택되었거나 빌드된 경우 모든 변형 마커는 False로 평가됩니다.

ABI 종속성 변형 네임스페이스(선택 사항)

이 절에서는 휠 변형 사양에 대한 선택적 확장을 설명합니다. 이 기능을 구현하기로 선택한 도구는 이 사양을 따라야 합니다. 이 기능을 구현하지 않는 도구는 이를 사용하는 변형을 호환되지 않는 것으로 처리해야 하며, 이러한 휠을 건너뛸 때 사용자에게 알리는 것이 좋습니다.

변형 네임스페이스 abi_dependency는 동일한 버전의 패키지에 대한 서로 다른 빌드가 종속성의 서로 다른 버전 또는 버전 범위와 호환됨을 나타내기 위한 용도로 예약되어 있습니다. 이 네임스페이스는 어떠한 변형 제공자 플러그인에서도 사용해서는 안 되며, providers 메타데이터에 나열해서도 안 되고, 빌드된 휠의 변형 속성에만 나타날 수 있습니다.

이 네임스페이스에서는 호환 가능한 종속성 버전을 나타내기 위해 0개 이상의 속성을 사용할 수 있습니다. 각 속성에서 기능 이름은 반드시 종속성의 정규화된 이름이어야 하며, 값은 Version specifiers 사양에 정의된 공개 버전 식별자의 유효한 릴리스 세그먼트여야 합니다. 값은 최대 세 개의 버전 구성 요소를 포함해야 하며, 이 구성 요소는 =={value}.* 지정자와 동일한 방식으로 설치된 버전과 매칭됩니다. 특히 뒤따르는 0은 더 적은 구성 요소를 가진 버전과 매칭됩니다(예: 2.0은 릴리스 2와 매칭되지만 2.1과는 매칭되지 않습니다). 이는 또한 속성 값이 PEP 440 버전과 다른 의미 체계를 가짐을 뜻하며, 특히 2, 2.0, 2.0.0은 서로 다른 범위를 나타냅니다.

0이 아닌 에포크가 포함된 버전은 지원되지 않습니다.

변형 속성 매칭 규칙
abi_dependency :: torch :: 2 torch==2.*
abi_dependency :: torch :: 2.9 torch==2.9.*
abi_dependency :: torch :: 2.8.0 torch==2.8.0.*

동일한 기능 이름을 가진 여러 변형 속성을 사용하여 다음과 같이 여러 제공 패키지 버전과 호환되는 휠임을 나타낼 수 있습니다.

abi_dependency :: torch :: 2.8.0
abi_dependency :: torch :: 2.9.0

이는 해당 휠이 PyTorch 2.8.0과 2.9.0 모두와 호환됨을 의미합니다.

이를 가르치는 방법

Python 패키지 사용자

Python 패키지 사용자를 위한 주요 정보 출처는 설치 도구 문서여야 하며, 명령줄 인터페이스의 유용한 정보 메시지와 튜토리얼이 이를 보완해야 합니다. 특별한 요구 사항이 없는 사용자는 변형에 관한 특별한 인식을 요구받지 않아야 합니다. 고급 사용자는 다음 사항에 관한 문서가 특히 필요합니다(해당 설치 도구가 이러한 기능을 구현하는 경우).

  • 신뢰할 수 없는 공급자 플러그인을 활성화하는 방법과 그에 따른 보안 영향
  • 공급자 사용을 제어하는 방법, 특히 선택적 공급자를 활성화하고, 원치 않는 플러그인을 비활성화하거나, 일반적으로 변형 사용을 비활성화하는 방법
  • 변형을 명시적으로 선택하는 방법과 변형 선택 프로세스를 제어하는 방법
  • 원격 배포 대상에 대한 변형 선택을 구성하는 방법, 예를 들어 대상에서 생성한 정적 파일을 사용하는 방법

설치 도구 문서는 Python 프로젝트에 특화된 문서, 특히 해당 프로젝트의 설치 지침으로 보완할 수도 있습니다.

일부 패키지 관리자는 변형 휠을 지원하고 일부는 지원하지 않는 전환 기간 동안, 사용자는 특정 기능이 특정 도구에서만 제공될 수 있음을 알아야 합니다.

Python 패키지 유지 관리자

Python 패키지 유지 관리자를 위한 주요 정보 출처는 빌드 백엔드 문서여야 하며, 튜토리얼이 이를 보완해야 합니다. 문서에는 다음 사항이 명시되어야 합니다.

  • pyproject.toml에서 변형 지원을 선언하는 방법
  • 의존성을 지정하기 위해 변형 환경 마커를 사용하는 방법
  • 변형 휠을 빌드하는 방법
  • 로컬 패키지 색인에 변형 휠을 게시하고 *-variants.json 파일을 생성하는 방법

유지 관리자는 공급자 플러그인 문서도 검토해야 합니다. 또한 일반적으로 사용되는 설치 도구가 어떤 공급자 플러그인을 신뢰할 수 있는 것으로 간주하는지 알고, 신뢰할 수 없는 플러그인을 사용할 때의 영향을 인지해야 합니다. 이러한 자료는 변형 휠 게시 방법을 설명하는 일반 문서와 구체적인 사용 사례 예시로 보완할 수도 있습니다.

전환 기간 동안 패키지 유지 관리자는 하위 호환성을 위해 비변형 휠도 계속 게시해야 함을 알아야 합니다.

하위 호환성

기존 설치 도구는 변형 휠을 실수로 설치해서는 절대로 안 됩니다. 변형 휠에는 휠이 사용자의 시스템과 호환되는지 판단하기 위한 추가 로직이 필요하기 때문입니다. 이는 파일 이름 끝에 -{variant label} 구성 요소를 추가하여 휠 파일 이름 확장 을 수행함으로써 달성되며, 결과적으로 일반적인 설치 도구 구현에서는 변형 휠이 거부됩니다. 하위 호환성을 위해 변형 휠에 더해 비변형 휠을 게시할 수 있습니다. 비호환 설치 도구가 지원하는 유일한 휠은 이것이며, 변형 호환 설치 도구에서는 선호도가 가장 낮은 휠입니다.

이러한 명시적인 비호환성을 제외하면, 이 사양은 바이너리 패키지 형식을 최소한으로 변경하며 비침해적인 변경만 적용합니다. Variant metadata.dist-info 디렉터리의 별도 파일에 배치되며, 변형을 고려하지 않는 도구가 이를 보존해야 하므로 필요한 변경 사항은 파일 이름 검증 알고리즘이 있는 경우 이를 업데이트하는 것으로 제한됩니다.

새로운 Variant environment markers를 휠 의존성에 사용하면 해당 휠은 기존 도구와 호환되지 않게 됩니다. 이는 환경 마커 설계에 따른 일반적인 문제이며, 휠 변형에만 국한된 문제는 아닙니다. 빌드 시 환경 마커를 부분적으로 평가하고 비변형 휠에서 변형 휠에 특화된 마커 또는 의존성을 제거하면 이 문제를 우회할 수 있습니다.

Build backends는 기존 프런트엔드와의 하위 호환성을 유지하기 위해 변형이 없는 휠을 생성합니다. 변형 휠은 사용자가 명시적으로 요청한 경우에만 출력할 수 있습니다.

공유 메타데이터를 위한 별도의 *-variants.json 파일을 사용하면, 변형 휠 메타데이터를 특별히 지원하지 않는 색인에서도 변형 휠을 사용할 수 있습니다. 그러나 색인은 확장된 파일 이름 구문과 JSON 파일을 사용하는 휠의 배포를 반드시 허용해야 합니다.

참조 구현

variantlib 프로젝트에는 이 PEP에서 소개한 모든 프로토콜과 알고리즘의 참조 구현과 휠을 변환하고, *-variants.json 색인을 생성하며, 플러그인을 조회하는 명령줄 도구가 포함되어 있습니다.

변형 휠을 설치하기 위한 클라이언트가 uv 브랜치에 구현되어 있습니다.

Wheel Variants 모노레포에는 제공자 플러그인의 예제 구현과 변형 휠 빌드를 지원하도록 수정된 빌드 백엔드 버전, 그리고 변형 휠 사용을 보여 주는 일부 Python 패키지의 수정 버전이 포함되어 있습니다.

거부된 아이디어

변형을 전면 또는 단계적으로 옵트인하기

보안 문제를 논의하는 과정에서 변형 제공자 사용을 영구적으로 또는 추가 테스트를 용이하게 하기 위해 적어도 초기에는 전면적으로 옵트인하도록 하자는 제안이 있었습니다. 이러한 접근 방식은 제공자 코드를 심사할 책임을 지는 주체와 그에 따른 유지 관리 노력 또는 신뢰해야 하는 패키지 및 유지 관리자의 수를 바꿀 수 있지만, 장기적인 해결책으로는 적합하지 않습니다.

가장 중요한 점은 옵트인 메커니즘이 기본 상태에서 훨씬 더 나쁜 사용자 경험을 초래한다는 것입니다. 변형이 활성화된 패키지의 경우 기본적인 사용 경험은 최적이 아니거나 완전히 손상된 변형을 설치하는 것이 될 것입니다. 변형이 활성화된 패키지는 직접 설치될 뿐만 아니라 다른 패키지의 의존성으로 설치될 수도 있다는 점에 유의해야 합니다. 따라서 최적의 사용자 경험을 위해서는 변형이 활성화된 모든 패키지 또는 변형이 활성화된 의존성을 포함하는 모든 패키지가 해당 제공자 플러그인을 활성화하기 위한 적절한 설치 프로그램별 메커니즘을 문서화해야 합니다.

이러한 경험이 확산되면 두 가지 중요한 결과가 발생할 수 있습니다. 사용자가 기본 상태에서 변형을 작동시킬 수 없으면 패키지 유지 관리자가 변형 사용을 포기하고 대신 이전의 우회 방법을 계속 사용하게 될 수 있습니다. 더 나쁜 점은 사용자가 결국 모든 변형 공급자를 무조건 활성화하도록 설치 관리자를 순진하게 구성하게 될 수도 있다는 것입니다. 이렇게 되면 다수의 사용자에게 공급자 사용을 사실상 옵트아웃으로 만들고, Security implications 절에서 설명한 모든 종류의 공급망 공격을 가능하게 할 수 있습니다.

저자들은 사용자 경험을 심각하게 저해하는 대가로 보안을 달성해서는 안 된다는 점을 강조하고자 합니다. 대신 이 PEP는 중앙에서 유지 관리하고 심사하는 신뢰할 수 있는 제공자 풀을 도입하여 균형을 이루고자 합니다.

제공자 플러그인을 사용하지 않는 접근 방식

추가 변형 속성에 대한 지원은 제공자 플러그인을 도입하지 않고 기술적으로 구현할 수도 있지만, 현재 휠 태그가 구현된 방식과 유사하게 사용 가능한 속성과 그 검색 방법을 사양의 일부로 정의하는 방식이 될 수 있습니다. 그러나 기존 휠 태그 로직은 지원되는 태그를 생성하는 로직을 유지 관리해야 하는 패키징 도구에 이미 상당한 복잡성을 부과하고 있으며, 이는 Python 인터프리터 자체가 제공하는 데이터에 의해 일부 상쇄됩니다.

새로운 축이 추가될 때마다 속성 호환성을 결정하는 알고리즘을 유지 관리해야 하는 패키지 관리자 유지 관리자의 부담이 더욱 커질 것입니다. 이 알고리즘은 서로 다른 플랫폼과 하드웨어 버전을 고려해야 할 수 있고 플랫폼 태그용 알고리즘보다 더 자주 업데이트해야 할 수도 있으므로 상당히 복잡해질 수 있습니다. 또한 새로운 축을 추가하는 장벽이 크게 높아지고, 그에 따라 설치 프로그램 간 기능 동등성이 부족해질 위험도 커집니다. 새로운 축이 추가될 때마다 유지 관리 비용이 증가하기 때문입니다.

비교해 보면 플러그인 설계는 본질적으로 변형 속성을 민주화합니다. 제공자 플러그인은 필요한 지식과 하드웨어를 갖춘 사람들이 독립적으로 유지 관리할 수 있습니다. 제공자 플러그인은 패키지 관리자와 독립적으로 필요한 만큼 자주 업데이트할 수 있습니다. 특정 제공자를 사용할지는 전적으로 해당 제공자가 필요한 패키지의 유지 관리자가 결정하지만, 일반 설치 프로그램의 심사를 받지 않은 플러그인을 사용하면 사용자가 불편을 겪게 된다는 점을 고려해야 합니다.

변형을 별도의 패키지로 해결하기

다른 제안으로는 색인에 패키지의 변형을 별도의 프로젝트로 게시하고, 주 패키지는 메타데이터를 통해 다른 변형으로 연결하는 “리졸버” 역할을 하도록 하는 방안이 있었습니다. 예를 들어 torch 패키지가 torch-cpu, torch-cu129 등의 하위 패키지를 사용할 조건을 나타낼 수 있습니다.

이러한 접근 방식은 기존 도구와의 하위 호환성이 더 우수할 수 있습니다. 변경 사항은 설치 프로그램으로 제한되며, 변형 지원 이전의 설치 프로그램을 사용하더라도 사용자는 특정 변형의 설치를 명시적으로 요청할 수 있습니다. 그러나 이는 여러 수준에서 문제를 일으킵니다.

모든 변형마다 새 프로젝트를 만들어야 하므로 torch-cu123과 같은 오래된 프로젝트가 확산될 것입니다. 리졸버 패키지를 사용하면 최신 변형만 사용되도록 보장할 수 있지만, 패키지와 패키지 간 의존성을 수동으로 설치하는 사용자는 실수로 오래된 변형 프로젝트를 고정하거나, 심지어 이름 선점의 피해를 볼 수 있습니다. 이에 비해 변형 휠 제안은 각 프로젝트 버전에 변형의 범위를 한정하고, 프로젝트 관리 담당자만 해당 변형을 업로드할 수 있도록 보장합니다.

또한 의존성 리졸버와 패키지 메타데이터 형식에 상당한 변경이 필요합니다. 특히 의존성 리졸버는 해결을 수행하기 전에 모든 “resolver” 패키지를 조회해야 합니다. 범용 해결을 수행하는 동안 이러한 변형을 어떻게 반영할지는 명확하지 않습니다. 의존성과 설치된 패키지 간의 일대일 매핑이 사라지게 됩니다. torch의존성이 사실상 torch-cu129로 충족될 수 있기 때문입니다.

부록

참고 문헌

감사의 말

Python 패키징 커뮤니티의 많은 분들이 기여하고 의견을 보내 주지 않았다면 이 작업은 불가능했을 것입니다. 특히 다음 분들이 이 PEP를 구체화하는 데 도움을 주셨으며, 이에 감사드립니다(알파벳순).

Alban Desmaison, Bradley Dice, Chris Gottbrath, Dmitry Rogozhkin, Emma Smith, Geoffrey Thomas, Henry Schreiner, Jeff Daily, Jeremy Tanner, Jithun Nair, Keith Kraus, Leo Fang, Mike McCarty, Nikita Shulga, Paul Ganssle, Philip Hyunsu Cho, Robert Maynard, Vyas Ramasubramani, Zanie Blue입니다.

변경 이력

  • 2026년 3월 18일
    • 패키징 도구 유형별로 제안된 구현 로직의 상위 수준 개요와 설치 프로그램 동작을 보여 주는 다이어그램을 추가했습니다.
    • 설치 프로그램에서 제공자를 벤더링하는 방식의 비중을 낮췄습니다. 이는 여전히 구현 선택 사항으로 허용되지만, 더 이상 보안을 향상하기 위한 권장 해결책으로 제시하지 않습니다.
    • 중앙에서 관리되는 허용 목록을 제공자를 기본적으로 활성화하기 위한 주된 해결책으로 삼았습니다. 이러한 허용 목록은 전담 팀이 관리하며, PEP 작성자 중 일부로 시작합니다.
    • 제공자 조회 대신 사용자가 제공한 호환성 정보를 사용할 수 있도록 명세를 명확히 했습니다.
    • 옵트인 메커니즘과 관련된 불필요한 UX 제안을 제거했습니다.
    • 색인 수준의 변형 메타데이터 파일은 색인 자체에서 생성하거나, 색인이 이를 지원하지 않는 경우 패키지 관리 담당자가 업로드할 수 있음을 명확히 했습니다.
    • 색인 수준의 변형 메타데이터 파일이 게시된 후에는 새로운 변형을 도입하지 말라는 권고를 추가했습니다.
    • 변형 제공자 패키지는 격리된 환경에서 실행해야 한다는 명시적인 권고를 추가했습니다.
    • get_supported_configs()가 반환하는 값은 캐시할 수 있음을 명확히 했습니다.
    • 전면적인 옵트인 접근 방식의 위험을 강조했습니다.
    • 어두운 테마를 준수하도록 GROMACS 플롯을 업데이트했습니다.