coderofpears commited on
Commit
626bca9
·
verified ·
1 Parent(s): df43f42

Upload folder using huggingface_hub

Browse files
Files changed (2) hide show
  1. modal_train.py +106 -0
  2. train.py +73 -30
modal_train.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ clankerDiffusion — Modal L4 training.
3
+
4
+ Bakes the project code into the image, keeps data + checkpoints on a persistent
5
+ Modal Volume (/vol), injects HF_TOKEN from .env, trains a big hybrid model on an
6
+ L4 (24 GB), and pushes every checkpoint to the HF repo:
7
+
8
+ coderofpears/clankerDiffusion-checkpoints
9
+
10
+ Launch (after `modal token new` on this machine):
11
+ modal run modal_train.py::train_on_l4 --hours 20 --size large
12
+
13
+ Data: stage train.bin + tokenizer.json onto the volume once, e.g.
14
+ modal volume put clanker-vol C:\Users\User\CalcGPU\clankerDiffusion\data /data
15
+ (or it is downloaded from the HF data repo if present there).
16
+ """
17
+ import os
18
+ from modal import App, Image, Volume, Secret, function
19
+
20
+ HERE = os.path.dirname(os.path.abspath(__file__))
21
+ CODE_REPO = "coderofpears/clankerDiffusion-base"
22
+ DATA_REPO = "coderofpears/clankerDiffusion-data"
23
+ CKPT_REPO = "coderofpears/clankerDiffusion-checkpoints"
24
+ DOTENV = os.path.join(HERE, ".env")
25
+
26
+ image = (
27
+ Image.debian_slim()
28
+ .pip_install(
29
+ "torch==2.11.0", index_url="https://download.pytorch.org/whl/cu128",
30
+ "numpy", "tokenizers", "datasets", "safetensors",
31
+ "huggingface_hub", "accelerate", "hf-transfer",
32
+ )
33
+ .env({"HF_HUB_ENABLE_HF_TRANSFER": "1", "TOKENIZERS_PARALLELISM": "false"})
34
+ .add_local_dir(
35
+ HERE, "/root/clanker",
36
+ ignore=["data", "checkpoints", ".venv", "__pycache__", "*.bin",
37
+ "*.pt", ".git", "*.log", "temp_up"],
38
+ )
39
+ )
40
+
41
+ app = App("clanker-diffusion", image=image)
42
+ volume = Volume.from_name("clanker-vol", create_if_missing=True)
43
+
44
+ SIZES = {
45
+ "base": dict(d_model=768, n_layers=12, n_heads=12, d_ff=2048, batch=24),
46
+ "large": dict(d_model=2048, n_layers=24, n_heads=16, d_ff=5504, batch=8),
47
+ "xl": dict(d_model=2560, n_layers=28, n_heads=20, d_ff=6912, batch=4),
48
+ }
49
+
50
+
51
+ @app.function(
52
+ image=image,
53
+ gpu="L4",
54
+ timeout=60 * 60 * 25,
55
+ volumes={"/vol": volume},
56
+ secrets=[Secret.from_dotenv(DOTENV)],
57
+ _allow_background_volume_commits=True,
58
+ )
59
+ def train_on_l4(hours: float = 20.0, ckpt_every: int = 250,
60
+ size: str = "large", batch: int = None):
61
+ import os
62
+ import subprocess
63
+ import sys
64
+
65
+ os.chdir("/root/clanker")
66
+ data_dir, ckpt_dir = "/vol/data", "/vol/checkpoints"
67
+ os.makedirs(data_dir, exist_ok=True)
68
+ os.makedirs(ckpt_dir, exist_ok=True)
69
+
70
+ # ---- ensure data ----
71
+ if not os.path.exists(os.path.join(data_dir, "train.bin")):
72
+ print("[modal] train.bin missing; trying HF download ...")
73
+ try:
74
+ subprocess.run(["hf", "download", DATA_REPO, "train.bin", "-d", data_dir],
75
+ check=True)
76
+ subprocess.run(["hf", "download", DATA_REPO, "tokenizer.json", "-d", data_dir],
77
+ check=True)
78
+ except Exception as e:
79
+ print(f"[modal] HF data download failed: {e}; bailing.")
80
+ raise
81
+
82
+ spec = dict(SIZES.get(size, SIZES["large"]))
83
+ if batch:
84
+ spec["batch"] = batch
85
+
86
+ cmd = [
87
+ sys.executable, "train.py",
88
+ "--hours", str(hours),
89
+ "--batch", str(spec["batch"]),
90
+ "--ckpt-every", str(ckpt_every),
91
+ "--data-dir", data_dir,
92
+ "--ckpt-dir", ckpt_dir,
93
+ "--hf-repo", CKPT_REPO,
94
+ "--d-model", str(spec["d_model"]),
95
+ "--n-layers", str(spec["n_layers"]),
96
+ "--n-heads", str(spec["n_heads"]),
97
+ "--d-ff", str(spec["d_ff"]),
98
+ ]
99
+ print("[modal] launching:", " ".join(cmd), flush=True)
100
+ subprocess.run(cmd, check=True)
101
+ print("[modal] training finished", flush=True)
102
+
103
+
104
+ @app.local_entrypoint()
105
+ def main(hours: float = 20.0, ckpt_every: int = 250, size: str = "large", batch: int = None):
106
+ train_on_l4.remote(hours=hours, ckpt_every=ckpt_every, size=size, batch=batch)
train.py CHANGED
@@ -1,16 +1,19 @@
1
  """
