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
4 changes: 3 additions & 1 deletion docs/a5-sdma-overlay.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ pre-allocates the per-rank PTO-ISA async-SDMA scratch workspace via
read `workSpace` to submit SQEs to the SDMA hardware engine; the deferred
completion machinery polls workspace event-records to signal consumers.

This is the a5 mirror of the a2a3 SDMA path, which is always on.
This is the a5 mirror of the a2a3 SDMA path. On a2a3 the provider is always
compiled in, but provisioning it is opt-in at runtime: `Worker(...,
enable_sdma=True)`, which defaults to `False`.

## Why it is gated off

Expand Down
3 changes: 2 additions & 1 deletion docs/aicpu-kernel-launch-mechanisms.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,8 @@ above stays valid.
- Issue [#822](https://github.com/hw-native-sys/simpler/issues/822) —
the bug report with the diagnostic D2H recipe and CANN source
pointers
- PR #537 — the migration that attempted Path B
- PR #537 — the migration that shipped Path A (dispatcher bootstrap plus
per-task `rtsLaunchCpuKernel`)
- [`src/common/aicpu_loader/`](../src/common/aicpu_loader/) —
the dispatcher bootstrap, three-symbol device contract, and per-task
launch loader
Expand Down
6 changes: 3 additions & 3 deletions docs/comm-domain.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ symmetric window is realized:

| Aspect | Sim | HCCL (onboard) |
| ------ | --- | -------------- |
| Window memory | POSIX shm + `ftruncate`, mmap'd per rank | VMM physical allocation + shareable-handle import; peer access via `aclrtDeviceEnablePeerAccess` |
| Window memory | POSIX shm + `ftruncate`, mmap'd per rank | a2a3: Fabric V2 handle exchange (`ACL_MEM_SHARE_HANDLE_TYPE_FABRIC`), falling back to VMM + shareable-handle IPC where Fabric is unsupported. a5: VMM shareable handles only. Cross-card P2P via `aclrtDeviceEnablePeerAccess` on both |
| Subset barrier | shm-header atomic, `allocation_id`-scoped | file barriers, `allocation_id`-scoped |
| Window init | window zeroed before the subset barrier (`memset`) | window zeroed before the handle is announced (`aclrtMemset`) |
| Async-DMA workspace | n/a | a2a3: opt-in per Worker (`enable_sdma`); a5: optional communication overlay, gated off by default |
Expand Down Expand Up @@ -260,5 +260,5 @@ the child — allocate it with `create_host_buffer` instead.
- `examples/workers/l3/dual_domain_overlap/` — overlapping domains where one
worker participates in both.
- `examples/a2a3/tensormap_and_ringbuffer/sdma_async_completion_demo/` — host
staging via `copy_to` + cross-rank `SdmaTget`; its producer `CoreCallable`
declares the SDMA workspace requirement.
staging via `copy_to` + cross-rank `SdmaTget`; the SDMA workspace is
provisioned by constructing the `Worker` with `enable_sdma=True`.
9 changes: 5 additions & 4 deletions docs/dynamic-linking.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ Applies to all 4 runtime executors: a2a3 (hbg, tmr), a5 (hbg, tmr).
| Dispatcher SO bytes | `DeviceRunnerBase::dispatcher_so_binary_` | Init-only: passed to `LoadAicpuOp::BootstrapDispatcher`, then cleared |
| Runtime AICPU SO | Preinstall file `simpler_inner_<fp>_<device_id>.so` | Written once through dispatcher bootstrap, then registered via `rtsBinaryLoadFromFile` |
| AICPU entry handles | `LoadAicpuOp` cached `rtFuncHandle`s | Per-runtime-group: reused by `rtsLaunchCpuKernel` on every task |
| AICore binary | `rtRegisterAllKernel` handle | Per-run: registered each `launch_aicore_kernel()` call |
| AICore binary | `rtRegisterAllKernel` handle | Lazily registered on the first `launch_aicore_kernel()`, then the cached handle is reused |
| Kernel binaries | `func_id_to_addr_` (device GM addresses) | Per-task: uploaded to device GM, cached by func_id |
| CANN HAL | `g_hal_handle` (file-scope static) | Process lifetime: loaded once for profiling, never closed |

