gqa(adr-0065): P5(B) — decode opt2 two-composite kernel + dispatch measurement
New _gqa_attention_decode_opt2.py: per-tile attention as #1 Q.Kt GEMM composite + #2 softmax_merge recipe composite (online merge + P.V + add). Runnable in op_log mode; full data-mode numeric parity is a deferred follow-up (P5-numerics). Measured per-tile PE_CPU dispatch (ADR-0064 Rev2 default): opt3=960ns vs opt2=184ns = 5.22x (gate >2x), FIXED-dominated (command-count reduction) — the ADR-0060/0064 CPU-offload win. opt2<opt3 across R in {0.25,0.0625,0.03125}. K-before-V: softmax_merge prologue carries no DMA; V (ref) streams only in the GEMM tile loop. Fix latent P3 bug surfaced by the first e2e recipe run: prologue MATH stages were FOLDED into the first GEMM tile, but a folded MATH->DMA_READ boundary needs a PE_MATH->PE_DMA token route the pipeline never wires (KeyError pe_dma). Now prologue/post-loop stages are fed as standalone 1-stage tiles (each completes on its component); completion count includes them. Existing benches have no prologue -> tiles + feed order unchanged (byte-equal); full suite 806 pass / 3 pre-existing fail. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -283,22 +283,13 @@ def generate_plan_from_ops(
|
||||
epilogue_specs=tuple(post_ops),
|
||||
)
|
||||
|
||||
pre_stages = tuple(_math_stage(o, pe_prefix) for o in pre_ops)
|
||||
post_stages = tuple(_math_stage(o, pe_prefix) for o in post_ops
|
||||
if o.scope == _Scope.KERNEL)
|
||||
plan.prologue_stages = pre_stages
|
||||
plan.epilogue_stages = post_stages
|
||||
|
||||
# Fold prologue/post-loop stages into the first/last tile so the feeder
|
||||
# and completion counting are untouched (existing benches have neither,
|
||||
# so their tiles are unchanged → byte-equal op_log).
|
||||
if pre_stages and plan.tiles:
|
||||
t0 = plan.tiles[0]
|
||||
plan.tiles[0] = TilePlan(tile_id=t0.tile_id,
|
||||
stages=(*pre_stages, *t0.stages))
|
||||
if post_stages and plan.tiles:
|
||||
tl = plan.tiles[-1]
|
||||
plan.tiles[-1] = TilePlan(tile_id=tl.tile_id,
|
||||
stages=(*tl.stages, *post_stages))
|
||||
|
||||
# Prologue (pre-GEMM KERNEL) + post-loop KERNEL ops become single-shot
|
||||
# MATH stages. They are fed as standalone 1-stage tiles by the scheduler
|
||||
# (PE_SCHEDULER._feed_loop) — NOT folded into the GEMM tiles, because a
|
||||
# folded MATH→DMA_READ boundary would require a PE_MATH→PE_DMA token
|
||||
# route that the pipeline does not wire. Existing benches have neither →
|
||||
# their tiles + feed order are unchanged (byte-equal op_log).
|
||||
plan.prologue_stages = tuple(_math_stage(o, pe_prefix) for o in pre_ops)
|
||||
plan.epilogue_stages = tuple(_math_stage(o, pe_prefix) for o in post_ops
|
||||
if o.scope == _Scope.KERNEL)
|
||||
return plan
|
||||
|
||||
Reference in New Issue
Block a user