gqa(decode-4cases): Case 4 anchor in dedicated bench (5C.D)
First milestone of the decode 4-cases comparative study per GQA_full_deck.pptx slides 11-17. Case 4 (Cube-SP × PE-SP, the optimal case per slide 11) is structurally the existing _gqa_attention_decode_long at sub_w=4 (Increment 2's lrab-adapted center-root reduce). This commit wires it into a dedicated bench so the remaining cases land alongside. Changes: - New bench: src/kernbench/benches/milestone_gqa_decode_4cases.py Houses the 4 case panels under one milestone-gqa-decode-4cases entry (gated by GQA_DECODE_4CASES_RUN=1; output to 1H_milestone_output/gqa_decode_4cases/sweep.json). Cases 1-3 are TBD in subsequent sub-increments (5C.A/B/C). - New panel: single_kv_group_decode_gqa_cube_sp_pe_sp C=8, P=8, sub_w=4, T_q=1, S_kv=131_072, d_head=128, h_q=8, h_kv=1. - src/kernbench/benches/milestone_gqa_headline.py: _run_decode_panel extended with keyword-only sub_w/T_q/d_head/h_q/h_kv overrides (defaults preserve existing-panel behaviour). - tests/attention/test_milestone_gqa_decode_4cases.py: 4 new tests asserting registration, smoke completion, reduce-to-root at the lrab center cube (cube 6), and the predicted 189-ipcq Case-4 traffic pattern (168 intra-CUBE + 21 inter-CUBE lrab Phase 1+2). - tests/attention/test_milestone_gqa_headline.py: rename test_sweep_json_has_four_panels -> test_sweep_json_has_expected_panels and switch hardcoded 4 to len(PANELS) (the panel set grew to 5 with Increment 5's single_kv_group_prefill_gqa_c8_p8). Deviation noted: slide 13 prescribes AllReduce on (m,ℓ,O); our kernel does reduce-to-root (only the lrab center cube has the answer) per ADR-0060 §4. Treated as the kernbench Case-4 baseline. Verification: all 4 new tests pass; 90 regression tests pass; the previously-failing test_sweep_json_has_four_panels now passes under its renamed form. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
"""Tests for the decode 4-cases comparative-study bench.
|
||||
|
||||
Per ``GQA_full_deck.pptx`` slides 11-17, the 4 cases differ in how KV
|
||||
cache is sharded across the 8 cubes and 8 PEs of a single KV-head
|
||||
group on LLaMA-3.1-70B GQA:
|
||||
|
||||
Case 1 Cube-SP / PE-TP → KV split by S_kv across cubes; PEs TP on batch
|
||||
Case 2 Cube-Repl / PE-TP → full KV per cube; PEs TP on batch
|
||||
Case 3 Cube-Repl / PE-SP → full KV per cube; PEs SP on S_kv (intra-cube AR)
|
||||
Case 4 Cube-SP / PE-SP → KV split 64-way; 2-phase AR on (m,ℓ,O) ★ optimal
|
||||
|
||||
This file grows as each case lands. Phase 1 of 5C.D adds Case 4 first,
|
||||
which is structurally the existing ``_gqa_attention_decode_long.py`` at
|
||||
``sub_w=4`` (Increment 2's lrab-adapted center-root reduce). The Phase 2
|
||||
production change introduces a new bench file
|
||||
``src/kernbench/benches/milestone_gqa_decode_4cases.py`` housing the 4
|
||||
case panels under a single ``milestone-gqa-decode-4cases`` entry, and
|
||||
extends ``_run_decode_panel`` in ``milestone_gqa_headline`` to accept
|
||||
``sub_w``/``d_head``/``h_q``/``h_kv`` overrides.
|
||||
|
||||
Deviation from slide 13: slide prescribes AllReduce on (m,ℓ,O); the
|
||||
kernel does reduce-to-root (only the lrab center cube has the answer)
|
||||
per ADR-0060 §4. Treated as the kernbench Case-4 baseline.
|
||||
|
||||
Phase 1: tests only. T1, T2, T3, T4 fail today (new bench file does
|
||||
not yet exist; ``_run_decode_panel`` does not yet accept ``sub_w``).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from kernbench.benches.milestone_gqa_headline import _run_decode_panel
|
||||
from kernbench.runtime_api.bench_runner import run_bench
|
||||
from kernbench.runtime_api.types import resolve_device
|
||||
from kernbench.sim_engine.engine import GraphEngine
|
||||
from kernbench.topology.builder import resolve_topology
|
||||
|
||||
TOPOLOGY_DEFAULT = Path(__file__).resolve().parents[2] / "topology.yaml"
|
||||
|
||||
_CASE4_PANEL = "single_kv_group_decode_gqa_cube_sp_pe_sp"
|
||||
_CUBE_RE = re.compile(r"\bcube(\d+)\b")
|
||||
|
||||
|
||||
def _engine_factory(t, d):
|
||||
return GraphEngine(getattr(t, "topology_obj", t), enable_data=True)
|
||||
|
||||
|
||||
def _count(op_log, name: str) -> int:
|
||||
return sum(1 for r in op_log if r.op_name == name)
|
||||
|
||||
|
||||
def _dma_write_cubes(op_log) -> list[int]:
|
||||
cubes: list[int] = []
|
||||
for r in op_log:
|
||||
if r.op_name != "dma_write":
|
||||
continue
|
||||
m = _CUBE_RE.search(r.component_id)
|
||||
if m is not None:
|
||||
cubes.append(int(m.group(1)))
|
||||
return cubes
|
||||
|
||||
|
||||
def _run_case4_smoke(*, S_kv: int):
|
||||
"""Drive the Case 4 decode panel via ``_run_decode_panel``.
|
||||
|
||||
Uses ``S_kv=8192`` (smoke) to keep test time bounded; the headline
|
||||
``S_kv=128K`` runs come from ``kernbench run --bench
|
||||
milestone-gqa-decode-4cases``, not pytest.
|
||||
"""
|
||||
topo = resolve_topology(str(TOPOLOGY_DEFAULT))
|
||||
|
||||
def _bench_fn(ctx):
|
||||
_run_decode_panel(
|
||||
ctx, panel=_CASE4_PANEL,
|
||||
C=8, P=8, sub_w=4,
|
||||
S_kv=S_kv,
|
||||
d_head=128, h_q=8, h_kv=1,
|
||||
)
|
||||
|
||||
return run_bench(
|
||||
topology=topo, bench_fn=_bench_fn,
|
||||
device=resolve_device(None),
|
||||
engine_factory=_engine_factory,
|
||||
)
|
||||
|
||||
|
||||
# ── T1: Case 4 panel is registered in the new bench ──────────────────
|
||||
|
||||
|
||||
def test_case4_panel_registered():
|
||||
"""The Case 4 panel must be in the new bench's ``_PANELS`` +
|
||||
``_PANEL_DISPATCH`` with the expected LLaMA-3.1-70B target dims.
|
||||
|
||||
Headline config:
|
||||
C = 8 (head-parallel CUBE Group)
|
||||
P = 8 (intra-CUBE PE-SP)
|
||||
sub_w = 4 (lrab-adapted center-root reduce; root cube 6)
|
||||
T_q = 1 (decode: one new token per pass)
|
||||
S_kv = 131_072 (LLaMA long-context decode target)
|
||||
d_head = 128, h_q = 8, h_kv = 1
|
||||
"""
|
||||
from kernbench.benches.milestone_gqa_decode_4cases import (
|
||||
_PANEL_DISPATCH,
|
||||
_PANELS,
|
||||
)
|
||||
assert _CASE4_PANEL in _PANELS, (
|
||||
f"{_CASE4_PANEL!r} not in _PANELS; got {_PANELS}"
|
||||
)
|
||||
assert _CASE4_PANEL in _PANEL_DISPATCH
|
||||
kind, params = _PANEL_DISPATCH[_CASE4_PANEL]
|
||||
assert kind == "decode", f"kind={kind!r}, expected 'decode'"
|
||||
assert params.get("C") == 8
|
||||
assert params.get("P") == 8
|
||||
assert params.get("sub_w") == 4
|
||||
assert params.get("T_q") == 1
|
||||
assert params.get("S_kv") == 131_072
|
||||
assert params.get("d_head") == 128
|
||||
assert params.get("h_q") == 8
|
||||
assert params.get("h_kv") == 1
|
||||
|
||||
|
||||
# ── T2: Case 4 runner drives the kernel to completion ───────────────
|
||||
|
||||
|
||||
def test_case4_runner_smoke():
|
||||
"""``_run_decode_panel`` must accept the new ``sub_w``,
|
||||
``d_head``, ``h_q``, ``h_kv`` kwargs and launch the kernel at
|
||||
``(C, P, sub_w) = (8, 8, 4)`` (the Case 4 / lrab path).
|
||||
|
||||
Smoke uses ``S_kv=8192`` so the simulation completes quickly. The
|
||||
headline 128K dims run via ``kernbench run --bench
|
||||
milestone-gqa-decode-4cases``.
|
||||
"""
|
||||
result = _run_case4_smoke(S_kv=8192)
|
||||
assert result.completion.ok, (
|
||||
f"Case 4 decode smoke at C=8 P=8 sub_w=4 must complete; "
|
||||
f"got {result.completion}"
|
||||
)
|
||||
|
||||
|
||||
# ── T3: reduce-to-root lands at the lrab center cube (cube 6) ───────
|
||||
|
||||
|
||||
def test_case4_root_at_center_cube_6():
|
||||
"""For ``sub_w=4, sub_h=2``: root_col=2, root_row=1, root_cube=6.
|
||||
The decode kernel writes the final O exclusively from PE 0 of cube
|
||||
6 (ADR-0060 §4 reduce-to-root variant of the Case-4 AR pattern).
|
||||
"""
|
||||
result = _run_case4_smoke(S_kv=8192)
|
||||
assert result.completion.ok
|
||||
cubes = _dma_write_cubes(result.engine.op_log)
|
||||
assert cubes, "expected at least one dma_write for the final O store"
|
||||
distinct = set(cubes)
|
||||
assert distinct == {6}, (
|
||||
f"Case 4 root must be the lrab center cube 6; "
|
||||
f"got cubes={sorted(distinct)}"
|
||||
)
|
||||
|
||||
|
||||
# ── T4: 2-phase AR ipcq pattern matches the predicted Case-4 traffic ─
|
||||
|
||||
|
||||
def test_case4_two_level_ar_ipcq_pattern():
|
||||
"""Total ipcq_copy for the Case 4 reduce at (C, P, sub_w) =
|
||||
(8, 8, 4):
|
||||
|
||||
Intra-CUBE (per CUBE = 8 PEs in a 2×4 grid):
|
||||
row chain along intra_W: cols 1,2,3 each row × 2 rows ×
|
||||
3 tensors = 18
|
||||
col bridge along intra_N: pe4 only × 3 tensors = 3
|
||||
per-CUBE intra total = 21
|
||||
× 8 CUBEs = 168
|
||||
|
||||
Inter-CUBE lrab (sub_w=4, sub_h=2):
|
||||
Phase 1 row reduce — 3 sends/row × 3 tensors × 2 rows = 18
|
||||
Phase 2 col reduce — cube 2 → S × 3 tensors = 3
|
||||
inter-CUBE total = 21
|
||||
|
||||
Grand total: 168 + 21 = 189
|
||||
"""
|
||||
result = _run_case4_smoke(S_kv=8192)
|
||||
assert result.completion.ok
|
||||
n_copy = _count(result.engine.op_log, "ipcq_copy")
|
||||
assert n_copy == 189, (
|
||||
f"Case 4 expected 189 ipcq_copy "
|
||||
f"(168 intra-CUBE + 21 inter-CUBE lrab); got {n_copy}"
|
||||
)
|
||||
@@ -37,6 +37,7 @@ BENCH_NAME = "milestone-gqa-headline"
|
||||
PANELS = (
|
||||
"single_user_prefill_gqa",
|
||||
"multi_user_prefill_gqa",
|
||||
"single_kv_group_prefill_gqa_c8_p8",
|
||||
"single_user_decode_gqa",
|
||||
"multi_user_decode_gqa",
|
||||
)
|
||||
@@ -88,12 +89,12 @@ def test_validation_run_completes_ok(monkeypatch):
|
||||
# ── sweep.json shape ──────────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_sweep_json_has_four_panels(monkeypatch):
|
||||
def test_sweep_json_has_expected_panels(monkeypatch):
|
||||
data = _sweep_json(monkeypatch)
|
||||
assert set(data["panels"]) == set(PANELS), (
|
||||
f"panels mismatch: expected {set(PANELS)}, got {set(data['panels'])}"
|
||||
)
|
||||
assert len(data["rows"]) == 4
|
||||
assert len(data["rows"]) == len(PANELS)
|
||||
assert {r["panel"] for r in data["rows"]} == set(PANELS)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user