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
291 changes: 220 additions & 71 deletions src/a5/runtime/host_build_graph/aicore/aicore_executor.cpp

Large diffs are not rendered by default.

1,563 changes: 330 additions & 1,233 deletions src/a5/runtime/host_build_graph/aicpu/aicpu_executor.cpp

Large diffs are not rendered by default.

28 changes: 22 additions & 6 deletions src/a5/runtime/host_build_graph/build_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,28 @@
# 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.
# -----------------------------------------------------------------------------------------------------------
# Runtime build configuration
# All paths are relative to this file's directory (src/runtime/)
# host_build_graph Runtime build configuration
# All paths are relative to this file's directory (src/runtime/host_build_graph/)
#
# This is the host-orchestration variant of tensormap_and_ringbuffer: it shares
# the same scheduler, ring buffers, and shared-memory layout, differing only in
# WHEN the orchestrator runs.
# - Host runs the orchestrator to completion, populating SM + arena, then H2Ds
# the image to device (vs tensormap, where AICPU thread N-1 orchestrates on
# device concurrently with the scheduler threads)
# - AICPU threads 0..N-1 all run schedulers (no on-device orchestrator thread)
# - AICore executes tasks via an aligned PTO2DispatchPayload + pre-built dispatch_args
#
# The "orchestration" directory contains source files compiled into both
# runtime targets AND the orchestration .so (e.g., tensor methods needed
# by the Tensor constructor's validation logic).

BUILD_CONFIG = {
"aicore": {"include_dirs": ["runtime"], "source_dirs": ["aicore", "runtime"]},
"aicpu": {"include_dirs": ["runtime", ".."], "source_dirs": ["aicpu", "runtime"]},
"host": {"include_dirs": ["runtime", "orchestration", ".."], "source_dirs": ["host", "runtime"]},
"orchestration": {"include_dirs": ["runtime", "orchestration"], "source_dirs": []},
"aicore": {"include_dirs": ["runtime", "common", ".."], "source_dirs": ["aicore", "orchestration"]},
"aicpu": {"include_dirs": ["runtime", "common", ".."], "source_dirs": ["aicpu", "runtime", "orchestration"]},
"host": {
"include_dirs": ["runtime", "common", ".."],
"source_dirs": ["host", "runtime/orchestrator_core", "runtime/shared", "orchestration"],
},
"orchestration": {"include_dirs": ["runtime", "orchestration", "common", ".."], "source_dirs": ["orchestration"]},
}
199 changes: 199 additions & 0 deletions src/a5/runtime/host_build_graph/common/intrinsic.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/*
* 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.
* -----------------------------------------------------------------------------------------------------------
*/

/**
* @file intrinsic.h
* @brief SPMD execution context for AICore user kernels
*
* Topology data exposed to user kernels has two distinct lifetimes:
*
* 1. Global topology (per-core, fixed after runtime init):
* - sub_block_id : identifies the AIV lane within a cluster
* (0 = AIV0/left, 1 = AIV1/right). Initialized once at runtime
* startup based on each core's cluster position; never changes.
* Only meaningful for AIV kernels in MIX tasks.
*
* 2. Local per-dispatch context (changes each dispatch):
* - block_idx : which logical block the current worker is executing
* - block_num : total number of blocks in this task (= block_dim)
* Written by build_payload() before each dispatch.
*
* Both categories are injected via two pointer slots appended at the tail
* of the kernel args[] array:
*
* args layout:
* [0 .. tensor_count-1] = tensor GM pointers
* [tensor_count .. +scalar_count-1] = scalar values
* ...
* [SPMD_LOCAL_CONTEXT_INDEX] = (uint64_t)&LocalContext (per-dispatch)
* [SPMD_GLOBAL_CONTEXT_INDEX] = (uint64_t)&GlobalContext (per-core)
*
* The suffix positions are compile-time constants and do not depend on the
* runtime tensor_count or scalar_count.
*
* Include this header in AICore kernel source files to use the Get* accessors.
* Do NOT depend on the raw index constants; always use the accessor functions.
*
* On CCEC (real hardware), __gm__ and __aicore__ must be defined before
* including this header (e.g. via <pto/pto-inst.hpp> or manual #define).
* The #ifndef guards below provide fallbacks for non-kernel builds
* (AICPU, HOST) where these qualifiers are not needed.
*
* IMPORTANT — do NOT mix these with the CCE built-in topology intrinsics
* (`get_subblockid()`, `get_block_idx()`, `get_block_num()` declared in
* `kernel_operator.h` / tikcfw). Those intrinsics read AICore hardware
* registers that simpler's tensormap_and_ringbuffer runtime does NOT
* program. Specifically:
*
* - CCE `get_subblockid()` returns whatever stale value the AICore
* sub-block register holds — under simpler's MIX dispatch it is 0
* for BOTH AIV0 and AIV1 of every cluster, so a kernel that uses
* it to partition heads will silently have AIV1 redo AIV0's work
* and the AIV1 share of the output is never written. This is the
* exact failure mode that produced the partial-zero output in
* issue #900 (PR #899 spmd_paged_attention_highperf); the kernel
* compiled, ran without error, and produced wrong output. Use
* `get_sub_block_id(args)` instead, which reads from the runtime's
* `GlobalContext.sub_block_id` that the scheduler initializes per
* AIV core in `scheduler_cold_path.cpp::SchedulerContext::init`.
*
* - `get_block_idx()` and `get_block_num()` are not redirected to
* simpler's LocalContext either — use the `(args)` variants below
* so the values reflect simpler's logical block_dim (which can
* differ from `RUNTIME_CONFIG.block_dim`, the physical core count).
*
* If you are porting a kernel originally written for native CANN dispatch
* (AscendC, ascend-transformer-boost, etc.), every reference to those
* three CCE intrinsics needs to be rewritten against this header. See
* `docs/aicore-kernel-programming.md` for the full author contract,
* porting checklist, and the worked example from PR #899 / issue #900.
*/

