diff --git a/docs/installation.md b/docs/installation.md index 0ac6f3ff..84256fb5 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -31,9 +31,12 @@ If you plan to use the hardware testing modules, you need to build the CUDA memo ```bash cd infinimetrics/hardware/cuda-memory-benchmark -bash build.sh +bash build.sh --platform cuda ``` +For MetaX, Iluvatar, Hygon, Moore Threads, Cambricon, and Ascend build and +runtime instructions, see [Hardware Benchmarks](../infinimetrics/hardware/README.md). + **Note**: This requires: - CUDA toolkit (compatible with your GPU driver) - C++ compiler with CUDA support diff --git a/docs/plans/2026-07-29-hardware-platform-integration.md b/docs/plans/2026-07-29-hardware-platform-integration.md new file mode 100644 index 00000000..9fc717dd --- /dev/null +++ b/docs/plans/2026-07-29-hardware-platform-integration.md @@ -0,0 +1,104 @@ +# Hardware Platform Integration Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use executing-plans to implement this plan task-by-task. + +**Goal:** Merge `hardware_adapt` and `cambricon_test` into `master` while preserving existing CUDA adapter behavior and adding testable support for MetaX, Iluvatar, Hygon, Moore Threads, Cambricon, and Ascend. + +**Architecture:** Keep `HardwareTestAdapter` as the public entry point and preserve its existing constructor, methods, CUDA command shape, output parsing, and metric names. Add a small platform registry behind the adapter for platform-specific detection, build commands, binary paths, capabilities, and output parsers. CUDA-compatible platforms reuse the CUDA benchmark source; Cambricon and Ascend retain native benchmark directories. + +**Tech Stack:** Python 3.10, pytest, C++17, CUDA/CMake, MetaX cu-bridge, CoreX, DTK/HIP, MUSA, CNToolkit/CNRT/BANG C, CANN/AscendCL. + +--- + +### Task 1: Capture Existing CUDA Behavior + +**Files:** +- Create: `tests/test_hardware_adapter.py` +- Modify: `infinimetrics/hardware/hardware_adapter.py` + +**Steps:** +1. Add tests for the existing constructor argument `cuda_perf_path`, default binary path, `_build_command`, memory parsing, STREAM parsing, cache parsing, and metric names. +2. Run `pytest tests/test_hardware_adapter.py -v` against the unmerged baseline and record passing behavior. +3. Do not change implementation until the compatibility tests pass. + +### Task 2: Merge Source Histories + +**Files:** +- Merge: `origin/hardware_adapt` +- Merge: `origin/cambricon_test` +- Resolve: `infinimetrics/hardware/hardware_adapter.py` +- Modify: `infinimetrics/dispatcher.py` + +**Steps:** +1. Merge `origin/hardware_adapt` with a merge commit. +2. Merge `origin/cambricon_test` without committing. +3. Preserve all non-conflicting native benchmark source directories. +4. Resolve `hardware_adapter.py` using the baseline compatibility tests. +5. Register `cudaunified`, `cuda`, `metax`, `corex`, `iluvatar`, `hygon`, `moore`, `cambricon`, and `ascend` hardware frameworks. + +### Task 3: Add the Platform Registry + +**Files:** +- Modify: `infinimetrics/hardware/hardware_adapter.py` +- Modify: `infinimetrics/common/constants.py` +- Test: `tests/test_hardware_adapter.py` + +**Steps:** +1. Define immutable platform specifications for binary path, build platform, native benchmark directory, supported tests, aliases, and detection probes. +2. Resolve platform in this order: explicit `config.device`, testcase framework, runtime detection, CUDA fallback. +3. Preserve `_build_cuda_project`, `_build_command`, `_execute_test`, and `_parse_output` compatibility wrappers. +4. Preserve existing CUDA metric names: `hardware.mem_sweep_*`, `hardware.stream_*`, and `hardware.gpu_cache_*`. +5. Put platform identity in result configuration instead of metric names. +6. Add tests for every alias, resolution priority, build command, and unsupported capability. + +### Task 4: Repair Native Platform Benchmarks + +**Files:** +- Modify: `infinimetrics/hardware/cambricon-memory-benchmark/CMakeLists.txt` +- Modify: `infinimetrics/hardware/cambricon-memory-benchmark/src/main.mlu` +- Modify: `infinimetrics/hardware/cambricon-memory-benchmark/include/*.h` +- Modify: `infinimetrics/hardware/ascend-memory-benchmark/src/main.cc` +- Modify: `infinimetrics/hardware/ascend-memory-benchmark/include/*.h` +- Test: `tests/test_hardware_adapter.py` + +**Steps:** +1. Pass the selected device ID into every native test instead of resetting to device 0. +2. Wire Cambricon cache flags to the existing NRAM and L2 implementations. +3. Fix the Cambricon CMake source filename. +4. Parse Cambricon cache into the existing cache metric schema. +5. Report Ascend D2D hierarchy sweep separately from CUDA L1/L2 cache metrics. +6. Mark estimated Ascend STREAM operations in result metadata and preserve their existing numeric output. +7. Keep default allocation sizes and CLI defaults unchanged. + +### Task 5: Automated Verification + +**Files:** +- Modify: `tests/test_hardware_adapter.py` +- Create: `test_inputs/configs/hardware.cambricon.Comprehensive.json` +- Create: `test_inputs/configs/hardware.ascend.Comprehensive.json` +- Modify: platform benchmark documentation + +**Steps:** +1. Run `pytest tests/test_hardware_adapter.py -v`. +2. Run the complete repository test suite. +3. Run `git diff --check`. +4. Parse every modified Python file with `compileall`. +5. Validate build scripts with `bash -n`. + +### Task 6: Hardware Verification + +**Steps:** +1. On A100, select one verified idle GPU and build/run CUDA memory, STREAM, cache, and comprehensive modes. +2. On MetaX, Iluvatar, Moore Threads, Cambricon, and Ascend hosts, build in the prepared platform container and run `--help` plus a short STREAM smoke test. +3. Test Hygon when an SSH host or DTK environment is available; otherwise report it explicitly as unverified. +4. Save commit, command, device selection, stdout, stderr, and exit code for every platform. +5. Re-run automated tests after any platform-specific fix. + +### Task 7: Integrate Master + +**Steps:** +1. Review the final diff against `origin/master`. +2. Confirm the original local dirty worktree is untouched. +3. Fetch the latest `origin/master` and merge it if necessary. +4. Push the verified integration commit to `origin/master` with a normal fast-forward push. +5. Report the final commit and per-platform verification matrix. diff --git a/infinimetrics/common/constants.py b/infinimetrics/common/constants.py index 29991b0f..d3c5a4e4 100644 --- a/infinimetrics/common/constants.py +++ b/infinimetrics/common/constants.py @@ -19,6 +19,7 @@ class AcceleratorType(str, Enum): AMD = "amd" # ROCm ASCEND = "ascend" # Huawei NPU CAMBRICON = "cambricon" # Cambricon MLU + MTHREADS = "moore" # Moore Threads MUSA GENERIC = "generic" diff --git a/infinimetrics/common/hardware_info.py b/infinimetrics/common/hardware_info.py index 97e4b321..869b6bff 100644 --- a/infinimetrics/common/hardware_info.py +++ b/infinimetrics/common/hardware_info.py @@ -22,6 +22,11 @@ "default_name": "NVIDIA GPU", "parse_output": True, }, + "moore": { + "command": ["mthreads-gmi", "-q"], + "pattern": r"\bGPU\b|\bProduct\b", + "default_name": "Moore Threads GPU", + }, "amd": { "candidates": ["amd-smi", "rocm-smi"], "pattern": r"\bGPU\b", @@ -99,13 +104,17 @@ def collect(self, accel_type: str = "", device_ids: Any = None) -> Dict[str, Any hw["cuda_version"] = ( self._collect_cuda_version() or hw["cuda_version"] ) + elif probe_type == "moore": + musa_ver = self._collect_musa_version() + if musa_ver: + hw["cuda_version"] = f"MUSA {musa_ver}" return hw return hw def _get_probe_order(self, hint: str) -> List[str]: """Get probe order based on accelerator type hint.""" - order = ["nvidia", "amd", "ascend", "cambricon"] + order = ["nvidia", "moore", "amd", "ascend", "cambricon"] if hint in order: return [hint] + [p for p in order if p != hint] return order @@ -137,6 +146,7 @@ def _probe(self, probe_type: str, hw: Dict[str, Any]) -> ProbeResult: """Generic probe dispatcher.""" probe_methods = { "nvidia": self._probe_nvidia, + "moore": self._probe_mthreads, "amd": self._probe_amd, "ascend": self._probe_generic_command, "cambricon": self._probe_generic_command, @@ -182,6 +192,42 @@ def _probe_amd(self, probe_type: str, hw: Dict[str, Any]) -> ProbeResult: cmd, config["pattern"], config["default_name"], hw ) + def _probe_mthreads(self, probe_type: str, hw: Dict[str, Any]) -> ProbeResult: + """Probe Moore Threads GPU using mthreads-gmi.""" + config = PROBE_CONFIGS["moore"] + if not _which(config["command"][0]): + return ProbeResult(success=False) + + try: + r = subprocess.run( + config["command"], capture_output=True, text=True, timeout=5 + ) + if r.returncode != 0 or not r.stdout.strip(): + return ProbeResult(success=False) + + gpu_count = 0 + gpu_name = None + for line in r.stdout.splitlines(): + stripped = line.strip() + if re.match(r"^GPU\d+\s", stripped): + gpu_count += 1 + if stripped.startswith("Product Name") and ":" in stripped: + gpu_name = stripped.split(":", 1)[1].strip() + + if gpu_count > 0: + hw["gpu_count"] = max(hw["gpu_count"], gpu_count) + if hw["gpu_model"] == "Unknown": + hw["gpu_model"] = gpu_name or config["default_name"] + + # Try to get MUSA version + musa_ver = self._collect_musa_version() + if musa_ver: + hw["cuda_version"] = f"MUSA {musa_ver}" + + return ProbeResult(success=True, count=hw["gpu_count"]) + except Exception: + return ProbeResult(success=False) + def _probe_generic_command( self, probe_type: str, hw: Dict[str, Any] ) -> ProbeResult: @@ -228,6 +274,21 @@ def _collect_cuda_version(self) -> Optional[str]: logger.debug(f"Failed to collect CUDA version: {e}") return None + def _collect_musa_version(self) -> Optional[str]: + """Collect MUSA version using mcc.""" + try: + r = subprocess.run( + ["mcc", "--version"], capture_output=True, text=True, timeout=2 + ) + if r.returncode == 0: + for line in r.stdout.splitlines(): + match = re.search(r"(\d+\.\d+\.\d+)", line) + if match: + return match.group(1) + except Exception: + pass + return None + # Singleton instance for convenience _collector = HardwareCollector() diff --git a/infinimetrics/dispatcher.py b/infinimetrics/dispatcher.py index 75856cc1..d3001d13 100644 --- a/infinimetrics/dispatcher.py +++ b/infinimetrics/dispatcher.py @@ -18,6 +18,14 @@ _ADAPTER_REGISTRY = { (TestCategory.OPERATOR, "infinicore"): lambda: _create_infinicore_adapter(), (TestCategory.HARDWARE, "cudaunified"): lambda: _create_hardware_adapter(), + (TestCategory.HARDWARE, "cuda"): lambda: _create_hardware_adapter(), + (TestCategory.HARDWARE, "metax"): lambda: _create_hardware_adapter(), + (TestCategory.HARDWARE, "corex"): lambda: _create_hardware_adapter(), + (TestCategory.HARDWARE, "iluvatar"): lambda: _create_hardware_adapter(), + (TestCategory.HARDWARE, "hygon"): lambda: _create_hardware_adapter(), + (TestCategory.HARDWARE, "moore"): lambda: _create_hardware_adapter(), + (TestCategory.HARDWARE, "cambricon"): lambda: _create_hardware_adapter(), + (TestCategory.HARDWARE, "ascend"): lambda: _create_hardware_adapter(), (TestCategory.COMM, "nccltest"): lambda: _create_nccltests_adapter(), (TestCategory.INFER, "infinilm"): lambda: _create_inference_adapter(), (TestCategory.INFER, "vllm"): lambda: _create_inference_adapter(), diff --git a/infinimetrics/hardware/README.md b/infinimetrics/hardware/README.md new file mode 100644 index 00000000..d0dffa0d --- /dev/null +++ b/infinimetrics/hardware/README.md @@ -0,0 +1,80 @@ +# Hardware Benchmarks + +InfiniBench provides one hardware adapter for NVIDIA CUDA and six additional +accelerator platforms. Existing CUDA command shapes and metric names are kept +unchanged. + +## Platforms + +| Platform | Testcase framework | Build command | Binary | +| --- | --- | --- | --- | +| NVIDIA CUDA | `cuda`, `cudaUnified` | `bash build.sh --platform cuda` | `cuda-memory-benchmark/build/cuda_perf_suite` | +| MetaX | `metax` | `bash build.sh --platform metax` | `cuda-memory-benchmark/build/cuda_perf_suite` | +| 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` | +| Cambricon | `cambricon` | `bash build.sh` | `cambricon-memory-benchmark/build/mlu_perf_suite` | +| Ascend | `ascend` | `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: + +```bash +./build/ --all +./build/ --memory +./build/ --stream --iterations 3 --array-size 1048576 +./build/ --cache --device 0 +``` + +## Platform Selection + +`HardwareTestAdapter` resolves a platform in this order: + +1. `config.device`, when supplied. +2. The framework in a testcase such as `hardware.cambricon.Stream`. +3. The locally installed accelerator toolchain. +4. CUDA as the compatibility fallback. + +The aliases `nvidia`, `musa`, `mthreads`, `mlu`, and `npu` 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. + +## Device Visibility + +Restrict the process to an idle physical device before starting a benchmark. +The selected physical device is renumbered to device 0 inside the process. + +| Platform | Visibility variable | +| --- | --- | +| NVIDIA, MetaX, Iluvatar | `CUDA_VISIBLE_DEVICES` | +| Hygon | `HIP_VISIBLE_DEVICES` and `ROCR_VISIBLE_DEVICES` | +| Moore Threads | `MUSA_VISIBLE_DEVICES` | +| Cambricon | `MLU_VISIBLE_DEVICES` | +| Ascend | `ASCEND_RT_VISIBLE_DEVICES` | + +For example: + +```bash +CUDA_VISIBLE_DEVICES=2 ./build/cuda_perf_suite --stream --device 0 +MLU_VISIBLE_DEVICES=3 ./build/mlu_perf_suite --stream --device 0 +ASCEND_RT_VISIBLE_DEVICES=4 ./build/npu_perf_suite --stream --device 0 +``` + +## Container Notes + +Hygon DTK containers may require the host driver libraries to be mounted at +the path expected by DTK: + +```bash +docker run --rm --privileged \ + --mount type=bind,source=/opt/hyhal,target=/opt/hyhal,readonly \ + bash +``` + +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/infinimetrics/hardware/ascend-memory-benchmark/CMakeLists.txt b/infinimetrics/hardware/ascend-memory-benchmark/CMakeLists.txt new file mode 100644 index 00000000..d8c58870 --- /dev/null +++ b/infinimetrics/hardware/ascend-memory-benchmark/CMakeLists.txt @@ -0,0 +1,66 @@ +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 ascendcl library +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() + +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} 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 "") diff --git a/infinimetrics/hardware/ascend-memory-benchmark/build.sh b/infinimetrics/hardware/ascend-memory-benchmark/build.sh new file mode 100755 index 00000000..cacad948 --- /dev/null +++ b/infinimetrics/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/infinimetrics/hardware/ascend-memory-benchmark/include/acl_utils.h b/infinimetrics/hardware/ascend-memory-benchmark/include/acl_utils.h new file mode 100644 index 00000000..4ebf3b36 --- /dev/null +++ b/infinimetrics/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/infinimetrics/hardware/ascend-memory-benchmark/include/cache_benchmark.h b/infinimetrics/hardware/ascend-memory-benchmark/include/cache_benchmark.h new file mode 100644 index 00000000..4e70995e --- /dev/null +++ b/infinimetrics/hardware/ascend-memory-benchmark/include/cache_benchmark.h @@ -0,0 +1,105 @@ +#pragma once + +#include "acl_utils.h" + +namespace npu_perf { + +// Ascend NPUs have a different memory hierarchy than GPUs: +// - No L1/L2 cache in the GPU sense +// - Instead: L1 (instruction/data cache per AI Core), L2 (unified buffer), +// and HBM (global memory) +// This test does a D2D bandwidth sweep at different sizes to characterize +// the memory hierarchy, similar to how CUDA L1/L2 tests work. + +class CacheBenchmarkTest { +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 << "Memory Hierarchy Sweep Test (D2D Bandwidth)\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/infinimetrics/hardware/ascend-memory-benchmark/include/memory_bandwidth_test.h b/infinimetrics/hardware/ascend-memory-benchmark/include/memory_bandwidth_test.h new file mode 100644 index 00000000..603086d0 --- /dev/null +++ b/infinimetrics/hardware/ascend-memory-benchmark/include/memory_bandwidth_test.h @@ -0,0 +1,214 @@ +#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 size_t max_bytes = 2ULL * 1024 * 1024 * 1024; // 2 GB + const int warmup = cfg.warmup_iterations; + const int measure = cfg.measure_iterations; + + AclStream queue; + + AclHostBuffer host_buf(max_bytes); + AclDeviceBuffer dev1(max_bytes); + AclDeviceBuffer dev2(max_bytes); + + memset(host_buf.data(), 0xAB, max_bytes); + ACL_CHECK(aclrtMemcpy(dev1.data(), max_bytes, host_buf.data(), max_bytes, + ACL_MEMCPY_HOST_TO_DEVICE)); + ACL_CHECK(aclrtMemcpy(dev2.data(), max_bytes, host_buf.data(), max_bytes, + ACL_MEMCPY_HOST_TO_DEVICE)); + + std::vector sizes_kb = { + 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, + 32768, 65536, 131072, 262144, 524288, 1048576 + }; + + 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_buf.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_buf.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_buf.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_buf.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_buf.data(), + bytes, ACL_MEMCPY_HOST_TO_DEVICE, q1.get())); + ACL_CHECK(aclrtMemcpyAsync(host_buf.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_buf.data(), + bytes, ACL_MEMCPY_HOST_TO_DEVICE, q1.get())); + ACL_CHECK(aclrtMemcpyAsync(host_buf.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/infinimetrics/hardware/ascend-memory-benchmark/include/stream_benchmark.h b/infinimetrics/hardware/ascend-memory-benchmark/include/stream_benchmark.h new file mode 100644 index 00000000..60133193 --- /dev/null +++ b/infinimetrics/hardware/ascend-memory-benchmark/include/stream_benchmark.h @@ -0,0 +1,190 @@ +#pragma once + +#include "acl_utils.h" + +namespace npu_perf { + +// STREAM benchmark using D2D memcpy for Copy. +// Note: STREAM Scale/Add/Triad require device-side compute kernels +// (custom Ascend C operators). This implementation measures Copy bandwidth +// which is the primary indicator of device memory bandwidth. +// Scale/Add/Triad are estimated from Copy bandwidth. + +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); + + // Initialize device buffers + AclHostBuffer h_init(total_bytes); + T* h_ptr = static_cast(h_init.data()); + for (size_t i = 0; i < array_size; ++i) { + h_ptr[i] = static_cast(1.0); + } + ACL_CHECK(aclrtMemcpy(d_a.data(), total_bytes, h_init.data(), + total_bytes, ACL_MEMCPY_HOST_TO_DEVICE)); + + for (size_t i = 0; i < array_size; ++i) { + h_ptr[i] = static_cast(2.0); + } + ACL_CHECK(aclrtMemcpy(d_b.data(), total_bytes, h_init.data(), + total_bytes, ACL_MEMCPY_HOST_TO_DEVICE)); + + for (size_t i = 0; i < array_size; ++i) { + h_ptr[i] = static_cast(0.0); + } + ACL_CHECK(aclrtMemcpy(d_c.data(), total_bytes, h_init.data(), + total_bytes, ACL_MEMCPY_HOST_TO_DEVICE)); + + queue.sync(); + + struct Result { std::string name; double bw; double ms; double cv; }; + std::vector results; + + // ---- STREAM Copy: dst[i] = src[i] (2 * N * sizeof(T) bytes) ---- + { + for (int i = 0; i < warmup; ++i) { + ACL_CHECK(aclrtMemcpyAsync(d_c.data(), total_bytes, d_b.data(), + total_bytes, ACL_MEMCPY_DEVICE_TO_DEVICE, queue.get())); + queue.sync(); + } + PerfMetrics bw_m; + for (int i = 0; i < measure; ++i) { + queue.sync(); + auto t0 = std::chrono::high_resolution_clock::now(); + ACL_CHECK(aclrtMemcpyAsync(d_c.data(), total_bytes, d_b.data(), + total_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_m.add(((double)2 * sizeof(T) * array_size / 1e9) / sec); + } + double avg = bw_m.trimmed_mean(); + results.push_back({"STREAM_Copy", avg, + ((double)2 * sizeof(T) * array_size / 1e9) / avg * 1000, + bw_m.cv() * 100.0}); + } + + // ---- STREAM Scale: dst[i] = scalar * src[i] (2 * N * sizeof(T)) ---- + // Estimated from D2D copy bandwidth (compute is memory-bound) + { + // Scale has same data movement as Copy: 1 read + 1 write + // Without Ascend C custom kernel, we measure D2D copy as approximation + for (int i = 0; i < warmup; ++i) { + ACL_CHECK(aclrtMemcpyAsync(d_c.data(), total_bytes, d_b.data(), + total_bytes, ACL_MEMCPY_DEVICE_TO_DEVICE, queue.get())); + queue.sync(); + } + PerfMetrics bw_m; + for (int i = 0; i < measure; ++i) { + queue.sync(); + auto t0 = std::chrono::high_resolution_clock::now(); + ACL_CHECK(aclrtMemcpyAsync(d_c.data(), total_bytes, d_b.data(), + total_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_m.add(((double)2 * sizeof(T) * array_size / 1e9) / sec); + } + double avg = bw_m.trimmed_mean(); + results.push_back({"STREAM_Scale", avg, + ((double)2 * sizeof(T) * array_size / 1e9) / avg * 1000, + bw_m.cv() * 100.0}); + } + + // ---- STREAM Add: dst[i] = src1[i] + src2[i] (3 * N * sizeof(T)) ---- + // Uses two D2D copies to approximate 2 reads + 1 write + { + for (int i = 0; i < warmup; ++i) { + ACL_CHECK(aclrtMemcpyAsync(d_c.data(), total_bytes, d_a.data(), + total_bytes, ACL_MEMCPY_DEVICE_TO_DEVICE, queue.get())); + ACL_CHECK(aclrtMemcpyAsync(d_c.data(), total_bytes, d_b.data(), + total_bytes, ACL_MEMCPY_DEVICE_TO_DEVICE, queue.get())); + queue.sync(); + } + PerfMetrics bw_m; + for (int i = 0; i < measure; ++i) { + queue.sync(); + auto t0 = std::chrono::high_resolution_clock::now(); + ACL_CHECK(aclrtMemcpyAsync(d_c.data(), total_bytes, d_a.data(), + total_bytes, ACL_MEMCPY_DEVICE_TO_DEVICE, queue.get())); + ACL_CHECK(aclrtMemcpyAsync(d_c.data(), total_bytes, d_b.data(), + total_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(); + // 2 D2D copies = 4 reads + 2 writes total device-side + // Effective bytes for Add: 3 * N * sizeof(T) + bw_m.add(((double)3 * sizeof(T) * array_size / 1e9) / sec); + } + double avg = bw_m.trimmed_mean(); + results.push_back({"STREAM_Add", avg, + ((double)3 * sizeof(T) * array_size / 1e9) / avg * 1000, + bw_m.cv() * 100.0}); + } + + // ---- STREAM Triad: dst[i] = src1[i] + scalar * src2[i] (3 * N * sizeof(T)) ---- + // Same approach as Add (2 D2D copies) + { + for (int i = 0; i < warmup; ++i) { + ACL_CHECK(aclrtMemcpyAsync(d_c.data(), total_bytes, d_a.data(), + total_bytes, ACL_MEMCPY_DEVICE_TO_DEVICE, queue.get())); + ACL_CHECK(aclrtMemcpyAsync(d_c.data(), total_bytes, d_b.data(), + total_bytes, ACL_MEMCPY_DEVICE_TO_DEVICE, queue.get())); + queue.sync(); + } + PerfMetrics bw_m; + for (int i = 0; i < measure; ++i) { + queue.sync(); + auto t0 = std::chrono::high_resolution_clock::now(); + ACL_CHECK(aclrtMemcpyAsync(d_c.data(), total_bytes, d_a.data(), + total_bytes, ACL_MEMCPY_DEVICE_TO_DEVICE, queue.get())); + ACL_CHECK(aclrtMemcpyAsync(d_c.data(), total_bytes, d_b.data(), + total_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_m.add(((double)3 * sizeof(T) * array_size / 1e9) / sec); + } + double avg = bw_m.trimmed_mean(); + results.push_back({"STREAM_Triad", avg, + ((double)3 * sizeof(T) * array_size / 1e9) / avg * 1000, + bw_m.cv() * 100.0}); + } + + 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& r : results) { + std::cout << std::fixed << std::setprecision(2); + std::cout << std::left << std::setw(16) << r.name; + std::cout << std::right << std::setw(18) << r.bw; + std::cout << std::setw(14) << r.ms; + std::cout << std::setw(10) << std::setprecision(2) << r.cv << "\n"; + } + std::cout << "\n" + << "NOTE: Scale/Add/Triad bandwidth is estimated from D2D memcpy.\n" + << " For precise results, use Ascend C custom operators.\n\n"; + } +}; + +} // namespace npu_perf diff --git a/infinimetrics/hardware/ascend-memory-benchmark/src/main.cc b/infinimetrics/hardware/ascend-memory-benchmark/src/main.cc new file mode 100644 index 00000000..b61772f0 --- /dev/null +++ b/infinimetrics/hardware/ascend-memory-benchmark/src/main.cc @@ -0,0 +1,120 @@ +#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 & Cache 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 only\n" + << " --cache Run memory hierarchy 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 device FIRST — aclrtGetMemInfo requires an active context + ACL_CHECK(aclrtSetDevice(cfg.device_id)); + + for (int i = 0; i < dev_count; ++i) { + std::cout << "\n"; + NpuDeviceInfo::print(i); + } + 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) { + CacheBenchmarkTest 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/infinimetrics/hardware/cambricon-memory-benchmark/CMakeLists.txt b/infinimetrics/hardware/cambricon-memory-benchmark/CMakeLists.txt new file mode 100644 index 00000000..c3265954 --- /dev/null +++ b/infinimetrics/hardware/cambricon-memory-benchmark/CMakeLists.txt @@ -0,0 +1,60 @@ +cmake_minimum_required(VERSION 3.18) +project(MluPerfSuite 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 NEUWARE / CNToolkit +if(DEFINED ENV{NEUWARE_HOME}) + set(NEUWARE_HOME $ENV{NEUWARE_HOME}) +else() + set(NEUWARE_HOME "/usr/local/neuware") +endif() +message(STATUS "NEUWARE_HOME: ${NEUWARE_HOME}") + +# Find cncc compiler (used as C++ compiler for Cambricon) +find_program(CNCC cncc HINTS ${NEUWARE_HOME}/bin) +if(NOT CNCC) + message(FATAL_ERROR "cncc not found. Set NEUWARE_HOME.") +endif() +message(STATUS "Found cncc: ${CNCC}") + +# Find CNRT library +find_library(CNRT_LIB cnrt HINTS ${NEUWARE_HOME}/lib64 ${NEUWARE_HOME}/lib) +if(NOT CNRT_LIB) + message(FATAL_ERROR "libcnrt not found in ${NEUWARE_HOME}") +endif() + +# Find CNRT headers +find_path(CNRT_INCLUDE_DIR NAMES cnrt.h HINTS ${NEUWARE_HOME}/include) +if(NOT CNRT_INCLUDE_DIR) + message(FATAL_ERROR "cnrt.h not found in ${NEUWARE_HOME}/include") +endif() + +add_executable(mlu_perf_suite src/main.mlu) +target_include_directories(mlu_perf_suite PRIVATE + ${CNRT_INCLUDE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/include +) +target_link_libraries(mlu_perf_suite ${CNRT_LIB} pthread) + +# Use cncc as compiler +set_source_files_properties(src/main.mlu PROPERTIES LANGUAGE CXX) +set_target_properties(mlu_perf_suite PROPERTIES + CXX_COMPILER_LAUNCHER ${CNCC} + RULE_LAUNCH_COMPILE "${CNCC}" +) + +install(TARGETS mlu_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 " cncc: ${CNCC}") +message(STATUS " CNRT: ${CNRT_LIB}") +message(STATUS "") diff --git a/infinimetrics/hardware/cambricon-memory-benchmark/build.sh b/infinimetrics/hardware/cambricon-memory-benchmark/build.sh new file mode 100755 index 00000000..17517f1a --- /dev/null +++ b/infinimetrics/hardware/cambricon-memory-benchmark/build.sh @@ -0,0 +1,54 @@ +#!/bin/bash +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +echo "==========================================" +echo " MLU Performance Suite - Build Script" +echo "==========================================" +echo "" + +if ! command -v cncc &> /dev/null; then + echo -e "${RED}ERROR: cncc not found. Install CNToolkit.${NC}" + exit 1 +fi + +if [ -z "${NEUWARE_HOME}" ]; then + export NEUWARE_HOME="/usr/local/neuware" +fi + +# MLU architecture - must be set for device kernel compilation +if [ -z "${MLU_ARCH}" ]; then + MLU_ARCH="mtp_592" +fi + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BUILD_DIR="${SCRIPT_DIR}/build" + +mkdir -p "${BUILD_DIR}" + +echo -e "${YELLOW}Compiling (arch=${MLU_ARCH})...${NC}" +cncc "${SCRIPT_DIR}/src/main.mlu" \ + -o "${BUILD_DIR}/mlu_perf_suite" \ + -O3 -std=c++17 \ + --bang-mlu-arch="${MLU_ARCH}" \ + -I"${NEUWARE_HOME}/include" \ + -I"${SCRIPT_DIR}/include" \ + -L"${NEUWARE_HOME}/lib64" \ + -lcnrt -lstdc++ -lm + +echo "" +echo -e "${GREEN}Build succeeded!${NC}" +echo "" +echo "Executable: ${BUILD_DIR}/mlu_perf_suite" +echo "" +echo "Usage:" +echo " ${BUILD_DIR}/mlu_perf_suite --all" +echo " ${BUILD_DIR}/mlu_perf_suite --memory" +echo " ${BUILD_DIR}/mlu_perf_suite --stream" +echo " ${BUILD_DIR}/mlu_perf_suite --cache" +echo " MLU_ARCH=mtp_592 ./build.sh # override arch" +echo "" diff --git a/infinimetrics/hardware/cambricon-memory-benchmark/include/cache_benchmark.h b/infinimetrics/hardware/cambricon-memory-benchmark/include/cache_benchmark.h new file mode 100644 index 00000000..b2e0c56a --- /dev/null +++ b/infinimetrics/hardware/cambricon-memory-benchmark/include/cache_benchmark.h @@ -0,0 +1,306 @@ +#pragma once + +#include "cnrt_utils.h" + +namespace mlu_perf { + +#define NRAM_MAX_CB (1024 * 240) +#define ALIGN_CB 128 + +// ============================================================ +// NRAM Bandwidth Kernel (对标 CUDA L1 Cache) +// ============================================================ +// 数据加载到 NRAM 后,用 __bang_add 反复做 NRAM 内的向量加。 +// 和 CUDA L1 测试对齐:只计 reads,用大量 repeat 放大时间。 +// 每个 __bang_add(dst, src0, src1, n) = 2 reads + 1 write + +template +__mlu_global__ void nram_add_kernel(T* dst, const T* src, size_t n, + int repeat) { + __nram__ char nram_raw[NRAM_MAX_CB]; + char* aligned = (char*)(((size_t)nram_raw + ALIGN_CB - 1) & ~(ALIGN_CB - 1)); + size_t usable = NRAM_MAX_CB - (aligned - nram_raw); + // 2 buffers: input + output + size_t chunk = usable / (2 * sizeof(T)); + chunk = (chunk / (ALIGN_CB / sizeof(T))) * (ALIGN_CB / sizeof(T)); + if (chunk == 0) return; + + T* buf_a = (T*)aligned; + T* buf_b = buf_a + chunk; + + size_t per_core = (n + taskDim - 1) / taskDim; + size_t start = taskId * per_core; + size_t end = start + per_core > n ? n : start + per_core; + if (start >= end) return; + + size_t cnt = end - start; + if (cnt > chunk) cnt = chunk; + __memcpy(buf_a, src + start, cnt * sizeof(T), GDRAM2NRAM); + + // Repeat __bang_add alternating between two buffers + for (int r = 0; r < repeat; r++) { + __bang_add(buf_b, buf_a, buf_a, cnt); + __bang_add(buf_a, buf_b, buf_b, cnt); + } + + __memcpy(dst + start, buf_a, cnt * sizeof(T), NRAM2GDRAM); +} + +// ============================================================ +// GDRAM<->NRAM Copy Kernel (用于 L2 Cache 测试) +// ============================================================ + +template +__mlu_global__ void cache_rw_kernel(T* dst, const T* src, size_t n, int repeat) { + __nram__ char nram_raw[NRAM_MAX_CB]; + char* aligned = (char*)(((size_t)nram_raw + ALIGN_CB - 1) & ~(ALIGN_CB - 1)); + size_t usable = NRAM_MAX_CB - (aligned - nram_raw); + size_t chunk = usable / sizeof(T); + chunk = (chunk / (ALIGN_CB / sizeof(T))) * (ALIGN_CB / sizeof(T)); + if (chunk == 0) return; + + T* buf = (T*)aligned; + + size_t per_core = (n + taskDim - 1) / taskDim; + size_t start = taskId * per_core; + size_t end = start + per_core > n ? n : start + per_core; + if (start >= end) return; + + for (int r = 0; r < repeat; r++) { + for (size_t off = start; off < end; off += chunk) { + size_t c = off + chunk > end ? end - off : chunk; + __memcpy(buf, src + off, c * sizeof(T), GDRAM2NRAM); + __memcpy(dst + off, buf, c * sizeof(T), NRAM2GDRAM); + } + } +} + +// ============================================================ +// NRAM Bandwidth Sweep Test (对标 CUDA L1 Cache Sweep) +// ============================================================ + +class NRAMBandwidthTest { +public: + void execute(const TestConfig& cfg = TestConfig()) { + MLU_CHECK(cnrtSetDevice(cfg.device_id)); + + cnrtQueue_t queue; + MLU_CHECK(cnrtQueueCreate(&queue)); + + cnrtDeviceProp_t prop; + MLU_CHECK(cnrtGetDeviceProperties(&prop, cfg.device_id)); + int total_cores = prop.clusterCount * prop.McorePerCluster; + + cnrtDim3_t dim; + dim.x = prop.McorePerCluster; + dim.y = prop.clusterCount; + dim.z = 1; + cnrtFunctionType_t k_type = cnrtFuncTypeUnion1; + + using T = float; + int warmup = cfg.warmup_iterations; + int measure = cfg.measure_iterations; + + std::cout << "\n===================================================\n"; + std::cout << "NRAM Bandwidth Test (BANG Kernel)\n"; + std::cout << "Cores: " << total_cores << "\n"; + std::cout << "===================================================\n\n"; + + // Use max NRAM chunk: 2 buffers in 240KB → ~120KB each + size_t nram_bytes = NRAM_MAX_CB - ALIGN_CB; + size_t chunk = nram_bytes / (2 * sizeof(T)); + chunk = (chunk / (ALIGN_CB / sizeof(T))) * (ALIGN_CB / sizeof(T)); + size_t chunk_bytes = chunk * sizeof(T); + + // Large repeat to amortize per-call overhead + // Aligned with CUDA L1: ~1e9 / ARRAY_N + 2 + size_t repeat_count = 1000000000ULL / chunk + 2; + + void* src = nullptr; + void* dst = nullptr; + MLU_CHECK(cnrtMalloc(&src, chunk_bytes)); + MLU_CHECK(cnrtMalloc(&dst, chunk_bytes)); + + std::cout << "Chunk size per core: " << (chunk_bytes / 1024) << " kB\n"; + std::cout << "Repeat count: " << repeat_count << "\n\n"; + + // Warmup + for (int i = 0; i < warmup; ++i) { + nram_add_kernel<<>>( + (T*)dst, (const T*)src, chunk, (int)repeat_count); + MLU_CHECK(cnrtQueueSync(queue)); + } + + // Measure + PerfMetrics bw_metrics; + for (int i = 0; i < measure; ++i) { + cnrtNotifier_t ns, ne; + MLU_CHECK(cnrtNotifierCreate(&ns)); + MLU_CHECK(cnrtNotifierCreate(&ne)); + + MLU_CHECK(cnrtPlaceNotifier(ns, queue)); + nram_add_kernel<<>>( + (T*)dst, (const T*)src, chunk, (int)repeat_count); + MLU_CHECK(cnrtPlaceNotifier(ne, queue)); + MLU_CHECK(cnrtQueueSync(queue)); + + float us; + MLU_CHECK(cnrtNotifierDuration(ns, ne, &us)); + double sec = us / 1e6; + + // Aligned with CUDA L1: data_volume × grid_count × repeat_count / time + // data_volume = 2 reads × chunk_bytes per iter + double data_volume = 2.0 * chunk_bytes; + double total_bw = data_volume * total_cores * repeat_count / sec / 1e9; + bw_metrics.add(total_bw); + + MLU_CHECK(cnrtNotifierDestroy(ns)); + MLU_CHECK(cnrtNotifierDestroy(ne)); + } + + double avg_bw = bw_metrics.trimmed_mean(); + double avg_time_sec = (2.0 * chunk_bytes * total_cores * repeat_count / 1e9) + / avg_bw; + + // Also compute TFLOPS: 2 adds per iter, each is 1 FLOP per element + double total_flops = 2.0 * chunk * total_cores * repeat_count; + double tflops = total_flops / avg_time_sec / 1e12; + + std::cout << std::left << std::setw(20) << "NRAM chunk/core" + << std::right << std::setw(12) << "Time (ms)" + << std::setw(15) << "Eff. BW (GB/s)" + << std::setw(12) << "TFLOPS" + << std::setw(10) << "Spread\n"; + std::cout << std::string(69, '-') << "\n"; + + std::cout << std::fixed << std::setprecision(1); + std::cout << std::left << std::setw(20) + << std::to_string(chunk_bytes / 1024) + " kB"; + std::cout << std::right << std::setw(12) << std::setprecision(1) + << avg_time_sec * 1000; + std::cout << std::setw(15) << std::setprecision(1) << avg_bw; + std::cout << std::setw(12) << std::setprecision(1) << tflops; + std::cout << std::setw(10) << std::setprecision(1) + << (bw_metrics.cv() * 100.0) << "%\n"; + + cnrtFree(src); + cnrtFree(dst); + MLU_CHECK(cnrtQueueDestroy(queue)); + std::cout << "\n"; + } +}; + +// ============================================================ +// L2 Cache Bandwidth Sweep Test (对标 CUDA L2 Cache Sweep) +// ============================================================ + +class L2CacheBandwidthTest { +public: + void execute(const TestConfig& cfg = TestConfig()) { + MLU_CHECK(cnrtSetDevice(cfg.device_id)); + + cnrtQueue_t queue; + MLU_CHECK(cnrtQueueCreate(&queue)); + + cnrtDeviceProp_t prop; + MLU_CHECK(cnrtGetDeviceProperties(&prop, cfg.device_id)); + int total_cores = prop.clusterCount * prop.McorePerCluster; + size_t l2_bytes = prop.maxL2CacheSize; + + cnrtDim3_t dim; + dim.x = prop.McorePerCluster; + dim.y = prop.clusterCount; + dim.z = 1; + cnrtFunctionType_t k_type = cnrtFuncTypeUnion1; + + using T = float; + const int repeat = 10; + int warmup = cfg.warmup_iterations; + int measure = cfg.measure_iterations; + + std::cout << "\n===================================================\n"; + std::cout << "L2 Cache Bandwidth Sweep Test (BANG Kernel)\n"; + std::cout << "Cores: " << total_cores + << ", L2 Cache: " << (l2_bytes / 1024) << " kB\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 256KB to 128MB + // L2 is ~40MB, so <40MB should show high bandwidth (L2 hit) + // >40MB should show lower bandwidth (L2 miss → DRAM) + std::vector sizes_kb; + for (size_t s = 256; 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 <= 131072; s *= 2) sizes_kb.push_back(s); + + size_t max_bytes = 256ULL * 1024 * 1024; + void* src = nullptr; + void* dst = nullptr; + MLU_CHECK(cnrtMalloc(&src, max_bytes)); + MLU_CHECK(cnrtMalloc(&dst, max_bytes)); + + for (size_t skb : sizes_kb) { + size_t bytes = skb * 1024; + if (bytes > max_bytes) break; + size_t n = bytes / sizeof(T); + + // Warmup + for (int i = 0; i < warmup; ++i) { + cache_rw_kernel<<>>( + (T*)dst, (const T*)src, n, repeat); + MLU_CHECK(cnrtQueueSync(queue)); + } + + // Measure + PerfMetrics time_metrics; + for (int i = 0; i < measure; ++i) { + cnrtNotifier_t ns, ne; + MLU_CHECK(cnrtNotifierCreate(&ns)); + MLU_CHECK(cnrtNotifierCreate(&ne)); + + MLU_CHECK(cnrtPlaceNotifier(ns, queue)); + cache_rw_kernel<<>>( + (T*)dst, (const T*)src, n, repeat); + MLU_CHECK(cnrtPlaceNotifier(ne, queue)); + MLU_CHECK(cnrtQueueSync(queue)); + + float us; + MLU_CHECK(cnrtNotifierDuration(ns, ne, &us)); + time_metrics.add(us / 1e3); // ms + + MLU_CHECK(cnrtNotifierDestroy(ns)); + MLU_CHECK(cnrtNotifierDestroy(ne)); + } + + 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"; + } + + cnrtFree(src); + cnrtFree(dst); + MLU_CHECK(cnrtQueueDestroy(queue)); + std::cout << "\n"; + } +}; + +} // namespace mlu_perf diff --git a/infinimetrics/hardware/cambricon-memory-benchmark/include/cnrt_utils.h b/infinimetrics/hardware/cambricon-memory-benchmark/include/cnrt_utils.h new file mode 100644 index 00000000..fee1b383 --- /dev/null +++ b/infinimetrics/hardware/cambricon-memory-benchmark/include/cnrt_utils.h @@ -0,0 +1,108 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mlu_perf { + +// Own check macro with exceptions (cnrt.h's CNRT_CHECK calls exit) +#define MLU_CHECK(call) \ + do { \ + cnrtRet_t ret = call; \ + if (ret != cnrtSuccess) { \ + std::ostringstream oss; \ + oss << "CNRT error at " << __FILE__ << ":" << __LINE__ \ + << ": " << cnrtGetErrorStr(ret) \ + << " (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 +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 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; + } + +private: + std::vector samples_; +}; + +struct TestConfig { + int warmup_iterations = 5; + int measure_iterations = 10; + int device_id = 0; + bool verbose = true; +}; + +// Device info +struct MluDeviceInfo { + static void print(int device_id = 0) { + cnrtDeviceProp_t prop; + MLU_CHECK(cnrtGetDeviceProperties(&prop, device_id)); + + std::cout << "Device " << device_id << ": " << prop.name << "\n"; + std::cout << " Total Memory: " + << prop.totalMem << " MB\n"; + std::cout << " L2 Cache Size: " + << (prop.maxL2CacheSize / 1024.0) << " KB\n"; + std::cout << " Clusters: " << prop.clusterCount << "\n"; + std::cout << " Cores per Cluster: " << prop.McorePerCluster << "\n"; + std::cout << " Max Block Dim X: " << prop.maxDim[0] << "\n"; + } +}; + +inline int get_device_count() { + unsigned int count; + MLU_CHECK(cnrtGetDeviceCount(&count)); + return static_cast(count); +} + +} // namespace mlu_perf diff --git a/infinimetrics/hardware/cambricon-memory-benchmark/include/memory_bandwidth_test.h b/infinimetrics/hardware/cambricon-memory-benchmark/include/memory_bandwidth_test.h new file mode 100644 index 00000000..6dc6ef07 --- /dev/null +++ b/infinimetrics/hardware/cambricon-memory-benchmark/include/memory_bandwidth_test.h @@ -0,0 +1,258 @@ +#pragma once + +#include "cnrt_utils.h" + +namespace mlu_perf { + +class MemoryBandwidthTest { +public: + void execute(const TestConfig& cfg = TestConfig()) { + MLU_CHECK(cnrtSetDevice(cfg.device_id)); + + const size_t max_bytes = 2ULL * 1024 * 1024 * 1024; // 2 GB + const int warmup = cfg.warmup_iterations; + const int measure = cfg.measure_iterations; + + cnrtQueue_t queue; + MLU_CHECK(cnrtQueueCreate(&queue)); + + void* host_buf; + void* dev1; + void* dev2; + MLU_CHECK(cnrtHostMalloc(&host_buf, max_bytes)); + MLU_CHECK(cnrtMalloc(&dev1, max_bytes)); + MLU_CHECK(cnrtMalloc(&dev2, max_bytes)); + + memset(host_buf, 0xAB, max_bytes); + MLU_CHECK(cnrtMemcpy(dev1, host_buf, max_bytes, cnrtMemcpyHostToDev)); + MLU_CHECK(cnrtMemcpy(dev2, host_buf, max_bytes, cnrtMemcpyHostToDev)); + + std::vector sizes_kb = { + 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, + 32768, 65536, 131072, 262144, 524288, 1048576 + }; + + 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) { + MLU_CHECK(cnrtMemcpyAsync(dev1, host_buf, bytes, queue, cnrtMemcpyHostToDev)); + MLU_CHECK(cnrtQueueSync(queue)); + } + + PerfMetrics bw; + for (int i = 0; i < measure; ++i) { + cnrtNotifier_t ns, ne; + MLU_CHECK(cnrtNotifierCreate(&ns)); + MLU_CHECK(cnrtNotifierCreate(&ne)); + + MLU_CHECK(cnrtPlaceNotifier(ns, queue)); + MLU_CHECK(cnrtMemcpyAsync(dev1, host_buf, bytes, queue, cnrtMemcpyHostToDev)); + MLU_CHECK(cnrtPlaceNotifier(ne, queue)); + MLU_CHECK(cnrtQueueSync(queue)); + + float us; + MLU_CHECK(cnrtNotifierDuration(ns, ne, &us)); + double sec = us / 1e6; + bw.add((bytes / 1e9) / sec); + + MLU_CHECK(cnrtNotifierDestroy(ns)); + MLU_CHECK(cnrtNotifierDestroy(ne)); + } + + 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) { + MLU_CHECK(cnrtMemcpyAsync(host_buf, dev1, bytes, queue, cnrtMemcpyDevToHost)); + MLU_CHECK(cnrtQueueSync(queue)); + } + + PerfMetrics bw; + for (int i = 0; i < measure; ++i) { + cnrtNotifier_t ns, ne; + MLU_CHECK(cnrtNotifierCreate(&ns)); + MLU_CHECK(cnrtNotifierCreate(&ne)); + + MLU_CHECK(cnrtPlaceNotifier(ns, queue)); + MLU_CHECK(cnrtMemcpyAsync(host_buf, dev1, bytes, queue, cnrtMemcpyDevToHost)); + MLU_CHECK(cnrtPlaceNotifier(ne, queue)); + MLU_CHECK(cnrtQueueSync(queue)); + + float us; + MLU_CHECK(cnrtNotifierDuration(ns, ne, &us)); + double sec = us / 1e6; + bw.add((bytes / 1e9) / sec); + + MLU_CHECK(cnrtNotifierDestroy(ns)); + MLU_CHECK(cnrtNotifierDestroy(ne)); + } + + 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) { + MLU_CHECK(cnrtMemcpyAsync(dev2, dev1, bytes, queue, cnrtMemcpyDevToDev)); + MLU_CHECK(cnrtQueueSync(queue)); + } + + PerfMetrics bw; + for (int i = 0; i < measure; ++i) { + cnrtNotifier_t ns, ne; + MLU_CHECK(cnrtNotifierCreate(&ns)); + MLU_CHECK(cnrtNotifierCreate(&ne)); + + MLU_CHECK(cnrtPlaceNotifier(ns, queue)); + MLU_CHECK(cnrtMemcpyAsync(dev2, dev1, bytes, queue, cnrtMemcpyDevToDev)); + MLU_CHECK(cnrtPlaceNotifier(ne, queue)); + MLU_CHECK(cnrtQueueSync(queue)); + + float us; + MLU_CHECK(cnrtNotifierDuration(ns, ne, &us)); + double sec = us / 1e6; + bw.add((bytes / 1e9) / sec); + + MLU_CHECK(cnrtNotifierDestroy(ns)); + MLU_CHECK(cnrtNotifierDestroy(ne)); + } + + 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(); + { + cnrtQueue_t q1, q2; + MLU_CHECK(cnrtQueueCreate(&q1)); + MLU_CHECK(cnrtQueueCreate(&q2)); + + for (size_t skb : sizes_kb) { + size_t bytes = skb * 1024; + if (bytes > max_bytes) break; + + for (int i = 0; i < warmup; ++i) { + MLU_CHECK(cnrtMemcpyAsync(dev1, host_buf, bytes, q1, cnrtMemcpyHostToDev)); + MLU_CHECK(cnrtMemcpyAsync(host_buf, dev2, bytes, q2, cnrtMemcpyDevToHost)); + MLU_CHECK(cnrtQueueSync(q1)); + MLU_CHECK(cnrtQueueSync(q2)); + } + + PerfMetrics bw; + for (int i = 0; i < measure; ++i) { + cnrtNotifier_t ns1, ne1, ns2, ne2; + MLU_CHECK(cnrtNotifierCreate(&ns1)); + MLU_CHECK(cnrtNotifierCreate(&ne1)); + MLU_CHECK(cnrtNotifierCreate(&ns2)); + MLU_CHECK(cnrtNotifierCreate(&ne2)); + + MLU_CHECK(cnrtPlaceNotifier(ns1, q1)); + MLU_CHECK(cnrtMemcpyAsync(dev1, host_buf, bytes, q1, cnrtMemcpyHostToDev)); + MLU_CHECK(cnrtPlaceNotifier(ne1, q1)); + + MLU_CHECK(cnrtPlaceNotifier(ns2, q2)); + MLU_CHECK(cnrtMemcpyAsync(host_buf, dev2, bytes, q2, cnrtMemcpyDevToHost)); + MLU_CHECK(cnrtPlaceNotifier(ne2, q2)); + + MLU_CHECK(cnrtQueueSync(q1)); + MLU_CHECK(cnrtQueueSync(q2)); + + float us1, us2; + MLU_CHECK(cnrtNotifierDuration(ns1, ne1, &us1)); + MLU_CHECK(cnrtNotifierDuration(ns2, ne2, &us2)); + + double sec = std::max(us1, us2) / 1e6; + bw.add((2.0 * bytes / 1e9) / sec); + + MLU_CHECK(cnrtNotifierDestroy(ns1)); + MLU_CHECK(cnrtNotifierDestroy(ne1)); + MLU_CHECK(cnrtNotifierDestroy(ns2)); + MLU_CHECK(cnrtNotifierDestroy(ne2)); + } + + 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"; + } + + MLU_CHECK(cnrtQueueDestroy(q1)); + MLU_CHECK(cnrtQueueDestroy(q2)); + } + + cnrtFreeHost(host_buf); + cnrtFree(dev1); + cnrtFree(dev2); + MLU_CHECK(cnrtQueueDestroy(queue)); + std::cout << "\n"; + } +}; + +} // namespace mlu_perf diff --git a/infinimetrics/hardware/cambricon-memory-benchmark/include/stream_benchmark.h b/infinimetrics/hardware/cambricon-memory-benchmark/include/stream_benchmark.h new file mode 100644 index 00000000..b19c1f9f --- /dev/null +++ b/infinimetrics/hardware/cambricon-memory-benchmark/include/stream_benchmark.h @@ -0,0 +1,318 @@ +#pragma once + +#include "cnrt_utils.h" +#include + +namespace mlu_perf { + +// NRAM: 240KB per core, single buffer manually partitioned +#define NRAM_MAX (1024 * 240) +#define ALIGN 128 + +// ---- init kernel ---- +template +__mlu_global__ void init_kernel(T* a, T* b, T* c, size_t n, + T va, T vb, T vc) { + __nram__ char nram_raw[NRAM_MAX]; + char* aligned = (char*)(((size_t)nram_raw + ALIGN - 1) & ~(ALIGN - 1)); + size_t usable = NRAM_MAX - (aligned - nram_raw); + size_t chunk = usable / (3 * sizeof(T)); + chunk = (chunk / (ALIGN / sizeof(T))) * (ALIGN / sizeof(T)); + if (chunk == 0) return; + + T* na = (T*)aligned; + T* nb = na + chunk; + T* nc = nb + chunk; + + size_t per_core = (n + taskDim - 1) / taskDim; + size_t start = taskId * per_core; + size_t end = start + per_core > n ? n : start + per_core; + + for (size_t off = start; off < end; off += chunk) { + size_t cnt = off + chunk > end ? end - off : chunk; + __bang_write_value(na, cnt, va); + __bang_write_value(nb, cnt, vb); + __bang_write_value(nc, cnt, vc); + __memcpy(a + off, na, cnt * sizeof(T), NRAM2GDRAM); + __memcpy(b + off, nb, cnt * sizeof(T), NRAM2GDRAM); + __memcpy(c + off, nc, cnt * sizeof(T), NRAM2GDRAM); + } +} + +// ---- STREAM Copy: dst[i] = src[i] (2 * N * sizeof(T) bytes moved) ---- +template +__mlu_global__ void stream_copy_kernel(T* dst, const T* src, size_t n) { + __nram__ char nram_raw[NRAM_MAX]; + char* aligned = (char*)(((size_t)nram_raw + ALIGN - 1) & ~(ALIGN - 1)); + size_t usable = NRAM_MAX - (aligned - nram_raw); + size_t chunk = usable / sizeof(T); + chunk = (chunk / (ALIGN / sizeof(T))) * (ALIGN / sizeof(T)); + if (chunk == 0) return; + + T* buf = (T*)aligned; + + size_t per_core = (n + taskDim - 1) / taskDim; + size_t start = taskId * per_core; + size_t end = start + per_core > n ? n : start + per_core; + + for (size_t off = start; off < end; off += chunk) { + size_t cnt = off + chunk > end ? end - off : chunk; + __memcpy(buf, src + off, cnt * sizeof(T), GDRAM2NRAM); + __memcpy(dst + off, buf, cnt * sizeof(T), NRAM2GDRAM); + } +} + +// ---- STREAM Scale: dst[i] = scalar * src[i] (2 * N * sizeof(T)) ---- +template +__mlu_global__ void stream_scale_kernel(T* dst, const T* src, T scalar, size_t n) { + __nram__ char nram_raw[NRAM_MAX]; + char* aligned = (char*)(((size_t)nram_raw + ALIGN - 1) & ~(ALIGN - 1)); + size_t usable = NRAM_MAX - (aligned - nram_raw); + size_t chunk = usable / (2 * sizeof(T)); + chunk = (chunk / (ALIGN / sizeof(T))) * (ALIGN / sizeof(T)); + if (chunk == 0) return; + + T* ns = (T*)aligned; + T* nd = ns + chunk; + + size_t per_core = (n + taskDim - 1) / taskDim; + size_t start = taskId * per_core; + size_t end = start + per_core > n ? n : start + per_core; + + for (size_t off = start; off < end; off += chunk) { + size_t cnt = off + chunk > end ? end - off : chunk; + __memcpy(ns, src + off, cnt * sizeof(T), GDRAM2NRAM); + __bang_mul_scalar(nd, ns, scalar, cnt); + __memcpy(dst + off, nd, cnt * sizeof(T), NRAM2GDRAM); + } +} + +// ---- STREAM Add: dst[i] = src1[i] + src2[i] (3 * N * sizeof(T)) ---- +template +__mlu_global__ void stream_add_kernel(T* dst, const T* src1, const T* src2, size_t n) { + __nram__ char nram_raw[NRAM_MAX]; + char* aligned = (char*)(((size_t)nram_raw + ALIGN - 1) & ~(ALIGN - 1)); + size_t usable = NRAM_MAX - (aligned - nram_raw); + size_t chunk = usable / (3 * sizeof(T)); + chunk = (chunk / (ALIGN / sizeof(T))) * (ALIGN / sizeof(T)); + if (chunk == 0) return; + + T* na = (T*)aligned; + T* nb = na + chunk; + T* nc = nb + chunk; + + size_t per_core = (n + taskDim - 1) / taskDim; + size_t start = taskId * per_core; + size_t end = start + per_core > n ? n : start + per_core; + + for (size_t off = start; off < end; off += chunk) { + size_t cnt = off + chunk > end ? end - off : chunk; + __memcpy(na, src1 + off, cnt * sizeof(T), GDRAM2NRAM); + __memcpy(nb, src2 + off, cnt * sizeof(T), GDRAM2NRAM); + __bang_add(nc, na, nb, cnt); + __memcpy(dst + off, nc, cnt * sizeof(T), NRAM2GDRAM); + } +} + +// ---- STREAM Triad: dst[i] = src1[i] + scalar * src2[i] (3 * N * sizeof(T)) ---- +template +__mlu_global__ void stream_triad_kernel(T* dst, const T* src1, const T* src2, + T scalar, size_t n) { + __nram__ char nram_raw[NRAM_MAX]; + char* aligned = (char*)(((size_t)nram_raw + ALIGN - 1) & ~(ALIGN - 1)); + size_t usable = NRAM_MAX - (aligned - nram_raw); + size_t chunk = usable / (3 * sizeof(T)); + chunk = (chunk / (ALIGN / sizeof(T))) * (ALIGN / sizeof(T)); + if (chunk == 0) return; + + T* na = (T*)aligned; + T* nb = na + chunk; + T* nc = nb + chunk; + + size_t per_core = (n + taskDim - 1) / taskDim; + size_t start = taskId * per_core; + size_t end = start + per_core > n ? n : start + per_core; + + for (size_t off = start; off < end; off += chunk) { + size_t cnt = off + chunk > end ? end - off : chunk; + __memcpy(na, src1 + off, cnt * sizeof(T), GDRAM2NRAM); + __memcpy(nb, src2 + off, cnt * sizeof(T), GDRAM2NRAM); + __bang_mul_scalar(nb, nb, scalar, cnt); + __bang_add(nc, na, nb, cnt); + __memcpy(dst + off, nc, cnt * sizeof(T), NRAM2GDRAM); + } +} + +// ============================================================ +// Benchmark suite +// ============================================================ + +class StreamBenchmarkTest { +public: + void execute(size_t array_size, const TestConfig& cfg = TestConfig()) { + MLU_CHECK(cnrtSetDevice(cfg.device_id)); + + cnrtQueue_t queue; + MLU_CHECK(cnrtQueueCreate(&queue)); + + cnrtDeviceProp_t prop; + MLU_CHECK(cnrtGetDeviceProperties(&prop, cfg.device_id)); + int total_cores = prop.clusterCount * prop.McorePerCluster; + + using T = float; + + cnrtDim3_t dim; + dim.x = prop.McorePerCluster; + dim.y = prop.clusterCount; + dim.z = 1; + cnrtFunctionType_t k_type = cnrtFuncTypeUnion1; + + int warmup = cfg.warmup_iterations; + int measure = cfg.measure_iterations; + + std::cout << "\n===================================================\n"; + std::cout << "STREAM Benchmark Suite\n"; + std::cout << "Array size: " << (array_size * sizeof(T) / 1024.0 / 1024.0) + << " MB (" << array_size << " elements)\n"; + std::cout << "===================================================\n\n"; + + T* d_a; T* d_b; T* d_c; + MLU_CHECK(cnrtMalloc(reinterpret_cast(&d_a), array_size * sizeof(T))); + MLU_CHECK(cnrtMalloc(reinterpret_cast(&d_b), array_size * sizeof(T))); + MLU_CHECK(cnrtMalloc(reinterpret_cast(&d_c), array_size * sizeof(T))); + + init_kernel<<>>(d_a, d_b, d_c, array_size, + (T)1.0, (T)2.0, (T)0.0); + MLU_CHECK(cnrtQueueSync(queue)); + + struct Result { std::string name; double bw; double ms; double cv; }; + std::vector results; + + // --- STREAM Copy --- + { + for (int i = 0; i < warmup; ++i) { + stream_copy_kernel<<>>(d_c, d_b, array_size); + MLU_CHECK(cnrtQueueSync(queue)); + } + PerfMetrics bw_m; + for (int i = 0; i < measure; ++i) { + cnrtNotifier_t ns, ne; + MLU_CHECK(cnrtNotifierCreate(&ns)); + MLU_CHECK(cnrtNotifierCreate(&ne)); + MLU_CHECK(cnrtPlaceNotifier(ns, queue)); + stream_copy_kernel<<>>(d_c, d_b, array_size); + MLU_CHECK(cnrtPlaceNotifier(ne, queue)); + MLU_CHECK(cnrtQueueSync(queue)); + float us; + MLU_CHECK(cnrtNotifierDuration(ns, ne, &us)); + bw_m.add(((double)2 * sizeof(T) * array_size / 1e9) / (us / 1e6)); + MLU_CHECK(cnrtNotifierDestroy(ns)); + MLU_CHECK(cnrtNotifierDestroy(ne)); + } + double avg = bw_m.trimmed_mean(); + results.push_back({"STREAM_Copy", avg, + ((double)2 * sizeof(T) * array_size / 1e9) / avg * 1000, + bw_m.cv() * 100.0}); + } + + // --- STREAM Scale --- + { + for (int i = 0; i < warmup; ++i) { + stream_scale_kernel<<>>(d_c, d_b, (T)3.5, array_size); + MLU_CHECK(cnrtQueueSync(queue)); + } + PerfMetrics bw_m; + for (int i = 0; i < measure; ++i) { + cnrtNotifier_t ns, ne; + MLU_CHECK(cnrtNotifierCreate(&ns)); + MLU_CHECK(cnrtNotifierCreate(&ne)); + MLU_CHECK(cnrtPlaceNotifier(ns, queue)); + stream_scale_kernel<<>>(d_c, d_b, (T)3.5, array_size); + MLU_CHECK(cnrtPlaceNotifier(ne, queue)); + MLU_CHECK(cnrtQueueSync(queue)); + float us; + MLU_CHECK(cnrtNotifierDuration(ns, ne, &us)); + bw_m.add(((double)2 * sizeof(T) * array_size / 1e9) / (us / 1e6)); + MLU_CHECK(cnrtNotifierDestroy(ns)); + MLU_CHECK(cnrtNotifierDestroy(ne)); + } + double avg = bw_m.trimmed_mean(); + results.push_back({"STREAM_Scale", avg, + ((double)2 * sizeof(T) * array_size / 1e9) / avg * 1000, + bw_m.cv() * 100.0}); + } + + // --- STREAM Add --- + { + for (int i = 0; i < warmup; ++i) { + stream_add_kernel<<>>(d_c, d_a, d_b, array_size); + MLU_CHECK(cnrtQueueSync(queue)); + } + PerfMetrics bw_m; + for (int i = 0; i < measure; ++i) { + cnrtNotifier_t ns, ne; + MLU_CHECK(cnrtNotifierCreate(&ns)); + MLU_CHECK(cnrtNotifierCreate(&ne)); + MLU_CHECK(cnrtPlaceNotifier(ns, queue)); + stream_add_kernel<<>>(d_c, d_a, d_b, array_size); + MLU_CHECK(cnrtPlaceNotifier(ne, queue)); + MLU_CHECK(cnrtQueueSync(queue)); + float us; + MLU_CHECK(cnrtNotifierDuration(ns, ne, &us)); + bw_m.add(((double)3 * sizeof(T) * array_size / 1e9) / (us / 1e6)); + MLU_CHECK(cnrtNotifierDestroy(ns)); + MLU_CHECK(cnrtNotifierDestroy(ne)); + } + double avg = bw_m.trimmed_mean(); + results.push_back({"STREAM_Add", avg, + ((double)3 * sizeof(T) * array_size / 1e9) / avg * 1000, + bw_m.cv() * 100.0}); + } + + // --- STREAM Triad --- + { + for (int i = 0; i < warmup; ++i) { + stream_triad_kernel<<>>(d_c, d_a, d_b, (T)3.5, array_size); + MLU_CHECK(cnrtQueueSync(queue)); + } + PerfMetrics bw_m; + for (int i = 0; i < measure; ++i) { + cnrtNotifier_t ns, ne; + MLU_CHECK(cnrtNotifierCreate(&ns)); + MLU_CHECK(cnrtNotifierCreate(&ne)); + MLU_CHECK(cnrtPlaceNotifier(ns, queue)); + stream_triad_kernel<<>>(d_c, d_a, d_b, (T)3.5, array_size); + MLU_CHECK(cnrtPlaceNotifier(ne, queue)); + MLU_CHECK(cnrtQueueSync(queue)); + float us; + MLU_CHECK(cnrtNotifierDuration(ns, ne, &us)); + bw_m.add(((double)3 * sizeof(T) * array_size / 1e9) / (us / 1e6)); + MLU_CHECK(cnrtNotifierDestroy(ns)); + MLU_CHECK(cnrtNotifierDestroy(ne)); + } + double avg = bw_m.trimmed_mean(); + results.push_back({"STREAM_Triad", avg, + ((double)3 * sizeof(T) * array_size / 1e9) / avg * 1000, + bw_m.cv() * 100.0}); + } + + 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& r : results) { + std::cout << std::fixed << std::setprecision(2); + std::cout << std::left << std::setw(16) << r.name; + std::cout << std::right << std::setw(18) << r.bw; + std::cout << std::setw(14) << r.ms; + std::cout << std::setw(10) << std::setprecision(2) << r.cv << "\n"; + } + std::cout << "\n"; + + cnrtFree(d_a); cnrtFree(d_b); cnrtFree(d_c); + MLU_CHECK(cnrtQueueDestroy(queue)); + } +}; + +} // namespace mlu_perf diff --git a/infinimetrics/hardware/cambricon-memory-benchmark/src/main.mlu b/infinimetrics/hardware/cambricon-memory-benchmark/src/main.mlu new file mode 100644 index 00000000..c8295b34 --- /dev/null +++ b/infinimetrics/hardware/cambricon-memory-benchmark/src/main.mlu @@ -0,0 +1,124 @@ +#include +#include +#include "cnrt_utils.h" +#include "memory_bandwidth_test.h" +#include "stream_benchmark.h" +#include "cache_benchmark.h" + +using namespace mlu_perf; + +void print_banner() { + std::cout << R"( +================================================================ + MLU Performance Benchmark Suite v1.0 + Cambricon Memory & Cache 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 only\n" + << " --cache Run NRAM + L2 cache tests\n" + << " --nram Run NRAM bandwidth test only\n" + << " --l2cache Run L2 cache bandwidth test only\n" + << " --device Specify MLU 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; + bool run_nram = false; + bool run_l2cache = 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 == "--nram") { cfg.run_all = false; cfg.run_nram = true; } + else if (arg == "--l2cache") { cfg.run_all = false; cfg.run_l2cache = 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); + + // System info + std::cout << "=== System Information ===\n"; + int dev_count = get_device_count(); + std::cout << "MLU Devices: " << dev_count << "\n"; + for (int i = 0; i < dev_count; ++i) { + std::cout << "\n"; + MluDeviceInfo::print(i); + } + std::cout << "\n"; + + if (cfg.device_id >= dev_count) { + std::cerr << "Error: Device ID " << cfg.device_id << " not available\n"; + return 1; + } + MLU_CHECK(cnrtSetDevice(cfg.device_id)); + + 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 || cfg.run_nram) { + NRAMBandwidthTest test; + test.execute(tc); + } + + if (cfg.run_all || cfg.run_cache || cfg.run_l2cache) { + L2CacheBandwidthTest test; + test.execute(tc); + } + + 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/infinimetrics/hardware/cuda-memory-benchmark/CMakeLists.txt b/infinimetrics/hardware/cuda-memory-benchmark/CMakeLists.txt index 1326088c..223a6627 100644 --- a/infinimetrics/hardware/cuda-memory-benchmark/CMakeLists.txt +++ b/infinimetrics/hardware/cuda-memory-benchmark/CMakeLists.txt @@ -1,31 +1,33 @@ cmake_minimum_required(VERSION 3.18) -project(CudaPerfSuite VERSION 1.0.0 LANGUAGES CXX CUDA) + +# Platform detection +if(NOT DEFINED PLATFORM) + message(FATAL_ERROR "PLATFORM is not set. Use -DPLATFORM=cuda, -DPLATFORM=metax, -DPLATFORM=corex, or -DPLATFORM=hygon") +endif() + +if(PLATFORM STREQUAL "cuda") + project(CudaPerfSuite LANGUAGES CXX CUDA VERSION 1.0.0) +elseif(PLATFORM STREQUAL "metax") + project(CudaPerfSuite LANGUAGES CXX VERSION 1.0.0) +elseif(PLATFORM STREQUAL "corex") + project(CudaPerfSuite LANGUAGES CXX CUDA VERSION 1.0.0) +elseif(PLATFORM STREQUAL "hygon") + project(CudaPerfSuite LANGUAGES CXX VERSION 1.0.0) +else() + message(FATAL_ERROR "Unsupported PLATFORM: ${PLATFORM}. Use 'cuda', 'metax', 'corex', or 'hygon'") +endif() # Set C++ standard set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CUDA_STANDARD 17) -set(CMAKE_CUDA_STANDARD_REQUIRED ON) - -# Set CUDA architecture (adjust based on your GPU) -set(CMAKE_CUDA_ARCHITECTURES "80;86;89;90" CACHE STRING "CUDA architectures") # Build type if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() -# Compiler flags -set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Xcompiler -Wall") -set(CMAKE_CUDA_FLAGS_RELEASE "${CMAKE_CUDA_FLAGS_RELEASE} -O3 -lineinfo") -set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Wno-deprecated-gpu-targets") - -# Find CUDA -find_package(CUDAToolkit REQUIRED) - # Include directories -include_directories(${CUDA_INCLUDE_DIRS} - ${CMAKE_CURRENT_SOURCE_DIR}/include) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) # Source files set(CUDA_SOURCES @@ -36,29 +38,69 @@ set(CXX_SOURCES # Add any .cpp files here if needed ) -# Create executable -add_executable(cuda_perf_suite - ${CUDA_SOURCES} - ${CXX_SOURCES} -) +# ---- Platform-specific settings ---- + +if(PLATFORM STREQUAL "cuda") + set(CMAKE_CUDA_STANDARD 17) + set(CMAKE_CUDA_STANDARD_REQUIRED ON) + set(CMAKE_CUDA_ARCHITECTURES "80;86;89;90" CACHE STRING "CUDA architectures") + set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Xcompiler -Wall") + set(CMAKE_CUDA_FLAGS_RELEASE "${CMAKE_CUDA_FLAGS_RELEASE} -O3 -lineinfo") + + add_executable(cuda_perf_suite ${CUDA_SOURCES} ${CXX_SOURCES}) + +elseif(PLATFORM STREQUAL "metax") + # Use cu-bridge compiler + set(CMAKE_CXX_COMPILER "$ENV{CUDA_PATH}/bin/nvcc") + set(CMAKE_C_COMPILER "$ENV{CUDA_PATH}/bin/nvcc") + + # Treat .cu as CXX for cu-bridge + set_source_files_properties(src/main.cu PROPERTIES LANGUAGE CXX) + + add_executable(cuda_perf_suite ${CUDA_SOURCES} ${CXX_SOURCES}) + +elseif(PLATFORM STREQUAL "corex") + # CoreX adapted CMake handles CUDA compiler automatically (clang++) + set(CMAKE_CUDA_STANDARD 17) + set(CMAKE_CUDA_STANDARD_REQUIRED ON) + set(CMAKE_CUDA_ARCHITECTURES "ivcore20" CACHE STRING "CoreX GPU architectures") + + include_directories($ENV{COREX_PATH}/include) + link_directories($ENV{COREX_PATH}/lib64) + + add_executable(cuda_perf_suite ${CUDA_SOURCES} ${CXX_SOURCES}) + +elseif(PLATFORM STREQUAL "hygon") + # Hygon DCU: use hipcc from DTK + find_program(HIPCC hipcc REQUIRED) + set(CMAKE_CXX_COMPILER "${HIPCC}") + set(CMAKE_C_COMPILER "${HIPCC}") + + # Treat .cu as CXX so hipcc handles kernel launch syntax + set_source_files_properties(src/main.cu PROPERTIES LANGUAGE CXX) + + add_compile_definitions(GPU_PLATFORM_HIP) + + add_executable(cuda_perf_suite ${CUDA_SOURCES} ${CXX_SOURCES}) +endif() # Link libraries -target_link_libraries(cuda_perf_suite - ${CUDA_LIBRARIES} - ${CMAKE_THREADS_LIB_INIT} -) +target_link_libraries(cuda_perf_suite ${CMAKE_THREADS_LIB_INIT}) # Installation -install(TARGETS cuda_perf_suite - RUNTIME DESTINATION bin -) +install(TARGETS cuda_perf_suite RUNTIME DESTINATION bin) # Print configuration message(STATUS "") message(STATUS "Configuration Summary:") message(STATUS " Project: ${PROJECT_NAME}") message(STATUS " Version: ${PROJECT_VERSION}") +message(STATUS " Platform: ${PLATFORM}") message(STATUS " Build type: ${CMAKE_BUILD_TYPE}") message(STATUS " C++ standard: ${CMAKE_CXX_STANDARD}") -message(STATUS " CUDA architectures: ${CMAKE_CUDA_ARCHITECTURES}") +if(PLATFORM STREQUAL "cuda") + message(STATUS " CUDA architectures: ${CMAKE_CUDA_ARCHITECTURES}") +elseif(PLATFORM STREQUAL "corex") + message(STATUS " CoreX arch: ${CMAKE_CUDA_ARCHITECTURES}") +endif() message(STATUS "") diff --git a/infinimetrics/hardware/cuda-memory-benchmark/QUICKSTART.md b/infinimetrics/hardware/cuda-memory-benchmark/QUICKSTART.md index c18c803f..862b1a06 100644 --- a/infinimetrics/hardware/cuda-memory-benchmark/QUICKSTART.md +++ b/infinimetrics/hardware/cuda-memory-benchmark/QUICKSTART.md @@ -1,16 +1,40 @@ # Quick Start Guide -## 1. Build the Project +## 1. Build ```bash -cd benchmarks/hardware/cuda-memory-benchmark -./build.sh +cd cuda-memory-benchmark + +# NVIDIA GPU +bash build.sh --platform cuda + +# MetaX +bash build.sh --platform metax + +# Iluvatar CoreX +bash build.sh --platform corex + +# Hygon DCU +bash build.sh --platform hygon + +# Moore Threads +bash build.sh --platform moore ``` ## 2. Run All Tests ```bash +# NVIDIA ./build/cuda_perf_suite --all + +# Moore Threads (specify GPU device) +MUSA_VISIBLE_DEVICES=0 ./build/cuda_perf_suite --all + +# MetaX +MACA_VISIBLE_DEVICES=0 ./build/cuda_perf_suite --all + +# Hygon +HIP_VISIBLE_DEVICES=0 ./build/cuda_perf_suite --all ``` ## 3. Run Individual Test Suites @@ -33,15 +57,14 @@ Standard STREAM benchmark measuring sustainable memory bandwidth. ``` Tests L1 and L2 cache performance with varying working set sizes. - ## 4. Common Usage Patterns -### Quick Performance Check (Default Settings) +### Quick Performance Check ```bash ./build/cuda_perf_suite --all ``` -### Detailed STREAM Benchmark (More Iterations) +### Detailed STREAM Benchmark ```bash ./build/cuda_perf_suite --stream --iterations 50 ``` @@ -51,7 +74,7 @@ Tests L1 and L2 cache performance with varying working set sizes. ./build/cuda_perf_suite --all --device 1 ``` -### Quiet Mode (Less Output) +### Quiet Mode ```bash ./build/cuda_perf_suite --all --quiet ``` @@ -65,42 +88,8 @@ The tests report: Lower CV = more consistent results. -## 6. Example Output - -``` -╔═══════════════════════════════════════════════════════════════╗ -║ ║ -║ CUDA Performance Benchmark Suite v1.0 ║ -║ ║ -║ Comprehensive GPU Memory & Cache Testing ║ -║ ║ -╚═══════════════════════════════════════════════════════════════╝ - -=== System Information === -CUDA Devices: 1 - -Device 0: NVIDIA A100-SXM4-40GB - Compute Capability: 8.0 - Total Global Memory: 39.25 GB - L2 Cache Size: 40960 KB - Multiprocessors: 108 - Max Threads per Block: 1024 - -... - -Memory Copy Bandwidth Sweep Test -Direction: Host to Device -Memory Type: Pinned - -Size (MB) Time (ms) Bandwidth (GB/s) CV (%) --------------------------------------------------------------- - 0.06 1.234 25.60 2.30 - 0.13 2.456 25.80 1.90 - ... -``` - -## 7. Next Steps +## 6. Next Steps -- Read [README.md](README.md) for detailed documentation +- Read [README.md](README.md) for detailed documentation and platform notes - Adjust test parameters for your specific use case - Integrate into your performance testing workflow diff --git a/infinimetrics/hardware/cuda-memory-benchmark/README.md b/infinimetrics/hardware/cuda-memory-benchmark/README.md index 93e8650b..89bc3b01 100644 --- a/infinimetrics/hardware/cuda-memory-benchmark/README.md +++ b/infinimetrics/hardware/cuda-memory-benchmark/README.md @@ -1,37 +1,63 @@ -# CUDA Performance Benchmark Suite +# GPU Performance Benchmark Suite -A comprehensive, modern CUDA performance testing suite written in C++17 with CMake build system. +A comprehensive GPU performance testing suite for memory bandwidth, STREAM benchmark, and cache analysis. Supports NVIDIA CUDA and domestic GPU platforms via a unified build system. + +## Supported Platforms + +| Platform | Flag | Compiler | Notes | +|----------|------|----------|-------| +| NVIDIA GPU | `--platform cuda` | nvcc | Native CUDA | +| MetaX | `--platform metax` | cucc (cu-bridge) | CUDA-compatible via cu-bridge | +| Iluvatar CoreX | `--platform corex` | nvcc (CoreX SDK) | CUDA-compatible via CoreX | +| Hygon DCU | `--platform hygon` | hipcc (DTK) | HIP backend | +| Moore Threads | `--platform moore` | mcc (MUSA SDK) | MUSA backend via `-mtgpu` | ## Features - **Memory Bandwidth Tests**: Host-to-Device, Device-to-Host, Device-to-Device transfers - **STREAM Benchmark**: Standard memory bandwidth benchmark (Copy, Scale, Add, Triad operations) - **Cache Performance Tests**: L1 and L2 cache bandwidth analysis +- **Multi-Platform**: Unified source code, per-platform build via `--platform` flag - **Modern C++ Design**: RAII patterns, smart pointers, exception safety -- **CMake Build System**: Cross-platform, easy to configure -- **Comprehensive Metrics**: Statistical analysis with trimmed mean, coefficient of variation ## Requirements -- CUDA Toolkit 11.0 or higher +Common: - CMake 3.18 or higher - C++17 compatible compiler -- NVIDIA GPU with compute capability 8.0+ (configurable) -## Building +Platform-specific: +- **NVIDIA / MetaX / CoreX**: CUDA Toolkit 11.0+ +- **Hygon**: DTK (HIP) +- **Moore Threads**: MUSA SDK (mcc) -### Quick Start +## Building ```bash -cd benchmarks/hardware/cuda-memory-benchmark -./build.sh +cd cuda-memory-benchmark + +# NVIDIA +bash build.sh --platform cuda + +# MetaX +bash build.sh --platform metax + +# Iluvatar CoreX +bash build.sh --platform corex + +# Hygon DCU +bash build.sh --platform hygon + +# Moore Threads +bash build.sh --platform moore ``` -### Manual Build +All platforms produce the same output binary: `build/cuda_perf_suite` + +### Manual Build (NVIDIA CUDA only) ```bash -mkdir build -cd build +mkdir build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release make -j$(nproc) ``` @@ -52,15 +78,12 @@ Common architectures: ## Usage -### Run All Tests +All platforms share the same CLI interface: ```bash +# Run all tests ./build/cuda_perf_suite --all -``` - -### Run Specific Tests -```bash # Memory bandwidth tests only ./build/cuda_perf_suite --memory @@ -69,8 +92,28 @@ Common architectures: # Cache performance tests only ./build/cuda_perf_suite --cache + +# Specify GPU device +./build/cuda_perf_suite --all --device 1 + +# More iterations +./build/cuda_perf_suite --all --iterations 20 + +# Quiet mode +./build/cuda_perf_suite --all --quiet ``` +### Environment Variables + +Some platforms use environment variables to select GPU devices: + +| Platform | Environment Variable | Example | +|----------|---------------------|---------| +| NVIDIA | `CUDA_VISIBLE_DEVICES` | `CUDA_VISIBLE_DEVICES=0 ./build/cuda_perf_suite --all` | +| Moore Threads | `MUSA_VISIBLE_DEVICES` | `MUSA_VISIBLE_DEVICES=1 ./build/cuda_perf_suite --all` | +| Hygon | `HIP_VISIBLE_DEVICES` | `HIP_VISIBLE_DEVICES=0 ./build/cuda_perf_suite --all` | +| MetaX | `MACA_VISIBLE_DEVICES` | `MACA_VISIBLE_DEVICES=0 ./build/cuda_perf_suite --all` | + ### Command Line Options ``` @@ -79,29 +122,13 @@ Options: --memory Run memory bandwidth tests only --stream Run STREAM benchmark only --cache Run cache benchmarks only - --device Specify CUDA device ID (default: 0) + --device Specify GPU device ID (default: 0) --iterations Number of measurement iterations (default: 10) --array-size Array size for STREAM test (default: 67108864) --quiet Reduce output verbosity --help Show help message ``` -### Examples - -```bash -# Run STREAM benchmark with 128M elements -./build/cuda_perf_suite --stream --array-size 134217728 - -# Run memory tests with 512MB buffer -./build/cuda_perf_suite --memory --buffer-size 512 - -# Run all tests with 20 iterations -./build/cuda_perf_suite --all --iterations 20 - -# Run tests on specific GPU -./build/cuda_perf_suite --all --device 1 -``` - ## Test Descriptions ### 1. Memory Bandwidth Tests @@ -144,14 +171,16 @@ Tests L1 and L2 cache bandwidth by varying working set sizes ``` cuda-memory-benchmark/ ├── include/ # Header files -│ ├── performance_test.h # Base testing framework +│ ├── gpu_runtime.h # Cross-platform GPU API abstraction │ ├── cuda_utils.h # CUDA utilities (RAII wrappers) +│ ├── performance_test.h # Base testing framework │ ├── memory_bandwidth_test.h # Memory copy tests │ ├── stream_benchmark.h # STREAM benchmark │ └── cache_benchmark.h # Cache tests ├── src/ │ └── main.cu # Main program entry ├── CMakeLists.txt # CMake build configuration -├── build.sh # Build script -└── README.md # This file +├── build.sh # Unified build script (all platforms) +├── README.md # This file +└── QUICKSTART.md # Quick start guide ``` diff --git a/infinimetrics/hardware/cuda-memory-benchmark/build.sh b/infinimetrics/hardware/cuda-memory-benchmark/build.sh old mode 100644 new mode 100755 index 37a9ff3a..723f3551 --- a/infinimetrics/hardware/cuda-memory-benchmark/build.sh +++ b/infinimetrics/hardware/cuda-memory-benchmark/build.sh @@ -1,6 +1,12 @@ #!/bin/bash # Build script for CUDA Performance Suite +# Usage: +# bash build.sh --platform cuda # Build with native CUDA (NVIDIA GPU) +# bash build.sh --platform metax # Build with cu-bridge (MetaX GPU) +# bash build.sh --platform corex # Build with CoreX SDK (Iluvatar GPU) +# bash build.sh --platform hygon # Build with DTK/HIP (Hygon DCU) +# bash build.sh --platform moore # Build with mcc -mtgpu (Moore Threads) set -e # Exit on error @@ -10,34 +16,180 @@ GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color +# Parse arguments +PLATFORM="" +while [[ $# -gt 0 ]]; do + case $1 in + --platform) + PLATFORM="$2" + shift 2 + ;; + *) + echo -e "${RED}Unknown option: $1${NC}" + echo "Usage: bash build.sh --platform " + exit 1 + ;; + esac +done + +if [[ -z "$PLATFORM" ]]; then + echo -e "${RED}ERROR: --platform is required.${NC}" + echo "Usage: bash build.sh --platform " + exit 1 +fi + echo "==========================================" echo " CUDA Performance Suite - Build Script" +echo " Platform: ${PLATFORM}" echo "==========================================" echo "" -# Check if CUDA is available -if ! command -v nvcc &> /dev/null; then - echo -e "${RED}ERROR: nvcc not found. Please install CUDA toolkit.${NC}" - exit 1 -fi +if [[ "$PLATFORM" == "metax" ]]; then + # ---- MetaX platform: using cu-bridge ---- + + export MACA_PATH=${MACA_PATH:-/opt/maca} + export CUCC_PATH=${CUCC_PATH:-${MACA_PATH}/tools/cu-bridge} + export PATH=$PATH:${CUCC_PATH}/tools:${CUCC_PATH}/bin + export CUCC_CMAKE_ENTRY=2 + export CUDA_PATH=${CUCC_PATH} + + # Create nvcc symlink (if it doesn't exist) + if [ ! -e ${CUCC_PATH}/bin/nvcc ]; then + ln -s ${CUCC_PATH}/bin/cucc ${CUCC_PATH}/bin/nvcc + fi + + if ! command -v cucc &> /dev/null; then + echo -e "${RED}ERROR: cucc not found. Please check cu-bridge installation at ${CUCC_PATH}${NC}" + exit 1 + fi + + echo -e "${YELLOW}[MetaX] Using cu-bridge: ${CUCC_PATH}${NC}" + + mkdir -p build + + if command -v cmake &> /dev/null && \ + command -v cmake_maca &> /dev/null && \ + command -v make_maca &> /dev/null; then + cd build + + echo -e "${YELLOW}Configuring with cmake_maca...${NC}" + cmake_maca .. -DCMAKE_BUILD_TYPE=Release -DPLATFORM=metax + + echo -e "${YELLOW}Building with make_maca...${NC}" + make_maca -j$(nproc) + else + echo -e "${YELLOW}CMake unavailable; compiling directly with cucc...${NC}" + cucc -O3 -std=c++17 \ + -I./include \ + -I"${CUCC_PATH}/include" \ + ./src/main.cu \ + -o build/cuda_perf_suite + fi + +elif [[ "$PLATFORM" == "corex" ]]; then + # ---- CoreX (Iluvatar) platform ---- + + export COREX_PATH=${COREX_PATH:-/usr/local/corex} + export LD_LIBRARY_PATH=${COREX_PATH}/lib64:${LD_LIBRARY_PATH:-} + + if [ ! -d "${COREX_PATH}" ]; then + echo -e "${RED}ERROR: CoreX SDK not found at ${COREX_PATH}${NC}" + exit 1 + fi + + echo -e "${YELLOW}[CoreX] Using CoreX SDK: ${COREX_PATH}${NC}" + echo -e "${YELLOW}[CoreX] CMake: $(cmake --version | head -1)${NC}" + + # Clean CMake cache per CoreX migration guide requirement + if [ -d "build" ]; then + rm -rf build/CMakeCache.txt build/CMakeFiles build/Makefile + fi + + mkdir -p build + cd build + + echo -e "${YELLOW}Configuring with CoreX CMake...${NC}" + cmake .. -DCMAKE_BUILD_TYPE=Release -DPLATFORM=corex \ + -DCMAKE_CUDA_ARCHITECTURES=ivcore20 + + echo -e "${YELLOW}Building...${NC}" + make -j$(nproc) -# Create build directory -echo -e "${YELLOW}Creating build directory...${NC}" -mkdir -p build -cd build +elif [[ "$PLATFORM" == "hygon" ]]; then + # ---- Hygon DCU platform: using DTK (HIP) ---- -# Configure with CMake -echo -e "${YELLOW}Configuring with CMake...${NC}" -cmake .. -DCMAKE_BUILD_TYPE=Release + export DTK_PATH=${DTK_PATH:-/opt/dtk} + export PATH=$DTK_PATH/bin:$PATH + export LD_LIBRARY_PATH=$DTK_PATH/lib64:${LD_LIBRARY_PATH:-} -# Build -echo -e "${YELLOW}Building...${NC}" -make -j$(nproc) + if ! command -v hipcc &> /dev/null; then + echo -e "${RED}ERROR: hipcc not found. Please install DTK at ${DTK_PATH}${NC}" + exit 1 + fi + + echo -e "${YELLOW}[Hygon DCU] Using DTK: ${DTK_PATH}${NC}" + echo -e "${YELLOW}[Hygon DCU] hipcc: $(which hipcc)${NC}" + + mkdir -p build + cd build + + echo -e "${YELLOW}Configuring with CMake + HIP...${NC}" + cmake .. -DCMAKE_BUILD_TYPE=Release -DPLATFORM=hygon + + echo -e "${YELLOW}Building...${NC}" + make -j$(nproc) + +elif [[ "$PLATFORM" == "moore" ]]; then + # ---- Moore Threads platform: using mcc -mtgpu ---- + + export MUSA_HOME=${MUSA_HOME:-/usr/local/musa} + + if ! command -v mcc &> /dev/null; then + echo -e "${RED}ERROR: mcc not found. Please install MUSA SDK at ${MUSA_HOME}${NC}" + exit 1 + fi + + echo -e "${YELLOW}[Moore Threads] Using mcc: $(which mcc)${NC}" + echo -e "${YELLOW}[Moore Threads] MUSA_HOME: ${MUSA_HOME}${NC}" + + mkdir -p build + + echo -e "${YELLOW}Building with mcc -mtgpu (MUSA)...${NC}" + mcc -O3 -DGPU_PLATFORM_MUSA -mtgpu \ + --musa-path="$MUSA_HOME" \ + -I./include \ + ./src/main.cu \ + -o build/cuda_perf_suite \ + -L"$MUSA_HOME/lib" -lmusart + +elif [[ "$PLATFORM" == "cuda" ]]; then + # ---- NVIDIA CUDA platform ---- + + if ! command -v nvcc &> /dev/null; then + echo -e "${RED}ERROR: nvcc not found. Please install CUDA toolkit.${NC}" + exit 1 + fi + + echo -e "${YELLOW}[CUDA] Using native nvcc: $(which nvcc)${NC}" + + mkdir -p build + cd build + + echo -e "${YELLOW}Configuring with CMake...${NC}" + cmake .. -DCMAKE_BUILD_TYPE=Release -DPLATFORM=cuda + + echo -e "${YELLOW}Building...${NC}" + make -j$(nproc) + +else + echo -e "${RED}ERROR: Unsupported platform '${PLATFORM}'. Use 'cuda', 'metax', 'corex', 'hygon', or 'moore'.${NC}" + exit 1 +fi # Check if build was successful if [ $? -eq 0 ]; then echo "" - echo -e "${GREEN}✓ Build completed successfully!${NC}" + echo -e "${GREEN}Build completed successfully!${NC}" echo "" echo "Executable: build/cuda_perf_suite" echo "" @@ -50,8 +202,7 @@ if [ $? -eq 0 ]; then echo "" else echo "" - echo -e "${RED}✗ Build failed!${NC}" + echo -e "${RED}Build failed!${NC}" echo "Please check the error messages above." exit 1 fi - diff --git a/infinimetrics/hardware/cuda-memory-benchmark/include/cache_benchmark.h b/infinimetrics/hardware/cuda-memory-benchmark/include/cache_benchmark.h index d4ffca1b..d3ceb57c 100644 --- a/infinimetrics/hardware/cuda-memory-benchmark/include/cache_benchmark.h +++ b/infinimetrics/hardware/cuda-memory-benchmark/include/cache_benchmark.h @@ -2,7 +2,6 @@ #include "cuda_utils.h" #include "performance_test.h" -#include #include #include #include diff --git a/infinimetrics/hardware/cuda-memory-benchmark/include/cuda_utils.h b/infinimetrics/hardware/cuda-memory-benchmark/include/cuda_utils.h index db5db05b..b3a32a78 100644 --- a/infinimetrics/hardware/cuda-memory-benchmark/include/cuda_utils.h +++ b/infinimetrics/hardware/cuda-memory-benchmark/include/cuda_utils.h @@ -1,6 +1,6 @@ #pragma once -#include +#include "gpu_runtime.h" #include #include #include @@ -241,9 +241,9 @@ struct CudaDeviceInfo { info.shared_mem_per_block = prop.sharedMemPerBlock; info.max_threads_per_block = prop.maxThreadsPerBlock; info.multi_processor_count = prop.multiProcessorCount; -#if CUDART_VERSION >= 11000 - // clockRate was removed in CUDA 11.0+ - // Set to 0 as it's no longer available +#if defined(GPU_PLATFORM_MUSA) + info.clock_rate = 0; +#elif CUDART_VERSION >= 11000 info.clock_rate = 0; #else info.clock_rate = prop.clockRate; diff --git a/infinimetrics/hardware/cuda-memory-benchmark/include/gpu_runtime.h b/infinimetrics/hardware/cuda-memory-benchmark/include/gpu_runtime.h new file mode 100644 index 00000000..1a262fe2 --- /dev/null +++ b/infinimetrics/hardware/cuda-memory-benchmark/include/gpu_runtime.h @@ -0,0 +1,120 @@ +#pragma once + +/// @file gpu_runtime.h +/// Unified GPU runtime header for cross-platform GPU computing. +/// +/// - GPU_PLATFORM_MUSA: Moore Threads MUSA (maps cuda* -> musa*) +/// - GPU_PLATFORM_HIP: Hygon DCU / AMD ROCm (maps cuda* -> hip*) +/// - Otherwise: Native CUDA (NVIDIA, MetaX cu-bridge, CoreX) + +#ifdef GPU_PLATFORM_MUSA +// ---- MUSA backend (Moore Threads / 摩尔线程) ---- +#include + +// --- Error types --- +#define cudaError_t musaError_t +#define cudaSuccess musaSuccess +#define cudaGetErrorString musaGetErrorString +#define cudaGetLastError musaGetLastError + +// --- Device management --- +#define cudaSetDevice musaSetDevice +#define cudaGetDevice musaGetDevice +#define cudaGetDeviceCount musaGetDeviceCount +#define cudaGetDeviceProperties musaGetDeviceProperties +#define cudaDeviceSynchronize musaDeviceSynchronize +#define cudaDeviceProp musaDeviceProp + +// --- Memory management --- +#define cudaMalloc musaMalloc +#define cudaFree musaFree +#define cudaMallocHost musaMallocHost +#define cudaFreeHost musaFreeHost +#define cudaMemcpy musaMemcpy +#define cudaMemcpyAsync musaMemcpyAsync +#define cudaMemset musaMemset + +// --- Stream --- +#define cudaStream_t musaStream_t +#define cudaStreamCreate musaStreamCreate +#define cudaStreamDestroy musaStreamDestroy +#define cudaStreamSynchronize musaStreamSynchronize + +// --- Event --- +#define cudaEvent_t musaEvent_t +#define cudaEventCreate musaEventCreate +#define cudaEventDestroy musaEventDestroy +#define cudaEventRecord musaEventRecord +#define cudaEventSynchronize musaEventSynchronize +#define cudaEventElapsedTime musaEventElapsedTime + +// --- Memory copy constants --- +#define cudaMemcpyHostToDevice musaMemcpyHostToDevice +#define cudaMemcpyDeviceToHost musaMemcpyDeviceToHost +#define cudaMemcpyDeviceToDevice musaMemcpyDeviceToDevice + +// --- Version --- +#define cudaRuntimeGetVersion musaRuntimeGetVersion +#define cudaDriverGetVersion musaDriverGetVersion + +#elif defined(GPU_PLATFORM_HIP) +// ---- HIP backend (Hygon DCU, AMD ROCm) ---- +#include + +// --- Error types --- +#define cudaError_t hipError_t +#define cudaSuccess hipSuccess +#define cudaGetErrorString hipGetErrorString + +// --- Memory management --- +#define cudaMalloc hipMalloc +#define cudaFree hipFree +#define cudaMallocHost hipMallocHost +#define cudaFreeHost hipFreeHost +#define cudaMemset hipMemset + +// --- Stream --- +#define cudaStream_t hipStream_t +#define cudaStreamCreate hipStreamCreate +#define cudaStreamDestroy hipStreamDestroy +#define cudaStreamSynchronize hipStreamSynchronize + +// --- Event --- +#define cudaEvent_t hipEvent_t +#define cudaEventCreate hipEventCreate +#define cudaEventDestroy hipEventDestroy +#define cudaEventRecord hipEventRecord +#define cudaEventSynchronize hipEventSynchronize +#define cudaEventElapsedTime hipEventElapsedTime + +// --- Device management --- +#define cudaSetDevice hipSetDevice +#define cudaGetDevice hipGetDevice +#define cudaGetDeviceCount hipGetDeviceCount +#define cudaGetDeviceProperties hipGetDeviceProperties +#define cudaDeviceSynchronize hipDeviceSynchronize +#define cudaGetLastError hipGetLastError + +// --- Device property struct --- +#define cudaDeviceProp hipDeviceProp_t + +// --- Memory copy --- +#define cudaMemcpy hipMemcpy +#define cudaMemcpyAsync hipMemcpyAsync +#define cudaMemcpyHostToDevice hipMemcpyHostToDevice +#define cudaMemcpyDeviceToHost hipMemcpyDeviceToHost +#define cudaMemcpyDeviceToDevice hipMemcpyDeviceToDevice + +// --- Version --- +#define cudaRuntimeGetVersion hipRuntimeGetVersion +#define cudaDriverGetVersion hipDriverGetVersion + +// Provide CUDART_VERSION equivalent for HIP +#ifndef CUDART_VERSION +#define CUDART_VERSION (HIP_VERSION_MAJOR * 1000 + HIP_VERSION_MINOR * 10) +#endif + +#else +// ---- CUDA backend (NVIDIA, MetaX cu-bridge, CoreX) ---- +#include +#endif diff --git a/infinimetrics/hardware/hardware_adapter.py b/infinimetrics/hardware/hardware_adapter.py index 426c60a7..46954030 100644 --- a/infinimetrics/hardware/hardware_adapter.py +++ b/infinimetrics/hardware/hardware_adapter.py @@ -1,67 +1,178 @@ #!/usr/bin/env python3 -"""Hardware Test Adapter for CUDA Unified Benchmark Suite""" +"""Hardware test adapter for native and CUDA-compatible accelerators.""" import logging -import subprocess import re +import shutil +import subprocess from pathlib import Path -from typing import Any, Dict, Optional, List +from typing import Any, Dict, List, Optional from infinimetrics.adapter import BaseAdapter -from infinimetrics.common.csv_utils import save_csv, create_timeseries_metric from infinimetrics.common.command_builder import build_command_from_config from infinimetrics.common.constants import ( - TEST_TYPE_MAP, - MEMORY_DIRECTIONS, - STREAM_OPERATIONS, - MEMORY_CSV_FIELDS, + CACHE_TEST_TIMEOUT, + DEFAULT_TEST_TIMEOUT, L1_CACHE_CSV_FIELDS, - L2_CACHE_CSV_FIELDS, L1_CACHE_PATTERN, + L2_CACHE_CSV_FIELDS, L2_CACHE_PATTERN, - CACHE_TEST_TIMEOUT, - DEFAULT_TEST_TIMEOUT, + MEMORY_CSV_FIELDS, + MEMORY_DIRECTIONS, METRIC_PREFIX_MEM_SWEEP, + STREAM_OPERATIONS, + TEST_TYPE_MAP, InfiniMetricsJson, ) +from infinimetrics.common.csv_utils import create_timeseries_metric from infinimetrics.utils.time_utils import get_timestamp logger = logging.getLogger(__name__) +_PLATFORM_ALIASES = { + "cuda": "cuda", + "cudaunified": "cuda", + "nvidia": "cuda", + "metax": "metax", + "corex": "corex", + "iluvatar": "corex", + "hygon": "hygon", + "moore": "moore", + "mthreads": "moore", + "musa": "moore", + "cambricon": "cambricon", + "mlu": "cambricon", + "ascend": "ascend", + "npu": "ascend", +} + +_PLATFORM_CONFIGS = { + "cuda": { + "binary_name": "cuda_perf_suite", + "benchmark_subdir": "cuda-memory-benchmark", + "build_platform": "cuda", + "cache_parser": "cuda", + }, + "metax": { + "binary_name": "cuda_perf_suite", + "benchmark_subdir": "cuda-memory-benchmark", + "build_platform": "metax", + "cache_parser": "cuda", + }, + "corex": { + "binary_name": "cuda_perf_suite", + "benchmark_subdir": "cuda-memory-benchmark", + "build_platform": "corex", + "cache_parser": "cuda", + }, + "hygon": { + "binary_name": "cuda_perf_suite", + "benchmark_subdir": "cuda-memory-benchmark", + "build_platform": "hygon", + "cache_parser": "cuda", + }, + "moore": { + "binary_name": "cuda_perf_suite", + "benchmark_subdir": "cuda-memory-benchmark", + "build_platform": "moore", + "cache_parser": "cuda", + }, + "cambricon": { + "binary_name": "mlu_perf_suite", + "benchmark_subdir": "cambricon-memory-benchmark", + "build_platform": None, + "cache_parser": "cambricon", + }, + "ascend": { + "binary_name": "npu_perf_suite", + "benchmark_subdir": "ascend-memory-benchmark", + "build_platform": None, + "cache_parser": "ascend", + }, +} + + +def detect_platform() -> str: + """Detect the installed accelerator toolchain.""" + if shutil.which("cncc") or Path("/usr/local/neuware").exists(): + return "cambricon" + 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" + + maca_path = Path("/opt/maca") + if ( + (maca_path / "tools" / "cu-bridge" / "bin" / "cucc").exists() + or shutil.which("cucc") + or shutil.which("mxcc") + ): + return "metax" + + dtk_path = Path("/opt/dtk") + if ( + (dtk_path / "bin" / "hy-smi").exists() + or shutil.which("hy-smi") + or (dtk_path.exists() and shutil.which("hipcc")) + ): + return "hygon" + + corex_path = Path("/usr/local/corex") + if ( + (corex_path / "bin" / "ixsmi").exists() + or (corex_path / "bin" / "clang++").exists() + ): + return "corex" + return "cuda" + + class HardwareTestAdapter(BaseAdapter): - """Adapter for CUDA Unified hardware performance tests.""" + """Adapter for unified hardware performance tests. + + The existing CUDA constructor and private method signatures remain supported. + Additional platforms are selected through ``config.device`` or the testcase + framework name. + """ def __init__( self, cuda_perf_path: str = None, output_dir: str = "./output", + perf_binary_path: str = None, ): + self.hardware_dir = Path(__file__).parent self.cuda_perf_path = cuda_perf_path or str( - Path(__file__).parent + self.hardware_dir / "cuda-memory-benchmark" / "build" / "cuda_perf_suite" ) + self.perf_binary_path = perf_binary_path self.output_dir = Path(output_dir) self.output_dir.mkdir(parents=True, exist_ok=True) - self.build_dir = Path(__file__).parent / "cuda-memory-benchmark" + + # Preserve attributes used by existing callers. + self.build_dir = self.hardware_dir / "cuda-memory-benchmark" self.build_script = self.build_dir / "build.sh" def setup(self, config: Dict[str, Any]) -> None: - """Initialize resources before running tests.""" - device = config.get("device", "cuda").lower() + """Build the selected benchmark binary if it is missing.""" + device = self._get_device_type(config) if ( device == "cpu" or config.get("skip_build", False) - or Path(self.cuda_perf_path).exists() + or Path(self._get_binary_path(device)).exists() ): return - self._build_cuda_project() + self._build_project(device) def process(self, test_input: Any) -> Dict[str, Any]: - """Process test input and return results.""" - # Normalize test input to dict format + """Process a hardware test input and return normalized metrics.""" test_input = self._normalize_test_input(test_input) if not test_input: raise ValueError(f"Invalid test_input type: {type(test_input)}") @@ -70,33 +181,37 @@ def process(self, test_input: Any) -> Dict[str, Any]: config = test_input.get(InfiniMetricsJson.CONFIG, {}) run_id = test_input.get(InfiniMetricsJson.RUN_ID, "unknown") - logger.info(f"HardwareTestAdapter: Processing {testcase}") - - # Put CSV files in hardware/ subdirectory to match JSON location + logger.info("HardwareTestAdapter: Processing %s", testcase) self.output_dir = Path(config.get("output_dir", "./output")) / "hardware" self.output_dir.mkdir(parents=True, exist_ok=True) - device = config.get("device", "cuda").lower() + device = self._get_device_type(config) test_type = config.get("test_type", "comprehensive") try: if device == "cpu": logger.info( - "CPU mode: Skipping hardware tests (not supported on CPU), returning empty results" + "CPU mode: Skipping hardware tests (not supported on CPU), " + "returning empty results" ) metrics = [] command = None else: - logger.info("GPU mode (device=%s): Executing CUDA tests", device) - cmd = self._build_command(config) - command = " ".join(cmd) # Store command as string - output = self._execute_test(cmd, test_type) - metrics = self._parse_output(output, test_type, run_id) + logger.info("Accelerator mode (platform=%s): Executing tests", device) + cmd = self._build_command(config, device) + command = " ".join(cmd) + output = self._execute_test(cmd, test_type, device) + metrics = self._parse_output(output, test_type, run_id, device) - # Add command to config for traceability result_config = config.copy() if command: result_config["command"] = command + if device not in ("cpu", "cuda"): + result_config.setdefault("platform", device) + if device == "ascend" and test_type in ("Comprehensive", "Stream"): + result_config.setdefault( + "estimated_stream_operations", ["scale", "add", "triad"] + ) return { InfiniMetricsJson.RESULT_CODE: 0, @@ -106,61 +221,117 @@ def process(self, test_input: Any) -> Dict[str, Any]: InfiniMetricsJson.CONFIG: result_config, InfiniMetricsJson.METRICS: metrics, } - - except Exception as e: - # Log error with context, then re-raise for Executor to handle + except Exception as exc: logger.error( - f"HardwareTestAdapter: Test failed for {testcase}\n" - f" Device: {device}\n" - f" Test Type: {test_type}\n" - f" Error: {str(e)}", + "HardwareTestAdapter: Test failed for %s\n" + " Device: %s\n" + " Test Type: %s\n" + " Error: %s", + testcase, + device, + test_type, + str(exc), exc_info=True, ) raise + def _get_device_type(self, config: Dict[str, Any]) -> str: + """Resolve an explicit device, testcase framework, or local toolchain.""" + explicit = str(config.get("device", "")).lower().strip() + if explicit == "cpu": + return "cpu" + if explicit: + # Unknown explicit device values historically used the CUDA suite. + return _PLATFORM_ALIASES.get(explicit, "cuda") + + testcase = str(config.get("_testcase", "")) + parts = testcase.split(".") + if len(parts) >= 2: + framework = parts[1].lower() + if framework in _PLATFORM_ALIASES: + resolved = _PLATFORM_ALIASES[framework] + if framework not in ("cuda", "cudaunified", "nvidia"): + return resolved + + return detect_platform() + + @staticmethod + def _get_device_config(device: str) -> Dict[str, Any]: + config = _PLATFORM_CONFIGS.get(device) + if not config: + raise ValueError(f"Unknown device type: {device}") + return config + + def _get_binary_path(self, device: str) -> str: + if self.perf_binary_path: + return self.perf_binary_path + if device in ("cuda", "metax", "corex", "hygon", "moore"): + return self.cuda_perf_path + device_config = self._get_device_config(device) + return str( + self.hardware_dir + / device_config["benchmark_subdir"] + / "build" + / device_config["binary_name"] + ) + def _build_cuda_project(self) -> None: - """Build CUDA project if needed.""" - if not self.build_dir.exists(): - raise FileNotFoundError( - f"CUDA benchmark directory not found: {self.build_dir}" - ) - if not self.build_script.exists(): - raise FileNotFoundError(f"Build script not found: {self.build_script}") - logger.info("Building CUDA project in: %s", self.build_dir) + """Build the native CUDA project, preserving the existing method.""" + self._build_project("cuda") + + def _build_project(self, device: str) -> None: + device_config = self._get_device_config(device) + build_dir = self.hardware_dir / device_config["benchmark_subdir"] + build_script = build_dir / "build.sh" + if not build_dir.exists(): + raise FileNotFoundError(f"Benchmark directory not found: {build_dir}") + if not build_script.exists(): + raise FileNotFoundError(f"Build script not found: {build_script}") + + command = ["bash", str(build_script)] + if device_config["build_platform"]: + command.extend(["--platform", device_config["build_platform"]]) + + logger.info("Building %s project in: %s", device, build_dir) try: result = subprocess.run( - ["bash", str(self.build_script)], - cwd=str(self.build_dir), + command, + cwd=str(build_dir), capture_output=True, text=True, timeout=300, ) if result.returncode != 0: - raise RuntimeError(f"Failed to build CUDA project:\n{result.stderr}") - logger.info("CUDA project build completed successfully") + raise RuntimeError( + f"Failed to build {device} hardware project:\n{result.stderr}" + ) + logger.info("%s hardware project build completed successfully", device) except subprocess.TimeoutExpired: - raise RuntimeError("CUDA project build timed out after 5 minutes") + raise RuntimeError("Hardware project build timed out after 5 minutes") - def _build_command(self, config: Dict[str, Any]) -> List[str]: - """Build command for CUDA test suite.""" + def _build_command( + self, config: Dict[str, Any], device: str = "cuda" + ) -> List[str]: + """Build a benchmark command using the existing CUDA CLI contract.""" test_type = config.get("test_type", "all") - cuda_test_type = TEST_TYPE_MAP.get(test_type, test_type.lower()) - - base_command = [self.cuda_perf_path, f"--{cuda_test_type}"] - - # Use command builder for optional parameters + cli_test_type = TEST_TYPE_MAP.get(test_type, test_type.lower()) + base_command = [self._get_binary_path(device), f"--{cli_test_type}"] param_mappings = [ ("device_id", "--device"), ("iterations", "--iterations"), ("array_size", "--array-size"), ] - return build_command_from_config(base_command, config, param_mappings) - def _execute_test(self, cmd: List[str], test_type: str) -> str: - """Execute CUDA test and return output.""" - if not Path(self.cuda_perf_path).exists(): - raise RuntimeError(f"cuda_perf_suite not found: {self.cuda_perf_path}") + def _execute_test( + self, cmd: List[str], test_type: str, device: str = "cuda" + ) -> str: + """Execute a benchmark and return stdout.""" + binary = self._get_binary_path(device) + if not Path(binary).exists(): + if device == "cuda": + raise RuntimeError(f"cuda_perf_suite not found: {binary}") + raise RuntimeError(f"{device} benchmark binary not found: {binary}") logger.info("Executing: %s", " ".join(cmd)) timeout = ( @@ -171,42 +342,50 @@ def _execute_test(self, cmd: List[str], test_type: str) -> str: ) return result.stdout - def _parse_output(self, output: str, test_type: str, run_id: str) -> List[Dict]: - """Parse test output based on test type.""" + def _parse_output( + self, + output: str, + test_type: str, + run_id: str, + device: str = "cuda", + ) -> List[Dict]: + """Parse benchmark output while preserving existing CUDA metric names.""" + cache_parser = self._get_device_config(device)["cache_parser"] if test_type == "Comprehensive": return ( - self._parse_memory_bandwidth(output, run_id, METRIC_PREFIX_MEM_SWEEP) + self._parse_memory_bandwidth( + output, run_id, METRIC_PREFIX_MEM_SWEEP + ) + self._parse_stream_benchmark(output) - + self._parse_cache_bandwidth(output, run_id) + + self._parse_cache_for_platform( + output, run_id, cache_parser + ) ) - # Single test type metric_map = { - "MemSweep": (METRIC_PREFIX_MEM_SWEEP, self._parse_memory_bandwidth), - "Stream": (None, self._parse_stream_benchmark), - "Cache": (None, self._parse_cache_bandwidth), + "MemSweep": lambda: self._parse_memory_bandwidth( + output, run_id, METRIC_PREFIX_MEM_SWEEP + ), + "Stream": lambda: self._parse_stream_benchmark(output), + "Cache": lambda: self._parse_cache_for_platform( + output, run_id, cache_parser + ), } - - if test_type in metric_map: - prefix, parser = metric_map[test_type] - return parser(output, run_id, prefix) if prefix else parser(output, run_id) - return [] + parser = metric_map.get(test_type) + return parser() if parser else [] def _parse_memory_bandwidth( self, output: str, run_id: str, metric_prefix: str ) -> List[Dict]: - """Parse memory bandwidth test output.""" + """Parse memory bandwidth sweep output.""" metrics = [] is_sweep = "sweep" in metric_prefix - for direction_label, key in MEMORY_DIRECTIONS: csv_data = self._parse_bandwidth_data(output, direction_label) - if not csv_data: continue metric_name = f"{metric_prefix}_{key}" - if is_sweep: metrics.append( self._create_timeseries_metric( @@ -226,36 +405,36 @@ def _parse_memory_bandwidth( "unit": "GB/s", } ) - return metrics def _parse_bandwidth_data(self, output: str, direction: str) -> List[Dict]: - """Parse bandwidth data for a specific direction.""" + """Parse bandwidth rows for one transfer direction.""" csv_data = [] - - # Sweep format - match from direction header to next section - # The pattern stops at: ==== (next section), Direction:, STREAM:, or end of string - sweep_pattern = rf"{direction}.*?Size \(MB\)\s+Time \(ms\)Bandwidth \(GB/s\)\s+CV \(%\)\s*-+\s*(.*?)\s*(?=\n=+|Direction:|STREAM:|\Z)" + sweep_pattern = ( + rf"{direction}.*?Size \(MB\)\s+Time \(ms\)Bandwidth \(GB/s\)" + rf"\s+CV \(%\)\s*-+\s*(.*?)\s*" + rf"(?=\n=+|Direction:|STREAM:|\Z)" + ) sweep_match = re.search(sweep_pattern, output, re.DOTALL) - if sweep_match: - data_block = sweep_match.group(1) - for line in data_block.strip().split("\n"): + for line in sweep_match.group(1).strip().split("\n"): line = line.strip() - if line and not line.startswith("-"): + if line and not line.startswith("-") and not line.startswith("NOTE"): result = self._parse_sweep_line(line) if result: csv_data.append(result) - return csv_data @staticmethod def _parse_sweep_line(line: str) -> Optional[Dict]: - """Parse a line from sweep format output.""" + """Parse one memory sweep row.""" parts = line.split() if len(parts) >= 3: try: - return {"size_mb": float(parts[0]), "bandwidth_gbps": float(parts[2])} + return { + "size_mb": float(parts[0]), + "bandwidth_gbps": float(parts[2]), + } except (ValueError, IndexError): pass return None @@ -268,7 +447,7 @@ def _create_timeseries_metric( fields: List[str], unit: str = "GB/s", ) -> Dict: - """Create a timeseries metric with CSV file.""" + """Create a timeseries metric and its CSV file.""" return create_timeseries_metric( output_dir=self.output_dir, metric_name=name, @@ -278,15 +457,19 @@ def _create_timeseries_metric( unit=unit, ) - def _parse_stream_benchmark(self, output: str, run_id: str = None) -> List[Dict]: + def _parse_stream_benchmark( + self, output: str, run_id: str = None + ) -> List[Dict]: """Parse STREAM benchmark output.""" metrics = [] - for op in STREAM_OPERATIONS: - match = re.search(rf"STREAM_{op.capitalize()}\s+(\d+\.\d+)", output) + for operation in STREAM_OPERATIONS: + match = re.search( + rf"STREAM_{operation.capitalize()}\s+(\d+\.\d+)", output + ) if match: metrics.append( { - "name": f"hardware.stream_{op}", + "name": f"hardware.stream_{operation}", "value": float(match.group(1)), "type": "scalar", "unit": "GB/s", @@ -294,14 +477,21 @@ def _parse_stream_benchmark(self, output: str, run_id: str = None) -> List[Dict] ) return metrics + def _parse_cache_for_platform( + self, output: str, run_id: str, parser_name: str + ) -> List[Dict]: + if parser_name == "cambricon": + return self._parse_cambricon_cache(output, run_id) + if parser_name == "ascend": + return self._parse_ascend_hierarchy(output, run_id) + return self._parse_cache_bandwidth(output, run_id) + def _parse_cache_bandwidth(self, output: str, run_id: str) -> List[Dict]: - """Parse cache bandwidth sweep test output.""" + """Parse the existing CUDA L1 and L2 cache output.""" metrics = [] - - # Parse L1 l1_match = re.search(L1_CACHE_PATTERN, output, re.DOTALL) if l1_match: - l1_data = self._parse_cache_lines(l1_match.group(1), cache_level="l1") + l1_data = self._parse_cache_lines(l1_match.group(1), "l1") if l1_data: metrics.append( self._create_timeseries_metric( @@ -312,10 +502,9 @@ def _parse_cache_bandwidth(self, output: str, run_id: str) -> List[Dict]: ) ) - # Parse L2 l2_match = re.search(L2_CACHE_PATTERN, output, re.DOTALL) if l2_match: - l2_data = self._parse_cache_lines(l2_match.group(1), cache_level="l2") + l2_data = self._parse_cache_lines(l2_match.group(1), "l2") if l2_data: metrics.append( self._create_timeseries_metric( @@ -325,41 +514,96 @@ def _parse_cache_bandwidth(self, output: str, run_id: str) -> List[Dict]: L2_CACHE_CSV_FIELDS, ) ) - return metrics - def _parse_cache_lines(self, text: str, cache_level: str) -> List[Dict]: - """ - Parse cache metrics from text lines. + def _parse_cambricon_cache(self, output: str, run_id: str) -> List[Dict]: + """Map Cambricon NRAM and L2 results to the existing cache schema.""" + metrics = [] + nram_match = re.search( + r"NRAM Bandwidth Test.*?Spread\s*-+\s*\n(.*?)(?=\n\s*=+|" + r"\nL2 Cache|\Z)", + output, + re.DOTALL, + ) + if nram_match: + rows = [] + for line in nram_match.group(1).strip().splitlines(): + parts = line.split() + if len(parts) >= 6: + try: + rows.append( + { + "data_set": f"{parts[0]} {parts[1]}", + "_sort_key": float(parts[0]), + "exec_time": parts[2], + "spread": parts[5], + "eff_bw": float(parts[3]), + } + ) + except (ValueError, IndexError): + pass + if rows: + metrics.append( + self._create_timeseries_metric( + "hardware.gpu_cache_l1", + rows, + f"cache_l1_bandwidth_{run_id}", + L1_CACHE_CSV_FIELDS, + ) + ) - Args: - text: Text containing cache data lines - cache_level: Either 'l1' or 'l2' + l2_match = re.search( + r"L2 Cache Bandwidth Sweep Test.*?Eff\. bw\s*-+\s*\n(.*?)(?=\Z)", + output, + re.DOTALL, + ) + if l2_match: + rows = self._parse_cache_lines(l2_match.group(1), "l2") + if rows: + metrics.append( + self._create_timeseries_metric( + "hardware.gpu_cache_l2", + rows, + f"cache_l2_bandwidth_{run_id}", + L2_CACHE_CSV_FIELDS, + ) + ) + return metrics - Returns: - List of parsed cache metric dictionaries - """ - csv_data = [] + def _parse_ascend_hierarchy(self, output: str, run_id: str) -> List[Dict]: + """Parse Ascend's D2D memory-hierarchy sweep without relabeling it L1/L2.""" + match = re.search( + r"Memory Hierarchy 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.memory_hierarchy_d2d", + rows, + f"memory_hierarchy_d2d_{run_id}", + L2_CACHE_CSV_FIELDS, + ) + ] + def _parse_cache_lines(self, text: str, cache_level: str) -> List[Dict]: + """Parse cache metric rows.""" + rows = [] for line in text.strip().split("\n"): - if parsed := self._parse_cache_line(line, cache_level): - csv_data.append(parsed) - - return csv_data - - def _parse_cache_line(self, line: str, cache_level: str) -> Optional[Dict]: - """ - Parse a single cache line. + parsed = self._parse_cache_line(line, cache_level) + if parsed: + rows.append(parsed) + return rows - Args: - line: Line of text containing cache metrics - cache_level: Either 'l1' or 'l2' - - Returns: - Dictionary with parsed metrics or None if parsing fails - """ + @staticmethod + def _parse_cache_line(line: str, cache_level: str) -> Optional[Dict]: + """Parse one CUDA-style cache metric row.""" parts = line.split() - if cache_level == "l1" and len(parts) >= 5: try: return { @@ -371,7 +615,6 @@ def _parse_cache_line(self, line: str, cache_level: str) -> Optional[Dict]: } except (ValueError, IndexError): pass - elif cache_level == "l2" and len(parts) >= 7: try: return { @@ -384,5 +627,4 @@ def _parse_cache_line(self, line: str, cache_level: str) -> Optional[Dict]: } except (ValueError, IndexError): pass - return None diff --git a/infinimetrics/utils/hardware_detector.py b/infinimetrics/utils/hardware_detector.py index 2af64bcc..ad9bb40a 100644 --- a/infinimetrics/utils/hardware_detector.py +++ b/infinimetrics/utils/hardware_detector.py @@ -26,6 +26,7 @@ class HardwareDetector: "--format=csv,noheader", ] AMD_SMI_CANDIDATES = ["amd-smi", "rocm-smi"] + MTHREADS_SMI_QUERY = ["mthreads-gmi", "-q"] @classmethod def detect(cls, accel_type_hint: str = "") -> Dict[str, Any]: @@ -53,6 +54,12 @@ def detect(cls, accel_type_hint: str = "") -> Dict[str, Any]: hw["accelerator_type"] = "nvidia" hw["cuda_version"] = cls._get_cuda_version() or hw["cuda_version"] return hw + if probe == "moore" and cls._probe_mthreads(hw): + hw["accelerator_type"] = "moore" + musa_ver = cls._get_musa_version() + if musa_ver: + hw["cuda_version"] = f"MUSA {musa_ver}" + return hw if probe == "amd" and cls._probe_amd(hw): hw["accelerator_type"] = "amd" return hw @@ -107,12 +114,9 @@ def _detect_memory(cls, hw: Dict[str, Any]) -> None: @classmethod def _get_probe_order(cls, hint: str) -> List[str]: hint = hint.lower().strip() - probes = ( - [hint] - if hint in ("nvidia", "amd", "ascend", "cambricon", "generic") - else [] - ) - for p in ["nvidia", "amd", "ascend", "cambricon", "generic"]: + valid_types = ("nvidia", "moore", "amd", "ascend", "cambricon", "generic") + probes = [hint] if hint in valid_types else [] + for p in ["nvidia", "moore", "amd", "ascend", "cambricon", "generic"]: if p not in probes: probes.append(p) return probes @@ -140,6 +144,34 @@ def _probe_nvidia(cls, hw: Dict[str, Any]) -> bool: except Exception: return False + @classmethod + def _probe_mthreads(cls, hw: Dict[str, Any]) -> bool: + try: + if not _which("mthreads-gmi"): + return False + r = subprocess.run( + cls.MTHREADS_SMI_QUERY, capture_output=True, text=True, timeout=5 + ) + if r.returncode != 0 or not r.stdout.strip(): + return False + + gpu_count = 0 + gpu_name = None + for line in r.stdout.splitlines(): + stripped = line.strip() + if re.match(r"^GPU\d+\s", stripped): + gpu_count += 1 + if stripped.startswith("Product Name") and ":" in stripped: + gpu_name = stripped.split(":", 1)[1].strip() + + if gpu_count > 0: + hw["gpu_count"] = max(hw["gpu_count"], gpu_count) + if hw["gpu_model"] == "Unknown": + hw["gpu_model"] = gpu_name or "Moore Threads GPU" + return True + except Exception: + return False + @classmethod def _probe_amd(cls, hw: Dict[str, Any]) -> bool: try: @@ -238,3 +270,18 @@ def _get_cuda_version(cls) -> Optional[str]: except Exception: pass return None + + @classmethod + def _get_musa_version(cls) -> Optional[str]: + try: + r = subprocess.run( + ["mcc", "--version"], capture_output=True, text=True, timeout=2 + ) + if r.returncode == 0: + for line in r.stdout.splitlines(): + m = re.search(r"(\d+\.\d+\.\d+)", line) + if m: + return m.group(1) + except Exception: + pass + return None diff --git a/tests/test_hardware_adapter.py b/tests/test_hardware_adapter.py new file mode 100644 index 00000000..c95739ae --- /dev/null +++ b/tests/test_hardware_adapter.py @@ -0,0 +1,260 @@ +from pathlib import Path + +import pytest + +from infinimetrics.dispatcher import Dispatcher +from infinimetrics.hardware import hardware_adapter +from infinimetrics.hardware.hardware_adapter import HardwareTestAdapter + + +CUDA_OUTPUT = """ +Direction: Host to Device +Size (MB) Time (ms)Bandwidth (GB/s) CV (%) +------------------------------------------------------- +64.00 1.000 12.50 1.0 + +=================================================== +STREAM Benchmark Suite +STREAM_Copy 100.00 +STREAM_Scale 90.00 +STREAM_Add 80.00 +STREAM_Triad 70.00 + +L1 Cache Bandwidth Sweep Test +Eff. bw +------------------------------------------------------- +4 kB 1.0ms 0.1% 200.0GB/s +L2 Cache Bandwidth Sweep Test +Eff. bw +------------------------------------------------------- +256 kB 64 kB 1.0ms 0.1% 300.0GB/s +""" + + +def test_constructor_preserves_cuda_perf_path_and_default_layout(tmp_path): + custom_binary = tmp_path / "custom_cuda_perf_suite" + adapter = HardwareTestAdapter(str(custom_binary), output_dir=str(tmp_path)) + + assert adapter.cuda_perf_path == str(custom_binary) + assert adapter.output_dir == tmp_path + assert adapter.build_dir.name == "cuda-memory-benchmark" + assert adapter.build_script == adapter.build_dir / "build.sh" + + +def test_build_command_preserves_cuda_cli_shape(tmp_path): + binary = tmp_path / "cuda_perf_suite" + adapter = HardwareTestAdapter(str(binary), output_dir=str(tmp_path)) + + command = adapter._build_command( + { + "test_type": "Comprehensive", + "device_id": 2, + "iterations": 7, + "array_size": 1024, + } + ) + + assert command == [ + str(binary), + "--all", + "--device", + "2", + "--iterations", + "7", + "--array-size", + "1024", + ] + + +def test_parse_stream_preserves_metric_names(tmp_path): + adapter = HardwareTestAdapter(output_dir=str(tmp_path)) + + metrics = adapter._parse_stream_benchmark(CUDA_OUTPUT) + + assert metrics == [ + { + "name": "hardware.stream_copy", + "value": 100.0, + "type": "scalar", + "unit": "GB/s", + }, + { + "name": "hardware.stream_scale", + "value": 90.0, + "type": "scalar", + "unit": "GB/s", + }, + { + "name": "hardware.stream_add", + "value": 80.0, + "type": "scalar", + "unit": "GB/s", + }, + { + "name": "hardware.stream_triad", + "value": 70.0, + "type": "scalar", + "unit": "GB/s", + }, + ] + + +def test_parse_comprehensive_preserves_cuda_metric_names(tmp_path): + adapter = HardwareTestAdapter(output_dir=str(tmp_path)) + + metrics = adapter._parse_output(CUDA_OUTPUT, "Comprehensive", "run-1") + names = [metric["name"] for metric in metrics] + + assert names == [ + "hardware.mem_sweep_h2d", + "hardware.stream_copy", + "hardware.stream_scale", + "hardware.stream_add", + "hardware.stream_triad", + "hardware.gpu_cache_l1", + "hardware.gpu_cache_l2", + ] + assert Path(tmp_path, "mem_sweep_h2d_run-1_").parent == tmp_path + assert any(tmp_path.glob("mem_sweep_h2d_run-1_*.csv")) + assert any(tmp_path.glob("cache_l1_bandwidth_run-1_*.csv")) + assert any(tmp_path.glob("cache_l2_bandwidth_run-1_*.csv")) + + +def test_parse_unknown_test_type_returns_no_metrics(tmp_path): + adapter = HardwareTestAdapter(output_dir=str(tmp_path)) + + assert adapter._parse_output(CUDA_OUTPUT, "Unknown", "run-1") == [] + + +@pytest.mark.parametrize( + ("device", "expected"), + [ + ("cuda", "cuda"), + ("nvidia", "cuda"), + ("metax", "metax"), + ("iluvatar", "corex"), + ("hygon", "hygon"), + ("moore", "moore"), + ("musa", "moore"), + ("cambricon", "cambricon"), + ("mlu", "cambricon"), + ("ascend", "ascend"), + ("npu", "ascend"), + ("legacy-unknown-device", "cuda"), + ], +) +def test_explicit_device_aliases_preserve_unknown_cuda_fallback( + tmp_path, device, expected +): + adapter = HardwareTestAdapter(output_dir=str(tmp_path)) + + assert adapter._get_device_type({"device": device}) == expected + + +def test_testcase_framework_precedes_runtime_detection(tmp_path, monkeypatch): + adapter = HardwareTestAdapter(output_dir=str(tmp_path)) + monkeypatch.setattr(hardware_adapter, "detect_platform", lambda: "moore") + + assert ( + adapter._get_device_type({"_testcase": "hardware.iluvatar.Stream"}) + == "corex" + ) + assert adapter._get_device_type({}) == "moore" + + +def test_native_platform_paths_do_not_change_cuda_path(tmp_path): + cuda_binary = tmp_path / "cuda_perf_suite" + adapter = HardwareTestAdapter(str(cuda_binary), output_dir=str(tmp_path)) + + assert adapter._get_binary_path("cuda") == str(cuda_binary) + assert adapter._get_binary_path("metax") == str(cuda_binary) + assert adapter._get_binary_path("cambricon").endswith( + "cambricon-memory-benchmark/build/mlu_perf_suite" + ) + assert adapter._get_binary_path("ascend").endswith( + "ascend-memory-benchmark/build/npu_perf_suite" + ) + + +@pytest.mark.parametrize( + "framework", + [ + "cudaunified", + "cuda", + "metax", + "corex", + "iluvatar", + "hygon", + "moore", + "cambricon", + "ascend", + ], +) +def test_dispatcher_registers_every_hardware_framework(framework): + adapter = Dispatcher()._create_adapter("hardware", framework) + + assert isinstance(adapter, HardwareTestAdapter) + + +def test_cambricon_cache_maps_to_existing_metric_names(tmp_path): + output = """ +NRAM Bandwidth Test (BANG Kernel) +NRAM chunk/core Time (ms) Eff. BW (GB/s) TFLOPS Spread +--------------------------------------------------------------------- +120 kB 1.0 200.0 1.2 0.5% + +=================================================== +L2 Cache Bandwidth Sweep Test (BANG Kernel) +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", "cam-run", "cambricon") + + assert [metric["name"] for metric in metrics] == [ + "hardware.gpu_cache_l1", + "hardware.gpu_cache_l2", + ] + + +def test_ascend_hierarchy_is_not_relabelled_as_cuda_cache(tmp_path): + output = """ +Memory Hierarchy Sweep Test (D2D Bandwidth) +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.memory_hierarchy_d2d" + ] + + +def test_native_benchmarks_use_selected_device_id(): + hardware_dir = Path(hardware_adapter.__file__).parent + native_files = [ + *hardware_dir.glob("cambricon-memory-benchmark/include/*.h"), + *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_cambricon_uses_current_cnrt_success_enum(): + source = ( + Path(hardware_adapter.__file__).parent + / "cambricon-memory-benchmark" + / "include" + / "cnrt_utils.h" + ).read_text(encoding="utf-8") + + assert "cnrtSuccess" in source + assert "CNRT_RET_SUCCESS" not in source diff --git a/tests/test_hardware_detection.py b/tests/test_hardware_detection.py new file mode 100644 index 00000000..723cbf6d --- /dev/null +++ b/tests/test_hardware_detection.py @@ -0,0 +1,59 @@ +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 + + +MTHREADS_OUTPUT = """ +Attached GPUs : 2 + +GPU0 00000000:03:00.0 + Product Name : MTT S5000 + Product Brand : MTT + GPU UUID : first-uuid + GPU Link Info + +GPU1 00000000:05:00.0 + Product Name : MTT S5000 + Product Brand : MTT + GPU UUID : second-uuid + GPU Link Info +""" + + +def _empty_hardware(): + return { + "gpu_count": 0, + "gpu_model": "Unknown", + "cuda_version": "Unknown", + } + + +def _successful_probe(*args, **kwargs): + return SimpleNamespace(returncode=0, stdout=MTHREADS_OUTPUT) + + +def test_hardware_collector_counts_only_mthreads_device_headers(monkeypatch): + monkeypatch.setattr(hardware_info, "_which", lambda command: command) + monkeypatch.setattr(hardware_info.subprocess, "run", _successful_probe) + monkeypatch.setattr(HardwareCollector, "_collect_musa_version", lambda self: None) + hardware = _empty_hardware() + + result = HardwareCollector()._probe_mthreads("moore", hardware) + + assert result.success + assert result.count == 2 + assert hardware["gpu_count"] == 2 + assert hardware["gpu_model"] == "MTT S5000" + + +def test_hardware_detector_counts_only_mthreads_device_headers(monkeypatch): + monkeypatch.setattr(hardware_detector, "_which", lambda command: command) + monkeypatch.setattr(hardware_detector.subprocess, "run", _successful_probe) + hardware = _empty_hardware() + + assert HardwareDetector._probe_mthreads(hardware) + assert hardware["gpu_count"] == 2 + assert hardware["gpu_model"] == "MTT S5000"