From 3fdd7ab3f025de95d83ed0444df6ce78f52fe502 Mon Sep 17 00:00:00 2001 From: nbright Date: Fri, 21 Aug 2026 12:33:22 +0900 Subject: [PATCH] Split the unattended run into a GPU-free phase and a GPU phase The target machine's card is busy with someone else's job, so a single end-to-end script stalls on work that does not actually need a GPU. Compiling the CUDA extensions needs nvcc, not a device, and downloading 18 GB of data needs neither. Those are the slow parts (~50 min + ~30 min), so phase A now runs entirely without the card: run_setup.sh bootstrap, conda, extensions, patches, data no GPU run_train.sh voxel_max measurement, training, evaluation GPU run_setup reports the GPU but never fails on it, and verify_env.py gained SKIP_CUDA_CHECK so import coverage still runs when no device is visible. TORCH_CUDA_ARCH_LIST is stated rather than probed, since the card may be unavailable at build time. run_train waits for the GPU instead of failing when it is busy: it polls until enough VRAM frees up (12h default), so it can be queued ahead of time. Past the deadline it proceeds anyway and lets the measured voxel_max adapt to whatever is actually free. keepalive.sh now takes the phase to supervise. Replaces run_all.sh and RUN.md with SETUP.md and TRAIN.md. Adds selfcheck.sh, which syntax-checks every script and flags CRLF endings - a shell script with either fails at its first line, which for an unattended weekend run means losing the weekend. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 52 ++++-- RUN.md | 207 ----------------------- SETUP.md | 334 +++++++++++++++++--------------------- TRAIN.md | 215 ++++++++++++++++++++++++ scripts/keepalive.sh | 74 +++++---- scripts/run_all.sh | 305 ---------------------------------- scripts/run_setup.sh | 209 ++++++++++++++++++++++++ scripts/run_train.sh | 288 ++++++++++++++++++++++++++++++++ scripts/selfcheck.sh | 37 +++++ scripts/test_preflight.sh | 7 - scripts/verify_env.py | 19 ++- 11 files changed, 989 insertions(+), 758 deletions(-) delete mode 100644 RUN.md create mode 100644 TRAIN.md delete mode 100644 scripts/run_all.sh create mode 100644 scripts/run_setup.sh create mode 100644 scripts/run_train.sh create mode 100644 scripts/selfcheck.sh delete mode 100644 scripts/test_preflight.sh diff --git a/README.md b/README.md index 4da35b3..ef4dc5f 100644 --- a/README.md +++ b/README.md @@ -5,46 +5,62 @@ 대상 데이터: 서산 명천 도로 프로젝트 — ContextCapture OBJ 6블록, EPSG:5186. -## 이 레포에 있는 것 +## 문서 | 경로 | 내용 | |---|---| -| [RUN.md](RUN.md) | **무인 실행 — 명령 하나로 학습까지. 여기부터** | -| [SETUP.md](SETUP.md) | 단계별 수동 실행 (문제 생겼을 때 확인용) | -| [docs/pipeline.html](docs/pipeline.html) | 6단계 공정 정의 (브라우저로 열 것) | +| [SETUP.md](SETUP.md) | **1단계 — 환경 구축 (GPU 불필요). 여기부터** | +| [TRAIN.md](TRAIN.md) | 2단계 — 학습 (GPU 필요) | +| [docs/pipeline.html](docs/pipeline.html) | 전체 6단계 공정 정의 (브라우저로 열 것) | | [docs/SUM-Parts-검토노트.md](docs/SUM-Parts-검토노트.md) | 트러블슈팅 16건, 데이터 스키마 실측, 라이선스 | -| [scripts/](scripts/) | 환경 구축 · 학습 · 평가 · 변환 스크립트 47개 | +| [scripts/](scripts/) | 환경 구축 · 학습 · 평가 · 변환 스크립트 | 데이터와 체크포인트는 커밋하지 않는다(`.gitignore`). 스크립트로 재생성한다. -## 요약 +## 왜 두 단계로 나눴나 -- **모델**: PointVector (논문 mIoU 70.0%, 번들 최고이자 최속) -- **학습 자산**: SUM Parts face 트랙 13클래스 → 우리 4클래스로 통합 -- **제약**: 저자가 학습 가중치를 공개하지 않아 직접 학습이 유일한 경로 -- **VRAM**: 논문 설정 `voxel_max 64000`은 16.5GB 필요 → 12GB 카드 불가, 24GB 필요 +CUDA 확장 빌드는 **`nvcc` 컴파일이지 GPU 실행이 아니다.** 데이터 다운로드도 마찬가지다. +오래 걸리는 작업(빌드 50분 + 데이터 30분)이 전부 GPU 없이 되므로, +카드가 남의 작업에 물려 있어도 1단계를 미리 끝낼 수 있다. + +| | GPU | 시간 | +|---|---|---| +| 1단계 · 환경 구축 | 불필요 | ~80분 | +| 2단계 · 학습·평가 | 필요 | 3~6시간 | + +2단계는 카드가 바쁘면 **실패하지 않고 빌 때까지 기다린다.** ## 빠른 시작 (무인) ```bash -# 1. HF 토큰 (게이트는 계정 단위 — 이미 수락한 계정 토큰을 복사) +# HF 토큰 — 유일한 수동 작업. 게이트는 계정 단위라 토큰만 옮기면 된다 mkdir -p ~/.cache/huggingface && echo hf_xxxxx > ~/.cache/huggingface/token -# 2. 사전점검 — OK 안 나오면 여기서 해결하고 갈 것 -bash scripts/test_preflight.sh +# 1단계 — GPU 불필요 +bash scripts/selfcheck.sh # 스크립트 무결성 +RUN_DRYRUN=1 bash scripts/run_setup.sh # 전제조건 검사 +setsid nohup bash scripts/keepalive.sh setup > ~/keepalive-setup.out 2>&1 & -# 3. 실행. 베어 머신 → 학습 완료까지 5~8시간, 멈춰도 이어간다 -setsid nohup bash scripts/keepalive.sh > ~/keepalive.out 2>&1 & +# 2단계 — GPU 필요. 바쁘면 빌 때까지 대기 +setsid nohup bash scripts/keepalive.sh train > ~/keepalive-train.out 2>&1 & ``` 확인: ```bash -cat ~/sum-parts/runs/run_all/STATUS -cat ~/sum-parts/runs/coarse_eval/coarse.txt # 4클래스 통합 성적 +cat ~/sum-parts/runs/setup/STATUS +cat ~/sum-parts/runs/train/STATUS +cat ~/sum-parts/runs/coarse_eval/coarse.txt # 4클래스 통합 성적 ← 핵심 ``` -자세한 것은 [RUN.md](RUN.md). +멈춰도 이어간다 — 감시자가 재기동하고, 학습은 체크포인트에서 재개한다. + +## 요약 + +- **모델**: PointVector (논문 mIoU 70.0 %, 번들 최고이자 최속) +- **학습 자산**: SUM Parts face 트랙 13클래스 → 우리 4클래스로 통합 +- **제약**: 저자가 학습 가중치를 공개하지 않아 직접 학습이 유일한 경로 +- **VRAM**: 논문 설정 `voxel_max 64000`은 약 16.5 GB 필요 → 12 GB 카드 불가, 24 GB 필요 ## 라이선스 diff --git a/RUN.md b/RUN.md deleted file mode 100644 index eeeacf8..0000000 --- a/RUN.md +++ /dev/null @@ -1,207 +0,0 @@ -# 무인 실행 — 3090 머신 - -주말에 손 못 대는 상황에서 **베어 머신 → 학습 완료**까지 한 번에 돌리는 절차. - -전체 5~8시간. 멈춰도 알아서 이어간다. - ---- - -## 퇴근 전 — 3단계 - -### 1. HF 토큰 옮기기 - -**자동화할 수 없는 유일한 부분이다.** 데이터셋에 게이트가 걸려 있고 -수락은 브라우저에서 해야 한다. 다만 **수락은 계정 단위**라 이미 수락한 계정의 -토큰만 옮기면 된다. - -기존 머신(3060)에서 토큰 확인: - -```powershell -type $env:USERPROFILE\.cache\huggingface\token -``` - -3090 머신 WSL에서: - -```bash -mkdir -p ~/.cache/huggingface -echo hf_xxxxx > ~/.cache/huggingface/token -``` - -아직 어느 계정으로도 수락한 적이 없다면 브라우저에서 1회: -https://huggingface.co/datasets/gwxgrxhyz/SUM-Parts → 로그인 → CC BY-NC 4.0 수락 - -### 2. 클론 + 사전점검 - -```bash -git clone https://gitea.hmac.kr/kimminsung/sum-parts-test.git -cd sum-parts-test -bash scripts/test_preflight.sh -``` - -**`preflight OK` 가 나와야 한다.** 확인하는 것: - -| 항목 | 실패하면 | -|---|---| -| `git` `curl` `python3` `tar` | 설치 필요 | -| `nvidia-smi` + GPU 인식 | WSL 드라이버 문제 — 여기서 해결하고 가야 함 | -| `$HOME` 여유 35 GB | 공간 확보 | -| HF 토큰 + 게이트 통과 | 위 1번 | - -**여기서 실패한 채로 나가면 주말을 날린다.** 오래 걸리는 단계는 전부 이 뒤에 있다. - -### 3. 실행 - -```bash -setsid nohup bash scripts/keepalive.sh > ~/keepalive.out 2>&1 & -``` - -터미널을 닫아도, 로그아웃해도 계속 돈다. -(`wsl --shutdown`이나 Windows 재부팅은 못 버틴다 — WSL 안에서 도는 건 다 마찬가지다. -그 경우 월요일에 같은 명령을 다시 치면 중단 지점부터 이어간다.) - ---- - -## 월요일 확인 - -```bash -cat ~/sum-parts/runs/run_all/STATUS -``` - -``` -state : DONE -phase : done -cfg : pointvector-xl -voxel_max: 64000 -``` - -`state`가 볼 값이다: - -| state | 뜻 | -|---|---| -| `DONE` | 완료 | -| `running` / `retrying` | 진행 중 | -| `FAILED` | 재시도로 안 고쳐지는 문제 — `note` 줄에 원인 | - -로그: - -```bash -tail -80 ~/sum-parts/runs/run_all/run.log # 전체 진행 -cat ~/sum-parts/runs/run_all/keepalive.log # 재기동 이력 -bash scripts/check_training.sh # 학습 상세 -``` - -결과: - -```bash -cat ~/sum-parts/runs/coarse_eval/coarse.txt # 4클래스 통합 성적 ← 핵심 -``` - ---- - -## 멈춰도 계속 도는 구조 - -3중으로 감싸져 있다. - -| 층 | 담당 | 동작 | -|---|---|---| -| `keepalive.sh` | 프로세스 전체 사망 | VM 재시작·OOM kill 후 재기동 (최대 40회, 90초 간격) | -| `run_all.sh` | 단계 실패 | 지수 백오프 재시도 (단계당 4회, 60→120→240초) | -| `train_watchdog.sh` | 학습 크래시 | 최신 체크포인트에서 재개 (최대 8회) | - -**전 단계가 멱등이라 처음부터 다시 돌려도 안전하다.** -conda 환경·소스 clone·패치·다운로드 아카이브·학습 체크포인트를 각각 감지해서 건너뛴다. -학습은 `RESUME_CKPT`로 이어받으므로 재시작해도 epoch 1로 돌아가지 않는다. - -### preflight만 재시도하지 않는다 — 의도적이다 - -HF 토큰 없음이나 GPU 미인식은 재시도해도 고쳐지지 않는다. -주말 내내 무의미하게 재시도하는 것보다 1분 만에 실패하고 `FAILED`를 남기는 게 낫다. - ---- - -## voxel_max 자동 결정 - -논문 설정은 `voxel_max: 64000`이고 약 16.5 GB가 필요하다. -3060(12 GB)에서는 못 들어가서 24000으로 낮췄고, **그게 3090으로 옮기는 이유다.** - -스크립트가 높은 값부터 내려가며 **실측**해서 실제로 들어가는 첫 값을 쓴다. - -``` -64000 → 48000 → 40000 → 32000 → 24000 -``` - -> ⚠️ **WSL2에서 VRAM 초과는 OOM을 내지 않는다.** -> 드라이버가 호스트 RAM으로 흘려서 **조용히 완주한다 — 25~100배 느리게.** -> 12 GB 카드에서 `peak 15.47 GB`가 찍힌다. 그래서 "돌아가더라"를 믿지 않고 -> peak 할당량으로 판정한다. -> -> 판별 보조 지표는 **전력**이다. 사용률 100 %인데 전력이 낮으면 -> (3060 기준 60 W대) 연산이 아니라 PCIe 전송 대기다. 정상이면 140 W대. - ---- - -## 실행 순서와 소요 - -| # | 단계 | 시간 | 재시도 | -|---|---|---|---| -| 1 | preflight | 10초 | ✗ 즉시 실패 | -| 2 | bootstrap (miniconda + 업스트림 clone) | 5분 | ✓ | -| 3 | conda 환경 (CUDA 11.8 + torch 2.0.1) | 20분 | ✓ | -| 4 | CUDA 확장 5종 빌드 | 25분 | ✓ | -| 5 | 소스 패치 3건 | 10초 | ✓ | -| 6 | 환경 검증 | 30초 | ✓ | -| 7 | 데이터 다운로드·전개 (5.3 → 18 GB) | 30분 | ✓ | -| 8 | split 정리 + 링크 | 10초 | ✓ | -| 9 | voxel_max 측정 | 10분 | ✓ | -| 10 | **학습 100 epoch** | **3~6시간** | ✓ 체크포인트 재개 | -| 11 | 평가 (val + test + 4클래스 통합) | 10분 | ✓ | - ---- - -## 설정 변경 - -기본값으로 두면 된다. 바꾸려면 환경변수로: - -```bash -CFG=pointnext-xl \ -EPOCHS=50 \ -VOXEL_CANDIDATES="64000 48000" \ - setsid nohup bash scripts/keepalive.sh > ~/keepalive.out 2>&1 & -``` - -| 변수 | 기본 | 뜻 | -|---|---|---| -| `CFG` | `pointvector-xl` | 모델 (논문 mIoU 70.0 %, 번들 최고이자 최속) | -| `EPOCHS` | `100` | | -| `VAL_FREQ` | `5` | 검증 주기 | -| `VOXEL_CANDIDATES` | `64000 48000 40000 32000 24000` | 높은 값부터 시도 | -| `STEP_RETRIES` | `4` | 단계별 재시도 | -| `MAX_RESTARTS` | `40` | keepalive 재기동 상한 | - ---- - -## 중단 - -```bash -pkill -f keepalive.sh # 감시자 먼저 — 안 그러면 다시 살린다 -bash scripts/stop_training.sh -``` - -체크포인트는 남는다. 다시 켜면 이어간다. - ---- - -## Claude는 필요 없다 - -이 스크립트들은 순수 bash다. Claude 승인이 필요 없고, 직접 돌리는 게 확실하다. - -Claude로 감독시키고 싶다면 `--dangerously-skip-permissions`가 필요한데, -무인 실행에는 스크립트가 더 안전하다. - ---- - -## 관련 문서 - -- [SETUP.md](SETUP.md) — 단계별 수동 실행 (문제 생겼을 때 하나씩 확인용) -- [docs/pipeline.html](docs/pipeline.html) — 전체 공정 정의 -- [docs/SUM-Parts-검토노트.md](docs/SUM-Parts-검토노트.md) — 트러블슈팅 16건 diff --git a/SETUP.md b/SETUP.md index abc8708..144291b 100644 --- a/SETUP.md +++ b/SETUP.md @@ -1,224 +1,180 @@ -# 새 머신에서 시작하기 +# 1단계 — 환경 구축 (GPU 불필요) -RTX 3090(24GB) 머신에서 SUM Parts + PointVector 학습을 재현하는 절차. +**GPU가 다른 작업에 물려 있어도 지금 돌릴 수 있다.** +오래 걸리는 작업(확장 빌드 50분 + 데이터 30분)이 전부 여기 들어 있다. -3060(12GB)에서는 VRAM이 모자라 `voxel_max`를 24000으로 낮춰야 했다. -**24GB에서는 논문 설정 64000을 그대로 쓴다** — 이게 이 머신으로 옮기는 유일한 이유다. +끝나면 [TRAIN.md](TRAIN.md)로 간다. --- -## 0. 전제 +## 왜 나눴나 + +CUDA 확장 빌드는 **`nvcc` 컴파일이지 GPU 실행이 아니다.** 데이터 다운로드도 마찬가지다. +즉 카드가 남의 작업으로 바빠도 이 단계는 전부 끝낼 수 있다. + +| 작업 | GPU 필요 | 시간 | +|---|---|---| +| miniconda 설치 · 업스트림 clone | ✗ | 5분 | +| conda 환경 · CUDA 11.8 · torch 2.0.1 | ✗ | 20분 | +| **CUDA 확장 5종 빌드** | ✗ (nvcc만) | 25분 | +| 소스 패치 3건 | ✗ | 10초 | +| import 검증 | ✗ | 30초 | +| 데이터 다운로드·전개 (5.3 → 18 GB) | ✗ | 30분 | +| split 정리 · 링크 | ✗ | 10초 | + +**합계 약 80분.** 이걸 미리 해두면 GPU가 비는 순간 바로 학습에 들어간다. + +--- + +## 전제 | 항목 | 필요 | |---|---| | OS | Windows + WSL2 (Ubuntu 22.04) 또는 네이티브 Linux | -| GPU | RTX 3090 24GB, 드라이버가 WSL에서 인식될 것 | -| 디스크 | **35GB 이상 여유** | -| 계정 | HuggingFace 계정 (데이터 게이트 수락에 필요) | -| 도구 | `git`, `curl` (sudo는 불필요) | +| 디스크 | `$HOME`에 **35 GB 이상** | +| 도구 | `git` `curl` `python3` `tar` (sudo 불필요) | +| 계정 | HuggingFace 토큰 | -디스크 내역 — 전부 이 절차가 만들어낸다: +GPU는 **없어도 된다.** 있으면 정보로만 표시한다. + +### 디스크 내역 | 항목 | 크기 | |---|---| | miniconda + 환경 | 12 GB | | 업스트림 소스 | 1.7 GB | | 데이터셋 (아카이브 포함) | 18 GB | -| 체크포인트·로그 | 1.5 GB | - -WSL이면 `nvidia-smi`가 WSL 안에서 GPU를 보여야 한다. 안 보이면 여기서 멈추고 드라이버부터. - -```bash -nvidia-smi # RTX 3090 24576MiB 가 보여야 함 -``` --- -## 1. 클론 +## 1. HF 토큰 — 유일한 수동 작업 + +데이터셋에 게이트가 걸려 있다. 수락은 브라우저에서만 되고 **자동화 불가**다. +다만 **수락은 계정 단위**라, 이미 수락한 계정의 토큰만 옮기면 된다. + +기존 머신에서 값 확인: + +```powershell +type $env:USERPROFILE\.cache\huggingface\token +``` + +이 머신 WSL에서: ```bash -git clone /sum-parts-test.git +mkdir -p ~/.cache/huggingface +echo hf_xxxxx > ~/.cache/huggingface/token +``` + +아직 어느 계정으로도 수락한 적이 없다면 브라우저에서 1회: +https://huggingface.co/datasets/gwxgrxhyz/SUM-Parts → 로그인 → CC BY-NC 4.0 수락 + +--- + +## 2. 클론 + 사전점검 + +```bash +git clone https://gitea.hmac.kr/kimminsung/sum-parts-test.git cd sum-parts-test + +bash scripts/selfcheck.sh # 스크립트 무결성 +RUN_DRYRUN=1 bash scripts/run_setup.sh # 전제조건만 검사 ``` -스크립트는 경로를 `/mnt/d/MYCLAUDE_PROJECT/sum-parts-test/scripts`로 하드코딩한 곳이 있다. -다른 경로에 두면 아래 한 줄로 일괄 치환한다. +**`preflight OK` 가 나와야 한다.** 검사 항목: -```bash -NEW=$(pwd)/scripts -grep -rl '/mnt/d/MYCLAUDE_PROJECT/sum-parts-test/scripts' scripts/ \ - | xargs sed -i "s|/mnt/d/MYCLAUDE_PROJECT/sum-parts-test/scripts|$NEW|g" -``` - ---- - -## 2. 환경 구축 (약 50분) - -먼저 부트스트랩. **새 머신에는 miniconda도 업스트림 소스도 없다** — -이 레포에는 우리 스크립트만 들어 있고, 벤치마크 본체는 별도 clone이 필요하다. - -```bash -bash scripts/bootstrap.sh # miniconda 설치 + 업스트림 clone + GPU 확인 -``` - -이게 만드는 것: - -| 경로 | 내용 | 크기 | -|---|---|---| -| `~/miniconda3` | conda (sudo 불필요, $HOME에 설치) | 0.5 GB | -| `~/sum-parts` | [SUM-Parts-Benchmarks](https://github.com/tudelft3d/SUM-Parts-Benchmarks) clone | 1.7 GB | - -부트스트랩 끝에 GPU 용량을 찍어준다. **24GB면 `voxel_max=64000` 가능**하다고 알려준다. - -이어서 환경: - -```bash -bash scripts/setup_env.sh # conda env + CUDA 11.8 + torch 2.0.1 -bash scripts/setup_pointnext.sh # 의존성 + CUDA 확장 5종 빌드 -``` - -`setup_env.sh` 안의 `TORCH_CUDA_ARCH_LIST`를 3090에 맞춰야 한다. - -```bash -# RTX 3060 = 8.6, RTX 3090 = 8.6 (둘 다 Ampere라 동일, 수정 불필요) -# RTX 4090이면 8.9로 변경 -``` - -이어서 업스트림 소스 패치 4건. **전부 멱등이라 여러 번 실행해도 안전하다.** - -```bash -bash scripts/patch_numpy_aliases.sh # np.long 등 제거된 별칭 + collections ABC -bash scripts/patch_unlabeled_test.sh # 블라인드 test셋의 label=-1 처리 -bash scripts/patch_val_mode.sh # mode=val 의 UnboundLocalError -``` - -**게이트 — 통과 못 하면 다음으로 가지 말 것:** - -```bash -python scripts/verify_env.py -# 기대: ALL OK (확장 6종 import + openpoints 체인 전부) -``` - ---- - -## 3. 데이터 (약 30분) - -HuggingFace 게이트를 **브라우저에서 1회 수동 수락**해야 한다. 자동화 불가. - -1. https://huggingface.co/datasets/gwxgrxhyz/SUM-Parts -2. 로그인 → CC BY-NC 4.0 수락 -3. 토큰을 `~/.cache/huggingface/token`에 두거나 `huggingface-cli login` - -```bash -bash scripts/download_data.sh all # 5.3GB 받아 13GB로 전개 -bash scripts/prepare_full_split.sh # validate/ -> val/ 심볼릭 링크 -bash scripts/link_data.sh # PointNeXt_bundle/data 연결 -``` - -**게이트:** 세 split이 `24 / 8 / 8`로 집계될 것. - ---- - -## 4. VRAM 확인 (5분) — 24GB에서 반드시 먼저 - -논문 설정이 실제로 들어가는지 확인한다. 3060에서는 여기서 실패했다. - -```bash -CFG=pointvector-xl ITERS=5 bash scripts/sweep_voxel_max.sh 40000 48000 64000 -``` - -기대 결과 (3090 24GB): - -| voxel_max | peak VRAM | 판정 | -|---|---|---| -| 64000 | 약 16.5G | ✅ 들어감 | - -**`fits? = yes`가 나와야 한다.** `NO (spilling)`이면 그 값은 쓰면 안 된다. - -> ⚠️ **WSL2에서 VRAM 초과는 OOM을 내지 않는다.** 드라이버가 호스트 RAM으로 흘려서 -> 학습이 **조용히 완주한다 — 25~100배 느리게.** 반드시 peak VRAM 수치로 판정할 것. -> 판별 보조 지표는 **전력**: 사용률 100%인데 전력이 낮으면 연산이 아니라 PCIe 전송 대기다. - ---- - -## 5. 학습 (논문 설정, 약 3~5시간) - -```bash -CFG_VOXEL_MAX=64000 VAL_VOXEL_MAX=64000 \ - bash scripts/launch_overnight.sh pointvector-xl -``` - -`setsid nohup`으로 분리 실행되므로 터미널·세션을 닫아도 살아남는다. -크래시하면 최신 체크포인트에서 자동 재개한다(최대 8회). - -진행 확인: - -```bash -bash scripts/check_training.sh # epoch, GPU, best miou -bash scripts/verify_speed.sh # HEALTHY / DEGRADED 판정 -bash scripts/epoch_timing.sh # epoch별 소요시간, 감속 지점 특정 -``` - -중단: - -```bash -bash scripts/stop_training.sh # 워치독 먼저 죽여서 자동 재시작 방지 -``` - -**게이트:** `verify_speed.sh`가 `HEALTHY`, peak VRAM이 카드 용량의 95% 미만. - ---- - -## 6. 평가 - -```bash -bash scripts/final_eval.sh # val(라벨 있음) + test(블라인드, 예측만) -bash scripts/eval_coarse.sh # 4클래스 통합 성적 ← 우리 과제 기준 -``` - -`eval_coarse.sh`가 실제로 중요한 수치를 낸다. SUM 13클래스를 -건물 / 수목 / 차량 / 지면으로 합쳐서 채점한다. - -**게이트:** 통합 mIoU가 **"전부 건물" 무지성 분류기(IoU 약 67%)를 이길 것.** -절대값이 아니라 baseline 대비로 판정한다. - ---- - -## 참고 — 3060 실측값 (비교 기준) - -| 모델 | voxel_max | peak VRAM | s/iter | 100 epoch | -|---|---|---|---|---| -| pointnet | 64000 | 6.01G | 0.291 | 2.9h | -| pointnet++msg | 64000 | 4.16G | 0.675 | 6.8h | -| pointvector-xl | 24000 | 6.46G | 0.402 | 2.9h | -| pointvector-xl | 64000 | **16.49G** | 13.113 | ❌ 12GB 불가 | -| pointnext-xl | 32000 | 8.03G | 0.635 | 6.4h | -| pointnext-xl | 64000 | **15.47G** | 46.980 | ❌ 12GB 불가 | - -논문 보고치 (face 트랙, 12클래스): - -| 모델 | mIoU | +| 항목 | 실패하면 | |---|---| -| PointNet | 15.1% | -| PointNet++ | 33.1% | -| PointNeXt | 65.3% | -| **PointVector** | **70.0%** | +| `git` `curl` `python3` `tar` | 설치 | +| `$HOME` 여유 35 GB | 공간 확보 | +| HF 토큰 + 게이트 통과 | 위 1번 | -3060에서 pointnet 100 epoch 실측 = **17.19%** (논문 15.1%와 근사, 재현 확인됨). +GPU는 검사하지 않는다 — 이 단계엔 필요 없다. --- -## 알려진 제약 +## 3. 실행 -- **test 세트는 블라인드다.** 라벨이 전부 `-1`이라 로컬 채점이 불가능하다. - 논문 수치와 직접 대조하려면 예측을 저자(gaoweixiaocuhk@gmail.com)에게 보내야 한다. -- **`voxel_max`는 중립적 손잡이가 아니다.** 모델의 동작점 일부다. - 같은 체크포인트가 검증 프로토콜에 따라 mIoU 17.19 / 4.20으로 갈렸다. -- **라이선스**: 데이터 CC BY-NC 4.0, 코드 GPL-3.0. 상업 이용은 저자 허락 필요. +```bash +setsid nohup bash scripts/keepalive.sh setup > ~/keepalive-setup.out 2>&1 & +``` + +터미널을 닫아도 계속 돈다. 약 80분. + +### 아키텍처가 8.6이 아니라면 + +확장은 특정 아키텍처로 컴파일된다. GPU를 조회할 수 없을 수도 있으니 **명시**한다. + +| GPU | 값 | +|---|---| +| RTX 3060 / 3070 / 3080 / **3090** | `8.6` (기본값) | +| RTX 4090 | `8.9` | +| A100 | `8.0` | + +3090이면 기본값 그대로 두면 된다. 다르면: + +```bash +TORCH_CUDA_ARCH_LIST=8.9 setsid nohup bash scripts/keepalive.sh setup \ + > ~/keepalive-setup.out 2>&1 & +``` --- -## 상세 기록 +## 4. 확인 -- [docs/SUM-Parts-검토노트.md](docs/SUM-Parts-검토노트.md) — 트러블슈팅 16건, 실측 데이터 스키마, 라이선스 검토 -- [docs/pipeline.html](docs/pipeline.html) — 전체 공정 정의 (브라우저로 열 것) +```bash +cat ~/sum-parts/runs/setup/STATUS +``` + +``` +state : DONE +phase : done +arch : 8.6 +``` + +| state | 뜻 | +|---|---| +| `DONE` | 완료 → [TRAIN.md](TRAIN.md)로 | +| `running` / `retrying` | 진행 중 | +| `FAILED` | `note` 줄에 원인 | + +로그: + +```bash +tail -60 ~/sum-parts/runs/setup/setup.log +cat ~/sum-parts/runs/setup/keepalive.log +``` + +--- + +## 멈춰도 계속 도는 구조 + +| 층 | 담당 | +|---|---| +| `keepalive.sh setup` | 프로세스 전체 사망 시 재기동 (최대 40회) | +| `run_setup.sh` | 단계 실패 시 지수 백오프 재시도 (4회, 60→120→240초) | + +**전 단계 멱등이다.** conda 환경·clone·패치·다운로드 아카이브를 각각 감지해서 건너뛴다. +처음부터 다시 돌려도 안전하다. + +### preflight만 재시도하지 않는다 — 의도적이다 + +HF 토큰 없음은 재시도해도 안 고쳐진다. 무의미하게 반복하느니 +1분 만에 실패하고 `FAILED`를 남기는 게 낫다. + +--- + +## 중단 + +```bash +pkill -f keepalive.sh +pkill -f run_setup.sh +``` + +다시 켜면 중단 지점부터 이어간다. + +--- + +## 다음 + +[TRAIN.md](TRAIN.md) — GPU가 필요한 학습 단계. +`SETUP_DONE` 마커가 없으면 실행을 거부한다. diff --git a/TRAIN.md b/TRAIN.md new file mode 100644 index 0000000..2b11945 --- /dev/null +++ b/TRAIN.md @@ -0,0 +1,215 @@ +# 2단계 — 학습 (GPU 필요) + +[SETUP.md](SETUP.md)가 끝난 뒤 실행한다. `SETUP_DONE` 마커가 없으면 거부한다. + +**GPU가 아직 바빠도 지금 걸어둘 수 있다.** 카드가 빌 때까지 기다렸다가 알아서 시작한다. + +--- + +## 이 단계가 하는 것 + +| 작업 | 시간 | +|---|---| +| GPU 대기 (VRAM 확보될 때까지) | 가변 | +| `voxel_max` 실측 선택 | 10분 | +| **학습 100 epoch** | 3~6시간 | +| 평가 (val + test + 4클래스 통합) | 10분 | + +--- + +## 1. 사전점검 + +```bash +cd sum-parts-test +RUN_DRYRUN=1 bash scripts/run_train.sh +``` + +**`preflight OK` 가 나와야 한다.** 검사 항목: + +| 항목 | 실패하면 | +|---|---| +| `SETUP_DONE` 마커 | [SETUP.md](SETUP.md) 먼저 | +| conda 환경 `sumparts` | 동상 | +| 데이터 `24 / 8 / 8` | 동상 | +| `nvidia-smi` + GPU 인식 | 드라이버 문제 — 여기서 해결 | + +GPU가 **바쁜 것은 실패가 아니다.** 인식만 되면 통과한다. + +--- + +## 2. 실행 + +```bash +setsid nohup bash scripts/keepalive.sh train > ~/keepalive-train.out 2>&1 & +``` + +터미널을 닫아도 계속 돈다. + +### GPU 대기 동작 + +남의 작업이 카드를 쓰고 있으면 **실패하지 않고 기다린다.** + +``` +[11:40:02] card has 24576 MiB; waiting until 16000 MiB is free +[11:40:02] 3200 MiB free, need 16000 -- checking again in 5 min +``` + +5분마다 확인하고, 확보되면 자동으로 시작한다. 기본 대기 상한 12시간. + +조정: + +```bash +GPU_FREE_MB=20000 GPU_WAIT_MINUTES=1440 \ + setsid nohup bash scripts/keepalive.sh train > ~/keepalive-train.out 2>&1 & +``` + +상한을 넘기면 **실패시키지 않고 그냥 진행한다.** 그 시점의 가용 VRAM에 맞춰 +`voxel_max`가 측정되므로, 작게라도 학습은 된다. + +--- + +## 3. 확인 + +```bash +cat ~/sum-parts/runs/train/STATUS +``` + +``` +state : running +phase : train +cfg : pointvector-xl +voxel_max: 64000 +``` + +| state | 뜻 | +|---|---| +| `DONE` | 완료 | +| `waiting` | GPU 비기를 기다리는 중 | +| `running` / `retrying` | 진행 중 | +| `FAILED` | `note` 줄에 원인 | + +상세: + +```bash +tail -60 ~/sum-parts/runs/train/train_phase.log +bash scripts/check_training.sh # epoch, GPU, best miou +bash scripts/verify_speed.sh # HEALTHY / DEGRADED 판정 +bash scripts/epoch_timing.sh # epoch별 소요, 감속 지점 +``` + +결과: + +```bash +cat ~/sum-parts/runs/coarse_eval/coarse.txt # 4클래스 통합 성적 ← 핵심 +``` + +--- + +## voxel_max 자동 결정 + +논문 설정은 `voxel_max: 64000`, 약 **16.5 GB** 필요하다. +3060(12 GB)에서는 못 들어가 24000으로 낮춰야 했고, **그게 3090으로 옮기는 이유다.** + +높은 값부터 내려가며 **실측**해서 실제로 들어가는 첫 값을 쓴다. + +``` +64000 → 48000 → 40000 → 32000 → 24000 +``` + +3060 실측 (비교 기준): + +| voxel_max | peak VRAM | s/iter | 12 GB | +|---|---|---|---| +| 64000 | **16.49 G** | 13.113 | ❌ | +| 48000 | 11.75 G | 29.169 | ❌ 폴백 | +| 40000 | 9.88 G | 1.169 | ✅ | +| 32000 | 8.03 G | 0.635 | ✅ | +| 24000 | 6.46 G | 0.402 | ✅ | + +> ⚠️ **WSL2에서 VRAM 초과는 OOM을 내지 않는다.** +> 드라이버가 호스트 RAM으로 흘려서 **조용히 완주한다 — 25~100배 느리게.** +> 12 GB 카드에서 `peak 15.47 GB`가 찍힌다. +> 그래서 "돌아가더라"를 믿지 않고 peak 할당량으로 판정한다. +> +> 보조 지표는 **전력**이다. 사용률 100 %인데 전력이 낮으면 +> (3060 기준 60 W대) 연산이 아니라 PCIe 전송 대기다. 정상이면 140 W대. + +--- + +## 멈춰도 계속 도는 구조 — 3중 + +| 층 | 담당 | 동작 | +|---|---|---| +| `keepalive.sh train` | 프로세스 전체 사망 | VM 재시작·OOM kill 후 재기동 (최대 40회) | +| `run_train.sh` | 단계 실패 | 지수 백오프 재시도 (4회, 60→120→240초) | +| `train_watchdog.sh` | 학습 크래시 | 최신 체크포인트에서 재개 (최대 8회) | + +**재시작해도 epoch 1로 돌아가지 않는다.** 최신 체크포인트를 워치독에 넘겨 +optimizer·scheduler·epoch을 복원한다. + +`wsl --shutdown`이나 Windows 재부팅은 못 버틴다 — WSL 안에서 도는 건 다 마찬가지다. +그 경우 같은 명령을 다시 치면 중단 지점부터 이어간다. + +--- + +## 설정 + +| 변수 | 기본 | 뜻 | +|---|---|---| +| `CFG` | `pointvector-xl` | 모델 (논문 mIoU 70.0 %, 번들 최고이자 최속) | +| `EPOCHS` | `100` | | +| `VAL_FREQ` | `5` | 검증 주기 | +| `VOXEL_CANDIDATES` | `64000 48000 40000 32000 24000` | 높은 값부터 시도 | +| `GPU_FREE_MB` | `16000` | 이만큼 비어야 시작 | +| `GPU_WAIT_MINUTES` | `720` | 대기 상한 | +| `STEP_RETRIES` | `4` | 단계별 재시도 | +| `MAX_RESTARTS` | `40` | keepalive 재기동 상한 | + +예: + +```bash +CFG=pointnext-xl EPOCHS=50 \ + setsid nohup bash scripts/keepalive.sh train > ~/keepalive-train.out 2>&1 & +``` + +--- + +## 중단 + +```bash +pkill -f keepalive.sh # 감시자 먼저 — 안 그러면 다시 살린다 +bash scripts/stop_training.sh +``` + +체크포인트는 남는다. + +--- + +## 참고 — 논문 보고치 + +face 트랙, 12클래스: + +| 모델 | mIoU | +|---|---| +| PointNet | 15.1 % | +| PointNet++ | 33.1 % | +| PointNeXt | 65.3 % | +| **PointVector** | **70.0 %** | + +3060에서 PointNet 100 epoch 실측 = **17.19 %** (논문 15.1 %와 근사, 재현 확인). + +### 최종 판정 기준 + +`coarse.txt`의 통합 mIoU가 **"전부 건물" 무지성 분류기(IoU 약 67 %)를 이겨야 한다.** +절대값이 아니라 baseline 대비로 본다 — 3060 PointNet은 building IoU 59.41 %로 +그 baseline보다 낮았다. + +--- + +## 알려진 제약 + +- **test 세트는 블라인드다.** 라벨이 전부 `-1`이라 로컬 채점 불가. + 논문 수치와 직접 대조하려면 예측을 저자(gaoweixiaocuhk@gmail.com)에게 보내야 한다. +- **`voxel_max`는 중립적 손잡이가 아니다.** 모델 동작점의 일부다. + 같은 체크포인트가 검증 프로토콜에 따라 mIoU 17.19 / 4.20으로 갈렸다. +- **라이선스**: 데이터 CC BY-NC 4.0, 코드 GPL-3.0. 상업 이용은 저자 허락 필요. diff --git a/scripts/keepalive.sh b/scripts/keepalive.sh index 36fb3f1..9aff336 100644 --- a/scripts/keepalive.sh +++ b/scripts/keepalive.sh @@ -1,31 +1,44 @@ #!/usr/bin/env bash -# SUM Parts - keep run_all.sh alive across anything that kills it +# SUM Parts - keep a phase script alive across anything that kills it # -# run_all.sh already retries individual phases, and train_watchdog resumes -# training from its checkpoint. This is the layer above both: it restarts -# run_all itself if the whole process disappears -- a WSL VM restart, an OOM -# kill, a stray pkill. +# The phase scripts already retry their own steps, and train_watchdog resumes +# training from its checkpoint. This is the layer above both: it relaunches the +# phase if the whole process disappears - a WSL VM restart, an OOM kill, a +# stray pkill. # -# That is safe because every phase is idempotent. A restart re-checks what is -# already done (conda env, patches, downloaded archives, training checkpoint) -# and continues from there rather than redoing it. +# Safe because every phase is idempotent. A restart re-checks what is already +# done (conda env, patches, downloaded archives, training checkpoint) and +# continues from there rather than redoing it. +# +# Usage: +# bash scripts/keepalive.sh setup # phase A, no GPU needed +# bash scripts/keepalive.sh train # phase B, needs the GPU +# +# Unattended: +# setsid nohup bash scripts/keepalive.sh setup > ~/keepalive.out 2>&1 & # # Stops when: -# - STATUS says DONE -> success, exits 0 -# - STATUS says FAILED -> a hard error like a missing HF token; -# retrying cannot fix it, exits 1 -# - MAX_RESTARTS reached -> exits 1 -# -# Usage (this is the one command to run before leaving): -# setsid nohup bash scripts/keepalive.sh > ~/keepalive.out 2>&1 & -# -# Check on it: -# cat ~/sum-parts/runs/run_all/STATUS -# tail -f ~/sum-parts/runs/run_all/run.log +# STATUS says DONE -> success, exit 0 +# STATUS says FAILED -> hard error (missing HF token, no GPU for phase B); +# retrying cannot fix it, exit 1 +# MAX_RESTARTS reached -> exit 1 set -uo pipefail SCRIPTS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -OUT="$HOME/sum-parts/runs/run_all" + +TARGET="${1:-}" +case "$TARGET" in + setup|run_setup) TARGET=setup; SCRIPT="$SCRIPTS/run_setup.sh"; OUT="$HOME/sum-parts/runs/setup" ;; + train|run_train) TARGET=train; SCRIPT="$SCRIPTS/run_train.sh"; OUT="$HOME/sum-parts/runs/train" ;; + *) + echo "usage: bash keepalive.sh {setup|train}" + echo + echo " setup phase A - bootstrap, conda, CUDA extensions, data. No GPU needed." + echo " train phase B - voxel_max measurement, training, evaluation. Needs the GPU." + exit 2 + ;; +esac + STATUS="$OUT/STATUS" KLOG="$OUT/keepalive.log" @@ -41,24 +54,29 @@ state_of() { grep -E '^state' "$STATUS" | head -1 | cut -d: -f2- | tr -d ' ' } -klog "keepalive starting (max $MAX_RESTARTS restarts, ${COOLDOWN}s cooldown)" +klog "keepalive starting for phase '$TARGET' (max $MAX_RESTARTS restarts, ${COOLDOWN}s cooldown)" restarts=0 while :; do s=$(state_of) case "$s" in DONE) - klog "run_all reports DONE -- finished" + klog "phase '$TARGET' reports DONE" + [ "$TARGET" = setup ] && { + klog "next, once the GPU is free:" + klog " setsid nohup bash $SCRIPTS/keepalive.sh train > ~/keepalive-train.out 2>&1 &" + } exit 0 ;; FAILED) - klog "run_all reports FAILED -- a hard error that restarting will not fix:" + klog "phase '$TARGET' reports FAILED -- restarting will not fix this:" sed 's/^/ /' "$STATUS" | tee -a "$KLOG" exit 1 ;; esac - if pgrep -f "run_all.sh" | grep -qv "$$"; then + # already running (started by hand, or by a previous loop)? + if pgrep -f "$(basename "$SCRIPT")" | grep -qv "^$$\$"; then sleep 30 continue fi @@ -69,14 +87,14 @@ while :; do fi if [ "$restarts" -gt 0 ]; then - klog "run_all is not running (state='$s') -- restart #$restarts" + klog "not running (state='$s') -- restart #$restarts" else - klog "launching run_all" + klog "launching $(basename "$SCRIPT")" fi - bash "$SCRIPTS/run_all.sh" + bash "$SCRIPT" rc=$? - klog "run_all exited rc=$rc, state='$(state_of)'" + klog "$(basename "$SCRIPT") exited rc=$rc, state='$(state_of)'" restarts=$((restarts + 1)) sleep "$COOLDOWN" diff --git a/scripts/run_all.sh b/scripts/run_all.sh deleted file mode 100644 index 09a2b47..0000000 --- a/scripts/run_all.sh +++ /dev/null @@ -1,305 +0,0 @@ -#!/usr/bin/env bash -# SUM Parts - unattended end-to-end run: bare machine to trained model -# -# Built for a weekend with nobody at the keyboard. Everything that can stop the -# run is checked in PREFLIGHT, before any long step, so a failure surfaces in -# the first minute rather than after three hours of setup. -# -# preflight -> bootstrap -> env -> patches -> verify -> data -> vram -> train -> eval -# -# The one thing this cannot do for you is accept the HuggingFace dataset gate: -# it needs a browser and a logged-in account. It IS per-account though, so if -# you already accepted it elsewhere, copying the token to this machine is -# enough. Preflight fails immediately if the token is missing or rejected. -# -# Usage: -# bash scripts/run_all.sh # blocking, logs to stdout + file -# bash scripts/run_all.sh --detach # survives terminal/session close -# -# Watch it later: -# tail -f ~/sum-parts/runs/run_all/run.log -# cat ~/sum-parts/runs/run_all/STATUS -set -uo pipefail - -SCRIPTS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -OUT="$HOME/sum-parts/runs/run_all" -LOG="$OUT/run.log" -STATUS="$OUT/STATUS" - -CFG="${CFG:-pointvector-xl}" -EPOCHS="${EPOCHS:-100}" -VAL_FREQ="${VAL_FREQ:-5}" -# candidates tried high to low; first one that fits in VRAM wins. -# 64000 is the paper setting and needs ~16.5 GB. -VOXEL_CANDIDATES="${VOXEL_CANDIDATES:-64000 48000 40000 32000 24000}" - -# ---------------------------------------------------------------- detach - -if [ "${1:-}" = "--detach" ]; then - mkdir -p "$OUT" - echo "detaching; log: $LOG" - setsid nohup bash "${BASH_SOURCE[0]}" > "$OUT/nohup.out" 2>&1 < /dev/null & - sleep 2 - pgrep -af "run_all.sh" | grep -v detach || true - exit 0 -fi - -mkdir -p "$OUT" -exec > >(tee -a "$LOG") 2>&1 - -PHASE="starting" -STARTED=$(date '+%F %T') - -say() { echo "[$(date '+%F %T')] $*"; } -head_() { echo; echo "════ $* ════"; } - -write_status() { - { - echo "state : $1" - echo "phase : $PHASE" - echo "cfg : $CFG" - echo "voxel_max: ${VOXEL_MAX:-(not chosen yet)}" - echo "started : $STARTED" - echo "updated : $(date '+%F %T')" - [ -n "${EXTRA:-}" ] && echo "note : $EXTRA" - echo "log : $LOG" - } > "$STATUS" -} - -die() { - say "GIVING UP in phase '$PHASE': $*" - EXTRA="$*" write_status "FAILED" - exit 1 -} - -# Every phase is idempotent -- bootstrap skips an existing install, the patches -# detect themselves, download_data skips cached archives, training resumes from -# its checkpoint. So retrying a phase is always safe, and so is rerunning the -# whole script from the top (see keepalive.sh). -STEP_RETRIES="${STEP_RETRIES:-4}" -STEP_BACKOFF="${STEP_BACKOFF:-60}" - -step() { - PHASE="$1"; shift - head_ "$PHASE" - write_status "running" - - local attempt=1 wait=$STEP_BACKOFF - while :; do - if "$@"; then - [ "$attempt" -gt 1 ] && say "phase '$PHASE' succeeded on attempt $attempt" - return 0 - fi - if [ "$attempt" -ge "$STEP_RETRIES" ]; then - die "$* (failed $attempt times)" - fi - say "phase '$PHASE' failed (attempt $attempt/$STEP_RETRIES); retrying in ${wait}s" - EXTRA="retrying $PHASE ($attempt/$STEP_RETRIES)" write_status "retrying" - sleep "$wait" - attempt=$((attempt + 1)) - wait=$((wait * 2)) - done -} - -# Preflight is the exception: a missing HF token or absent GPU will not fix -# itself, so retrying just burns the weekend. Fail loudly and immediately. -step_once() { - PHASE="$1"; shift - head_ "$PHASE" - write_status "running" - "$@" || die "$*" -} - -# ---------------------------------------------------------------- preflight - -preflight() { - local fail=0 - - say "checking tools" - for t in git curl python3 tar; do - command -v "$t" > /dev/null || { say " MISSING: $t"; fail=1; } - done - - say "checking GPU" - if ! command -v nvidia-smi > /dev/null; then - say " MISSING: nvidia-smi -- the driver is not exposing a GPU here" - fail=1 - else - nvidia-smi --query-gpu=name,memory.total,driver_version \ - --format=csv,noheader | sed 's/^/ /' - VRAM_MB=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits | head -1) - say " usable VRAM: ${VRAM_MB} MiB" - fi - - say "checking disk (need 35 GB free in \$HOME)" - local free_gb - free_gb=$(df -BG --output=avail "$HOME" | tail -1 | tr -dc '0-9') - say " free: ${free_gb} GB" - [ "${free_gb:-0}" -lt 35 ] && { say " NOT ENOUGH"; fail=1; } - - say "checking HuggingFace credentials" - # the dataset is gated; the token must exist AND the account must already - # have accepted the licence in a browser - local tok="" - [ -n "${HF_TOKEN:-}" ] && tok="$HF_TOKEN" - [ -z "$tok" ] && [ -f "$HOME/.cache/huggingface/token" ] \ - && tok=$(tr -d '\r\n' < "$HOME/.cache/huggingface/token") - if [ -z "$tok" ]; then - say " MISSING: no HF token" - say " fix: copy the token from a machine that already accepted the gate:" - say " mkdir -p ~/.cache/huggingface" - say " echo hf_xxxxx > ~/.cache/huggingface/token" - say " the gate itself is per-account and needs a browser once:" - say " https://huggingface.co/datasets/gwxgrxhyz/SUM-Parts" - fail=1 - else - local code - code=$(curl -s -o /dev/null -w '%{http_code}' -I \ - -H "Authorization: Bearer $tok" \ - "https://huggingface.co/datasets/gwxgrxhyz/SUM-Parts/resolve/main/demo.zip") - say " gate probe: HTTP $code" - case "$code" in - 200|302) say " gate OK" ;; - 401|403) say " REJECTED -- token invalid, or this account has not accepted the gate" - say " accept it in a browser: https://huggingface.co/datasets/gwxgrxhyz/SUM-Parts" - fail=1 ;; - *) say " unexpected response; continuing but the download may fail" ;; - esac - fi - - [ "$fail" -eq 0 ] || return 1 - say "preflight OK" -} - -# ---------------------------------------------------------------- vram pick - -pick_voxel_max() { - say "measuring which voxel_max fits in VRAM (high to low, first fit wins)" - say "NOTE: on WSL2 an oversized value does not OOM -- the driver spills into" - say " host RAM and the run completes 25-100x slower. So this measures" - say " peak allocation instead of trusting that it 'worked'." - - source "$HOME/miniconda3/etc/profile.d/conda.sh" - conda activate sumparts - export WANDB_MODE=disabled WANDB_SILENT=true CUDA_HOME="$CONDA_PREFIX" - export PYTORCH_CUDA_ALLOC_CONF="garbage_collection_threshold:0.7,max_split_size_mb:128" - - cd "$HOME/sum-parts/semantic_segmentation/PointNeXt_bundle/examples/segmentation" \ - || return 1 - - local vm log line fits - for vm in $VOXEL_CANDIDATES; do - log="$OUT/vram_${vm}.log" - say " trying voxel_max=$vm" - python -u "$SCRIPTS/bench_models.py" --iters 4 --voxel-max "$vm" \ - --cfgs "$CFG" > "$log" 2>&1 - line=$(grep -aE "^${CFG} +[0-9]" "$log" | tail -1) - if [ -z "$line" ]; then - say " no result (probably OOM or an error); see $log" - continue - fi - fits=$(echo "$line" | grep -o 'yes$' || true) - say " $(echo "$line" | awk '{print "peak", $5, "s/iter", $6}')" - if [ -n "$fits" ]; then - VOXEL_MAX="$vm" - say " chosen: voxel_max=$VOXEL_MAX" - return 0 - fi - say " does not fit -- spilling to host RAM" - done - - say " nothing fit; falling back to the smallest candidate" - VOXEL_MAX=$(echo "$VOXEL_CANDIDATES" | awk '{print $NF}') - return 0 -} - -# ---------------------------------------------------------------- phases - -do_verify() { - source "$HOME/miniconda3/etc/profile.d/conda.sh" - conda activate sumparts - WANDB_MODE=disabled python "$SCRIPTS/verify_env.py" -} - -do_train() { - # Already finished? Don't retrain on a rerun. - local done_marker="$OUT/TRAIN_DONE" - if [ -f "$done_marker" ]; then - say "training already completed (marker: $done_marker)" - return 0 - fi - - # Pick up an earlier run's progress. train_watchdog only auto-discovers - # checkpoints written after IT started, so a fresh invocation would restart - # from epoch 1 without this. - local ckpt - ckpt=$(find "$HOME/sum-parts/semantic_segmentation/PointNeXt_bundle/examples/segmentation/log/sumv2_triangle" \ - -name '*_ckpt_latest.pth' -printf '%T@ %p\n' 2>/dev/null \ - | sort -rn | head -1 | cut -d' ' -f2-) - - if [ -n "$ckpt" ]; then - say "resuming from $(basename "$ckpt")" - else - say "no checkpoint found; starting fresh" - fi - - say "training $CFG for $EPOCHS epochs at voxel_max=$VOXEL_MAX" - CFG_VOXEL_MAX="$VOXEL_MAX" \ - VAL_VOXEL_MAX="$VOXEL_MAX" \ - EPOCHS="$EPOCHS" VAL_FREQ="$VAL_FREQ" MAX_RETRIES=8 \ - RESUME_CKPT="$ckpt" \ - bash "$SCRIPTS/train_watchdog.sh" "$CFG" || return 1 - - touch "$done_marker" - return 0 -} - -do_eval() { - bash "$SCRIPTS/final_eval.sh" || say "final_eval reported non-zero (test split is blind; that is expected)" - bash "$SCRIPTS/eval_coarse.sh" || say "eval_coarse reported non-zero" - return 0 -} - -# ---------------------------------------------------------------- run - -say "run_all starting -- cfg=$CFG epochs=$EPOCHS" -say "log: $LOG" -write_status "running" - -step_once "preflight" preflight - -# RUN_ALL_DRYRUN lets you prove the preflight checks pass without starting the -# long phases -- worth doing before walking away for the weekend. -if [ -n "${RUN_ALL_DRYRUN:-}" ]; then - say "DRYRUN set -- preflight passed, stopping before the real work" - write_status "dryrun-ok" - exit 0 -fi - -step "bootstrap" bash "$SCRIPTS/bootstrap.sh" -step "conda env" bash "$SCRIPTS/setup_env.sh" -step "cuda extensions" bash "$SCRIPTS/setup_pointnext.sh" -step "patch numpy" bash "$SCRIPTS/patch_numpy_aliases.sh" -step "patch test split" bash "$SCRIPTS/patch_unlabeled_test.sh" -step "patch val mode" bash "$SCRIPTS/patch_val_mode.sh" -step "verify env" do_verify -step "download data" bash "$SCRIPTS/download_data.sh" all -step "prepare splits" bash "$SCRIPTS/prepare_full_split.sh" -step "link data" bash "$SCRIPTS/link_data.sh" -step "choose voxel_max" pick_voxel_max -step "train" do_train -step "evaluate" do_eval - -PHASE="done" -write_status "DONE" - -head_ "SUMMARY" -say "cfg : $CFG" -say "voxel_max : $VOXEL_MAX" -grep -aE 'Best ckpt' "$OUT/../"*/train.log 2>/dev/null | tail -2 -[ -f "$HOME/sum-parts/runs/coarse_eval/coarse.txt" ] && { - echo - echo "--- coarse (building / vegetation / vehicle / ground) ---" - cat "$HOME/sum-parts/runs/coarse_eval/coarse.txt" -} -say "RUN ALL DONE" diff --git a/scripts/run_setup.sh b/scripts/run_setup.sh new file mode 100644 index 0000000..f55a81e --- /dev/null +++ b/scripts/run_setup.sh @@ -0,0 +1,209 @@ +#!/usr/bin/env bash +# SUM Parts - PHASE A: everything that does not need the GPU +# +# Splitting the work here is deliberate. Compiling the CUDA extensions needs +# nvcc, not a GPU, and downloading 18 GB of data needs neither. Those are the +# slow parts (~50 min + ~30 min), so they can run while the card is busy with +# someone else's job. +# +# preflight -> bootstrap -> conda env -> cuda extensions -> patches +# -> verify imports -> download data -> splits -> link +# +# Ends by writing SETUP_DONE. run_train.sh refuses to start without it. +# +# The one thing this cannot do for you is accept the HuggingFace dataset gate: +# it needs a browser and a logged-in account. The gate IS per-account, so a +# token from a machine that already accepted it is enough. Preflight fails +# immediately if the token is missing or rejected. +# +# Usage: +# bash scripts/run_setup.sh +# bash scripts/run_setup.sh --detach +# RUN_DRYRUN=1 bash scripts/run_setup.sh # preflight only +set -uo pipefail + +SCRIPTS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +OUT="$HOME/sum-parts/runs/setup" +LOG="$OUT/setup.log" +STATUS="$OUT/STATUS" +DONE_MARKER="$OUT/SETUP_DONE" + +# The extensions are compiled for this architecture. We may not be able to ask +# the GPU which one it is (it can be busy or absent), so it is stated instead. +# RTX 3060 / 3070 / 3080 / 3090 = 8.6 (Ampere consumer) +# RTX 4090 = 8.9 +# A100 = 8.0 +export TORCH_CUDA_ARCH_LIST="${TORCH_CUDA_ARCH_LIST:-8.6}" + +if [ "${1:-}" = "--detach" ]; then + mkdir -p "$OUT" + echo "detaching; log: $LOG" + setsid nohup bash "${BASH_SOURCE[0]}" > "$OUT/nohup.out" 2>&1 < /dev/null & + sleep 2 + pgrep -af "run_setup.sh" | grep -v detach || true + exit 0 +fi + +mkdir -p "$OUT" +exec > >(tee -a "$LOG") 2>&1 + +PHASE="starting" +STARTED=$(date '+%F %T') + +say() { echo "[$(date '+%F %T')] $*"; } +head_() { echo; echo "════ $* ════"; } + +write_status() { + { + echo "state : $1" + echo "phase : $PHASE" + echo "arch : $TORCH_CUDA_ARCH_LIST" + echo "started : $STARTED" + echo "updated : $(date '+%F %T')" + [ -n "${EXTRA:-}" ] && echo "note : $EXTRA" + echo "log : $LOG" + } > "$STATUS" +} + +die() { + say "GIVING UP in phase '$PHASE': $*" + EXTRA="$*" write_status "FAILED" + exit 1 +} + +STEP_RETRIES="${STEP_RETRIES:-4}" +STEP_BACKOFF="${STEP_BACKOFF:-60}" + +# Every phase is idempotent, so retrying one - or rerunning the whole script - +# is always safe. Existing installs, applied patches and cached archives are +# detected and skipped. +step() { + PHASE="$1"; shift + head_ "$PHASE" + write_status "running" + local attempt=1 wait=$STEP_BACKOFF + while :; do + if "$@"; then + [ "$attempt" -gt 1 ] && say "phase '$PHASE' succeeded on attempt $attempt" + return 0 + fi + [ "$attempt" -ge "$STEP_RETRIES" ] && die "$* (failed $attempt times)" + say "phase '$PHASE' failed (attempt $attempt/$STEP_RETRIES); retrying in ${wait}s" + EXTRA="retrying $PHASE ($attempt/$STEP_RETRIES)" write_status "retrying" + sleep "$wait" + attempt=$((attempt + 1)); wait=$((wait * 2)) + done +} + +step_once() { + PHASE="$1"; shift + head_ "$PHASE" + write_status "running" + "$@" || die "$*" +} + +# ---------------------------------------------------------------- preflight + +preflight() { + local fail=0 + + say "checking tools" + for t in git curl python3 tar; do + command -v "$t" > /dev/null || { say " MISSING: $t"; fail=1; } + done + + say "checking disk (need 35 GB free in \$HOME)" + local free_gb + free_gb=$(df -BG --output=avail "$HOME" | tail -1 | tr -dc '0-9') + say " free: ${free_gb} GB" + [ "${free_gb:-0}" -lt 35 ] && { say " NOT ENOUGH"; fail=1; } + + # A GPU is NOT required for this phase. Report what is there, but never + # fail on it -- the whole point of the split is to work while the card is + # occupied. + say "checking GPU (informational only for this phase)" + if command -v nvidia-smi > /dev/null; then + nvidia-smi --query-gpu=name,memory.total,memory.used --format=csv,noheader \ + | sed 's/^/ /' || say " nvidia-smi failed; continuing anyway" + else + say " nvidia-smi absent -- fine for setup, required before training" + fi + say " building extensions for arch $TORCH_CUDA_ARCH_LIST" + + say "checking HuggingFace credentials" + local tok="" + [ -n "${HF_TOKEN:-}" ] && tok="$HF_TOKEN" + [ -z "$tok" ] && [ -f "$HOME/.cache/huggingface/token" ] \ + && tok=$(tr -d '\r\n' < "$HOME/.cache/huggingface/token") + if [ -z "$tok" ]; then + say " MISSING: no HF token" + say " fix: copy it from a machine that already accepted the gate:" + say " mkdir -p ~/.cache/huggingface" + say " echo hf_xxxxx > ~/.cache/huggingface/token" + say " the gate needs a browser once, per account:" + say " https://huggingface.co/datasets/gwxgrxhyz/SUM-Parts" + fail=1 + else + local code + code=$(curl -s -o /dev/null -w '%{http_code}' -I \ + -H "Authorization: Bearer $tok" \ + "https://huggingface.co/datasets/gwxgrxhyz/SUM-Parts/resolve/main/demo.zip") + say " gate probe: HTTP $code" + case "$code" in + 200|302) say " gate OK" ;; + 401|403) say " REJECTED -- token invalid, or this account has not accepted the gate" + fail=1 ;; + *) say " unexpected response; continuing but the download may fail" ;; + esac + fi + + [ "$fail" -eq 0 ] || return 1 + say "preflight OK -- no GPU needed from here to the end of this phase" +} + +do_verify() { + source "$HOME/miniconda3/etc/profile.d/conda.sh" + conda activate sumparts + # SKIP_CUDA_CHECK keeps this to imports only. Querying the device is + # harmless even on a busy card, but it fails outright if the driver is not + # present yet - and that must not block a GPU-free setup. + SKIP_CUDA_CHECK=1 WANDB_MODE=disabled python "$SCRIPTS/verify_env.py" +} + +# ---------------------------------------------------------------- run + +say "run_setup starting (phase A - no GPU required)" +say "log: $LOG" +write_status "running" + +step_once "preflight" preflight + +if [ -n "${RUN_DRYRUN:-}" ]; then + say "DRYRUN set -- preflight passed, stopping before the real work" + write_status "dryrun-ok" + exit 0 +fi + +step "bootstrap" bash "$SCRIPTS/bootstrap.sh" +step "conda env" bash "$SCRIPTS/setup_env.sh" +step "cuda extensions" bash "$SCRIPTS/setup_pointnext.sh" +step "patch numpy" bash "$SCRIPTS/patch_numpy_aliases.sh" +step "patch test split" bash "$SCRIPTS/patch_unlabeled_test.sh" +step "patch val mode" bash "$SCRIPTS/patch_val_mode.sh" +step "verify imports" do_verify +step "download data" bash "$SCRIPTS/download_data.sh" all +step "prepare splits" bash "$SCRIPTS/prepare_full_split.sh" +step "link data" bash "$SCRIPTS/link_data.sh" + +PHASE="done" +touch "$DONE_MARKER" +write_status "DONE" + +head_ "PHASE A COMPLETE" +say "built for arch : $TORCH_CUDA_ARCH_LIST" +say "marker : $DONE_MARKER" +echo +say "next, once the GPU is free:" +say " bash scripts/run_train.sh" +say "or, to keep it alive unattended:" +say " setsid nohup bash scripts/keepalive.sh run_train > ~/keepalive.out 2>&1 &" diff --git a/scripts/run_train.sh b/scripts/run_train.sh new file mode 100644 index 0000000..482b4e2 --- /dev/null +++ b/scripts/run_train.sh @@ -0,0 +1,288 @@ +#!/usr/bin/env bash +# SUM Parts - PHASE B: the GPU work +# +# Assumes run_setup.sh already finished (it checks for the marker). Everything +# here needs the card to itself: +# +# preflight -> choose voxel_max (measured) -> train -> evaluate +# +# Waits for the GPU rather than failing when it is busy: if another job holds +# the card, this parks until enough VRAM frees up. So it can be started ahead +# of time and left alone. +# +# Usage: +# bash scripts/run_train.sh +# bash scripts/run_train.sh --detach +# RUN_DRYRUN=1 bash scripts/run_train.sh # preflight only +set -uo pipefail + +SCRIPTS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SETUP_MARKER="$HOME/sum-parts/runs/setup/SETUP_DONE" +OUT="$HOME/sum-parts/runs/train" +LOG="$OUT/train_phase.log" +STATUS="$OUT/STATUS" +DONE_MARKER="$OUT/TRAIN_DONE" + +CFG="${CFG:-pointvector-xl}" +EPOCHS="${EPOCHS:-100}" +VAL_FREQ="${VAL_FREQ:-5}" +# tried high to low; first one that actually fits in VRAM wins. +# 64000 is the paper setting and needs ~16.5 GB. +VOXEL_CANDIDATES="${VOXEL_CANDIDATES:-64000 48000 40000 32000 24000}" + +# how long to wait for someone else's job to release the card +GPU_WAIT_MINUTES="${GPU_WAIT_MINUTES:-720}" +GPU_FREE_MB="${GPU_FREE_MB:-16000}" + +if [ "${1:-}" = "--detach" ]; then + mkdir -p "$OUT" + echo "detaching; log: $LOG" + setsid nohup bash "${BASH_SOURCE[0]}" > "$OUT/nohup.out" 2>&1 < /dev/null & + sleep 2 + pgrep -af "run_train.sh" | grep -v detach || true + exit 0 +fi + +mkdir -p "$OUT" +exec > >(tee -a "$LOG") 2>&1 + +PHASE="starting" +STARTED=$(date '+%F %T') +VOXEL_MAX="" + +say() { echo "[$(date '+%F %T')] $*"; } +head_() { echo; echo "════ $* ════"; } + +write_status() { + { + echo "state : $1" + echo "phase : $PHASE" + echo "cfg : $CFG" + echo "voxel_max: ${VOXEL_MAX:-(not chosen yet)}" + echo "started : $STARTED" + echo "updated : $(date '+%F %T')" + [ -n "${EXTRA:-}" ] && echo "note : $EXTRA" + echo "log : $LOG" + } > "$STATUS" +} + +die() { + say "GIVING UP in phase '$PHASE': $*" + EXTRA="$*" write_status "FAILED" + exit 1 +} + +STEP_RETRIES="${STEP_RETRIES:-4}" +STEP_BACKOFF="${STEP_BACKOFF:-60}" + +step() { + PHASE="$1"; shift + head_ "$PHASE" + write_status "running" + local attempt=1 wait=$STEP_BACKOFF + while :; do + if "$@"; then + [ "$attempt" -gt 1 ] && say "phase '$PHASE' succeeded on attempt $attempt" + return 0 + fi + [ "$attempt" -ge "$STEP_RETRIES" ] && die "$* (failed $attempt times)" + say "phase '$PHASE' failed (attempt $attempt/$STEP_RETRIES); retrying in ${wait}s" + EXTRA="retrying $PHASE ($attempt/$STEP_RETRIES)" write_status "retrying" + sleep "$wait" + attempt=$((attempt + 1)); wait=$((wait * 2)) + done +} + +step_once() { + PHASE="$1"; shift + head_ "$PHASE" + write_status "running" + "$@" || die "$*" +} + +# ---------------------------------------------------------------- preflight + +preflight() { + local fail=0 + + say "checking phase A completed" + if [ -f "$SETUP_MARKER" ]; then + say " marker present: $SETUP_MARKER" + else + say " MISSING: $SETUP_MARKER" + say " run phase A first: bash scripts/run_setup.sh" + fail=1 + fi + + say "checking environment" + if [ -x "$HOME/miniconda3/bin/conda" ]; then + source "$HOME/miniconda3/etc/profile.d/conda.sh" + if conda env list | grep -q '^sumparts '; then + say " conda env sumparts present" + else + say " MISSING: conda env 'sumparts'"; fail=1 + fi + else + say " MISSING: miniconda"; fail=1 + fi + + say "checking data" + local d="$HOME/sum-parts/data/face_labeling/texsp_pcl" + if [ -d "$d/train" ]; then + say " train/val/test = $(find -L "$d/train" -name '*.ply' | wc -l)/$(find -L "$d/val" -name '*.ply' 2>/dev/null | wc -l)/$(find -L "$d/test" -name '*.ply' | wc -l)" + else + say " MISSING: $d/train"; fail=1 + fi + + say "checking GPU driver" + if ! command -v nvidia-smi > /dev/null; then + say " MISSING: nvidia-smi -- no GPU visible, and this phase cannot run without one" + fail=1 + else + nvidia-smi --query-gpu=name,memory.total,memory.used,utilization.gpu \ + --format=csv,noheader | sed 's/^/ /' + fi + + [ "$fail" -eq 0 ] || return 1 + say "preflight OK" +} + +# ------------------------------------------------------------ wait for GPU + +wait_for_gpu() { + local deadline=$(( $(date +%s) + GPU_WAIT_MINUTES * 60 )) + local total used free + + total=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits | head -1) + say "card has ${total} MiB; waiting until ${GPU_FREE_MB} MiB is free" + say "(will wait up to ${GPU_WAIT_MINUTES} minutes)" + + while :; do + used=$(nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits | head -1) + free=$(( total - used )) + if [ "$free" -ge "$GPU_FREE_MB" ]; then + say " ${free} MiB free -- proceeding" + return 0 + fi + if [ "$(date +%s)" -ge "$deadline" ]; then + say " timed out with only ${free} MiB free" + say " continuing anyway; voxel_max will be measured against what is actually available" + return 0 + fi + EXTRA="waiting for GPU (${free} MiB free, need ${GPU_FREE_MB})" write_status "waiting" + say " ${free} MiB free, need ${GPU_FREE_MB} -- checking again in 5 min" + sleep 300 + done +} + +# ---------------------------------------------------------------- vram pick + +pick_voxel_max() { + say "measuring which voxel_max fits (high to low, first fit wins)" + say "NOTE: on WSL2 an oversized value does not OOM -- the driver spills into" + say " host RAM and the run completes 25-100x slower. So this checks peak" + say " allocation instead of trusting that it 'worked'." + + source "$HOME/miniconda3/etc/profile.d/conda.sh" + conda activate sumparts + export WANDB_MODE=disabled WANDB_SILENT=true CUDA_HOME="$CONDA_PREFIX" + export PYTORCH_CUDA_ALLOC_CONF="garbage_collection_threshold:0.7,max_split_size_mb:128" + + cd "$HOME/sum-parts/semantic_segmentation/PointNeXt_bundle/examples/segmentation" \ + || return 1 + + local vm log line fits + for vm in $VOXEL_CANDIDATES; do + log="$OUT/vram_${vm}.log" + say " trying voxel_max=$vm" + python -u "$SCRIPTS/bench_models.py" --iters 4 --voxel-max "$vm" \ + --cfgs "$CFG" > "$log" 2>&1 + line=$(grep -aE "^${CFG} +[0-9]" "$log" | tail -1) + if [ -z "$line" ]; then + say " no result (OOM or error); see $log" + continue + fi + say " $(echo "$line" | awk '{print "peak", $5, " s/iter", $6}')" + fits=$(echo "$line" | grep -o 'yes$' || true) + if [ -n "$fits" ]; then + VOXEL_MAX="$vm" + say " chosen: voxel_max=$VOXEL_MAX" + return 0 + fi + say " does not fit -- would spill to host RAM" + done + + say " nothing fit; falling back to the smallest candidate" + VOXEL_MAX=$(echo "$VOXEL_CANDIDATES" | awk '{print $NF}') + return 0 +} + +# ---------------------------------------------------------------- train + +do_train() { + if [ -f "$DONE_MARKER" ]; then + say "training already completed (marker: $DONE_MARKER)" + return 0 + fi + + # train_watchdog only auto-discovers checkpoints written after it starts, + # so hand it the newest one explicitly or a rerun restarts from epoch 1. + local ckpt + ckpt=$(find "$HOME/sum-parts/semantic_segmentation/PointNeXt_bundle/examples/segmentation/log/sumv2_triangle" \ + -name '*_ckpt_latest.pth' -printf '%T@ %p\n' 2>/dev/null \ + | sort -rn | head -1 | cut -d' ' -f2-) + if [ -n "$ckpt" ]; then + say "resuming from $(basename "$ckpt")" + else + say "no checkpoint found; starting fresh" + fi + + say "training $CFG for $EPOCHS epochs at voxel_max=$VOXEL_MAX" + CFG_VOXEL_MAX="$VOXEL_MAX" VAL_VOXEL_MAX="$VOXEL_MAX" \ + EPOCHS="$EPOCHS" VAL_FREQ="$VAL_FREQ" MAX_RETRIES=8 \ + RESUME_CKPT="$ckpt" \ + bash "$SCRIPTS/train_watchdog.sh" "$CFG" || return 1 + + touch "$DONE_MARKER" + return 0 +} + +do_eval() { + bash "$SCRIPTS/final_eval.sh" || say "final_eval non-zero (test split is blind; expected)" + bash "$SCRIPTS/eval_coarse.sh" || say "eval_coarse non-zero" + return 0 +} + +# ---------------------------------------------------------------- run + +say "run_train starting (phase B - GPU required)" +say "cfg=$CFG epochs=$EPOCHS" +say "log: $LOG" +write_status "running" + +step_once "preflight" preflight + +if [ -n "${RUN_DRYRUN:-}" ]; then + say "DRYRUN set -- preflight passed, stopping before the real work" + write_status "dryrun-ok" + exit 0 +fi + +step_once "wait for GPU" wait_for_gpu +step "choose voxel_max" pick_voxel_max +step "train" do_train +step "evaluate" do_eval + +PHASE="done" +write_status "DONE" + +head_ "PHASE B COMPLETE" +say "cfg : $CFG" +say "voxel_max : $VOXEL_MAX" +grep -ahE 'Best ckpt' "$HOME/sum-parts/runs/"*/train.log 2>/dev/null | tail -2 +if [ -f "$HOME/sum-parts/runs/coarse_eval/coarse.txt" ]; then + echo + echo "--- coarse (building / vegetation / vehicle / ground) ---" + cat "$HOME/sum-parts/runs/coarse_eval/coarse.txt" +fi +say "TRAIN PHASE DONE" diff --git a/scripts/selfcheck.sh b/scripts/selfcheck.sh new file mode 100644 index 0000000..79479a7 --- /dev/null +++ b/scripts/selfcheck.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# SUM Parts - syntax-check every script in this directory +# +# Cheap guard: a shell script with a syntax error fails at the first line it +# reaches, which for an unattended weekend run means losing the weekend. +set -uo pipefail + +cd "$(dirname "${BASH_SOURCE[0]}")" + +fail=0 + +echo "=== bash ===" +for f in *.sh; do + printf ' %-26s' "$f" + if bash -n "$f" 2>/dev/null; then echo "OK"; else echo "SYNTAX ERROR"; fail=1; fi +done + +echo +echo "=== python ===" +for f in *.py; do + printf ' %-26s' "$f" + if python3 -c "import ast,sys; ast.parse(open(sys.argv[1],encoding='utf-8').read())" "$f" 2>/dev/null + then echo "OK"; else echo "SYNTAX ERROR"; fail=1; fi +done + +echo +echo "=== CRLF check (breaks bash on Linux) ===" +if grep -rlU $'\r' ./*.sh ./*.py 2>/dev/null; then + echo " ^ these have CRLF line endings and will fail with: bash: \$'\\r': command not found" + fail=1 +else + echo " clean" +fi + +echo +[ "$fail" -eq 0 ] && echo "ALL OK" || echo "PROBLEMS FOUND" +exit "$fail" diff --git a/scripts/test_preflight.sh b/scripts/test_preflight.sh deleted file mode 100644 index 9839962..0000000 --- a/scripts/test_preflight.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash -# SUM Parts - run only run_all.sh's preflight, to prove it before leaving -# -# Sources run_all.sh with RUN_ALL_DRYRUN set so the phase list is skipped and -# only the checks execute. Cheap, no side effects, no GPU work. -set -uo pipefail -RUN_ALL_DRYRUN=1 bash "$(dirname "${BASH_SOURCE[0]}")/run_all.sh" diff --git a/scripts/verify_env.py b/scripts/verify_env.py index 1044862..5bcef36 100644 --- a/scripts/verify_env.py +++ b/scripts/verify_env.py @@ -62,12 +62,23 @@ def main() -> int: if root is None: ok = False + # SKIP_CUDA_CHECK exists for the GPU-free setup phase: the extensions can be + # built and imported without a card present, and querying the device would + # fail on a machine whose GPU is absent or still occupied. Import coverage + # is unaffected -- only the device query is skipped. + skip_cuda = bool(os.environ.get("SKIP_CUDA_CHECK")) + try: import torch - print(f"torch {torch.__version__} | cuda {torch.version.cuda} | " - f"available {torch.cuda.is_available()}") - if torch.cuda.is_available(): - print(f"device: {torch.cuda.get_device_name(0)}") + line = f"torch {torch.__version__} | cuda {torch.version.cuda}" + if skip_cuda: + print(line + " | device check skipped (SKIP_CUDA_CHECK)") + else: + print(line + f" | available {torch.cuda.is_available()}") + if torch.cuda.is_available(): + print(f"device: {torch.cuda.get_device_name(0)}") + else: + print("device: none visible -- fine for setup, required to train") except Exception as e: # noqa: BLE001 print(f"torch import failed: {e}") return 1