Skip to content
Open
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/a5/docs/hardware.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,25 @@ For cross-generation portable code: **always go through ACL or CANN
ini, never HAL**. HAL's CORE_NUM semantics shift between a3 and a5 in
ways that have no public documentation.

### CPU_TOPO compatibility on newer a5 drivers

On `Ascend950PR_9579` with driver `25.7.rc1.6`, both host-side and
device-side `AICPU + OCCUPY` report `0x3e`, so cpu_ids 1 through 5 are
the complete user-schedulable pool. Launching five AICPU threads reaches
each of those cpu_ids exactly once.

The same driver returns `DRV_ERROR_NOT_SUPPORT` for both
`halGetDeviceInfoByBuff(SYSTEM, CPU_TOPO)` and
`dsmi_get_device_info(SOC_INFO, CPU_TOPO)`. Its public DSMI header only
defines SOC_INFO subcommands 0 and 1.

The OCCUPY-only fallback is restricted to the verified x86 standard-card
signature `Ascend950PR_9579` with `OCCUPY=0x3e`. In that case, the host
runtime treats each set bit as a distinct non-SMT physical CPU. Other hosts,
SoCs, and masks remain unsupported when CPU_TOPO is unavailable. Drivers
that provide CPU_TOPO continue to use its detailed physical and hyperthread
metadata.

## CANN AICPU thread dispatch under varying launch budgets

How CANN distributes N AICPU threads across the user pool determines
Expand Down
49 changes: 48 additions & 1 deletion src/a5/platform/onboard/host/aicpu_topology_probe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ constexpr int32_t kInfoCpuTopo = 59;
constexpr unsigned int kDsmiSocInfoMainCmd = 14; // DSMI_MAIN_CMD_SOC_INFO
constexpr unsigned int kDsmiSocInfoSubCmdCpuTopo = 2; // SUB_CMD_CPU_TOPO
constexpr unsigned int kCpuTopoMaxLogical = 64; // headroom for any a5 SKU
constexpr char kVerifiedFallbackSoc[] = "Ascend950PR_9579";
constexpr uint64_t kVerifiedFallbackOccupy = 0x3e;

// Natural-alignment layout (no pack pragma). Mirrors
// tools/cann-examples/query/query.cpp's struct; the HAL/DSMI driver
Expand All @@ -64,6 +66,7 @@ using HalGetDeviceInfoByBuffFn =
int (*)(uint64_t deviceId, int32_t moduleType, int32_t infoType, void *buf, int32_t *size);
using DsmiGetDeviceInfoFn =
int (*)(uint32_t device_id, unsigned int main_cmd, unsigned int sub_cmd, void *buf, unsigned int *size);
using AclrtGetSocNameFn = const char *(*)();

