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
13 changes: 10 additions & 3 deletions docs/callable-identity-registration.md
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,11 @@ The callable digest remains part of the immutable payload, while the trailer
lets both sides reject a stale activation, acceptance, or completion from an
older use of the same pipeline frame. `FRAME_STAGED` confirms that this exact
payload has been validated and retained; it does not resolve to a different
callable identity and does not mean a native run has been prepared.
callable identity and does not by itself prove that a native run is prepared.
An eligible HBG successor normally prepares before this publication when its
predecessor is already active. A successor staged before any active claim
publishes validation-only and may later gain a native token without another
mailbox state transition.

The target child loop owns the final execution resolve:

Expand Down Expand Up @@ -698,8 +702,11 @@ Local mailbox task frames are hashid-based:
- Chip and sub child loops resolve `hashid -> local_slot` immediately before
execution.
- A two-frame chip loop retains the resolved slot and immutable payload at
`FRAME_STAGED`; runtime-native prepare waits for activation and native-run
availability.
`FRAME_STAGED`; an eligible non-diagnostic HBG successor prepares a native
token in the lease-selected inactive bank once its predecessor owns the
active claim. A successor staged before that claim may gain the token without
another state transition. HBG tasks adjacent to diagnostics and all TMR
tasks wait for activation and native-run availability.
- Protocol/run/lease-generation/dispatch identity guards task-frame reuse, and
a separate sticky word records only real native launch acceptance.
- `ChipWorker.run(local_slot)` remains private to the child process.
Expand Down
9 changes: 7 additions & 2 deletions docs/dynamic-linking.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,11 +291,16 @@ ChipWorker.init(device_id, bins) # Python wrapper
ctypes.CDLL(libcpu_sim_context.so, RTLD_GLOBAL) # sim only, once per process
_ChipWorker.init(host_path, aicpu_path, aicore_path, device_id) # C++
dlopen(host_runtime.so, RTLD_LOCAL)
dlsym: create_device_context, destroy_device_context, simpler_init,
dlsym every required export declared in pto_runtime_c_api.h, including:
create_device_context, destroy_device_context, simpler_init,
get_runtime_size, get_runtime_alignment, simpler_register_callable,
simpler_prepare_run, simpler_launch_run, simpler_poll_run,
simpler_wait_run, simpler_finalize_run, simpler_run,
simpler_unregister_callable, finalize_device
simpler_unregister_callable, get_pipeline_contract,
supports_concurrent_native_prepare_ctx,
set_native_run_identity_ctx, set_task_accepted_state_ctx,
get_arena_bank_gm_heap_base_ctx, get_retained_temp_addr_ctx,
finalize_device
create_device_context() → DeviceContextHandle
allocate zeroed, aligned, stable native-run storage per pipeline slot
simpler_init(ctx, device_id, aicpu*, aicpu_size, aicore*, aicore_size)
Expand Down
60 changes: 46 additions & 14 deletions docs/task-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ follows `CallConfig`, and the 32-byte digest prefixes the args blob. SUB,
nested/remote L3, simulation, A5, and depth-one fallbacks carry that payload in
the base compatibility frame. A direct A2/A3 onboard chip child can instead
use either of two task frames after the base control frame, with
`PREPARE_READY -> FRAME_STAGED -> ACTIVATE` separating validation from native
launch. The receiving child resolves the digest in its own address space in
both forms.
`PREPARE_READY -> FRAME_STAGED -> ACTIVATE` separating endpoint preparation
from native launch. The receiving child resolves the digest in its own address
space in both forms.

