unparallelled commited on
Commit
e9bd690
·
verified ·
1 Parent(s): 2ff33ec

Upload source/files/needle_sweep.sh with huggingface_hub

Browse files
Files changed (1) hide show
  1. source/files/needle_sweep.sh +254 -0
source/files/needle_sweep.sh ADDED
@@ -0,0 +1,254 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # =============================================================================
3
+ # needle_sweep.sh -- run the retrieval probe across an extension-factor row set
4
+ # for ONE model, then score it. Forward-only, cache-only (no fine-tuning).
5
+ #
6
+ # Reuses the existing CP_EVAL_PPL + CP_EVAL_TOKENS_BIN path unchanged: the .bin
7
+ # holds synthetic trials, and per-position mean_nll in the CSV gives retrieval
8
+ # NLL at the answer span. See needle_gen.py for the fixed-slot construction.
9
+ #
10
+ # GROUP-SLOT FAN-OUT: slots = NGPU / CP_SIZE, so EVERY row parallelises --
11
+ # T<=128k -> CP=1 -> 8 concurrent | 256k -> CP=2 -> 4 | 512k -> CP=4 -> 2
12
+ # Each group is pinned to a contiguous device subset so cudaSetDevice(rank) binds
13
+ # rank->GPU correctly. Concurrency changes only WHEN work happens, never what is
14
+ # computed (independent processes, separate CSVs), so no determinism argument is
15
+ # needed -- unlike the search's EvaluatorPool.
16
+ #
17
+ # RESUMABLE: one marker per (T, cache, variant, block) in $STATE_DIR.
18
+ #
19
+ # Launch (114M 4k base, NTK, all five factors):
20
+ # MODEL_KEY=114m_run1 CKPT_RUN=1 \
21
+ # MODEL_ARCH="CP_N_EMBD=768 CP_N_LAYER=12 CP_N_HEAD=12 CP_N_KVHEAD=4 CP_FFN=2048 CP_WEIGHT_TYING=1" \
22
+ # FACTORS="8 16 32" CACHES="ntk" \
23
+ # nohup bash Tests/bluscriptcp/needle_sweep.sh > needle_114m.log 2>&1 &
24
+ #
25
+ # The single searched cell (48M@16x) is a separate invocation:
26
+ # MODEL_KEY=48m_run200 CKPT_RUN=200 MODEL_ARCH="..." FACTORS="16" \
27
+ # CACHES="searched" SEARCHED_LAMBDA=longrope_best_48m_s16_repr.txt bash ...
28
+ # =============================================================================
29
+ set -euo pipefail
30
+ cd "$(dirname "$0")/../.." # -> repo root
31
+
32
+ MODEL_KEY=${MODEL_KEY:?set MODEL_KEY (label for the results rows, e.g. 114m_run1)}
33
+ # INIT_FROM=<path/to/base.ckpt> loads WEIGHTS ONLY instead of resuming a run.
34
+ # Needed whenever the checkpoint's optimizer state cannot be read back in the
35
+ # EVAL configuration: a ZeRO/Muon-trained base evaluated forward-only builds a
36
+ # plain nn::AdamW, which would try to parse ShardedMuon's state and fail. Also
37
+ # the only way to evaluate a base trained at a different world size, since ZeRO
38
+ # state is sharded to the size that wrote it. A forward-only probe needs no
39
+ # optimizer state at all, so this is the correct loader for it.
40
+ INIT_FROM=${INIT_FROM:-}
41
+ if [ -n "$INIT_FROM" ]; then
42
+ [ -f "$INIT_FROM" ] || { echo "INIT_FROM: no such file: $INIT_FROM" >&2; exit 1; }
43
+ CKPT_RUN=${CKPT_RUN:-0}
44
+ CKPT_ENV="CP_INIT_FROM=$INIT_FROM"
45
+ else
46
+ CKPT_RUN=${CKPT_RUN:?set CKPT_RUN (or INIT_FROM=<path>)}
47
+ CKPT_ENV="CP_CKPT_RESUME=$CKPT_RUN"
48
+ fi
49
+ MODEL_ARCH=${MODEL_ARCH:?set MODEL_ARCH (must match how CKPT_RUN was trained)}
50
+
51
+ FACTORS=${FACTORS:-"8 16 32 64 128"} # extension factors s; T = PHYS_T * s
52
+ CACHES=${CACHES:-"ntk"} # ntk | yarn | pi | searched
53
+ # YARN_SCALE: evaluate under the trainer's YaRN cache instead of the LongRoPE lambda
54
+ # ramp. These are DIFFERENT ways to reach the same T and a checkpoint must be scored
55
+ # under the one it was TRAINED with, or the numbers are meaningless -- measured:
56
+ # run7 (extended at YARN_SCALE=4, m=1.1386) scored under the lambda ramp (scale=1.0)
57
+ # had its retrieval FLOOR collapse from 4.17 to 11.34 nats, i.e. it failed even with
58
+ # the needle 12 tokens from the probe. bluscriptCP.cpp:914 states the intended matrix:
59
+ # base ckpt + YARN_SCALE=1 (naive extrapolation @ T)
60
+ # extended ckpt + YARN_SCALE=4 (YaRN @ T)
61
+ # Set YARN_SCALE to take that route; leave it empty for the LongRoPE route.
62
+ YARN_SCALE=${YARN_SCALE:-}
63
+ SEARCHED_LAMBDA=${SEARCHED_LAMBDA:-} # required when CACHES contains "searched"
64
+ PHYS_T=${PHYS_T:-4096}
65
+ HEAD_DIM=${HEAD_DIM:-64}
66
+ ROPE_THETA=${ROPE_THETA:-500000}
67
+ BLOCKS=${BLOCKS:-5}
68
+ NEEDLE_LEN=${NEEDLE_LEN:-8}
69
+ SEED=${SEED:-0}
70
+ GPUS=${GPUS:-0,1,2,3,4,5,6,7}
71
+ FILLER=${FILLER:-Data_Loader/Data/edufineweb_val_00000.bin}
72
+ DATA_ROOT=${DATA_ROOT:-$PWD/Data_Loader/Data}
73
+ NEEDLE_DIR=${NEEDLE_DIR:-needles}
74
+ # Absolute offset for the probe+answer span (needle_gen --probe-pos). Unset = the
75
+ # end of the trial, the historical layout. Set it to decouple needle-to-probe
76
+ # DISTANCE from the needle's ABSOLUTE POSITION, which the default layout welds
77
+ # into a single variable. Filenames do NOT encode it, so give each probe position
78
+ # its own NEEDLE_DIR/CSV_DIR/STATE_DIR.
79
+ PROBE_POS=${PROBE_POS:-}
80
+ CSV_DIR=${CSV_DIR:-needle_csv}
81
+ STATE_DIR=${STATE_DIR:-needle_state}
82
+ RESULTS=${RESULTS:-needle_results.csv}
83
+ # Per-rank forward-only logits are (T/CP)*vocab*4 bytes. Keep <= this so a 49 GB
84
+ # card holds logits + activations. Pre-flight 2b must confirm it (and that logits
85
+ # are fp32 -- a bf16 model triggers an as_type copy that doubles the peak).
86
+ MAX_TOK_PER_RANK=${MAX_TOK_PER_RANK:-131072}
87
+
88
+ EXE=./build/bluscriptCP_exec
89
+ export LD_LIBRARY_PATH=BluTrain/Tensor-Implementations/lib:BluTrain/Profiler/lib:${LD_LIBRARY_PATH:-}
90
+ IFS=',' read -r -a GPUARR <<< "$GPUS"
91
+ NGPU=${#GPUARR[@]}
92
+ KV=$(sed -E 's/.*CP_N_KVHEAD=([0-9]+).*/\1/' <<< "$MODEL_ARCH")
93
+ mkdir -p "$NEEDLE_DIR" "$CSV_DIR" "$STATE_DIR"
94
+ log(){ echo "[$(date '+%F %H:%M:%S')] $*"; }
95
+
96
+ # ---- pre-flight -------------------------------------------------------------
97
+ [ -x "$EXE" ] || { echo "ERROR: $EXE missing -- build first"; exit 1; }
98
+ [ -f "$FILLER" ] || { echo "ERROR: filler stream $FILLER not found"; exit 1; }
99
+ # Detect a live evaluator by RESOLVED EXECUTABLE, not by command-line text. `pgrep -f
100
+ # bluscriptCP_exec` also matches any shell whose argv merely CONTAINS that string --
101
+ # including the wrapper that launches this script -- which produced a false "already
102
+ # running" abort. `pgrep -x` is no good either: comm truncates to 15 chars and
103
+ # "bluscriptCP_exec" is 16. Comparing /proc/PID/exe is exact.
104
+ exec_running(){
105
+ local want; want=$(readlink -f "$EXE") || return 1
106
+ local p
107
+ for p in /proc/[0-9]*; do
108
+ [ "$(readlink -f "$p/exe" 2>/dev/null)" = "$want" ] && { echo "${p#/proc/}"; return 0; }
109
+ done
110
+ return 1
111
+ }
112
+ if pids=$(exec_running); then
113
+ # ALLOW_CONCURRENT_EXE=1 permits a DELIBERATE overlap -- e.g. probing a mid-training
114
+ # checkpoint while the pretrain keeps running. Only use it when the memory headroom is
115
+ # known: a forward-only probe is ~10-15 GB against a training job's ~47 GB on an 80 GB
116
+ # card. The context_rot_2026-08-19 copies of this script already carried this flag; the
117
+ # tracked one never got it.
118
+ if [ "${ALLOW_CONCURRENT_EXE:-0}" = "1" ]; then
119
+ log "WARNING: $EXE already running (pid $pids); ALLOW_CONCURRENT_EXE=1 -- proceeding"
120
+ else
121
+ echo "ERROR: $EXE already running (pid $pids) -- this sweep would oversubscribe GPUs $GPUS"
122
+ echo " set ALLOW_CONCURRENT_EXE=1 to override when the overlap is intended"
123
+ exit 1
124
+ fi
125
+ fi
126
+ case " $CACHES " in *" searched "*)
127
+ [ -n "$SEARCHED_LAMBDA" ] || { echo "ERROR: CACHES has 'searched' but SEARCHED_LAMBDA is unset"; exit 1; }
128
+ [ -f "$SEARCHED_LAMBDA" ] || { echo "ERROR: SEARCHED_LAMBDA '$SEARCHED_LAMBDA' not found"; exit 1; };;
129
+ esac
130
+
131
+ # CP degree per row, from the plan's CP table (memory-driven, not a free choice).
132
+ cp_for_T(){
133
+ local T=$1 cp=1
134
+ while [ $(( T / cp )) -gt "$MAX_TOK_PER_RANK" ]; do cp=$(( cp * 2 )); done
135
+ echo "$cp"
136
+ }
137
+ # Verified topology only: pure ring>=4 is known-broken in this repo.
138
+ topo_for(){
139
+ local cp=$1
140
+ if [ "$cp" = "1" ]; then echo ""
141
+ elif [ $(( KV % cp )) -eq 0 ]; then echo "CP_ATTN_MODE=ulysses"
142
+ elif [ $(( cp % KV )) -eq 0 ]; then echo "CP_ATTN_MODE=hybrid CP_ULYSSES_SIZE=$KV"
143
+ else echo "UNSUPPORTED"; fi
144
+ }
145
+
146
+ # lambda file per (cache, head_dim, theta, s). NTK/PI are theta-independent closed
147
+ # forms; YaRN depends on base via find_correction_range, so it MUST be regenerated
148
+ # per theta. All are head_dim-dependent (vector length = head_dim/2).
149
+ lambda_file(){
150
+ local cache=$1 s=$2
151
+ if [ "$cache" = "searched" ]; then echo "$SEARCHED_LAMBDA"; return; fi
152
+ local out="$NEEDLE_DIR/lam_${cache}_hd${HEAD_DIM}_th${ROPE_THETA}_s${s}.txt"
153
+ if [ ! -f "$out" ]; then
154
+ python3 - "$cache" "$s" "$out" "$HEAD_DIM" "$ROPE_THETA" "$PHYS_T" <<'PY'
155
+ import sys, importlib.util
156
+ cache, s, out, hd, theta, orig = (sys.argv[1], int(sys.argv[2]), sys.argv[3],
157
+ int(sys.argv[4]), float(sys.argv[5]), float(sys.argv[6]))
158
+ spec = importlib.util.spec_from_file_location('lrs', 'Tests/bluscriptcp/longrope_search.py')
159
+ lrs = importlib.util.module_from_spec(spec); spec.loader.exec_module(lrs)
160
+ half, lam_max = hd // 2, s * 1.25
161
+ if cache == 'ntk': lam = lrs.seed_NTK(hd, s, lam_max)
162
+ elif cache == 'pi': lam = lrs.seed_PI(half, s, lam_max)
163
+ elif cache == 'yarn': lam = lrs.seed_YaRN(hd, s, orig, 32.0, 1.0, theta, lam_max)
164
+ else: sys.exit(f"unknown cache '{cache}'")
165
+ lrs.write_factors(out, lam, 0, s, int(orig * s))
166
+ PY
167
+ fi
168
+ echo "$out"
169
+ }
170
+
171
+ # Overridable so a REFINED distance grid can be added around a crossing. Set
172
+ # VARIANTS to the UNION of old and new points: already-completed ones are skipped
173
+ # by their state markers, so only the new points actually run, and the scorer then
174
+ # reports the whole grid in one table.
175
+ VARIANTS=${VARIANTS:-"p0 p0.25 p0.5 p0.75 p1 absent null"}
176
+ log "needle sweep: model=$MODEL_KEY run=$CKPT_RUN factors=[$FACTORS] caches=[$CACHES] blocks=$BLOCKS"
177
+ log " GPUS=$GPUS (n=$NGPU) kv=$KV head_dim=$HEAD_DIM theta=$ROPE_THETA"
178
+
179
+ for S in $FACTORS; do
180
+ T=$(( PHYS_T * S ))
181
+ CP=$(cp_for_T "$T")
182
+ TOPO=$(topo_for "$CP")
183
+ [ "$TOPO" = "UNSUPPORTED" ] && { echo "ERROR: no verified topology for CP=$CP with kv=$KV at T=$T"; exit 1; }
184
+ [ "$CP" -le "$NGPU" ] || { echo "ERROR: T=$T needs CP=$CP but only $NGPU GPU(s) given"; exit 1; }
185
+ N=$([ "$T" -le 131072 ] && echo 8 || echo 4) # pinned trials-per-block (plan A1)
186
+ SLOTS=$(( NGPU / CP ))
187
+ log "== s=$S T=$T CP=$CP ${TOPO:-(single-GPU)} N=$N slots=$SLOTS =="
188
+
189
+ # .bin files depend only on (T, variant, block) -- shared across models and caches.
190
+ for V in $VARIANTS; do for B in $(seq 0 $((BLOCKS-1))); do
191
+ [ -f "$NEEDLE_DIR/needle_T${T}_${V}_b${B}.bin" ] && continue
192
+ python3 Tests/bluscriptcp/needle_gen.py --T "$T" --variant "$V" --block "$B" \
193
+ --trials "$N" --needle-len "$NEEDLE_LEN" --filler "$FILLER" \
194
+ --seed "$SEED" --out-dir "$NEEDLE_DIR" \
195
+ ${PROBE_POS:+--probe-pos "$PROBE_POS"} >/dev/null
196
+ done; done
197
+
198
+ for CACHE in $CACHES; do
199
+ LAM=$(lambda_file "$CACHE" "$S")
200
+ # The lambda vector is length-specific: reject a mismatch loudly rather than
201
+ # scoring a cache tuned for another length (the T == S_search invariant).
202
+ SS=$(awk '/^S_search/{print $2}' "$LAM")
203
+ [ "$SS" = "$T" ] || { echo "ERROR: $LAM has S_search=$SS but this row is T=$T"; exit 1; }
204
+
205
+ # ---- job list, then a group-slot loop ----
206
+ jobs=()
207
+ for V in $VARIANTS; do for B in $(seq 0 $((BLOCKS-1))); do jobs+=("$V:$B"); done; done
208
+ running=0; g=0
209
+ for job in "${jobs[@]}"; do
210
+ V=${job%%:*}; B=${job##*:}
211
+ MK="$STATE_DIR/${MODEL_KEY}.T${T}.${CACHE}.${V}.b${B}.done"
212
+ OUTCSV="$CSV_DIR/ppl_needle_T${T}_${V}_b${B}.csv"
213
+ [ -f "$MK" ] && continue
214
+ # pin this group to its own contiguous device subset
215
+ devs=$(IFS=,; echo "${GPUARR[*]:$((g*CP)):$CP}")
216
+ if [ -n "$YARN_SCALE" ]; then
217
+ ROPE_ENV="YARN_SCALE=$YARN_SCALE" # trainer's YaRN cache
218
+ else
219
+ ROPE_ENV="CP_LONGROPE_FACTORS=$LAM" # LongRoPE lambda ramp
220
+ fi
221
+ (
222
+ env $MODEL_ARCH CP_SIZE=$CP $TOPO \
223
+ CP_EVAL_PPL=1 CP_T=$T CP_B=1 \
224
+ CP_EVAL_WINDOWS=$N CP_EVAL_SKIP_WINDOWS=0 \
225
+ CP_EVAL_TOKENS_BIN="$NEEDLE_DIR/needle_T${T}_${V}_b${B}.bin" \
226
+ $ROPE_ENV $CKPT_ENV \
227
+ CP_EVAL_OUT="$OUTCSV" CP_DATA_ROOT="$DATA_ROOT" \
228
+ CUDA_VISIBLE_DEVICES=$devs mpirun -np $CP "$EXE" \
229
+ > "$CSV_DIR/${MODEL_KEY}_T${T}_${CACHE}_${V}_b${B}.log" 2>&1 \
230
+ && touch "$MK"
231
+ ) &
232
+ g=$(( (g + 1) % SLOTS )); running=$(( running + 1 ))
233
+ if [ "$running" -ge "$SLOTS" ]; then wait -n; running=$(( running - 1 )); fi
234
+ done
235
+ wait
236
+
237
+ # every job must have produced its marker, or scoring would read a stale CSV
238
+ miss=0
239
+ for V in $VARIANTS; do for B in $(seq 0 $((BLOCKS-1))); do
240
+ [ -f "$STATE_DIR/${MODEL_KEY}.T${T}.${CACHE}.${V}.b${B}.done" ] || {
241
+ echo " MISSING: $V b$B -- see $CSV_DIR/${MODEL_KEY}_T${T}_${CACHE}_${V}_b${B}.log"; miss=1; }
242
+ done; done
243
+ [ "$miss" = "0" ] || { echo "ERROR: s=$S cache=$CACHE incomplete; not scoring"; exit 1; }
244
+
245
+ log " scoring s=$S cache=$CACHE -> $RESULTS"
246
+ python3 Tests/bluscriptcp/needle_score.py \
247
+ --needle-dir "$NEEDLE_DIR" --csv-dir "$CSV_DIR" --T "$T" --blocks "$BLOCKS" \
248
+ --model "$MODEL_KEY" --cache "$CACHE" --factor "$S" \
249
+ --head-dim "$HEAD_DIM" --rope-theta "$ROPE_THETA" --out "$RESULTS" \
250
+ --p-fracs "$(tr ' ' '\n' <<< "$VARIANTS" | grep '^p' | paste -sd,)"
251
+ done
252
+ done
253
+
254
+ log "SWEEP DONE -> $RESULTS (CSVs in $CSV_DIR, markers in $STATE_DIR)"