Download scripts/probe_attn_ram.py from StarAtNyte1/beam-boot: direct link, hf CLI and curl.
- Browser
- Download file 5.1 kB
-
https://huggingface.co/StarAtNyte1/beam-boot/resolve/main/scripts/probe_attn_ram.py
- Command line
-
hf download hf://StarAtNyte1/beam-boot/scripts/probe_attn_ram.py
-
curl -L -o probe_attn_ram.py https://huggingface.co/StarAtNyte1/beam-boot/resolve/main/scripts/probe_attn_ram.py
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() | |