#pragma once

#include <stdint.h>

#include "aicore_completion_mailbox_types.h"
#include "pto_task_id.h"

#ifndef __gm__
#define __gm__
#endif

#ifndef __aicore__
#define __aicore__
#endif

/** Number of extra pointer slots appended to the args[] tail (LocalContext + GlobalContext). */
static constexpr int32_t PTO2_EXT_PARAMS_COUNT = 2;

/**
* Args[] suffix indices for context pointers.
* Derived from MAX_TENSOR_ARGS(32) + MAX_SCALAR_ARGS(16).
* Users should not depend on these values; use the Get* functions below.
*/
static constexpr int32_t SPMD_LOCAL_CONTEXT_INDEX = 48;
static constexpr int32_t SPMD_GLOBAL_CONTEXT_INDEX = 49;
static constexpr int32_t PAYLOAD_LOCAL_CONTEXT_INDEX = SPMD_LOCAL_CONTEXT_INDEX;
static constexpr int32_t PAYLOAD_GLOBAL_CONTEXT_INDEX = SPMD_GLOBAL_CONTEXT_INDEX;

/**
* Per-core global context, stored in PTO2DispatchPayload.
* Initialized once at runtime startup (init_global_context) based on each
* core's cluster position. Never modified after initialization.
*/
struct GlobalContext {
// AIV lane within cluster: 0=AIV0(left), 1=AIV1(right).
// Used by AIV to select the correct intra-cluster hw instruction.
// Not meaningful for AIC kernels or single-AIV tasks.
int32_t sub_block_id;
};

struct AsyncCtx {
volatile __gm__ uint32_t *completion_count;
volatile __gm__ int32_t *completion_error_code;
volatile __gm__ DeferredCompletionEntry *completion_entries;
uint32_t completion_capacity;
PTO2TaskId task_token;

static inline AsyncCtx make(PTO2TaskId task_token, volatile __gm__ DeferredCompletionSlab *buffer) {
AsyncCtx ctx{};
ctx.task_token = task_token;
if (buffer == nullptr) {
ctx.task_token = PTO2TaskId::invalid();
return ctx;
}
ctx.completion_count = &buffer->count;
ctx.completion_error_code = &buffer->error_code;
ctx.completion_entries = &buffer->entries[0];
ctx.completion_capacity = MAX_COMPLETIONS_PER_TASK;
return ctx;
}
};

/**
* Per-dispatch local context, stored in PTO2DispatchPayload.
* Written by build_payload() before each dispatch. Different blocks of the
* same task receive different block_idx values but the same block_num.
*
*/
struct LocalContext {
int32_t block_idx; // Logical block index within the task [0, block_num)
int32_t block_num; // How many logical blocks this task requires.
// Currently fixed to 1 (block_dim > 1 not yet implemented).
// NOT the same as RUNTIME_CONFIG.block_dim in kernel_config.py,
// which controls how many physical cores the runtime launches.
AsyncCtx async_ctx;
};

