File size: 57,444 Bytes
973468b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
"""

Quazimoto-LM — a language model whose channel-mixing block is a bank of

coupled phase oscillators (Kuramoto dynamics) arranged in concentric rings.



Backbone: standard causal transformer (token mixing = attention, tied LM head).

Novelty:  the per-layer MLP is replaced by a QuazimotoBlock that maps the hidden

          state to N oscillator phases on rings [4, 8, 16, 32, ...], runs a few

          differentiable Euler steps of structured Kuramoto coupling with a

          learnable frustration alpha, then reads out [cos, sin] of the phases.



Set ring coupling to dense + alpha=0 and it degenerates toward AKOrN-style

oscillatory neurons; the ring structure + hierarchy-to-center is the Quazimoto bit.

"""

from dataclasses import dataclass, field
import math
import os
import torch
import torch.nn as nn
import torch.nn.functional as F

_PKG_DIR = os.path.dirname(os.path.abspath(__file__))

# family traits (DERF soft_clamp, RMSNorm, and the opt-in refinement/aux blocks)
from family import (soft_clamp, RMSNorm, HRMRefinementBlock, MoESwiGLU,
                    MTPHead, JEPAPredictorBlock, PhaseAttentionRing, EngramRing,
                    RingControllerBank, RingSpecialists)
import instrument as _viz   # live-visualizer capture hooks (no-op unless a recorder is active)
from wheeler import WheelerDeWittBlock   # Wheeler-DeWitt quantum-gravity wave mixer


@dataclass
class QuazimotoConfig:
    vocab_size: int = 32000
    n_layer: int = 10
    n_head: int = 12
    d_model: int = 768
    block_size: int = 1024
    dropout: float = 0.0
    # Quazimoto oscillator bank
    ring_sizes: tuple = (4, 8, 16, 32, 64, 128, 256)  # (unused by the Hydra tip mixer)
    osc_steps: int = 4          # differentiable Kuramoto Euler steps
    osc_dt: float = 0.5
    readout_mult: int = 3       # MLP expansion on the readout
    # Wheeler-DeWitt mixer: K minisuperspace modes with a Lorentzian DeWitt supermetric.
    # The block runs a leapfrog WAVE integration (Klein-Gordon on superspace) rather than
    # a forward flow; "time" is the volume/scale mode (mode 0, timelike). See wheeler.py.
    wdw_modes: int = 64         # K minisuperspace modes (analog of n_osc)
    wdw_steps: int = 4          # leapfrog steps of the Wheeler-DeWitt wave equation
    wdw_dt: float = 0.5         # base intrinsic-time step (scaled by the learnable lapse)
    wdw_constraint_weight: float = 0.05  # weight on the Hamiltonian-constraint aux loss <H^2>
    # Mandelbrot phase seeding: seed the K minisuperspace modes from each token's fractal
    # orbit angles (frozen, parameter-free), added through a zero-init gate.
    use_fractal_phase_seed: bool = False
    # family-trait discipline
    osc_bound: float = 10.0     # DERF soft_clamp bound on block output (BPTT-stability)
    gate_init_open: float = 0.9 # tanh-gate opening at init (Quazimoto is the main mixer,
                                #   so it starts OPEN; set 0.0 for a pure no-op refinement)
    # interstitial rings: between each pair of oscillator rings, insert a phase-attention
    # ring + an engram ring that ABSORB info and INJECT it into the two neighbor rings.
    use_rings: bool = False
    ring_attn_heads: int = 4
    ring_attn_head_dim: int = 16
    ring_engram_compress: int = 32
    ring_engram_heads: int = 2
    ring_engram_table: int = 2048
    ring_engram_ngram: int = 3
    # per-ring memory specialists: a small MoE of `ring_n_specialists` mini experts
    # PER oscillator ring, each holding test-time-writable input/output stores that
    # act as an addressable context memory at inference (see family.RingSpecialists).
    use_ring_specialists: bool = False
    ring_n_specialists: int = 7
    ring_spec_key_dim: int = 32
    ring_spec_slot_dim: int = 64
    ring_spec_top_k: int = 2
    ring_spec_write_lr: float = 0.1
    # per-ring self-organizing controllers (~1M total: one tiny net per oscillator
    # ring, shared across layers, weights self-optimize by a predictive/free-energy rule)
    use_ring_controllers: bool = False
    ring_ctrl_feat: int = 384        # fast-weight predictor width (dominates the ~150k/ring)
    ring_ctrl_local_lr: float = 0.01 # delta-rule (surprise-minimization) step size
    # opt-in family trait modules (all safe no-ops at init)
    use_hrm: bool = False
    hrm_steps: int = 3
    hrm_dim: int = 256
    hrm_gate_init: float = 0.1   # HRM gates start OPEN (random initial state needs a path out)
    use_moe: bool = False
    moe_intermediate: int = 768
    moe_n_routed: int = 4
    moe_n_shared: int = 1
    moe_top_k: int = 2
    use_mtp: bool = False
    mtp_layers: int = 4   # depth of MTP draft heads (predict +1..+mtp_layers); used for spec-decode
    mtp_loss_weight: float = 0.3
    use_jepa: bool = False
    jepa_pred_dim: int = 256
    jepa_horizon: int = 1
    jepa_loss_weight: float = 0.1
    # ---- attention: ported from model_v2.MLADerfXSAAttention ----
    head_dim: int = 64
    qk_rope_head_dim: int = 32       # partial RoPE: rope part of each head
    nope_head_dim: int = 32          #               no-pos part (sum == head_dim)
    num_key_value_heads: int = 4     # GQA (== n_head for full MHA)
    q_lora_rank: int = 384           # MLA low-rank q projection
    o_lora_rank: int = 384           # MLA low-rank output projection
    use_qk_norm: bool = True         # per-head RMSNorm on Q/K before RoPE
    use_derf: bool = False           # erf attention instead of softmax (ablate)
    use_xsa: bool = False            # value-subspace removal (ablate)
    # Elo attention (AlphaFold-style ranking / branch-until-winner): softmax attention
    # IS a Bradley-Terry tournament over keys; this gives each key a PERSISTENT rating
    # that accumulates across the sequence (winners of attention get boosted for later
    # queries) and feeds back as a logit bias. Rating is carried in the KV cache so the
    # tournament continues through incremental decoding. Zero-init gain -> no-op at start.
    use_elo: bool = False
    elo_k_init: float = 0.5          # Elo K-factor (rating step per "match"); per-head, learnable
    # TriAttention (arXiv:2604.04921): trigonometric KV-cache compression. When
    # enabled and calibrated, generation prunes the KV cache to `triattn_budget`
    # tokens every `triattn_window` decoded tokens using the pre-RoPE Q/K
    # concentration + trigonometric-series key scoring. See triattention.py.
    use_triattention: bool = False
    # Morphable hybrid attention (morphable.py): wrap each attention layer with a
    # trainable linear-attention twin + learnable gate α, select the top-k layers to
    # keep as full attention, linearize the rest. The surviving full layers use
    # TriAttention KV compression; the linear layers carry an O(1) recurrent state.
    use_morphable: bool = False
    morph_alpha_init: float = 0.5     # initial gate α for every layer
    morph_k_full: int = 4             # layers kept as full attention after discretize
    morph_reg_lambda: float = 1e-2    # λ on the linearization penalty Σ α
    triattn_budget: int = 2048        # B: retained KV tokens
    triattn_window: int = 128         # β: prune once every β decoded tokens
    triattn_max_offset: int = 1 << 16  # largest future offset in D (Eq. 11)
    rope_theta: float = 10000.0
    # Fractal RoPE: place the RoPE frequency spectrum on a Cantor set instead of the
    # geometric ladder (fractal.fractal_rope_inv_freq). Baked into the model, so it
    # must be set before training from scratch. TriAttention auto-binds its scorer to
    # whatever spectrum the model uses, so compression stays exact. gamma=0 -> plain
    # RoPE; gamma=1 -> fully fractal.
    use_fractal_rope: bool = False
    fractal_rope_gamma: float = 1.0
    # Legacy fractal spectrum. Default True so checkpoints predating this flag
    # (which carry no such key) rebuild the exact spectrum they trained on.
    # Set False for new runs to get the corrected Cantor endpoint; see fractal.py.
    fractal_rope_legacy: bool = True
    max_position_embeddings: int = 4096
    rms_norm_eps: float = 1e-6
    initializer_range: float = 0.02
    # ---- model-level v2 features ----
    zloss_coef: float = 1e-4         # z-loss on lm logits
    use_value_embed: bool = False    # per-block value-embedding residual (zero-init gate)
    use_hyper_connections: bool = False
    hc_mult: int = 2

    @property
    def n_osc(self):
        return self.wdw_modes   # fractal seeds the K minisuperspace modes here


