Skip to content

feat(a2a3 scheduler): MIX split placement with doorbell-enforced simultaneity#1272

Closed
ChaoWao wants to merge 3 commits into
hw-native-sys:mainfrom
ChaoWao:worktree-iterative-humming-papert
Closed

feat(a2a3 scheduler): MIX split placement with doorbell-enforced simultaneity#1272
ChaoWao wants to merge 3 commits into
hw-native-sys:mainfrom
ChaoWao:worktree-iterative-humming-papert

Conversation

@ChaoWao

@ChaoWao ChaoWao commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

Allow MIX (cube+AIV) task blocks to stage their AIC/AIV subtasks on whatever core slot is free — running slot if the core is idle, pending slot if the core is busy with a free pending slot — independently. This fixes the "stranded AIV cores" problem: when AIV finishes early but its sibling AIC is still running, the cluster is partially-idle and currently no MIX block can use it.

The in-kernel simultaneity contract (FFTS syncall + cross-core pipe tpush/tpop) is upheld by gating the whole task behind the speculative doorbell until every subtask has promoted to a running slot, so all subtasks activate at the same instant at doorbell-ring.

Changes

File Change
scheduler_types.h classify_mix_clusterMixPlacementPlan{SPLIT, pending_mask} with deadlock guard (pending subtask must NOT land behind a gated-running core)
pto_scheduler.h maybe_ring_split_doorbell: rings only when fanin complete + fully staged (next_block_idx >= logical_block_num) + all subtasks promoted. try_speculative_release: split tasks defer ring; non-split keep immediate ring
scheduler_dispatch.cpp prepare_block_for_dispatch per-subtask pending_mask; stage_consumer_blocks sources split clusters + counts promoted_subtasks + calls maybe_ring; try_speculative_early_dispatch split sourcing with deadlock guard
scheduler_completion.cpp Case 3.3 promote: increments promoted_subtasks + triggers maybe_ring
pto_runtime2_types.h promoted_subtasks + requires_split_gate fields on PTO2TaskPayload
scheduler_context.h Updated signatures
test_scheduler_state.cpp SPLIT classifier UTs

Key design decisions

  1. Full-staging gate: maybe_ring_split_doorbell checks next_block_idx >= logical_block_num BEFORE ringing — prevents a partial ring that would spin the launched blocks of a full-occupancy syncall task (e.g. fa_fused) holding cores the un-staged blocks need.
  2. Deadlock guard: classify_mix_cluster excludes cores whose running task is gated (STAGING) from pending-eligibility — staging a pending subtask behind a gated running task deadlocks.
  3. Non-split tasks unchanged: only tasks with requires_split_gate use the deferred gate; non-split tasks ring immediately at fanin (sticky doorbell token handles pending subtasks).

Test

  • ctest -R test_scheduler_state — classifier + gate UTs pass.
  • python decode_fwd.py -p a2a3 -d <dev> --validate-fwd --fwd-layers 2 --enable-l2-swimlane — golden argmax passes.

🤖 Generated with Claude Code

@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: 82fc56fb-f6a2-485e-b418-308eca3cbf04

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

Adds a promoted_subtasks atomic counter to task payloads and a maybe_ring_split_doorbell gating mechanism to coordinate speculative dispatch completion. Extends CoreTracker::classify_mix_cluster with a SPLIT placement mode and pending_mask, threading pending/running decisions through dispatch preparation and staging paths. Updates corresponding unit tests.

Changes

Split-Placement Doorbell Gate

Layer / File(s) Summary
Promoted subtasks counter
src/a2a3/runtime/tensormap_and_ringbuffer/runtime/pto_runtime2_types.h
Adds atomic promoted_subtasks field to PTO2TaskPayload and resets it during init(...).
Doorbell gating logic
src/a2a3/.../scheduler/pto_scheduler.h
Adds maybe_ring_split_doorbell(...) checking fanin, staging completion, and promotion count before CAS-transitioning STAGING to DISPATCHED; try_speculative_release delegates and always returns true.
Completion path integration
src/a2a3/.../scheduler/scheduler_completion.cpp
Increments promoted_subtasks on slot promotion and invokes the doorbell gate; updates a prepare_block_for_dispatch call to pass pending_mask as 0.
MIX SPLIT classification
src/a2a3/.../scheduler/scheduler_types.h
Adds SPLIT to MixPlacement, introduces MixPlacementPlan with pending_mask, rewrites classify_mix_cluster with blocked_pending support, and updates dependent query helpers.
Context signature updates
src/a2a3/.../scheduler/scheduler_context.h
Changes prepare_block_for_dispatch to take pending_mask and stage_consumer_blocks to take extra split/blocked_pending parameters.
Dispatch and staging implementation
src/a2a3/.../scheduler/scheduler_dispatch.cpp
Computes per-subtask pending_mask, updates MIX cluster selection, threads pending decisions through dispatch loops, expands staging for split clusters, and adjusts memory ordering and self-ring behavior.
Test updates
tests/ut/cpp/a2a3/test_scheduler_state.cpp
Updates assertions to use placement.mode/placement.pending_mask, changes one expected outcome to SPLIT, and adds a busy-core setup step.

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

