Zdalny Trening GPU

Projekt ma gotowy przeplyw uruchamiania skryptow na zdalnym Jupyter/Kaggle GPU przez pyrun-jupyter.

Aktualny mechanizm

Wykorzystywane sa:

  • pyrun-jupyter do wykonywania kodu na zdalnym kernelu Jupyter,
  • scripts/run_remote.py jako lokalny runner,
  • scripts/remote_dispatch.py jako entrypoint po stronie zdalnej,
  • scripts/train.py i YAML configi jako glowny workflow treningowy,
  • opcjonalnie MinIO jako storage bridge dla datasetow i artefaktow.

Runner nie wysyla calego repo. Domyslny upload jest allowlista:

  • easy_rtdetr/
  • configs/
  • scripts/
  • pyproject.toml
  • README.md

Celowo pomijane sa katalogi robocze i duze dane:

  • .venv/
  • data/
  • artifacts/
  • runs/
  • .cache/
  • .exp/
  • site/
  • .git/

Przed wlasciwym uruchomieniem runner wypisuje:

  • sync_paths=...
  • sync_files=...
  • sync_size_mb=...
  • sync_uploaded=...

To jest najprostszy sanity-check. Dla normalnego treningu kodu projektu upload powinien byc maly. Ostatni smoke dla HGNet-V2-S wysylal 50 plikow i okolo 0.24 MB.

Przy dodaniu nowych configow liczba plikow moze minimalnie wzrosnac. Bieg BDD vehicle-3 + HGNet-V2-S wysylal 51 plikow i nadal okolo 0.24 MB.

Kaggle z dwiema kartami T4

Kaggle potrafi udostepnic dwie karty Tesla T4. Zwykly trening --device cuda uzywa jednego procesu, czyli praktycznie jednej karty. Aby uzyc obu kart, uruchom runner z:

.venv/bin/python scripts/run_remote.py custom \
  --device cuda \
  --nproc-per-node 2 \
  --script scripts/train.py \
  -- --config configs/synthetic/hgnetv2_s.yaml

--nproc-per-node 2 powoduje, ze scripts/remote_dispatch.py uruchamia zdalnie:

torchrun --standalone --nproc_per_node=2 scripts/train.py ...

Po poprawnym starcie log treningu powinien zawierac:

distributed=True world_size=2

To oznacza, ze trening idzie przez DDP na dwoch procesach. Przy CUDA kazdy proces dostaje swoj LOCAL_RANK i odpowiednia karte.

Przyklad smoke na Kaggle

Minimalny test HGNet-V2-S na synthetic dataset:

.venv/bin/python scripts/run_remote.py custom \
  --device cuda \
  --nproc-per-node 2 \
  --upload-chunk-kb 512 \
  --artifact artifacts/synthetic_hgnetv2_s_ddp_remote.pt \
  --script scripts/train.py \
  -- \
  --config configs/synthetic/hgnetv2_s.yaml \
  --output artifacts/synthetic_hgnetv2_s_ddp_remote.pt \
  --set solver.epochs=1 \
  --set data.train_samples=8 \
  --set data.eval_samples=2 \
  --set solver.batch_size=2 \
  --set runtime.amp=true \
  --set runtime.output_dir=runs/synthetic_hgnetv2_s_ddp_remote

Oczekiwane sygnaly poprawnego smoke:

  • sync_size_mb jest male, rzedu pojedynczych MB lub mniej,
  • log pokazuje distributed=True world_size=2,
  • log pokazuje checkpoint_saved=...,
  • lokalnie pojawia sie pobrany checkpoint w artifacts/remote/...,
  • optimizer_steps jest wieksze od 0, jezeli test ma potwierdzic realna aktualizacje wag.

Jesli optimizer_steps=0, to sam pipeline mogl przejsc, ale AMP GradScaler odrzucil wszystkie kroki optymalizatora. Dla prawdziwego treningu trzeba wtedy zmniejszyc runtime.amp_init_scale, wylaczyc AMP albo sprawdzic stabilnosc lossow.

MinIO dla datasetow i artefaktow

Przy wiekszych datasetach synchronizacja przez websocket jest niepraktyczna:

  • wolna,
  • podatna na zrywanie transferu,
  • zbyt ciezka dla calych katalogow datasetow.

Dlatego duze datasety powinny isc przez MinIO:

  1. lokalny dataset jest stage'owany do MinIO,
  2. generowany jest presigned URL,
  3. zdalny kernel pobiera archive bezposrednio do data/,
  4. po treningu checkpoint moze wrocic przez MinIO lub przez standardowy download artefaktow.

Przyklad:

.venv/bin/python scripts/run_remote.py train-voc-car \
  --device cuda \
  --minio-endpoint http://188.245.77.217:9000 \
  --minio-access-key admin \
  --minio-secret-key '***' \
  -- --set solver.epochs=5 --set solver.batch_size=4

Ustawienia MinIO mozna trzymac w scripts/.env:

  • MINIO_ENDPOINT
  • MINIO_ACCESS_KEY
  • MINIO_SECRET_KEY
  • MINIO_BUCKET

URL do Kaggle/Jupyter tez moze byc w scripts/.env jako:

  • KAGGLE_PROXY_URL
  • PYRUN_JUPYTER_URL

Aktualnie aktywny dataset roboczy w MinIO to vehicles3:

