dimitarpg13 commited on
Commit
c7017d2
·
verified ·
1 Parent(s): e6fd098

Upload code/parf/test_cfc_baoab.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. code/parf/test_cfc_baoab.py +545 -0
code/parf/test_cfc_baoab.py ADDED
@@ -0,0 +1,545 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Correctness tests for the CfC / BAOAB integrator path.
2
+
3
+ Run directly (CPU, a few seconds)::
4
+
5
+ python test_cfc_baoab.py
6
+
7
+ The tests are ordered from "the maths is right" to "the model still
8
+ trains", and three of them are load-bearing for interpreting any
9
+ CfC-vs-Verlet training comparison:
10
+
11
+ * :func:`test_analytic_vtheta_equivalence` -- switching V_theta's force
12
+ from autograd to its closed form must leave *every parameter
13
+ gradient* unchanged. If it did not, the "analytic V_theta" arm would
14
+ be a different model, not a different implementation.
15
+ * :func:`test_cfc_force_preservation` -- the CfC split must reproduce
16
+ the same force field as the unsplit integrator, with the two agreeing
17
+ to third order in dt. A second-order discrepancy would mean the
18
+ propagator changed the physics rather than the integration.
19
+ * :func:`test_cfc_stiffness_immunity` -- the reason for the whole
20
+ exercise: a well sharp enough to blow up the explicit step must leave
21
+ the CfC step bounded.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import math
27
+ import sys
28
+ from pathlib import Path
29
+
30
+ import torch
31
+
32
+ sys.path.insert(0, str(Path(__file__).parent))
33
+
34
+ from cfc_baoab import cfc_substep, ou_step # noqa: E402
35
+ from model_aniso_gaussian_vtheta import ( # noqa: E402
36
+ AnisotropicDepthConditionedGaussianVTheta,
37
+ install_aniso_depth_routing,
38
+ )
39
+ from model_fock_parf_multixi import ( # noqa: E402
40
+ FockMultiXiPARFConfig,
41
+ FockMultiXiPARFLM,
42
+ )
43
+
44
+ TOL = 1e-5
45
+
46
+
47
+ # ---------------------------------------------------------------------------
48
+ # Fixtures
49
+ # ---------------------------------------------------------------------------
50
+ def _make_model(integrator="verlet", analytic=False, d=16, L=3, seed=0,
51
+ **cfg_kwargs):
52
+ """A small CPU Fock-PARFLM with the production aniso-Gaussian V_theta."""
53
+ torch.manual_seed(seed)
54
+ cfg = FockMultiXiPARFConfig(
55
+ vocab_size=97, d=d, max_len=32, L=L, v_hidden=32, v_depth=2,
56
+ dt=1.0, fixed_gamma=0.10, init_gamma=1.0,
57
+ mass_mode="global",
58
+ causal_force=True, ln_after_step=True,
59
+ xi_channels=3, xi_alpha_inits=[0.5, 0.9, 0.99],
60
+ xi_learnable=True, xi_alpha_init_mode="explicit",
61
+ v_phi_kind="structural_competitive",
62
+ v_phi_d_type=8, v_phi_d_angle=4,
63
+ v_phi_phi_hidden=16, v_phi_theta_hidden=16, v_phi_mlp_hidden=16,
64
+ v_phi_n_heads=2, top_k=4, score_head_hidden=8,
65
+ use_gathered_v_phi=True, use_layer_checkpoint=False,
66
+ ln_before_distance=True, per_layer_v_phi_scale=True,
67
+ fock_version="v2", n_registers=4, d_k=8,
68
+ creation_gate_hidden=8, stack_discipline=True,
69
+ reverse_channel=True, reverse_channel_stable=True,
70
+ reverse_channel_per_layer=True, reverse_channel_warmup_steps=0,
71
+ register_repulsion=False,
72
+ prefix_causal_registers=True,
73
+ use_output_bias=True, tie_embeddings=False,
74
+ integrator=integrator, vtheta_analytic_force=analytic,
75
+ **cfg_kwargs,
76
+ )
77
+ torch.manual_seed(seed)
78
+ model = FockMultiXiPARFLM(cfg)
79
+ torch.manual_seed(seed)
80
+ model.V_theta = AnisotropicDepthConditionedGaussianVTheta(
81
+ d=d, K=4, n_ctx=cfg.xi_channels, n_layers=L, rank=2,
82
+ init_log_precision=-math.log(d), precision_max=2.0 / d,
83
+ )
84
+ install_aniso_depth_routing(model)
85
+ model.eval()
86
+ return model, cfg
87
+
88
+
89
+ def _layer_inputs(model, cfg, B=2, T=6, seed=1):
90
+ torch.manual_seed(seed)
91
+ d = cfg.d
92
+ h = torch.randn(B, T, d, requires_grad=True)
93
+ v = torch.randn(B, T, d) * 0.1
94
+ m_b = torch.ones(B, T, 1)
95
+ return h, v, m_b
96
+
97
+
98
+ # ---------------------------------------------------------------------------
99
+ # 1. Propagator maths
100
+ # ---------------------------------------------------------------------------
101
+ def test_propagator_exactness():
102
+ """cfc_substep reproduces the analytic harmonic solution."""
103
+ torch.manual_seed(0)
104
+ B, T, d = 2, 3, 4
105
+ m = torch.ones(B, T, 1)
106
+ k = torch.rand(B, T, d) * 9.0 + 1.0
107
+ mu = torch.randn(B, T, d)
108
+ h, v = torch.randn(B, T, d), torch.randn(B, T, d)
109
+ dt = 0.83
110
+
111
+ h1, v1 = cfc_substep(h, v, -k * (h - mu), k, m, dt)
112
+
113
+ omega = (k / m).sqrt()
114
+ x = h - mu
115
+ h_exact = mu + x * torch.cos(omega * dt) + (v / omega) * torch.sin(omega * dt)
116
+ v_exact = -omega * x * torch.sin(omega * dt) + v * torch.cos(omega * dt)
117
+ assert (h1 - h_exact).abs().max() < TOL
118
+ assert (v1 - v_exact).abs().max() < TOL
119
+ print(" [ok] propagator matches the analytic harmonic solution")
120
+
121
+
122
+ def test_ou_exact_decay():
123
+ """The O-step decays by exp(-gamma dt), not the Verlet 1/(1+gamma dt)."""
124
+ v = torch.randn(2, 3, 4)
125
+ gamma, dt = 0.30, 1.0
126
+ out = ou_step(v, gamma, dt, T=0.0)
127
+ assert (out - math.exp(-gamma * dt) * v).abs().max() < TOL
128
+ verlet_factor = 1.0 / (1.0 + gamma * dt)
129
+ rel = abs(math.exp(-gamma * dt) - verlet_factor) / verlet_factor
130
+ assert rel > 0.03, "expected the two friction models to differ measurably"
131
+ print(f" [ok] O-step friction exact (differs from Verlet's by {rel:.1%} "
132
+ f"at gamma={gamma})")
133
+
134
+
135
+ # ---------------------------------------------------------------------------
136
+ # 2. The analytic V_theta force is the same model, not a different one
137
+ # ---------------------------------------------------------------------------
138
+ def test_analytic_vtheta_equivalence():
139
+ """Closed-form V_theta force must give identical parameter gradients."""
140
+ grads = {}
141
+ for analytic in (False, True):
142
+ model, cfg = _make_model(integrator="verlet", analytic=analytic)
143
+ model.train()
144
+ torch.manual_seed(7)
145
+ x = torch.randint(0, cfg.vocab_size, (2, 8))
146
+ y = torch.randint(0, cfg.vocab_size, (2, 8))
147
+ _, loss = model(x, y)
148
+ loss.backward()
149
+ grads[analytic] = {
150
+ n: p.grad.detach().clone()
151
+ for n, p in model.named_parameters() if p.grad is not None
152
+ }
153
+ grads[f"loss_{analytic}"] = float(loss)
154
+
155
+ assert abs(grads["loss_False"] - grads["loss_True"]) < 1e-6, (
156
+ grads["loss_False"], grads["loss_True"])
157
+
158
+ common = set(grads[False]) & set(grads[True])
159
+ assert len(common) > 20, f"suspiciously few shared params: {len(common)}"
160
+ worst, worst_name = 0.0, ""
161
+ for n in common:
162
+ a, b = grads[False][n], grads[True][n]
163
+ denom = max(a.abs().max().item(), 1e-8)
164
+ err = (a - b).abs().max().item() / denom
165
+ if err > worst:
166
+ worst, worst_name = err, n
167
+ assert worst < 1e-4, f"gradient mismatch {worst:.2e} at {worst_name}"
168
+
169
+ # ...and V_theta really is being trained through the closed form.
170
+ vt = [n for n in common if n.startswith("V_theta")]
171
+ assert vt, "no V_theta parameters received gradients"
172
+ assert any(grads[True][n].abs().max() > 0 for n in vt)
173
+ print(f" [ok] analytic V_theta force: identical loss and gradients "
174
+ f"(worst rel. err {worst:.1e} over {len(common)} tensors)")
175
+
176
+
177
+ # ---------------------------------------------------------------------------
178
+ # 3. Verlet path is untouched by the refactor
179
+ # ---------------------------------------------------------------------------
180
+ def test_verlet_formula_unchanged():
181
+ """_layer_step still computes the historical damped-Verlet update."""
182
+ model, cfg = _make_model(integrator="verlet")
183
+ h, v, m_b = _layer_inputs(model, cfg)
184
+ dt, gamma = cfg.dt, model.gamma
185
+ h_prev = (h - dt * v).detach()
186
+
187
+ model.V_theta.set_active_layer(0)
188
+ got = model._layer_step(h, h_prev, m_b, gamma, dt, layer_idx=0)
189
+
190
+ # Reference: force from a single joint autograd call, then the
191
+ # historical update, written out longhand.
192
+ h_ref = h.detach().clone().requires_grad_(True)
193
+ xis = model.xi_module(h_ref.detach())
194
+ model.V_theta.set_active_layer(0)
195
+ U = model.V_theta(xis, h_ref).sum() + model._pair_potential(h_ref, 0)
196
+ grad_U, = torch.autograd.grad(U, h_ref)
197
+ f = -grad_U
198
+ denom = 1.0 + dt * gamma
199
+ expect = h_ref + (h_ref - h_prev) / denom + (dt * dt / (m_b * denom)) * f
200
+ expect = model._project(expect)
201
+
202
+ err = (got - expect).abs().max().item()
203
+ assert err < 1e-5, err
204
+
205
+ h_new, h_prev_out = model._layer_step_ex(h, h_prev, m_b, gamma, dt, 0)
206
+ assert torch.equal(h_prev_out, h), "Verlet must pass h through as h_prev"
207
+ print(f" [ok] Verlet update unchanged (max dev {err:.1e}) and "
208
+ f"_layer_step_ex passes h through")
209
+
210
+
211
+ # ---------------------------------------------------------------------------
212
+ # 4. The CfC split preserves the force field
213
+ # ---------------------------------------------------------------------------
214
+ def _step_positions(model, cfg, integrator, dt):
215
+ """One layer step from a fixed (h, v), returning (h, v, h_new)."""
216
+ model.cfg.integrator = integrator
217
+ h, v, m_b = _layer_inputs(model, cfg)
218
+ h, v, m_b = h.double(), v.double(), m_b.double()
219
+ h = h.detach().requires_grad_(True)
220
+ h_prev = (h - dt * v).detach()
221
+ model.V_theta.set_active_layer(0)
222
+ h_new, _ = model._layer_step_langevin(h, h_prev, m_b, 0.0, dt, layer_idx=0)
223
+ return h, v, h_new
224
+
225
+
226
+ def test_cfc_force_preservation():
227
+ """CfC and plain BAOAB must agree to O(dt^3): same forces, different flow.
228
+
229
+ Both schemes expand to ``h + dt v + (dt^2/2m) f + O(dt^3)`` with the
230
+ *same* f. So their difference must fall by 8x when dt is halved. If
231
+ the CfC propagator had changed the force field rather than only the
232
+ way it is integrated, the leading discrepancy would be O(dt^2) and the
233
+ ratio would be ~4.
234
+
235
+ Run in float64: at float32 the true dt^3 term is below the rounding
236
+ floor and this test would just measure ulp noise.
237
+ """
238
+ model, cfg = _make_model(integrator="baoab")
239
+ model.cfg.ln_after_step = False # LN would mask the scaling law
240
+ model.double()
241
+
242
+ dts = (0.2, 0.1)
243
+ for other in ("baoab_cfc", "baoab_cfc_lowrank"):
244
+ devs = []
245
+ for dt in dts:
246
+ _, _, h_plain = _step_positions(model, cfg, "baoab", dt)
247
+ _, _, h_other = _step_positions(model, cfg, other, dt)
248
+ devs.append((h_plain - h_other).abs().max().item())
249
+
250
+ # Normalise by dt^2 so the comparison is against the force term both
251
+ # schemes are supposed to share, not against the raw displacement.
252
+ rel = devs[0] / (dts[0] ** 2)
253
+ ratio = (devs[0] / dts[0] ** 2) / max(devs[1] / dts[1] ** 2, 1e-300)
254
+
255
+ assert rel < 1e-3, f"{other} force term deviates by {rel:.2e} (absolute)"
256
+ assert 1.6 < ratio < 2.6, (
257
+ f"{other} force-term deviation scales as dt^{math.log2(ratio):.2f}, "
258
+ f"expected dt^1 after normalising (i.e. dt^3 raw); a flat ratio "
259
+ f"(~1) would mean the force field itself changed")
260
+ print(f" [ok] {other} preserves the force field: raw deviation "
261
+ f"{devs[0]:.2e} -> {devs[1]:.2e} when dt halves "
262
+ f"(dt^{math.log2(devs[0]/devs[1]):.2f}, expect dt^3)")
263
+
264
+
265
+ # ---------------------------------------------------------------------------
266
+ # 4b. Backward pass must survive k_diag == 0 (a token far from every well)
267
+ # ---------------------------------------------------------------------------
268
+ def test_cfc_substep_zero_stiffness_no_nan():
269
+ """k_diag == 0 is a real, expected state, not a synthetic corner case.
270
+
271
+ ``model_aniso_gaussian_vtheta.harmonic_terms`` documents ``k_diag >= 0``
272
+ and the CfC forward pass handles ``omega -> 0`` exactly via
273
+ ``torch.sinc`` -- but before the ``_OMEGA_SQ_FLOOR`` fix, the
274
+ ``sqrt()`` that *produces* omega from ``k_diag`` had an infinite
275
+ derivative at 0. Whenever the upstream gradient through
276
+ ``sinc``/``cos`` at that element was exactly 0 (the common case, since
277
+ ``sinc'(0) == 0``), the product ``0 * inf`` gave ``nan``, which then
278
+ poisons every parameter ``k_diag`` traces back to via the chain rule --
279
+ this is what showed up in a live OWT run as ``grad=nan`` starting
280
+ around step 2250 and growing more frequent as the wells sharpened
281
+ (more tokens landing in the ``g_k = w_k * exp(-0.5 * quad_form) == 0``
282
+ underflow regime).
283
+ """
284
+ B, T, d = 2, 3, 4
285
+ m = torch.ones(B, T, 1)
286
+ dt = 1.0
287
+ # Mix exact zeros with ordinary positive stiffness in the same tensor,
288
+ # exactly as a real per-token, per-dimension k_diag would.
289
+ k = torch.tensor([0.0, 3.0, 0.0, 5.0]).view(1, 1, d).expand(B, T, d).clone()
290
+ k.requires_grad_(True)
291
+ h = torch.randn(B, T, d, requires_grad=True)
292
+ v = torch.randn(B, T, d, requires_grad=True)
293
+ f_harm = torch.randn(B, T, d)
294
+
295
+ h_new, v_new = cfc_substep(h, v, f_harm, k, m, dt)
296
+ assert torch.isfinite(h_new).all() and torch.isfinite(v_new).all()
297
+ (h_new.pow(2).sum() + v_new.pow(2).sum()).backward()
298
+
299
+ assert torch.isfinite(k.grad).all(), f"nan/inf in k.grad: {k.grad}"
300
+ assert torch.isfinite(h.grad).all(), f"nan/inf in h.grad: {h.grad}"
301
+ assert torch.isfinite(v.grad).all(), f"nan/inf in v.grad: {v.grad}"
302
+ print(" [ok] backward through k_diag == 0 stays finite "
303
+ f"(k.grad at zero-stiffness dims: {k.grad[0, 0, [0, 2]].tolist()})")
304
+
305
+
306
+ # ---------------------------------------------------------------------------
307
+ # 5. The point of the exercise: stiffness immunity
308
+ # ---------------------------------------------------------------------------
309
+ def test_cfc_stiffness_immunity():
310
+ """A well sharp enough to blow up the explicit step stays bounded."""
311
+ B, T, d = 1, 4, 8
312
+ m = torch.ones(B, T, 1)
313
+ mu = torch.zeros(B, T, d)
314
+ h0 = torch.full((B, T, d), 0.5)
315
+ v0 = torch.zeros(B, T, d)
316
+ dt = 1.0
317
+
318
+ # The explicit step is stable only for omega*dt < 2, i.e. K < 4/dt^2 = 4
319
+ # here; the CfC step has no such limit.
320
+ for k_val, tag in ((0.25, "mild"), (1e4, "stiff")):
321
+ k = torch.full((B, T, d), k_val)
322
+ # Explicit (Verlet-style) integration of the same spring.
323
+ h, h_prev = h0.clone(), h0.clone()
324
+ for _ in range(12):
325
+ f = -k * (h - mu)
326
+ h_next = 2 * h - h_prev + (dt * dt / m) * f
327
+ h_prev, h = h, h_next
328
+ verlet_amp = h.abs().max().item()
329
+
330
+ # CfC integration of the same spring.
331
+ hc, vc = h0.clone(), v0.clone()
332
+ for _ in range(12):
333
+ hc, vc = cfc_substep(hc, vc, -k * (hc - mu), k, m, dt)
334
+ cfc_amp = hc.abs().max().item()
335
+
336
+ if tag == "mild":
337
+ assert verlet_amp < 10.0 and cfc_amp < 10.0
338
+ else:
339
+ # 12 explicit steps at omega*dt = 100 amplify by ~1e48, which
340
+ # overflows float32 outright -- exactly the runaway that shows
341
+ # up in training as a gradient spike.
342
+ assert not (verlet_amp < 1e6), (
343
+ f"expected the explicit step to diverge at K={k_val}, "
344
+ f"got {verlet_amp:.2e}")
345
+ assert cfc_amp <= 0.5 + 1e-4, cfc_amp
346
+ blew_up = not math.isfinite(verlet_amp)
347
+ print(f" [ok] K={k_val:<8g} ({tag:>5}): explicit -> "
348
+ f"{'overflow (non-finite)' if blew_up else f'{verlet_amp:.3e}'}, "
349
+ f"CfC -> {cfc_amp:.3e}")
350
+
351
+
352
+ # ---------------------------------------------------------------------------
353
+ # 6. End-to-end: every integrator trains
354
+ # ---------------------------------------------------------------------------
355
+ def test_end_to_end_all_integrators():
356
+ """Forward + backward through the full Fock stack, for each integrator."""
357
+ results = {}
358
+ for integrator in ("verlet", "baoab", "baoab_cfc", "baoab_cfc_lowrank"):
359
+ for ckpt in (False, True):
360
+ model, cfg = _make_model(integrator=integrator, analytic=True)
361
+ model.cfg.use_layer_checkpoint = ckpt
362
+ model.train()
363
+ torch.manual_seed(11)
364
+ x = torch.randint(0, cfg.vocab_size, (2, 8))
365
+ y = torch.randint(0, cfg.vocab_size, (2, 8))
366
+ _, loss = model(x, y)
367
+ loss.backward()
368
+ assert torch.isfinite(loss), f"{integrator}: non-finite loss"
369
+ n_grad = sum(
370
+ 1 for p in model.parameters()
371
+ if p.grad is not None and torch.isfinite(p.grad).all()
372
+ )
373
+ n_param = sum(1 for _ in model.parameters())
374
+ assert n_grad > 0.8 * n_param, (
375
+ f"{integrator}: only {n_grad}/{n_param} params got finite grads")
376
+ if not ckpt:
377
+ results[integrator] = float(loss)
378
+ print(f" [ok] {integrator:<10} loss={results[integrator]:.4f} "
379
+ f"(with and without layer checkpointing)")
380
+
381
+ assert abs(results["verlet"] - results["baoab"]) > 1e-6, (
382
+ "baoab produced a bit-identical result to verlet -- the integrator "
383
+ "flag is probably not reaching the layer step")
384
+
385
+
386
+ def test_lowrank_integrator_wired_and_finite():
387
+ """baoab_cfc_lowrank reaches the step, stays finite with a sharp
388
+ off-diagonal well, and produces dynamics distinct from baoab_cfc.
389
+
390
+ Sharpening ``B_proj`` blows up the off-diagonal curvature ``sigma_max(B_k)``:
391
+ under ``baoab_cfc`` that force sits in the explicit kick (the residual
392
+ channel with an ``omega dt < 2`` wall), whereas ``baoab_cfc_lowrank``
393
+ routes it through the exact low-rank rotation, so it must (a) stay
394
+ finite even with the force clamp off and (b) differ from the cfc path.
395
+ """
396
+ model, cfg = _make_model(integrator="baoab_cfc_lowrank", analytic=True)
397
+ model.cfg.ln_after_step = False
398
+ model.cfg.force_clamp_max = None
399
+ for bank in model.V_theta.banks: # sharpen the off-diagonal
400
+ with torch.no_grad():
401
+ bank.B_proj.weight.mul_(30.0)
402
+ bank.B_proj.bias.add_(2.0)
403
+
404
+ h, v, m_b = _layer_inputs(model, cfg)
405
+ dt = cfg.dt
406
+ h_prev = (h - dt * v).detach()
407
+ model.V_theta.set_active_layer(0)
408
+
409
+ h_l, hp_l = model._layer_step_langevin(h, h_prev, m_b, model.gamma, dt, 0)
410
+ assert torch.isfinite(h_l).all() and torch.isfinite(hp_l).all(), (
411
+ "baoab_cfc_lowrank produced non-finite state with a sharp well")
412
+
413
+ model.cfg.integrator = "baoab_cfc"
414
+ h_c, _ = model._layer_step_langevin(h, h_prev, m_b, model.gamma, dt, 0)
415
+ assert not torch.allclose(h_l, h_c, atol=1e-5), (
416
+ "lowrank flag did not change the dynamics -- not reaching the step")
417
+ excursion_l = (h_l - h).abs().max().item()
418
+ excursion_c = (h_c - h).abs().max().item()
419
+ print(f" [ok] baoab_cfc_lowrank wired + finite (sharp-well excursion "
420
+ f"{excursion_l:.3e} vs baoab_cfc {excursion_c:.3e}), distinct paths")
421
+
422
+
423
+ def test_truncated_lowrank_checkpoint_recompute():
424
+ """Truncated low-rank modes (``lowrank_max_modes`` set) must recompute
425
+ identically under gradient checkpointing.
426
+
427
+ The mode extractor runs inside the checkpointed layer step, so its output
428
+ metadata must match on the forward and on the backward recompute. The old
429
+ ``torch.svd_lowrank`` path drew from the global RNG and had a ``try/except``
430
+ CPU fallback, so the recompute could take a different branch/device and
431
+ trip ``CheckpointError: recomputed values ... have different metadata``.
432
+ The deterministic, branch-free, device-stable randomised SVD must not.
433
+ """
434
+ for ckpt in (False, True):
435
+ model, cfg = _make_model(integrator="baoab_cfc_lowrank", analytic=True,
436
+ lowrank_max_modes=4)
437
+ model.cfg.use_layer_checkpoint = ckpt
438
+ model.train()
439
+ torch.manual_seed(11)
440
+ x = torch.randint(0, cfg.vocab_size, (2, 8))
441
+ y = torch.randint(0, cfg.vocab_size, (2, 8))
442
+ _, loss = model(x, y)
443
+ loss.backward() # CheckpointError before fix
444
+ assert torch.isfinite(loss), "truncated low-rank: non-finite loss"
445
+ n_grad = sum(1 for p in model.parameters()
446
+ if p.grad is not None and torch.isfinite(p.grad).all())
447
+ assert n_grad > 0.8 * sum(1 for _ in model.parameters()), (
448
+ "truncated low-rank: too few finite grads")
449
+ print(" [ok] truncated low-rank modes recompute cleanly under "
450
+ "gradient checkpointing")
451
+
452
+
453
+ def test_velocity_encoding_roundtrip():
454
+ """The BAOAB step's h_prev_out really carries the outgoing velocity."""
455
+ model, cfg = _make_model(integrator="baoab_cfc", analytic=True)
456
+ h, v, m_b = _layer_inputs(model, cfg)
457
+ dt = cfg.dt
458
+ h_prev = (h - dt * v).detach()
459
+ model.V_theta.set_active_layer(0)
460
+ h_new, h_prev_out = model._layer_step_langevin(
461
+ h, h_prev, m_b, model.gamma, dt, layer_idx=0,
462
+ )
463
+ v_out = (h_new - h_prev_out) / dt
464
+ assert torch.isfinite(v_out).all()
465
+ assert not torch.equal(h_prev_out, h), (
466
+ "h_prev_out should encode a velocity, not just pass h through")
467
+ print(f" [ok] velocity encoded in h_prev_out "
468
+ f"(|v_out|={v_out.abs().mean():.4f})")
469
+
470
+
471
+ # ---------------------------------------------------------------------------
472
+ def main():
473
+ tests = [
474
+ test_propagator_exactness,
475
+ test_ou_exact_decay,
476
+ test_analytic_vtheta_equivalence,
477
+ test_verlet_formula_unchanged,
478
+ test_cfc_force_preservation,
479
+ test_cfc_substep_zero_stiffness_no_nan,
480
+ test_cfc_stiffness_immunity,
481
+ test_lowrank_integrator_wired_and_finite,
482
+ test_truncated_lowrank_checkpoint_recompute,
483
+ test_velocity_encoding_roundtrip,
484
+ test_end_to_end_all_integrators,
485
+ ]
486
+ for t in tests:
487
+ print(f"\n{t.__name__}:")
488
+ t()
489
+ print("\nAll CfC/BAOAB tests passed.")
490
+
491
+
492
+ if __name__ == "__main__":
493
+ main()
494
+
495
+
496
+ # ---------------------------------------------------------------------------
497
+ # 4c. _sinc must not reach for torch.sinc (jiterator / NVRTC dependency)
498
+ # ---------------------------------------------------------------------------
499
+ def test_sinc_matches_reference_and_avoids_jiterator():
500
+ """``torch.sinc`` has no prebuilt CUDA kernel -- it is NVRTC-compiled at
501
+ runtime through PyTorch's jiterator, so it dies on any host whose
502
+ ``libnvrtc-builtins`` does not match the build. A Colab image refresh on
503
+ 2026-09-23 produced exactly that, killing Cell 5 mid-programme on code
504
+ that three earlier runs had executed cleanly:
505
+
506
+ nvrtc: error: failed to open libnvrtc-builtins.so.13.0
507
+
508
+ The integrator must therefore not depend on the host's ability to
509
+ compile CUDA at runtime. This test pins both halves of that: the source
510
+ is free of ``torch.sinc``, and the replacement still agrees with it to
511
+ floating-point precision (``torch.sinc`` on CPU is a fine oracle -- the
512
+ jiterator is a CUDA-only path).
513
+ """
514
+ import inspect
515
+ import math as _math
516
+ import cfc_baoab as _mod
517
+
518
+ for _fn in (_mod._sinc, _mod._psi):
519
+ # strip the docstring first: it names torch.sinc on purpose, to say
520
+ # why the function does not call it.
521
+ _src = inspect.getsource(_fn)
522
+ if _fn.__doc__:
523
+ _src = _src.replace(_fn.__doc__, '')
524
+ assert 'torch.sinc' not in _src, (
525
+ f'{_fn.__name__} reaches for torch.sinc again -- that is a '
526
+ f'jiterator op and will fail on hosts with a mismatched NVRTC.')
527
+
528
+ for dtype, tol in ((torch.float32, 1e-6), (torch.float64, 1e-12)):
529
+ x = torch.tensor(
530
+ [0.0, 1e-12, 1e-6, 1e-4, 1e-3, 1e-2, 0.1, 1.0,
531
+ _math.pi, 10.0, -2.5, 1e3], dtype=dtype)
532
+ assert torch.allclose(_mod._sinc(x), torch.sinc(x / _math.pi), atol=tol)
533
+ assert torch.allclose(
534
+ _mod._psi(x), 0.5 * torch.sinc(x / (2 * _math.pi)) ** 2, atol=tol)
535
+
536
+ # the omega -> 0 limits the BAOAB A-step degenerates to
537
+ zero = torch.zeros(3, dtype=torch.float64)
538
+ assert torch.allclose(_mod._sinc(zero), torch.ones(3, dtype=torch.float64))
539
+ assert torch.allclose(_mod._psi(zero), torch.full((3,), 0.5, dtype=torch.float64))
540
+
541
+ # and the gradient stays finite there: the naive sin(x)/x would give
542
+ # 0/0 -> nan in the discarded branch, which where() back-propagates.
543
+ g = torch.zeros(4, requires_grad=True)
544
+ _mod._sinc(g).sum().backward()
545
+ assert torch.isfinite(g.grad).all(), g.grad