Expand Down Expand Up @@ -354,9 +354,10 @@ device_worker_main(device_id)
bind_callable_to_runtime() replay + rtMalloc, rtMemcpy to device
DeviceRunner::run()
ensure_binaries_loaded() already done by init
rtsLaunchCpuKernel() cached rtFuncHandle
rtStreamSynchronize() wait for completion
launch_aicore_kernel() rtRegisterAllKernel + rtKernelLaunch
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

ChipWorker.finalize()
Expand Down
2 changes: 1 addition & 1 deletion docs/hardware/chip-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ A complete `Worker::run()` call traverses all three tiers:
- writes FIN to its completion register

4. AICPU
- polls AICore completion registers (Device-nGnRnE MMIO)
- polls AICore completion registers (Device-nGnRE MMIO)
- rmb() before reading any AICore-produced slot
(see cache-coherency.md)
- releases dependents into the ready queue
Expand Down
6 changes: 4 additions & 2 deletions docs/investigations/2026-07-a2a3-sdma-fault-teardown.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,10 @@ the rest of the system on the fast path.
- A Worker constructed without `enable_sdma` creates no SDMA streams, so an
AICore fault on it recovers on main's fast path (`507046`, ~0.3 s reset)
rather than the ~306 s SDMA-generation stall documented above.
- The two-device `sdma_async_completion_demo` (a separate, pre-existing
comm-domain SDMA overlay, not the injection path) is unaffected by this change.
- The two-device `sdma_async_completion_demo` is a separate, pre-existing
comm-domain SDMA overlay rather than the injection path, but it does
provision SDMA itself (`enable_sdma=True`), so it carries the same
teardown cost.

## When to reconsider

