Skip to content

perf(a2a3/tmr): shrink dispatch cold-write cost on the sync_start drain#1328

Merged
ChaoWao merged 2 commits into
hw-native-sys:mainfrom
ChaoWao:perf/tmr-dispatch-drain-cold-writes
Jul 11, 2026
Merged

perf(a2a3/tmr): shrink dispatch cold-write cost on the sync_start drain#1328
ChaoWao merged 2 commits into
hw-native-sys:mainfrom
ChaoWao:perf/tmr-dispatch-drain-cold-writes

Conversation

@ChaoWao

@ChaoWao ChaoWao commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

What

The sync_start drain (drain_worker_dispatch) launches every block of the barriered task serially on one elected scheduler thread, so its per-subtask cost is dominated by writes to cold, per-core structures. This shrinks that cost by taking writes off the dispatch path, folding the rest onto one cache line, and offloading what can be offloaded.

Changes

  • PTO2DispatchPayload re-layout (still 512 B). All per-dispatch control writes now land on cache line 0 (function_bin_addr + local_context + src_payload). The not_ready gate is folded into src_payload (0 = ready, non-zero = gated and the source PTO2TaskPayload address — a payload pointer is never 0, so the flag is lossless). Cold global_context moves to the tail; the explicit ABI pad is dropped (alignas(64) rounds to 512).
  • Init-time prefill. async_ctx's slab pointers + capacity and the two context-pointer args (args[48]/[49]) are per-(core,buf_idx) constants — set once in init() instead of every dispatch, so build_payload writes only CL0.
  • deferred_slab reset moved off the dispatch path. Cleared once at init, then re-cleared on the completion path only after a task actually recorded a deferred completion (count > 0, rare). This removes a large, sparse (~222 KB, TLB-heavy) cold write from every dispatch — the biggest single win.
  • AICore-side arg materialization on the gated path. When a task is gated the AICPU writes only src_payload; the idle AICore fills args[0..num_args) from the source payload during its doorbell wait. Cross-toolchain offsets into PTO2TaskPayload are pinned with static_asserts.
  • Software-pipelined prefetch of the next block's cold per-core structures during the drain (read-prefetch CoreExecState first, then the dispatch buffer).

Measurement

Instrumented, on pypto-lib qwen3_14b 2-layer decode: sync_start drain burst ~38.9 µs → ~12 µs/drain. The build_payload path is shared with normal dispatch, so it benefits too. (The AICore arg-fill only helps the gated/early-dispatch path, ~8% of dispatches here.)

Correctness

  • tmr onboard ST suite passes — 22 tests incl all spmd_sync_start* (drain), SPMD multiblock, paged attention, basics.
  • decode_fwd --validate-fwd sample-match 16/16, logits within tolerance, across repeated runs (deferred-completion path exercised).

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 36b8eeae-4d5b-4b8d-b883-68b6bd790cfe

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The dispatch protocol replaces the not_ready flag with a source payload address for gated tasks. The scheduler preinitializes reusable completion state, ready tasks populate arguments directly, and AICore materializes gated arguments before continuing execution.

Changes

Dispatch protocol and execution flow