The proposed remote L3 path keeps the same callable identity contract, but
sends it in a versioned TASK frame. The remote endpoint resolves the digest
Expand Down Expand Up @@ -348,16 +348,20 @@ finalize`, and the existing `simpler_run` / `ChipWorker.run` surface is the
blocking composition of those phases. `prepare` constructs and binds the
per-run `Runtime` without crossing the device launch fence; `launch` returns
after the backend has actually submitted its execution; `finalize` owns
validation, copy-back, DFX, and Runtime destruction. One runner still permits
only one unfinished native run, including a prepared-but-not-launched run.
validation, copy-back, DFX, and Runtime destruction. A backend that advertises
concurrent native preparation may own one active token and one prepared but
unlaunched and unaccepted successor token in separate lease-selected banks.
Other backends permit only one unfinished native run, including a
prepared-but-not-launched run.

#### Two-frame endpoint staging lane

A direct A2/A3 chip endpoint with a negotiated depth of at least two uses two
task frames and advertises `supports_frame_staging`. One `WorkerThread` owns
both frames and drives them through a non-blocking progress interface; the
child process likewise has one loop that services control traffic, both task
frames, and the single native-run lifecycle. There is no thread per frame.
frames, and the bounded active/prepared native lifecycles. There is no thread
per frame.

The active and successor paths are:

Expand All @@ -367,11 +371,28 @@ IDLE -> PREPARE_READY -> FRAME_STAGED -> ACTIVATE -> TASK_LAUNCHED
-> TASK_DONE | TASK_FAILED
```

`FRAME_STAGED` means only that the child validated and owns an immutable frame
snapshot. It is distinct from both an L3 run in `RunPhase::PREPARED` and a
runtime-specific `ChipWorkerNativeRun` in its prepared phase. While an active
native run exists, the successor remains at `FRAME_STAGED`; native prepare is
deferred until the predecessor has polled complete and finalized.
`FRAME_STAGED` means that the child owns an immutable frame snapshot; it does
not by itself distinguish validation-only staging from completed native
preparation. For a `host_build_graph` successor whose own configuration and
active predecessor are both non-diagnostic, the child constructs a
generation-bound native run in the leased inactive arena bank while the
predecessor executes. If the predecessor is already active, that preparation
finishes before `FRAME_STAGED` publication. A successor that reaches the child
before any predecessor owns the active claim publishes validation-only, so the
parent can activate it without deadlock; native prepare follows activation or
a later predecessor claim without another mailbox state transition. HBG tasks
adjacent to diagnostic state and all
`tensormap_and_ringbuffer` tasks also use this as a validation-only state:
native prepare waits for the predecessor's complete device fence because their
shared diagnostic or device-scratch state is not safe to rewrite early.
Backend and per-run capabilities, rather than the mailbox protocol, select
between these meanings.

An HBG successor's prepared token remains unlaunched and unaccepted until
`ACTIVATE`, and activation still cannot launch it until the predecessor has
polled complete and finalized. The sticky acceptance word therefore remains
zero throughout preparation. Shutdown, stale activation, and pre-launch
failure finalize the token exactly once before the frame becomes terminal.

The scheduler stages only the first eligible single NEXT_LEVEL task from the
prepared FIFO successor. Tasks from the active run use only the active lane, so
Expand All @@ -380,14 +401,25 @@ remain on their normal queue and dispatch synchronously after FIFO promotion.
Remote, SUB, A5, simulation, nested-worker, and single-frame endpoints retain
the blocking compatibility path.

The child validates newly visible metadata from both frames before selecting
the next active `dispatch_id`. It prepares that active token first; preparation
of an ordinary HBG successor starts only after the selected predecessor owns
the native active claim. Physical frame order therefore cannot reorder native
prepare or launch.

Every task frame carries protocol, run, lease slot, generation, dispatch, and
callable identity. Its sticky acceptance word is separate from the state word
and is set only by the real native launch marker. `FRAME_STAGED` never satisfies
the launch fence. A terminal pre-launch failure may conservatively retire the
run-level acceptance waiter, but it does not set the frame's acceptance word.
The parent clears that word only immediately before reusing an `IDLE` frame.
Control commands continue to use a separate base frame, so they cannot
overwrite either staged task frame.
overwrite either staged task frame. Callable prepare/register/unregister
commands defer while an active or backend-prepared token owns runtime state.
The default unbounded control wait therefore follows child liveness through a
long run; a finite control timeout includes this deferral interval, and expiry
poisons the local endpoint because the pending command's completion is
uncertain.

#### TRB temporary buffer

