diff --git a/docs/installation.md b/docs/installation.md index 17e62349..a5a13d62 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -34,8 +34,8 @@ cd infinibench/hardware/cuda-memory-benchmark bash build.sh --platform cuda ``` -For MetaX, Iluvatar, Hygon, and Moore Threads build and runtime instructions, -see [Hardware Benchmarks](../infinibench/hardware/README.md). +For MetaX, Iluvatar, Hygon, Moore Threads, and Ascend build and runtime +instructions, see [Hardware Benchmarks](../infinibench/hardware/README.md). **Note**: This requires: - CUDA toolkit (compatible with your GPU driver) diff --git a/infinibench/hardware/README.md b/infinibench/hardware/README.md index 27573cfe..05ce4f93 100644 --- a/infinibench/hardware/README.md +++ b/infinibench/hardware/README.md @@ -1,8 +1,8 @@ # Hardware Benchmarks -InfiniBench provides one hardware adapter for NVIDIA CUDA and four additional -CUDA-compatible accelerator platforms. Existing CUDA command shapes and metric -names are kept unchanged. +InfiniBench provides one hardware adapter for NVIDIA CUDA and five additional +accelerator platforms. Existing CUDA command shapes, behavior, and metric names +are kept unchanged. Platform-specific tests use distinct metric names. ## Platforms @@ -13,6 +13,7 @@ names are kept unchanged. | Iluvatar CoreX | `corex`, `iluvatar` | `bash build.sh --platform corex` | `cuda-memory-benchmark/build/cuda_perf_suite` | | Hygon DCU | `hygon` | `bash build.sh --platform hygon` | `cuda-memory-benchmark/build/cuda_perf_suite` | | Moore Threads | `moore` | `bash build.sh --platform moore` | `cuda-memory-benchmark/build/cuda_perf_suite` | +| Ascend | `ascend`, `npu` | `bash build.sh` | `ascend-memory-benchmark/build/npu_perf_suite` | Run each build command from its benchmark directory. All binaries use the same test selectors and common arguments: @@ -44,9 +45,13 @@ example, Moore Threads STREAM uses: } ``` -The aliases `nvidia`, `musa`, and `mthreads` are also accepted as explicit -device values. A selected non-CUDA platform is recorded in the result -configuration as `platform`; metric names remain compatible with CUDA results. +The aliases `nvidia`, `musa`, `mthreads`, and `npu` are also accepted as +explicit device values. A selected non-CUDA platform is recorded in the result +configuration as `platform`. Ascend publishes all four STREAM operations: Copy +uses ACL D2D memcpy, Scale uses `aclnnMuls`, and Add/Triad use `aclnnAdd` with +the corresponding scalar. Its ACL D2D memcpy size sweep is published as +`hardware.d2d_memcpy_size_sweep` and does not claim to isolate AI Core memory +levels. ## Device Visibility @@ -58,12 +63,14 @@ The selected physical device is renumbered to device 0 inside the process. | NVIDIA, MetaX, Iluvatar | `CUDA_VISIBLE_DEVICES` | | Hygon | `HIP_VISIBLE_DEVICES` and `ROCR_VISIBLE_DEVICES` | | Moore Threads | `MUSA_VISIBLE_DEVICES` | +| Ascend | `ASCEND_RT_VISIBLE_DEVICES` | For example: ```bash CUDA_VISIBLE_DEVICES=2 ./build/cuda_perf_suite --stream --device 0 MUSA_VISIBLE_DEVICES=0 ./build/cuda_perf_suite --stream --device 0 +ASCEND_RT_VISIBLE_DEVICES=4 ./build/npu_perf_suite --stream --device 0 ``` ## Container Notes @@ -79,3 +86,8 @@ docker run --rm --privileged \ Without this mount, management tools can list DCUs while HIP applications fail to load `libhsa-runtime64.so` or `libhydmi.so`. + +Some Ascend development images can return exit code 137 after the benchmark has +printed its completion message. The adapter intentionally treats every nonzero +exit code as a failure; fix the container lifecycle rather than suppressing that +error in application code. diff --git a/infinibench/hardware/ascend-memory-benchmark/CMakeLists.txt b/infinibench/hardware/ascend-memory-benchmark/CMakeLists.txt new file mode 100644 index 00000000..09252ef4 --- /dev/null +++ b/infinibench/hardware/ascend-memory-benchmark/CMakeLists.txt @@ -0,0 +1,83 @@ +cmake_minimum_required(VERSION 3.18) +project(NpuPerfSuite VERSION 1.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +# Find Ascend CANN Toolkit +if(DEFINED ENV{ASCEND_HOME_PATH}) + set(ASCEND_HOME $ENV{ASCEND_HOME_PATH}) +elseif(DEFINED ENV{ASCEND_TOOLKIT_HOME}) + set(ASCEND_HOME $ENV{ASCEND_TOOLKIT_HOME}) +else() + set(ASCEND_HOME "/usr/local/Ascend/ascend-toolkit/latest") +endif() +message(STATUS "ASCEND_HOME: ${ASCEND_HOME}") + +# Detect architecture for library path +execute_process( + COMMAND uname -m + OUTPUT_VARIABLE ARCH_TYPE + OUTPUT_STRIP_TRAILING_WHITESPACE +) +if(ARCH_TYPE STREQUAL "aarch64") + set(ARCH_DIR "aarch64-linux") +else() + set(ARCH_DIR "x86_64-linux") +endif() + +set(ASCEND_INCLUDE_DIR "${ASCEND_HOME}/${ARCH_DIR}/include") +set(ASCEND_LIB_DIR "${ASCEND_HOME}/${ARCH_DIR}/lib64") + +# Verify headers exist +find_path(ACL_INCLUDE_DIR NAMES acl/acl.h HINTS ${ASCEND_INCLUDE_DIR}) +if(NOT ACL_INCLUDE_DIR) + message(FATAL_ERROR "acl/acl.h not found in ${ASCEND_INCLUDE_DIR}. " + "Set ASCEND_HOME_PATH or ASCEND_TOOLKIT_HOME.") +endif() + +# Find Ascend runtime and operator API libraries +find_library(ASCENDCL_LIB ascendcl HINTS ${ASCEND_LIB_DIR}) +if(NOT ASCENDCL_LIB) + message(FATAL_ERROR "libascendcl.so not found in ${ASCEND_LIB_DIR}") +endif() + +find_library(OPAPI_LIB opapi HINTS ${ASCEND_LIB_DIR}) +if(NOT OPAPI_LIB) + message(FATAL_ERROR "libopapi.so not found in ${ASCEND_LIB_DIR}") +endif() + +find_library(NNOPBASE_LIB nnopbase HINTS ${ASCEND_LIB_DIR}) +if(NOT NNOPBASE_LIB) + message(FATAL_ERROR "libnnopbase.so not found in ${ASCEND_LIB_DIR}") +endif() + +add_executable(npu_perf_suite src/main.cc) +target_include_directories(npu_perf_suite PRIVATE + ${ACL_INCLUDE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/include +) +target_link_libraries(npu_perf_suite + ${ASCENDCL_LIB} + ${OPAPI_LIB} + ${NNOPBASE_LIB} + pthread +) +target_compile_options(npu_perf_suite PRIVATE -Wall -O3) + +install(TARGETS npu_perf_suite RUNTIME DESTINATION bin) + +message(STATUS "") +message(STATUS "Configuration Summary:") +message(STATUS " Project: ${PROJECT_NAME} v${PROJECT_VERSION}") +message(STATUS " Build: ${CMAKE_BUILD_TYPE}") +message(STATUS " Arch: ${ARCH_DIR}") +message(STATUS " Include: ${ACL_INCLUDE_DIR}") +message(STATUS " ascendcl: ${ASCENDCL_LIB}") +message(STATUS " opapi: ${OPAPI_LIB}") +message(STATUS " nnopbase: ${NNOPBASE_LIB}") +message(STATUS "") diff --git a/infinibench/hardware/ascend-memory-benchmark/build.sh b/infinibench/hardware/ascend-memory-benchmark/build.sh new file mode 100755 index 00000000..cacad948 --- /dev/null +++ b/infinibench/hardware/ascend-memory-benchmark/build.sh @@ -0,0 +1,68 @@ +#!/bin/bash +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +echo "==========================================" +echo " NPU Performance Suite - Build Script" +echo "==========================================" +echo "" + +# Detect CANN installation +if [ -z "${ASCEND_HOME_PATH}" ] && [ -z "${ASCEND_TOOLKIT_HOME}" ]; then + # Try common locations + for dir in /usr/local/Ascend/ascend-toolkit/latest \ + /usr/local/Ascend/ascend-toolkit/latest/*/ascend-toolkit/latest; do + if [ -d "$dir" ]; then + export ASCEND_HOME_PATH="$dir" + break + fi + done + if [ -z "${ASCEND_HOME_PATH}" ]; then + echo -e "${RED}ERROR: CANN toolkit not found.${NC}" + echo "Set ASCEND_HOME_PATH or ASCEND_TOOLKIT_HOME environment variable." + echo "Example: export ASCEND_TOOLKIT_HOME=/usr/local/Ascend/ascend-toolkit/latest" + exit 1 + fi +fi + +# Check for g++ +if ! command -v g++ &> /dev/null; then + echo -e "${RED}ERROR: g++ not found.${NC}" + exit 1 +fi + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BUILD_DIR="${SCRIPT_DIR}/build" + +echo -e "${YELLOW}Creating build directory...${NC}" +mkdir -p "${BUILD_DIR}" +cd "${BUILD_DIR}" + +echo -e "${YELLOW}Configuring with CMake...${NC}" +cmake .. -DCMAKE_BUILD_TYPE=Release + +echo -e "${YELLOW}Building...${NC}" +make -j$(nproc) + +if [ $? -eq 0 ]; then + echo "" + echo -e "${GREEN}Build succeeded!${NC}" + echo "" + echo "Executable: ${BUILD_DIR}/npu_perf_suite" + echo "" + echo "Usage:" + echo " ${BUILD_DIR}/npu_perf_suite --all" + echo " ${BUILD_DIR}/npu_perf_suite --memory" + echo " ${BUILD_DIR}/npu_perf_suite --stream" + echo " ${BUILD_DIR}/npu_perf_suite --cache" + echo "" +else + echo "" + echo -e "${RED}Build failed!${NC}" + echo "Please check the error messages above." + exit 1 +fi diff --git a/infinibench/hardware/ascend-memory-benchmark/include/acl_utils.h b/infinibench/hardware/ascend-memory-benchmark/include/acl_utils.h new file mode 100644 index 00000000..4ebf3b36 --- /dev/null +++ b/infinibench/hardware/ascend-memory-benchmark/include/acl_utils.h @@ -0,0 +1,226 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace npu_perf { + +// ACL error checking macro +#define ACL_CHECK(call) \ + do { \ + aclError ret = call; \ + if (ret != ACL_SUCCESS) { \ + std::ostringstream oss; \ + oss << "ACL error at " << __FILE__ << ":" << __LINE__ \ + << ": error code=" << ret; \ + throw std::runtime_error(oss.str()); \ + } \ + } while(0) + +// Host-side high resolution timer +class Timer { +public: + using Clock = std::chrono::high_resolution_clock; + using TimePoint = std::chrono::time_point; + + Timer() : start_(Clock::now()) {} + void reset() { start_ = Clock::now(); } + + double elapsed_seconds() const { + return std::chrono::duration(Clock::now() - start_).count(); + } + double elapsed_ms() const { return elapsed_seconds() * 1000.0; } + +private: + TimePoint start_; +}; + +// Statistics collector +class PerfMetrics { +public: + void add(double v) { samples_.push_back(v); } + + double mean() const { + if (samples_.empty()) return 0.0; + return std::accumulate(samples_.begin(), samples_.end(), 0.0) / samples_.size(); + } + + double trimmed_mean() const { + if (samples_.size() <= 2) return mean(); + auto s = samples_; + std::sort(s.begin(), s.end()); + return std::accumulate(s.begin() + 1, s.end() - 1, 0.0) / (s.size() - 2); + } + + double min_val() const { + if (samples_.empty()) return 0.0; + return *std::min_element(samples_.begin(), samples_.end()); + } + + double max_val() const { + if (samples_.empty()) return 0.0; + return *std::max_element(samples_.begin(), samples_.end()); + } + + double cv() const { + double avg = mean(); + if (avg == 0.0) return 0.0; + double var = 0.0; + for (double v : samples_) var += (v - avg) * (v - avg); + var /= samples_.size(); + return std::sqrt(var) / avg; + } + + size_t count() const { return samples_.size(); } + +private: + std::vector samples_; +}; + +struct TestConfig { + int warmup_iterations = 5; + int measure_iterations = 10; + int device_id = 0; + bool verbose = true; +}; + +// RAII wrapper for ACL device memory +class AclDeviceBuffer { +public: + AclDeviceBuffer() : data_(nullptr), size_(0) {} + explicit AclDeviceBuffer(size_t bytes) : data_(nullptr), size_(bytes) { + if (bytes > 0) { + ACL_CHECK(aclrtMalloc(&data_, size_, ACL_MEM_MALLOC_HUGE_FIRST)); + } + } + ~AclDeviceBuffer() { + if (data_) aclrtFree(data_); + } + + AclDeviceBuffer(const AclDeviceBuffer&) = delete; + AclDeviceBuffer& operator=(const AclDeviceBuffer&) = delete; + + AclDeviceBuffer(AclDeviceBuffer&& o) noexcept : data_(o.data_), size_(o.size_) { + o.data_ = nullptr; o.size_ = 0; + } + AclDeviceBuffer& operator=(AclDeviceBuffer&& o) noexcept { + if (this != &o) { + if (data_) aclrtFree(data_); + data_ = o.data_; size_ = o.size_; + o.data_ = nullptr; o.size_ = 0; + } + return *this; + } + + void* data() { return data_; } + const void* data() const { return data_; } + size_t size() const { return size_; } + bool is_valid() const { return data_ != nullptr; } + +private: + void* data_; + size_t size_; +}; + +// RAII wrapper for ACL host (pinned) memory +class AclHostBuffer { +public: + AclHostBuffer() : data_(nullptr), size_(0) {} + explicit AclHostBuffer(size_t bytes) : data_(nullptr), size_(bytes) { + if (bytes > 0) { + ACL_CHECK(aclrtMallocHost(&data_, size_)); + } + } + ~AclHostBuffer() { + if (data_) aclrtFreeHost(data_); + } + + AclHostBuffer(const AclHostBuffer&) = delete; + AclHostBuffer& operator=(const AclHostBuffer&) = delete; + + AclHostBuffer(AclHostBuffer&& o) noexcept : data_(o.data_), size_(o.size_) { + o.data_ = nullptr; o.size_ = 0; + } + AclHostBuffer& operator=(AclHostBuffer&& o) noexcept { + if (this != &o) { + if (data_) aclrtFreeHost(data_); + data_ = o.data_; size_ = o.size_; + o.data_ = nullptr; o.size_ = 0; + } + return *this; + } + + void* data() { return data_; } + const void* data() const { return data_; } + size_t size() const { return size_; } + bool is_valid() const { return data_ != nullptr; } + +private: + void* data_; + size_t size_; +}; + +// RAII wrapper for ACL stream +class AclStream { +public: + AclStream() : stream_(nullptr) { + ACL_CHECK(aclrtCreateStream(&stream_)); + } + ~AclStream() { + if (stream_) aclrtDestroyStream(stream_); + } + + AclStream(const AclStream&) = delete; + AclStream& operator=(const AclStream&) = delete; + + void sync() { + ACL_CHECK(aclrtSynchronizeStream(stream_)); + } + + aclrtStream get() const { return stream_; } + +private: + aclrtStream stream_; +}; + +// Device info +struct NpuDeviceInfo { + static void print(int device_id = 0) { + size_t free_mem = 0, total_mem = 0; + ACL_CHECK(aclrtGetMemInfo(ACL_HBM_MEM, &free_mem, &total_mem)); + + std::cout << "Device " << device_id << ": Ascend NPU\n"; + std::cout << " Total HBM Memory: " + << (total_mem / 1024.0 / 1024.0 / 1024.0) << " GB\n"; + std::cout << " Free HBM Memory: " + << (free_mem / 1024.0 / 1024.0 / 1024.0) << " GB\n"; + } +}; + +inline int get_device_count() { + uint32_t count = 0; + ACL_CHECK(aclrtGetDeviceCount(&count)); + return static_cast(count); +} + +// ACL initialization guard (call once per process) +class AclInitGuard { +public: + AclInitGuard() { + ACL_CHECK(aclInit(nullptr)); + } + ~AclInitGuard() { + aclFinalize(); + } +}; + +} // namespace npu_perf diff --git a/infinibench/hardware/ascend-memory-benchmark/include/cache_benchmark.h b/infinibench/hardware/ascend-memory-benchmark/include/cache_benchmark.h new file mode 100644 index 00000000..163fc60b --- /dev/null +++ b/infinibench/hardware/ascend-memory-benchmark/include/cache_benchmark.h @@ -0,0 +1,101 @@ +#pragma once + +#include "acl_utils.h" + +namespace npu_perf { + +// Sweep D2D memcpy sizes through the ACL runtime. This measures the copy path +// and does not claim to isolate any AI Core memory level. + +class D2DMemcpySizeSweepTest { +public: + void execute(const TestConfig& cfg = TestConfig()) { + ACL_CHECK(aclrtSetDevice(cfg.device_id)); + + AclStream queue; + int warmup = cfg.warmup_iterations; + int measure = cfg.measure_iterations; + const int repeat = 10; + + std::cout << "\n===================================================\n"; + std::cout << "D2D Memcpy Size Sweep Test\n"; + std::cout << "===================================================\n\n"; + + std::cout << std::left << std::setw(13) << "data set" + << std::setw(12) << "exec data" + << std::right << std::setw(12) << "exec time" + << std::setw(11) << "spread" + << std::setw(15) << "Eff. bw\n"; + std::cout << std::string(63, '-') << "\n"; + + // Sweep from 4KB to 256MB + std::vector sizes_kb; + for (size_t s = 4; s <= 512; s *= 2) sizes_kb.push_back(s); + for (size_t s = 1024; s <= 8192; s *= 2) sizes_kb.push_back(s); + for (size_t s = 10240; s <= 65536; s += 4096) sizes_kb.push_back(s); + for (size_t s = 65536; s <= 262144; s *= 2) sizes_kb.push_back(s); + + size_t max_bytes = 512ULL * 1024 * 1024; + AclDeviceBuffer src(max_bytes); + AclDeviceBuffer dst(max_bytes); + + // Initialize buffers + AclHostBuffer h_buf(max_bytes); + memset(h_buf.data(), 0xCD, max_bytes); + ACL_CHECK(aclrtMemcpy(src.data(), max_bytes, h_buf.data(), + max_bytes, ACL_MEMCPY_HOST_TO_DEVICE)); + ACL_CHECK(aclrtMemcpy(dst.data(), max_bytes, h_buf.data(), + max_bytes, ACL_MEMCPY_HOST_TO_DEVICE)); + queue.sync(); + + for (size_t skb : sizes_kb) { + size_t bytes = skb * 1024; + if (bytes > max_bytes) break; + + // Warmup + for (int i = 0; i < warmup; ++i) { + for (int r = 0; r < repeat; ++r) { + ACL_CHECK(aclrtMemcpyAsync(dst.data(), max_bytes, src.data(), + bytes, ACL_MEMCPY_DEVICE_TO_DEVICE, queue.get())); + } + queue.sync(); + } + + // Measure + PerfMetrics time_metrics; + for (int i = 0; i < measure; ++i) { + queue.sync(); + auto t0 = std::chrono::high_resolution_clock::now(); + for (int r = 0; r < repeat; ++r) { + ACL_CHECK(aclrtMemcpyAsync(dst.data(), max_bytes, src.data(), + bytes, ACL_MEMCPY_DEVICE_TO_DEVICE, queue.get())); + } + queue.sync(); + auto t1 = std::chrono::high_resolution_clock::now(); + double ms = std::chrono::duration(t1 - t0).count(); + time_metrics.add(ms); + } + + double avg_time_ms = time_metrics.trimmed_mean(); + double total_data = 2.0 * bytes * repeat; + double bw_gbps = total_data / (avg_time_ms / 1e3) / 1e9; + + std::cout << std::fixed << std::setprecision(0); + std::cout << std::left << std::setw(13) + << std::to_string(bytes / 1024) + " kB"; + std::cout << std::setw(12) + << std::to_string(bytes * repeat / 1024) + " kB"; + std::cout << std::right << std::setw(12) + << std::setprecision(0) << avg_time_ms << "ms"; + std::cout << std::setprecision(1) << std::setw(11) + << (time_metrics.cv() * 100.0) << "%"; + std::cout << std::setprecision(1) << std::setw(15) + << bw_gbps << " GB/s"; + std::cout << "\n"; + } + + std::cout << "\n"; + } +}; + +} // namespace npu_perf diff --git a/infinibench/hardware/ascend-memory-benchmark/include/memory_bandwidth_test.h b/infinibench/hardware/ascend-memory-benchmark/include/memory_bandwidth_test.h new file mode 100644 index 00000000..95950f59 --- /dev/null +++ b/infinibench/hardware/ascend-memory-benchmark/include/memory_bandwidth_test.h @@ -0,0 +1,216 @@ +#pragma once + +#include "acl_utils.h" + +namespace npu_perf { + +class MemoryBandwidthTest { +public: + void execute(const TestConfig& cfg = TestConfig()) { + ACL_CHECK(aclrtSetDevice(cfg.device_id)); + + const int warmup = cfg.warmup_iterations; + const int measure = cfg.measure_iterations; + + const std::vector sizes_kb = { + 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, + 32768, 65536, 131072, 262144, 524288, 1048576 + }; + const size_t max_bytes = sizes_kb.back() * 1024; + + AclStream queue; + + AclHostBuffer host_src(max_bytes); + AclHostBuffer host_dst(max_bytes); + AclDeviceBuffer dev1(max_bytes); + AclDeviceBuffer dev2(max_bytes); + + memset(host_src.data(), 0xAB, max_bytes); + memset(host_dst.data(), 0, max_bytes); + ACL_CHECK(aclrtMemcpy(dev1.data(), max_bytes, host_src.data(), max_bytes, + ACL_MEMCPY_HOST_TO_DEVICE)); + ACL_CHECK(aclrtMemcpy(dev2.data(), max_bytes, host_src.data(), max_bytes, + ACL_MEMCPY_HOST_TO_DEVICE)); + + auto print_table_header = [&]() { + std::cout << std::left << std::setw(15) << "Size (MB)" + << std::right << std::setw(12) << "Time (ms)" + << std::setw(18) << "Bandwidth (GB/s)" + << std::setw(10) << "CV (%)\n"; + std::cout << std::string(55, '-') << "\n"; + }; + + // ---- H2D ---- + std::cout << "\n===================================================\n"; + std::cout << "Memory Copy Bandwidth Sweep Test\n"; + std::cout << "Direction: Host to Device\n"; + std::cout << "===================================================\n\n"; + print_table_header(); + for (size_t skb : sizes_kb) { + size_t bytes = skb * 1024; + if (bytes > max_bytes) break; + + for (int i = 0; i < warmup; ++i) { + ACL_CHECK(aclrtMemcpyAsync(dev1.data(), max_bytes, host_src.data(), + bytes, ACL_MEMCPY_HOST_TO_DEVICE, queue.get())); + queue.sync(); + } + + PerfMetrics bw; + for (int i = 0; i < measure; ++i) { + queue.sync(); + auto t0 = std::chrono::high_resolution_clock::now(); + ACL_CHECK(aclrtMemcpyAsync(dev1.data(), max_bytes, host_src.data(), + bytes, ACL_MEMCPY_HOST_TO_DEVICE, queue.get())); + queue.sync(); + auto t1 = std::chrono::high_resolution_clock::now(); + double sec = std::chrono::duration(t1 - t0).count(); + bw.add((bytes / 1e9) / sec); + } + + double avg_bw = bw.trimmed_mean(); + double avg_time_ms = (bytes / 1e9) / avg_bw * 1000; + std::cout << std::fixed << std::setprecision(2); + std::cout << std::left << std::setw(15) << (bytes / 1024.0 / 1024.0); + std::cout << std::right << std::setw(12) << std::setprecision(3) << avg_time_ms; + std::cout << std::setw(18) << std::setprecision(2) << avg_bw; + std::cout << std::setw(10) << std::setprecision(1) + << (bw.cv() * 100.0) << "\n"; + } + std::cout << "\n"; + + // ---- D2H ---- + std::cout << "===================================================\n"; + std::cout << "Memory Copy Bandwidth Sweep Test\n"; + std::cout << "Direction: Device to Host\n"; + std::cout << "===================================================\n\n"; + print_table_header(); + for (size_t skb : sizes_kb) { + size_t bytes = skb * 1024; + if (bytes > max_bytes) break; + + for (int i = 0; i < warmup; ++i) { + ACL_CHECK(aclrtMemcpyAsync(host_dst.data(), max_bytes, dev1.data(), + bytes, ACL_MEMCPY_DEVICE_TO_HOST, queue.get())); + queue.sync(); + } + + PerfMetrics bw; + for (int i = 0; i < measure; ++i) { + queue.sync(); + auto t0 = std::chrono::high_resolution_clock::now(); + ACL_CHECK(aclrtMemcpyAsync(host_dst.data(), max_bytes, dev1.data(), + bytes, ACL_MEMCPY_DEVICE_TO_HOST, queue.get())); + queue.sync(); + auto t1 = std::chrono::high_resolution_clock::now(); + double sec = std::chrono::duration(t1 - t0).count(); + bw.add((bytes / 1e9) / sec); + } + + double avg_bw = bw.trimmed_mean(); + double avg_time_ms = (bytes / 1e9) / avg_bw * 1000; + std::cout << std::fixed << std::setprecision(2); + std::cout << std::left << std::setw(15) << (bytes / 1024.0 / 1024.0); + std::cout << std::right << std::setw(12) << std::setprecision(3) << avg_time_ms; + std::cout << std::setw(18) << std::setprecision(2) << avg_bw; + std::cout << std::setw(10) << std::setprecision(1) + << (bw.cv() * 100.0) << "\n"; + } + std::cout << "\n"; + + // ---- D2D ---- + std::cout << "===================================================\n"; + std::cout << "Memory Copy Bandwidth Sweep Test\n"; + std::cout << "Direction: Device to Device\n"; + std::cout << "===================================================\n\n"; + std::cout << "NOTE: Small sizes may reflect cache bandwidth, not DRAM bandwidth.\n\n"; + print_table_header(); + for (size_t skb : sizes_kb) { + size_t bytes = skb * 1024; + if (bytes > max_bytes) break; + + for (int i = 0; i < warmup; ++i) { + ACL_CHECK(aclrtMemcpyAsync(dev2.data(), max_bytes, dev1.data(), + bytes, ACL_MEMCPY_DEVICE_TO_DEVICE, queue.get())); + queue.sync(); + } + + PerfMetrics bw; + for (int i = 0; i < measure; ++i) { + queue.sync(); + auto t0 = std::chrono::high_resolution_clock::now(); + ACL_CHECK(aclrtMemcpyAsync(dev2.data(), max_bytes, dev1.data(), + bytes, ACL_MEMCPY_DEVICE_TO_DEVICE, queue.get())); + queue.sync(); + auto t1 = std::chrono::high_resolution_clock::now(); + double sec = std::chrono::duration(t1 - t0).count(); + bw.add((bytes / 1e9) / sec); + } + + double avg_bw = bw.trimmed_mean(); + double avg_time_ms = (bytes / 1e9) / avg_bw * 1000; + std::cout << std::fixed << std::setprecision(2); + std::cout << std::left << std::setw(15) << (bytes / 1024.0 / 1024.0); + std::cout << std::right << std::setw(12) << std::setprecision(3) << avg_time_ms; + std::cout << std::setw(18) << std::setprecision(2) << avg_bw; + std::cout << std::setw(10) << std::setprecision(1) + << (bw.cv() * 100.0) << "\n"; + } + std::cout << "\n"; + + // ---- Bidirectional ---- + std::cout << "===================================================\n"; + std::cout << "Memory Copy Bandwidth Sweep Test\n"; + std::cout << "Direction: Bidirectional\n"; + std::cout << "===================================================\n\n"; + print_table_header(); + { + AclStream q1, q2; + + for (size_t skb : sizes_kb) { + size_t bytes = skb * 1024; + if (bytes > max_bytes) break; + + for (int i = 0; i < warmup; ++i) { + ACL_CHECK(aclrtMemcpyAsync(dev1.data(), max_bytes, host_src.data(), + bytes, ACL_MEMCPY_HOST_TO_DEVICE, q1.get())); + ACL_CHECK(aclrtMemcpyAsync(host_dst.data(), max_bytes, dev2.data(), + bytes, ACL_MEMCPY_DEVICE_TO_HOST, q2.get())); + q1.sync(); + q2.sync(); + } + + PerfMetrics bw; + for (int i = 0; i < measure; ++i) { + q1.sync(); + q2.sync(); + auto t0 = std::chrono::high_resolution_clock::now(); + + ACL_CHECK(aclrtMemcpyAsync(dev1.data(), max_bytes, host_src.data(), + bytes, ACL_MEMCPY_HOST_TO_DEVICE, q1.get())); + ACL_CHECK(aclrtMemcpyAsync(host_dst.data(), max_bytes, dev2.data(), + bytes, ACL_MEMCPY_DEVICE_TO_HOST, q2.get())); + + q1.sync(); + q2.sync(); + auto t1 = std::chrono::high_resolution_clock::now(); + + double sec = std::chrono::duration(t1 - t0).count(); + bw.add((2.0 * bytes / 1e9) / sec); + } + + double avg_bw = bw.trimmed_mean(); + double avg_time_ms = (2.0 * bytes / 1e9) / avg_bw * 1000; + std::cout << std::fixed << std::setprecision(2); + std::cout << std::left << std::setw(15) << (bytes / 1024.0 / 1024.0); + std::cout << std::right << std::setw(12) << std::setprecision(3) << avg_time_ms; + std::cout << std::setw(18) << std::setprecision(2) << avg_bw; + std::cout << std::setw(10) << std::setprecision(1) + << (bw.cv() * 100.0) << "\n"; + } + } + std::cout << "\n"; + } +}; + +} // namespace npu_perf diff --git a/infinibench/hardware/ascend-memory-benchmark/include/stream_benchmark.h b/infinibench/hardware/ascend-memory-benchmark/include/stream_benchmark.h new file mode 100644 index 00000000..dace9bd7 --- /dev/null +++ b/infinibench/hardware/ascend-memory-benchmark/include/stream_benchmark.h @@ -0,0 +1,289 @@ +#pragma once + +#include "acl_utils.h" + +#include +#include + +#include + +namespace npu_perf { + +class AclTensorDescriptor { +public: + AclTensorDescriptor(void* data, size_t element_count) { + if (element_count > + static_cast(std::numeric_limits::max())) { + throw std::runtime_error("STREAM array is too large"); + } + + dims_[0] = static_cast(element_count); + tensor_ = aclCreateTensor(dims_, 1, ACL_FLOAT, strides_, 0, + ACL_FORMAT_ND, dims_, 1, data); + if (tensor_ == nullptr) { + throw std::runtime_error("aclCreateTensor failed"); + } + } + + ~AclTensorDescriptor() { + if (tensor_ != nullptr) { + aclDestroyTensor(tensor_); + } + } + + AclTensorDescriptor(const AclTensorDescriptor&) = delete; + AclTensorDescriptor& operator=(const AclTensorDescriptor&) = delete; + + aclTensor* get() const { return tensor_; } + +private: + int64_t dims_[1] = {0}; + int64_t strides_[1] = {1}; + aclTensor* tensor_ = nullptr; +}; + +class AclScalarDescriptor { +public: + explicit AclScalarDescriptor(float value) : value_(value) { + scalar_ = aclCreateScalar(&value_, ACL_FLOAT); + if (scalar_ == nullptr) { + throw std::runtime_error("aclCreateScalar failed"); + } + } + + ~AclScalarDescriptor() { + if (scalar_ != nullptr) { + aclDestroyScalar(scalar_); + } + } + + AclScalarDescriptor(const AclScalarDescriptor&) = delete; + AclScalarDescriptor& operator=(const AclScalarDescriptor&) = delete; + + aclScalar* get() const { return scalar_; } + +private: + float value_; + aclScalar* scalar_ = nullptr; +}; + +class AclnnOperation { +public: + using ExecuteFn = aclnnStatus (*)(void*, uint64_t, aclOpExecutor*, + aclrtStream); + + AclnnOperation(uint64_t workspace_size, aclOpExecutor* executor, + ExecuteFn execute) + : workspace_size_(workspace_size), executor_(executor), + execute_(execute) { + if (executor_ == nullptr) { + throw std::runtime_error("ACLNN did not create an executor"); + } + + try { + ACL_CHECK(aclSetAclOpExecutorRepeatable(executor_)); + workspace_ = AclDeviceBuffer(workspace_size_); + } catch (...) { + aclDestroyAclOpExecutor(executor_); + executor_ = nullptr; + throw; + } + } + + ~AclnnOperation() { + if (executor_ != nullptr) { + aclDestroyAclOpExecutor(executor_); + } + } + + AclnnOperation(const AclnnOperation&) = delete; + AclnnOperation& operator=(const AclnnOperation&) = delete; + + void run(aclrtStream stream) { + ACL_CHECK(execute_(workspace_.data(), workspace_size_, executor_, + stream)); + } + +private: + uint64_t workspace_size_ = 0; + aclOpExecutor* executor_ = nullptr; + ExecuteFn execute_ = nullptr; + AclDeviceBuffer workspace_; +}; + +class StreamBenchmarkTest { +public: + void execute(size_t array_size, const TestConfig& cfg = TestConfig()) { + ACL_CHECK(aclrtSetDevice(cfg.device_id)); + + AclStream queue; + int warmup = cfg.warmup_iterations; + int measure = cfg.measure_iterations; + + using T = float; + size_t total_bytes = array_size * sizeof(T); + + std::cout << "\n===================================================\n"; + std::cout << "STREAM Benchmark Suite\n"; + std::cout << "Array size: " << (total_bytes / 1024.0 / 1024.0) + << " MB (" << array_size << " elements)\n"; + std::cout << "===================================================\n\n"; + + AclDeviceBuffer d_a(total_bytes); + AclDeviceBuffer d_b(total_bytes); + AclDeviceBuffer d_c(total_bytes); + + AclHostBuffer h_init(total_bytes); + T* h_ptr = static_cast(h_init.data()); + initialize_buffer(d_a, h_ptr, array_size, static_cast(1.0)); + initialize_buffer(d_b, h_ptr, array_size, static_cast(2.0)); + initialize_buffer(d_c, h_ptr, array_size, static_cast(0.0)); + + AclTensorDescriptor tensor_a(d_a.data(), array_size); + AclTensorDescriptor tensor_b(d_b.data(), array_size); + AclTensorDescriptor tensor_c(d_c.data(), array_size); + AclScalarDescriptor scale_scalar(3.0f); + AclScalarDescriptor add_alpha(1.0f); + AclScalarDescriptor triad_alpha(3.0f); + + uint64_t scale_workspace_size = 0; + aclOpExecutor* scale_executor = nullptr; + ACL_CHECK(aclnnMulsGetWorkspaceSize( + tensor_b.get(), scale_scalar.get(), tensor_c.get(), + &scale_workspace_size, &scale_executor)); + AclnnOperation scale_op(scale_workspace_size, scale_executor, + aclnnMuls); + + uint64_t add_workspace_size = 0; + aclOpExecutor* add_executor = nullptr; + ACL_CHECK(aclnnAddGetWorkspaceSize( + tensor_a.get(), tensor_b.get(), add_alpha.get(), tensor_c.get(), + &add_workspace_size, &add_executor)); + AclnnOperation add_op(add_workspace_size, add_executor, aclnnAdd); + + uint64_t triad_workspace_size = 0; + aclOpExecutor* triad_executor = nullptr; + ACL_CHECK(aclnnAddGetWorkspaceSize( + tensor_a.get(), tensor_b.get(), triad_alpha.get(), tensor_c.get(), + &triad_workspace_size, &triad_executor)); + AclnnOperation triad_op(triad_workspace_size, triad_executor, + aclnnAdd); + + std::vector results; + double copy_bytes = static_cast(2 * total_bytes); + results.push_back(measure_operation( + "STREAM_Copy", copy_bytes, warmup, measure, queue, [&]() { + ACL_CHECK(aclrtMemcpyAsync( + d_c.data(), total_bytes, d_b.data(), total_bytes, + ACL_MEMCPY_DEVICE_TO_DEVICE, queue.get())); + })); + validate_output("STREAM_Copy", d_c.data(), array_size, 2.0f); + + double scale_bytes = static_cast(2 * total_bytes); + results.push_back(measure_operation( + "STREAM_Scale", scale_bytes, warmup, measure, queue, + [&]() { scale_op.run(queue.get()); })); + validate_output("STREAM_Scale", d_c.data(), array_size, 6.0f); + + double add_bytes = static_cast(3 * total_bytes); + results.push_back(measure_operation( + "STREAM_Add", add_bytes, warmup, measure, queue, + [&]() { add_op.run(queue.get()); })); + validate_output("STREAM_Add", d_c.data(), array_size, 3.0f); + + double triad_bytes = static_cast(3 * total_bytes); + results.push_back(measure_operation( + "STREAM_Triad", triad_bytes, warmup, measure, queue, + [&]() { triad_op.run(queue.get()); })); + validate_output("STREAM_Triad", d_c.data(), array_size, 7.0f); + + std::cout << std::left << std::setw(16) << "Operation" + << std::right << std::setw(18) << "Bandwidth (GB/s)" + << std::setw(14) << "Time (ms)" + << std::setw(10) << "CV (%)\n"; + std::cout << std::string(58, '-') << "\n"; + for (const auto& result : results) { + std::cout << std::fixed << std::setprecision(2); + std::cout << std::left << std::setw(16) << result.name; + std::cout << std::right << std::setw(18) << result.bandwidth; + std::cout << std::setw(14) << result.time_ms; + std::cout << std::setw(10) << std::setprecision(2) + << result.cv_percent << "\n"; + } + std::cout << "\n"; + } + +private: + struct Result { + std::string name; + double bandwidth; + double time_ms; + double cv_percent; + }; + + static void initialize_buffer(AclDeviceBuffer& device_buffer, + float* host_buffer, size_t element_count, + float value) { + for (size_t i = 0; i < element_count; ++i) { + host_buffer[i] = value; + } + size_t bytes = element_count * sizeof(float); + ACL_CHECK(aclrtMemcpy(device_buffer.data(), bytes, host_buffer, bytes, + ACL_MEMCPY_HOST_TO_DEVICE)); + } + + template + static Result measure_operation(const std::string& name, double bytes, + int warmup, int measure, AclStream& queue, + Operation operation) { + for (int i = 0; i < warmup; ++i) { + operation(); + queue.sync(); + } + + PerfMetrics bandwidth; + for (int i = 0; i < measure; ++i) { + queue.sync(); + auto start = std::chrono::high_resolution_clock::now(); + operation(); + queue.sync(); + auto stop = std::chrono::high_resolution_clock::now(); + double seconds = + std::chrono::duration(stop - start).count(); + bandwidth.add((bytes / 1e9) / seconds); + } + + double average = bandwidth.trimmed_mean(); + double time_ms = average == 0.0 ? 0.0 : (bytes / 1e9) / average * 1000; + return {name, average, time_ms, bandwidth.cv() * 100.0}; + } + + static void validate_output(const std::string& operation, + void* device_data, size_t element_count, + float expected) { + if (element_count == 0) { + throw std::runtime_error("STREAM array size must be positive"); + } + + AclHostBuffer samples(2 * sizeof(float)); + float* sample_values = static_cast(samples.data()); + ACL_CHECK(aclrtMemcpy(sample_values, sizeof(float), device_data, + sizeof(float), ACL_MEMCPY_DEVICE_TO_HOST)); + + const auto* bytes = static_cast(device_data); + const void* last = bytes + (element_count - 1) * sizeof(float); + ACL_CHECK(aclrtMemcpy(sample_values + 1, sizeof(float), last, + sizeof(float), ACL_MEMCPY_DEVICE_TO_HOST)); + + for (int i = 0; i < 2; ++i) { + if (std::fabs(sample_values[i] - expected) > 1e-4f) { + std::ostringstream message; + message << operation << " validation failed: expected " + << expected << ", got " << sample_values[i]; + throw std::runtime_error(message.str()); + } + } + } +}; + +} // namespace npu_perf diff --git a/infinibench/hardware/ascend-memory-benchmark/src/main.cc b/infinibench/hardware/ascend-memory-benchmark/src/main.cc new file mode 100644 index 00000000..ade354a2 --- /dev/null +++ b/infinibench/hardware/ascend-memory-benchmark/src/main.cc @@ -0,0 +1,118 @@ +#include +#include +#include "acl_utils.h" +#include "memory_bandwidth_test.h" +#include "stream_benchmark.h" +#include "cache_benchmark.h" + +using namespace npu_perf; + +void print_banner() { + std::cout << R"( +================================================================ + NPU Performance Benchmark Suite v1.0 + Ascend Memory Testing +================================================================ +)" << std::endl; +} + +void print_usage(const char* prog) { + std::cout << "Usage: " << prog << " [OPTIONS]\n\n" + << "Options:\n" + << " --all Run all tests (default)\n" + << " --memory Run memory bandwidth tests only\n" + << " --stream Run STREAM benchmark suite\n" + << " --cache Run D2D memcpy size sweep test\n" + << " --device Specify NPU device ID (default: 0)\n" + << " --iterations Number of measurement iterations (default: 10)\n" + << " --array-size Array size for STREAM test (default: 67108864)\n" + << " --help Show this help\n"; +} + +struct Config { + bool run_all = true; + bool run_memory = false; + bool run_stream = false; + bool run_cache = false; + int device_id = 0; + int iterations = 10; + size_t array_size = 67108864; +}; + +Config parse_args(int argc, char* argv[]) { + Config cfg; + for (int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + if (arg == "--help" || arg == "-h") { print_usage(argv[0]); exit(0); } + else if (arg == "--all") { cfg.run_all = true; } + else if (arg == "--memory") { cfg.run_all = false; cfg.run_memory = true; } + else if (arg == "--stream") { cfg.run_all = false; cfg.run_stream = true; } + else if (arg == "--cache") { cfg.run_all = false; cfg.run_cache = true; } + else if (arg == "--device" && i + 1 < argc) { cfg.device_id = std::atoi(argv[++i]); } + else if (arg == "--iterations" && i + 1 < argc) { cfg.iterations = std::atoi(argv[++i]); } + else if (arg == "--array-size" && i + 1 < argc) { cfg.array_size = std::atoll(argv[++i]); } + else { std::cerr << "Unknown option: " << arg << "\n"; print_usage(argv[0]); exit(1); } + } + return cfg; +} + +int main(int argc, char* argv[]) { + try { + print_banner(); + Config cfg = parse_args(argc, argv); + + // Initialize ACL + AclInitGuard acl_guard; + + // System info + std::cout << "=== System Information ===\n"; + int dev_count = get_device_count(); + std::cout << "NPU Devices: " << dev_count << "\n"; + + if (cfg.device_id >= dev_count) { + std::cerr << "Error: Device ID " << cfg.device_id << " not available\n"; + return 1; + } + // Set the device first because aclrtGetMemInfo requires an active context. + ACL_CHECK(aclrtSetDevice(cfg.device_id)); + + std::cout << "\n"; + NpuDeviceInfo::print(cfg.device_id); + std::cout << "\n"; + + TestConfig tc; + tc.warmup_iterations = 5; + tc.measure_iterations = cfg.iterations; + tc.device_id = cfg.device_id; + + std::cout << "=== Test Configuration ===\n" + << "Device ID: " << cfg.device_id << "\n" + << "Iterations: " << cfg.iterations << "\n" + << "Stream array size: " << cfg.array_size + << " elements (" << cfg.array_size * sizeof(float) / 1024.0 / 1024.0 << " MB)\n"; + + if (cfg.run_all || cfg.run_memory) { + MemoryBandwidthTest test; + test.execute(tc); + } + + if (cfg.run_all || cfg.run_stream) { + StreamBenchmarkTest test; + test.execute(cfg.array_size, tc); + } + + if (cfg.run_all || cfg.run_cache) { + D2DMemcpySizeSweepTest test; + test.execute(tc); + } + + ACL_CHECK(aclrtResetDevice(cfg.device_id)); + + std::cout << "\nAll tests completed successfully.\n\n"; + return 0; + + } catch (const std::exception& e) { + std::cerr << "\nERROR: " << e.what() << "\n"; + return 1; + } +} diff --git a/infinibench/hardware/constants.py b/infinibench/hardware/constants.py index e2b117f8..ebf99cfd 100644 --- a/infinibench/hardware/constants.py +++ b/infinibench/hardware/constants.py @@ -11,6 +11,8 @@ "moore": "moore", "mthreads": "moore", "musa": "moore", + "ascend": "ascend", + "npu": "ascend", } PLATFORM_CONFIGS = { @@ -44,4 +46,10 @@ "build_platform": "moore", "cache_parser": "cuda", }, + "ascend": { + "binary_name": "npu_perf_suite", + "benchmark_subdir": "ascend-memory-benchmark", + "build_platform": None, + "cache_parser": "ascend", + }, } diff --git a/infinibench/hardware/hardware_adapter.py b/infinibench/hardware/hardware_adapter.py index d2d0c232..f234a069 100644 --- a/infinibench/hardware/hardware_adapter.py +++ b/infinibench/hardware/hardware_adapter.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Hardware test adapter for CUDA-compatible accelerators.""" +"""Hardware test adapter for native and CUDA-compatible accelerators.""" import logging import re @@ -33,6 +33,12 @@ def detect_platform() -> str: """Detect the installed accelerator toolchain.""" + if ( + shutil.which("npu-smi") + or shutil.which("atc") + or Path("/usr/local/Ascend/ascend-toolkit").exists() + ): + return "ascend" if shutil.which("mcc") or shutil.which("mthreads-gmi"): return "moore" @@ -382,10 +388,33 @@ def _parse_stream_benchmark(self, output: str, run_id: str = None) -> List[Dict] def _parse_cache_for_platform( self, output: str, run_id: str, parser_name: str ) -> List[Dict]: + if parser_name == "ascend": + return self._parse_ascend_d2d_size_sweep(output, run_id) if parser_name == "cuda": return self._parse_cache_bandwidth(output, run_id) raise ValueError(f"Unknown cache parser: {parser_name}") + def _parse_ascend_d2d_size_sweep(self, output: str, run_id: str) -> List[Dict]: + """Parse Ascend's ACL D2D memcpy size sweep.""" + match = re.search( + r"D2D Memcpy Size Sweep Test.*?Eff\. bw\s*-+\s*\n(.*?)(?=\Z)", + output, + re.DOTALL, + ) + if not match: + return [] + rows = self._parse_cache_lines(match.group(1), "l2") + if not rows: + return [] + return [ + self._create_timeseries_metric( + "hardware.d2d_memcpy_size_sweep", + rows, + f"d2d_memcpy_size_sweep_{run_id}", + L2_CACHE_CSV_FIELDS, + ) + ] + def _parse_cache_bandwidth(self, output: str, run_id: str) -> List[Dict]: """Parse the existing CUDA L1 and L2 cache output.""" metrics = [] diff --git a/tests/test_hardware_adapter.py b/tests/test_hardware_adapter.py index 4262fab2..b9debe59 100644 --- a/tests/test_hardware_adapter.py +++ b/tests/test_hardware_adapter.py @@ -2,10 +2,9 @@ import pytest -from infinimetrics.dispatcher import Dispatcher -from infinimetrics.hardware import hardware_adapter -from infinimetrics.hardware.hardware_adapter import HardwareTestAdapter - +from infinibench.dispatcher import Dispatcher +from infinibench.hardware import hardware_adapter +from infinibench.hardware.hardware_adapter import HardwareTestAdapter CUDA_OUTPUT = """ Direction: Host to Device @@ -153,6 +152,8 @@ def test_parse_unknown_test_type_returns_no_metrics(tmp_path): ("hygon", "hygon"), ("moore", "moore"), ("musa", "moore"), + ("ascend", "ascend"), + ("npu", "ascend"), ("legacy-unknown-device", "cuda"), ], ) @@ -183,6 +184,18 @@ def test_cuda_compatible_platforms_share_binary(tmp_path, device): assert adapter._get_binary_path(device) == str(cuda_binary) +def test_ascend_uses_native_binary_path(tmp_path): + cuda_binary = tmp_path / "cuda_perf_suite" + adapter = HardwareTestAdapter(str(cuda_binary), output_dir=str(tmp_path)) + + assert Path(adapter._get_binary_path("ascend")).parts[-3:] == ( + "ascend-memory-benchmark", + "build", + "npu_perf_suite", + ) + assert adapter._get_binary_path("cuda") == str(cuda_binary) + + def test_build_cuda_project_preserves_runtime_platform_detection(tmp_path, monkeypatch): adapter = HardwareTestAdapter(output_dir=str(tmp_path)) built_platforms = [] @@ -209,8 +222,111 @@ def test_dispatcher_registers_cudaunified_hardware_framework(): "iluvatar", "hygon", "moore", + "ascend", ], ) def test_dispatcher_does_not_register_devices_as_frameworks(device): with pytest.raises(ValueError, match="Adapter not registered"): Dispatcher()._create_adapter("hardware", device) + + +def test_ascend_d2d_size_sweep_has_copy_specific_metric_name(tmp_path): + output = """ +D2D Memcpy Size Sweep Test +data set exec data exec time spread Eff. bw +--------------------------------------------------------------- +256 kB 2560 kB 1ms 0.5% 300 GB/s +""" + adapter = HardwareTestAdapter(output_dir=str(tmp_path)) + + metrics = adapter._parse_output(output, "Cache", "ascend-run", "ascend") + + assert [metric["name"] for metric in metrics] == ["hardware.d2d_memcpy_size_sweep"] + + +def test_ascend_benchmark_uses_selected_device_id(): + hardware_dir = Path(hardware_adapter.__file__).parent + native_files = list(hardware_dir.glob("ascend-memory-benchmark/include/*.h")) + + assert native_files + for path in native_files: + source = path.read_text(encoding="utf-8") + assert "SetDevice(0)" not in source, path + + +def test_ascend_memory_buffers_match_largest_sweep_case(): + source = ( + Path(hardware_adapter.__file__).parent + / "ascend-memory-benchmark" + / "include" + / "memory_bandwidth_test.h" + ).read_text(encoding="utf-8") + + assert "const size_t max_bytes = sizes_kb.back() * 1024;" in source + assert "2ULL * 1024 * 1024 * 1024" not in source + + +def test_ascend_system_info_only_prints_selected_device(): + source = ( + Path(hardware_adapter.__file__).parent + / "ascend-memory-benchmark" + / "src" + / "main.cc" + ).read_text(encoding="utf-8") + + assert "NpuDeviceInfo::print(cfg.device_id);" in source + assert "NpuDeviceInfo::print(i);" not in source + + +def test_ascend_stream_source_uses_device_arithmetic_operations(): + source = ( + Path(hardware_adapter.__file__).parent + / "ascend-memory-benchmark" + / "include" + / "stream_benchmark.h" + ).read_text(encoding="utf-8") + + assert '"STREAM_Copy"' in source + assert '"STREAM_Scale"' in source + assert '"STREAM_Add"' in source + assert '"STREAM_Triad"' in source + assert "aclnnMulsGetWorkspaceSize" in source + assert "aclnnMuls" in source + assert source.count("aclnnAddGetWorkspaceSize") == 2 + assert "AclScalarDescriptor add_alpha(1.0f)" in source + assert "AclScalarDescriptor triad_alpha(3.0f)" in source + assert "estimated" not in source.lower() + + +def test_ascend_stream_output_publishes_four_metrics(tmp_path): + output = """ +STREAM Benchmark Suite +Operation Bandwidth (GB/s) Time (ms) CV (%) +---------------------------------------------------------- +STREAM_Copy 220.00 0.04 1.00 +STREAM_Scale 210.00 0.04 1.00 +STREAM_Add 200.00 0.06 1.00 +STREAM_Triad 190.00 0.06 1.00 +""" + adapter = HardwareTestAdapter(output_dir=str(tmp_path)) + + metrics = adapter._parse_output(output, "Stream", "ascend-run", "ascend") + + assert [metric["name"] for metric in metrics] == [ + "hardware.stream_copy", + "hardware.stream_scale", + "hardware.stream_add", + "hardware.stream_triad", + ] + + +def test_ascend_bidirectional_copy_uses_distinct_host_buffers(): + source = ( + Path(hardware_adapter.__file__).parent + / "ascend-memory-benchmark" + / "include" + / "memory_bandwidth_test.h" + ).read_text(encoding="utf-8") + + assert "dev1.data(), max_bytes, host_src.data()" in source + assert "host_dst.data(), max_bytes, dev2.data()" in source diff --git a/tests/test_hardware_detection.py b/tests/test_hardware_detection.py index 723cbf6d..3878a8b4 100644 --- a/tests/test_hardware_detection.py +++ b/tests/test_hardware_detection.py @@ -1,10 +1,9 @@ from types import SimpleNamespace -from infinimetrics.common import hardware_info -from infinimetrics.common.hardware_info import HardwareCollector -from infinimetrics.utils import hardware_detector -from infinimetrics.utils.hardware_detector import HardwareDetector - +from infinibench.common import hardware_info +from infinibench.common.hardware_info import HardwareCollector +from infinibench.utils import hardware_detector +from infinibench.utils.hardware_detector import HardwareDetector MTHREADS_OUTPUT = """ Attached GPUs : 2