NPU-based Environment Test
Based on the functionality of the custom communication operator, developers need to write their own test cases for verification, which primarily includes input data generation and semantic logic checks. This section describes how to test communication operators.
Writing a Test Program
The test program involves the following steps:
- Allocate memory required for collective communication.
- Construct the input data.
- Initialize the communicator.
- Call the custom communication operator.
- Verify the communication result.
- Destroy allocations.
The following is the test program sample code. (You need to modify the logic as required. Do not directly execute the sample code.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | #include "acl/acl_rt.h" #include "hccl/hccl_types.h" #include "<custom_ops_header>" // User-defined API header file of the communication operator #define ACLCHECK(ret) \ do { \ if (ret != ACL_SUCCESS) { \ printf("acl interface return err %s:%d, retcode: %d \n", __FILE__, __LINE__, ret); \ return ret; \ } \ } while (0) #define HCCLCHECK(ret) \ do { \ if (ret != HCCL_SUCCESS) { \ printf("hccl interface return err %s:%d, retcode: %d \n", __FILE__, __LINE__, ret); \ return ret; \ } \ } while (0) struct ThreadContext { HcclRootInfo *rootInfo; uint32_t device; uint32_t devCount; }; int Sample(void *arg) { ThreadContext *ctx = (ThreadContext *)arg; void *sendBuf = nullptr; void *recvBuf = nullptr; uint32_t device = ctx->device; uint64_t count = ctx->devCount; size_t mallocSize = count * sizeof(float); // Set the device operated by the current thread. ACLCHECK(aclrtSetDevice(static_cast<int32_t>(device))); // Initialize the collective communicator. HcclComm hcclComm; HCCLCHECK(HcclCommInitRootInfo(ctx->devCount, ctx->rootInfo, device, &hcclComm)); // Create a task flow. aclrtStream stream; ACLCHECK(aclrtCreateStream(&stream)); // Construct the input data. void *hostBuf = nullptr; ACLCHECK(aclrtMallocHost(&hostBuf, mallocSize)); // TODO: Write the input data to hostBuf. // Copy the input data to the device. ACLCHECK(aclrtMalloc(&sendBuf, mallocSize, ACL_MEM_MALLOC_HUGE_ONLY)); ACLCHECK(aclrtMemcpy(sendBuf, mallocSize, hostBuf, mallocSize, ACL_MEMCPY_HOST_TO_DEVICE)); ACLCHECK(aclrtFreeHost(hostBuf)); // TODO: Call the custom collective communication operator. HCCLCHECK(HcclOpsCustom(sendBuf, count, HCCL_DATA_TYPE_FP32, hcclComm, stream)); // Wait until the collective communication task in the task flow is complete. ACLCHECK(aclrtSynchronizeStream(stream)); // Copy the collective communication task result from the device to the host. void *resultHostBuf; ACLCHECK(aclrtMallocHost(&resultHostBuf, mallocSize)); ACLCHECK(aclrtMemcpy(resultHostBuf, mallocSize, recvBuf, mallocSize, ACL_MEMCPY_DEVICE_TO_HOST)); // TODO: Verify the result data in resultHostBuf. ACLCHECK(aclrtFreeHost(resultHostBuf)); // Free resources. HCCLCHECK(HcclCommDestroy(hcclComm)); // Destroy the communicator. if (sendBuf) { ACLCHECK(aclrtFree(sendBuf)); // Free the device memory. } if (recvBuf) { ACLCHECK(aclrtFree(recvBuf)); // Free the device memory. } ACLCHECK(aclrtDestroyStream(stream)); // Destroy the task flow. ACLCHECK(aclrtResetDevice(device)); // Reset the device. return 0; } int main() { // Initialize device resources. ACLCHECK(aclInit(NULL)); // Query the number of devices. uint32_t devCount; ACLCHECK(aclrtGetDeviceCount(&devCount)); std::cout << "Found " << devCount << " NPU device(s) available" << std::endl; int32_t rootRank = 0; ACLCHECK(aclrtSetDevice(rootRank)); // Generate root rank information. The same rootInfo is used for each thread. void *rootInfoBuf = nullptr; ACLCHECK(aclrtMallocHost(&rootInfoBuf, sizeof(HcclRootInfo))); HcclRootInfo *rootInfo = (HcclRootInfo *)rootInfoBuf; HCCLCHECK(HcclGetRootInfo(rootInfo)); // Start the thread to perform collective communication. std::vector<std::thread> threads(devCount); std::vector<ThreadContext> args(devCount); for (uint32_t i = 0; i < devCount; i++) { args[i].rootInfo = rootInfo; args[i].device = i; args[i].devCount = devCount; threads[i] = std::thread(Sample, (void *)&args[i]); } for (uint32_t i = 0; i < devCount; i++) { threads[i].join(); } // Free resources. ACLCHECK(aclrtFreeHost(rootInfoBuf)); // Free the host memory. ACLCHECK(aclFinalize()); // Deinitialize the device. return 0; } |
Writing Makefile
The following is an example of the Makefile file. Modify it as required.
ifndef ASCEND_HOME_PATH
$(error "ASCEND_HOME_PATH is not set, please ensure CANN is properly installed and \
source environment variables by running `source /path/to/Ascend/cann/set_env.sh`")
endif
CXXFLAGS := -std=c++14 \
-Werror \
-fstack-protector-strong \
-fPIE -pie \
-O2 \
-s \
-Wl,-z,relro \
-Wl,-z,now \
-Wl,-z,noexecstack \
-Wl,--copy-dt-needed-entries
SOURCES = main.cc
ASCEND_INC_DIR = ${ASCEND_HOME_PATH}/include
ASCEND_LIB_DIR = ${ASCEND_HOME_PATH}/lib64
CUSTOM_OPS_INC_DIR = ${ASCEND_HOME_PATH}/opp/vendors/<vendor>/include
CUSTOM_OPS_LIB_DIR = ${ASCEND_HOME_PATH}/opp/vendors/<vendor>/lib64
LIBS = -L$(ASCEND_LIB_DIR) -lacl_rt -L${CUSTOM_OPS_LIB_DIR} -l<custom_ops_so>
INCS = -I$(ASCEND_INC_DIR) -I${CUSTOM_OPS_INC_DIR}
TARGET = test_custom_ops
# Default target
all:
g++ $(CXXFLAGS) $(SOURCES) $(INCS) $(LIBS) -o ${TARGET}
@echo "${TARGET} compile completed"
# Test target
test:
export LD_LIBRARY_PATH=${CUSTOM_P2P_LIB_DIR}:${LD_LIBRARY_PATH}; \
./$(TARGET)
# Clean build artifacts
clean:
rm ${TARGET}
.PHONY: all test clean
Executing the Test Program
- Disable signature verification for the AICPU operators.
The AICPU operator package is loaded to the device when the service is started. During the loading, the driver performs security signature verification by default to ensure the reliability of the package. However, the AICPU operator package generated after your compilation does not contain the signature header. Therefore, you need to manually disable the signature verification mechanism of the driver before loading the package.
Run the following commands on the physical machine as the root user. Device 0 is used as an example.
npu-smi set -t custom-op-secverify-enable -i 0 -d 1 # Enable signature verification configuration. npu-smi set -t custom-op-secverify-mode -i 0 -d 0 # Disable custom signature verification.
Disabling the driver security verification mechanism poses security risks. Ensure the security and reliability of custom communication operators to prevent malicious attacks.
- Modify the AICPU trustlist.
If you add an AICPU operator package, include it in the AICPU trustlist. The default installation path for the root user is used as an example. Edit the ascend_package_load.ini file.
vim /usr/local/Ascend/cann/conf/ascend_package_load.ini
Add the following content to the ascend_package_load.ini file:name:<aicpu_kernel_file_name> install_path:2 optional:true package_path:<aicpu_kernel_file_path>
Where:
- <aicpu_kernel_file_name>: name of the AICPU operator package in .tar.gz format, for example, aicpu_hccl_custom_p2p.tar.gz.
- <aicpu_kernel_file_path>: relative path of the AICPU operator package in the CANN package, for example, opp/vendors/cust/aicpu/kernel.
- Build and execute the test sample.
1 2 3 4
# Build the project. make # Execute the test cases. make test