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
17 changes: 12 additions & 5 deletions docs/chip-level-arch.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,20 @@ simpler_init(ctx, device_id, // attach + binary takeove
aicpu_binary, aicpu_size,
aicore_binary, aicore_size);
size_t size = get_runtime_size();
register_callable(ctx, cid, callable); // one-time per callable
simpler_run(ctx, runtime, cid, args, config); // per-launch — no binaries; config
// carries aicpu_thread_num,
// diagnostics + ring overrides
unregister_callable(ctx, cid);
size_t alignment = get_runtime_alignment();
void *runtime = allocate_zeroed_aligned(size, alignment); // stable until finalize
simpler_register_callable(ctx, cid, callable); // one-time per callable

// Progressable form; simpler_run(...) composes these phases synchronously.
simpler_prepare_run(ctx, runtime, cid, args, config); // bind, no device launch
simpler_launch_run(ctx, runtime); // returns after launch fence
simpler_wait_run(ctx, runtime); // or poll until complete
simpler_finalize_run(ctx, runtime); // validate, copy back, destroy

simpler_unregister_callable(ctx, cid);
finalize_device(ctx);
destroy_device_context(ctx);
free(runtime);
```

### Layer 3: Python API (`python/bindings/task_interface.cpp` via nanobind)
Expand Down
7 changes: 4 additions & 3 deletions docs/dfx/dep-gen.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,9 @@ nothing to capture-then-reconstruct.
shared-memory ring, and the drain thread are all skipped
(`dep_gen_host_graph_active()` tells the runner). Nothing is dropped under
back-pressure because nothing is streamed.
- **Output.** The same `deps.json`, written at run teardown from the graph that
run's orchestration built.
- **Output.** The same `deps.json`, written at run teardown. After prepare's
host orchestration builds the graph, the phased runtime moves it into
run-owned storage and the executor adopts it before execution.

---

Expand Down Expand Up @@ -394,7 +395,7 @@ list; only the dep_gen replay graph loses the tail.
| Capture call site (device-orch) | `src/{a2a3,a5}/runtime/tensormap_and_ringbuffer/runtime/pto_orchestrator.cpp` `submit_task_common` | One conditional block that snapshots inputs into the ring when `is_dep_gen_enabled()`; fires for both `submit_task` and `submit_dummy_task`. The schema carries `kernel_ids[3] = {aic, aiv0, aiv1}` so the swimlane post-processor can resolve `task_id → kernel` from `deps.json` at level=1 where the AICore record is the sole device-side identity source. Inactive subslots stay at `INVALID_KERNEL_ID = -1`. It also carries the SPMD logical block num (`block_num` on a2a3, `core_num` on a5's launch spec) as `tasks[].block_num`. |
| Replay | `src/{a2a3,a5}/runtime/tensormap_and_ringbuffer/host/dep_gen_replay.{h,cpp}` | Pure CPU; runs dual-pass differential replay — `compute_task_fanin` (oracle) + inlined STEP A/B mirror (annotated) against two `PTO2TensorMap` instances. Emits `deps.json` when both passes agree per record. Platform-agnostic — a5 reuses the a2a3 source verbatim. |
| Host-direct capture (host-orch) | `src/a2a3/runtime/host_build_graph/runtime/dep_gen_host_graph.h`, `src/a2a3/runtime/host_build_graph/host/dep_gen_host_graph.cpp` | Task / tensor / edge tables filled from `submit_task_common` + `compute_task_fanin`'s `Annotate` hooks (`src/a2a3/runtime/host_build_graph/runtime/pto_dep_compute.h`), reset per orchestration by `run_host_orchestration`, serialized by the same `deps.json` writer. The runtime translation unit carries weak no-op fallbacks so the AICPU build links without it. |
| Device-runner hookup | `src/{a2a3,a5}/platform/{onboard,sim}/host/device_runner.cpp` | `dep_gen_host_graph_active()` picks the shape: host-orch calls `dep_gen_host_graph_emit(deps_path)` and skips collector init/start/reconcile entirely; device-orch calls `dep_gen_replay_emit_deps_json(records.data(), records.size(), deps_path)` post-`reconcile_counters`. The c_api latches the CallConfig before the bind so host capture is armed before the orchestration it records. |
| Device-runner hookup | `src/{a2a3,a5}/platform/{onboard,sim}/host/device_runner.cpp` | `dep_gen_host_graph_active()` picks the shape: host-orch moves the prepare thread's completed capture into run-owned storage, adopts it on the executor, calls `dep_gen_host_graph_emit(deps_path)` at teardown, and skips collector init/start/reconcile entirely; device-orch calls `dep_gen_replay_emit_deps_json(records.data(), records.size(), deps_path)` post-`reconcile_counters`. The c_api latches the CallConfig before the bind so host capture is armed before the orchestration it records. |
| Viewer | `simpler_setup/tools/deps_viewer.py` | `deps.json` → text (default) or pan/zoom HTML |
| Test | `tests/st/{a2a3,a5}/tensormap_and_ringbuffer/dfx/dep_gen/test_dep_gen.py` + `test_dep_gen_chain.py`, `tests/st/a2a3/host_build_graph/dfx/dep_gen/test_dep_gen.py` | Smoke test + 6-edge validation against `vector_example` orchestration (both platforms share byte-identical orchestration code). The host_build_graph case runs the *same* orchestration through host-direct capture and asserts the same 6 edges, so a divergence between the two shapes fails a test. |

Expand Down
17 changes: 17 additions & 0 deletions docs/dfx/host-trace.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,23 @@ device-log lines. A phase that was never stamped
(0 ns) is skipped — e.g. `so_load` is ~0 on a cached-callable run. See
[device-phases.md](device-phases.md) for the device-side mechanism.

The phased native-run interface preserves this same marker contract. Prepare
allocates one `inv` and records the host-wall start; prepare, the blocking
executor thread, and finalize temporarily bind that `(inv, hid)` while emitting
their spans. Finalize releases the runner claim, destroys the per-run state, and
then emits the stored `simpler_run` wall, so the root includes that cleanup tail.
No trace scope or synthetic nesting remains active between C API calls. For
direct phased use the host wall is the full prepare-to-finalize lifetime,
including time the caller spends polling or doing other host work; blocking
`simpler_run` is the same phases composed back-to-back.

| Depth | Span names |
| ----- | ---------- |
| 0 | `simpler_run` |
| 1 | `simpler_run.bind`, `simpler_run.runner_run`, `simpler_run.validate` |
| 2 | `simpler_run.bind.args`, `simpler_run.bind.prebuilt`, `simpler_run.runner_run.device_wall` |
| 3 | `simpler_run.runner_run.device_wall.{preamble,so_load,graph_build,config_validate,arena_wire,sm_reset,post_orch,orch,sched,task_slot_*}` |

## Reading the markers — `strace_timing.py`

```bash
Expand Down
54 changes: 32 additions & 22 deletions docs/dynamic-linking.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,12 @@ ChipWorker.init(device_id, bins) # Python wrapper
_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,
get_runtime_size, register_callable, simpler_run, unregister_callable,
finalize_device
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
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)
DeviceRunner::attach_current_thread(device_id)
pto_cpu_sim_bind_device(device_id)
Expand All @@ -303,16 +306,20 @@ ChipWorker.init(device_id, bins) # Python wrapper

