Cion-lab commited on
Commit
120ad22
·
verified ·
1 Parent(s): 001ecbf

Upload kernels/phase4_session.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. kernels/phase4_session.py +50 -23
kernels/phase4_session.py CHANGED
@@ -24,11 +24,15 @@ import sys
24
  import time
25
 
26
  WORK = "/kaggle/working"
 
27
  MIX_REPO = "Cion-lab/ounce100m-mix-v1"
28
  CKPT_REPO = "Cion-lab/ounce100m-ckpt"
29
 
30
- # The frozen recipe (D-011, D-012). Anything here that a future session might want to change is a
31
- # change to the run itself, so it is asserted against the trainer's own numbers below rather than trusted.
 
 
 
32
  SEQ_LEN = 1024
33
  MICRO_BATCH = 4
34
  ACCUM = 32
@@ -51,24 +55,38 @@ def sh(argv, label, timeout=None, env=None):
51
  e["PYTHONPATH"] = WORK
52
  e.update(env or {})
53
  t0 = time.time()
54
- try:
55
- p = subprocess.run(argv, cwd=WORK, capture_output=True, text=True, timeout=timeout, env=e)
56
- except subprocess.TimeoutExpired as ex:
57
- print(f"{label} TIMEOUT after {time.time() - t0:.0f}s", flush=True)
58
- return -9, (ex.stdout or b"").decode()[-200000:], (ex.stderr or b"").decode()[-4000:]
59
- out, err = p.stdout or "", p.stderr or ""
60
- for line in out.splitlines():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  print(" |", line[:300], flush=True)
62
- if p.returncode != 0:
63
- lines = err.splitlines()
64
- keep = []
65
- for i, ln in enumerate(lines): # first traceback, not a wrapper's tail (E-023)
66
- if "Traceback" in ln or "Error" in ln:
67
- keep = lines[max(0, i - 2):i + 30]
68
- break
69
- print(" STDERR_HEAD:\n" + "\n".join(keep or lines[:45])[-4000:], flush=True)
70
- print(f"{label}_RC {p.returncode} seconds {round(time.time() - t0, 1)}", flush=True)
71
- return p.returncode, out
72
 
73
 
74
  def fetch(rev, want):
@@ -88,19 +106,28 @@ def fetch(rev, want):
88
  def plan(start_step, free_gb):
89
  """Largest whole number of checkpoint intervals that fits this session's remaining hours."""
90
  seconds_per_ckpt = CKPT_EVERY * TOKENS_PER_STEP / PLANNING_TOK_PER_S
91
- usable = min(SESSION_GPU_HOURS, max(0.0, QUOTA_LEFT_HOURS - RESERVE_HOURS))
 
 
 
