3.3 Bináris csomag, BLAS és conda-döntés

3.3.1 Cél és konkrét helyzet

A Northstar labor pyproject.toml-ja NumPy-t és SciPy-t ír elő. Három különböző gépen három eltérő telepítési hibát látunk, mielőtt bármilyen elemzés elindulhatna:

  1. ERROR: No matching distribution found for numpy==2.1.1
  2. Failed building wheel for numpy; error: command 'gcc' failed with exit code 1
  3. ImportError: libcblas.so.3: cannot open shared object file: No such file or directory

A megfigyelési egység egy telepítési kísérlet: a teljes hibakimenet, a használt Python-verzió, az operációs rendszer és a processzorarchitektúra. A cél annak eldöntése, hogy az adott hiba wheel-, rendszerkönyvtár- vagy solverprobléma-e, és hogy uv/pip vagy conda/mamba környezetet érdemes-e választani. Az ellenőrzés feltétele: a telepítés után a Python sikeresen importálja a NumPy-t, és egy kézzel ellenőrizhető mátrixszorzás a várt eredményt adja.

Induló feladat: a három üzenet közül melyikhez melyik első vizsgálati lépés tartozik – (a) Python-verzió ellenőrzése, (b) C/Fortran fordító megléte, (c) a megosztott könyvtárak dinamikus feloldása? A megoldást a fejezet végén adjuk meg.

3.3.2 Wheel és forrásfordítás

A wheel (kerék) a Python-csomagok előre lefordított bináris formátuma. A .whl fájlban a csomag készre fordított kódja és metaadatai vannak; telepítéskor a csomagkezelő csak a megfelelő helyre csomagolja ki őket. Ennek akkor van jelentősége, ha a csomag natív, C, C++ vagy Fortran kódon alapul, és a telepítőgépen nincs fordító vagy a szükséges rendszerfejléc.

Egy wheel-fájlnév platformcímkéi pontosan megmondják, milyen környezethez készült. Például:

numpy-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Ha a wheel nem elérhető az adott platformra, a pip vagy uv megpróbálhatja az sdist-ből (source distribution) felépíteni a csomagot. Ez a forrásfordítás (source build): a telepítőgépen fut le a fordító, amihez megfelelő C- és gyakran Fortran-fordítóra, a függő rendszerkönyvtárak fejléceire és build-eszközökre van szükség. A második hibaüzenetben szereplő gcc az egyik ilyen fordító; ha hiányzik vagy nem kompatibilis, a build meghiúsul.

3.3.3 NumPy/SciPy natív függősége és a BLAS szerepe

A NumPy és a SciPy nagy része C-ben és Fortranban írt, nagy teljesítményű numerikus rutinokat hív meg. A lineáris algebrai alapműveletek szabványos felületét a BLAS (Basic Linear Algebra Subprograms) adja, amelynek három szintje van: vektor–vektor, mátrix–vektor és mátrix–mátrix műveletek. A NumPy például a mátrixszorzást általában egy külső BLAS-megvalósításra, például az OpenBLAS-ra vagy az Intel MKL-re bízza. A SciPy ehhez még a LAPACK-ot is használja, amely a lineáris egyenletrendszerek megoldására, sajátérték-számításra és hasonlókra épül.

Ennek matematikai hídja egyszerű: a mátrixszorzás mint könyvtári művelet.

Legyen A∈ℝm×kA \in \mathbb{R}^{m \times k} és B∈ℝk×nB \in \mathbb{R}^{k \times n}. A szorzat C=AB∈ℝm×nC = AB \in \mathbb{R}^{m \times n}, ahol minden (i,j)(i, j) indexre

Cij=∑l=1kAilBlj. C_{ij} = \sum_{l=1}^{k} A_{il} B_{lj}.

Itt mm az AA sorainak száma, kk a közös belső dimenzió, nn a BB oszlopainak száma. A példában dimenzió nélküli, tiszta számokkal dolgozunk; valós adatelemzésben a mértékegységet a mátrix elemeinek jelentése hordozza, ezért a szorzatnál mindig külön kell megnevezni, mit jelentenek a bemenő oszlopok.

Kis kézi kontroll:

A=(1234),B=(5678). A = \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix}, \quad B = \begin{pmatrix} 5 & 6 \\ 7 & 8 \end{pmatrix}.

Ekkor m=k=n=2m = k = n = 2, és

