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
19 changes: 19 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 @@ -56,12 +56,31 @@
#include "../runtime/runtime.h"
#include "../../../../common/runtime_status/error_log.h"
#include "../../../../common/task_interface/call_config.h"
#include "../../../../common/worker/pto_runtime_c_api.h"
#include "callable.h"
#include "common/platform_config.h"
#include "common/unified_log.h"
#include "utils/device_arena.h"
#include "prepare_callable_common.h"

extern "C" const PipelineContract *get_pipeline_contract(void) {
// Host orchestration materializes this run's own graph into the image it
// uploads, so every device-resident region carries per-run content.
static const PipelineContract contract = {
PTO_PIPELINE_CONTRACT_ABI_VERSION,
5,
1,
{
{PTO_PIPELINE_GM_HEAP, PTO_PIPELINE_HOST_PER_RUN, 0},
{PTO_PIPELINE_GM_SM, PTO_PIPELINE_HOST_PER_RUN, 0},
{PTO_PIPELINE_RUNTIME_IMAGE, PTO_PIPELINE_HOST_PER_RUN, 0},
{PTO_PIPELINE_AICPU_STREAM, PTO_PIPELINE_EXEC_HANDLE, 0},
{PTO_PIPELINE_AICORE_STREAM, PTO_PIPELINE_EXEC_HANDLE, 0},
},
};
return &contract;
}

// RuntimeEnv (call_config.h) is the cross-runtime ABI for per-ring config and
// carries RUNTIME_ENV_RING_COUNT slots, shared with tensormap_and_ringbuffer.
// host_build_graph is single-ring (PTO2_MAX_RING_DEPTH == 1) and reads only the
Expand Down
23 changes: 23 additions & 0 deletions src/a2a3/runtime/tensormap_and_ringbuffer/host/runtime_maker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
#include "../runtime/runtime.h"
#include "../../../../common/runtime_status/error_log.h"
#include "../../../../common/task_interface/call_config.h"
#include "../../../../common/worker/pto_runtime_c_api.h"
#include "callable.h"
#include "common/platform_config.h"
#include "common/strace.h"
Expand All @@ -57,6 +58,28 @@
#include "utils/device_arena.h"
#include "prepare_callable_common.h"

extern "C" const PipelineContract *get_pipeline_contract(void) {
// Orchestration runs on the device, so this run's own content is confined to
// its task args. The three pooled regions are built and uploaded once per
// callable sizing and then served from the prebuilt-arena cache
// (build_and_cache_prebuilt_arena runs only on a miss), so a run reuses the
// instance the previous run left behind.
static const PipelineContract contract = {
PTO_PIPELINE_CONTRACT_ABI_VERSION,
6,
1,
{
{PTO_PIPELINE_TASK_ARGS, PTO_PIPELINE_HOST_PER_RUN, 0},
{PTO_PIPELINE_GM_HEAP, PTO_PIPELINE_DEVICE_SCRATCH, 0},
{PTO_PIPELINE_GM_SM, PTO_PIPELINE_DEVICE_SCRATCH, 0},
{PTO_PIPELINE_RUNTIME_IMAGE, PTO_PIPELINE_DEVICE_SCRATCH, 0},
{PTO_PIPELINE_AICPU_STREAM, PTO_PIPELINE_EXEC_HANDLE, 0},
{PTO_PIPELINE_AICORE_STREAM, PTO_PIPELINE_EXEC_HANDLE, 0},
},
};
return &contract;
}

static_assert(
RUNTIME_ENV_RING_COUNT == PTO2_MAX_RING_DEPTH, "RuntimeEnv ring count must match PTO2 runtime ring depth"
);
Expand Down
28 changes: 28 additions & 0 deletions src/common/worker/chip_worker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

#include "chip_worker.h"

#include "pipeline_contract.h"

#include <dlfcn.h>

#include <cstring>
Expand All @@ -37,6 +39,14 @@ T load_symbol(void *handle, const char *name) {
return reinterpret_cast<T>(sym);
}

