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>
Python의 기존 휠 패키징 형식은 Platform compatibility tags를 사용하여 특정 휠이 지원하는 환경을 지정합니다. 이러한 태그는 GPU 가속의 사용 가능 여부와 같은 최신 하드웨어 구성 및 그 기능을 표현할 수 없습니다. 또한 서로 다른 종속성 ABI에 맞춰 빌드하는 경우와 같은 사용자 지정 패키지 변형을 제공하지 못합니다. 이러한 한계는 과학 컴퓨팅, 인공지능(AI), 머신 러닝(ML), 고성능 컴퓨팅(HPC) 커뮤니티에서 특히 큰 문제입니다.
이 PEP는 Binary distribution format의 확장인 “휠 변형”을 제안합니다. 이 확장은 패키지 유지 관리자가 동일한 패키지 버전에 대해 여러 빌드 변형을 선언할 수 있게 하면서, 설치 프로그램이 시스템 하드웨어 및 소프트웨어 특성에 따라 가장 적합한 변형을 자동으로 선택할 수 있도록 하는 메커니즘을 도입합니다. 더 구체적으로는 다음을 제안합니다.
하드웨어 또는 소프트웨어 속성에 따라 휠을 구분할 수 있도록 하는 Wheel Variant라는 휠 형식의 발전입니다.
설치 프로그램이 플랫폼 속성을 동적으로 감지하고 가장 적합한 휠을 선택할 수 있도록 하는 variant provider plugin 인터페이스입니다.
목표는 일반적인 설치 명령({tool}install<package>를)을 통해 가장 적합한 휠을 선택하고 최상의 사용자 경험을 제공하는 것입니다.
예를 들어 PyTorch와 같은 패키지는 특정 CUDA 또는 ROCm 버전에 맞춰 빌드해야 하지만, 현재는 해당 정보를 휠 태그에 포함할 수 없습니다. 매우 다른 하드웨어 구성을 대상으로 여러 휠을 빌드해야 하면 유지 관리자는 최적이 아닌 다양한 배포 전략을 사용하게 되며, 해당 패키지에 의존하려는 사용자와 다른 소프트웨어 작성자에게 마찰을 일으킵니다.
기존의 몇 가지 접근 방식은 Current workarounds and their drawbacks 에서 살펴봅니다. 여기에는 서로 다른 하드웨어 구성에 대해 별도의 패키지 색인을 유지 관리하는 방법, 가능한 모든 변형을 상당한 크기의 단일 휠에 묶는 방법, 별도의 패키지 이름(mypackage-gpu, mypackage-cpu, 등)을 사용하는 방법이 포함됩니다. 이러한 접근 방식은 각각 상당한 단점과 잠재적인 보안 영향을 지닙니다.
Python tag: 최소 Python 버전을 인코딩하고 선택적으로 Python 배포판을 제한합니다(예: 모든 Python 3에 해당하는 py3, Python 3.13 이상에 해당하는 py313, CPython 3.13 이상에만 해당하는 cp313).
ABI tag: 확장 모듈에 필요한 Python ABI를 인코딩합니다(예: 요구 사항이 없음을 나타내는 none, CPython 안정 ABI를 나타내는 abi3, CPython 3.13 ABI가 필요한 확장을 나타내는 cp313).
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 팀이 제공하는 수동 설치 명령 선택기와 같이 최적이 아니지만 필요한 우회책을 찾게 되었습니다. 이러한 복잡성은 현재 태그 시스템의 근본적인 확장성 문제를 나타내며, 현재 시스템은 빌드 옵션의 조합적 복잡성을 처리할 만큼 확장 가능하지 않습니다.
NumPy와 같은 프로젝트는 현재 기준 CPU 대상을 위한 휠을 빌드하고 성능이 중요한 루틴에 런타임 디스패칭을 사용하는 방식을 택하고 있습니다. 이러한 해결책은 패키지 유지 관리자의 추가 노력을 필요로 하며, 일반적으로 일부 선택된 함수 외부의 코드가 컴파일러 최적화의 혜택을 받지 못하게 합니다.
비교를 위해 더 높은 CPU 기준을 대상으로 GROMACS_를 빌드하면 상당한 속도 향상이 제공되는 것으로 확인되었습니다.
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%의 속도 향상이 있습니다.
PyTorch 및 RAPIDS와 같은 프로젝트는 현재 사용자 지정 URL을 사용하는 별도 패키지 색인을 통해 “변형”을 근사하는 패키지를 배포하고 있습니다. 여기서는 PyTorch를 예로 사용하지만, 문제와 해결 방법, 사용자에게 미치는 영향은 다른 패키지에도 적용됩니다.
도구는 PyTorch가 로컬 버전 세그먼트를 사용하는 방식에 대한 특별한 처리를 구현해야 합니다. 이러한 요구 사항은 일반적으로 패키지가 설치되는 방식을 깨뜨립니다. PyTorch 설치 문제는 사용자가 매우 흔하게 혼란을 겪는 지점입니다. 이를 수치로 나타내면, 2025-12-05 기준으로 uv의 이슈 추적기의 이슈 8136건 중 552건(6.8%)에 “torch”라는 용어가 포함되어 있습니다.
보안 위험: 이 접근 방식은 안타깝게도 공급망 공격으로 이어졌습니다. 자세한 내용은 PyTorch 블로그를 참조하십시오. 이는 해결하기 쉽지 않은 문제이며, PyTorch 팀은 모든 종속 항목의 완전한 미러를 만들어야 했습니다. 또한 이는 PEP 766의 핵심 동기 중 하나입니다.
구성의 복잡성으로 인해 프로젝트에서 원활한 패키지 업그레이드를 지원하지 않는 임시방편적인 설치 지침을 제공하는 경우가 많습니다.
다른 소프트웨어의 유지 관리자는 사용 가능한 변형 중 하나가 선택된다는 종속성을 표현할 수 없습니다. 특정 변형에 종속되거나, extras를 사용하여 여러 대체 종속성 집합을 제공하거나, 심지어 업스트림 변형에 맞는 여러 패키지 이름을 사용하여 자체 소프트웨어를 배포해야 합니다.
이러한 패키지는 대개 서로 겹치는 파일을 설치합니다. Python 패키징은 두 패키지가 상호 배타적임을 표현하는 기능을 지원하지 않으므로, 설치 도구는 두 패키지를 동일한 환경에 설치할 수 있으며, 나중에 설치된 패키지가 먼저 설치된 패키지의 파일을 덮어쓰게 됩니다. 이로 인해 런타임 오류가 발생하며, 패키지 업그레이드 순서에 따라 변형이 우연히 전환될 가능성도 생깁니다.
이 접근 방식의 추가적인 한계는 현재 여러 패키지 이름에 걸쳐 새 릴리스를 동시에 게시할 수 없다는 점입니다. PEP 694는 단일 패키지 내 여러 휠에 이러한 메커니즘을 추가할 것을 제안하지만, 이를 여러 패키지로 확장하는 것은 목표가 아닙니다.
보안 위험: 접미사가 붙은 변형 패키지가 확산되면 사용자는 다른 패키지에서도 이러한 접미사를 기대하게 되므로, 이름 선점이 훨씬 쉬워집니다. 예를 들어 사용자가 악성 numpy-cuda 패키지를 NumPy의 CUDA 변형이라고 믿게 만들 수 있습니다.
이 글을 작성하는 시점에 CuPy는 이미 서로 다른 이름의 cupy* 패키지 총 55개를 등록했으며, 그중 대부분은 실제로 사용된 적이 없고(Simple API를 사용해야만 표시됨), 나머지 패키지 중 상당수도 더 이상 업데이트되지 않습니다. 이는 문제의 규모와 이름 선점 위험에 대응하기 위해 투입된 노력을 분명히 보여 줍니다.
JAX는 플러그인 기반 접근 방식을 사용합니다. 중앙 jax 패키지는 추가 플러그인을 설치하는 데 사용할 수 있는 여러 엑스트라를 제공하며, 예를 들어 jax[cuda12] 또는 jax[tpu]가 있습니다. 이는 결코 이상적이지 않습니다. pipinstalljax(추가 항목 없음)는 작동하지 않는 설치로 이어지며, 그 결과 Python 생태계에서 기본적으로 기대되는 동작인 의존성 연쇄가 제대로 작동하지 않습니다.
JAX에는 모든 사용 사례를 지원하기 위한 12개의 엑스트라가 포함되어 있으며, 이 중 상당수는 서로 겹치므로 사용자가 문서를 자세히 읽지 않으면 혼란을 일으킬 수 있습니다. 대부분은 기술적으로 상호 배타적이지만, 현재로서는 이를 패키지 메타데이터에 올바르게 표현할 수 없습니다.
가능한 모든 변형을 하나의 휠에 포함하는 것도 또 다른 선택지이지만, 이 경우 아티팩트가 지나치게 커져 대역폭을 낭비하고 특정 변형 하나만 필요한 사용자의 설치 시간이 길어집니다. 경우에 따라 이러한 아티팩트는 PyPI의 크기 제한을 초과하므로 PyPI에서 호스팅할 수 없습니다.
FlashAttention은 PyPI에 휠을 전혀 게시하지 않고, 대신 플랫폼을 감지하고 업스트림 서버에서 적절한 휠을 다운로드한 다음 이를 설치 프로그램에 제공하는 사용자 지정 소스 배포 패키지를 게시합니다. 이 접근 방식은 최적의 변형을 자동으로 선택할 수 있지만, 바이너리 전용 설치가 작동하지 않게 하고, 소스 배포 패키지를 통한 느리고 오류가 발생하기 쉬운 빌드를 필요로 하며, 휠 파일 이름에 연결된 일반적인 캐싱 가정을 깨뜨립니다. 또한 소프트웨어가 실행될 버전과 일치하는 torch 패키지가 포함된 특별히 준비된 빌드 환경이 필요하며, 이를 위해 빌드 격리 없이 빌드해야 합니다. 프로젝트 측에서는 휠을 별도로 호스팅해야 합니다.
보안 위험: 일반적인 소스 빌드와 마찬가지로 이 모델은 설치 시 임의의 코드를 실행해야 합니다. 휠은 패키지 관리자의 통제 밖에서 전적으로 다운로드되므로 공격 표면이 서로 다른 두 개의 휠 다운로드 구현으로 확장되고, 적절한 출처 추적이 방지됩니다.
이러한 패키징의 한계는 성능 최적화가 중요한 과학 컴퓨팅 및 AI/ML 애플리케이션에 특히 큰 영향을 미칩니다.
현재 휠 형식에는 하드웨어 인식 기능이 없으므로 하드웨어 의존적 패키지를 사용할 때 최적이 아닌 경험이 발생합니다. 플러그인은 규모가 작고 범위가 명확한 패키지에 도움이 되지만, 사용자는 현재 일반적인 기본값이나 호환되지 않는 조합을 피하기 위해 올바른 변형(예: jax[cuda13])을 수동으로 식별해야 합니다. 명시적으로 재정의하지 않는 한 pipinstalljax가 사용자의 하드웨어에 맞는 패키지를 자동으로 선택하는 시스템이 필요합니다.
이러한 문제는 패키지 작성자부터 모든 숙련도 수준의 최종 사용자에 이르기까지 학생, 과학자 및 엔지니어를 포함한 모든 사람에게 영향을 미칩니다.
모델을 실행하고 대규모 데이터세트를 처리하기 위한 컴퓨팅 자원에 접근하는 일은 10년 넘게 과학 컴퓨팅의 골칫거리였습니다. 오늘날에도 연구자와 데이터 과학자는 업무를 시작하기 전에 PyTorch와 같은 핵심 도구를 설치하는 데 몇 시간에서 며칠을 소비합니다. 이러한 복잡성은 일상 업무에서 Python을 사용하려는 사용자에게 진입 장벽으로 크게 작용합니다. WheelNext Wheel Variants 제안은 또 다른 새롭고 별도의 해결책을 만들지 않고, 더 넓은 패키징 생태계 안에서 지속되는 설치 및 컴퓨팅 자원 접근 문제를 해결할 방법을 제시합니다. 사용자 경험을 향상한다는 큰 그림에 집중합시다. 이는 실질적인 차이를 만들어 낼 것입니다.
연구 기관과 클라우드 제공업체는 서로 다른 아키텍처(CPU, 하드웨어 가속기, ASIC 등)를 사용하는 이기종 컴퓨팅 클러스터를 관리합니다. 현재 시스템에서는 환경별 설치 절차가 필요하므로 재현 가능한 배포가 어렵습니다. 이러한 상황은 “과학 논문”을 재현하기 어렵게 만드는 데에도 영향을 줍니다. 이를 개선하는 데 주력하는 애플리케이션 작성자들도 패키징의 장애물로 어려움을 겪습니다.
과학자, 엔지니어 및 데이터 분석가를 위한 Python IDE인 Spyder의 패키지 관리자를 세 가지 주요 목표를 두고 개발해 왔습니다. 첫째, 터미널에서 난해한 명령을 입력하는 대신 GUI를 사용하여 사용자가 환경을 만들고 패키지를 설치할 수 있도록 함으로써 사용자의 작업을 더 쉽게 만드는 것입니다. 둘째, 연구 코드를 재현 가능하게 만들어 동료들과 코드 및 그 종속성을 공유할 수 있도록 하는 것입니다. 셋째, 사용자가 번거로움 없이 HPC 클러스터나 클라우드의 머신으로 코드를 옮겨 그곳에서 이용 가능한 방대한 컴퓨팅 자원을 활용할 수 있도록 하는 것입니다. 이 PEP에서 제안하는 개선 사항을 적용하면 모든 PyPI 사용자가 이를 실현할 수 있습니다. uv/pip를 기반으로 구축된 도구에서 적절한 GPU와 명령어 집합에 맞는 널리 사용되는 과학 라이브러리(PyTorch 및 CuPy 등)를 간단하고 투명하게 설치할 수 있기 때문입니다.
최근 현대 AI 워크플로의 발전은 점점 더 GPU 가속에 의존하고 있지만, 현재의 패키징 시스템은 배포를 복잡하게 만들며 전체 도구 스택의 오픈 소스 개발자(빌드 백엔드부터 설치 관리자, 그리고 패키지 유지 관리자까지)에게 상당한 부담을 줍니다.
PyTorch의 광범위한 휠 지원은 언제나 최고 수준이었으며, package selector를 통해 첫 출시 때부터 하드웨어 가속기를 지원했습니다. 이는 PyTorch가 사용자를 위해 별도의 설정 없이 바로 작동하게 만드는 늘 강력한 장점이었다고 생각합니다. 안타깝게도 이를 지원하는 인프라는 매우 복잡하고 유지 관리가 어려우며 비효율적입니다(우리와 사용자 및 패키지 저장소 모두에게 그렇습니다).
지원하는 하드웨어의 수가 다시 빠르게 증가하고 있는 상황에서, PyTorch가 처음 출시된 이후 사용자들이 기대해 온 형태인 pipinstalltorch로 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를 번들로 포함하지 않습니다.)
전문 과학 데이터에 대한 사용자 지정 모델 훈련을 위한 프런트엔드로서 사용하기 쉽다는 점 덕분에 많은 사용자(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
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
해시(변형 메타데이터에서 계산됨)는 파일명 충돌을 방지하며, 실제 변형 선택은 솔버에서 표준 의존성 제약을 통해 이루어집니다. 특별한 메타데이터 파싱은 필요하지 않으며, 설치 프로그램은 다음과 같이 의존성을 단순히 해결합니다.
condainstallpytorchmkl
뮤텍스 메타패키지: Python 메타데이터와 conda 메타데이터에는 “이 패키지는 저 패키지와 충돌합니다”와 같은 개념을 표현할 적절한 방법이 없습니다. 이를 강제하는 주된 메커니즘은 공통 패키지 이름을 공유하는 것이며, 한 번에 특정 이름을 가진 패키지는 하나만 존재할 수 있습니다. 뮤텍스 메타패키지는 이름은 같지만 빌드 문자열이 다른 패키지 집합입니다. 패키지는 서로 다른 의존성 라이브러리를 사용하는 관련 패키지로 인해 발생하는 문제를 피하기 위해 특정 뮤텍스 빌드(예: blas=*=openblas 대 blas=*=mkl)에 의존합니다. 예를 들어 NumPy_가 OpenBLAS를 사용하고 SciPy_가 MKL를 사용하는 경우가 이에 해당합니다.
가상 패키지: 2019년에 도입됨. 가상 패키지는 시스템 감지 정보(CUDA 버전, glibc, CPU 기능)를 솔버 제약 조건으로 주입합니다. 빌드된 패키지는 __cuda>=12.8과 같은 의존성을 표현하며, 설치 프로그램은 설치 시 호환성을 확인합니다. 현재 가상 패키지에는 archspec(CPU 기능), OS/시스템 라이브러리 및 CUDA 드라이버 버전이 포함됩니다. 감지 로직은 도구별로 다릅니다(rattler, mamba).
archspec는 Spack 패키지 관리자를 위해 개발된, CPU 마이크로아키텍처 변형을 감지하고 레이블을 지정하며 추론하는 라이브러리입니다.
변형 모델: CPU 마이크로아키텍처(예: haswell, skylake, zen2, armv8.1a)는 바이너리 호환성을 인코딩하는 유향 비순환 그래프(DAG)를 형성하며, 이를 통해 해결 과정에서 packageB가 packageA에 의존한다는 것을 표현할 수 있습니다. 순서가 부분적인 이유는 (1) 서로 다른 ISA 계열은 비교할 수 없고, (2) 현대적인 설계에는 호환되지 않는 기능 집합이 있을 수 있기 때문입니다. cascadelake와 cannonlake는 둘 다 skylake에서 파생되었음에도 서로 비교할 수 없는데, 각각 고유한 AVX-512 확장을 갖기 때문입니다.
구현: 언어에 종속되지 않는 JSON 데이터베이스가 마이크로아키텍처 메타데이터(기능, 호환성 관계, 컴파일러별 최적화 플래그)를 저장합니다. 언어 바인딩은 감지(/proc/cpuinfo를 조회하고, 호환되는 기능 부분집합이 가장 큰 마이크로아키텍처와 일치시킴) 및 호환성 비교 연산자를 제공합니다.
패키지 관리자 통합: Spack은 대상 마이크로아키텍처를 패키지 출처 정보로 기록하고(spackinstallfftwtarget=broadwell), 컴파일러 플래그를 자동으로 선택하며, 마이크로아키텍처 인식 바이너리 캐싱을 활성화합니다. European Environment for Scientific Software Installations (EESSI)는 마이크로아키텍처별로 별도의 하위 디렉터리(예: x86_64, armv8.1a, haswell)에 최적화된 빌드를 배포합니다. 런타임 초기화는 정확히 일치하는 항목이 없을 때 archspec을 사용하여 가장 호환되는 빌드를 선택합니다.
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 주 버전을 선택합니다.
휠 변형은 기존 휠 태그가 제공하는 것보다 빌드된 휠의 특성을 더 세밀하게 지정합니다. 개별 휠에는 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() 훅에 의해 반환될 수 있으므로, 다른 빌드 의존성과 함께 설치됩니다. 따라서 플러그인 패키지가 의존성을 지나치게 좁게 고정하지 않는 것이 중요합니다. 그렇게 하면 동일한 환경에 서로 다른 패키지가 동시에 설치되지 못할 수 있기 때문입니다.
이 설계의 핵심 요구 사항 중 하나는 이 PEP보다 먼저 만들어진 설치 프로그램이 휠 변형 파일을 무시하도록 보장하는 것입니다. 이를 통해 하나의 색인에 변형 휠과 비변형 휠을 모두 게시할 수 있으며, 변형을 지원하지 않는 설치 프로그램은 전자를 안전하게 무시하고 후자로 대체할 수 있습니다.
변형 레이블 구성 요소는 파일 이름에서 변형 속성 집합으로의 고유한 매핑을 제공하고 변형을 사람이 읽을 수 있도록 식별하기 위한 두 가지 목적으로 파일 이름에 추가됩니다. 레이블은 서로 다른 파일 시스템에서 발생할 수 있는 문제를 피하기 위해 짧고 소문자로 유지됩니다. 기존 파일 이름 검증 알고리즘이 이를 거부하도록 파일 이름 끝에 -로 구분된 구성 요소로 추가됩니다.
빌드 태그와 변형 레이블이 모두 있으면 파일 이름에 구성 요소가 너무 많이 포함됩니다. 예:
변형 속성은 변형의 특성을 표현하는 데 사용됩니다. 플랫폼 호환성 태그와 달리, 변형 속성은 변형 메타데이터에 저장되므로 휠 파일 이름의 길이에 영향을 주지 않습니다. 변형 속성은 계층적 키-값 설계를 따르며, 키는 다시 네임스페이스와 기능 이름으로 나뉩니다. 네임스페이스는 단일 제공자가 정의한 기능을 그룹화하고, 여러 제공자가 같은 이름의 기능을 정의할 경우 충돌을 방지하는 데 사용됩니다. 이를 통해 각 네임스페이스를 독립적으로 관리하고 발전시킬 수 있습니다.
키는 소문자, 숫자 및 밑줄로 제한됩니다. 같은 이름이 서로 다르게 표기되는 것을 방지하기 위해 대문자는 허용되지 않습니다. 값에 허용되는 문자 집합은 버전과 유사한 값을 허용할 수 있도록 더 완화되어 있습니다.
변형 속성은 PEP 301의 Trove 분류자에서 영감을 받은 구조화된 3-튜플 형식으로 직렬화됩니다:
{namespace} :: {feature_name} :: {feature_value}
속성은 변형 휠의 호환성을 판단하고 설치할 최적의 변형을 선택하는 데 모두 사용됩니다. 제공자 플러그인은 어떤 변형 속성이 시스템과 호환되는지 나타내고, 이를 중요도 순으로 정렬합니다. 이 순서는 변형 휠 메타데이터에서 추가로 변경할 수 있습니다.
변형 기능은 하나의 변형 휠 안에 여러 값이 존재하도록 허용하는 것으로 선언할 수 있습니다. 이 경우 해당 값들은 논리적 OR로 매칭되므로, 휠이 지원되는 것으로 간주되려면 단 하나의 값만 시스템과 호환되면 됩니다. 반면 기능들은 논리적 AND로 처리되므로, 모든 기능이 호환되어야 합니다. 이를 통해 완전한 불리언 논리를 구현하지 않고도 변형 호환성을 지정하는 데 어느 정도 유연성을 제공합니다.
일반적으로 변형 기능은 단일 값이며, 최소 요구 사항 또는 상호 배타적인 요구 사항을 나타냅니다. 시스템은 여러 호환 가능한 값을 나타낼 수 있습니다. 예를 들어 기능이 최소 CUDA 런타임 버전을 선언하는 경우, 제공자는 현재 설치된 버전에 해당하거나 그보다 오래된 최소 버전을 요구하는 휠과의 호환성을 나타냅니다. 예를 들어 CUDA 12.8의 경우, 휠에서 사용되는 호환 가능한 최소 버전은 선호도가 높은 순서대로 다음과 같습니다:
마찬가지로 휠은 필요한 최소 CPU 버전을 나타낼 수 있으며, 제공자는 호환 가능한 모든 CPU 버전을 나타냅니다.
다중 값 기능은 하나의 패키지가 서로 호환되지 않는 여러 대상을 지원하는 “fat” 패키지에 유용합니다. 대표적인 예로 GPU가 있습니다. 이 경우 휠은 지원되는 여러 GPU를 선언하고, 제공자는 실제로 설치된 GPU를 나타냅니다(일반적으로 하나입니다). 두 목록 사이에 겹치는 항목이 있으면 휠이 호환됩니다.
널 변형은 속성이 없는 변형 휠이지만, null 변형 레이블과 변형 메타데이터를 가진다는 점에서 비변형 휠과 구별됩니다. 전환 기간 동안 이는 제공된 변형을 지원하지 않는 시스템과 변형 휠을 전혀 지원하지 않는 시스템에 별도의 폴백을 제공할 수 있게 합니다.
예를 들어 선택적 GPU 지원을 제공하는 패키지는 세 종류의 휠을 게시할 수 있습니다:
각각 단일 CUDA 버전과 이에 맞는 지원 GPU 집합을 대상으로 빌드되며, 제공자 플러그인이 시스템과 호환된다고 나타낼 때만 사용되는 여러 GPU 지원 휠입니다.
제공자 플러그인이 호환 가능한 GPU가 설치되지 않았다고 나타낼 때 설치되는, GPU 변형보다 훨씬 작은 CPU 전용 널 변형입니다.
변형을 지원하는 설치 프로그램이 없는 시스템에 설치되는 GPU+CPU 비변형 휠입니다.
널 변형을 게시하는 것은 선택 사항이며, 서로 다른 폴백이 사용자에게 이점을 제공하는 경우에만 의미가 있습니다. 하나를 게시하면, 휠 변형을 지원하는 설치 프로그램은 이를 비변형 휠보다 우선합니다. 게시하지 않으면 대신 비변형 휠로 폴백합니다. 설치 프로그램 플래그로 변형 지원을 명시적으로 비활성화한 경우에도 비변형 휠이 사용됩니다.
변형 휠 메타데이터는 해당 속성에 어떤 제공자가 사용되는지 지정합니다. 제공자는 두 가지 목적을 수행합니다.
설치 시: 어떤 변형 휠이
사용자 시스템과 호환되는지, 그리고 그중 어떤 것이 최선의 선택인지 결정하는 것,
빌드 시: 어떤 변형 속성이
휠을 빌드하는 데 유효한지 결정하는 것.
이 사양은 설치 시 제공자와 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가 자주 변경되면 고정 범위가 매우 좁아지므로, 사용자는 동일한 의존성의 서로 다른 버전에 고정될 수 있는 두 패키지를 설치해야 할 때 문제에 직면합니다. 서로 다른 의존성 버전에 대해 빌드된 변형을 제공하면 의존성 해결자가 설치 중인 모든 패키지와 호환되는 의존성 버전을 찾을 가능성이 높아집니다.
안타깝게도 이러한 변형 제공자는 사양에서 정의한 플러그인 API 내에서 구현할 수 없습니다. 견고한 구현에는 의존성 해결자와의 인터페이스가 필요하므로, 이 사용 사례를 지원하도록 API를 확장하여 결과적으로 상당한 복잡성을 추가하기보다는, 사양에서는 이 기능을 제공하려는 설치 도구가 구현할 수 있는 특별한 변형 네임스페이스로 abi_dependency를 예약합니다.
문제의 복잡성을 고려하여 이 확장은 전적으로 선택 사항으로 제공합니다. 따라서 이를 사용하는 모든 패키지는 변형이 적용되지 않은 휠도 제공해야 합니다.
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 확장이 필요한 변형을 제공할 수 있습니다. 런타임 디스패칭이 실용적이지 않거나, 패키지가 기준선보다 높은 명령어를 사용하는 사전 빌드 구성 요소에 의존하거나, 명령어 집합의 가용성이 라이브러리 ABI 변경을 의미하거나, 단순히 코드베이스 전체에 적용되는 자동 벡터화와 같은 컴파일러 최적화의 이점을 얻으려는 경우에 특히 유용합니다.
예를 들어 x86-64 CPU 플러그인은 설치된 CPU의 기능을 감지하여 이를 적절한 x86-64 아키텍처 수준과 확장 명령어 집합에 매핑할 수 있습니다. 변형 휠은 어떤 수준 및/또는 명령어 집합이 필요한지를 나타냅니다. 설치 프로그램은 요구 사항을 충족하지 않는 변형을 걸러 내고 가장 잘 최적화된 변형을 선택합니다. 지원되는 경우, 비변형 휠을 아키텍처 기준선으로 나타내는 데 사용할 수 있습니다.
휠 변형을 사용하여 구현하면 필요한 명령어 집합을 세밀하게 표시할 수 있으며, 플러그인은 필요한 만큼 자주 업데이트할 수 있습니다. 특히 처음부터 사용 가능한 모든 명령어 집합을 포괄할 필요도 없고, 명령어 집합 지원 범위를 개선해야 할 때마다 설치 프로그램을 업데이트할 필요도 없습니다.
NumPy와 SciPy같은 패키지는 서로 다른 BLAS / LAPACK 라이브러리를 사용하여 빌드할 수 있습니다. 사용자는 특정 하드웨어에서 성능을 향상하거나 라이선스 관련 고려 사항에 따라 특정 라이브러리를 선택할 수 있습니다. 또한 라이브러리마다 서로 다른 OpenMP 구현을 사용할 수 있지만, 스택 전체에서 일관된 구현을 사용하면 너무 많은 스레드를 생성하여 성능이 저하되는 것을 방지할 수 있습니다.
특정 플랫폼용으로 빌드된 모든 변형이 해당 플랫폼과 호환되므로 BLAS / LAPACK 변형은 설치 시 플러그인이 필요하지 않습니다. 따라서 미리 정의된 BLAS / LAPACK 라이브러리 이름 집합을 제공하는 선행 제공자(install-time=false인)를 사용할 수 있습니다. 패키지가 설치되면 일반적으로 기본 변형이 사용되지만, 사용자가 다른 변형을 명시적으로 선택할 수 있습니다.
패키지는 일반 릴리스 빌드와 더불어 디버깅 또는 CI 목적으로 특수한 디버그 활성화 빌드를 제공할 수 있습니다. 이를 위해 디버그 빌드의 사용자 지정 속성을 정의하는 선택적 선행 제공자(install-time=false이고 optional=true인)를 사용할 수 있습니다. 제공자는 기본적으로 비활성화되어 있으므로 사용자는 일반적으로 릴리스 빌드를 제공하는 비변형 휠을 설치합니다. 그러나 선택적 제공자를 활성화하거나 변형을 명시적으로 선택하면 디버그 빌드를 쉽게 얻을 수 있습니다.
vLLM같은 패키지는 애플리케이션 바이너리 인터페이스(Application Binary Interface, ABI) 호환성을 유지하기 위해 해당 패키지가 빌드된 PyTorch 버전에 고정되어야 합니다. 이로 인해 패키지 버전의 고정이 불필요하게 엄격해지는 경우가 많으며, 서로 다른 PyTorch 버전을 요구하는 여러 패키지가 포함된 환경에서 만족스러운 해결책을 찾지 못하거나 소스 빌드를 사용하게 됩니다. 변형 휠을 사용하여 서로 다른 PyTorch 버전에 맞춰 빌드된 vLLM 변형을 게시할 수 있으므로, 업스트림에서 여러 버전을 동시에 쉽게 지원할 수 있습니다.
선택적 abi_dependency 확장을 사용하면 서로 다른 PyTorch 버전에 고정된 여러 vllm 변형을 빌드할 수 있습니다. 예를 들면 다음과 같습니다.
이 제안은 변형 휠의 기능을 확인하기 위해 시스템 기능을 조회하는 플러그인 시스템을 도입합니다. 이 시스템에서는 패키지 색인 메타데이터에 플러그인을 제공하는 추가 Python 패키지를 지정할 수 있습니다. 특정 휠을 설치할 수 있는지 판단하거나 여러 변형 휠 중 가장 선호되는 변형을 선택해야 하는 설치 프로그램 및 기타 도구는 종속성을 해결하거나 휠을 처리하는 동안 이러한 패키지를 설치하고 그 안의 코드를 실행해야 할 수 있습니다.
이로 인해 악의적인 행위자가 임의의 코드 페이로드를 주입할 수 있는 새로운 두 지점을 도입하게 되어 공급망 공격 가능성이 높아집니다.
변형 제공자 플러그인 또는 해당 종속성 중 하나를 악성 코드와 함께 게시합니다.
기존 패키지 메타데이터에 악성 변형 제공자 플러그인을 도입합니다.
이러한 공격은 이미 패키지 의존성 수준에서 가능하지만, 일부 시나리오에서는 영향을 받는 도구가 상승된 권한으로 실행된다는 점을 강조해야 합니다. 예를 들어 다중 사용자 시스템에 패키지를 설치할 때가 그러하며, 설치된 패키지는 이후 일반 사용자 권한으로만 사용됩니다. 따라서 변형 제공자 플러그인은 상승된 권한으로 원격 코드 실행(Remote Code Execution) 취약점을 유발할 수 있습니다.
패키징 생태계에는 소스 배포판에서 패키지를 설치할 때 이미 이와 유사한 문제가 존재하며, 이 과정에서 빌드 백엔드와 기타 빌드 의존성이 설치되고 실행됩니다. 그러나 휠만을 대상으로 작동하는 다양한 도구와 소스 배포판 사용을 비활성화하는 도구별 옵션을 사용하는 사용자들은 의존성을 확인하거나 휠을 설치하거나 그 밖의 방식으로 처리하는 동안 시스템 외부의 코드가 실행되지 않는다는 가정에 의존해 왔습니다. 이 가정을 유지하기 위해 제안에서는 신뢰할 수 없는 제공자 플러그인 패키지를 사용자의 명시적인 동의 없이 절대 설치하지 않도록 명시적으로 요구합니다.
사양의 Providers 섹션에서는 보안과 사용자 경험을 모두 개선하기 위한 추가 제안을 제공합니다. 특히 가장 인기 있는 제공자 플러그인은 기본적으로 제공될 것으로 예상되며, 전담 유지 관리자 팀(처음에는 PEP 작성자 중 일부를 포함함)이 해당 플러그인의 보안 위험을 검사하고 안전하게 사용할 수 있는지 심사할 책임을 집니다. 설치 프로그램은 게시된 허용 목록을 사용하거나, 특정 제공자 플러그인 버전을 공급하거나, 이를 재구현하거나, 원하는 대로 Python 라이브러리로 사용할 수 있습니다.
이에 따라 대다수 패키지는 서로 경쟁하는 솔루션을 구현하기보다는 이러한 특정 플러그인에 집중하게 됩니다. 명시적인 옵트인을 요구하는 플러그인은 드물어야 하며, 주로 전문가 사용자에게 영향을 미쳐야 합니다. 이는 변형 사용을 기본적으로 안전하게 만드는 데 중요합니다. 또한 작업 흐름이 자주 중단되면 사용자는 모든 플러그인을 일괄적으로 허용하게 됩니다(보안 피로).
또한 사양에서는 정적 구성을 입력으로 사용하여 플러그인 실행을 완전히 건너뛸 수 있도록 허용합니다.
이 문서에서 “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY” 및 “OPTIONAL”이라는 핵심 단어는 RFC 2119에 설명된 의미로 해석해야 합니다.
이 PEP에서 도입한 확장을 사용하는 휠은 변형 레이블 구성 요소를 반드시 포함해야 합니다. 레이블은 다음 규칙을 반드시 준수해야 합니다.
대문자를 사용하지 않습니다(대소문자를 구분하는 파일 시스템과 구분하지 않는 파일 시스템 간의 문제를 방지하기 위함).
길이는 1~16자입니다.
0-9, a-z, . 또는 _ ASCII 문자만 사용합니다.
이는 다음 정규 표현식과 같습니다: ^[0-9a-z._]{1,16}$.
모든 레이블은 특정 변형 속성 집합에 고유하게 대응해야 하며, 단일 패키지 버전 내에서 동일한 레이블을 사용하는 모든 휠에 대해 그 속성 집합이 동일해야 합니다. 변형 레이블은 사람이 읽을 수 있는 문자열로 휠 빌드 시 지정해야 합니다. null 레이블은 null 변형을 위해 예약되며, 변형 속성이 빈 집합이어야 합니다.
이 사양을 구현하지 않는 설치 프로그램은 색인에서 설치할 때 변형 레이블이 있는 휠을 반드시 무시해야 하며, 그러한 레이블이 없는 휠을 사용할 수 있으면 해당 휠로 대체해야 합니다. 그러한 휠을 사용할 수 없다면 설치 프로그램은 적절한 진단 메시지를 출력해야 하며, 특히 그 결과 이전 패키지 버전을 선택하게 되는 경우에는 경고를 출력하고, 설치할 수 있는 패키지 버전이 없는 경우에는 명확한 오류를 출력해야 합니다.
모든 변형 휠은 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에 대해 조건부로 지정할 수도 있습니다. 이 경우 설치 프로그램은 휠을 설치할 환경에 대해 마커를 확인해야 합니다. 설치 프로그램은 마커가 일치하지 않는 제공자를 사용해서는 안 되며, 대신 해당 제공자의 속성이 호환되지 않는 것으로 간주해야 합니다.
변형 제공자를 조회해야 하며 보안에 민감한 컨텍스트에서 실행되는 모든 도구는 신뢰할 수 있는 특정 제공자 패키지 버전을 확인할 수 없는 한 제공자 패키지를 설치하거나 실행해서는 안 됩니다. 이를 수행하는 데 사용되는 정확한 메커니즘은 구현에 따라 다릅니다. 그러나 설치 도구는 가장 일반적으로 사용되는 제공자를 명시적인 사용자 옵트인 없이도 안전하게 사용할 수 있도록 보장해야 합니다.
제공자 패키지를 설치할 때 도구는 격리된 가상 환경을 사용해야 합니다.
설치 시점 제공자 패키지는 공급망 공격을 방지하기 위한 조치를 취해야 하며, 예를 들어 모든 의존성을 벤더링해야 합니다.
도구 간에 일관된 경험을 제공하기 위해 변형 휠은 기본적으로 지원되어야 합니다. 도구는 변형이 아닌 휠만 사용하도록 하는 옵션을 제공할 수 있습니다.
최상위 객체는 포함하는 파일의 특정 지점을 루트로 하는 딕셔너리입니다. 개별 키는 하위 절에서 해당 키의 존재 요건과 함께 설명하는 하위 딕셔너리입니다. 도구는 PEP 업데이트의 향후 호환성을 위해 딕셔너리에서 알 수 없는 키를 반드시 무시해야 합니다. 그러나 사용자는 향후 발생할 수 있는 충돌을 방지하기 위해 지원되지 않는 키를 사용해서는 안 됩니다.
이 PEP의 부록에는 메타데이터 형식의 이해와 검증을 돕기 위한 JSON schema가 포함되어 있습니다. 이 스키마는 변형 메타데이터 사양이 개정될 때마다 업데이트됩니다. 스키마는 부록: 변형 메타데이터용 JSON 스키마에서 사용할 수 있습니다.
providers는 딕셔너리이며, 키는 네임스페이스이고 값은 제공자 정보가 포함된 딕셔너리입니다. 이는 변형 제공자를 설치하고 사용하는 방법을 지정합니다. 패키지가 지원하는 모든 변형 네임스페이스에 대해 pyproject.toml에 제공자 정보 딕셔너리를 선언해야 합니다. 특정 휠에서 사용되지 않는 제공자의 데이터까지 포함하여, 이를 variant.json에 있는 그대로 복사해야 합니다.
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 엔드포인트로 평가되어야 합니다.
다음 예외를 제외한 모든 필드는 선택 사항입니다.
install-time이 true이면 딕셔너리는 설치 시점 프로바이더를 설명하며, requires키가 반드시 존재하고 하나 이상의 의존성을 지정해야 합니다.
install-time이 false이면 AoT 프로바이더를 설명하며, requires키는 선택 사항입니다. 이 경우:
requires가 제공되고 비어 있지 않으면 프로바이더 딕셔너리는
static-properties를 채우기 위해 빌드 시 조회될 AoT 프로바이더 플러그인을 반드시 참조해야 합니다.
그렇지 않으면 static-properties를
pyproject.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-time이 false이고 requires가 없거나 비어 있는 프로바이더)에 대응해야 합니다. 휠을 빌드할 때 빌드 백엔드는 AoT 프로바이더 플러그인(즉, install-time이 false이고 requires가 비어 있지 않은 플러그인)을 조회하여 지원되는 속성을 얻고 이를 딕셔너리에 포함해야 합니다. 따라서 variant.json 및 *-variants.json의 딕셔너리는 모든 AoT 프로바이더(즉, install-time이 false인 모든 프로바이더)의 네임스페이스를 포함해야 합니다.
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 specification에 정의된 표준 프로젝트 구성 파일입니다. 변형 메타데이터는 variant라는 최상위 테이블을 루트로 해야 합니다. variants 딕셔너리를 지정해서는 안 됩니다. 빌드 백엔드가 변형 휠을 빌드할 때 사용합니다.
예시 구조:
[variant.default-priorities]# prefer CPU features over BLAS/LAPACK variantsnamespace=["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 versionrequires=["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 machinesenable-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 machinesenable-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 requiresrequires=["blas-lapack-variant-provider"]# plugin used only when building package, properties will be inlined# into variant.jsoninstall-time=false
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} 자리 표시자는 패키지 이름과 버전에 해당하며, Binary Distribution Format 사양의 File name convention에 명시된 휠 파일과 동일한 규칙에 따라 정규화됩니다. 변형 휠 링크가 연결된 모든 색인 페이지에는 이 파일에 대한 링크가 반드시 있어야 합니다. 이 링크는 해시(선택 사항)를 포함하여 색인의 소스 배포 및 휠 링크와 동일한 단순 저장소 형식으로 표시됩니다.
이 파일은 앞에서 설명한 variant.json과 동일한 구조를 사용하지만, variants 객체에는 해당 패키지 버전에 대해 패키지 색인에서 사용할 수 있는 모든 변형이 반드시 나열되어야 합니다. 도구는 파일에 나열된 모든 변형에 대해 default-priorities, providers 및 static-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"]}}}}
여러 휠이 호환될 때 설치할 변형 휠을 결정하려면, 변형 휠을 해당 변형 속성에 따라 반드시 정렬해야 합니다.
순서를 지정할 때 변형 속성은 기능으로 그룹화되고, 기능은 네임스페이스로 그룹화됩니다. 순서는 다음 알고리즘과 동등해야 합니다:
default-priorities.namespace 키의 값을 복사하여 네임스페이스의 순서 목록을 구성합니다.
각 네임스페이스에 대해:
기능 이름의 초기 순서 목록을 구성할 때 다음 값을 복사합니다.
해당 default-priorities.feature.{namespace} 키의 값을 사용합니다.
제공자로부터 지원되는 기능 이름을 순서대로 가져옵니다.
구성된 목록에 없는 모든 기능 이름을 목록 끝에 추가합니다.
이 단계가 끝나면 각 네임스페이스에 대해 순서가 지정된 기능 이름 목록을 사용할 수 있습니다.
각 기능에 대해:
값의 초기 순서 목록을 구성할 때 다음 값을 복사합니다.
해당 default-priorities.property.{namespace}.{feature_name} 키의 값을 사용합니다.
지원되는 값을 제공자로부터 순서대로 가져옵니다. 다음의 경우:
구성된 목록에 없는 모든 값은 끝에 추가합니다.
이 단계가 끝나면 모든 기능에 대해 순서가 지정된 속성 값 목록을 사용할 수 있습니다.
호환 가능한 변형 휠 중 하나 이상에 있는 모든 변형 속성에 대해, 해당 네임스페이스, 기능 이름 및 각 순서가 지정된 목록에서의 기능 값 인덱스로 구성된 3-튜플인 정렬 키를 생성합니다.
호환 가능한 각 변형 휠의 속성을 정렬 키에 따라 오름차순으로 정렬합니다.
변형 휠을 정렬하려면 해당 휠의 정렬된 속성을 비교합니다. 첫 번째 위치의 속성이 서로 다르면 해당 속성의 3-튜플이 더 작은 변형을 먼저 정렬합니다. 서로 같으면 두 번째 위치의 속성을 비교하고, 동률을 판별할 수 있거나 한 휠의 속성 목록이 소진될 때까지 이 과정을 계속합니다. 후자의 경우에는 속성이 더 많은 변형을 먼저 정렬합니다.
이 과정이 끝나면 변형 휠은 가장 선호되는 것부터 가장 덜 선호되는 것까지 정렬됩니다. 널 변형은 자연스럽게 다른 모든 변형 뒤에 정렬되며, 비변형 휠은 널 변형 뒤에 반드시 정렬되어야 합니다. 동일한 변형 집합을 가진 여러 휠과 여러 비변형 휠은 그다음 플랫폼 호환성 태그에 따라 반드시 정렬되어야 합니다.
또는 변형 휠의 정렬 알고리즘은 다음 의사 코드로 설명할 수 있습니다. 단순화를 위해 이 코드는 비변형 휠이나 태그를 고려하지 않습니다.
fromtypingimportSelfdefget_supported_feature_names(namespace:str)->list[str]:"""Get feature names from plugin's get_supported_configs()"""...defget_supported_feature_values(namespace:str,feature_name:str)->list[str]:"""Get feature values from plugin's get_supported_configs()"""...# default-priorities dict from variant metadatadefault_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={}fornamespaceinnamespace_order:# 2. Construct the ordered lists of feature names.feature_order[namespace]=default_priorities["feature"].get(namespace,[])forfeature_nameinget_supported_feature_names(namespace):iffeature_namenotinfeature_order[namespace]:feature_order[namespace].append(feature_name)value_order[namespace]={}forfeature_nameinfeature_order[namespace]:# 3. Construct the ordered lists of feature values.value_order[namespace][feature_name]=(default_priorities["property"].get(namespace,{}).get(feature_name,[]))forfeature_valueinget_supported_feature_values(namespace,feature_name):iffeature_valuenotinvalue_order[namespace][feature_name]:value_order[namespace][feature_name].append(feature_value)defproperty_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=propreturn(namespace_order.index(namespace),feature_order[namespace].index(feature_name),value_order[namespace][feature_name].index(feature_value),)classVariantWheel:"""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.)"""forself_prop,other_propinzip(self.properties,other.properties):ifself_prop!=other_prop:returnproperty_key(self_prop)<property_key(other_prop)returnlen(self.properties)>len(other.properties)# A list of variant wheels to sort.wheels:list[VariantWheel]=[...]forwheelinwheels:# 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-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] 섹션이 있으면 설치 프로그램은 최적의 휠 파일을 선택하도록 변형을 해결해야 합니다.
모든 제공자 플러그인은 단일 네임스페이스 내에서 반드시 작동해야 합니다. 이 네임스페이스는 모든 플러그인 관련 작업의 고유 키로 사용됩니다. 플러그인이 정의하는 모든 속성은 플러그인의 네임스페이스 내에 속하며, 플러그인은 해당 네임스페이스 내에서 유효한 모든 기능 이름과 값을 정의합니다.
제공자 플러그인 작성자는 자신이 나타내는 프로젝트와 명확하게 연관 지을 수 있는 네임스페이스를 선택하고, 향후 이름 충돌을 일으킬 수 있는 다른 프로젝트를 가리키는 네임스페이스나 일반적인 용어는 피해야 합니다.
특정 패키지 버전에 대해 단일 색인에 게시된 모든 변형은 주어진 네임스페이스에 대해 반드시 동일한 제공자를 사용해야 합니다. 동일한 릴리스 버전에서 동일한 네임스페이스에 대해 둘 이상의 플러그인을 로드하려고 시도하면 치명적 오류가 발생해야 합니다. 유지 관리되지 않아 플러그인이 포크되는 경우처럼 서로 다른 패키지나 릴리스 버전에 걸쳐 동일한 네임스페이스에 대한 여러 플러그인이 존재할 수는 있지만, 단일 릴리스 버전 내에서는 상호 배타적입니다.
플러그인을 더 쉽게 검색하고 설치할 수 있도록 플러그인은 해당 플러그인을 사용하는 패키지가 게시되는 동일한 색인에 게시하는 것이 좋습니다. 특히 PyPI에 게시되는 패키지는 다른 색인에서 설치해야 하는 플러그인에 의존해서는 안 됩니다.
이 PEP의 일부로 예약된 네임스페이스를 제외하고, 플러그인용 설치 가능한 Python 패키지를 제공해야 합니다. 그러나 Providers 섹션에서 언급한 것처럼, 이러한 플러그인은 이를 필요로 하는 도구에서 다시 구현할 수도 있습니다. 후자의 경우 결과로 생성되는 재구현은 이 섹션에서 정의한 API를 따를 필요가 없습니다.
플러그인 패키지는 격리된 환경에서 실행될 수 있습니다. 플러그인은 설치된 패키지를 기반으로 결정을 내려서는 안 됩니다.
Python 패키지로 구현된 플러그인은 지정된 API 엔드포인트에서 두 종류의 객체를 노출합니다:
다음을 통해 액세스한 후 특정 값을 반환하는 속성:
{API endpoint}.{attribute name}
다음을 통해 호출되는 호출 가능 객체:
{API endpoint}.{callable name}({arguments}...)
이러한 객체는 모듈로 구현하거나, 클래스 메서드 또는 정적 메서드를 포함하는 클래스로 구현할 수 있습니다. 자세한 내용은 다음 섹션에서 제공합니다.
variant 메타데이터의 plugin-api 키에서 명시적으로 또는
requires 키의 패키지 이름에서 추론하여 사용됩니다. 이는 휠을 빌드하고 설치할 때 플러그인을 사용하는 기본 방법입니다.
variant_plugins 그룹에 설치된 엔트리 포인트의 값으로 사용됩니다.
해당 엔트리 포인트의 이름은 중요하지 않습니다. 이 방법은 선택 사항이지만 권장됩니다. 이를 통해 variant 관련 유틸리티가 사용자의 환경에 설치된 variant 플러그인을 검색할 수 있기 때문입니다. Variant 기능 구성 클래스
이 클래스는 하나의 variant 기능과 가능한 값의 목록을 정의합니다. 컨텍스트에 따라 값의 순서가 중요할 수 있습니다. 이 클래스는 다음 프로토콜을 사용하여 정의합니다: 다음 프로토콜을 사용하여 정의합니다.
fromabcimportabstractmethodfromtypingimportProtocolclassVariantFeatureConfigType(Protocol):@property@abstractmethoddefname(self)->str:"""Feature name"""raiseNotImplementedError@property@abstractmethoddefmulti_value(self)->bool:"""Does this property allow multiple values per variant?"""raiseNotImplementedError@property@abstractmethoddefvalues(self)->list[str]:"""List of values, possibly ordered from most preferred to least"""raiseNotImplementedError
“variant feature config”는 다음 속성 또는 어트리뷰트를 반드시 제공해야 합니다.
단일 variant 휠 내에서 해당 기능이 여러 값을 가질 수 있는지 지정하는 multi_value:bool입니다.
multi_value:bool은 단일 변형 휠 내에서 해당 기능이 여러 대응 값을 가질 수 있는지를 지정합니다. 기능 값을 지정하는 values:list[str]입니다.
values:list[str]은 기능 값을 지정합니다. 순서가 중요한 컨텍스트에서는 값을 가장 선호하는 값부터 가장 선호하지 않는 값 순서로 정렬해야 합니다.
fromabcimportabstractmethodfromtypingimportProtocolclassPluginType(Protocol):# Note: properties are used here for docstring purposes, these# must be actually implemented as attributes.@property@abstractmethoddefnamespace(self)->str:"""The provider namespace"""raiseNotImplementedError@propertydefis_aot_plugin(self)->bool:"""Is this plugin valid for `install-time = false`?"""returnFalse@classmethod@abstractmethoddefget_all_configs(cls)->list[VariantFeatureConfigType]:"""Get all valid configs for the plugin"""raiseNotImplementedError@classmethod@abstractmethoddefget_supported_configs(cls)->list[VariantFeatureConfigType]:"""Get supported configs for the current system"""raiseNotImplementedError
플러그인 인터페이스는 다음 속성을 정의해야 합니다.
플러그인의 네임스페이스를 지정하는 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()가 반환하는 값은 단일 설치 세션에서 여러 패키지에 걸쳐 캐시할 수 있습니다.
fromdataclassesimportdataclass@dataclassclassVariantFeatureConfig:name:strvalues: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 provideddef_is_gpu_available(codename:str)->bool:"""Is specified GPU installed?"""...# implementation not providedclassMyPlugin:namespace="example"# optional, defaults to Falseis_aot_plugin=False# all valid properties@staticmethoddefget_all_configs()->list[VariantFeatureConfig]:return[VariantFeatureConfig(# example :: gpu -- multi-valued, since the package# can target multiple GPUsname="gpu",# [narf, poit, zort]values=_ALL_GPUS,multi_value=True,),VariantFeatureConfig(# example :: min_version -- single-valued, since# there is always one minimumname="min_version",# [1, 2, 3, 4] (order doesn't matter)values=[str(x)forxinrange(1,_MAX_VERSION+1)],multi_value=False,),]# properties compatible with the system@staticmethoddefget_supported_configs()->list[VariantFeatureConfig]:current_version=_get_current_version()ifcurrent_versionisNone:# no runtime found, system not supported at allreturn[]return[VariantFeatureConfig(name="min_version",# [current, current - 1, ..., 1]values=[str(x)forxinrange(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 installedvalues=[xforxin_ALL_GPUSif_is_gpu_available(x)],multi_value=True,),]
빌드 백엔드는 프런트엔드가 변형 휠을 지원하는지 여부를 판단할 수 없으므로, PEP 517및 PEP 660후크는 기본적으로 비변형 휠을 빌드해야 합니다. 빌드 백엔드는 변형 빌드를 요청하는 방법을 제공할 수 있습니다. 이 사양은 특정 구성 방식을 정의하지 않습니다.
변형 휠을 빌드할 때 빌드 백엔드는 변형 메타데이터의 정확성을 검증해야 하며, 부적합한 variant.json파일이 포함된 휠을 생성해서는 안 됩니다. 또한 사용자가 요청한 변형 속성이 유효한지 확인하기 위해 제공자를 조회해야 하지만, 이 검증을 건너뛰도록 허용하여 잠재적으로 알려지지 않은 속성이 포함된 변형 휠을 생성할 수도 있습니다.
휠 변형이 빌드된 모든 변형 속성의 네임스페이스 집합에 해당하는 variant_namespaces입니다.
휠 변형이 빌드된 모든 변형 속성의 namespace::feature 쌍 집합에 해당하는 variant_features입니다.
휠 변형이 빌드된 모든 변형 속성의 namespace::feature::value 튜플 집합에 해당하는 variant_properties입니다.
휠이 빌드된 정확한 변형 레이블에 해당하는 variant_label입니다. 변형이 아닌 휠의 경우 빈 문자열입니다.
문자열 집합으로 평가되는 마커는 다음과 같이 in 또는 notin 연산자를 사용하여 매칭해야 합니다.
# satisfied by any "foo :: * :: *" propertydep1;"foo"invariant_namespaces# satisfied by any "foo :: bar :: *" propertydep2;"foo :: bar"invariant_features# satisfied only by "foo :: bar :: baz" propertydep3;"foo :: bar :: baz"invariant_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 wheeldep6;variant_label==""
구현은 기능과 속성을 매칭할 때 공백의 차이를 무시해야 합니다.
변형 마커 표현식은 현재 제공자 플러그인의 출력이 아니라 설치 중인 휠에 저장된 변형 속성을 기준으로 평가해야 합니다. 변형이 아닌 휠이 선택되었거나 빌드된 경우 모든 변형 마커는 False로 평가됩니다.
이 절에서는 휠 변형 사양에 대한 선택적 확장을 설명합니다. 이 기능을 구현하기로 선택한 도구는 이 사양을 따라야 합니다. 이 기능을 구현하지 않는 도구는 이를 사용하는 변형을 호환되지 않는 것으로 처리해야 하며, 이러한 휠을 건너뛸 때 사용자에게 알리는 것이 좋습니다.
변형 네임스페이스 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.*
동일한 기능 이름을 가진 여러 변형 속성을 사용하여 다음과 같이 여러 제공 패키지 버전과 호환되는 휠임을 나타낼 수 있습니다.
Python 패키지 사용자를 위한 주요 정보 출처는 설치 도구 문서여야 하며, 명령줄 인터페이스의 유용한 정보 메시지와 튜토리얼이 이를 보완해야 합니다. 특별한 요구 사항이 없는 사용자는 변형에 관한 특별한 인식을 요구받지 않아야 합니다. 고급 사용자는 다음 사항에 관한 문서가 특히 필요합니다(해당 설치 도구가 이러한 기능을 구현하는 경우).
신뢰할 수 없는 공급자 플러그인을 활성화하는 방법과 그에 따른 보안 영향
공급자 사용을 제어하는 방법, 특히 선택적 공급자를 활성화하고, 원치 않는 플러그인을 비활성화하거나, 일반적으로 변형 사용을 비활성화하는 방법
변형을 명시적으로 선택하는 방법과 변형 선택 프로세스를 제어하는 방법
원격 배포 대상에 대한 변형 선택을 구성하는 방법, 예를 들어 대상에서 생성한 정적 파일을 사용하는 방법
설치 도구 문서는 Python 프로젝트에 특화된 문서, 특히 해당 프로젝트의 설치 지침으로 보완할 수도 있습니다.
일부 패키지 관리자는 변형 휠을 지원하고 일부는 지원하지 않는 전환 기간 동안, 사용자는 특정 기능이 특정 도구에서만 제공될 수 있음을 알아야 합니다.
Python 패키지 유지 관리자를 위한 주요 정보 출처는 빌드 백엔드 문서여야 하며, 튜토리얼이 이를 보완해야 합니다. 문서에는 다음 사항이 명시되어야 합니다.
pyproject.toml에서 변형 지원을 선언하는 방법
의존성을 지정하기 위해 변형 환경 마커를 사용하는 방법
변형 휠을 빌드하는 방법
로컬 패키지 색인에 변형 휠을 게시하고 *-variants.json 파일을 생성하는 방법
유지 관리자는 공급자 플러그인 문서도 검토해야 합니다. 또한 일반적으로 사용되는 설치 도구가 어떤 공급자 플러그인을 신뢰할 수 있는 것으로 간주하는지 알고, 신뢰할 수 없는 플러그인을 사용할 때의 영향을 인지해야 합니다. 이러한 자료는 변형 휠 게시 방법을 설명하는 일반 문서와 구체적인 사용 사례 예시로 보완할 수도 있습니다.
전환 기간 동안 패키지 유지 관리자는 하위 호환성을 위해 비변형 휠도 계속 게시해야 함을 알아야 합니다.
기존 설치 도구는 변형 휠을 실수로 설치해서는 절대로 안 됩니다. 변형 휠에는 휠이 사용자의 시스템과 호환되는지 판단하기 위한 추가 로직이 필요하기 때문입니다. 이는 파일 이름 끝에 -{variantlabel} 구성 요소를 추가하여 휠 파일 이름 확장 을 수행함으로써 달성되며, 결과적으로 일반적인 설치 도구 구현에서는 변형 휠이 거부됩니다. 하위 호환성을 위해 변형 휠에 더해 비변형 휠을 게시할 수 있습니다. 비호환 설치 도구가 지원하는 유일한 휠은 이것이며, 변형 호환 설치 도구에서는 선호도가 가장 낮은 휠입니다.
이러한 명시적인 비호환성을 제외하면, 이 사양은 바이너리 패키지 형식을 최소한으로 변경하며 비침해적인 변경만 적용합니다. Variant metadata는 .dist-info 디렉터리의 별도 파일에 배치되며, 변형을 고려하지 않는 도구가 이를 보존해야 하므로 필요한 변경 사항은 파일 이름 검증 알고리즘이 있는 경우 이를 업데이트하는 것으로 제한됩니다.
새로운 Variant environment markers를 휠 의존성에 사용하면 해당 휠은 기존 도구와 호환되지 않게 됩니다. 이는 환경 마커 설계에 따른 일반적인 문제이며, 휠 변형에만 국한된 문제는 아닙니다. 빌드 시 환경 마커를 부분적으로 평가하고 비변형 휠에서 변형 휠에 특화된 마커 또는 의존성을 제거하면 이 문제를 우회할 수 있습니다.
Build backends는 기존 프런트엔드와의 하위 호환성을 유지하기 위해 변형이 없는 휠을 생성합니다. 변형 휠은 사용자가 명시적으로 요청한 경우에만 출력할 수 있습니다.
공유 메타데이터를 위한 별도의 *-variants.json파일을 사용하면, 변형 휠 메타데이터를 특별히 지원하지 않는 색인에서도 변형 휠을 사용할 수 있습니다. 그러나 색인은 확장된 파일 이름 구문과 JSON 파일을 사용하는 휠의 배포를 반드시 허용해야 합니다.
보안 문제를 논의하는 과정에서 변형 제공자 사용을 영구적으로 또는 추가 테스트를 용이하게 하기 위해 적어도 초기에는 전면적으로 옵트인하도록 하자는 제안이 있었습니다. 이러한 접근 방식은 제공자 코드를 심사할 책임을 지는 주체와 그에 따른 유지 관리 노력 또는 신뢰해야 하는 패키지 및 유지 관리자의 수를 바꿀 수 있지만, 장기적인 해결책으로는 적합하지 않습니다.
가장 중요한 점은 옵트인 메커니즘이 기본 상태에서 훨씬 더 나쁜 사용자 경험을 초래한다는 것입니다. 변형이 활성화된 패키지의 경우 기본적인 사용 경험은 최적이 아니거나 완전히 손상된 변형을 설치하는 것이 될 것입니다. 변형이 활성화된 패키지는 직접 설치될 뿐만 아니라 다른 패키지의 의존성으로 설치될 수도 있다는 점에 유의해야 합니다. 따라서 최적의 사용자 경험을 위해서는 변형이 활성화된 모든 패키지 또는 변형이 활성화된 의존성을 포함하는 모든 패키지가 해당 제공자 플러그인을 활성화하기 위한 적절한 설치 프로그램별 메커니즘을 문서화해야 합니다.
이러한 경험이 확산되면 두 가지 중요한 결과가 발생할 수 있습니다. 사용자가 기본 상태에서 변형을 작동시킬 수 없으면 패키지 유지 관리자가 변형 사용을 포기하고 대신 이전의 우회 방법을 계속 사용하게 될 수 있습니다. 더 나쁜 점은 사용자가 결국 모든 변형 공급자를 무조건 활성화하도록 설치 관리자를 순진하게 구성하게 될 수도 있다는 것입니다. 이렇게 되면 다수의 사용자에게 공급자 사용을 사실상 옵트아웃으로 만들고, 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입니다.