Reproduces the SUM Parts (CVPR 2025) face-labeling benchmark on a single consumer GPU, then applies it to drone-photogrammetry road survey meshes. Verified on RTX 3060 12GB / WSL2 Ubuntu 22.04 / CUDA 11.8 / torch 2.0.1: - CUDA extensions build (pointnet2_batch, pointops, chamfer_dist, emd, subsampling) - PointNet 100 epochs reaches mIoU 17.19, matching the paper's reported 15.1 - OBJ -> PLY conversion round-trips through the model and yields per-point predictions Four upstream source patches, all idempotent, originals preserved: - numpy aliases removed in 1.24 (np.long etc.) and collections ABCs moved in python 3.10 - the blind test split ships label = -1, which crashed ConfusionMatrix - mode=val referenced `epoch` before assignment Documents the traps that cost the most time, including VRAM overflow silently falling back to host RAM on WSL2 (25-100x slowdown, no OOM) and the colour scale mismatch between r/g/b float32 and red/green/blue uint8. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1186 lines
47 KiB
Markdown
1186 lines
47 KiB
Markdown
# SUM Parts 검토 노트
|
||
|
||
> 대상: [tudelft3d/SUM-Parts-Benchmarks](https://github.com/tudelft3d/SUM-Parts-Benchmarks)
|
||
> 목적: 로컬 재현 테스트 + 한국 샘플 데이터 적용 가능성 확인
|
||
> 작성일: 2026-08-20
|
||
|
||
---
|
||
|
||
## 1. 프로젝트 개요
|
||
|
||
**SUM Parts** — TU Delft 3D geoinformation, CVPR 2025.
|
||
도시 **텍스처 3D 메시**의 **part-level(부품 단위) 시맨틱 세그멘테이션** 벤치마크.
|
||
|
||
| 항목 | 내용 |
|
||
|---|---|
|
||
| 규모 | 2.5 km² 도시 메시 |
|
||
| 어노테이션 | 이중 트랙 — face 단위 + texture 픽셀 단위 |
|
||
| 클래스 | 21종 |
|
||
| 텍스처 메시 포맷 | ASCII PLY |
|
||
| 시맨틱 포인트클라우드 포맷 | binary PLY |
|
||
| 데이터 배포 | Hugging Face `gwxgrxhyz/SUM-Parts` (**게이트 있음**, 아래 참조) |
|
||
| 논문 | [arXiv:2503.15300](https://arxiv.org/abs/2503.15300) |
|
||
| 프로젝트 페이지 | https://tudelft3d.github.io/SUMParts/ |
|
||
| 연락처 | gaoweixiaocuhk@gmail.com (Weixiao Gao) |
|
||
|
||
### 클래스 — README는 21이라 하지만, 코드상 트랙별로 다르다
|
||
|
||
README 전체 목록 (21종):
|
||
|
||
```
|
||
unclassified, terrain, high vegetation, water, car, boat,
|
||
wall, roof surface, facade surface, chimney, dormer, balcony,
|
||
roof installation, window, door, low vegetation,
|
||
impervious surface, road, road marking, cycle lane, sidewalk
|
||
```
|
||
|
||
**실제 학습 코드에서는 트랙별로 분리된다** (PointNeXt `openpoints/dataset/` 확인 결과):
|
||
|
||
| 트랙 | 클래스 수 | 목록 |
|
||
|---|---|---|
|
||
| `SUMV2_Triangle` (face/메시) | **13** | unclassified, terrain, high_vegetation, facade_surface, water, car, boat, roof_surface, chimney, dormer, balcony, roof_installation, wall |
|
||
| `SUMV2_Texture` (텍스처 픽셀) | **20** | 위 + window, door, low_vegetation, road, road_marking 등 세부 클래스 |
|
||
|
||
→ 벤치마크 재현 시 **cfg의 `num_classes`와 데이터 트랙을 반드시 일치**시켜야 한다.
|
||
`ignore_index: 0` (unclassified는 손실 계산에서 제외).
|
||
|
||
### PLY 필드 규약
|
||
|
||
| 필드 | 의미 |
|
||
|---|---|
|
||
| `f:color` | face 단위 색상 |
|
||
| `v:color` | vertex(point) 단위 색상 |
|
||
| `f:label` / `v:label` | 시맨틱 라벨 |
|
||
| `h:texcoord` | 텍스처 좌표 (halfedge) |
|
||
|
||
### 리포지토리 구조
|
||
|
||
- `semantic_segmentation/` — 딥러닝 베이스라인
|
||
- KPConv (`train_UrbanMesh.py`, `UrbanMesh.py`)
|
||
- PointNeXt_bundle (PointNet, PointNet++, PointNeXt, PointVector)
|
||
- Open3D_ML (SparseConvUNet, RandLA-Net)
|
||
- SPG
|
||
- RF_MRF / SUM_RF / PSSNet → 별도 PSSNet repo의 `sumv2` 브랜치
|
||
- `interactive_annotation/` — SAM, SimpleClick 기반 텍스처 어노테이션 도구
|
||
- `assets/` — 문서 이미지, 설정 파일
|
||
|
||
### 데이터 접근 — 게이트 걸려 있음 ⚠️
|
||
|
||
HF 데이터셋이 `gated: auto`. 토큰만으로는 안 되고 **계정별 약관 수락이 1회 필요**하다.
|
||
수락 전에는 모든 다운로드가 `HTTP 403`.
|
||
|
||
**수동 절차 (브라우저 필요, 자동화 불가)**
|
||
|
||
1. https://huggingface.co/datasets/gwxgrxhyz/SUM-Parts 접속
|
||
2. 로그인 후 게이트 폼에서 CC BY-NC 4.0 수락
|
||
3. 이후 캐시된 토큰으로 자동 다운로드 가능
|
||
|
||
**배포 파일**
|
||
|
||
| 파일 | 내용 |
|
||
|---|---|
|
||
| `demo.zip` | 소량 샘플 — 스모크 테스트용. **여기부터 시작** |
|
||
| `mesh.zip` | 텍스처 메시 (ASCII PLY) |
|
||
| `pcl.zip` | 시맨틱 포인트클라우드 (binary PLY) |
|
||
|
||
### 데이터 레이아웃 (PointNeXt 기준)
|
||
|
||
`cfgs/sumv2_triangle/default.yaml` → `data_root: ../../data/sumv2_tri_texpcl/`
|
||
|
||
⚠️ **이 상대경로는 `main.py`의 작업 디렉토리 기준**이다. `main.py`는
|
||
`examples/segmentation/`에서 실행되므로 `../../` = **`PointNeXt_bundle/`**이지
|
||
repo 루트가 아니다. 착각하면 `Totally 0 samples in train set`으로 조용히 실패한다.
|
||
|
||
```
|
||
PointNeXt_bundle/data/sumv2_tri_texpcl/
|
||
├── train/*.ply
|
||
├── val/*.ply
|
||
├── test/*.ply
|
||
└── processed/ # presample 캐시, 자동 생성
|
||
```
|
||
|
||
여기서는 데이터를 `<repo>/data`에 두고 심볼릭 링크로 연결했다
|
||
([scripts/link_data.sh](../scripts/link_data.sh)).
|
||
|
||
**demo.zip 실제 구조** (train/val/test 아님 — 타일 1장짜리 쇼케이스):
|
||
|
||
```
|
||
data/
|
||
├── mesh/
|
||
│ ├── textured_mesh/ demo.ply + demo.jpg
|
||
│ ├── face_label/ demo_face_label.ply + .jpg
|
||
│ ├── pixel_label/ demo_pixel_label.ply + .png
|
||
│ └── full_pixel_label/ demo_full_pixel_label.ply + .png
|
||
└── pcl/
|
||
├── face_labeling_pcl/ demo_{texsp,fdcen,poisson,rand}_pcl.ply
|
||
└── texture_labeling_pcl/ demo_{texsp,poisson,rand}_pcl.ply
|
||
```
|
||
|
||
cfg 디렉토리명 `sumv2_tri_texpcl` = **triangle 트랙 + texsp 샘플러**
|
||
→ `pcl/face_labeling_pcl/demo_texsp_pcl.ply`
|
||
|
||
**PLY vertex 속성** (실측 + `read_ply_with_plyfilelib` 확인):
|
||
|
||
| 속성 | 실제 배포본 | 필수 | 비고 |
|
||
|---|---|---|---|
|
||
| `x`, `y`, `z` | ✅ | ✅ | float32 |
|
||
| `nx`, `ny`, `nz` | ✅ | ✗ | 로더가 무시 |
|
||
| `r`, `g`, `b` | ✅ | ✅ | **`red,green,blue` 아님.** 로더는 둘 다 받는다 |
|
||
| `label` | ✅ | ✅ | int32 |
|
||
| `sp_id` | texsp 파일만 | ✗ | 슈퍼픽셀 ID |
|
||
| `object_index` | ✗ | ✗ | 로더는 지원하나 demo엔 없음 |
|
||
|
||
**실측 라벨 분포**
|
||
|
||
| 트랙 | 라벨 값 | 클래스 수 |
|
||
|---|---|---|
|
||
| `face_labeling_pcl` | `{0..10, 12}` | 13 ✅ `SUMV2_Triangle` 일치 |
|
||
| `texture_labeling_pcl` | `{0..9, 11..19}` | 20 ✅ `SUMV2_Texture` 일치 |
|
||
|
||
**타일 규모**: 252 × 252 × 40 m, 47만 포인트 (fdcen만 21만).
|
||
좌표 원점 `(7749, 5499, -1.6)` — **저자도 전체 CRS 좌표가 아닌 로컬 미터계로 배포**한다.
|
||
|
||
→ **한국 데이터 변환 시 이 스키마만 맞추면 된다.** 텍스처 불필요.
|
||
|
||
### 주요 학습 하이퍼파라미터 (sumv2_triangle 기본값)
|
||
|
||
| 항목 | 값 |
|
||
|---|---|
|
||
| `voxel_size` | 0.02 |
|
||
| `voxel_max` (train) | 64000 |
|
||
| `loop` | 30 (학습셋 30회 반복 → epoch 수는 적어도 됨) |
|
||
| `epochs` | 100 |
|
||
| `batch_size` | 2 (PointNeXt-XL 기준. PointNet: 6, PointNet++: 10) |
|
||
| `ignore_index` | 0 (unclassified 제외) |
|
||
| optimizer | adamw, lr 0.01, cosine |
|
||
| `cls_weighed_loss` | True (클래스 불균형 보정) |
|
||
|
||
제공 모델 cfg: `pointnet.yaml`, `pointnet++msg.yaml`, `pointnext-xl.yaml`, `pointvector-xl.yaml`
|
||
|
||
### 평가
|
||
|
||
현재는 각 데이터에 내장된 GT 라벨로 자체 평가. 논문과 동일한 fine-grained 테스트셋 평가는
|
||
**예측 결과를 저자 이메일로 보내면 로컬에서 채점**해 줌. 자동 평가 코드는 HF에 추가 예정.
|
||
|
||
### 시각화
|
||
|
||
- **Mapple** (권장) — `f:color`, `v:color`, 라벨 범례 표시 가능
|
||
- **MeshLab** — face color/texture는 보이나 스칼라(라벨)는 처리 못 함
|
||
|
||
---
|
||
|
||
## 2. 라이선스 검토
|
||
|
||
**두 개가 따로 논다. 분리해서 봐야 한다.**
|
||
|
||
| 대상 | 라이선스 | 배포 조건 |
|
||
|---|---|---|
|
||
| 코드 (GitHub repo) | **GPL-3.0** | 파생 코드 배포 시 소스 공개 + GPL-3.0 승계. 비공개 상용 제품에 혼합 불가 |
|
||
| 데이터셋 (HF SUM-Parts) | **CC BY-NC 4.0** | **비상업 한정**. 출처 표기 필수. 재배포/개변 허용 |
|
||
|
||
### 결론
|
||
|
||
- **로컬 실험/학습 목적** → 제약 없음. 그냥 쓰면 된다.
|
||
- **논문 / 오픈소스 릴리즈** → 가능. 단 코드는 GPL-3.0으로 내고, SUM Parts(2025) + SUM(2021) 둘 다 인용.
|
||
- **상업 이용** → 저자에게 별도 허락 필요 (gaoweixiaocuhk@gmail.com).
|
||
|
||
### 주의 3가지
|
||
|
||
1. **학습 가중치** — NC 데이터로 뽑은 weight는 NC로 취급하는 게 안전. 상업 배포 금지 쪽으로 본다.
|
||
2. **베이스라인별 개별 라이선스** — KPConv/PointNeXt/Open3D-ML은 대개 MIT, SPG는 별도 확인 필요. vendoring 시 각 LICENSE 동봉.
|
||
3. **데이터 자체를 repo에 커밋 금지** — HF 링크 + 다운로드 스크립트만 둔다.
|
||
|
||
> 법률 자문 아님. 최종 확인은 repo `LICENSE` 파일 + HF 데이터셋 카드 원문.
|
||
|
||
### 인용
|
||
|
||
```bibtex
|
||
@InProceedings{Gao_2025_CVPR,
|
||
author = {Gao, Weixiao and Nan, Liangliang and Ledoux, Hugo},
|
||
title = {SUM Parts: Benchmarking Part-Level Semantic Segmentation of Urban Meshes},
|
||
booktitle = {Proceedings of CVPR},
|
||
month = {June},
|
||
year = {2025},
|
||
pages = {24474-24484}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 실행 환경 판단
|
||
|
||
### 왜 Windows 네이티브로 안 되나
|
||
|
||
`railway-client` 프로젝트의 SAM 3.1은 Windows에서 잘 돈다. 이유는 **순수 PyTorch, 컴파일 없음**.
|
||
SUM 베이스라인은 성격이 다르다 — `nvcc + 컴파일러`로 **커스텀 CUDA 커널을 직접 빌드**해야 한다.
|
||
|
||
| 하려는 것 | Windows 네이티브 |
|
||
|---|---|
|
||
| `interactive_annotation/` (SAM, SimpleClick) | ✅ 가능 |
|
||
| PLY 로드 / 변환 / 시각화 | ✅ 가능 |
|
||
| KPConv / PointNeXt / SPG 학습 | ❌ `pointnet2_ops`, cut-pursuit 등 Windows 패치 필요 |
|
||
|
||
**결론**: WSL2가 이미 설치돼 있으므로 그대로 사용. 학습만 WSL, 데이터 변환/어노테이션은 Windows.
|
||
`/mnt/d/`로 파일 공유되니 복사 불필요.
|
||
|
||
### 실측 환경
|
||
|
||
**Windows 호스트**
|
||
|
||
| 항목 | 값 |
|
||
|---|---|
|
||
| OS | Windows 10 Enterprise 19045 |
|
||
| GPU | RTX 3060 12GB, 드라이버 610.47 |
|
||
| Python | 3.11.9 |
|
||
| MSVC | Visual Studio Professional 2022 ✅ |
|
||
| CUDA Toolkit (nvcc) | ❌ 없음 |
|
||
| WSL | Ubuntu-22.04 (v2), docker-desktop |
|
||
|
||
**WSL2 / Ubuntu-22.04**
|
||
|
||
| 항목 | 값 | 상태 |
|
||
|---|---|---|
|
||
| GPU 패스스루 | RTX 3060 12GB 인식됨 | ✅ |
|
||
| gcc / g++ | 11.4.0 | ✅ |
|
||
| git | 2.34.1 | ✅ |
|
||
| Python (시스템) | 3.10.12 | ✅ |
|
||
| 디스크 `/` | 251G 중 192G 여유 | ✅ |
|
||
| 디스크 `/mnt/d` | 7.3T 중 2.9T 여유 | ✅ |
|
||
| RAM | 31G | ✅ |
|
||
| nvcc | 없음 | ❌ 설치 필요 |
|
||
| conda | 없음 | ❌ 설치 필요 |
|
||
| **sudo** | **암호 필요 (NOPASSWD 아님)** | ⚠️ apt 사용 불가 |
|
||
|
||
### 최종 구성 (검증 완료)
|
||
|
||
```
|
||
nvcc : 11.8.89 (~/miniconda3/envs/sumparts/bin/nvcc)
|
||
torch : 2.0.1+cu118
|
||
cuda : available → NVIDIA GeForce RTX 3060
|
||
numpy : 1.26.4
|
||
python : 3.10
|
||
```
|
||
|
||
**업스트림 `install.sh`와 다르게 간 이유**
|
||
|
||
원본은 python 3.7 / torch 1.12.1 / cu113 / `cudatoolkit=11.3` 전제.
|
||
py3.7은 EOL이고 `requirements.txt` 핀(`setuptools==59.5.0`, `protobuf==3.19.4`,
|
||
`tensorboard==2.8.0` 등)이 py3.10에서 해결되지 않는다. 그래서:
|
||
|
||
| 항목 | 원본 | 여기 | 이유 |
|
||
|---|---|---|---|
|
||
| python | 3.7 | 3.10 | 3.7 EOL |
|
||
| torch | 1.12.1+cu113 | 2.0.1+cu118 | 드라이버 610.47 / sm_86 대응 |
|
||
| 채널 | anaconda defaults | conda-forge + nvidia | ToS/상용 라이선스 회피 |
|
||
| `deepspeed` | 설치 | 제외 | sumv2 학습 경로에서 미사용, 빌드 무거움 |
|
||
| `mkdocs-*` | 설치 | 제외 | 문서 전용 |
|
||
| `chamfer_dist`, `emd` 확장 | 빌드 | 제외 | reconstruction 태스크 전용 |
|
||
| `plyfile` | **누락됨** | 추가 | 데이터 로더가 import 하는데 requirements.txt에 없음 |
|
||
| `setuptools` | 59.5.0 | <80 | 80+에서 `python setup.py install` 제거됨 |
|
||
|
||
빌드 대상 CUDA 확장 3종: `pointnet2_batch`, `subsampling`, `pointops`.
|
||
`TORCH_CUDA_ARCH_LIST=8.6` 고정 (RTX 3060) — 전 아키텍처 빌드 방지.
|
||
|
||
### Anaconda 채널 회피 (중요)
|
||
|
||
기본 miniconda는 `repo.anaconda.com/pkgs/main`, `pkgs/r`을 쓰는데,
|
||
이 채널은 **ToS 수락이 필요하고 일정 규모 이상 조직에는 상용 라이선스 의무**가 붙는다.
|
||
`conda create` 시 `CondaToSNonInteractiveError`로 막힌다.
|
||
|
||
→ 수락하지 않고 우회. `--override-channels -c conda-forge` + CUDA는 `nvidia` 채널.
|
||
`conda config --remove channels defaults` + `channel_priority strict`.
|
||
|
||
### sudo 제약 대응
|
||
|
||
`sudo` 암호가 필요해 `apt install cuda-toolkit`을 자동화할 수 없다.
|
||
→ **conda 경로로 우회**. 둘 다 sudo 없이 설치된다.
|
||
|
||
- miniconda → `~/miniconda3` (홈 디렉토리, sudo 불필요)
|
||
- CUDA Toolkit → `conda install -c nvidia cuda-toolkit` (env 내부 설치, sudo 불필요)
|
||
|
||
### 배치 원칙
|
||
|
||
| 대상 | 위치 | 이유 |
|
||
|---|---|---|
|
||
| 코드 / conda env | WSL ext4 (`~`) | `/mnt/d`는 9p 파일시스템이라 CUDA 확장 빌드가 매우 느림 |
|
||
| 데이터셋 | `/mnt/d` | 용량 크고 Windows 도구와 공유 필요 |
|
||
|
||
---
|
||
|
||
## 3-1. 빌드 트러블슈팅 기록
|
||
|
||
CUDA 확장 3종(`pointnet2_batch`, `subsampling`, `pointops`) 빌드에서 걸린 것들.
|
||
전부 **최신 툴체인 × 2022년 코드베이스** 충돌이다.
|
||
|
||
### ① Anaconda ToS 차단
|
||
|
||
```
|
||
CondaToSNonInteractiveError: Terms of Service have not been accepted for the
|
||
following channels: https://repo.anaconda.com/pkgs/main, .../pkgs/r
|
||
```
|
||
|
||
수락하면 넘어가지만 상용 라이선스 의무가 붙는다.
|
||
→ `--override-channels -c conda-forge` + `nvidia` 채널로 회피. defaults 미사용.
|
||
|
||
### ② ninja SIGPIPE
|
||
|
||
```
|
||
subprocess.CalledProcessError: Command '['ninja', '-v']' died with <Signals.SIGPIPE: 13>.
|
||
RuntimeError: Error compiling objects for extension
|
||
```
|
||
|
||
컴파일 에러가 아니다. torch 2.0의 `cpp_extension`이 `ninja -v` 출력을 파이프로 읽는데
|
||
**ninja ≥1.12가 거기서 SIGPIPE로 죽는다**. torch가 원인을 뭉개고
|
||
"Error compiling objects"로만 표시해 오해하기 쉽다.
|
||
|
||
→ `pip install ninja==1.11.1.1`
|
||
|
||
### ③ setuptools에서 distutils.msvccompiler 제거
|
||
|
||
```
|
||
ModuleNotFoundError: No module named 'distutils.msvccompiler'
|
||
```
|
||
|
||
`openpoints/cpp/subsampling/setup.py:2` 가 `numpy.distutils.misc_util`를 import 하는데,
|
||
`numpy.distutils`는 내부적으로 `distutils.msvccompiler`를 찾는다.
|
||
이 모듈은 **setuptools 74.0에서 제거**됨. (초기 핀 `<80`은 79.0.1을 뽑아서 여전히 실패)
|
||
|
||
→ `pip install setuptools==69.5.1`
|
||
(단, `numpy<2` 도 함께 필요. numpy 2에는 `numpy.distutils` 자체가 없다.)
|
||
|
||
### ④ WSL 인스턴스 사망
|
||
|
||
```
|
||
Wsl/Service/CreateInstance/E_FAIL
|
||
```
|
||
|
||
컴파일 도중 WSL VM이 통째로 내려갔다. 컴파일 에러와 무관.
|
||
|
||
→ `wsl --shutdown` 후 재접속으로 복구. 재발 시 `MAX_JOBS` 낮춰서 병렬 컴파일 부하 감소.
|
||
|
||
### ⑤ 확장 모듈 이름 (함정)
|
||
|
||
`pointnet2_batch/setup.py`가 만드는 모듈명은 **`pointnet2_batch_cuda`**다.
|
||
업스트림 PointNet++ 포크들의 `pointnet2_cuda`가 아니다.
|
||
검증 스크립트에서 이걸 틀리면 빌드는 다 성공했는데 `ModuleNotFoundError`로 실패한 것처럼 보인다.
|
||
|
||
| 확장 | import 이름 |
|
||
|---|---|
|
||
| pointnet2_batch | `pointnet2_batch_cuda` |
|
||
| pointops | `pointops_cuda` |
|
||
| subsampling | `from openpoints.cpp.subsampling import grid_subsampling` |
|
||
|
||
### ⑥ `unzip` 미설치
|
||
|
||
exit 127. sudo 암호가 필요해 `apt install unzip`을 못 한다.
|
||
→ python `zipfile`로 압축 해제.
|
||
|
||
### ⑦ `wandb`가 모듈 스코프에서 import 됨
|
||
|
||
```
|
||
main.py:8: import argparse, yaml, os, logging, numpy as np, csv, wandb, glob
|
||
ModuleNotFoundError: No module named 'wandb'
|
||
```
|
||
|
||
`wandb.use_wandb=False`로 꺼도 소용없다. **import 자체가 조건 없이 실행**되므로 설치는 필수.
|
||
→ `pip install wandb` + `WANDB_MODE=disabled` (로그인 요구 차단).
|
||
|
||
### ⑧ chamfer_dist / emd는 "선택"이 아니다
|
||
|
||
```
|
||
openpoints/cpp/chamfer_dist/__init__.py:10: import chamfer
|
||
ModuleNotFoundError: No module named 'chamfer'
|
||
```
|
||
|
||
기능상 reconstruction 전용이 맞다. 하지만 `openpoints/models/__init__.py:9`가
|
||
`.reconstruction`을 **조건 없이 import** → `maskedpointvit.py:10` → `chamfer_dist`.
|
||
세그멘테이션만 돌려도 import 체인에 걸린다. 빌드 필수.
|
||
|
||
(`deepspeed` 제외는 유효 — import 체인에 없음.)
|
||
|
||
### ⑨ emd 모듈명도 다르다
|
||
|
||
`emd/setup.py`: `setup(name='emd_ext', ext_modules=[CUDAExtension(name='emd_cuda', ...)])`
|
||
→ import 이름은 **`emd_cuda`**. `emd`는 `openpoints/cpp/emd/__init__.py`의 파이썬 별칭일 뿐.
|
||
|
||
### ⑩ `data_root` 상대경로 기준점
|
||
|
||
```
|
||
[SUMV2_Triangle]: Totally 0 samples in train set.
|
||
```
|
||
|
||
예외 없이 조용히 0개. `data_root: ../../data/...`는 **`main.py`의 cwd 기준**이고,
|
||
`main.py`는 `examples/segmentation/`에서 돈다 → `../../` = `PointNeXt_bundle/`.
|
||
repo 루트가 아니다.
|
||
|
||
→ [scripts/link_data.sh](../scripts/link_data.sh)로 `PointNeXt_bundle/data` → `<repo>/data` 심볼릭 링크.
|
||
|
||
### ⑪ 샘플 수 < batch_size → 엉뚱한 예외
|
||
|
||
```
|
||
0it [00:00, ?it/s]
|
||
AttributeError: 'int' object has no attribute 'diag'
|
||
at openpoints/utils/metrics.py:84 -> return self.value.diag()
|
||
```
|
||
|
||
메시지가 원인을 전혀 안 가리킨다. 실제 원인은 **dataloader의 `drop_last`**:
|
||
타일 1장 × `loop=1` = 샘플 1개인데 pointnet `batch_size=6` → **배치 0개** →
|
||
혼동행렬이 `int 0`으로 남아 `.diag()` 호출에서 터진다.
|
||
|
||
→ `dataset.train.loop`를 키우고 `batch_size=1`. 데이터가 적을 때 반드시 걸리는 함정.
|
||
|
||
### ⑫ Git Bash에서 WSL 호출 시 경로 변환
|
||
|
||
```
|
||
bash: C:/Program Files/Git/mnt/d/.../smoke_train.sh: No such file or directory
|
||
```
|
||
|
||
Git Bash(MSYS)가 `/mnt/d/...`를 Windows 경로로 자동 변환한다.
|
||
→ WSL 호출은 PowerShell에서 하거나 `MSYS_NO_PATHCONV=1`.
|
||
|
||
### ⑬ numpy 제거 별칭
|
||
|
||
```
|
||
sumv2_triangle.py:134: label = label.astype(np.long)
|
||
AttributeError: module 'numpy' has no attribute 'long'
|
||
```
|
||
|
||
`np.long` / `np.int` / `np.float` / `np.bool` / `np.object` / `np.str`는
|
||
**numpy 1.24에서 제거**됐다. 원본은 numpy 1.20 전제.
|
||
|
||
numpy를 내리는 건 불가 — torch 2.0.1은 `numpy<2`를 요구하고, numpy 1.20에는 py3.10 휠이 없다.
|
||
→ 소스 패치. [scripts/patch_numpy_aliases.sh](../scripts/patch_numpy_aliases.sh) (8개 파일)
|
||
|
||
| 별칭 | 치환 |
|
||
|---|---|
|
||
| `np.long` | `np.int64` |
|
||
| `np.int` | `int` |
|
||
| `np.float` | `float` |
|
||
| `np.bool` | `bool` |
|
||
| `np.object` | `object` |
|
||
| `np.str` | `str` |
|
||
|
||
`\b` 경계 필수 — 안 그러면 `np.int32`, `np.float32`까지 망가진다.
|
||
|
||
### ⑭ collections ABC 이동
|
||
|
||
```
|
||
point_transformer_gpu.py:282: isinstance(self.angle, collections.Iterable)
|
||
AttributeError: module 'collections' has no attribute 'Iterable'
|
||
```
|
||
|
||
Python 3.10에서 `collections.abc`로 이동. 같은 패치 스크립트가 처리한다.
|
||
|
||
### ⑮ BatchNorm은 배치 1을 거부
|
||
|
||
```
|
||
ValueError: Expected more than 1 value per channel when training,
|
||
got input size torch.Size([1, 512])
|
||
```
|
||
|
||
⑪ 해결하려고 `batch_size=1`로 내렸더니 이번엔 BN이 막는다.
|
||
**양쪽에서 조인다**: 너무 크면 `drop_last`로 배치 0개, 너무 작으면 BN 거부.
|
||
→ `batch_size=2` (BN 하한) + `loop`로 타일 반복해 온전한 배치 확보.
|
||
|
||
### ⑯ WSL 크래시가 남긴 0바이트 패키지 ★
|
||
|
||
가장 찾기 어려웠던 것.
|
||
|
||
```
|
||
main.py:666: all_logits = scatter(all_logits, idx_points, dim=0, reduce='mean')
|
||
TypeError: 'module' object is not callable
|
||
```
|
||
|
||
`from torch_scatter import scatter`가 함수가 아니라 서브모듈을 반환. 이유:
|
||
|
||
```
|
||
torch_scatter/__init__.py 0 bytes
|
||
torch_scatter/scatter.py 0 bytes
|
||
torch_scatter/_scatter_cuda.so 0 bytes
|
||
```
|
||
|
||
④의 WSL VM 사망 당시 pip이 열고 있던 파일이 **전부 빈 껍데기로 디스크에 남았다**.
|
||
`gdown/*.py` 전부, `vtkmodules/*.so` 다수도 동일.
|
||
|
||
**고약한 이유**: import가 성공한다. 모듈이 비어있을 뿐이라 에러가 한참 뒤 호출 지점에서
|
||
엉뚱한 메시지로 터진다. 설치 로그에도 `Successfully installed`로 남아 있다.
|
||
|
||
**탐지**:
|
||
```bash
|
||
find <site-packages> -type f \( -name '*.py' -o -name '*.so' \) -size 0 ! -name '__init__.py'
|
||
```
|
||
`__init__.py`는 원래 빈 경우가 많으니 제외. 잔여 정상 케이스:
|
||
`torch/cuda/error.py`, `torch/ao/.../observation_type.py`, `sklearn/_built_with_meson.py`.
|
||
|
||
→ [scripts/repair_env.sh](../scripts/repair_env.sh)로 크래시 당시 배치 전체 강제 재설치.
|
||
|
||
**교훈**: VM/컨테이너가 설치 도중 죽으면 그 시점 패키지들을 무조건 재설치해라.
|
||
pip은 손상을 감지하지 못한다.
|
||
|
||
### 무시해도 되는 경고
|
||
|
||
```
|
||
warning: 'T* at::Tensor::data() const' is deprecated:
|
||
Tensor.data<T>() is deprecated. Please use Tensor.data_ptr<T>() instead.
|
||
warning: There are no g++ version bounds defined for CUDA version 11.8
|
||
```
|
||
|
||
전부 경고. 빌드 실패 원인 아니다.
|
||
|
||
### 재실행 전략
|
||
|
||
빌드 단계만 [scripts/build_ext.sh](../scripts/build_ext.sh)로 분리했다.
|
||
의존성 재설치 없이 컴파일만 재시도 가능하고, 이미 import 되는 확장은 건너뛴다
|
||
(`FORCE=1`이면 전부 재빌드).
|
||
|
||
---
|
||
|
||
## 3-2. 실학습 가능성 — RTX 3060 12GB 실측 (2026-08-20)
|
||
|
||
### 결론: 가능하다. 단 XL 계열은 `voxel_max`를 낮춰야 한다.
|
||
|
||
### 데이터 규모
|
||
|
||
| 항목 | 값 |
|
||
|---|---|
|
||
| `pcl.zip` | 4.66 GB |
|
||
| `mesh.zip` | 0.52 GB |
|
||
| `demo.zip` | 0.12 GB |
|
||
| 타일 수 | **train 24 / val 8 / test 8** (총 40) |
|
||
|
||
디스크 192GB 여유 → 저장은 문제 없음.
|
||
|
||
### ★ WSL2의 함정 — VRAM 초과가 OOM을 내지 않는다
|
||
|
||
WSL2의 NVIDIA 드라이버는 VRAM을 넘으면 **호스트 RAM으로 흘린다**(system memory fallback).
|
||
네이티브 Linux라면 `torch.cuda.OutOfMemoryError`로 즉시 죽을 설정이 여기서는
|
||
**조용히 완주한다 — 25~100배 느리게.**
|
||
|
||
```
|
||
peak VRAM 15.47 GB on a 12 GB card <- 물리적으로 불가능한 수치가 보고된다
|
||
```
|
||
|
||
"돌아간다"는 사실만 보고 설정을 확정하면 20일짜리 학습을 시작하게 된다.
|
||
**반드시 peak VRAM을 카드 용량과 대조해야 한다.**
|
||
|
||
### `voxel_max` 스윕 (pointnext-xl, batch_size 2)
|
||
|
||
| voxel_max | pts/batch | peak VRAM | s/iter | 판정 |
|
||
|---|---|---|---|---|
|
||
| 24000 | 48,000 | 6.17 G | **0.434** | ✅ |
|
||
| 32000 | 64,000 | 8.03 G | **0.635** | ✅ |
|
||
| 40000 | 80,000 | 9.88 G | **1.169** | ✅ |
|
||
| 48000 | 96,000 | 11.75 G | **29.169** | ❌ 폴백 |
|
||
| **64000** (cfg 기본값) | 128,000 | 15.47 G | **46.980** | ❌ 폴백 |
|
||
|
||
경계가 선명하다. 40000 → 48000 사이에서 **25배** 절벽.
|
||
정상 구간 안에서도 s/iter가 초선형으로 증가한다(0.434 → 0.635 → 1.169).
|
||
|
||
### 모델별 실측
|
||
|
||
| cfg | params | bs | 설정 | peak VRAM | s/iter | 적합 |
|
||
|---|---|---|---|---|---|---|
|
||
| pointnet | 3.6M | 2 | voxel_max 64000 (원본) | 6.01 G | 0.291 | ✅ |
|
||
| pointnet++msg | 3.0M | 2 | voxel_max 64000 (원본) | 4.16 G | 0.675 | ✅ |
|
||
| pointnext-xl | 41.6M | 2 | voxel_max 32000 | 8.03 G | 0.635 | ✅ |
|
||
| pointvector-xl | 24.1M | 2 | voxel_max 24000 | 6.46 G | 0.402 | ✅ |
|
||
| pointnext-xl | 41.6M | 2 | voxel_max 64000 | 15.47 G | 46.980 | ❌ |
|
||
| pointvector-xl | 24.1M | 2 | voxel_max 64000 | 16.49 G | 13.113 | ❌ |
|
||
|
||
> `batch_size`는 네 cfg 모두 `default.yaml`의 **2**를 그대로 쓴다.
|
||
> default.yaml 주석의 "PointNet: 6, PointNet++: 10"은 실제 설정에 반영돼 있지 않다.
|
||
|
||
### 학습 시간 추정
|
||
|
||
반복 횟수 = train 24타일 × `loop` 30 ÷ `batch_size` 2 = **360 iter/epoch**
|
||
× `epochs` 100 = **36,000 iter**
|
||
|
||
| 모델 | 설정 | 학습 시간 (val 제외) |
|
||
|---|---|---|
|
||
| pointnet | 원본 | **2.9 h** |
|
||
| pointnet++msg | 원본 | **6.8 h** |
|
||
| pointnext-xl | voxel_max 32000 | **6.4 h** |
|
||
| pointvector-xl | voxel_max 24000 | **4.0 h** |
|
||
| ~~pointnext-xl~~ | ~~원본 64000~~ | ~~**470 h ≈ 20일**~~ |
|
||
|
||
`val_freq: 1`이라 매 epoch 8타일 검증이 붙는다 → **1~3시간 추가**.
|
||
`val_freq: 5`로 올리면 대부분 회수된다.
|
||
|
||
### 남는 제약
|
||
|
||
- **XL 계열은 논문 설정 재현 불가.** `voxel_max` 64000 → 32000은 샘플당 포인트를 절반으로
|
||
줄이는 것이고, 컨텍스트 윈도우가 좁아진다. 논문 수치와 직접 비교하면 안 된다.
|
||
- **작은 모델 2개(pointnet, pointnet++msg)는 원본 설정 그대로 학습 가능하다.**
|
||
재현성이 중요하면 이쪽부터.
|
||
- 측정은 5~8 iteration 평균이며 warm-up 2회는 제외했다. 장시간 학습의 열/클럭 변동은 반영 안 됨.
|
||
|
||
### 스크립트
|
||
|
||
| 파일 | 역할 |
|
||
|---|---|
|
||
| [scripts/bench_models.py](../scripts/bench_models.py) | 모델별 peak VRAM + s/iter 측정, VRAM 초과 여부 판정 |
|
||
| [scripts/run_bench.sh](../scripts/run_bench.sh) | 벤치 실행 래퍼 |
|
||
| [scripts/sweep_voxel_max.sh](../scripts/sweep_voxel_max.sh) | `voxel_max` 스윕으로 VRAM 한계 탐색 |
|
||
| [scripts/check_hf_sizes.py](../scripts/check_hf_sizes.py) | HF 아카이브 크기 조회 |
|
||
| [scripts/list_archive.sh](../scripts/list_archive.sh) | 아카이브 내용·타일 수 확인 (압축 해제 없이) |
|
||
| [scripts/verify_archives.sh](../scripts/verify_archives.sh) | 다운로드 아카이브 무결성 검사 |
|
||
| [scripts/check_alloc_conf.sh](../scripts/check_alloc_conf.sh) | `PYTORCH_CUDA_ALLOC_CONF` 유효성 사전 검증 |
|
||
| [scripts/epoch_timing.sh](../scripts/epoch_timing.sh) | epoch별 소요 시간 → 감속 시작 지점 특정 |
|
||
| [scripts/iter_rate.sh](../scripts/iter_rate.sh) | 초기 vs 최근 반복 속도 비교 |
|
||
| [scripts/verify_speed.sh](../scripts/verify_speed.sh) | 현재 속도가 정상 범위인지 판정 |
|
||
| [scripts/diag_run.sh](../scripts/diag_run.sh) | 실행 실패 시 로그 일괄 덤프 |
|
||
|
||
### 무인 실행 스크립트
|
||
|
||
| 파일 | 역할 |
|
||
|---|---|
|
||
| [scripts/launch_overnight.sh](../scripts/launch_overnight.sh) | `setsid nohup` 분리 실행. `RESUME_CKPT`로 이어하기 |
|
||
| [scripts/train_watchdog.sh](../scripts/train_watchdog.sh) | 크래시 시 최신 체크포인트에서 자동 재개 (최대 8회) |
|
||
| [scripts/check_training.sh](../scripts/check_training.sh) | 진행 epoch·GPU·best miou·워치독 이벤트 |
|
||
| [scripts/stop_training.sh](../scripts/stop_training.sh) | 워치독 먼저 종료 후 학습 중단 |
|
||
| [scripts/monitor_training.sh](../scripts/monitor_training.sh) | 진행·감속·크래시·완료 이벤트 스트림 |
|
||
| [scripts/train_full.sh](../scripts/train_full.sh) | 단발 실학습 (워치독 없이) |
|
||
| [scripts/prepare_full_split.sh](../scripts/prepare_full_split.sh) | `val` → `validate` 심볼릭 링크 |
|
||
| [scripts/show_layout.sh](../scripts/show_layout.sh) | 전개된 데이터 레이아웃 확인 |
|
||
|
||
---
|
||
|
||
## 3-2-1. 실학습 중 발생한 무증상 10배 감속 ★★
|
||
|
||
벤치마크에서 발견한 "VRAM 초과가 OOM을 안 낸다"가 **실제 학습에서 재현됐다.**
|
||
다만 원인이 예상과 달랐다.
|
||
|
||
### 증상
|
||
|
||
```
|
||
epoch 1-10 ~103 s/epoch (벤치 예측 105 s와 일치)
|
||
epoch 11 +1011 s ← 20:45부터 급변
|
||
epoch 12-20 ~1100 s/epoch
|
||
```
|
||
|
||
반복당 속도: **0.29 s/it → 3.14 s/it (10.7배)**. 남은 80 epoch에 24시간.
|
||
|
||
에러 없음. 경고 없음. GPU 사용률 100%, 클럭 1942MHz 만빵, 온도 51°C, 스로틀 전무.
|
||
**로그만 보면 완벽하게 정상이다.**
|
||
|
||
### 오진과 정정
|
||
|
||
처음엔 데스크톱 앱(브라우저·Electron)이 VRAM을 잠식했다고 봤다.
|
||
`nvidia-smi`에 Brave, Edge, WebView2, VS Code 등이 잔뜩 잡혀 있었기 때문이다.
|
||
|
||
**학습을 죽이고 재측정해서 뒤집혔다:**
|
||
|
||
| 상태 | Dedicated (VRAM) | Shared (호스트 RAM) |
|
||
|---|---|---|
|
||
| 학습 중 | 11,715 MB | **18,967 MB** |
|
||
| 학습 종료 후 | **1,245 MB** | 468 MB |
|
||
|
||
데스크톱 앱은 1.2GB뿐. **11.7GB + 19GB를 전부 학습 프로세스가 잡고 있었다.**
|
||
벤치 측정값 6.01GB의 약 2배로 불어난 것.
|
||
|
||
> 교훈: 남 탓하기 전에 **의심 대상을 죽여보고 기준선을 재라.** 30초면 된다.
|
||
|
||
### 진단 경로
|
||
|
||
| 단계 | 관측 | 배제/확정 |
|
||
|---|---|---|
|
||
| GPU 스로틀 | 51°C, `HW Slowdown: Not Active` | 열/전력 배제 |
|
||
| WSL 자원 | swap 0, IO wait 0, load 1.16 | 메모리·디스크 배제 |
|
||
| 프로세스 | main python **CPU 99.8% 단일 스레드** | CUDA 동기화 스핀 = GPU 대기 |
|
||
| **전력** | **61.7W** (3060 TDP 170W) | 연산 아님 → **전송 대기** |
|
||
| 반복 속도 | 초기 0.29 s/it → 3.14 s/it | epoch 간 오버헤드 아닌 **반복 자체** 감속 |
|
||
| **Perf 카운터** | **Shared Usage 18,967 MB** | **시스템 메모리 폴백 확정** |
|
||
|
||
**전력 소비가 가장 빠른 판별 지표였다.** 사용률 100%인데 전력이 낮으면 연산이 아니라 대기다.
|
||
|
||
```powershell
|
||
# 폴백 여부 직접 확인 (nvidia-smi로는 안 보인다)
|
||
(Get-Counter '\GPU Adapter Memory(*)\Shared Usage','\GPU Adapter Memory(*)\Dedicated Usage').CounterSamples
|
||
```
|
||
|
||
### 기여 요인 — cfg의 검증 설정
|
||
|
||
```yaml
|
||
train: { voxel_max: 64000 }
|
||
val: { voxel_max: null } # ← 타일 전체(47만 포인트)를 통째로
|
||
```
|
||
|
||
학습 스텝은 상한이 있는데 **검증만 무제한**이다. 이 거대한 일시 할당이 캐싱 할당자 풀을
|
||
부풀린다. 감속 시작이 epoch 10 검증 직후인 것과 일치한다.
|
||
|
||
**핵심**: PyTorch 캐싱 할당자는 한 번 shared로 넘어간 풀을 되돌리지 않는다.
|
||
VRAM이 비어도 자동 회복 없음 → **프로세스 재시작이 유일한 복구 경로.**
|
||
|
||
### 조치
|
||
|
||
| 항목 | 값 |
|
||
|---|---|
|
||
| 할당자 | `PYTORCH_CUDA_ALLOC_CONF=garbage_collection_threshold:0.7,max_split_size_mb:128` |
|
||
| 검증 상한 | `dataset.val.voxel_max=64000` (학습과 동일) |
|
||
| 재개 | epoch 20 체크포인트 (진행분 손실 없음) |
|
||
|
||
**복구 확인**
|
||
|
||
```
|
||
반복 속도 : 3.56 it/s = 0.28 s/it (벤치 0.291과 일치)
|
||
GPU 전력 : 61.7W → 140.08W
|
||
```
|
||
|
||
### ⚠️ `expandable_segments`는 torch 2.0에서 못 쓴다
|
||
|
||
첫 시도에서 `PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True`를 썼다가 즉시 크래시.
|
||
|
||
```
|
||
RuntimeError: Unrecognized CachingAllocator option: expandable_segments
|
||
```
|
||
|
||
**torch 2.1부터 지원**한다. 이 환경은 2.0.1.
|
||
게다가 traceback이 `model.to(device)`를 가리켜 모델 문제처럼 보인다.
|
||
|
||
→ [scripts/check_alloc_conf.sh](../scripts/check_alloc_conf.sh)로 **실행 전 2초 만에 검증**하도록 만들었다.
|
||
|
||
워치독은 이때 "재시도해도 새 체크포인트가 안 생긴다 = 학습 진입 전 실패"를 감지하고
|
||
무한 재시도 없이 정상 중단했다. 설계 의도대로 동작.
|
||
|
||
### 남는 편차 — 명시
|
||
|
||
`dataset.val.voxel_max`를 64000으로 제한했으므로 **val_miou는 서브샘플된 타일 기준**이다.
|
||
체크포인트 선택 신호로만 유효하다. 최종 test는 `test()`가 전체 타일을 슬라이딩 윈도우로
|
||
처리하므로 **보고 수치에는 영향이 없다.**
|
||
|
||
### 감시에 반영
|
||
|
||
침묵이 성공으로 오인되지 않도록 [scripts/monitor_training.sh](../scripts/monitor_training.sh)에
|
||
**속도 회귀 감지**를 추가했다. 1.5 s/it을 넘으면 GPU 메모리·전력과 함께 즉시 알린다.
|
||
크래시만 감시하면 이런 무증상 감속은 영원히 못 잡는다.
|
||
|
||
---
|
||
|
||
## 3-4. 실학습 결과 — pointnet 100 epoch (2026-08-21)
|
||
|
||
### 완주
|
||
|
||
```
|
||
epoch 100/100 완료 01:56:58
|
||
Best ckpt @E90
|
||
val_oa 49.12
|
||
val_macc 25.47
|
||
val_miou 17.19
|
||
```
|
||
|
||
| 항목 | 값 |
|
||
|---|---|
|
||
| 모델 | pointnet (3.6M params) |
|
||
| 데이터 | `face_labeling/texsp_pcl`, train 24 / val 8 / test 8 |
|
||
| 설정 | `voxel_max 64000`(원본), `batch_size 2`, `val_freq 5`, cosine LR 0.01→1.2e-5 |
|
||
| 소요 | 20:08 시작 → 01:57 완료. 순수 학습 시간 약 3.4h (감속 사고 3h 별도) |
|
||
| 속도 | 102초/epoch, 0.28 s/it (벤치 0.291과 일치) |
|
||
|
||
### 수렴 추이
|
||
|
||
| epoch | train_miou | best val_miou |
|
||
|---|---|---|
|
||
| 10 | 12.80 | 2.77 |
|
||
| 20 | 15.79 | 8.04 |
|
||
| 31 | 17.56 | 8.49 |
|
||
| 40 | 17.86 | 10.40 |
|
||
| 50 | 18.89 | 15.62 |
|
||
| 60 | 19.22 | 16.04 |
|
||
| 70 | 19.09 | 16.93 |
|
||
| 80 | 19.62 | 16.93 |
|
||
| **90** | — | **17.19** ← best |
|
||
| 100 | 19.95 | 17.19 |
|
||
|
||
train 19.95 / val 17.19 — 격차가 작아 과적합 징후 없다.
|
||
|
||
### ★ test 세트는 라벨이 없다 (블라인드)
|
||
|
||
학습 직후 test 단계에서 크래시:
|
||
|
||
```
|
||
main.py:672 cm.update(pred, label)
|
||
metrics.py:74 RuntimeError: bincount only supports 1-d non-negative integral inputs
|
||
```
|
||
|
||
원인 조사 결과 **우리 설정 문제가 아니었다**:
|
||
|
||
| split | 라벨 값 |
|
||
|---|---|
|
||
| train 24타일 | `0 ~ 12` 정상 |
|
||
| val 8타일 | `0 ~ 12` 정상 |
|
||
| **test 8타일** | **전부 `-1`** (dtype int32, uniq=1) |
|
||
|
||
README와 일치한다:
|
||
|
||
> "For fine-grained test set evaluation consistent with the paper,
|
||
> **send predictions to our email for local assessment.**"
|
||
|
||
의도적인 블라인드 테스트셋이고 `-1`은 자리표시자다.
|
||
업스트림 코드가 `if label is not None`만 검사하고 `-1`을 안 걸러서,
|
||
`true × num_classes + pred`가 음수가 되어 `torch.bincount`가 거부한 것.
|
||
|
||
> **로컬에서 test_miou는 원천적으로 계산 불가.** 논문 수치와 직접 대조하려면
|
||
> 예측 결과를 저자에게 보내야 한다 (gaoweixiaocuhk@gmail.com).
|
||
|
||
### 업스트림 버그 패치 2건
|
||
|
||
| # | 증상 | 원인 | 패치 |
|
||
|---|---|---|---|
|
||
| A | `bincount ... non-negative` | 블라인드 test의 `-1` 라벨 미처리 | [patch_unlabeled_test.sh](../scripts/patch_unlabeled_test.sh) — 전부 음수면 "정답 없음"으로 간주, 예측만 생성 |
|
||
| B | `UnboundLocalError: 'epoch'` (`mode=val`) | `epoch`이 학습 루프 안에서만 바인딩되는데 val 경로가 참조 | [patch_val_mode.sh](../scripts/patch_val_mode.sh) — `epoch = best_epoch` 선바인딩 |
|
||
|
||
둘 다 멱등이고 원본은 `main.py.orig`로 보존한다. 라벨 있는 데이터의 채점 동작은 바뀌지 않는다.
|
||
|
||
### 워치독 동작 검증
|
||
|
||
학습 완주 후 test 크래시로 rc=1이 되자 워치독이 재개를 시도했고,
|
||
2회째에 **"새 체크포인트가 생기지 않음 = 학습 진입 전 실패"**를 감지해 중단했다.
|
||
무한 재시도 없이 설계대로 동작했다.
|
||
|
||
### 남는 편차
|
||
|
||
학습 중 `dataset.val.voxel_max`를 64000으로 제한했다(§3-2-1). 따라서 위 `val_miou 17.19`는
|
||
**서브샘플된 검증셋 기준**이다. `voxel_max: null`(전체 타일)로 다시 평가한 수치는
|
||
[scripts/final_eval.sh](../scripts/final_eval.sh) 결과로 별도 기록한다.
|
||
|
||
---
|
||
|
||
## 3-3. WSL2 안정화
|
||
|
||
VM이 **2회** 사망했다.
|
||
|
||
| # | 시점 | 상황 |
|
||
|---|---|---|
|
||
| 1 | CUDA 확장 컴파일 중 | `Wsl/Service/CreateInstance/E_FAIL`. pip 진행 중 파일들이 **0바이트**로 남음 (§3-1 ⑯) |
|
||
| 2 | GPU 벤치(VRAM 만재) + 4.7GB 다운로드 동시 | 두 작업 동시 exit 253 |
|
||
|
||
`.wslconfig`가 없어 WSL2가 호스트 64GB의 50%(≈32GB)를 동적으로 잡고 있었다.
|
||
|
||
**적용한 설정** (`C:\Users\nbright\.wslconfig`)
|
||
|
||
```ini
|
||
[wsl2]
|
||
memory=24GB # 동적 50% → 고정 상한
|
||
swap=16GB # 스파이크 시 VM 사망 대신 페이지아웃
|
||
processors=8 # cfg의 num_workers: 6 감안
|
||
vmIdleTimeout=60000 # 연속 실행 시 VM 부팅 비용 회피
|
||
```
|
||
|
||
`pageReporting`은 뺐다 — 기본값이 `true`이고, 명시하면 WSL 2.7.12가 파싱 경고를 낸다.
|
||
|
||
적용 후 확인: `Mem 23GB / Swap 16GB / 8 cpus`, GPU 정상 인식, 경고 없음.
|
||
|
||
**운용 규칙**: GPU 만재 작업과 대용량 다운로드를 **동시에 돌리지 않는다.**
|
||
|
||
> `wsl --shutdown`은 WSL 배포판만 종료한다. Windows 네이티브 프로세스는 영향 없다.
|
||
|
||
---
|
||
|
||
## 4. 한국 데이터 적용 계획
|
||
|
||
### 두 갈래 경로
|
||
|
||
**A. 포인트 클라우드 경로 (권장, 스모크 테스트용)**
|
||
|
||
포인트 기반 방법(KPConv/PointNeXt/RandLA-Net)은 `xyz + rgb + label`만 필요. 텍스처 불필요.
|
||
|
||
```
|
||
한국 메시(OBJ+texture) → face 샘플링 → binary PLY (v:color, v:label)
|
||
```
|
||
|
||
- SUM으로 학습한 모델 → 한국 타일 zero-shot 추론
|
||
- 라벨 없으면 전부 `0`(unclassified)로 채워도 추론은 동작
|
||
|
||
**B. 메시 경로 (텍스처 기반 21클래스 풀 파이프라인)**
|
||
|
||
`h:texcoord` + `f:label` 포함 ASCII PLY 필요. 변환 공수 큼. 후순위.
|
||
|
||
### 확보된 실데이터 — 서산 명천 (2026-08)
|
||
|
||
`D:\MyProject_대용량샘플\02. 데이터\01. 서산 명천_08월`
|
||
|
||
```
|
||
├── 01. 드론원본이미지 JPG 8,120장 (132 GB) + PPK(.obs/.nav/.MRK)
|
||
│ 고고도 120m / 저고도 70m × 수직 / 경사, 1구역(미션1)·2구역(미션7)
|
||
├── 02. 본태모델 ★ OBJ 6개 (3.0 GB) + JPG 텍스처 144장 (908 MB)
|
||
├── 03. 지오이드모델 .tif 2.8 GB
|
||
├── 04. 카메라파라미터 .txt 6개
|
||
└── 05. 정사영상 .tif 25.8 GB + .tfw + .dwg
|
||
```
|
||
|
||
**본태모델 = ContextCapture 계열 OBJ, 블록 6개**
|
||
|
||
| 구역 | 블록 | OBJ 크기 |
|
||
|---|---|---|
|
||
| 구역1(미션1) | BlockYBY / BlockYYA / BlockYYX | 645 / 433 / 464 MB |
|
||
| 구역2(미션7) | BlockBXY / BlockYBA / BlockYBX | 497 / 355 / 634 MB |
|
||
|
||
**`metadata.xml` — 좌표계 (결정적)**
|
||
|
||
```xml
|
||
<SRS>EPSG:5186+9999</SRS>
|
||
<SRSOrigin>154113.37472199186,466558.57182090241,151.18628846539363</SRSOrigin> <!-- 구역1 -->
|
||
<SRSOrigin>155184.79128718161,469705.02542312664,149.57080945797654</SRSOrigin> <!-- 구역2 -->
|
||
```
|
||
|
||
→ **좌표가 이미 로컬 미터계 + 원점 오프셋.** SUM Parts 배포 관례와 동일.
|
||
재투영 작업 불필요. (단 원본 easting/northing이나 경위도를 그대로 넣으면 cfg의
|
||
반경·voxel 설정이 전부 미터 기준이라 조용히 망가진다.)
|
||
|
||
**메시 실측** (`scripts/inspect_obj.py`)
|
||
|
||
| | BlockYBA | BlockYYA | SUM Parts demo |
|
||
|---|---|---|---|
|
||
| vertex | 2,287,325 | 2,699,696 | — |
|
||
| face | 4,553,583 | 5,377,777 | — |
|
||
| UV | ✅ 3,121,900 | ✅ 3,886,962 | ✅ |
|
||
| vertex color | ✗ | ✗ | — |
|
||
| 머티리얼 | **17** | **21** | — |
|
||
| extent | 465×487×28 m | 439×404×46 m | 252×252×40 m |
|
||
| 면적 | 0.227 km² | 0.177 km² | — |
|
||
| 밀도 | 20.1 face/m² | 30.3 face/m² | — |
|
||
|
||
**적합 판정 근거**
|
||
- 텍스처 메시 + UV 완비 → SUM Parts와 동일 모달리티
|
||
- 좌표계 관례 일치
|
||
- 블록이 SUM 타일(252m)의 약 4배 면적 → 블록당 4타일, 6블록 = **약 24타일** 확보 가능
|
||
|
||
**주의 2가지**
|
||
1. **멀티머티리얼** — 블록당 텍스처 아틀라스 17~21장. `trimesh.load(force='mesh')`로
|
||
합치면 텍스처가 전부 날아가 결과가 회색이 된다. Scene으로 받아 머티리얼별로 샘플링해야 함.
|
||
2. **vertex color 없음** — 텍스처가 유일한 색상 소스. 폴백 경로가 없다.
|
||
|
||
### 왜 PLY로 변환하나 (OBJ/GLB 아니고)
|
||
|
||
포맷 선택이 아니라 **모델 요구사항**이다.
|
||
|
||
```python
|
||
# openpoints/dataset/sumv2_triangle/sumv2_triangle.py
|
||
self.data_list = glob.glob(os.path.join(data_root, split, "*.ply"))
|
||
plydata = PlyData.read(filename)
|
||
labels = plydata['vertex']['label']
|
||
```
|
||
|
||
- 데이터로더가 `*.ply`만 glob한다. OBJ/GLB는 스캔조차 안 함
|
||
- 네트워크 입력은 **포인트 클라우드**지 메시가 아니다
|
||
- OBJ/GLB에는 포인트별 `label` 스칼라를 담을 표준 필드가 없다.
|
||
SUM Parts 저자가 텍스처 메시조차 PLY로 배포하는 이유도 같다 (`f:label`, `v:label`)
|
||
|
||
원본 OBJ는 읽기만 하고 손대지 않는다. PLY는 모델 입력용 중간 산출물이다.
|
||
|
||
```
|
||
BlockYBA.obj (원본 불변) → 면적가중 샘플링 → tile.ply → 모델 → 예측
|
||
```
|
||
|
||
### 다른 한국 데이터 후보 (참고)
|
||
|
||
- **V-World 3D** (국토지리정보원) — 3D 건물 / 지형
|
||
- **서울 S-Map** — 3D 서울, 텍스처 메시
|
||
|
||
---
|
||
|
||
## 4-1. POC 결과 (2026-08-20) — 통과
|
||
|
||
범위를 의도적으로 **타일 1장**으로 제한. "우리 데이터가 이 파이프라인을 통과하는가"만 확인.
|
||
성능 측정 아님.
|
||
|
||
**입력**: 구역2(미션7)/BlockYBA, 중앙 252m bbox `(300, 720) ~ (552, 972)`
|
||
|
||
```
|
||
[17:10:12] loaded 17 geometry group(s), 4,553,583 faces total
|
||
[17:10:13] cropped surface area: 14,609 m^2 across 283,099 faces
|
||
geom 0..16 : 머티리얼별 면적 비례 샘플링
|
||
[17:10:19] extent: [137.84 178.07 19.48] m origin: [299.6 794.32 -67.58]
|
||
[17:10:19] wrote seosan_BlockYBA_tile0.ply (470,000 points, label=0, colour=sum)
|
||
```
|
||
|
||
**스키마 대조** (`scripts/check_ply.py`)
|
||
|
||
| | 변환 결과 | SUM demo | 판정 |
|
||
|---|---|---|---|
|
||
| properties | `x,y,z,r,g,b,label` | `x,y,z,nx,ny,nz,r,g,b,label,sp_id` | OK (법선·sp_id는 로더가 무시) |
|
||
| colour | float32 [0,1] | float32 [0,1] | ✅ 일치 |
|
||
| label dtype | int32 | int32 | ✅ |
|
||
| extent | 137.8 × 178.1 × 19.5 m | 252.2 × 252.2 × 39.7 m | 노선형 vs 정사각 타일 |
|
||
| density | 19.1 pts/m² | 7.4 pts/m² | 2.6× 조밀 |
|
||
|
||
**추론** (`scripts/poc_infer.sh`)
|
||
|
||
```
|
||
Successful Loading the ckpt from ..._ckpt_best.pth
|
||
length of test dataset: 1
|
||
Test on 0-th cloud [2]/[3]: 100%
|
||
test_oa nan, test_macc 0.00, test_miou 0.00
|
||
POC INFER DONE (exit=0)
|
||
```
|
||
|
||
`test_oa = nan`은 **정상**이다. 전 포인트 라벨이 0이고 `ignore_index: 0`이라 채점 대상이 없다.
|
||
|
||
**예측 산출물** (`scripts/check_pred.py`)
|
||
|
||
```
|
||
visualization/seosan_BlockYBA_tile0_pred.ply 13 MB
|
||
points : 470,000
|
||
properties : ['x','y','z','red','green','blue','label']
|
||
palette fit: exact
|
||
|
||
class points share
|
||
wall 470,000 100.00%
|
||
```
|
||
|
||
470,000 포인트 전부에 클래스 예측이 붙었다. 전부 `wall`인 건 1 epoch 모델(val_miou 0.21)이
|
||
사실상 단일 클래스만 뱉기 때문이며, 예상된 결과다.
|
||
|
||
> **결론: 배관은 뚫렸다.** OBJ → 샘플링 → PLY → 모델 → 포인트별 예측 → 시각화 PLY.
|
||
> 모델 품질에 대해서는 이 POC가 아무것도 말해주지 않는다.
|
||
|
||
### POC에서 드러난 것 3가지
|
||
|
||
**① 색상 스케일 — 조용히 망가지는 함정 ★**
|
||
|
||
첫 변환에서 `red,green,blue` uint8 [0,255]로 썼다. SUM 배포본은 `r,g,b` **float32 [0,1]**이다.
|
||
|
||
로더는 정규화하지 않는다:
|
||
```python
|
||
rgb = np.stack([...'r','g','b'...]).astype(np.float32)
|
||
if np.max(rgb) > 1:
|
||
rgb = rgb # ← no-op. 읽으면 정규화할 것 같지만 아무것도 안 한다
|
||
```
|
||
cfg의 활성 transform도 `[PointsToTensor, PointCloudScaling, PointCloudRotation,
|
||
PointCloudJitter]` 뿐 — `NumpyChromaticNormalize`는 주석 처리돼 있다.
|
||
|
||
→ **파일에 든 값이 그대로 네트워크에 들어간다.** 255배 큰 색상 특징을 주게 되고,
|
||
에러 없이 결과만 무의미해진다. `--colour-style sum`이 기본값인 이유.
|
||
|
||
**② 블록은 정사각형이 아니다 — 노선을 따라 뻗은 형태**
|
||
|
||
252×252m bbox로 잘랐는데 실제 표면적은 14,609 m² (bbox 면적 63,504 m²의 **23%**).
|
||
결과 extent도 137×178m. 도로 프로젝트라 노선을 따라 길게 뻗은 형태다. 도시 전역을 덮는 SUM Parts와 형상이 다르다.
|
||
|
||
→ 균등 격자로 타일링하면 빈 타일이 대량 발생한다. **노선 축을 따라 자르는 전략**이 필요하다.
|
||
앞서 "블록당 4타일 × 6블록 = 24타일" 추정은 낙관적이며, 실제로는 더 적다.
|
||
|
||
**③ 멀티머티리얼 처리 필수**
|
||
|
||
`trimesh.load(..., force='mesh')`로 합치면 머티리얼별 텍스처가 전부 유실되어 결과가
|
||
회색으로 나온다. Scene으로 받아 geometry(=머티리얼)별로 UV 샘플링해야 한다.
|
||
BlockYBA는 17개, BlockYYA는 21개 그룹.
|
||
|
||
### 다음 단계에서 필요한 것
|
||
|
||
POC는 배관만 확인했다. 의미 있는 결과를 내려면:
|
||
|
||
1. **제대로 학습된 체크포인트** — SUM Parts 전체 데이터(`mesh.zip`/`pcl.zip`)로 학습.
|
||
현재는 demo 타일 1장 1 epoch짜리뿐이다.
|
||
2. **노선 축 기반 타일링** — 빈 타일 회피
|
||
3. **한국 타일 라벨링** — zero-shot 성능을 측정하려면 GT가 필요하다. 최소 2~3타일.
|
||
SAM 기반 어노테이션 도구(`interactive_annotation/`)가 repo에 있다.
|
||
4. **클래스 정의 대조** — SUM 13클래스(terrain/roof/facade/chimney/dormer...)는 도시 건물 중심.
|
||
도로 프로젝트에서 필요한 건물/수목/차량/지면은 SUM 13클래스를 합치면 그대로 나온다 (§4-2).
|
||
|
||
### 예상 결과
|
||
|
||
SUM Parts는 헬싱키 항공 메시 기반. 한국 도시는 지붕 형태(평지붕 + 옥탑), 건물 밀도, 도로 표시가 다르다.
|
||
→ **zero-shot mIoU 큰 폭 하락 예상**. 그게 정상이며, 그 수치 자체가 저자에게 보낼 메일의 근거가 된다.
|
||
타일 2~3장 라벨링해 fine-tune 하면 크게 개선될 것.
|
||
|
||
---
|
||
|
||
## 5. 진행 순서
|
||
|
||
1. ~~환경 조사~~ ✅
|
||
2. ~~WSL2 환경 셋업~~ ✅ (miniconda, conda env py3.10, CUDA 11.8, torch 2.0.1+cu118)
|
||
3. ~~repo clone~~ ✅ `~/sum-parts`
|
||
4. ~~PointNeXt CUDA 확장 빌드~~ ✅ (3종 전부 import 확인)
|
||
5. ~~HF 게이트 수락~~ ✅ (브라우저 수동)
|
||
6. ~~`demo.zip` 다운로드 + 압축 해제 + 스키마 검증~~ ✅
|
||
7. ~~1 epoch 스모크 테스트~~ ✅ **파이프라인 생존 확인**
|
||
8. ~~한국 실데이터 조사~~ ✅ 서산 명천, ContextCapture OBJ 6블록
|
||
9. ~~한국 타일 → PLY 변환~~ ✅ BlockYBA 타일 1장, 470k 포인트
|
||
10. ~~POC 추론~~ ✅ **배관 관통 확인** (§4-1)
|
||
11. **제대로 된 학습** ← 다음. `pcl.zip` 전체 받아 실학습
|
||
12. 노선 축 기반 타일링 + 한국 타일 라벨링
|
||
13. Zero-shot 평가 + Mapple 시각화
|
||
14. 결과 정리 후 저자에게 메일
|
||
|
||
### 스모크 테스트 결과 (2026-08-20)
|
||
|
||
```
|
||
cfg=pointnet epochs=1 loop=4 bs=2
|
||
|
||
Train Epoch [1/1] Loss 3.474 Acc 0.08
|
||
Best ckpt @E1 val_oa 2.48 val_macc 8.33 val_miou 0.21
|
||
Test [0]/[1] cloud
|
||
Best ckpt @E1 test_oa 2.48 test_macc 8.33 test_miou 0.21
|
||
save results in log/.../ALo5iXF6AAHrWkAghKA8nv.csv
|
||
exit=0
|
||
```
|
||
|
||
**체인 전 구간 통과**: PLY 로드 → grid subsample → CUDA ops → forward/backward
|
||
→ 검증 → 체크포인트 저장 → 재로드 → 테스트 추론 → CSV 저장.
|
||
|
||
⚠️ **수치는 무의미하다.** 1 epoch, 타일 1장, train/val/test 모두 같은 타일.
|
||
파이프라인 생존만 확인한 것이고, 어떤 성능 주장에도 쓸 수 없다.
|
||
|
||
### 검증된 스택
|
||
|
||
```
|
||
python 3.10
|
||
nvcc 11.8.89
|
||
torch 2.0.1+cu118 cuda available: True (RTX 3060)
|
||
numpy 1.26.4
|
||
ninja 1.11.1.1 (1.12+ 금지)
|
||
setuptools 69.5.1 (74+ 금지)
|
||
torch-scatter 2.1.2+pt20cu118
|
||
|
||
pointnet2_batch_cuda : OK
|
||
pointops_cuda : OK
|
||
grid_subsampling : OK
|
||
plyfile / torch_scatter : OK
|
||
```
|
||
|
||
### 스크립트
|
||
|
||
| 파일 | 역할 |
|
||
|---|---|
|
||
| [scripts/setup_env.sh](../scripts/setup_env.sh) | conda env + CUDA + torch |
|
||
| [scripts/setup_pointnext.sh](../scripts/setup_pointnext.sh) | python 의존성 → build_ext.sh 호출 |
|
||
| [scripts/build_ext.sh](../scripts/build_ext.sh) | CUDA 확장 빌드만 (재실행 가능, `FORCE=1`로 전체 재빌드) |
|
||
| [scripts/repair_env.sh](../scripts/repair_env.sh) | WSL 크래시로 0바이트가 된 패키지 재설치 |
|
||
| [scripts/patch_numpy_aliases.sh](../scripts/patch_numpy_aliases.sh) | 제거된 numpy 별칭 / collections ABC 소스 패치 |
|
||
| [scripts/verify_env.py](../scripts/verify_env.py) | 컴파일 확장 + import 체인 일괄 점검 |
|
||
| [scripts/download_data.sh](../scripts/download_data.sh) | HF 데이터 다운로드 + 압축 해제 |
|
||
| [scripts/prepare_demo_split.sh](../scripts/prepare_demo_split.sh) | demo 타일을 train/val/test로 배치 |
|
||
| [scripts/link_data.sh](../scripts/link_data.sh) | `PointNeXt_bundle/data` 심볼릭 링크 |
|
||
| [scripts/smoke_train.sh](../scripts/smoke_train.sh) | 1 epoch 파이프라인 생존 테스트 |
|
||
| [scripts/inspect_obj.py](../scripts/inspect_obj.py) | 대용량 OBJ 스트리밍 통계 (vertex/face/UV/bbox/머티리얼) |
|
||
| [scripts/mesh_to_ply.py](../scripts/mesh_to_ply.py) | 멀티머티리얼 텍스처 메시 → SUM 스키마 PLY (bbox 크롭 지원) |
|
||
| [scripts/check_ply.py](../scripts/check_ply.py) | PLY를 SUM 스키마와 대조 검증 |
|
||
| [scripts/poc_korea.sh](../scripts/poc_korea.sh) | 서산 명천 타일 1장 변환 + 검증 |
|
||
| [scripts/poc_infer.sh](../scripts/poc_infer.sh) | 변환된 타일로 추론 |
|
||
| [scripts/check_pred.py](../scripts/check_pred.py) | 예측 PLY의 클래스 분포 복원 (팔레트 역매핑) |
|
||
| [scripts/poc_check_pred.sh](../scripts/poc_check_pred.sh) | 최신 예측 PLY 자동 탐색 후 검사 |
|
||
|
||
### 실행 순서 (처음부터 재현 시)
|
||
|
||
```bash
|
||
bash scripts/setup_env.sh # conda env + CUDA 11.8 + torch 2.0.1
|
||
bash scripts/setup_pointnext.sh # 의존성 + CUDA 확장 빌드
|
||
bash scripts/patch_numpy_aliases.sh # 소스 현대화 패치
|
||
bash scripts/download_data.sh # HF 게이트 수락 후
|
||
bash scripts/prepare_demo_split.sh
|
||
bash scripts/link_data.sh
|
||
python scripts/verify_env.py # 전부 OK 확인
|
||
bash scripts/smoke_train.sh
|
||
```
|
||
|
||
WSL이 중간에 죽으면 `bash scripts/repair_env.sh` 먼저 돌리고 재개.
|
||
|
||
---
|
||
|
||
## 6. 메모
|
||
|
||
- VSCode에서 WSL 전환: 좌하단 `><` 버튼 → "Connect to WSL"
|
||
- 다만 Windows 셸에서 `wsl -d Ubuntu-22.04 -- bash -lc "<cmd>"` 로 직접 실행도 가능 (전환 불필요)
|
||
- `Downloads`에 `교각.hmeg`, `교량.hmeg`, `sss.obj` 등 자체 3D 데이터 존재 → 한국 샘플 후보로 검토 가치 있음
|