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:
@@ -89,11 +89,23 @@ def _dispatch_cycles(emitter, cost_model) -> float:
|
||||
# ── dispatch ratio (ADR-0064 Test #9 / ADR-0065 Test #7) ─────────────
|
||||
|
||||
|
||||
def test_opt3_dispatch_exceeds_opt2_by_2x():
|
||||
def test_opt3_dispatch_exceeds_opt2():
|
||||
"""ADR-0064 Test #9 / ADR-0065 Test #7 — fusing opt3's per-tile
|
||||
primitives into opt2's two composites lowers dispatch cost (the
|
||||
CPU-offload win).
|
||||
|
||||
ADR-0064 D8 (single-op fast-path) narrowed this win: single-op
|
||||
commands now pay FIXED=8 while a composite pays FIXED=40, so opt2's
|
||||
two composites no longer dominate opt3's many (now cheap) single-ops
|
||||
by the pre-D8 2x. At the default model opt2 is still ~46% cheaper
|
||||
(opt3 ≈ 1.87x opt2); the invariant that survives recalibration is the
|
||||
direction plus a >1.5x margin. The sibling R-sweep test gates the
|
||||
formula-level (direction + monotonicity) claim.
|
||||
"""
|
||||
opt3 = _dispatch_cycles(_opt3_tile, DEFAULT_PE_COST_MODEL)
|
||||
opt2 = _dispatch_cycles(_opt2_tile, DEFAULT_PE_COST_MODEL)
|
||||
assert opt2 > 0 and opt3 > 0
|
||||
assert opt3 > 2 * opt2, f"opt3={opt3} must exceed 2x opt2={opt2}"
|
||||
assert opt3 > 1.5 * opt2, f"opt3={opt3} must exceed 1.5x opt2={opt2}"
|
||||
|
||||
|
||||
def test_dispatch_ratio_R_sensitivity():
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Rev2 replaces the Rev1 per-op-type cost table (``cpu_issue_cost.py``,
|
||||
``DEFAULT_CPU_ISSUE_COST``) with a structural formula
|
||||
|
||||
dispatch_cycles(cmd) = FIXED_PER_CMD + cmd.logical_bytes * R
|
||||
dispatch_cycles(cmd) = FIXED(cmd_type) + cmd.logical_bytes * R
|
||||
|
||||
where ``logical_bytes`` is a structural property of each Pe command
|
||||
(ADR-0064 D2). Tests are written against the *formula* (D1) and the byte
|
||||
@@ -12,8 +12,9 @@ rule (D2), not specific absolute latencies, so they survive recalibration.
|
||||
Assumed post-Phase-2 surface:
|
||||
- ``kernbench.common.pe_cost_model`` exports ``PeCostModel``
|
||||
(fields: ``fixed_per_cmd_cycles``, ``byte_cycles_recip``,
|
||||
``max_composite_logical_bytes``; method ``dispatch_cycles(lb)``) and
|
||||
``DEFAULT_PE_COST_MODEL`` (FIXED=40, R=0.0625, cap=1024).
|
||||
``max_composite_logical_bytes``, ``fixed_per_single_op_cmd_cycles``;
|
||||
method ``dispatch_cycles(cmd_type, lb)``) and
|
||||
``DEFAULT_PE_COST_MODEL`` (FIXED=40, FIXED_SINGLE_OP=8, R=0.0625, cap=1024).
|
||||
- ``OpSpec``/``CompositeCmd``/``DmaReadCmd``/… expose ``logical_bytes``.
|
||||
- ``TLContext(cost_model=...)`` charges ``FIXED + lb*R`` (÷ clock) as a
|
||||
``PeCpuOverheadCmd`` before each dispatched command; ``tl.cycles(n)``
|
||||
@@ -67,15 +68,59 @@ def test_cost_model_defaults():
|
||||
assert m.fixed_per_cmd_cycles == 40
|
||||
assert m.byte_cycles_recip == 0.0625 # = 16 bytes/cycle
|
||||
assert m.max_composite_logical_bytes == 1024
|
||||
assert m.fixed_per_single_op_cmd_cycles == 8 # D8 single-op-cmd fast-path
|
||||
|
||||
|
||||
def test_dispatch_cycles_formula():
|
||||
"""dispatch_cycles(lb) == FIXED + lb*R (ADR-0064 D1)."""
|
||||
def test_dispatch_cycles_composite_general_path():
|
||||
"""CompositeCmd is the only command that keeps the general control-path
|
||||
FIXED (= fixed_per_cmd_cycles); dispatch_cycles == FIXED + lb*R
|
||||
(ADR-0064 D1/D8)."""
|
||||
from kernbench.common.pe_cost_model import PeCostModel
|
||||
from kernbench.common.pe_commands import CompositeCmd
|
||||
|
||||
m = PeCostModel(fixed_per_cmd_cycles=40, byte_cycles_recip=0.0625)
|
||||
assert m.dispatch_cycles(54) == pytest.approx(43.375) # D3 anchor
|
||||
assert m.dispatch_cycles(0) == 40
|
||||
assert m.dispatch_cycles(CompositeCmd, 54) == pytest.approx(43.375)
|
||||
assert m.dispatch_cycles(CompositeCmd, 0) == 40
|
||||
|
||||
|
||||
def test_dispatch_cycles_single_op_fast_path():
|
||||
"""ADR-0064 D8: every single-op command (DmaRead/DmaWrite/Gemm/Math/
|
||||
Copy) uses fixed_per_single_op_cmd_cycles instead of the general
|
||||
fixed_per_cmd_cycles. Only CompositeCmd is excluded."""
|
||||
from kernbench.common.pe_cost_model import PeCostModel
|
||||
from kernbench.common.pe_commands import (
|
||||
DmaReadCmd, DmaWriteCmd, GemmCmd, MathCmd, CopyCmd,
|
||||
)
|
||||
|
||||
m = PeCostModel(
|
||||
fixed_per_cmd_cycles=40,
|
||||
fixed_per_single_op_cmd_cycles=8,
|
||||
byte_cycles_recip=0.0625,
|
||||
)
|
||||
for T in (DmaReadCmd, DmaWriteCmd, GemmCmd, MathCmd, CopyCmd):
|
||||
assert m.dispatch_cycles(T, 64) == pytest.approx(12.0)
|
||||
assert m.dispatch_cycles(T, 0) == 8
|
||||
|
||||
|
||||
def test_dispatch_cycles_yaml_override():
|
||||
"""ADR-0064 D4 + D8: pe_cost_model.fixed_per_single_op_cmd_cycles in
|
||||
topology attrs overrides the default; single-op cmds use it while
|
||||
CompositeCmd uses the general fixed_per_cmd_cycles."""
|
||||
from kernbench.common.pe_cost_model import from_node_attrs
|
||||
from kernbench.common.pe_commands import GemmCmd, CompositeCmd
|
||||
|
||||
attrs = {
|
||||
"pe_cost_model": {
|
||||
"fixed_per_cmd_cycles": 50,
|
||||
"fixed_per_single_op_cmd_cycles": 5,
|
||||
"byte_cycles_recip": 0.125,
|
||||
},
|
||||
}
|
||||
m = from_node_attrs(attrs)
|
||||
assert m.fixed_per_cmd_cycles == 50
|
||||
assert m.fixed_per_single_op_cmd_cycles == 5
|
||||
assert m.dispatch_cycles(GemmCmd, 0) == 5
|
||||
assert m.dispatch_cycles(CompositeCmd, 0) == 50
|
||||
|
||||
|
||||
# ── logical_bytes byte rule (D2 / D3 worked example) ─────────────────
|
||||
@@ -146,7 +191,10 @@ def test_tlcontext_charges_formula_per_command():
|
||||
|
||||
tl = TLContext(
|
||||
pe_id=0, num_programs=1,
|
||||
cost_model=PeCostModel(fixed_per_cmd_cycles=100, byte_cycles_recip=0.0),
|
||||
cost_model=PeCostModel(
|
||||
fixed_per_cmd_cycles=100, fixed_per_single_op_cmd_cycles=100,
|
||||
byte_cycles_recip=0.0,
|
||||
),
|
||||
)
|
||||
tl.load(0x1000, shape=(4, 4), dtype="f16")
|
||||
overheads = [c for c in tl.commands if isinstance(c, PeCpuOverheadCmd)]
|
||||
|
||||
Reference in New Issue
Block a user