HalGetDeviceInfoFn load_hal_get_device_info() {
auto fn = reinterpret_cast<HalGetDeviceInfoFn>(dlsym(nullptr, "halGetDeviceInfo"));
Expand Down Expand Up @@ -96,6 +99,15 @@ DsmiGetDeviceInfoFn load_dsmi_get_device_info() {
return fn;
}

const char *query_soc_name() {
auto fn = reinterpret_cast<AclrtGetSocNameFn>(dlsym(nullptr, "aclrtGetSocName"));
if (fn == nullptr) {
LOG_WARN("aicpu_topology_probe: aclrtGetSocName not found via dlsym");
return nullptr;
}
return fn();
}

bool query_occupy(uint32_t device_id, uint64_t &out_mask) {
auto fn = load_hal_get_device_info();
if (fn == nullptr) return false;
Expand Down Expand Up @@ -144,7 +156,14 @@ bool probe_aicpu_topology_uncached(uint32_t device_id, std::vector<AicpuLogicalC
if (!query_occupy(device_id, occupy)) return false;

DsmiCpuTopo topo{};
if (!query_cpu_topo(device_id, topo)) return false;
if (!query_cpu_topo(device_id, topo)) {
const char *soc_name = query_soc_name();
LOG_WARN(
"aicpu_topology_probe: CPU_TOPO unavailable; checking OCCUPY fallback for soc=%s mask=0x%llx",
soc_name == nullptr ? "(unknown)" : soc_name, static_cast<unsigned long long>(occupy)
);
return derive_topology_from_occupy(soc_name, occupy, out_user_cpus);
}

for (uint32_t i = 0; i < topo.total_nums; ++i) {
const DsmiSingleCpu &c = topo.cpus[i];
Expand All @@ -170,6 +189,34 @@ bool probe_aicpu_topology_uncached(uint32_t device_id, std::vector<AicpuLogicalC

} // namespace

bool derive_topology_from_occupy(const char *soc_name, uint64_t occupy, std::vector<AicpuLogicalCpu> &out_user_cpus) {
out_user_cpus.clear();
#if defined(__aarch64__)
(void)soc_name;
(void)occupy;
return false;
#elif defined(__x86_64__)
if (soc_name == nullptr || std::strcmp(soc_name, kVerifiedFallbackSoc) != 0 || occupy != kVerifiedFallbackOccupy) {
return false;
}
#else
(void)soc_name;
(void)occupy;
return false;
#endif
for (int32_t cpu_id = 0; cpu_id < 64; ++cpu_id) {
if (((occupy >> cpu_id) & 1ULL) == 0) continue;
AicpuLogicalCpu cpu{};
cpu.cpu_id = cpu_id;
cpu.phy_cpu_id = cpu_id;
cpu.hyperthread_id = 0;
cpu.cluster_id = cpu.phy_cpu_id / 2;
cpu.die_id = cpu.phy_cpu_id / 4;
out_user_cpus.push_back(cpu);
}
return !out_user_cpus.empty();
}

bool probe_aicpu_topology(uint32_t device_id, std::vector<AicpuLogicalCpu> &out_user_cpus) {
{
std::lock_guard<std::mutex> lk(s_topo_cache_mu);
Expand Down
19 changes: 15 additions & 4 deletions src/a5/platform/onboard/host/aicpu_topology_probe.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@

namespace pto::a5 {

// Per-cpu_id metadata used by the packing algorithm. Filled from DSMI
// CPU_TOPO + halGetDeviceInfo(AICPU, OCCUPY). cluster/die ids derive from
// phy_cpu_id via the a5 mapping (cluster = phy/2, die = phy/4).
// Per-cpu_id metadata used by the packing algorithm. Filled from the driver
// CPU_TOPO data when available. The verified x86 standard-card fallback
// derives it from the AICPU OCCUPY bitmap. cluster/die ids follow the a5
// mapping (cluster = phy/2, die = phy/4).
struct AicpuLogicalCpu {
int32_t cpu_id;
int32_t phy_cpu_id;
Expand All @@ -33,15 +34,25 @@ struct AicpuLogicalCpu {
// only contains cpu_ids that are in the device-side OCCUPY bitmap (i.e.
// user-schedulable), sorted by cpu_id ascending.
//
// This function performs three driver calls:
// This function uses these driver calls:
// * halGetDeviceInfo(AICPU, OCCUPY) — user-schedulable bitmap
// * halGetDeviceInfoByBuff(SYSTEM, CPU_TOPO) (primary)
// * dsmi_get_device_info(SOC_INFO, CPU_TOPO) (fallback)
//
// CPU_TOPO-less x86 Ascend950PR_9579 standard cards with OCCUPY=0x3e use
// the verified OCCUPY-only topology below. Other signatures remain
// unsupported.
// All driver entry points are dlsym'd from the host process (CANN is
// expected to be already loaded by the surrounding `aclInit` path).
bool probe_aicpu_topology(uint32_t device_id, std::vector<AicpuLogicalCpu> &out_user_cpus);

// Build topology metadata only for the verified x86 standard-card signature:
// Ascend950PR_9579 with OCCUPY=0x3e. Every set bit is a distinct non-SMT
// physical CPU and retains the a5 two-physical-CPUs-per-cluster,
// two-clusters-per-die layout. Returns false on all other hosts, SoCs, or
// masks.
bool derive_topology_from_occupy(const char *soc_name, uint64_t occupy, std::vector<AicpuLogicalCpu> &out_user_cpus);

// Compute the `ALLOWED_CPUS` selection for the surviving threads.
//
// Inputs:
Expand Down
21 changes: 21 additions & 0 deletions tests/ut/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,27 @@ target_link_libraries(test_a2a3_aicpu_affinity_select PRIVATE
add_test(NAME test_a2a3_aicpu_affinity_select COMMAND test_a2a3_aicpu_affinity_select)
set_tests_properties(test_a2a3_aicpu_affinity_select PROPERTIES LABELS "no_hardware")

# a5 CPU_TOPO fallback and affinity selection. Pure logic; the fallback is
# restricted to the verified x86 Ascend950PR_9579 standard-card signature.
set(A5_ONBOARD_HOST_DIR ${CMAKE_SOURCE_DIR}/../../../src/a5/platform/onboard/host)
add_executable(test_a5_aicpu_topology_fallback
a5/test_aicpu_topology_fallback.cpp
${A5_ONBOARD_HOST_DIR}/aicpu_topology_probe.cpp
)
target_include_directories(test_a5_aicpu_topology_fallback PRIVATE
${GTEST_INCLUDE_DIRS}
${A5_ONBOARD_HOST_DIR}
${SIMPLER_LOG_DIR}/include
)
target_link_libraries(test_a5_aicpu_topology_fallback PRIVATE
${GTEST_MAIN_LIB}
${GTEST_LIB}
${CMAKE_DL_LIBS}
pthread
)
add_test(NAME test_a5_aicpu_topology_fallback COMMAND test_a5_aicpu_topology_fallback)
set_tests_properties(test_a5_aicpu_topology_fallback PROPERTIES LABELS "no_hardware")

# Hardware-gated tests. Block is only entered when the project is configured
# with -DSIMPLER_ENABLE_HARDWARE_TESTS=ON. CI's no-hw `ut` job does not pass
# this flag, so nothing here is compiled there — this is the structural
Expand Down
85 changes: 85 additions & 0 deletions tests/ut/cpp/a5/test_aicpu_topology_fallback.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright (c) PyPTO Contributors.
* This program is free software, you can redistribute it and/or modify it under the terms and conditions of
* CANN Open Software License Agreement Version 2.0 (the "License").
* Please refer to the License for details. You may not use this file except in compliance with the License.
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
* See LICENSE in the root of the software repository for the full text of the License.
* -----------------------------------------------------------------------------------------------------------
*/

#include <gtest/gtest.h>

#include <cstdint>
#include <vector>

#include "aicpu_topology_probe.h"

extern "C" {
void unified_log_error(const char *, const char *, ...) {}
void unified_log_warn(const char *, const char *, ...) {}
void unified_log_info_v(const char *, int, const char *, ...) {}
}

namespace {

using pto::a5::AicpuLogicalCpu;
using pto::a5::compute_allowed_cpus;
using pto::a5::derive_topology_from_occupy;

#if defined(__aarch64__)
TEST(A5AicpuTopologyFallback, RejectsNonX86Host) {
std::vector<AicpuLogicalCpu> cpus;

EXPECT_FALSE(derive_topology_from_occupy("Ascend950PR_9579", 0x3e, cpus));
EXPECT_TRUE(cpus.empty());
}
#elif defined(__x86_64__)
TEST(A5AicpuTopologyFallback, EnumeratesEveryOccupiedCpu) {
std::vector<AicpuLogicalCpu> cpus;

ASSERT_TRUE(derive_topology_from_occupy("Ascend950PR_9579", 0x3e, cpus));
ASSERT_EQ(cpus.size(), 5U);

for (int32_t i = 0; i < 5; ++i) {
EXPECT_EQ(cpus[i].cpu_id, i + 1);
EXPECT_EQ(cpus[i].phy_cpu_id, i + 1);
EXPECT_EQ(cpus[i].hyperthread_id, 0);
EXPECT_EQ(cpus[i].cluster_id, (i + 1) / 2);
EXPECT_EQ(cpus[i].die_id, (i + 1) / 4);
}
}

TEST(A5AicpuTopologyFallback, PreservesAffinitySelection) {
std::vector<AicpuLogicalCpu> cpus;
ASSERT_TRUE(derive_topology_from_occupy("Ascend950PR_9579", 0x3e, cpus));

std::vector<int32_t> allowed;
ASSERT_TRUE(compute_allowed_cpus(cpus, /*n_sched=*/2, /*n_orch=*/1, allowed));
EXPECT_EQ(allowed, (std::vector<int32_t>{4, 5, 1}));
}
#else
TEST(A5AicpuTopologyFallback, RejectsUnsupportedHost) {
std::vector<AicpuLogicalCpu> cpus;

EXPECT_FALSE(derive_topology_from_occupy("Ascend950PR_9579", 0x3e, cpus));
EXPECT_TRUE(cpus.empty());
}
#endif

TEST(A5AicpuTopologyFallback, RejectsUnverifiedSoc) {
std::vector<AicpuLogicalCpu> cpus = {{1, 1, 0, 0, 0}};

EXPECT_FALSE(derive_topology_from_occupy("Ascend950PR_9599", 0x3e, cpus));
EXPECT_TRUE(cpus.empty());
}

TEST(A5AicpuTopologyFallback, RejectsUnexpectedOccupyMask) {
std::vector<AicpuLogicalCpu> cpus = {{1, 1, 0, 0, 0}};

EXPECT_FALSE(derive_topology_from_occupy("Ascend950PR_9579", 0x1f8, cpus));
EXPECT_TRUE(cpus.empty());
}

} // namespace
Loading