Files
kernbench2/tests/attention/test_gqa_short_context_sweep_decode.py
T
ywkang 711a9a257f gqa short-context: exclude KV deploy from wall; correct HBM peak; batch scaffold
Metric fixes (test harnesses, deploy artifact + wrong constant):
- wall = max(t_end) - min(t_start): exclude the one-time KV-cache deploy
  from the measured decode/prefill step (it was 92-99% of wall, mapping-
  invariant, and masked the real per-mapping separation).
- PEAK_PE_HBM_BPS 128 -> 256 B/ns (8 channels/PE x 32 GB/s); the old value
  was half the modeled per-PE HBM BW, so hbm_bw_util read >1.0 once wall
  was corrected. All six short-context sweep CSVs regenerated/repatched.

Result: the four KV mappings now separate along a {64,32,16,8}-active-PE
ladder (decode 8-kv/1-kv = 4.8x at 8K, 7.4x at 64K), not "modest/tied" as
before; decode is bandwidth-bound at 46-76% of the 256 GB/s per-PE ceiling.

Report (S5.2 rewrite):
- Replace the tied-wall / 8x-per-PE-util claims (both deploy artifacts)
  with the corrected separation and a density trade-off (per-CUBE KV
  footprint, Fig 16 top panel switched to a wall-invariant metric).
- Add a projected latency-vs-batched-throughput analysis (marked
  projected, not measured): dense mappings win throughput, 1-kv wins
  latency; converges at long context.
- Regenerate Fig 15/16/17/18; fix plot script hardcoded ROOT path.

Batch experiment (Part 2, cube_base):
- Add backward-compatible cube_base=0 scalar to the decode kernel so a
  batched user placed at DPPolicy.cube_start addresses 0-based shards.
  Default preserves single-user behavior (32 decode tests pass unchanged).
- New batch harness (skipped): concurrent B>=2 launches hit a sim routing
  issue (sip0.cube0.pe8); single-user path verified. Concurrency fix next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 16:25:04 -07:00

132 lines
4.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Decode mode-comparison sweep across context lengths.
Measures per (mode ∈ {A1, A2, A4, B}) × (S_kv ∈ {8K, 16K, 32K, 64K}):
1. Wall clock latency (μs)
2. Hardware utilization
- GEMM engine utilization (Σ gemm time / wall × n_pe)
- MATH op count
3. HBM bandwidth utilization
(Σ dma_read+write bytes / (wall × n_pe × peak_pe_hbm_bw))
4. Communication cost (IPCQ bytes — chain reduce)
5. KV cache space cost (per-cube bytes)
Output: ``docs/sweeps/short_context_decode_sweep.csv`` (16 rows).
Run: ``pytest tests/attention/test_gqa_short_context_sweep_decode.py -m slow``
"""
from __future__ import annotations
import csv
from pathlib import Path
import pytest
from tests.attention.test_gqa_short_context import (
D_HEAD, H_KV, P, TILE_S_KV, _run_decode,
)
PEAK_PE_HBM_BPS = 8 * 32.0 # 8 channels/PE x 32 GB/s = 256 bytes/ns per PE
MATH_OPS = {"max", "sum", "exp", "sub", "mul_bcast", "add", "div",
"maximum", "minimum", "neg", "rmax", "rsum", "exp_diff",
"max_elem"}
MODES = [
("A1", 1, 8),
("A2", 2, 4),
("A4", 4, 2),
("B", 8, 1),
]
CONTEXT_LENGTHS = [8 * 1024, 16 * 1024, 32 * 1024, 64 * 1024]
CSV_OUT = (Path(__file__).resolve().parents[2]
/ "docs" / "sweeps" / "short_context_decode_sweep.csv")
def _compute_metrics(r, *, mode, kv_per_cube, C, S_kv):
op_log = r.engine.op_log
wall_ns = max(rec.t_end for rec in op_log) - min(rec.t_start for rec in op_log)
active_pes = set()
for rec in op_log:
cid = rec.component_id or ""
if cid and ".cube" in cid and ".pe" in cid:
parts = cid.split(".")
active_pes.add(".".join(parts[:3]))
n_pe = len(active_pes)
gemm_time_ns = 0.0
math_count = 0
dma_read_bytes = 0
dma_write_bytes = 0
ipcq_bytes = 0
for rec in op_log:
name = rec.op_name
nbytes = rec.params.get("nbytes", 0) or 0
if name == "gemm_f16":
gemm_time_ns += rec.t_end - rec.t_start
elif name in MATH_OPS:
math_count += 1
elif name == "dma_read":
dma_read_bytes += nbytes
elif name == "dma_write":
dma_write_bytes += nbytes
elif name == "ipcq_copy":
ipcq_bytes += nbytes
gemm_util = gemm_time_ns / (wall_ns * n_pe) if n_pe else 0.0
hbm_bw_util = (
(dma_read_bytes + dma_write_bytes)
/ (wall_ns * n_pe * PEAK_PE_HBM_BPS)
) if n_pe else 0.0
kv_cache_per_cube_bytes = kv_per_cube * S_kv * D_HEAD * 2 * 2
return {
"mode": mode,
"kv_per_cube": kv_per_cube,
"C": C,
"S_kv": S_kv,
"wall_us": wall_ns / 1000,
"n_pe": n_pe,
"gemm_util": gemm_util,
"math_count": math_count,
"hbm_bw_util": hbm_bw_util,
"hbm_read_mb": dma_read_bytes / (1024 * 1024),
"hbm_write_kb": dma_write_bytes / 1024,
"ipcq_kb": ipcq_bytes / 1024,
"kv_cache_per_cube_mb": kv_cache_per_cube_bytes / (1024 * 1024),
}
@pytest.mark.slow
def test_decode_mode_context_sweep():
"""Run 4 modes × 4 context lengths, dump 5 metrics to CSV."""
rows = []
for mode, kv_per_cube, C in MODES:
for S_kv in CONTEXT_LENGTHS:
print(f">>> DECODE {mode} S_kv={S_kv//1024}K "
f"(kv_per_cube={kv_per_cube}, C={C})", flush=True)
r = _run_decode(kv_per_cube=kv_per_cube, C=C, S_kv=S_kv, h_q=64)
assert r.completion.ok, (
f"DECODE {mode} S_kv={S_kv}: {r.completion}"
)
m = _compute_metrics(r, mode=mode, kv_per_cube=kv_per_cube,
C=C, S_kv=S_kv)
rows.append(m)
print(f" wall={m['wall_us']:.1f}μs "
f"gemm_util={m['gemm_util']:.3f} "
f"hbm_bw_util={m['hbm_bw_util']:.3f} "
f"ipcq={m['ipcq_kb']:.1f}KB "
f"kv_cache={m['kv_cache_per_cube_mb']:.1f}MB/cube",
flush=True)
CSV_OUT.parent.mkdir(parents=True, exist_ok=True)
with CSV_OUT.open("w", newline="") as f:
w = csv.DictWriter(f, fieldnames=list(rows[0].keys()))
w.writeheader()
for row in rows:
w.writerow(row)
print(f"\nWrote {len(rows)} rows to {CSV_OUT}")