Create aleph_routed_attention.py
Browse files
experiments/exp_007_aleph_routed_attention/aleph_routed_attention.py
ADDED
|
@@ -0,0 +1,851 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# aleph_routed_attention.py
|
| 2 |
+
"""
|
| 3 |
+
Aleph-Routed Attention β routing attention through a learned projective codebook
|
| 4 |
+
=================================================================================
|
| 5 |
+
|
| 6 |
+
Two variants of attention whose routing medium is the aleph signed-projective
|
| 7 |
+
address (geolip-svae aleph_model.py lineage):
|
| 8 |
+
|
| 9 |
+
HUB : linear attention whose feature map IS the aleph address.
|
| 10 |
+
score(i,j) = <addr(q_i), addr(k_j)> over 2K oriented axes [+A; -A].
|
| 11 |
+
Factors through two K-wide memories (the antipodal closed-form trick:
|
| 12 |
+
the 2K tensor is never materialized). O(n*K*d), PURE GEMM β no gathers.
|
| 13 |
+
Denominator is a dot product of strictly positive distributions, so it
|
| 14 |
+
cannot vanish or flip sign (structurally stabler than elu+1 feature maps).
|
| 15 |
+
Attention-matrix rank is bounded by 2K: K is the bandwidth knob,
|
| 16 |
+
tau is the hardness knob.
|
| 17 |
+
|
| 18 |
+
BUCKET : hard address. Each token's winner oriented half-axis is its bucket;
|
| 19 |
+
exact softmax attention within sorted equal-width blocks (Reformer-style
|
| 20 |
+
sort-and-window), masked to same-bucket pairs. One gather-bound mode for
|
| 21 |
+
the A/B against the GEMM mode. Codebook receives gradient through a
|
| 22 |
+
differentiable address-agreement bias added to the scores (hard argmax
|
| 23 |
+
alone is gradient-dead w.r.t. the codebook).
|
| 24 |
+
|
| 25 |
+
Shared geometric discipline (geolip-svae invariants honored):
|
| 26 |
+
- q/k address rows are sphere-normalized onto S^(D_addr-1) (geometric premise)
|
| 27 |
+
- nn.init.orthogonal_ on the address projections (load-bearing)
|
| 28 |
+
- no BatchNorm, no Dropout on the geometric path, no GAP
|
| 29 |
+
- codebook init: 'random' | 'fibonacci' (super-Fibonacci S^3 at D=4) | (K,D) array
|
| 30 |
+
β 'custom' array supports TRANSPLANTING a trained AlephModel codebook.
|
| 31 |
+
|
| 32 |
+
Preregistered basin test (decide before running):
|
| 33 |
+
Train the routing codebook from scratch on a sequence task, then run the
|
| 34 |
+
geolip-svae antipodal-collapse extraction on export_codebook().
|
| 35 |
+
CLEAN (|deviation| < 0.05 on RP^(D-1)) -> cross-objective attractor evidence.
|
| 36 |
+
DIRTY -> the attractor is reconstruction-specific.
|
| 37 |
+
Either answer is data.
|
| 38 |
+
|
| 39 |
+
Compile discipline (Phil's rule): forward() returns a single Tensor. All
|
| 40 |
+
diagnostics (perplexity, margin, bucket load, confidence) live in the separate
|
| 41 |
+
no-grad address_stats() method β never in the compiled hot path.
|
| 42 |
+
|
| 43 |
+
Prior-art honesty for the writeup: hub is the linear-transformer/Performer
|
| 44 |
+
family (kernel feature maps) crossed with Set-Transformer inducing points;
|
| 45 |
+
bucket rhymes with Reformer/Routing Transformer. Novel content: the signed
|
| 46 |
+
antipodal closed form as feature map, spherical D-space addresses, codebook
|
| 47 |
+
transplant from reconstruction alephs, and the attractor test.
|
| 48 |
+
|
| 49 |
+
Author: AbstractPhil + Mirel
|
| 50 |
+
Date: 2026-06-09
|
| 51 |
+
License: MIT
|
| 52 |
+
"""
|
| 53 |
+
|
| 54 |
+
from __future__ import annotations
|
| 55 |
+
|
| 56 |
+
import math
|
| 57 |
+
from dataclasses import dataclass
|
| 58 |
+
from typing import Optional, Tuple, Dict
|
| 59 |
+
|
| 60 |
+
import torch
|
| 61 |
+
import torch.nn as nn
|
| 62 |
+
import torch.nn.functional as F
|
| 63 |
+
from torch import Tensor
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 67 |
+
# Config
|
| 68 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 69 |
+
|
| 70 |
+
@dataclass
|
| 71 |
+
class AlephAttentionConfig:
|
| 72 |
+
"""Configuration for AlephRoutedAttention.
|
| 73 |
+
|
| 74 |
+
Args:
|
| 75 |
+
dim: model dimension
|
| 76 |
+
num_heads: attention heads
|
| 77 |
+
mode: 'hub' (linear, GEMM-only) | 'bucket' (hard-address cliques)
|
| 78 |
+
K: codebook axes (oriented axes = 2K). Rank/bandwidth knob.
|
| 79 |
+
D_addr: address-space dimension (rows live on S^(D_addr-1))
|
| 80 |
+
tau: address temperature. Small -> near-discrete routing,
|
| 81 |
+
large -> mean-pool collapse. aleph reference: 0.1
|
| 82 |
+
codebook_init: 'random' | 'fibonacci' | (K, D_addr) tensor/array
|
| 83 |
+
(transplant a trained AlephModel codebook here)
|
| 84 |
+
freeze_codebook: register codebook as a buffer (no gradient). Only safe
|
| 85 |
+
once a drift check confirms the init IS the attractor.
|
| 86 |
+
causal: autoregressive masking (both modes)
|
| 87 |
+
chunk_size: hub-causal chunk width (exact chunked linear attention)
|
| 88 |
+
block_size: bucket-mode sorted-window width W (keys window = 2W
|
| 89 |
+
via 1-block lookback)
|
| 90 |
+
bucket_bias_scale_init: init of the learnable scale on the differentiable
|
| 91 |
+
address-agreement bias (the codebook's gradient path in
|
| 92 |
+
bucket mode)
|
| 93 |
+
confidence_gate: multiply head outputs by aleph address confidence
|
| 94 |
+
||(p+ - p-) @ A|| (experimental; default off)
|
| 95 |
+
qkv_bias / out_bias: projection biases
|
| 96 |
+
dropout: output-projection dropout ONLY (never on the geometric path)
|
| 97 |
+
eps: numerical floor for denominators
|
| 98 |
+
"""
|
| 99 |
+
dim: int = 512
|
| 100 |
+
num_heads: int = 8
|
| 101 |
+
mode: str = "hub" # 'hub' | 'bucket'
|
| 102 |
+
K: int = 64
|
| 103 |
+
D_addr: int = 4
|
| 104 |
+
tau: float = 0.1
|
| 105 |
+
codebook_init: object = "fibonacci"
|
| 106 |
+
freeze_codebook: bool = False
|
| 107 |
+
causal: bool = False
|
| 108 |
+
chunk_size: int = 128
|
| 109 |
+
block_size: int = 64
|
| 110 |
+
bucket_bias_scale_init: float = 1.0
|
| 111 |
+
confidence_gate: bool = False
|
| 112 |
+
tied_address: bool = False # share q/k address projection. EMPIRICAL (2026-06-09
|
| 113 |
+
# CPU recall A/B): tying HURTS β sharp self-affinity
|
| 114 |
+
# at low tau structurally biases routing to self
|
| 115 |
+
# (same family as softmax(1/d) collapse). Keep False.
|
| 116 |
+
qkv_bias: bool = False
|
| 117 |
+
out_bias: bool = True
|
| 118 |
+
dropout: float = 0.0
|
| 119 |
+
eps: float = 1e-8
|
| 120 |
+
|
| 121 |
+
def __post_init__(self):
|
| 122 |
+
assert self.mode in ("hub", "bucket"), f"mode must be 'hub'|'bucket', got {self.mode!r}"
|
| 123 |
+
assert self.dim % self.num_heads == 0, \
|
| 124 |
+
f"dim ({self.dim}) must be divisible by num_heads ({self.num_heads})"
|
| 125 |
+
self.head_dim = self.dim // self.num_heads
|
| 126 |
+
assert self.K >= 2 and self.D_addr >= 2
|
| 127 |
+
assert self.tau > 0 and self.eps > 0
|
| 128 |
+
assert self.chunk_size > 0 and self.block_size > 0
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 132 |
+
# Codebook init (ported from geolip-svae aleph_model.py β self-contained)
|
| 133 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 134 |
+
|
| 135 |
+
def _super_fibonacci_s3(n: int, dtype=torch.float32) -> Tensor:
|
| 136 |
+
"""n near-uniform unit quaternions on S^3 via super-Fibonacci spirals
|
| 137 |
+
(Alexa, CVPR 2022). Deterministic, low-discrepancy. Returns (n, 4)."""
|
| 138 |
+
PHI = math.sqrt(2.0)
|
| 139 |
+
PSI = 1.533751168755204288118041
|
| 140 |
+
i = torch.arange(n, dtype=torch.float64) + 0.5
|
| 141 |
+
s = i / n
|
| 142 |
+
r = torch.sqrt(s)
|
| 143 |
+
R = torch.sqrt(1.0 - s)
|
| 144 |
+
alpha = 2.0 * math.pi * i / PHI
|
| 145 |
+
beta = 2.0 * math.pi * i / PSI
|
| 146 |
+
q = torch.stack([r * torch.sin(alpha), r * torch.cos(alpha),
|
| 147 |
+
R * torch.sin(beta), R * torch.cos(beta)], dim=-1)
|
| 148 |
+
return q.to(dtype)
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def _init_codebook(K: int, D: int, init, dtype=torch.float32) -> Tensor:
|
| 152 |
+
"""'random' Gaussian | 'fibonacci' near-uniform spread (exact at D=4,
|
| 153 |
+
seeded-normalized fallback otherwise) | caller (K, D) array, row-normalized
|
| 154 |
+
β the transplant path for a trained AlephModel codebook."""
|
| 155 |
+
if isinstance(init, str):
|
| 156 |
+
if init == "random":
|
| 157 |
+
return torch.randn(K, D, dtype=dtype)
|
| 158 |
+
if init == "fibonacci":
|
| 159 |
+
if D == 4:
|
| 160 |
+
return F.normalize(_super_fibonacci_s3(K, dtype=dtype), dim=-1)
|
| 161 |
+
g = torch.Generator().manual_seed(0)
|
| 162 |
+
return F.normalize(torch.randn(K, D, generator=g, dtype=dtype), dim=-1)
|
| 163 |
+
raise ValueError(f"unknown codebook_init '{init}'")
|
| 164 |
+
A = torch.as_tensor(init, dtype=dtype)
|
| 165 |
+
if tuple(A.shape) != (K, D):
|
| 166 |
+
raise ValueError(f"codebook_init array shape {tuple(A.shape)} != ({K}, {D})")
|
| 167 |
+
return F.normalize(A, dim=-1)
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 171 |
+
# Main module
|
| 172 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 173 |
+
|
| 174 |
+
class AlephRoutedAttention(nn.Module):
|
| 175 |
+
"""Attention routed through a learned (K, D_addr) projective codebook.
|
| 176 |
+
|
| 177 |
+
Per head, queries and keys are projected to D_addr and sphere-normalized.
|
| 178 |
+
The aleph address p(x) = softmax([u; -u]), u = (x_hat @ A^T) / tau
|
| 179 |
+
is the routing medium:
|
| 180 |
+
|
| 181 |
+
mode='hub' tokens communicate THROUGH the codebook β linear attention
|
| 182 |
+
with p as the feature map, antipodal-factored to K-wide ops.
|
| 183 |
+
mode='bucket' tokens attend only to same-winner-half-axis peers β exact
|
| 184 |
+
softmax within sorted blocks.
|
| 185 |
+
|
| 186 |
+
forward(x, attn_mask=None) -> Tensor (B, S, dim). Diagnostics: address_stats().
|
| 187 |
+
"""
|
| 188 |
+
|
| 189 |
+
def __init__(self, config: AlephAttentionConfig):
|
| 190 |
+
super().__init__()
|
| 191 |
+
self.cfg = config
|
| 192 |
+
c = config
|
| 193 |
+
self.dim, self.H, self.hd = c.dim, c.num_heads, c.head_dim
|
| 194 |
+
self.K, self.Da, self.tau = c.K, c.D_addr, c.tau
|
| 195 |
+
|
| 196 |
+
# ββ projections ββ
|
| 197 |
+
# address projections: per-head D_addr rows for q and k (the routing space)
|
| 198 |
+
self.q_addr = nn.Linear(c.dim, self.H * self.Da, bias=c.qkv_bias)
|
| 199 |
+
nn.init.orthogonal_(self.q_addr.weight) # load-bearing convention
|
| 200 |
+
if c.tied_address:
|
| 201 |
+
self.k_addr = self.q_addr # one routing space
|
| 202 |
+
else:
|
| 203 |
+
self.k_addr = nn.Linear(c.dim, self.H * self.Da, bias=c.qkv_bias)
|
| 204 |
+
nn.init.orthogonal_(self.k_addr.weight)
|
| 205 |
+
# value projection: full head_dim payload
|
| 206 |
+
self.v_proj = nn.Linear(c.dim, c.dim, bias=c.qkv_bias)
|
| 207 |
+
self.out_proj = nn.Linear(c.dim, c.dim, bias=c.out_bias)
|
| 208 |
+
self.dropout = nn.Dropout(c.dropout) # output path only
|
| 209 |
+
|
| 210 |
+
# bucket mode additionally scores with full-width q/k (payload attention
|
| 211 |
+
# inside the clique); hub routes purely through the address
|
| 212 |
+
if c.mode == "bucket":
|
| 213 |
+
self.q_proj = nn.Linear(c.dim, c.dim, bias=c.qkv_bias)
|
| 214 |
+
self.k_proj = nn.Linear(c.dim, c.dim, bias=c.qkv_bias)
|
| 215 |
+
nn.init.orthogonal_(self.q_proj.weight)
|
| 216 |
+
nn.init.orthogonal_(self.k_proj.weight)
|
| 217 |
+
self.bucket_bias_scale = nn.Parameter(
|
| 218 |
+
torch.tensor(float(c.bucket_bias_scale_init)))
|
| 219 |
+
self.scale = 1.0 / math.sqrt(self.hd)
|
| 220 |
+
|
| 221 |
+
# ββ the aleph codebook ββ
|
| 222 |
+
A0 = _init_codebook(c.K, c.D_addr, c.codebook_init)
|
| 223 |
+
if c.freeze_codebook:
|
| 224 |
+
self.register_buffer("codebook", A0)
|
| 225 |
+
else:
|
| 226 |
+
self.codebook = nn.Parameter(A0)
|
| 227 |
+
|
| 228 |
+
# diversity-loss hook: stash the mean address (WITH grad) when armed
|
| 229 |
+
self.emit_diversity: bool = False
|
| 230 |
+
self._mean_address: Optional[Tensor] = None # (2K,) when armed
|
| 231 |
+
|
| 232 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 233 |
+
# Address machinery (shared)
|
| 234 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 235 |
+
|
| 236 |
+
def oriented_codebook(self) -> Tensor:
|
| 237 |
+
"""(2K, D_addr) oriented half-axes [+A; -A], unit rows."""
|
| 238 |
+
A = F.normalize(self.codebook, dim=-1)
|
| 239 |
+
return torch.cat([A, -A], dim=0)
|
| 240 |
+
|
| 241 |
+
def export_codebook(self) -> Tensor:
|
| 242 |
+
"""Normalized (K, D_addr) axes for the geolip-svae antipodal-collapse
|
| 243 |
+
extraction β the preregistered basin test entry point."""
|
| 244 |
+
return F.normalize(self.codebook.detach(), dim=-1).cpu()
|
| 245 |
+
|
| 246 |
+
def _split_addr(self, t: Tensor, B: int, S: int) -> Tensor:
|
| 247 |
+
"""(B, S, H*Da) -> (B, H, S, Da), rows sphere-normalized."""
|
| 248 |
+
t = t.view(B, S, self.H, self.Da).transpose(1, 2)
|
| 249 |
+
return F.normalize(t, dim=-1) # S^(D_addr-1): the premise
|
| 250 |
+
|
| 251 |
+
def _address(self, x_hat: Tensor) -> Tuple[Tensor, Tensor]:
|
| 252 |
+
"""Aleph address of unit rows x_hat (..., Da) against the codebook.
|
| 253 |
+
|
| 254 |
+
Returns (p_plus, p_minus), each (..., K), with
|
| 255 |
+
p_plus_k = e^{ u_k} / Z, p_minus_k = e^{-u_k} / Z,
|
| 256 |
+
Z = sum_k (e^{u_k} + e^{-u_k}), u = (x_hat @ A^T)/tau
|
| 257 |
+
i.e. the exact softmax over the 2K oriented axes, antipodally factored:
|
| 258 |
+
the 2K tensor is never materialized. Stable via max|u| subtraction
|
| 259 |
+
(at least one exponent is exactly e^0, so Z' >= 1)."""
|
| 260 |
+
A = F.normalize(self.codebook, dim=-1) # (K, Da)
|
| 261 |
+
u = (x_hat @ A.t()) * (1.0 / self.tau) # (..., K) signed
|
| 262 |
+
m = u.abs().amax(dim=-1, keepdim=True)
|
| 263 |
+
ep = torch.exp(u - m) # β e^{+u}
|
| 264 |
+
en = torch.exp(-u - m) # β e^{-u}
|
| 265 |
+
Z = (ep + en).sum(dim=-1, keepdim=True) # >= 1 by construction
|
| 266 |
+
return ep / Z, en / Z
|
| 267 |
+
|
| 268 |
+
def _confidence(self, pq_p: Tensor, pq_m: Tensor) -> Tensor:
|
| 269 |
+
"""Aleph address confidence ||(p+ - p-) @ A|| in (0, 1] β the norm of the
|
| 270 |
+
soft codebook reconstruction (the hub analogue of ||M_hat||)."""
|
| 271 |
+
A = F.normalize(self.codebook, dim=-1)
|
| 272 |
+
return ((pq_p - pq_m) @ A).norm(dim=-1) # (..., )
|
| 273 |
+
|
| 274 |
+
def _stash_diversity(self, pk_p: Tensor, pk_m: Tensor,
|
| 275 |
+
mask: Optional[Tensor]) -> None:
|
| 276 |
+
"""Mean address over valid key rows -> (2K,) with grad, for diversity_loss()."""
|
| 277 |
+
if not (self.emit_diversity and self.training):
|
| 278 |
+
return
|
| 279 |
+
if mask is not None:
|
| 280 |
+
w = mask[:, None, :, None].to(pk_p.dtype) # (B,1,S,1)
|
| 281 |
+
n = w.sum().clamp_min(1.0) * self.H
|
| 282 |
+
mp = (pk_p * w).sum(dim=(0, 1, 2)) / n
|
| 283 |
+
mm = (pk_m * w).sum(dim=(0, 1, 2)) / n
|
| 284 |
+
else:
|
| 285 |
+
mp = pk_p.mean(dim=(0, 1, 2))
|
| 286 |
+
mm = pk_m.mean(dim=(0, 1, 2))
|
| 287 |
+
self._mean_address = torch.cat([mp, mm], dim=0) # (2K,)
|
| 288 |
+
|
| 289 |
+
def diversity_loss(self) -> Tensor:
|
| 290 |
+
"""Anti-collapse term (train_aleph div_weight semantics):
|
| 291 |
+
log(2K) - H(mean address). Zero at uniform usage. Arm with
|
| 292 |
+
model.emit_diversity = True; read after forward; weight ~0.01."""
|
| 293 |
+
if self._mean_address is None:
|
| 294 |
+
return torch.zeros((), device=self.codebook.device)
|
| 295 |
+
p = self._mean_address.clamp_min(1e-12)
|
| 296 |
+
H = -(p * p.log()).sum()
|
| 297 |
+
return math.log(2 * self.K) - H
|
| 298 |
+
|
| 299 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 300 |
+
# HUB mode β linear attention through the codebook (pure GEMM)
|
| 301 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 302 |
+
|
| 303 |
+
def _hub_full(self, pq_p, pq_m, pk_p, pk_m, v) -> Tensor:
|
| 304 |
+
"""Non-causal hub. p*: (B,H,S,K), v: (B,H,S,hd) -> (B,H,S,hd).
|
| 305 |
+
|
| 306 |
+
score(i,j) = pq+(i)Β·pk+(j) + pq-(i)Β·pk-(j) factors through two K-wide
|
| 307 |
+
memories; out_i = num_i / den_i with den strictly positive."""
|
| 308 |
+
Mp = torch.einsum('bhsk,bhsd->bhkd', pk_p, v) # (B,H,K,hd)
|
| 309 |
+
Mm = torch.einsum('bhsk,bhsd->bhkd', pk_m, v)
|
| 310 |
+
zp = pk_p.sum(dim=2) # (B,H,K)
|
| 311 |
+
zm = pk_m.sum(dim=2)
|
| 312 |
+
num = torch.einsum('bhsk,bhkd->bhsd', pq_p, Mp) \
|
| 313 |
+
+ torch.einsum('bhsk,bhkd->bhsd', pq_m, Mm)
|
| 314 |
+
den = torch.einsum('bhsk,bhk->bhs', pq_p, zp) \
|
| 315 |
+
+ torch.einsum('bhsk,bhk->bhs', pq_m, zm)
|
| 316 |
+
return num / den.unsqueeze(-1).clamp_min(self.cfg.eps)
|
| 317 |
+
|
| 318 |
+
def _hub_causal(self, pq_p, pq_m, pk_p, pk_m, v,
|
| 319 |
+
state: Optional[Tuple[Tensor, ...]] = None
|
| 320 |
+
) -> Tuple[Tensor, Tuple[Tensor, ...]]:
|
| 321 |
+
"""Exact chunked causal hub: running K-wide state across chunks +
|
| 322 |
+
lower-triangular intra-chunk correction. Loop count = S/chunk_size
|
| 323 |
+
(the standard chunked linear-attention recurrence β not a per-token loop).
|
| 324 |
+
|
| 325 |
+
`state` = (Mp, Mm, zp, zm) carried from previous segments. The state is
|
| 326 |
+
constant-size β (B,H,K,hd)+(B,H,K) per sign β regardless of how much
|
| 327 |
+
past it summarizes: Mp/Mm are what has been written to each oriented
|
| 328 |
+
codebook axis so far. Returns (out, final_state) for streaming."""
|
| 329 |
+
B, H, S, _ = v.shape
|
| 330 |
+
C = min(self.cfg.chunk_size, S)
|
| 331 |
+
if state is None:
|
| 332 |
+
Mp = v.new_zeros(B, H, self.K, self.hd)
|
| 333 |
+
Mm = v.new_zeros(B, H, self.K, self.hd)
|
| 334 |
+
zp = v.new_zeros(B, H, self.K)
|
| 335 |
+
zm = v.new_zeros(B, H, self.K)
|
| 336 |
+
else:
|
| 337 |
+
Mp, Mm, zp, zm = state
|
| 338 |
+
outs = []
|
| 339 |
+
tri_cache: Dict[int, Tensor] = {}
|
| 340 |
+
for s0 in range(0, S, C):
|
| 341 |
+
s1 = min(s0 + C, S)
|
| 342 |
+
qp, qm = pq_p[:, :, s0:s1], pq_m[:, :, s0:s1]
|
| 343 |
+
kp, km = pk_p[:, :, s0:s1], pk_m[:, :, s0:s1]
|
| 344 |
+
vc = v[:, :, s0:s1]
|
| 345 |
+
c = s1 - s0
|
| 346 |
+
if c not in tri_cache:
|
| 347 |
+
tri_cache[c] = torch.tril(
|
| 348 |
+
torch.ones(c, c, device=v.device, dtype=v.dtype))
|
| 349 |
+
tri = tri_cache[c]
|
| 350 |
+
# intra-chunk (causal) scores β strictly positive entries pre-mask
|
| 351 |
+
intra = (torch.einsum('bhik,bhjk->bhij', qp, kp)
|
| 352 |
+
+ torch.einsum('bhik,bhjk->bhij', qm, km)) * tri
|
| 353 |
+
num = intra @ vc \
|
| 354 |
+
+ torch.einsum('bhsk,bhkd->bhsd', qp, Mp) \
|
| 355 |
+
+ torch.einsum('bhsk,bhkd->bhsd', qm, Mm)
|
| 356 |
+
den = intra.sum(dim=-1) \
|
| 357 |
+
+ torch.einsum('bhsk,bhk->bhs', qp, zp) \
|
| 358 |
+
+ torch.einsum('bhsk,bhk->bhs', qm, zm)
|
| 359 |
+
outs.append(num / den.unsqueeze(-1).clamp_min(self.cfg.eps))
|
| 360 |
+
# state update (inclusive of this chunk, for the next one)
|
| 361 |
+
Mp = Mp + torch.einsum('bhck,bhcd->bhkd', kp, vc)
|
| 362 |
+
Mm = Mm + torch.einsum('bhck,bhcd->bhkd', km, vc)
|
| 363 |
+
zp = zp + kp.sum(dim=2)
|
| 364 |
+
zm = zm + km.sum(dim=2)
|
| 365 |
+
return torch.cat(outs, dim=2), (Mp, Mm, zp, zm)
|
| 366 |
+
|
| 367 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 368 |
+
# BUCKET mode β hard-address cliques (sort + windowed exact attention)
|
| 369 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 370 |
+
|
| 371 |
+
@staticmethod
|
| 372 |
+
def _take(t: Tensor, idx: Tensor) -> Tensor:
|
| 373 |
+
"""Gather along dim=2. t: (B,H,S,X) or (B,H,S); idx: (B,H,S)."""
|
| 374 |
+
if t.dim() == 3:
|
| 375 |
+
return torch.gather(t, 2, idx)
|
| 376 |
+
return torch.gather(t, 2, idx.unsqueeze(-1).expand(-1, -1, -1, t.shape[-1]))
|
| 377 |
+
|
| 378 |
+
@staticmethod
|
| 379 |
+
def _window(t: Tensor, nb: int, W: int) -> Tensor:
|
| 380 |
+
"""Blocked tensor (B,H,nb,W,...) -> (B,H,nb,2W,...) keys window =
|
| 381 |
+
[previous block ; this block]. Block 0's previous half is junk β
|
| 382 |
+
callers must kill it via the validity window."""
|
| 383 |
+
prev = torch.cat([torch.zeros_like(t[:, :, :1]), t[:, :, :-1]], dim=2)
|
| 384 |
+
return torch.cat([prev, t], dim=3)
|
| 385 |
+
|
| 386 |
+
def _bucket_attend(self, q, k, v, pq_p, pq_m, pk_p, pk_m,
|
| 387 |
+
mask: Optional[Tensor]) -> Tensor:
|
| 388 |
+
"""q,k,v: (B,H,S,hd); p*: (B,H,S,K); mask: (B,S) 1=valid or None.
|
| 389 |
+
|
| 390 |
+
1. bucket = winner oriented half-axis (argmax |u|, sign-resolved)
|
| 391 |
+
2. stable-sort tokens by bucket; pad S to a multiple of W
|
| 392 |
+
3. exact softmax attention within [prev block ; block] windows,
|
| 393 |
+
masked to same-bucket, valid, (and causal by original position)
|
| 394 |
+
4. differentiable codebook path: scores += scale * address-agreement
|
| 395 |
+
5. inverse-permute, un-pad."""
|
| 396 |
+
cfg = self.cfg
|
| 397 |
+
B, H, S, hd = q.shape
|
| 398 |
+
W = min(cfg.block_size, max(8, S))
|
| 399 |
+
dev = q.device
|
| 400 |
+
|
| 401 |
+
# ββ 1. hard bucket ids ββ (recover signed u from the address: u = (log ep - log en)/2
|
| 402 |
+
# is unnecessary β argmax of p_plus vs p_minus IS argmax |u| with sign)
|
| 403 |
+
win_p, idx_p = pq_p.max(dim=-1) # query side unused for ids
|
| 404 |
+
# bucket from the KEY/QUERY shared address rows: use each token's own address
|
| 405 |
+
# (q-side and k-side addresses may differ; routing identity = q-address for
|
| 406 |
+
# queries, k-address for keys β a token can listen in one clique and speak in
|
| 407 |
+
# another. We bucket by the K-side address for keys and Q-side for queries,
|
| 408 |
+
# then require equality β implemented by bucketing each side independently.)
|
| 409 |
+
def hard_ids(pp: Tensor, pm: Tensor) -> Tensor:
|
| 410 |
+
vp, ip = pp.max(dim=-1)
|
| 411 |
+
vm, im = pm.max(dim=-1)
|
| 412 |
+
plus_wins = vp >= vm
|
| 413 |
+
return torch.where(plus_wins, ip, im + self.K) # (B,H,S) in [0, 2K)
|
| 414 |
+
|
| 415 |
+
bq = hard_ids(pq_p, pq_m)
|
| 416 |
+
bk = hard_ids(pk_p, pk_m)
|
| 417 |
+
valid = (mask if mask is not None
|
| 418 |
+
else torch.ones(B, S, device=dev, dtype=torch.bool))
|
| 419 |
+
valid = valid.bool()[:, None, :].expand(B, H, S)
|
| 420 |
+
JUNK = 2 * self.K + 1
|
| 421 |
+
bq = torch.where(valid, bq, torch.full_like(bq, JUNK))
|
| 422 |
+
bk = torch.where(valid, bk, torch.full_like(bk, JUNK))
|
| 423 |
+
|
| 424 |
+
# ββ 2. pad to multiple of W, sort by key-bucket ββ
|
| 425 |
+
pad = (-S) % W
|
| 426 |
+
if pad:
|
| 427 |
+
def padS(t, fill=0.0):
|
| 428 |
+
shape = list(t.shape); shape[2] = pad
|
| 429 |
+
return torch.cat([t, t.new_full(shape, fill)], dim=2)
|
| 430 |
+
q, k, v = padS(q), padS(k), padS(v)
|
| 431 |
+
pq_p, pq_m, pk_p, pk_m = padS(pq_p), padS(pq_m), padS(pk_p), padS(pk_m)
|
| 432 |
+
bq, bk = padS(bq, JUNK), padS(bk, JUNK)
|
| 433 |
+
valid = padS(valid, False)
|
| 434 |
+
Sp = S + pad
|
| 435 |
+
nb = Sp // W
|
| 436 |
+
pos = torch.arange(Sp, device=dev).view(1, 1, Sp).expand(B, H, Sp)
|
| 437 |
+
|
| 438 |
+
sort_idx = bk.argsort(dim=-1, stable=True) # cluster keys by bucket
|
| 439 |
+
inv_idx = sort_idx.argsort(dim=-1)
|
| 440 |
+
gq, gk, gv = self._take(q, sort_idx), self._take(k, sort_idx), self._take(v, sort_idx)
|
| 441 |
+
gpq_p, gpq_m = self._take(pq_p, sort_idx), self._take(pq_m, sort_idx)
|
| 442 |
+
gpk_p, gpk_m = self._take(pk_p, sort_idx), self._take(pk_m, sort_idx)
|
| 443 |
+
gbq, gbk = self._take(bq, sort_idx), self._take(bk, sort_idx)
|
| 444 |
+
gvalid, gpos = self._take(valid.long(), sort_idx).bool(), self._take(pos, sort_idx)
|
| 445 |
+
|
| 446 |
+
def blk(t):
|
| 447 |
+
return t.view(B, H, nb, W, *t.shape[3:])
|
| 448 |
+
q_b, v_b = blk(gq), blk(gv)
|
| 449 |
+
k_w = self._window(blk(gk), nb, W) # (B,H,nb,2W,hd)
|
| 450 |
+
v_w = self._window(blk(gv), nb, W)
|
| 451 |
+
pkp_w = self._window(blk(gpk_p), nb, W)
|
| 452 |
+
pkm_w = self._window(blk(gpk_m), nb, W)
|
| 453 |
+
bq_b = blk(gbq)
|
| 454 |
+
bk_w = self._window(blk(gbk).unsqueeze(-1), nb, W).squeeze(-1)
|
| 455 |
+
val_w = self._window(blk(gvalid.long()).unsqueeze(-1), nb, W).squeeze(-1).bool()
|
| 456 |
+
pos_b = blk(gpos)
|
| 457 |
+
pos_w = self._window(blk(gpos).unsqueeze(-1), nb, W).squeeze(-1)
|
| 458 |
+
val_w[:, :, 0, :W] = False # block 0 has no previous
|
| 459 |
+
|
| 460 |
+
# ββ 3. scores: payload qΒ·k within the window ββ
|
| 461 |
+
scores = torch.einsum('bhnwd,bhnud->bhnwu', q_b, k_w) * self.scale
|
| 462 |
+
|
| 463 |
+
# ββ 4. differentiable address-agreement bias (codebook gradient path) ββ
|
| 464 |
+
pqp_b, pqm_b = blk(gpq_p), blk(gpq_m)
|
| 465 |
+
agreement = torch.einsum('bhnwk,bhnuk->bhnwu', pqp_b, pkp_w) \
|
| 466 |
+
+ torch.einsum('bhnwk,bhnuk->bhnwu', pqm_b, pkm_w)
|
| 467 |
+
scores = scores + self.bucket_bias_scale * agreement
|
| 468 |
+
|
| 469 |
+
# ββ masks: same bucket, valid, causal ββ
|
| 470 |
+
same = bq_b.unsqueeze(-1) == bk_w.unsqueeze(-2) # (B,H,nb,W,2W)
|
| 471 |
+
keep = same & val_w.unsqueeze(-2)
|
| 472 |
+
if cfg.causal:
|
| 473 |
+
keep = keep & (pos_w.unsqueeze(-2) <= pos_b.unsqueeze(-1))
|
| 474 |
+
scores = scores.masked_fill(~keep, float('-inf'))
|
| 475 |
+
attn = F.softmax(scores, dim=-1)
|
| 476 |
+
attn = torch.nan_to_num(attn, nan=0.0) # all-masked rows = pads only
|
| 477 |
+
out_b = torch.einsum('bhnwu,bhnud->bhnwd', attn, v_w)
|
| 478 |
+
|
| 479 |
+
# ββ 5. inverse permute, un-pad βοΏ½οΏ½
|
| 480 |
+
out = out_b.reshape(B, H, Sp, hd)
|
| 481 |
+
out = self._take(out, inv_idx)
|
| 482 |
+
return out[:, :, :S]
|
| 483 |
+
|
| 484 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 485 |
+
# Forward (returns a single Tensor β compile-rule compliant)
|
| 486 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 487 |
+
|
| 488 |
+
def forward(self, x: Tensor, attn_mask: Optional[Tensor] = None) -> Tensor:
|
| 489 |
+
"""x: (B, S, dim); attn_mask: (B, S) with 1 = valid, 0 = padding.
|
| 490 |
+
Returns (B, S, dim)."""
|
| 491 |
+
B, S, _ = x.shape
|
| 492 |
+
cfg = self.cfg
|
| 493 |
+
|
| 494 |
+
qh = self._split_addr(self.q_addr(x), B, S) # (B,H,S,Da) on the sphere
|
| 495 |
+
kh = self._split_addr(self.k_addr(x), B, S)
|
| 496 |
+
v = self.v_proj(x).view(B, S, self.H, self.hd).transpose(1, 2)
|
| 497 |
+
|
| 498 |
+
pq_p, pq_m = self._address(qh) # (B,H,S,K) each
|
| 499 |
+
pk_p, pk_m = self._address(kh)
|
| 500 |
+
self._stash_diversity(pk_p, pk_m, attn_mask)
|
| 501 |
+
|
| 502 |
+
if attn_mask is not None:
|
| 503 |
+
mk = attn_mask[:, None, :, None].to(v.dtype) # kill masked KEYS
|
| 504 |
+
pk_p, pk_m, v_in = pk_p * mk, pk_m * mk, v * mk
|
| 505 |
+
else:
|
| 506 |
+
v_in = v
|
| 507 |
+
|
| 508 |
+
if cfg.mode == "hub":
|
| 509 |
+
if cfg.causal:
|
| 510 |
+
out, _ = self._hub_causal(pq_p, pq_m, pk_p, pk_m, v_in)
|
| 511 |
+
else:
|
| 512 |
+
out = self._hub_full(pq_p, pq_m, pk_p, pk_m, v_in)
|
| 513 |
+
else: # bucket
|
| 514 |
+
qf = self.q_proj(x).view(B, S, self.H, self.hd).transpose(1, 2)
|
| 515 |
+
kf = self.k_proj(x).view(B, S, self.H, self.hd).transpose(1, 2)
|
| 516 |
+
out = self._bucket_attend(qf, kf, v, pq_p, pq_m, pk_p, pk_m, attn_mask)
|
| 517 |
+
|
| 518 |
+
if cfg.confidence_gate:
|
| 519 |
+
out = out * self._confidence(pq_p, pq_m).unsqueeze(-1)
|
| 520 |
+
|
| 521 |
+
out = out.transpose(1, 2).reshape(B, S, self.dim)
|
| 522 |
+
return self.dropout(self.out_proj(out))
|
| 523 |
+
|
| 524 |
+
def forward_stream(self, x: Tensor,
|
| 525 |
+
state: Optional[Tuple[Tensor, ...]] = None,
|
| 526 |
+
attn_mask: Optional[Tensor] = None
|
| 527 |
+
) -> Tuple[Tensor, Tuple[Tensor, ...]]:
|
| 528 |
+
"""Segment-recurrent forward (mode='hub', causal=True only).
|
| 529 |
+
|
| 530 |
+
Processes a segment with the codebook memory carried in `state`
|
| 531 |
+
(init None = empty past), returns (out, new_state). Context is
|
| 532 |
+
unbounded at constant memory: state is (Mp, Mm, zp, zm), shape
|
| 533 |
+
(B,H,K,hd)x2 + (B,H,K)x2, independent of total past length.
|
| 534 |
+
TBPTT discipline: .detach() each state tensor between backward
|
| 535 |
+
passes β graphs are freed per segment."""
|
| 536 |
+
assert self.cfg.mode == "hub" and self.cfg.causal, \
|
| 537 |
+
"forward_stream requires mode='hub', causal=True (bucket sorts globally)"
|
| 538 |
+
B, S, _ = x.shape
|
| 539 |
+
qh = self._split_addr(self.q_addr(x), B, S)
|
| 540 |
+
kh = self._split_addr(self.k_addr(x), B, S)
|
| 541 |
+
v = self.v_proj(x).view(B, S, self.H, self.hd).transpose(1, 2)
|
| 542 |
+
pq_p, pq_m = self._address(qh)
|
| 543 |
+
pk_p, pk_m = self._address(kh)
|
| 544 |
+
self._stash_diversity(pk_p, pk_m, attn_mask)
|
| 545 |
+
if attn_mask is not None:
|
| 546 |
+
mk = attn_mask[:, None, :, None].to(v.dtype)
|
| 547 |
+
pk_p, pk_m, v = pk_p * mk, pk_m * mk, v * mk
|
| 548 |
+
out, new_state = self._hub_causal(pq_p, pq_m, pk_p, pk_m, v, state)
|
| 549 |
+
if self.cfg.confidence_gate:
|
| 550 |
+
out = out * self._confidence(pq_p, pq_m).unsqueeze(-1)
|
| 551 |
+
out = out.transpose(1, 2).reshape(B, S, self.dim)
|
| 552 |
+
return self.dropout(self.out_proj(out)), new_state
|
| 553 |
+
|
| 554 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 555 |
+
# Diagnostics (eval-only; never in the hot path)
|
| 556 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 557 |
+
|
| 558 |
+
@torch.no_grad()
|
| 559 |
+
def address_stats(self, x: Tensor,
|
| 560 |
+
attn_mask: Optional[Tensor] = None,
|
| 561 |
+
max_rows: Optional[int] = None) -> Dict[str, float]:
|
| 562 |
+
"""Codebook-health monitors (train_aleph semantics):
|
| 563 |
+
perplexity : exp(H(mean address)) β effective oriented axes in use,
|
| 564 |
+
in [1, 2K]. The collapse detector.
|
| 565 |
+
margin : mean (top1 - top2) of per-row address β decisiveness.
|
| 566 |
+
confidence : mean ||(p+ - p-) @ A|| β address sharpness in (0, 1].
|
| 567 |
+
bucket_cv : coefficient of variation of hard-bucket occupancy
|
| 568 |
+
(load-balance; bucket mode's health metric).
|
| 569 |
+
"""
|
| 570 |
+
B, S, _ = x.shape
|
| 571 |
+
kh = self._split_addr(self.k_addr(x), B, S)
|
| 572 |
+
pp, pm = self._address(kh)
|
| 573 |
+
if attn_mask is not None:
|
| 574 |
+
m = attn_mask.bool()[:, None, :].expand(B, self.H, S)
|
| 575 |
+
pp = pp[m]; pm = pm[m] # (R, K)
|
| 576 |
+
else:
|
| 577 |
+
pp = pp.reshape(-1, self.K); pm = pm.reshape(-1, self.K)
|
| 578 |
+
full = torch.cat([pp, pm], dim=-1) # (R, 2K)
|
| 579 |
+
if max_rows is not None and full.shape[0] > max_rows:
|
| 580 |
+
full = full[torch.randperm(full.shape[0])[:max_rows]]
|
| 581 |
+
pp, pm = full[:, :self.K], full[:, self.K:]
|
| 582 |
+
|
| 583 |
+
mean_addr = full.mean(dim=0).clamp_min(1e-12)
|
| 584 |
+
H = -(mean_addr * mean_addr.log()).sum()
|
| 585 |
+
perplexity = H.exp().item()
|
| 586 |
+
|
| 587 |
+
top2 = full.topk(2, dim=-1).values
|
| 588 |
+
margin = (top2[:, 0] - top2[:, 1]).mean().item()
|
| 589 |
+
|
| 590 |
+
A = F.normalize(self.codebook, dim=-1)
|
| 591 |
+
confidence = ((pp - pm) @ A).norm(dim=-1).mean().item()
|
| 592 |
+
|
| 593 |
+
ids = full.argmax(dim=-1)
|
| 594 |
+
occ = torch.bincount(ids, minlength=2 * self.K).float()
|
| 595 |
+
bucket_cv = (occ.std(unbiased=False) / occ.mean().clamp_min(1e-12)).item()
|
| 596 |
+
|
| 597 |
+
return {"perplexity": perplexity, "margin": margin,
|
| 598 |
+
"confidence": confidence, "bucket_cv": bucket_cv,
|
| 599 |
+
"max_perplexity": float(2 * self.K)}
|
| 600 |
+
|
| 601 |
+
def extra_repr(self) -> str:
|
| 602 |
+
c = self.cfg
|
| 603 |
+
return (f"dim={c.dim}, heads={c.num_heads}, mode={c.mode}, "
|
| 604 |
+
f"K={c.K} (2K={2*c.K} oriented), D_addr={c.D_addr}, "
|
| 605 |
+
f"tau={c.tau}, causal={c.causal}")
|
| 606 |
+
|
| 607 |
+
|
| 608 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 609 |
+
# Reference baseline (for the harness A/B)
|
| 610 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 611 |
+
|
| 612 |
+
class StandardAttention(nn.Module):
|
| 613 |
+
"""Plain softmax attention, same I/O contract, for the A/B."""
|
| 614 |
+
|
| 615 |
+
def __init__(self, dim: int, num_heads: int, causal: bool = False):
|
| 616 |
+
super().__init__()
|
| 617 |
+
assert dim % num_heads == 0
|
| 618 |
+
self.H, self.hd, self.causal = num_heads, dim // num_heads, causal
|
| 619 |
+
self.qkv = nn.Linear(dim, 3 * dim, bias=False)
|
| 620 |
+
self.out_proj = nn.Linear(dim, dim)
|
| 621 |
+
self.scale = 1.0 / math.sqrt(self.hd)
|
| 622 |
+
|
| 623 |
+
def forward(self, x: Tensor, attn_mask: Optional[Tensor] = None) -> Tensor:
|
| 624 |
+
B, S, D = x.shape
|
| 625 |
+
q, k, v = self.qkv(x).view(B, S, 3, self.H, self.hd) \
|
| 626 |
+
.permute(2, 0, 3, 1, 4).unbind(0)
|
| 627 |
+
scores = (q @ k.transpose(-2, -1)) * self.scale
|
| 628 |
+
if attn_mask is not None:
|
| 629 |
+
scores = scores.masked_fill(
|
| 630 |
+
~attn_mask.bool()[:, None, None, :], float('-inf'))
|
| 631 |
+
if self.causal:
|
| 632 |
+
tri = torch.ones(S, S, device=x.device, dtype=torch.bool).tril()
|
| 633 |
+
scores = scores.masked_fill(~tri, float('-inf'))
|
| 634 |
+
out = torch.nan_to_num(F.softmax(scores, dim=-1), nan=0.0) @ v
|
| 635 |
+
return self.out_proj(out.transpose(1, 2).reshape(B, S, D))
|
| 636 |
+
|
| 637 |
+
|
| 638 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 639 |
+
# Harness β associative recall (routing-sensitive synthetic task)
|
| 640 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 641 |
+
#
|
| 642 |
+
# Sequence = [k1 v1 k2 v2 ... kn vn Q kq] -> predict the value paired with kq.
|
| 643 |
+
# Solvable only by routing the query token to the matching key token: a task
|
| 644 |
+
# where the routing medium IS the bottleneck. Trained with pure Adam (never
|
| 645 |
+
# AdamW β weight decay fights the geometric basin).
|
| 646 |
+
|
| 647 |
+
class TinyRecallModel(nn.Module):
|
| 648 |
+
def __init__(self, vocab: int, dim: int, attn: nn.Module, n_layers: int = 2,
|
| 649 |
+
attn_factory=None):
|
| 650 |
+
super().__init__()
|
| 651 |
+
self.emb = nn.Embedding(vocab, dim)
|
| 652 |
+
self.pos = nn.Parameter(0.02 * torch.randn(1, 512, dim))
|
| 653 |
+
layers = []
|
| 654 |
+
for i in range(n_layers):
|
| 655 |
+
a = attn if (i == 0 and attn_factory is None) else attn_factory()
|
| 656 |
+
layers.append(nn.ModuleDict({
|
| 657 |
+
"norm1": nn.LayerNorm(dim), "attn": a,
|
| 658 |
+
"norm2": nn.LayerNorm(dim),
|
| 659 |
+
"mlp": nn.Sequential(nn.Linear(dim, 2 * dim), nn.GELU(),
|
| 660 |
+
nn.Linear(2 * dim, dim)),
|
| 661 |
+
}))
|
| 662 |
+
self.layers = nn.ModuleList(layers)
|
| 663 |
+
self.head = nn.Linear(dim, vocab)
|
| 664 |
+
|
| 665 |
+
def forward(self, ids: Tensor) -> Tensor:
|
| 666 |
+
x = self.emb(ids) + self.pos[:, :ids.shape[1]]
|
| 667 |
+
for L in self.layers:
|
| 668 |
+
x = x + L["attn"](L["norm1"](x))
|
| 669 |
+
x = x + L["mlp"](L["norm2"](x))
|
| 670 |
+
return self.head(x[:, -1]) # predict from final token
|
| 671 |
+
|
| 672 |
+
|
| 673 |
+
def make_recall_batch(B: int, n_pairs: int, n_keys: int, n_vals: int,
|
| 674 |
+
device) -> Tuple[Tensor, Tensor]:
|
| 675 |
+
"""Tokens: [0, n_keys) keys | [n_keys, n_keys+n_vals) values | Q = last id."""
|
| 676 |
+
Q = n_keys + n_vals
|
| 677 |
+
keys = torch.stack([torch.randperm(n_keys, device=device)[:n_pairs]
|
| 678 |
+
for _ in range(B)]) # unique keys per row
|
| 679 |
+
vals = torch.randint(0, n_vals, (B, n_pairs), device=device) + n_keys
|
| 680 |
+
seq = torch.stack([keys, vals], dim=-1).reshape(B, 2 * n_pairs)
|
| 681 |
+
qi = torch.randint(0, n_pairs, (B,), device=device)
|
| 682 |
+
kq = keys.gather(1, qi[:, None])
|
| 683 |
+
target = vals.gather(1, qi[:, None]).squeeze(1)
|
| 684 |
+
ids = torch.cat([seq, torch.full((B, 1), Q, device=device), kq], dim=1)
|
| 685 |
+
return ids, target
|
| 686 |
+
|
| 687 |
+
|
| 688 |
+
def run_harness(mode: str, steps: int = 300, device: str = "cpu",
|
| 689 |
+
seed: int = 1234, log_every: int = 50,
|
| 690 |
+
dim: int = 128, n_heads: int = 4, K: int = 32, D_addr: int = 4,
|
| 691 |
+
n_pairs: int = 12, n_keys: int = 48, n_vals: int = 24,
|
| 692 |
+
batch: int = 64, lr: float = 3e-4,
|
| 693 |
+
div_weight: float = 0.0, tied_address: bool = False,
|
| 694 |
+
codebook_init="fibonacci", lr_decay: bool = True,
|
| 695 |
+
snapshot_codebook: bool = False) -> Dict[str, float]:
|
| 696 |
+
torch.manual_seed(seed)
|
| 697 |
+
vocab = n_keys + n_vals + 1
|
| 698 |
+
if mode == "standard":
|
| 699 |
+
attn_factory = lambda: StandardAttention(dim, n_heads)
|
| 700 |
+
first = attn_factory()
|
| 701 |
+
else:
|
| 702 |
+
cfg = AlephAttentionConfig(dim=dim, num_heads=n_heads, mode=mode,
|
| 703 |
+
K=K, D_addr=D_addr, tau=0.1,
|
| 704 |
+
tied_address=tied_address,
|
| 705 |
+
codebook_init=codebook_init)
|
| 706 |
+
attn_factory = lambda: AlephRoutedAttention(cfg)
|
| 707 |
+
first = attn_factory()
|
| 708 |
+
model = TinyRecallModel(vocab, dim, first, n_layers=2,
|
| 709 |
+
attn_factory=attn_factory).to(device)
|
| 710 |
+
opt = torch.optim.Adam(model.parameters(), lr=lr) # pure Adam, never AdamW
|
| 711 |
+
sched = (torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=steps,
|
| 712 |
+
eta_min=lr * 0.1) if lr_decay else None)
|
| 713 |
+
|
| 714 |
+
aleph_layers = [m for m in model.modules() if isinstance(m, AlephRoutedAttention)]
|
| 715 |
+
for a in aleph_layers:
|
| 716 |
+
a.emit_diversity = div_weight > 0
|
| 717 |
+
|
| 718 |
+
print(f"\n=== mode={mode} params={sum(p.numel() for p in model.parameters()):,} ===")
|
| 719 |
+
final = {}
|
| 720 |
+
snapshots = [] # (step, (K,D)) trajectory
|
| 721 |
+
if snapshot_codebook and aleph_layers:
|
| 722 |
+
snapshots.append((0, aleph_layers[0].export_codebook()))
|
| 723 |
+
for step in range(1, steps + 1):
|
| 724 |
+
ids, target = make_recall_batch(batch, n_pairs, n_keys, n_vals, device)
|
| 725 |
+
logits = model(ids)
|
| 726 |
+
loss = F.cross_entropy(logits, target)
|
| 727 |
+
if div_weight > 0:
|
| 728 |
+
loss = loss + div_weight * sum(a.diversity_loss() for a in aleph_layers)
|
| 729 |
+
opt.zero_grad(set_to_none=True)
|
| 730 |
+
loss.backward()
|
| 731 |
+
gnorm = torch.nn.utils.clip_grad_norm_(
|
| 732 |
+
model.parameters(), max(loss.item(), 1.0)) # Phil's clip rule
|
| 733 |
+
opt.step()
|
| 734 |
+
if sched is not None:
|
| 735 |
+
sched.step()
|
| 736 |
+
|
| 737 |
+
if step % log_every == 0 or step == steps:
|
| 738 |
+
with torch.no_grad():
|
| 739 |
+
acc = (logits.argmax(-1) == target).float().mean().item()
|
| 740 |
+
line = f" step {step:4d} loss {loss.item():.4f} acc {acc:.3f} |g| {gnorm:.2f}"
|
| 741 |
+
if aleph_layers:
|
| 742 |
+
model.eval()
|
| 743 |
+
x_probe = model.emb(ids) + model.pos[:, :ids.shape[1]]
|
| 744 |
+
st = aleph_layers[0].address_stats(x_probe)
|
| 745 |
+
model.train()
|
| 746 |
+
line += (f" ppl {st['perplexity']:.1f}/{st['max_perplexity']:.0f}"
|
| 747 |
+
f" margin {st['margin']:.3f} conf {st['confidence']:.3f}"
|
| 748 |
+
f" bktCV {st['bucket_cv']:.2f}")
|
| 749 |
+
final.update(st)
|
| 750 |
+
print(line)
|
| 751 |
+
final.update({"loss": loss.item(), "acc": acc})
|
| 752 |
+
if snapshot_codebook and aleph_layers:
|
| 753 |
+
snapshots.append((step, aleph_layers[0].export_codebook()))
|
| 754 |
+
if snapshots:
|
| 755 |
+
final["codebook_snapshots"] = snapshots
|
| 756 |
+
return final
|
| 757 |
+
|
| 758 |
+
|
| 759 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 760 |
+
# Smoke tests + activation
|
| 761 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 762 |
+
|
| 763 |
+
def _smoke():
|
| 764 |
+
torch.manual_seed(0)
|
| 765 |
+
print("=" * 70)
|
| 766 |
+
print("AlephRoutedAttention β smoke tests")
|
| 767 |
+
print("=" * 70)
|
| 768 |
+
|
| 769 |
+
for mode in ("hub", "bucket"):
|
| 770 |
+
for causal in (False, True):
|
| 771 |
+
cfg = AlephAttentionConfig(dim=64, num_heads=4, mode=mode, K=16,
|
| 772 |
+
D_addr=4, causal=causal, block_size=16,
|
| 773 |
+
chunk_size=32)
|
| 774 |
+
m = AlephRoutedAttention(cfg)
|
| 775 |
+
x = torch.randn(2, 50, 64, requires_grad=True) # odd S: pad path
|
| 776 |
+
mask = torch.ones(2, 50); mask[1, 40:] = 0
|
| 777 |
+
y = m(x, attn_mask=mask)
|
| 778 |
+
assert y.shape == (2, 50, 64), y.shape
|
| 779 |
+
assert torch.isfinite(y).all()
|
| 780 |
+
y.sum().backward()
|
| 781 |
+
assert torch.isfinite(x.grad).all()
|
| 782 |
+
assert m.codebook.grad is not None and torch.isfinite(m.codebook.grad).all(), \
|
| 783 |
+
f"codebook got no/bad gradient in mode={mode}"
|
| 784 |
+
cb_g = m.codebook.grad.norm().item()
|
| 785 |
+
print(f" β mode={mode:6s} causal={causal!s:5s} out {tuple(y.shape)} "
|
| 786 |
+
f"codebook |grad|={cb_g:.4f}")
|
| 787 |
+
x.grad = None
|
| 788 |
+
|
| 789 |
+
# hub causal == hub full restricted? sanity: causal output at position i must
|
| 790 |
+
# not depend on tokens > i. Perturb a late token; early outputs must not move.
|
| 791 |
+
cfg = AlephAttentionConfig(dim=64, num_heads=4, mode="hub", K=16, D_addr=4,
|
| 792 |
+
causal=True, chunk_size=16)
|
| 793 |
+
m = AlephRoutedAttention(cfg).eval()
|
| 794 |
+
x = torch.randn(1, 40, 64)
|
| 795 |
+
y1 = m(x)
|
| 796 |
+
x2 = x.clone(); x2[0, 35] += 10.0
|
| 797 |
+
y2 = m(x2)
|
| 798 |
+
assert torch.allclose(y1[0, :35], y2[0, :35], atol=1e-5), "causality leak!"
|
| 799 |
+
print(" β hub causal: no future leakage (perturbation test)")
|
| 800 |
+
|
| 801 |
+
# stats sanity
|
| 802 |
+
st = m.address_stats(x)
|
| 803 |
+
assert 1.0 <= st["perplexity"] <= st["max_perplexity"] + 1e-3
|
| 804 |
+
print(f" β stats: {st}")
|
| 805 |
+
|
| 806 |
+
# diversity hook
|
| 807 |
+
m2 = AlephRoutedAttention(AlephAttentionConfig(dim=64, num_heads=4, K=16))
|
| 808 |
+
m2.train(); m2.emit_diversity = True
|
| 809 |
+
_ = m2(torch.randn(2, 20, 64))
|
| 810 |
+
d = m2.diversity_loss()
|
| 811 |
+
assert d.requires_grad and torch.isfinite(d)
|
| 812 |
+
print(f" β diversity_loss = {d.item():.4f} (grad-carrying)")
|
| 813 |
+
|
| 814 |
+
# fibonacci init at D=4 is unit + deterministic
|
| 815 |
+
A = _init_codebook(32, 4, "fibonacci")
|
| 816 |
+
assert torch.allclose(A.norm(dim=-1), torch.ones(32), atol=1e-5)
|
| 817 |
+
print(" β super-Fibonacci codebook init (D=4) unit rows")
|
| 818 |
+
print("All smoke tests passed.\n")
|
| 819 |
+
|
| 820 |
+
|
| 821 |
+
#if __name__ == "__main__":
|
| 822 |
+
# import argparse
|
| 823 |
+
# ap = argparse.ArgumentParser(description="Aleph-routed attention β smoke + A/B harness")
|
| 824 |
+
# ap.add_argument("--steps", type=int, default=300)
|
| 825 |
+
# ap.add_argument("--device", default="cuda" if torch.cuda.is_available() else "cpu")
|
| 826 |
+
# ap.add_argument("--modes", nargs="+", default=["hub", "bucket", "standard"])
|
| 827 |
+
# ap.add_argument("--div-weight", type=float, default=0.0)
|
| 828 |
+
# ap.add_argument("--K", type=int, default=32)
|
| 829 |
+
# ap.add_argument("--tau", type=float, default=0.1)
|
| 830 |
+
# ap.add_argument("--smoke-only", action="store_true")
|
| 831 |
+
# # parse_known_args: ignore foreign argv (e.g. Jupyter/Colab injects
|
| 832 |
+
# # `-f /.../kernel-*.json`), so the module runs in notebooks unchanged
|
| 833 |
+
# args, _unknown = ap.parse_known_args()
|
| 834 |
+
#
|
| 835 |
+
# _smoke()
|
| 836 |
+
# if not args.smoke_only:
|
| 837 |
+
# results = {}
|
| 838 |
+
# for mode in args.modes:
|
| 839 |
+
# results[mode] = run_harness(mode, steps=args.steps, device=args.device,
|
| 840 |
+
# K=args.K, div_weight=args.div_weight)
|
| 841 |
+
# print("\n" + "=" * 70)
|
| 842 |
+
# print("A/B summary (associative recall)")
|
| 843 |
+
# for mode, r in results.items():
|
| 844 |
+
# extra = (f" ppl {r.get('perplexity', float('nan')):.1f}"
|
| 845 |
+
# f" margin {r.get('margin', float('nan')):.3f}"
|
| 846 |
+
# if "perplexity" in r else "")
|
| 847 |
+
# print(f" {mode:9s} loss {r['loss']:.4f} acc {r['acc']:.3f}{extra}")
|
| 848 |
+
# print("=" * 70)
|
| 849 |
+
# print("\nBasin test entry point: model.export_codebook() -> feed to the")
|
| 850 |
+
# print("geolip-svae antipodal-collapse extraction. Preregistered criterion:")
|
| 851 |
+
# print("|deviation| < 0.05 on RP^(D-1) = cross-objective attractor evidence.")
|