Skip to content
Merged
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
384 changes: 378 additions & 6 deletions simpler_setup/tools/swimlane_converter.py

Large diffs are not rendered by default.

15 changes: 14 additions & 1 deletion src/a2a3/platform/include/aicpu/l2_swimlane_collector_aicpu.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,12 @@ void l2_swimlane_aicpu_init_phase(int worker_count, int num_sched_phase_threads,
* pool. Silently drops records when the buffer is full or the pool was not
* primed (init failed for this thread).
*
* Queue-depth snapshots distinguish "task hidden in T0's local_buf" from
* "shared queue has it but peers spin on the wrong shape" — the former shows
* `local_depth > 0, shared_depth == 0` for the owning thread while peers see
* `shared_depth == 0` until overflow. Pass nullptr for any of the four arrays
* when not capturing (the record's corresponding slot is zero-filled).
*
* @param thread_idx Scheduler thread index
* @param kind Complete or Dispatch
* @param start_time Phase start timestamp
Expand All @@ -175,10 +181,17 @@ void l2_swimlane_aicpu_init_phase(int worker_count, int num_sched_phase_threads,
* @param tasks_processed Tasks processed in this phase batch
* @param pop_hit Dispatch delta since last emit (0 for Complete)
* @param pop_miss Dispatch delta since last emit (0 for Complete)
* @param local_at_start Per-shape PTO2LocalReadyBuffer.count at phase start (size L2SWIMLANE_NUM_QUEUE_SHAPES; may be
* nullptr)
* @param shared_at_start Per-shape sched.ready_queues[shape].size() at phase start (may be nullptr)
* @param local_at_end Per-shape PTO2LocalReadyBuffer.count at phase end (may be nullptr)
* @param shared_at_end Per-shape sched.ready_queues[shape].size() at phase end (may be nullptr)
*/
void l2_swimlane_aicpu_record_sched_phase(
int thread_idx, L2SwimlaneSchedPhaseKind kind, uint64_t start_time, uint64_t end_time, uint32_t loop_iter,
uint32_t tasks_processed, uint32_t pop_hit = 0, uint32_t pop_miss = 0
uint32_t tasks_processed, uint32_t pop_hit = 0, uint32_t pop_miss = 0, const int16_t *local_at_start = nullptr,
const int16_t *shared_at_start = nullptr, const int16_t *local_at_end = nullptr,
const int16_t *shared_at_end = nullptr
);

/**
Expand Down
37 changes: 27 additions & 10 deletions src/a2a3/platform/include/common/l2_swimlane_profiling.h
Original file line number Diff line number Diff line change
Expand Up @@ -473,26 +473,43 @@ enum class L2SwimlaneSchedPhaseKind : uint32_t {
Dispatch = 1, // Dispatch ready tasks to idle cores
};

/** Index layout of the queue-depth snapshot arrays below: AIC=0, AIV=1, MIX=2.
* Must match PTO2ResourceShape's first three values (see pto_submit_types.h).
* Hardcoded here rather than included to keep this header runtime-independent. */
constexpr int L2SWIMLANE_NUM_QUEUE_SHAPES = 3;

/**
* AICPU scheduler phase record (40 bytes).
* AICPU scheduler phase record (64 bytes).
*
* Position in the per-thread buffer is the identity — no thread_id field.
*
* pop_hit / pop_miss carry SCHED_DISPATCH delta counters since the last emit
* (zero for Complete). Kept named, not "extra1"/"extra2", so the device-side
* commit and the host-side JSON emit don't drift on which extra means which.
*
* Queue-depth snapshots (local_depth_*, shared_depth_*) record the per-shape
* scheduler queue occupancy at phase boundaries. They surface the
* dep-release-then-discovery latency that head OH alone can't distinguish from
* register-write latency: a phase whose start sees `local_depth=N, shared=0`
* and end sees `local_depth=N-K` shows that K tasks were popped from this
* thread's private buffer (invisible to peer threads) — peers must spin until
* those tasks overflow into shared. Filled with 0 below SCHED_PHASES.
*/
struct L2SwimlaneAicpuSchedPhaseRecord {
uint64_t start_time; // Phase start timestamp
uint64_t end_time; // Phase end timestamp
uint32_t loop_iter; // Scheduler-loop iteration number on this thread
L2SwimlaneSchedPhaseKind kind; // Complete or Dispatch
uint32_t tasks_processed; // Tasks processed in this phase batch
uint32_t pop_hit; // SCHED_DISPATCH delta since last emit (0 for Complete)
uint32_t pop_miss; // SCHED_DISPATCH delta since last emit (0 for Complete)
uint32_t _pad; // 40B alignment padding
uint64_t start_time; // Phase start timestamp
uint64_t end_time; // Phase end timestamp
uint32_t loop_iter; // Scheduler-loop iteration number on this thread
L2SwimlaneSchedPhaseKind kind; // Complete or Dispatch
uint32_t tasks_processed; // Tasks processed in this phase batch
uint32_t pop_hit; // SCHED_DISPATCH delta since last emit (0 for Complete)
uint32_t pop_miss; // SCHED_DISPATCH delta since last emit (0 for Complete)
int16_t local_depth_at_start[L2SWIMLANE_NUM_QUEUE_SHAPES]; // this thread's PTO2LocalReadyBuffer.count
int16_t local_depth_at_end[L2SWIMLANE_NUM_QUEUE_SHAPES];
int16_t shared_depth_at_start[L2SWIMLANE_NUM_QUEUE_SHAPES]; // sched->ready_queues[shape].size()
int16_t shared_depth_at_end[L2SWIMLANE_NUM_QUEUE_SHAPES];
uint32_t _pad; // 64B alignment padding
};
static_assert(sizeof(L2SwimlaneAicpuSchedPhaseRecord) == 40, "L2SwimlaneAicpuSchedPhaseRecord layout drift");
static_assert(sizeof(L2SwimlaneAicpuSchedPhaseRecord) == 64, "L2SwimlaneAicpuSchedPhaseRecord layout drift");

/**
* AICPU orchestrator phase record (32 bytes).
Expand Down
16 changes: 15 additions & 1 deletion src/a2a3/platform/shared/aicpu/l2_swimlane_collector_aicpu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -797,7 +797,8 @@ static Record *acquire_phase_slot(

void l2_swimlane_aicpu_record_sched_phase(
int thread_idx, L2SwimlaneSchedPhaseKind kind, uint64_t start_time, uint64_t end_time, uint32_t loop_iter,
uint32_t tasks_processed, uint32_t pop_hit, uint32_t pop_miss
uint32_t tasks_processed, uint32_t pop_hit, uint32_t pop_miss, const int16_t *local_at_start,
const int16_t *shared_at_start, const int16_t *local_at_end, const int16_t *shared_at_end
) {
if (!s_phase_initialized) return;
auto *state = s_sched_phase_pools[thread_idx];
Expand All @@ -820,6 +821,19 @@ void l2_swimlane_aicpu_record_sched_phase(
record->tasks_processed = tasks_processed;
record->pop_hit = pop_hit;
record->pop_miss = pop_miss;
auto copy_snapshot = [](int16_t dst[L2SWIMLANE_NUM_QUEUE_SHAPES], const int16_t *src) {
if (src == nullptr) {
for (int i = 0; i < L2SWIMLANE_NUM_QUEUE_SHAPES; i++)
dst[i] = 0;
} else {
for (int i = 0; i < L2SWIMLANE_NUM_QUEUE_SHAPES; i++)
dst[i] = src[i];
}
};
copy_snapshot(record->local_depth_at_start, local_at_start);
copy_snapshot(record->shared_depth_at_start, shared_at_start);
copy_snapshot(record->local_depth_at_end, local_at_end);
copy_snapshot(record->shared_depth_at_end, shared_at_end);
}

void l2_swimlane_aicpu_set_orch_thread_idx(int thread_idx) { s_orch_thread_idx = thread_idx; }
Expand Down
8 changes: 8 additions & 0 deletions src/a2a3/platform/shared/host/l2_swimlane_collector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,9 @@ int L2SwimlaneCollector::export_swimlane_json() {
return "unknown";
};

auto emit_depth_array = [&outfile](const char *key, const int16_t arr[L2SWIMLANE_NUM_QUEUE_SHAPES]) {
outfile << ", \"" << key << "\": [" << arr[0] << "," << arr[1] << "," << arr[2] << "]";
};
outfile << ",\n \"aicpu_scheduler_phases\": [\n";
for (size_t t = 0; t < collected_sched_phase_records_.size(); t++) {
outfile << " [";
Expand All @@ -829,6 +832,11 @@ int L2SwimlaneCollector::export_swimlane_json() {
if (pr.kind == L2SwimlaneSchedPhaseKind::Dispatch) {
outfile << ", \"pop_hit\": " << pr.pop_hit << ", \"pop_miss\": " << pr.pop_miss;
}
// Queue-depth snapshots — [AIC, AIV, MIX] per L2SwimlaneAicpuSchedPhaseRecord docstring.
emit_depth_array("local_at_start", pr.local_depth_at_start);
emit_depth_array("shared_at_start", pr.shared_depth_at_start);
emit_depth_array("local_at_end", pr.local_depth_at_end);
emit_depth_array("shared_at_end", pr.shared_depth_at_end);
outfile << "}";
first = false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include <algorithm>
#include <cinttypes>
#include <limits>

#include "common.h" // debug_assert

Expand Down Expand Up @@ -540,6 +541,63 @@ int32_t SchedulerContext::resolve_and_dispatch(Runtime *runtime, int32_t thread_
l2_swimlane.sched_start_ts = get_sys_cnt_aicpu();
#endif

#if PTO2_PROFILING
// Queue-depth snapshot carried across the iteration boundary: each phase
// emit consumes (phase_start_*) and refreshes them with its own end snapshot
// so the next phase's "at_start" equals the previous phase's "at_end".
//
// L2SWIMLANE_NUM_QUEUE_SHAPES (3) matches PTO2_NUM_RESOURCE_SHAPES: AIC/AIV/MIX.
//
// **Hot-path cost discipline.** Local depth (this thread's PTO2LocalReadyBuffer)
// is a single int read on a register-cached stack — free. Shared depth
// (PTO2ReadyQueue::size) is two atomic relaxed loads against cache lines
// that all peer sched threads also write to (enqueue_pos and dequeue_pos
// bounce on every flush_local_bufs + every pop). With both phases emitting
// per iter that's 12 cross-core loads × thousands of iters per run, a
// measurable AICPU slowdown. Mitigation: lazy + per-iter cached shared
// snapshot, refreshed at most once per iteration. The complete-emit and
// dispatch-emit in the same iter both reuse the same shared sample; the
// big transitions (local→shared flush) still show up across iter boundaries.
static_assert(
L2SWIMLANE_NUM_QUEUE_SHAPES == PTO2_NUM_RESOURCE_SHAPES,
"queue snapshot width must match runtime resource shape count"
);
int16_t phase_start_local[L2SWIMLANE_NUM_QUEUE_SHAPES] = {0};
int16_t phase_start_shared[L2SWIMLANE_NUM_QUEUE_SHAPES] = {0};
int16_t iter_shared_snapshot[L2SWIMLANE_NUM_QUEUE_SHAPES] = {0};
bool iter_shared_sampled = false;
auto capture_local_snapshot = [&](int16_t local_out[L2SWIMLANE_NUM_QUEUE_SHAPES]) {
for (int s = 0; s < L2SWIMLANE_NUM_QUEUE_SHAPES; s++) {
local_out[s] = static_cast<int16_t>(local_bufs[s].count);
}
};
auto get_or_sample_shared = [&]() -> const int16_t * {
if (!iter_shared_sampled) {
// Clamp to int16_t max before narrowing. PTO2_PROF_READYQUEUE_SIZE
// is in the low thousands today but could grow with platform
// scaling — without clamp, sizes above 32767 wrap to negatives
// and silently corrupt the snapshot.
constexpr size_t kMax = static_cast<size_t>(std::numeric_limits<int16_t>::max());
for (int s = 0; s < L2SWIMLANE_NUM_QUEUE_SHAPES; s++) {
const size_t qsize = sched_->ready_queues[s].size();
iter_shared_snapshot[s] = static_cast<int16_t>(std::min(qsize, kMax));
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
iter_shared_sampled = true;
}
return iter_shared_snapshot;
};
auto capture_phase_end = [&](int16_t local_out[L2SWIMLANE_NUM_QUEUE_SHAPES],
int16_t shared_out[L2SWIMLANE_NUM_QUEUE_SHAPES]) {
capture_local_snapshot(local_out);
const int16_t *shared_cached = get_or_sample_shared();
for (int s = 0; s < L2SWIMLANE_NUM_QUEUE_SHAPES; s++)
shared_out[s] = shared_cached[s];
};
if (l2_swimlane_level_ >= L2SwimlaneLevel::SCHED_PHASES) {
capture_phase_end(phase_start_local, phase_start_shared);
}
#endif

// Wall-clock timestamp of the last completed task on this thread.
// Updated on made_progress; consulted to decide whether the wall-clock
// budget for declaring a scheduler hang has elapsed. Initialized to
Expand All @@ -556,6 +614,11 @@ int32_t SchedulerContext::resolve_and_dispatch(Runtime *runtime, int32_t thread_
CYCLE_COUNT_START();
l2_swimlane.sched_loop_count++;
uint64_t _t0_phase = _t0;
// Per-iter lazy shared-queue snapshot: first phase emit in this iter
// pays the atomic-load cost, subsequent emits in the same iter reuse
// the cached value. Reset here so we re-sample exactly once per iter
// (or skip entirely on iters with no phase emit).
iter_shared_sampled = false;
#endif
int32_t task_count = 0;
if (!tracker.has_any_running_cores()) {
Expand Down Expand Up @@ -635,10 +698,24 @@ int32_t SchedulerContext::resolve_and_dispatch(Runtime *runtime, int32_t thread_
} else {
CYCLE_COUNT_LAP(l2_swimlane.sched_complete_cycle);
if (l2_swimlane_level_ >= L2SwimlaneLevel::SCHED_PHASES && l2_swimlane.phase_complete_count > 0) {
// Local depth is cheap (this thread's own buffer counter).
// Shared depth is NOT sampled here: complete's release_fanin
// pushes to local_bufs in the fast path (try_push succeeds
// until cap=64). Shared only changes on dispatch's flush
// path. Carrying phase_start_shared forward as end_shared
// is the right answer 99% of the time AND skips three
// contended atomic loads per emit.
int16_t phase_end_local[L2SWIMLANE_NUM_QUEUE_SHAPES];
capture_local_snapshot(phase_end_local);
l2_swimlane_aicpu_record_sched_phase(
thread_idx, L2SwimlaneSchedPhaseKind::Complete, _t0_phase, _t1, l2_swimlane.sched_loop_count,
l2_swimlane.phase_complete_count
l2_swimlane.phase_complete_count, /*pop_hit=*/0, /*pop_miss=*/0, phase_start_local,
phase_start_shared, phase_end_local, phase_start_shared
);
for (int s = 0; s < L2SWIMLANE_NUM_QUEUE_SHAPES; s++) {
phase_start_local[s] = phase_end_local[s];
// phase_start_shared unchanged — carried forward
}
_t0_phase = _t1;
l2_swimlane.phase_complete_count = 0;
}
Expand Down Expand Up @@ -727,11 +804,19 @@ int32_t SchedulerContext::resolve_and_dispatch(Runtime *runtime, int32_t thread_
// realistic dispatch cadence and silently truncates without this guard.
debug_assert(pop_hit_delta < (1ULL << 32));
debug_assert(pop_miss_delta < (1ULL << 32));
int16_t phase_end_local[L2SWIMLANE_NUM_QUEUE_SHAPES];
int16_t phase_end_shared[L2SWIMLANE_NUM_QUEUE_SHAPES];
capture_phase_end(phase_end_local, phase_end_shared);
l2_swimlane_aicpu_record_sched_phase(
thread_idx, L2SwimlaneSchedPhaseKind::Dispatch, _t0_phase, _t1, l2_swimlane.sched_loop_count,
l2_swimlane.phase_dispatch_count, static_cast<uint32_t>(pop_hit_delta),
static_cast<uint32_t>(pop_miss_delta)
static_cast<uint32_t>(pop_miss_delta), phase_start_local, phase_start_shared, phase_end_local,
phase_end_shared
);
for (int s = 0; s < L2SWIMLANE_NUM_QUEUE_SHAPES; s++) {
phase_start_local[s] = phase_end_local[s];
phase_start_shared[s] = phase_end_shared[s];
}
_t0_phase = _t1;
l2_swimlane.phase_dispatch_count = 0;
l2_swimlane.pop_hit_at_last_emit = l2_swimlane.pop_hit;
Expand Down Expand Up @@ -841,9 +926,13 @@ int32_t SchedulerContext::resolve_and_dispatch(Runtime *runtime, int32_t thread_
debug_assert(final_pop_miss_delta < (1ULL << 32));
if (final_pop_hit_delta != 0 || final_pop_miss_delta != 0) {
uint64_t t_now = get_sys_cnt_aicpu();
int16_t phase_end_local[L2SWIMLANE_NUM_QUEUE_SHAPES];
int16_t phase_end_shared[L2SWIMLANE_NUM_QUEUE_SHAPES];
capture_phase_end(phase_end_local, phase_end_shared);
l2_swimlane_aicpu_record_sched_phase(
thread_idx, L2SwimlaneSchedPhaseKind::Dispatch, t_now, t_now, l2_swimlane.sched_loop_count, 0,
static_cast<uint32_t>(final_pop_hit_delta), static_cast<uint32_t>(final_pop_miss_delta)
static_cast<uint32_t>(final_pop_hit_delta), static_cast<uint32_t>(final_pop_miss_delta),
phase_end_local, phase_end_shared, phase_end_local, phase_end_shared
);
l2_swimlane.pop_hit_at_last_emit = l2_swimlane.pop_hit;
l2_swimlane.pop_miss_at_last_emit = l2_swimlane.pop_miss;
Expand Down
15 changes: 14 additions & 1 deletion src/a5/platform/include/aicpu/l2_swimlane_collector_aicpu.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,12 @@ void l2_swimlane_aicpu_init_phase(int worker_count, int num_sched_phase_threads,
* pool. Silently drops records when the buffer is full or the pool was not
* primed (init failed for this thread).
*
* Queue-depth snapshots distinguish "task hidden in T0's local_buf" from
* "shared queue has it but peers spin on the wrong shape" — the former shows
* `local_depth > 0, shared_depth == 0` for the owning thread while peers see
* `shared_depth == 0` until overflow. Pass nullptr for any of the four arrays
* when not capturing (the record's corresponding slot is zero-filled).
*
* @param thread_idx Scheduler thread index
* @param kind Complete or Dispatch
* @param start_time Phase start timestamp
Expand All @@ -175,10 +181,17 @@ void l2_swimlane_aicpu_init_phase(int worker_count, int num_sched_phase_threads,
* @param tasks_processed Tasks processed in this phase batch
* @param pop_hit Dispatch delta since last emit (0 for Complete)
* @param pop_miss Dispatch delta since last emit (0 for Complete)
* @param local_at_start Per-shape PTO2LocalReadyBuffer.count at phase start (size L2SWIMLANE_NUM_QUEUE_SHAPES; may be
* nullptr)
* @param shared_at_start Per-shape sched.ready_queues[shape].size() at phase start (may be nullptr)
* @param local_at_end Per-shape PTO2LocalReadyBuffer.count at phase end (may be nullptr)
* @param shared_at_end Per-shape sched.ready_queues[shape].size() at phase end (may be nullptr)
*/
void l2_swimlane_aicpu_record_sched_phase(
int thread_idx, L2SwimlaneSchedPhaseKind kind, uint64_t start_time, uint64_t end_time, uint32_t loop_iter,
uint32_t tasks_processed, uint32_t pop_hit = 0, uint32_t pop_miss = 0
uint32_t tasks_processed, uint32_t pop_hit = 0, uint32_t pop_miss = 0, const int16_t *local_at_start = nullptr,
const int16_t *shared_at_start = nullptr, const int16_t *local_at_end = nullptr,
const int16_t *shared_at_end = nullptr
);

/**
Expand Down
Loading
Loading