Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions docs/tensormap-and-ringbuffer-a2a3-vs-a5.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ The functional differences group into the following themes:
| URMA completion | A5-specific implementation and product capability gate | Yes, for now | Retain the A5 path; do not claim that URMA is available in the default build |
| Next-block prefetch | A2/A3-only performance optimization | No | Retain on A2/A3; validate on A5 before considering a port |
| Scheduler progress publication | AICPU topology and measured publication cost | No | Retain A5's 16-task batching; keep per-advance publication on A2/A3, where the portable implementation showed no significant benefit |
| Terminal deferred release | Measured end-of-run Scheduler release cost | No | Keep the current experiment scoped to A5; A2/A3 traces do not show the same large terminal release stall |
| Fatal teardown | Software reliability strategy | No | Retain the current implementations; decide whether to converge after measuring the worst-case A5 teardown time |
| Scheduler trace attribution | Software diagnostic strategy | No | Preserve the current traces; converge only after comparing generated timelines |

Expand Down Expand Up @@ -293,6 +294,36 @@ measurements instead showed lower Effective time in all eight workloads, with
an unweighted mean reduction of `2.81%`. Full A2/A3 measurements are recorded
in the [PR benchmark follow-up](https://github.com/hw-native-sys/simpler/pull/1575#issuecomment-5310909143).

### Terminal Deferred Release: A5 Experiment Scope

The terminal deferred-release experiment is currently A5-only. A5 swimlanes
showed Scheduler time extending beyond useful work while a large accumulated
release backlog updated task reference counts and advanced ring reclamation at
the end of a run. The corresponding A2/A3 traces reviewed for this work did
not show a comparable block of terminal release work, so there is no measured
A2/A3 bottleneck for this optimization to address.

On A5, the experiment keeps exact per-task release while orchestration can
still create work. Once orchestration is sealed, Schedulers may discard their
local deferred-release backlogs at existing release boundaries. After every
task has completed and all Schedulers leave dispatch, the last thread at a
terminal barrier closes the remaining live ring slots in one pass and
publishes the final ring state. Errors and non-terminal exits retain exact
per-task lifecycle handling.

The A5 benchmark covers seven workloads for 100 rounds and Qwen3 for five
rounds. No workload regressed by 5% in Orchestrator time. Batch Paged Attention,
the workload that exposed the terminal release cost, changed from
`5918.916 us` on the refreshed A5 Main baseline to `5819.991 us` with the
experiment (`-1.67%`). The same experiment had measured `6947.106 us` before
restoring the Main Scheduler entry gate, which also demonstrates that hot-code
layout must be preserved when evaluating the lifecycle optimization.

These results establish an A5 optimization target, not a platform-independent
policy. A2/A3 keeps its existing release behavior unless a future A2/A3
swimlane shows the same terminal release bottleneck and a separate benchmark
demonstrates a benefit.

### Fatal Teardown

The A2/A3 scheduler uses a dedicated fatal latch to elect an owner, broadcasts
Expand Down
4 changes: 4 additions & 0 deletions simpler_setup/tools/swimlane_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1759,6 +1759,8 @@ def sched_lane_tid(thread_idx, lane=0):
"drain_prepare": "cq_build_attempt_runnable", # inner: cluster scan + build_payload
"drain_publish": "cq_build_attempt_passed", # inner: MMIO write_reg per subtask (the cohort launch)
"graph_prepare": "rail_animation", # bounded Scheduler-side Definition expansion
# 调用阶段:设备侧 Scheduler 已结束,Host 正在离线转换 terminal phase。
"terminal_close": "olive", # successful-run bulk lifecycle closure
# Inner in TMR; standalone on HBG's dedicated P thread.
"resolve": "vsync_highlight_color", # on_task_complete: walk consumer list
# Separate-lane (Worker View AICPU_N) — fallback color if it ever lands on Sched
Expand Down Expand Up @@ -1935,6 +1937,8 @@ def _find_containing_complete(thread_idx: int, finish_us: float):
"drain_prepare",
"drain_publish",
"graph_prepare",
# 调用阶段:设备侧 Scheduler 已结束,Host 筛选并导出已回传的 terminal phase。
"terminal_close",
):
continue
start_us = record["start_time_us"]
Expand Down
12 changes: 6 additions & 6 deletions src/a5/runtime/tensormap_and_ringbuffer/aicpu/aicpu_executor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -304,13 +304,11 @@ int32_t AicpuExecutor::init(Runtime *runtime) {
if (init_failed_.load(std::memory_order_acquire)) return -1;
}
#else
// 调用阶段:Orchestrator 和 Scheduler 启动前的 AICPU 握手阶段。
// Perf path: a scheduler that sees an invalid core report (its own or a
// peer's, observed so far) latches completed_ via abort_and_shutdown, which
// stops any peer still entering dispatch (run()'s is_completed() gate). A
// peer that already passed that gate is not joined here — its own cores are
// valid (it handshaked them), and the failure ends in the host device reset
// that reaps every core, so the residual overlap is bounded and
// non-corrupting. finished_count_ is reset per-run in deinit(), not here.
// peer's, observed so far) latches completed_ via abort_and_shutdown.
// Peers that have not entered dispatch stop at run()'s completion gate;
// the failure ends in the host device reset that reaps every core.
if (sched_ctx_.handshake_failed()) {
sched_ctx_.abort_and_shutdown(runtime);
init_failed_.store(true, std::memory_order_release);
Expand Down Expand Up @@ -851,6 +849,8 @@ int32_t AicpuExecutor::run(Runtime *runtime) {
LOG_INFO("Thread %d: Orchestrator completed", thread_idx);
}

// 调用阶段:Scheduler 线程进入调度;正常并发模式下 Orchestrator 此时仍可能运行。
// 成功路径的 Scheduler 参加统一终态协议;已完成的失败路径直接进入 shutdown。
// Scheduler thread (orchestrator thread skips dispatch and exits after orchestration)
if (!sched_ctx_.is_completed() && thread_idx < sched_thread_num_) {
// Device orchestration: wait for the primary orchestrator to initialize the SM header
Expand Down
5 changes: 4 additions & 1 deletion src/a5/runtime/tensormap_and_ringbuffer/runtime/async_wait.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ struct AsyncWaitList {
ChipTaskSlotState **deferred_release_slot_states{nullptr};
int32_t *deferred_release_count{nullptr};
int32_t deferred_release_capacity{0};
// 调用阶段:Scheduler 运行期间;Orchestrator 可能仍在运行,也可能已经结束。
const std::atomic<bool> *release_seal{nullptr};
int32_t inline_completed{0};
#if SIMPLER_SCHED_PROFILING
int32_t thread_idx{0};
Expand Down Expand Up @@ -303,10 +305,11 @@ struct AsyncWaitList {
}

template <bool Profiling>
// 调用阶段:Scheduler 主循环轮询异步完成;Orchestrator 可能仍在运行,也可能已经结束。
AsyncPollResult poll_and_complete(
AICoreCompletionMailbox *aicore_mailbox, SchedulerState *sched,
ChipTaskSlotState **deferred_release_slot_states, int32_t &deferred_release_count,
int32_t deferred_release_capacity
int32_t deferred_release_capacity, const std::atomic<bool> *release_seal
#if SIMPLER_SCHED_PROFILING
,
int thread_idx
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,45 @@ struct SchedulerState {
return advanced;
}

// 调用阶段:Orchestrator 已结束、所有任务已完成,并且全部 Scheduler 调度循环均已退出。
// 仅 terminal barrier 的最后到达线程调用,统一关闭仍存活的 ring slot。
int32_t terminal_close_live_slots() {
int32_t current_task_indices[CHIP_MAX_RING_DEPTH];
int32_t total_closed = 0;

for (int32_t ring_id = 0; ring_id < CHIP_MAX_RING_DEPTH; ring_id++) {
auto &ring_sched = ring_sched_states[ring_id];
int32_t current_task_index = ring_sched.ring->fc.current_task_index.load(std::memory_order_acquire);
int32_t live_count = current_task_index - ring_sched.last_task_alive;
if (live_count < 0 || static_cast<uint64_t>(live_count) > ring_sched.ring->task_window_size) {
LOG_ERROR(
"terminal lifecycle close has invalid ring %d interval [%d, %d) for window %" PRIu64, ring_id,
ring_sched.last_task_alive, current_task_index, ring_sched.ring->task_window_size
);
return -1;
}
current_task_indices[ring_id] = current_task_index;
total_closed += live_count;
}

for (int32_t ring_id = 0; ring_id < CHIP_MAX_RING_DEPTH; ring_id++) {
auto &ring_sched = ring_sched_states[ring_id];
int32_t current_task_index = current_task_indices[ring_id];
for (int32_t id = ring_sched.last_task_alive; id < current_task_index; id++) {
ChipTaskSlotState &slot_state = ring_sched.ring->get_slot_state_by_task_id(id);
slot_state.task_state.store(CHIP_TASK_CONSUMED, std::memory_order_relaxed);
slot_state.reset_for_reuse();
}
ring_sched.last_task_alive = current_task_index;
ring_sched.sync_to_sm(true);
}

advance_pending_mask.store(0, std::memory_order_relaxed);
publication_request_mask.store(0, std::memory_order_relaxed);
publication_ack_mask.store(0, std::memory_order_relaxed);
return total_closed;
}

bool try_claim_ready_once(ChipTaskSlotState &slot_state) {
uint8_t flags = slot_state.lifecycle_flags.load(std::memory_order_acquire);
for (;;) {
Expand Down Expand Up @@ -1323,26 +1362,37 @@ AsyncWaitList::try_inline_complete_locked(AsyncWaitList::DrainCompletionSink &si
#else
sink.sched->on_task_complete(slot_state);
#endif
// 调用阶段:Scheduler drain 正在处理异步完成;Orchestrator 可能仍在运行,也可能已经结束。
// 仅当本地 release buffer 满时读取 seal,Orchestrator 结束后跳过逐 task release。
bool release_elided = false;
if (*sink.deferred_release_count >= sink.deferred_release_capacity) {
while (*sink.deferred_release_count > 0) {
release_elided = sink.release_seal != nullptr && sink.release_seal->load(std::memory_order_acquire);
if (release_elided) {
*sink.deferred_release_count = 0;
} else {
while (*sink.deferred_release_count > 0) {
#if SIMPLER_SCHED_PROFILING
(void)sink.sched->on_task_release(
*sink.deferred_release_slot_states[--(*sink.deferred_release_count)], sink.thread_idx
);
(void)sink.sched->on_task_release(
*sink.deferred_release_slot_states[--(*sink.deferred_release_count)], sink.thread_idx
);
#else
sink.sched->on_task_release(*sink.deferred_release_slot_states[--(*sink.deferred_release_count)]);
sink.sched->on_task_release(*sink.deferred_release_slot_states[--(*sink.deferred_release_count)]);
#endif
}
}
}
sink.deferred_release_slot_states[(*sink.deferred_release_count)++] = &slot_state;
if (!release_elided) {
sink.deferred_release_slot_states[(*sink.deferred_release_count)++] = &slot_state;
}
sink.inline_completed++;
return true;
}

template <bool Profiling>
// 调用阶段:Scheduler 主循环轮询异步任务;Orchestrator 可能仍在运行,也可能已经结束。
inline AsyncPollResult AsyncWaitList::poll_and_complete(
AICoreCompletionMailbox *aicore_mailbox, SchedulerState *sched, ChipTaskSlotState **deferred_release_slot_states,
int32_t &deferred_release_count, int32_t deferred_release_capacity
int32_t &deferred_release_count, int32_t deferred_release_capacity, const std::atomic<bool> *release_seal
#if SIMPLER_SCHED_PROFILING
,
int thread_idx
Expand All @@ -1356,6 +1406,8 @@ inline AsyncPollResult AsyncWaitList::poll_and_complete(
sink.deferred_release_slot_states = deferred_release_slot_states;
sink.deferred_release_count = &deferred_release_count;
sink.deferred_release_capacity = deferred_release_capacity;
// 调用阶段:Scheduler 运行期间向 mailbox drain 传递 Orchestrator 完成标志。
sink.release_seal = release_seal;
#if SIMPLER_SCHED_PROFILING
sink.thread_idx = thread_idx;
#endif
Expand Down Expand Up @@ -1394,16 +1446,28 @@ inline AsyncPollResult AsyncWaitList::poll_and_complete(
#else
sched->on_task_complete(*entry.slot_state);
#endif
// 调用阶段:Scheduler 完成异步 task;Orchestrator 可能仍在运行,也可能已经结束。
// seal 只在 release buffer 满的边界生效。
bool release_elided = false;
if (deferred_release_count >= deferred_release_capacity) {
while (deferred_release_count > 0) {
release_elided = release_seal != nullptr && release_seal->load(std::memory_order_acquire);
if (release_elided) {
deferred_release_count = 0;
} else {
while (deferred_release_count > 0) {
#if SIMPLER_SCHED_PROFILING
(void)sched->on_task_release(*deferred_release_slot_states[--deferred_release_count], thread_idx);
(void)sched->on_task_release(
*deferred_release_slot_states[--deferred_release_count], thread_idx
);
#else
sched->on_task_release(*deferred_release_slot_states[--deferred_release_count]);
sched->on_task_release(*deferred_release_slot_states[--deferred_release_count]);
#endif
}
}
}
deferred_release_slot_states[deferred_release_count++] = entry.slot_state;
if (!release_elided) {
deferred_release_slot_states[deferred_release_count++] = entry.slot_state;
}
result.completed++;

int32_t last = count - 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
*/
#include "scheduler_context.h"

// 使用阶段:Scheduler 调度结束后的 terminal closure 冷路径。
#include <algorithm>
#include <cinttypes>
#include <cstdio>
#include <limits>

#include "common/unified_log.h"
#include "aicpu/aicpu_device_config.h"
Expand Down Expand Up @@ -52,6 +55,48 @@ static bool latch_scheduler_error(SharedMemoryHeader *header, int32_t thread_idx
return won;
}

// 调用阶段:当前 Scheduler 线程已经退出调度循环;Orchestrator 已结束且全部任务已完成时进入 barrier。
// 最后到达的 Scheduler 执行 bulk closure,其他 Scheduler 等待 closure 发布完成。
int32_t SchedulerContext::finish_successful_terminal(SharedMemoryHeader *header, int32_t thread_idx) {
bool successful_terminal = orchestrator_done_.load(std::memory_order_acquire) && total_tasks_ > 0 &&
completed_tasks_.load(std::memory_order_acquire) >= total_tasks_ &&
header->orch_error_code.load(std::memory_order_acquire) == SIMPLER_ERROR_NONE &&
header->sched_error_code.load(std::memory_order_acquire) == SIMPLER_ERROR_NONE;
if (!successful_terminal) return 0;

int32_t terminal_close_result = 0;
int32_t arrived = terminal_close_arrived_.fetch_add(1, std::memory_order_acq_rel) + 1;
if (arrived == active_sched_threads_) {
#if SIMPLER_DFX
uint64_t terminal_close_t0 = get_sys_cnt_aicpu();
#endif
terminal_close_result = sched_->terminal_close_live_slots();
#if SIMPLER_DFX
if (chip_swimlane_level_ >= ChipSwimlaneLevel::SCHED_PHASES) {
uint64_t terminal_close_t1 = get_sys_cnt_aicpu();
int16_t phase_depth[CHIP_SWIMLANE_NUM_QUEUE_SHAPES];
constexpr size_t kMax = static_cast<size_t>(std::numeric_limits<int16_t>::max());
for (int s = 0; s < CHIP_SWIMLANE_NUM_QUEUE_SHAPES; s++) {
size_t qsize = sched_->ready_queues[s].size() + sched_->ready_sync_queues[s].size();
phase_depth[s] = static_cast<int16_t>(std::min(qsize, kMax));
}
chip_swimlane_aicpu_record_sched_phase(
thread_idx, ChipSwimlaneSchedPhaseKind::TerminalClose, terminal_close_t0, terminal_close_t1,
sched_chip_swimlane_[thread_idx].sched_loop_count,
static_cast<uint32_t>(terminal_close_result < 0 ? 0 : terminal_close_result), /*pop_hit=*/0,
/*pop_miss=*/0, phase_depth, phase_depth
);
}
#endif
terminal_close_status_.store(terminal_close_result < 0 ? -1 : 1, std::memory_order_release);
} else {
while ((terminal_close_result = terminal_close_status_.load(std::memory_order_acquire)) == 0) {
SPIN_WAIT_HINT();
}
}
return terminal_close_result < 0 ? -1 : 0;
}

LoopAction SchedulerContext::handle_orchestrator_exit(
int32_t thread_idx, SharedMemoryHeader *header, Runtime *runtime, int32_t &task_count
) {
Expand Down Expand Up @@ -1152,6 +1197,9 @@ int32_t SchedulerContext::pre_handshake_init(
// released to dispatch.
completed_tasks_.store(0, std::memory_order_release);
orchestrator_done_.store(false, std::memory_order_release);
// 调用阶段:Orchestrator 与 Scheduler 启动前,由握手 leader 初始化本轮 terminal barrier。
terminal_close_arrived_.store(0, std::memory_order_release);
terminal_close_status_.store(0, std::memory_order_release);
func_id_to_addr_ = runtime->dev.func_id_to_addr_;

// total_tasks_ must be read before hs_setup_done_ is published: on the
Expand Down Expand Up @@ -1315,6 +1363,9 @@ void SchedulerContext::deinit() {
total_tasks_ = 0;
orchestrator_done_.store(false, std::memory_order_release);
completed_.store(false, std::memory_order_release);
// 调用阶段:Orchestrator 与全部 Scheduler 均已结束,deinit 为下一轮清理终态协调状态。
terminal_close_arrived_.store(0, std::memory_order_release);
terminal_close_status_.store(0, std::memory_order_release);

// Reset core discovery and assignment state
aic_count_ = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,12 @@ void SchedulerContext::complete_slot_task(
}
chip_swimlane.phase_complete_count++;
#endif
// 调用阶段:Scheduler 处理 AICore completion;Orchestrator 可能仍在运行,也可能已经结束。
// 仅在本地 release buffer 满时读取 seal;编排结束后丢弃 release 债务,稍后统一收口。
if (deferred_release_count < DEFERRED_RELEASE_CAP) {
deferred_release_slot_states[deferred_release_count++] = &slot_state;
} else if (orchestrator_done_.load(std::memory_order_acquire)) {
deferred_release_count = 0;
} else {
while (deferred_release_count > 0) {
#if SIMPLER_SCHED_PROFILING
Expand Down
Loading
Loading