def _ring_index(ring_sizes):
    """Return a LongTensor of length N mapping each oscillator -> its ring id."""
    idx = []
    for r, n in enumerate(ring_sizes):
        idx += [r] * n
    return torch.tensor(idx, dtype=torch.long)


class QuazimotoBlock(nn.Module):
    """Oscillatory channel-mixing block: hidden state -> ring phases -> Kuramoto -> readout."""

    def __init__(self, cfg: QuazimotoConfig):
        super().__init__()
        self.cfg = cfg
        N = cfg.n_osc
        R = len(cfg.ring_sizes)
        self.N, self.R = N, R

        self.norm = RMSNorm(cfg.d_model)
        # project hidden -> initial phases and natural frequencies
        self.to_theta = nn.Linear(cfg.d_model, N)
        self.to_omega = nn.Linear(cfg.d_model, N)

        # structured, LEARNABLE coupling: an RxR gain between ring blocks.
        # Block-constant coupling lets us use per-ring mean fields (order
        # parameters) -> O(N*R) instead of the O(N^2) pairwise sum.
        rid = _ring_index(cfg.ring_sizes)
        self.register_buffer("ring_id", rid)
        onehot = F.one_hot(rid, R).float()       # [N,R] ring membership
        self.register_buffer("onehot", onehot)
        self.block_gain = nn.Parameter(self._init_gain(R))
        self.center_phase = nn.Parameter(torch.zeros(()))  # the rotating center ball
        self.center_omega = nn.Parameter(torch.tensor(0.3))
        self.center_gain = nn.Parameter(torch.full((R,), 0.5))  # ring -> center pull
        self.alpha = nn.Parameter(torch.zeros(R))              # per-ring frustration

        # readout MLP on [cos theta, sin theta]
        hidden = cfg.readout_mult * cfg.d_model
        self.readout = nn.Sequential(
            nn.Linear(2 * N, hidden),
            nn.GELU(),
            nn.Linear(hidden, cfg.d_model),
        )
        self.drop = nn.Dropout(cfg.dropout)

        # family-trait gate: out = readout * tanh(gate). gate_init_open seeds it so the
        # block is a warm-startable signal whose magnitude the optimizer controls; the
        # content weights are nonzero so the gate always receives gradient.
        go = math.atanh(min(cfg.gate_init_open, 0.9)) if cfg.gate_init_open > 0 else 0.0
        self.gate = nn.Parameter(torch.full((1,), go))

        # Mandelbrot phase-seed gate: theta <- to_theta(h) + tanh(seed_gate) * orbit.
        # Zero-init -> no-op at start; opens only if the fractal prior helps.
        if cfg.use_fractal_phase_seed:
            self.phase_seed_gate = nn.Parameter(torch.zeros(1))

        # std=0.02 init discipline (matches the family's linear-layer init)
        for m in self.modules():
            if isinstance(m, nn.Linear):
                nn.init.normal_(m.weight, std=0.02)
                if m.bias is not None:
                    nn.init.zeros_(m.bias)

        # interstitial rings (instantiated AFTER the std-init loop). Oscillators are
        # ordered ring-by-ring, so rings r and r+1 are the contiguous slice
        # [off_r, off_{r+2}); the slot between them injects into exactly that block.
        self.use_rings = cfg.use_rings
        self.slot_bounds = []
        if self.use_rings:
            off = [0]
            for n in cfg.ring_sizes:
                off.append(off[-1] + n)
            self.slot_bounds = [(off[r], off[r + 2]) for r in range(R - 1)]
            self.attn_rings = nn.ModuleList([
                PhaseAttentionRing(e - s, cfg.ring_attn_heads, cfg.ring_attn_head_dim, cfg.osc_bound)
                for (s, e) in self.slot_bounds])
            self.engram_rings = nn.ModuleList([
                EngramRing(cfg.d_model, e - s, cfg.ring_engram_compress, cfg.ring_engram_heads,
                           cfg.ring_engram_table, cfg.ring_engram_ngram, cfg.osc_bound)
                for (s, e) in self.slot_bounds])

        # one memory-specialist MoE bank PER oscillator ring (injects into that ring)
        self.use_ring_specialists = cfg.use_ring_specialists
        if self.use_ring_specialists:
            self.specialists = nn.ModuleList([
                RingSpecialists(n, cfg.d_model, cfg.ring_n_specialists, cfg.ring_spec_key_dim,
                                cfg.ring_spec_slot_dim, cfg.ring_spec_top_k,
                                cfg.ring_spec_write_lr, cfg.osc_bound)
                for n in cfg.ring_sizes])

    @staticmethod
    def _init_gain(R):
        # within-ring coupling strong on the diagonal, weak neighbor coupling
        g = torch.zeros(R, R)
        for i in range(R):
            g[i, i] = 1.0
            if i + 1 < R:
                g[i, i + 1] = g[i + 1, i] = 0.3
        return g

    def forward(self, x, ring_ctl=None, phase_seed=None):
        cfg = self.cfg
        B, T, _ = x.shape
        h = self.norm(x)

        theta = self.to_theta(h)                 # [B,T,N]  initial phases
        if phase_seed is not None:               # Mandelbrot orbit phase prior (gated, no-op at init)
            theta = theta + torch.tanh(self.phase_seed_gate) * phase_seed
        omega = torch.tanh(self.to_omega(h))     # [B,T,N]  natural frequencies (bounded)

        # per-ring self-organizing controller: observe each ring (order-parameter
        # magnitude/phase + mean freq), get no-op-at-init modulations of that ring's
        # coupling / frustration / center-pull / injection.
        d_coup = d_alpha = d_center = d_inj = None
        if ring_ctl is not None:
            with torch.no_grad():
                c0, s0 = torch.cos(theta), torch.sin(theta)
                cnt = self.onehot.sum(0).clamp(min=1.0)       # [R] ring sizes
                zc = (c0 @ self.onehot) / cnt                 # [B,T,R]
                zs = (s0 @ self.onehot) / cnt
                mag = torch.sqrt(zc ** 2 + zs ** 2 + 1e-8)
                om = (omega @ self.onehot) / cnt
                obs = torch.stack([mag.mean((0, 1)), (zc / mag).mean((0, 1)),
                                   (zs / mag).mean((0, 1)), om.mean((0, 1))], dim=-1)  # [R,4]
            ctrl = ring_ctl(obs)                              # [R,4] (grad -> enc/dec only)
            d_coup = torch.tanh(ctrl[:, 0])                   # ring coupling scale (1 + .)
            d_alpha = 0.5 * torch.tanh(ctrl[:, 1])            # frustration shift
            d_center = torch.tanh(ctrl[:, 2])                 # center-pull scale (1 + .)
            d_inj = torch.tanh(ctrl[:, 3])                    # injection scale (1 + .)

        # interstitial rings absorb info ONCE (from the initial phases / hidden) and
        # inject a constant drive into their two neighbor oscillator rings' phase update.
        inject = 0.0
        if self.use_rings:
            inject = torch.zeros_like(theta)
            for (s, e), ar, er in zip(self.slot_bounds, self.attn_rings, self.engram_rings):
                contrib = ar(theta[..., s:e]) + er(h)         # [B,T,e-s]
                inject = inject + F.pad(contrib, (s, self.N - e))  # place at [s:e], sum overlaps
            if d_inj is not None:
                inject = inject * (1.0 + d_inj)[self.ring_id]      # controller scales the drive

        # memory specialists: each per-ring bank injects retrieved store_out into its
        # ring's oscillators. Rings are contiguous and tile [0,N), so cat == full drive.
        if self.use_ring_specialists:
            spec_inj = torch.cat([bank(h) for bank in self.specialists], dim=-1)  # [B,T,N]
            inject = inject + spec_inj

        rid = self.ring_id                        # [N]
        gain = self.block_gain if d_coup is None else self.block_gain * (1.0 + d_coup).unsqueeze(1)
        G = gain[rid]                             # [N,R]  per-oscillator ring gains
        alpha_vec = self.alpha if d_alpha is None else self.alpha + d_alpha
        alpha_i = alpha_vec[rid]                  # [N]
        center = self.center_phase                # scalar phase, advances in time
        cpull = self.center_gain if d_center is None else self.center_gain * (1.0 + d_center)
        center_pull = cpull[rid]                  # [N]
        invN = 1.0 / self.N

        for s in range(cfg.osc_steps):
            ctr = center + self.center_omega * (s * cfg.osc_dt)
            c, sn = torch.cos(theta), torch.sin(theta)            # [B,T,N]
            # per-ring summed mean fields, then routed back through gains G
            zc = (c @ self.onehot) @ G.t()                        # [B,T,N]
            zs = (sn @ self.onehot) @ G.t()                       # [B,T,N]
            A = alpha_i - theta                                   # [B,T,N]
            # (1/N) * sum_r G_ir * Im( e^{i(alpha_i - theta_i)} * Zsum_r )
            coupling = invN * (torch.sin(A) * zc + torch.cos(A) * zs)
            to_center = center_pull * torch.sin(ctr - theta + alpha_i)            # [B,T,N]
            theta = theta + cfg.osc_dt * (omega + coupling + to_center + inject)  # ring drive

        feat = torch.cat([torch.cos(theta), torch.sin(theta)], dim=-1)  # [B,T,2N]
        out = self.readout(feat) * torch.tanh(self.gate)
        out = self.drop(soft_clamp(out, self.cfg.osc_bound))           # bounded, live grad

        rec = _viz.get_rec()
        if rec is not None and rec.enabled:                            # live-viz capture
            th = theta[0, -1]                                          # last-token phases [N]
            cnt = self.onehot.sum(0).clamp(min=1.0)
            zc = (torch.cos(th) @ self.onehot) / cnt                   # per-ring order param
            zs = (torch.sin(th) @ self.onehot) / cnt
            R = torch.sqrt(zc ** 2 + zs ** 2 + 1e-9)
            psi = torch.atan2(zs, zc)
            rec.log_ring(R.tolist(), psi.tolist(), th.tolist())
            rec.log_quaz_norm(out[0, -1].norm().item())
            rec.flush_spec()                                           # group this layer's routes
        return out


