Files
uwca-semantic-mac/experiments/e9_topk_online.py
T

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})