"""Phase 1 spec tests for ADR-0065 P2 — composite emit-time validation. Covers the two reachable invariants enforced by the TLContext lowering pass (ADR-0065 D6.6 / D6.7): - **Auto-bind conflict (D6.6).** If a prologue recipe declares a `primary_out` (which auto-binds into the head GEMM's `a`) AND the kernel also passes `a` explicitly, lowering raises — ambiguous which value wins. - **MATH operand TCM-only (D6.7).** Every operand of a *prologue recipe* MATH op must be TCM-resident; passing an HBM handle as a recipe operand raises at emit time. (The head op — gemm *or* math — keeps the existing DMA-staged-from-HBM behavior; D6.7 does not apply to it.) (D6.1 GEMM-count ≤ 1 is implemented as a defensive guard but is not reachable through the public API while `softmax_merge` is MATH-only and the head carries a single GEMM — its dedicated test is deferred until a GEMM-emitting recipe exists.) Phase 1 (this commit): tests only. FAIL until P2. """ from __future__ import annotations import math import pytest from kernbench.common.pe_commands import TensorHandle from kernbench.triton_emu.tl_context import TLContext G, TILE, D = 8, 64, 128 def _tcm(addr: int, shape: tuple[int, ...]) -> TensorHandle: return TensorHandle( id=f"h{addr:x}", addr=addr, shape=shape, dtype="f16", nbytes=2 * math.prod(shape), space="tcm", ) def _hbm(addr: int, shape: tuple[int, ...]) -> TensorHandle: return TensorHandle( id=f"hb{addr:x}", addr=addr, shape=shape, dtype="f16", nbytes=2 * math.prod(shape), space="hbm", ) def test_autobind_conflict_raises(monkeypatch=None): """D6.6 — explicit `a` + a primary_out recipe is ambiguous.""" tl = TLContext(pe_id=0, num_programs=1, scratch_base=0x100000) A = _tcm(0x9000, (G, TILE)) s = _tcm(0x1000, (G, TILE)) m = _tcm(0x2000, (G,)) l = _tcm(0x3000, (G,)) O = _tcm(0x4000, (G, D)) V = tl.ref(0x5000, shape=(TILE, D), dtype="f16") with pytest.raises(ValueError, match="auto-bind"): tl.composite( op="gemm", a=A, b=V, out=O, prologue=[{"op": "softmax_merge", "s": s, "m": m, "l": l, "O": O}], ) def test_math_operand_must_be_tcm_raises(): """D6.7 — an HBM handle reaching a prologue recipe MATH op is rejected. softmax_merge's first op (rmax) reads ``s``; an HBM ``s`` makes the expanded MATH op reference HBM, which the recipe prohibits. """ tl = TLContext(pe_id=0, num_programs=1, scratch_base=0x100000) s_hbm = _hbm(0x1000, (G, TILE)) # Sj as HBM — illegal recipe operand m = _tcm(0x2000, (G,)) l = _tcm(0x3000, (G,)) O = _tcm(0x4000, (G, D)) V = tl.ref(0x5000, shape=(TILE, D), dtype="f16") with pytest.raises(ValueError, match="TCM"): tl.composite( op="gemm", b=V, out=O, prologue=[{"op": "softmax_merge", "s": s_hbm, "m": m, "l": l, "O": O}], ) def test_math_head_from_hbm_is_allowed(): """The head op (op="math") keeps the existing HBM-streamed behavior — D6.7 does not apply to it (only to prologue recipe ops).""" tl = TLContext(pe_id=0, num_programs=1, scratch_base=0x100000) a_hbm = _hbm(0x1000, (G, TILE)) tl.composite(op="math", a=a_hbm, math_op="exp", out_ptr=0x6000) # no raise