OpenTransformer commited on
Commit
e52ca6f
·
verified ·
1 Parent(s): fd70b9b

Upload dblock training helper

Browse files
Files changed (1) hide show
  1. dblocks_train.py +432 -0
dblocks_train.py ADDED
@@ -0,0 +1,432 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """DiffusionBlocks training mode folded into AGILLM-4 (gated by --dblock).
2
+
3
+ Block-wise EDM denoising on the real Encoder blocks, supervising AR + SAT(fixed+var)
4
+ + NAT each step on ONE block, with grad-checkpointed layers and fused vocab-streaming
5
+ CE. Reuses the live data stream / optimizer / checkpointing of nB300_agillm4.
6
+ Lazy-imports nB300 inside functions to avoid a circular import.
7
+ """
8
+ import math
9
+ import random
10
+ import time
11
+ from collections import defaultdict
12
+ import numpy as np
13
+ import torch
14
+ import torch.nn as nn
15
+ import torch.nn.functional as F
16
+ import torch.utils.checkpoint as _ck
17
+ from fused_ce import fused_ce
18
+
19
+ SD = 0.5
20
+
21
+
22
+
23
+
24
+ def _profile_active(state, args):
25
+ limit = int(getattr(args, "profile_steps", 0) or 0)
26
+ return limit > 0 and int(state.get("profile_n", 0)) < limit
27
+
28
+
29
+ def _profile_add(state, name, seconds):
30
+ if seconds is None:
31
+ return
32
+ prof = state.setdefault("profile_times", defaultdict(float))
33
+ prof[name] += float(seconds)
34
+
35
+
36
+ def _profile_tic(enabled):
37
+ if not enabled:
38
+ return None
39
+ if torch.cuda.is_available():
40
+ torch.cuda.synchronize()
41
+ return time.perf_counter()
42
+
43
+
44
+ def _profile_toc(state, name, start):
45
+ if start is None:
46
+ return
47
+ if torch.cuda.is_available():
48
+ torch.cuda.synchronize()
49
+ _profile_add(state, name, time.perf_counter() - start)
50
+
51
+
52
+ def _profile_step_done(state, args):
53
+ limit = int(getattr(args, "profile_steps", 0) or 0)
54
+ if limit <= 0:
55
+ return
56
+ n_prev = int(state.get("profile_n", 0))
57
+ if n_prev >= limit:
58
+ return
59
+ state["profile_n"] = n_prev + 1
60
+ n = int(state["profile_n"])
61
+ log_every = max(1, int(getattr(args, "profile_log_every", 25) or 25))
62
+ if n % log_every != 0 and n != limit:
63
+ return
64
+ times = state.get("profile_times", {})
65
+ keys = [
66
+ "data_stream", "tensor", "setup",
67
+ "ar_forward", "ar_ce", "ar_backward",
68
+ "sat_forward", "sat_ce", "sat_backward",
69
+ "nat_forward", "nat_ce", "nat_backward",
70
+ "opt_step", "step_total",
71
+ ]
72
+ parts = []
73
+ for key in keys:
74
+ val = float(times.get(key, 0.0)) * 1000.0 / max(1, n)
75
+ if val > 0.01:
76
+ parts.append(f"{key}={val:.2f}ms")
77
+ print(f"[profile] n={n}/{limit} avg " + " ".join(parts), flush=True)
78
+
79
+ def _cdf(x):
80
+ return 0.5 * (1 + math.erf(x / math.sqrt(2)))
81
+
82
+
83
+ def _ppf(p):
84
+ return float(torch.erfinv(torch.tensor(2 * p - 1.0)) * math.sqrt(2))
85
+
86
+
87
+ def _block_sigmas(B, smin=0.002, smax=80.0, pm=-1.2, ps=1.2):
88
+ a, b = _cdf((math.log(smin) - pm) / ps), _cdf((math.log(smax) - pm) / ps)
89
+ return [float(np.exp(pm + ps * _ppf(a + (b - a) * (i / B)))) for i in range(B + 1)]
90
+
91
+
92
+ def _edm_pre(s):
93
+ s = s[:, None, None]
94
+ return SD**2 / (s**2 + SD**2), s * SD / (s**2 + SD**2) ** 0.5, 1 / (s**2 + SD**2) ** 0.5
95
+
96
+
97
+ def _edm_w(s, wmax=5.0):
98
+ return float(((s**2 + SD**2) / (s * SD) ** 2).clamp(max=wmax).mean())
99
+
100
+
101
+ def _dblock_init(core, args):
102
+ B = int(getattr(args, "dblock_blocks", 4))
103
+ L = len(core.blocks)
104
+ sp = max(1, L // B)
105
+ asg = [list(range(i * sp, (i + 1) * sp)) for i in range(B)]
106
+ asg[-1] = list(range((B - 1) * sp, L))
107
+ bsig = _block_sigmas(B)
108
+ schedule = getattr(args, "dblock_schedule", "loss_balanced")
109
+ print(f"[dblock] DiffusionBlocks mode: {L} layers -> {B} blocks {asg}")
110
+ print(f"[dblock] schedule={schedule} sigma boundaries: {[round(x, 3) for x in bsig]}")
111
+ return {
112
+ "B": B,
113
+ "assign": asg,
114
+ "bsig": bsig,
115
+ "step": 0,
116
+ "counts": [0 for _ in range(B)],
117
+ "loss_ema": [None for _ in range(B)],
118
+ }
119
+
120
+
121
+ def _choose_block(state, args):
122
+ B = state["B"]
123
+ schedule = str(getattr(args, "dblock_schedule", "loss_balanced") or "loss_balanced").lower()
124
+ step = int(state.get("step", 0))
125
+ counts = state.setdefault("counts", [0 for _ in range(B)])
126
+ emas = state.setdefault("loss_ema", [None for _ in range(B)])
127
+ if schedule == "random":
128
+ return random.randrange(B)
129
+ if schedule == "roundrobin":
130
+ return step % B
131
+ explore = float(getattr(args, "dblock_explore", 0.05))
132
+ warmup = int(getattr(args, "dblock_warmup_steps", max(8, B * 2)))
133
+ if step < warmup or any(c == 0 for c in counts):
134
+ return min(range(B), key=lambda i: (counts[i], i))
135
+ if explore > 0.0 and random.random() < explore:
136
+ return min(range(B), key=lambda i: (counts[i], i))
137
+ return max(range(B), key=lambda i: (-1.0 if emas[i] is None else emas[i], -counts[i]))
138
+
139
+
140
+ def _sample_sigma(ids, lo, hi, args, state):
141
+ cur_step = int(state.get("step", 0))
142
+ curriculum = int(getattr(args, "dblock_sigma_curriculum_steps", 0))
143
+ if curriculum > 0:
144
+ frac = min(1.0, max(0.05, (cur_step + 1) / float(curriculum)))
145
+ hi = lo * ((hi / max(lo, 1e-8)) ** frac)
146
+ sig_np = np.exp(
147
+ np.random.uniform(
148
+ math.log(max(lo, 1e-4)),
149
+ math.log(max(hi, lo + 1e-4)),
150
+ ids.size(0),
151
+ ).astype("float32")
152
+ )
153
+ return torch.from_numpy(sig_np).to(ids.device)
154
+
155
+
156
+ def _maybe_log(state, args, bi, layers, ar_val, sat_val, nat_val, total_val, peak_alloc, peak_reserved, objective=None):
157
+ log_every = int(getattr(args, "dblock_log_every", 50))
158
+ step = int(state.get("step", 0))
159
+ if log_every <= 0 or step % log_every != 0:
160
+ return
161
+ counts = ",".join(str(x) for x in state.get("counts", []))
162
+ emas = ",".join("nan" if x is None else f"{x:.2f}" for x in state.get("loss_ema", []))
163
+ mem = ""
164
+ if peak_alloc is not None:
165
+ mem = f" peak_alloc={peak_alloc:.2f}GB peak_reserved={peak_reserved:.2f}GB"
166
+ print(
167
+ f"[dblock] step={step} block={bi} obj={objective or 'mixed'} layers={layers} "
168
+ f"loss={total_val:.3f} ar={ar_val:.3f} sat={sat_val:.3f} nat={nat_val:.3f} "
169
+ f"counts=[{counts}] ema=[{emas}]{mem}",
170
+ flush=True,
171
+ )
172
+
173
+
174
+ def _update_stats(state, bi, loss_value):
175
+ B = state["B"]
176
+ counts = state.setdefault("counts", [0 for _ in range(B)])
177
+ emas = state.setdefault("loss_ema", [None for _ in range(B)])
178
+ counts[bi] += 1
179
+ prev = emas[bi]
180
+ beta = 0.96
181
+ emas[bi] = float(loss_value) if prev is None else beta * float(prev) + (1.0 - beta) * float(loss_value)
182
+ state["step"] = int(state.get("step", 0)) + 1
183
+
184
+
185
+ def _activation_offload_enabled(args):
186
+ return bool(getattr(args, "dblock_activation_offload", False)) and torch.cuda.is_available()
187
+
188
+
189
+ def _activation_offload_hooks(args):
190
+ min_bytes = int(float(getattr(args, "dblock_activation_offload_min_mb", 1.0) or 1.0) * 1024 * 1024)
191
+
192
+ def pack(t):
193
+ if not torch.is_tensor(t) or not t.is_cuda or not t.is_floating_point() or t.numel() * t.element_size() < min_bytes:
194
+ return t
195
+ return ("cpu_offload", t.device, t.detach().to("cpu", non_blocking=True))
196
+
197
+ def unpack(x):
198
+ if isinstance(x, tuple) and len(x) == 3 and x[0] == "cpu_offload":
199
+ _, dev, cpu_t = x
200
+ return cpu_t.to(dev, non_blocking=True)
201
+ return x
202
+
203
+ return torch.autograd.graph.saved_tensors_hooks(pack, unpack)
204
+
205
+
206
+ def _run_block(block, x, mask, use_checkpoint, args=None):
207
+ if use_checkpoint:
208
+ return _ck.checkpoint(lambda y, block=block: block(y, mask), x, use_reentrant=False)
209
+ if args is not None and _activation_offload_enabled(args):
210
+ with _activation_offload_hooks(args):
211
+ return block(x, mask)
212
+ return block(x, mask)
213
+
214
+
215
+ def _dblock_checkpoint_this_layer(args, base_enabled, layer_pos, layer_count=None):
216
+ if not base_enabled:
217
+ return False
218
+ pos = int(layer_pos)
219
+ count = int(layer_count or 0)
220
+ skip_tail = max(0, int(getattr(args, "dblock_checkpoint_skip_tail", 0) or 0))
221
+ if skip_tail > 0 and count > 0 and pos >= max(0, count - skip_tail):
222
+ return False
223
+ stride = int(getattr(args, "dblock_checkpoint_stride", 1) or 1)
224
+ if stride <= 0:
225
+ return False
226
+ if stride == 1:
227
+ return True
228
+ return (pos % stride) == 0
229
+
230
+
231
+ def _sample_token_loss_inputs(hidden, targets, max_tokens):
232
+ max_tokens = int(max_tokens or 0)
233
+ if max_tokens <= 0:
234
+ return hidden.contiguous(), targets.contiguous(), int(targets.numel()), int(targets.numel())
235
+ flat_targets = targets.reshape(-1)
236
+ total = int(flat_targets.numel())
237
+ if total <= max_tokens:
238
+ return hidden.contiguous(), targets.contiguous(), total, total
239
+ # With-replacement sampling avoids building a full randperm each step; the sampled
240
+ # mean remains an unbiased estimator of the dense token CE mean.
241
+ idx = torch.randint(total, (max_tokens,), device=targets.device)
242
+ flat_hidden = hidden.reshape(total, hidden.size(-1))
243
+ return flat_hidden.index_select(0, idx).contiguous(), flat_targets.index_select(0, idx).contiguous(), int(max_tokens), total
244
+
245
+
246
+ def _choose_objectives(state, args, ar_weight, sat_weight, nat_weight, do_sat_periodic, do_nat_periodic):
247
+ mode = str(getattr(args, "dblock_objective_mode", "periodic") or "periodic").lower()
248
+ if mode != "stochastic":
249
+ return ar_weight > 0.0, sat_weight > 0.0 and do_sat_periodic, nat_weight > 0.0 and do_nat_periodic, "periodic"
250
+ choices = []
251
+ probs = []
252
+ if ar_weight > 0.0:
253
+ choices.append("ar")
254
+ probs.append(max(0.0, float(getattr(args, "dblock_ar_prob", 0.80))))
255
+ if sat_weight > 0.0 and not getattr(args, "ar_only", False):
256
+ choices.append("sat")
257
+ probs.append(max(0.0, float(getattr(args, "dblock_sat_prob", 0.10))))
258
+ if nat_weight > 0.0 and not getattr(args, "ar_only", False):
259
+ choices.append("nat")
260
+ probs.append(max(0.0, float(getattr(args, "dblock_nat_prob", 0.10))))
261
+ if not choices:
262
+ return False, False, False, "none"
263
+ total = sum(probs)
264
+ if total <= 0.0:
265
+ probs = [1.0 / len(choices) for _ in choices]
266
+ else:
267
+ probs = [p / total for p in probs]
268
+ picked = random.choices(choices, weights=probs, k=1)[0]
269
+ return picked == "ar", picked == "sat", picked == "nat", picked
270
+
271
+
272
+ def _dblock_step(core, ar_h, sat_h, nat_h, opt, scaler, args, ids, state):
273
+ import nB300_agillm4 as M
274
+
275
+ prof = _profile_active(state, args)
276
+ _step_t = _profile_tic(prof)
277
+ if torch.cuda.is_available():
278
+ torch.cuda.reset_peak_memory_stats()
279
+
280
+ _setup_t = _profile_tic(prof)
281
+ B = state["B"]
282
+ asg = state["assign"]
283
+ bs = state["bsig"]
284
+ T = ids.size(1)
285
+ use_layer_checkpoint = bool(getattr(args, "grad_checkpoint", False))
286
+ bi = _choose_block(state, args)
287
+ lo, hi = sorted([bs[bi], bs[bi + 1]])
288
+ layers = asg[bi]
289
+ sig = _sample_sigma(ids, lo, hi, args, state)
290
+ cs, co, ci = _edm_pre(sig)
291
+ w = _edm_w(sig, float(getattr(args, "dblock_edm_wmax", 5.0)))
292
+ SATB = M.SAT_BLOCK
293
+ ar_weight = float(getattr(args, "dblock_ar_weight", 1.0))
294
+ sat_weight = float(getattr(args, "dblock_sat_weight", 1.0))
295
+ nat_weight = float(getattr(args, "dblock_nat_weight", 1.0)) * float(getattr(args, "nat_loss_weight", 1.0))
296
+ do_sat_periodic = (not getattr(args, "ar_only", False)) and (
297
+ int(getattr(args, "sat_every", 1)) <= 1 or ((int(state.get("step", 0)) + 1) % int(getattr(args, "sat_every", 1)) == 0)
298
+ )
299
+ do_nat_periodic = (
300
+ nat_h is not None
301
+ and (not getattr(args, "ar_only", False))
302
+ and int(getattr(args, "nat_every", 1)) > 0
303
+ and (
304
+ int(getattr(args, "nat_every", 1)) <= 1
305
+ or ((int(state.get("step", 0)) + 1) % int(getattr(args, "nat_every", 1)) == 0)
306
+ )
307
+ )
308
+ run_ar, run_sat, run_nat, objective = _choose_objectives(
309
+ state, args, ar_weight, sat_weight, nat_weight, do_sat_periodic, do_nat_periodic
310
+ )
311
+ _profile_toc(state, "setup", _setup_t)
312
+
313
+ ar_val = 0.0
314
+ sat_val = 0.0
315
+ nat_val = 0.0
316
+
317
+ if run_ar:
318
+ causal = M.causal_mask(T, structured=M.use_structured_masks(args))
319
+ _t = _profile_tic(prof)
320
+ with M.amp(args.amp):
321
+ emb = core.emb(ids)
322
+ zt = emb + sig[:, None, None] * torch.randn_like(emb)
323
+ h = ci * zt
324
+ for lpos, li in enumerate(layers):
325
+ h = _run_block(core.blocks[li], h, causal, _dblock_checkpoint_this_layer(args, use_layer_checkpoint, lpos, len(layers)), args)
326
+ Dn = core.ln(cs * zt + co * h)
327
+ _profile_toc(state, "ar_forward", _t)
328
+ _t = _profile_tic(prof)
329
+ ar_hidden, ar_targets, ar_used, ar_total = _sample_token_loss_inputs(
330
+ Dn[:, :-1], ids[:, 1:], int(getattr(args, "dblock_ar_loss_tokens", 0))
331
+ )
332
+ ar = ar_weight * w * fused_ce(ar_hidden, ar_h.proj.weight, ar_targets)
333
+ ar_val = float(ar.detach())
334
+ _profile_toc(state, "ar_ce", _t)
335
+ _t = _profile_tic(prof)
336
+ scaler.scale(ar).backward()
337
+ _profile_toc(state, "ar_backward", _t)
338
+ del causal, emb, zt, h, Dn, ar_hidden, ar_targets, ar, ar_used, ar_total
339
+
340
+ if run_sat:
341
+ smask = M.sat_mask(T, structured=M.use_structured_masks(args))
342
+ _t = _profile_tic(prof)
343
+ with M.amp(args.amp):
344
+ emb2 = core.emb(ids)
345
+ zt2 = emb2 + sig[:, None, None] * torch.randn_like(emb2)
346
+ h2 = ci * zt2
347
+ for lpos, li in enumerate(layers):
348
+ h2 = _run_block(core.blocks[li], h2, smask, _dblock_checkpoint_this_layer(args, use_layer_checkpoint, lpos, len(layers)), args)
349
+ Ds = core.ln(cs * zt2 + co * h2)
350
+ last = Ds[:, -SATB:]
351
+ _profile_toc(state, "sat_forward", _t)
352
+ _t = _profile_tic(prof)
353
+ sat_hidden, sat_targets, sat_used, sat_total = _sample_token_loss_inputs(
354
+ last, ids[:, 1 : SATB + 1], int(getattr(args, "dblock_sat_loss_tokens", 0))
355
+ )
356
+ with M.amp(args.amp):
357
+ satf = fused_ce(sat_hidden, sat_h.proj.weight, sat_targets)
358
+ satv = (
359
+ M.EMIT_LAMBDA
360
+ * F.cross_entropy(
361
+ sat_h.gate(Ds[:, 0].float()),
362
+ torch.ones(ids.size(0), dtype=torch.long, device=ids.device),
363
+ )
364
+ if sat_h.gate is not None
365
+ else 0.0
366
+ )
367
+ sat = sat_weight * w * (satf + satv)
368
+ _profile_toc(state, "sat_ce", _t)
369
+ sat_val = float(sat.detach())
370
+ _t = _profile_tic(prof)
371
+ scaler.scale(sat).backward()
372
+ _profile_toc(state, "sat_backward", _t)
373
+ del smask, emb2, zt2, h2, Ds, last, sat_hidden, sat_targets, satf, satv, sat
374
+
375
+ if run_nat:
376
+ ratio = min(max(float(getattr(args, "nat_mask_ratio", 0.5)), 0.05), 0.95)
377
+ nat_ids = M._nat_ids_for_training(ids, int(getattr(args, "nat_max_tokens", 0)))
378
+ _t = _profile_tic(prof)
379
+ with M.amp(args.amp):
380
+ nat_in = nat_ids.clone()
381
+ m = torch.rand(nat_ids.shape, device=nat_ids.device) < ratio
382
+ if not bool(m.any()):
383
+ m[..., -1] = True
384
+ nat_in[m] = M.BLANK
385
+ hn = core.emb(nat_in)
386
+ for lpos, li in enumerate(layers):
387
+ hn = _run_block(core.blocks[li], hn, None, _dblock_checkpoint_this_layer(args, use_layer_checkpoint, lpos, len(layers)), args)
388
+ Dnat = core.ln(hn)
389
+ _profile_toc(state, "nat_forward", _t)
390
+ _t = _profile_tic(prof)
391
+ nat_hidden = Dnat[m]
392
+ nat_targets = nat_ids[m]
393
+ nat_hidden, nat_targets, nat_used, nat_total = _sample_token_loss_inputs(
394
+ nat_hidden.unsqueeze(0), nat_targets.unsqueeze(0), int(getattr(args, "dblock_nat_loss_tokens", 0))
395
+ )
396
+ nat = nat_weight * fused_ce(nat_hidden, nat_h.proj.weight, nat_targets)
397
+ nat_val = float(nat.detach())
398
+ _profile_toc(state, "nat_ce", _t)
399
+ _t = _profile_tic(prof)
400
+ scaler.scale(nat).backward()
401
+ _profile_toc(state, "nat_backward", _t)
402
+ del nat_ids, nat_in, m, hn, Dnat, nat_hidden, nat_targets, nat, nat_used, nat_total
403
+
404
+ total_val = ar_val + sat_val + nat_val
405
+ if not math.isfinite(total_val):
406
+ opt.zero_grad(set_to_none=True)
407
+ if torch.cuda.is_available():
408
+ torch.cuda.empty_cache()
409
+ print(f"[dblock] non-finite loss {total_val}; skipped optimizer step", flush=True)
410
+ _profile_toc(state, "step_total", _step_t)
411
+ _profile_step_done(state, args)
412
+ _update_stats(state, bi, total_val)
413
+ return total_val
414
+
415
+ _t = _profile_tic(prof)
416
+ scaler.unscale_(opt)
417
+ nn.utils.clip_grad_norm_([p for g in opt.param_groups for p in g["params"]], 1.0)
418
+ scaler.step(opt)
419
+ scaler.update()
420
+ opt.zero_grad(set_to_none=True)
421
+ _profile_toc(state, "opt_step", _t)
422
+
423
+ peak_alloc = None
424
+ peak_reserved = None
425
+ if torch.cuda.is_available():
426
+ peak_alloc = torch.cuda.max_memory_allocated() / (1024**3)
427
+ peak_reserved = torch.cuda.max_memory_reserved() / (1024**3)
428
+ _profile_toc(state, "step_total", _step_t)
429
+ _profile_step_done(state, args)
430
+ _update_stats(state, bi, total_val)
431
+ _maybe_log(state, args, bi, layers, ar_val, sat_val, nat_val, total_val, peak_alloc, peak_reserved, objective=objective)
432
+ return total_val