gqa(decode-4cases): Case 2 — Cube-Repl × PE-TP (no comm; 8× memory) (5C.B)
Second case in the GQA decode 4-cases comparative study per
GQA_full_deck.pptx slide 11. Case 2 replicates K, V across all 8
cubes × 8 PEs (the slide-11 8 KB/tok/PE memory waste) and has zero
inter-rank comm. For B=1 (single-user decode default), only PE 0
of CUBE 0 has work — the inherent PE-TP waste slide 11 calls out.
Changes:
- New kernel: src/kernbench/benches/_gqa_attention_decode_cube_repl_pe_tp.py
Simplest of the 4 cases. Active rank loads full Q/K/V from HBM,
computes attention via S_kv tile sweep with online-softmax merge,
writes O. All non-(0,0) ranks early-return. No tl.send/recv.
- src/kernbench/benches/milestone_gqa_decode_4cases.py:
- Add panel single_kv_group_decode_gqa_cube_repl_pe_tp (Case 2)
to _PANELS and _PANEL_DISPATCH.
- Add _run_decode_panel_cube_repl_pe_tp helper: DPPolicy K/V/Q/O
= cube=replicate, pe=replicate (models 8× memory waste).
- Extend _make_bench_fn to dispatch kind="decode_cube_repl_pe_tp"
to the new runner.
- tests/attention/test_milestone_gqa_decode_4cases.py:
4 new tests assert Case 2 contract: panel registered, smoke
completion, zero ipcq_copy (no comm), single dma_write from cube 0.
Verification: 8/8 tests pass (4 Case 4 anchor + 4 new Case 2).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,7 @@ 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"
|
||||
_CASE2_PANEL = "single_kv_group_decode_gqa_cube_repl_pe_tp"
|
||||
_CUBE_RE = re.compile(r"\bcube(\d+)\b")
|
||||
|
||||
|
||||
@@ -161,6 +162,112 @@ def test_case4_root_at_center_cube_6():
|
||||
# ── T4: 2-phase AR ipcq pattern matches the predicted Case-4 traffic ─
|
||||
|
||||
|
||||
def _run_case2_smoke(*, S_kv: int):
|
||||
"""Drive the Case 2 decode panel via the case-specific runner.
|
||||
|
||||
Case 2 = Cube-Repl × PE-TP. K, V are replicated everywhere (the
|
||||
slide-11 memory waste); for B=1 only one rank does the work; no
|
||||
inter-rank comm.
|
||||
"""
|
||||
from kernbench.benches.milestone_gqa_decode_4cases import (
|
||||
_run_decode_panel_cube_repl_pe_tp,
|
||||
)
|
||||
topo = resolve_topology(str(TOPOLOGY_DEFAULT))
|
||||
|
||||
def _bench_fn(ctx):
|
||||
_run_decode_panel_cube_repl_pe_tp(
|
||||
ctx, panel=_CASE2_PANEL,
|
||||
C=8, P=8,
|
||||
T_q=1, 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,
|
||||
)
|
||||
|
||||
|
||||
# ── Case 2 — T1: panel registered ───────────────────────────────────
|
||||
|
||||
|
||||
def test_case2_panel_registered():
|
||||
"""The Case 2 panel must be in the bench's ``_PANELS`` +
|
||||
``_PANEL_DISPATCH`` with the expected single-KV-group dims.
|
||||
|
||||
Case 2: Cube-Repl × PE-TP. K, V replicated everywhere
|
||||
(8 KB/tok/PE — slide-11 memory waste); no inter-rank comm.
|
||||
For B=1 only one rank works (PEs 1-7 idle — slide-11 calls
|
||||
out this PE-TP waste).
|
||||
"""
|
||||
from kernbench.benches.milestone_gqa_decode_4cases import (
|
||||
_PANEL_DISPATCH,
|
||||
_PANELS,
|
||||
)
|
||||
assert _CASE2_PANEL in _PANELS, (
|
||||
f"{_CASE2_PANEL!r} not in _PANELS; got {_PANELS}"
|
||||
)
|
||||
assert _CASE2_PANEL in _PANEL_DISPATCH
|
||||
kind, params = _PANEL_DISPATCH[_CASE2_PANEL]
|
||||
assert kind == "decode_cube_repl_pe_tp"
|
||||
assert params.get("C") == 8
|
||||
assert params.get("P") == 8
|
||||
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
|
||||
|
||||
|
||||
# ── Case 2 — T2: smoke runner completes ─────────────────────────────
|
||||
|
||||
|
||||
def test_case2_runner_smoke():
|
||||
"""Case 2 runner drives the new kernel to completion at smoke S_kv."""
|
||||
result = _run_case2_smoke(S_kv=8192)
|
||||
assert result.completion.ok, (
|
||||
f"Case 2 decode smoke at C=8 P=8 must complete; "
|
||||
f"got {result.completion}"
|
||||
)
|
||||
|
||||
|
||||
# ── Case 2 — T3: zero inter-rank comm by design ─────────────────────
|
||||
|
||||
|
||||
def test_case2_zero_ipcq_copy_no_comm():
|
||||
"""Case 2's defining property: full KV per rank ⇒ NO inter-rank
|
||||
communication. Slide 11 lists comm cost as 'none'.
|
||||
"""
|
||||
result = _run_case2_smoke(S_kv=8192)
|
||||
assert result.completion.ok
|
||||
n_copy = _count(result.engine.op_log, "ipcq_copy")
|
||||
assert n_copy == 0, (
|
||||
f"Case 2 must have zero inter-rank comm; got ipcq_copy={n_copy}"
|
||||
)
|
||||
|
||||
|
||||
# ── Case 2 — T4: single dma_write from cube 0 (B=1 single-rank work) ─
|
||||
|
||||
|
||||
def test_case2_single_dma_write_at_cube_0():
|
||||
"""For B=1, only PE 0 of CUBE 0 does the work (the inherent PE-TP
|
||||
waste at B=1). Exactly 1 dma_write, from cube 0.
|
||||
"""
|
||||
result = _run_case2_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 == {0}, (
|
||||
f"Case 2 B=1 single writer must be cube 0; "
|
||||
f"got cubes={sorted(distinct)}"
|
||||
)
|
||||
|
||||
|
||||
# ── Case 4 — T4 (existing, kept) ────────────────────────────────────
|
||||
|
||||
|
||||
def test_case4_two_level_ar_ipcq_pattern():
|
||||
"""Total ipcq_copy for the Case 4 reduce at (C, P, sub_w) =
|
||||
(8, 8, 4):
|
||||
|
||||
Reference in New Issue
Block a user