ChipWorker.run(handle, args, config) # public wrapper path
simpler_run(ctx, buf, internal callable entry, args, config)
new (buf) Runtime()
DeviceRunner::bind_callable_to_runtime(r, cid, api, args, rings) # replay + per-run bind
DeviceRunner::run(r, config) # applies config; width already resolved pre-bind
clear_cpu_sim_shared_storage()
ensure_binaries_loaded() dlopen aicpu/aicore SOs once
launch AICPU + AICore threads
join all threads
unload_executor_binaries() dlclose aicpu/aicore SOs
validate_runtime_impl(r) copy results, remove kernels
r->~Runtime()
simpler_prepare_run(...)
new (buf) NativeRunState() owns Runtime + executor state
DeviceRunner::bind_callable_to_runtime(r, cid, api, args, rings)
simpler_launch_run(...)
executor thread: DeviceRunner::run(r, config)
clear_cpu_sim_shared_storage()
ensure_binaries_loaded() dlopen aicpu/aicore SOs once
launch AICPU + AICore threads
join all threads
unload_executor_binaries() dlclose aicpu/aicore SOs
simpler_wait_run(...)
simpler_finalize_run(...)
validate_runtime_impl(r) copy results, remove kernels
state->~NativeRunState() destroys Runtime

ChipWorker.finalize()
finalize_device(ctx)
Expand Down Expand Up @@ -351,20 +358,23 @@ device_worker_main(device_id)

