CodeMasterCody3D commited on
Commit
fc4f546
·
verified ·
1 Parent(s): c9806e8

ROCm-safe flip rule (kthvalue/where), --accum, --freeze-aux, --lr-horizon, launch_rig.sh/bench_rig.py for the RX 5600 XT rig

Browse files
Files changed (5) hide show
  1. bench_rig.py +51 -0
  2. flip_lm.py +24 -9
  3. launch_rig.sh +27 -0
  4. ternary_lm.py +1 -2
  5. train_live.py +33 -15
bench_rig.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """bench_rig — does the v2 model train on this GPU, and how fast? (ROCm / any CUDA-like device)
2
+ Builds the big1 architecture, runs fwd/bwd+Adam steps at a few batch sizes with random tokens,
3
+ reports s/step, tokens/s and peak memory; then times one FlipOptimizer step (svd_lowrank on GPU).
4
+ python3 bench_rig.py [K] [batches e.g. 1,2,4]
5
+ """
6
+ import sys, os, time, torch, torch.nn.functional as F
7
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
8
+ from ternary_lm import Model
9
+ from flip_lm import FlipOptimizer
10
+ from train_live import chunked_ce
11
+
12
+ K = int(sys.argv[1]) if len(sys.argv) > 1 else 2048
13
+ batches = [int(x) for x in (sys.argv[2] if len(sys.argv) > 2 else "1,2,4").split(",")]
14
+ dev = "cuda"
15
+ print("device:", torch.cuda.get_device_name(0), "| torch", torch.__version__, "| hip", getattr(torch.version, "hip", None))
16
+ m = Model(32768, d=512, blocks=8, K=K, lanes=4, heads=8, pos="rope", emb_bits=8, qkv_bits=8, res_shift=2, final_norm=True, qk_norm=True).to(dev)
17
+ print("params", sum(p.numel() for p in m.parameters()))
18
+ for amp in (False,):
19
+ for B in batches:
20
+ try:
21
+ opt = torch.optim.Adam(m.parameters(), lr=1e-4); scaler = torch.amp.GradScaler("cuda", enabled=amp)
22
+ torch.cuda.empty_cache(); torch.cuda.reset_peak_memory_stats()
23
+ def step():
24
+ ctx = torch.randint(0, 32768, (B, K), device=dev); tgt = torch.randint(0, 32768, (B, K), device=dev)
25
+ with torch.autocast("cuda", dtype=torch.float16, enabled=amp):
26
+ h = m.hidden(ctx); loss = chunked_ce(m, h, tgt, 32768)
27
+ opt.zero_grad(set_to_none=True); scaler.scale(loss).backward(); scaler.step(opt); scaler.update()
28
+ return loss
29
+ for _ in range(2): step()
30
+ torch.cuda.synchronize(); t0 = time.time(); n = 4
31
+ for _ in range(n): l = step()
32
+ torch.cuda.synchronize(); dt = (time.time() - t0) / n
33
+ print(f"amp={amp} B={B} K={K}: {dt:.2f} s/step | {B*K/dt:,.0f} tok/s | peak {torch.cuda.max_memory_allocated()/2**30:.2f} GB | loss {l.item():.2f}")
34
+ del opt, scaler
35
+ for p in m.parameters(): p.grad = None
36
+ torch.cuda.empty_cache()
37
+ except Exception as e:
38
+ print(f"amp={amp} B={B}: FAILED {type(e).__name__}: {str(e)[:160]}"); torch.cuda.empty_cache()
39
+ # flip step timing (codes only, no Adam state on weights)
40
+ try:
41
+ for p in m.parameters(): p.grad = None
42
+ fo = FlipOptimizer(m, rank=64, refresh=10, rho=1e-5, aux_lr=0)
43
+ B = batches[0]; ctx = torch.randint(0, 32768, (B, K), device=dev); tgt = torch.randint(0, 32768, (B, K), device=dev)
44
+ torch.cuda.reset_peak_memory_stats(); torch.cuda.synchronize(); t0 = time.time()
45
+ for i in range(3):
46
+ h = m.hidden(ctx); loss = chunked_ce(m, h, tgt, 32768)
47
+ fo.zero_grad(); loss.backward(); fo.step()
48
+ torch.cuda.synchronize()
49
+ print(f"FLIP B={B}: {(time.time()-t0)/3:.2f} s/step | peak {torch.cuda.max_memory_allocated()/2**30:.2f} GB | flips {fo.total_flips} | state {fo.state_bytes()/2**20:.0f} MB")
50
+ except Exception as e:
51
+ print("FLIP FAILED", type(e).__name__, str(e)[:200])
flip_lm.py CHANGED
@@ -21,6 +21,22 @@ per weight = the 2-bit code. Fixed rho "boils" after a while in the source's run
21
  import torch
