Skip to content
Draft
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
42 changes: 24 additions & 18 deletions docs/dfx/scope-stats.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ high-water mark. When a model runs out of task windows, heap, or
tensormap / dependency-list entries, the failure tells you *which* resource is exhausted
but not *where*; scope stats gives you the where.

It is a diagnostic-only, opt-in feature for the
**tensormap_and_ringbuffer (T&R)** runtime. When disabled (the default)
it costs a single bool load per probe.
It is a diagnostic-only, opt-in feature for both
**tensormap_and_ringbuffer (T&R)** and **host_build_graph (HBG)**. When
disabled (the default) it costs a single bool load per probe.

This guide also covers the background behind the feature, the T&R
resource/ring_depth/scope model, and the data flow behind the HTML report.
Expand All @@ -21,17 +21,17 @@ resulting `scope_stats/scope_stats.jsonl` into an HTML report.

### Step 1 — Run with `--enable-scope-stats`

Pass the flag to any T&R example or scene test:
Pass the flag to a T&R or HBG example or scene test:

```bash
CASE=...
NAME=...
python "tests/st/${CASE}/test_${NAME}.py" -p a2a3 -d 0 --enable-scope-stats
```

The flag is bit 4 of `enable_profiling_flag`; on a T&R run it turns on
per-scope peak tracking. On other runtimes the flag is accepted but
produces no records.
The flag is bit 4 of `enable_profiling_flag` on the device-orchestrated
T&R path. HBG consumes the same flag before graph construction and records
the host orchestrator's scope boundaries directly.

### Step 2 — Locate the output

Expand Down Expand Up @@ -68,6 +68,8 @@ PY
The three arrays are indexed by `ring` (`0..3`) and should match the effective
runtime configuration. Per-sample `ring` values show which scope-depth rings
were actually touched by the run; they are scope records, not task counts.
HBG is whole-graph-resident with one polling ring and no dependency-list pool,
so only index 0 is populated and `dep_pool_max[0]` is zero.

### Step 3 — Visualize with `scope_stats_plot.py`

Expand Down Expand Up @@ -310,15 +312,16 @@ render `used/cap` without a second device→host query.
| chip swimlane | platform only | all runtimes | reads AICore ring buffers |
| dep_gen | platform only | all runtimes | traces `submit_task` |
| args dump | platform only | all runtimes | dumps argument data |
| **scope stats** | **platform API + runtime call sites** | **T&R only** | runtime extracts values, platform tracks peaks |
| **scope stats** | **platform API + runtime call sites** | **all runtimes** | T&R streams from AICPU; HBG records during host graph construction |

### 4.4 Symbol resolution

`kernel.cpp` (platform, shared by all runtimes) always calls
`set_scope_stats_enabled` / `set_platform_scope_stats_base`, so the
collector symbols resolve into every AICPU `.so`. Only the T&R runtime
adds the `begin`/`end`/capacity call sites, so only it produces records;
host_build_graph links the collector but never invokes it.
`kernel.cpp` (platform, shared by all runtimes) calls
`set_scope_stats_enabled` / `set_platform_scope_stats_base` for the
device-backed T&R collector. HBG resolves the same begin/end probes to a
host capture implementation. Its runner turns the device flag off, avoids
allocating an unused device buffer pool, and emits the captured host records
after execution completes.

### 4.5 Data flow

Expand Down Expand Up @@ -347,9 +350,12 @@ A worked example is in
— it runs the `vector_example` orchestration with `--enable-scope-stats`
and asserts the resulting NDJSON.

### 4.6 Future: cross-runtime support
### 4.6 Host-build-graph capture

