AbstractPhil commited on
Commit
0f7b996
Β·
verified Β·
1 Parent(s): d3f6a06

Create test_cases.py

Browse files
Files changed (1) hide show
  1. test_cases.py +459 -0
test_cases.py ADDED
@@ -0,0 +1,459 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ geolip.flows β€” Multi-flow ensemble for constellation geometry.
3
+
4
+ Each flow predicts the same geometric output using a different mathematical
5
+ formulation. The ensemble fuses predictions based on learned confidence.
6
+
7
+ Flows:
8
+ QuaternionFlow β€” Full MHA quaternion rotation (existing, heavyweight)
9
+ QuaternionLiteFlow β€” Staged quaternion with lighter spectral computation
10
+ VelocityFlow β€” Angular velocity dq/dt on the tangent bundle
11
+ MagnitudeFlow β€” Flow magnitude via Gram eigenvalue spectrum
12
+ OrbitalFlow β€” Omega-based orbital resonance using FL eigh
13
+ AlignmentFlow β€” SVD alignment via Procrustes rotation
14
+
15
+ Architecture:
16
+ Each flow: same input (anchors [B,k,d], queries [B,n,d]) β†’ output [B,n,d]
17
+ Ensemble: weighted fusion with learned per-flow confidence
18
+
19
+ Usage:
20
+ from geolip.flows import FlowEnsemble, OrbitalFlow, AlignmentFlow
21
+
22
+ ensemble = FlowEnsemble(
23
+ flows=[OrbitalFlow(d=256, k=128), AlignmentFlow(d=256, k=128)],
24
+ d_model=256,
25
+ )
26
+ output = ensemble(anchors, queries) # [B, n, d]
27
+ """
28
+
29
+ import math
30
+ import torch
31
+ import torch.nn as nn
32
+ import torch.nn.functional as F
33
+ from torch import Tensor
34
+ from typing import List, Optional, Tuple
35
+
36
+
37
+ # ═══════════════════════════════════════════════════════════════════
38
+ # Base Flow
39
+ # ═══════════════════════════════════════════════════════════════════
40
+
41
+ class BaseFlow(nn.Module):
42
+ """Base class for all geometric flows.
43
+
44
+ All flows share the same interface:
45
+ Input: anchors [B, k, d], queries [B, n, d]
46
+ Output: prediction [B, n, d], confidence [B, n, 1]
47
+
48
+ Subclasses implement _flow() with their specific math.
49
+ """
50
+ def __init__(self, d_model: int, n_anchors: int, name: str = 'base'):
51
+ super().__init__()
52
+ self.d_model = d_model
53
+ self.n_anchors = n_anchors
54
+ self.name = name
55
+ # Confidence head: scalar per query position
56
+ self.confidence = nn.Sequential(
57
+ nn.Linear(d_model, d_model // 4),
58
+ nn.GELU(),
59
+ nn.Linear(d_model // 4, 1),
60
+ )
61
+
62
+ def forward(self, anchors: Tensor, queries: Tensor) -> Tuple[Tensor, Tensor]:
63
+ """
64
+ Args:
65
+ anchors: [B, k, d] constellation anchor points
66
+ queries: [B, n, d] query embeddings
67
+
68
+ Returns:
69
+ prediction: [B, n, d] geometric prediction
70
+ confidence: [B, n, 1] per-query confidence score
71
+ """
72
+ pred = self._flow(anchors, queries)
73
+ conf = torch.sigmoid(self.confidence(pred))
74
+ return pred, conf
75
+
76
+ def _flow(self, anchors: Tensor, queries: Tensor) -> Tensor:
77
+ raise NotImplementedError
78
+
79
+
80
+ # ═══════════════════════════════════════════════════════════════════
81
+ # QuaternionFlow β€” Full MHA quaternion rotation
82
+ # ═══════════════════════════════════════════════════════════════════
83
+
84
+ class QuaternionFlow(BaseFlow):
85
+ """Full multi-head attention with quaternion geometric rotation.
86
+
87
+ Computes query-anchor attention, extracts rotation quaternion from
88
+ attention-weighted anchor geometry, applies rotation to queries.
89
+ Heavyweight β€” the full-fidelity path.
90
+ """
91
+ def __init__(self, d_model: int, n_anchors: int, n_heads: int = 4):
92
+ super().__init__(d_model, n_anchors, name='quaternion')
93
+ self.n_heads = n_heads
94
+ self.head_dim = d_model // n_heads
95
+ self.q_proj = nn.Linear(d_model, d_model)
96
+ self.k_proj = nn.Linear(d_model, d_model)
97
+ self.v_proj = nn.Linear(d_model, d_model)
98
+ self.out_proj = nn.Linear(d_model, d_model)
99
+ # Quaternion components: scalar + 3 imaginary from attention output
100
+ self.quat_proj = nn.Linear(d_model, 4)
101
+
102
+ def _flow(self, anchors, queries):
103
+ B, n, d = queries.shape
104
+ k = anchors.shape[1]
105
+ h = self.n_heads; hd = self.head_dim
106
+
107
+ Q = self.q_proj(queries).view(B, n, h, hd).transpose(1, 2)
108
+ K = self.k_proj(anchors).view(B, k, h, hd).transpose(1, 2)
109
+ V = self.v_proj(anchors).view(B, k, h, hd).transpose(1, 2)
110
+
111
+ attn = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(hd)
112
+ attn = F.softmax(attn, dim=-1)
113
+ ctx = torch.matmul(attn, V).transpose(1, 2).reshape(B, n, d)
114
+
115
+ # Extract quaternion and apply rotation
116
+ q = self.quat_proj(ctx) # [B, n, 4]
117
+ q = F.normalize(q, dim=-1)
118
+ rotated = self._quat_rotate(queries, q)
119
+ return self.out_proj(ctx + rotated)
120
+
121
+ def _quat_rotate(self, v, q):
122
+ """Apply quaternion rotation to vectors. q: [B,n,4], v: [B,n,d]."""
123
+ # For d > 3: rotate first 3 dims, pass rest through
124
+ w, x, y, z = q[..., 0:1], q[..., 1:2], q[..., 2:3], q[..., 3:4]
125
+ v3 = v[..., :3]
126
+ # q * v * q^-1 via Rodriguez
127
+ t = 2.0 * torch.cross(torch.cat([x, y, z], dim=-1), v3, dim=-1)
128
+ v3_rot = v3 + w * t + torch.cross(torch.cat([x, y, z], dim=-1), t, dim=-1)
129
+ if v.shape[-1] > 3:
130
+ return torch.cat([v3_rot, v[..., 3:]], dim=-1)
131
+ return v3_rot
132
+
133
+
134
+ # ═══════════════════════════════════════════════════════════════════
135
+ # QuaternionLiteFlow β€” Staged lighter quaternion
136
+ # ═══════════════════════════════════════════════════════════════════
137
+
138
+ class QuaternionLiteFlow(BaseFlow):
139
+ """Lightweight quaternion prediction without full MHA.
140
+
141
+ Uses anchor centroid + query projection to predict rotation directly.
142
+ Much lighter than full QuaternionFlow β€” trades attention resolution
143
+ for speed.
144
+ """
145
+ def __init__(self, d_model: int, n_anchors: int):
146
+ super().__init__(d_model, n_anchors, name='quat_lite')
147
+ self.anchor_compress = nn.Linear(d_model, d_model)
148
+ self.query_proj = nn.Linear(d_model, d_model)
149
+ self.quat_head = nn.Sequential(
150
+ nn.Linear(d_model * 2, d_model),
151
+ nn.GELU(),
152
+ nn.Linear(d_model, 4),
153
+ )
154
+ self.out_proj = nn.Linear(d_model, d_model)
155
+
156
+ def _flow(self, anchors, queries):
157
+ B, n, d = queries.shape
158
+ # Compress anchors to single geometric summary
159
+ anchor_ctx = self.anchor_compress(anchors.mean(dim=1, keepdim=True)) # [B, 1, d]
160
+ anchor_ctx = anchor_ctx.expand(B, n, d)
161
+
162
+ q_proj = self.query_proj(queries)
163
+ combined = torch.cat([q_proj, anchor_ctx], dim=-1) # [B, n, 2d]
164
+
165
+ q = F.normalize(self.quat_head(combined), dim=-1)
166
+ rotated = self._quat_rotate_simple(queries, q)
167
+ return self.out_proj(rotated)
168
+
169
+ def _quat_rotate_simple(self, v, q):
170
+ w, xyz = q[..., 0:1], q[..., 1:4]
171
+ v3 = v[..., :3]
172
+ t = 2.0 * torch.cross(xyz, v3, dim=-1)
173
+ v3_rot = v3 + w * t + torch.cross(xyz, t, dim=-1)
174
+ if v.shape[-1] > 3:
175
+ return torch.cat([v3_rot, v[..., 3:]], dim=-1)
176
+ return v3_rot
177
+
178
+
179
+ # ═══════════════════════════════════════════════════════════════════
180
+ # VelocityFlow β€” Angular velocity on tangent bundle
181
+ # ═══════════════════════════════════════════════════════════════════
182
+
183
+ class VelocityFlow(BaseFlow):
184
+ """Angular velocity flow on the tangent space of the constellation.
185
+
186
+ Models dq/dt: the rate of change of the query embedding induced by
187
+ the anchor geometry. Predicts velocity, integrates with Euler step.
188
+
189
+ The velocity is tangent to the hypersphere at each query point.
190
+ """
191
+ def __init__(self, d_model: int, n_anchors: int):
192
+ super().__init__(d_model, n_anchors, name='velocity')
193
+ # Anchor-query interaction β†’ velocity field
194
+ self.anchor_proj = nn.Linear(d_model, d_model)
195
+ self.query_proj = nn.Linear(d_model, d_model)
196
+ self.vel_head = nn.Sequential(
197
+ nn.Linear(d_model, d_model),
198
+ nn.GELU(),
199
+ nn.Linear(d_model, d_model),
200
+ )
201
+ self.dt = nn.Parameter(torch.tensor(0.1)) # learnable step size
202
+
203
+ def _flow(self, anchors, queries):
204
+ B, n, d = queries.shape
205
+ # Compute direction from queries toward anchor centroid
206
+ a_proj = self.anchor_proj(anchors) # [B, k, d]
207
+ q_proj = self.query_proj(queries) # [B, n, d]
208
+
209
+ # Soft attention: query-anchor similarity β†’ weighted anchor direction
210
+ sim = torch.bmm(q_proj, a_proj.transpose(-2, -1)) # [B, n, k]
211
+ weights = F.softmax(sim / math.sqrt(d), dim=-1)
212
+ direction = torch.bmm(weights, a_proj) # [B, n, d]
213
+
214
+ # Velocity: project onto tangent space at query
215
+ velocity = self.vel_head(direction - q_proj)
216
+
217
+ # Tangent projection: remove component along query direction
218
+ q_norm = F.normalize(queries, dim=-1)
219
+ radial = (velocity * q_norm).sum(dim=-1, keepdim=True) * q_norm
220
+ tangent_vel = velocity - radial
221
+
222
+ # Euler integration
223
+ return queries + self.dt * tangent_vel
224
+
225
+
226
+ # ═══════════════════════════════════════════════════════════════════
227
+ # MagnitudeFlow β€” Gram eigenvalue spectrum
228
+ # ═══════════════════════════════════════════════════════════════════
229
+
230
+ class MagnitudeFlow(BaseFlow):
231
+ """Flow based on the Gram matrix eigenvalue magnitude spectrum.
232
+
233
+ Computes the anchor Gram matrix, extracts eigenvalues via FL eigh,
234
+ uses the spectral profile to modulate query embeddings.
235
+
236
+ The eigenvalue magnitudes encode the constellation's energy distribution
237
+ across geometric modes.
238
+ """
239
+ def __init__(self, d_model: int, n_anchors: int):
240
+ super().__init__(d_model, n_anchors, name='magnitude')
241
+ # Project anchors to small geometric space for Gram computation
242
+ self.geom_dim = min(n_anchors, 12) # FL eigh sweet spot
243
+ self.anchor_proj = nn.Linear(d_model, self.geom_dim)
244
+ # Spectral β†’ modulation
245
+ self.spec_proj = nn.Sequential(
246
+ nn.Linear(self.geom_dim, d_model),
247
+ nn.GELU(),
248
+ nn.Linear(d_model, d_model),
249
+ )
250
+ self.query_proj = nn.Linear(d_model, d_model)
251
+ self.gate = nn.Linear(d_model * 2, d_model)
252
+
253
+ def _flow(self, anchors, queries):
254
+ B, n, d = queries.shape
255
+ # Project anchors to geometric space
256
+ a_geom = self.anchor_proj(anchors) # [B, k, geom_dim]
257
+
258
+ # Gram matrix eigenvalues β†’ spectral profile
259
+ G = torch.bmm(a_geom.transpose(-2, -1), a_geom) # [B, geom_dim, geom_dim]
260
+ # Use torch.linalg.eigh for now; swap to FL eigh in geolip.linalg
261
+ eigenvalues, _ = torch.linalg.eigh(G) # [B, geom_dim]
262
+
263
+ # Magnitude spectrum: how energy distributes across modes
264
+ magnitudes = eigenvalues.abs().sqrt() # [B, geom_dim] β€” the Ο‰ spectrum
265
+ spec_embed = self.spec_proj(magnitudes) # [B, d]
266
+ spec_embed = spec_embed.unsqueeze(1).expand(B, n, d)
267
+
268
+ # Gate: blend spectral modulation with query
269
+ q_proj = self.query_proj(queries)
270
+ gate_input = torch.cat([q_proj, spec_embed], dim=-1)
271
+ g = torch.sigmoid(self.gate(gate_input))
272
+ return queries + g * spec_embed
273
+
274
+
275
+ # ═══════════════════════════════════════════════════════════════════
276
+ # OrbitalFlow β€” Omega angular resonance via FL eigh
277
+ # ═══════════════════════════════════════════════════════════════════
278
+
279
+ class OrbitalFlow(BaseFlow):
280
+ """Omega-based orbital resonance flow.
281
+
282
+ Computes the constellation's resonance frequencies (Ο‰α΅’ = √λᡒ from
283
+ Gram eigendecomposition), then uses the full eigendecomposition to
284
+ project queries into the resonance basis, apply frequency-dependent
285
+ modulation, and project back.
286
+
287
+ This flow directly uses the Ο‰ spectrum to shape the geometric response.
288
+ Modes in the CV band [0.447, 0.480] (corresponding to λ ∈ [0.20, 0.23])
289
+ are amplified. Modes outside are attenuated.
290
+ """
291
+ def __init__(self, d_model: int, n_anchors: int, cv_lo: float = 0.20, cv_hi: float = 0.23):
292
+ super().__init__(d_model, n_anchors, name='orbital')
293
+ self.geom_dim = min(n_anchors, 12)
294
+ self.anchor_proj = nn.Linear(d_model, self.geom_dim)
295
+ self.cv_lo = cv_lo
296
+ self.cv_hi = cv_hi
297
+ # Per-mode learnable response curve
298
+ self.mode_response = nn.Parameter(torch.ones(self.geom_dim))
299
+ # Projection back to d_model
300
+ self.query_to_geom = nn.Linear(d_model, self.geom_dim)
301
+ self.geom_to_query = nn.Linear(self.geom_dim, d_model)
302
+ self.out_proj = nn.Linear(d_model, d_model)
303
+
304
+ def _flow(self, anchors, queries):
305
+ B, n, d = queries.shape
306
+ a_geom = self.anchor_proj(anchors) # [B, k, geom_dim]
307
+ G = torch.bmm(a_geom.transpose(-2, -1), a_geom) # [B, gd, gd]
308
+
309
+ # Eigendecomposition β€” the Ο‰ spectrum
310
+ eigenvalues, eigenvectors = torch.linalg.eigh(G) # [B, gd], [B, gd, gd]
311
+
312
+ # Ο‰ = √|Ξ»|
313
+ omega = eigenvalues.abs().sqrt() # [B, gd]
314
+
315
+ # CV band resonance: modes near the attractor basin get amplified
316
+ in_band = ((eigenvalues >= self.cv_lo) & (eigenvalues <= self.cv_hi)).float()
317
+ near_binding = torch.exp(-10.0 * (eigenvalues - 0.29154).pow(2))
318
+
319
+ # Mode weighting: learned response Γ— geometric structure
320
+ mode_weight = self.mode_response.unsqueeze(0) * (1.0 + in_band + near_binding)
321
+
322
+ # Project queries into resonance basis
323
+ q_geom = self.query_to_geom(queries) # [B, n, gd]
324
+ # Rotate into eigenbasis: q_eigen = q_geom @ V
325
+ q_eigen = torch.bmm(q_geom, eigenvectors) # [B, n, gd]
326
+
327
+ # Apply frequency-dependent modulation
328
+ q_modulated = q_eigen * mode_weight.unsqueeze(1) # [B, n, gd]
329
+
330
+ # Rotate back: q_out = q_modulated @ V^T
331
+ q_out = torch.bmm(q_modulated, eigenvectors.transpose(-2, -1))
332
+
333
+ # Project back to d_model
334
+ return self.out_proj(self.geom_to_query(q_out) + queries)
335
+
336
+
337
+ # ═══════════════════════════════════════════════════════════════════
338
+ # AlignmentFlow β€” SVD-based Procrustes alignment
339
+ # ═══════════════════════════════════════════════════════════════════
340
+
341
+ class AlignmentFlow(BaseFlow):
342
+ """SVD alignment flow via soft Procrustes rotation.
343
+
344
+ Computes the optimal rotation aligning queries toward the anchor
345
+ geometry using SVD of the cross-covariance matrix. The rotation
346
+ is applied as a soft geometric bias.
347
+ """
348
+ def __init__(self, d_model: int, n_anchors: int):
349
+ super().__init__(d_model, n_anchors, name='alignment')
350
+ self.anchor_proj = nn.Linear(d_model, d_model)
351
+ self.query_proj = nn.Linear(d_model, d_model)
352
+ self.strength = nn.Parameter(torch.tensor(0.1)) # learnable blend
353
+
354
+ def _flow(self, anchors, queries):
355
+ B, n, d = queries.shape
356
+ a_proj = self.anchor_proj(anchors) # [B, k, d]
357
+ q_proj = self.query_proj(queries) # [B, n, d]
358
+
359
+ # Cross-covariance: C = Q^T A
360
+ C = torch.bmm(q_proj.transpose(-2, -1), a_proj) # [B, d, d]
361
+
362
+ # SVD β†’ optimal rotation (Procrustes)
363
+ U, _, Vh = torch.linalg.svd(C)
364
+ R = torch.bmm(U, Vh) # [B, d, d] rotation matrix
365
+
366
+ # Apply soft rotation
367
+ q_rotated = torch.bmm(queries, R)
368
+ return queries + self.strength * (q_rotated - queries)
369
+
370
+
371
+ # ═══════════════════════════════════════════════════════════════════
372
+ # Flow Ensemble
373
+ # ═══════════════════════════════════════════════════════════════════
374
+
375
+ class FlowEnsemble(nn.Module):
376
+ """Ensemble fusion of multiple geometric flows.
377
+
378
+ Each flow produces a prediction and a confidence score.
379
+ The ensemble fuses predictions weighted by confidence.
380
+
381
+ The fusion can be:
382
+ 'weighted': confidence-weighted average
383
+ 'gated': learned gate over concatenated predictions
384
+ 'residual': sum of confidence-weighted residuals from input
385
+ """
386
+ def __init__(self, flows: List[BaseFlow], d_model: int, fusion: str = 'weighted'):
387
+ super().__init__()
388
+ self.flows = nn.ModuleList(flows)
389
+ self.d_model = d_model
390
+ self.fusion = fusion
391
+ self.n_flows = len(flows)
392
+
393
+ if fusion == 'gated':
394
+ self.gate = nn.Sequential(
395
+ nn.Linear(d_model * self.n_flows, d_model),
396
+ nn.GELU(),
397
+ nn.Linear(d_model, d_model),
398
+ )
399
+
400
+ # Per-flow learnable temperature
401
+ self.temperature = nn.Parameter(torch.ones(self.n_flows))
402
+
403
+ def forward(self, anchors: Tensor, queries: Tensor) -> Tensor:
404
+ """
405
+ Args:
406
+ anchors: [B, k, d] constellation anchors
407
+ queries: [B, n, d] query embeddings
408
+
409
+ Returns:
410
+ fused: [B, n, d] ensemble prediction
411
+ """
412
+ predictions = []
413
+ confidences = []
414
+
415
+ for i, flow in enumerate(self.flows):
416
+ pred, conf = flow(anchors, queries)
417
+ predictions.append(pred)
418
+ confidences.append(conf * self.temperature[i])
419
+
420
+ if self.fusion == 'weighted':
421
+ return self._weighted_fusion(predictions, confidences)
422
+ elif self.fusion == 'gated':
423
+ return self._gated_fusion(predictions, confidences)
424
+ elif self.fusion == 'residual':
425
+ return self._residual_fusion(predictions, confidences, queries)
426
+ else:
427
+ raise ValueError(f"Unknown fusion: {self.fusion}")
428
+
429
+ def _weighted_fusion(self, preds, confs):
430
+ # Stack confidences and normalize
431
+ conf_stack = torch.cat(confs, dim=-1) # [B, n, n_flows]
432
+ weights = F.softmax(conf_stack, dim=-1) # [B, n, n_flows]
433
+ pred_stack = torch.stack(preds, dim=-1) # [B, n, d, n_flows]
434
+ return (pred_stack * weights.unsqueeze(-2)).sum(dim=-1)
435
+
436
+ def _gated_fusion(self, preds, confs):
437
+ cat = torch.cat(preds, dim=-1) # [B, n, d * n_flows]
438
+ return self.gate(cat)
439
+
440
+ def _residual_fusion(self, preds, confs, queries):
441
+ conf_stack = torch.cat(confs, dim=-1)
442
+ weights = F.softmax(conf_stack, dim=-1)
443
+ residuals = torch.stack([p - queries for p in preds], dim=-1)
444
+ fused_residual = (residuals * weights.unsqueeze(-2)).sum(dim=-1)
445
+ return queries + fused_residual
446
+
447
+ def flow_diagnostics(self, anchors: Tensor, queries: Tensor) -> dict:
448
+ """Run all flows and return per-flow diagnostics."""
449
+ diag = {}
450
+ for i, flow in enumerate(self.flows):
451
+ pred, conf = flow(anchors, queries)
452
+ diag[flow.name] = {
453
+ 'pred_norm': pred.norm(dim=-1).mean().item(),
454
+ 'confidence_mean': conf.mean().item(),
455
+ 'confidence_std': conf.std().item(),
456
+ 'residual_norm': (pred - queries).norm(dim=-1).mean().item(),
457
+ 'temperature': self.temperature[i].item(),
458
+ }
459
+ return diag