Skip to content

Add: bounded asynchronous native run lane - #1650

Open
Crane-Liu wants to merge 1 commit into
hw-native-sys:mainfrom
Crane-Liu:codex/worker-async-w1-native-prepared-lane
Open

Add: bounded asynchronous native run lane#1650
Crane-Liu wants to merge 1 commit into
hw-native-sys:mainfrom
Crane-Liu:codex/worker-async-w1-native-prepared-lane

Conversation

@Crane-Liu

@Crane-Liu Crane-Liu commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add a bounded direct-L2 asynchronous native lane: one active run plus one eligible prepared successor; a third submission is backpressured before native preparation or publication.
  • Return a live RunHandle from direct-L2 submit while keeping run() as submit(...).wait(); the lane and handle own launch, completion, finalization, FIFO handoff, and cleanup.
  • Carry slot, arena bank, run, generation, dispatch, and epoch in a required NativeRunDescriptor copied into opaque per-run state.
  • Bind runtime host callbacks with an immutable HostApi containing runner, slot, bank, and its function table; remove current-runner/resource-selection TLS and the four setter exports. simpler_init keeps its original flat binary and prewarm arguments.
  • Publish launch acceptance through the per-run launch signal only at the real kernel-launch marker; failure before the marker leaves the mailbox unchanged and no acceptance pointer survives launch return.
  • Keep scheduler rejection, registry/control, interruption, and teardown deterministic.

Semantics

  • Device execution remains single-active; overlap is eligible successor preparation, not two concurrent kernels.
  • Concurrent preparation remains capability-gated; diagnostics fall back to depth one.
  • A2/A3 HBG owns the per-slot resources needed for depth two.
  • A5 HBG remains depth one until its DeviceRunner owns per-slot execution resources.
  • HostApi is compiled together with every host runtime; no optional symbol or version/size descriptor layer is introduced.

Validation

  • All eight host runtime DSOs rebuilt successfully; obsolete TLS and setter exports are absent.
  • C++ no-hardware unit tests: 77/77 passed.
  • Python unit tests: 1104 passed, 13 skipped.
  • A2A3 simulation full sweep: 125 passed, 4 skipped.
  • Targeted HostApi and launch-signal tests passed, including two-context routing and pre-marker failure semantics.

The branch contains one commit rebased onto the latest main at push time.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds bounded concurrent native-run preparation for eligible backends, asynchronous L2 submission with RunHandle, run identity propagation, per-slot resource management, FIFO launch gating, non-throwing dispatch failures, and expanded lifecycle tests and documentation.

Changes

Concurrent native-run pipeline

