Skip to content

[Optimization] Parallelize AICore handshake across AICPU threads -- 3~22% Device Speedup #1279

Merged
ChaoWao merged 2 commits into
hw-native-sys:mainfrom
huawei-csl:parallel-aicore-handshake
Jul 9, 2026
Merged

[Optimization] Parallelize AICore handshake across AICPU threads -- 3~22% Device Speedup #1279
ChaoWao merged 2 commits into
hw-native-sys:mainfrom
huawei-csl:parallel-aicore-handshake

Conversation

@SergioMartin86

@SergioMartin86 SergioMartin86 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Human Summary

Currently, upstream/main has a single thread handle the handshake for all compute cores. This is a fixed cost that represents a significant part of the kernel running time. This PR parallelizes the process among the other AICPU cores, obtaining an up to 22% speedup.

The effect is mostly notable on small kernels, whose fixed runtime component is more significant. This improvements is complementary to that of #1137 which improves the dynamic aspect, favoring large kernels with many small tasks.

AI Summary

Every time the runtime starts a run, it does a "roll call" of all 72 AICore
compute units — greeting each one and setting up its registers before any work
can begin. Today one AICPU thread does this roll call one core at a time,
while the other three threads sit idle waiting. That serial roll call takes
~217 µs of the ~283 µs startup on a2a3, and because it's a fixed cost paid
every single run, it hurts short jobs the most.

This PR has all four AICPU threads greet the cores in parallel, each handling
a quarter of them. Startup drops from ~283 µs to ~85 µs, which takes ~172 µs
off the device time of every run
−7.5% on qwen3-14B decode and up to
−22% on small workloads
. The actual compute is untouched; only the startup gets
faster. Correctness is unchanged (golden-verified on 8 cases).


The problem

Before any tasks run, the scheduler has to discover the AICore units it will
dispatch to. For each core it: waits for the core to boot and report in, reads
its physical ID, initializes its control registers over MMIO, and waits for the
core to acknowledge. This "handshake" happens fresh on every round (the cores
exit at the end of each round and are relaunched).

The catch: this loop ran serially on a single thread. On the Ascend AICPU,
MMIO register access is strictly serial (~95 ns per access, no pipelining — a
documented hardware constraint), so greeting 72 cores one-by-one is slow by
construction. Meanwhile the other three AICPU threads spun idle, doing nothing
but waiting for the one thread to finish.

We profiled it directly: of the ~283 µs startup ("preamble") on qwen3-14B decode,
~217 µs (82%) was the serial per-core handshake work. Only ~11 µs was
unavoidable (waiting for the first core to physically boot).

Because this cost is fixed per round, it's a small slice of a big job but a
huge slice of a small one — e.g. ~26% of a simple matmul's device time, and it
was a major contributor to why small workloads looked slow.

The fix

The hardware guidance is explicit: the only way to speed up cross-core polling
is to use more threads
. So we do exactly that.

SchedulerContext::init is split into three phases:

  1. pre_handshake_init (leader thread only): the shared setup — zero the
    per-core state, wire configuration, initialize profiling/swimlane buffers,
    and record the core count. Published to the other threads via a flag.
  2. handshake_partition (all four threads): each thread greets a disjoint,
    contiguous slice of the cores (18 each for 72). Because the slices don't
    overlap, there's no contention — the expensive MMIO work now runs four-wide.
  3. post_handshake_init (leader thread only, after a barrier): the cheap
    bookkeeping — build the ordered AIC/AIV worker lists, assign cores to
    scheduler threads, initialize dispatch payloads.

AicpuExecutor::init orchestrates the three phases with a simple barrier: the
leader publishes "setup done", all threads handshake their slice and report in,
and the leader waits for all of them before finishing. Startup timing:

                serial (before)            parallel (after)
  broadcast     ~35 us                     ~9 us  (split 4 ways)
  boot wait     ~11 us                     ~11 us (unavoidable, physical)
  per-core work ~217 us  <-- the tax       ~55 us (split 4 ways)
  ---------------------------------------------------------------
  handshake     ~263 us                    ~75 us

Results

Direct A/B of this PR vs upstream/main (both the same upstream runtime, so
this isolates the handshake change), back-to-back on the same NPU (device 2),
100 rounds each, trimmed-80. Device time is the metric of record (on-NPU
wall clock):

