gqa(adr-0065): P3 — flat-ops PE_SCHEDULER plan (position-scan + prologue stages)

tiling.generate_plan_from_ops: scan flat ops for the GEMM (<=1); pre-GEMM KERNEL ops become single-shot prologue MATH stages, post-GEMM ops split by scope (K_TILE/OUTPUT_TILE epilogue + KERNEL post-loop). MATH-only composite reproduces the legacy math-head plan. Prologue/post-loop stages fold into the first/last tile so the feeder + completion counting are untouched (existing benches have neither -> byte-equal op_log).

PipelinePlan gains prologue_stages/epilogue_stages. pe_scheduler._generate_plan delegates to generate_plan_from_ops. DMA keeps the existing pinned signal (NOT a space flip); recipe scratch/primary-out handles are pinned=True so the head GEMM's auto-bound a (=P) is consumed in place.

ADR-0065 D4/D6.7/Test#5 amended (EN+KO): DMA decision from pinned, not space; prologue recipe ops TCM-only (head op exempt -- it may stream from HBM).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-10 20:24:33 -07:00
parent 184f654295
commit 55f025c4b1
7 changed files with 304 additions and 90 deletions
@@ -147,45 +147,17 @@ class PeSchedulerComponent(ComponentBase):
yield self.out_ports[first_stage.component].put(token)
def _generate_plan(self, cmd: Any) -> Any:
"""Generate a PipelinePlan from CompositeCmd."""
from kernbench.components.builtin.tiling import (
generate_gemm_plan,
generate_math_plan,
"""Generate a PipelinePlan from a flat-ops CompositeCmd (ADR-0065 D3).
Position-scan for the GEMM (≤1) + prologue/epilogue placement lives
in ``generate_plan_from_ops``; a legacy composite (GEMM at index 0,
no prologue) produces the identical plan it did before.
"""
from kernbench.components.builtin.tiling import generate_plan_from_ops
return generate_plan_from_ops(
ops=cmd.ops,
tile_m=self.TILE_M, tile_k=self.TILE_K, tile_n=self.TILE_N,
bytes_per_element=2, # f16
pe_prefix=self._pe_prefix,
)
pp = self._pe_prefix
bpe = 2 # default bytes per element (f16)
# Flat-ops (ADR-0065 D1): ops[0] is the head, ops[1:] are epilogue
# specs placed by scope. The head's kind selects the engine path.
head = cmd.ops[0]
epi_specs = tuple(cmd.ops[1:])
if head.kind == "gemm" and "b" in head.operands:
a = head.operands["a"]
b = head.operands["b"]
M, K = a.shape[-2], a.shape[-1]
N = b.shape[-1]
return generate_gemm_plan(
M=M, K=K, N=N,
tile_m=self.TILE_M, tile_k=self.TILE_K, tile_n=self.TILE_N,
bytes_per_element=bpe,
A_addr=a.addr, B_addr=b.addr, C_addr=head.out.addr,
pe_prefix=pp,
a_pinned=getattr(a, "pinned", False),
b_pinned=getattr(b, "pinned", False),
epilogue_specs=epi_specs,
)
else:
# Math composite
a = head.operands["a"]
M = a.shape[-2] if len(a.shape) >= 2 else a.shape[0]
N = a.shape[-1] if len(a.shape) >= 2 else 1
return generate_math_plan(
M=M, N=N,
tile_m=self.TILE_M, tile_n=self.TILE_N,
bytes_per_element=bpe,
math_op=head.extra.get("math_op") or "identity",
src_addr=a.addr, dst_addr=head.out.addr,
pe_prefix=pp,
)