File size: 9,114 Bytes
6a1771b | 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 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | #!/bin/bash
#SBATCH --partition=songmei
#SBATCH --nodelist=feanor
#SBATCH --gres=gpu:1
#SBATCH --cpus-per-task=8
#SBATCH --mem=64G
#SBATCH --time=72:00:00
#SBATCH --job-name=wave12
#SBATCH --output=/scratch/users/gatmiry/llm-reasoning-logic-puzzles/sudoku-code/wavecurriculum_run/logs/slurm_%x_%j.out
#SBATCH --error=/scratch/users/gatmiry/llm-reasoning-logic-puzzles/sudoku-code/wavecurriculum_run/logs/slurm_%x_%j.err
# Wave-depth curriculum, 12 stages.
#
# The curriculum axis is PROPAGATION DEPTH, not puzzle difficulty. Each puzzle's
# ~25 solver waves are subsampled to 12 evenly spaced candidate snapshots
# (staged_candidate_gen.py --stages 12, last snapshot = the unique solution).
# Curriculum stage t means: t recurrence passes, latent slots 1..t active, wave
# snapshots 1..t supervised. Promotion adds exactly one wave block.
#
# Difficulty is NOT gated: every puzzle is available from step 0, drawn
# uniformly from the corpus. The old level<=stage+2 filter is gone -- the
# difficulty tag has only 6 values (so it cannot express a 12-step ladder), is
# uncorrelated with puzzle size (r=-0.003 vs empty cells), and explains only
# ~19% of the variance in solver round count.
#
# Promotion is plateau-driven: a stage graduates when the candidate-set accuracy
# at its deepest slot stops improving, scored on cells that CHANGED from the
# previous snapshot. Unchanged cells are copies of slot j-1 and stay correct for
# a head that learned nothing, so they cannot signal acquisition. The accuracy
# threshold is a fast path and the patience cap prevents a stuck stage stalling.
#
# Submit one job per arm:
# sbatch --job-name=w12_flat --export=ALL,ARM=flat sbatch_wave12.sh
# sbatch --job-name=w12_datacur --export=ALL,ARM=datacur sbatch_wave12.sh
# sbatch --job-name=w12_latent --export=ALL,ARM=latent sbatch_wave12.sh
# sbatch --job-name=w12_latent_bt --export=ALL,ARM=latent_bt sbatch_wave12.sh
#
# ARM:
# flat K=0, no curriculum at all: pure control.
# datacur K=0 with the 12-stage round-count DATA curriculum. Stage t admits
# only puzzles in the first t round-count bins, so the propagation
# ladder is expressed in the puzzle POOL rather than in latent
# supervision. This is the no-latent curriculum arm: same axis as
# the latent arms, no latent tokens. Promotion is gated on accuracy
# over the newest round-bin.
# latent K=12 latent chain, 12-stage wave supervision, plateau promotion.
# latent_bt as latent, plus adaptive backtracking keyed on DEPTH: a repair
# replays num_passes=t when snapshot t's accuracy regresses below
# its graduation value.
#
# From scratch is forced, not chosen: pos_embeddings is exactly (3*81+K, emb_dim),
# so no 6-slot checkpoint can restore into a 12-slot model.
#
# The 12-stage masks live on feanor's node-local /tmp (sbatch_gen_s12_masks.sh)
# because the 20 G scratch quota has ~2 G free and the train masks are 3.5 G.
set -u
hostname
nvidia-smi -L
echo "[$(date)] ARM=${ARM:-unset}"
ARM="${ARM:?set ARM=flat|datacur|latent|latent_bt via --export=ALL,ARM=...}"
NAME="w12_${ARM}"
SCRATCH_ROOT=/scratch/users/gatmiry/llm-reasoning-logic-puzzles
RUN_DIR=${SCRATCH_ROOT}/sudoku-code/wavecurriculum_run
ENV_LOCAL=/tmp/logicpuzzles
CAND_DIR=/tmp/sudoku_s12
LOCAL_LOG=/tmp/sudoku_wave_runs/${NAME}
TARBALL_GANDALF=/tmp/logicpuzzles_env.tar.gz
TARBALL_LOCAL=/tmp/logicpuzzles_env_${SLURM_JOB_ID}.tar.gz
mkdir -p "${RUN_DIR}/logs" "${LOCAL_LOG}" /tmp/sudoku_wave_runs
if ${ENV_LOCAL}/bin/python -u -c "import jax; assert jax.default_backend()=='gpu' or 'cuda' in str(jax.devices()[0]).lower()" 2>/dev/null; then
echo "[$(date)] reusing ${ENV_LOCAL}"
else
echo "[$(date)] fetching env tarball"
rm -rf "${ENV_LOCAL}"
SCP_OPTS="-o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new"
[ -f "${HOME}/.ssh/id_ed25519_berkeley" ] && SCP_OPTS="${SCP_OPTS} -i ${HOME}/.ssh/id_ed25519_berkeley"
scp ${SCP_OPTS} "gandalf.berkeley.edu:${TARBALL_GANDALF}" "${TARBALL_LOCAL}"
tar xzf "${TARBALL_LOCAL}" -C /tmp
rm -f "${TARBALL_LOCAL}"
fi
export PY=${ENV_LOCAL}/bin/python
export LD_LIBRARY_PATH=\
${ENV_LOCAL}/lib/python3.9/site-packages/nvidia/cudnn/lib:\
${ENV_LOCAL}/lib/python3.9/site-packages/nvidia/cublas/lib:\
${ENV_LOCAL}/lib/python3.9/site-packages/nvidia/cuda_runtime/lib:\
${ENV_LOCAL}/lib/python3.9/site-packages/nvidia/cuda_nvrtc/lib:\
${ENV_LOCAL}/lib/python3.9/site-packages/nvidia/nccl/lib:\
${LD_LIBRARY_PATH:-}
${PY} -u -c "import jax; print(jax.__version__, jax.devices(), jax.default_backend())"
# ---- Shared recipe: identical across arms ----
export SUDOKU_RESUME=0
export SUDOKU_START_STAGE=1
export SUDOKU_MAX_STAGE="${SUDOKU_MAX_STAGE:-12}"
# Plateau promotion. A stage graduates when its frontier-depth accuracy has not
# gained 0.005 for 20k steps; PATIENCE is only a hard ceiling. This replaces the
# old fixed 15k timer, under which every promotion fired on patience at 0.32-0.46
# accuracy and the 0.70 threshold was dead code.
export SUDOKU_PLATEAU_STEPS=20000
export SUDOKU_PLATEAU_DELTA=0.005
export SUDOKU_PATIENCE=80000
export SUDOKU_MIN_STAGE_STEPS=8000
export SUDOKU_PROMOTE_ACC=0.90
# Matches the flat reference run (which reached level-3 acc 0.945 at ~800k), so
# the arms are comparable to it at equal steps rather than stopping at 250k.
export SUDOKU_MAX_STEPS="${SUDOKU_MAX_STEPS:-800000}"
# Difficulty tag selects nothing: uniform over the corpus (~68% level 3).
export SUDOKU_LEVEL_BALANCED=0
export SUDOKU_EVAL_EVERY=2000
export SUDOKU_SAVE_EVERY=10000
export SUDOKU_CKPT_KEEP=3
export SUDOKU_LR=0.0002
export SUDOKU_DROPOUT=0.2
export SUDOKU_WD=0.005
export SUDOKU_TRAIN_PATH="../datasets/train_sudoku_puzzles.npy"
export SUDOKU_TEST_PATH="../datasets/test_sudoku_puzzles.npy"
export XLA_PYTHON_CLIENT_MEM_FRACTION=0.9
# ---- Per-arm knobs ----
export SUDOKU_BACKTRACK=0
case "${ARM}" in
flat)
# No latents: no candidate head, no depth ladder. Masks left unset.
export SUDOKU_LATENT_SLOTS=0
export SUDOKU_RECURRENT=0
export SUDOKU_AUX_WEIGHT=0.0
export SUDOKU_TRAIN_CAND=""
export SUDOKU_TEST_CAND=""
;;
datacur)
# No latents, but a real 12-stage curriculum over the puzzle pool, ordered
# by solver round count. Candidate masks stay unset (nothing to supervise);
# only the meta files are needed, for the round counts.
export SUDOKU_LATENT_SLOTS=0
export SUDOKU_RECURRENT=0
export SUDOKU_AUX_WEIGHT=0.0
export SUDOKU_TRAIN_CAND=""
export SUDOKU_TEST_CAND=""
export SUDOKU_DATA_CURRICULUM=rounds
export SUDOKU_TRAIN_META="${CAND_DIR}/train_meta.npy"
export SUDOKU_TEST_META="${CAND_DIR}/test_meta.npy"
for f in "${SUDOKU_TRAIN_META}" "${SUDOKU_TEST_META}"; do
[ -s "${f}" ] || { echo "missing meta ${f}; run sbatch_gen_s12_masks.sh on this node" >&2; exit 1; }
done
;;
latent|latent_bt)
export SUDOKU_LATENT_SLOTS=12
export SUDOKU_RECURRENT=1
export SUDOKU_AUX_WEIGHT=1.0
export SUDOKU_CAND_SLOT_MODE=depth # k = num_passes, slot j -> snapshot j
export SUDOKU_PASSES_PER_STAGE=1 # stage t -> depth t, 12 stages -> 12 slots
export SUDOKU_CAND_DELTA_BG=0.25 # down-weight cells copied from slot j-1
export SUDOKU_TRAIN_CAND="${CAND_DIR}/train_cand_masks.npy"
export SUDOKU_TEST_CAND="${CAND_DIR}/test_cand_masks.npy"
for f in "${SUDOKU_TRAIN_CAND}" "${SUDOKU_TEST_CAND}"; do
[ -s "${f}" ] || { echo "missing masks ${f}; run sbatch_gen_s12_masks.sh on this node" >&2; exit 1; }
done
;;
*) echo "unknown ARM '${ARM}'" >&2; exit 1 ;;
esac
if [ "${ARM}" = "latent_bt" ]; then
# Adaptive replay keyed on depth: deficits are measured on snapshot t's
# candidate-set accuracy, and a repair trains at num_passes=t on the same
# full-corpus batch distribution the frontier uses.
export SUDOKU_BACKTRACK=1
export SUDOKU_BACKTRACK_MODE=adaptive
export SUDOKU_BACKTRACK_MARGIN=0.03
export SUDOKU_BACKTRACK_MAX_REPAIR_STEPS=4000
export SUDOKU_BACKTRACK_MIN_FRONTIER_STEPS=8000
export SUDOKU_BACKTRACK_MAX_REPAIR_FRACTION=0.25
export SUDOKU_BACKTRACK_GRAD_DECAY=0.05
export SUDOKU_BACKTRACK_FRONTIER_MIX=1
fi
cd "${RUN_DIR}"
# Log-only periodic sync: checkpoints stay on /tmp (20 G scratch quota).
(
while true; do sleep 900
rsync -a "${LOCAL_LOG}.log" "${RUN_DIR}/logs/${NAME}.log" 2>/dev/null || true
done
) &
SYNC_PID=$!
echo "[$(date)] starting ${NAME} from scratch"
echo " K=${SUDOKU_LATENT_SLOTS} recurrent=${SUDOKU_RECURRENT} bt=${SUDOKU_BACKTRACK}"
echo " max_stage=${SUDOKU_MAX_STAGE} plateau=${SUDOKU_PLATEAU_STEPS} patience=${SUDOKU_PATIENCE}"
echo " slot_mode=${SUDOKU_CAND_SLOT_MODE:-n/a} pps=${SUDOKU_PASSES_PER_STAGE:-n/a} delta_bg=${SUDOKU_CAND_DELTA_BG:-n/a}"
CUDA_VISIBLE_DEVICES=0 ${PY} -u -m train.main \
--workdir="${LOCAL_LOG}" --exp_name="${NAME}" \
> "${LOCAL_LOG}.log" 2>&1
EC=$?
kill ${SYNC_PID} 2>/dev/null || true
rsync -a "${LOCAL_LOG}.log" "${RUN_DIR}/logs/${NAME}.log" 2>/dev/null || true
echo "[$(date)] ${NAME} exit ${EC}"
exit ${EC}
|