Workload upstream/main this PR change
paged_attention_unroll C2 866.5 µs 675.3 µs −22.1%
pa_unroll_manual_scope C2 854.3 µs 678.9 µs −20.5%
benchmark_bgemm 962.5 µs 790.3 µs −17.9%
alternating_matmul_add 987.2 µs 815.9 µs −17.4%
paged_attention_unroll C1 1448.2 µs 1261.0 µs −12.9%
pa_unroll_manual_scope C1 1430.9 µs 1261.2 µs −11.9%
qwen3_14b_decode 2409.8 µs 2228.3 µs −7.5%
batch_paged_attention 3400.9 µs 3270.6 µs −3.8%

Every workload improves by a near-constant ~172 µs — the tell-tale signature
of removing a fixed overhead. The relative win scales inversely with job size:
biggest on the small workloads that the fixed cost dominated, smallest on the
long-running ones. The compute window itself is unchanged (−0.3% to +2.0%,
noise), because this only touches startup — not scheduling or kernel execution.

Correctness

Golden-verified (output compared against a PyTorch reference) on a deliberately
diverse spread, all PASSED:

  • vector_example — tiny, simple graph
  • benchmark_bgemm — AIC matrix-multiply (Case0, Bgemm64)
  • paged_attention — 65,792-task orchestration-heavy run (Case1) + a small case
  • qwen3_14b_decode — dense two-layer transformer decode

The 65k-task and multi-scope cases are the strongest exercise of the new
multi-thread partition + barrier path.

Why it's safe

  • No new config or feature flag — parallelizing the handshake is always
    correct, so it's unconditional.
  • No data races — each core is handshaked by exactly one thread (contiguous,
    gap-free partition), so the per-core writes never overlap.
  • Ordering preserved — the AIC/AIV worker lists are rebuilt in the same
    core-index order as before, so core-to-thread assignment is identical.
  • Memory ordering — a setup failure is stored before the "setup done" flag
    (both release/acquire), so a failing init can never let a thread proceed. The
    swimlane-buffer init still happens before any core is signaled.
  • Profiling-agnostic — under PTO2_PROFILING the post-pass reads core type
    and physical ID from the Handshake struct, so it works whether or not
    profiling is compiled in.

Files changed (5)

All in src/a2a3/runtime/tensormap_and_ringbuffer/:

  • runtime/scheduler/scheduler_cold_path.cpp — split init into
    pre_handshake_init / post_handshake_init; replace handshake_all_cores
    with the sliced handshake_partition.
  • runtime/scheduler/scheduler_context.h — updated declarations; added the
    handshake_failed_ flag.
  • aicpu/aicpu_executor.cpp — parallel init orchestration + barrier; removed
    the dead single-leader CAS.
  • aicore/aicore_executor.cpp — comment updated to reference the new function.
  • docs/RUNTIME_LOGIC.md — doc updated to reference the new functions.

Notes for reviewers

  • The Results table is a direct A/B of this branch against upstream/main — both
    built from the same upstream runtime and run back-to-back on the same NPU — so
    it isolates the handshake change with nothing else varying. The ~85 µs startup
    and per-phase handshake breakdown come from direct profiling of the same
    optimization.
  • A separate, pre-existing defect was noticed during this work: the L2 swimlane
    collector produces no data on the tensormap_and_ringbuffer runtime. It is
    unrelated to this change and tracked separately.

@coderabbitai

coderabbitai Bot commented Jul 6, 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: 78bfbb69-f716-406a-b775-f8d9304d3fcf

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 PR refactors AICPU/scheduler initialization from a single-threaded init()/handshake_all_cores() flow into a three-phase model: pre_handshake_init, parallelized handshake_partition, and post_handshake_init. AicpuExecutor is rewritten to coordinate all threads through this flow via new atomics, and related comments/docs are updated accordingly.

Changes

Scheduler Handshake Refactor

Layer / File(s) Summary
Scheduler init API and state contract
src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_context.h
Replaces the single init() declaration with pre_handshake_init, handshake_partition, and post_handshake_init; removes handshake_all_cores; adds handshake_failed_ atomic flag.
Partitioned handshake and split init implementation
src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_cold_path.cpp
Implements handshake_partition to process only a per-thread worker-id slice, latches failures into a shared handshake_failed_ flag instead of returning immediately, and splits init logic into pre_handshake_init (setup) and post_handshake_init (worker-list building, assign_cores_to_threads, profiling/task-count init, emergency shutdown on failure).
AICPU executor multi-thread init coordination
src/a2a3/runtime/tensormap_and_ringbuffer/aicpu/aicpu_executor.cpp
Adds hs_setup_done_/hs_arrived_ atomics; rewrites init() so a leader thread runs pre_handshake_init/post_handshake_init while all threads run handshake_partition via a barrier; updates deinit() and simplifies aicpu_execute() to use init()'s return value.
Comment and documentation updates
src/a2a3/runtime/tensormap_and_ringbuffer/aicore/aicore_executor.cpp, src/a2a3/runtime/tensormap_and_ringbuffer/docs/RUNTIME_LOGIC.md
Updates inline comment and RUNTIME_LOGIC.md to describe the new handshake phase names and ordering.

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

