Files
nbrightandClaude Opus 5 609d9a6972 Add SUM Parts reproduction and Seosan Myeongcheon application pipeline
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>
2026-08-21 10:29:25 +09:00

114 lines
4.6 KiB
Bash

#!/usr/bin/env bash
# SUM Parts - emit training progress as discrete events, exit when the run ends
#
# Designed to be selective: a 100-epoch run writes ~100 epoch lines, which would
# be 100 notifications. So this emits every Nth epoch, plus anything that would
# change what to do next -- watchdog retries, tracebacks, OOM, GPU trouble, and
# the final outcome.
#
# Silence must not be mistakable for success, so the terminal check keys off the
# watchdog process disappearing, not off a success marker appearing.
set -uo pipefail
RUNROOT="$HOME/sum-parts/runs"
EVERY="${EVERY:-10}" # emit one progress line per this many epochs
POLL="${POLL:-120}"
TOTAL="${TOTAL:-100}" # target epoch count, for the "n/TOTAL" display
WORKDIR="${1:-}"
if [ -z "$WORKDIR" ]; then
WORKDIR=$(cat "$RUNROOT/.latest" 2>/dev/null || true)
fi
if [ -z "$WORKDIR" ] || [ ! -d "$WORKDIR" ]; then
echo "MONITOR-ERROR: no run directory (looked at $RUNROOT/.latest)"
exit 1
fi
TRAINLOG="$WORKDIR/train.log"
WDLOG="$WORKDIR/watchdog.log"
echo "MONITOR-START $(basename "$WORKDIR") every=${EVERY}ep poll=${POLL}s"
last_epoch_reported=0
wd_lines=0
err_lines=0
while true; do
# --- progress ------------------------------------------------------
if [ -f "$TRAINLOG" ]; then
line=$(grep -aE 'Epoch [0-9]+ LR' "$TRAINLOG" 2>/dev/null | tail -1)
if [ -n "$line" ]; then
# Report the epoch number the trainer itself prints. Counting log
# lines is wrong after a resume: the new log starts mid-run, so a
# run continuing at epoch 31 would be announced as epoch 11.
ep=$(echo "$line" | grep -oE 'Epoch [0-9]+' | grep -oE '[0-9]+')
if [ -n "$ep" ] && [ $((ep / EVERY)) -gt $((last_epoch_reported / EVERY)) ]; then
best=$(grep -aE 'Find a better ckpt' "$TRAINLOG" | tail -1 \
| grep -oE 'val_miou [0-9.]+' | tail -1)
echo "EPOCH $ep/$TOTAL | ${line#*] } | best: ${best:-none}"
last_epoch_reported=$ep
fi
fi
fi
# --- watchdog events (retries, resumes, give-ups) ------------------
if [ -f "$WDLOG" ]; then
c=$(wc -l < "$WDLOG")
if [ "$c" -gt "$wd_lines" ]; then
tail -n +$((wd_lines + 1)) "$WDLOG" \
| grep -E 'attempt|exited rc=|giving up|exhausted|finished cleanly|FATAL' || true
wd_lines=$c
fi
fi
# --- speed regression -----------------------------------------------
# The first run silently fell from 0.29 s/it to 3.1 s/it when the caching
# allocator pool overflowed VRAM into host RAM. No error, no OOM -- just a
# 24-hour ETA. Watch the rate, not only the exit code.
if [ -f "$TRAINLOG" ]; then
r=$(grep -aoE '[0-9.]+(s/it|it/s)' "$TRAINLOG" | tail -1)
if [ -n "$r" ]; then
sec=$(awk -v x="$r" 'BEGIN {
if (x ~ /it\/s$/) { sub(/it\/s$/,"",x); printf "%.3f", (x>0 ? 1.0/x : 0) }
else { sub(/s\/it$/,"",x); printf "%.3f", x }
}')
slow=$(awk -v v="$sec" 'BEGIN { print (v > 1.5) ? 1 : 0 }')
if [ "$slow" = "1" ] && [ "${slow_reported:-0}" = "0" ]; then
mem=$(nvidia-smi --query-gpu=memory.used,power.draw --format=csv,noheader 2>/dev/null)
echo "SLOWDOWN: ${sec} s/it (healthy is ~0.29) | GPU $mem"
slow_reported=1
elif [ "$slow" = "0" ]; then
slow_reported=0
fi
fi
fi
# --- failure signatures in the trainer itself ----------------------
if [ -f "$TRAINLOG" ]; then
e=$(grep -cE 'Traceback|CUDA error|out of memory|OutOfMemory|Killed|AssertionError' \
"$TRAINLOG" 2>/dev/null || echo 0)
if [ "$e" -gt "$err_lines" ]; then
echo "ERROR-SIGNAL x$((e - err_lines)):"
grep -E 'Traceback|CUDA error|out of memory|OutOfMemory|Killed|AssertionError' \
"$TRAINLOG" | tail -3
err_lines=$e
fi
fi
# --- terminal check: watchdog gone means the run is over, either way
if ! pgrep -f train_watchdog.sh > /dev/null 2>&1; then
sleep 5
state=$(grep -E '^state' "$WORKDIR/status.txt" 2>/dev/null | cut -d: -f2- | xargs)
final=$(grep -aE 'Epoch [0-9]+ LR' "$TRAINLOG" 2>/dev/null | tail -1 \
| grep -oE 'Epoch [0-9]+' | grep -oE '[0-9]+')
echo "MONITOR-END state='${state:-unknown}' last_epoch=${final:-0}/$TOTAL"
if [ -f "$TRAINLOG" ]; then
echo "--- tail ---"
tail -8 "$TRAINLOG"
fi
exit 0
fi
sleep "$POLL"
done