datasets/vehicles3/roboflow-v2-release/vehicles3-roboflow-v2-release.tar.gz

Jest to przetworzony Roboflow vehicles v2 release z klasami:

  • car
  • bus
  • truck

Oczekiwany root po rozpakowaniu:

data/vehicles3

SHA256 archiwum:

df67861809359786237779affa2fe3f0334ab48f855494922199c9c3a065be56

Przy uzyciu gotowego obiektu MinIO trzeba podac oczekiwany root po rozpakowaniu. Bez --dataset-expected-root runner inferuje root z nazwy archiwum, co przy nazwach wersjonowanych moze dac zly path.

Przyklad treningu vehicles3 na dwoch T4:

.venv/bin/python scripts/run_remote.py custom \
  --device cuda \
  --nproc-per-node 2 \
  --timeout 7200 \
  --dataset-source .exp/cache/datasets/vehicles3-roboflow-v2-release.tar.gz \
  --dataset-object-name datasets/vehicles3/roboflow-v2-release/vehicles3-roboflow-v2-release.tar.gz \
  --dataset-expected-root data/vehicles3 \
  --artifact artifacts/vehicles3_resnet50.pt \
  --script scripts/train.py \
  -- \
  --config configs/vehicles3/resnet50.yaml \
  --output artifacts/vehicles3_resnet50.pt \
  --set solver.epochs=5 \
  --set runtime.output_dir=runs/vehicles3_resnet50

Wazna obserwacja praktyczna: MinIO pozwala nie wysylac datasetu przez websocket, ale Kaggle nie zawsze zachowuje lokalny katalog data/ miedzy sesjami. To znaczy, ze ten sam obiekt MinIO jest reuse'owany jako zrodlo, ale zdalny kernel moze pobrac i rozpakowac archiwum ponownie w nowej sesji.

Historyczny dataset BDD vehicle-3 byl trzymany pod:

datasets/bdd100k_vehicle3/trainval90-seed0/bdd100k_vehicle3-trainval90-seed0.tar.gz

Historyczny subset COCO traffic-5 byl trzymany pod:

datasets/coco_traffic5/5k1k-seed0-area0002/coco_traffic5-5k1k-seed0-area0002.tar.gz

Te dwa obiekty zostaly usuniete z MinIO 2026-05-10, zeby zwolnic miejsce. Ich configi i historia eksperymentow zostaja w repo jako dokumentacja, ale nie nalezy zakladac, ze dane nadal sa dostepne w storage.

Dostepne presety

Runner ma przygotowane presety:

  • train-pennfudan
  • train-synthetic
  • eval-pennfudan
  • visualize-pennfudan
  • train-voc-car
  • eval-voc-car
  • visualize-voc-car
  • custom --script path/to/script.py

Presety sa cienka warstwa nad wspolnymi wrapperami:

  • scripts/train.py
  • scripts/eval.py
  • scripts/visualize.py

Inputy poza datasetem (--remote-input-object)

Czasami trening wymaga dodatkowych plikow oprocz datasetu - np. pretrenowane wagi backbone'u. Runner ma do tego flag --remote-input-object OBJECT=DEST:

--remote-input-object pretrained/hgnetv2_b0_ssld_stage1.pt=artifacts/hgnetv2_b0_pretrained.pt

Format: OBJECT_NAME_W_MINIO=DEST_PATH_NA_KAGGLE. Plik musi byc juz wgrany do MinIO przed uruchomieniem. Mozna podac flag wielokrotnie.

Konwencja: artifacts/ jest excludowane z domyslnego sync, wiec wszystko wieksze powinno isc przez MinIO + --remote-input-object, a nie przez websocket upload.

Fragmentacja pamieci GPU

scripts/remote_dispatch.py ustawia teraz domyslnie:

os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")

To pomaga gdy PyTorch ma duzo reserved-but-unallocated pamieci przez fragmentacje (typowy objaw przy zmiennych ksztaltach lub mocnych zmianach memory pressure miedzy stepami). Nigdy nie szkodzi, czasem ratuje przed OOM bez zmiany batcha.

Typowe ostrzezenia

Nie kazde ostrzezenie z Kaggle oznacza blad treningu:

  • hostname of the client socket cannot be retrieved zwykle jest ostrzezeniem srodowiska Kaggle/c10d i nie blokuje DDP.
  • padding='same'... zero-padded copy pochodzi z konwolucji i jest ostrzezeniem wydajnosciowym.
  • Grad strides do not match bucket view strides moze obnizac wydajnosc DDP, ale nie jest bledem funkcjonalnym.
  • scheduler_step_skipped=no_optimizer_step oznacza, ze w danej epoce nie bylo realnego kroku optymalizatora, najczesciej przez AMP overflow w bardzo krotkim smoke tescie.

Co wymaga dyscypliny

  • Nie dodawac przez --sync-path duzych katalogow typu .venv, data, artifacts, runs.
  • Dla duzych danych uzywac MinIO, a nie websocket uploadu przez kernel.
  • Po duzej zmianie architektury traktowac stare checkpointy jako niekompatybilne.
  • Przy dluzszych biegach ustawic kontrolowany runtime.output_dir i --artifact-dir.
  • Dla treningu na dwoch T4 sprawdzac w logu world_size=2, a nie tylko device=cuda.