Files
kernbench2/scripts/paper/paper_plot_gqa.py
T
ywkang dd525bfcb7 paper: add /paper skill + 1H HW-SW codesign report (GEMM, All-Reduce, fused GQA)
New `/paper` slash-command skill that synthesizes ADR/SPEC content and live
KernBench benchmark results into a sectioned LaTeX technical paper compiled
to PDF with Tectonic (auto-installed). The skill negotiates a TOC, grounds
every number in committed artifacts or fresh bench runs, and keeps
report-only benches isolated.

This commit also includes the first generated report:
- docs/report/1H-codesign-paper/ — main.tex + per-section .tex, figures,
  toc.md contract, and the built 8-page main.pdf. Covers the platform
  (source-level kernels, latency model + accuracy, HW config from
  topology.yaml), GEMM via composite command, All-Reduce via PE_IPCQ, and
  fused GQA combining both, plus discussion/conclusion/2H future work.
- scripts/paper/ — isolated report harnesses (not registered benches):
  paper_gqa_latency.py harvests per-panel GQA end-to-end latency + engine
  occupancy (the milestone only emitted op-counts); paper_plot_gqa.py
  renders the GQA figures.

GEMM/All-Reduce reuse committed milestone figures/CSVs; GQA results are
generated fresh. Honest flags retained: PE_CPU dispatch cost is 0 in this
config, and the proposed two-composite softmax_merge decode is marked
designed-not-measured.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 22:15:14 -07:00

109 lines
3.6 KiB
Python

"""Report harness: render GQA figures from ``gqa_latency.json``.
Isolated under ``scripts/paper/`` for the 1H codesign report only.
Reads the JSON emitted by ``paper_gqa_latency.py`` and writes two PNGs into
``docs/report/1H-codesign-paper/figures/``:
gqa_latency_by_panel.png end-to-end latency per panel
gqa_op_engine_breakdown.png op-counts + engine occupancy per panel
Run (after paper_gqa_latency.py):
python scripts/paper/paper_plot_gqa.py
"""
from __future__ import annotations
import json
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt # noqa: E402
_FIG_DIR = Path(__file__).resolve().parents[2] / "docs" / "report" / "1H-codesign-paper" / "figures"
_IN_JSON = _FIG_DIR / "gqa_latency.json"
_LABELS = {
"single_user_prefill_gqa": "prefill\nC=1",
"multi_user_prefill_gqa": "prefill\nC=4 (Ring KV)",
"single_user_decode_gqa": "decode\nC=1, P=8",
"multi_user_decode_gqa": "decode\nC=4, P=8",
}
def _load() -> list[dict]:
return json.loads(_IN_JSON.read_text())["rows"]
def _plot_latency(rows: list[dict]) -> Path:
labels = [_LABELS[r["panel"]] for r in rows]
lat = [r["latency_ns"] for r in rows]
fig, ax = plt.subplots(figsize=(7.0, 4.0))
bars = ax.bar(labels, lat, color="#3b6ea5", width=0.6)
ax.set_ylabel("end-to-end latency (ns)")
ax.set_title("Fused GQA — end-to-end latency per panel")
ax.bar_label(bars, fmt="%.0f", padding=3, fontsize=9)
ax.grid(axis="y", ls=":", alpha=0.5)
ax.set_ylim(0, max(lat) * 1.15)
fig.tight_layout()
out = _FIG_DIR / "gqa_latency_by_panel.png"
fig.savefig(out, dpi=150)
plt.close(fig)
return out
def _plot_breakdown(rows: list[dict]) -> Path:
labels = [_LABELS[r["panel"]] for r in rows]
x = range(len(rows))
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11.0, 4.2))
# Left: op-count breakdown (grouped bars).
keys = ["gemm_count", "ipcq_copy_count", "dma_read_count", "dma_write_count"]
disp = ["GEMM", "IPCQ copy", "DMA read", "DMA write"]
colors = ["#3b6ea5", "#c0504d", "#9bbb59", "#8064a2"]
w = 0.2
for i, (k, d, c) in enumerate(zip(keys, disp, colors)):
vals = [r["op_log_summary"][k] for r in rows]
ax1.bar([xi + (i - 1.5) * w for xi in x], vals, width=w, label=d, color=c)
ax1.set_xticks(list(x))
ax1.set_xticklabels(labels, fontsize=8)
ax1.set_ylabel("op count")
ax1.set_title("Per-panel op-count breakdown")
ax1.legend(fontsize=8)
ax1.grid(axis="y", ls=":", alpha=0.5)
# Right: engine occupancy (log scale — compute vs data movement).
engs = ["pe_gemm", "pe_math", "pe_dma"]
edisp = ["GEMM engine", "MATH engine", "DMA engine"]
ecolors = ["#3b6ea5", "#e8a33d", "#c0504d"]
w2 = 0.25
for i, (e, d, c) in enumerate(zip(engs, edisp, ecolors)):
vals = [max(r["engine_occupancy_ns"][e], 0.1) for r in rows]
ax2.bar([xi + (i - 1) * w2 for xi in x], vals, width=w2, label=d, color=c)
ax2.set_yscale("log")
ax2.set_xticks(list(x))
ax2.set_xticklabels(labels, fontsize=8)
ax2.set_ylabel("summed engine occupancy (ns, log)")
ax2.set_title("Compute vs data-movement occupancy")
ax2.legend(fontsize=8)
ax2.grid(axis="y", ls=":", alpha=0.5)
fig.suptitle("Fused GQA — where the work goes", fontsize=12)
fig.tight_layout()
out = _FIG_DIR / "gqa_op_engine_breakdown.png"
fig.savefig(out, dpi=150)
plt.close(fig)
return out
def main() -> None:
rows = _load()
p1 = _plot_latency(rows)
p2 = _plot_breakdown(rows)
print(f"wrote {p1}")
print(f"wrote {p2}")
if __name__ == "__main__":
main()