mirror ops/ so the kept code is self-contained
Browse files- ops/SESSION_LOOP.md +117 -0
- ops/watch_ckpt.sh +38 -0
ops/SESSION_LOOP.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SESSION_LOOP β unattended Phase 4 session turnover
|
| 2 |
+
|
| 3 |
+
You are a fresh agent session, probably at an odd hour, with no conversation history. Your only job is to
|
| 4 |
+
advance the ounce100m main training run by **one** step of this loop. Do nothing else. Read
|
| 5 |
+
`memory/STATE.md` first; it overrides this file if they disagree.
|
| 6 |
+
|
| 7 |
+
**Standing instruction from the user (2026-09-20T14:02Z):** run the sleep β wake β check β launch loop across
|
| 8 |
+
all remaining sessions and do not start any other work unless it is an emergency as defined in Β§5.
|
| 9 |
+
|
| 10 |
+
## 1. Check (always do this, ~30 s, no GPU)
|
| 11 |
+
|
| 12 |
+
```
|
| 13 |
+
curl -s "https://huggingface.co/api/datasets/Cion-lab/ounce100m-ckpt/commits/main"
|
| 14 |
+
curl -sL "https://huggingface.co/datasets/Cion-lab/ounce100m-ckpt/resolve/main/latest.json"
|
| 15 |
+
curl -sL "https://huggingface.co/datasets/Cion-lab/ounce100m-ckpt/resolve/main/ckpt/checkpoint-<step>/cursor.json"
|
| 16 |
+
curl -sL "https://huggingface.co/datasets/Cion-lab/ounce100m-ckpt/resolve/main/ckpt/checkpoint-<step>/trainer_state.json"
|
| 17 |
+
```
|
| 18 |
+
|
| 19 |
+
Pass criteria, all of them:
|
| 20 |
+
|
| 21 |
+
| Check | Expected |
|
| 22 |
+
|---|---|
|
| 23 |
+
| head commit | `latest -> N`, and `ckpt/checkpoint-N` exists 7β10 s earlier |
|
| 24 |
+
| `latest.json.step` | equals `N`, and `path_in_repo` is `ckpt/checkpoint-N` |
|
| 25 |
+
| `cursor.json.tokens_consumed` | exactly `N Γ 262,144` |
|
| 26 |
+
| `cursor.json.samples_consumed` | exactly `N Γ 256` |
|
| 27 |
+
| `dataset_files_sha` / `shuffle_perm_sha` | `53df4708526da5c8` / `c6f617b96f502d96` β **unchanged** |
|
| 28 |
+
| `trainer_state.json` | `global_step == N`, `max_steps == 3814`, `save_steps == 127`, loss lower than the previous checkpoint |
|
| 29 |
+
|
| 30 |
+
Also read the live quota (`get_accelerator_quota` β `gpu_quota.time_used`, `total_time_allowed`) and the session
|
| 31 |
+
status for `dodosoomro/ounce100m-p4-sessionK`.
|
| 32 |
+
|
| 33 |
+
## 2. Decide which of the three states you are in
|
| 34 |
+
|
| 35 |
+
- **A session is still running** and the pointer is advancing on the 127 grid β nothing to do. Report the step
|
| 36 |
+
and stop. Do not launch anything.
|
| 37 |
+
- **A session ended and `latest -> 762 Γ k`** for k = 1..4, k < 5 β launch session k+1 (Β§3).
|
| 38 |
+
- **`latest -> 3814`** β the run is finished (Β§4).
|
| 39 |
+
|
| 40 |
+
Session grid: S1 0β762, S2 β1,524, S3 β2,286, S4 β3,048, S5 β3,814. A session that stopped short of its
|
| 41 |
+
boundary (platform kill) is **not** an emergency β the next session resumes from wherever `latest` points,
|
| 42 |
+
because `phase4_session.py` plans from the pointer. Launch the next session with the *remaining* step count it
|
| 43 |
+
would need; if the planner's own arithmetic disagrees, that is an emergency (Β§5).
|
| 44 |
+
|
| 45 |
+
## 3. Launch the next session
|
| 46 |
+
|
| 47 |
+
**The one editable line.** `code/kernels/phase4_bootstrap.py` is the whole kernel body; it pins
|
| 48 |
+
`REV = "c237b478076f68da43dabd3403daa5639286899e"` and `LAUNCHER_SHA = "5fb14bc3β¦"` and fetches
|
| 49 |
+
`kernels/phase4_session.py` at that REV, sha256-asserting it. **Only line 13 changes per session:**
|
| 50 |
+
`os.environ["QUOTA_LEFT_HOURS"] = "<live hours from the Β§1 read>"`. Nothing else β it resumes from
|
| 51 |
+
`latest.json` and needs no other input.
|
| 52 |
+
|
| 53 |
+
1. **Book the ledger row first** (`memory/QUOTA.md`, next row number, before the job starts). Β§8 forbids an
|
| 54 |
+
unbooked GPU job. State the live hours remaining and the CPU-alternative check (Β§3.7: there is none β the
|
| 55 |
+
quantity is 762 steps of real T4 training).
|
| 56 |
+
2. **Confirm session K has actually exited** before launching K+1 β two concurrent GPU sessions would
|
| 57 |
+
double-bill and the second hits the stale-stop guard. Use `get_notebook_session_status`, not the Hub
|
| 58 |
+
pointer, as the liveness answer (`time_reserved` in the quota read is always `0s` and means nothing).
|
| 59 |
+
3. Submit with `mcp__kaggle__save_notebook`, `kernelExecutionType: "SaveAndRunAll"` (this creates *and* runs β
|
| 60 |
+
there is no separate run call; `create_notebook_session` takes only a slug and a machine shape, so it is not
|
| 61 |
+
how a script is uploaded). Send the **full verified envelope** every time; a missing `language` fails with a
|
| 62 |
+
message-less error (E-020):
|
| 63 |
+
|
| 64 |
+
```
|
| 65 |
+
slug = "dodosoomro/ounce100m-p4-sessionK"
|
| 66 |
+
text = <contents of code/kernels/phase4_bootstrap.py, line 13 updated>
|
| 67 |
+
language = "python"
|
| 68 |
+
kernelType = "script"
|
| 69 |
+
machineShape = "GPU" # + enableGpu: true β 2ΓT4. Do not invent a T4-specific shape string.
|
| 70 |
+
kernelExecutionType = "SaveAndRunAll"
|
| 71 |
+
enableGpu = true
|
| 72 |
+
isPrivate = true
|
| 73 |
+
enableInternet = true
|
| 74 |
+
sessionTimeoutSeconds = 25200
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
`newTitle` is **required for a kernel that does not exist yet** β session 2 was refused with
|
| 78 |
+
"New kernels must have a title specified" until it was sent. For a new kernel set it **equal to the slug
|
| 79 |
+
suffix** so the returned `ref` is the slug you asked for; for an **existing** kernel never send a title that
|
| 80 |
+
differs from its current suffix, because `newTitle` rewrites the slug (E-020) and breaks every reference.
|
| 81 |
+
Always poll the returned `ref`, never the requested slug.
|
| 82 |
+
4. **Poll the returned slug, never the requested one**, and record slug + version + kernel id + submission
|
| 83 |
+
time in `ASSETS.md`. Expect ~60-90 s of queue and boot before anything happens; don't poll sooner than 60 s.
|
| 84 |
+
Note that `get_notebook_info` may be denied by API scope β **liveness comes from the quota delta**: read
|
| 85 |
+
`get_accelerator_quota` twice a few minutes apart and see whether `time_used` is advancing (it is, at 1Γ
|
| 86 |
+
wall clock, from the moment the container starts).
|
| 87 |
+
5. Confirm within ~3 minutes that it did **not** exit fast. An exit inside ~2 min is a `REFUSED_TO_START`
|
| 88 |
+
verdict (free space / pointer read / manifest arithmetic) β read the log, and treat it as Β§5 only if the
|
| 89 |
+
reason is not self-evidently transient.
|
| 90 |
+
6. Do **not** babysit it. Write "next check at <time>" into `STATE.md` and stop, or arm one long-lived waiter
|
| 91 |
+
(`watch_ckpt.sh <boundary_step> <seconds>`, or a read-only watcher agent for a whole session leg).
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
## 4. When `latest -> 3814`
|
| 95 |
+
|
| 96 |
+
1. Verify all six final checkpoints and `final/` exist and are byte-verified (compare oids against the local
|
| 97 |
+
manifest the run wrote).
|
| 98 |
+
2. Run **Phase 5 on CPU** (`code/build/publish_model.py`) β 0 GPU hours. Gate 5's clean-room check must pop
|
| 99 |
+
`HF_TOKEN`, not just pass `token=False` (E-045/E-046).
|
| 100 |
+
3. Then Phase 6 per `docs/05-eval-plan.md`, tasks in constitution order, incrementally saved, smoke `--limit 5`
|
| 101 |
+
first, installed **with** deps. Spend whatever GPU hours remain this week; the rest waits for the reset
|
| 102 |
+
(D-019). **Never publish a partial eight-task table as a result.**
|
| 103 |
+
4. Fill the four PENDING blocks in `docs/final-report.md`, set `STATE.md` to COMPLETE, and stop. Do not start
|
| 104 |
+
new work after that.
|
| 105 |
+
|
| 106 |
+
## 5. Emergencies β the only reasons to do more than the above
|
| 107 |
+
|
| 108 |
+
- The pointer **moved backwards**, or a cursor hash changed, or `tokens != step Γ 262,144`. Stop everything;
|
| 109 |
+
this is data corruption. Do not launch another session. Write the evidence into `ERRORS.md` and `STATE.md`
|
| 110 |
+
as a blocker and surface it to the user.
|
| 111 |
+
- Two consecutive launches that exit inside ~2 min for the same reason.
|
| 112 |
+
- Quota cannot fund a full remaining session. Then **park**: do not change configuration to fit (Β§8). Write the
|
| 113 |
+
resume step, the reset instant (2026-09-26T00:00Z, a Saturday) and the exact next command into `STATE.md`.
|
| 114 |
+
- Loss discontinuity beyond noise at a checkpoint boundary, or grad-norm moving with it.
|
| 115 |
+
|
| 116 |
+
Anything else is not an emergency. Refactoring, "improving throughput", re-auditing settled decisions, and
|
| 117 |
+
re-running completed probes are all explicitly out of scope for these turns.
|
ops/watch_ckpt.sh
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# watch_ckpt.sh β print the run's checkpoint cadence, then wait for the NEXT one.
|
| 3 |
+
# Usage: watch_ckpt.sh <expected_step> <max_wait_seconds>
|
| 4 |
+
# Exits 0 early with "ARRIVED <step>" once commits/main shows "latest -> <expected_step>".
|
| 5 |
+
# Exits 0 with "TIMEOUT" if it does not arrive inside the window (that is the signal to investigate).
|
| 6 |
+
set -u
|
| 7 |
+
API="https://huggingface.co/api/datasets/Cion-lab/ounce100m-ckpt/commits/main"
|
| 8 |
+
want="${1:-508}"
|
| 9 |
+
max="${2:-540}"
|
| 10 |
+
deadline=$(( $(date -u +%s) + max ))
|
| 11 |
+
echo "now=$(date -u +%FT%TZ) waiting for latest -> ${want} (up to ${max}s)"
|
| 12 |
+
echo "--- commits so far ---"
|
| 13 |
+
curl -s "$API" | python -c "
|
| 14 |
+
import json,sys
|
| 15 |
+
for c in json.load(sys.stdin)[:14]:
|
| 16 |
+
print(' ', c['date'], '|', (c.get('title') or '').strip())
|
| 17 |
+
"
|
| 18 |
+
while :; do
|
| 19 |
+
head_line=$(curl -s "$API" | python -c "
|
| 20 |
+
import json,sys
|
| 21 |
+
try:
|
| 22 |
+
d=json.load(sys.stdin)
|
| 23 |
+
print(d[0]['date'], '|', (d[0].get('title') or '').strip())
|
| 24 |
+
except Exception as e:
|
| 25 |
+
print('READ_ERROR', e)
|
| 26 |
+
")
|
| 27 |
+
case "$head_line" in
|
| 28 |
+
*"latest -> ${want}"*) echo "now=$(date -u +%FT%TZ) ARRIVED ${want} :: ${head_line}"; exit 0 ;;
|
| 29 |
+
esac
|
| 30 |
+
if echo "$head_line" | grep -q READ_ERROR; then
|
| 31 |
+
echo "now=$(date -u +%FT%TZ) READ_ERROR (transient; retrying): ${head_line}"
|
| 32 |
+
fi
|
| 33 |
+
if [ "$(date -u +%s)" -ge "$deadline" ]; then
|
| 34 |
+
echo "now=$(date -u +%FT%TZ) TIMEOUT latest->${want} not present. head=${head_line}"
|
| 35 |
+
exit 0
|
| 36 |
+
fi
|
| 37 |
+
sleep 45
|
| 38 |
+
done
|