gqa(adr-0065): D8 — composite returns output handle + output-space DMA

tl.composite now returns the output TensorHandle (not a CompletionHandle) so its result chains like tl.dot's; the handle carries the completion in a CompositeFuture pending so downstream ops and tl.wait auto-await it. out is a handle: out=tl.ref(addr,shape) (HBM, DMA_WRITE inside the composite) or an in-place TCM handle (STORE only); omitted -> TLContext auto-allocates a TCM scratch. out_ptr kept as HBM shorthand (= out=tl.ref(out_ptr, shape)) to avoid churning ~30 existing call sites.

tl.ref now returns space=hbm (it references HBM data; operand-input DMA stays pinned-based per D4 so input streaming is unchanged). tiling: the tile loop's DMA_WRITE is gated on out.space==hbm (out analog of the operand pinned rule) — a TCM output stays on-chip (chainable) and its high-bit scratch address no longer hits the DMA PA decoder.

Fixes the opt2 data-mode crash: the recipe accumulator O is TCM -> no DMA_WRITE -> opt2 now RUNS end-to-end in data mode (enable_data=True). Numeric parity of the recipe MATH ops is the next step. Suite 812 pass / 3 pre-existing fail; existing composite benches use out_ptr->hbm->DMA_WRITE unchanged (byte-equal).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-10 22:29:10 -07:00
parent dd525bfcb7
commit e8d6c283d8
7 changed files with 267 additions and 26 deletions
@@ -241,6 +241,41 @@ TLContext (compiler analog) PE_SCHEDULER (scheduler/dispatcher)
imports: recipe 관련 없음
```
### D8. Composite 출력 handle + 출력-space DMA
`tl.composite(...)` 는 **출력 `TensorHandle`** 을 반환(단순 `CompletionHandle`
아님) — composite 결과를 `tl.dot` 결과처럼 downstream op 에 먹일 수 있음.
handle 의 `pending` 필드가 composite 완료를 참조; downstream consumer 가
auto-await(`_await_pending`, ADR-0062 lazy 패턴), `tl.wait(handle)` 동작.
**`out` 은 항상 `TensorHandle`** (raw 주소 아님) — 위치는 커널의 선택, 추측 안 함:
- **HBM 출력** → `out=tl.ref(addr, shape)`. `tl.ref` 가 `space="hbm"` handle
반환 → composite 가 DMA_WRITE 로 write-back. STORE + DMA_WRITE 는 composite
파이프라인 **내부**에 유지 — fused op 의 일부이지 별도 `tl.store` 아님.
- **In-place TCM** → `out=<기존 TCM handle>` (예: recipe 누적기 `O`).
`space="tcm"` → STORE 만, 결과 TCM 에 남음 (chainable).
- **미지정** → TLContext 가 **TCM scratch 자동 할당** (`tl.dot` 과 동일) 후 반환.
편의상 `out_ptr: int` 는 **HBM 출력 shorthand** 로 허용 —
`out=tl.ref(out_ptr, <출력 shape>)` 와 동치(컴파일러가 `space="hbm"` handle 로
감쌈). 출력 미지정 시 scratch 주소는 커널이 아니라 컴파일러가 소유.
(`tl.ref` 는 `space="hbm"` 반환 — HBM 데이터 참조이므로. operand-**입력** DMA
결정은 D4 대로 `pinned` 기반이라 이 라벨이 입력 스트리밍에 영향 없음.)
출력 handle 의 **`space` 가 타일 루프의 write-back 을 결정** (D4 의 operand
`pinned` 규칙의 출력판):
| `out.space` | 타일 루프 write-back |
| --- | --- |
| `"tcm"` | STORE 만 — TCM 에 남음 (chainable; **DMA_WRITE 없음**) |
| `"hbm"` | STORE + DMA_WRITE — 최종 HBM 출력 |
이로써 출력이 TCM 에 남는 composite(예: recipe 누적기)가 합법이 되고,
TCM-scratch 주소가 DMA 엔진의 PA 디코더(고비트 scratch 주소를 거부)로 가는
일을 막음. 기존 벤치는 HBM 출력을 `tl.ref` 로 감싸므로 write-back
불변(byte-equal).
## Alternatives
### A1. tile 당 3 composite (prologue 개념 없이)
@@ -267,6 +267,46 @@ TLContext (compiler analog) PE_SCHEDULER (scheduler/dispatcher)
imports: nothing recipe-related
```
### D8. Composite output handle + output-space DMA
`tl.composite(...)` returns the **output `TensorHandle`** (not a bare
`CompletionHandle`), so a composite's result can feed a downstream op the
same way `tl.dot`'s result does. The handle's `pending` field references
the composite's completion; downstream consumers auto-await it
(`_await_pending`, ADR-0062 lazy pattern) and `tl.wait(handle)` works.
**`out` is always a `TensorHandle`** (never a raw address) — its location is
the kernel's choice, never guessed:
- **HBM output** → `out=tl.ref(addr, shape)`. `tl.ref` returns a handle with
`space="hbm"`, so the composite writes it back via DMA_WRITE. The
STORE + DMA_WRITE stay **inside** the composite pipeline — they are part of
the fused op, *not* a separate `tl.store`.
- **In-place TCM** → `out=<existing TCM handle>` (e.g. the recipe accumulator
`O`). `space="tcm"` → STORE only, result stays in TCM (chainable).
- **Omitted** → TLContext **auto-allocates a TCM scratch** (like `tl.dot`)
and returns it.
For ergonomics, `out_ptr: int` is accepted as **shorthand for an HBM
output** — equivalent to `out=tl.ref(out_ptr, <output shape>)` (the compiler
wraps it into an `space="hbm"` handle). When no output is specified the
compiler owns the scratch address, never the kernel. (`tl.ref` returns
`space="hbm"` — it references HBM data. The operand-**input** DMA decision
stays `pinned`-based per D4, so this label does not affect input streaming.)
The output handle's **`space` drives the tile loop's write-back** (the
output analog of the operand `pinned` rule in D4):
| `out.space` | tile-loop write-back |
| --- | --- |
| `"tcm"` | STORE only — result stays in TCM (chainable; **no DMA_WRITE**) |
| `"hbm"` | STORE + DMA_WRITE — final HBM output |
This makes a TCM-resident composite output (e.g. a recipe accumulator) legal
**and** keeps a TCM-scratch address out of the DMA engine's PA decoder
(which would otherwise reject the high-bit scratch address). Existing benches
wrap their HBM output via `tl.ref` → write-back unchanged (byte-equal).
## Alternatives
### A1. Three composites per tile (no prologue concept)