File size: 8,482 Bytes
b9301f1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | #!/usr/bin/env bash
# Resumable driver for the field-leak branch (docs/FIELD_SATURATION.md).
#
# Continues the fieldsat branch in 8k chunks with the exact live recipe from
# scripts/train_until_coherent.sh + --last-atom-dropout 0.5 + --field-leak 0.3.
# Each chunk saves checkpoints/byte_tick_fieldsat/atom_native_step_<END>_d32_ms1M_lrd_b4828_fieldsat.pt,
# so the run can be resumed or switched at any chunk boundary. Re-running the
# script resumes from the newest existing branch checkpoint in [START_STEP, END_TARGET]
# (an interrupted chunk is simply re-run) and measures any checkpoint that has no
# row in docs/artifacts/field_saturation/branch_progress.tsv yet.
#
# Env:
# THREADS=1 OMP/MKL threads for every python step (8 only when the live loop is stopped)
# YIELD=1 1 = SIGSTOP this whole process group while the live b4828 chunk is in its
# multi-threaded phase (docs/artifacts/field_saturation/yield_to_live.py);
# requires launching via setsid (own process group). 0 = never pause.
# START_STEP=4876000 END_TARGET=4916000 CHUNK=8000
# LR=1.5e-5 SLR=3.6e-5 LEAK=0.3 LADROP=0.5
#
# Launch (background, own process group):
# setsid nohup env THREADS=1 YIELD=1 bash scripts/train_fieldsat_branch.sh \
# >> logs/fieldsat_branch_master.log 2>&1 < /dev/null &
# Stop cleanly (group may be SIGSTOPped, so CONT after TERM):
# PG=$(cat logs/fieldsat/branch.pgid); kill -TERM -- -$PG; kill -CONT -- -$PG
set -u
cd /workspace/exports/atom-ai
THREADS=${THREADS:-1}
YIELD=${YIELD:-1}
START_STEP=${START_STEP:-4876000}
END_TARGET=${END_TARGET:-4916000}
CHUNK=${CHUNK:-8000}
LR=${LR:-1.5e-5}
SLR=${SLR:-3.6e-5}
LEAK=${LEAK:-0.3}
LADROP=${LADROP:-0.5}
BASE=496000
OUT=checkpoints/byte_tick_fieldsat
SUF=d32_ms1M_lrd_b4828_fieldsat
A=docs/artifacts/field_saturation
AB=$A/branch
TSV=$A/branch_progress.tsv
LOGD=logs/fieldsat/branch
RING=$OUT/val_ring_4876000.pt # fixed 256-transition stream-ring val (same window as the probe table)
PY=.venv/bin/python
export PYTHONPATH=. OMP_NUM_THREADS=$THREADS MKL_NUM_THREADS=$THREADS OPENBLAS_NUM_THREADS=$THREADS
mkdir -p "$OUT" "$AB" "$LOGD" logs/fieldsat
ts() { date -Is; }
exec 9>logs/fieldsat/branch.lock
if ! flock -n 9; then echo "$(ts) another train_fieldsat_branch.sh holds logs/fieldsat/branch.lock; exiting"; exit 1; fi
PG=$(ps -o pgid= -p $$ | tr -d ' ')
echo "$PG" > logs/fieldsat/branch.pgid
echo "=== $(ts) fieldsat branch driver pid=$$ pgid=$PG THREADS=$THREADS YIELD=$YIELD START_STEP=$START_STEP END_TARGET=$END_TARGET CHUNK=$CHUNK LR=$LR SLR=$SLR LEAK=$LEAK LADROP=$LADROP ==="
if [ "$YIELD" = "1" ]; then
if [ "$PG" != "$$" ]; then
echo "$(ts) YIELD=1 needs its own process group: launch with setsid (see header). exiting"; exit 1
fi
# Helper in its own session (so it never stops itself); pauses/resumes only our pgid.
setsid "$PY" "$A/yield_to_live.py" logs/fieldsat/branch.pgid 9>&- \
>> logs/fieldsat/branch_yield.err 2>&1 < /dev/null &
echo "$(ts) yield helper pid=$! watching pgid=$PG"
fi
latest() {
ls "$OUT"/atom_native_step_*_${SUF}.pt 2>/dev/null \
| sed -n 's/.*step_\([0-9]*\)_.*/\1/p' | sort -n \
| awk -v lo="$START_STEP" -v hi="$END_TARGET" '$1>=lo && $1<=hi' | tail -1
}
has_row() { [ -f "$TSV" ] && awk -F'\t' -v s="$1" 'NR>1 && $1==s {f=1} END {exit !f}' "$TSV"; }
measure() {
local END=$1 CK="$OUT/atom_native_step_$1_${SUF}.pt" ML="$LOGD/measure_$1.log"
echo "$(ts) measure $END start"
"$PY" -u tools/eval_last_atom_ablation.py --end-steps 4876000 --ring-cache "$RING" \
--checkpoints "$CK" --out "$AB/val_ablation_$END.json" >> "$ML" 2>&1
"$PY" -u tools/diag_field_saturation.py --checkpoints "b$END=$CK" --episode-ticks 8000 \
--prefixes 3000 --probe --out "$AB/diag_$END.json" >> "$ML" 2>&1
local G="$AB/gen_coherent_$END.txt"; : > "$G"
for p in "Bonjour, comment ça va ?" "Qui es-tu ?" "Il était une fois"; do
RESP=$("$PY" tools/chat_atom_native.py --checkpoint "$CK" --prompt "$p" --no-role-prime \
--max-packets 96 --max-length 100 --temperature 0.8 --top-k 12 2>/dev/null | sed -n 's/^Response: //p' | head -1)
echo "PROMPT: $p" >> "$G"; echo "RESP: $RESP" >> "$G"
done
"$PY" - "$END" "$CK" "$TSV" "$AB" "$OUT/run_metrics_${END}_${SUF}.json" <<'PYEOF'
import json, os, sys
end, ck, tsv, ab, rm_path = sys.argv[1:6]
def load(p):
try:
return json.load(open(p))
except Exception:
return {}
rm = load(rm_path); tr = rm.get("train", {}); va = rm.get("validation", {})
ev = load(f"{ab}/val_ablation_{end}.json")
ring = next(iter(ev.get("rings", {}).values()), {}).get("checkpoints", {}).get(ck, {})
dg = (load(f"{ab}/diag_{end}.json").get("rows") or [{}])[0]
fork = (dg.get("shared_prefix_forks") or {}).get("3000", {})
chats = []
try:
for line in open(f"{ab}/gen_coherent_{end}.txt", encoding="utf-8"):
if line.startswith("RESP: "):
chats.append(" ".join(line[6:].split())[:80].replace("\t", " "))
except FileNotFoundError:
pass
def f(x, n=4):
return "" if x is None or x == "" else (f"{x:.{n}f}" if isinstance(x, (int, float)) else str(x))
cols = [
("end_step", end),
("chunk_val_loss_own_ring", f(va.get("loss"))),
("val_loss_fixed_ring", f(ring.get("normal", {}).get("loss"))),
("val_byte_ce_fixed_ring", f(ring.get("normal", {}).get("byte_loss"))),
("field_alone_byte_ce", f(ring.get("la_zeroed", {}).get("byte_loss"))),
("a_hat_linear_next_byte_ce", f((dg.get("probe_next_byte_from_a_hat") or {}).get("test_ce"), 3)),
("train_frac_at_cap", f(tr.get("field_limited_fraction"), 3)),
("diag_frac_at_cap", f(dg.get("frac_at_cap"), 3)),
("fork_cos_alpha", f(fork.get("mean_pairwise_cos_alpha"), 3)),
("fork_cos_a_hat", f(fork.get("mean_pairwise_cos_a_hat"), 3)),
("train_steps_per_s", f(tr.get("transitions_per_second"), 1)),
("field_leak", f((rm.get("config") or {}).get("field_leak"), 2)),
("chat_bonjour", chats[0] if len(chats) > 0 else ""),
("chat_qui", chats[1] if len(chats) > 1 else ""),
("chat_il_etait", chats[2] if len(chats) > 2 else ""),
]
new = not os.path.exists(tsv)
with open(tsv, "a", encoding="utf-8") as fh:
if new:
fh.write("\t".join(c for c, _ in cols) + "\n")
fh.write("\t".join(v for _, v in cols) + "\n")
print("TSV:", "\t".join(v for _, v in cols))
PYEOF
echo "$(ts) measure $END done"
}
measure_missing() {
for ck in $(ls "$OUT"/atom_native_step_*_${SUF}.pt 2>/dev/null | sed -n 's/.*step_\([0-9]*\)_.*/\1/p' | sort -n); do
if [ "$ck" -ge "$START_STEP" ] && [ "$ck" -le "$END_TARGET" ] && ! has_row "$ck"; then measure "$ck"; fi
done
}
CUR=$(latest)
if [ -z "$CUR" ]; then echo "$(ts) no branch checkpoint in [$START_STEP,$END_TARGET] under $OUT; exiting"; exit 1; fi
while [ "$CUR" -lt "$END_TARGET" ]; do
END=$((CUR + CHUNK)); [ "$END" -gt "$END_TARGET" ] && END=$END_TARGET
STEPS=$((END - CUR)); SKIP=$((CUR - BASE)); [ "$SKIP" -lt 0 ] && SKIP=0
RESUME="$OUT/atom_native_step_${CUR}_${SUF}.pt"
NAME="atom_native_step_${END}_${SUF}.pt"
LOG="$LOGD/train_${CUR}_${END}_fieldsat.log"
if [ ! -f "$OUT/$NAME" ]; then
echo "$(ts) chunk $CUR->$END skip=$SKIP resume=$RESUME log=$LOG"
T0=$(date +%s)
"$PY" -u tools/run_atom_native.py \
--stream --data-glob 'data/corpus_fr_medium*.txt' --loop-shards --chunk-bytes 65536 \
--stream-skip-packets "$SKIP" --resume "$RESUME" --output-dir "$OUT" \
--checkpoint-name "$NAME" --steps "$STEPS" \
--d-model 32 --n-modes 32 --n-atoms-max 64 --max-span-bytes 1 \
--field-obligatory-hard --no-enable-merge --no-payload-copy --last-atom-readout \
--efference-every 10 --learning-rate "$LR" --surface-learning-rate "$SLR" --seed 20260913 \
--atom-flush-every 64 --episode-length 8000 --episode-reset --slow-every 1 \
--field-loss-weight 0.05 --field-contrast-weight 0 --allow-parallel-train \
--last-atom-dropout "$LADROP" --field-leak "$LEAK" \
> "$LOG" 2>&1
RC=$?
if [ "$RC" -ne 0 ] || [ ! -f "$OUT/$NAME" ]; then
echo "$(ts) chunk $CUR->$END FAILED rc=$RC (see $LOG)"; exit 1
fi
cp "$OUT/run_metrics.json" "$OUT/run_metrics_${END}_${SUF}.json"
cp "$OUT/run_metrics.json" "$AB/run_metrics_${END}_fieldsat.json"
echo "$(ts) chunk $CUR->$END saved $NAME wall=$(( $(date +%s) - T0 ))s"
else
echo "$(ts) skip exists $NAME"
fi
measure_missing
CUR=$END
done
measure_missing
echo "$(ts) DONE fieldsat branch at $CUR (END_TARGET=$END_TARGET)"
|