beam-boot / scripts /probe_attn_ram.py
StarAtNyte1's picture
Publish Beam Boot / Super Beam source
324ecd2 verified
Raw History Blame Contribute Delete
5.1 kB
"""Probe the BeamAttention RAM (beamboot/attn_ram.py) at long horizons.
Seeded random writes/reads vs a dict. Clock = row position, T = 2^18 (or the
next power of two >= horizon). Reports read errors, the minimum logit margin
(true winner minus best other key, fp32 recompute from the real cache keys and
the module's own q_norm/q_proj/query scaling), rows/s of the attention forward
alone, and peak GPU memory.
.venv-gpu/bin/python scripts/probe_attn_ram.py --horizons 4096 32768 262144
"""
import argparse
import json
import random
import sys
import time
from pathlib import Path
import torch
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from beamboot.attn_ram import AttnRAM, HEAD # noqa: E402
from transformers.models.beam.modeling_beam import compute_attention_scaling # noqa: E402
def make_stream(rng, n):
"""Writes ~70% (bursts to one address model CHIP-8 V/I/stack traffic), reads ~30%."""
ev, hot = [], [rng.randrange(4096) for _ in range(96)]
while len(ev) < n:
r = rng.random()
if r < 0.10: # burst: same address written on adjacent clocks
a = rng.choice(hot)
for _ in range(rng.randrange(2, 5)): ev.append(('w', a, rng.randrange(256)))
ev.append(('r', a))
elif r < 0.70:
a = rng.choice(hot) if rng.random() < 0.6 else rng.randrange(4096)
ev.append(('w', a, rng.randrange(256)))
else:
a = rng.choice(hot) if rng.random() < 0.6 else rng.randrange(4096)
ev.append(('r', a))
return ev[:n]
def probe(horizon, impl, dtype, device, chunk, seed, margins=True):
T = max(2 ** 18, 1 << (horizon - 1).bit_length())
torch.cuda.reset_peak_memory_stats() if device == 'cuda' else None
ram = AttnRAM(max_clock=T, device=device, dtype=dtype, attn_impl=impl)
events = make_stream(random.Random(seed), horizon - 1)
latest, ref = {}, {} # addr -> position of latest write / value
errors = reads = 0
min_margin, fwd_time = float('inf'), 0.0
L = ram.layer
for start in range(0, len(events), chunk):
block = events[start:start + chunk]
past = ram.length
win, want, qpos, qaddr = [], [], [], []
for i, e in enumerate(block):
p = past + i
if e[0] == 'w': latest[e[1]] = p; ref[e[1]] = e[2]
else:
win.append(latest.get(e[1], 0)); want.append(ref.get(e[1], 0))
qpos.append(p); qaddr.append(e[1])
if device == 'cuda': torch.cuda.synchronize()
t0 = time.perf_counter()
got = ram.run(block)
if device == 'cuda': torch.cuda.synchronize()
fwd_time += time.perf_counter() - t0
reads += len(got)
errors += sum(g != w for g, w in zip(got, want))
if margins and qpos:
x = torch.stack([ram.read_row(a) for a in qaddr]).to(device, dtype)[None]
with torch.inference_mode():
q = L.q_norm(L.q_proj(x).view(1, -1, 1, HEAD)).transpose(1, 2).float()
pos = torch.tensor([qpos], device=device)
q = (q * compute_attention_scaling(pos))[0, 0]
k = ram.cache.layers[0].keys[0, 0].float()
s = (q @ k.T) * L.scaling
cols = torch.arange(k.shape[0], device=device)[None]
s.masked_fill_(cols > pos[0][:, None], float('-inf'))
w = torch.tensor(win, device=device)
ws = s.gather(1, w[:, None])[:, 0]
s.scatter_(1, w[:, None], float('-inf'))
min_margin = min(min_margin, float((ws - s.max(1).values).min()))
res = dict(horizon=horizon, max_clock=T, impl=impl, dtype=str(dtype).split('.')[-1], device=device,
rows=ram.length, reads=reads, errors=errors,
min_margin=round(min_margin, 2) if margins else None,
rows_per_s=round(ram.length / fwd_time, 1), fwd_seconds=round(fwd_time, 2))
if device == 'cuda': res['peak_gpu_MiB'] = round(torch.cuda.max_memory_allocated() / 2 ** 20)
del ram
if device == 'cuda': torch.cuda.empty_cache()
return res
def main():
ap = argparse.ArgumentParser()
ap.add_argument('--horizons', type=int, nargs='+', default=[4096, 32768, 262144])
ap.add_argument('--impls', nargs='+', default=['sdpa', 'eager'])
ap.add_argument('--dtypes', nargs='+', default=['float32', 'bfloat16'])
ap.add_argument('--device', default='cuda' if torch.cuda.is_available() else 'cpu')
ap.add_argument('--chunk', type=int, default=512)
ap.add_argument('--seed', type=int, default=18)
a = ap.parse_args()
for dt in a.dtypes:
for h in a.horizons:
for impl in a.impls:
try:
r = probe(h, impl, getattr(torch, dt), a.device, a.chunk, a.seed)
except torch.OutOfMemoryError as exc:
r = dict(horizon=h, impl=impl, dtype=dt, error=f'OOM: {str(exc)[:80]}')
print(json.dumps(r), flush=True)
if __name__ == '__main__':
main()