File size: 7,635 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 | #!/usr/bin/env bash
# Controlled A/B driver for the slow-mode readout (docs/SLOW_MODE_READOUT.md), derived
# from scripts/train_leakspec_arm.sh (same recipe, seed, stream skip, chunking).
# Extra per-chunk measurements: 2048-transition val (eval_windows.py) and the slow-mode
# probes (probe_leak_spectrum.py --slow-probes). Artifacts: docs/artifacts/slow_mode_readout.
#
# One ARM = the exact fieldsat recipe (scripts/train_fieldsat_branch.sh: live recipe +
# --last-atom-dropout 0.5 + LR 1.5e-5 / SLR 3.6e-5, seed 20260913, stream-skip = START-496000)
# resumed from START_CKPT, in CHUNK-step chunks up to END_TARGET, with its own output dir and
# checkpoint suffix. The ONLY difference between arms is LEAK_FLAGS:
# ARM=A LEAK_FLAGS="--field-leak 0.3" (control)
# ARM=B LEAK_FLAGS="--field-leak-spectrum ... --slow-mode-readout --slow-memory-loss W ..."
# Resumable: restarts from the newest ckpt of this arm (<= END_TARGET), else START_CKPT.
# After each chunk: val normal / field-alone on the fixed 4876k ring, probe_leak_spectrum
# (cap fraction, a_hat linear next-byte CE, prompt-memory @16/32/64), coherence chat
# (3 prompts, standard params), seeded chat (seeds 1-3 x T 0.8/0.6/1.0) + prompt-dependence;
# output-dependence (JS divergence of next-byte distributions across prompts at the reply
# start); one row per chunk in $TSV (docs/artifacts/field_leak_spectrum/tsv_row.py).
#
# Env: ARM OUT SUF LEAK_FLAGS TSV [START_CKPT START_STEP END_TARGET CHUNK THREADS MEASURE_THREADS LR SLR LADROP]
# THREADS=8 OMP/MKL threads for training
# MEASURE_THREADS=1 threads for the (sequential, tiny-op) measurement tools; 8-thread OMP
# measurement next to an 8-thread training run thrashes (seen: chat_seeds
# 35 CPU-min, training 0.3 steps/s), and thread count never touches training.
set -u
cd /workspace/exports/atom-ai
ARM=${ARM:?ARM}
OUT=${OUT:?OUT}
SUF=${SUF:?SUF}
LEAK_FLAGS=${LEAK_FLAGS:?LEAK_FLAGS}
TSV=${TSV:?TSV}
START_CKPT=${START_CKPT:-checkpoints/byte_tick_fieldsat/atom_native_step_4996000_d32_ms1M_lrd_b4828_fieldsat.pt}
START_STEP=${START_STEP:-4996000}
END_TARGET=${END_TARGET:-5004000}
CHUNK=${CHUNK:-8000}
THREADS=${THREADS:-8}
MEASURE_THREADS=${MEASURE_THREADS:-1}
LR=${LR:-1.5e-5}
SLR=${SLR:-3.6e-5}
LADROP=${LADROP:-0.5}
BASE=496000
A=docs/artifacts/slow_mode_readout
AB=$A/arm_$ARM
LOGD=logs/slowread/arm_$ARM
RING=checkpoints/byte_tick_fieldsat/val_ring_4876000.pt
PY=.venv/bin/python
export PYTHONPATH=. OMP_NUM_THREADS=$THREADS MKL_NUM_THREADS=$THREADS OPENBLAS_NUM_THREADS=$THREADS
mkdir -p "$OUT" "$AB" "$LOGD"
ts() { date -Is; }
exec 9>"$LOGD/lock"
if ! flock -n 9; then echo "$(ts) arm $ARM already running; exiting"; exit 1; fi
echo "=== $(ts) slowread arm=$ARM OUT=$OUT SUF=$SUF LEAK_FLAGS=[$LEAK_FLAGS] START=$START_STEP END=$END_TARGET THREADS=$THREADS ==="
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" -v a="$ARM" 'NR>1 && $1==a && $2==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 $ARM $END start (threads=$MEASURE_THREADS)"
local OMP_NUM_THREADS=$MEASURE_THREADS MKL_NUM_THREADS=$MEASURE_THREADS OPENBLAS_NUM_THREADS=$MEASURE_THREADS
export OMP_NUM_THREADS MKL_NUM_THREADS OPENBLAS_NUM_THREADS
"$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 docs/artifacts/field_leak_spectrum/eval_windows.py "$AB/eval_windows_$END.json" "$CK" >> "$ML" 2>&1
"$PY" -u tools/probe_leak_spectrum.py --checkpoint "$CK" --variants ckpt --slow-probes \
--out "$AB/probe_$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" tools/chat_seeds_probe.py --checkpoint "$CK" --seeds 1 2 3 --temps 0.8 0.6 1.0 \
--out "$AB/chat_seeds_$END.json" > "$AB/chat_seeds_$END.txt" 2>&1
"$PY" -u tools/probe_leak_spectrum.py --checkpoint "$CK" --variants ckpt --skip-episode --skip-memory \
--output-dependence --out "$AB/outdep_$END.json" >> "$ML" 2>&1
"$PY" "$A/tsv_row.py" "$ARM" "$END" "$CK" "$TSV" "$AB" "$OUT/run_metrics_${END}_${SUF}.json"
echo "$(ts) measure $ARM $END done"
}
recover_metrics() {
# ckpt produced by a run whose driver shell was stopped: recover its metrics files
# (run_metrics.json in $OUT belongs to this ckpt iff its checkpoint path matches).
local END=$1 NAME="atom_native_step_$1_${SUF}.pt"
if [ ! -f "$AB/run_metrics_${END}.json" ] && grep -q "$NAME" "$OUT/run_metrics.json" 2>/dev/null; then
cp "$OUT/run_metrics.json" "$OUT/run_metrics_${END}_${SUF}.json"
cp "$OUT/run_metrics.json" "$AB/run_metrics_${END}.json"
cp "$OUT/loss_trajectory.json" "$AB/loss_trajectory_${END}.json" 2>/dev/null || true
echo "$(ts) recovered metrics for existing $NAME"
fi
}
CUR=$(latest); [ -z "$CUR" ] && CUR=$START_STEP
while [ "$CUR" -lt "$END_TARGET" ]; do
END=$((CUR + CHUNK)); [ "$END" -gt "$END_TARGET" ] && END=$END_TARGET
STEPS=$((END - CUR)); SKIP=$((CUR - BASE))
if [ "$CUR" -eq "$START_STEP" ]; then RESUME="$START_CKPT"; else RESUME="$OUT/atom_native_step_${CUR}_${SUF}.pt"; fi
NAME="atom_native_step_${END}_${SUF}.pt"
LOG="$LOGD/train_${CUR}_${END}.log"
if [ ! -f "$OUT/$NAME" ]; then
echo "$(ts) chunk $CUR->$END skip=$SKIP resume=$RESUME log=$LOG"
T0=$(date +%s)
# shellcheck disable=SC2086
"$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" $LEAK_FLAGS \
> "$LOG" 2>&1
RC=$?
if [ "$RC" -ne 0 ] || [ ! -f "$OUT/$NAME" ]; then echo "$(ts) chunk 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}.json"
cp "$OUT/loss_trajectory.json" "$AB/loss_trajectory_${END}.json" 2>/dev/null || true
echo "$(ts) chunk $CUR->$END saved $NAME wall=$(( $(date +%s) - T0 ))s"
else
recover_metrics "$END"
fi
has_row "$END" || measure "$END"
CUR=$END
done
# measure any checkpoint of this arm in (START_STEP, END_TARGET] that has no TSV row yet
for E in $(ls "$OUT"/atom_native_step_*_${SUF}.pt 2>/dev/null | sed -n 's/.*step_\([0-9]*\)_.*/\1/p' | sort -n); do
if [ "$E" -gt "$START_STEP" ] && [ "$E" -le "$END_TARGET" ] && ! has_row "$E"; then
recover_metrics "$E"; measure "$E"
fi
done
echo "$(ts) DONE arm $ARM at $CUR"
|