Ebben az egységben nem a modellt, hanem a modellt futtató környezetet
vizsgáljuk. A 2.2. Lebegőpont és stabil elemi
számítás egységben láttuk, hogy egy lebegőpontos eredmény csak
toleranciával értelmezhető; most azt a kérdést tesszük fel, hogy
egyáltalán melyik Python hajtotta végre a számítást,
milyen csomagverziókkal és milyen kernelben. Induló
kérdés: egy notebook cellája 0.91 pontosságot mutat, de a
cella tegnap futott. Milyen további adat kell ahhoz, hogy ezt a
jelenlegi kód eredményének tekinthessük?
Előismeret: 2.2. Lebegőpont és stabil elemi
számítás fogalmai (float32/float64,
allclose, tolerancia), valamint alapvető parancssori és
fájlkezelési ismeret. Nem feltételezünk csomagkezelői vagy
notebook-szerver üzemeltetői gyakorlatot.
A három fogalom különböző rétegre vonatkozik, ezért pontosan definiáljuk őket.
Definíció (Python interpreter). A Python interpreter
az a folyamat, amely a Python bytecode-ot végrehajtja. Egy futó Python
program számára az interpreter elérési útja a
sys.executable karakterlánc. Ugyanazon a gépen több
interpreter is létezhet, például a rendszer
/usr/bin/python3 és egy projekt
.venv/bin/python fájlja; a kettő különböző
csomagkörnyezetet láthat.
Definíció (Jupyter kernel). A Jupyter kernel egy
háttérfolyamat, amely egy adott interpreterrel hajtja végre a notebook
celláinak kódját, és a notebook szerverrel kommunikál. A notebook
dokumentum (.ipynb) csak cellákat, kimeneteket és
metaadatokat tárol; a kernel nem a notebook része.
Definíció (csomagprofil). A csomagprofil a
futtatáshoz specifikált csomagok és azok verzióinak zárt halmaza. Egy
lock fájl, például uv.lock, a közvetlen függőségek mellett
a tranzitív függőségeket is rögzíti, ezért két uv sync
futtatás elvileg ugyanazt a profilt állítja elő.
A három réteget a következő példa választja el. Tegyük fel, hogy a
notebook kernelt python3 néven indítottuk, de a projekt
interpretere .venv/bin/python. A notebook cellája ekkor a
kernel interpreterével fut, nem feltétlenül azzal, amelyet a
parancssorban uv run python hív. A pontos állapotot a
notebookban is a sys.executable mutatja; ha a cella más
interpreterrel fut, a numpy verzió is eltérhet.
A fejezet levezetési kötelezettsége: milyen feltételek mellett várjuk, hogy azonos input azonos ellenőrző számítást adjon? Ehhez formalizáljuk a futás fogalmát.
Legyen egy determinisztikus program, azaz olyan program, amelynek minden állapotátmenete csak a megelőző állapottól és a bemenettől függ; nincs benne rejtett véletlen forrás, időbélyeg vagy külső hálózati válasz. Legyen a bemenet, a környezeti leíró, ahol
Jelölje a program kimenetét az környezetben. A reprodukálhatóság alapesete a következő.
Lemma (azonos környezet, azonos kimenet). Ha determinisztikus, akkor rögzített és mellett minden futásban ugyanaz az érték.
Bizonyításvázlat. A futás állapotsorozatát jelöli, ahol a kezdőállapot az bemenettel. A determinisztikusság miatt minden lépéshez van olyan átmenetfüggvény, hogy , és csak a programkódtól és -től függ. Mivel és rögzített, rögzített; teljes indukcióval minden rögzített, így is. A bizonyítás erőssége, hogy a környezeti leíró minden befolyásoló komponensét rögzítettük; ha bármelyik rejtve változik, a lemma feltétele nem teljesül.
A lemma azonban nem ígér bitazonosságot, ha változik. Ugyanaz a Python- és könyvtárverzió két különböző CPU-n más lebegőpontos utasítást, más reduction-sorrendet vagy más gyorsított könyvtárat használhat. A [D02] dokumentáció kifejezetten rögzíti, hogy azonos seed sem garantál platformok és verziók közötti bitazonos eredményt. Ezért a gyakorlati reprodukciós szerződés nem pontos egyenlőséget, hanem a 2.2. Lebegőpont és stabil elemi számítás egységből ismert toleranciás egyezést követel meg:
ahol
a referenciaként elfogadott érték,
az új környezetben kapott érték,
és
pedig a számítás skálájához és dtype-jához indokolt korlát.
float64 gradienseket ellenőrző kis konvolúciós példán
,
észszerű középső differenciás ellenőrzéshez; a pontos értéket a konkrét
számítás kondíciója és az alkalmazott differencia lépése határozza meg.
Ha a program float32 redukciót tartalmaz, a tolerancia nem
húzható be tetszőlegesen.
A Pythonhoz több környezetkezelő létezik; a főút a [D01] dokumentáció
alapján az uv. A uv egy parancssori eszköz,
amely a pyproject.toml és a uv.lock alapján
szinkronizálja a projekt csomagprofilját. A labor könyvtára az 1.1. Modell, tanulás és felhasználási szerződés
laborcsomagjának (cnn-labor-1.0.0) lab/
könyvtára; ha a uv még nincs telepítve, a [D01] szerinti
telepítő vagy a python -m pip install uv parancs adja. A
labor könyvtárában a következő parancsok hozzák létre a tanulási
profilt:
cd lab
uv venv --python 3.12
uv sync
uv run python --versionA uv sync a lock fájlban rögzített csomagokat telepíti a
projekt .venv könyvtárába; önmagában is létrehozza a
környezetet, a uv venv lépés csak az interpreter
választását teszi explicitté. A --python 3.12 a tanulási
interpreter verzióját adja meg; a megengedett tartományt
(>=3.12,<3.13) a labor pyproject.toml-ja
rögzíti, a csomagverziókat (NumPy 2.3.5, PyTorch 2.14.1 CPU-build,
Matplotlib 3.10.8, pandas 2.3.3, SciPy 1.18.1) a uv.lock. A
uv run előtaggal futtatott parancsok a projekt környezetét
használják anélkül, hogy a felhasználónak kézzel aktiválnia kellene a
virtuális környezetet. Ez a megkülönböztetés fontos: a
python parancs a shell aktuális környezetét hívja, a
uv run python a projekt profilját.
A CPU/GPU build kérdése szintén a csomagprofil része. A PyTorch-nak
külön wheel-változatai vannak CPU-s és CUDA-s builddel. A labor főútja
kis CPU-s buildet használ. Ha valaki CUDA-s buildet telepít, a
torch.cuda.is_available() értéke változhat; a modellkódnak
ezt explicit módon kell kezelnie, például
device = "cuda" if torch.cuda.is_available() else "cpu"
formában. A build azért is része a reprodukciós feltételnek, mert
ugyanaz a Python-csomagnév más natív kódot takarhat.
A Jupyter notebook egy JSON dokumentum, amely cellákból, azok
kimeneteiből és metaadatokból áll. A kernel a cella végrehajtásakor
interpretert indít vagy használ; a notebook kimenete a végrehajtás
múltbeli rögzítése. A sys.executable a kernelben
is elérhető, és a következő cellával ellenőrizhető:
import sys
print(sys.executable)A kimenet például .../lab/.venv/bin/python lehet, de
csak akkor, ha a kernel valóban ezt az interpretert használja. A
jupyter kernelspec list parancs a rendelkezésre álló
kernelek nevét és indítási útvonalát mutatja; ugyanaz a
python3 név több környezetre is mutathat.
A notebook régi kimenete nem bizonyítja a jelenlegi kód futását.
Ennek oka, hogy a notebook cellái megosztott kernelállapotot használnak:
ha az 1. cellában a = 1 szerepelt, majd a cellát töröltük,
a 2. cella print(a) akkor is 1-et írhat ki, ha
az aktuális notebookban az 1. cella már nincs jelen. A megbízható
ellenőrzés a Restart & Run All, amely újraindítja a
kernelt, és minden cellát a dokumentumbeli sorrendben futtat. Ezt
követően a mentett notebook kimenete már a jelenlegi kód és a friss
kernelállapot együttes eredménye.
A labor két részből áll, és a kiinduló lab könyvtárban
végezhető el. Az első rész verziójelentést készít notebookból és
parancssorból, a második rész új környezetben reprodukálja a konvolúciós
ellenőrző számítást.
Első lépés: verziójelentés parancssorból. Hozzuk
létre a lab/version_report.py fájlt:
import json
import platform
import sys
import numpy as np
try:
import torch
except ImportError:
torch = None
report = {
"py_executable": sys.executable,
"python_version": platform.python_version(),
"python_implementation": platform.python_implementation(),
"platform": platform.platform(),
"numpy_version": np.__version__,
"torch_version": torch.__version__ if torch is not None else None,
"torch_cuda_available": torch.cuda.is_available() if torch is not None else False,
"device_contract": "cpu",
}
print(json.dumps(report, indent=2, sort_keys=True))Futtatás és elmentés:
cd lab
uv run python version_report.py | tee reports/version_report_cli.jsonA reports könyvtárat előzetesen létre kell hozni
(mkdir -p reports). A lockolt profilban mért kimenet; a
py_executable és a platform mező a saját gép
útvonalát és kernelét mutatja, itt rövidítve:
{
"device_contract": "cpu",
"numpy_version": "2.3.5",
"platform": "Linux-…-x86_64-with-glibc2.39",
"py_executable": "…/cnn-labor-1.0.0/lab/.venv/bin/python",
"python_implementation": "CPython",
"python_version": "3.12.3",
"torch_cuda_available": false,
"torch_version": "2.14.1+cpu"
}A NumPy- és PyTorch-verziónak a lock fájllal kell egyeznie; a Python
javítóverziója (3.12.x) és a platformsor gépenként
eltérhet.
Második lépés: ugyanaz a jelentés notebookból. Indítsuk a Jupyter szervert a projekt környezetéből:
cd lab
uv run jupyter notebookEgy új notebook első cellájában futtassuk a fenti
version_report.py tartalmát, majd válasszuk a
Restart & Run All műveletet, és mentsük
reports/version_report.ipynb néven. A notebook kernelét úgy
válasszuk, hogy a cella sys.executable értéke megegyezzen a
CLI-jelentés py_executable mezőjével. Ha a két érték eltér,
a notebook nem a projekt interpreterét használja; ilyenkor a
kernelkonfigurációt kell javítani, nem a kódot.
Harmadik lépés: konvolúciós kontroll új
környezetben. A laborcsomag konvolúciós referenciája, a
lab/conv_reference.py, a NumPy-referenciát véges
differenciával, --torch kapcsolóval PyTorch autograddal is
összeveti (float64, CPU, rögzített bemenetek), és az eredményt JSON-ként
a standard kimenetre írja. A csomag
lab/conv_check_report.json jelentése ennek a szkriptnek egy
tényleges futását rögzíti. Az új környezetben a cél annak ellenőrzése,
hogy a számítás a jelentésben rögzített értékekkel toleranciásan
egyezik:
cd lab
uv run python conv_reference.py --torch > reports/conv_check_local.jsonAz összehasonlítást a version_report.py mintájára egy
kis ellenőrző kóddal végezzük: beolvassuk a
lab/conv_check_report.json és a friss
reports/conv_check_local.json mezőit (a beágyazott
szótárakat is), és minden numerikus mezőre np.allclose-t
alkalmazunk rtol=1e-9, atol=1e-11 paraméterrel; a
numpy_version és torch_version szövegmezőket
csak kiírjuk. A lockolt profilban minden numerikus mező a tűrésen belül
egyezett. Az átadandó eredmény a három fájl:
version_report_cli.json, version_report.ipynb,
conv_check_local.json, valamint az összehasonlítás rövid
összegzése, amely megnevezi az eltérő mezőket, ha vannak.
A labor célja nem annak bizonyítása, hogy a konvolúció matematikailag helyes; azt a későbbi 15.1.–15.4. egységekben önállóan levezetjük. Itt a környezet reprodukálhatóságát ellenőrizzük: ha a friss környezetben ugyanaz a seed, dtype és device ugyanazt az ellenőrző számítást adja, akkor a csomagprofil és a futási szerződés konzisztens.
A leggyakoribb tévedés, hogy a notebook régi kimenetét az aktuális
kód futási bizonyítékának tekintjük. Ellenpélda: indítsunk egy kernelt,
futtassuk az x = 0.1 + 0.2 cellát, majd töröljük a cellát,
és írjunk egy új cellát, amely csak print(x)-et tartalmaz.
A notebook kimenete 0.30000000000000004 lesz, pedig a
jelenlegi dokumentumban az x definíciója már nem szerepel.
A kimenet tehát nem a dokumentumban látható kódból, hanem a kernel
rejtett állapotából származik. A Restart & Run All ezt a rejtett
állapotot megszünteti; ha ekkor a print(x) cella
NameError-t ad, akkor a korábbi kimenet valóban csak régi
kernelállapot volt.
F1 (fogalmi, nehézség 1). Magyarázza el, mi a
különbség a Python interpreter, a Jupyter kernel és a csomagprofil
között. Adjon egy konkrét forgatókönyvet, amelyben ugyanaz a notebook
két különböző sys.executable értékkel futhat. Átadandó:
három definíció és a forgatókönyv.
F2 (levezetési, nehézség 2). A szövegben kimondott lemma szerint determinisztikus program azonos környezeti leíróval azonos kimenetet ad. Bontsa ki a bizonyítást arra az esetre, amikor a program három lépésből áll: , , . Mutassa meg, hogy ha minden determinisztikus, akkor egyértelmű. Ezután indokolja meg, miért nem következik ebből a bitazonosság két különböző CPU-n. Átadandó: a háromlépéses levezetés, a feltételek és egy konkrét ellenpélda a lebegőpontos eltérésre.
F3 (implementációs, nehézség 1). Készítsen
environment_check.py CLI-szkriptet, amely kiírja a
sys.executable, a Python-verzió, a NumPy verzió, a PyTorch
verzió, a CUDA-elérhetőség és a device_contract mezőket
JSON formában. A szkript fogadjon egy --device
argumentumot, amelynek értéke cpu vagy cuda.
Ha cuda kérése esetén a CUDA nem elérhető, a szkript írjon
error mezőt, és 1-es kóddal lépjen ki. Átadandó: a teljes
kód, egy futtatási parancs, a kimenet és a hibaellenőrzés.
F4 (hibakeresési, nehézség 2). Egy hallgató a
notebookban a sys.executable cellát futtatja, és a kimenet
/usr/bin/python3. A parancssori
uv run python -c 'import sys; print(sys.executable)'
viszont .../lab/.venv/bin/python-t ad. A hallgató arra
következtet, hogy a uv elromlott. Mi a valódi hiba? Mi a
következménye annak, ha a notebookot a rendszerinterpretálóval futtatja?
Hogyan ellenőrizhető a javítás? Átadandó: ok, következmény, javítás és a
visszaellenőrzés módja.
F5 (kísérlettervezési, nehézség 3). Tervezzen kis
kísérletet annak eldöntésére, hogy a konvolúciós ellenőrző számítás két
izolált környezetben toleranciásan reprodukálható-e. Az egyik környezet
a labor CPU-profilja, a másik egy frissen létrehozott uv
környezet. Adja meg a kérdést, a rögzített seedet, dtype-ot, devicet,
baseline-t, metrikát, szükséges erőforrást és a következtetési korlátot.
Átadandó: a protokoll és annak magyarázata, hogy miért nem állítható
ebből általános platformfüggetlen bitazonosság.
F1 megoldás. Az interpreter a Python bytecode-ot
végrehajtó folyamat; azonosítója a sys.executable útvonal.
A Jupyter kernel egy szerverhez kapcsolódó folyamat, amely egy
interpreterrel futtatja a notebook celláit; a kernel és a notebook
dokumentum külön objektum. A csomagprofil a specifikált függőségek és
verziók zárt halmaza, például uv.lock. Forgatókönyv: a
notebookot a rendszer python3 kerneljével indítjuk,
miközben a projekt .venv-je már létezik. Ekkor a notebook
sys.executable értéke /usr/bin/python3, míg
ugyanaz a kód uv run python alatt a
.venv/bin/python értéket adja; a NumPy verziók is
eltérhetnek.
F2 megoldás. A három lépésre: rögzített, mert és adott. Ekkor egyértelmű, mert függvény. Ha egyértelmű, akkor is egyértelmű, végül is. A bizonyítás a függvény fogalmából és a teljes indukció specializálásából áll. A bitazonosság hiánya: két CPU eltérhet az utasításkészletben és a lebegőpontos reduction sorrendjében. Például egy nyolcelemű összeget az egyik gép módon, a másik páronkénti vagy szekvenciális sorrendben számol; a kerekítési hibák miatt a két eredmény utolsó bitje eltérhet. Ezért az azonos Python- és PyTorch-verzió sem garantál bitazonos kimenetet különböző platformon.
F3 megoldás. A kód:
import argparse
import json
import platform
import sys
import numpy as np
try:
import torch
except ImportError:
torch = None
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--device", choices=["cpu", "cuda"], default="cpu")
args = parser.parse_args()
cuda_available = bool(torch is not None and torch.cuda.is_available())
report = {
"py_executable": sys.executable,
"python_version": platform.python_version(),
"python_implementation": platform.python_implementation(),
"platform": platform.platform(),
"numpy_version": np.__version__,
"torch_version": torch.__version__ if torch is not None else None,
"cuda_available": cuda_available,
"device_contract": args.device,
}
if args.device == "cuda" and not cuda_available:
report["error"] = "CUDA requested but not available"
print(json.dumps(report, indent=2, sort_keys=True))
return 1
print(json.dumps(report, indent=2, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())Futtatás:
uv run python environment_check.py --device cpu. A CPU-s
kimenet cuda_available: false mellett
device_contract: "cpu"; a --device cuda
változat CPU-s gépen error mezővel és 1-es visszatérési
kóddal zárul. Az ellenőrzés a kilépési kódból és a JSON
error mezőjének jelenlétéből áll.
F4 megoldás. Az ok nem a uv hibája,
hanem az, hogy a notebook kernelét a rendszer-interpreterhez kötötték,
nem a projekt környezetéhez. Következmény: a notebook a rendszer
csomagprofilját látja, így a verziójelentés nem a labor profilját írja
le, és a reprodukálhatósági állítás sérül. A javítás a Jupyter
kernelspec átállítása vagy új kernel létrehozása a .venv
interpreterével, például
uv run python -m ipykernel install --user --name glyphlab
formában, majd a notebookban a glyphlab kernel
kiválasztása. Visszaellenőrzés: Restart & Run All után a notebook
sys.executable értékének meg kell egyeznie a
uv run python -c 'import sys; print(sys.executable)'
kimenettel.
F5 megoldás. Kérdés: ugyanazt az ellenőrző számítást
adja-e a laborprofil és egy frissen létrehozott izolált környezet?
Protokoll: a második környezetet uv venv --python 3.12 és
uv sync hozza létre ugyanabból a lock fájlból. Mindkét
környezetben a conv_reference.py --torch fut (float64, CPU,
a szkriptben rögzített bemenetekkel). Baseline: a kezdőlabor
lab/conv_check_report.json numerikus értékei. Metrika:
minden számszerű mezőre
np.allclose(..., rtol=1e-9, atol=1e-11); ezen kívül a
jelentés metaadatainak egyezését is ellenőrizzük. Erőforrás: kis CPU-s
futás, néhány másodperc. Következtetési korlát: a toleranciás egyezés a
két adott környezetre vonatkozik; nem bizonyít platformfüggetlen
bitazonosságot, és ha a másik gép buildje eltér, az egyezés meghiúsulhat
anélkül, hogy a kód hibás lenne. A [D02] dokumentáció szerint a seed
szükséges, de nem elégséges feltétel a bitazonos reprodukcióhoz.
Önellenőrző kérdések: meg tudom-e nevezni a
sys.executable értéket abban a környezetben, ahol a
modellemet futtattam? Rögzítettem-e a csomagprofilt lock fájlban? A
notebook kimenetét Restart & Run All után mentettem-e? Ha bármelyik
válasz nem, a kísérleti naplóból hiányzik a reprodukciós feltétel.
Visszahívás az 1.3. Adatfelosztás és a
megfigyelési egység egységből: ahogyan a split érvényessége attól
függ, hogy a megfigyelési egység szerint csoportosítottunk-e, úgy a
futási állítás érvényessége attól függ, hogy a környezeti leíró minden
befolyásoló komponensét rögzítettük-e. Visszahívás a 2.2. Lebegőpont és stabil elemi számítás egységből:
a numerikus egyenlőséget allclose-zal és indokolt
toleranciával ellenőrizzük, nem == művelettel.
A Python interpreter, a Jupyter kernel és a csomagprofil három
különböző réteg. A reprodukálhatóság akkor értelmes, ha a
determinisztikus program bemenete mellett az interpretert, a lockolt
függőségeket és a buildkörnyezetet is rögzítjük; ebből azonos
környezetben azonos kimenet következik. Platformok között bitazonosság
nem ígérhető, ezért a toleranciás allclose a helyes
ellenőrzés. A notebook csak Restart & Run All után tekinthető az
aktuális kód futási bizonyítékának. A labor verziójelentést készít
CLI-ből és notebookból, majd új környezetben reprodukálja a konvolúciós
kontrollt a kezdőlabor jelentésével összehasonlítva.
A 2.4. Kis tudományos szoftverprojekt
egységben kis tudományos szoftverprojektet szervezünk: a most rögzített
környezeti jelentés lesz a projekt README-jének és
futtatási szerződésének első szakasza. A verziójelentés adatai akkor
válnak hasznossá, amikor egy hiba vagy eltérő eredmény okát kell
lokalizálni.