9e1242039b
End-to-end wires C=8 P=8 d_head=128 prefill with snake-ring inter-CUBE SFR, intra-CUBE PE-SP (all 64 ranks active), and the milestone bench panel. Decode kernel gains lrab-adapted center-root reduce for the 2×4 sub-mesh per ADR-0060 §4.2. Increment 1 — SFR multi-row snake src/kernbench/ccl/sfr_config.py: configure_sfr_intercube_ring gains submesh_shape / submesh_origin kwargs; installs a Hamiltonian snake ring through a rectangular sub-mesh (every hop is 1-hop physical neighbour). Backward-compat: 1D-row behaviour preserved when submesh_shape is None. tests/test_intercube_snake_ring.py (12 tests) Increment 2 — Decode lrab-adapted center-root reduce src/kernbench/benches/_gqa_attention_decode_long.py: new sub_w param (default 0 = existing 1D-chain). sub_w >= 2 selects the ADR-0060 §4.2 prescribed lrab-adapted Phase 1+2 reduce (bidirectional row + bidirectional col converge to the center cube), with log-sum-exp _merge_running replacing the plain + of lrab. tests/attention/test_gqa_decode_long_2d_reduce.py (4 tests) Increment 3 — Prefill kernel at C=8 (no production change) Verified by inspection that the existing prefill_long kernel + Increment 1's snake SFR already work at C=8 without any kernel edit. The kernel speaks logical W/E; the snake routes it. tests/attention/test_gqa_prefill_long_c8_snake.py (3 tests) Increment 4 — Intra-CUBE PE-SP in prefill (all 64 ranks) src/kernbench/benches/_gqa_attention_prefill_long.py: new P param (default 1 = existing PE-0-only). P > 1 splits T_q query-axis-wise across the P PEs of each CUBE; output rows are disjoint per PE so no intra-CUBE reduce is needed; each PE drives its own same-lane ring (P parallel rings). tests/attention/test_gqa_prefill_long_pe_sp.py (5 tests) Increment 5 — LLaMA-scale milestone bench panel src/kernbench/benches/milestone_gqa_headline.py: new panel single_kv_group_prefill_gqa_c8_p8 (C=8, P=8, T_q=S_kv=32K, d_head=128). _run_prefill_panel extended with P/T_q/d_head defaults; routes snake SFR when C > mesh_w. tests/attention/test_milestone_gqa_single_kv_group_prefill_panel.py (3 tests) Total: 4 production files modified, 5 new test files, 27 new tests. Followed the Phase 1/2 protocol per CLAUDE.md throughout. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
192 lines
7.2 KiB
Python
192 lines
7.2 KiB
Python
"""Tests for lrab-adapted center-root inter-CUBE reduce in decode_long.
|
||
|
||
Verifies the ADR-0060 §4.2 prescribed Level-1 collective for the
|
||
single-KV-group LLaMA-3.1-70B target (C=8 over a 2×4 sub-mesh, root at
|
||
the geometric center CUBE).
|
||
|
||
Activation contract:
|
||
- ``sub_w == 0`` (default; omitted from launch args) → existing 1D
|
||
inter-CUBE chain that converges at CUBE 0. Byte-for-byte unchanged.
|
||
- ``sub_w >= 2`` with ``sub_h = C // sub_w >= 2`` → lrab-adapted
|
||
center-root mesh reduce. Root cube is
|
||
``(sub_h//2)*sub_w + (sub_w//2)``.
|
||
|
||
Degenerate (``sub_h < 2`` or ``sub_w < 2``) combinations are rejected at
|
||
launch time per ADR-0060 §4.2 + CLAUDE.md "Simplicity First": those
|
||
configurations belong to the 1D-chain code path, not lrab.
|
||
|
||
Phase 1 (this commit): tests only — production code lands in Phase 2.
|
||
T1, T2, T4 fail today (kernel signature does not accept ``sub_w``).
|
||
T3 passes today as the backward-compat anchor for the existing
|
||
1D-chain path.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
|
||
from kernbench.benches._gqa_attention_decode_long import gqa_attention_decode_long_kernel # noqa: F401
|
||
from kernbench.ccl.install import load_ccl_config, resolve_algorithm_config
|
||
from kernbench.ccl.sfr_config import configure_sfr_intercube_multisip
|
||
from kernbench.policy.placement.dp import DPPolicy
|
||
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"
|
||
|
||
D_HEAD = 64
|
||
DTYPE = "f16"
|
||
_CUBE_RE = re.compile(r"\bcube(\d+)\b")
|
||
|
||
|
||
def _ccl_cfg():
|
||
return resolve_algorithm_config(
|
||
load_ccl_config(), name="lrab_hierarchical_allreduce",
|
||
)
|
||
|
||
|
||
def _engine_factory(t, d):
|
||
return GraphEngine(getattr(t, "topology_obj", t), enable_data=True)
|
||
|
||
|
||
def _dma_write_cubes(op_log) -> list[int]:
|
||
"""Return the list of CUBE ids that emitted a ``dma_write``.
|
||
|
||
Decode's final-store path uses one ``tl.store`` from the root rank.
|
||
Multiple HBM-channel-level write records may correspond to the same
|
||
logical store; this just reports the source CUBE for each.
|
||
"""
|
||
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_decode_long(
|
||
*, C: int, P: int, S_kv: int, sub_w: int | None,
|
||
):
|
||
"""Drive a decode_long launch. ``sub_w=None`` means omit the arg
|
||
entirely (exercises the kernel's current default behaviour)."""
|
||
topo = resolve_topology(str(TOPOLOGY_DEFAULT))
|
||
|
||
def _bench_fn(ctx):
|
||
configure_sfr_intercube_multisip(ctx.engine, ctx.spec, _ccl_cfg())
|
||
dp_full = DPPolicy(cube="replicate", pe="replicate",
|
||
num_cubes=C, num_pes=P)
|
||
dp_kv = DPPolicy(cube="row_wise" if C > 1 else "replicate",
|
||
pe="row_wise", num_cubes=C, num_pes=P)
|
||
T_q = 1
|
||
h_q = 8
|
||
h_kv = 1
|
||
ctx.zeros((T_q, h_q * D_HEAD),
|
||
dtype=DTYPE, dp=dp_full, name=f"q_dec_2d_c{C}")
|
||
k = ctx.zeros((S_kv, h_kv * D_HEAD),
|
||
dtype=DTYPE, dp=dp_kv, name=f"k_dec_2d_c{C}")
|
||
v = ctx.zeros((S_kv, h_kv * D_HEAD),
|
||
dtype=DTYPE, dp=dp_kv, name=f"v_dec_2d_c{C}")
|
||
o = ctx.empty((T_q, h_q * D_HEAD),
|
||
dtype=DTYPE, dp=dp_full, name=f"o_dec_2d_c{C}")
|
||
q = ctx.zeros((T_q, h_q * D_HEAD),
|
||
dtype=DTYPE, dp=dp_full, name=f"q_dec_2d_c{C}_2")
|
||
launch_args = [q, k, v, o, T_q, S_kv, h_q, h_kv, D_HEAD, C, P]
|
||
if sub_w is not None:
|
||
launch_args.append(sub_w)
|
||
ctx.launch(
|
||
f"gqa_decode_long_2d_c{C}_sw{sub_w}",
|
||
gqa_attention_decode_long_kernel,
|
||
*launch_args,
|
||
_auto_dim_remap=False,
|
||
)
|
||
|
||
return run_bench(
|
||
topology=topo, bench_fn=_bench_fn,
|
||
device=resolve_device(None), engine_factory=_engine_factory,
|
||
)
|
||
|
||
|
||
# ── T1: C=8 lrab-adapted center-root completes ───────────────────────
|
||
|
||
|
||
def test_decode_2d_sub_w4_sub_h2_completes():
|
||
"""ADR-0060 §4.2 prescribed center-root mesh reduce at the
|
||
milestone target: C=8 over a 2×4 sub-mesh (sub_w=4, sub_h=2),
|
||
P=8 PEs per CUBE. With zero inputs, output is trivially zero;
|
||
the test guards that the kernel reaches completion without
|
||
deadlock or scratch overflow.
|
||
"""
|
||
result = _run_decode_long(C=8, P=8, S_kv=2048, sub_w=4)
|
||
assert result.completion.ok, (
|
||
f"decode at C=8, sub_w=4 (lrab-adapted) must complete; "
|
||
f"got {result.completion}"
|
||
)
|
||
|
||
|
||
# ── T2: root lives at the geometric center CUBE (cube 6) ─────────────
|
||
|
||
|
||
def test_decode_2d_sub_w4_sub_h2_root_at_center_cube_6():
|
||
"""For sub_w=4, sub_h=2: root_col=2, root_row=1, root_cube=6.
|
||
|
||
Only the root CUBE issues the final ``tl.store`` — every other
|
||
rank short-circuits. The dma_write records must come exclusively
|
||
from CUBE 6.
|
||
"""
|
||
result = _run_decode_long(C=8, P=8, S_kv=2048, sub_w=4)
|
||
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"final dma_write must come exclusively from CUBE 6 (sub_w=4, "
|
||
f"sub_h=2 root); got cubes={sorted(distinct)}"
|
||
)
|
||
|
||
|
||
# ── T3: backward-compat — sub_w omitted → existing 1D chain at C=4 ───
|
||
|
||
|
||
def test_decode_2d_backward_compat_sub_w0_default():
|
||
"""When ``sub_w`` is omitted from the launch, the kernel must
|
||
behave identically to today: 1D inter-CUBE chain along W,
|
||
converging at CUBE 0. This serves as a regression anchor — Phase 2
|
||
must not change the existing C=4 panel behaviour.
|
||
|
||
Passes today as well as after Phase 2.
|
||
"""
|
||
result = _run_decode_long(C=4, P=8, S_kv=2048, sub_w=None)
|
||
assert result.completion.ok, (
|
||
f"decode at C=4 with default sub_w (1D chain) must complete; "
|
||
f"got {result.completion}"
|
||
)
|
||
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"1D-chain root must be CUBE 0; got cubes={sorted(distinct)}"
|
||
)
|
||
|
||
|
||
# ── T4: degenerate sub_w configurations are rejected ─────────────────
|
||
|
||
|
||
def test_decode_2d_invalid_sub_w_rejected_when_sub_h_lt_2():
|
||
"""ADR-0060 §4.2 + CLAUDE.md "Simplicity First": only sub-meshes
|
||
with both sub_w >= 2 and sub_h >= 2 use lrab. C=4 with sub_w=4
|
||
would give sub_h=1 (degenerate; Phase 2 of lrab becomes a no-op).
|
||
Callers must use the 1D-chain path (sub_w=0/omitted) for that case.
|
||
|
||
The kernel must reject the degenerate combination with a clear
|
||
error rather than silently producing a 1D-chain result at the
|
||
wrong root location.
|
||
"""
|
||
with pytest.raises((ValueError, AssertionError), match=r"sub_[wh]"):
|
||
_run_decode_long(C=4, P=8, S_kv=2048, sub_w=4)
|