145 lines
5.4 KiB
Python
Executable File
145 lines
5.4 KiB
Python
Executable File
"""E9 — Online relevance acquisition for sparse top-k attention and measured
|
|
selection overhead.
|
|
|
|
Protocol (U=32, 8 clusters of 4, k=4):
|
|
frames 1..3 : full attention; the BS estimates beta_hat from the decoded
|
|
embeddings by an EWMA of pairwise cosines (no oracle knowledge)
|
|
frames >=4 : top-k attention using beta_hat (self + k-1 best peers)
|
|
Reports the per-frame fidelity trajectory against the oracle top-k (true
|
|
clusters) and full attention, plus wall-clock timing of the full pipeline
|
|
including estimation and argpartition selection for U in {8..128}.
|
|
"""
|
|
import time
|
|
|
|
import numpy as np
|
|
import torch
|
|
|
|
import lib
|
|
from lib import (UWCA, DEVICE, block_masks, channel, gen_embeddings,
|
|
mean_cos, save_json, semantic_loss, ser, set_seed)
|
|
|
|
rng = set_seed(42)
|
|
d, H, k = 64, 4, 4
|
|
U = 32
|
|
G = U // k # 8 clusters of 4
|
|
scen = {"beta_u": [0.65] * U, "scenes": [i // k for i in range(U)]}
|
|
masks = block_masks(U, d)
|
|
snrs = [0.0, 10.0, 20.0]
|
|
|
|
|
|
def gen(n):
|
|
return gen_embeddings(n, d, U, rng, scen).to(DEVICE)
|
|
|
|
|
|
def cluster_mask():
|
|
m = torch.zeros(U, U, dtype=torch.bool, device=DEVICE)
|
|
for u in range(U):
|
|
c = u // k
|
|
m[u, c * k:(c + 1) * k] = True
|
|
return m
|
|
|
|
|
|
def topk_from_beta(bhat):
|
|
m = torch.zeros(U, U, dtype=torch.bool, device=DEVICE)
|
|
b = bhat.clone()
|
|
b.fill_diagonal_(2.0) # always keep self
|
|
idx = torch.topk(b, k, dim=1).indices
|
|
m.scatter_(1, idx, True)
|
|
return m
|
|
|
|
|
|
print("[E9] training U=32 model (full attention)", flush=True)
|
|
model = UWCA(d, U, H).to(DEVICE)
|
|
mask_p = [p for nm, p in model.named_parameters() if "mask_logits" in nm]
|
|
other = [p for nm, p in model.named_parameters() if "mask_logits" not in nm]
|
|
opt = torch.optim.Adam([{"params": other, "lr": 1e-3},
|
|
{"params": mask_p, "lr": 0.1}])
|
|
for ep in range(1, 181):
|
|
opt.zero_grad()
|
|
loss = 0.0
|
|
for s in snrs:
|
|
E = gen(64)
|
|
ch = channel(E, snr_db=s)
|
|
Eh = model(ch["yI"], ch["yQ"])
|
|
loss = loss + semantic_loss(Eh, E, 0.1)
|
|
(loss / len(snrs)).backward()
|
|
torch.nn.utils.clip_grad_norm_(model.parameters(), 5.0)
|
|
opt.step()
|
|
if ep % 45 == 0:
|
|
print(f" [E9] ep {ep}/180 loss={float(loss)/len(snrs):.4f}", flush=True)
|
|
|
|
# ---- online protocol trajectory at 10 dB
|
|
T, warm, gamma_ewma = 16, 3, 0.5
|
|
oracle = cluster_mask()
|
|
traj = {"frame": list(range(1, T + 1)), "online_cos": [], "oracle_cos": [],
|
|
"full_cos": [], "beta_err": []}
|
|
bhat = torch.zeros(U, U, device=DEVICE)
|
|
model.eval()
|
|
Btrue = torch.zeros(U, U, device=DEVICE)
|
|
for u in range(U):
|
|
for v in range(U):
|
|
if u != v and scen["scenes"][u] == scen["scenes"][v]:
|
|
Btrue[u, v] = scen["beta_u"][u] * scen["beta_u"][v]
|
|
|
|
with torch.no_grad():
|
|
for t in range(1, T + 1):
|
|
E = gen(256)
|
|
ch = channel(E, snr_db=10.0)
|
|
tk = None if t <= warm else topk_from_beta(bhat)
|
|
Eh = model(ch["yI"], ch["yQ"], topk_mask=tk)
|
|
# BS-side estimate from decoded embeddings only
|
|
Cb = torch.einsum("nud,nvd->uv", Eh, Eh) / Eh.shape[0]
|
|
Cb.fill_diagonal_(0.0)
|
|
bhat = gamma_ewma * bhat + (1 - gamma_ewma) * Cb
|
|
Ehf = model(ch["yI"], ch["yQ"])
|
|
Eho = model(ch["yI"], ch["yQ"], topk_mask=oracle)
|
|
traj["online_cos"].append(mean_cos(Eh, E))
|
|
traj["full_cos"].append(mean_cos(Ehf, E))
|
|
traj["oracle_cos"].append(mean_cos(Eho, E))
|
|
traj["beta_err"].append(float((bhat - Btrue).abs().mean()))
|
|
print(f"[E9] frame {t}: online={traj['online_cos'][-1]:.4f} "
|
|
f"oracle={traj['oracle_cos'][-1]:.4f} "
|
|
f"full={traj['full_cos'][-1]:.4f}", flush=True)
|
|
|
|
# ---- wall-clock overhead incl. estimation + argpartition selection
|
|
timing = {"U": [8, 16, 32, 64, 128], "full_ms": [], "topk_ms": [],
|
|
"select_ms": []}
|
|
for Ut in timing["U"]:
|
|
dt = max(d, 2 * Ut) # keep at least 2 dims per user slot
|
|
mt = UWCA(dt, Ut, H).to(DEVICE).eval()
|
|
ch = {"yI": torch.randn(256, dt, device=DEVICE),
|
|
"yQ": torch.randn(256, dt, device=DEVICE)}
|
|
bh = torch.rand(Ut, Ut, device=DEVICE)
|
|
with torch.no_grad():
|
|
for _ in range(3):
|
|
mt(ch["yI"], ch["yQ"]) # warm-up
|
|
torch.cuda.synchronize()
|
|
t0 = time.perf_counter()
|
|
for _ in range(20):
|
|
mt(ch["yI"], ch["yQ"])
|
|
torch.cuda.synchronize()
|
|
t_full = (time.perf_counter() - t0) / 20 * 1e3
|
|
t0 = time.perf_counter()
|
|
for _ in range(20):
|
|
b = bh.clone(); b.fill_diagonal_(2.0)
|
|
idx = torch.topk(b, k, dim=1).indices
|
|
tkm = torch.zeros(Ut, Ut, dtype=torch.bool, device=DEVICE)
|
|
tkm.scatter_(1, idx, True)
|
|
mt(ch["yI"], ch["yQ"], topk_mask=tkm)
|
|
torch.cuda.synchronize()
|
|
t_topk = (time.perf_counter() - t0) / 20 * 1e3
|
|
t0 = time.perf_counter()
|
|
for _ in range(100):
|
|
b = bh.clone(); b.fill_diagonal_(2.0)
|
|
idx = torch.topk(b, k, dim=1).indices
|
|
torch.cuda.synchronize()
|
|
t_sel = (time.perf_counter() - t0) / 100 * 1e3
|
|
timing["full_ms"].append(t_full)
|
|
timing["topk_ms"].append(t_topk)
|
|
timing["select_ms"].append(t_sel)
|
|
print(f"[E9] U={Ut}: full={t_full:.2f}ms topk={t_topk:.2f}ms "
|
|
f"select={t_sel:.3f}ms", flush=True)
|
|
|
|
save_json("e9_topk_online.json", {"trajectory": traj, "timing": timing,
|
|
"k": k, "warm_frames": warm})
|