Possibly related PRs

  • hw-native-sys/simpler#989: Introduced the prepare-then-publish dispatch flow (prepare_block_for_dispatch, prepare_subtask_to_core) that this PR extends with pending_mask logic.
  • hw-native-sys/simpler#1079: Established the speculative doorbell/release path (try_speculative_release) directly extended here by maybe_ring_split_doorbell and promoted_subtasks.
  • hw-native-sys/simpler#1097: Introduced MIX active-core classification via classify_mix_cluster, which this PR extends with SPLIT placement and pending_mask.

Poem

A rabbit counts each promoted task,
Ding! goes the doorbell, no need to ask—
Split clusters dance, running and pending too,
Blocked paths avoided, deadlocks subdue.
Hop hop, the scheduler's fair and true! 🐇🔔

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main scheduler change: MIX split placement with gated doorbell release.
Description check ✅ Passed The description is detailed and directly matches the MIX split placement and doorbell gating changes.
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.

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 a split-placement doorbell gate to support mixed running and pending subtask placements within a single task block, preventing stranded cores while upholding simultaneity contracts. The review feedback highlights critical concurrency and deadlock issues: it recommends upgrading memory ordering to seq_cst for spec_state and staged_core_mask to avoid Dekker's-style reordering bugs, and suggests centralizing the promoted_subtasks increment logic inside prepare_block_for_dispatch to prevent deadlocks during partial staging.

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 +1086 to +1087
inline bool maybe_ring_split_doorbell(PTO2TaskSlotState &slot_state, SpecReleaseSink *sink = nullptr) {
if (slot_state.payload->spec_state.load(std::memory_order_acquire) != PTO2_SPEC_STAGING) return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Change the memory order from std::memory_order_acquire to std::memory_order_seq_cst when loading spec_state in maybe_ring_split_doorbell. This is required to maintain Sequential Consistency with the staged_core_mask stores, preventing Dekker's-style reordering bugs where some cores are never rung.

    inline bool maybe_ring_split_doorbell(PTO2TaskSlotState &slot_state, SpecReleaseSink *sink = nullptr) {
        if (slot_state.payload->spec_state.load(std::memory_order_seq_cst) != PTO2_SPEC_STAGING) return false;

Comment on lines 615 to +624
for (int w = 0; w < PTO2_SPEC_CORE_MASK_WORDS; w++)
if (my_cores[w] != 0) c->payload->staged_core_mask[w].fetch_or(my_cores[w], std::memory_order_seq_cst);

// If release already flipped DISPATCHED, it may have read the mask before our
// bits landed — ring our own cores so none is left gated forever.
if (staged > 0 && c->payload->spec_state.load(std::memory_order_seq_cst) == PTO2_SPEC_DISPATCHED) {
if (my_cores[w] != 0) c->payload->staged_core_mask[w].fetch_or(my_cores[w], std::memory_order_release);
// Running-direct subtasks are now in their running slots, gated-spinning — count
// them toward the doorbell gate; pending subtasks count at their promote (Case 3.3).
if (running_direct > 0)
c->payload->promoted_subtasks.fetch_add(running_direct, std::memory_order_acq_rel);
// Try the gate (rings if fully staged + fanin done + all promoted). If a peer
// already flipped DISPATCHED before our mask OR landed, self-ring our cores.
if (!sched_->maybe_ring_split_doorbell(*c) &&
c->payload->spec_state.load(std::memory_order_acquire) == PTO2_SPEC_DISPATCHED) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Restore std::memory_order_seq_cst for both the staged_core_mask fetch_or and the spec_state load. Using release and acquire here violates Sequential Consistency and allows the compiler or hardware to reorder the load of spec_state before the store to staged_core_mask. This can result in a race where the releasing thread misses the newly staged cores and the staging thread fails to self-ring them, leading to a permanent deadlock.

    for (int w = 0; w < PTO2_SPEC_CORE_MASK_WORDS; w++)
        if (my_cores[w] != 0) c->payload->staged_core_mask[w].fetch_or(my_cores[w], std::memory_order_seq_cst);
    // Running-direct subtasks are now in their running slots, gated-spinning — count
    // them toward the doorbell gate; pending subtasks count at their promote (Case 3.3).
    if (running_direct > 0)
        c->payload->promoted_subtasks.fetch_add(running_direct, std::memory_order_acq_rel);
    // Try the gate (rings if fully staged + fanin done + all promoted). If a peer
    // already flipped DISPATCHED before our mask OR landed, self-ring our cores.
    if (!sched_->maybe_ring_split_doorbell(*c) &&
        c->payload->spec_state.load(std::memory_order_seq_cst) == PTO2_SPEC_DISPATCHED) {

Comment on lines 231 to 273
if (shape == PTO2ResourceShape::MIX) {
// pending_mask: per-subtask PENDING decision (set = pending slot, clear = running).
uint8_t cmask = slot_state.active_mask.core_mask();
int n = 0;
if (cmask & PTO2_SUBTASK_MASK_AIC) {
out_handles[n++] = prepare_subtask_to_core(
thread_idx, tracker.get_aic_core_offset(core_offset), slot_state, PTO2SubtaskSlot::AIC, to_pending,
block_idx
thread_idx, tracker.get_aic_core_offset(core_offset), slot_state, PTO2SubtaskSlot::AIC,
(pending_mask & PTO2_SUBTASK_MASK_AIC) != 0, block_idx
);
}
if (cmask & PTO2_SUBTASK_MASK_AIV0) {
out_handles[n++] = prepare_subtask_to_core(
thread_idx, tracker.get_aiv0_core_offset(core_offset), slot_state, PTO2SubtaskSlot::AIV0, to_pending,
block_idx
thread_idx, tracker.get_aiv0_core_offset(core_offset), slot_state, PTO2SubtaskSlot::AIV0,
(pending_mask & PTO2_SUBTASK_MASK_AIV0) != 0, block_idx
);
}
if (cmask & PTO2_SUBTASK_MASK_AIV1) {
out_handles[n++] = prepare_subtask_to_core(
thread_idx, tracker.get_aiv1_core_offset(core_offset), slot_state, PTO2SubtaskSlot::AIV1, to_pending,
block_idx
thread_idx, tracker.get_aiv1_core_offset(core_offset), slot_state, PTO2SubtaskSlot::AIV1,
(pending_mask & PTO2_SUBTASK_MASK_AIV1) != 0, block_idx
);
}
#if PTO2_PROFILING
sched_l2_swimlane_[thread_idx].phase_dispatch_count += __builtin_popcount(cmask);
#endif
return n;
} else if (shape == PTO2ResourceShape::AIC) {
out_handles[0] =
prepare_subtask_to_core(thread_idx, core_offset, slot_state, PTO2SubtaskSlot::AIC, to_pending, block_idx);
out_handles[0] = prepare_subtask_to_core(
thread_idx, core_offset, slot_state, PTO2SubtaskSlot::AIC, (pending_mask & PTO2_SUBTASK_MASK_AIC) != 0,
block_idx
);
#if PTO2_PROFILING
sched_l2_swimlane_[thread_idx].phase_dispatch_count += 1;
#endif
return 1;
} else {
out_handles[0] =
prepare_subtask_to_core(thread_idx, core_offset, slot_state, PTO2SubtaskSlot::AIV0, to_pending, block_idx);
out_handles[0] = prepare_subtask_to_core(
thread_idx, core_offset, slot_state, PTO2SubtaskSlot::AIV0, (pending_mask & PTO2_SUBTASK_MASK_AIV0) != 0,
block_idx
);
#if PTO2_PROFILING
sched_l2_swimlane_[thread_idx].phase_dispatch_count += 1;
#endif

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Centralize the promoted_subtasks increment inside prepare_block_for_dispatch for running-direct subtasks. If a speculative task is partially staged, the remaining blocks are dispatched via the normal ready queue. However, the normal dispatch loop (dispatch_shape) does not increment promoted_subtasks for blocks placed in running slots. This causes promoted_subtasks to never reach total_required_subtasks, permanently deadlocking the task. Centralizing this logic ensures every subtask increments promoted_subtasks exactly once when entering a running slot.

    bool is_staging = slot_state.payload != nullptr &&
                      slot_state.payload->spec_state.load(std::memory_order_acquire) == PTO2_SPEC_STAGING;
    if (shape == PTO2ResourceShape::MIX) {
        // pending_mask: per-subtask PENDING decision (set = pending slot, clear = running).
        uint8_t cmask = slot_state.active_mask.core_mask();
        int n = 0;
        if (cmask & PTO2_SUBTASK_MASK_AIC) {
            bool to_pending = (pending_mask & PTO2_SUBTASK_MASK_AIC) != 0;
            out_handles[n++] = prepare_subtask_to_core(
                thread_idx, tracker.get_aic_core_offset(core_offset), slot_state, PTO2SubtaskSlot::AIC,
                to_pending, block_idx
            );
            if (is_staging && !to_pending) {
                slot_state.payload->promoted_subtasks.fetch_add(1, std::memory_order_acq_rel);
            }
        }
        if (cmask & PTO2_SUBTASK_MASK_AIV0) {
            bool to_pending = (pending_mask & PTO2_SUBTASK_MASK_AIV0) != 0;
            out_handles[n++] = prepare_subtask_to_core(
                thread_idx, tracker.get_aiv0_core_offset(core_offset), slot_state, PTO2SubtaskSlot::AIV0,
                to_pending, block_idx
            );
            if (is_staging && !to_pending) {
                slot_state.payload->promoted_subtasks.fetch_add(1, std::memory_order_acq_rel);
            }
        }
        if (cmask & PTO2_SUBTASK_MASK_AIV1) {
            bool to_pending = (pending_mask & PTO2_SUBTASK_MASK_AIV1) != 0;
            out_handles[n++] = prepare_subtask_to_core(
                thread_idx, tracker.get_aiv1_core_offset(core_offset), slot_state, PTO2SubtaskSlot::AIV1,
                to_pending, block_idx
            );
            if (is_staging && !to_pending) {
                slot_state.payload->promoted_subtasks.fetch_add(1, std::memory_order_acq_rel);
            }
        }
#if PTO2_PROFILING
        sched_l2_swimlane_[thread_idx].phase_dispatch_count += __builtin_popcount(cmask);
#endif
        return n;
    } else if (shape == PTO2ResourceShape::AIC) {
        bool to_pending = (pending_mask & PTO2_SUBTASK_MASK_AIC) != 0;
        out_handles[0] = prepare_subtask_to_core(
            thread_idx, core_offset, slot_state, PTO2SubtaskSlot::AIC, to_pending,
            block_idx
        );
        if (is_staging && !to_pending) {
            slot_state.payload->promoted_subtasks.fetch_add(1, std::memory_order_acq_rel);
        }
#if PTO2_PROFILING
        sched_l2_swimlane_[thread_idx].phase_dispatch_count += 1;
#endif
        return 1;
    } else {
        bool to_pending = (pending_mask & PTO2_SUBTASK_MASK_AIV0) != 0;
        out_handles[0] = prepare_subtask_to_core(
            thread_idx, core_offset, slot_state, PTO2SubtaskSlot::AIV0, to_pending,
            block_idx
        );
        if (is_staging && !to_pending) {
            slot_state.payload->promoted_subtasks.fetch_add(1, std::memory_order_acq_rel);
        }
#if PTO2_PROFILING
        sched_l2_swimlane_[thread_idx].phase_dispatch_count += 1;
#endif

Comment on lines +617 to +620
// Running-direct subtasks are now in their running slots, gated-spinning — count
// them toward the doorbell gate; pending subtasks count at their promote (Case 3.3).
if (running_direct > 0)
c->payload->promoted_subtasks.fetch_add(running_direct, std::memory_order_acq_rel);

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

Since the promoted_subtasks increment for running-direct subtasks is now handled centrally inside prepare_block_for_dispatch, we can remove the manual running_direct increment logic here to simplify the code and avoid double-counting.

    // Running-direct subtasks are now counted centrally inside prepare_block_for_dispatch.

@ChaoWao ChaoWao force-pushed the worktree-iterative-humming-papert branch 2 times, most recently from e64dc0b to 5d1b304 Compare July 6, 2026 02:34
…ltaneity

Allow MIX (cube+AIV) blocks to stage their AIC/AIV subtasks on whatever core
slot is free (running if idle, pending if busy+pending-free), independently.
The in-kernel simultaneity contract (FFTS syncall + cross-core pipe) is upheld
by gating the whole task behind the speculative doorbell until every subtask
has promoted to a running slot, so all subtasks activate at the same instant.

Changes:
- classify_mix_cluster: new SPLIT mode + MixPlacementPlan{mode, pending_mask}
  with deadlock guard (pending subtask must NOT land behind a gated-running core)
- prepare_block_for_dispatch: per-subtask pending_mask (was single bool)
- stage_consumer_blocks: sources split clusters, sets requires_split_gate,
  counts promoted_subtasks, calls maybe_ring_split_doorbell
- maybe_ring_split_doorbell: rings only when fanin complete + fully staged
  (next_block_idx >= logical_block_num) + all subtasks promoted (promoted ==
  total_required_subtasks). The full-staging check prevents partial-ring
  deadlocks on full-occupancy syncall tasks (e.g. fa_fused).
- try_speculative_release: split tasks (requires_split_gate) defer ring via
  maybe_ring; non-split tasks keep immediate ring at fanin (sticky token)
- Case 3.3 promote path: increments promoted_subtasks + triggers maybe_ring
- promoted_subtasks + requires_split_gate fields on PTO2TaskPayload

Co-Authored-By: Claude <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.

Actionable comments posted: 3

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)

1086-1115: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Widen or bound the doorbell counter. promoted_subtasks and total_required_subtasks are int16_t, but total_required_subtasks is computed as block_num * popcount(active_mask) with only a block_num >= 1 check. Large launches can wrap the threshold and leave the gate closed forever; widen these counters or add an upper-bound assert before submit.

🤖 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 1086 - 1115, The doorbell gate in maybe_ring_split_doorbell is
using int16_t counters for promoted_subtasks and total_required_subtasks, but
total_required_subtasks can overflow when computed from block_num and
popcount(active_mask). Fix this by either widening both counters to a larger
integer type or adding a hard upper-bound check before submission so the
threshold cannot wrap. Use the symbols maybe_ring_split_doorbell,
promoted_subtasks, total_required_subtasks, and the block_num/active_mask-based
computation to locate the change.
🧹 Nitpick comments (1)
src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_types.h (1)

324-328: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No direct unit-test coverage for the blocked_pending deadlock guard.

classify_mix_cluster's blocked_pending parameter is the core new deadlock-avoidance mechanism described in the PR objective, but none of the added/updated tests in test_scheduler_state.cpp exercise a non-default blocked_pending value (e.g., a core that would otherwise classify as pending-eligible but is excluded because its running task is gated). Consider adding a test that sets a bit in blocked_pending for a busy-but-otherwise-pending-free core and asserts the cluster falls back to REJECT/SPLIT-without-that-subtask instead of PENDING/SPLIT.

🤖 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_types.h`
around lines 324 - 328, Add direct test coverage for the new blocked_pending
deadlock guard in classify_mix_cluster. Update test_scheduler_state.cpp to call
classify_mix_cluster with a non-default blocked_pending bit set on a core that
would otherwise be pending-eligible, and assert the result no longer treats that
subtask as pending-eligible (for example, it falls back to REJECT or a split
that excludes that subtask). Use the existing classify_mix_cluster and
blocked_pending symbols so the test covers the gated-running-task case described
in the scheduler_types.h contract.
🤖 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.

Inline comments:
In `@src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/pto_scheduler.h`:
- Around line 1117-1139: The early-dispatch overflow path in
try_speculative_release leaves PTO2_SPEC_STAGING tasks stranded because the
failed push only logs and does not restore a normal dispatch path. Update the
overflow handling in this flow so that when the early-dispatch queue cannot
accept the task, spec_state is reset from PTO2_SPEC_STAGING to a non-staging
state and the task is requeued through the normal ready path or otherwise safely
retried. Keep the fix localized around try_speculative_release and the
staging/doorbell handoff so staged tasks never lose their only route to
dispatch.

In
`@src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_dispatch.cpp`:
- Around line 593-602: The split-cluster dispatch loop in scheduler_dispatch.cpp
is dropping work when re-classification no longer returns SPLIT after
split.pop_first() consumes an offset. Update the block handling around
classify_mix_cluster and prepare_block_for_dispatch so a failed re-check does
not silently skip the block; instead, reinsert the popped cluster offset or
otherwise requeue the shortfall before continuing. Use the surrounding split,
block, count, staged, and prepare_block_for_dispatch logic to keep the current
claim consistent.
- Around line 661-679: The MIX pending path is missing the same deadlock guard
used for split clusters, so a STAGING-gated running core can still be treated as
pending-eligible. Update `stage_consumer_blocks()` and/or
`prepare_block_for_dispatch()` so the `pend` path also applies `blocked_pending`
when using `get_pending_core_offset_states(PTO2ResourceShape::MIX)`, and make
sure the MIX pending selection excludes cores whose
`CoreExecState::running_slot_state->payload->spec_state` is `PTO2_SPEC_STAGING`.
Use the existing `blocked_pending` logic from `scheduler_dispatch.cpp` as the
reference point and keep the whole-cluster PENDING dispatch from selecting a
fully-running MIX cluster when a gated core is present.

---

Outside diff comments:
In `@src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/pto_scheduler.h`:
- Around line 1086-1115: The doorbell gate in maybe_ring_split_doorbell is using
int16_t counters for promoted_subtasks and total_required_subtasks, but
total_required_subtasks can overflow when computed from block_num and
popcount(active_mask). Fix this by either widening both counters to a larger
integer type or adding a hard upper-bound check before submission so the
threshold cannot wrap. Use the symbols maybe_ring_split_doorbell,
promoted_subtasks, total_required_subtasks, and the block_num/active_mask-based
computation to locate the change.

---

Nitpick comments:
In
`@src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_types.h`:
- Around line 324-328: Add direct test coverage for the new blocked_pending
deadlock guard in classify_mix_cluster. Update test_scheduler_state.cpp to call
classify_mix_cluster with a non-default blocked_pending bit set on a core that
would otherwise be pending-eligible, and assert the result no longer treats that
subtask as pending-eligible (for example, it falls back to REJECT or a split
that excludes that subtask). Use the existing classify_mix_cluster and
blocked_pending symbols so the test covers the gated-running-task case described
in the scheduler_types.h contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3ce28feb-9467-469d-b4db-28d72b8844e9

📥 Commits

Reviewing files that changed from the base of the PR and between fb65fe9 and 5d1b304.

📒 Files selected for processing (7)
  • 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_completion.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_types.h
  • tests/ut/cpp/a2a3/test_scheduler_state.cpp

Comment on lines +1117 to 1139
inline bool try_speculative_release(PTO2TaskSlotState &slot_state, SpecReleaseSink *sink = nullptr) {
// Never staged => CAS NONE->DISPATCHED wins => dispatch normally.
uint8_t expect = PTO2_SPEC_NONE;
if (slot_state.payload->spec_state.compare_exchange_strong(
expect, PTO2_SPEC_DISPATCHED, std::memory_order_seq_cst, std::memory_order_seq_cst
)) {
return false;
}
// Staged (STAGING). A STAGING task is exclusively owned by the early-
// dispatch drain — it must NEVER fall back to normal dispatch (which would
// build_payload with not_ready=1 but skip staged_core_mask / promoted_subtasks
// tracking, leaving those blocks invisible to the gate → deadlocked spin).
//
// maybe_ring fires now if all conditions are met (fully staged + fanin +
// promoted==total). If not yet fully staged, it leaves STAGING; the drain
// continues staging remaining blocks and re-checks via maybe_ring after each
// batch + on each pending subtask promote (Case 3.3).
//
// Return true unconditionally so release_fanin_and_check_ready does NOT push
// the task to the ready queue — the early-dispatch drain owns its lifecycle.
maybe_ring_split_doorbell(slot_state, sink);
return true;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find early_dispatch_queue.push overflow handling and any spec_state reset to NONE
rg -nP 'early_dispatch_queue\.push' src/a2a3/runtime/tensormap_and_ringbuffer -C4
rg -nP 'spec_state\.(store|compare_exchange).*(NONE|PTO2_SPEC_NONE)' src/a2a3/runtime/tensormap_and_ringbuffer -C2

Repository: hw-native-sys/simpler

Length of output: 4529


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant scheduler code paths and the queue implementation/usage.
sed -n '980,1085p' src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/pto_scheduler.h
sed -n '1090,1145p' src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/pto_scheduler.h
sed -n '650,715p' src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_dispatch.cpp

# Find the early_dispatch_queue type/definition and any overflow/drop behavior.
rg -n "early_dispatch_queue|queue full|drop \(no re-push\)|spec_state|PTO2_SPEC_STAGING|PTO2_SPEC_NONE" \
  src/a2a3/runtime/tensormap_and_ringbuffer/runtime -C 3

# Inspect the runtime type initialization where spec_state is reset.
sed -n '330,365p' src/a2a3/runtime/tensormap_and_ringbuffer/runtime/pto_runtime2_types.h

Repository: hw-native-sys/simpler

Length of output: 50377


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look for any runtime transition from STAGING back to NONE or any queue-drop handler.
rg -n "PTO2_SPEC_STAGING|PTO2_SPEC_NONE|spec_state\.store|compare_exchange_strong|compare_exchange_weak|queue full|overflow|drop" \
  src/a2a3/runtime/tensormap_and_ringbuffer/runtime -C 2

# Inspect the ready-queue implementation used by early_dispatch_queue.
fd -a "ready_queue" src/a2a3/runtime/tensormap_and_ringbuffer/runtime
rg -n "class PTO2ReadyQueue|struct PTO2ReadyQueue|push\(|pop\(|overflow|full|drop" \
  src/a2a3/runtime/tensormap_and_ringbuffer/runtime -C 2

Repository: hw-native-sys/simpler

Length of output: 50378


Reset staged tasks on early-dispatch queue overflow The overflow path only logs the failed push; it doesn’t clear spec_state or requeue the task. A PTO2_SPEC_STAGING consumer can then be left with no route back to normal dispatch and strand its gated cores.

🤖 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 1117 - 1139, The early-dispatch overflow path in
try_speculative_release leaves PTO2_SPEC_STAGING tasks stranded because the
failed push only logs and does not restore a normal dispatch path. Update the
overflow handling in this flow so that when the early-dispatch queue cannot
accept the task, spec_state is reset from PTO2_SPEC_STAGING to a non-staging
state and the task is requeued through the normal ready path or otherwise safely
retried. Keep the fix localized around try_speculative_release and the
staging/doorbell handoff so staged tasks never lose their only route to
dispatch.

Comment on lines +593 to +602
// Split clusters (per-subtask placement: idle cores → running, busy → pending).
// Re-classify under the deadlock guard; skip if no longer SPLIT (state raced).
while (count > 0 && split.has_value()) {
int32_t cluster_offset = split.pop_first();
CoreTracker::MixPlacementPlan plan = tracker.classify_mix_cluster(cluster_offset, cmask, blocked_pending);
if (plan.mode != CoreTracker::MixPlacement::SPLIT) continue;
n += prepare_block_for_dispatch(thread_idx, cluster_offset, *c, shape, plan.pending_mask, block, &handles[n]);
running_direct += __builtin_popcount(cmask & static_cast<uint8_t>(~plan.pending_mask));
block++; count--; staged++;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect how staged/claimed mismatches are handled elsewhere, and whether stage_consumer_blocks
# callers ever reconcile a return value < claim.
rg -n -B3 -A10 'stage_consumer_blocks' src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_dispatch.cpp
rg -n -B3 -A15 'int32_t SchedulerContext::try_speculative_early_dispatch' src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_dispatch.cpp

Repository: hw-native-sys/simpler

Length of output: 3107


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the relevant function body with line numbers.
sed -n '565,715p' src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_dispatch.cpp | cat -n

# Find the classification helper and the MixPlacement definitions.
rg -n -A20 -B10 'classify_mix_cluster|struct MixPlacementPlan|enum class MixPlacement|MixPlacement::SPLIT' src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler

Repository: hw-native-sys/simpler

Length of output: 34946


Requeue the block when SPLIT re-classification fails

split.pop_first() consumes the cluster before the re-check. If classify_mix_cluster(...).mode no longer returns SPLIT, the continue drops that block from the current claim without advancing block/count, and there’s no retry path here. Reinsert the offset or requeue the shortfall instead of silently skipping it.

🤖 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 593 - 602, The split-cluster dispatch loop in
scheduler_dispatch.cpp is dropping work when re-classification no longer returns
SPLIT after split.pop_first() consumes an offset. Update the block handling
around classify_mix_cluster and prepare_block_for_dispatch so a failed re-check
does not silently skip the block; instead, reinsert the popped cluster offset or
otherwise requeue the shortfall before continuing. Use the surrounding split,
block, count, staged, and prepare_block_for_dispatch logic to keep the current
claim consistent.

Comment on lines +661 to +679
// Split clusters (mixed running+pending within one MIX block) — the fix for
// stranded AIV cores. Source with the deadlock guard: a core whose running
// task is gated (STAGING) is not pending-eligible.
CoreTracker::BitStates split(0ULL);
CoreTracker::BitStates blocked_pending(0ULL);
if (shape == PTO2ResourceShape::MIX) {
const int32_t *cids = tracker.core_ids();
int32_t nc = tracker.core_num();
for (int k = 0; k < nc; k++) {
CoreExecState &ce = core_exec_states_[cids[k]];
if (ce.running_slot_state != nullptr && ce.running_slot_state->payload != nullptr &&
ce.running_slot_state->payload->spec_state.load(std::memory_order_acquire) == PTO2_SPEC_STAGING) {
blocked_pending |= CoreTracker::BitStates(1ULL << k);
}
}
split = tracker.get_mix_split_cluster_offset_states(c->active_mask.core_mask(), blocked_pending);
}
int32_t freecores = (idle.has_value() ? idle.count() : 0) + (pend.has_value() ? pend.count() : 0) +
(split.has_value() ? split.count() : 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
ast-grep outline src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_types.h --match 'get_pending_core_offset_states' --items all
rg -n -A20 'BitStates get_pending_core_offset_states' src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_types.h

Repository: hw-native-sys/simpler

Length of output: 1575


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find where the pending helper is used in the scheduler path
rg -n -A8 -B8 'get_pending_core_offset_states\(' src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler

# Map the classification helper that the inline comment references
ast-grep outline src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_types.h --match 'classify_mix_cluster' --items all
rg -n -A80 -B20 'classify_mix_cluster' src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_types.h

Repository: hw-native-sys/simpler

Length of output: 17238


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '658,720p' src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_dispatch.cpp

Repository: hw-native-sys/simpler

Length of output: 3525


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -A40 -B20 'stage_consumer_blocks\(' src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_dispatch.cpp
rg -n -A40 -B20 'blocked_pending' src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/scheduler_dispatch.cpp

Repository: hw-native-sys/simpler

Length of output: 18536


Apply the deadlock guard to the MIX pending path. get_pending_core_offset_states(PTO2ResourceShape::MIX) can still return a fully-running cluster, and stage_consumer_blocks() forwards pend to prepare_block_for_dispatch(..., pending_mask=0xFF) without blocked_pending. That leaves a STAGING-gated running core eligible for whole-cluster PENDING dispatch and can deadlock the MIX path.

🤖 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 661 - 679, The MIX pending path is missing the same deadlock guard
used for split clusters, so a STAGING-gated running core can still be treated as
pending-eligible. Update `stage_consumer_blocks()` and/or
`prepare_block_for_dispatch()` so the `pend` path also applies `blocked_pending`
when using `get_pending_core_offset_states(PTO2ResourceShape::MIX)`, and make
sure the MIX pending selection excludes cores whose
`CoreExecState::running_slot_state->payload->spec_state` is `PTO2_SPEC_STAGING`.
Use the existing `blocked_pending` logic from `scheduler_dispatch.cpp` as the
reference point and keep the whole-cluster PENDING dispatch from selecting a
fully-running MIX cluster when a gated core is present.

@Crane-Liu

Copy link
Copy Markdown

Latest perf-only comparison (PR691 pto-lib, Qwen3-14B decode, fwd_layers=2, seed=1234, device=1):

set pass device_wall_us sched_us 对比 baseline
baseline_x5 5/5 4132.152 3513.652 0
current_step4_x3 3/3 4089.180 3475.460 device -1.040%, sched -1.087%

相对收益:device 1.040%,sched 1.087%。

@ChaoWao ChaoWao closed this Jul 9, 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.

2 participants