A paired bootstrap over the 149 unseen gold frames (2026-10-02) showed that a difference between two models carries about +-0.023 of noise in F1, +-0.018 in the mask score and +-0.10 in crystal F1, while the gate's tolerance was 0.005. - tools/gate_stats.py: per-frame counts and a paired bootstrap (pure Python). - train_run.py: when the baseline's gold predictions exist, the gate requires the candidate to beat it on F1 or the mask score in 90 % of the draws and not to lose the other in more than 90 %; the point rule is the fallback and is always reported (pass_point_rule). - tools/compare_unseen.py replaces the Spark's ~/fair_gate_all.py: all models on the frames none trained on, on the frozen core (data/gold/unseen_core.json, 149 frames) and on all unseen frames once the gold set grows, with the interval and win share against the active model. - measure_weights.py keeps a version's gate verdict when it re-measures it. - tools/spark_cycle.sh: gold growth, export, re-measure, train, gate, compare, push, with what cycles 3 and 4 taught built in. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
85 lines
5.7 KiB
Bash
Executable File
85 lines
5.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# One training cycle on the Spark, start to finish, unattended.
|
|
#
|
|
# nohup bash ~/AareLC_train/tools/spark_cycle.sh v5 > ~/cycle_v5.log 2>&1 &
|
|
# bash ~/AareLC_train/tools/spark_cycle.sh v5 --quick # also the fast settings-A run first
|
|
#
|
|
# Steps: wait for a running pipeline batch; grow the gold set by its rule (studio `gold`
|
|
# stage: gold batches, share, floors); export the reviewed pool as dataset <tag> into this
|
|
# repo; stop vLLM; re-measure every registry model on the grown gold set so the gate and
|
|
# the comparison stand on the same frames; train the promoted recipe from the base weights;
|
|
# gate (paired bootstrap against the active model), promote on a clear win; compare every
|
|
# model on the unseen frames; push; start vLLM again.
|
|
#
|
|
# What earlier cycles taught, built in:
|
|
# - a failed gate is a verdict, not a crash: train_run.py is called once per run;
|
|
# - a full-resolution-mask training does not fit next to vLLM in 121 GB: vLLM is stopped
|
|
# while it runs and started again on exit, whatever happens;
|
|
# - torch's native JIT wants Python headers the Spark lacks: switched off, headers on CPATH;
|
|
# - a grown gold set invalidates stored predictions: everything is re-measured first.
|
|
set -eo pipefail
|
|
TAG="${1:?usage: spark_cycle.sh <dataset tag, e.g. v5> [--quick]}"; QUICK="${2:-}"
|
|
export PATH=$HOME/bin:$PATH
|
|
export CPATH=$HOME/local/root/usr/include:$HOME/local/root/usr/include/python3.12
|
|
export TORCH_DISABLE_NATIVE_JIT=1
|
|
PY=$HOME/AareLC_studio/.venv/bin/python
|
|
STUDIO=$HOME/AareLC_studio; TRAIN=$HOME/AareLC_train; CFG=pipeline.prod.json
|
|
say() { echo "== $(date +%H:%M) $*"; }
|
|
|
|
while pgrep -f "[p]ipeline.run auto" >/dev/null; do sleep 30; done # the reviewers' batch goes first
|
|
cd "$STUDIO"; set -a; . ~/.aaredb_env; set +a
|
|
say "gold: growing by rule"
|
|
$PY -m src.pipeline.run gold --config $CFG 2>&1 | grep -E "^gold" | cut -c1-220 || true
|
|
say "export + import into AareLC_train (dataset $TAG)"
|
|
$PY -m src.pipeline.run export --config $CFG --dest "data/pipeline_prod/dataset_$(date +%F)_$TAG" --train-repo "$TRAIN" --train-tag "$TAG" 2>&1 | grep -E "^export|dataset |committed|tagged|gold" | cut -c1-220 || true
|
|
cd "$TRAIN"
|
|
DS=$(git tag -l "dataset/*_$TAG" | tail -1); [ -n "$DS" ] || { echo "no dataset tag for $TAG"; exit 1; }
|
|
DSDIR="data/datasets/${DS#dataset/}"
|
|
say "dataset $DS: $(ls $DSDIR/images/train | wc -l) train / $(ls $DSDIR/images/val | wc -l) val; gold $(ls data/gold/labels | wc -l) frames"
|
|
|
|
vllm_back() {
|
|
for i in 1 2 3; do
|
|
docker start vllm >> ~/vllm_restart.log 2>&1 || true; sleep 60
|
|
if docker ps --format "{{.Names}}" | grep -q "^vllm$"; then echo "$(date) vllm up (try $i)" >> ~/vllm_restart.log; return 0; fi
|
|
done
|
|
echo "$(date) vllm did NOT come back" >> ~/vllm_restart.log
|
|
}
|
|
trap vllm_back EXIT
|
|
docker stop vllm >/dev/null 2>&1 || true
|
|
say "vLLM stopped, $(free -g | awk 'NR==2{print $7}') GB available"
|
|
|
|
say "re-measuring every registry model on the $(ls data/gold/labels | wc -l) gold frames"
|
|
for d in models/registry/*/; do
|
|
v=$(basename "$d"); [ -f "$d/weights.pt" ] || continue
|
|
$PY tools/measure_weights.py "$d/weights.pt" --version "$v" 2>&1 | tail -n 1
|
|
done
|
|
BEAMLINE_PT=$STUDIO/models/best_yolo26n-seg-overlap-false_2026-04-12.pt # the model before the loop lives in the studio
|
|
[ -f "$BEAMLINE_PT" ] && $PY tools/measure_weights.py "$BEAMLINE_PT" --version yolo26n-seg-overlap-false-2026-04-12 2>&1 | tail -n 1
|
|
git add models/registry data/gold && git commit -q -m "gold grown to $(ls data/gold/labels | wc -l) frames; every registry model re-measured on it" || true
|
|
|
|
run() { # tag suffix, note, extra train_run.py args...
|
|
local suffix="$1" note="$2"; shift 2
|
|
local id; id=$(python3 tools/ledger.py new --dataset "$DS" --tag "$TAG-$suffix" --base models/base/yolo26n-seg.pt --note "$note")
|
|
git add runs && git commit -q -m "run $id: exported on $DS" || true
|
|
say "training $id"
|
|
set +e
|
|
$PY tools/train_run.py "$id" --device 0 --workers 8 "$@" > "runs/$id/train.log" 2>&1 < /dev/null
|
|
local rc=$? # 0 = gate passed (or no baseline), 2 = gate failed: both are verdicts
|
|
set -e
|
|
[ $rc -eq 0 ] || [ $rc -eq 2 ] || echo "!! $id ended with exit code $rc: $(grep -aiE "error|out of memory" runs/$id/train.log | tail -1 | cut -c1-160)"
|
|
local v; v=$(python3 -c "import json;print(json.load(open('runs/$id/run.json')).get('version') or '')")
|
|
if [ -n "$v" ] && [ -d "runs/$id/gold_predictions" ]; then mkdir -p "runs/_measure_$v" && cp -r "runs/$id/gold_predictions" "runs/_measure_$v/"; fi
|
|
python3 -c "import json; m=json.load(open('runs/$id/metrics.json')); g=m.get('gate') or {}; b=g.get('bootstrap') or {}; print(' gate:', g.get('pass'), '| rule:', g.get('rule'), '| F1', g.get('f1_delta'), 'mask', round(g.get('mask_score',0)-g.get('mask_score_baseline',0),4), '| win F1', (b.get('f1') or {}).get('win'), 'win mask', (b.get('mask_score') or {}).get('win'))" 2>/dev/null || true
|
|
python3 tools/ledger.py render >/dev/null 2>&1 || true
|
|
git add runs models && git commit -q -m "run $id: $(python3 -c "import json;print(json.load(open('runs/$id/run.json'))['status'])")" || true
|
|
}
|
|
[ "$QUICK" = "--quick" ] && run recipeA "settings A (fliplr 0.5, mask_ratio 4) on dataset $TAG: quick signal, gate only, Spark" --no-promote --set fliplr=0.5 mask_ratio=4
|
|
run noflip-mask1 "promoted recipe (fliplr 0, mask_ratio 1) on dataset $TAG: candidate for the beamlines, Spark" --set fliplr=0 mask_ratio=1
|
|
|
|
say "every model on the unseen gold frames"
|
|
$PY tools/compare_unseen.py 2>&1 | cut -c1-200 || true
|
|
git add runs && git commit -q -m "unseen-frame comparison after cycle $TAG" || true
|
|
say "active model now: $(python3 -c 'import json;print(json.load(open("models/active/production.json"))["version"])')"
|
|
bash ~/sync_push.sh 2>&1 | tail -2
|
|
say "cycle $TAG done"
|