File size: 10,258 Bytes
e2c9d42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
Box-embedding encoding layer and geometric losses.

A box B = [c_min, c_max] is an axis-aligned hyperrectangle in R^d. The encoder
maps an LLM hidden state at each position to a box, forming a per-prefix lattice
element. We parameterise boxes by their min corner and a strictly positive width
(via softplus) so that c_max = c_min + width > c_min always holds.

This module provides:
  * BoxEncoder              : hidden state -> (c_min, c_max)
  * BoxBatch                : container with volume / log-volume helpers
  * geometric_signals       : leakage residual r_t, contraction rate kappa_t
  * containment_loss        : hinge loss enforcing B_{t+1} subset B_t
  * volume_loss             : prevents collapse / unbounded growth
  * anchored_contraction_loss : penalises contraction faster than info gain
  * concretization_residual : Channel 6, prediction-vs-box in vocabulary space

Tensor conventions: boxes are (B, T, d); B = batch, T = time (tokens).
"""

from __future__ import annotations
from dataclasses import dataclass
import torch
from torch import nn, Tensor
import torch.nn.functional as F

EPS = 1e-6


@dataclass
class BoxBatch:
    """A batch of per-step boxes. Shapes: (B, T, d)."""
    c_min: Tensor
    c_max: Tensor

    @property
    def width(self) -> Tensor:
        return (self.c_max - self.c_min).clamp_min(EPS)

    @property
    def log_volume(self) -> Tensor:
        """log V_t = sum_j log(width_j).  Shape (B, T)."""
        return torch.log(self.width).sum(dim=-1)

    @property
    def volume(self) -> Tensor:
        """V_t in (0, inf). Clamped for stability. Shape (B, T)."""
        return torch.exp(self.log_volume.clamp(-60.0, 20.0))

    @property
    def modulation_scale(self) -> Tensor:
        """Dimension-normalized box scale used by the dynamics layer.

        A raw d-dimensional product volume shrinks exponentially with ``d`` and
        drove the RSNN time constant into its lower clamp for every token. The
        geometric mean side length preserves the box-size ordering while staying
        on a dimension-independent scale.
        """
        return torch.exp(self.log_volume / self.width.shape[-1])


class BoxEncoder(nn.Module):
    """Map a hidden state to box corners.

    Output corners are squashed into the unit hypercube [0, 1]^d so that the
    learned lattice is nested inside the root box B_0 = [0, 1]^d.
    """

    def __init__(
        self,
        d_model: int,
        d_box: int = 256,
        hidden: int = 512,
        architecture: str = "mlp",
    ):
        super().__init__()
        self.d_box = d_box
        self.architecture = architecture
        if architecture == "mlp":
            self.net = nn.Sequential(
                nn.Linear(d_model, hidden),
                nn.GELU(),
                nn.Linear(hidden, 2 * d_box),
            )
            output_layer = self.net[-1]
        elif architecture == "linear":
            self.net = nn.Linear(d_model, 2 * d_box)
            output_layer = self.net
        else:
            raise ValueError("BoxEncoder architecture must be 'mlp' or 'linear'")
        # Initialise so the first boxes are a slight contraction of the root box.
        with torch.no_grad():
            output_layer.bias[:d_box].fill_(0.05)      # c_min near 0
            output_layer.bias[d_box:].fill_(1.5)       # pre-softplus width -> large

    def forward(self, h: Tensor) -> BoxBatch:
        out = self.net(h)
        base, width_raw = out[..., : self.d_box], out[..., self.d_box:]
        c_min = torch.sigmoid(base)                    # in (0, 1)
        width = F.softplus(width_raw)                  # > 0
        c_max = torch.clamp(c_min + width, max=1.0)
        c_max = torch.maximum(c_max, c_min + EPS)      # guarantee positive width
        return BoxBatch(c_min=c_min, c_max=c_max)


# ----------------------------------------------------------------------------
# Geometric signals (Channels 1 & 2)
# ----------------------------------------------------------------------------
def geometric_signals(box: BoxBatch) -> tuple[Tensor, Tensor]:
    """Return (leakage residual r_t, contraction rate kappa_t).

    r_t      : (B, T, 2d) containment violation of B_t inside B_{t-1}.
    kappa_t  : (B, T)     log-volume contraction, >= 0 for a shrinking box.

    Both signals are aligned to the current causal token position: step zero is
    zero-padded because it has no predecessor, and step t uses only B_{t-1} and
    B_t. In particular, r_t must never read B_{t+1}, whose cached hidden state
    can already reflect token t.
    """
    c_min, c_max = box.c_min, box.c_max
    # The adjacent-pair residual belongs to the child/current step, not the
    # parent/previous step.
    child_min, parent_min = c_min[:, 1:], c_min[:, :-1]
    child_max, parent_max = c_max[:, 1:], c_max[:, :-1]

    leak_lo = F.relu(parent_min - child_min)   # child_min should be >= parent_min
    leak_hi = F.relu(child_max - parent_max)   # child_max should be <= parent_max
    r = torch.cat([leak_lo, leak_hi], dim=-1)  # (B, T-1, 2d)
    r = F.pad(r, (0, 0, 1, 0))                 # first step has no predecessor

    logV = box.log_volume                      # (B, T)
    kappa = (logV[:, :-1] - logV[:, 1:])       # (B, T-1), >0 when shrinking
    kappa = F.pad(kappa, (1, 0))               # first step has no predecessor
    return r, kappa


# ----------------------------------------------------------------------------
# Concretization (Channel 6)
# ----------------------------------------------------------------------------
def concretization_residual(
    box: BoxBatch,
    topk_probs: Tensor,
    topk_embeddings: Tensor,
) -> Tensor:
    """Escaped probability mass m_t (Channel 6).

    Implements  m_t = sum_v p_{t+1}(v) * dist(e_v, B_t)  over the top-k tokens of
    the model's next-token distribution, where
        dist(e, B) = || relu(c_min - e) + relu(e - c_max) ||_1.

    Args:
        box             : boxes B_t, corners (B, T, d).
        topk_probs      : (B, T, K) probabilities of the top-K next tokens.
        topk_embeddings : (B, T, K, d) those tokens' embeddings in box space.

    Returns:
        m_t : (B, T) escaped mass weighted by L1 distance outside the box.
    """
    c_min = box.c_min.unsqueeze(2)   # (B, T, 1, d)
    c_max = box.c_max.unsqueeze(2)
    below = F.relu(c_min - topk_embeddings)
    above = F.relu(topk_embeddings - c_max)
    dist = (below + above).sum(dim=-1)            # (B, T, K) L1 distance
    m = (topk_probs * dist).sum(dim=-1)           # (B, T)
    return m


# ----------------------------------------------------------------------------
# Losses
# ----------------------------------------------------------------------------
def containment_loss(box: BoxBatch) -> Tensor:
    """Hinge loss enforcing B_{t+1} subset B_t (paper Eq. 1).

    Containment requires, elementwise, child_min >= parent_min and
    child_max <= parent_max, where child = step t+1, parent = step t. Each
    violation is penalised linearly by its magnitude.
    """
    c_min, c_max = box.c_min, box.c_max
    lo = F.relu(c_min[:, :-1] - c_min[:, 1:])   # parent_min - child_min  (>0 = violation)
    hi = F.relu(c_max[:, 1:] - c_max[:, :-1])   # child_max - parent_max  (>0 = violation)
    return (lo.sum(dim=-1) + hi.sum(dim=-1)).mean()


def box_intersection(a: BoxBatch, b: BoxBatch) -> BoxBatch:
    """Lattice meet (intersection) of two aligned box batches.

    The meet of boxes A and B is the largest box contained in both:
        meet_min = max(A.min, B.min)   (elementwise)
        meet_max = min(A.max, B.max)
    When A and B are disjoint along a dimension, meet_max < meet_min there; the
    returned BoxBatch clamps such widths to EPS (BoxBatch.width), so the meet
    volume is a lower bound. `a` and `b` must share the same (B, T, d) shape.
    """
    meet_min = torch.maximum(a.c_min, b.c_min)
    meet_max = torch.minimum(a.c_max, b.c_max)
    return BoxBatch(c_min=meet_min, c_max=torch.maximum(meet_max, meet_min + EPS))


def join_loss(box: BoxBatch) -> Tensor:
    """Volumetric containment via the meet of consecutive boxes.

    A soft, volume-based complement to `containment_loss` (a corner-wise
    hinge). For each adjacent pair (parent B_t, child B_{t+1}) we form the
    intersection box and penalise the child log-volume that the meet fails to
    capture:

        L_join = mean relu( logV(child) - logV(meet(parent, child)) ).

    Under perfect nesting B_{t+1} subset B_t the meet equals the child and the
    term is zero; when the child leaks outside the parent the meet shrinks along
    the leaking dimensions and the penalty grows. Working in log-volume makes
    the term scale-free across dimensions and numerically stable.
    """
    parent = BoxBatch(c_min=box.c_min[:, :-1], c_max=box.c_max[:, :-1])
    child = BoxBatch(c_min=box.c_min[:, 1:], c_max=box.c_max[:, 1:])
    meet = box_intersection(parent, child)
    leaked = F.relu(child.log_volume - meet.log_volume)   # (B, T-1)
    return leaked.mean()


def volume_loss(box: BoxBatch, lam_collapse: float = 1.0, lam_expand: float = 1.0) -> Tensor:
    """Penalise both collapse (zero volume) and unbounded growth.

    The paper's first term exp(-sum log width) is a barrier against collapse; we
    use the numerically-stable mean form  exp(-mean log width)  and add a linear
    volume penalty. Both are averaged over (B, T).
    """
    log_width = torch.log(box.width)                       # (B, T, d)
    collapse = torch.exp((-log_width.mean(dim=-1)).clamp(max=30.0))  # (B, T)
    expand = box.volume                                    # (B, T)
    return lam_collapse * collapse.mean() + lam_expand * expand.mean()


def anchored_contraction_loss(kappa: Tensor, kl_mean: Tensor, gamma: Tensor) -> Tensor:
    """L_anchor = mean relu(kappa_t - gamma * kl_mean_t).

    Penalises box contraction that is not justified by incoming distributional
    information (the knowledge-gap / confabulation signature).

    Args:
        kappa   : (B, T) contraction rate.
        kl_mean : (B, T) mean layer-wise KL divergence (incoming surprise).
        gamma   : scalar tensor (learned, kept positive by the caller).
    """
    return F.relu(kappa - gamma * kl_mean).mean()