If host_build_graph adds scope-like concepts, extending scope_stats only
requires adding the same platform call sites in HBG — no platform
changes. The collector is already runtime-agnostic: it accepts plain
values and has no knowledge of T&R types.
HBG runs its complete orchestration during `bind_callable_to_runtime`, before
`prepare_execution` creates any device collectors. Its begin/end probes
therefore write to a thread-local host recorder. The recorder captures the
same `ScopeStatsRecord` values and calls the shared JSONL serializer, so the
version 6 schema and plotting tool are identical across runtime variants.
The capture is reset once per host orchestration and emitted by the same
progress thread, matching HBG's host-direct `dep_gen` lifecycle.
6 changes: 4 additions & 2 deletions src/a2a3/platform/onboard/host/device_runner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,9 @@ int DeviceRunner::prepare_execution(
if (enable_dep_gen_ && !dep_gen_host_graph_active()) {
SIMPLER_SET_DFX_FLAG(enable_profiling_flag, SIMPLER_DFX_FLAG_DEP_GEN);
}
if (enable_scope_stats_) SIMPLER_SET_DFX_FLAG(enable_profiling_flag, SIMPLER_DFX_FLAG_SCOPE_STATS);
if (enable_scope_stats_ && !scope_stats_uses_host_capture()) {
SIMPLER_SET_DFX_FLAG(enable_profiling_flag, SIMPLER_DFX_FLAG_SCOPE_STATS);
}
execution->kernel_args.args.enable_profiling_flag = enable_profiling_flag;

resolve_task_binary_addrs(runtime);
Expand Down Expand Up @@ -403,7 +405,7 @@ int DeviceRunner::prepare_execution(
}
}