Sequence Diagram(s)

sequenceDiagram
  participant Thread as AICPU Thread
  participant SchedulerContext
  participant Handshake as Handshake Records

  Thread->>SchedulerContext: pre_handshake_init(...)
  SchedulerContext-->>Thread: clears handshake_failed_, sets cores_total_num_
  Thread->>SchedulerContext: handshake_partition(tidx, nthreads)
  SchedulerContext->>Handshake: write payload pointer, set aicpu_ready=1 for slice
  SchedulerContext->>Handshake: wait aicore_regs_ready/aicore_done for slice
  alt invalid physical_core_id
    SchedulerContext->>SchedulerContext: handshake_failed_.store(true)
  end
  Thread->>SchedulerContext: post_handshake_init()
  alt handshake_failed_
    SchedulerContext->>SchedulerContext: emergency_shutdown
  else success
    SchedulerContext->>Handshake: build AIC/AIV worker-id lists
    SchedulerContext->>SchedulerContext: assign_cores_to_threads()
    SchedulerContext->>SchedulerContext: profiling/task-count init
  end
Loading

Possibly related PRs

  • hw-native-sys/simpler#1060: Addresses the same AICPU↔AICore handshake coupling by changing host launch order around the old handshake_all_cores() spin-wait race that this PR replaces.
  • hw-native-sys/simpler#1217: Also directly modifies the SchedulerContext::init machinery and core-transition flow in the same components.

Poem

A hop, a skip, three phases neat,
pre, partition, post — complete!
No more waiting, all threads race,
each core finds its rightful place. 🐇
Handshake done, the burrow cheers,
init flows clean, away with fears!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 clearly summarizes the main change: parallelizing the AICore handshake across AICPU threads.
Description check ✅ Passed The description is directly about the handshake parallelization and its implementation and results.

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 parallelizes the per-core AICore handshake during initialization to optimize the preamble execution time. It splits the previous single-threaded initialization into three phases: a leader-only pre-handshake setup, a parallelized handshake partition where each thread handles a disjoint slice of cores, and a leader-only post-handshake phase. Feedback suggests that instead of silently falling back to a default index when platform_aicpu_affinity_thread_idx() returns an invalid value, the code should validate the thread index and fail loudly with an error code.

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 thread src/a2a3/runtime/tensormap_and_ringbuffer/aicpu/aicpu_executor.cpp
The AICPU->AICore handshake in scheduler init discovered all cores
serially on a single thread while the other AICPU threads spun idle
waiting on init_done_. On a2a3 (72 cores) this serial per-core MMIO
(reg init + two spin round-trips per core) measured ~217 us of the
~283 us preamble -- a fixed per-round cost that dominates device time
for small workloads and is a flat tax on every run.

Parallelize it: split SchedulerContext::init into three parts so every
AICPU thread handshakes a disjoint slice of cores.

- pre_handshake_init (leader): zero per-core state, wire config, init
  l2-swimlane buffers, set core count -- published before any thread
  handshakes so the swimlane-before-aicpu_ready ordering still holds.
- handshake_partition (all threads): each thread handshakes its
  contiguous [lo, hi) slice; core writes are race-free (disjoint), and
  an invalid physical_core_id sets an atomic flag.
- post_handshake_init (leader, after a barrier): build the AIC/AIV
  worker-id lists in core-index order (identical clusters to before,
  read from the Handshake struct so it stays correct under
  PTO2_PROFILING), assign cores, init profiling subsystems, payloads.

AicpuExecutor::init orchestrates it: the leader (affinity exec_idx 0)
publishes hs_setup_done_, all threads handshake their slice, then an
hs_arrived_ barrier gates the leader's finish. init_failed_ is stored
before hs_setup_done_ (both release) so a pre-handshake failure is
always visible before any thread proceeds. Drops the now-dead
initialized_ CAS and replaces the preamble spin with a direct
init-return check.