template <typename T>
T load_optional_symbol(void *handle, const char *name) {
dlerror();
void *sym = dlsym(handle, name);
if (dlerror() != nullptr) return nullptr;
return reinterpret_cast<T>(sym);
}

std::vector<uint8_t> read_binary_file(const std::string &path) {
std::ifstream f(path, std::ios::binary | std::ios::ate);
if (!f) {
Expand Down Expand Up @@ -71,6 +81,7 @@ void ChipWorker::init(
if (device_id < 0) {
throw std::runtime_error("ChipWorker::init requires a non-negative device_id");
}
pipeline_contract_ = {PTO_PIPELINE_CONTRACT_ABI_VERSION, 0, 1, {}};

// libsimpler_log.so (RTLD_GLOBAL, with HostLogger already seeded via
// simpler_log_init) and — on sim — libcpu_sim_context.so (RTLD_GLOBAL) must
Expand All @@ -94,6 +105,7 @@ void ChipWorker::init(
throw std::runtime_error(err);
}

GetPipelineContractFn get_pipeline_contract_fn = nullptr;
try {
create_device_context_fn_ = load_symbol<CreateDeviceContextFn>(handle, "create_device_context");
destroy_device_context_fn_ = load_symbol<DestroyDeviceContextFn>(handle, "destroy_device_context");
Expand All @@ -105,6 +117,7 @@ void ChipWorker::init(
simpler_init_fn_ = load_symbol<SimplerInitFn>(handle, "simpler_init");
register_callable_fn_ = load_symbol<SimplerRegisterCallableFn>(handle, "simpler_register_callable");
run_fn_ = load_symbol<SimplerRunFn>(handle, "simpler_run");
get_pipeline_contract_fn = load_optional_symbol<GetPipelineContractFn>(handle, "get_pipeline_contract");
unregister_callable_fn_ = load_symbol<SimplerUnregisterCallableFn>(handle, "simpler_unregister_callable");
get_aicpu_dlopen_count_fn_ = load_symbol<GetAicpuDlopenCountFn>(handle, "get_aicpu_dlopen_count");
get_host_dlopen_count_fn_ = load_symbol<GetAicpuDlopenCountFn>(handle, "get_host_dlopen_count");
Expand Down Expand Up @@ -134,6 +147,16 @@ void ChipWorker::init(
throw;
}

PipelineContract resolved_contract{PTO_PIPELINE_CONTRACT_ABI_VERSION, 0, 1, {}};
if (get_pipeline_contract_fn != nullptr) {
const PipelineContract *contract = get_pipeline_contract_fn();
if (!is_valid_depth1_pipeline_contract(contract)) {
dlclose(handle);
throw std::runtime_error("host runtime returned a PipelineContract this build cannot accept");
}
resolved_contract = *contract;
}

lib_handle_ = handle;

device_ctx_ = create_device_context_fn_();
Expand Down Expand Up @@ -252,6 +275,10 @@ void ChipWorker::init(
}

device_id_ = device_id;
// Published only once the runtime is up: the rollback paths above leave the
// default K=1 contract in place, so a failed init never reports the counts
// of a runtime this worker is not bound to.
pipeline_contract_ = resolved_contract;
initialized_ = true;

// Provision async-DMA workspaces (SDMA) once, now that the device is up. The
Expand Down Expand Up @@ -313,6 +340,7 @@ void ChipWorker::finalize() {
comm_barrier_fn_ = nullptr;
comm_destroy_fn_ = nullptr;
runtime_buf_.clear();
pipeline_contract_ = {PTO_PIPELINE_CONTRACT_ABI_VERSION, 0, 1, {}};
initialized_ = false;
device_id_ = -1;
finalized_ = true;
Expand Down
3 changes: 3 additions & 0 deletions src/common/worker/chip_worker.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ class ChipWorker {

int device_id() const { return device_id_; }
bool initialized() const { return initialized_; }
unsigned pipeline_depth() const { return pipeline_contract_.pipeline_depth; }

private:
using CreateDeviceContextFn = void *(*)();
Expand All @@ -157,6 +158,7 @@ class ChipWorker {
);
using SimplerRegisterCallableFn = int (*)(void *, int32_t, const void *);
using SimplerRunFn = int (*)(void *, void *, int32_t, const void *, const CallConfig *);
using GetPipelineContractFn = const PipelineContract *(*)();
using SimplerUnregisterCallableFn = int (*)(void *, int32_t);
using GetAicpuDlopenCountFn = size_t (*)(void *);
using SimplerProvisionDmaWorkspaceFn = int (*)(void *, uint32_t);
Expand Down Expand Up @@ -226,6 +228,7 @@ class ChipWorker {
uint64_t base_comm_handle_ = 0;

std::vector<uint8_t> runtime_buf_;
PipelineContract pipeline_contract_{PTO_PIPELINE_CONTRACT_ABI_VERSION, 0, 1, {}};
// device_id_ is set once in init() and never modified afterward. All
// ChipWorker callers run on the thread that called init() (the same
// thread is the only one that subsequently calls malloc / copy_to /
Expand Down
54 changes: 54 additions & 0 deletions src/common/worker/pipeline_contract.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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.
* -----------------------------------------------------------------------------------------------------------
*/

/**
* Admission check for the PipelineContract a host runtime declares.
*
* Lives beside the contract rather than inside ChipWorker so the rules a build
* will honor are stated in one place and can be exercised on their own.
*/

#ifndef SRC_COMMON_WORKER_PIPELINE_CONTRACT_H_
#define SRC_COMMON_WORKER_PIPELINE_CONTRACT_H_

#include <cstdint>

#include "pto_runtime_c_api.h"

/**
* Whether this build can honor `contract`.
*
* Rejects a contract whose ABI version is not the one compiled in, that
* declares more resources than fit, that asks for a pipeline depth other than
* 1, or whose resources carry an unspecified or out-of-range kind, an
* out-of-range class, or a non-zero reserved `bytes_per_copy`.
*
* The unspecified-kind rule is what catches a `resource_count` larger than the
* entries a runtime actually filled in: the trailing entries are still zeroed,
* and zero is not a resource. Repeated kinds stay legal, so a runtime may
* declare several resources of one kind.
*/
inline bool is_valid_depth1_pipeline_contract(const PipelineContract *contract) {
if (contract == nullptr || contract->abi_version != PTO_PIPELINE_CONTRACT_ABI_VERSION ||
contract->resource_count > PTO_PIPELINE_MAX_RESOURCES || contract->pipeline_depth != 1) {
return false;
}
for (uint32_t i = 0; i < contract->resource_count; ++i) {
const PipelineResource &resource = contract->resources[i];
if (resource.kind == PTO_PIPELINE_KIND_UNSPECIFIED || resource.kind > PTO_PIPELINE_AICORE_STREAM ||
resource.resource_class > PTO_PIPELINE_EXEC_HANDLE || resource.bytes_per_copy != 0) {
return false;
}
}
return true;
}

#endif // SRC_COMMON_WORKER_PIPELINE_CONTRACT_H_
72 changes: 68 additions & 4 deletions src/common/worker/pto_runtime_c_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@
* Both the ChipWorker (consumer, resolves public symbols via dlsym) and the
* platform implementations (producers, define all symbols) include this file.
*
* Public API — resolved by ChipWorker via dlsym (every host_runtime.so must
* export ALL of these; runtimes without a real backend ship not-supported
* stubs rather than omitting symbols, so ChipWorker can dlsym the full set
* unconditionally without per-symbol probing):
* Required API — resolved by ChipWorker via dlsym (every host_runtime.so must
* export all of these; runtimes without a real backend ship not-supported
* stubs rather than omitting symbols):
* - lifecycle: create_device_context, destroy_device_context,
* simpler_init, finalize_device
* - sizing: get_runtime_size
Expand All @@ -32,6 +31,9 @@
* - comm: comm_init, comm_alloc_windows, comm_get_local_window_base,
* comm_get_window_size, comm_barrier, comm_destroy
*
* Optional metadata:
* - pipeline: get_pipeline_contract
*
* Memory management: caller allocates a buffer of get_runtime_size() bytes
* and passes it to simpler_run(). Error codes: 0 = success, negative = error.
*/
Expand Down Expand Up @@ -61,6 +63,65 @@ enum {
PTO_RUNTIME_ERR_UNSUPPORTED = -2,
};

enum {
PTO_PIPELINE_CONTRACT_ABI_VERSION = 1,
PTO_PIPELINE_MAX_RESOURCES = 8,
/* Ceiling on pipeline_depth once a depth above 1 is enabled. */
PTO_PIPELINE_MAX_DEPTH = 2,
};

/**
* How a resource behaves across the KernelLaunch boundary, which is what
* decides its copy count: HOST_PER_RUN and EXEC_HANDLE need one instance per
* in-flight run (`pipeline_depth`), DEVICE_SCRATCH needs exactly one.
*/
typedef enum PipelineResourceClass {
/* Carries this run's own content, so the device is still reading the
previous run's content while the next run is prepared. */
PTO_PIPELINE_HOST_PER_RUN = 0,
/* Not rewritten per run: whoever populates it does so once, and device ops
run one at a time, so a single instance is reused across runs. */
PTO_PIPELINE_DEVICE_SCRATCH = 1,
/* Execution context (stream) a run owns while its op runs and is reaped. */
PTO_PIPELINE_EXEC_HANDLE = 2,
} PipelineResourceClass;

typedef enum PipelineResourceKind {
/* Zero is not a resource: it is what an entry a runtime never filled in
reads as, so a declaration that overstates resource_count is rejected
instead of decaying into a valid-looking resource. */
PTO_PIPELINE_KIND_UNSPECIFIED = 0,
PTO_PIPELINE_GM_HEAP = 1,
PTO_PIPELINE_GM_SM = 2,
PTO_PIPELINE_RUNTIME_IMAGE = 3,
PTO_PIPELINE_TASK_ARGS = 4,
PTO_PIPELINE_AICPU_STREAM = 5,
PTO_PIPELINE_AICORE_STREAM = 6,
} PipelineResourceKind;

typedef struct PipelineResource {
uint32_t kind;
uint32_t resource_class;
/* Size of one copy. Reserved: currently declared as 0 and required to be 0. */
uint64_t bytes_per_copy;
} PipelineResource;

/**
* Runtime-owned declaration of resources that cross KernelLaunch.
*
* `pipeline_depth` is the only replication count: a resource needs
* `pipeline_depth` copies unless its class is DEVICE_SCRATCH, which needs one.
* Per-resource copy counts stay derivable from the class, so a runtime that
* wants a cheap resource replicated and an expensive one shared says so by
* classifying them, not by carrying a second global count.
*/
typedef struct PipelineContract {
uint32_t abi_version;
uint32_t resource_count;
uint32_t pipeline_depth;
PipelineResource resources[PTO_PIPELINE_MAX_RESOURCES];
} PipelineContract;

/* Per-stage run timing is no longer returned. The platform emits it as
* `[STRACE]` log markers (host stages + the AICPU device-phase breakdown,
* gated on SIMPLER_HOST_STRACE) — parse with simpler_setup.tools.strace_timing.
Expand All @@ -70,6 +131,9 @@ enum {
* Public API (resolved by ChipWorker via dlsym)
* =========================================================================== */

/** Return this runtime's immutable pipeline resource declaration. Optional. */
const PipelineContract *get_pipeline_contract(void);

/**
* Create a new device context (heap-allocated DeviceRunner).
* Each ChipWorker should own one context for the lifetime of its init→finalize cycle.
Expand Down
1 change: 1 addition & 0 deletions tests/ut/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ add_hierarchical_test(test_orchestrator hierarchical/test_orchestrator.cpp)
add_hierarchical_test(test_scheduler hierarchical/test_scheduler.cpp)
add_hierarchical_test(test_remote_wire hierarchical/test_remote_wire.cpp)
add_hierarchical_test(test_remote_endpoint hierarchical/test_remote_endpoint.cpp)
add_hierarchical_test(test_pipeline_contract hierarchical/test_pipeline_contract.cpp)

# ---------------------------------------------------------------------------
# Types / task_interface tests (src/common/task_interface/)
Expand Down
Loading
Loading