92
  n = int(usable * 3600.0 // seconds_per_ckpt)
93
  remaining_intervals = max(0, (HORIZON_STEPS - start_step + CKPT_EVERY - 1) // CKPT_EVERY)
94
  n = min(n, remaining_intervals)
95
- stop = min(HORIZON_STEPS, start_step + n * CKPT_EVERY) if n else 0
 
 
 
 
96
  return {"start_step": start_step, "horizon_steps": HORIZON_STEPS, "ckpt_every": CKPT_EVERY,
97
  "tokens_per_step": TOKENS_PER_STEP, "planning_tok_per_s": PLANNING_TOK_PER_S,
98
  "hours_per_checkpoint_interval": round(seconds_per_ckpt / 3600.0, 3),
99
  "session_gpu_hours": SESSION_GPU_HOURS, "quota_left_hours": QUOTA_LEFT_HOURS,
100
  "reserve_hours": RESERVE_HOURS, "usable_hours": round(usable, 3),
101
  "intervals_this_session": n, "stop_after_steps": stop,
102
- "tokens_at_stop": stop * TOKENS_PER_STEP,
103
- "pct_of_run_at_stop": round(100.0 * stop * TOKENS_PER_STEP / TOKENS, 2),
 
 
104
  "free_gb_before_start": free_gb}
105
 
106
 
 
24
  import time
25
 
26
  WORK = "/kaggle/working"
27
+ T0 = time.time() # session start, so every step log line can say how far into the run it is
28
  MIX_REPO = "Cion-lab/ounce100m-mix-v1"
29
  CKPT_REPO = "Cion-lab/ounce100m-ckpt"
30
 
31
+ # The frozen recipe (D-011, D-012, D-015). TOKENS_PER_STEP and HORIZON_STEPS below are re-derived here
32
+ # with the same integer arithmetic the trainer uses (`seqs_per_step = micro*world*accum`,
33
+ # `steps_planned = int(tokens/tokens_per_step)` in train_ounce100m.py), so the two agree by construction
34
+ # rather than by copy -- if that formula ever changes in the trainer, change it here too and the mix-size
35
+ # check below will disagree.
36
  SEQ_LEN = 1024
37
  MICRO_BATCH = 4
38
  ACCUM = 32
 
55
  e["PYTHONPATH"] = WORK
56
  e.update(env or {})
57
  t0 = time.time()
58
+ # Streamed, not buffered: capture_output=True would hold an eleven-hour session's loss curve until
59
+ # the child exited, so nobody could see the run going wrong while it was still running (a review
60
+ # finding; §5 requires monitoring loss on every wake). stderr is merged into stdout so a torchrun
61
+ # child's traceback arrives in the same stream instead of a separate tail that hides it (E-023).
62
+ import threading
63
+ p = subprocess.Popen(argv, cwd=WORK, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
64
+ text=True, bufsize=1, env=e)
65
+ state = {"killed": False}
66
+
67
+ def _kill():
68
+ state["killed"] = True
69
+ try:
70
+ p.kill()
71
+ except Exception:
72
+ pass
73
+
74
+ timer = threading.Timer(timeout, _kill) if timeout else None
75
+ if timer:
76
+ timer.daemon = True
77
+ timer.start()
78
+ lines = []
79
+ for line in (p.stdout or []):
80
+ lines.append(line.rstrip("\n"))
81
  print(" |", line[:300], flush=True)
82
+ rc = p.wait()
83
+ if timer:
84
+ timer.cancel()
85
+ if state["killed"]:
86
+ rc = -9
87
+ print(f"{label}_RC {rc}{' TIMEOUT' if state['killed'] else ''} seconds "
88
+ f"{round(time.time() - t0, 1)} elapsed {round(time.time() - T0)}", flush=True)
89
+ return rc, "\n".join(lines)[-400000:]
 
 
90
 
91
 
92
  def fetch(rev, want):
 
106
  def plan(start_step, free_gb):
107
  """Largest whole number of checkpoint intervals that fits this session's remaining hours."""
108
  seconds_per_ckpt = CKPT_EVERY * TOKENS_PER_STEP / PLANNING_TOK_PER_S
109
+ # The reserve comes off the total, not off one term: `min(session, quota - reserve)` let an 11 h
110
+ # session plan 11 h of training and leave nothing for the code fetch, the 2.3 GB mix download, the
111
+ # push/verify cycles and the end-of-run evaluation (review finding).
112
+ usable = max(0.0, min(SESSION_GPU_HOURS, QUOTA_LEFT_HOURS) - RESERVE_HOURS)
113
  n = int(usable * 3600.0 // seconds_per_ckpt)
114
  remaining_intervals = max(0, (HORIZON_STEPS - start_step + CKPT_EVERY - 1) // CKPT_EVERY)
115
  n = min(n, remaining_intervals)
116
+ target = start_step + n * CKPT_EVERY
117
+ # The trainer refuses a --stop-after-steps that is not a whole multiple of its own interval, and
118
+ # 3,814 is not one (3814 % 381 = 385). So the session that would cross the horizon is told to run
119
+ # to it instead -- otherwise the final leg of the run could never be launched.
120
+ stop = 0 if target >= HORIZON_STEPS else min((HORIZON_STEPS // CKPT_EVERY) * CKPT_EVERY, target)
121
  return {"start_step": start_step, "horizon_steps": HORIZON_STEPS, "ckpt_every": CKPT_EVERY,
122
  "tokens_per_step": TOKENS_PER_STEP, "planning_tok_per_s": PLANNING_TOK_PER_S,
123
  "hours_per_checkpoint_interval": round(seconds_per_ckpt / 3600.0, 3),
124
  "session_gpu_hours": SESSION_GPU_HOURS, "quota_left_hours": QUOTA_LEFT_HOURS,
125
  "reserve_hours": RESERVE_HOURS, "usable_hours": round(usable, 3),
126
  "intervals_this_session": n, "stop_after_steps": stop,
127
+ "runs_to_horizon_this_session": stop == 0 and n > 0,
128
+ "tokens_at_stop": (HORIZON_STEPS if stop == 0 else stop) * TOKENS_PER_STEP,
129
+ "pct_of_run_at_stop": round(100.0 * (HORIZON_STEPS if stop == 0 else stop)
130
+ * TOKENS_PER_STEP / TOKENS, 2),
131
  "free_gb_before_start": free_gb}
132
 
133