for each callable:
ChipWorker.register_callable(callable) # returns opaque handle
register_callable(ctx, internal callable entry, callable)
simpler_register_callable(ctx, internal callable entry, callable)
upload child kernels, copy orch SO to device buffer
for each launch with that handle:
ChipWorker.run(handle, args, config)
simpler_run(ctx, buf, internal callable entry, args, config)
new (buf) Runtime()
bind_callable_to_runtime() replay + rtMalloc, rtMemcpy to device
DeviceRunner::run()
ensure_binaries_loaded() already done by init
launch_aicore_kernel() cached rtRegisterAllKernel handle
+ rtKernelLaunchWithHandleV2
launch_aicpu_kernel(Run) rtsLaunchCpuKernel, cached rtFuncHandle
aclrtSynchronizeStreamWithTimeout() wait on both streams
validate_runtime_impl() rtMemcpy results back to host
simpler_prepare_run(...)
new (buf) NativeRunState() owns Runtime + executor state
bind_callable_to_runtime() replay + rtMalloc, rtMemcpy to device
simpler_launch_run(...)
executor thread: DeviceRunner::run()
ensure_binaries_loaded() already done by init
launch_aicore_kernel() cached rtRegisterAllKernel handle
+ rtKernelLaunchWithHandleV2
launch_aicpu_kernel(Run) rtsLaunchCpuKernel, cached rtFuncHandle
aclrtSynchronizeStreamWithTimeout() wait on both streams
simpler_wait_run(...)
simpler_finalize_run(...) rtMemcpy results back; destroy state

ChipWorker.finalize()
finalize_device(ctx) rtDeviceReset()
Expand Down
12 changes: 7 additions & 5 deletions docs/l3-l2-orch-comm.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,11 +162,13 @@ therefore release the child allocation only after no parent operation can still
touch it.

If an unadopted native owner reports a cleanup failure while an import unwinds,
the diagnostic stays on the importing thread until the Worker consumes it and
poisons itself. It cannot be consumed by a Worker importing concurrently on a
different thread. If the diagnostic survives to a later create on the same
thread, its original Worker is no longer identifiable, so that Worker is
conservatively stopped with an explicit attribution message.
the owner records the diagnostic under its Worker's stable owner token. The
owning Worker consumes that diagnostic at its next admission, create rollback,
or close boundary and poisons itself. Transfer is two-phase: native storage is
read non-destructively and acknowledged only after the Python poison is
published, so an interruption at that boundary cannot lose the diagnostic.
Another Worker cannot consume or be poisoned by that error, even when both
Workers run on the same thread.

## 4. Signal Counters

Expand Down
11 changes: 11 additions & 0 deletions docs/task-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,17 @@ second device execution, or cross-run publication overlap: the endpoint remains
a single synchronous round trip, and the scheduler dispatches only the run that
holds the FIFO head and still owns its lease.

The L2 host-runtime boundary is nevertheless progressable. Its opaque native
run storage supports `prepare -> launch -> poll/wait -> 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. Until execution-only `DeviceRunner` state is made
per-run, the backend admits only one prepared or launched native run at a time.
The single-frame child intentionally continues to use the blocking composition;
the phase split is the B3a ownership seam, not a claim of mailbox overlap.

Simulation implements the same depth, so the contract means the same thing on
both platforms: its runner owns one arena bank and one retained temporary
buffer per slot, and its single-entry prebuilt-arena cache stays owned by
Expand Down
Loading
Loading