#include <iostream>
#include <vector>
#include <cerrno>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <random>
#include <chrono>
#include <algorithm>
#include <limits>
#include <map>
#include <string>
#include "acl/acl.h"
#include "aclnn_unique_v3.h"
#define CHECK_RET(cond, return_expr) \
do { \
if (!(cond)) { \
return_expr; \
} \
} while (0)
#define LOG_PRINT(message, ...) \
do { \
printf(message, ##__VA_ARGS__); \
} while (0)
static constexpr int64_t TILE_LENGTH = 8192;
static int64_t AlignUp(int64_t n, int64_t align)
{
return ((n + align - 1) / align) * align;
}
int64_t GetShapeSize(const std::vector<int64_t>& shape)
{
int64_t shapeSize = 1;
for (auto i : shape) {
shapeSize *= i;
}
return shapeSize;
}
int Init(int32_t deviceId, aclrtStream* stream)
{
auto ret = aclInit(nullptr);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclInit failed. ERROR: %d\n", ret); return ret);
ret = aclrtSetDevice(deviceId);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSetDevice failed. ERROR: %d\n", ret); return ret);
ret = aclrtCreateStream(stream);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtCreateStream failed. ERROR: %d\n", ret); return ret);
return 0;
}
template <typename T>
int CreateAclTensorAligned(
const std::vector<T>& hostData,
const std::vector<int64_t>& tensorShape,
int64_t allocElems,
void** deviceAddr,
aclDataType dataType,
aclTensor** tensor)
{
int64_t allocSize = allocElems * sizeof(T);
int64_t copySize = static_cast<int64_t>(hostData.size()) * sizeof(T);
auto ret = aclrtMalloc(deviceAddr, allocSize, ACL_MEM_MALLOC_HUGE_FIRST);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMalloc failed. ERROR: %d\n", ret); return ret);
ret = aclrtMemset(*deviceAddr, allocSize, 0, allocSize);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMemset failed. ERROR: %d\n", ret); return ret);
if (copySize > 0) {
ret = aclrtMemcpy(*deviceAddr, allocSize, hostData.data(), copySize, ACL_MEMCPY_HOST_TO_DEVICE);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy failed. ERROR: %d\n", ret); return ret);
}
std::vector<int64_t> strides(tensorShape.size(), 1);
for (int64_t i = static_cast<int64_t>(tensorShape.size()) - 2; i >= 0; i--) {
strides[i] = tensorShape[i + 1] * strides[i + 1];
}
*tensor = aclCreateTensor(
tensorShape.data(), tensorShape.size(), dataType, strides.data(), 0,
aclFormat::ACL_FORMAT_ND, tensorShape.data(), tensorShape.size(), *deviceAddr);
return 0;
}
void CpuUniqueV3(const std::vector<float>& input,
std::vector<float>& outputUnique,
int32_t& uniqueCnt,
std::vector<int32_t>& inverse,
std::vector<int32_t>& counts)
{
int64_t N = static_cast<int64_t>(input.size());
std::vector<float> sorted = input;
std::sort(sorted.begin(), sorted.end(), std::greater<float>());
outputUnique.clear();
counts.clear();
if (N == 0) {
uniqueCnt = 0;
return;
}
outputUnique.push_back(sorted[0]);
int32_t cnt = 1;
for (int64_t i = 1; i < N; i++) {
if (sorted[i] != sorted[i - 1]) {
counts.push_back(cnt);
outputUnique.push_back(sorted[i]);
cnt = 1;
} else {
cnt++;
}
}
counts.push_back(cnt);
uniqueCnt = static_cast<int32_t>(outputUnique.size());
std::map<float, int32_t> valToIdx;
for (int32_t i = 0; i < uniqueCnt; i++) {
valToIdx[outputUnique[i]] = i;
}
inverse.resize(N);
for (int64_t i = 0; i < N; i++) {
inverse[i] = valToIdx[input[i]];
}
}
int RunUniqueV3Test(
aclrtStream stream,
const std::vector<float>& inputHostData,
bool flagInverse,
bool flagCounts,
const char* testName)
{
const int64_t N = static_cast<int64_t>(inputHostData.size());
const int64_t alignedN = AlignUp(N, TILE_LENGTH);
LOG_PRINT("\n========================================\n");
LOG_PRINT("Test: %s\n", testName);
LOG_PRINT(" N=%ld, alignedN=%ld, flag_inverse=%s, flag_counts=%s\n",
N, alignedN, flagInverse ? "true" : "false", flagCounts ? "true" : "false");
LOG_PRINT("========================================\n");
LOG_PRINT("Computing CPU reference...\n");
auto cpuT0 = std::chrono::high_resolution_clock::now();
std::vector<float> cpuOutput;
int32_t cpuUniqueCnt = 0;
std::vector<int32_t> cpuInverse;
std::vector<int32_t> cpuCounts;
CpuUniqueV3(inputHostData, cpuOutput, cpuUniqueCnt, cpuInverse, cpuCounts);
auto cpuT1 = std::chrono::high_resolution_clock::now();
double cpuMs = std::chrono::duration<double, std::milli>(cpuT1 - cpuT0).count();
LOG_PRINT(" CPU reference time: %.3f ms\n", cpuMs);
LOG_PRINT(" CPU uniqueCnt = %d\n", cpuUniqueCnt);
LOG_PRINT("Creating ACL tensors (aligned to %ld)...\n", alignedN);
int ret;
std::vector<int64_t> inputShape = {N};
aclTensor* inputTensor = nullptr;
void* inputDeviceAddr = nullptr;
ret = CreateAclTensorAligned(inputHostData, inputShape, alignedN,
&inputDeviceAddr, aclDataType::ACL_FLOAT, &inputTensor);
CHECK_RET(ret == ACL_SUCCESS, return 1);
std::vector<int64_t> outputShape = {N};
std::vector<float> outputHostData(N, 0.0f);
aclTensor* outputTensor = nullptr;
void* outputDeviceAddr = nullptr;
ret = CreateAclTensorAligned(outputHostData, outputShape, alignedN,
&outputDeviceAddr, aclDataType::ACL_FLOAT, &outputTensor);
CHECK_RET(ret == ACL_SUCCESS, return 1);
std::vector<int64_t> uniqueCntShape = {1};
std::vector<int32_t> uniqueCntHostData(1, 0);
aclTensor* uniqueCntTensor = nullptr;
void* uniqueCntDeviceAddr = nullptr;
ret = CreateAclTensorAligned(uniqueCntHostData, uniqueCntShape, 8,
&uniqueCntDeviceAddr, aclDataType::ACL_INT32, &uniqueCntTensor);
CHECK_RET(ret == ACL_SUCCESS, return 1);
std::vector<int64_t> inverseShape = {N};
std::vector<int32_t> inverseHostData(N, 0);
aclTensor* inverseTensor = nullptr;
void* inverseDeviceAddr = nullptr;
ret = CreateAclTensorAligned(inverseHostData, inverseShape, alignedN,
&inverseDeviceAddr, aclDataType::ACL_INT32, &inverseTensor);
CHECK_RET(ret == ACL_SUCCESS, return 1);
std::vector<int64_t> countsShape = {N};
std::vector<int32_t> countsHostData(N, 0);
aclTensor* countsTensor = nullptr;
void* countsDeviceAddr = nullptr;
ret = CreateAclTensorAligned(countsHostData, countsShape, alignedN,
&countsDeviceAddr, aclDataType::ACL_INT32, &countsTensor);
CHECK_RET(ret == ACL_SUCCESS, return 1);
uint64_t workspaceSize = 0;
aclOpExecutor* executor;
LOG_PRINT("Calling aclnnUniqueV3GetWorkspaceSize...\n");
ret = aclnnUniqueV3GetWorkspaceSize(inputTensor, flagInverse, flagCounts,
outputTensor, uniqueCntTensor, inverseTensor, countsTensor,
&workspaceSize, &executor);
CHECK_RET(ret == ACL_SUCCESS,
LOG_PRINT("aclnnUniqueV3GetWorkspaceSize failed. ERROR: %d\n", ret); return 1);
LOG_PRINT(" workspaceSize = %lu bytes\n", workspaceSize);
void* workspaceAddr = nullptr;
if (workspaceSize > 0) {
ret = aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return 1);
}
LOG_PRINT("Executing aclnnUniqueV3...\n");
auto t0 = std::chrono::high_resolution_clock::now();
ret = aclnnUniqueV3(workspaceAddr, workspaceSize, executor, stream);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnUniqueV3 failed. ERROR: %d\n", ret); return 1);
ret = aclrtSynchronizeStream(stream);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", ret); return 1);
auto t1 = std::chrono::high_resolution_clock::now();
double deviceMs = std::chrono::duration<double, std::milli>(t1 - t0).count();
LOG_PRINT(" Device execution time: %.3f ms\n", deviceMs);
LOG_PRINT("Copying results from device to host...\n");
std::vector<int32_t> resultUniqueCnt(1, 0);
ret = aclrtMemcpy(resultUniqueCnt.data(), sizeof(int32_t), uniqueCntDeviceAddr, sizeof(int32_t),
ACL_MEMCPY_DEVICE_TO_HOST);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy uniqueCnt failed. ERROR: %d\n", ret); return 1);
int32_t deviceUniqueCnt = resultUniqueCnt[0];
LOG_PRINT(" Device uniqueCnt = %d\n", deviceUniqueCnt);
std::vector<float> resultOutput(N, 0.0f);
ret = aclrtMemcpy(resultOutput.data(), N * sizeof(float), outputDeviceAddr, N * sizeof(float),
ACL_MEMCPY_DEVICE_TO_HOST);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy output failed. ERROR: %d\n", ret); return 1);
std::vector<int32_t> resultInverse(N, 0);
ret = aclrtMemcpy(resultInverse.data(), N * sizeof(int32_t), inverseDeviceAddr, N * sizeof(int32_t),
ACL_MEMCPY_DEVICE_TO_HOST);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy inverse failed. ERROR: %d\n", ret); return 1);
std::vector<int32_t> resultCounts(N, 0);
ret = aclrtMemcpy(resultCounts.data(), N * sizeof(int32_t), countsDeviceAddr, N * sizeof(int32_t),
ACL_MEMCPY_DEVICE_TO_HOST);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("copy counts failed. ERROR: %d\n", ret); return 1);
bool pass = true;
int64_t mismatchCount = 0;
constexpr int64_t MAX_PRINT_MISMATCH = 20;
LOG_PRINT("\n--- Verifying uniqueCnt ---\n");
if (deviceUniqueCnt != cpuUniqueCnt) {
LOG_PRINT(" [MISMATCH] uniqueCnt: device=%d, cpu_ref=%d\n", deviceUniqueCnt, cpuUniqueCnt);
pass = false;
} else {
LOG_PRINT(" [OK] uniqueCnt = %d\n", deviceUniqueCnt);
}
int32_t checkCnt = cpuUniqueCnt;
LOG_PRINT("\n--- Verifying unique output values (using cpuUniqueCnt=%d) ---\n", checkCnt);
mismatchCount = 0;
for (int32_t i = 0; i < checkCnt; i++) {
if (resultOutput[i] != cpuOutput[i]) {
if (mismatchCount < MAX_PRINT_MISMATCH) {
LOG_PRINT(" [MISMATCH] output[%d]: device=%.0f, cpu_ref=%.0f\n", i, resultOutput[i], cpuOutput[i]);
}
mismatchCount++;
}
}
if (mismatchCount == 0) {
LOG_PRINT(" [OK] All %d unique values match CPU reference.\n", checkCnt);
} else {
LOG_PRINT(" [FAIL] %ld / %d unique values mismatch.\n", mismatchCount, checkCnt);
pass = false;
}
LOG_PRINT(" Sample unique values (first 20): ");
for (int32_t i = 0; i < std::min(checkCnt, (int32_t)20); i++) {
LOG_PRINT("%.0f ", resultOutput[i]);
}
LOG_PRINT("...\n");
LOG_PRINT(" CPU ref unique values (first 20): ");
for (int32_t i = 0; i < std::min(cpuUniqueCnt, (int32_t)20); i++) {
LOG_PRINT("%.0f ", cpuOutput[i]);
}
LOG_PRINT("...\n");
if (flagInverse) {
LOG_PRINT("\n--- Verifying inverse mapping (using cpuUniqueCnt=%d as valid range) ---\n", cpuUniqueCnt);
mismatchCount = 0;
for (int64_t i = 0; i < N; i++) {
int32_t invIdx = resultInverse[i];
if (invIdx < 0 || invIdx >= cpuUniqueCnt) {
if (mismatchCount < MAX_PRINT_MISMATCH) {
LOG_PRINT(" [MISMATCH] inverse[%ld]=%d out of range [0, %d)\n", i, invIdx, cpuUniqueCnt);
}
mismatchCount++;
continue;
}
if (resultOutput[invIdx] != inputHostData[i]) {
if (mismatchCount < MAX_PRINT_MISMATCH) {
LOG_PRINT(" [MISMATCH] input[%ld]=%.0f, but output[inverse[%ld]]=output[%d]=%.0f\n",
i, inputHostData[i], i, invIdx, resultOutput[invIdx]);
}
mismatchCount++;
}
}
if (mismatchCount == 0) {
LOG_PRINT(" [OK] All %ld inverse mappings are correct.\n", N);
} else {
LOG_PRINT(" [FAIL] %ld / %ld inverse mappings mismatch.\n", mismatchCount, N);
pass = false;
}
mismatchCount = 0;
for (int64_t i = 0; i < N; i++) {
if (resultInverse[i] != cpuInverse[i]) {
mismatchCount++;
}
}
LOG_PRINT(" [INFO] %ld / %ld inverse indices differ from CPU reference.\n", mismatchCount, N);
LOG_PRINT(" Device inverse (first 20): ");
for (int64_t i = 0; i < std::min(N, (int64_t)20); i++) {
LOG_PRINT("%d ", resultInverse[i]);
}
LOG_PRINT("...\n");
LOG_PRINT(" CPU inverse (first 20): ");
for (int64_t i = 0; i < std::min(N, (int64_t)20); i++) {
LOG_PRINT("%d ", cpuInverse[i]);
}
LOG_PRINT("...\n");
}
if (flagCounts) {
LOG_PRINT("\n--- Verifying counts (using cpuUniqueCnt=%d) ---\n", cpuUniqueCnt);
mismatchCount = 0;
for (int32_t j = 0; j < checkCnt; j++) {
if (resultCounts[j] != cpuCounts[j]) {
if (mismatchCount < MAX_PRINT_MISMATCH) {
LOG_PRINT(" [MISMATCH] counts[%d]: device=%d, cpu_ref=%d (value=%.0f)\n",
j, resultCounts[j], cpuCounts[j], cpuOutput[j]);
}
mismatchCount++;
}
}
if (mismatchCount == 0) {
LOG_PRINT(" [OK] All %d counts match CPU reference.\n", checkCnt);
} else {
LOG_PRINT(" [FAIL] %ld / %d counts mismatch.\n", mismatchCount, checkCnt);
pass = false;
}
int64_t countsSum = 0;
for (int32_t j = 0; j < cpuUniqueCnt; j++) {
countsSum += resultCounts[j];
}
LOG_PRINT(" Sum of counts (first %d) = %ld, expected N = %ld\n", cpuUniqueCnt, countsSum, N);
LOG_PRINT(" Device counts (first 20): ");
for (int32_t j = 0; j < std::min(cpuUniqueCnt, (int32_t)20); j++) {
LOG_PRINT("%d ", resultCounts[j]);
}
LOG_PRINT("...\n");
LOG_PRINT(" CPU counts (first 20): ");
for (int32_t j = 0; j < std::min(cpuUniqueCnt, (int32_t)20); j++) {
LOG_PRINT("%d ", cpuCounts[j]);
}
LOG_PRINT("...\n");
}
aclDestroyTensor(inputTensor);
aclDestroyTensor(outputTensor);
aclDestroyTensor(uniqueCntTensor);
aclDestroyTensor(inverseTensor);
aclDestroyTensor(countsTensor);
aclrtFree(inputDeviceAddr);
aclrtFree(outputDeviceAddr);
aclrtFree(uniqueCntDeviceAddr);
aclrtFree(inverseDeviceAddr);
aclrtFree(countsDeviceAddr);
if (workspaceSize > 0) {
aclrtFree(workspaceAddr);
}
LOG_PRINT("\n========================================\n");
if (pass) {
LOG_PRINT("[PASS] %s\n", testName);
} else {
LOG_PRINT("[FAIL] %s\n", testName);
}
LOG_PRINT("========================================\n");
return pass ? 0 : 1;
}
bool ParseIntegerArgument(
const char* text, int64_t minValue, int64_t maxValue, int64_t& result)
{
if (text == nullptr || *text == '\0') {
return false;
}
errno = 0;
char* end = nullptr;
const long long value = std::strtoll(text, &end, 10);
if (errno == ERANGE || end == text || *end != '\0' ||
value < minValue || value > maxValue) {
return false;
}
result = static_cast<int64_t>(value);
return true;
}
void PrintUsage(const char* program)
{
LOG_PRINT(
"Usage: %s [element_count] [value_range] [device_id] [seed]\n"
" element_count: positive integer, default 10000000\n"
" value_range: positive integer, default 10000000\n"
" device_id: non-negative integer, default 0\n"
" seed: non-negative integer, default 42\n",
program);
}
int main(int argc, char* argv[])
{
int64_t elementCount = 10000000;
int32_t valueRange = 10000000;
int32_t deviceId = 0;
uint32_t seed = 42;
if (argc > 1 && (std::string(argv[1]) == "-h" || std::string(argv[1]) == "--help")) {
PrintUsage(argv[0]);
return 0;
}
if (argc > 5) {
PrintUsage(argv[0]);
return 1;
}
int64_t parsedValue = 0;
if (argc > 1 && !ParseIntegerArgument(
argv[1], 1, std::numeric_limits<uint32_t>::max(), elementCount)) {
LOG_PRINT("Invalid element_count: %s\n", argv[1]);
PrintUsage(argv[0]);
return 1;
}
if (argc > 2) {
if (!ParseIntegerArgument(
argv[2], 1, std::numeric_limits<int32_t>::max(), parsedValue)) {
LOG_PRINT("Invalid value_range: %s\n", argv[2]);
PrintUsage(argv[0]);
return 1;
}
valueRange = static_cast<int32_t>(parsedValue);
}
if (argc > 3) {
if (!ParseIntegerArgument(
argv[3], 0, std::numeric_limits<int32_t>::max(), parsedValue)) {
LOG_PRINT("Invalid device_id: %s\n", argv[3]);
PrintUsage(argv[0]);
return 1;
}
deviceId = static_cast<int32_t>(parsedValue);
}
if (argc > 4) {
if (!ParseIntegerArgument(
argv[4], 0, std::numeric_limits<uint32_t>::max(), parsedValue)) {
LOG_PRINT("Invalid seed: %s\n", argv[4]);
PrintUsage(argv[0]);
return 1;
}
seed = static_cast<uint32_t>(parsedValue);
}
LOG_PRINT("========================================\n");
LOG_PRINT("UniqueV3 Test Suite (float32, integer-valued)\n");
LOG_PRINT(" N = %lld, alignedN = %lld, value range = [0.0, %.0f)\n",
static_cast<long long>(elementCount),
static_cast<long long>(AlignUp(elementCount, TILE_LENGTH)),
static_cast<float>(valueRange));
LOG_PRINT(" device = %d, seed = %u\n", deviceId, seed);
LOG_PRINT("========================================\n");
aclrtStream stream;
auto ret = Init(deviceId, &stream);
CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret);
std::mt19937 rng(seed);
std::uniform_int_distribution<int32_t> valueDist(0, valueRange - 1);
LOG_PRINT(
"Generating input data [%lld] (float32, integer-valued)...\n",
static_cast<long long>(elementCount));
std::vector<float> inputHostData(elementCount);
for (int64_t i = 0; i < elementCount; i++) {
inputHostData[i] = static_cast<float>(valueDist(rng));
}
LOG_PRINT(" Input preview (first 20): ");
for (int64_t i = 0; i < 20 && i < elementCount; i++) {
LOG_PRINT("%.0f ", inputHostData[i]);
}
LOG_PRINT("...\n");
int totalResult = 0;
ret = RunUniqueV3Test(stream, inputHostData, true, true,
"Test3: Unique + Inverse + Counts (flag_inverse=true, flag_counts=true)");
totalResult |= ret;
aclrtDestroyStream(stream);
aclrtResetDevice(deviceId);
aclFinalize();
LOG_PRINT("\n========================================\n");
if (totalResult == 0) {
LOG_PRINT("[ALL PASS] All UniqueV3 tests passed!\n");
} else {
LOG_PRINT("[SOME FAILED] One or more UniqueV3 tests failed.\n");
}
LOG_PRINT("========================================\n");
return totalResult;
}