22
  import torch.nn as nn
23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
  class FlipState:
26
  """Low-rank Adam state + flip rule for ONE ternary weight tensor (a Lin.weight holding codes)."""
@@ -48,8 +64,7 @@ class FlipState:
48
  if not torch.isfinite(G).all(): # a bad batch must not flip anything
49
  return 0
50
  if self.P is None or step_idx % self.refresh == 0:
51
- U_, _, _ = torch.svd_lowrank(G, q=self.rank, niter=2) # top-r directions of THIS step's gradient
52
- self.P = U_.contiguous() # [O, r]
53
  g = self.P.t() @ G # [r, I]
54
  b1, b2 = self.betas
55
  self.t += 1
@@ -70,12 +85,11 @@ class FlipState:
70
  valid = (step != 0) & (new.abs() <= 1)
71
  gain = torch.where(valid, D.abs(), torch.zeros_like(D))
72
  k = max(1, int(rho * D.numel()))
73
- top = torch.topk(gain.flatten(), k).indices
74
- sel = torch.zeros(D.numel(), dtype=torch.bool, device=D.device)
75
- sel[top] = True
76
- sel = sel.view_as(D) & valid & (gain > 0)
77
  n = int(sel.sum())
78
- codes[sel] = new[sel]
79
  self.flips += n
80
  return n
81
 
@@ -148,8 +162,9 @@ class FlipOptimizer:
148
 
149
  def assert_on_grid(self):
150
  for n, st in self.states.items():
151
- u = set(st.w.detach().unique().tolist())
152
- assert u <= {-1.0, 0.0, 1.0}, f"{n} left the ternary grid: {u}"
 
153
 
154
  # ---- persistence (the codes themselves live in the model's state_dict) ----
155
  def state_dict(self):
 
21
  import torch
22
  import torch.nn as nn
23
 
24
+ _SVD_ON_DEVICE = {} # device -> bool; some ROCm builds (gfx1010) lack the LAPACK kernels svd_lowrank needs
25
+
26
+
27
+ def _top_directions(G, rank):
28
+ """Top-`rank` left singular directions of G [O, I] via randomized SVD; falls back to CPU when the
29
+ device's linear-algebra kernels are missing (HIP 'invalid device function' on Navi 10)."""
30
+ dev = str(G.device)
31
+ if _SVD_ON_DEVICE.get(dev, True):
32
+ try:
33
+ U_, _, _ = torch.svd_lowrank(G, q=rank, niter=2)
34
+ return U_
35
+ except Exception:
36
+ _SVD_ON_DEVICE[dev] = False
37
+ U_, _, _ = torch.svd_lowrank(G.detach().cpu(), q=rank, niter=2)
38
+ return U_.to(G.device)
39
+
40
 
41
  class FlipState:
42
  """Low-rank Adam state + flip rule for ONE ternary weight tensor (a Lin.weight holding codes)."""
 
64
  if not torch.isfinite(G).all(): # a bad batch must not flip anything
65
  return 0
66
  if self.P is None or step_idx % self.refresh == 0:
67
+ self.P = _top_directions(G, self.rank).contiguous() # [O, r]: top-r left singular directions
 
68
  g = self.P.t() @ G # [r, I]
69
  b1, b2 = self.betas
70
  self.t += 1
 
85
  valid = (step != 0) & (new.abs() <= 1)
86
  gain = torch.where(valid, D.abs(), torch.zeros_like(D))
87
  k = max(1, int(rho * D.numel()))
88
+ # threshold via kthvalue (ROCm gfx1010 lacks the topk kernel for mid-sized tensors; kthvalue works)
89
+ thr = torch.kthvalue(gain.flatten(), max(1, D.numel() - k + 1)).values
90
+ sel = valid & (gain >= thr) & (gain > 0)
 
91
  n = int(sel.sum())
92
+ codes.copy_(torch.where(sel, new, codes)) # elementwise (masked index_put is missing on ROCm gfx1010)
93
  self.flips += n
94
  return n
95
 
 
162
 
163
  def assert_on_grid(self):
164
  for n, st in self.states.items():
165
+ w = st.w.detach()
166
+ ok = bool(((w == -1) | (w == 0) | (w == 1)).all()) # (torch.unique is missing on some ROCm builds)
167
+ assert ok, f"{n} left the ternary grid"
168
 
169
  # ---- persistence (the codes themselves live in the model's state_dict) ----
