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
11 changes: 5 additions & 6 deletions .claude/rules/project-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,11 @@ How this repo organizes Python packages, the build system, and example / test di

`simpler` exposes `Worker` and the `task_interface` submodule lazily (PEP 562
`__getattr__`), so `from simpler import Worker` works while `import simpler` does
not require the `_task_interface` extension. Import time is not quite free: it
dlopens `build/lib/libsimpler_log.so` into the global symbol scope (~3 ms when
that library is built, ~25 µs when it is not), which must precede the first
worker fork. When `_task_interface` is available, `_log` passes the logger's
host-span entry point into the extension's nullable, extension-local sink slot. The dlopen and
binding are best-effort and raise nothing — see `python/simpler/_log_preload.py`.
not require the `_task_interface` extension. Importing `simpler` only configures
the Python logger; it performs no native `dlopen`. Once `_task_interface` is
loaded, it owns the process's host-log state. `Worker.init()` seeds that state
before the first fork, and each host runtime module compiles a private logger
implementation and binds it to the state during module initialization.
`simpler.task_interface.ChipTensor` is the GM-address-bearing device descriptor
a `ChipWorker` consumes; `simpler_setup.TensorArg` is the address-free
scene-test arg spec `NamedTuple`. They are separate types in separate
Expand Down
5 changes: 4 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ if(SKBUILD_MODE)
DESTINATION simpler_setup/_assets/src)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/build/lib/
DESTINATION simpler_setup/_assets/build/lib
OPTIONAL)
OPTIONAL
# The standalone logger is not a package artifact; every host
# consumer compiles its own logging implementation.
PATTERN "libsimpler_log.so" EXCLUDE)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if(EXISTS "${CMAKE_SOURCE_DIR}/pto_isa.pin")
install(FILES ${CMAKE_SOURCE_DIR}/pto_isa.pin
DESTINATION simpler_setup/_assets)
Expand Down
13 changes: 13 additions & 0 deletions cmake/host_log_sources.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright (c) PyPTO Contributors.
# This program is free software, you can redistribute it and/or modify it under the terms and conditions of
# CANN Open Software License Agreement Version 2.0 (the "License").
# Please refer to the License for details. You may not use this file except in compliance with the License.
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
# See LICENSE in the root of the software repository for the full text of the License.
# -----------------------------------------------------------------------------------------------------------

set(SIMPLER_HOST_LOG_SOURCES
"${CMAKE_CURRENT_LIST_DIR}/../src/common/log/host_log.cpp"
"${CMAKE_CURRENT_LIST_DIR}/../src/common/log/unified_log_host.cpp"
)
28 changes: 18 additions & 10 deletions docs/chip-level-arch.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,20 @@ runner.finalize();
### Layer 2: C API (`src/common/worker/pto_runtime_c_api.h`)