/**
* Return the AIV lane index within the cluster.
* In a MIX 1C2V task: AIV0(left)=0, AIV1(right)=1.
*
* This value is only meaningful for AIV kernels in MIX tasks. It tells
* the AIV whether it is the left lane or the right lane within the cluster,
* which determines the correct hardware instruction for intra-cluster
* communication.
*
* AIC kernels should NOT call this function.
* Single-AIV tasks have no intra-cluster communication, so sub_block_id
* has no meaning and should not be used.
*/
static __aicore__ inline int32_t get_sub_block_id(__gm__ int64_t *args) {
__gm__ GlobalContext *ctx =
reinterpret_cast<__gm__ GlobalContext *>(static_cast<uint64_t>(args[SPMD_GLOBAL_CONTEXT_INDEX]));
return ctx->sub_block_id;
}

/**
* Return the logical block index assigned to the current worker.
* Range: [0, get_block_num(args)).
* Within the same task, different blocks receive different indices.
*/
static __aicore__ inline int32_t get_block_idx(__gm__ int64_t *args) {
__gm__ LocalContext *ctx =
reinterpret_cast<__gm__ LocalContext *>(static_cast<uint64_t>(args[SPMD_LOCAL_CONTEXT_INDEX]));
return ctx->block_idx;
}

/**
* Return how many logical blocks the current task requires.
* All blocks of the same task see the same value.
* Currently always returns 1 (block_dim>1 not yet implemented).
*
* Note: this is NOT the same as RUNTIME_CONFIG.block_dim in
* kernel_config.py, which controls how many physical cores are launched.
*/
static __aicore__ inline int32_t get_block_num(__gm__ int64_t *args) {
__gm__ LocalContext *ctx =
reinterpret_cast<__gm__ LocalContext *>(static_cast<uint64_t>(args[SPMD_LOCAL_CONTEXT_INDEX]));
return ctx->block_num;
}
53 changes: 53 additions & 0 deletions src/a5/runtime/host_build_graph/common/pto_runtime_status.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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.
* -----------------------------------------------------------------------------------------------------------
*/

/**
* PTO2 Runtime Status Helpers
*
* Shared error-code contract used inside the tensormap_and_ringbuffer runtime.
*/

#ifndef SRC_A2A3_RUNTIME_TENSORMAP_AND_RINGBUFFER_COMMON_PTO_RUNTIME_STATUS_H_
#define SRC_A2A3_RUNTIME_TENSORMAP_AND_RINGBUFFER_COMMON_PTO_RUNTIME_STATUS_H_

#include <stdint.h>

// Orchestrator errors (1-99): detected in orchestrator thread
#define PTO2_ERROR_NONE 0 // Explicitly means "no error"; it is not an "unknown/unspecified" error code.
#define PTO2_ERROR_SCOPE_DEADLOCK 1
#define PTO2_ERROR_HEAP_RING_DEADLOCK 2
#define PTO2_ERROR_FLOW_CONTROL_DEADLOCK 3
#define PTO2_ERROR_DEP_POOL_OVERFLOW 4
#define PTO2_ERROR_INVALID_ARGS 5 // Arg construction error (invalid args)
#define PTO2_ERROR_DEPENDENCY_OVERFLOW 6 // Too many unique fanin dependencies for one task
#define PTO2_ERROR_REQUIRE_SYNC_START_INVALID 7
#define PTO2_ERROR_TENSOR_WAIT_TIMEOUT 8
#define PTO2_ERROR_EXPLICIT_ORCH_FATAL 9
#define PTO2_ERROR_SCOPE_TASKS_OVERFLOW 10 // scope_tasks buffer saturated (all rings full)
#define PTO2_ERROR_TENSORMAP_OVERFLOW 11 // tensormap entry pool wedged (last_task_alive not advancing)

// Scheduler errors (100+): detected in scheduler threads
#define PTO2_ERROR_SCHEDULER_TIMEOUT 100
#define PTO2_ERROR_ASYNC_COMPLETION_INVALID 101
#define PTO2_ERROR_ASYNC_WAIT_OVERFLOW 102
#define PTO2_ERROR_ASYNC_REGISTRATION_FAILED 103

static inline int32_t runtime_status_from_error_codes(int32_t orch_error_code, int32_t sched_error_code) {
if (orch_error_code != PTO2_ERROR_NONE) {
return orch_error_code < 0 ? orch_error_code : -orch_error_code;
}
if (sched_error_code != PTO2_ERROR_NONE) {
return sched_error_code < 0 ? sched_error_code : -sched_error_code;
}
return 0;
}

#endif // SRC_A2A3_RUNTIME_TENSORMAP_AND_RINGBUFFER_COMMON_PTO_RUNTIME_STATUS_H_
Loading
Loading