170
  def state_dict(self):
launch_rig.sh ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # launch_rig.sh — run a TernaryTest training arm on the desktop rig's RX 5600 XT (ROCm container).
3
+ # usage (run ON the rig, in ~/TernaryTest): bash launch_rig.sh <name> <flip|adam> [steps] [extra train_live args...]
4
+ # e.g. bash launch_rig.sh flipB flip 1000 # arm B: low-rank-Adam flips from big1's step-22k codes
5
+ # bash launch_rig.sh adamA adam 1000 # arm A: true Adam on latents, same start, same data
6
+ # 6 GB VRAM: fp32 (fp16 is slower on Navi 10), batch 1 x K2048 with --accum 8 = 16k tokens/step like big1.
7
+ # Streams the same 36 sources (the rig has 46 GB RAM). Banks to HF <name>/ (token mounted from ~/.cache).
8
+ cd "$(dirname "$0")"
9
+ NAME=${1:?name}; MODE=${2:?flip|adam}; STEPS=${3:-1000}; shift 3 2>/dev/null || shift $#
10
+ mkdir -p "$NAME"
11
+ if [ "$MODE" = "flip" ]; then
12
+ ARM="--flip --rho 1e-5 --flip-rank 64 --flip-refresh 10 --rollback-tol 0.0 --freeze-aux"
13
+ else
14
+ ARM="--freeze-aux --clip 1.0 --lr-horizon 200000" # big1's own (nearly flat) lr over a short window
15
+ fi
16
+ docker rm -f "tt-$NAME" >/dev/null 2>&1
17
+ docker run -d --name "tt-$NAME" --device=/dev/kfd --device=/dev/dri --group-add video --group-add render \
18
+ --security-opt seccomp=unconfined --shm-size 16g \
19
+ -v "$HOME/TernaryTest:/work" -v "$HOME/.cache/huggingface:/root/.cache/huggingface" -w /work \
20
+ rocm63-gfx1010:trainer bash -lc "python3 train_live.py --out /work/$NAME --init /work/big1/best_model.pt \
21
+ --tokenizer /work/tokenizer.json --pack --mix big \
22
+ --d 512 --blocks 8 --heads 8 --lanes 4 --emb-bits 8 --qkv-bits 8 --res-shift 2 --final-norm --qk-norm \
23
+ --K 2048 --batch 1 --accum 8 --steps $STEPS --warmup 0 --lr 3e-3 --eval-every 100 --eval-windows 4 \
24
+ --heldout-chars 100000 --buffer-chars 3000000 --amp 0 --sample-chars 200 \
25
+ --hf-repo CodeMasterCody3D/ternary-composer-charlm --hf-subdir $NAME --bank-every-min 30 $ARM $* \
26
+ > /work/$NAME/run.out 2>&1"
27
+ echo "started container tt-$NAME (mode $MODE, $STEPS steps); log: ~/TernaryTest/$NAME/run.out"
ternary_lm.py CHANGED
@@ -284,8 +284,7 @@ class Model(nn.Module):
284
  params[n].copy_(det_tern(params[n]))
285
  vals.append(params[n].flatten())
286
  v = torch.cat(vals)
287
- u = set(v.unique().tolist())
288
- assert u <= {-1.0, 0.0, 1.0}, f"non-ternary values present: {u}"
289
  n = v.numel()