```c
// libsimpler_log.so (RTLD_GLOBAL, loaded first by the Python wrapper):
simpler_log_init(log_level); // seed HostLogger once

// host_runtime.so (RTLD_LOCAL, loaded after):
// _task_interface owns this process's SimplerHostLogState. Python seeds its
// threshold before entering the C++ worker.

// host_runtime.so is self-contained and loaded RTLD_LOCAL:
SimplerHostLogBindStateFn bind_host_log_state =
(SimplerHostLogBindStateFn)dlsym(host_runtime,
"simpler_host_log_bind_state");
bind_host_log_state(host_log_state);
DeviceContextHandle ctx = create_device_context();
simpler_init(ctx, device_id, // attach + binary takeover
aicpu_binary, aicpu_size,
aicore_binary, aicore_size);
aicore_binary, aicore_size,
dispatcher_binary, dispatcher_size,
prewarm_config);
size_t size = get_runtime_size();
size_t alignment = get_runtime_alignment();
void *runtime = allocate_zeroed_aligned(size, alignment); // stable until finalize
Expand Down Expand Up @@ -191,13 +197,15 @@ Python test_*.py (SceneTestCase)
│
└─→ ChipWorker()
└─→ init(device_id, bins) # Python wrapper
├─→ ctypes.CDLL(libsimpler_log.so, RTLD_GLOBAL) # once per process
├─→ simpler_log_init(log_level) → HostLogger seeded
├─→ ctypes.CDLL(libcpu_sim_context.so, RTLD_GLOBAL) # sim only, once
└─→ _ChipWorker.init(host_path, aicpu_path, aicore_path, device_id) # C++
├─→ _initialize_host_log(log_level) → seed extension-owned state
└─→ _ChipWorker.init(host_path, aicpu_path, aicore_path,
dispatcher_path, device_id, ...,
sim_context_path) # C++
├─→ process-registry sim_context.so + bind state # sim PTO hooks
├─→ dlopen(host.so, RTLD_LOCAL) → resolve C API symbols via dlsym
├─→ simpler_host_log_bind_state(host_log_state)
├─→ create_device_context() → DeviceContextHandle
└─→ simpler_init(ctx, device_id, aicpu*, aicpu_size, aicore*, aicore_size)
└─→ simpler_init(ctx, device_id, binaries...)
├─→ (onboard) dlog_setlevel(HostLogger.cann_level()) # before context open
├─→ DeviceRunner::attach_current_thread(device_id)
│ ├─→ rtSetDevice(device_id) on onboard
Expand Down
33 changes: 20 additions & 13 deletions docs/dfx/host-trace.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ calculations remain entirely on the monotonic clock and are unaffected by
wall-clock corrections. Records tagged `clk=dev` use the separate device-clock
domain described below and do not use this anchor.

`strace_timing.py` applies that mapping to both `--trace-out` and `--swimlane`.
The visible Perfetto axis remains monotonic; each mapped host event exposes the
exact decimal `wall_ts_ns` and a UTC `wall_time` in its arguments, while the JSON
top level retains the source mappings in `clockAnchors`. Nanosecond epoch values
are strings because JSON consumers commonly use IEEE-754 numbers, which cannot
represent current epoch nanoseconds exactly. Old logs without an anchor retain
their existing output, and `clk=dev` records never receive host wall time.

One line per span, emitted on scope exit
(`src/common/log/include/common/strace.h`):

Expand Down Expand Up @@ -112,8 +120,8 @@ including time the caller spends polling or doing other host work; blocking
## L3/L4 host scheduler spans

Every hierarchical worker that drives next-level children emits these spans
through the same process-global `libsimpler_log.so` sink — an L3 with chips and
an L4 pod alike, since the orchestrator and scheduler code they run is the same:
through the logger compiled into `_task_interface` — an L3 with chips and an
L4 pod alike, since the orchestrator and scheduler code they run is the same:

| Span | Host decision point |
| ---- | ------------------- |
Expand All @@ -140,17 +148,16 @@ scheduler thread emits the other four. `role=worker` on `l3.frame_submit`,
`l3.activate` and `l3.complete` names the worker a dispatch targets, not the
thread that ran it.

The spans reach the logger over a fixed POD C ABI, `SimplerHostSpan` in
`common/host_span.h`. `_task_interface` cannot link `libsimpler_log.so` — that
library is reached by `RTLD_GLOBAL` dlopen at runtime — so each extension/DSO
owns its own nullable sink function pointer instead of an undefined link symbol.
`simpler._log_preload` loads the library, and `simpler._log` passes the exported
entry-point address into `_task_interface` before Worker initialization. Every later
`fork()` therefore inherits the bound pointer and logger mapping, so parent and
child markers reach one sink. A process that never loads the logger leaves the
extension-local pointer null, which disables host spans without failing anything. A later
`ChipWorker.init` refreshes the binding after loading the required runtime
logger. `host_runtime.so` links the library directly and needs none of this.
The spans reach the logger over the fixed POD `SimplerHostSpan` ABI in
`common/host_span.h`. `_task_interface` compiles the host logger directly, so
there is no nullable sink and host-span support cannot disappear because a
separate logger DSO was absent. Every other host consumer also compiles a
private logger implementation, then binds it to the
`SimplerHostLogState` owned by `_task_interface`. The hierarchical parent seeds
that state before `fork()`; a chip child re-seeds its inherited copy and passes
the same pointer to every runtime module it loads. The threshold and one-anchor
coordination are therefore shared within each process without relying on
`RTLD_GLOBAL` logger symbols.

## Reading the markers — `strace_timing.py`

Expand Down
20 changes: 12 additions & 8 deletions docs/dynamic-linking.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ Applies to all 4 runtime executors: a2a3 (hbg, tmr), a5 (hbg, tmr).

| SO | Caching | Lifecycle |
| -- | ------- | --------- |
| Sim context | Process registry keyed by path | Process lifetime: loaded once with `RTLD_GLOBAL`, never explicitly closed |
| Host runtime | `ChipWorker::lib_handle_` | Per-init: dlopen in `init()`, dlclose in `finalize()` |
| AICPU | `DeviceRunner::aicpu_so_handle_` | Per-init: loaded lazily by the first `prepare_execution()`, retained across runs, closed by `finalize()` |
| AICore | `DeviceRunner::aicore_so_handle_` | Per-run: reloaded for the run's kernel binary, closed after a successful `drain_execution()` (or by final cleanup) |
Expand Down Expand Up @@ -287,11 +288,14 @@ AICore receives only a device copy of that same `KernelArgs` payload.

```text
ChipWorker.init(device_id, bins) # Python wrapper
ctypes.CDLL(libsimpler_log.so, RTLD_GLOBAL) # once per process
simpler_log_init(log_level) seeds HostLogger before host_runtime
ctypes.CDLL(libcpu_sim_context.so, RTLD_GLOBAL) # sim only, once per process
_ChipWorker.init(host_path, aicpu_path, aicore_path, device_id) # C++
_initialize_host_log(log_level) seeds extension-owned state
_ChipWorker.init(host_path, aicpu_path, aicore_path,
dispatcher_path, device_id, ...,
sim_context_path) # C++
process registry loads cpu_sim_context.so once RTLD_GLOBAL PTO hooks
dlsym(handle, simpler_host_log_bind_state)(state) first load only
dlopen(host_runtime.so, RTLD_LOCAL)
dlsym(handle, simpler_host_log_bind_state)(state)
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,
Expand All @@ -303,7 +307,7 @@ ChipWorker.init(device_id, bins) # Python wrapper
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)
simpler_init(ctx, device_id, aicpu*, aicpu_size, aicore*, aicore_size, ...)
DeviceRunner::attach_current_thread(device_id)
pto_cpu_sim_bind_device(device_id)
pto_cpu_sim_acquire_device(device_id)
Expand Down Expand Up @@ -342,15 +346,15 @@ ChipWorker.finalize()
device_worker_main(device_id)
for each runtime_group:
ChipWorker.init(device_id, bins) # Python wrapper
ctypes.CDLL(libsimpler_log.so, RTLD_GLOBAL) # once per process
simpler_log_init(log_level)
_initialize_host_log(log_level) # seed shared host state
_ChipWorker.init(host_path, aicpu_path, aicore_path,
dispatcher_path, device_id) # C++
dlopen(host_runtime.so, RTLD_LOCAL)
dlsym(handle, simpler_host_log_bind_state)(state)
create_device_context()
simpler_init(ctx, device_id,
aicpu*, aicpu_size, aicore*, aicore_size,
dispatcher*, dispatcher_size)
dispatcher*, dispatcher_size, ...)
dlog_setlevel(HostLogger.cann_level()) sync CANN dlog before context open
DeviceRunner::attach_current_thread(device_id) rtSetDevice()
DeviceRunner::set_executors(aicpu, aicore)
Expand Down
Loading
Loading