class RotaryEmbedding(nn.Module):
    """RoPE for the rope partition of Q/K (qk_rope_head_dim dims). Ported from v2."""

    def __init__(self, dim, max_positions=4096, theta=10000.0, inv_freq=None):
        super().__init__()
        # inv_freq may be supplied (e.g. a fractal Cantor spectrum from
        # fractal.fractal_rope_inv_freq); otherwise use the standard geometric RoPE.
        if inv_freq is None:
            inv_freq = 1.0 / (theta ** (torch.arange(0, dim, 2).float() / dim))
        t = torch.arange(max_positions).float()
        freqs = torch.outer(t, inv_freq)
        self.register_buffer("cos_cache", freqs.cos(), persistent=False)
        self.register_buffer("sin_cache", freqs.sin(), persistent=False)

    def forward(self, x, position_ids):
        cos = self.cos_cache[position_ids].unsqueeze(1)
        sin = self.sin_cache[position_ids].unsqueeze(1)
        d = cos.shape[-1]
        x1, x2 = x[..., :d], x[..., d:]
        return torch.cat([x1 * cos - x2 * sin, x1 * sin + x2 * cos], dim=-1)


class MLADerfXSAAttention(nn.Module):
    """Family attention, ported from model_v2.MLADerfXSAAttention: MLA low-rank

    q/o projections, partial RoPE (nope+rope split), QK-Norm, optional DERF erf

    attention, optional XSA value-subspace removal, GQA. Supports an optional KV

    cache for incremental decoding (cache holds post-RoPE, pre-GQA-expansion k/v

    of shape [B, num_kv_heads, S, head_dim])."""

    def __init__(self, cfg: QuazimotoConfig):
        super().__init__()
        self.num_heads = cfg.n_head
        self.num_kv_heads = cfg.num_key_value_heads
        self.head_dim = cfg.head_dim
        self.nope_head_dim = cfg.nope_head_dim
        self.use_qk_norm = cfg.use_qk_norm
        self.use_derf = cfg.use_derf
        self.use_xsa = cfg.use_xsa
        self.use_elo = cfg.use_elo
        self.dropout_p = cfg.dropout
        self.triattn = None          # TriAttention manager, set by the model if enabled
        self.kv_groups = self.num_heads // self.num_kv_heads
        assert self.nope_head_dim + cfg.qk_rope_head_dim == self.head_dim, \
            "nope_head_dim + qk_rope_head_dim must equal head_dim"

        self.q_a_proj = nn.Linear(cfg.d_model, cfg.q_lora_rank, bias=False)
        self.q_a_norm = RMSNorm(cfg.q_lora_rank, cfg.rms_norm_eps)
        self.q_b_proj = nn.Linear(cfg.q_lora_rank, self.num_heads * self.head_dim, bias=False)
        self.k_proj = nn.Linear(cfg.d_model, self.num_kv_heads * self.head_dim, bias=False)
        self.v_proj = nn.Linear(cfg.d_model, self.num_kv_heads * self.head_dim, bias=False)
        self.o_a_proj = nn.Linear(self.num_heads * self.head_dim, cfg.o_lora_rank, bias=False)
        self.o_b_proj = nn.Linear(cfg.o_lora_rank, cfg.d_model, bias=False)

        if self.use_qk_norm:
            self.q_norm = RMSNorm(self.head_dim, cfg.rms_norm_eps)
            self.k_norm = RMSNorm(self.head_dim, cfg.rms_norm_eps)
        inv_freq = None
        if getattr(cfg, "use_fractal_rope", False):
            from fractal import fractal_rope_inv_freq
            inv_freq = fractal_rope_inv_freq(
                cfg.qk_rope_head_dim, cfg.rope_theta, cfg.fractal_rope_gamma,
                legacy=getattr(cfg, "fractal_rope_legacy", True))
        self.rope = RotaryEmbedding(cfg.qk_rope_head_dim, cfg.max_position_embeddings,
                                    cfg.rope_theta, inv_freq=inv_freq)
        if self.use_derf:
            self.derf_alpha = nn.Parameter(torch.ones(self.num_heads))
            self.derf_bias = nn.Parameter(torch.zeros(self.num_heads))
            self.derf_gamma = nn.Parameter(torch.ones(self.num_heads))
        if self.use_elo:
            # per-head Elo K-factor (softplus>0) and rating->logit gain (tanh, zero-init
            # => no-op at start, so the tournament reduces to plain softmax attention).
            self.elo_log_k = nn.Parameter(torch.full((self.num_heads,),
                                                      math.log(math.expm1(cfg.elo_k_init))))
            self.elo_gain = nn.Parameter(torch.zeros(self.num_heads))
        for m in (self.q_a_proj, self.q_b_proj, self.k_proj, self.v_proj,
                  self.o_a_proj, self.o_b_proj):
            nn.init.normal_(m.weight, std=cfg.initializer_range)

    def _elo_attention(self, q, k, v, offset, S, L, past_rating, device):
        """Elo/Bradley-Terry tournament attention. Softmax attention already gives each

        key its win-probability against the field; Elo adds a PERSISTENT rating r_j that

        accumulates across queries and biases future logits (winners get boosted), exactly

        like the AlphaFold-style branch-until-winner reinforcement.



          e_ij   = q_i.k_j/sqrt(d)  +  gain * rating_j          (rating = prior + intra-call)

          match  : S_ij = softmax weight key j won at query i ;  E_i = 1/n_i (uniform baseline)

          update : dR_ij = K * (S_ij - E_i)   (over-performers gain rating, under-perform lose)

          rating_j(query i) = sum over earlier queries t<i of dR_tj   (causal prefix sum)



        Two passes keep it feed-forward: pass A scores on PRIOR ratings only to define the

        matches; pass B adds the causal in-call accumulation; the summed update is written

        back to the KV cache so the tournament continues through incremental decoding.

        Zero-init `elo_gain` => pass B == pass A == plain softmax (family no-op contract).

        Returns (y, rating_out) with rating_out: [B, num_heads, L]."""
        B, H = q.shape[0], self.num_heads
        scale = 1.0 / math.sqrt(self.head_dim)
        scores = torch.matmul(q, k.transpose(-2, -1)) * scale            # [B,H,S,L]
        qpos = torch.arange(S, device=device).view(S, 1) + offset
        kpos = torch.arange(L, device=device).view(1, L)
        keep = (kpos <= qpos)                                            # [S,L] causal
        keepf = keep.to(scores.dtype)
        neg = torch.finfo(scores.dtype).min
        K = F.softplus(self.elo_log_k).view(1, H, 1, 1)                  # per-head K-factor >0
        gain = torch.tanh(self.elo_gain).view(1, H, 1, 1)               # per-head, 0 at init

        # prior rating of each key = what it accumulated BEFORE this forward (from the
        # cache) + 0 for the keys introduced this call. [B,H,L] -> bias [B,H,1,L].
        if past_rating is not None:
            prior = torch.cat([past_rating,
                               past_rating.new_zeros(B, H, S)], dim=-1)  # new keys start at 0
        else:
            prior = scores.new_zeros(B, H, L)
        prior_bias = gain * prior.unsqueeze(2)                           # [B,H,1,L]

        masked = scores.masked_fill(~keep, neg)
        # Match outcome = innate q.k compatibility (the "game result"), NOT the accumulated
        # rating. Keeping matches rating-INDEPENDENT is what makes the parallel prefix-sum
        # (tril @ dR, training) bit-identical to the incremental running sum (decoding);
        # rating is the REPUTATION that biases attention, not what defines the match.
        a_base = torch.softmax(masked, dim=-1)                          # [B,H,S,L]
        n_i = keepf.sum(-1).clamp(min=1.0).view(1, 1, S, 1)             # valid keys per query
        dR = (K * (a_base - 1.0 / n_i)) * keepf                         # over/under-perform vs field
        # rating seen by query i = prior (from cache) + causal sum of dR from earlier queries t<i
        tril = torch.tril(torch.ones(S, S, device=device, dtype=scores.dtype), -1)
        intra = torch.matmul(tril, dR)                                  # [B,H,S,L]
        a = torch.softmax(masked + prior_bias + gain * intra, dim=-1)   # reputation biases attention
        if self.dropout_p > 0 and self.training:
            a = F.dropout(a, p=self.dropout_p)
        y = torch.matmul(a, v)                                          # [B,H,S,hd]
        # rating carried forward = prior + everything this call's queries contributed
        rating_out = prior + dR.sum(2)                                  # [B,H,L]
        return y, rating_out

    def forward(self, x, position_ids, past_kv=None, use_cache=False):
        B, S, _ = x.shape
        q = self.q_b_proj(self.q_a_norm(self.q_a_proj(x)))
        q = q.view(B, S, self.num_heads, self.head_dim).transpose(1, 2)
        k = self.k_proj(x).view(B, S, self.num_kv_heads, self.head_dim).transpose(1, 2)
        v = self.v_proj(x).view(B, S, self.num_kv_heads, self.head_dim).transpose(1, 2)
        if self.use_qk_norm:
            q, k = self.q_norm(q), self.k_norm(k)

        d = self.nope_head_dim
        # TriAttention calibration: record the pre-RoPE query rope-partition so the
        # Q-center statistics E[q_f], E[||q_f||] can be estimated (triattention.py).
        if self.triattn is not None and self.triattn.collecting:
            self.triattn.collect(self.layer_idx, q[..., d:].detach())
        q = torch.cat([q[..., :d], self.rope(q[..., d:], position_ids)], dim=-1)
        k = torch.cat([k[..., :d], self.rope(k[..., d:], position_ids)], dim=-1)

        # KV cache: prepend previously-seen post-RoPE k/v (pre-GQA-expansion so
        # the cache stays GQA-compact), then this step's tokens become the tail.
        past_rating = None
        if past_kv is not None:
            if len(past_kv) == 3:                       # (k, v, elo_rating) cache
                past_k, past_v, past_rating = past_kv
            else:
                past_k, past_v = past_kv
            k = torch.cat([past_k, k], dim=2)
            v = torch.cat([past_v, v], dim=2)
        present = (k, v) if use_cache else None
        L = k.size(2)                                  # total keys (cache + current)

        if self.kv_groups > 1:
            k = k.unsqueeze(2).expand(-1, -1, self.kv_groups, -1, -1).reshape(
                B, self.num_heads, L, self.head_dim)
            v = v.unsqueeze(2).expand(-1, -1, self.kv_groups, -1, -1).reshape(
                B, self.num_heads, L, self.head_dim)

        # Causal mask over the [S queries x L keys] block. When there is no cache
        # and S == L this is the plain lower triangle; with a cache the S new
        # queries sit at absolute positions [L-S, L) and may attend all keys <=
        # their own position. allowed[i,j] = j <= (L - S + i).
        offset = L - S
        if self.use_elo:                               # Elo tournament over keys
            y, rating_out = self._elo_attention(q, k, v, offset, S, L, past_rating, x.device)
            if use_cache:
                present = (present[0], present[1], rating_out)
        elif self.use_derf:
            qpos = torch.arange(S, device=x.device).view(S, 1) + offset
            kpos = torch.arange(L, device=x.device).view(1, L)
            is_masked = (kpos > qpos).unsqueeze(0).unsqueeze(0)   # [1,1,S,L]
            scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.head_dim)
            safe = scores.masked_fill(is_masked, -10000.0)
            a, b, g = (self.derf_alpha.view(1, -1, 1, 1), self.derf_bias.view(1, -1, 1, 1),
                       self.derf_gamma.view(1, -1, 1, 1))
            w = g * torch.erf(a * safe + b)
            w = (w + g) / 2.0
            w = w.masked_fill(is_masked, 0.0)
            w = w / (w.sum(-1, keepdim=True) + 1e-8)
            if self.dropout_p > 0 and self.training:
                w = F.dropout(w, p=self.dropout_p)
            y = torch.matmul(w, v)
        else:
            if offset == 0:
                attn_mask, causal = None, True
            else:
                qpos = torch.arange(S, device=x.device).view(S, 1) + offset
                kpos = torch.arange(L, device=x.device).view(1, L)
                attn_mask = (kpos <= qpos).unsqueeze(0).unsqueeze(0)  # bool: True=keep
                causal = False
            y = F.scaled_dot_product_attention(
                q.contiguous(), k.contiguous(), v.contiguous(), attn_mask=attn_mask,
                is_causal=causal, dropout_p=self.dropout_p if self.training else 0.0)

        rec = _viz.get_rec()
        if rec is not None and rec.enabled:                  # last-query attention, mean over heads
            sc = (q[:, :, -1:] @ k.transpose(-2, -1)) / math.sqrt(self.head_dim)  # [B,H,1,L]
            w = torch.softmax(sc, dim=-1).mean(1)[0, 0]      # [L]
            rec.log_attn(getattr(self, "layer_idx", 0), w.tolist())

        if self.use_xsa:
            # remove each query's component along its OWN value direction; with a
            # cache the queries are the last S of the L cached value positions.
            vq = v[:, :, -S:, :]
            vn = vq / (vq.norm(dim=-1, keepdim=True) + 1e-8)
            y = y - (y * vn).sum(-1, keepdim=True) * vn

        y = y.transpose(1, 2).contiguous().view(B, S, self.num_heads * self.head_dim)
        out = self.o_b_proj(self.o_a_proj(y))
        return (out, present) if use_cache else out


