Technical specification of the Adaptive Bridge Interface as implemented in Experiment 45AS (cross_arch_t5_nib_v53.py).
The NIB is the immutable evaluation standard. A result is valid only if produced by exactly this protocol.
Evaluate the agreement between an anchor model
For each token position SKIP=5 positions per chunk):
- Compute softmax distributions
$p_A(t)$ and$p_C(t)$ over the 32,128-token T5 vocabulary - Compute the mixture
$m(t) = 0.5 \cdot (p_A(t) + p_C(t))$ - Compute Jensen-Shannon divergence:
$\text{JS}(t) = \frac{1}{2} \text{KL}(p_A | m) + \frac{1}{2} \text{KL}(p_C | m)$ - Compute top-1 agreement:
$\mathbb{1}[\arg\max p_A = \arg\max p_C]$ - Compute top-5 agreement:
$|\text{top}_5(p_A) \cap \text{top}_5(p_C)| / 5$ - Compute entropy difference:
$|\mathcal{H}(p_A) - \mathcal{H}(p_C)|$
Average all metrics across all token positions in all 5 chunks.
| Metric | Threshold | Direction |
|---|---|---|
| mean JS divergence | < 0.10 | lower is better |
| mean top-1 agreement | ≥ 0.68 | higher is better |
| mean top-5 agreement | ≥ 0.86 | higher is better (binding criterion) |
| mean entropy difference | < 0.35 | lower is better |
rng = np.random.default_rng(7777) # FIXED seed, never changed
n_chunks = 5 # 5 × 64-token sequences
SKIP = 5 # skip first 5 decoder positions
ENC_LEN = 64 # encoder context
PRED_LEN = 64 # decoder prediction window
VOCAB = 32128 # T5-large vocabulary sizeThe official n=5 evaluation has high variance (SE ≈ 0.015). For robustness reporting, the extended evaluation runs the same protocol with 5 random seeds (7777, 1111, 2222, 3333, 4444), yielding n=25 observations.
The extended evaluation is not the official protocol — it is a supplementary robustness measurement. The official threshold applies only to the rng=7777 evaluation.
These facts govern all architectural decisions. Do not estimate them.
| Property | Value |
|---|---|
| Architecture | Encoder-Decoder Transformer |
d_model |
1024 |
| Encoder layers | 24 |
| Decoder layers | 24 (numbered 1–24 in hidden_states) |
| Vocabulary | 32,128 tokens |
tie_word_embeddings |
True |
lm_head has bias |
False |
| Total parameters | ~737.7 M |
lm_head scaling |
h_final *= d_model ** -0.5 applied before lm_head |
Critical: hidden_states Indexing
When calling the T5 decoder with output_hidden_states=True:
dec_out = t5.decoder(
input_ids=dec_ids,
encoder_hidden_states=enc_out.last_hidden_state,
output_hidden_states=True,
)
dec_out.hidden_states[k] # output of decoder layer k (k=1..24)
dec_out.last_hidden_state # == dec_out.hidden_states[24]hidden_states[0] is the embedding layer output (pre-attention). hidden_states[24] is the final decoder layer output, identical to last_hidden_state. In the ABI, tap indices [19,20,21,22,23,24] correspond directly to these indices.
T5-large uses tie_word_embeddings=True, which means the embedding matrix is shared with lm_head. When tie_word_embeddings is True, T5 applies a scale factor of lm_head. This is hardcoded in T5's forward(). The ABI correctly replicates this:
if self.t5.config.tie_word_embeddings:
h_final = h_final * (self.model_dim ** -0.5)
return self.t5.lm_head(h_final)Failure to apply this scaling causes a 30–40× perplexity penalty. Every ABI model variant correctly includes this line.
A model
The backbone is shared and frozen. Only the ABI modules (proj_in, abi_ln, proj_out, domain) differ between
Both
This correction is added to the backbone's residual stream (h_24). The resulting modified hidden state is:
The corrMSE objective minimizes the mean squared error between
This is computed in hidden space (dimension 1024), not in logit space (dimension 32,128). This is key: it avoids the pathological gradient geometry of logit-space objectives.
Fine-tuning T5-large modifies the backbone. This:
- Destroys the pretrained knowledge (catastrophic forgetting)
- Is expensive (full backprop through 730 M params)
- Produces a model that cannot hot-swap ABI modules
The ABI preserves the backbone exactly. The correction is surgical: it shifts the hidden state by a learned offset that encodes the domain adaptation, while the backbone's pretrained linguistic knowledge remains intact.
class MultiTap6SV_PerTapLN(nn.Module):
"""
45AS: 6-tap [19..24], per-tap LayerNorm, D_ABI=4096.
Defined in cross_arch_t5_nib_v53.py.
"""
def __init__(self, abi_seed=99, d_abi=4096):
super().__init__()
self.t5 = T5ForConditionalGeneration.from_pretrained("t5-large")
self.model_dim = self.t5.config.d_model # 1024
self.d_abi = d_abi # 4096
# Per-tap LayerNorms: 6 × LN(1024)
self.tap_lns = nn.ModuleList([nn.LayerNorm(1024) for _ in TAP_LAYERS])
self.proj_in = nn.Linear(6144, 4096, bias=False) # 6×1024 → 4096
self.abi_ln = nn.LayerNorm(4096)
self.proj_out = nn.Linear(4096, 1024, bias=False) # 4096 → 1024
self.domain = DomainModuleSV(4096) # 4096→16384→4096+LN
self.domain_alpha = nn.Parameter(torch.ones(1))
torch.manual_seed(abi_seed)
nn.init.xavier_uniform_(self.proj_in.weight)
nn.init.xavier_uniform_(self.proj_out.weight)def encode_core(self, enc_ids, dec_ids):
enc_out = self.t5.encoder(input_ids=enc_ids)
dec_out = self.t5.decoder(
input_ids=dec_ids,
encoder_hidden_states=enc_out.last_hidden_state,
output_hidden_states=True,
)
# Per-tap normalization before concatenation
h_tap = torch.cat(
[self.tap_lns[i](dec_out.hidden_states[layer_idx])
for i, layer_idx in enumerate([19,20,21,22,23,24])],
dim=-1,
) # [B, T, 6144]
h_24 = dec_out.last_hidden_state # residual connection (un-normalized)
h_abi = self.abi_ln(self.proj_in(h_tap))
return h_24, h_abi
def forward(self, enc_ids, dec_ids, use_domain=True):
h_24, h_abi = self.encode_core(enc_ids, dec_ids)
h_out = h_abi + domain_alpha * domain(h_abi) if use_domain else h_abi
h_final = self.proj_out(h_out) + h_24
if self.t5.config.tie_word_embeddings:
h_final = h_final * (self.model_dim ** -0.5)
return self.t5.lm_head(h_final)class DomainModuleSV(nn.Module):
def __init__(self, d): # d=4096
super().__init__()
self.net = nn.Sequential(
nn.Linear(d, d * 4), # 4096 → 16384
nn.GELU(),
nn.Linear(d * 4, d), # 16384 → 4096
)
self.ln = nn.LayerNorm(d)
def forward(self, h):
return self.ln(self.net(h))| Component | Formula | Parameters |
|---|---|---|
tap_lns (6 × LN(1024)) |
6 × 1024 × 2 | 12,288 |
proj_in (no bias) |
6144 × 4096 | 25,165,824 |
abi_ln |
4096 × 2 | 8,192 |
proj_out (no bias) |
4096 × 1024 | 4,194,304 |
domain.net[0] weight |
4096 × 16384 | 67,108,864 |
domain.net[0] bias |
16384 | 16,384 |
domain.net[2] weight |
16384 × 4096 | 67,108,864 |
domain.net[2] bias |
4096 | 4,096 |
domain.ln |
4096 × 2 | 8,192 |
domain_alpha |
scalar | 1 |
| Total ABI | 163,627,009 |
The T5-large backbone (~737.7 M parameters) is entirely frozen during calibration.
T5-large's decoder layers produce hidden states at different magnitudes. Layer 19's hidden state has a different L2 norm than layer 24's. When 6 layers are concatenated raw, the higher-norm layers dominate proj_in's learned projection.
Per-tap LN normalizes each layer's hidden state independently before concatenation. This ensures that proj_in processes a uniformly-scaled input and can learn to use information from all 6 taps equally.
Empirical result: Per-tap LN contributed +0.008 to extended NIB top-5 compared to an otherwise identical architecture without it (45AP vs 45AQ baseline comparison). This was the most impactful single architectural change in the search.
The proj_out layer maps from proj_out produce zero contribution to the output hidden state — they are geometrically invisible to the NIB metric.
With D_ABI=1024 (square), proj_out is
With D_ABI=4096, proj_out is
Empirical result: D=4096 outperformed D=1024 and D=2048 at the same corrMSE floor level.
Model: SVAT5Large (single-tap, layer 24, D_ABI=512, SEED_A=42)
Steps: 2000
LR: 3e-4 (AdamW, weight_decay=0.01)
Objective: Cross-entropy on Python tokens
Batch: B=2, no accumulation
Purpose: Learn the domain-shifted hidden representation that will serve as the calibration target
Model: MultiTap6SV_PerTapLN (45AS architecture, SEED_C=99)
Backbone: shared with anchor (frozen during calibration, but domain LM objective uses its logits here)
Steps: 2000
LR: 3e-4
Objective: Cross-entropy on Python tokens
Purpose: Initialize the 6-tap ABI parameters in the domain before corrMSE calibration begins
Starting from the Stage C checkpoint, calibrate against the anchor's correction vectors.
Phase | Steps | LR | Notes
------+-------+---------+--------------------------------
P1 | 4000 | 5e-3 | warmup 400 steps (0 → 5e-3)
P2 | 3000 | 5e-4 |
P3 | 3000 | 5e-5 |
P4 | 6000 | 5e-6 | extended; best checkpoint ~step 15466
Total | 16000 | |
Best checkpoint tracking: The training loop maintains a rolling 50-step average of corrMSE. The state dict of the best (lowest avg50) checkpoint is saved in memory and restored at the end. The best corrMSE of 0.003047 was found at step 15466 of 16000.
for step in range(16000):
lr = get_lr(step) # 4-phase schedule
for _ in range(ACCUM_STEPS=4): # gradient accumulation
enc_ids, dec_ids, _ = make_batch(py_ids, seed=9000 + global_mini)
with torch.no_grad():
_, corr_A = anchor.forward_with_correction(enc_ids, dec_ids)
_, corr_C = calibrated.forward_with_correction(enc_ids, dec_ids)
loss = F.mse_loss(corr_C.float(), corr_A.float())
(loss / ACCUM_STEPS).backward()
nn.utils.clip_grad_norm_(cal_params, 1.0)
opt.step()Effective batch size: BATCH_SV=2 × ACCUM_STEPS=4 = 8 sequences per optimizer step.
During Stage D, the ABI module parameters are trained; the T5-large backbone is frozen:
# Trainable:
calibrated.proj_in.weight # 6144×4096
calibrated.abi_ln.weight # 4096
calibrated.abi_ln.bias # 4096
calibrated.proj_out.weight # 4096×1024
calibrated.domain_alpha # scalar
calibrated.domain.ln.weight # 4096
calibrated.domain.ln.bias # 4096
calibrated.domain.net[*] # 4096→16384→4096
# Per-tap LN weights (frozen from Stage C initialization)
# NOTE: tap_lns are NOT in the calibration parameter list —
# they are initialized during Stage C and not further updated.
# This is intentional: LN statistics stabilize during domain pretraining.where correction vectors live in
KL divergence operates in logit space (32,128 dimensions). Gradients must flow through:
-
lm_head(weight matrix 1024 × 32,128) - The
$d_{\text{model}}^{-0.5}$ scaling - Then back to
proj_out
The gradient signal is diluted by a factor of ~32,000 / 1024 ≈ 31× in terms of effective dimensionality. Moreover, KL divergence gradients are highly non-uniform: small changes in the top-k logits dominate the loss, creating pathological gradient landscapes for the correction task.
Experiment 45AX (raw KL, LR/10) achieved corrMSE=0.002659 — 12.7% lower than 45AS — but NIB top-5 was 0.8244, 0.030 lower. The KL objective found a solution with lower correction norm that nevertheless placed errors in logit-relevant directions.
Weighting correction MSE by
- The weighted gradient creates interference between easy (already well-corrected) positions and hard positions
- Even at very low learning rate (LR=5e-6 in the P4 phase only), the interference catastrophically degrades rng=7777 (-0.039 top-5)
- The gradient directions for different position difficulties are misaligned in the ABI parameter space
The standard corrMSE objective is the uniquely optimal calibration objective for this task.
Across all 45AS-architecture variants, the corrMSE floor converges to approximately 0.003050 ± 0.0003. This is an information-theoretic limit of the ABI architecture:
- Single-tap baseline (
SVAT5Large): floor ≈ 0.003668 - 3-tap
[20,22,24]D=1024: floor ≈ 0.003339 - 6-tap
[19..24]D=1024: floor ≈ 0.003339 (same — tap count does not lower the floor) - 6-tap
[19..24]D=4096 + per-tap LN: floor ≈ 0.003047 (per-tap LN breaks the floor by ~10%)
The floor is determined by the information content of the domain corpus that can be extracted through the ABI's linear bottleneck. The tap count controls the null-space dimensionality (NIB geometry), not the floor.
The NIB metric (top-5 agreement) measures whether
Let
The correction error is
This is possible if
With D_ABI=4096, proj_out has a 3072-dimensional null space. During optimization, gradient descent can find solutions where the residual correction error
Defined in cross_arch_t5_nib_v48.py (base module):
TAP_LAYERS = [19, 20, 21, 22, 23, 24]
D_MODEL = 1024
D_ABI = 1024 # base module (v48); overridden to 4096 in v53
D_IN_MULTI = 6144 # 6 × 1024
ENC_LEN = 64
PRED_LEN = 64
DOMAIN_STEPS= 2000
LR_ABI = 3e-4 # domain pre-training LR
P1_N, P1_LR = 4000, 5e-3
P2_N, P2_LR = 3000, 5e-4
P3_N, P3_LR = 3000, 5e-5
P4_N, P4_LR = 2000, 5e-6 # overridden to 6000 in v53
LR_WARMUP = 400
CAL_WD = 0.0
ACCUM_STEPS = 4
BATCH_SV = 2
SEED_A = 42
SEED_C = 99
VOCAB_SIZE = 32128
MAX_PY = 500_000
PAD_ID = 0
SKIP = 5Overrides in cross_arch_t5_nib_v53.py (45AS):
D_ABI_NEW = 4096
SEED_C_NEW = 99 # same as base
P4_EXTENDED = 6000 # was 2000 in base