C=(1⋅5+2⋅71⋅6+2⋅83⋅5+4⋅73⋅6+4⋅8)=(19224350). C = \begin{pmatrix} 1 \cdot 5 + 2 \cdot 7 & 1 \cdot 6 + 2 \cdot 8 \\ 3 \cdot 5 + 4 \cdot 7 & 3 \cdot 6 + 4 \cdot 8 \end{pmatrix} = \begin{pmatrix} 19 & 22 \\ 43 & 50 \end{pmatrix}.

A NumPy-ban a @ operátor ezt a BLAS-hívást takarja. A Python-kód tehát nem maga írja meg a belső ciklusokat; a helyes működés a telepített natív könyvtártól függ. Ha a BLAS hiányzik, rosszul linkelt vagy nem kompatibilis az ABI-val, akkor nem az algoritmus hibás, hanem a futtatókörnyezet.

3.3.4 conda/mamba és a csatornák

A conda és a gyorsabb solverű mamba nemcsak Python-csomagokat, hanem a Pythont és a natív könyvtárakat is csomagként kezelik. Egy conda create -n labor python=3.12 numpy scipy parancs a Python-interpretert, a NumPy-t, a SciPy-t és az általuk igényelt BLAS/LAPACK-megvalósítást is binárisan, előre lefordítva telepíti a környezetbe. Ezért a conda különösen akkor előnyös, ha a PyPI-n nincs megfelelő wheel, vagy ha a rendszerfordító hiányzik.

A conda-csomagok csatornákból (channel) érkeznek. A defaults az Anaconda által karbantartott, míg a conda-forge közösségi csatorna, amely rendszerint frissebb és szélesebb körű buildeket kínál. A tanulási főútban a csatornák pontos verzióját nem kötjük meg e fejezethez; a választásnál az a kérdés, hogy a projekt által igényelt csomagok és a Python-verzió konzisztensen elérhetők-e.

Példa conda/mamba munkamenet (Linux/macOS, a projektgyökérből):

mamba create -n northstar python=3.12 numpy scipy -c conda-forge
mamba activate northstar
python -c "import numpy as np; print(np.__version__); print(np.show_config())"

A np.show_config() kimenete szemléltető lehet; egy lehetséges részlet:

blas_mkl_info:
  NOT AVAILABLE
blas_opt_info:
    libraries = ['openblas', 'openblas']
    library_dirs = ['/home/user/miniforge3/envs/northstar/lib']
    language = f77

Ez azt mutatja, hogy a NumPy az OpenBLAS-t használja. A tényleges kimenet gép- és verziófüggő, ezért ezt csak a saját gépen futtatva érdemes ellenőrizni.

A conda és mamba parancsok szintaxisa a mindennapi használatban nagyrészt megegyezik; a mamba gyorsabb feloldást ígér. A könyvben conda/mamba néven hivatkozunk rájuk, mert a döntési szerep azonos.

3.3.5 Keverés kockázata

A legnagyobb telepítési csapda a pip/uv és a conda tetszőleges sorrendű keverése. Ennek oka, hogy a conda saját solverrel és saját függőségi gráfbal dolgozik, míg a pip/uv a PyPI wheeljeiből építkezik. A két rendszer nem látja egymás döntéseit.

Ellenpélda:

conda create -n mixed python=3.12 numpy
conda activate mixed
pip install scipy
conda install pandas

A pip install scipy telepíthet egy olyan SciPy buildet, amely más NumPy-ABI-ra épül, mint amit a conda telepített. Ezután a conda install pandas a conda solver döntése alapján kicserélheti vagy downgrade-elheti a NumPy-t, miközben a pip által telepített SciPy ugyanott marad. Végeredmény: inkonzisztens bináris környezet, amelyben egy import vagy egy BLAS-hívás hibát ad. A reprodukálhatósági stratégia ezért az: egy környezetben vagy conda/mamba, vagy uv/pip kezelje a Python-csomagokat; a kettőt ne váltogassuk ugyanazon a környezeten.

3.3.6 Teljes minimális példa: három hibaüzenet vizsgálata

Az alábbi három üzenet szemléltető példa, nem valós futási napló. A diagnosztikai lépések azonban konkrétak és futtathatók.

3.3.6.1 1. üzenet – nincs megfelelő wheel

ERROR: Could not find a version that satisfies the requirement numpy==2.1.1
ERROR: No matching distribution found for numpy==2.1.1

Első vizsgálati lépés: a Python-verzió és a platform ellenőrzése:

python -V
python -c "import platform; print(platform.platform(), platform.machine())"

