gqa(adr-0064/0065): flat-ops CompositeCmd (P1) + structural dispatch cost (ADR-0064 Rev2); promote ADR-0064

ADR-0065 P1: CompositeCmd -> flat ordered ops list (drop legacy op/a/b/out_addr fields); OpSpec.operands dict + out handle. Meaning-preserving (op_log byte-equal); pe_scheduler + op_log read the head op.

ADR-0064 Rev2: replace Rev1 per-op cost table with structural FIXED + logical_bytes*R formula. logical_bytes on every PeCommand; new common/pe_cost_model.py; cost centralized in TLContext._emit (load/recv_async charge explicitly); pe_cpu/kernel_runner wire the per-PE model + clock. D7: cap exceeded -> ValueError (no auto-segmentation). Remove Rev1 cpu_issue_cost.py + its tests. No goldens churn.

Promote ADR-0064 Rev2 Proposed->Accepted (docs/adr/ + docs/adr-ko/); amend D7 (error not segmentation) + record P1-before-P0 ordering in ADR-0064/0065 Migration notes (EN+KO).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-10 19:18:04 -07:00
parent 79ddb12b42
commit 47e2c78c66
18 changed files with 703 additions and 550 deletions
+72 -12
View File
@@ -10,7 +10,7 @@ from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
from typing import TYPE_CHECKING, Any, Literal
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
import simpy
@@ -22,6 +22,24 @@ class Scope(Enum):
KERNEL = "kernel"
def _extra_bytes(v: Any) -> int:
"""Type-aware HW-logical byte count for an OpSpec.extra value (ADR-0064 D2).
bool→1, int/float→4 (scalar), tuple/list→1 + 4·len (length marker + 4
per element, e.g. shape/axes), str→1 (opcode-like tag). Default→4.
``bool`` is checked first because it subclasses ``int``.
"""
if isinstance(v, bool):
return 1
if isinstance(v, (int, float)):
return 4
if isinstance(v, (tuple, list)):
return 1 + 4 * len(v)
if isinstance(v, str):
return 1
return 4
@dataclass(frozen=True)
class OpSpec:
"""One operation in a multi-op composite (head + epilogue, ADR-0014 D3.3).
@@ -33,9 +51,22 @@ class OpSpec:
kind: str # "gemm" | "bias" | "relu" | ...
scope: "Scope" = Scope.OUTPUT_TILE
operands: tuple[Any, ...] = () # tuple[TensorHandle, ...]
operands: dict[str, Any] = field(default_factory=dict) # name → TensorHandle
scalar: float | None = None
extra: dict[str, Any] = field(default_factory=dict)
out: "TensorHandle | None" = None # explicit write-back handle
@property
def logical_bytes(self) -> int:
"""HW-logical byte size (ADR-0064 D2). ``scalar`` is a transitional
field not part of the flat-ops model (ADR-0065 D2) — excluded,
pending its removal."""
return (
1 + 1 # opcode + scope enum
+ 1 + 8 * len(self.operands) # len marker + handles
+ (8 if self.out is not None else 0) # out handle
+ 1 + sum(_extra_bytes(v) for v in self.extra.values())
)
# Epilogue op contracts: kind → (required field names, default scope).
@@ -103,6 +134,10 @@ class DmaReadCmd:
nbytes: int
data_op: bool = True
@property
def logical_bytes(self) -> int:
return 4 + 8 + 4 + 4 # framing + handle + src_addr + nbytes
@dataclass(frozen=True)
class DmaWriteCmd:
@@ -113,6 +148,10 @@ class DmaWriteCmd:
nbytes: int
data_op: bool = True
@property
def logical_bytes(self) -> int:
return 4 + 8 + 4 + 4 # framing + handle + dst_addr + nbytes
@dataclass(frozen=True)
class GemmCmd:
@@ -129,6 +168,10 @@ class GemmCmd:
n: int
data_op: bool = True
@property
def logical_bytes(self) -> int:
return 4 + 8 * 3 + 4 * 3 # framing + 3 handles + m/k/n scalars
@dataclass(frozen=True)
class MathCmd:
@@ -145,6 +188,13 @@ class MathCmd:
axis: int | None = None # for reductions
data_op: bool = True
@property
def logical_bytes(self) -> int:
return (
4 + 1 + 1 + 8 * len(self.inputs) + 8 # framing+opcode+len+inputs+out
+ (4 if self.axis is not None else 0)
)
@dataclass(frozen=True)
class CopyCmd:
@@ -161,6 +211,10 @@ class CopyCmd:
nbytes: int
data_op: bool = True
@property
def logical_bytes(self) -> int:
return 4 + 8 * 2 + 4 # framing + src/dst handles + nbytes
@dataclass(frozen=True)
class CompositeCmd:
@@ -168,20 +222,26 @@ class CompositeCmd:
Non-blocking — submitted to PE_SCHEDULER which manages tile splitting
and pipeline overlaps (ADR-0014 D3.2).
Flat-ops shape (ADR-0065 D1): ``ops`` is an ordered list of OpSpecs.
The GEMM op (if any, ≤1) drives the tile loop; preceding/following
OpSpecs are placed by position + scope. ``rw_handles`` carries
cross-composite hazard metadata (ADR-0065 D6.3); unused in P1.
"""
completion: CompletionHandle
op: Literal["gemm", "math"]
a: TensorHandle
b: TensorHandle | None
out_addr: int
out_nbytes: int
math_op: str | None = None # for op="math": which math operation
data_op: bool = True
# Multi-op composite (ADR-0014 D3.3): when non-empty, ops[0] is the
# head and ops[1:] are epilogue stages with explicit scope. When empty,
# the legacy single-op semantics (op/a/b/math_op) apply.
ops: tuple[OpSpec, ...] = ()
rw_handles: tuple["TensorHandle", ...] = ()
data_op: bool = True
@property
def logical_bytes(self) -> int:
"""HW-logical byte size (ADR-0064 D2). Per-op summation, no dedup."""
return (
4 # framing
+ 1 + sum(op.logical_bytes for op in self.ops)
+ 1 + 8 * len(self.rw_handles)
)
@dataclass(frozen=True)