2
- clankerDiffusion — local training loop (RTX 5060 Ti, ~5h budget).
3
 
4
  Hybrid objective (per step, mode chosen at random, p(AR)=0.5):
5
  AR (mode 0): causal LM cross-entropy over the whole window.
6
- DIFF (mode 1): MDLM absorbing-state masked diffusion mask each token
7
  independently with ratio r~U(0,1); reconstruct masked tokens
8
- with bidirectional attention, conditioned on r via the time embed.
9
 
10
- Runs in bf16 (Blackwell), AdamW + cosine LR, grad-clip, checkpoints locally.
11
- Stops after --hours (default 5) of wall-clock time.
 
 
 
12
  """
13
- import os, json, time, argparse
14
  import numpy as np
15
  import torch
16
  import torch.nn as nn
@@ -24,21 +27,38 @@ DATADIR = os.path.join(HERE, "data")
24
  CKPTDIR = os.path.join(HERE, "checkpoints")
25
  os.makedirs(CKPTDIR, exist_ok=True)
26
 
27
- # ---- architecture for 16 GB -------------------------------------------------
28
- CFG = dict(
29
  d_model=768, n_layers=12, n_heads=12, d_ff=2048,
30
  max_len=1024, vocab_size=32768,
31
  )
32
 
33
 
34
- def load_data():
35
- meta = json.load(open(os.path.join(DATADIR, "meta.json")))
36
- arr = np.memmap(os.path.join(DATADIR, "train.bin"), dtype=np.uint16, mode="r")
37
- CFG["vocab_size"] = meta["vocab_size"]
38
- CFG["max_len"] = meta["seq_len"]
39
- print(f"[train] data n_tokens={meta['n_tokens']:,} seq_len={meta['seq_len']} "
40
- f"vocab={meta['vocab_size']}")
41
- return arr, meta["seq_len"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
 
44
  def sample_batch(arr, seq_len, batch):
@@ -49,24 +69,33 @@ def sample_batch(arr, seq_len, batch):
49
 
50
 
51
  def train(args):
52
- tok = YKTokenizer.load(os.path.join(DATADIR, "tokenizer.json"))
53
- arr, seq_len = load_data()
54
-
55
- model = YKDiff(CFG).cuda()
 
 
 
 
 
 
 
 
 
56
  n_params = sum(p.numel() for p in model.parameters())
57
- print(f"[train] model params = {n_params/1e6:.1f}M")
58
 
59
  optim = torch.optim.AdamW(model.parameters(), lr=args.lr, betas=(0.9, 0.95),
60
  weight_decay=0.1)
61
- V = CFG["vocab_size"]
62
  pad_id = tok.pad_id
63
  mask_id = tok.mask_id
64
 
65
  # resume
66
  step0 = 0
67
- ckpts = sorted([f for f in os.listdir(CKPTDIR) if f.endswith(".pt")])
68
  if ckpts and not args.fresh:
69
- path = os.path.join(CKPTDIR, ckpts[-1])
70
  sd = torch.load(path, map_location="cuda")
71
  model.load_state_dict(sd["model"]); optim.load_state_dict(sd["optim"])
72
  step0 = sd["step"]
@@ -104,7 +133,7 @@ def train(args):
104
  masked = idx.clone(); masked[is_mask] = mask_id
105
  logits = model(masked, m, t=r)
106
  ce = F.cross_entropy(logits.reshape(-1, V), idx.reshape(-1),
107
- reduction="none", ignore_index=-100)
108
  ce = ce * is_mask.reshape(-1) * not_pad.reshape(-1)
109
  denom = (is_mask & not_pad).reshape(-1).sum().clamp(min=1)
110
  loss = ce.sum() / denom
@@ -121,16 +150,19 @@ def train(args):
121
  f"t={(time.time()-t0)/60:.1f}m", flush=True)
122
 
123
  if step % args.ckpt_every == 0:
124
- path = os.path.join(CKPTDIR, f"clanker_{step:07d}.pt")
125
  torch.save({"model": model.state_dict(), "optim": optim.state_dict(),
126
- "step": step, "cfg": CFG, "vocab": V}, path)
127
  print(f"[train] checkpoint -> {path}", flush=True)
 
 
 
128
 
129
  # final save
130
- path = os.path.join(CKPTDIR, f"clanker_{step:07d}_final.pt")
131
  torch.save({"model": model.state_dict(), "optim": optim.state_dict(),
132
- "step": step, "cfg": CFG, "vocab": V}, path)
133
- json.dump(CFG, open(os.path.join(CKPTDIR, "config.json"), "w"))
134
  print(f"[train] DONE final={path} steps={step}")
135
 
136
 
@@ -142,4 +174,15 @@ if __name__ == "__main__":
142
  ap.add_argument("--log-every", type=int, default=25)
143
  ap.add_argument("--ckpt-every", type=int, default=500)
144
  ap.add_argument("--fresh", action="store_true")
 
 
 
 
 
 
 
 
 
 
 
145
  train(ap.parse_args())
 
1
  """
