Repository navigation
[Core] Modernize execution synchronization and cancellation - #29700
Gopalakrishnan Nallasamy (GopalakrishnanN) wants to merge 14 commits into
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR modernizes ONNX Runtime’s graph execution synchronization and cancellation path by replacing busy-waiting with C++20 blocking primitives, making cancellation propagation and failure publication thread-safe, and updating call sites (execution steps, control-flow subgraphs, training partial execution, and Python bindings) to pass a std::stop_token snapshot by value.
Changes:
- Replace
StreamExecutionContext::WaitAll()spinning withstd::atomic::wait/notify_alland introduce a thread-safe “first failure wins” status publication plus context-local stop fan-out. - Snapshot and propagate termination via
std::stop_tokenthroughInferenceSession::Run/PartialRun, executors, kernel contexts, and CPU control-flow / contrib subgraphs. - Update tests and microbenchmarks to validate completion wake behavior, competing failures, cancellation fan-out/reset, and termination behavior.
Reviewed changes
Copilot reviewed 29 out of 29 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| onnxruntime/test/providers/cpu/controlflow/loop_test.cc | Updates loop termination test to use the new RunOptions termination API. |
| onnxruntime/test/onnx/microbenchmark/tptest.cc | Adds a microbenchmark for StreamExecutionContext completion wake behavior. |
| onnxruntime/test/framework/stream_execution_context_test.cc | Adds unit tests for CountDownBarrier, FirstFailureStatus, and RunOptions termination snapshot/reset. |
| onnxruntime/test/framework/parallel_executor_test.cc | Adds concurrent Run cancellation fan-out/reset coverage using a custom op that waits on cancellation. |
| onnxruntime/python/onnxruntime_pybind_state.cc | Rebinds RunOptions.terminate as a property backed by stop-token state, preserving the Python surface API. |
| onnxruntime/core/session/inference_session.h | Extends internal RunImpl to accept a std::stop_token snapshot. |
| onnxruntime/core/session/inference_session.cc | Snapshots termination token at Run/PartialRun entry and propagates it down to graph execution utilities. |
| onnxruntime/core/providers/cpu/controlflow/scan_utils.cc | Propagates stop-token cancellation into Scan subgraph execution. |
| onnxruntime/core/providers/cpu/controlflow/loop.cc | Propagates stop-token cancellation into Loop subgraph execution. |
| onnxruntime/core/providers/cpu/controlflow/if.cc | Propagates stop-token cancellation into If subgraph execution. |
| onnxruntime/core/framework/utils.h | Updates execution utility signatures to accept std::stop_token and removes the RunOptions-based ExecuteGraph overload. |
| onnxruntime/core/framework/utils.cc | Implements the updated stop-token-based ExecuteGraph/ExecuteSubgraph/ExecutePartialGraph plumbing. |
| onnxruntime/core/framework/stream_execution_context.h | Introduces atomic wait/notify completion barrier, first-failure status wrapper, and stop-token integration into the stream execution context. |
| onnxruntime/core/framework/stream_execution_context.cc | Implements first-failure publication, stop fan-out, scope-guarded task completion, and the new blocking WaitAll. |
| onnxruntime/core/framework/sequential_executor.h | Updates executor APIs to take std::stop_token rather than a bool& terminate flag. |
| onnxruntime/core/framework/sequential_executor.cc | Threads cancellation token through scheduling, kernel execution contexts, and partial execution. |
| onnxruntime/core/framework/sequential_execution_plan.h | Updates execution-step interface to take a stop-token. |
| onnxruntime/core/framework/run_options.cc | Implements mutex-protected stop-source state for RunOptions terminate/set/unset with snapshot semantics. |
| onnxruntime/core/framework/partial_graph_execution_state.h | Adds stop-token propagation for training partial execution contexts. |
| onnxruntime/core/framework/partial_graph_execution_state.cc | Resets/reuses StreamExecutionContext with per-run stop-token and re-computes valid streams consistently. |
| onnxruntime/core/framework/op_kernel_context_internal.h | Stores and exposes a per-run cancellation stop-token to kernels via OpKernelContextInternal. |
| onnxruntime/core/framework/execution_steps.h | Updates step Execute signatures to use stop-token. |
| onnxruntime/core/framework/execution_steps.cc | Threads stop-token through kernel launch and downstream scheduling steps. |
| onnxruntime/contrib_ops/cpu/transformers/greedy_search_impl_gpt.h | Propagates stop-token cancellation into contrib subgraph execution. |
| onnxruntime/contrib_ops/cpu/transformers/beam_search_impl_whisper.h | Propagates stop-token cancellation into contrib subgraph execution. |
| onnxruntime/contrib_ops/cpu/transformers/beam_search_impl_t5.h | Propagates stop-token cancellation into contrib subgraph execution. |
| onnxruntime/contrib_ops/cpu/transformers/beam_search_impl_gpt.h | Propagates stop-token cancellation into contrib subgraph execution. |
| include/onnxruntime/core/framework/run_options.h | Updates public RunOptions to provide stop-token based termination APIs and internal termination state storage. |
| cmake/CMakeLists.txt | Adds an AppleClang(<17) compile/link flag for libc++ stop-token support on legacy Xcode. |
Fix cross-platform CI failures introduced by the std::stop_token-based cancellation: - std::stop_token/stop_source/stop_callback are unavailable on the Android NDK libc++ and require -fexperimental-library on Apple libc++ before Xcode 26 (which then fails to link libc++experimental on iOS). Replace them with an in-house CancellationToken/CancellationSource/ CancellationCallback in core/framework/cancellation.h that mirrors the needed API using only <atomic>/<mutex>/<condition_variable>, available on every supported toolchain. std::atomic wait/notify (used by CountDownBarrier) is portable and kept as-is. - Make OrtRunOptions special members inline again so dynamically loaded provider unit-test libraries can resolve the constructor; ONNX Runtime only exports the C API, so an out-of-line OrtRunOptions() was an undefined symbol for libonnxruntime_providers_*_ut.so (CUDA/TensorRT). - Remove the -fexperimental-library CMake block that broke the iOS link. Public C ABI signatures and the cancellation error text are unchanged. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The modernized non-spinning executor synchronization and stop-token-style cancellation pull in the C++20 atomic wait/notify runtime, std::mutex/ condition_variable, and shared cancellation state that replace the former WaitAll() busy-spin and the plain OrtRunOptions::terminate bool. This grows the MinSizeRel arm64 libonnxruntime.so of the minimal-baseline Android build from ~1.41 MB to ~1.46 MB (measured sections total 1463980 bytes), which exceeds the previous 1440768-byte budget. Raise the threshold to 1474560 bytes (1440 KB) to cover the measured size with modest headroom. Increase is inherent to the feature. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…modernize-executor-waits
… notify - CountDownBarrier: store the counter as std::atomic<int32_t> instead of std::atomic_int_fast32_t so Set()/Get()/Inc() and the Inc() overflow guard all use the same 32-bit range (int_fast32_t is 64-bit on x86-64 Linux); Get() no longer needs a narrowing cast. - RunSince: mark the gsl::finally CompleteTask guard [[maybe_unused]] to avoid an unused-variable warning under MSVC /W4 /WX. - parallel_executor_test: remove the dead started_count_.notify_all(); the WaitForStartedCount helper polls and never waits on the atomic. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Build one CPU-only InferenceSession directly and run it concurrently instead of relying on OpTester provider enumeration. TensorRT builds enumerate only TensorRT in OpTester, so excluding TensorRT previously skipped both runs and made the fanout/reset coverage fail without exercising cancellation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Includes the upstream Windows QNN Java toolchain fix. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Review synthesis — synchronization & cancellation modernizationI ran a multi-angle review over the whole diff (readability, correctness, adversarial/race, memory-model/spec, cross-module integration). Overall this is a solid, well-tested refactor and the hard core holds up: the Two issues in the new Major1.
(The path where 2. The 2-party stream barriers are Reachability depends on whether reused partial-graph contexts run a plan with 3. No dedicated conformance test for the new primitive — the 4. The parameter named Minor
Note on the
|
Keep completion state alive through the final atomic notification, rearm cross-stream barriers when partial execution contexts are reused, and avoid throwing task construction after reserving completion work. Harden portable cancellation and RunOptions move semantics, and add focused conformance tests. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Resolve the PR branch against the latest upstream main. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> # Conflicts: # .github/workflows/android.yml
Preserve session serialization, generation status handling, cancellation tokens and updated Android allowance. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Capture the termination token before acquiring the session lock, preserving cancellation across resets while a run waits for a non-concurrent EP. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Additional related baseline evidence: #33187 records an ARM64 lost-status failure in unchanged main executor code on #32194, while this PR current ARM64 gate passed (job113040928202). Local actual TestStatusPropagation + competing FirstFailureStatus tests are now running1000 repetitions. A green control is not proof of eliminating an intermittent race; preserving the failure report rather than rerunning it away. |
|
Completed actual native Windows Release stress: ParallelExecutor.TestStatusPropagation and StreamExecutionContextTest.FirstFailureStatusPublishesOneCompetingFailure ran1000 repetitions each (2000 executions), with1000 passed pairs, zero failed cases and clean process exit0. The real error-return/throw paths are exercised. This strengthens the first-failure publication evidence but is not ARM64 stress or proof of eliminating every race in #33187. |
Description
Modernizes executor synchronization and cancellation with C++20 atomic wait/notify, race-free first-failure publication, linked context cancellation, exactly-once task completion, and reusable barriers.
Snapshots termination state at
Runentry and propagates tokens through steps, kernel contexts, partial execution, CPU control-flow and generation/search subgraphs. Reset affects future runs without reviving already-snapshotted cancelled work. C ABI signatures/API version remain unchanged; the C header clarifiesUnsetTerminate.Uses the existing portable in-house cancellation primitive instead of assuming uniform
std::stop_tokensupport across configured toolchains.Refreshed on main
9f6baac7319a789e729ccee1dba9b8e3ac9c2e48, preserving main's session serialization, EPContext and node-test-loader fixes:CancellationToken, preserving their intent rather than introducing implicit bool conversion.Motivation and Context
Busy-waiting callers consume worker capacity, unsynchronized status publication races, and a plain terminate flag is unsuitable for the public cross-thread termination contract. Token snapshots and acquire/release completion provide explicit lifetime and reset semantics.
This is independent of #29817's planner change; no hard prerequisite is asserted. Their shared header and behavior were integrated and exercised together.
Fresh validation
Windows Release CPU/DirectML MSVC/Ninja build of the actual runtime, provider tests and Python extension passed. This is broader than the earlier syntax/extracted-method harnesses.
51 passed, zero skips: primitive 4, RunOptions 2, stream context 7, real parallel executor 2, capability 7, migrated MoE contexts 11, planner 17, worker-count 1.
ConcurrentRunCancellationFanoutAndResetexecuted against real initialized sessions.69 passed, zero skips; 1 existing disabled case. Includes
Loop.InfiniteLoopTermination.The freshly built native Python extension, not the installed older wheel, also passed terminate getter/setter reset checks and five cancellation/reset/reuse cycles through an initialized parallel CPU session, verifying full output shape and every element against an independent reference.
Earlier focused evidence remains valid in its scope: 4 primitive cases repeated 100 times; inference/training+ATEN syntax checks. Those were not a substitute for the full integration results above.
Performance and remaining gates
The earlier body's completion-primitive and SqueezeNet/SSD measurements are historical author-reported results; they have not been repeated for this main reconciliation. No fresh CPU-efficiency/SLA claim is made here.
Fresh Linux training passed on this head: training job, CTest 15/15 passed.
ConcurrentRunCancellationFanoutAndResetandSnapshotFanoutAndResetexplicitly ran and passed. Windows native first-failure publication/status-propagation stress also completed 1,000 repetitions per case, 2,000 executions, zero failures.Current-head checks are settled without unsuccessful checks. Targeted cached PartialRun reuse and exact device-stream dispatch remain narrower coverage questions; a green general GPU job is not silently treated as proof of every path. Architecture-owner approval is still required.
The final main refresh includes #33168 rather than retrying its deterministic dtype failures; #33183 was closed only after the complete 1,650-model gate passed.