290
  return {"n": n, "frac_neg": (v == -1).float().mean().item(),
291
  "frac_zero": (v == 0).float().mean().item(),
 
284
  params[n].copy_(det_tern(params[n]))
285
  vals.append(params[n].flatten())
286
  v = torch.cat(vals)
287
+ assert bool(((v == -1) | (v == 0) | (v == 1)).all()), "non-ternary values present" # no torch.unique (ROCm)
 
288
  n = v.numel()
289
  return {"n": n, "frac_neg": (v == -1).float().mean().item(),
290
  "frac_zero": (v == 0).float().mean().item(),
train_live.py CHANGED
@@ -57,9 +57,11 @@ def get_args():
57
  ap.add_argument("--qk-norm", action="store_true", help="RMS-normalize q,k per head (bounded attention scores)")
58
  ap.add_argument("--float", action="store_true", help="float weights (teacher / baseline)")
59
  ap.add_argument("--batch", type=int, default=8)
 
60
  ap.add_argument("--steps", type=int, default=60000)
61
  ap.add_argument("--lr", type=float, default=3e-3)
62
  ap.add_argument("--warmup", type=int, default=200)
 
63
  ap.add_argument("--clip", type=float, default=1.0, help="grad-norm clip (0 = off)")
64
  ap.add_argument("--eval-every", type=int, default=500)
65
  ap.add_argument("--eval-windows", type=int, default=8, help="held-out windows per source")
@@ -79,6 +81,7 @@ def get_args():
79
  ap.add_argument("--rho", type=float, default=1e-5, help="fraction of each layer's weights flipped per step")
80
  ap.add_argument("--aux-lr", type=float, default=1e-4, help="Adam lr for the non-ternary params (emb, norms, biases)")
81
  ap.add_argument("--rollback-tol", type=float, default=0.0, help="on an eval worse than best*(1+tol): restore best codes, rho *= 0.5")
 
82
  ap.add_argument("--fixed-corpus", default=None, help="dir from fixed_corpus.py build (no HF streams; ~3 GB RSS)")
83
  ap.add_argument("--hf-repo", default=None)
84
  ap.add_argument("--hf-subdir", default=None)
@@ -329,9 +332,17 @@ def main():
329
  say("NON-FINITE WEIGHTS in the resume checkpoint — refusing to continue from it"); return 5
330
  if ctrl.get("teacher"):
331
  say(attach_teacher(ctrl["teacher"]))
 
 
 
 
 
 
 
332
  if a.flip:
333
  from flip_lm import FlipOptimizer
334
- flip = FlipOptimizer(m, rank=a.flip_rank, refresh=a.flip_refresh, rho=float(ctrl["rho"]), aux_lr=a.aux_lr)
 
335
  if a.resume and os.path.exists(a.resume) and ck.get("flip"):
336
  flip.load_state_dict(ck["flip"])
337
  flip.assert_on_grid()
@@ -345,7 +356,7 @@ def main():
345
  event("start", arch=m.arch(), args=vars(a), control=ctrl, vocab=Vsz)
346
 
347
  def lr_at(s):
348
- T = max(1, ctrl["steps"] - a.warmup)
349
  base = (s + 1) / a.warmup if s < a.warmup else 0.5 * (1 + math.cos(math.pi * min(1.0, (s - a.warmup) / T)))
350
  return a.lr * base * float(ctrl["lr_mult"])
351
 
@@ -367,28 +378,35 @@ def main():
367
  os.path.join(a.out, "best_model.pt"))
368
 
369
  def train_step():
 
370
  nonlocal tokens_seen
371
  K, B = int(ctrl["K"]), int(ctrl["batch"])
372
- ctx, tgt, _, _ = mixer.batch(B, K, device=dev)
373
- with torch.autocast("cuda", dtype=torch.float16, enabled=amp):
374
- h = m.hidden(ctx)
375
- th = teacher.hidden(ctx) if teacher is not None else None
376
- loss = chunked_ce(m, h, tgt, Vsz, th, teacher, float(ctrl["kd_temp"]), float(ctrl["kd_alpha"]))
377
- if flip is not None: # codes-only training: low-rank Adam -> flips (scale-invariant, no scaler)
378
  flip.zero_grad()
379
- loss.backward()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
380
  flip.rho = float(ctrl["rho"])
381
  flip.step()
382
- tokens_seen += B * K
383
- return loss.item()
384
- opt.zero_grad(set_to_none=True)
385
- scaler.scale(loss).backward()
386
  if a.clip > 0: # finite-but-astronomical gradients poison Adam's moments
387
  scaler.unscale_(opt)
388
  torch.nn.utils.clip_grad_norm_(m.parameters(), a.clip)
389
  scaler.step(opt); scaler.update()
390
- tokens_seen += B * K
391
- return loss.item()
392
 
393
  t0, run_loss, run_n, nan_run = time.time(), 0.0, 0, 0
394
  best_codes = flip.snapshot() if flip is not None else None
 
57
  ap.add_argument("--qk-norm", action="store_true", help="RMS-normalize q,k per head (bounded attention scores)")
58
  ap.add_argument("--float", action="store_true", help="float weights (teacher / baseline)")
59
  ap.add_argument("--batch", type=int, default=8)
60
+ ap.add_argument("--accum", type=int, default=1, help="micro-batches per optimizer step (small-VRAM GPUs)")
61
  ap.add_argument("--steps", type=int, default=60000)
62
  ap.add_argument("--lr", type=float, default=3e-3)
63
  ap.add_argument("--warmup", type=int, default=200)
64
+ ap.add_argument("--lr-horizon", type=int, default=None, help="cosine horizon in steps (default: --steps); e.g. 200000 keeps big1's schedule in a short arm")
65
  ap.add_argument("--clip", type=float, default=1.0, help="grad-norm clip (0 = off)")
66
  ap.add_argument("--eval-every", type=int, default=500)
67
  ap.add_argument("--eval-windows", type=int, default=8, help="held-out windows per source")
 
81
  ap.add_argument("--rho", type=float, default=1e-5, help="fraction of each layer's weights flipped per step")
82
  ap.add_argument("--aux-lr", type=float, default=1e-4, help="Adam lr for the non-ternary params (emb, norms, biases)")
83
  ap.add_argument("--rollback-tol", type=float, default=0.0, help="on an eval worse than best*(1+tol): restore best codes, rho *= 0.5")
84
+ ap.add_argument("--freeze-aux", action="store_true", help="freeze non-ternary params (emb, norms, biases) in BOTH modes — equal-footing A/B")
85
  ap.add_argument("--fixed-corpus", default=None, help="dir from fixed_corpus.py build (no HF streams; ~3 GB RSS)")
86
  ap.add_argument("--hf-repo", default=None)
87
  ap.add_argument("--hf-subdir", default=None)
 
332
  say("NON-FINITE WEIGHTS in the resume checkpoint — refusing to continue from it"); return 5
333
  if ctrl.get("teacher"):
334
  say(attach_teacher(ctrl["teacher"]))
335
+ if a.freeze_aux and not a.flip: # Adam arm on the ternary latents only (matches --flip --aux-lr 0)
336
+ tern = set(m.ternary_weight_names())
337
+ for n_, p in m.named_parameters():
338
+ if n_ not in tern:
339
+ p.requires_grad_(False)
340
+ opt = torch.optim.Adam([p for n_, p in m.named_parameters() if n_ in tern], lr=a.lr)
341
+ say(f"freeze-aux: Adam on {len(tern)} ternary latent tensors only")
342
  if a.flip:
343
  from flip_lm import FlipOptimizer
344
+ flip = FlipOptimizer(m, rank=a.flip_rank, refresh=a.flip_refresh, rho=float(ctrl["rho"]),
345
+ aux_lr=0.0 if a.freeze_aux else a.aux_lr)
346
  if a.resume and os.path.exists(a.resume) and ck.get("flip"):
347
  flip.load_state_dict(ck["flip"])
348
  flip.assert_on_grid()
 
356
  event("start", arch=m.arch(), args=vars(a), control=ctrl, vocab=Vsz)
357
 
358
  def lr_at(s):
359
+ T = max(1, (a.lr_horizon or ctrl["steps"]) - a.warmup)
360
  base = (s + 1) / a.warmup if s < a.warmup else 0.5 * (1 + math.cos(math.pi * min(1.0, (s - a.warmup) / T)))
361
  return a.lr * base * float(ctrl["lr_mult"])
362
 
 
378
  os.path.join(a.out, "best_model.pt"))