Measured on a2a3: preamble ~283 us -> ~85 us, a ~187 us/round device
reduction (-7.5% on qwen3-14b decode, up to -22% on small workloads
where the fixed handshake cost dominated). Compute window unchanged.
Golden-verified across vector_example, benchmark_bgemm, paged_attention
(65k tasks), and qwen3_14b_decode.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MqeALZTPEnDXTnbYfcnPfq
@ChaoWao ChaoWao force-pushed the parallel-aicore-handshake branch from b0f9314 to 64f6264 Compare July 9, 2026 04:34
…#1279

- Sim regression (segfault): in a2a3sim, platform_aicpu_affinity_thread_idx()
  returns -1 during init() (sim gates with platform_aicpu_affinity_gate, which
  never sets the exec idx; set_thread_idx runs later in run()). The prior
  `if (tidx<0) tidx=0` made every sim AICPU thread the leader, so all of them
  ran pre_/post_handshake_init concurrently and raced the shared scheduler
  state (aic_count_++, worker-list writes, memset) -> memory corruption ->
  SIGSEGV on the simplest tensormap sim runs (vector_example, per_task_runtime_env).
  Fix: hand a distinct [0,nthreads) index from a counter (hs_thread_seq_, reset
  in deinit) when affinity is unavailable, mirroring run()'s thread_idx_++
  fallback, so sim gets one leader + disjoint slices like onboard.

- Add the upper-bound guard init() was missing vs run(): a tidx >= nthreads
  would index the partition arrays out of bounds. Fail only that thread
  (not init_failed_, which would hang the leader at the barrier).

- Refresh the now-stale aicpu_execute docstring (init is no longer
  "first thread only" with an external wait).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ChaoWao ChaoWao force-pushed the parallel-aicore-handshake branch from ae0484e to 18fee9c Compare July 9, 2026 06:20
@ChaoWao ChaoWao merged commit 3a23d22 into hw-native-sys:main Jul 9, 2026
15 checks passed
@ChaoWao ChaoWao deleted the parallel-aicore-handshake branch July 9, 2026 06:36
ChaoZheng109 added a commit to ChaoWao/simpler-fork that referenced this pull request Jul 9, 2026
Collapse the per-core AICPU<->AICore handshake to a single round-trip on
a2a3 and a5, and bring a5's handshake up to the parallel multi-thread
structure a2a3 already carries from hw-native-sys#1279.

Single round-trip (both arches):
- AICore reports {physical_core_id, core_type, aicore_done} in one write,
  then polls its own DATA_MAIN_BASE SPR for the AICPU to open its register
  window. A kernel launch resets that SPR to 0 (verified on a2a3 silicon);
  the AICPU writes IDLE (non-zero) as it opens FAST_PATH, so a non-zero
  read means the window is open. Opening the window IS the acknowledgement
  -- the aicpu_regs_ready / aicore_regs_ready Handshake fields and the
  second round-trip are removed.
- The AICPU sweeps its core slice (poll aicore_done, service whoever has
  reported) instead of blocking core-by-core, so per-core wakeups overlap.

Parallelize a5:
- a5 previously handshaked all cores serially on one AICPU thread while
  the others spun idle. Split SchedulerContext::init into pre_handshake_init
  (leader) / handshake_partition (all threads, each a disjoint core slice)
  / post_handshake_init (leader, after a barrier), mirroring a2a3.
  AicpuExecutor::init orchestrates the barrier and hands sim threads a
  distinct index when the affinity gate is inactive.
- Worker-id list building is deferred to post_handshake_init (serial) so
  aic_count_/aiv_count_ are never incremented from more than one thread.

handshake_partition and AicpuExecutor::init are now identical across a2a3
and a5. Builds clean on a2a3/a5 (onboard + sim); a2a3sim/a5sim scene tests
pass. a2a3/a5 onboard validation relies on CI (no NPU on this box).
SergioMartin86 added a commit to huawei-csl/simpler that referenced this pull request Jul 9, 2026
…native-sys#1279)

Upstream merged the parallel AICore handshake (hw-native-sys#1279, "3~22% Device
Speedup") — the shared aicpu_executor.cpp now drives init via
pre_handshake_init / handshake_partition / post_handshake_init (leader
setup, per-thread core-slice handshake, barrier, leader finalize) instead
of the serial sched_ctx_.init() → handshake_all_cores().

The polling squash predates hw-native-sys#1279 and carried the serial handshake, so
after rebasing onto upstream the executor referenced methods the polling
SchedulerContext did not have. Port the split parallel-handshake methods
(the same implementation now upstream) into the polling scheduler_context
so it builds against and benefits from the upstream driver.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MqeALZTPEnDXTnbYfcnPfq
SergioMartin86 added a commit to huawei-csl/simpler that referenced this pull request Jul 9, 2026
…native-sys#1279)

Upstream merged the parallel AICore handshake (hw-native-sys#1279, "3~22% Device
Speedup") — the shared aicpu_executor.cpp now drives init via
pre_handshake_init / handshake_partition / post_handshake_init (leader
setup, per-thread core-slice handshake, barrier, leader finalize) instead
of the serial sched_ctx_.init() → handshake_all_cores().

The polling squash predates hw-native-sys#1279 and carried the serial handshake, so
after rebasing onto upstream the executor referenced methods the polling
SchedulerContext did not have. Port the split parallel-handshake methods
(the same implementation now upstream) into the polling scheduler_context
so it builds against and benefits from the upstream driver.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MqeALZTPEnDXTnbYfcnPfq
ChaoWao pushed a commit to huawei-csl/simpler that referenced this pull request Jul 9, 2026
Collapse the AICore init handshake from a three-phase ping-pong
(aicore_regs_ready -> aicpu_regs_ready -> aicore_done) to a single round
trip. AICore now publishes {physical_core_id, core_type, aicore_done} in
one write, then polls its own DATA_MAIN_BASE SPR. The AICPU opens the
core's register window (platform_init_aicore_regs writes
DATA_MAIN_BASE=IDLE) only after observing aicore_done, so that
window-open write doubles as the release acknowledgement -- the dedicated
aicpu_regs_ready / aicore_regs_ready fields are removed from struct
Handshake.

a2a3 (already parallel via hw-native-sys#1279): within each thread's core slice,
sweep-poll on aicore_done -- a GM read, not the strictly-serial nGnRE
COND poll -- so per-core wakeups overlap (~max, not sum), and batch the
release barrier (one OUT_OF_ORDER_STORE_BARRIER per slice instead of one
per core).

a5: port the parallel-handshake split (pre_handshake_init /
handshake_partition / post_handshake_init) alongside the single-round-trip
protocol; a5 previously handshaked serially on one thread.

Reproduces the approach of upstream PR hw-native-sys#1214 on a fresh branch off main.

Verified a2a3 onboard (device 3): paged_attention Case1 200/200 golden
rounds, zero mismatches; preamble median 27us vs 50us baseline (-46%, device 3, CaseSmall1,
n=100). a5 compiles clean
(onboard + sim); a5 onboard testing deferred to CI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MqeALZTPEnDXTnbYfcnPfq
ChaoWao pushed a commit that referenced this pull request Jul 9, 2026
)

Collapse the AICore init handshake from a three-phase ping-pong
(aicore_regs_ready -> aicpu_regs_ready -> aicore_done) to a single round
trip. AICore now publishes {physical_core_id, core_type, aicore_done} in
one write, then polls its own DATA_MAIN_BASE SPR. The AICPU opens the
core's register window (platform_init_aicore_regs writes
DATA_MAIN_BASE=IDLE) only after observing aicore_done, so that
window-open write doubles as the release acknowledgement -- the dedicated
aicpu_regs_ready / aicore_regs_ready fields are removed from struct
Handshake.

