네이티브 의존성 악몽: Python의 BLAS, LAPACK, 및 OpenMP
현대 데이터 과학자에게 NumPy나 scikit-learn 같은 라이브러리를 설치하는 것은 사소한 pip install처럼 느껴집니다. 하지만 이러한 Python 휠의 이면에는 복잡하고 종종 취약한 네이티브 의존성의 얽힘이 존재합니다. PyData 스택의 고성능 컴퓨팅(HPC) 기반—특히 BLAS, LAPACK, 그리고 OpenMP—은 오늘날 Python 패키징에서 가장 중요한 기술적 장벽 중 하나를 차지합니다.
핵심 삼위일체: BLAS, LAPACK, 및 OpenMP
- BLAS (Basic Linear Algebra Subprograms): 벡터와 행렬 연산을 위한 기본 루틴.
- LAPACK (Linear Algebra PACKage): BLAS 위에 구축된 고수준 라이브러리로, 보다 복잡한 선형대수 루틴을 제공.
- OpenMP (Open Multi-Processing): 공유 메모리 병렬 프로그래밍을 위한 API로, 코드가 여러 CPU 코어에서 실행될 수 있게 함.
참조 구현(예: Netlib)도 존재하지만, 실제 성능 향상은 Intel MKL, OpenBLAS, Apple Accelerate, AMD AOCL과 같은 최적화된 구현에서 나옵니다. 이러한 최적화 버전은 종종 참조 코드 대비 10배에서 100배의 성능 향상을 제공하여 딥러닝 및 과학 연구에 필수적입니다.
"Vendoring" 함정과 PyPI 딜레마
PyPI는 주로 Python 코드용으로 설계되었기 때문에 복잡한 애플리케이션 바이너리 인터페이스(ABI)를 가진 네이티브 라이브러리를 다루는 데 어려움을 겪습니다. 사용자가 실제로 코드를 실행할 수 있도록 하기 위해 많은 주요 프로젝트가 vendoring—컴파일된 네이티브 라이브러리를 Python 휠 내부에 직접 포함하는 방식—을 채택했습니다.
이 접근 방식은 여러 중요한 문제를 야기합니다:
1. "One Version Rule" 위반
건전한 환경에서는 라이브러리 하나의 버전만을 원합니다. 그러나 NumPy와 SciPy가 모두 OpenBLAS를 vendoring하기 때문에, 하나의 환경에 동일한 라이브러리의 여러 복사본이 서로 다른 버전으로 존재할 수 있습니다. 예를 들어, NumPy는 64비트(ILP64) 빌드로 전환될 수 있지만 SciPy는 32비트 빌드에 머물러 잠재적인 불안정을 초래합니다.
2. 스레딩 과다 할당 및 교착 상태
여러 라이브러리가 각각 자체 스레딩 모델을 가져오면 혼란이 발생합니다. 일부 OpenBLAS 빌드에서 사용되는 pthread와 OpenMP를 혼합하면 과다 할당이 발생하여 시스템이 물리적 코어 수보다 더 많은 스레드를 생성하려고 하여 성능이 급격히 저하됩니다.
더 위험한 점은 특정 OpenMP 구현(예: GCC의 libgomp)이 "fork-safe"하지 않다는 것입니다. Python의 multiprocessing 모듈( POSIX에서 기본값이 fork인)과 결합될 경우, 이는 디버깅이 어려운 교착 상태를 자주 초래합니다.
3. 다이아몬드 의존성 문제
NumPy와 SciPy가 모두 OpenBLAS에 의존하면 다이아몬드 형태의 의존성이 형성됩니다. 서로 다른 버전을 vendoring하면, 하나만 업그레이드했을 때 잠재적인 세그멘테이션 오류나 수치 부정확성의 위험 지대가 됩니다.
비교: PyPI vs. 시스템 패키지 관리자
Python 커뮤니티의 어려움은 언어별 패키지 관리자와 시스템 수준 관리자(예: Debian의 apt, Fedora의 dnf, 또는 Conda) 사이의 근본적인 차이를 강조합니다.
| Feature | PyPI (Wheels) | System Managers (Conda/Debian) |
|---|---|---|
| Dependency Model | 자체 포함(벤더링) | 공유/가상 의존성 |
| BLAS Selection | 빌드 시 고정 | 런타임 전환(예: FlexiBLAS) |
| ABI Management | 암묵적/취약 | 뮤텍스 메타패키지를 통한 명시적 |
| Parallelism | 종종 충돌 | 조정됨 |
예를 들어 Conda는 "mutex metapackages"를 사용하여 한 번에 하나의 BLAS 구현만 설치되도록 보장함으로써 사용자가 환경을 깨뜨리지 않고 MKL과 OpenBLAS 사이를 전환할 수 있게 합니다.
앞으로의 방향
네이티브 의존성 위기를 해결하려면 언어별 패키지 관리자의 "작은 섬" 사고방식에서 벗어나야 합니다. 가능한 해결책은 다음과 같습니다:
- 전용 네이티브 휠: OpenBLAS와 OpenMP를 위한 별도 PyPI 패키지를 만들어 중복을 없앰.
- 라이브러리 디멀티플렉싱: FlexiBLAS 또는 SciPy의
cython_blas와 같은 래퍼 라이브러리를 활용해 일관된 API/ABI를 제공하고, 런타임에 구현을 교체할 수 있게 함. - 시스템 통합: Python 패키지가 PyPI 외부에 존재하는 라이브러리 의존성을 표현할 수 있는 능력을 향상시켜, Python 생태계와 OS 패키지 관리자 간의 격차를 메우는 것.
커뮤니티 관점
이 아키텍처에 대한 논쟁은 현대 언어가 네이티브 코드를 다루는 방식에 대한 광범위한 불만을 반영합니다. 한 기여자는 언어별 관리자가 설계 단계에서 시스템 라이브러리와 ABI를 무시하는 경우가 많으며, 일정 규모에 도달하면 생태계가 "프로덕션에서 spectacularly 폭발"한다는 점을 지적했습니다.
일부는 C++26의 표준 라이브러리 내 선형대수 서브셋 제안과 같이 미래를 바라보지만, 당면 과제는 여전히 Fortran 기반 선형대수와 Python 중심의 PyPI 세계 사이의 복잡한 상호작용을 조정하는 것입니다.