if (enable_scope_stats_) {
if (enable_scope_stats_ && !scope_stats_uses_host_capture()) {
rc = init_scope_stats(launch_aicpu_num, device_id_, execution->kernel_args);
if (rc != 0) {
LOG_ERROR("init_scope_stats failed: %d", rc);
Expand Down
23 changes: 16 additions & 7 deletions src/a2a3/platform/sim/host/device_runner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ int DeviceRunner::prepare_execution(
if (enable_dep_gen_ && !dep_gen_host_graph_active()) {
SIMPLER_SET_DFX_FLAG(enable_profiling_flag, SIMPLER_DFX_FLAG_DEP_GEN);
}
if (enable_scope_stats_) {
if (enable_scope_stats_ && !scope_stats_uses_host_capture()) {
SIMPLER_SET_DFX_FLAG(enable_profiling_flag, SIMPLER_DFX_FLAG_SCOPE_STATS);
}
kernel_args_.enable_profiling_flag = enable_profiling_flag;
Expand Down Expand Up @@ -407,7 +407,7 @@ int DeviceRunner::prepare_execution(
}
}

if (enable_scope_stats_) {
if (enable_scope_stats_ && !scope_stats_uses_host_capture()) {
rc = init_scope_stats(launch_aicpu_num);
if (rc != 0) {
LOG_ERROR("init_scope_stats failed: %d", rc);
Expand Down Expand Up @@ -519,7 +519,7 @@ DeviceRunner::launch_execution(std::unique_ptr<PreparedExecution> prepared, Laun
set_pmu_enabled_func_(enable_pmu_);
set_platform_dep_gen_base_func_(kernel_args_.dep_gen_data_base);
set_dep_gen_enabled_func_(enable_dep_gen_ && !dep_gen_host_graph_active());
set_scope_stats_enabled_func_(enable_scope_stats_);
set_scope_stats_enabled_func_(enable_scope_stats_ && !scope_stats_uses_host_capture());
set_platform_scope_stats_base_func_(kernel_args_.scope_stats_data_base);

auto thread_factory = [this](std::function<void()> fn) {
Expand All @@ -529,7 +529,9 @@ DeviceRunner::launch_execution(std::unique_ptr<PreparedExecution> prepared, Laun
if (enable_dump_args_) dump_collector_.start(thread_factory);
if (enable_pmu_) pmu_collector_.start(thread_factory);
if (enable_dep_gen_ && !dep_gen_host_graph_active()) dep_gen_collector_.start(thread_factory);
if (enable_scope_stats_) scope_stats_collector_.start(thread_factory);
if (enable_scope_stats_ && !scope_stats_uses_host_capture()) {
scope_stats_collector_.start(thread_factory);
}

if (kernel_args_.device_wall_data_base != 0) {
*reinterpret_cast<uint64_t *>(kernel_args_.device_wall_data_base) = 0;
Expand Down Expand Up @@ -693,9 +695,16 @@ int DeviceRunner::drain_execution(ActiveExecution &) {
}

if (enable_scope_stats_) {
scope_stats_collector_.stop();
scope_stats_collector_.reconcile_counters();
scope_stats_collector_.write_jsonl(output_prefix_);
if (scope_stats_uses_host_capture()) {
int rc = write_host_scope_stats();
if (rc != 0) {
LOG_ERROR("scope_stats host graph emit failed (%d) — scope_stats.jsonl not produced", rc);
}
} else {
scope_stats_collector_.stop();
scope_stats_collector_.reconcile_counters();
scope_stats_collector_.write_jsonl(output_prefix_);
}
}

print_handshake_results();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,9 @@
* See LICENSE in the root of the software repository for the full text of the License.
* -----------------------------------------------------------------------------------------------------------
*/
/**
* Host-side weak stubs for AICPU-only scope-stats symbols.
*
* host_build_graph runs the orchestrator on the host (host-orch-first), so the
* orchestrator core (pto_orchestrator.cpp / pto_runtime2.cpp) is compiled into
* libhost_runtime.so, which is dlopen'd RTLD_LOCAL and must therefore resolve
* all of its symbols. The scope-stats collector is AICPU-only (defined in
* common/platform/.../aicpu) and is NOT linked into the host library. It records
* on-device diagnostics; the host orchestrator only builds the task graph, so
* no-op definitions are correct here.
*
* Marked weak + hidden so they never leak into the global dynamic symbol table
* (RTLD_LOCAL keeps them library-local anyway) and never shadow the AICPU
* library's strong definitions, mirroring the weak-stub pattern in
* pto_orchestrator.cpp.
*/

#include "aicpu/scope_stats_collector_aicpu.h"

// Minimal host-orchestrator link targets omit the host capture implementation.
__attribute__((weak, visibility("hidden"))) void
scope_stats_begin(int, int32_t, int32_t, uint64_t, uint64_t, int32_t, int32_t, int32_t) {}

Expand Down
6 changes: 6 additions & 0 deletions src/a2a3/runtime/host_build_graph/host/runtime_maker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
#include "../runtime/pto_shared_memory.h"
#include "../runtime/pto_types.h"
#include "../runtime/runtime.h"
#include "../runtime/scope_stats_host_graph.h"
#include "../../../../common/runtime_status/error_log.h"
#include "../../../../common/task_interface/call_config.h"
#include "../../../../common/worker/pto_runtime_c_api.h"
Expand Down Expand Up @@ -498,6 +499,11 @@ int32_t run_host_orchestration(
}
rt->orchestrator.wire_arena_pointers(layout.orch, host_arena, &rt->scheduler);

auto &scope_alloc = rt->orchestrator.ring.task_allocator;
scope_stats_host_graph_begin_capture(
scope_alloc.window_size(), scope_alloc.heap_capacity(), rt->orchestrator.tensor_map.pool_capacity()
);

PTO2SharedMemoryHandle host_sm_handle;
if (!host_sm_handle.init_per_ring(host_sm, sm_size, eff_task_window_sizes, eff_heap_sizes)) {
LOG_ERROR("host-orch: host SM init_per_ring failed");
Expand Down
58 changes: 58 additions & 0 deletions src/a2a3/runtime/host_build_graph/host/scope_stats_host_graph.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* 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.
* -----------------------------------------------------------------------------------------------------------
*/

#include "scope_stats_host_graph.h"

#include "host/scope_stats_host_capture.h"

namespace {
thread_local ScopeStatsHostCapture g_scope_stats_capture;
} // namespace

void scope_stats_host_graph_begin_capture(int32_t task_window_cap, uint64_t heap_cap, int32_t tensormap_cap) {
g_scope_stats_capture.begin_capture(task_window_cap, heap_cap, tensormap_cap);
}

extern "C" bool scope_stats_host_graph_active() { return true; }

extern "C" void scope_stats_host_graph_set_enabled(bool enabled) { g_scope_stats_capture.set_enabled(enabled); }

extern "C" int scope_stats_host_graph_write_jsonl(const char *output_dir) {
return g_scope_stats_capture.write_jsonl(output_dir);
}

extern "C" bool is_scope_stats_enabled() { return g_scope_stats_capture.enabled(); }

extern "C" void scope_stats_set_pending_site(const char *file, int line) {
g_scope_stats_capture.set_pending_site(file, line);
}

extern "C" void scope_stats_begin(
int ring_id, int32_t task_start, int32_t task_end, uint64_t heap_start, uint64_t heap_end, int32_t dep_pool_start,
int32_t dep_pool_end, int32_t tensormap_used
) {
g_scope_stats_capture.begin(
ring_id, task_start, task_end, heap_start, heap_end, dep_pool_start, dep_pool_end, tensormap_used
);
}

extern "C" void scope_stats_end(
int ring_id, int32_t task_start, int32_t task_end, uint64_t heap_start, uint64_t heap_end, int32_t dep_pool_start,
int32_t dep_pool_end, int32_t tensormap_used
) {
g_scope_stats_capture.end(
ring_id, task_start, task_end, heap_start, heap_end, dep_pool_start, dep_pool_end, tensormap_used
);
}

extern "C" void scope_stats_note_heap_wrap(int side) { g_scope_stats_capture.note_heap_wrap(side); }

extern "C" void scope_stats_on_fatal() { g_scope_stats_capture.on_fatal(); }
24 changes: 24 additions & 0 deletions src/a2a3/runtime/host_build_graph/runtime/scope_stats_host_graph.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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.
* -----------------------------------------------------------------------------------------------------------
*/

#pragma once

#include <cstdint>

// Reset host-side capture after the HBG orchestrator has initialized its
// resource pools and before the outer executor scope begins.
void scope_stats_host_graph_begin_capture(int32_t task_window_cap, uint64_t heap_cap, int32_t tensormap_cap);

extern "C" {
bool scope_stats_host_graph_active();
void scope_stats_host_graph_set_enabled(bool enabled);
int scope_stats_host_graph_write_jsonl(const char *output_dir);
}
6 changes: 4 additions & 2 deletions src/a5/platform/onboard/host/device_runner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,9 @@ int DeviceRunner::prepare_execution(
if (enable_chip_swimlane_) SIMPLER_SET_DFX_FLAG(enable_profiling_flag, SIMPLER_DFX_FLAG_CHIP_SWIMLANE);
if (enable_pmu_) SIMPLER_SET_DFX_FLAG(enable_profiling_flag, SIMPLER_DFX_FLAG_PMU);
if (enable_dep_gen_) SIMPLER_SET_DFX_FLAG(enable_profiling_flag, SIMPLER_DFX_FLAG_DEP_GEN);
if (enable_scope_stats_) SIMPLER_SET_DFX_FLAG(enable_profiling_flag, SIMPLER_DFX_FLAG_SCOPE_STATS);
if (enable_scope_stats_ && !scope_stats_uses_host_capture()) {
SIMPLER_SET_DFX_FLAG(enable_profiling_flag, SIMPLER_DFX_FLAG_SCOPE_STATS);
}
execution->kernel_args.args.enable_profiling_flag = enable_profiling_flag;

resolve_task_binary_addrs(runtime);
Expand Down Expand Up @@ -407,7 +409,7 @@ int DeviceRunner::prepare_execution(
}
}

if (enable_scope_stats_) {
if (enable_scope_stats_ && !scope_stats_uses_host_capture()) {
rc = init_scope_stats(active_aicpu_num, device_id_, execution->kernel_args);
if (rc != 0) {
LOG_ERROR("init_scope_stats failed: %d", rc);
Expand Down
23 changes: 16 additions & 7 deletions src/a5/platform/sim/host/device_runner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ int DeviceRunner::prepare_execution(
if (enable_dep_gen_) {
SIMPLER_SET_DFX_FLAG(enable_profiling_flag, SIMPLER_DFX_FLAG_DEP_GEN);
}
if (enable_scope_stats_) {
if (enable_scope_stats_ && !scope_stats_uses_host_capture()) {
SIMPLER_SET_DFX_FLAG(enable_profiling_flag, SIMPLER_DFX_FLAG_SCOPE_STATS);
}

Expand Down Expand Up @@ -375,7 +375,7 @@ int DeviceRunner::prepare_execution(
}
}

if (enable_scope_stats_) {
if (enable_scope_stats_ && !scope_stats_uses_host_capture()) {
rc = init_scope_stats(launch_aicpu_num);
if (rc != 0) {
LOG_ERROR("init_scope_stats failed: %d", rc);
Expand Down Expand Up @@ -464,7 +464,7 @@ DeviceRunner::launch_execution(std::unique_ptr<PreparedExecution> prepared, Laun
set_pmu_enabled_func_(enable_pmu_);
set_platform_dep_gen_base_func_(kernel_args_.dep_gen_data_base);
set_dep_gen_enabled_func_(enable_dep_gen_);
set_scope_stats_enabled_func_(enable_scope_stats_);
set_scope_stats_enabled_func_(enable_scope_stats_ && !scope_stats_uses_host_capture());
set_platform_scope_stats_base_func_(kernel_args_.scope_stats_data_base);

auto thread_factory = [this](std::function<void()> fn) {
Expand All @@ -474,7 +474,9 @@ DeviceRunner::launch_execution(std::unique_ptr<PreparedExecution> prepared, Laun
if (enable_dump_args_) dump_collector_.start(thread_factory);
if (enable_pmu_) pmu_collector_.start(thread_factory);
if (enable_dep_gen_) dep_gen_collector_.start(thread_factory);
if (enable_scope_stats_) scope_stats_collector_.start(thread_factory);
if (enable_scope_stats_ && !scope_stats_uses_host_capture()) {
scope_stats_collector_.start(thread_factory);
}

if (kernel_args_.device_wall_data_base != 0) {
*reinterpret_cast<uint64_t *>(kernel_args_.device_wall_data_base) = 0;
Expand Down Expand Up @@ -630,9 +632,16 @@ int DeviceRunner::drain_execution(ActiveExecution &) {
}

if (enable_scope_stats_) {
scope_stats_collector_.stop();
scope_stats_collector_.reconcile_counters();
scope_stats_collector_.write_jsonl(output_prefix_);
if (scope_stats_uses_host_capture()) {
int rc = write_host_scope_stats();
if (rc != 0) {
LOG_ERROR("scope_stats host graph emit failed (%d) — scope_stats.jsonl not produced", rc);
}
} else {
scope_stats_collector_.stop();
scope_stats_collector_.reconcile_counters();
scope_stats_collector_.write_jsonl(output_prefix_);
}
}

print_handshake_results();
Expand Down
18 changes: 1 addition & 17 deletions src/a5/runtime/host_build_graph/host/host_orch_compat_stubs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,9 @@
* See LICENSE in the root of the software repository for the full text of the License.
* -----------------------------------------------------------------------------------------------------------
*/
/**
* Host-side weak stubs for AICPU-only scope-stats symbols.
*
* host_build_graph runs the orchestrator on the host (host-orch-first), so the
* orchestrator core (pto_orchestrator.cpp / pto_runtime2.cpp) is compiled into
* libhost_runtime.so, which is dlopen'd RTLD_LOCAL and must therefore resolve
* all of its symbols. The scope-stats collector is AICPU-only (defined in
* common/platform/.../aicpu) and is NOT linked into the host library. It records
* on-device diagnostics; the host orchestrator only builds the task graph, so
* no-op definitions are correct here.
*
* Marked weak + hidden so they never leak into the global dynamic symbol table
* (RTLD_LOCAL keeps them library-local anyway) and never shadow the AICPU
* library's strong definitions, mirroring the weak-stub pattern in
* pto_orchestrator.cpp.
*/

#include "aicpu/scope_stats_collector_aicpu.h"

// Minimal host-orchestrator link targets omit the host capture implementation.
__attribute__((weak, visibility("hidden"))) void
scope_stats_begin(int, int32_t, int32_t, uint64_t, uint64_t, int32_t, int32_t, int32_t) {}

Expand Down
Loading
Loading