Expand Down Expand Up @@ -695,8 +727,8 @@ Step-by-step (one chip worker):
| 3 | `Orchestrator::submit_next_level` | `slot = ring.alloc()`; move `chip_args` into `slot.task_args`; walk tags → `tensormap.lookup(a.data)`, `tensormap.lookup(b.data)`, `tensormap.insert(c.data, slot)`; push ready |
| 4 | Scheduler thread | pop `slot` from worker 0's FIFO; resolve stable worker ID 0 to WT_chip_0; dispatch |
| 5 | WT_chip_0 parent side | encode one leased task frame: write `config`, digest prefix, and the args blob; publish `TASK_READY` for the active lane or `PREPARE_READY` for a staged successor |
| 6 | chip_0 child process | validate the frame and resolve its digest, then publish `FRAME_STAGED`; a successor waits for `ACTIVATE`, while the active frame may proceed immediately |
| 7 | chip_0 native-run path | after activation, prepare and launch the native run; poll it to completion and finalize it before another staged frame may launch. Compatibility endpoints perform the equivalent operation through blocking `ChipWorker::run` |
| 6 | chip_0 child process | validate the frame and resolve its digest; ordinary HBG with an active predecessor also prepares the leased inactive arena bank before publishing `FRAME_STAGED`, while a frame with no active predecessor, diagnostic HBG, and TMR publish after validation and defer native prepare |
| 7 | chip_0 native-run path | after activation and the predecessor's finalization fence, launch an already-prepared HBG run or finish deferred native preparation and then launch; poll it to completion and finalize it before another staged frame may launch. Compatibility endpoints perform the equivalent operation through blocking `ChipWorker::run` |
| 8 | runtime.so | translate host ptrs → device ptrs; dispatch AICPU / AICore; write output into `c`'s shm |
| 9 | chip_0 child | native finalization returns; write `TASK_DONE` |
| 10 | WT_chip_0 parent | observe `TASK_DONE`; push success completion |
Expand Down
38 changes: 27 additions & 11 deletions docs/worker-manager.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ A two-frame `LocalMailboxEndpoint` advertises `supports_frame_staging` and is
driven through `submit_progress`, `activate_progress`, and `poll_progress`.
The owning `WorkerThread` is the only parent-side progress owner. The child also
uses one `run_two_frame_loop`; it services the separate control base, stages
both task frames, and owns the one native-run lifecycle.
both task frames, and owns the bounded active/prepared native lifecycles.

The ordinary active dispatch and the staged successor use distinct initial
states:
Expand All @@ -238,24 +238,40 @@ successor: IDLE -> PREPARE_READY -> FRAME_STAGED -> ACTIVATE

`FRAME_STAGED` means the child validated the frame identity and arguments,
resolved the callable digest, rewrote any mapped host addresses, and retained
an immutable snapshot. It does **not** mean that the runtime-specific native
run is prepared. While one native run is active, the successor remains at
`FRAME_STAGED`. Only after activation and after the predecessor is polled and
finalized does the child call native `prepare`, `launch`, `poll`, and
`finalize` for the successor. This preserves the current one-unfinished-native-
run contract.
an immutable snapshot. The state does not by itself distinguish validation-only
staging from completed native preparation. When both an HBG successor and its
already-active predecessor are non-diagnostic, the child prepares a
generation-bound native token in the successor's leased inactive bank before
publishing `FRAME_STAGED`. A successor that arrives before any active claim
publishes validation-only, allowing the parent to activate it; native prepare
follows that activation or a later predecessor claim without another mailbox
state transition. HBG tasks adjacent to diagnostic state and all TMR tasks keep
the same two-frame protocol but defer native prepare because their shared
diagnostic or device-scratch state cannot be rewritten while another run is
active.

An HBG token remains unlaunched and unaccepted until activation, and no backend
launches a successor until the predecessor is polled and finalized. Shutdown,
stale activation, and pre-launch failure finalize any unlaunched token exactly
once.

