feat: 2O+2S Pipline#1238
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces a new multi-threaded pipeline strategy (S2_O2_SPLIT_CTRL_STRATEGY2) that defers dependency resolution, TensorMap registration, and task publication to a secondary orchestrator worker thread. The review feedback identifies several critical concurrency and synchronization issues in this new pipeline implementation. These include a potential data race on TensorMap during synchronous fallback, a potential deadlock caused by not updating the producer_blocked state when the pipeline worker is blocked, and multiple potential permanent hangs in spin loops (submit_pipeline_queue_push and flush_submit_pipeline) if a fatal error occurs.
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.
| } | ||
|
|
||
| PTO2FaninBuilder fanin_builder(orch, orch->rings[ring_id].fanin_pool, next_fanin_seen_epoch(orch)); |
There was a problem hiding this comment.
When the task has too many explicit dependencies or metadata serialization fails, the orchestrator falls through to the synchronous submit path on the primary thread (O0). This allows O0 to access the shared tensor_map concurrently with the pipeline worker thread (O1) processing deferred tasks, leading to data races. Since TensorMap is accessed by multiple threads, its operations must be protected by a mutex to prevent data races, even if current usage patterns attempt to avoid overlap.
std::lock_guard<std::mutex> lock(orch->tensor_map_mutex);
PTO2FaninBuilder fanin_builder(orch, orch->rings[ring_id].fanin_pool, next_fanin_seen_epoch(orch));References
- If a component (e.g., TensorMap) is accessed by multiple threads, its operations should be protected by a mutex to prevent data races, even if current usage patterns do not show overlap.
| } else { | ||
| while (!record.scheduler->wiring.queue.push(record.slot_state)) { | ||
| publish_spins++; | ||
| if (orch->sm_header->orch_error_code.load(std::memory_order_acquire) != PTO2_ERROR_NONE) { | ||
| orch->fatal = true; | ||
| return; | ||
| } | ||
| SPIN_WAIT_HINT(); | ||
| } | ||
| signal_scheduler_drain_if_needed(orch, record); | ||
| } |
There was a problem hiding this comment.
When the pipeline worker thread (O1) is blocked pushing to the scheduler's wiring queue, it does not set producer_blocked to 1. This prevents the scheduler's deadlock detector from realizing that the producer is blocked, which can lead to permanent deadlocks. Please set and reset producer_blocked around the blocking push loop, matching the baseline's pattern.
} else {
if (!record.scheduler->wiring.queue.push(record.slot_state)) {
record.scheduler->wiring.producer_blocked.store(1, std::memory_order_release);
while (!record.scheduler->wiring.queue.push(record.slot_state)) {
publish_spins++;
if (orch->sm_header->orch_error_code.load(std::memory_order_acquire) != PTO2_ERROR_NONE) {
orch->fatal = true;
record.scheduler->wiring.producer_blocked.store(0, std::memory_order_release);
return;
}
SPIN_WAIT_HINT();
}
record.scheduler->wiring.producer_blocked.store(0, std::memory_order_release);
}
signal_scheduler_drain_if_needed(orch, record);
}| static void submit_pipeline_queue_push(PTO2SubmitPipelineQueue &queue, const PTO2SubmitCommitRecord &record) { | ||
| uint64_t seq = queue.tail.load(std::memory_order_acquire); | ||
| int32_t slot = static_cast<int32_t>(seq % PTO2_SUBMIT_PIPELINE_QUEUE_CAP); | ||
| while (queue.slot_state[slot].load(std::memory_order_acquire) != 0) { | ||
| SPIN_WAIT_HINT(); | ||
| } | ||
| queue.records[slot] = record; | ||
| queue.slot_state[slot].store(1, std::memory_order_release); | ||
| queue.tail.store(seq + 1, std::memory_order_release); | ||
| } |
There was a problem hiding this comment.
If the submit pipeline queue is full, the primary orchestrator thread (O0) spins indefinitely in submit_pipeline_queue_push. If the pipeline worker thread (O1) or the scheduler encounters a fatal error and aborts, O1 will stop popping, causing O0 to hang forever. Please pass orch to submit_pipeline_queue_push and check for fatal errors inside the spin loop to prevent permanent hangs.
static void submit_pipeline_queue_push(PTO2OrchestratorState *orch, PTO2SubmitPipelineQueue &queue, const PTO2SubmitCommitRecord &record) {
uint64_t seq = queue.tail.load(std::memory_order_acquire);
int32_t slot = static_cast<int32_t>(seq % PTO2_SUBMIT_PIPELINE_QUEUE_CAP);
while (queue.slot_state[slot].load(std::memory_order_acquire) != 0) {
if (orch->fatal || orch->sm_header->orch_error_code.load(std::memory_order_acquire) != PTO2_ERROR_NONE) {
orch->fatal = true;
return;
}
SPIN_WAIT_HINT();
}
queue.records[slot] = record;
queue.slot_state[slot].store(1, std::memory_order_release);
queue.tail.store(seq + 1, std::memory_order_release);
}| uint64_t enqueue_start = get_sys_cnt_aicpu(); | ||
| #endif | ||
| mark_submit_pipeline_work_available(orch); | ||
| submit_pipeline_queue_push(orch->submit_pipeline_queues[0], record); |
| uint64_t enqueue_start = get_sys_cnt_aicpu(); | ||
| #endif | ||
| mark_submit_pipeline_work_available(orch); | ||
| submit_pipeline_queue_push(orch->submit_pipeline_queues[0], record); |
| while (submit_pipeline_completed.load(std::memory_order_acquire) < target) { | ||
| SPIN_WAIT_HINT(); | ||
| } |
There was a problem hiding this comment.
If the pipeline worker thread (O1) aborts due to a fatal error, submit_pipeline_completed will never reach target, causing the primary orchestrator thread (O0) to hang indefinitely in flush_submit_pipeline. Please check for fatal errors inside the spin loop to break out safely.
while (submit_pipeline_completed.load(std::memory_order_acquire) < target) {
if (fatal || sm_header->orch_error_code.load(std::memory_order_acquire) != PTO2_ERROR_NONE) {
fatal = true;
break;
}
SPIN_WAIT_HINT();
}6bd07d8 to
b1a34c9
Compare
Add pipeline_strategy to CallConfig, worker/runtime C API plumbing, and runtime launch descriptors while preserving upstream worker cleanup behavior. Co-authored-by: Crane-Liu <c.wliu@outlook.com>
Implement the 2S2O strategy2 submit/dependency pipeline in the a2a3 tensormap runtime and scheduler. Co-authored-by: Crane-Liu <c.wliu@outlook.com>
b1a34c9 to
b813f1c
Compare
Summary
This PR adds the strategy2
2O + 2Spipeline path for the a2a3 tensormap runtime.The change is split into two parts:
pipeline_strategyplumbing throughCallConfig, Python bindings, remote wire encoding,simpler_run, and Runtime launch descriptors.O-side Pipeline Split
Strategy2 uses 4 AICPU threads:
S0/S1: scheduler threadsO0: primary orchestrator threadO1: submit-commit pipeline workerThe original O-side submit flow mixed several responsibilities in one orchestrator thread:
This PR splits that flow:
O0: prepare and enqueue
O0keeps the orchestration/front-half work:PTO2TaskPayloadPTO2SubmitCommitRecordinto the submit pipeline queueO1: dependency commit and publish
O1runsrun_submit_pipeline_worker()and handles the back-half:For no-fanin tasks, O1 directly publishes to the ready queue via
publish_ready_no_fanin(), avoiding the wiring queue path.For tasks with fanin, O1 pushes into the scheduler wiring queue and signals scheduler drain when needed.
Behavior
pipeline_strategyis unset.pipeline_strategy=2orSIMPLER_PIPELINE_STRATEGY=2.2S + 2O, runtime falls back to baseline layout.SIMPLER_PIPELINE_DEFER_SUBMIT=0/false/off/nocan disable deferred submit for debugging.Results
Summary
Note: A negative value for the change in the table indicates a reduction in time consumption relative to the baseline, which means improved performance.
Qwen14b-decode
Case:
StressBatch16Seq35009 case Benchmark
Test