Layer / File(s) Summary
Native-run contracts and bindings
src/common/worker/*, python/bindings/task_interface.cpp, python/simpler/task_interface.py
Native runs now carry identity metadata and expose backend concurrent-preparation capability.
Per-run resource reservation and provisioning
src/common/platform/onboard/host/*, src/a2a3/platform/onboard/host/*
Pipeline and arena selections use thread-local state. Slot reservations and stream resources support one prepared successor.
Runtime preparation, launch, and finalization
src/common/platform/onboard/host/c_api_shared.cpp, src/common/log/include/common/strace.h
Preparation, launch, polling, and finalization manage identities, resources, trace attributes, and outstanding-run checks.
ChipWorker native-run state machine
src/common/worker/chip_worker.cpp
Native-run phases validate tokens and handle preparation, launch failures, polling, waiting, finalization, and cleanup.
Hierarchical staged-frame and dispatch behavior
python/simpler/worker.py, src/common/hierarchical/*
Two-frame execution supports prepared successors and FIFO activation. Dispatch rejection now completes through callbacks without throwing.
Asynchronous L2 submission and handles
python/simpler/worker.py
L2 submission returns live handles after launch, applies two-run admission limits, and performs ordered completion and finalization.
Lifecycle validation and documentation
tests/*, docs/*
Tests cover overlap, ordering, failures, cleanup, resource reuse, and handle completion. Documentation describes the updated lifecycle.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Worker
  participant ChipWorker
  participant Runtime
  participant RunHandle

  Client->>Worker: submit L2 task
  Worker->>ChipWorker: prepare native run
  ChipWorker->>Runtime: reserve and prepare run identity
  Worker->>ChipWorker: launch FIFO front run
  ChipWorker->>Runtime: launch native run
  Worker-->>Client: return RunHandle
  Client->>RunHandle: wait
  RunHandle->>Worker: poll and finalize
  Worker->>ChipWorker: finalize native run
Loading

Possibly related PRs

Poem

A rabbit queues runs in a neat little line,
One hops while the next waits in pipeline time.
Handles hold dreams till completion is near,
FIFO keeps every launch crystal clear.
Streams bloom, then retire when the work is done—
Thump-thump, concurrent execution has begun!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: a bounded asynchronous native-run lane.
Description check ✅ Passed The description directly explains the asynchronous native-run lane, bounded admission, RunHandle behavior, fallback semantics, and validation.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (8)
src/common/platform/onboard/host/device_runner_base.cpp (1)

1356-1359: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Guard the division by cores_per_blockdim_.

block_dim_ = worker_count_ / cores_per_blockdim_ introduces a division on a field that the previous code only multiplied by. If any arch leaves cores_per_blockdim_ at 0, this is undefined behaviour instead of a benign zero.

🛡️ Proposed guard
 void DeviceRunnerBase::activate_launch_shape(const Runtime &runtime) {
     worker_count_ = runtime.get_worker_count();
-    block_dim_ = worker_count_ / cores_per_blockdim_;
+    block_dim_ = cores_per_blockdim_ > 0 ? worker_count_ / cores_per_blockdim_ : 0;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/common/platform/onboard/host/device_runner_base.cpp` around lines 1356 -
1359, Update DeviceRunnerBase::activate_launch_shape to guard
cores_per_blockdim_ before dividing worker_count_. Preserve the benign zero
behavior by setting block_dim_ to zero when cores_per_blockdim_ is zero;
otherwise retain the existing worker-count division.
src/common/platform/onboard/host/c_api_shared.cpp (2)

711-713: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Explain why runner_resources_owned is set before provisioning.

Line 711 sets the flag to true before provision_native_run_resources runs at Line 712. On a provisioning failure, cleanup_failed_prepare therefore calls abandon_native_run_resources for a slot that provisioning did not complete.

That is the right choice for a partial provision, but the pre-set reads as a sequencing mistake. Add a one-line comment stating that a failed provision may still hold partial resources, so ownership is claimed before the attempt.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/common/platform/onboard/host/c_api_shared.cpp` around lines 711 - 713, In
the preparation flow around runner_resources_owned and
provision_native_run_resources, add a concise one-line comment explaining that
provisioning failure may leave partial native resources held, so ownership must
be claimed before the provisioning attempt for cleanup_failed_prepare to release
them.

697-701: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

A rejected reservation drops its allocated trace invocation.

Line 676 allocates trace_inv and Line 677 stamps trace_start_ns. When try_reserve_native_run fails, Line 700 returns without calling emit_native_run_host_wall. Every other failure path routes through cleanup_failed_prepare, which emits the span. Admission rejections therefore leave a gap in the host trace exactly where contention analysis needs a record.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/common/platform/onboard/host/c_api_shared.cpp` around lines 697 - 701,
Update the admission-rejection branch in the native-run preparation flow around
try_reserve_native_run so it emits the allocated trace invocation via the
existing cleanup_failed_prepare path or equivalent before destroying state and
returning. Preserve the current error logging and -1 return while ensuring
rejected reservations produce the same host-wall trace record as other failure
paths.
src/common/platform/onboard/host/device_runner_base.h (1)

132-135: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

pipeline_slot(), selected_arena_bank(), and arena_bank() can now throw.

These accessors were plain field reads. They now route through the pthread TLS helper, which throws on key-creation or allocation failure. arena_bank() at Line 1021 is on the setup_static_arena and acquire_pooled_* paths, and pipeline_slot() is read at the top of DeviceRunner::run. Callers that previously treated these as infallible now have a new exception edge.

Document the new throwing contract on the declarations so callers on noexcept paths do not adopt them by accident.

Also applies to: 148-148, 1021-1021

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/common/platform/onboard/host/device_runner_base.h` around lines 132 -
135, Document on the declarations of pipeline_slot(), selected_arena_bank(), and
arena_bank() that each accessor may throw due to pthread TLS key creation or
allocation failure. Keep the existing signatures unchanged and make the contract
visible to callers before they use these accessors in run, setup_static_arena,
or acquire_pooled_* paths.
src/common/worker/chip_worker.cpp (1)

684-688: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Simplify the occupancy check.

occupied != 0 && occupied != 1 is occupied > 1. The direct form states the intent: at most one predecessor may exist.

♻️ Proposed simplification
-        if (occupied != 0 && occupied != 1) {
+        if (occupied > 1) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/common/worker/chip_worker.cpp` around lines 684 - 688, In the occupancy
validation near the prepare_native_run ownership check, replace the `occupied !=
0 && occupied != 1` condition with the equivalent `occupied > 1` check,
preserving the existing runtime error and identity formatting.
src/common/hierarchical/worker_manager.cpp (1)

318-325: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Make the non-queued mapping explicit in dispatch.

dispatch passes no staged_run_id, so enqueue_dispatch cannot return STAGED_IDENTITY_CHANGED today. The else branch nevertheless reports any future non-STOPPING result as "endpoint capacity exceeded". dispatch_prepared already switches on each enumerator. Match that shape so a new enumerator cannot be reported under the wrong message.

♻️ Proposed change
     if (result == EnqueueDispatchResult::STOPPING) {
         complete_unpublished(d, "WorkerThread::dispatch: worker is stopping");
-    } else {
+    } else if (result == EnqueueDispatchResult::CAPACITY_EXCEEDED) {
         complete_unpublished(d, "WorkerThread::dispatch: endpoint capacity exceeded");
+    } else {
+        complete_unpublished(d, "WorkerThread::dispatch: enqueue rejected the dispatch");
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/common/hierarchical/worker_manager.cpp` around lines 318 - 325, Update
dispatch to explicitly handle every non-queued EnqueueDispatchResult, matching
dispatch_prepared’s switch structure. Preserve the STOPPING message, map the
currently impossible STAGED_IDENTITY_CHANGED result explicitly, and ensure any
newly added enumerator cannot fall through to “endpoint capacity exceeded.”
python/simpler/worker.py (1)

2405-2414: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Share one diagnostics predicate.

config_has_diagnostics here and Worker._l2_config_has_diagnostics at lines 8194-8202 contain the identical field list. Both must track CallConfig::diagnostics_any(). Extract one module-level helper and call it from both sites, so a new diagnostic flag cannot be added to only one copy.

♻️ Proposed shared helper
def _call_config_has_diagnostics(config: CallConfig) -> bool:
    # Mirrors CallConfig::diagnostics_any().
    return bool(
        config.enable_l2_swimlane
        or config.enable_dump_args
        or config.enable_pmu
        or config.enable_dep_gen
        or config.enable_scope_stats
    )
-        def config_has_diagnostics(config: CallConfig) -> bool:
-            # Mirrors CallConfig::diagnostics_any(); these modes share native
-            # diagnostic state and therefore use the serial prepare fallback.
-            return bool(
-                config.enable_l2_swimlane
-                or config.enable_dump_args
-                or config.enable_pmu
-                or config.enable_dep_gen
-                or config.enable_scope_stats
-            )
+        config_has_diagnostics = _call_config_has_diagnostics
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/simpler/worker.py` around lines 2405 - 2414, Extract the shared
field-list logic from the local config_has_diagnostics function into a
module-level _call_config_has_diagnostics(config: CallConfig) helper, preserving
the fields that mirror CallConfig::diagnostics_any(). Update both
config_has_diagnostics and Worker._l2_config_has_diagnostics to delegate to this
helper so future diagnostic flags are maintained in one place.
tests/ut/py/test_worker/test_host_worker.py (1)

405-432: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Let the harness vary the frame run id.

publish writes a constant run_id of 5 into every frame. The new concurrency tests therefore stage an active frame and a successor frame that share one run id and differ only by dispatch_id. Production stages a successor that belongs to a different run. The tests still prove dispatch-id ordering, so nothing is wrong today. If run_two_frame_loop later gates preparation on run identity, these tests would pass without exercising that gate. Add a run_id parameter with a default of 5 and give the successor a distinct value in the concurrent-prepare tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/ut/py/test_worker/test_host_worker.py` around lines 405 - 432, Update
the test harness publish method to accept a run_id parameter defaulting to 5,
and write that value into _OFF_FRAME_RUN_ID instead of the hardcoded constant.
In the concurrent-prepare tests, pass a distinct run_id for the successor frame
while preserving the existing default for other callers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/worker-manager.md`:
- Around line 241-247: Update the registry-control deferral sentence in the
worker-manager documentation to wait for both the active native run and any
prepared successor holding a native token. Keep the rule aligned with
run_two_frame_loop, where controls remain deferred while any staged frame owns a
native token.

In `@python/simpler/worker.py`:
- Around line 8570-8583: Update _wait_run_handle_accepted so it advances the L2
FIFO in bounded steps, rechecking the target run state after each
_l2_progress_locked call rather than allowing one call to run through terminal
completion. Return as soon as the target phase is no longer "prepared", while
preserving unknown-run and propagated-error handling; leave the orchestrator
path unchanged.

In `@src/common/platform/onboard/host/c_api_shared.cpp`:
- Around line 873-881: Update simpler_finalize_run to wrap
capture_native_run_thread_selection, select_pipeline_slot, and select_arena_bank
in exception handling that releases runner_claimed and runner_reserved, calls
destroy_native_run_state, and returns -1 on selection failure or thrown
exceptions. Apply the equivalent exception guard to simpler_launch_run around
its native thread selection calls, preserving its existing claim-release
behavior.

In `@src/common/platform/onboard/host/device_runner_base.cpp`:
- Around line 1659-1665: Protect the native_launch_signal_ read in
DeviceRunnerBase::publish_task_accepted with native_run_mu_, matching the
synchronization used by try_acquire_native_run and release_native_run. Hold the
mutex while copying or checking the pointer and notifying it, so the pointer
cannot race with updates or destruction; preserve the existing accepted_state
release store.
- Around line 72-107: Update the native-run selection flow around
run_selection(), select_pipeline_slot(), and select_arena_bank() so selection
state is isolated per DeviceRunnerBase instance rather than shared by thread
alone. Attach or copy NativeRunThreadSelection to the owning runner, or make the
existing TLS state resolve through a runner-specific key, ensuring contexts used
on the same host thread cannot reuse each other’s arena bank, retained buffers,
or run identity.

In `@src/common/platform/onboard/host/device_runner_base.h`:
- Around line 136-140: Remove noexcept from restore_native_run_thread_selection
in both its declaration and definition, allowing exceptions from run_selection
during create_thread’s initial TLS allocation to propagate safely instead of
terminating the process.

In `@src/common/worker/chip_worker.cpp`:
- Around line 814-826: Re-validate both lease_generation and run_epoch under
native_run_mu_ before writing completion state in poll_native_run and
wait_native_run. If the slot identity no longer matches, do not update the
successor state; otherwise preserve the existing writes to phase REAPED and, in
wait_native_run, wait_rc.

In
`@tests/st/a2a3/host_build_graph/native_run_lifecycle/test_native_run_lifecycle.py`:
- Around line 196-199: Remove the timing-dependent torch.count_nonzero assertion
from the direct L2 submit test, while retaining the run_handle._terminal
assertion to verify submit returns a non-completed compatibility handle. Keep
the subsequent run_handle.wait(30.0) lifecycle check unchanged.

---

Nitpick comments:
In `@python/simpler/worker.py`:
- Around line 2405-2414: Extract the shared field-list logic from the local
config_has_diagnostics function into a module-level
_call_config_has_diagnostics(config: CallConfig) helper, preserving the fields
that mirror CallConfig::diagnostics_any(). Update both config_has_diagnostics
and Worker._l2_config_has_diagnostics to delegate to this helper so future
diagnostic flags are maintained in one place.

In `@src/common/hierarchical/worker_manager.cpp`:
- Around line 318-325: Update dispatch to explicitly handle every non-queued
EnqueueDispatchResult, matching dispatch_prepared’s switch structure. Preserve
the STOPPING message, map the currently impossible STAGED_IDENTITY_CHANGED
result explicitly, and ensure any newly added enumerator cannot fall through to
“endpoint capacity exceeded.”

In `@src/common/platform/onboard/host/c_api_shared.cpp`:
- Around line 711-713: In the preparation flow around runner_resources_owned and
provision_native_run_resources, add a concise one-line comment explaining that
provisioning failure may leave partial native resources held, so ownership must
be claimed before the provisioning attempt for cleanup_failed_prepare to release
them.
- Around line 697-701: Update the admission-rejection branch in the native-run
preparation flow around try_reserve_native_run so it emits the allocated trace
invocation via the existing cleanup_failed_prepare path or equivalent before
destroying state and returning. Preserve the current error logging and -1 return
while ensuring rejected reservations produce the same host-wall trace record as
other failure paths.

In `@src/common/platform/onboard/host/device_runner_base.cpp`:
- Around line 1356-1359: Update DeviceRunnerBase::activate_launch_shape to guard
cores_per_blockdim_ before dividing worker_count_. Preserve the benign zero
behavior by setting block_dim_ to zero when cores_per_blockdim_ is zero;
otherwise retain the existing worker-count division.

In `@src/common/platform/onboard/host/device_runner_base.h`:
- Around line 132-135: Document on the declarations of pipeline_slot(),
selected_arena_bank(), and arena_bank() that each accessor may throw due to
pthread TLS key creation or allocation failure. Keep the existing signatures
unchanged and make the contract visible to callers before they use these
accessors in run, setup_static_arena, or acquire_pooled_* paths.

In `@src/common/worker/chip_worker.cpp`:
- Around line 684-688: In the occupancy validation near the prepare_native_run
ownership check, replace the `occupied != 0 && occupied != 1` condition with the
equivalent `occupied > 1` check, preserving the existing runtime error and
identity formatting.

In `@tests/ut/py/test_worker/test_host_worker.py`:
- Around line 405-432: Update the test harness publish method to accept a run_id
parameter defaulting to 5, and write that value into _OFF_FRAME_RUN_ID instead
of the hardcoded constant. In the concurrent-prepare tests, pass a distinct
run_id for the successor frame while preserving the existing default for other
callers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6baf5a84-ef87-4133-9053-7b0416dc8098

📥 Commits

Reviewing files that changed from the base of the PR and between 810fbcd and b160bcf.

📒 Files selected for processing (24)
  • docs/task-flow.md
  • docs/worker-manager.md
  • python/bindings/task_interface.cpp
  • python/simpler/task_interface.py
  • python/simpler/worker.py
  • src/a2a3/platform/onboard/host/device_runner.cpp
  • src/a2a3/platform/onboard/host/device_runner.h
  • src/common/hierarchical/scheduler.cpp
  • src/common/hierarchical/worker_manager.cpp
  • src/common/hierarchical/worker_manager.h
  • src/common/log/include/common/strace.h
  • src/common/platform/onboard/host/c_api_shared.cpp
  • src/common/platform/onboard/host/device_runner_base.cpp
  • src/common/platform/onboard/host/device_runner_base.h
  • src/common/worker/chip_worker.cpp
  • src/common/worker/chip_worker.h
  • src/common/worker/native_run_state.h
  • src/common/worker/pto_runtime_c_api.h
  • tests/st/a2a3/host_build_graph/native_run_lifecycle/kernels/orchestration/long_vector_orch.cpp
  • tests/st/a2a3/host_build_graph/native_run_lifecycle/test_native_run_lifecycle.py
  • tests/ut/cpp/hierarchical/test_run_stream_slots.cpp
  • tests/ut/cpp/hierarchical/test_scheduler.cpp
  • tests/ut/py/test_worker/test_host_worker.py
  • tests/ut/py/test_worker/test_startup_readiness.py

Comment thread docs/worker-manager.md
Comment thread python/simpler/worker.py
Comment thread src/common/platform/onboard/host/c_api_shared.cpp Outdated
Comment thread src/common/platform/onboard/host/device_runner_base.cpp Outdated
Comment thread src/common/platform/onboard/host/device_runner_base.cpp
Comment thread src/common/platform/onboard/host/device_runner_base.h Outdated
Comment thread src/common/worker/chip_worker.cpp Outdated
Comment thread tests/st/a2a3/host_build_graph/native_run_lifecycle/test_native_run_lifecycle.py Outdated
@Crane-Liu
Crane-Liu force-pushed the codex/worker-async-w1-native-prepared-lane branch from b160bcf to 6526943 Compare August 3, 2026 08:10
@Crane-Liu Crane-Liu changed the title Add: introduce common native prepared lane Refactor: align native prepared lane with v2 ownership Aug 3, 2026
@ChaoWao

ChaoWao commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Reviewed against main @ 673aecea. Three things to flag before this can go further, plus a rebase.

1. This reverts D1, which merged 4 hours ago in #1587

The title says "align native prepared lane with v2 ownership", but the diff also undoes the uniform host-runtime pipeline ABI:

  • load_optional_symbol is reintroduced, and all six pipeline symbols go back to optional loading
  • get_pipeline_contract is deleted from both a5 runtime_maker.cpp files
  • supports_concurrent_native_prepare_ctx, set_task_accepted_state_ctx, and set_native_run_identity_ctx are deleted from the sim c_api_shared.cpp
  • tests/ut/py/test_host_runtime_abi.py is deleted entirely — the check that all eight built DSOs export the required set

The commit message justifies this as "keep pipeline metadata optional for older runtimes". There are no older runtimes: host_runtime.so is built from this tree by the same pip install that installs its consumer, which is the reasoning #1587's own body gave for making the loads strict. A stale build/lib/ is the only way to hit the "older runtime" case, and failing loudly at ChipWorker::init is the intended handling — #1653 added a rebuild hint to that exact error for this reason.

If the v2 ownership rework genuinely needs a symbol to be optional, please name which one and why, rather than reverting the set.

2. Deleting the sim set_task_accepted_state_ctx re-creates the defect #1649 was written to catch

#1649's commit message describes it precisely: with no sim export, ChipWorker's optional load yields nullptr, both bind sites are skipped, and SimDeviceRunnerBase::publish_task_accepted stores through a null target — so a sim child never publishes acceptance and the launch fence silently degrades into a completion fence.

That is observable on the stacked #1588 right now: st-sim-a2a3 fails tests/st/a2a3/tensormap_and_ringbuffer/test_l3_launch_acceptance.py with the chip worker never published launch acceptance: [0, 0, 0].

3. The new worker_async_endpoint assertion fails on real hardware

st-onboard-a2a3 fails at test_worker_async_endpoint.py:214:

AssertionError: the successor staged only after the predecessor native run had already terminalized
assert 0 == 9

To be clear about attribution: that assertion is added by this PR, so this is not a pre-existing invariant being broken — it is a new claim that does not hold on device. 0 is IDLE, so when the successor published FRAME_STAGED the predecessor's frame had already been reset. Worth deciding which is true before fixing: the assertion's timing assumption is too strong (the predecessor is pinned by a SubTask fence, but nothing pins its frame state at _TASK_LAUNCHED), or the Worker-side rework genuinely delays staging past the predecessor's terminal transition. ut-a5 and st-onboard-a5 are also red.

4. Rebase

Merge-base is 2a650f2d; main is 673aecea. #1653 landed in between and touched chip_worker.cpp, device_runner_base.{h,cpp}, and run_stream_slots.h — all files this PR also edits, so the conflict only gets worse with time.

Happy to help dig into (3) if useful — that one blocks #1588 as well, since #1588 stacks on this branch and inherits the same failure.

@Crane-Liu
Crane-Liu force-pushed the codex/worker-async-w1-native-prepared-lane branch 3 times, most recently from 774646d to bc204de Compare August 3, 2026 13:29
@ChaoWao
ChaoWao force-pushed the codex/worker-async-w1-native-prepared-lane branch from bc204de to 8a70285 Compare August 4, 2026 05:19
@ChaoWao ChaoWao changed the title Refactor: align native prepared lane with v2 ownership Add: bounded asynchronous native run lane Aug 4, 2026
@ChaoWao

ChaoWao commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

@ChaoWao Addressed the review summary:

  • Rebased the single commit onto the latest main and kept every pipeline ABI
    symbol required across all eight host-runtime DSOs; optional symbol loading
    is not used.
  • Kept simulation launch-acceptance and run-identity exports in parity with
    onboard, with the host-runtime ABI tests retained.
  • Removed the timing-dependent output/frame-state assertions while preserving
    deterministic handle and lifecycle checks.
  • Added stable cross-runner TLS ownership, serialized native phase access, and
    interruption-safe direct-L2 registry/control cleanup found during the final
    audit.

Final validation includes all pre-commit hooks, 1093 Python unit tests, 78 C++
unit tests, clean A2A3sim/A5sim sweeps, and final-HEAD A2A3 hardware coverage for
worker_async_endpoint plus native_run_lifecycle.

@ChaoWao
ChaoWao force-pushed the codex/worker-async-w1-native-prepared-lane branch from 8a70285 to e84ad57 Compare August 4, 2026 05:51
- Return a live RunHandle from direct L2 submit and bound admission to
  one active run plus one eligible prepared successor.
- Carry immutable per-run identity and resource selection through the
  required host-runtime ABI with context-bound HostApi callbacks,
  replacing thread-local runner and resource selection state.
- Publish acceptance from the launch signal while preserving FIFO
  launch, serialized opaque runtime phases, and deterministic cleanup.
- Keep simpler_init flat and carry run context in required per-run calls.
- Keep A2/A3 HBG depth-two preparation capability-gated and A5 HBG
  depth-one until it owns per-slot execution resources.
- Document the contracts and add isolation, acceptance, concurrency,
  and lifecycle regressions.
@ChaoWao

ChaoWao commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Splitting out PR-A of this change as an independent, narrower PR: #1685 ("Refactor: bind platform host callbacks per run instead of per thread").

#1685 lands only the platform-layer prerequisite this PR bundles first:

  • HostApi → immutable HostApiFunctions table + per-run HostApi value object (runner + slot + bank).
  • NativeRunDescriptor carried across the C ABI; simpler_prepare_run / simpler_run take it (required).
  • Launch acceptance moves from the set_task_accepted_state_ctx TLS setter into the per-run launch signal (configure_acceptance / publish_acceptance).
  • Deletes the pthread-TLS resource-selection mechanism (NativeRunThreadSelection, both pthread keys, capture/restore) and all four setter exports.
  • PTO_PIPELINE_CONTRACT_ABI_VERSION bumped 1 → 2 so a stale .so is rejected at load.

It deliberately excludes the other two concerns still on this branch:

  • PR-B — the direct-L2 two-slot async lane (_L2NativeRun / _l2_fifo / live RunHandle in worker.py + the binding). ChipWorker::run_on_slot keeps main's synchronous _run_slot path.
  • PR-C — the HostTensorAccessScope RAII conversion (a minimal host_tensor_access_reset(const HostApi*) bridge lands in Refactor: bind platform host callbacks per run instead of per thread #1685 so HBG still compiles; the full RAII scope follows).

Implemented independently from this branch (not pushed to it). Once #1685 merges, the remaining two can rebase onto it — HostApi already exists, so neither depends on the new lane. PR body has the per-thread-state safety argument for doing B6c while the per-run executor thread still exists, and the ABI-version answer.

ChaoWao added a commit to ChaoWao/simpler-fork that referenced this pull request Aug 4, 2026
Split the platform HostApi into an immutable HostApiFunctions table plus a
per-run HostApi value object that binds it to one runner and one run's
pipeline slot / arena bank. Carry the run's resource selection and trace
identity across the C ABI in a required NativeRunDescriptor, and delete the
pthread-TLS resource-selection mechanism (NativeRunThreadSelection, both
pthread keys, capture/restore) and the four setter exports it made necessary.
Launch acceptance moves from a TLS setter into the per-run launch signal.

Bump PTO_PIPELINE_CONTRACT_ABI_VERSION 1 -> 2 so a stale host_runtime.so
(whose simpler_run/prepare_run still take the old flat argument list) is
rejected at load rather than crashing at the first call.

PR-A of the hw-native-sys#1650 three-way split; PR-B (direct-L2 async lane) and PR-C
(HostTensorAccessScope RAII) follow.

Co-Authored-By: Claude <noreply@anthropic.com>
ChaoWao added a commit to ChaoWao/simpler-fork that referenced this pull request Aug 4, 2026
Split the platform HostApi into an immutable HostApiFunctions table plus a
per-run HostApi value object that binds it to one runner and one run's
pipeline slot / arena bank. Carry the run's resource selection and trace
identity across the C ABI in a required NativeRunDescriptor, and delete the
pthread-TLS resource-selection mechanism (NativeRunThreadSelection, both
pthread keys, capture/restore) and the four setter exports it made necessary.
Launch acceptance moves from a TLS setter into the per-run launch signal.

Bump PTO_PIPELINE_CONTRACT_ABI_VERSION 1 -> 2 so a stale host_runtime.so
(whose simpler_run/prepare_run still take the old flat argument list) is
rejected at load rather than crashing at the first call.

PR-A of the hw-native-sys#1650 three-way split; PR-B (direct-L2 async lane) and PR-C
(HostTensorAccessScope RAII) follow.

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants