dragonlimited commited on
Commit
546e98c
·
verified ·
1 Parent(s): 8f92fd4

Add DragonCode family pretraining marimo notebook (方案A data source; token from env)

Browse files
Files changed (1) hide show
  1. notebook.py +465 -0
notebook.py ADDED
@@ -0,0 +1,465 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import marimo
2
+
3
+ __generated_with = "0.24.0"
4
+ app = marimo.App(width="medium", auto_download=["html"])
5
+
6
+
7
+ @app.cell
8
+ def _():
9
+ # =============================================================== #
10
+ # A) DragonCode 方案A — infra: imports, auth, helpers, data source
11
+ # =============================================================== #
12
+ # 規則:完全跳過 DragonCode-Tokenized-Pretrain(視為垃圾);全部 token
13
+ # streaming FineWeb-Edu -> SlimPajama -> The-Pile;不下載完整檔案到本機。
14
+ import os, sys, time, json, math, shutil, tempfile, random, logging
15
+ from dataclasses import dataclass, field, asdict
16
+ from collections import deque
17
+ from typing import Optional
18
+ import numpy as np
19
+ import torch
20
+ import torch.nn.functional as F
21
+
22
+ # ---- auth (source from env; never hard-coded, to avoid leaking on a
23
+ # public repo). Set HF_TOKEN in your Molab env before running: either
24
+ # export it in the environment, or uncomment the next line and paste
25
+ # your own token. ------------------------------------------------ #
26
+ DRAGON_HF_TOKEN = os.environ.get("HF_TOKEN", "") or ""
27
+ os.environ["HF_TOKEN"] = DRAGON_HF_TOKEN
28
+ os.environ["HF_HOME"] = "/home/marimo/.cache/huggingface"
29
+ if not DRAGON_HF_TOKEN:
30
+ print("⚠️ 請先設定 HF_TOKEN 環境變數(例如 os.environ['HF_TOKEN']='hf_...')再執行。")
31
+ print(" 未設定 token 無法 load dataset / push checkpoint 至 HF。")
32
+
33
+ TOKENIZER_REPO = "bigcode/starcoder2-3b"
34
+ VOCAB_SIZE = 49152
35
+ MAX_SEQ_LEN = 2048
36
+
37
+ logger = logging.getLogger("dragoncode")
38
+ if not logger.handlers:
39
+ _h = logging.StreamHandler()
40
+ _h.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))
41
+ logger.addHandler(_h)
42
+ logger.setLevel(logging.INFO)
43
+
44
+ # ---- HF helpers ------------------------------------------------------- #
45
+ def _hf_api():
46
+ from huggingface_hub import HfApi
47
+ return HfApi(token=DRAGON_HF_TOKEN)
48
+
49
+ def _run_with_retry(fn, tries=8, base_wait=4.0, max_wait=120.0, label="op"):
50
+ last = None
51
+ for attempt in range(1, tries + 1):
52
+ try:
53
+ return fn()
54
+ except Exception as e:
55
+ last = e
56
+ wait = min(base_wait * (2 ** (attempt - 1)), max_wait)
57
+ logger.warning("[%s] %d/%d failed: %s — retry in %.0fs", label, attempt, tries, e, wait)
58
+ time.sleep(wait)
59
+ raise RuntimeError(f"[{label}] failed after {tries} attempts: {last}")
60
+
61
+ def _seed_everything(seed):
62
+ random.seed(seed); np.random.seed(seed)
63
+ torch.manual_seed(seed); torch.cuda.manual_seed_all(seed)
64
+
65
+ # ---- tokenizer -------------------------------------------------------- #
66
+ _tok = None
67
+ def _get_tokenizer():
68
+ global _tok
69
+ if _tok is not None:
70
+ return _tok
71
+ from transformers import AutoTokenizer
72
+ _tok = AutoTokenizer.from_pretrained(TOKENIZER_REPO, token=DRAGON_HF_TOKEN, trust_remote_code=True)
73
+ if _tok.pad_token is None:
74
+ _tok.pad_token = _tok.eos_token
75
+ if _tok.pad_token_id is None:
76
+ _tok.pad_token_id = _tok.eos_token_id
77
+ logger.info("Tokenizer %s (vocab=%d)", TOKENIZER_REPO, _tok.vocab_size)
78
+ return _tok
79
+
80
+ # ---- model / tier registry -------------------------------------------- #
81
+ def _build_model_config(hidden, layers, ffn, heads, kv_heads=None, name="DragonCode-150M"):
82
+ from transformers import LlamaConfig
83
+ return LlamaConfig(
84
+ vocab_size=VOCAB_SIZE, hidden_size=hidden, intermediate_size=ffn,
85
+ num_hidden_layers=layers, num_attention_heads=heads,
86
+ num_key_value_heads=kv_heads or heads, max_position_embeddings=MAX_SEQ_LEN,
87
+ rope_theta=10000.0, rms_norm_eps=1e-5, tie_word_embeddings=False,
88
+ hidden_act="silu", _name_or_path=f"DragonLimited/{name}",
89
+ )
90
+
91
+ @dataclass
92
+ class TierSpec:
93
+ name: str; size_str: str; hidden: int; layers: int; ffn: int
94
+ heads: int; kv_heads: Optional[int]; chinchilla_tokens: int
95
+
96
+ # Chinchilla 20x 鎖死: 150M->3.0B, 387M->7.74B, 787M->15.74B, 1.2B->24B, 2.4B->48B
97
+ def _tier_specs():
98
+ return {
99
+ "150M": TierSpec("DragonCode-150M", "150M", 768, 12, 3072, 12, None, 3_000_000_000),
100
+ "387M": TierSpec("DragonCode-387M", "387M", 1024, 22, 4096, 16, None, 7_740_000_000),
101
+ "787M": TierSpec("DragonCode-787M", "787M", 1280, 30, 5120, 16, 16, 15_740_000_000),
102
+ "1.2B": TierSpec("DragonCode-1.2B", "1.2B", 1536, 34, 6144, 24, 24, 24_000_000_000),
103
+ "2.4B": TierSpec("DragonCode-2.4B", "2.4B", 2048, 38, 8192, 32, 32, 48_000_000_000),
104
+ }
105
+
106
+ def _hf_repos():
107
+ return {
108
+ "150M": "DragonLimited/DragonCode-150M",
109
+ "387M": "DragonLimited/DragonCode-387M",
110
+ "787M": "DragonLimited/DragonCode-787M",
111
+ "1.2B": "DragonLimited/DragonCode-1.2B",
112
+ "2.4B": "DragonLimited/DragonCode-2.4B",
113
+ "family": "DragonLimited/DragonCode-Family",
114
+ }
115
+
116
+ # ---- 方案A data source: 純外部 streaming,絕不讀 Tokenized-Pretrain ---- #
117
+ DATA_SOURCES = [ # strict fallback 優先序
118
+ "HuggingFaceFW/fineweb-edu",
119
+ "cerebras/SlimPajama-627B",
120
+ "EleutherAI/the_pile",
121
+ ]
122
+
123
+ def _tokenize_string(text, tok):
124
+ ids = tok.encode(text)
125
+ if hasattr(ids, "ids"):
126
+ ids = ids.ids
127
+ return list(ids)
128
+
129
+ class StreamingCorpus:
130
+ """純外部 HF streaming 數據源(方案A)。單向前向 pass,唔讀 Tokenized repo。"""
131
+ def __init__(self, token, seq_len, budget_tokens, offset_tokens=0):
132
+ self.token = token; self.seq = seq_len; self.budget = budget_tokens
133
+ self.off = offset_tokens; self.data_used = 0
134
+
135
+ def __iter__(self):
136
+ from datasets import load_dataset
137
+ buf = deque(); cursor = self.off; need = self.budget
138
+ for src in DATA_SOURCES:
139
+ if cursor >= need:
140
+ return
141
+ try:
142
+ ds = load_dataset(src, split="train", streaming=True, token=self.token)
143
+ logger.info("Streaming %s (cursor=%d budget=%d)", src, cursor, need)
144
+ except Exception as e:
145
+ logger.warning("source %s failed (%s); next", src, repr(e)); continue
146
+ try:
147
+ for row in ds:
148
+ txt = row.get("text") or ""
149
+ for v in _tokenize_string(txt, _get_tokenizer()):
150
+ cursor += 1
151
+ if cursor <= self.off:
152
+ continue
153
+ buf.append(int(v))
154
+ while len(buf) >= self.seq:
155
+ window = [buf.popleft() for _ in range(self.seq)]
156
+ self.data_used = cursor
157
+ yield torch.tensor(window, dtype=torch.long), cursor
158
+ if cursor >= need:
159
+ return
160
+ except Exception as e:
161
+ logger.warning("source %s mid-stream (%s); next", src, repr(e)); continue
162
+ logger.warning("All external sources exhausted at cursor %d (budget %d)", cursor, need)
163
+
164
+ def _make_batches(gen, batch_size, seq_len):
165
+ batch = []
166
+ for token_tensor, tok_cursor in gen:
167
+ batch.append(token_tensor)
168
+ if len(batch) == batch_size:
169
+ x = torch.stack(batch)[:, :-1].contiguous(); y = torch.stack(batch)[:, 1:].contiguous()
170
+ yield x, y, tok_cursor, True; batch = []
171
+ if batch:
172
+ x = torch.stack(batch)[:, :-1].contiguous(); y = torch.stack(batch)[:, 1:].contiguous()
173
+ yield x, y, tok_cursor, False
174
+
175
+
176
+ return (
177
+ DRAGON_HF_TOKEN,
178
+ MAX_SEQ_LEN,
179
+ StreamingCorpus,
180
+ json,
181
+ logger,
182
+ math,
183
+ os,
184
+ shutil,
185
+ tempfile,
186
+ time,
187
+ torch,
188
+ )
189
+
190
+
191
+ @app.cell
192
+ def _(DRAGON_HF_TOKEN, json, logger, os, shutil, tempfile, torch):
193
+ # =============================================================== #
194
+ # B) DragonCode 方案A — HF checkpoint push / resume
195
+ # =============================================================== #
196
+ # checkpoint 直接推送至該 tier 嘅 HF public model repo;中斷後 resume
197
+ # 由 HF 拉取,完全唔依賴 Molab 本地儲存。
198
+
199
+ class _TrainState:
200
+ def __init__(self, step=0, tokens_seen=0, epoch=0, global_seed=0,
201
+ best_val_loss=float("inf"), metadata=None):
202
+ self.step = step; self.tokens_seen = tokens_seen; self.epoch = epoch
203
+ self.global_seed = global_seed; self.best_val_loss = best_val_loss
204
+ self.metadata = metadata or {}
205
+ def to_dict(self):
206
+ return {"step": self.step, "tokens_seen": self.tokens_seen,
207
+ "epoch": self.epoch, "global_seed": self.global_seed,
208
+ "best_val_loss": self.best_val_loss, "metadata": self.metadata}
209
+
210
+
211
+ def _find_latest_hf_checkpoint(repo):
212
+ from huggingface_hub import list_repo_commits, hf_hub_download
213
+ try:
214
+ commits = _run_with_retry(lambda: list_repo_commits(repo, token=DRAGON_HF_TOKEN), label="list-ckpt")
215
+ except Exception as e:
216
+ logger.warning("list commits %s: %s", repo, e); return None
217
+ for c in commits:
218
+ sha = c.commit_id
219
+ try:
220
+ tree = _hf_api().repo_info(repo, revision=sha, repo_type="model").siblings
221
+ names = [t.rfilename for t in tree]
222
+ if "train_state.json" in names:
223
+ st = hf_hub_download(repo, "train_state.json", revision=sha, token=DRAGON_HF_TOKEN)
224
+ with open(st) as f: state = json.load(f)
225
+ return sha, state
226
+ except Exception:
227
+ continue
228
+ return None
229
+
230
+
231
+ def _push_checkpoint_hf(repo, ckpt_paths, commit_message, size_cls):
232
+ api = _hf_api(); api.create_repo(repo, repo_type="model", exist_ok=True, private=False)
233
+ tmpdir = tempfile.mkdtemp(prefix="dc_ck_")
234
+ dst = os.path.join(tmpdir, "checkpoints", f"DragonCode-{size_cls}")
235
+ for name, src in ckpt_paths.items():
236
+ if os.path.isdir(src):
237
+ shutil.copytree(src, os.path.join(dst, name), dirs_exist_ok=True)
238
+ else:
239
+ os.makedirs(os.path.dirname(os.path.join(dst, name)), exist_ok=True)
240
+ shutil.copy(src, os.path.join(dst, name))
241
+ def _upload():
242
+ api.upload_folder(folder_path=os.path.join(tmpdir, "checkpoints"), repo_id=repo,
243
+ repo_type="model", commit_message=commit_message,
244
+ allow_duplicate_filename=True)
245
+ _run_with_retry(_upload, label="push-ckpt", tries=6)
246
+ shutil.rmtree(tmpdir, ignore_errors=True)
247
+ logger.info("Pushed checkpoint -> %s (%s)", repo, commit_message)
248
+
249
+
250
+ def _load_local_ckpt(path, model, optimizer, scheduler):
251
+ ck = torch.load(path, map_location="cpu", weights_only=False)
252
+ if model is not None: model.load_state_dict(ck["model"])
253
+ if optimizer is not None and ck.get("optimizer") is not None: optimizer.load_state_dict(ck["optimizer"])
254
+ if scheduler is not None and ck.get("lr_scheduler") is not None: scheduler.load_state_dict(ck["lr_scheduler"])
255
+ return ck
256
+
257
+
258
+ return
259
+
260
+
261
+ @app.cell
262
+ def _(
263
+ DRAGON_HF_TOKEN,
264
+ MAX_SEQ_LEN,
265
+ StreamingCorpus,
266
+ json,
267
+ logger,
268
+ math,
269
+ os,
270
+ shutil,
271
+ tempfile,
272
+ time,
273
+ torch,
274
+ ):
275
+ # =============================================================== #
276
+ # C) DragonCode 方案A — pretrain 主迴圈(主執行緒,無後台thread)
277
+ # =============================================================== #
278
+ # 全部 CPU/GPU 繁重邏輯喺 Notebook Cell 主執行緒循序執行(Molab 合規);
279
+ # 不開 daemon / 後台業務 thread。
280
+
281
+ def _lr_warmup(step, warm, total):
282
+ if step < warm: return step / max(1.0, warm)
283
+ return max(0.0, 1.0 - (step - warm) / max(1, total - warm))
284
+
285
+
286
+ def _try_resume(model, optimizer, scheduler, kwargs, repo, size_cls, step, tokens_seen, dev):
287
+ if not kwargs.get("resume", True):
288
+ return model, optimizer, scheduler, step, tokens_seen, 0
289
+ local_dir = kwargs.get("local_ckpt_dir", "/home/marimo/DragonCode/checkpoints")
290
+ ck = os.path.join(local_dir, f"DragonCode-{size_cls}", "checkpoint-latest.pt")
291
+ loaded = False
292
+ if os.path.exists(ck):
293
+ logger.info("Resume LOCAL %s", ck)
294
+ data = _load_local_ckpt(ck, model, optimizer, scheduler)
295
+ st = data["train_state"]; step = st["step"]; tokens_seen = st["tokens_seen"]
296
+ loaded = True
297
+ else:
298
+ hit = _find_latest_hf_checkpoint(repo)
299
+ if hit:
300
+ sha, st = hit
301
+ logger.info("Resume HF commit %s (step=%s tokens=%s)", sha, st.get("step"), st.get("tokens_seen"))
302
+ tmp = tempfile.mkdtemp(prefix="dc_rs_")
303
+ from huggingface_hub import snapshot_download
304
+ _run_with_retry(lambda: snapshot_download(repo, revision=sha, token=DRAGON_HF_TOKEN, local_dir=tmp),
305
+ label="snap-resume")
306
+ ck = os.path.join(tmp, "checkpoints", f"DragonCode-{size_cls}", "checkpoint-latest.pt")
307
+ if os.path.exists(ck):
308
+ data = _load_local_ckpt(ck, model, optimizer, scheduler)
309
+ stt = data["train_state"]; step = stt["step"]; tokens_seen = stt["tokens_seen"]
310
+ loaded = True
311
+ shutil.rmtree(tmp, ignore_errors=True)
312
+ else:
313
+ logger.info("No checkpoint found; starting fresh (%s)", kwargs.get("tier_name", "?"))
314
+ if loaded:
315
+ _seed_everything(kwargs.get("seed", 42) + step)
316
+ return model, optimizer, scheduler, step, tokens_seen, tokens_seen
317
+
318
+
319
+ def _save_and_push(model, optimizer, scheduler, step, tokens_seen, epoch, seed, kwargs, repo, size_cls, tier):
320
+ local_dir = kwargs.get("local_ckpt_dir", "/home/marimo/DragonCode/checkpoints")
321
+ cdir = os.path.join(local_dir, f"DragonCode-{size_cls}"); os.makedirs(cdir, exist_ok=True)
322
+ ckpt_path = os.path.join(cdir, "checkpoint-latest.pt")
323
+ st = _TrainState(step=step, tokens_seen=tokens_seen, epoch=epoch, global_seed=seed,
324
+ best_val_loss=float("inf"), metadata={"tier": tier.size_cls, "name": tier.name})
325
+ torch.save({"model": model.state_dict(), "optimizer": optimizer.state_dict(),
326
+ "lr_scheduler": scheduler.state_dict(), "train_state": st.to_dict(),
327
+ "format": "dragoncode-checkpoint-v1"}, ckpt_path)
328
+ sj = os.path.join(cdir, "train_state.json")
329
+ with open(sj, "w") as f: json.dump(st.to_dict(), f, indent=2)
330
+ _push_checkpoint_hf(repo, {"checkpoint-latest.pt": ckpt_path, "train_state.json": sj},
331
+ f"[{tier.name}] checkpoint step={step} tokens={tokens_seen:,}", size_cls)
332
+ logger.info("Saved+push step=%d tokens=%d", step, tokens_seen)
333
+
334
+
335
+ def train_pretrain(tier, kwargs):
336
+ """主執行緒訓練單一 tier 至 Chinchilla 預算(純外部 streaming)。"""
337
+ torch.backends.cuda.matmul.allow_tf32 = False
338
+ torch.backends.cudnn.allow_tf32 = False
339
+ dev = kwargs["device"]; seed = kwargs.get("seed", 42)
340
+ seq_len = kwargs.get("seq_len", MAX_SEQ_LEN); batch_size = kwargs.get("batch_size", 8)
341
+ grad_acc = kwargs.get("grad_accum", 1); lr = kwargs.get("lr", 3e-4)
342
+ warmup_frac = kwargs.get("warmup_frac", 0.01); max_grad_norm = kwargs.get("max_grad_norm", 1.0)
343
+ save_every = kwargs.get("save_every_steps", 2000); log_every = kwargs.get("log_every", 50)
344
+ size_cls = tier.size_str; repo = kwargs["hf_repo"]; token_budget = tier.chinchilla_tokens
345
+ _seed_everything(seed); _get_tokenizer()
346
+
347
+ from transformers import LlamaConfig, LlamaForCausalLM
348
+ cfg = _build_model_config(tier.hidden, tier.layers, tier.ffn, tier.heads, tier.kv_heads, name=tier.name)
349
+ cfg.attn_implementation = "flash_attention_2"
350
+ model = LlamaForCausalLM(cfg).to(dev).to(torch.bfloat16)
351
+ n_params = sum(p.numel() for p in model.parameters())
352
+ logger.info("Pretrain %s on %s — %.1fM params, budget %d tokens", tier.name, dev, n_params/1e6, token_budget)
353
+
354
+ optimizer = torch.optim.AdamW(model.parameters(), lr=lr, betas=(0.9, 0.95), weight_decay=0.1, fused=True)
355
+ tokens_per_step_eff = batch_size * grad_acc * (seq_len - 1)
356
+ total_steps_est = math.ceil(token_budget / tokens_per_step_eff)
357
+ scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=lambda s: _lr_warmup(s, warmup_frac * total_steps_est, total_steps_est))
358
+
359
+ # 方案A:純外部 streaming
360
+ src = StreamingCorpus(DRAGON_HF_TOKEN, seq_len, token_budget, offset_tokens=0)
361
+ logger.info("數據源[方案A]: 跳過 DragonCode-Tokenized-Pretrain; 全部 token 流式讀取 FineWeb-Edu → SlimPajama → The-Pile. Budget=%d", token_budget)
362
+
363
+ step = 0; tokens_seen = 0; epoch = 0
364
+ model, optimizer, scheduler, step, tokens_seen, offset_toks = _try_resume(
365
+ model, optimizer, scheduler, kwargs, repo, size_cls, step, tokens_seen, dev)
366
+ if offset_toks:
367
+ src.off = offset_toks
368
+ logger.info("Resume offset=%d tokens(純外部流,不含舊shard)", offset_toks)
369
+
370
+ model.train(); gen = _make_batches(iter(src), batch_size, seq_len)
371
+ prog_start = time.time()
372
+ while tokens_seen < token_budget:
373
+ local_steps = 0
374
+ for x, y, tok_cursor, is_full in gen:
375
+ x = x.to(dev); y = y.to(dev)
376
+ out = model(x, labels=y); loss = out.loss / grad_acc; loss.backward()
377
+ local_steps += 1
378
+ tokens_seen = min(tok_cursor, token_budget)
379
+ cond = (is_full and (step + 1) % grad_acc == 0) or (not is_full)
380
+ if cond:
381
+ torch.nn.utils.clip_grad_norm_(model.parameters(), max_grad_norm)
382
+ optimizer.step(); scheduler.step(); optimizer.zero_grad(set_to_none=True)
383
+ step += 1
384
+ if step % log_every == 0 and step > 0:
385
+ el = time.time() - prog_start; tps = tokens_seen / max(el, 1e-9)
386
+ logger.info("[%s] step=%d tokens=%d/%d (%.2f%%) loss=%.4f lr=%.2e tps=%.0f",
387
+ tier.name, step, tokens_seen, token_budget,
388
+ 100.0 * tokens_seen / token_budget, out.loss.item() * grad_acc,
389
+ scheduler.get_last_lr()[0], tps)
390
+ if step % save_every == 0 and step > 0:
391
+ _save_and_push(model, optimizer, scheduler, step, tokens_seen, epoch, seed, kwargs, repo, size_cls, tier)
392
+ if tokens_seen >= token_budget: break
393
+ if local_steps > 10_000_000: break
394
+ if tokens_seen < token_budget:
395
+ logger.warning("All 方案A external sources exhausted at token %d (budget %d) — stop %s.",
396
+ tokens_seen, token_budget, tier.name)
397
+ break
398
+ _save_and_push(model, optimizer, scheduler, step, tokens_seen, epoch, seed, kwargs, repo, size_cls, tier)
399
+ logger.info("PRETRAIN DONE %s: %d/%d tokens", tier.name, tokens_seen, token_budget)
400
+ return {"model": model, "step": step, "tokens_seen": tokens_seen}
401
+
402
+
403
+ return (train_pretrain,)
404
+
405
+
406
+ @app.cell
407
+ def _(DRAGON_HF_TOKEN, torch, train_pretrain):
408
+ # =============================================================== #
409
+ # D) DragonCode 方案A — runner:偵測下一個未���成 tier
410
+ # =============================================================== #
411
+
412
+ TIER_ORDER = ["150M", "387M", "787M", "1.2B", "2.4B"]
413
+
414
+
415
+ def hf_has_model(repo):
416
+ from huggingface_hub import list_repo_files
417
+ try:
418
+ files = list_repo_files(repo, repo_type="model", token=DRAGON_HF_TOKEN)
419
+ return any("config.json" in f or f.endswith(".safetensors") for f in files)
420
+ except Exception:
421
+ return False
422
+
423
+
424
+ def next_tier():
425
+ for t in TIER_ORDER:
426
+ if not hf_has_model(_hf_repos()[t]):
427
+ return t
428
+ return None
429
+
430
+
431
+ def run_pipeline(tier=None, device="cuda", dry_run=False):
432
+ """喺主執行緒訓練下一個未完成 tier(或指定 tier)。"""
433
+ if tier is None:
434
+ tier = next_tier()
435
+ if tier is None:
436
+ print("[RUN] 所有 tier 都已有公開 weights。無需再做。")
437
+ return {"tier": None, "tokens_seen": None}
438
+ spec = _tier_specs()[tier]
439
+ kwargs = dict(tier_name=spec.name, hf_repo=_hf_repos()[tier], device=device, seed=42)
440
+ print(f"[RUN] PRETRAIN {spec.name} — budget {spec.chinchilla_tokens/1e9:.2f}B tokens (方案A外部streaming)")
441
+ if dry_run:
442
+ print("[RUN] dry_run:", spec.name); return {"tier": tier, "tokens_seen": 0}
443
+ out = train_pretrain(spec, kwargs)
444
+ print(f"[RUN] PRETRAIN {tier} DONE: tokens_seen={out['tokens_seen']}")
445
+ torch.cuda.empty_cache()
446
+ return {"tier": tier, "tokens_seen": out["tokens_seen"]}
447
+
448
+
449
+ return (run_pipeline,)
450
+
451
+
452
+ @app.cell
453
+ def _(run_pipeline):
454
+ # =============================================================== #
455
+ # E) DragonCode 方案A — 手動啟動 cell
456
+ # =============================================================== #
457
+ # 請手動點擊呢個 cell 嘅 Run (▶) 開始 150M 預訓練。
458
+ # 若中斷,重新 Run 即由 HF checkpoint resume(唔依賴 Molab 本地)。
459
+ run_pipeline(device="cuda")
460
+
461
+ return
462
+
463
+
464
+ if __name__ == "__main__":
465
+ app.run()