Skip to content

perf: prefetch dispatch buffers and inline timestamp reads#1243

Open
TaoZQY wants to merge 2 commits into
hw-native-sys:mainfrom
TaoZQY:analysis/issue-1103-perf
Open

perf: prefetch dispatch buffers and inline timestamp reads#1243
TaoZQY wants to merge 2 commits into
hw-native-sys:mainfrom
TaoZQY:analysis/issue-1103-perf

Conversation

@TaoZQY

@TaoZQY TaoZQY commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Add two small dispatch-path optimizations from issue #1103:

  1. Write-prefetch per-core dispatch buffers before initializing them.
  2. Inline the AICPU dispatch timestamp counter read near the doorbell publish path.

The prefetch change warms:

  • the first cache line of PTO2DispatchPayload
  • the second cache line of PTO2DispatchPayload
  • the associated DeferredCompletionSlab

The fast-counter change adds platform-selected device_time_fast.h helpers:

  • onboard AICPU: inline mrs cntvct_el0
  • sim AICPU: fall back to get_sys_cnt_aicpu() so CPU-sim keeps its chrono-backed tick scale

The changes are applied to both a2a3 and a5 dispatch paths. They do not change ABI layout, task state transitions, ring-buffer publishing, or completion semantics.

Motivation

Issue #1103 tracks remaining dispatch-path optimizations from the scheduler optimization branch. The AICPU scheduler writes per-core dispatch payloads and deferred completion slabs on every subtask dispatch. These buffers can be cold, so the first writes may pay cache-line ownership/RFO latency. Pulling ownership slightly earlier can hide part of that cost behind the existing dispatch setup work.

For L2 swimlane AICPU timing, dispatch timestamps are sampled immediately before publishing the DATA_MAIN_BASE doorbell. The onboard implementation of get_sys_cnt_aicpu() already reads cntvct_el0, but it is an out-of-line function call. Inlining this read at the dispatch timestamp sites reduces profiling overhead and keeps the timestamp closer to the doorbell write.

Test

Built and installed the branch:

PTO_ISA_ROOT=/data/pyptouser/zhangtao/zt/simpler/build/pto-isa python -m pip install --no-build-isolation .

Hardware tests submitted through task-submit:

task-submit --device auto --device-num 1 --max-time 3600 --timeout 7200 --run "bash /data/pyptouser/zhangtao/zt/issue-1103-prefetch-results/run_after.sh"
task-submit --device auto --device-num 1 --max-time 3600 --timeout 7200 --run "bash /data/pyptouser/zhangtao/zt/issue-1103-prefetch-results/run_fastcnt.sh"

Task ids:

prefetch: task_20260701_193641_390821221439
fastcnt:  task_20260701_200145_44514130629

All tested cases passed.

Default performance comparison, baseline -> prefetch:

Case Rounds Baseline Sched After Sched Delta
spmd_multiblock_mix 20 72.0 us 73.0 us +1.0 us / +1.39%
spmd_sync_start_stress 10 397.8 us 389.4 us -8.4 us / -2.11%
qwen3_14b_decode 5 2113.5 us 2110.8 us -2.7 us / -0.13%

Default performance comparison, prefetch -> prefetch + fast counter:

Case Rounds Prefetch Sched Fast counter Sched Delta
spmd_multiblock_mix 20 73.0 us 71.1 us -1.9 us / -2.60%
spmd_sync_start_stress 10 389.4 us 390.8 us +1.4 us / +0.29%
qwen3_14b_decode 5 2110.8 us 2108.9 us -2.1 us / -0.10%

L2 swimlane AICPU timing smoke tests with --enable-l2-swimlane 2:

Case Rounds Result Sched
spmd_multiblock_mix 1 PASS 186.4 us
spmd_sync_start_stress 1 PASS 644.8 us
qwen3_14b_decode 1 PASS 2241.5 us

