"""Phase 1 spec tests for ADR-0065 D8 — composite output handle + output-space DMA. `tl.composite(...)` returns the **output TensorHandle** (not a bare CompletionHandle) so its result chains into downstream ops like `tl.dot`'s. `out` is always a handle: `out=tl.ref(addr, shape)` for HBM output (DMA_WRITE inside the composite), `out=` for in-place, or omitted → TLContext auto-allocates a TCM scratch. The output handle's `space` decides the tile loop's write-back (tcm → STORE only; hbm → STORE + DMA_WRITE). `tl.ref` returns `space="hbm"`. Phase 1 (this commit): tests only. FAIL until D8: - `tl.ref` returns space="tcm"; `composite` returns CompletionHandle; output always DMA_WRITEs; `tl.wait` rejects a TensorHandle. """ from __future__ import annotations from kernbench.common.pe_commands import OpSpec, Scope, TensorHandle from kernbench.triton_emu.tl_context import TLContext def _tl() -> TLContext: return TLContext(pe_id=0, num_programs=1, scratch_base=0x200000, scratch_size=1 << 20) def test_ref_returns_hbm_space(): h = _tl().ref(0x1000, shape=(4, 4), dtype="f16") assert h.space == "hbm" def test_composite_returns_handle_auto_tcm(): tl = _tl() a = tl.ref(0x1000, shape=(8, 16), dtype="f16") b = tl.ref(0x2000, shape=(16, 8), dtype="f16") out = tl.composite(op="gemm", a=a, b=b) # out omitted → auto TCM assert isinstance(out, TensorHandle) assert out.space == "tcm" assert out.addr >= 0x200000 # auto-allocated scratch assert out.shape == (8, 8) def test_composite_returns_the_explicit_out_handle(): tl = _tl() a = tl.ref(0x1000, shape=(8, 16), dtype="f16") b = tl.ref(0x2000, shape=(16, 8), dtype="f16") O = TensorHandle(id="O", addr=0x5000, shape=(8, 8), dtype="f16", nbytes=128, space="tcm", pinned=True) out = tl.composite(op="gemm", a=a, b=b, out=O) assert out is O def test_composite_output_chains_into_next_op(): tl = _tl() a = tl.ref(0x1000, shape=(8, 16), dtype="f16") b = tl.ref(0x2000, shape=(16, 8), dtype="f16") scores = tl.composite(op="gemm", a=a, b=b) # handle out2 = tl.composite(op="math", a=scores, math_op="exp") assert isinstance(out2, TensorHandle) def test_wait_accepts_tensor_handle(): tl = _tl() a = tl.ref(0x1000, shape=(8, 16), dtype="f16") b = tl.ref(0x2000, shape=(16, 8), dtype="f16") out = tl.composite(op="gemm", a=a, b=b) tl.wait(out) # must not raise def test_output_space_drives_dma_write(): from kernbench.components.builtin.pe_types import StageType from kernbench.components.builtin.tiling import generate_plan_from_ops a = TensorHandle(id="a", addr=0x1000, shape=(64, 64), dtype="f16", nbytes=8192, space="tcm", pinned=True) b = TensorHandle(id="b", addr=0x2000, shape=(64, 64), dtype="f16", nbytes=8192, space="hbm") def _gemm(out): return OpSpec(kind="gemm", scope=Scope.OUTPUT_TILE, operands={"a": a, "b": b}, out=out, extra={"m": 64, "k": 64, "n": 64}) o_tcm = TensorHandle(id="ot", addr=0x300000, shape=(64, 64), dtype="f16", nbytes=8192, space="tcm", pinned=True) o_hbm = TensorHandle(id="oh", addr=0x9000, shape=(64, 64), dtype="f16", nbytes=8192, space="hbm") def _has_write(ops): plan = generate_plan_from_ops(ops, tile_m=64, tile_k=64, tile_n=64, bytes_per_element=2, pe_prefix="sip0.cube0.pe0") return any(s.stage_type == StageType.DMA_WRITE for s in plan.tiles[0].stages) assert not _has_write((_gemm(o_tcm),)), "tcm out → no DMA_WRITE" assert _has_write((_gemm(o_hbm),)), "hbm out → DMA_WRITE"