diff --git a/src/a5/runtime/host_build_graph/runtime/pto_runtime2_types.h b/src/a5/runtime/host_build_graph/runtime/pto_runtime2_types.h index b25d99ca9f..e7ce94fd01 100644 --- a/src/a5/runtime/host_build_graph/runtime/pto_runtime2_types.h +++ b/src/a5/runtime/host_build_graph/runtime/pto_runtime2_types.h @@ -93,8 +93,13 @@ // buffer (which would be UB on the arena's malloc'd backing). #define PTO2_SCOPE_TASKS_CAP (PTO2_TASK_WINDOW_SIZE * PTO2_MAX_RING_DEPTH) -// Ready queue -#define PTO2_READY_QUEUE_SIZE 65536 // Per-shape queue size +// Per-shape ready-queue capacity (power of two). This is a ring buffer that +// bounds peak CONCURRENT occupancy (enqueue_pos - dequeue_pos), not total task +// count: slots recycle, so capacity need only exceed the most tasks ever +// simultaneously ready in any one queue. Overflow on the ready/sync/dummy queues +// latches PTO2_ERROR_READY_QUEUE_OVERFLOW (safe-fail), so it must exceed the +// worst-case ready burst with margin. +#define PTO2_READY_QUEUE_SIZE 8192 // Cross-thread early-dispatch work queue (power of two) #define PTO2_EARLY_DISPATCH_QUEUE_SIZE 64 diff --git a/src/a5/runtime/host_build_graph/runtime/scheduler/pto_scheduler.h b/src/a5/runtime/host_build_graph/runtime/scheduler/pto_scheduler.h index 491c509a02..3cfad7c4c7 100644 --- a/src/a5/runtime/host_build_graph/runtime/scheduler/pto_scheduler.h +++ b/src/a5/runtime/host_build_graph/runtime/scheduler/pto_scheduler.h @@ -489,26 +489,27 @@ struct PTO2SchedulerState { // the per-shape ready_sync_queues[] (drained as Tier-0); everything else to // ready_queues[]. void push_ready_routed(PTO2TaskSlotState *slot_state) { - if (slot_state->task_kind == TaskKind::GRAPH) { - graph_ready_queue.push(slot_state); - return; - } - PTO2ResourceShape shape = slot_state->active_mask.to_shape(); bool pushed; - if (shape == PTO2ResourceShape::DUMMY || - (slot_state->task_attrs.has_predicate() && !slot_state->payload->predicate.pass())) { - pushed = dummy_ready_queue.push(slot_state); - } else if (slot_state->task_attrs.requires_sync_start()) { - pushed = ready_sync_queues[static_cast(shape)].push(slot_state); + if (slot_state->task_kind == TaskKind::GRAPH) { + pushed = graph_ready_queue.push(slot_state); } else { - pushed = ready_queues[static_cast(shape)].push(slot_state); + PTO2ResourceShape shape = slot_state->active_mask.to_shape(); + if (shape == PTO2ResourceShape::DUMMY || + (slot_state->task_attrs.has_predicate() && !slot_state->payload->predicate.pass())) { + pushed = dummy_ready_queue.push(slot_state); + } else if (slot_state->task_attrs.requires_sync_start()) { + pushed = ready_sync_queues[static_cast(shape)].push(slot_state); + } else { + pushed = ready_queues[static_cast(shape)].push(slot_state); + } } - // A queue is sized for the whole task window and each task is routed to one - // queue exactly once, so push cannot legitimately fail. A false return means - // the target slot fell outside the shipped prefix, or the window genuinely - // exceeds queue capacity — either way the task is dropped and the run would - // otherwise stall. Latch a named error so it surfaces as READY_QUEUE_OVERFLOW - // rather than an anonymous forward-progress timeout. + // Every ready / sync / dummy / graph task routes to exactly one queue. A + // false push means that queue's peak concurrent occupancy exceeded + // PTO2_READY_QUEUE_SIZE — a capacity mis-sizing, not a normal condition. + // Silently dropping the task would stall the run, so latch a named error + // (surfaces as READY_QUEUE_OVERFLOW rather than an anonymous + // forward-progress timeout). The graph_ready push is checked identically + // so a graph task cannot be dropped either. if (!pushed) { int32_t expected = PTO2_ERROR_NONE; sm_header->sched_error_code.compare_exchange_strong(