Note: spmd_serial_chain_mix was not present in the latest upstream tree used for this branch, so spmd_multiblock_mix was used as the available SPMD mix coverage.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds __builtin_prefetch cache hints in two scheduler dispatch implementations (a2a3 and a5 variants), warming the cache for per-core payload and deferred_slab structures before subsequent hot stores. No functional or control-flow changes are introduced.

Changes

Scheduler Dispatch Prefetching

Layer / File(s) Summary
Prefetch hints in a2a3 dispatch path
src/a2a3/.../scheduler_dispatch.cpp
prepare_subtask_to_core issues prefetch calls for payload (base, +64) and deferred_slab before hot stores that reset deferred_slab and build the dispatch payload.
Prefetch hints in a5 dispatch path
src/a5/.../scheduler_dispatch.cpp
dispatch_subtask_to_core issues prefetch calls for payload (base, +64) and deferred_slab before writing deferred_slab->count/error_code and building the dispatch payload.

Estimated code review effort: 1 (Trivial) | ~3 minutes

Possibly related PRs

  • hw-native-sys/simpler#989: Modifies the same scheduler_dispatch.cpp prepare/dispatch flow that this PR adds prefetch hints to.

Poem

A hop, a skip, a cache-line peek,
Prefetching payloads before they speak,
Deferred slabs warmed, ready and neat,
No logic changed, just cache-hop feet,
🐇✨ swift as burrow retreat!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
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.
Title check ✅ Passed The title matches the main optimization, though it mentions timestamp reads not shown in the change summary.
Description check ✅ Passed The description matches the PR’s dispatch-path prefetch optimization and is clearly related to the changeset.

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 introduces __builtin_prefetch instructions in the scheduler dispatch code for both a2a3 and a5 runtimes to pull ownership of per-core dispatch buffers (payload and deferred_slab) before performing hot stores. The reviewer suggests extending the prefetch range to cover tail cache lines (offsets 384+ and 504) that are also written to, in order to prevent RFO cache misses. Additionally, the reviewer recommends reordering the construction of async_ctx before the writes to provide an execution window that overlaps and hides the prefetch latency.

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.

Comment on lines +164 to 170
// Pull ownership of the per-core dispatch buffers before the hot stores below.
__builtin_prefetch(reinterpret_cast<const char *>(&payload), 1, 3);
__builtin_prefetch(reinterpret_cast<const char *>(&payload) + 64, 1, 3);
__builtin_prefetch(reinterpret_cast<const char *>(deferred_slab), 1, 3);
deferred_slab->count = 0;
deferred_slab->error_code = PTO2_ERROR_NONE;
AsyncCtx async_ctx = AsyncCtx::make(slot_state.task->task_id, deferred_slab);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The prefetch for payload only covers the first two cache lines (offsets 0 and 64). However, build_payload unconditionally writes to local_context (offset 384+) and not_ready (offset 504), which reside in cache lines 6 and 7. Prefetching these tail cache lines prevents RFO cache misses on these hot stores.

Additionally, deferred_slab is written to immediately after its prefetch instruction, leaving no execution cycles to hide memory latency. Moving the construction of async_ctx before the writes provides a small execution window to overlap the prefetch.

    // Pull ownership of the per-core dispatch buffers before the hot stores below.
    __builtin_prefetch(reinterpret_cast<const char *>(&payload), 1, 3);
    __builtin_prefetch(reinterpret_cast<const char *>(&payload) + 64, 1, 3);
    __builtin_prefetch(reinterpret_cast<const char *>(&payload) + 384, 1, 3);
    __builtin_prefetch(reinterpret_cast<const char *>(&payload) + 448, 1, 3);
    __builtin_prefetch(reinterpret_cast<const char *>(deferred_slab), 1, 3);

    // Construct AsyncCtx first to give prefetch instructions some cycles to run
    AsyncCtx async_ctx = AsyncCtx::make(slot_state.task->task_id, deferred_slab);
    deferred_slab->count = 0;
    deferred_slab->error_code = PTO2_ERROR_NONE;