Expand Down
26 changes: 22 additions & 4 deletions docs/worker-manager.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,24 @@ WorkerCompletion LocalMailboxEndpoint::run(Ring *ring, WorkerDispatch d) {
// Signal child
write_state(mailbox_, MailboxState::TASK_READY);

// Poll for completion
while (read_state(mailbox_) != MailboxState::TASK_DONE)
std::this_thread::sleep_for(std::chrono::microseconds(50));
// Spin-poll for completion. The task's latency runs through this wait, so
// it never sleeps (codestyle rule 5). A child that dies without publishing
// TASK_DONE would spin here forever, so its liveness is sampled on a
// kChildLivenessPollPeriod steady-clock period rather than an iteration
// count, which no longer maps to a bounded time at spin speed.
auto next_liveness_check = steady_clock::now() + kChildLivenessPollPeriod;
while (read_state(mailbox_) != MailboxState::TASK_DONE) {
auto now = steady_clock::now();
if (now >= next_liveness_check) {
next_liveness_check = now + kChildLivenessPollPeriod;
std::string death = check_child_death();
if (!death.empty()) {
completion.outcome = EndpointOutcome::ENDPOINT_FAILURE;
completion.error_message = "LocalMailboxEndpoint::run: " + death;
return completion;
}
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

int err = read_error(mailbox_);
write_state(mailbox_, MailboxState::IDLE);
Expand All @@ -187,7 +202,10 @@ Parent-side cost per dispatch:
- One reserved `uint64`, one `CallConfig`, one 32-byte digest, and one
TaskArgs blob
- One signal (`write_state`)
- Poll loop with `sleep_for(50us)` (not busy-wait)
- Spin-poll loop, never a sleep — a task's latency passes through this wait
([codestyle](../.claude/rules/codestyle.md) rule 5). Child liveness is sampled
on a steady-clock period, so a dead child ends the wait with
`ENDPOINT_FAILURE` instead of spinning the parent forever
- One explicit completion outcome: success, task failure, or endpoint failure

Total ~nanoseconds overhead; the wait is dominated by actual kernel execution.
Expand Down
1 change: 0 additions & 1 deletion src/a2a3/platform/onboard/host/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ list(APPEND HOST_RUNTIME_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/../../../../common/aicpu_loader/host/load_aicpu_op.cpp"
)
# Add common/platform/onboard/host sources (shared between a2a3 / a5 onboard).
# See .docs/ONBOARD_HOST_COMMON_REFACTOR.md for the migration plan.
list(APPEND HOST_RUNTIME_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/../../../../common/platform/onboard/host/device_runner_helpers.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/../../../../common/platform/onboard/host/device_runner_base.cpp"
Expand Down
3 changes: 1 addition & 2 deletions src/a2a3/platform/onboard/host/comm_hccl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -833,8 +833,7 @@ static FabricAttempt alloc_windows_via_fabric(CommHandle h, uint64_t win_size) {
// windowsOut[] is intentionally left zero: no kernel path reads it
// (verified by grep across simpler + pto-isa). The field is kept in
// CommContext only to preserve byte-equivalence with pto-isa's parallel
// HcclDeviceContext declaration; removing it is gated on the F4
// private-ization decision (see .docs/28.l3-comm/ext.01.pr-774-review.md).
// HcclDeviceContext declaration.
// host_ctx was value-initialized at handle construction (CommContext{}),
// and the idempotency guard in comm_alloc_windows prevents a second entry;
// no re-zero needed before populating it here.
Expand Down
3 changes: 1 addition & 2 deletions src/a2a3/platform/sim/host/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@ list(APPEND HOST_RUNTIME_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/../../../../common/platform_comm/comm_sim.cpp"
)
# Shared sim host sources (DeviceRunnerBase + c_api glue + MemoryAllocator),
# linked once per arch's libhost_runtime.so. Mirrors the onboard split from
# PR #928. See .docs/ONBOARD_HOST_COMMON_REFACTOR.md for the migration story.
# linked once per arch's libhost_runtime.so. Mirrors the onboard split.
list(APPEND HOST_RUNTIME_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/../../../../common/platform/sim/host/device_runner_base.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/../../../../common/platform/sim/host/c_api_shared.cpp"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ enum class SdmaOp : uint8_t {
// sync_id selects which event-record slot inside the workspace the engine
// writes into. Concurrent dispatches must use distinct sync_ids; today every
// caller submits one request per kernel invocation so passing 0 is safe.
// Future work (see .docs/25.comm-api-refactor/03.implementation-plan.md §5.2)
// Future work
// will fold sync_id allocation into the adapter.
template <typename DstTensor, typename SrcTensor, typename ScratchTileT>
struct SdmaRequestDescriptor {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ enum class SdmaOp : uint8_t {
// sync_id selects which event-record slot inside the workspace the engine
// writes into. Concurrent dispatches must use distinct sync_ids; today every
// caller submits one request per kernel invocation so passing 0 is safe.
// Future work (see .docs/25.comm-api-refactor/03.implementation-plan.md §5.2)
// Future work
// will fold sync_id allocation into the adapter.
template <typename DstTensor, typename SrcTensor, typename ScratchTileT>
struct SdmaRequestDescriptor {
Expand Down
1 change: 0 additions & 1 deletion src/a5/platform/onboard/host/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ list(APPEND HOST_RUNTIME_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/../../../../common/aicpu_loader/host/load_aicpu_op.cpp"
)
# Add common/platform/onboard/host sources (shared between a2a3 / a5 onboard).
# See .docs/ONBOARD_HOST_COMMON_REFACTOR.md for the migration plan.
list(APPEND HOST_RUNTIME_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/../../../../common/platform/onboard/host/device_runner_helpers.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/../../../../common/platform/onboard/host/device_runner_base.cpp"
Expand Down
6 changes: 2 additions & 4 deletions src/a5/platform/onboard/host/comm_hccl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
*
* Scope: L3 single-host multi-card only. The VMM shareable-handle
* exchange is host-local, so cross-host (L4) deployments need a different
* windows backend -- see .docs/28.l3-comm/ext.01.pr-774-review.md F2 /
* 05.plan.zh.md for the channel-API direction.
* windows backend.
*/

#include "platform_comm/comm.h"
Expand Down Expand Up @@ -613,8 +612,7 @@ static int alloc_windows_via_ipc(CommHandle h, uint64_t win_size) {
// windowsOut[] is intentionally left zero: no kernel path reads it
// (verified by grep across simpler + pto-isa). The field is kept in
// CommContext only to preserve byte-equivalence with pto-isa's parallel
// HcclDeviceContext declaration; removing it is gated on the F4
// private-ization decision (see .docs/28.l3-comm/ext.01.pr-774-review.md).
// HcclDeviceContext declaration.
// host_ctx was value-initialized at handle construction (CommContext{}),
// and the idempotency guard in comm_alloc_windows prevents a second entry;
// no re-zero needed before populating it here.
Expand Down
3 changes: 1 addition & 2 deletions src/a5/platform/sim/host/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ list(APPEND HOST_RUNTIME_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/../../../../common/platform_comm/comm_sim.cpp"
)
# Shared sim host sources (DeviceRunnerBase + c_api glue + MemoryAllocator),
# linked once per arch's libhost_runtime.so. Mirrors the onboard split from
# PR #928. See .docs/ONBOARD_HOST_COMMON_REFACTOR.md for the migration story.
# linked once per arch's libhost_runtime.so. Mirrors the onboard split.
list(APPEND HOST_RUNTIME_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/../../../../common/platform/sim/host/device_runner_base.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/../../../../common/platform/sim/host/c_api_shared.cpp"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ enum class SdmaOp : uint8_t {
// sync_id selects which event-record slot inside the workspace the engine
// writes into. Concurrent dispatches must use distinct sync_ids; today every
// caller submits one request per kernel invocation so passing 0 is safe.
// Future work (see .docs/25.comm-api-refactor/03.implementation-plan.md §5.2)
// Future work
// will fold sync_id allocation into the adapter.
template <typename DstTensor, typename SrcTensor, typename ScratchTileT>
struct SdmaRequestDescriptor {
Expand Down
14 changes: 8 additions & 6 deletions src/common/aicpu_loader/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@ concurrent bootstrap on a shared file corrupted the mmap'd image and faulted
The dispatcher SO itself is **never** persisted to disk and **never** dispatches
at per-task launch time. After bootstrap, the host registers the preinstall
file via `rtsBinaryLoadFromFile` (JSON load, cpuKernelMode=0) and
resolves `simpler_aicpu_exec` once via
`rtsFuncGetByName`; per-task launches go through `rtsLaunchCpuKernel` on the
resolves every symbol the runtime declares it exports via
`rtsFuncGetByName` (a miss is a build/registration error, not an optional
gap); per-task launches go through `rtsLaunchCpuKernel` on the
cached `rtFuncHandle`. The main `aicpu_scheduler` owns the dlopen of the
preinstall file; the dispatcher is out of the picture once bootstrap returns.

The source is runtime-agnostic. It is built per-arch under
`build/lib/<arch>/onboard/<runtime>/libsimpler_aicpu_dispatcher.so` as a
sibling of each runtime's host_runtime.so. A single process binding multiple
runtimes can share one dispatcher SO on disk; the host process-level
`build/lib/<arch>/dispatcher/libsimpler_aicpu_dispatcher.so`, one per arch and
shared across every runtime. A single process binding multiple runtimes
therefore shares one dispatcher SO on disk; the host process-level
fingerprint cache deduplicates bootstrap calls by inner-SO Build-ID.

## Argument ABI boundaries
Expand All @@ -47,7 +48,8 @@ This private bootstrap structure is distinct from the platform runtime
`KernelArgs` payload used by per-task launches. Per-task AICPU launch passes
the front-less `KernelArgs` payload directly to `rtsLaunchCpuKernel` (no CANN
launch front): `runtime_args` is at offset 0, followed by profiling/logging
fields, register tables, and `device_id`. AICore receives only the same
fields and register tables. `device_id` is not in this payload — it reaches
the device once via `InitArgs`. AICore receives only the same
front-less `KernelArgs` via a host-owned device copy.

## Exported entry points
Expand Down
2 changes: 1 addition & 1 deletion src/common/platform/onboard/host/device_runner_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
* - `KernelArgsHelper`: host-side `KernelArgs` wrapper with device-memory
* management for the H2D `Runtime` and `KernelArgs` copies.
*
* Future migrations (see `.docs/ONBOARD_HOST_COMMON_REFACTOR.md`):
* Future migrations:
* - `DeviceRunnerBase` (lifecycle + registration + profiling init).
* - C-API common shims.
*/
Expand Down
6 changes: 3 additions & 3 deletions src/common/platform/sim/host/device_runner_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ struct CallConfig; // task_interface/call_config.h — per-run config threaded
// PLATFORM_MAX_BLOCKDIM (24 on a2a3, 36 on a5). The simulator runs one OS
// thread per AICore, so taking the whole modelled chip would be 72-108 threads
// per case; under xdist that is several hundred threads for no added coverage.
// It is not a ceiling: an explicit block_dim is still honoured up to
// PLATFORM_MAX_BLOCKDIM. Onboard is unaffected — it auto-resolves from the real
// per-stream core limits.
// CallConfig exposes no block_dim knob, so this is what a sim run takes.
// Onboard is unaffected — it auto-resolves from the real per-stream core
// limits.
constexpr int SIM_AUTO_BLOCKDIM = 8;

class SimDeviceRunnerBase {
Expand Down
10 changes: 2 additions & 8 deletions src/common/platform_comm/comm_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,8 @@
* layout is owned end-to-end by simpler.
* - comm_sim.cpp: same shape, filled with malloc'd host pointers.
*
* The earlier "HCCL MESH reinterpret_cast / RING reverse-parse of
* HcclOpResParam" paths have been deleted along with their internal-ABI
* coupling -- see .docs/28.l3-comm/ext.01.pr-774-review.md for context.
*
* Long-term private-ization of this struct (decouple from the pto-isa
* HcclDeviceContext parallel declaration) is tracked as F4 in the same
* doc; this file is intentionally left at the current shared layout
* until that decision lands.
* The layout is shared with pto-isa's parallel HcclDeviceContext
* declaration and must stay byte-equivalent with it.
*/

#pragma once
Expand Down
2 changes: 1 addition & 1 deletion src/common/worker/pto_runtime_c_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ int simpler_register_callable(DeviceContextHandle ctx, int32_t callable_id, cons
* Per-stage run timing is not returned — the platform emits it as `[STRACE]`
* log markers (see docs/dfx/host-trace.md).
*
* `config` carries block_dim (0 = auto), aicpu_thread_num, the five diagnostic
* `config` carries aicpu_thread_num, the five diagnostic
* enables + output_prefix, and the per-task ring sizing overrides
* (`runtime_env.ring_task_window` / `.ring_heap` / `.ring_dep_pool`, each a
* per-scope-depth-ring array of RUNTIME_ENV_RING_COUNT entries; 0 = unset,
Expand Down
4 changes: 2 additions & 2 deletions tests/ut/cpp/a5/test_aicore_completion_mailbox.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ TEST(A5AICoreCompletionMailbox, ConditionAttachesToExistingEntry) {
wait_list.entries[0].normal_done = false;
wait_list.count = 1;

// a5 is counter-only (no SDMA event-record backend), so both conditions
// are COUNTER and bind counter_addr / expected_value.
// Both conditions here are COUNTER and bind counter_addr / expected_value;
// a5 also registers SDMA event-record and URMA event-handle backends.
constexpr uint64_t kAddr1 = 0x1000;
constexpr uint64_t kAddr2 = 0x2000;
ASSERT_TRUE(mb->try_push_condition(token, kAddr1, /*expected=*/3, COMPLETION_ENGINE_SDMA, COMPLETION_TYPE_COUNTER));
Expand Down
Loading