perf(cost-model): D8 single-op-cmd fast-path (FIXED=8 single-op / 40 composite)
ADR-0064 D8 broadened from "DMA fast-path" to "single-op-cmd fast-path": every single-op command (DmaRead/DmaWrite/Gemm/Math/Copy) now pays the lighter FIXED=8; only CompositeCmd keeps the 40-cycle control-path FIXED (it alone needs scheduler plan generation + per-tile RW-hazard tracking + completion wiring). Renamed knob fixed_per_dma_cmd_cycles -> fixed_per_single_op_cmd_cycles. dispatch_cycles now branches on "is CompositeCmd" rather than enumerating DMA types. Term choice: "single-op" (not "atomic", which read as sync/async) — the axis is composition (one engine op vs fused multi-op plan), orthogonal to timing. single-op <-> composite. Tests: test_pe_cost_model.py updated to the single-op surface (defaults, fast-path over all 5 single-op cmd types, composite general path, yaml override). All green. Recalibrated tests/attention/test_gqa_decode_opt2.py ::test_opt3_dispatch_exceeds_opt2 — NOT a regression: D8 makes single-op cmds 5x cheaper, so opt2's two-composite fusion win over opt3's many single-ops narrowed from pre-D8 ~3.7x to ~1.87x (opt3=224 > opt2=120). The CPU-offload invariant (opt2 cheaper) still holds; only the model- dependent ">2x" constant was over-fit to the old uniform-40 model. Gate now: direction + >1.5x margin (matches sibling R-sweep test's stated "absolute ratio informative-only" philosophy). NOTE for review: ADR-0065's "2x CPU-offload win" headline may want a refresh to reflect the post-D8 ~1.87x — left to user (architectural doc). Full regression: 826 passed, 1 skipped (tests/ excl. tests/gemm). --- Remaining work (resume here if interrupted) --- 5. Re-run scripts/paper/paper_plot_gemm_async_vs_composite.py with new cost model; verify async-tiled dispatch overhead drops (~4576ns -> ~1536ns expected) and the composite-vs-async-tiled gap narrows from the prior ~6.3x at K=3072. 6. Copy regenerated gemm_composite_vs_async_tflops.png to docs/report/1H-codesign-paper/figures/. 7. Paper §3.4 (03-gemm.tex sec:gemm-vs-async): finish naive->async-full / chunked->async-tiled rename AND reframe FIXED_DMA wording to single-op vs composite (currently still says "lighter FIXED for DMA descriptors, FIXED_DMA=8"). Table 2 (02-platform) + §2 dispatch prose already done. 8. Paper §3.4 K=3072 corner para + mechanism #3: update dispatch breakdown to new model (96 DMA*8 + 95 single-op*8 ≈ 1.5us vs old 4.6us); update headline ratio if it changed. 9. Rebuild docs/report/1H-codesign-paper/build/main.pdf (tectonic) + verify via pdftotext. 10. Then this is the bench-harness + paper commits (Groups 2 & 3). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,20 +1,34 @@
|
||||
"""Structural PE_CPU dispatch cost model (ADR-0064 Revision 2).
|
||||
"""Structural PE_CPU dispatch cost model (ADR-0064 Revision 2, with D8
|
||||
single-op-cmd fast-path amendment).
|
||||
|
||||
Replaces the Rev1 per-op-type calibration table (``cpu_issue_cost.py``,
|
||||
removed) with a structural formula derived from each command's
|
||||
``logical_bytes`` (ADR-0064 D2):
|
||||
|
||||
dispatch_cycles(cmd) = FIXED_PER_CMD + cmd.logical_bytes * R
|
||||
dispatch_cycles(cmd) = FIXED(cmd_type) + cmd.logical_bytes * R
|
||||
|
||||
The per-command ``FIXED_PER_CMD`` term models the queue-tail update /
|
||||
MMIO-class RTT / completion-event registration; the byte term ``R`` models
|
||||
queue-write bandwidth. The primary signal the model exposes is
|
||||
**command-count reduction** (FIXED-dominated); the byte term is a secondary
|
||||
refinement (ADR-0064 Context).
|
||||
The per-command ``FIXED`` term models the queue-tail update / MMIO-class
|
||||
RTT / completion-event registration; the byte term ``R`` models queue-write
|
||||
bandwidth. The primary signal the model exposes is **command-count
|
||||
reduction** (FIXED-dominated); the byte term is a secondary refinement
|
||||
(ADR-0064 Context).
|
||||
|
||||
ADR-0064 D8 (single-op-cmd fast-path): every single-op command — the DMA
|
||||
descriptors (``DmaReadCmd`` / ``DmaWriteCmd``) and the single-op compute
|
||||
dispatches (``GemmCmd`` / ``MathCmd`` / ``CopyCmd``) — carries a
|
||||
separate, lighter FIXED than the generic control-path cost. The default
|
||||
(``fixed_per_single_op_cmd_cycles = 8``) reflects descriptor-ring-push /
|
||||
single-instruction-dispatch patterns common in modern accelerators
|
||||
(NVIDIA TMA ~1 ISA cycle, AMD AQL packet write ~5–15 cycles, generic
|
||||
Synopsys/Xilinx DMA IP ~5–20 cycles). The general control-path cost
|
||||
(``fixed_per_cmd_cycles = 40``) applies only to ``CompositeCmd`` — the
|
||||
one command that needs scheduler-side plan generation, per-tile RW-hazard
|
||||
tracking, and completion-handle wiring.
|
||||
|
||||
Knobs are cycle-domain only; cycle→ns uses the PE node's ``clock_freq_ghz``
|
||||
(ADR-0064 D3). Defaults anchor a typical 1-OpSpec GEMM composite (≈54 bytes)
|
||||
at ≈43 ns on a 16 B/cycle on-die descriptor queue.
|
||||
at ≈43 ns on a 16 B/cycle on-die descriptor queue, while a 64 B single-op
|
||||
descriptor lands at ≈12 ns.
|
||||
|
||||
A composite whose ``logical_bytes`` exceeds ``max_composite_logical_bytes``
|
||||
is rejected at emit time with a ``ValueError`` (ADR-0064 D7, revised: hard
|
||||
@@ -27,15 +41,30 @@ from dataclasses import dataclass
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PeCostModel:
|
||||
"""Cycle-domain dispatch cost knobs (ADR-0064 D3/D4)."""
|
||||
"""Cycle-domain dispatch cost knobs (ADR-0064 D3/D4/D8)."""
|
||||
|
||||
fixed_per_cmd_cycles: float = 40.0
|
||||
byte_cycles_recip: float = 0.0625 # = 16 bytes/cycle
|
||||
max_composite_logical_bytes: int = 1024 # D7 hard cap
|
||||
fixed_per_single_op_cmd_cycles: float = 8.0 # D8 single-op-cmd fast-path
|
||||
|
||||
def dispatch_cycles(self, logical_bytes: int) -> float:
|
||||
"""FIXED + logical_bytes × R (ADR-0064 D1)."""
|
||||
return self.fixed_per_cmd_cycles + logical_bytes * self.byte_cycles_recip
|
||||
def dispatch_cycles(self, cmd_type: type, logical_bytes: int) -> float:
|
||||
"""FIXED(cmd_type) + logical_bytes × R (ADR-0064 D1/D8).
|
||||
|
||||
``CompositeCmd`` uses the general control-path
|
||||
``fixed_per_cmd_cycles``; every single-op command (DmaRead/
|
||||
DmaWrite/Gemm/Math/Copy) uses the lighter
|
||||
``fixed_per_single_op_cmd_cycles``.
|
||||
"""
|
||||
# Local import avoids a hard module-load cycle between common.pe_cost_model
|
||||
# and common.pe_commands; only CompositeCmd is needed to discriminate
|
||||
# the control path from the single-op fast path.
|
||||
from kernbench.common.pe_commands import CompositeCmd
|
||||
if cmd_type is CompositeCmd:
|
||||
fixed = self.fixed_per_cmd_cycles
|
||||
else:
|
||||
fixed = self.fixed_per_single_op_cmd_cycles
|
||||
return fixed + logical_bytes * self.byte_cycles_recip
|
||||
|
||||
|
||||
DEFAULT_PE_COST_MODEL = PeCostModel()
|
||||
@@ -54,4 +83,7 @@ def from_node_attrs(attrs: dict) -> PeCostModel:
|
||||
max_composite_logical_bytes=int(
|
||||
block.get("max_composite_logical_bytes",
|
||||
d.max_composite_logical_bytes)),
|
||||
fixed_per_single_op_cmd_cycles=float(
|
||||
block.get("fixed_per_single_op_cmd_cycles",
|
||||
d.fixed_per_single_op_cmd_cycles)),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user