24c705419e
Complete the composite-command study across both attention regimes and write up the result, resolving when the composite command helps latency. Decode (memory-bound, T_q=1, M=8): the command form is latency-neutral — the kernel is bound by KV streaming and the MAC array is near-idle, so the composite's only benefit here is host-issue offload (O(n_tiles) -> O(1) PE_CPU commands). Figure: gqa_decode_long_ctx_composite (latency flat, command count saturates). Prefill (compute-bound, large M=G*T_q tile-filling): add three command-form variants of a single-rank FlashAttention prefill kernel (_gqa_prefill_compute_bound: primitive / composite / composite_extended). Here the composite's scheduler-internal per-tile DMA<->compute pipeline keeps the MAC array fed while the primitive's blocking tl.dot starves it, so composite wins on MAC utilization (68% flat -> 83%) and wall-clock (19% faster at ctx=1024), with the margin growing with context (deeper P.V reduction = more tiles to pipeline) — the compute-bound mirror of the GEMM result in section 3. Figure: gqa_prefill_compute_bound (latency + MAC util). Sweep wired as GQA_1H_SWEEPS=prefill_cb; tests cover structure, e2e, and composite<primitive in the compute-bound regime. The synthesis: composite has two benefits set by roofline position — host-issue offload (always) and MAC-array feeding (compute-bound only). Decode exercises the first, prefill both. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
112 lines
3.8 KiB
Python
112 lines
3.8 KiB
Python
"""Comparative figure for the compute-bound prefill composite study.
|
|
|
|
Reads sweep_prefill_compute_bound.json (emitted by milestone-1h-gqa,
|
|
sweep ``prefill_cb``) and writes one two-panel PNG:
|
|
|
|
gqa_prefill_compute_bound.png
|
|
Left — end-to-end prefill latency (µs) vs context length.
|
|
Right — MAC utilization (achieved / 8 TFLOP·s⁻¹ per-PE peak) vs context.
|
|
|
|
Unlike memory-bound decode (where command form is latency-neutral), in
|
|
compute-bound prefill the composite command keeps the MAC array fed by
|
|
streaming DMA↔compute per HW tile, so it wins on both latency and
|
|
utilization — and the margin grows with context (deeper P·V reduction =
|
|
more tiles to pipeline).
|
|
|
|
Run (after the bench):
|
|
GQA_1H_RUN=1 GQA_1H_SWEEPS=prefill_cb python -m kernbench.cli.main run \\
|
|
--bench milestone-1h-gqa --topology topology.yaml
|
|
python scripts/paper/paper_plot_gqa_prefill_compute_bound.py
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import matplotlib
|
|
|
|
matplotlib.use("Agg")
|
|
import matplotlib.pyplot as plt # noqa: E402
|
|
|
|
_REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
_FIG_DIR = (
|
|
_REPO_ROOT / "src" / "kernbench" / "benches"
|
|
/ "1H_milestone_output" / "gqa" / "long_ctx"
|
|
)
|
|
_SWEEP_JSON = _FIG_DIR / "sweep_prefill_compute_bound.json"
|
|
_PAPER_FIG_DIR = (
|
|
_REPO_ROOT / "docs" / "report" / "1H-codesign-paper" / "figures"
|
|
)
|
|
|
|
_VARIANT_STYLE = {
|
|
"primitive": ("primitive (tl.dot, hand-tiled)", "#c0504d", "o"),
|
|
"composite": ("composite GEMM", "#3b6ea5", "s"),
|
|
"composite_extended": ("composite + softmax_merge", "#4f8a4f", "^"),
|
|
}
|
|
_ORDER = ("primitive", "composite", "composite_extended")
|
|
|
|
|
|
def _ctx_label(c: int) -> str:
|
|
return f"{c // 1024}K" if c >= 1024 else str(c)
|
|
|
|
|
|
def _series(rows, variant, key):
|
|
pts = sorted(((r["ctx_len"], r[key]) for r in rows
|
|
if r["variant"] == variant), key=lambda t: t[0])
|
|
return [p[0] for p in pts], [p[1] for p in pts]
|
|
|
|
|
|
def main() -> None:
|
|
sweep = json.loads(_SWEEP_JSON.read_text())
|
|
rows = sweep["rows"]
|
|
ctxs = sweep["ctx_points"]
|
|
|
|
fig, (ax_lat, ax_util) = plt.subplots(1, 2, figsize=(13.0, 4.8))
|
|
|
|
for v in _ORDER:
|
|
label, color, marker = _VARIANT_STYLE[v]
|
|
xs, lat = _series(rows, v, "latency_ns")
|
|
ax_lat.plot(xs, [y / 1e3 for y in lat], marker=marker,
|
|
color=color, label=label, lw=2)
|
|
xs, util = _series(rows, v, "mac_util")
|
|
ax_util.plot(xs, [u * 100 for u in util], marker=marker,
|
|
color=color, label=label, lw=2)
|
|
|
|
for ax in (ax_lat, ax_util):
|
|
ax.set_xscale("log", base=2)
|
|
ax.set_xticks(ctxs)
|
|
ax.set_xticklabels([_ctx_label(c) for c in ctxs])
|
|
ax.set_xlabel(r"context length (= $T_q$ = $S_{kv}$)")
|
|
ax.grid(True, ls=":", alpha=0.5)
|
|
ax.legend(fontsize=9)
|
|
|
|
ax_lat.set_ylabel("end-to-end prefill latency (µs)")
|
|
ax_lat.set_title("Compute-bound prefill latency per command form")
|
|
ax_util.set_ylabel("MAC utilization (% of 8 TFLOP·s⁻¹ peak)")
|
|
ax_util.set_title(
|
|
"MAC utilization — composite keeps the array fed; primitive starves"
|
|
)
|
|
ax_util.axhline(100, color="#888", ls="--", lw=1, alpha=0.6)
|
|
|
|
fig.suptitle(
|
|
"Compute-bound prefill attention — use of composite commands\n"
|
|
"single-rank, GQA single-KV-head group ($h_q{=}8$, $d_{\\text{head}}"
|
|
"{=}128$); $M{=}8T_q$ tile-filling",
|
|
fontsize=11,
|
|
)
|
|
fig.tight_layout(rect=(0, 0, 1, 0.92))
|
|
|
|
out = _FIG_DIR / "gqa_prefill_compute_bound.png"
|
|
fig.savefig(out, dpi=150)
|
|
plt.close(fig)
|
|
print(f"wrote {out}")
|
|
|
|
if _PAPER_FIG_DIR.is_dir():
|
|
dst = _PAPER_FIG_DIR / out.name
|
|
dst.write_bytes(out.read_bytes())
|
|
print(f"copied {dst}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|