File size: 14,757 Bytes
aa60529 | 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 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 | #!/usr/bin/env bash
# =============================================================================
# CONTINUE the 64k b5 extension on 8x H100 -- to a FULL b5 epoch.
#
# PARENT: ext64k run3 step 695 (the 64k model: 67/65/52/29 at 8k/16k/32k/64k).
# Recovered from strong-room-grows-fin-03 and now durable on HF at
# checkpoints/ext64k_b5_rank0_step_695.ckpt (byte-verified).
#
# WHY cp=8 AND NOT cp=4: at 64k, cp=4 puts 16,384 tokens on each rank, which
# MEASURED 108,493 MB on the H200 run. An H100 has 81,559 MiB. It does not
# fit. cp=8 halves that to 8,192 tok/rank. There is no other lever: CP_B is
# already 1 and bluscriptCP.cpp has no activation-recompute flag.
#
# WHY CP_INIT_FROM (weights only) AND NOT CP_CKPT_RESUME: ZeRO-1 shards
# optimizer state across the GLOBAL process group (bluscriptCP.cpp:24-28), so
# a sidecar written at world_size=4 cannot load at world_size=8 --
# ShardedOptimizer::load_state throws ZeroCheckpointError("cross-world-size
# resume not supported"). Weights-only is what every prior stage in this
# project used across a topology change, including the 64k run itself.
# TWO CONSEQUENCES, both deliberate:
# 1. the data loader restarts at token 0, so the first 42% of b5 is seen a
# second time and the remaining 58% for the first time -- a full pass;
# 2. Adam m/v reset, which CP_REWARMUP (100 steps to 1.8e-4) absorbs.
#
# WHY THE FULL EPOCH IS ONLY NOW POSSIBLE: yesterday's box had 5 b5 train
# shards (0.481 B tokens). All 9 are here now = 0.866 B = a real epoch.
#
# TAIL=100, NOT 2: yesterday's 2-step tail left the final LR at 1.2e-4, not the
# ~6e-5 floor -- the checkpoint was labelled decayed but effectively was not.
#
# UPLOAD IS VERIFIED BY BYTE SIZE. Yesterday's up() helper was
# `[ -e "$1" ] || return 0`, which silently SUCCEEDS when the source is
# missing. That is how the step-695 checkpoint was lost: the CLI never
# errored, the log said nothing, and 17 GB never left the box.
# =============================================================================
set -uo pipefail
cd "$(dirname "$0")"
ROOT="$(cd ../.. && pwd)"
export LD_LIBRARY_PATH="$ROOT/Tensor-Implementations/lib:$ROOT/Profiler/lib:${LD_LIBRARY_PATH:-}"
export OMPI_ALLOW_RUN_AS_ROOT=1 OMPI_ALLOW_RUN_AS_ROOT_CONFIRM=1
export PATH=/usr/local/cuda/bin:$PATH
PY=/root/gv/envv/bin/python3 # this box's venv (NOT /root/envv)
HF=/root/gv/envv/bin/hf
EXE=./build/bluscriptCP_exec
CK=checkpoints_bluscriptcp/rank_0
LOG=cont64k_logs; mkdir -p "$LOG"
M="$LOG/master.log"
REPO=unparallelled/BluScriptCP
T=65536; NP=8; GPUS=0,1,2,3,4,5,6,7
ORIG=32768; SCALE=2 # 2 * 32768 == 65536, the parent's own cache
DATA=Data_Loader/Data/b5_65536_131072
PARENT="$CK/blumodelcp_run3_step_695.ckpt"
HAY="$PWD/Data_Loader/Data/b2_8192_16384/val_shard_00000.bin"
WARM=100; TAIL=${TAIL:-100}; FREQ=${FREQ:-100}; KEEP=2
GB=524288
say(){ echo "[$(date -u +%H:%M:%S)] $*" | tee -a "$M" >&2; }
die(){ say "FATAL: $*"; exit 1; }
# ---- preflight -------------------------------------------------------------
$PY -c "import tiktoken" 2>/dev/null || die "tiktoken missing (eval would silently score nothing)"
[ -x "$EXE" ] || die "$EXE not built"
# CP_ATTN_FUSION=split needs BOTH kernel families compiled in: CP_FUSED_ROPE
# (Stage 2 QK-norm+RoPE) and CP_ATTN_SPLIT (Stage 3 causal attention). Without
# them SplitAttnSDPA_cp.h compiles to stubs that THROW at the first forward --
# a default `make` produced exactly that and the run died 73 s in. The Makefile
# records the flag state in this stamp; refuse to start if it is not right.
STAMP=build/objects/.cp_rope_flags
[ -f "$STAMP" ] || die "no build-flag stamp: rebuild with make CP_FUSED_ROPE=1 CP_ATTN_SPLIT=1 SM_ARCH=90 bluscript-cp"
grep -q 'CP_FUSED_ROPE=1' "$STAMP" && grep -q 'CP_ATTN_SPLIT=1' "$STAMP" || \
die "binary built WITHOUT the split kernels ($(cat "$STAMP")). Rebuild: make CP_FUSED_ROPE=1 CP_ATTN_SPLIT=1 SM_ARCH=90 bluscript-cp -j\$(nproc)"
[ "$EXE" -nt "$STAMP" ] || say "WARN: $EXE is older than the flag stamp -- rebuild if unsure"
[ -f "$PARENT" ] || die "parent missing: $PARENT"
[ -f "$HAY" ] || die "haystack missing: $HAY"
[ -d "$DATA" ] || die "$DATA missing"
[ $((SCALE*ORIG)) -eq $T ] || die "YaRN invariant: $SCALE * $ORIG != $T"
PSZ=$(stat -c%s "$PARENT")
[ "$PSZ" = "5225752094" ] || die "parent wrong size: $PSZ (want 5225752094)"
ntr=$(ls "$DATA"/*train*.bin 2>/dev/null | wc -l)
nva=$(ls "$DATA"/*val*.bin 2>/dev/null | wc -l)
[ "$ntr" -ge 1 ] && [ "$nva" -ge 1 ] || die "need train+val shards in $DATA (have $ntr/$nva)"
have=$(( $(du -cb "$DATA"/*train*.bin | tail -1 | cut -f1) / 2 ))
FREEG=$(df -BG --output=avail / | tail -1 | tr -dc 0-9)
[ "$FREEG" -ge 100 ] || die "only ${FREEG} GB free; 8-rank checkpoints need ~90 GB"
say "=== CONTINUE 64k b5 to a FULL EPOCH (8x H100, cp=$NP) ==="
say "parent: ext64k run3 step 695 (64k model), weights-only via CP_INIT_FROM"
say "YaRN SCALE=$SCALE ORIG=$ORIG -> $((SCALE*ORIG)) == CP_T=$T OK"
say "b5: $ntr train shards = $(echo "scale=3; $have/1000000000" | bc) B tokens on disk"
say "disk: ${FREEG} GB free"
COMMON="CP_SIZE=$NP CP_ATTN_MODE=ring CP_ATTN_FUSION=split CP_T=$T \
YARN_SCALE=$SCALE YARN_ORIG_MAXPOS=$ORIG \
CP_ZERO=1 CP_ZERO_V2=1 CP_ZERO_SYMMETRIC=1 CP_BF16=1 CP_OPTIMIZER=muon \
CP_WARMUP=$WARM CP_REWARMUP=$WARM CP_REWARMUP_PEAK=0.3"
ARCH="CP_N_EMBD=1536 CP_N_LAYER=36 CP_N_HEAD=12 CP_N_KVHEAD=4 CP_FFN=4096 CP_WEIGHT_TYING=0"
# ---- PHASE 1: probe -- does 64k fit in 80 GB at cp=8? ----------------------
if [ ! -f "$LOG/probe.ok" ]; then
say "PHASE 1: 3-step probe (8,192 tok/rank; extrapolates to ~67 GB of 81.5 GB)"
env $ARCH $COMMON CP_STABLE=0 CP_CKPT=0 \
CP_INIT_FROM="$PWD/$PARENT" CP_DATA_ROOT="$PWD/$DATA" \
CP_B=1 CP_GLOBAL_BATCH=$T CP_MAX_STEPS=3 \
CUDA_VISIBLE_DEVICES=$GPUS mpirun --allow-run-as-root -np $NP "$EXE" \
> "$LOG/probe.log" 2>&1
RC=$?
# The probe must PROVE it trained, not merely fail to say "OOM". The first
# version of this gate only grepped for OOM, so a probe that aborted on every
# rank with `sdpa_split_rope_once_forward: build with -DCP_FUSED_ROPE` was
# declared "PROBE OK" and the real run then died 73 s in. Three independent
# checks now, and the peak-memory parse is one of them: an empty peak means
# no step ever completed.
if grep -qaiE 'out of memory|bad_alloc' "$LOG/probe.log"; then
say "PROBE OOM -- 64k does NOT fit at cp=8 on 80 GB cards."
say " There is no further lever on 8 GPUs: CP_B is 1 and there is no recompute flag."
grep -aoE 'cudaMallocAsync\(size=[0-9]+\) failed' "$LOG/probe.log" | head -2 | sed 's/^/ /' >&2
exit 1
fi
if grep -qaE 'terminate called|std::runtime_error|Signal: Aborted' "$LOG/probe.log"; then
say "PROBE CRASHED (not OOM). First error:"
grep -aA1 'terminate called' "$LOG/probe.log" | grep -a 'what()' | head -1 | sed 's/^/ /' >&2
exit 1
fi
NSTEP=$(grep -acE '^step +[0-9]+ \|' "$LOG/probe.log")
PEAK=$(grep -aoE 'mem: [0-9]+MB' "$LOG/probe.log" | tail -1)
[ "$RC" -eq 0 ] || { say "PROBE exited $RC"; tail -15 "$LOG/probe.log" >&2; exit 1; }
[ "$NSTEP" -ge 1 ] || { say "PROBE produced NO step lines"; tail -15 "$LOG/probe.log" >&2; exit 1; }
[ -n "$PEAK" ] || { say "PROBE reported no peak memory"; tail -15 "$LOG/probe.log" >&2; exit 1; }
grep -aE '^step +[0-9]+ \|' "$LOG/probe.log" | sed 's/^/ /' >&2
say "PROBE OK: $NSTEP steps, peak $PEAK of 81559 MiB/card"
touch "$LOG/probe.ok"
fi
# ---- step count: a full epoch over what is on disk -------------------------
# The probe ran grad_accum=1. Its dt is NOT the per-step cost -- multiplying it
# by grad_accum overstated the real rate 5.8x on the 64k run (74.0 vs 12.85
# s/step) because fixed per-step costs got multiplied too. Reported for
# information only; the step count comes from the DATA, not the clock.
SPS=$($PY - "$LOG/probe.log" $GB $T <<'PYEOF'
import re,sys,statistics
t=open(sys.argv[1],errors='ignore').read()
d=[float(x) for x in re.findall(r'dt: ([0-9.]+)ms', t)]
if not d: print("0.0"); raise SystemExit
probe_accum=1
real_accum=int(sys.argv[2])//int(sys.argv[3])
per=statistics.median(d[1:]) if len(d)>2 else d[-1]
print(f"{per/1000.0*real_accum/probe_accum:.3f}")
PYEOF
)
say "probe rate (UPPER BOUND, inflated by fixed costs x grad_accum): ${SPS} s/step"
STEPS=${STEPS_OVERRIDE:-$(( have / GB ))}
STABLE=$(( STEPS - WARM - TAIL ))
[ "$STABLE" -lt 1 ] && STABLE=1
# bluscriptCP.cpp:1006 dies if warmup + stable >= max_steps - 1
[ $(( WARM + STABLE )) -lt $(( STEPS - 1 )) ] || die "illegal WSD: $WARM+$STABLE >= $STEPS-1"
say "PHASE 2: $STEPS steps x $GB = $(echo "scale=3; $STEPS*$GB/1000000000" | bc) B tokens = a full b5 epoch"
say " WSD: warmup $WARM, stable $STABLE, decay tail $TAIL -> final checkpoint IS decayed"
if [ -f "$LOG/cont.ckpt" ] && [ -f "$(cat "$LOG/cont.ckpt")" ]; then
say "already trained -> $(cat "$LOG/cont.ckpt")"
else
# Reclaim leaked .zero_g* sidecars while training runs: CP_CKPT_KEEP prunes
# .ckpt but NOT the sidecars (that leak filled a 193 GB disk on 2026-09-09).
( while sleep 300; do
for s in checkpoints_bluscriptcp/rank_*/*.zero_g*; do
[ -e "$s" ] || continue
base=${s%.zero_g*}
[ -f "$base.ckpt" ] || rm -f "$s"
done
done ) & JANITOR=$!
trap 'kill $JANITOR 2>/dev/null' EXIT
env $ARCH $COMMON CP_STABLE=$STABLE \
CP_CKPT=1 CP_CKPT_FREQ=$FREQ CP_CKPT_KEEP=$KEEP CP_CKPT_NEW_RUN=1 \
CP_INIT_FROM="$PWD/$PARENT" CP_DATA_ROOT="$PWD/$DATA" \
CP_B=1 CP_GLOBAL_BATCH=$GB CP_MAX_STEPS=$STEPS \
CUDA_VISIBLE_DEVICES=$GPUS mpirun --allow-run-as-root -np $NP "$EXE" \
> "$LOG/cont.log" 2>&1
kill $JANITOR 2>/dev/null; trap - EXIT
grep -qaiE 'out of memory|bad_alloc' "$LOG/cont.log" && die "OOM in the full run"
RN=$(grep -aoE 'CP_Training_log[0-9]+' "$LOG/cont.log" | head -1 | grep -oE '[0-9]+')
[ -n "$RN" ] || die "no run number"
CKF=$(ls -t $CK/blumodelcp_run${RN}_step_*.ckpt 2>/dev/null | head -1)
[ -n "$CKF" ] || die "run $RN wrote NO checkpoint"
n=$(ls checkpoints_bluscriptcp/rank_*/"$(basename "$CKF")" 2>/dev/null | wc -l)
[ "$n" -ge "$NP" ] || die "only $n/$NP rank shards for $(basename "$CKF")"
echo "$CKF" > "$LOG/cont.ckpt"; echo "$RN" > "$LOG/runnum"
say "cont64k -> run $RN, $(basename "$CKF"), $(grep -aE '^step ' "$LOG/cont.log" | tail -1 | grep -oE 'loss: [0-9.]+')"
say " peak mem $(grep -aoE 'mem: [0-9]+MB' "$LOG/cont.log" | tail -1)"
say " final lr $(grep -aE '^step ' "$LOG/cont.log" | tail -1 | grep -oE 'lr [0-9.e-]+') (want ~6e-05 = decayed)"
fi
# ---- PHASE 3: eval, 4 lengths in parallel ----------------------------------
EXT=$(basename "$(cat "$LOG/cont.ckpt")")
say "PHASE 3: eval cont64k -- 4 lengths, one GPU each"
# 65512 + MAX_NEW 24 = 65536 exactly; 65528 would pad to 65792 and abort.
for LEN in 8192 16360 32744 65512; do
( GEN_DATA_ROOT=$DATA HAY="$HAY" PY=$PY OUT=cont64k_len_$LEN TASKS="niah_single" \
CKPTS="cont64k" N=100 PROMPT_LEN=$LEN MAX_NEW=24 T=$T \
DEPTHS="0.0,0.25,0.5,0.75,1.0" YARN_ORIG=$ORIG YARN=$SCALE \
GPUS=$(( (LEN==8192)?0:(LEN==16360)?1:(LEN==32744)?2:3 )) JOBS_PER_GPU=1 \
BF16=1 GREEDY=1 CKPT_EXTRA="cont64k=$EXT" ./run_longeval.sh \
>> "$LOG/eval_$LEN.log" 2>&1 ) &
done
wait
for LEN in 8192 16360 32744 65512; do
[ -s "cont64k_len_$LEN/summary.csv" ] && say " len $LEN -> $(sed -n 2p cont64k_len_$LEN/summary.csv)" \
|| say " len $LEN -> NO RESULT"
done
# ---- PHASE 4: upload, VERIFIED ---------------------------------------------
say "PHASE 4: upload"
{
echo "# 64k b5 continuation to a full epoch -- $(date -u +%Y-%m-%d)"
echo
echo "Parent: ext64k run3 step 695 (67.0/65.0/52.0/29.0 at 8k/16k/32k/64k),"
echo " loaded WEIGHTS-ONLY: ZeRO sidecars cannot cross world_size 4 -> 8."
echo "Data: b5_65536_131072, $ntr shards = $(echo "scale=3; $have/1000000000" | bc) B tokens."
echo "Train: $STEPS steps x $GB = $(echo "scale=3; $STEPS*$GB/1000000000" | bc) B tokens = a full b5 epoch."
echo " The loader restarts at token 0, so b5's first 42% is seen a"
echo " second time and the rest for the first time."
echo " WSD warmup $WARM / stable $STABLE / decay tail $TAIL."
echo " 8x H100, cp=$NP, B=1 x grad_accum $((GB/T))."
echo "Eval: niah_single, N=100, greedy, shared haystack b2_8192_16384 val."
echo
echo "## Results"; echo
echo "| context | niah_single |"; echo "|---|---|"
for LEN in 8192 16360 32744 65512; do
v=$(sed -n 2p "cont64k_len_$LEN/summary.csv" 2>/dev/null | cut -d, -f2)
printf "| %s | %s |\n" "$LEN" "${v:-n/a}"
done
echo
echo "Parent for comparison: 67.0 / 65.0 / 52.0 / 29.0."
} > "$LOG/RESULTS.md"
# Upload, then CONFIRM the remote byte size. A missing source is a LOUD failure:
# the silent `[ -e "$1" ] || return 0` in ext64k_b5.sh is what lost the step-695
# checkpoint -- the CLI never errored and nothing was logged.
UPOK=1
up(){ # src dst
local src=$1 dst=$2
if [ ! -e "$src" ]; then say " MISSING SOURCE (NOT UPLOADED): $src"; UPOK=0; return 1; fi
if $HF upload "$REPO" "$src" "$dst" >>"$LOG/upload.log" 2>&1; then say " sent $dst"
else say " UPLOAD FAILED $dst"; UPOK=0; return 1; fi
}
verify(){ # localfile remotepath
$PY - "$REPO" "$1" "$2" <<'PYEOF'
import sys,os
from huggingface_hub import HfApi
repo,loc,rem=sys.argv[1],sys.argv[2],sys.argv[3]
want=os.path.getsize(loc)
got={f.rfilename:(f.size or 0) for f in HfApi().repo_info(repo,files_metadata=True).siblings}.get(rem)
print(f" {'VERIFIED' if got==want else 'MISMATCH/ABSENT'} {rem} local={want:,} remote={got}")
sys.exit(0 if got==want else 1)
PYEOF
}
up "$LOG/RESULTS.md" "docs/CONT64K_B5_RESULTS_$(date -u +%Y-%m-%d).md"
for LEN in 8192 16360 32744 65512; do up "cont64k_len_$LEN" "logs/cont64k_len_$LEN/"; done
up "$LOG" logs/cont64k/
up "$(basename "$0")" "scripts/$(basename "$0")"
RN=$(cat "$LOG/runnum" 2>/dev/null)
STEP=$(basename "$(cat "$LOG/cont.ckpt")" | grep -oE 'step_[0-9]+' | cut -d_ -f2)
say "uploading the model itself (the part that was lost yesterday)"
SRC="$CK/blumodelcp_run${RN}_step_${STEP}.ckpt"
DST="checkpoints/cont64k_b5_rank0_step_${STEP}.ckpt"
if up "$SRC" "$DST"; then verify "$SRC" "$DST" | tee -a "$M" || UPOK=0; fi
for r in $(seq 0 $((NP-1))); do
S="checkpoints_bluscriptcp/rank_$r/blumodelcp_run${RN}_step_${STEP}.zero_g1"
D="checkpoints/cont64k_b5_rank${r}_step_${STEP}.zero_g1"
if up "$S" "$D"; then verify "$S" "$D" | tee -a "$M" || UPOK=0; fi
done
[ "$UPOK" = 1 ] && say "=== CONT64K DONE -- all uploads verified, safe to tear down ===" \
|| say "=== CONT64K DONE but UPLOADS INCOMPLETE -- DO NOT TEAR DOWN ==="
|