Layer / File(s) Summary
Payload layout and ABI contract
src/a2a3/runtime/tensormap_and_ringbuffer/runtime/pto2_dispatch_payload.h, src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_dispatch.cpp
Defines fixed PTO2TaskPayload offsets, replaces not_ready with src_payload, and adds layout assertions for the shared ABI.
Scheduler payload preparation
src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_cold_path.cpp, src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_context.h, src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_dispatch.cpp, src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_completion.cpp
Prewires async completion slabs and context arguments, populates ready-path args directly, and resets deferred slabs after completion.
AICore dispatch and scheduler prefetch
src/a2a3/runtime/tensormap_and_ringbuffer/runtime/aicore/aicore_executor.cpp, src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_context.h, src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_completion.cpp, docs/investigations/*
Prefetches destination dispatch structures, copies gated tensor and scalar arguments on AICore, and documents measurements for the all-dispatch offload experiment.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SchedulerContext
  participant PTO2DispatchPayload
  participant aicore_execute
  SchedulerContext->>PTO2DispatchPayload: Populate args or set src_payload
  SchedulerContext->>PTO2DispatchPayload: Publish dispatch
  aicore_execute->>PTO2DispatchPayload: Check src_payload
  aicore_execute->>PTO2DispatchPayload: Materialize gated args
  aicore_execute->>PTO2DispatchPayload: Continue doorbell and acknowledgement flow
Loading

Possibly related PRs

Poem

A rabbit watched the payload fly,
With args tucked neatly by.
Gated hops wait by the bell,
Ready hops race as well.
The cache lines softly sing—
Spring dispatch takes wing!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main performance change around reducing cold-write cost on the sync_start drain.
Description check ✅ Passed The description is directly related to the code changes and measurement results in the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request optimizes the task dispatch path by offloading kernel argument materialization to the idle AICore on the gated path, restructuring the PTO2DispatchPayload cache layout, and prefetching destination structures. Feedback focuses on adding bounds checks: first, validating tensor_count and scalar_count in aicore_executor.cpp to prevent out-of-bounds writes to args; second, validating that claim does not exceed the size of the claimed array in scheduler_completion.cpp.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

ChaoWao added a commit to ChaoWao/simpler-fork that referenced this pull request Jul 11, 2026
…eady path

Records the experiment behind hw-native-sys#1328's asymmetric design (AICPU fills args
for ready tasks, offloads only gated/early-dispatch ones): making the AICore
fill its own args[] for every task adds ~1.0 us to each task's receive->start
setup (paged_attention_unroll: 349 ns -> 1356 ns), because a ready task has
no idle doorbell gate to hide the fill. Offload only pays off on the not_ready
path, where the AICore is already spinning at the gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_cold_path.cpp (1)

1014-1019: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stale init comment contradicts the new clearing model.

This block still states build_payload sets not_ready and that deferred_slab->count/error_code are reset inline on every dispatch. After this PR, not_ready is gone (folded into src_payload) and the slab is cleared once here plus re-cleared only in the completion path. The deinit comment (Lines 1078-1086) was updated; this one was missed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_cold_path.cpp`
around lines 1014 - 1019, Update the stale initialization comment near the
scheduler dispatch setup to reflect the current clearing model: remove
references to build_payload setting not_ready and resetting deferred_slab
count/error_code on every dispatch, and document that the slab is cleared once
during initialization and re-cleared in the completion path while src_payload
carries the readiness state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_cold_path.cpp`:
- Around line 1014-1019: Update the stale initialization comment near the
scheduler dispatch setup to reflect the current clearing model: remove
references to build_payload setting not_ready and resetting deferred_slab
count/error_code on every dispatch, and document that the slab is cleared once
during initialization and re-cleared in the completion path while src_payload
carries the readiness state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8464271f-de35-4a5d-8f15-e17e954ad8cc

📥 Commits

Reviewing files that changed from the base of the PR and between 9c752a7 and bd908c8.

📒 Files selected for processing (8)
  • docs/investigations/2026-07-aicore-fills-all-args-ready-path.md
  • docs/investigations/README.md
  • src/a2a3/runtime/tensormap_and_ringbuffer/aicore/aicore_executor.cpp
  • src/a2a3/runtime/tensormap_and_ringbuffer/runtime/pto2_dispatch_payload.h
  • src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_cold_path.cpp
  • src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_completion.cpp
  • src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_context.h
  • src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_dispatch.cpp

ChaoWao and others added 2 commits July 11, 2026 02:55
The sync_start drain launches every block of the barriered task serially on
one elected scheduler thread, so its per-subtask cost is dominated by writes
to cold, per-core structures. Cut that cost by removing writes from the
dispatch path, folding the remaining ones onto one cache line, and offloading
what can be offloaded:

- PTO2DispatchPayload re-layout (still 512B): all per-dispatch control writes
  now land on cache line 0 (function_bin_addr + local_context + src_payload).
  The not_ready gate is folded into src_payload (0 = ready, non-zero = gated
  AND the source PTO2TaskPayload address — a payload pointer is never 0, so the
  flag is lossless), and the cold global_context moves to the tail. The
  explicit ABI pad is dropped (alignas(64) rounds to 512).

- Init-time prefill: async_ctx's slab pointers + capacity and the two
  context-pointer args (args[48]/[49]) are per-(core,buf_idx) constants — set
  once in init() instead of every dispatch, so build_payload writes only CL0.

- deferred_slab reset moved off the dispatch path: cleared once at init, then
  re-cleared on the completion path only after a task actually recorded a
  deferred completion (count > 0), which is rare. This removes a large, sparse
  (~222 KB, TLB-heavy) cold write from every dispatch — the biggest single win.

- AICore-side arg materialization on the gated path: when a task is gated the
  AICPU writes only src_payload and the idle AICore fills args[0..num_args)
  from the source payload during its doorbell wait. Cross-toolchain offsets
  into PTO2TaskPayload are pinned with static_asserts.

- Software-pipelined prefetch of the next block's cold per-core structures
  during the drain (read-prefetch CoreExecState first, then the dispatch
  buffer; write-prefetch measured to help on this shallow-store-buffer core).

Measured (instrumented) on pypto-lib qwen3_14b 2-layer decode: sync_start
drain burst ~38.9 us -> ~12 us/drain. The same build_payload path is shared
with normal dispatch, so it benefits too.

Correctness: tmr onboard ST suite passes (22 tests incl all spmd_sync_start*,
SPMD multiblock, paged attention); decode_fwd --validate-fwd sample-match
16/16 with logits within tolerance.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eady path

Records the experiment behind hw-native-sys#1328's asymmetric design (AICPU fills args
for ready tasks, offloads only gated/early-dispatch ones): making the AICore
fill its own args[] for every task adds ~1.0 us to each task's receive->start
setup (paged_attention_unroll: 349 ns -> 1356 ns), because a ready task has
no idle doorbell gate to hide the fill. Offload only pays off on the not_ready
path, where the AICore is already spinning at the gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ChaoWao ChaoWao force-pushed the perf/tmr-dispatch-drain-cold-writes branch from bd908c8 to d9f1d97 Compare July 11, 2026 09:56
@ChaoWao ChaoWao merged commit 752559c into hw-native-sys:main Jul 11, 2026
15 checks passed
@ChaoWao ChaoWao deleted the perf/tmr-dispatch-drain-cold-writes branch July 11, 2026 10:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant