Skip to content

refactor(a2a3/early-dispatch): align acquisition and ordering with normal dispatch#1297

Open
ChaoZheng109 wants to merge 1 commit into
hw-native-sys:mainfrom
ChaoZheng109:refactor/unify-scheduler-dispatch
Open

refactor(a2a3/early-dispatch): align acquisition and ordering with normal dispatch#1297
ChaoZheng109 wants to merge 1 commit into
hw-native-sys:mainfrom
ChaoZheng109:refactor/unify-scheduler-dispatch

Conversation

@ChaoZheng109

@ChaoZheng109 ChaoZheng109 commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

What

Unifies the early-dispatch (ed) path in the a2a3 tensormap_and_ringbuffer
scheduler with the normal-dispatch (nd) path so the scheduler's two task sources
share the same primitives — pop sizing, gate helpers, staging, ordering, and
progress accounting. This change:

  1. Sizes the drain pop to free-slot count, not a fixed 8 — ed drained a
    hardcoded 8 consumers per pass while nd sizes its pop to available capacity.
    Both now use the same BitStates.count() sizing (new
    CoreTracker::get_free_slot_states() primitive; has_any_free_slot()
    re-expressed on top of it).

  2. Splits the early-dispatch queue per resource shape — the single
    shape-mixed early_dispatch_queue becomes early_dispatch_queues[3],
    mirroring the per-shape ready_queues[]. Candidates bucket by
    active_mask.to_shape() at push time; the drain pops per shape with the shape
    known as the queue index (reserve/init/wire/reset/destroy loop over the three
    queues, symmetric with ready_queues[]).

  3. Gives ed MIX-priority two-stage ordering — the same ordering nd uses: MIX
    strict priority, IDLE stage before PENDING stage, cross-thread idle gating —
    MIX-IDLE ▶ c/v-IDLE ▶ MIX-PEND ▶ c/v-PEND. New early_dispatch_shape() (the ed
    analog of dispatch_shape) reuses has_idle_in_other_threads and stages onto
    only the phase's bucket; new has_residual_early_mix() for the MIX gate
    (has_residual_mix reads the normal MIX ready queue, empty when ed runs).

  4. Encapsulates gating + updates progress flags — ed's gate (off-PMU,
    has_any_free_slot, all ready_queues empty) moves inside try_early_dispatch
    (mirroring how dispatch_ready_tasks owns its own PMU/gating), so the caller
    is a single unconditional call; made_progress/try_pushed are set when
    staging, so an iteration that only early-stages is no longer miscounted as
    idle.

  5. Renames try_speculative_early_dispatchtry_early_dispatch.

Why

The two dispatch sources had diverged: nd sized pops to capacity, ordered by
MIX-priority / IDLE-then-PENDING, and updated the loop's progress flags from a
single encapsulated call; ed used a flat fixed-8 drain over a mixed queue with
caller-side gating and no progress-flag updates. Aligning them makes the
scheduler's two task sources behave consistently and share the same primitives.

Scope / notes

  • a2a3 only — the a5 tensormap_and_ringbuffer runtime has no early-dispatch
    path.
  • PMU: ed stays fully disabled under PMU (unchanged); nd already does
    IDLE-only under PMU. Aligning ed to IDLE-only under PMU was considered and
    dropped — a gated core polls DMB (MMIO) while waiting for its doorbell, which
    would perturb the single-issue measurement windows PMU relies on.
  • Parallel skeleton (not folded into dispatch_shape): early staging is
    gated pre-stage + doorbell, distinct from normal immediate dispatch.
  • Docs: no design doc describes the early-dispatch queue/ordering; the
    early_dispatch L2-swimlane bar (name and semantics) is unchanged, so no doc
    update is needed.

Testing

a2a3sim: ~15 SPMD / MIX / dependency-heavy scene tests pass
(spmd_multiblock_mix/_aiv, spmd_starvation, spmd_sync_start/_stress,
paged_attention family, alternating_matmul_add, fanin_lookup_perf,
mixed_example, dummy_task) — no deadlock, no golden drift, no timeout/stall
regression. Built + resynced + tested against latest upstream/main. a2a3 onboard
not run (box is a5 silicon); sim is the silicon-agnostic path — onboard left to
CI.

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR converts the scheduler's single shared early-dispatch queue into an array of queues partitioned by PTO2_NUM_RESOURCE_SHAPES. Layout, state, dispatch logic (with a new early_dispatch_shape helper and MIX-strict priority ordering), a CoreTracker free-slot helper, and arena init/reset/wiring/destroy paths are all updated to route work by resource shape.

Changes

Per-shape early-dispatch queue refactor

Layer / File(s) Summary
Layout and state field changes for per-shape queues
pto_scheduler.h, pto_runtime2_types.h
off_early_dispatch_queue_slots and early_dispatch_queue become arrays indexed by resource shape; comments updated accordingly.
Fan-in staging enqueues into shape-indexed queue
pto_scheduler.h
propagate_dispatch_fanin enqueues staged consumers into early_dispatch_queues[shape].
SchedulerContext helper declarations
scheduler_context.h
Declares new private methods early_dispatch_shape(...) and has_residual_early_mix().
Early-dispatch draining and staging implementation
scheduler_dispatch.cpp
Implements early_dispatch_shape to drain per-shape queues and stage claimed blocks; rewrites try_speculative_early_dispatch with MIX-strict priority ordering and per-shape re-push logic.
Free-slot mask helper in CoreTracker
scheduler_types.h
Adds get_free_slot_states(); has_any_free_slot() delegates to it.
Arena layout, init, reset, wiring, and destroy for per-shape queues
pto_runtime2_init.cpp
reserve_layout, init_data_from_layout, reset_for_reuse, wire_arena_pointers, and destroy loop over early_dispatch_queues[i].

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

Sequence Diagram(s)

sequenceDiagram
  participant Scheduler as SchedulerContext
  participant EarlyQueue as early_dispatch_queues[shape]
  participant CoreTracker
  participant Consumer

  Scheduler->>EarlyQueue: drain queue for shape
  EarlyQueue-->>Scheduler: staged consumer
  Scheduler->>Consumer: check staging state (PTO2_SPEC_STAGING)
  Scheduler->>CoreTracker: get_free_slot_states() for phase
  CoreTracker-->>Scheduler: free core bitmask
  alt free cores available
    Scheduler->>Consumer: CAS-claim block range
    Scheduler->>CoreTracker: stage_consumer_blocks
  else no free cores
    Scheduler->>EarlyQueue: re-push consumer
  end
Loading

Possibly related PRs

  • hw-native-sys/simpler#1269: Both PRs modify early-speculative dispatch logic in scheduler_dispatch.cpp, specifically MIX idle/pending selection and try_speculative_early_dispatch staging.
  • hw-native-sys/simpler#1285: Changes to dispatch_fanin/propagate_dispatch_fanin seeding directly relate to this PR's reworked fan-in routing into early_dispatch_queues[shape].
  • hw-native-sys/simpler#1288: Both PRs touch CoreTracker::has_any_free_slot() in scheduler_types.h.

Poem

Once one queue held them all in line,
Now shapes have queues, each row divine 🐇
MIX hops first, then IDLE's turn,
Free slots checked, cores that yearn.
Staged and pushed, re-pushed with care—
A rabbit's warren, everywhere! 🥕

🚥 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: aligning early-dispatch acquisition and ordering with normal dispatch.
Description check ✅ Passed The description is directly related to the early-dispatch refactor and its scheduler behavior changes.

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

Copy link
Copy Markdown

Warning

Gemini encountered an error creating the review. You can try again by commenting /gemini review.

@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/pto_scheduler.h (1)

1060-1065: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Silent drop on early-dispatch queue overflow.

push(c)'s return value is discarded here, unlike the analogous re-push in early_dispatch_shape (scheduler_dispatch.cpp) which checks and logs on failure. Functionally this is safe — try_speculative_release still falls back to normal ready-queue dispatch when the consumer was never actually staged — but a full queue here silently loses the early-dispatch optimization with no diagnostic trail.

🔍 Proposed fix to log on overflow
-            early_dispatch_queues[static_cast<int32_t>(shape)].push(c);
+            if (!early_dispatch_queues[static_cast<int32_t>(shape)].push(c)) {
+                LOG_INFO_V9(
+                    "[SPEC] early-dispatch queue full on initial push, consumer=%" PRId64,
+                    static_cast<int64_t>(c->task->task_id.raw)
+                );
+            }
🤖 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/pto_scheduler.h`
around lines 1060 - 1065, The early-dispatch enqueue in try_speculative_release
silently ignores a failed push on early_dispatch_queues, so add a check for the
push(c) result and log a diagnostic when the queue is full. Use the existing
early-dispatch failure handling in early_dispatch_shape and the scheduler
logging path as the model, and keep the current fallback behavior to normal
ready-queue dispatch unchanged.
🤖 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/pto_scheduler.h`:
- Around line 1060-1065: The early-dispatch enqueue in try_speculative_release
silently ignores a failed push on early_dispatch_queues, so add a check for the
push(c) result and log a diagnostic when the queue is full. Use the existing
early-dispatch failure handling in early_dispatch_shape and the scheduler
logging path as the model, and keep the current fallback behavior to normal
ready-queue dispatch unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f0b07d6f-b667-46f8-a95d-89fe0add4984

📥 Commits

Reviewing files that changed from the base of the PR and between bd1025f and 79030ff.

📒 Files selected for processing (6)
  • src/a2a3/runtime/tensormap_and_ringbuffer/runtime/pto_runtime2_types.h
  • src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/pto_scheduler.h
  • 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_types.h
  • src/a2a3/runtime/tensormap_and_ringbuffer/runtime/shared/pto_runtime2_init.cpp

…rmal dispatch

Unify the early-dispatch (ed) path in the a2a3 tensormap_and_ringbuffer
scheduler with the normal-dispatch (nd) path so the scheduler's two task
sources share the same primitives — pop sizing, gate helpers, staging,
ordering, and progress accounting.

- Size the drain pop to this thread's free-slot count instead of a hardcoded 8,
  matching nd's cores.count() sizing. New CoreTracker::get_free_slot_states()
  primitive; has_any_free_slot() re-expressed on top of it.
- Split the single shape-mixed early_dispatch_queue into per-shape
  early_dispatch_queues[3], mirroring ready_queues[]; candidates bucket by
  active_mask.to_shape() at push time, so the drain pops per shape with the
  shape known as the queue index (reserve/init/wire/reset/destroy loop over the
  three queues, symmetric with ready_queues[]).
- Order ed like nd: MIX strict priority, IDLE before PENDING, cross-thread idle
  gating (MIX-IDLE > c/v-IDLE > MIX-PEND > c/v-PEND). New early_dispatch_shape()
  (the ed analog of dispatch_shape) reuses has_idle_in_other_threads and stages
  onto only the phase's bucket; new has_residual_early_mix() for the MIX gate
  (has_residual_mix reads the normal MIX ready queue, empty when ed runs).
- Move ed's gate (off-PMU, has_any_free_slot, all ready_queues empty) inside
  try_early_dispatch, mirroring how dispatch_ready_tasks owns its own PMU/gating,
  so the caller is a single unconditional call; set made_progress / try_pushed
  when staging, so an iteration that only early-stages is no longer miscounted as
  idle.
- Rename try_speculative_early_dispatch -> try_early_dispatch.

a2a3 only (a5 has no early-dispatch path). ed stays disabled under PMU. Tested
on a2a3sim across ~15 SPMD/MIX/dependency-heavy scene tests — no deadlock,
golden drift, or timeout/stall regression.
@ChaoZheng109 ChaoZheng109 force-pushed the refactor/unify-scheduler-dispatch branch from f886b58 to d798a6e Compare July 9, 2026 04:04
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