379
 
380
  def train_step():
381
+ """One optimizer step = `a.accum` micro-batches of `batch` x K tokens (gradients accumulate)."""
382
  nonlocal tokens_seen
383
  K, B = int(ctrl["K"]), int(ctrl["batch"])
384
+ if flip is not None:
 
 
 
 
 
385
  flip.zero_grad()
386
+ else:
387
+ opt.zero_grad(set_to_none=True)
388
+ tot = 0.0
389
+ for _ in range(a.accum):
390
+ ctx, tgt, _, _ = mixer.batch(B, K, device=dev)
391
+ with torch.autocast("cuda", dtype=torch.float16, enabled=amp):
392
+ h = m.hidden(ctx)
393
+ th = teacher.hidden(ctx) if teacher is not None else None
394
+ loss = chunked_ce(m, h, tgt, Vsz, th, teacher, float(ctrl["kd_temp"]), float(ctrl["kd_alpha"])) / a.accum
395
+ if flip is not None: # codes-only training: low-rank Adam -> flips (scale-invariant, no scaler)
396
+ loss.backward()
397
+ else:
398
+ scaler.scale(loss).backward()
399
+ tot += loss.item()
400
+ tokens_seen += B * K
401
+ if flip is not None:
402
  flip.rho = float(ctrl["rho"])
403
  flip.step()
404
+ return tot
 
 
 
405
  if a.clip > 0: # finite-but-astronomical gradients poison Adam's moments
406
  scaler.unscale_(opt)
407
  torch.nn.utils.clip_grad_norm_(m.parameters(), a.clip)
408
  scaler.step(opt); scaler.update()
409
+ return tot
 
410
 
411
  t0, run_loss, run_n, nan_run = time.time(), 0.0, 0, 0
412
  best_codes = flip.snapshot() if flip is not None else None