From ec408a35ae6d58e739ccdfd18f9ba54b2afb843a Mon Sep 17 00:00:00 2001 From: sunkaixuan2018 Date: Tue, 28 Jul 2026 20:41:37 +0800 Subject: [PATCH] Fix: scope A5 CPU topology fallback to x86 standard cards Use OCCUPY-derived topology only for the verified x86 Ascend950PR_9579 signature when CPU_TOPO is unavailable. Preserve the existing failure behavior for other hosts, SoCs, and masks, and cover both accepted and rejected signatures in the C++ regression test. --- src/a5/docs/hardware.md | 19 +++++ .../onboard/host/aicpu_topology_probe.cpp | 49 ++++++++++- .../onboard/host/aicpu_topology_probe.h | 19 ++++- tests/ut/cpp/CMakeLists.txt | 21 +++++ .../cpp/a5/test_aicpu_topology_fallback.cpp | 85 +++++++++++++++++++ 5 files changed, 188 insertions(+), 5 deletions(-) create mode 100644 tests/ut/cpp/a5/test_aicpu_topology_fallback.cpp diff --git a/src/a5/docs/hardware.md b/src/a5/docs/hardware.md index 84f4c2134e..ab32df0833 100644 --- a/src/a5/docs/hardware.md +++ b/src/a5/docs/hardware.md @@ -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 diff --git a/src/a5/platform/onboard/host/aicpu_topology_probe.cpp b/src/a5/platform/onboard/host/aicpu_topology_probe.cpp index b2f4e22ad5..e895c9abec 100644 --- a/src/a5/platform/onboard/host/aicpu_topology_probe.cpp +++ b/src/a5/platform/onboard/host/aicpu_topology_probe.cpp @@ -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 @@ -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(dlsym(nullptr, "halGetDeviceInfo")); @@ -96,6 +99,15 @@ DsmiGetDeviceInfoFn load_dsmi_get_device_info() { return fn; } +const char *query_soc_name() { + auto fn = reinterpret_cast(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; @@ -144,7 +156,14 @@ bool probe_aicpu_topology_uncached(uint32_t device_id, std::vector(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]; @@ -170,6 +189,34 @@ bool probe_aicpu_topology_uncached(uint32_t device_id, std::vector &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 &out_user_cpus) { { std::lock_guard lk(s_topo_cache_mu); diff --git a/src/a5/platform/onboard/host/aicpu_topology_probe.h b/src/a5/platform/onboard/host/aicpu_topology_probe.h index 99df94ebe8..f8bbbf799a 100644 --- a/src/a5/platform/onboard/host/aicpu_topology_probe.h +++ b/src/a5/platform/onboard/host/aicpu_topology_probe.h @@ -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; @@ -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 &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 &out_user_cpus); + // Compute the `ALLOWED_CPUS` selection for the surviving threads. // // Inputs: diff --git a/tests/ut/cpp/CMakeLists.txt b/tests/ut/cpp/CMakeLists.txt index 2e8cf1fcf7..d935ab0162 100644 --- a/tests/ut/cpp/CMakeLists.txt +++ b/tests/ut/cpp/CMakeLists.txt @@ -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 diff --git a/tests/ut/cpp/a5/test_aicpu_topology_fallback.cpp b/tests/ut/cpp/a5/test_aicpu_topology_fallback.cpp new file mode 100644 index 0000000000..4611807002 --- /dev/null +++ b/tests/ut/cpp/a5/test_aicpu_topology_fallback.cpp @@ -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 + +#include +#include + +#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 cpus; + + EXPECT_FALSE(derive_topology_from_occupy("Ascend950PR_9579", 0x3e, cpus)); + EXPECT_TRUE(cpus.empty()); +} +#elif defined(__x86_64__) +TEST(A5AicpuTopologyFallback, EnumeratesEveryOccupiedCpu) { + std::vector 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 cpus; + ASSERT_TRUE(derive_topology_from_occupy("Ascend950PR_9579", 0x3e, cpus)); + + std::vector allowed; + ASSERT_TRUE(compute_allowed_cpus(cpus, /*n_sched=*/2, /*n_orch=*/1, allowed)); + EXPECT_EQ(allowed, (std::vector{4, 5, 1})); +} +#else +TEST(A5AicpuTopologyFallback, RejectsUnsupportedHost) { + std::vector cpus; + + EXPECT_FALSE(derive_topology_from_occupy("Ascend950PR_9579", 0x3e, cpus)); + EXPECT_TRUE(cpus.empty()); +} +#endif + +TEST(A5AicpuTopologyFallback, RejectsUnverifiedSoc) { + std::vector 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 cpus = {{1, 1, 0, 0, 0}}; + + EXPECT_FALSE(derive_topology_from_occupy("Ascend950PR_9579", 0x1f8, cpus)); + EXPECT_TRUE(cpus.empty()); +} + +} // namespace