Ha a kimenet például Python 3.13-at mutat, de a kért NumPy-kiadáshoz csak 3.12-es wheel létezik, akkor a hiba nem a csomag hibája, hanem verzióillesztési probléma. A megoldás vagy a Python-verzió rögzítése a projektben (a tanulási alap CPython 3.12), vagy a NumPy-verzió tartományának módosítása a pyproject.toml-ban.

3.3.6.2 2. üzenet – forrásfordítás hiányzó toolchainnel

Failed building wheel for numpy
error: command 'gcc' failed with exit code 1

Itt a telepítő nem talált kész wheel-t, ezért megpróbálta az sdist-et lefordítani, de a rendszerfordító hiányzik vagy hibás. Első vizsgálati lépés a toolchain megléte:

gcc --version
gfortran --version || echo "gfortran nincs telepítve"

Ha a gfortran hiányzik, a NumPy és a SciPy forrásfordítása a legtöbb platformon nem fog menni. Ebben az esetben vagy a megfelelő wheel-t kell megkeresni (például Python-verzió emelésével/süllyesztésével), vagy conda/mamba környezetre váltani, amely binárisan hozza a csomagot és a BLAS-t.

3.3.6.3 3. üzenet – hiányzó megosztott könyvtár

ImportError: libcblas.so.3: cannot open shared object file: No such file or directory

Ez a hiba akkor jelentkezik, amikor a Python már elindult, de a betöltött natív modul egy megosztott C/Fortran könyvtárat keres, amely nincs a dinamikus keresési útvonalon. Első vizsgálati lépés a könyvtár-függőségek listázása Linuxon:

ldd "$(python -c 'import numpy; print(numpy.__file__)')"

Vagy magának a NumPy-nak a konfigurációját nézzük:

python -c "import numpy; numpy.show_config()"

Ha a libcblas hiányzik a rendszerből, a leggyakoribb ok: a NumPy-t egy olyan wheel-ből telepítettük, amely rendszer-BLAS-t feltételez, vagy egy másik környezetből másoltuk át. A javítás vagy a megfelelő, önálló OpenBLAS-t tartalmazó wheel telepítése, vagy conda/mamba használata.

3.3.6.4 uv és conda felelősségi körök összehasonlítása

Felelősség uv + venv conda/mamba
Python-interpreter kiválasztása A pyproject.toml-ban megadott verziót a uv letöltheti és kezelheti; a környezet Pythonja a .venv-ben van. A Pythont maga a conda telepíti csomagként a környezetbe.
Natív könyvtárak (BLAS/LAPACK) A PyPI-wheel-ek általában tartalmazzák az OpenBLAS-t; ha nincs wheel, forrásfordítás kell. A conda-csatorna binárisan szállítja a BLAS-t és a csomagokat együtt.
Függőségrögzítés uv.lock a Python-csomagokat pontosan rögzíti; a rendszerkönyvtárak nem részei. environment.yml vagy conda-lock a conda-csomagokat rögzíti; a rendszerkönyvtárak ugyanúgy nem.
Operációs rendszer függősége Windows/macOS/Linux wheel-ek; a rendszer-C lib verziója lehet korlát. A conda-csomagok saját előtaggal dolgoznak, így kevesebb rendszerfüggőség.

A táblázat lényege: az uv/pip a Python-ökoszisztéma natív eszköze, és a lockkal a Python-függőségeket rögzíti; a conda/mamba a Python mellett a natív könyvtárakat is csomagkezelő alá vonja. A 3.2-ben tanult uv.lock tehát a conda esetében nem ugyanaz a fogalom: a conda-környezetben a conda-lock vagy a environment.yml pontosítására van szükség.

Most futtassuk le a kis mátrixszorzás-kontrollt egy uv-környezetben. Az alábbi kódot nem futtattuk jelen írásban; a kimenet szemléltető, elméletileg várt érték.

import numpy as np

A = np.array([[1.0, 2.0], [3.0, 4.0]], dtype=np.float64)  # shape (2, 2)
B = np.array([[5.0, 6.0], [7.0, 8.0]], dtype=np.float64)  # shape (2, 2)
C = A @ B                                                   # shape (2, 2)

expected = np.array([[19.0, 22.0], [43.0, 50.0]], dtype=np.float64)
print(C)
print("shape:", C.shape, "dtype:", C.dtype)
print("allclose:", np.allclose(C, expected, rtol=1e-12, atol=1e-14))

Elméletileg várt szemléltető kimenet:

[[19. 22.]
 [43. 50.]]
shape: (2, 2) dtype: float64
allclose: True

Futtatás a lab projektben, Linux/macOS, a projektgyökérből:

