diff --git a/.claude/rules/codestyle.md b/.claude/rules/codestyle.md index 614e01b99e..d5d0a5877a 100644 --- a/.claude/rules/codestyle.md +++ b/.claude/rules/codestyle.md @@ -156,7 +156,7 @@ | Tier | What | Policy | | ---- | ---- | ------ | - | **A1 — brand prose** | The brand itself in docs, headings, skill descriptions, issue templates, and code comments (`# PTO Runtime2 Profiling Levels`, "the PTO Runtime consists of…") | **Fix on sight, unconditionally**, and the hook will make you. No compile risk, no contract, and no Tier-C name can hide in a file banner. Say `simpler`, or name the actual component ("the AICPU orchestrator", "the `tensormap_and_ringbuffer` runtime"). Follow the banner style already in the tree: `host_build_graph/shared/orchestrator.cpp` opens `* host_build_graph orchestrator implementation`. | + | **A1 — brand prose** | The brand itself in docs, headings, skill descriptions, issue templates, and code comments (`# PTO Runtime2 Profiling Levels`, "the PTO Runtime consists of…") | **Fix on sight, unconditionally**, and the hook will make you. No compile risk, no contract, and no Tier-C name can hide in a file banner. Say `simpler`, or name the actual component ("the AICPU orchestrator", "the `tensormap_and_ringbuffer` runtime"). Follow the banner style already in the tree: `host_build_graph/host/orchestrator.cpp` opens `* host_build_graph orchestrator implementation`. | | **A2 — comments naming a `pto_*` that does not exist** | e.g. `pto_submit_task` (the entry is `rt_submit_task`), `pto_runtime2_init` (the work is `SchedulerState::init_data_from_layout`) | **Fix on sight**, but this is a [`doc-consistency.md`](doc-consistency.md) §3 defect, not a naming preference: a reader who greps the name finds nothing. Confirm the symbol is absent before renaming it — `git grep -w ` returning only comments is the test. | | **B — internal identifiers** | `PTO2Foo` types, `pto2_*` functions, internal `PTO2_*` macros and enumerators, `pto_*.h` / `pto_runtime2*.cpp` file names | Rename **only when you are already modifying that code**, per rule 9, and finish the identifier you started (below). | | **C — external contracts** | `runtime_env` knobs, `extern "C"` symbols in `runtime_c_api.h`, on-wire / serialized names, and **every live `pto_cpu_sim_*` / `pto_sim_*` hook** | **Exempt until a migration ships.** Renaming these breaks callers in other repos. The sim hooks are the worst case and the reason A2 above demands a grep first: `cpu_sim_context.cpp` exports them for "pto-isa via `dlsym(RTLD_DEFAULT)`" and `device_runner_base.cpp` fetches `pto_sim_register_hooks` by `dlsym`, so a rename produces **no compile error** — just a hook that is never found at run time. Ask the user before touching one; land it only with a compatibility alias or a coordinated cross-repo change. | diff --git a/docs/investigations/2026-08-hbg-consumer-wait-cannot-observe-device.md b/docs/investigations/2026-08-hbg-consumer-wait-cannot-observe-device.md index 337d941a2b..d89e46b2c8 100644 --- a/docs/investigations/2026-08-hbg-consumer-wait-cannot-observe-device.md +++ b/docs/investigations/2026-08-hbg-consumer-wait-cannot-observe-device.md @@ -6,6 +6,10 @@ reading 1 below: the wait is gone and scalar access rejects a tensor with a producer outright. +Paths below name the tree as it stood then. `shared/runtime_core.cpp` and +`shared/orchestrator.cpp` now sit under `host/`, and neither still holds the wait +or the `last_consumer_local_id` seeding described here. + ## The defect `wait_for_tensor_ready()` in `src/common/host_build_graph/shared/runtime_core.cpp` diff --git a/src/a2a3/runtime/host_build_graph/aicpu/aicpu_executor.cpp b/src/a2a3/runtime/host_build_graph/aicpu/aicpu_executor.cpp index c525da4c78..bf15c91bcd 100644 --- a/src/a2a3/runtime/host_build_graph/aicpu/aicpu_executor.cpp +++ b/src/a2a3/runtime/host_build_graph/aicpu/aicpu_executor.cpp @@ -306,8 +306,6 @@ int32_t AicpuExecutor::run(Runtime *runtime) { } if (boot_ok) { - runtime_bind_ops(rt); - sched_ctx_.bind_runtime(rt); // Latch the host-built task count (on_graph_attached sets total_tasks_) diff --git a/src/a2a3/runtime/host_build_graph/host/runtime_maker.cpp b/src/a2a3/runtime/host_build_graph/host/runtime_maker.cpp index 945e46e5b8..d2475e1c92 100644 --- a/src/a2a3/runtime/host_build_graph/host/runtime_maker.cpp +++ b/src/a2a3/runtime/host_build_graph/host/runtime_maker.cpp @@ -935,10 +935,12 @@ int32_t run_host_orchestration( ~static_cast(CHIP_ALIGN_SIZE - 1) ); - // The copied zone carries no host address: the orchestrator is host-only and - // no device code may reach host memory through the image. Its work is done, so - // the pointer goes early rather than at the guard's scope exit. + // The copied zone carries no host address: the orchestrator and the ops table are + // both host-only, and no device code may reach host memory through the image. + // Their work is done, so the pointers go early rather than at the guard's scope + // exit. rt->orchestrator = nullptr; + rt->ops = nullptr; std::memcpy(upload_base, static_cast(host_arena.base()) + layout.off_copied_begin, copied_bytes); const uint64_t compacted = sm_layout::compact_live_image( static_cast(host_sm), task_capacity, bind_usage, heap_rebase, upload_base + copied_bytes diff --git a/src/a2a3/runtime/host_build_graph/orchestration/orchestration_api.h b/src/a2a3/runtime/host_build_graph/orchestration/orchestration_api.h index 6998ebbae5..2b9d31d06b 100644 --- a/src/a2a3/runtime/host_build_graph/orchestration/orchestration_api.h +++ b/src/a2a3/runtime/host_build_graph/orchestration/orchestration_api.h @@ -48,6 +48,7 @@ #include "common/host_phase_kind.h" // HostPhaseKind, for the phase records below #include "host_build_graph/graph_cache.h" // Graph Execution key and result helpers #include "host_build_graph/graph_host_state.h" // GRAPH_MAX_DEFINITIONS +#include "host_build_graph/runtime_ops.h" // RuntimeOps, RuntimeContext forward declaration #include "host_build_graph/runtime_types.h" // SIMPLER_ERROR_* #include "host_build_graph/submit_types.h" // MixedKernels, INVALID_KERNEL_ID, subtask slots #include "types.h" // Arg, TaskOutputTensors, TensorArgType @@ -63,77 +64,9 @@ // build ChipTensors through the same controlled path. // ============================================================================= -// Ops Table and Opaque Runtime +// Opaque Runtime // ============================================================================= -/** - * Forward declaration — the orchestration sees RuntimeContext as a partial - * struct whose first field is the ops pointer. The full definition - * lives in runtime_core.h (used only by runtime .cpp files). - */ -typedef struct RuntimeContext RuntimeContext; - -/** - * Function-pointer table for runtime operations. - * Populated by the runtime; called by orchestration through inline wrappers. - */ -typedef struct RuntimeOps { - TaskOutputTensors (*submit_task)(RuntimeContext *rt, const MixedKernels &mixed_kernels, const CoreTaskArgs &args); - void (*scope_begin)(RuntimeContext *rt); - void (*scope_end)(RuntimeContext *rt); - void (*orchestration_done)(RuntimeContext *rt); - bool (*is_fatal)(RuntimeContext *rt); - void (*report_fatal)(RuntimeContext *rt, int32_t error_code, const char *func, const char *fmt, ...); - - // Logging (populated by runtime, called by orchestration) - void (*log_error)(const char *func, const char *fmt, ...); - void (*log_warn)(const char *func, const char *fmt, ...); - void (*log_timing)(const char *func, const char *fmt, ...); - void (*log_info)(const char *func, const char *fmt, ...); - void (*log_debug)(const char *func, const char *fmt, ...); - - // Cross-layer data access (orchestration reads/writes tensor values via runtime) - // Placed after logging to avoid shifting hot-path field offsets. - uint64_t (*get_tensor_data)( - RuntimeContext *rt, const simpler::hbg::Tensor &tensor, uint32_t ndims, const uint32_t indices[] - ); - void (*set_tensor_data)( - RuntimeContext *rt, const simpler::hbg::Tensor &tensor, uint32_t ndims, const uint32_t indices[], uint64_t value - ); - TaskOutputTensors (*alloc_tensors)(RuntimeContext *rt, const CoreTaskArgs &args); - TaskOutputTensors (*submit_dummy_task)(RuntimeContext *rt, const CoreTaskArgs &args); - - // This-run core geometry latched by the host bind: MIX clusters - // (one AIC each) and standalone AIV cores. - int32_t (*available_cluster_count)(RuntimeContext *rt); - int32_t (*available_aiv_count)(RuntimeContext *rt); - GraphScopeResult (*graph_begin)(RuntimeContext *rt, uint64_t graph_key, const GraphTaskArgs &args); - bool (*graph_prepare)(RuntimeContext *rt, void *recording_handle, const GraphTaskArgs &args); - void (*graph_abort)(RuntimeContext *rt, void *recording_handle); - bool (*graph_end)(RuntimeContext *rt); - void (*graph_commit)(RuntimeContext *rt); - - // Record one orchestration-side phase. The submission segments this carries are - // measured here and invisible to the runtime, which sees only what it is called - // for. Always present to keep ops-table layout stable across SIMPLER_DFX - // settings; nullptr when DFX is off. - // - // This struct is declared twice — here and in the runtime's runtime_core.h — and - // the two must stay in lockstep field for field, since the runtime fills the table - // and this .so calls through it. - void (*record_orch_phase)(uint32_t kind, uint64_t start_ns, uint64_t end_ns, uint64_t detail); - // Queue one Graph body for asynchronous recording, and drain every queued one. - // `job` is a `std::function *` the pool moves out of -- - // whether or not it queues it, since start() takes the callable before it checks - // capacity -- so the caller must not invoke it afterwards. Nothing is owned across - // the boundary either way: the caller's std::function destructs normally, empty or - // not, and rt_graph_submit's fallback re-runs its own copy of the body. The pool is - // runtime-owned (host/graph_recorder_pool.h) and the AICPU build links a refusing - // fallback, which is what makes the device path record synchronously. - bool (*graph_record_start)(RuntimeContext *rt, const GraphTaskArgs &args, void *job); - void (*graph_record_wait)(RuntimeContext *rt); -} RuntimeOps; - /** * Partial RuntimeContext definition for orchestration. * diff --git a/src/a2a3/runtime/host_build_graph/runtime/scheduler/scheduler.h b/src/a2a3/runtime/host_build_graph/runtime/scheduler/scheduler.h index 19c869b314..c2c0546d41 100644 --- a/src/a2a3/runtime/host_build_graph/runtime/scheduler/scheduler.h +++ b/src/a2a3/runtime/host_build_graph/runtime/scheduler/scheduler.h @@ -45,7 +45,7 @@ #include "host_build_graph/runtime_types.h" #include "host_build_graph/shared_memory.h" -#include "aicpu/device_time.h" // get_sys_cnt_aicpu (weak; used by early-dispatch doorbell timing too) +#include "aicpu/device_time.h" // get_sys_cnt_aicpu (used by early-dispatch doorbell timing too) #if SIMPLER_SCHED_PROFILING #define SCHED_CYCLE_START() uint64_t _st0 = get_sys_cnt_aicpu(), _st1 #define SCHED_CYCLE_LAP(acc) \ diff --git a/src/a5/runtime/host_build_graph/aicpu/aicpu_executor.cpp b/src/a5/runtime/host_build_graph/aicpu/aicpu_executor.cpp index c525da4c78..bf15c91bcd 100644 --- a/src/a5/runtime/host_build_graph/aicpu/aicpu_executor.cpp +++ b/src/a5/runtime/host_build_graph/aicpu/aicpu_executor.cpp @@ -306,8 +306,6 @@ int32_t AicpuExecutor::run(Runtime *runtime) { } if (boot_ok) { - runtime_bind_ops(rt); - sched_ctx_.bind_runtime(rt); // Latch the host-built task count (on_graph_attached sets total_tasks_) diff --git a/src/a5/runtime/host_build_graph/host/runtime_maker.cpp b/src/a5/runtime/host_build_graph/host/runtime_maker.cpp index 01dbb17272..cf67fa9104 100644 --- a/src/a5/runtime/host_build_graph/host/runtime_maker.cpp +++ b/src/a5/runtime/host_build_graph/host/runtime_maker.cpp @@ -935,10 +935,12 @@ int32_t run_host_orchestration( ~static_cast(CHIP_ALIGN_SIZE - 1) ); - // The copied zone carries no host address: the orchestrator is host-only and - // no device code may reach host memory through the image. Its work is done, so - // the pointer goes early rather than at the guard's scope exit. + // The copied zone carries no host address: the orchestrator and the ops table are + // both host-only, and no device code may reach host memory through the image. + // Their work is done, so the pointers go early rather than at the guard's scope + // exit. rt->orchestrator = nullptr; + rt->ops = nullptr; std::memcpy(upload_base, static_cast(host_arena.base()) + layout.off_copied_begin, copied_bytes); const uint64_t compacted = sm_layout::compact_live_image( static_cast(host_sm), task_capacity, bind_usage, heap_rebase, upload_base + copied_bytes diff --git a/src/a5/runtime/host_build_graph/orchestration/orchestration_api.h b/src/a5/runtime/host_build_graph/orchestration/orchestration_api.h index 6998ebbae5..2b9d31d06b 100644 --- a/src/a5/runtime/host_build_graph/orchestration/orchestration_api.h +++ b/src/a5/runtime/host_build_graph/orchestration/orchestration_api.h @@ -48,6 +48,7 @@ #include "common/host_phase_kind.h" // HostPhaseKind, for the phase records below #include "host_build_graph/graph_cache.h" // Graph Execution key and result helpers #include "host_build_graph/graph_host_state.h" // GRAPH_MAX_DEFINITIONS +#include "host_build_graph/runtime_ops.h" // RuntimeOps, RuntimeContext forward declaration #include "host_build_graph/runtime_types.h" // SIMPLER_ERROR_* #include "host_build_graph/submit_types.h" // MixedKernels, INVALID_KERNEL_ID, subtask slots #include "types.h" // Arg, TaskOutputTensors, TensorArgType @@ -63,77 +64,9 @@ // build ChipTensors through the same controlled path. // ============================================================================= -// Ops Table and Opaque Runtime +// Opaque Runtime // ============================================================================= -/** - * Forward declaration — the orchestration sees RuntimeContext as a partial - * struct whose first field is the ops pointer. The full definition - * lives in runtime_core.h (used only by runtime .cpp files). - */ -typedef struct RuntimeContext RuntimeContext; - -/** - * Function-pointer table for runtime operations. - * Populated by the runtime; called by orchestration through inline wrappers. - */ -typedef struct RuntimeOps { - TaskOutputTensors (*submit_task)(RuntimeContext *rt, const MixedKernels &mixed_kernels, const CoreTaskArgs &args); - void (*scope_begin)(RuntimeContext *rt); - void (*scope_end)(RuntimeContext *rt); - void (*orchestration_done)(RuntimeContext *rt); - bool (*is_fatal)(RuntimeContext *rt); - void (*report_fatal)(RuntimeContext *rt, int32_t error_code, const char *func, const char *fmt, ...); - - // Logging (populated by runtime, called by orchestration) - void (*log_error)(const char *func, const char *fmt, ...); - void (*log_warn)(const char *func, const char *fmt, ...); - void (*log_timing)(const char *func, const char *fmt, ...); - void (*log_info)(const char *func, const char *fmt, ...); - void (*log_debug)(const char *func, const char *fmt, ...); - - // Cross-layer data access (orchestration reads/writes tensor values via runtime) - // Placed after logging to avoid shifting hot-path field offsets. - uint64_t (*get_tensor_data)( - RuntimeContext *rt, const simpler::hbg::Tensor &tensor, uint32_t ndims, const uint32_t indices[] - ); - void (*set_tensor_data)( - RuntimeContext *rt, const simpler::hbg::Tensor &tensor, uint32_t ndims, const uint32_t indices[], uint64_t value - ); - TaskOutputTensors (*alloc_tensors)(RuntimeContext *rt, const CoreTaskArgs &args); - TaskOutputTensors (*submit_dummy_task)(RuntimeContext *rt, const CoreTaskArgs &args); - - // This-run core geometry latched by the host bind: MIX clusters - // (one AIC each) and standalone AIV cores. - int32_t (*available_cluster_count)(RuntimeContext *rt); - int32_t (*available_aiv_count)(RuntimeContext *rt); - GraphScopeResult (*graph_begin)(RuntimeContext *rt, uint64_t graph_key, const GraphTaskArgs &args); - bool (*graph_prepare)(RuntimeContext *rt, void *recording_handle, const GraphTaskArgs &args); - void (*graph_abort)(RuntimeContext *rt, void *recording_handle); - bool (*graph_end)(RuntimeContext *rt); - void (*graph_commit)(RuntimeContext *rt); - - // Record one orchestration-side phase. The submission segments this carries are - // measured here and invisible to the runtime, which sees only what it is called - // for. Always present to keep ops-table layout stable across SIMPLER_DFX - // settings; nullptr when DFX is off. - // - // This struct is declared twice — here and in the runtime's runtime_core.h — and - // the two must stay in lockstep field for field, since the runtime fills the table - // and this .so calls through it. - void (*record_orch_phase)(uint32_t kind, uint64_t start_ns, uint64_t end_ns, uint64_t detail); - // Queue one Graph body for asynchronous recording, and drain every queued one. - // `job` is a `std::function *` the pool moves out of -- - // whether or not it queues it, since start() takes the callable before it checks - // capacity -- so the caller must not invoke it afterwards. Nothing is owned across - // the boundary either way: the caller's std::function destructs normally, empty or - // not, and rt_graph_submit's fallback re-runs its own copy of the body. The pool is - // runtime-owned (host/graph_recorder_pool.h) and the AICPU build links a refusing - // fallback, which is what makes the device path record synchronously. - bool (*graph_record_start)(RuntimeContext *rt, const GraphTaskArgs &args, void *job); - void (*graph_record_wait)(RuntimeContext *rt); -} RuntimeOps; - /** * Partial RuntimeContext definition for orchestration. * diff --git a/src/a5/runtime/host_build_graph/runtime/scheduler/scheduler.h b/src/a5/runtime/host_build_graph/runtime/scheduler/scheduler.h index 52196e1f5c..49ce21dc78 100644 --- a/src/a5/runtime/host_build_graph/runtime/scheduler/scheduler.h +++ b/src/a5/runtime/host_build_graph/runtime/scheduler/scheduler.h @@ -46,7 +46,7 @@ #include "host_build_graph/shared_memory.h" #include "scheduler_graph.h" -#include "aicpu/device_time.h" // get_sys_cnt_aicpu (weak; used by early-dispatch doorbell timing too) +#include "aicpu/device_time.h" // get_sys_cnt_aicpu (used by early-dispatch doorbell timing too) // scheduler_graph.h states the storage layout as literals, because the AICore .o // that reads it cannot include runtime_types.h. This translation unit sees both, diff --git a/src/common/host_build_graph/dep_gen_host_graph.h b/src/common/host_build_graph/dep_gen_host_graph.h index 3236e52536..e29d2113a6 100644 --- a/src/common/host_build_graph/dep_gen_host_graph.h +++ b/src/common/host_build_graph/dep_gen_host_graph.h @@ -33,8 +33,8 @@ * Control surface, called from the device runner (same host_runtime.so): * set_enabled() / active() / emit() * - * The runtime translation unit links weak no-op fallbacks (orchestrator.cpp) - * so the AICPU build, which has no host graph, resolves without this .cpp. + * Every runtime build links this .cpp. A unit test that takes the orchestrator + * without it resolves to the no-ops in tests/ut/cpp/stubs/hbg_orch_stubs.cpp. * * The graph lives in thread-local state, so capture is lock-free and two * prepared contexts on different threads cannot overwrite one another. Emit diff --git a/src/common/host_build_graph/graph_recorder_pool.h b/src/common/host_build_graph/graph_recorder_pool.h index 75a2e10259..20033ae226 100644 --- a/src/common/host_build_graph/graph_recorder_pool.h +++ b/src/common/host_build_graph/graph_recorder_pool.h @@ -48,8 +48,8 @@ #include #include -// Deliberately no runtime_core.h: it declares the real RuntimeOps while -// orchestration_api.h declares a partial one, so a translation unit that sees both fails +// Deliberately no runtime_core.h: it defines the full RuntimeContext while +// orchestration_api.h defines a partial one, so a translation unit that sees both fails // to compile. Only the ops implementations in graph_recorder_pool.cpp need it. #include "graph_host_state.h" // GRAPH_MAX_DEFINITIONS #include "host_build_graph/types.h" // GraphTaskArgs, GRAPH_MAX_{TENSOR,SCALAR}_ARGS diff --git a/src/common/host_build_graph/host/aicpu_shims.cpp b/src/common/host_build_graph/host/aicpu_shims.cpp new file mode 100644 index 0000000000..83abb28d3e --- /dev/null +++ b/src/common/host_build_graph/host/aicpu_shims.cpp @@ -0,0 +1,57 @@ +/* + * 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. + * ----------------------------------------------------------------------------------------------------------- + */ + +/** + * host_build_graph host-side definitions of two AICPU platform primitives + * + * The host orchestrator reaches both through headers that describe device + * hardware, so each needs a definition that works where that hardware is absent. + * The AICPU build has its own in platform/.../{device_time,platform_regs}.cpp and + * never compiles this file; the two definitions are selected by which target + * compiles which directory, so neither is weak. + * + * Both carry hidden visibility, which is load-bearing rather than tidiness. Each + * stands in for hardware only inside this library, and the AICPU carries a + * same-named definition whose value means something else — a real cycle counter, + * a real register window. Keeping these off the dynamic symbol table is what makes + * it impossible for another module to bind one of those names to a host wall-clock + * or a dummy sink. + */ + +#include + +#include + +#include "aicpu/device_time.h" +#include "aicpu/platform_regs.h" +#include "common/platform_config.h" + +// Monotonic wall-clock in AICPU cycle units, so a cycle-denominated deadline +// evaluated during host orchestration fires at the wall-clock it was sized for. +// A constant 0 would instead make every such backstop a no-op and spin forever. +__attribute__((visibility("hidden"))) uint64_t get_sys_cnt_aicpu() { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + // Scale sec and nsec separately (divisor is the constant 1e9): avoids a + // div-by-zero when PLATFORM_PROF_SYS_CNT_FREQ >= 1 GHz and the truncation + // error a `1e9 / FREQ` divisor would introduce for non-dividing frequencies. + return static_cast(ts.tv_sec) * PLATFORM_PROF_SYS_CNT_FREQ + + static_cast(ts.tv_nsec) * PLATFORM_PROF_SYS_CNT_FREQ / 1000000000ull; +} + +// AICore register window. The orchestrator's route_ready_once path transitively +// ODR-uses the early-dispatch doorbell inline (scheduler.h ring_one_doorbell), but +// host graph-build gates no core, so the doorbell never fires and the address only +// has to be readable and writable. +__attribute__((visibility("hidden"))) volatile uint32_t *get_reg_ptr(uint64_t, RegId) { + static volatile uint32_t sink = 0; + return &sink; +} diff --git a/src/common/host_build_graph/host/graph_recorder_pool.cpp b/src/common/host_build_graph/host/graph_recorder_pool.cpp index 8fd9459304..c2408f220e 100644 --- a/src/common/host_build_graph/host/graph_recorder_pool.cpp +++ b/src/common/host_build_graph/host/graph_recorder_pool.cpp @@ -14,9 +14,9 @@ #include #include "graph_host_state.h" -// The ops implementations below need the real RuntimeOps/RuntimeContext, which is why the +// The ops implementations below need the full RuntimeContext, which is why the // header does not include this: a translation unit cannot see both it and -// orchestration_api.h's partial declaration. +// orchestration_api.h's partial definition. #include "host_build_graph/runtime_core.h" GraphAsyncRecordingState &graph_recorder_pool() { @@ -26,9 +26,8 @@ GraphAsyncRecordingState &graph_recorder_pool() { bool graph_recorder_prewarm() { return graph_recorder_pool().prewarm(); } -// Strong overrides of the weak fallbacks in runtime_core.cpp, which the AICPU build -// links instead: there is no recorder pool on device, and its fallback refuses so the -// caller records synchronously. +// The only definitions of the two graph_record_* ops. The table that names them is +// host-only, so nothing on the device resolves either. // // `job` points to the caller's std::function. The pool moves the closure out of it // whether or not it queues it -- start() takes the callable before it checks capacity -- diff --git a/src/common/host_build_graph/shared/orchestrator.cpp b/src/common/host_build_graph/host/orchestrator.cpp similarity index 96% rename from src/common/host_build_graph/shared/orchestrator.cpp rename to src/common/host_build_graph/host/orchestrator.cpp index a7cc388d80..9564456aba 100644 --- a/src/common/host_build_graph/shared/orchestrator.cpp +++ b/src/common/host_build_graph/host/orchestrator.cpp @@ -48,6 +48,7 @@ #include "common/unified_log.h" #include "host_build_graph/dep_gen_host_graph.h" #include "host_build_graph/dep_compute.h" +#include "host_build_graph/host_phase_trace.h" #include "graph_execution.h" #include "graph_host_state.h" #include "host_build_graph/task_id.h" @@ -61,19 +62,6 @@ #include "aicpu/args_dump_aicpu.h" #endif -// Weak fallbacks: host/dep_gen_host_graph.cpp provides the strong symbols in the -// HOST build, where the orchestrator runs and the graph is captured. The AICPU -// build has no host graph and links these no-op stubs so the runtime translation -// unit is self-contained. Visibility is hidden so the HOST .so doesn't export -// them into the global dynamic symbol table where they'd shadow the strong -// symbols (same pattern as get_sys_cnt_aicpu / chip_swimlane_aicpu_record_orch_phase -// below). -__attribute__((weak, visibility("hidden"))) bool dep_gen_host_graph_enabled() { return false; } -__attribute__((weak, visibility("hidden"))) void dep_gen_host_graph_begin_task( - uint64_t, bool, bool, const int32_t[3], int32_t, int32_t, const TensorRef *, const TensorArgType * -) {} -__attribute__((weak, visibility("hidden"))) void dep_gen_host_graph_end_task() {} - // Raises the two edge kinds compute_task_fanin can discover, for the capture // instantiation. Shared by the ordinary submit path and the outer GRAPH task so // both describe an edge the same way. @@ -87,23 +75,6 @@ struct DepGraphAnnotate { dep_gen_host_graph_add_tensormap_edge(entry.producer_task_id.raw, arg_idx, consumer, entry, overlap); } }; -__attribute__((weak, visibility("hidden"))) void dep_gen_host_graph_add_explicit_edge(uint64_t) {} -__attribute__((weak, visibility("hidden"))) void -dep_gen_host_graph_add_creator_edge(uint64_t, int32_t, const simpler::hbg::Tensor &) {} -__attribute__((weak, visibility("hidden"))) void dep_gen_host_graph_add_tensormap_edge( - uint64_t, int32_t, const simpler::hbg::Tensor &, const ChipTensorMapEntry &, OverlapStatus -) {} - -// AICore register accessor (aicpu/platform_regs.h). The host orchestrator's -// route_ready_once path transitively ODR-uses the early-dispatch doorbell inline -// (scheduler.h ring_one_doorbell), but no core is gated during host -// graph-build, so the doorbell never fires and this weak host fallback only -// satisfies the linker. The AICPU build links the strong definition from -// platform/.../platform_regs.cpp; hidden so the HOST .so does not shadow it. -__attribute__((weak, visibility("hidden"))) volatile uint32_t *get_reg_ptr(uint64_t, RegId) { - static volatile uint32_t sink = 0; - return &sink; -} // ============================================================================= // Orchestrator Profiling (compile-time toggle) @@ -111,32 +82,6 @@ __attribute__((weak, visibility("hidden"))) volatile uint32_t *get_reg_ptr(uint6 #if SIMPLER_ORCH_PROFILING #include "aicpu/device_time.h" #include "aicpu/chip_swimlane_collector_aicpu.h" -// Weak fallback for builds that don't link device_time.cpp (e.g. host). -// The strong symbol from platform/.../device_time.cpp wins in the AICPU build. -// -// IMPORTANT: visibility("hidden") is required to prevent the HOST .so from -// exporting this weak fallback into the global dynamic symbol table via -// RTLD_GLOBAL. Without it, when the AICPU .so is loaded and its PLT entry -// for get_sys_cnt_aicpu is resolved, the dynamic linker finds the HOST .so's -// weak definition first (already in global table) and uses it — returning 0. -// With hidden visibility, the HOST .so does not export this symbol globally, -// so the AICPU .so's PLT resolves to its own strong definition from -// device_time.cpp. -__attribute__((weak, visibility("hidden"))) uint64_t get_sys_cnt_aicpu() { - // Host fallback: monotonic wall-clock in AICPU cycle units so the host-orch - // deadlock/timeout backstops fire at their intended wall-clock (see the - // detailed rationale on the same fallback in runtime_core.cpp). - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - // Scale sec and nsec separately (divisor is the constant 1e9): avoids a - // div-by-zero when PLATFORM_PROF_SYS_CNT_FREQ >= 1 GHz and the truncation - // error a `1e9 / FREQ` divisor would introduce for non-dividing frequencies. - return static_cast(ts.tv_sec) * PLATFORM_PROF_SYS_CNT_FREQ + - static_cast(ts.tv_nsec) * PLATFORM_PROF_SYS_CNT_FREQ / 1000000000ull; -} -// Weak fallback for builds that don't link chip_swimlane_collector_aicpu.cpp. -// The strong symbol from the AICPU build wins when profiling is available. -// Also hidden to prevent HOST .so from polluting the global symbol table. // Accumulated cycles per sub-step (only needed for ORCH_PROFILING export) static uint64_t g_orch_alloc_cycle = 0; // unified task+heap alloc static uint64_t g_orch_args_cycle = 0; // param copy @@ -163,18 +108,6 @@ uint64_t g_orch_args_atomic_count = 0; #elif SIMPLER_DFX #include "aicpu/device_time.h" #include "aicpu/chip_swimlane_collector_aicpu.h" -__attribute__((weak, visibility("hidden"))) uint64_t get_sys_cnt_aicpu() { - // Host fallback: monotonic wall-clock in AICPU cycle units so the host-orch - // deadlock/timeout backstops fire at their intended wall-clock (see the - // detailed rationale on the same fallback in runtime_core.cpp). - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - // Scale sec and nsec separately (divisor is the constant 1e9): avoids a - // div-by-zero when PLATFORM_PROF_SYS_CNT_FREQ >= 1 GHz and the truncation - // error a `1e9 / FREQ` divisor would introduce for non-dividing frequencies. - return static_cast(ts.tv_sec) * PLATFORM_PROF_SYS_CNT_FREQ + - static_cast(ts.tv_nsec) * PLATFORM_PROF_SYS_CNT_FREQ / 1000000000ull; -} // submit_idx tags a record with its position in the orchestration's submit order. static uint32_t g_orch_submit_idx = 0; // The per-sub-step accumulators exist only in an ORCH_PROFILING build, so at this @@ -186,19 +119,6 @@ static uint32_t g_orch_submit_idx = 0; #define CYCLE_COUNT_LAP(acc) #endif -// Host phase record sink. The host build links a strong definition that folds the -// record into its kind's counters and appends it to the platform's record pool; -// every other build keeps this no-op. Kind values are HostPhaseKind, passed as a -// plain integer because this file is also compiled for the AICPU, where the -// platform's host headers are absent. -__attribute__((weak, visibility("hidden"))) void host_phase_record(uint64_t, uint64_t, uint32_t, uint64_t, uint32_t) {} - -// Host monotonic clock shared with the `[STRACE]` span tree, so a record nests -// under chip.run.bind.host_orch without any clock conversion. The host build -// links the strong definition in host_phase_trace.cpp; this fallback keeps -// non-host builds linking, where the recorder above is a no-op anyway. -__attribute__((weak, visibility("hidden"))) uint64_t host_phase_now_ns() { return 0; } - #if SIMPLER_DFX // Only the host orchestrator reaches these sites, so this file names only the Orch* // half of HostPhaseKind; the bind kinds never appear here. @@ -287,6 +207,50 @@ void OrchestratorState::report_fatal(int32_t error_code, const char *func, const va_end(args); } +bool OrchestratorState::init( + void *sm_base, void *gm_heap, uint64_t heap_size, uint64_t max_tasks, SchedulerState *scheduler_arg +) { + // Reset in place rather than by move-assignment: fatal_code is a std::atomic, + // which is neither copy- nor move-assignable, and a re-init has to clear every + // field the previous pass left behind (the pool cursors below rely on it). + this->~OrchestratorState(); + auto *orch = new (static_cast(this)) OrchestratorState{}; + + always_assert(max_tasks > 0); + + orch->sm_header = reinterpret_cast(sm_base); + orch->scheduler = scheduler_arg; + + orch->task_allocator.init(static_cast(max_tasks), gm_heap, heap_size, &orch->fatal_code); + + // The mirror's argument pools. Offset arithmetic on the same base as sm_header, + // so it holds for whichever SM this orchestrator was pointed at. The cursors + // reset with the rest of the state above. + auto *sm_bytes = static_cast(sm_base); + const auto pools = sm_layout::segment_offsets(sm_layout::mirror_extents(max_tasks)); + orch->fanin_pool = reinterpret_cast(sm_bytes + pools.fanin_pool); + orch->tensor_pool = reinterpret_cast(sm_bytes + pools.tensor_pool); + orch->scalar_pool = reinterpret_cast(sm_bytes + pools.scalar_pool); + + // Polling: no fanin-spill pool — producer ids are inline on the payload. + const auto slots = static_cast(max_tasks); + orch->fanin_seen_epoch.reset(new (std::nothrow) uint32_t[slots]); + if (orch->fanin_seen_epoch == nullptr) { + LOG_ERROR("Orchestrator scratch allocation failed (max_tasks=%" PRIu64 ")", max_tasks); + return false; + } + memset(orch->fanin_seen_epoch.get(), 0, slots * sizeof(uint32_t)); + + if (!orch->tensor_map.init_default(static_cast(max_tasks))) { + return false; + } + + orch->scope_stack_top = -1; + orch->manual_begin_depth = CHIP_MAX_SCOPE_DEPTH; + + return true; +} + enum class GraphRecordedTensorSource : uint8_t { BOUNDARY_EXACT, BOUNDARY_VIEW, diff --git a/src/common/host_build_graph/shared/runtime_core.cpp b/src/common/host_build_graph/host/runtime_core.cpp similarity index 77% rename from src/common/host_build_graph/shared/runtime_core.cpp rename to src/common/host_build_graph/host/runtime_core.cpp index a7dfe3630b..dc61bdd185 100644 --- a/src/common/host_build_graph/shared/runtime_core.cpp +++ b/src/common/host_build_graph/host/runtime_core.cpp @@ -10,11 +10,11 @@ */ /** - * host_build_graph runtime implementation + * host_build_graph orchestration ops table * - * Implements the unified runtime API that combines orchestrator and scheduler. - * - * Based on: docs/RUNTIME_LOGIC.md + * The runtime entries the orchestration .so reaches through RuntimeContext::ops, + * and the table itself. host_build_graph orchestrates on the host, so the AICPU + * target compiles none of this and no device code reads the ops field. */ #include "host_build_graph/host_phase_trace.h" @@ -23,61 +23,11 @@ #include #include #include -#include -#include -#include "aicpu/device_time.h" -#include "common/platform_config.h" #include "common/unified_log.h" #include "host_build_graph/host_tensor_access.h" #include "host_build_graph/task_id.h" -// simpler::hbg::Tensor-byte access for a caller that can load a device address directly. -// The AICPU build compiles this translation unit and links these; the host -// build overrides them with host/host_tensor_access.cpp, where a device -// address is not loadable in general. Visibility is hidden so the host .so -// does not export them into the global dynamic symbol table (same pattern as -// get_sys_cnt_aicpu above and the dep_gen stubs in orchestrator.cpp). -__attribute__((weak, visibility("hidden"))) bool -host_tensor_read(HostTensorAccessor *, uint64_t dev_addr, void *dst, uint64_t bytes) { - memcpy(dst, reinterpret_cast(dev_addr), bytes); - return true; -} - -__attribute__((weak, visibility("hidden"))) bool -host_tensor_write(HostTensorAccessor *, uint64_t dev_addr, const void *src, uint64_t bytes) { - memcpy(reinterpret_cast(dev_addr), src, bytes); - return true; -} - -// No recorder pool exists on device, so refuse and let the caller record the body -// inline. Refusing leaves the caller's job untouched, which is the contract the ops -// table documents. The host build overrides both with host/graph_recorder_pool.cpp; -// visibility is hidden for the same reason as host_tensor_read above. -__attribute__((weak, visibility("hidden"))) bool -graph_record_start_impl(RuntimeContext *, const GraphTaskArgs &, void *) { - return false; -} - -__attribute__((weak, visibility("hidden"))) void graph_record_wait_impl(RuntimeContext *) {} - -// Host fallback for the host-orchestration path. The AICPU cycle counter is a -// device register unavailable on the host, so return a monotonic wall-clock -// scaled to that counter's cycle units (PLATFORM_PROF_SYS_CNT_FREQ). Any -// cycle-denominated deadline evaluated during host orchestration then fires at -// its intended wall-clock; a constant 0 would make it a no-op and spin forever. -// The AICPU build links the strong device counter from device_time.cpp; hidden -// visibility keeps this off the global dynamic symbol table. -__attribute__((weak, visibility("hidden"))) uint64_t get_sys_cnt_aicpu() { - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - // Scale sec and nsec separately (divisor is the constant 1e9): avoids a - // div-by-zero when PLATFORM_PROF_SYS_CNT_FREQ >= 1 GHz and the truncation - // error a `1e9 / FREQ` divisor would introduce for non-dividing frequencies. - return static_cast(ts.tv_sec) * PLATFORM_PROF_SYS_CNT_FREQ + - static_cast(ts.tv_nsec) * PLATFORM_PROF_SYS_CNT_FREQ / 1000000000ull; -} - // ============================================================================= // Orchestration Ops Table (function-pointer dispatch for orchestration .so) // ============================================================================= @@ -288,13 +238,14 @@ static const RuntimeOps s_runtime_ops = { }; // ============================================================================= -// Runtime Lifecycle (AICPU-only fixup) +// Runtime Lifecycle // ============================================================================= // // Layout / init_data / wire / destroy live in // host_build_graph/shared/runtime_init.cpp so the host build can pre-populate the -// prebuilt arena image. The piece below — wiring the ops table — depends on the -// device-side s_runtime_ops global, so it remains in the AICPU build. +// prebuilt arena image. Binding the ops table is host-only: the bind installs it +// for the orchestration .so and clears the field again before the RuntimeContext +// travels, so the device never receives a host address here. void runtime_bind_ops(RuntimeContext *rt) { rt->ops = &s_runtime_ops; } diff --git a/src/common/host_build_graph/shared/tensormap.cpp b/src/common/host_build_graph/host/tensormap.cpp similarity index 100% rename from src/common/host_build_graph/shared/tensormap.cpp rename to src/common/host_build_graph/host/tensormap.cpp diff --git a/src/common/host_build_graph/host_phase_trace.h b/src/common/host_build_graph/host_phase_trace.h index a5098f36a9..1953f143d5 100644 --- a/src/common/host_build_graph/host_phase_trace.h +++ b/src/common/host_build_graph/host_phase_trace.h @@ -90,9 +90,8 @@ uint64_t host_phase_now_ns(); /** * Append one record and fold it into its kind's counters. * - * `kind` is a HostPhaseKind; it is passed as a plain integer because the - * orchestrator core declares a weak fallback for this function and is also - * compiled for the AICPU, where the platform's host headers are absent. + * `kind` is a HostPhaseKind, passed as a plain integer so this header needs no + * dependency beyond . * * A record is an **interval**: this kind's operation, from when it started to * when it ended. `payload` says which one — a task id, a Graph key, the diff --git a/src/common/host_build_graph/host_tensor_access.h b/src/common/host_build_graph/host_tensor_access.h index be19507832..cdc51485ad 100644 --- a/src/common/host_build_graph/host_tensor_access.h +++ b/src/common/host_build_graph/host_tensor_access.h @@ -47,11 +47,9 @@ * `HostTensorAccessor` bounds the window and releases its mappings on every * exit path. * - * The read/write pair carries weak fallbacks in the runtime translation unit - * (`shared/runtime_core.cpp`) that dereference `dev_addr` directly, - * so the AICPU build — which compiles this path but never runs an - * orchestrator — resolves without this .cpp. libhost_runtime.so links the - * strong definitions from `host/host_tensor_access.cpp`. + * `host/host_tensor_access.cpp` holds the only definitions of the read/write + * pair, and libhost_runtime.so links them. Nothing in the AICPU build reaches + * either: every caller is host-side. */ #pragma once @@ -80,10 +78,8 @@ struct HostApi; // common/host_api.h — fwd-declared so this header stays out * A null `api` makes every `add` fail, so a registered region always implies a * usable `api`; `write`'s mirror push-back relies on that and does not re-check. * - * The state lives behind `Impl` because this header is also compiled by the - * AICPU build (through `shared/runtime_core.cpp`, which resolves the - * weak read/write fallbacks below), and that build has no ``. Keep the - * standard containers in `host/host_tensor_access.cpp`. + * The state lives behind `Impl` so this header pulls in no standard containers; + * they stay in `host/host_tensor_access.cpp`. */ class HostTensorAccessor { public: diff --git a/src/common/host_build_graph/runtime_core.h b/src/common/host_build_graph/runtime_core.h index 300388dfb6..d7ec2fc54d 100644 --- a/src/common/host_build_graph/runtime_core.h +++ b/src/common/host_build_graph/runtime_core.h @@ -39,6 +39,7 @@ #include "utils/device_arena.h" #include "host_build_graph/runtime_types.h" #include "graph_cache.h" +#include "host_build_graph/runtime_ops.h" #include "host_build_graph/submit_types.h" #include "host_build_graph/shared_memory.h" #include "host_build_graph/task_allocator.h" @@ -60,68 +61,8 @@ enum RuntimeMode { MODE_GRAPH_ONLY = 2 // Build graph only, no execution }; -/** - * Function-pointer ops table for runtime operations. - * - * The orchestration .so calls runtime functions through this table - * (via orchestration_api.h inline wrappers), so it has zero link - * dependencies on runtime .cpp files. - */ -typedef struct RuntimeContext RuntimeContext; // forward declare for ops signatures class HostTensorAccessor; -struct RuntimeOps { - TaskOutputTensors (*submit_task)(RuntimeContext *rt, const MixedKernels &mixed_kernels, const CoreTaskArgs &args); - void (*scope_begin)(RuntimeContext *rt); - void (*scope_end)(RuntimeContext *rt); - void (*orchestration_done)(RuntimeContext *rt); - bool (*is_fatal)(RuntimeContext *rt); - void (*report_fatal)(RuntimeContext *rt, int32_t error_code, const char *func, const char *fmt, ...); - - // Logging (populated by runtime, called by orchestration) - void (*log_error)(const char *func, const char *fmt, ...); - void (*log_warn)(const char *func, const char *fmt, ...); - void (*log_timing)(const char *func, const char *fmt, ...); - void (*log_info)(const char *func, const char *fmt, ...); - void (*log_debug)(const char *func, const char *fmt, ...); - - // Cross-layer data access (orchestration reads/writes tensor values via runtime) - // Placed after logging to avoid shifting hot-path field offsets. - uint64_t (*get_tensor_data)( - RuntimeContext *rt, const simpler::hbg::Tensor &tensor, uint32_t ndims, const uint32_t indices[] - ); - void (*set_tensor_data)( - RuntimeContext *rt, const simpler::hbg::Tensor &tensor, uint32_t ndims, const uint32_t indices[], uint64_t value - ); - TaskOutputTensors (*alloc_tensors)(RuntimeContext *rt, const CoreTaskArgs &args); - TaskOutputTensors (*submit_dummy_task)(RuntimeContext *rt, const CoreTaskArgs &args); - - // This-run core geometry latched by the host bind: MIX clusters - // (one AIC each) and standalone AIV cores. - int32_t (*available_cluster_count)(RuntimeContext *rt); - int32_t (*available_aiv_count)(RuntimeContext *rt); - GraphScopeResult (*graph_begin)(RuntimeContext *rt, uint64_t graph_key, const GraphTaskArgs &args); - bool (*graph_prepare)(RuntimeContext *rt, void *recording_handle, const GraphTaskArgs &args); - void (*graph_abort)(RuntimeContext *rt, void *recording_handle); - bool (*graph_end)(RuntimeContext *rt); - void (*graph_commit)(RuntimeContext *rt); - // Record one orchestration-side phase on the calling thread. The submission - // segments this carries are measured in the orchestration .so, which reaches the - // runtime only through this table. Always present in the struct so the layout does - // not move with SIMPLER_DFX; nullptr when off. - void (*record_orch_phase)(uint32_t kind, uint64_t start_ns, uint64_t end_ns, uint64_t detail); - // Queue one Graph body for asynchronous recording, and drain every queued one. - // `job` is a `std::function *` the pool moves out of -- - // whether or not it queues it, since start() takes the callable before it checks - // capacity -- so the caller must not invoke it afterwards. Nothing is owned across - // the boundary either way: the caller's std::function destructs normally, empty or - // not, and rt_graph_submit's fallback re-runs its own copy of the body. The pool is - // runtime-owned (host/graph_recorder_pool.h) and the AICPU build links a refusing - // fallback, which is what makes the device path record synchronously. - bool (*graph_record_start)(RuntimeContext *rt, const GraphTaskArgs &args, void *job); - void (*graph_record_wait)(RuntimeContext *rt); -}; - /** * Layout descriptor for the prebuilt runtime arena. Holds all sub-region * offsets (scheduler / sm_handle wrapper / runtime header / AICore mailbox) @@ -179,7 +120,9 @@ struct RuntimeArenaLayout { * In simulated mode, runs in single process with shared address space. */ struct RuntimeContext { - // Ops table (first field — used by orchestration .so via function pointers) + // Ops table (first field — used by orchestration .so via function pointers). + // Host-only, like the orchestrator below: the bind clears it before the copied + // zone is uploaded, so no device code may call through it. const RuntimeOps *ops; ScopeMode pending_scope_mode; @@ -278,16 +221,15 @@ RuntimeContext *runtime_init_data_from_layout( void runtime_wire_arena_pointers(DeviceArena &arena, const RuntimeArenaLayout &layout, RuntimeContext *rt); /** - * AICPU-only Phase 4 — install the ops table, the one field the host could not - * know at prebuilt-image build time (s_runtime_ops is a device-side file-local - * global, so the host cannot resolve its device address). Call once per boot - * after runtime_wire_arena_pointers. + * Install the ops table on the context the orchestration .so calls through. + * Host-only: the bind installs it before running orchestration and clears the + * field again before the context travels to the device. */ void runtime_bind_ops(RuntimeContext *rt); -// Backing the two graph_record_* ops. Weak fallbacks live in runtime_core.cpp so the -// AICPU build links a refusing start and a no-op wait; the host build overrides both in -// host/graph_recorder_pool.cpp, where the pool is. +// Backing the two graph_record_* ops, defined in host/graph_recorder_pool.cpp where +// the pool lives. The table naming them is host-only, so there is no device-side +// fallback. bool graph_record_start_impl(RuntimeContext *rt, const GraphTaskArgs &args, void *job); void graph_record_wait_impl(RuntimeContext *rt); diff --git a/src/common/host_build_graph/runtime_ops.h b/src/common/host_build_graph/runtime_ops.h new file mode 100644 index 0000000000..67255ada98 --- /dev/null +++ b/src/common/host_build_graph/runtime_ops.h @@ -0,0 +1,98 @@ +/* + * 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. + * ----------------------------------------------------------------------------------------------------------- + */ + +/** + * The runtime entry points an orchestration .so calls through. + * + * ChipWorker dlopens each runtime RTLD_LOCAL so two runtimes' identically-named + * exports (simpler_init, simpler_run, ...) cannot collide when a process switches + * between them, and so dlclose can actually unload one. A runtime's symbols + * therefore never reach the global symbol table, and the orchestration .so — itself + * dlopened RTLD_NOW|RTLD_LOCAL — has no way to bind them by name. This table is how + * it reaches the runtime instead: the runtime fills it, the .so calls through it. + * + * The runtime and the orchestration .so include this same definition, which is what + * makes the field order they agree on a single fact rather than two. Nothing would + * catch a disagreement: a call dispatches through whatever pointer sits at the + * offset the caller believes in. + * + * The header names only the argument types the signatures need, and no runtime + * implementation types, for the same reason the orchestration .so links no runtime + * .cpp. + */ + +#pragma once + +#include + +#include "host_build_graph/graph_cache.h" // GraphScopeResult +#include "host_build_graph/submit_types.h" // MixedKernels +#include "host_build_graph/tensor.h" // simpler::hbg::Tensor +#include "host_build_graph/types.h" // TaskOutputTensors, CoreTaskArgs, GraphTaskArgs + +// The orchestration .so sees RuntimeContext as an incomplete type plus the partial +// definition in orchestration_api.h; the runtime sees the full struct in +// runtime_core.h. Both agree on the first two fields, which is what makes the +// partial definition well-defined. +typedef struct RuntimeContext RuntimeContext; + +struct RuntimeOps { + TaskOutputTensors (*submit_task)(RuntimeContext *rt, const MixedKernels &mixed_kernels, const CoreTaskArgs &args); + void (*scope_begin)(RuntimeContext *rt); + void (*scope_end)(RuntimeContext *rt); + void (*orchestration_done)(RuntimeContext *rt); + bool (*is_fatal)(RuntimeContext *rt); + void (*report_fatal)(RuntimeContext *rt, int32_t error_code, const char *func, const char *fmt, ...); + + // Logging (populated by runtime, called by orchestration) + void (*log_error)(const char *func, const char *fmt, ...); + void (*log_warn)(const char *func, const char *fmt, ...); + void (*log_timing)(const char *func, const char *fmt, ...); + void (*log_info)(const char *func, const char *fmt, ...); + void (*log_debug)(const char *func, const char *fmt, ...); + + // Cross-layer data access (orchestration reads/writes tensor values via runtime) + // Placed after logging to avoid shifting hot-path field offsets. + uint64_t (*get_tensor_data)( + RuntimeContext *rt, const simpler::hbg::Tensor &tensor, uint32_t ndims, const uint32_t indices[] + ); + void (*set_tensor_data)( + RuntimeContext *rt, const simpler::hbg::Tensor &tensor, uint32_t ndims, const uint32_t indices[], uint64_t value + ); + TaskOutputTensors (*alloc_tensors)(RuntimeContext *rt, const CoreTaskArgs &args); + TaskOutputTensors (*submit_dummy_task)(RuntimeContext *rt, const CoreTaskArgs &args); + + // This-run core geometry latched by the host bind: MIX clusters + // (one AIC each) and standalone AIV cores. + int32_t (*available_cluster_count)(RuntimeContext *rt); + int32_t (*available_aiv_count)(RuntimeContext *rt); + GraphScopeResult (*graph_begin)(RuntimeContext *rt, uint64_t graph_key, const GraphTaskArgs &args); + bool (*graph_prepare)(RuntimeContext *rt, void *recording_handle, const GraphTaskArgs &args); + void (*graph_abort)(RuntimeContext *rt, void *recording_handle); + bool (*graph_end)(RuntimeContext *rt); + void (*graph_commit)(RuntimeContext *rt); + + // Record one orchestration-side phase on the calling thread. The submission + // segments this carries are measured in the orchestration .so and are invisible + // to the runtime, which sees only what it is called for. Always present so the + // layout does not move with SIMPLER_DFX; nullptr when DFX is off. + void (*record_orch_phase)(uint32_t kind, uint64_t start_ns, uint64_t end_ns, uint64_t detail); + // Queue one Graph body for asynchronous recording, and drain every queued one. + // `job` is a `std::function *` the pool moves out of -- + // whether or not it queues it, since start() takes the callable before it checks + // capacity -- so the caller must not invoke it afterwards. Nothing is owned across + // the boundary either way: the caller's std::function destructs normally, empty or + // not, and rt_graph_submit's fallback re-runs its own copy of the body. The pool is + // runtime-owned (host/graph_recorder_pool.h); a null or refusing start makes the + // caller record the body inline instead. + bool (*graph_record_start)(RuntimeContext *rt, const GraphTaskArgs &args, void *job); + void (*graph_record_wait)(RuntimeContext *rt); +}; diff --git a/src/common/host_build_graph/shared/runtime_init.cpp b/src/common/host_build_graph/shared/runtime_init.cpp index 3db42a8707..87766b6385 100644 --- a/src/common/host_build_graph/shared/runtime_init.cpp +++ b/src/common/host_build_graph/shared/runtime_init.cpp @@ -14,20 +14,15 @@ * Lives under host_build_graph/shared/ so it is included in both the * host_runtime.so build (host pre-populates the prebuilt arena image) and the * aicpu_runtime build (AICPU runs wire_arena_pointers + destroy after attach). - * The device-only parts of runtime_core.cpp / orchestrator.cpp / scheduler.cpp - * (ops table, scope/submit/dispatch business logic, profiling) stay in their - * original files and the aicpu build only. + * The scheduler's own dispatch logic stays in scheduler.cpp and the aicpu build; + * the ops table and the orchestrator are host-only and live under host/. */ -#include #include #include -#include "host_build_graph/orchestrator.h" #include "host_build_graph/runtime_core.h" -#include "host_build_graph/task_allocator.h" #include "host_build_graph/shared_memory.h" -#include "host_build_graph/tensormap.h" #include "scheduler/scheduler.h" // ============================================================================= @@ -199,54 +194,6 @@ void SchedulerState::destroy() { ready_queue_destroy(&sched->early_sync_start_queue); } -// ============================================================================= -// Orchestrator -// ============================================================================= - -bool OrchestratorState::init( - void *sm_base, void *gm_heap, uint64_t heap_size, uint64_t max_tasks, SchedulerState *scheduler_arg -) { - // Reset in place rather than by move-assignment: fatal_code is a std::atomic, - // which is neither copy- nor move-assignable, and a re-init has to clear every - // field the previous pass left behind (the pool cursors below rely on it). - this->~OrchestratorState(); - auto *orch = new (static_cast(this)) OrchestratorState{}; - - always_assert(max_tasks > 0); - - orch->sm_header = reinterpret_cast(sm_base); - orch->scheduler = scheduler_arg; - - orch->task_allocator.init(static_cast(max_tasks), gm_heap, heap_size, &orch->fatal_code); - - // The mirror's argument pools. Offset arithmetic on the same base as sm_header, - // so it holds for whichever SM this orchestrator was pointed at. The cursors - // reset with the rest of the state above. - auto *sm_bytes = static_cast(sm_base); - const auto pools = sm_layout::segment_offsets(sm_layout::mirror_extents(max_tasks)); - orch->fanin_pool = reinterpret_cast(sm_bytes + pools.fanin_pool); - orch->tensor_pool = reinterpret_cast(sm_bytes + pools.tensor_pool); - orch->scalar_pool = reinterpret_cast(sm_bytes + pools.scalar_pool); - - // Polling: no fanin-spill pool — producer ids are inline on the payload. - const auto slots = static_cast(max_tasks); - orch->fanin_seen_epoch.reset(new (std::nothrow) uint32_t[slots]); - if (orch->fanin_seen_epoch == nullptr) { - LOG_ERROR("Orchestrator scratch allocation failed (max_tasks=%" PRIu64 ")", max_tasks); - return false; - } - memset(orch->fanin_seen_epoch.get(), 0, slots * sizeof(uint32_t)); - - if (!orch->tensor_map.init_default(static_cast(max_tasks))) { - return false; - } - - orch->scope_stack_top = -1; - orch->manual_begin_depth = CHIP_MAX_SCOPE_DEPTH; - - return true; -} - // ============================================================================= // Top-level runtime arena // ============================================================================= diff --git a/src/common/platform/include/common/host_phase_kind.h b/src/common/platform/include/common/host_phase_kind.h index 1049fcd500..126cebc13c 100644 --- a/src/common/platform/include/common/host_phase_kind.h +++ b/src/common/platform/include/common/host_phase_kind.h @@ -17,13 +17,14 @@ * translation units need only the kinds: * * - the host trace and record store, which format and file the records; - * - host_build_graph/shared/orchestrator.cpp, compiled for the AICPU as well, where - * the platform's host headers are absent; - * - orchestration_api.h, compiled into the orchestration .so, likewise. + * - host_build_graph/host/orchestrator.cpp, which raises the kinds but needs + * none of the record machinery; + * - orchestration_api.h, compiled into the orchestration .so, where the + * platform's host headers are absent. * - * So keep this header free of anything the AICPU or the orchestration .so cannot - * take — no STL containers, no platform types. HostPhaseRecord stores a kind as a - * plain uint32_t, so it does not depend on this header either way. + * So keep this header free of anything the orchestration .so cannot take — no STL + * containers, no platform types. HostPhaseRecord stores a kind as a plain + * uint32_t, so it does not depend on this header either way. */ #pragma once @@ -51,7 +52,7 @@ enum class HostPhaseKind : uint32_t { BindSmH2d, BindArenaH2d, BindHostViewClose, - // Recorded by the host orchestrator (host_build_graph/shared/orchestrator.cpp). + // Recorded by the host orchestrator (host_build_graph/host/orchestrator.cpp). OrchSubmitTask, // submit_task_common: one ordinary task OrchAllocTensors, // prepare_task: one alloc_tensors slot OrchRecordInGraphTask, // graph_record_submit_in_graph_task: one recorded in-graph task diff --git a/tests/ut/cpp/CMakeLists.txt b/tests/ut/cpp/CMakeLists.txt index e6e6c0af38..fade2092d9 100644 --- a/tests/ut/cpp/CMakeLists.txt +++ b/tests/ut/cpp/CMakeLists.txt @@ -877,17 +877,21 @@ set(HBG_RUNTIME_DIR ${CMAKE_SOURCE_DIR}/../../../src/a2a3/runtime/host_build_gra set(A5_HBG_RUNTIME_DIR ${CMAKE_SOURCE_DIR}/../../../src/a5/runtime/host_build_graph/runtime) # The out-of-line members every hbg target that drives a real Runtime or # orchestrator has to link. Both architectures compile the same translation units -# (src/common/host_build_graph/shared), so one list serves the a2a3 and a5 targets -# alike; only the arch-specific headers they include differ. +# (src/common/host_build_graph), so one list serves the a2a3 and a5 targets +# alike; only the arch-specific headers they include differ. The host/ vs shared/ +# split there names which runtime targets compile a file; these tests build +# host-side, so they draw from both. set(HBG_SHARED_DIR ${CMAKE_SOURCE_DIR}/../../../src/common/host_build_graph/shared) +set(HBG_HOST_DIR ${CMAKE_SOURCE_DIR}/../../../src/common/host_build_graph/host) set(HBG_SHARED_RUNTIME_SOURCES ${HBG_SHARED_DIR}/shared_memory.cpp - ${HBG_SHARED_DIR}/tensormap.cpp + ${HBG_HOST_DIR}/tensormap.cpp ${HBG_SHARED_DIR}/runtime_init.cpp ${HBG_SHARED_DIR}/runtime.cpp ) set(HBG_ORCH_SHARED_SOURCES - ${HBG_SHARED_DIR}/orchestrator.cpp + ${HBG_HOST_DIR}/orchestrator.cpp + ${CMAKE_SOURCE_DIR}/stubs/hbg_orch_stubs.cpp ${HBG_SHARED_RUNTIME_SOURCES} ) add_a2a3_hbg_runtime_test(test_hbg_task_allocator a2a3/test_hbg_task_allocator.cpp) @@ -1024,7 +1028,7 @@ target_sources(test_a5_graph_activation PRIVATE # ChipTensorMap's out-of-line members (reserve_layout / init / valid_count) live # in this .cpp; no other add_a2a3_hbg_runtime_test target needs them. target_sources(test_hbg_tensormap PRIVATE - ${HBG_SHARED_DIR}/tensormap.cpp + ${HBG_HOST_DIR}/tensormap.cpp ) target_sources(test_hbg_dep_gen_host_graph PRIVATE ${CMAKE_SOURCE_DIR}/../../../src/common/host_build_graph/host/dep_gen_host_graph.cpp diff --git a/tests/ut/cpp/stubs/hbg_orch_stubs.cpp b/tests/ut/cpp/stubs/hbg_orch_stubs.cpp new file mode 100644 index 0000000000..8e6a050b66 --- /dev/null +++ b/tests/ut/cpp/stubs/hbg_orch_stubs.cpp @@ -0,0 +1,52 @@ +/* + * 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. + * ----------------------------------------------------------------------------------------------------------- + */ + +/** + * The two capture sinks host_build_graph's orchestrator raises into. + * + * A production build links the real ones from host/dep_gen_host_graph.cpp and + * host/host_phase_trace.cpp. A test that drives the orchestrator wants neither — + * it asserts on submitted tasks, not on captured graphs or phase records — and + * linking either would pull in the collector and the record pool behind it. + * + * These are ordinary definitions rather than weak ones, so a target gets exactly + * one of the two sets: this file rides on HBG_ORCH_SHARED_SOURCES, which + * test_hbg_dep_gen_host_graph (the one test that wants the real capture) does not + * use. Adding this file to a target that already links a real sink is a duplicate + * symbol at link time, which is the intended way to find out. + */ + +#include + +#include "host_build_graph/dep_gen_host_graph.h" +#include "host_build_graph/host_phase_trace.h" + +bool dep_gen_host_graph_enabled() { return false; } + +void dep_gen_host_graph_begin_task( + uint64_t, bool, bool, const int32_t[3], int32_t, int32_t, const TensorRef *, const TensorArgType * +) {} + +void dep_gen_host_graph_end_task() {} + +void dep_gen_host_graph_add_explicit_edge(uint64_t) {} + +void dep_gen_host_graph_add_creator_edge(uint64_t, int32_t, const simpler::hbg::Tensor &) {} + +void dep_gen_host_graph_add_tensormap_edge( + uint64_t, int32_t, const simpler::hbg::Tensor &, const ChipTensorMapEntry &, OverlapStatus +) {} + +// 0 is the "this bind records nothing" answer host_phase_trace.h documents, which +// is what keeps the orchestrator's hooks down to two calls returning a constant. +uint64_t host_phase_now_ns() { return 0; } + +void host_phase_record(uint64_t, uint64_t, uint32_t, uint64_t, uint32_t) {}