2
+ clankerDiffusion — training loop (hybrid AR / masked-diffusion).
3
 
4
  Hybrid objective (per step, mode chosen at random, p(AR)=0.5):
5
  AR (mode 0): causal LM cross-entropy over the whole window.
6
+ DIFF (mode 1): MDLM absorbing-state masked diffusion -- mask each token
7
  independently with ratio r~U(0,1); reconstruct masked tokens
8
+ with bidirectional attention, conditioned on r via time embed.
9
 
10
+ Runs in bf16, AdamW + cosine LR, grad-clip, checkpoints locally and (optionally)
11
+ pushes each checkpoint to a HuggingFace repo via the `hf` CLI.
12
+
13
+ Used both locally and on Modal L4 (override --data-dir/--ckpt-dir/--hf-repo and
14
+ the model dimensions for a bigger model).
15
  """
16
+ import os, json, time, argparse, subprocess, threading
17
  import numpy as np
18
  import torch
19
  import torch.nn as nn
 
27
  CKPTDIR = os.path.join(HERE, "checkpoints")
28
  os.makedirs(CKPTDIR, exist_ok=True)
29
 
30
+ # default architecture tuned for 16 GB (RTX 5060 Ti)
31
+ DEFAULT_CFG = dict(
32
  d_model=768, n_layers=12, n_heads=12, d_ff=2048,
33
  max_len=1024, vocab_size=32768,
34
  )
35
 
36
 
37
+ def build_cfg(args):
38
+ cfg = dict(DEFAULT_CFG)
39
+ for k in ("d_model", "n_layers", "n_heads", "d_ff", "vocab_size", "max_len"):
40
+ v = getattr(args, k, None)
41
+ if v is not None:
42
+ cfg[k] = v
43
+ return cfg
44
+
45
+
46
+ def _push_hf(path, repo):
47
+ """Upload a single checkpoint file to HF via the `hf` CLI (background)."""
48
+ if not repo:
49
+ return
50
+ try:
51
+ subprocess.run(["hf", "upload", repo, path, "--repo-type", "model"],
52
+ check=True, capture_output=True, timeout=600)
53
+ print(f"[hf] pushed {os.path.basename(path)} -> {repo}", flush=True)
54
+ except Exception as e:
55
+ print(f"[hf] push failed for {path}: {e}", flush=True)
56
+
57
+
58
+ def load_data(data_dir):
59
+ meta = json.load(open(os.path.join(data_dir, "meta.json")))
60
+ arr = np.memmap(os.path.join(data_dir, "train.bin"), dtype=np.uint16, mode="r")
61
+ return arr, meta["seq_len"], meta["vocab_size"], meta["n_tokens"]
62
 
63
 
64
  def sample_batch(arr, seq_len, batch):
 
69
 
70
 
71
  def train(args):
72
+ data_dir = args.data_dir or DATADIR
73
+ ckpt_dir = args.ckpt_dir or CKPTDIR
74
+ os.makedirs(ckpt_dir, exist_ok=True)
75
+ cfg = build_cfg(args)
76
+
77
+ tok = YKTokenizer.load(os.path.join(data_dir, "tokenizer.json"))
78
+ arr, seq_len, vocab, n_tokens = load_data(data_dir)
79
+ cfg["vocab_size"] = vocab
80
+ cfg["max_len"] = seq_len
81
+ print(f"[train] data n_tokens={n_tokens:,} seq_len={seq_len} vocab={vocab}")
82
+ print(f"[train] model params = {sum(p.numel() for p in YKDiff(cfg).parameters())/1e6:.1f}M")
83
+
84
+ model = YKDiff(cfg).cuda()
85
  n_params = sum(p.numel() for p in model.parameters())
86
+ print(f"[train] allocated params = {n_params/1e6:.1f}M")
87
 
88
  optim = torch.optim.AdamW(model.parameters(), lr=args.lr, betas=(0.9, 0.95),
89
  weight_decay=0.1)
90
+ V = cfg["vocab_size"]
91
  pad_id = tok.pad_id
92
  mask_id = tok.mask_id
93
 
94
  # resume
95
  step0 = 0
96
+ ckpts = sorted([f for f in os.listdir(ckpt_dir) if f.endswith(".pt")])
97
  if ckpts and not args.fresh:
98
+ path = os.path.join(ckpt_dir, ckpts[-1])
99
  sd = torch.load(path, map_location="cuda")
100
  model.load_state_dict(sd["model"]); optim.load_state_dict(sd["optim"])
101
  step0 = sd["step"]
 
133
  masked = idx.clone(); masked[is_mask] = mask_id
134
  logits = model(masked, m, t=r)
135
  ce = F.cross_entropy(logits.reshape(-1, V), idx.reshape(-1),
136
+ reduction="none", ignore_index=-100)
137
  ce = ce * is_mask.reshape(-1) * not_pad.reshape(-1)
138
  denom = (is_mask & not_pad).reshape(-1).sum().clamp(min=1)
139
  loss = ce.sum() / denom
 
150
  f"t={(time.time()-t0)/60:.1f}m", flush=True)
151
 
152
  if step % args.ckpt_every == 0:
153
+ path = os.path.join(ckpt_dir, f"clanker_{step:07d}.pt")
154
  torch.save({"model": model.state_dict(), "optim": optim.state_dict(),
155
+ "step": step, "cfg": cfg, "vocab": V}, path)
156
  print(f"[train] checkpoint -> {path}", flush=True)
157
+ if args.hf_repo:
158
+ threading.Thread(target=_push_hf, args=(path, args.hf_repo),
159
+ daemon=True).start()
160
 
161
  # final save
162
+ path = os.path.join(ckpt_dir, f"clanker_{step:07d}_final.pt")
163
  torch.save({"model": model.state_dict(), "optim": optim.state_dict(),
164
+ "step": step, "cfg": cfg, "vocab": V}, path)
165
+ json.dump(cfg, open(os.path.join(ckpt_dir, "config.json"), "w"))
166
  print(f"[train] DONE final={path} steps={step}")
167
 
168
 
 
174
  ap.add_argument("--log-every", type=int, default=25)
175
  ap.add_argument("--ckpt-every", type=int, default=500)
176
  ap.add_argument("--fresh", action="store_true")
177
+ ap.add_argument("--data-dir", default=None)
178
+ ap.add_argument("--ckpt-dir", default=None)
179
+ ap.add_argument("--hf-repo", default=None,
180
+ help="HuggingFace repo id to push checkpoints to (via `hf` CLI)")
181
+ # model overrides (for a bigger Modal model)
182
+ ap.add_argument("--d-model", type=int, default=None)
183
+ ap.add_argument("--n-layers", type=int, default=None)
184
+ ap.add_argument("--n-heads", type=int, default=None)
185
+ ap.add_argument("--d-ff", type=int, default=None)
186
+ ap.add_argument("--vocab-size", type=int, default=None)
187
+ ap.add_argument("--seq-len", type=int, default=None)
188
  train(ap.parse_args())