uv sync --locked
uv run --locked --no-sync python matrix_check.py

A --locked --no-sync kombináció a 3.2-ben tanult módon garantálja, hogy a lockolt verziókat használjuk, friss feloldás nélkül.

3.3.7 Eredmény értelmezése

A mátrixszorzás ellenőrzése három invariant teljesülését jelenti:

  1. Shape-invariant: ha A∈ℝ2×2A \in \mathbb{R}^{2 \times 2} és B∈ℝ2×2B \in \mathbb{R}^{2 \times 2}, akkor C∈ℝ2×2C \in \mathbb{R}^{2 \times 2}. A kimenet shape-je (2, 2).
  2. Dtype-invariant: a példában minden bemenet float64, ezért a kimenet is float64; vegyes típusnál a NumPy saját promóciós szabályai szerint alakul, amit külön ellenőrizni kell.
  3. Numerikus tolerancia: a két mátrix minden eleme egész pontossággal egyezik a kézi kontrollal; lebegőpontos műveleteknél az összehasonlításhoz relatív toleranciát (rtol=10−12\text{rtol}=10^{-12}) és abszolút küszöböt (atol=10−14\text{atol}=10^{-14}) használunk. Több lépéses, nagy dimenziós műveleteknél ez a tolerancia szigorodhat; a lényeg, hogy soha ne hasonlítsunk lebegőpontos eredményeket == művelettel.

Ha a fenti ellenőrzések teljesülnek, a NumPy BLAS-hívása működőképes. Ha allclose hamisat ad, akkor nem feltétlenül a BLAS hibás: először a saját expected mátrixunkat kell ellenőrizni, majd a dtype-ot és a shape-eket.

Numerikus bináris kompatibilitás. A Python-verzió önmagában nem írja le a numerikus stack futtatókörnyezetét. A wheel/conda csomagoknak CPU-architektúrához, rendszerkönyvtárhoz és adott esetben BLAS- vagy GPU-runtime-hoz is illeszkedniük kell. A csomag telepítése és egy egyszerű numerikus művelet sikeres lefutása két külön ellenőrzési pont.

A BLAS-szálak száma befolyásolhatja a teljesítményt és a reprodukálható időmérést; ezért benchmarknál a szálkeretet (például OPENBLAS_NUM_THREADS) és a platformot dokumentálni kell. Ne sugalljuk, hogy a conda minden bináris problémára automatikusan jobb megoldás: ez konkrét platform- és csomagfüggő döntés.

3.3.8 Feladatok

  1. Értelmezés. A wheel-fájl neve numpy-2.1.1-cp312-cp312-win_amd64.whl. Telepíthető-e ez CPython 3.13-ra Linux x86-64 gépen? Indokolj a fájlnév címkéi alapján.

  2. Megvalósítás. Írj egy rövid Python-szkriptet, amely kiírja, hogy a NumPy milyen BLAS/LAPACK-at talál (numpy.show_config()), majd végez el egy 3×23 \times 2-es mátrix és egy 2×42 \times 4-es mátrix szorzását. Add meg a várt eredmény shape-jét, és kézzel számold ki az eredmény első sorát, ha az első mátrix sorai rendre [1,2][1, 2], [3,4][3, 4], [5,6][5, 6], a második mátrix oszlopai pedig [1,0][1, 0], [0,1][0, 1], [2,3][2, 3], [1,1][1, 1].

  3. Hibakeresés. Egy gépen az uv sync --locked sikeres, de az import numpy ezt adja: ImportError: /lib/x86_64-linux-gnu/libm.so.6: version GLIBC_2.28 not found. Milyen típusú problémáról van szó? Milyen következő lépést javasolsz, és miért nem a uv lockfájl hibája?

  4. Megváltozott feltétel. A telepítőgépen nincs internet-hozzáférés a futtatáskor, viszont a csomagokat előre be kell csomagolni. Hogyan változik a döntés uv és conda/mamba között? Adj egy konkrét, offline munkamenet-vázlatot, és jelöld meg, mi az, ami mindkét úton kívül marad.