class HyperConnectionLayer(nn.Module):
    """Softmax pre-mix / post-distribute over hc_mult residual streams (v2)."""

    def __init__(self, hc_mult):
        super().__init__()
        self.pre_weight = nn.Parameter(torch.linspace(0.5, -0.5, hc_mult) / max(hc_mult, 1))
        self.post_weight = nn.Parameter(torch.linspace(-0.5, 0.5, hc_mult) / max(hc_mult, 1))

    def pre_op(self, copies):
        w = F.softmax(self.pre_weight, dim=0)
        return (copies * w.view(1, -1, 1, 1)).sum(dim=1)

    def post_op(self, copies, delta):
        w = F.softmax(self.post_weight, dim=0)
        return copies + delta.unsqueeze(1) * w.view(1, -1, 1, 1)


class HCOutputMix(nn.Module):
    """Learned softmax mix over hc_mult streams at the output (init == mean)."""

    def __init__(self, hc_mult):
        super().__init__()
        self.weight = nn.Parameter(torch.zeros(hc_mult))

    def forward(self, copies):
        w = F.softmax(self.weight, dim=0)
        return (copies * w.view(1, -1, 1, 1)).sum(dim=1)


class Layer(nn.Module):
    def __init__(self, cfg):
        super().__init__()
        self.cfg = cfg
        self.use_hc = cfg.use_hyper_connections
        self.use_value_embed = cfg.use_value_embed
        self.attn_norm = RMSNorm(cfg.d_model, cfg.rms_norm_eps)
        self.attn = MLADerfXSAAttention(cfg)
        if cfg.use_morphable:
            from morphable import MorphableAttention
            self.attn = MorphableAttention(self.attn, cfg, cfg.morph_alpha_init)
        self.quaz = WheelerDeWittBlock(cfg)          # the Wheeler-DeWitt wave sub-layer
        if self.use_hc:
            self.hc_attn = HyperConnectionLayer(cfg.hc_mult)
            self.hc_ffn = HyperConnectionLayer(cfg.hc_mult)
        if self.use_value_embed:
            self.ve_gate = nn.Parameter(torch.zeros(1))   # zero-init -> no-op

    def forward(self, x, position_ids, token_embed=None, ring_ctl=None,

                past_kv=None, use_cache=False, phase_seed=None):
        h = self.hc_attn.pre_op(x) if self.use_hc else x
        if self.use_value_embed and token_embed is not None:
            h = h + torch.tanh(self.ve_gate) * token_embed
        attn_out = self.attn(self.attn_norm(h), position_ids,
                             past_kv=past_kv, use_cache=use_cache)
        present = None
        if use_cache:
            attn_out, present = attn_out
        if self.use_hc:
            x = self.hc_attn.post_op(x, attn_out)
            h = self.hc_ffn.pre_op(x)
        else:
            h = h + attn_out
        ffn_out = self.quaz(h, ring_ctl, phase_seed)  # QuazimotoBlock norms internally
        if self.use_hc:
            x = self.hc_ffn.post_op(x, ffn_out)
        else:
            x = h + ffn_out
        return (x, present) if use_cache else x