a2a3 (already parallel via #1279): within each thread's core slice,
sweep-poll on aicore_done -- a GM read, not the strictly-serial nGnRE
COND poll -- so per-core wakeups overlap (~max, not sum), and batch the
release barrier (one OUT_OF_ORDER_STORE_BARRIER per slice instead of one
per core).

a5: port the parallel-handshake split (pre_handshake_init /
handshake_partition / post_handshake_init) alongside the single-round-trip
protocol; a5 previously handshaked serially on one thread.

Reproduces the approach of upstream PR #1214 on a fresh branch off main.

Verified a2a3 onboard (device 3): paged_attention Case1 200/200 golden
rounds, zero mismatches; preamble median 27us vs 50us baseline (-46%, device 3, CaseSmall1,
n=100). a5 compiles clean
(onboard + sim); a5 onboard testing deferred to CI.

Co-authored-by: ChaoZheng109 <zhengchao47@huawei.com>
ChaoWao pushed a commit to huawei-csl/simpler that referenced this pull request Jul 10, 2026
…native-sys#1279)

Upstream merged the parallel AICore handshake (hw-native-sys#1279, "3~22% Device
Speedup") — the shared aicpu_executor.cpp now drives init via
pre_handshake_init / handshake_partition / post_handshake_init (leader
setup, per-thread core-slice handshake, barrier, leader finalize) instead
of the serial sched_ctx_.init() → handshake_all_cores().

The polling squash predates hw-native-sys#1279 and carried the serial handshake, so
after rebasing onto upstream the executor referenced methods the polling
SchedulerContext did not have. Port the split parallel-handshake methods
(the same implementation now upstream) into the polling scheduler_context
so it builds against and benefits from the upstream driver.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MqeALZTPEnDXTnbYfcnPfq
ChaoWao added a commit to ChaoWao/simpler-fork that referenced this pull request Jul 11, 2026
…gbuffer

hbg's scheduler was a copy of tmr@fb28fd71 and had fallen a full generation
behind. This absorbs tmr's post-fork scheduler evolution while preserving hbg's
host-orchestration invariants (single ring, no execution-time reclaim, flat
Runtime, host-side orchestrator).

Absorbed from tmr:
- Remove PTO2LocalReadyBuffer local-first dispatch and its local_bufs threading
  (hw-native-sys#1245); pop/dispatch now take PTO2ReadyQueue* directly.
- Ready-claim completion protocol: ready_state / mark_completed /
  mark_any_subtask_deferred (replaces the any_subtask_deferred bool).
- Rename speculative -> early_dispatch and remove the auto-chain
  (spec_chain_active/depth, PTO2_SPEC_CHAIN_MAX); move allow_early_resolve from
  payload onto slot_state.
- Per-shape early_dispatch_queues[] (was a single queue) + ready_sync_queues[]
  Tier-0 sync_start staging (hw-native-sys#1319); run_staging_order template;
  try_early_dispatch / early_dispatch_shape; has_any_free_slot predicates.
- MIX subtasks placed per-core instead of uniform cluster placement (hw-native-sys#1308).
- Remove the on-device wiring subsystem (PTO2SpscQueue / WiringState /
  drain_wiring_queue / wire_task, hw-native-sys#1263): the host orchestrator now wires fanout
  adjacency inline during submit and seeds readiness via the scheduler's
  push_ready_routed / route_ready_once, with no device-side wiring queue.
- Single-round-trip / parallel AICore handshake (hw-native-sys#1279/hw-native-sys#1310): handshake_all_cores
  -> pre_handshake_init + per-thread handshake_partition + post_handshake_init,
  with the matching aicpu_executor boot restructure and AICore-worker protocol.
- Drop the per-run per-core memset in scheduler init/deinit (hw-native-sys#1212); gate dep_gen
  init under SIMPLER_DFX; compile out pmu_active when SIMPLER_DFX is off; remove
  the redundant second completion poll; remove the dead device-orch core
  transition (handle_core_transition / reassign_cores_for_all_threads /
  orch_to_sched_).

Remove vestigial device-orch synchronization: with host orchestration the
orchestrator runs to completion on the host before the device boots, so the
device-side orchestrator_done_ flag is always set before the scheduler threads
start (they gate on runtime_init_ready_, whose release/acquire already publishes
total_tasks_). Removed orchestrator_done_, the always-true completion guard, and
the never-called wait_for_orchestration_done_before_dispatch / orchestration_done()
carried over from tmr's concurrent device-orch model.

Preserved host-orch divergences (not overwritten by tmr): PTO2_MAX_RING_DEPTH==1
with rings[]/ring_id removed; no advance_ring_pointers / reset_for_reuse /
check_and_handle_consumed (completion via completed_tasks_, consumer wait keys on
fanout_refcount); flat runtime->workers; hbg's swimlane behavior (no on_aicore_ack
hook — the just-filled buffer drains via the completion-before-dispatch invariant
and the next-rotation / run-end backstop flushes).

Skipped as not applicable to hbg: stall-classification publishing (shared-memory
lacks the sched_stall_* fields; last_task_alive semantics differ); per-device
scheduler timeout (aicpu_device_config.h / get_scheduler_timeout_ms absent);
runtime->dev.* Runtime nesting.

get_reg_ptr weak host fallback: route_ready_once transitively ODR-uses the
early-dispatch doorbell inline (ring_one_doorbell -> get_reg_ptr) in the host
lib, but no core is gated during host graph-build so it never fires; a weak
hidden fallback satisfies the loader (same pattern as get_sys_cnt_aicpu).

Verified: all 8 runtime targets build -Werror; a2a3sim host_build_graph scene
suite 10/10; a2a3 onboard host_build_graph suite 10 passed / 1 skipped
(paged_attention exercises the new handshake, route_ready_once wiring, and
ready-claim path). tensormap_and_ringbuffer and the a5 runtimes are untouched.
ChaoWao added a commit to ChaoWao/simpler-fork that referenced this pull request Jul 11, 2026
…gbuffer

hbg's scheduler was a copy of tmr@fb28fd71 and had fallen a full generation
behind. This absorbs tmr's post-fork scheduler evolution while preserving hbg's
host-orchestration invariants (single ring, no execution-time reclaim, flat
Runtime, host-side orchestrator).

Absorbed from tmr:
- Remove PTO2LocalReadyBuffer local-first dispatch and its local_bufs threading
  (hw-native-sys#1245); pop/dispatch now take PTO2ReadyQueue* directly.
- Ready-claim completion protocol: ready_state / mark_completed /
  mark_any_subtask_deferred (replaces the any_subtask_deferred bool).
- Rename speculative -> early_dispatch and remove the auto-chain
  (spec_chain_active/depth, PTO2_SPEC_CHAIN_MAX); move allow_early_resolve from
  payload onto slot_state.
- Per-shape early_dispatch_queues[] (was a single queue) + ready_sync_queues[]
  Tier-0 sync_start staging (hw-native-sys#1319); run_staging_order template;
  try_early_dispatch / early_dispatch_shape; has_any_free_slot predicates.
- MIX subtasks placed per-core instead of uniform cluster placement (hw-native-sys#1308).
- Remove the on-device wiring subsystem (PTO2SpscQueue / WiringState /
  drain_wiring_queue / wire_task, hw-native-sys#1263): the host orchestrator now wires fanout
  adjacency inline during submit and seeds readiness via the scheduler's
  push_ready_routed / route_ready_once, with no device-side wiring queue.
- Single-round-trip / parallel AICore handshake (hw-native-sys#1279/hw-native-sys#1310): handshake_all_cores
  -> pre_handshake_init + per-thread handshake_partition + post_handshake_init,
  with the matching aicpu_executor boot restructure and AICore-worker protocol.
  Drop the now-dead two-round-trip Handshake fields aicpu_regs_ready /
  aicore_regs_ready (aligning the struct to tmr's).
- Drop the per-run per-core memset in scheduler init/deinit (hw-native-sys#1212); gate dep_gen
  init under SIMPLER_DFX; compile out pmu_active when SIMPLER_DFX is off; remove
  the redundant second completion poll; remove the dead device-orch core
  transition (handle_core_transition / reassign_cores_for_all_threads /
  orch_to_sched_).

Remove vestigial device-orch synchronization: with host orchestration the
orchestrator runs to completion on the host before the device boots, so the
device-side orchestrator_done_ flag is always set before the scheduler threads
start (they gate on runtime_init_ready_, whose release/acquire already publishes
total_tasks_). Removed orchestrator_done_, the always-true completion guard, and
the never-called wait_for_orchestration_done_before_dispatch / orchestration_done()
carried over from tmr's concurrent device-orch model.

Preserved host-orch divergences (not overwritten by tmr): PTO2_MAX_RING_DEPTH==1
with rings[]/ring_id removed; no advance_ring_pointers / reset_for_reuse /
check_and_handle_consumed (completion via completed_tasks_, consumer wait keys on
fanout_refcount); flat runtime->workers; hbg's swimlane behavior (no on_aicore_ack
hook — the just-filled buffer drains via the completion-before-dispatch invariant
and the next-rotation / run-end backstop flushes).

Skipped as not applicable to hbg: stall-classification publishing (shared-memory
lacks the sched_stall_* fields; last_task_alive semantics differ); per-device
scheduler timeout (aicpu_device_config.h / get_scheduler_timeout_ms absent);
runtime->dev.* Runtime nesting.

get_reg_ptr weak host fallback: route_ready_once transitively ODR-uses the
early-dispatch doorbell inline (ring_one_doorbell -> get_reg_ptr) in the host
lib, but no core is gated during host graph-build so it never fires; a weak
hidden fallback satisfies the loader (same pattern as get_sys_cnt_aicpu).

Verified: all 8 runtime targets build -Werror; a2a3sim host_build_graph scene
suite 10/10; a2a3 onboard host_build_graph suite 10 passed / 1 skipped
(paged_attention exercises the new handshake, route_ready_once wiring, and
ready-claim path). tensormap_and_ringbuffer and the a5 runtimes are untouched.
ChaoWao added a commit that referenced this pull request Jul 11, 2026
…gbuffer (#1327)

hbg's scheduler was a copy of tmr@fb28fd71 and had fallen a full generation
behind. This absorbs tmr's post-fork scheduler evolution while preserving hbg's
host-orchestration invariants (single ring, no execution-time reclaim, flat
Runtime, host-side orchestrator).

Absorbed from tmr:
- Remove PTO2LocalReadyBuffer local-first dispatch and its local_bufs threading
  (#1245); pop/dispatch now take PTO2ReadyQueue* directly.
- Ready-claim completion protocol: ready_state / mark_completed /
  mark_any_subtask_deferred (replaces the any_subtask_deferred bool).
- Rename speculative -> early_dispatch and remove the auto-chain
  (spec_chain_active/depth, PTO2_SPEC_CHAIN_MAX); move allow_early_resolve from
  payload onto slot_state.
- Per-shape early_dispatch_queues[] (was a single queue) + ready_sync_queues[]
  Tier-0 sync_start staging (#1319); run_staging_order template;
  try_early_dispatch / early_dispatch_shape; has_any_free_slot predicates.
- MIX subtasks placed per-core instead of uniform cluster placement (#1308).
- Remove the on-device wiring subsystem (PTO2SpscQueue / WiringState /
  drain_wiring_queue / wire_task, #1263): the host orchestrator now wires fanout
  adjacency inline during submit and seeds readiness via the scheduler's
  push_ready_routed / route_ready_once, with no device-side wiring queue.
- Single-round-trip / parallel AICore handshake (#1279/#1310): handshake_all_cores
  -> pre_handshake_init + per-thread handshake_partition + post_handshake_init,
  with the matching aicpu_executor boot restructure and AICore-worker protocol.
  Drop the now-dead two-round-trip Handshake fields aicpu_regs_ready /
  aicore_regs_ready (aligning the struct to tmr's).
- Drop the per-run per-core memset in scheduler init/deinit (#1212); gate dep_gen
  init under SIMPLER_DFX; compile out pmu_active when SIMPLER_DFX is off; remove
  the redundant second completion poll; remove the dead device-orch core
  transition (handle_core_transition / reassign_cores_for_all_threads /
  orch_to_sched_).

Remove vestigial device-orch synchronization: with host orchestration the
orchestrator runs to completion on the host before the device boots, so the
device-side orchestrator_done_ flag is always set before the scheduler threads
start (they gate on runtime_init_ready_, whose release/acquire already publishes
total_tasks_). Removed orchestrator_done_, the always-true completion guard, and
the never-called wait_for_orchestration_done_before_dispatch / orchestration_done()
carried over from tmr's concurrent device-orch model.

Preserved host-orch divergences (not overwritten by tmr): PTO2_MAX_RING_DEPTH==1
with rings[]/ring_id removed; no advance_ring_pointers / reset_for_reuse /
check_and_handle_consumed (completion via completed_tasks_, consumer wait keys on
fanout_refcount); flat runtime->workers; hbg's swimlane behavior (no on_aicore_ack
hook — the just-filled buffer drains via the completion-before-dispatch invariant
and the next-rotation / run-end backstop flushes).

Skipped as not applicable to hbg: stall-classification publishing (shared-memory
lacks the sched_stall_* fields; last_task_alive semantics differ); per-device
scheduler timeout (aicpu_device_config.h / get_scheduler_timeout_ms absent);
runtime->dev.* Runtime nesting.

get_reg_ptr weak host fallback: route_ready_once transitively ODR-uses the
early-dispatch doorbell inline (ring_one_doorbell -> get_reg_ptr) in the host
lib, but no core is gated during host graph-build so it never fires; a weak
hidden fallback satisfies the loader (same pattern as get_sys_cnt_aicpu).

Verified: all 8 runtime targets build -Werror; a2a3sim host_build_graph scene
suite 10/10; a2a3 onboard host_build_graph suite 10 passed / 1 skipped
(paged_attention exercises the new handshake, route_ready_once wiring, and
ready-claim path). tensormap_and_ringbuffer and the a5 runtimes are untouched.
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.

2 participants