3.3.9 Megoldások és indoklás

  1. Nem telepíthető. A cp312 címke CPython 3.12-höz kötött, a win_amd64 pedig 64 bites Windowsra. CPython 3.13 Linux x86-64-hez más wheel, például cp313-cp313-manylinux...x86_64.whl kellene. Ha ilyen nem létezik a kért verzióhoz, akkor vagy Python-verziót kell igazítani, vagy forrást fordítani, vagy conda/mamba csatornán keresni binárist. Ez a válasz a címkék pontos olvasását ellenőrzi.

  2. Várható shape: (3,4)(3, 4). Az első sor kiszámítása: az AA első sora [1,2][1, 2]. A szorzat első sora:

    Tehát a várt első sor [1,2,8,3][1, 2, 8, 3]. A szkriptben az A @ B kimenet shape-jét (3, 4)-ként ellenőrizzük. Általánosan: ha AA shape-je (3,2)(3, 2) és BB shape-je (2,4)(2, 4), akkor a belső 22 megegyezik, a kimenet pedig (3,4)(3, 4). A BLAS-konfiguráció kiírása a numpy.show_config() függvénnyel történik; annak tartalma gépenként eltér, ezért itt csak az ellenőrzés módját adjuk meg, konkrét kimenetet nem garantálunk.

  3. A GLIBC-verzió probléma. A hiba azt jelenti, hogy a NumPy wheel egy olyan Linux-disztribúcióra készült, amely újabb glibc-t feltételez, mint ami a gépen van. Ez rendszerkönyvtár-kompatibilitási probléma, nem solverprobléma. Az uv.lock a Python-csomagok verzióját rögzíti; a wheel platformkompatibilitása a manylinux címke szintjén múlik. A következő lépés: olyan régebbi manylinux-wheelű NumPy-verziót választani, amely a régebbi glibc-vel is működik, vagy conda/mamba-ra váltani, mert a conda-csomagok saját előtaggal, a rendszer glibc-jétől függetlenebbül telepíthetők.

  4. Offline megoldás. Mindkét úthoz elő kell készíteni a letöltéseket egy internetes gépen. Uv/pip esetén a wheel-eket töltsük le egy könyvtárba (pip download vagy uv pip download, itt a parancs neve verziófüggő lehet, ezért a konkrét hívás előtt a helyi uv --help ellenőrzendő), majd a célgépen ebből telepítsünk. Conda/mamba esetén a conda pack vagy egy helyi csatorna-mirror használható. Ami mindkét úton kívül marad: a rendszer szintű C könyvtárak (például a glibc) és az operációs rendszer frissítései; ezeket a csomagkezelő egyik eszköz sem viszi magával, ezért offline ismert, azonos OS-t kell feltételezni. A lock mindkét esetben csak a saját csomagkörére vonatkozik.

3.3.10 Gyakori tévedés ellenpéldával

A leggyakoribb tévedés az, hogy a pip és a conda keverése „rugalmasság”, pedig nem reprodukálhatósági stratégia. Ellenpélda: egy csapattag conda-val telepíti a NumPy-t, majd pip-pel telepíti a projektet, mert a pyproject.toml-ból szeretne dolgozni. A pip wheel-je felülírja a conda által telepített NumPy egyes fájljait, más conda-csomagok (SciPy, statsmodels) viszont az eredeti conda-verzióhoz lettek lefordítva. Egy mátrixszorzás még lehet, hogy fut, de egy ritkább SciPy-művelet undefined symbol hibát dob. A környezet reprodukálhatatlan, mert a konkrét felülírási sorrend nem dokumentált. A helyes stratégia: egy környezet = egy csomagkezelő, és ezt a 2.3-ben tanult Git-konfigurációban, a README-ben rögzítjük.

Önellenőrzés. A 3.2 lockolt függőségei most már kiegészültek a bináris kérdéssel: a uv.lock a Python-csomagok pontos verzióját adja, de nem tartalmazza a BLAS bináris tartalmát. Ha egy másik gépen nem ugyanaz a rendszer glibc-je, a lock önmagában nem garantál futást. Ezért a reprodukálhatósági nyilatkozatban külön kell nevezni a Python-csomag-zárat, a natív könyvtárakat és az operációs rendszert.

3.3.11 Rövid összefoglaló

A wheel előre lefordított bináris, a forrásfordítás pedig fordítóeszközt igényel a gépen. A NumPy és SciPy BLAS/LAPACK könyvtárakat hív, ezért a mátrixművelet nem pusztán Python-számítás, hanem natív futtatókörnyezet. A conda/mamba a Pythont és a natív könyvtárakat is csomagkezelő alá vonja, a csatorna határozza meg a bináris forrást. Az uv/pip és a conda keverése inkonzisztens ABI-hoz vezet, ezért egy környezetben egy csomagkezelő működjön. A következő alfejezetben a Jupyter-kernel regisztrációját vizsgáljuk: ott az interpreter, a csomagkészlet és a notebook futásidejének összerendelése kerül középpontba.