class QuazimotoLM(nn.Module):
    def __init__(self, cfg: QuazimotoConfig):
        super().__init__()
        self.cfg = cfg
        self.tok = nn.Embedding(cfg.vocab_size, cfg.d_model)
        self.drop = nn.Dropout(cfg.dropout)        # positions come from RoPE (no abs embed)
        # frozen Mandelbrot orbit-angle phase table [vocab, n_osc]; non-persistent
        # (deterministic, regenerated on load -> not stored in checkpoints).
        if cfg.use_fractal_phase_seed:
            from fractal import mandelbrot_phase_table, load_phase_table
            pt, mode = load_phase_table(cfg.vocab_size, cfg.n_osc,
                                        os.path.join(_PKG_DIR, "fractal_phase.pt"))
            if pt is None:
                pt = mandelbrot_phase_table(cfg.vocab_size, cfg.n_osc)
                print("  [fractal] flat Halton seed (run build_fractal_table.py for hierarchical)")
            else:
                print(f"  [fractal] loaded {mode} phase table")
            self.register_buffer("fractal_phase", pt, persistent=False)
        self.layers = nn.ModuleList([Layer(cfg) for _ in range(cfg.n_layer)])
        for i, layer in enumerate(self.layers):
            # with morphable wrapping, the pretrained full attn lives at .full
            getattr(layer.attn, "full", layer.attn).layer_idx = i  # live-viz / calib
        self.norm_f = RMSNorm(cfg.d_model, cfg.rms_norm_eps)
        self.head = nn.Linear(cfg.d_model, cfg.vocab_size, bias=False)
        self.head.weight = self.tok.weight  # weight tying
        self.logit_scale = nn.Parameter(torch.tensor(1.0))  # family trait: learned logit temp
        self.use_value_embed = cfg.use_value_embed
        self.hc_out_mix = HCOutputMix(cfg.hc_mult) if cfg.use_hyper_connections else None
        self.apply(self._init)
        # engram rings manage their own inits (frozen compressor, std-0.01 tables,
        # DERF bias -4); re-apply them after the global init clobbers those.
        for m in self.modules():
            if isinstance(m, (EngramRing, RingSpecialists)):
                m.family_reinit()

        # opt-in trait modules -- instantiated AFTER self.apply so their internal
        # zero-inits (MoE down-proj, MTP proj) and gate inits survive the global init.
        # HRM/MoE refine the trunk after the stack (the family's "top membrane
        # refinement" point); MTP/JEPA are train-time aux heads on the trunk rep.
        self.hrm = (HRMRefinementBlock(cfg.d_model, cfg.hrm_dim, cfg.hrm_steps,
                                       gate_init_open=cfg.hrm_gate_init)
                    if cfg.use_hrm else None)
        self.moe = MoESwiGLU(cfg.d_model, cfg.moe_intermediate, cfg.moe_n_routed,
                             cfg.moe_n_shared, cfg.moe_top_k) if cfg.use_moe else None
        self.mtp_heads = (nn.ModuleList([MTPHead(cfg.d_model) for _ in range(cfg.mtp_layers)])
                          if cfg.use_mtp else None)
        self.jepa = (JEPAPredictorBlock(cfg.d_model, cfg.jepa_pred_dim, cfg.jepa_horizon)
                     if cfg.use_jepa else None)
        self.ring_bank = (RingControllerBank(len(cfg.ring_sizes), 4, cfg.ring_ctrl_feat,
                                             cfg.ring_ctrl_local_lr)
                          if cfg.use_ring_controllers else None)

        # TriAttention KV-cache compressor (arXiv:2604.04921). Shared by all layers;
        # each attention module reports pre-RoPE Q stats during calibration and reads
        # its per-layer scoring stats at prune time. No-op until calibrated.
        self.triattn = None
        if cfg.use_triattention:
            from triattention import TriAttention
            self.triattn = TriAttention(cfg, budget=cfg.triattn_budget,
                                        window=cfg.triattn_window,
                                        max_offset=cfg.triattn_max_offset)
            for layer in self.layers:
                getattr(layer.attn, "full", layer.attn).triattn = self.triattn
            # bind the scorer's frequencies to the model's ACTUAL RoPE spectrum, so a
            # fractal (or any custom) schedule is scored exactly (see bind_omega).
            self.triattn.bind_omega(getattr(self.layers[0].attn, "full",
                                            self.layers[0].attn).rope)

        # exact kwargs to rebuild on resume (family `family_config` convention)
        self.family_config = {k: getattr(cfg, k) for k in (
            "vocab_size", "n_layer", "n_head", "d_model", "block_size",
            "ring_sizes", "osc_steps", "osc_dt", "readout_mult", "osc_bound", "gate_init_open",
            "use_hrm", "hrm_steps", "hrm_dim", "hrm_gate_init",
            "use_moe", "moe_intermediate", "moe_n_routed",
            "moe_n_shared", "moe_top_k", "use_mtp", "mtp_layers", "mtp_loss_weight",
            "use_jepa", "jepa_pred_dim", "jepa_horizon", "jepa_loss_weight",
            "head_dim", "qk_rope_head_dim", "nope_head_dim", "num_key_value_heads",
            "q_lora_rank", "o_lora_rank", "use_qk_norm", "use_derf", "use_xsa",
            "use_elo", "elo_k_init",
            "rope_theta", "use_fractal_rope", "fractal_rope_gamma",
            "fractal_rope_legacy",
            "max_position_embeddings", "rms_norm_eps", "initializer_range",
            "zloss_coef", "use_value_embed", "use_hyper_connections", "hc_mult",
            "use_rings", "ring_attn_heads", "ring_attn_head_dim", "ring_engram_compress",
            "ring_engram_heads", "ring_engram_table", "ring_engram_ngram",
            "use_ring_controllers", "ring_ctrl_feat", "ring_ctrl_local_lr",
            "use_ring_specialists", "ring_n_specialists", "ring_spec_key_dim",
            "ring_spec_slot_dim", "ring_spec_top_k", "ring_spec_write_lr",
            "use_fractal_phase_seed",
            # morphable hybrid + TriAttention -- so a hybrid checkpoint is self-describing
            # and rebuilds with the linear twins / gates / compressor in place.
            "use_morphable", "morph_alpha_init", "morph_k_full", "morph_reg_lambda",
            "use_triattention", "triattn_budget", "triattn_window", "triattn_max_offset",
            # Hydra tip mixer geometry -- so checkpoints rebuild at the exact colony
            "wdw_modes", "wdw_steps", "wdw_dt", "wdw_constraint_weight")}
        n = sum(p.numel() for p in self.parameters())
        print(f"Wheeler-LM: {n/1e6:.1f}M params | {cfg.wdw_modes} minisuperspace modes "
              f"under a Lorentzian DeWitt supermetric ({cfg.wdw_steps} wave steps) | "
              f"traits: {self.active_traits()}")

    def active_traits(self):
        t = []
        if self.cfg.use_hrm: t.append("HRM")
        if self.cfg.use_moe: t.append("MoE")
        if self.cfg.use_mtp: t.append("MTP")
        if self.cfg.use_jepa: t.append("JEPA")
        if self.cfg.use_rings: t.append("Rings")
        if self.cfg.use_ring_controllers: t.append("Controllers")
        if self.cfg.use_ring_specialists:
            t.append(f"Specialists({self.cfg.ring_n_specialists}/ring)")
        if self.cfg.use_fractal_phase_seed: t.append("FractalSeed")
        return ",".join(t) or "none"

    def reset_ring_memory(self):
        """Zero every RingSpecialists store. Call between independent prompts so

        the test-time context memory does not carry over from a previous sequence."""
        for m in self.modules():
            if isinstance(m, RingSpecialists):
                m.reset_memory()

    def set_ring_memory_writing(self, enabled: bool):
        """Toggle online writes to the specialist stores (e.g. freeze the memory

        during evaluation, or disable it to ablate the test-time-write behavior)."""
        for m in self.modules():
            if isinstance(m, RingSpecialists):
                m.write_enabled = enabled

    def _init(self, m):
        if isinstance(m, nn.Linear):
            nn.init.normal_(m.weight, std=0.02)
            if m.bias is not None:
                nn.init.zeros_(m.bias)
        elif isinstance(m, nn.Embedding):
            nn.init.normal_(m.weight, std=0.02)

    def _compute_trunk(self, idx, past_key_values=None, use_cache=False,

                       position_ids=None):
        """Shared backbone: tokens -> layers -> trunk refinements -> RMSNorm trunk.

        Returns (trunk, presents). Used by both forward() and forward_drafts().



        position_ids may be passed explicitly (TriAttention prunes the cache so

        cache size no longer equals the true position); otherwise it is derived

        from the cache length as before."""
        B, T = idx.shape
        if position_ids is None:
            # with a cache, the new tokens sit at absolute positions [past_len, past_len+T)
            past_len = past_key_values[0][0].size(2) if past_key_values else 0
            position_ids = (torch.arange(past_len, past_len + T, device=idx.device)
                            .unsqueeze(0).expand(B, -1))
        x = self.drop(self.tok(idx))
        token_embed = x if self.use_value_embed else None
        phase_seed = self.fractal_phase[idx] if self.cfg.use_fractal_phase_seed else None
        if self.cfg.use_hyper_connections:
            x = x.unsqueeze(1).expand(-1, self.cfg.hc_mult, -1, -1).clone()  # [B,M,T,H]
        presents = [] if use_cache else None
        for i, layer in enumerate(self.layers):
            past = past_key_values[i] if past_key_values is not None else None
            x = layer(x, position_ids, token_embed, self.ring_bank,
                      past_kv=past, use_cache=use_cache, phase_seed=phase_seed)
            if use_cache:
                x, present = x
                presents.append(present)
        if self.hc_out_mix is not None:
            x = self.hc_out_mix(x)               # learned mix over streams (init == mean)
        # trunk refinements (no-op at init): HRM iterative gated refine, MoE expert mix
        rec = _viz.get_rec()
        if self.hrm is not None:
            hx = self.hrm(x)
            if rec is not None and rec.enabled:
                rec.log_trait("hrm", (hx - x)[0, -1].norm().item())
            x = hx
        if self.moe is not None:
            mx = self.moe(x)
            if rec is not None and rec.enabled:
                rec.log_trait("moe", mx[0, -1].norm().item())
            x = x + mx
        return self.norm_f(x), presents          # v2: RMSNorm trunk (no tanh)

    @torch.no_grad()
    def forward_drafts(self, idx, past_key_values=None, use_cache=False):
        """Speculative-decoding support (DeepSpec-style draft+verify, self-drafted):

        return (main_logits, mtp_logits_list[, presents]) over ALL T positions.

          * main_logits[:, j]        predicts the token at position j+1   (verifier)

          * mtp_logits_list[k][:, j] predicts the token at position j+2+k  (k=0..mtp-1)

        So one forward yields, at the last position, the genuine next token (main)

        plus `mtp_layers` drafted future tokens to be verified next round."""
        trunk, presents = self._compute_trunk(idx, past_key_values, use_cache)
        main_logits = self.head(trunk) * self.logit_scale
        mtp_logits = ([self.head(h(trunk)) * self.logit_scale for h in self.mtp_heads]
                      if self.mtp_heads is not None else [])
        return (main_logits, mtp_logits, presents) if use_cache else (main_logits, mtp_logits)

    def forward(self, idx, targets=None, past_key_values=None, use_cache=False,

                position_ids=None):
        B, T = idx.shape
        trunk, presents = self._compute_trunk(idx, past_key_values, use_cache,
                                              position_ids=position_ids)
        logits = self.head(trunk) * self.logit_scale

        loss, aux = None, {}
        if targets is not None:
            flat = logits.view(-1, logits.size(-1))
            tflat = targets.reshape(-1)
            loss = F.cross_entropy(flat, tflat, ignore_index=-1)
            # z-loss (v2): penalise log^2 of the partition function -> no logit drift
            if self.cfg.zloss_coef > 0:
                valid = tflat != -1
                if valid.any():
                    log_z = torch.logsumexp(flat[valid].float(), dim=-1)
                    aux["zloss"] = self.cfg.zloss_coef * (log_z ** 2).mean()
            if self.moe is not None and self.moe.last_aux_loss is not None:
                aux["moe_aux"] = self.moe.last_aux_loss
            # Wheeler-DeWitt Hamiltonian constraint: pressure every layer's block toward
            # the physical surface H = 0 (H Psi = 0). Sum <H^2> over layers.
            if self.cfg.wdw_constraint_weight > 0:
                hsum, nh = 0.0, 0
                for layer in self.layers:
                    lc = getattr(layer.quaz, "last_constraint", None)
                    if lc is not None:
                        hsum = hsum + lc; nh += 1
                if nh:
                    aux["wdw_constraint"] = self.cfg.wdw_constraint_weight * hsum / nh
            if self.mtp_heads is not None:
                mtp_total, n_active = 0.0, 0
                for k, head in enumerate(self.mtp_heads, start=1):
                    if T - k <= 0:
                        break
                    mlog = self.head(head(trunk[:, :T - k])) * self.logit_scale
                    mtp_total = mtp_total + F.cross_entropy(
                        mlog.reshape(-1, self.cfg.vocab_size),
                        targets[:, k:].reshape(-1), ignore_index=-1)
                    n_active += 1
                if n_active:
                    aux["mtp_loss"] = self.cfg.mtp_loss_weight * mtp_total / n_active
            if self.jepa is not None and T > 1:
                jepa_total, n_j = 0.0, 0
                for k in range(1, self.cfg.jepa_horizon + 1):
                    if T - k <= 0:
                        break
                    pred = self.jepa(trunk[:, :T - k], k)
                    tgt = trunk[:, k:].detach()      # JEPA stop-grad target
                    cos = F.cosine_similarity(pred.float(), tgt.float(), dim=-1)
                    jepa_total = jepa_total + (1.0 - cos).mean()
                    n_j += 1
                if n_j:
                    aux["jepa_loss"] = self.cfg.jepa_loss_weight * jepa_total / n_j
        # use_cache returns the per-layer (k, v) presents as a 4th item; without
        # it the signature is the original 3-tuple so train.py is unaffected.
        if use_cache:
            return logits, loss, aux, presents
        return logits, loss, aux

    @torch.no_grad()
    def generate(self, idx, n_new, temperature=1.0, top_k=None, use_cache=True):
        """Autoregressive sampling with the family's NaN/inf sanitisation: long

        accumulation can push a logit to +/-inf or NaN, whose softmax -> NaN ->

        multinomial fires a CUDA device-side assert. Clamp first, greedy-fallback.



        With use_cache=True (default) the attention KV cache is kept across steps

        so each new token costs one forward over a single position instead of a

        full recompute. The cache holds absolute positions, so once it fills past

        block_size we drop the cache and fall back to a windowed recompute (RoPE

        positions would otherwise exceed max_position_embeddings)."""
        self.eval()
        past = None
        for _ in range(n_new):
            if use_cache and past is not None and past[0][0].size(2) < self.cfg.block_size:
                step_in = idx[:, -1:]                       # only the newest token
                logits, _, _, past = self(step_in, past_key_values=past, use_cache=True)
            elif use_cache:
                cond = idx[:, -self.cfg.block_size:]        # prefill / re-prime cache
                logits, _, _, past = self(cond, use_cache=True)
            else:
                cond = idx[:, -self.cfg.block_size:]
                logits, _, _ = self(cond)
            lg = torch.nan_to_num(logits[:, -1, :].float(), nan=0.0,
                                  posinf=1e4, neginf=-1e4) / max(temperature, 1e-6)
            if top_k:
                k = min(top_k, lg.size(-1))
                v, _ = torch.topk(lg, k)
                lg[lg < v[:, [-1]]] = -float("inf")
            probs = torch.softmax(lg, dim=-1)
            if not torch.isfinite(probs).all() or float(probs.sum()) <= 0.0:
                nxt = torch.argmax(lg, dim=-1, keepdim=True)
            else:
                nxt = torch.multinomial(probs, 1)
            idx = torch.cat([idx, nxt], dim=1)
        return idx

    # ------------------------------------------------------------------ #
    # TriAttention (arXiv:2604.04921)
    # ------------------------------------------------------------------ #
    @torch.no_grad()
    def calibrate_triattention(self, batches):
        """Estimate the pre-RoPE Q-center statistics over a calibration corpus.

        `batches` is an iterable of token-id LongTensors [B, T]. §5.1 notes the

        statistics are model-intrinsic and robust to the choice of calibration

        data (50k-960k tokens suffice)."""
        assert self.triattn is not None, "model built without use_triattention"
        self.eval()
        self.triattn.start_calibration()
        for batch in batches:
            self(batch.to(self.tok.weight.device))
        self.triattn.finish_calibration()
        return self.triattn

    @torch.no_grad()
    def generate_triattention(self, idx, n_new, temperature=1.0, top_k=None):
        """Autoregressive sampling with trigonometric KV-cache compression.



        Positions are tracked explicitly (cur_len) rather than inferred from the

        cache size, because the cache is pruned to `triattn_budget` tokens every

        `triattn_window` decoded tokens (§4.3). The current step always attends

        the full cache; pruning only shrinks what future steps see."""
        assert self.triattn is not None and self.triattn.stats[0] is not None, \
            "TriAttention not calibrated; call calibrate_triattention() first"
        self.eval()
        tri = self.triattn.to(idx.device)
        B, T0 = idx.shape

        # prefill
        pos = torch.arange(T0, device=idx.device).unsqueeze(0).expand(B, -1)
        logits, _, _, past = self(idx, use_cache=True, position_ids=pos)
        cur_len = T0
        if tri.cache_len(past) > tri.budget:      # compress the prompt if oversized
            past = tri.prune_cache(past)

        decoded = 0
        for _ in range(n_new):
            lg = torch.nan_to_num(logits[:, -1, :].float(), nan=0.0,
                                  posinf=1e4, neginf=-1e4) / max(temperature, 1e-6)
            if top_k:
                k = min(top_k, lg.size(-1))
                v, _ = torch.topk(lg, k)
                lg[lg < v[:, [-1]]] = -float("inf")
            probs = torch.softmax(lg, dim=-1)
            if not torch.isfinite(probs).all() or float(probs.sum()) <= 0.0:
                nxt = torch.argmax(lg, dim=-1, keepdim=True)
            else:
                nxt = torch.multinomial(probs, 1)
            idx = torch.cat([idx, nxt], dim=1)

            step_pos = torch.full((B, 1), cur_len, device=idx.device, dtype=torch.long)
            logits, _, _, past = self(nxt, past_key_values=past, use_cache=True,
                                      position_ids=step_pos)
            cur_len += 1
            decoded += 1
            # window-based pruning: every β decoded tokens, prune back to budget
            if decoded % tri.window == 0 and tri.cache_len(past) > tri.budget:
                past = tri.prune_cache(past)
        return idx

    # ------------------------------------------------------------------ #
    # Morphable hybrid (linear layers + TriAttention-compressed full layers)
    # ------------------------------------------------------------------ #
    @torch.no_grad()
    def generate_hybrid(self, idx, n_new, temperature=1.0, top_k=None):
        """Decode a discretized morphable model. Linear layers carry an O(1)

        recurrent state; full layers keep a KV cache that TriAttention prunes to

        budget every `window` tokens (only if the compressor is calibrated).

        Positions are tracked explicitly since the full-layer cache is pruned."""
        assert self.cfg.use_morphable, "model built without use_morphable"
        self.eval()
        tri = self.triattn.to(idx.device) if (self.triattn is not None
                                              and self.triattn.stats[0] is not None) else None
        B, T0 = idx.shape
        pos = torch.arange(T0, device=idx.device).unsqueeze(0).expand(B, -1)
        logits, _, _, past = self(idx, use_cache=True, position_ids=pos)
        cur_len = T0
        if tri is not None and tri.cache_len(past) > tri.budget:
            past = tri.prune_cache(past)

        decoded = 0
        for _ in range(n_new):
            lg = torch.nan_to_num(logits[:, -1, :].float(), nan=0.0,
                                  posinf=1e4, neginf=-1e4) / max(temperature, 1e-6)
            if top_k:
                k = min(top_k, lg.size(-1))
                v, _ = torch.topk(lg, k)
                lg[lg < v[:, [-1]]] = -float("inf")
            probs = torch.softmax(lg, dim=-1)
            if not torch.isfinite(probs).all() or float(probs.sum()) <= 0.0:
                nxt = torch.argmax(lg, dim=-1, keepdim=True)
            else:
                nxt = torch.multinomial(probs, 1)
            idx = torch.cat([idx, nxt], dim=1)
            step_pos = torch.full((B, 1), cur_len, device=idx.device, dtype=torch.long)
            logits, _, _, past = self(nxt, past_key_values=past, use_cache=True,
                                      position_ids=step_pos)
            cur_len += 1
            decoded += 1
            if tri is not None and decoded % tri.window == 0 \
                    and tri.cache_len(past) > tri.budget:
                past = tri.prune_cache(past)
        return idx


if __name__ == "__main__":
    # exercise the base model and all trait modules at once
    cfg = QuazimotoConfig(use_hrm=True, use_moe=True, use_mtp=True, use_jepa=True)
    model = QuazimotoLM(cfg)
    x = torch.randint(0, cfg.vocab_size, (2, 64))
    logits, loss, aux = model(x, x)
    total = loss + sum(aux.values())
    print("forward ok:", tuple(logits.shape), "loss", round(loss.item(), 3),
          "| aux:", {k: round(float(v), 4) for k, v in aux.items()})
    total.backward()
    print("backward ok")