Comment on lines +148 to 154
// Pull ownership of the per-core dispatch buffers before the hot stores below.
__builtin_prefetch(reinterpret_cast<const char *>(&payload), 1, 3);
__builtin_prefetch(reinterpret_cast<const char *>(&payload) + 64, 1, 3);
__builtin_prefetch(reinterpret_cast<const char *>(deferred_slab), 1, 3);
deferred_slab->count = 0;
deferred_slab->error_code = PTO2_ERROR_NONE;
AsyncCtx async_ctx = AsyncCtx::make(slot_state.task->task_id, deferred_slab);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The prefetch for payload only covers the first two cache lines (offsets 0 and 64). However, build_payload unconditionally writes to local_context (offset 384+), which resides in cache line 6. Prefetching this tail cache line prevents RFO cache misses on these hot stores.

Additionally, deferred_slab is written to immediately after its prefetch instruction, leaving no execution cycles to hide memory latency. Moving the construction of async_ctx before the writes provides a small execution window to overlap the prefetch.

    // Pull ownership of the per-core dispatch buffers before the hot stores below.
    __builtin_prefetch(reinterpret_cast<const char *>(&payload), 1, 3);
    __builtin_prefetch(reinterpret_cast<const char *>(&payload) + 64, 1, 3);
    __builtin_prefetch(reinterpret_cast<const char *>(&payload) + 384, 1, 3);
    __builtin_prefetch(reinterpret_cast<const char *>(deferred_slab), 1, 3);

    // Construct AsyncCtx first to give prefetch instructions some cycles to run
    AsyncCtx async_ctx = AsyncCtx::make(slot_state.task->task_id, deferred_slab);
    deferred_slab->count = 0;
    deferred_slab->error_code = PTO2_ERROR_NONE;

@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.

🧹 Nitpick comments (1)
src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_dispatch.cpp (1)

165-167: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Cache-line stride is a hardcoded magic number.

64 (cache-line size) appears as a bare literal here and is duplicated in the a5 variant. Extracting a named constant (e.g., CACHE_LINE_SIZE) in a shared header would make intent clearer and avoid silent drift if line size assumptions ever change per target.

♻️ Example
+static constexpr size_t CACHE_LINE_SIZE = 64;
...
-    __builtin_prefetch(reinterpret_cast<const char *>(&payload), 1, 3);
-    __builtin_prefetch(reinterpret_cast<const char *>(&payload) + 64, 1, 3);
+    __builtin_prefetch(reinterpret_cast<const char *>(&payload), 1, 3);
+    __builtin_prefetch(reinterpret_cast<const char *>(&payload) + CACHE_LINE_SIZE, 1, 3);
🤖 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_dispatch.cpp`
around lines 165 - 167, The prefetch logic in scheduler_dispatch.cpp uses a
hardcoded cache-line stride literal, which is duplicated across the a5 variant
and should be centralized. Introduce a shared named constant such as
CACHE_LINE_SIZE in the relevant common header, then update the prefetch calls in
the scheduler dispatch code to use that symbol instead of the bare 64 literal so
the intent stays clear and target-specific assumptions do not drift.
🤖 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.

Nitpick comments:
In
`@src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_dispatch.cpp`:
- Around line 165-167: The prefetch logic in scheduler_dispatch.cpp uses a
hardcoded cache-line stride literal, which is duplicated across the a5 variant
and should be centralized. Introduce a shared named constant such as
CACHE_LINE_SIZE in the relevant common header, then update the prefetch calls in
the scheduler dispatch code to use that symbol instead of the bare 64 literal so
the intent stays clear and target-specific assumptions do not drift.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 10dc6104-131f-42de-ad43-0ea82debb913

📥 Commits

Reviewing files that changed from the base of the PR and between 91c46a6 and 733cef9.

📒 Files selected for processing (2)
  • src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_dispatch.cpp
  • src/a5/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_dispatch.cpp

@TaoZQY TaoZQY changed the title perf: prefetch dispatch buffers before scheduler writes perf: prefetch dispatch buffers and inline timestamp reads Jul 1, 2026
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