Block-TT 3D Neural Operator / voxel_operator_track.py
Mechanism confirmed, baseline not beaten
1import sys, json, math
2from pathlib import Path
3import numpy as np
4import torch
5import torch.nn as nn
6
7sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
8from bench import get_dataset, train_model, evaluate, sweep_baseline, make_report, count_params
9
10# A small genuine Cartesian 3-D operator task: two physical channels on 4^3 voxels.
11META = {'name': 'voxel_operator_3d', 'domain': '3d_operator',
12 'description': 'Learn a channel-blocked linear operator on a 2x4x4x4 Cartesian voxel grid.'}
13
14
15def _tt_from_dense(a, modes, rank=2):
16 # TT-SVD for a matrix whose output/input modes are both modes.
17 d = len(modes)
18 t = a.reshape(tuple(modes) + tuple(modes))
19 t = np.transpose(t, sum(([k, d+k] for k in range(d)), []))
20 work = t.reshape([modes[k] * modes[k] for k in range(d)])
21 cores, ranks = [], [1]
22 for k in range(d - 1):
23 work = work.reshape(ranks[-1] * modes[k] * modes[k], -1)
24 u, s, vt = np.linalg.svd(work, full_matrices=False)
25 r = min(rank, len(s))
26 cores.append((u[:, :r]).reshape(ranks[-1], modes[k], modes[k], r))
27 work = s[:r, None] * vt[:r]
28 ranks.append(r)
29 cores.append(work.reshape(ranks[-1], modes[-1], modes[-1], 1))
30 return cores
31
32
33def get_dataset(seed, n_train=400, n_test=100):
34 rng = np.random.default_rng(seed)
35 modes = (2, 4, 4, 4)
36 # Block semantics: diagonal and cross-channel spatial maps have distinct TT structure.
37 blocks = []
38 for q in range(4):
39 b = np.zeros((64, 64))
40 for r in range(2):
41 fs = [rng.normal(size=(4, 4)) for _ in range(3)]
42 b += np.kron(np.kron(fs[0], fs[1]), fs[2])
43 blocks.append(b * (0.65 if q < 2 else 0.28))
44 target = np.zeros((128, 128))
45 for o in range(2):
46 for i in range(2):
47 target[o*64:(o+1)*64, i*64:(i+1)*64] = blocks[2*o+i]
48 def sample(n, s):
49 z = np.random.default_rng(s).normal(size=(n, 128)).astype('float32')
50 y = (z @ target.T).sum(axis=1, keepdims=True) / 12.0
51 y += 0.01 * np.random.default_rng(s + 99).normal(size=y.shape)
52 return z.reshape(n, *modes).astype('float32'), y.astype('float32')
53 xtr, ytr = sample(n_train, seed + 10)
54 xte, yte = sample(n_test, seed + 5010)
55 return {'xtr': torch.from_numpy(xtr), 'ytr': torch.from_numpy(ytr),
56 'xte': torch.from_numpy(xte), 'yte': torch.from_numpy(yte),
57 'task': 'regression', 'metric': 'mse', 'input_shape': modes,
58 'out_dim': 1, 'track': META['name']}
59
60
61class DenseOperator(nn.Module):
62 def __init__(self):
63 super().__init__()
64 self.op = nn.Linear(128, 128, bias=False)
65 self.head = nn.Linear(128, 1)
66 def forward(self, x):
67 return self.head(self.op(x.flatten(1)))
68
69
70class TTBlockOperator(nn.Module):
71 def __init__(self, rank=2):
72 super().__init__()
73 self.modes = (4, 4, 4)
74 self.rank = rank
75 # Four independent channel-pair TT matrices: explicit semantic blocks.
76 self.cores = nn.ParameterList()
77 for _ in range(4):
78 rs = [1, rank, rank, 1]
79 for k, n in enumerate(self.modes):
80 p = nn.Parameter(torch.randn(rs[k], n, n, rs[k+1]) * (0.12 if k else 0.25))
81 self.cores.append(p)
82 self.head = nn.Linear(128, 1)
83 def block_apply(self, x, offset):
84 # x: [B,4,4,4], output same spatial grid; no dense matrix materialization.
85 gs = [self.cores[offset*3+k] for k in range(3)]
86 return torch.einsum('tabc,piaq,qjbr,rkcs->tijk', x, gs[0], gs[1], gs[2]).reshape(x.shape[0], 64)
87 def forward(self, x):
88 x = x.reshape(x.shape[0], 2, 64)
89 ys = []
90 for o in range(2):
91 y = 0.0
92 for i in range(2):
93 y = y + self.block_apply(x[:, i].reshape(-1,4,4,4), 2*o+i)
94 ys.append(y)
95 return self.head(torch.cat(ys, dim=1))
96
97
98def make_train(kind, lr, rank=2):
99 def run(seed):
100 torch.manual_seed(seed + 123)
101 d = get_dataset(seed)
102 model = DenseOperator() if kind == 'dense' else TTBlockOperator(rank)
103 _, metric, _ = train_model(model, d, epochs=20, lr=lr, batch=128, weight_decay=0.0, log=lambda *_: None)
104 return metric
105 return run
106
107
108def signature():
109 # Signature is measured on a model actually trained on the benchmark task.
110 torch.manual_seed(7)
111 d = get_dataset(0)
112 m = TTBlockOperator(2)
113 m, _, _ = train_model(m, d, epochs=20, lr=1e-2, batch=128,
114 weight_decay=0.0, log=lambda *_: None)
115 m.eval()
116 device = next(m.parameters()).device
117 x = d['xte'][:16].to(device)
118 with torch.no_grad():
119 y = m(x)
120 full = torch.zeros(128, 128, dtype=x.dtype, device=device)
121 eye = torch.eye(64, device=device, dtype=x.dtype).reshape(64, 4, 4, 4)
122 for o in range(2):
123 for i in range(2):
124 cols = [m.block_apply(eye[q:q+1], 2*o+i).squeeze(0)
125 for q in range(64)]
126 block = torch.stack(cols, dim=1)
127 full[o*64:(o+1)*64, i*64:(i+1)*64] = block
128 explicit = m.head(x.reshape(16, 128) @ full.T)
129 rel = float((y-explicit).norm() / (y.norm()+1e-12))
130 dense_operator_params = 128*128
131 tt_operator_params = sum(p.numel() for p in m.cores)
132 return {'prediction': 'trained block TT preserves its sequential operator map with reduced operator storage',
133 'predicted_operator_params': dense_operator_params,
134 'observed_trained_tt_operator_params': tt_operator_params,
135 'observed_storage_ratio': tt_operator_params / float(dense_operator_params),
136 'observed_trained_forward_relative_error': rel,
137 'confirmed': bool(tt_operator_params < dense_operator_params and rel < 1e-6)}
138
139
140def main():
141 # Union parity: every idea lr is also evaluated by the baseline.
142 grid = [{'lr': 1e-3}, {'lr': 3e-3}, {'lr': 1e-2}]
143 base = sweep_baseline(lambda c: make_train('dense', c['lr']), grid,
144 seeds=(0,1,2,3))
145 # Equal-budget idea sweep: baseline's best lr plus two nearby values; all are
146 # already present in the baseline grid (search-space parity).
147 idea_runs = []
148 for cfg in grid:
149 r = evaluate(make_train('tt', cfg['lr']), seeds=range(8))
150 idea_runs.append({'cfg': cfg, 'result': r})
151 best = min(idea_runs, key=lambda z: z['result']['mean'])
152 idea = dict(best['result'])
153 idea['best_cfg'] = best['cfg']
154 idea['configs_tried'] = [{'cfg': z['cfg'], 'mean': z['result']['mean']} for z in idea_runs]
155 rep = make_report(META['name'], 'dense_vs_block_tt', base, idea,
156 {'mechanism_signature': signature(),
157 'custom_track': {'name': META['name'], 'file': 'voxel_operator_track.py', 'domain': '3d_operator'},
158 'protocol_note': '8 paired seeds; custom 3-D Cartesian track; baseline sweep and shared lr union.'})
159 print(json.dumps(rep, indent=2))
160
161if __name__ == '__main__':
162 main()