Activation is sticky on the parent side: FIFO promotion may be observed before
the child reaches `FRAME_STAGED`. The endpoint records that permission and
publishes `ACTIVATE` only by a compare/exchange from the matching
`FRAME_STAGED` state. The child chooses activated frames by `dispatch_id`, so a
later frame cannot bypass an earlier eligible dispatch.
`FRAME_STAGED` state. The child validates all newly visible frame metadata
before choosing the next active frame by `dispatch_id`; capable native prepare
starts for that frame first, and a successor prepares only after the selected
predecessor owns the active claim. Frame index therefore cannot let a later
dispatch bypass an earlier eligible dispatch.

Task-frame publication briefly shares the base control mutex so it has a
defined order relative to a control request. Once published, ordinary controls
may run while the device task is active. Registry-mutating controls are
deferred until the active native run is finalized; final unregister also waits
for every published frame using that digest to retire.
deferred until active and backend-prepared native state is finalized; final
unregister also waits for every published frame using that digest to retire.
The default unbounded control wait follows child liveness through this
deferral. A finite timeout includes the deferral interval and poisons the
endpoint if it expires with completion uncertain.

Each task frame is bound to its pipeline lease slot and carries a protocol
trailer with `{run_id, slot_id, generation, dispatch_id}`. Parent and child
Expand Down
17 changes: 13 additions & 4 deletions python/bindings/task_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1691,7 +1691,9 @@ NB_MODULE(_task_interface, m) {
nb::class_<ChipWorkerNativeRun>(m, "_ChipWorkerNativeRun")
.def_ro("slot_id", &ChipWorkerNativeRun::slot_id)
.def_ro("generation", &ChipWorkerNativeRun::generation)
.def_ro("run_epoch", &ChipWorkerNativeRun::run_epoch);
.def_ro("run_epoch", &ChipWorkerNativeRun::run_epoch)
.def_ro("run_id", &ChipWorkerNativeRun::run_id)
.def_ro("dispatch_id", &ChipWorkerNativeRun::dispatch_id);

// --- ChipWorker ---
nb::class_<ChipWorker>(m, "_ChipWorker")
Expand Down Expand Up @@ -1810,12 +1812,15 @@ NB_MODULE(_task_interface, m) {
.def(
"_prepare_native_run_from_blob",
[](ChipWorker &self, int32_t callable_id, uint64_t args_blob_ptr, size_t blob_capacity,
const CallConfig &config, uint32_t slot_id, uint64_t generation) {
const CallConfig &config, uint32_t slot_id, uint64_t generation, uint64_t run_id, uint64_t dispatch_id) {
TaskArgsView view = read_blob(reinterpret_cast<const uint8_t *>(args_blob_ptr), blob_capacity);
return self.prepare_native_run(callable_id, view, config, PipelineSlotLease{slot_id, 0, generation});
return self.prepare_native_run(
callable_id, view, config, PipelineSlotLease{slot_id, 0, generation}, run_id, dispatch_id
);
},
nb::arg("callable_id"), nb::arg("args_blob_ptr"), nb::arg("blob_capacity"), nb::arg("config"),
nb::arg("slot_id"), nb::arg("generation"), nb::call_guard<nb::gil_scoped_release>(),
nb::arg("slot_id"), nb::arg("generation"), nb::arg("run_id") = 0, nb::arg("dispatch_id") = 0,
nb::call_guard<nb::gil_scoped_release>(),
"Prepare a generation-bound native run from a raw mailbox TaskArgs blob."
)
.def(
Expand Down Expand Up @@ -1886,6 +1891,10 @@ NB_MODULE(_task_interface, m) {
.def_prop_ro("initialized", &ChipWorker::initialized)
.def_prop_ro("pipeline_depth", &ChipWorker::pipeline_depth)
.def_prop_ro("runtime_slot_count", &ChipWorker::runtime_slot_count)
.def_prop_ro(
"supports_concurrent_native_prepare", &ChipWorker::supports_concurrent_native_prepare,
"Whether non-diagnostic native preparation may overlap one active run in another slot."
)
.def_prop_ro(
"runtime_buffer_addrs", &ChipWorker::runtime_buffer_addrs,
"Host Runtime staging buffer address of every copy the runtime's "
Expand Down
Loading
Loading