文件最后提交记录最后更新时间
6 天前
6 天前
README

非零元素统计算子

算子概述

nnz 算子用于统计稠密矩阵中每行或每列的非零元素个数,以及总非零元素个数。该函数是 dense-to-sparse 格式转换流程的第一步,典型用法为:先调用 nnz 获取每行/列非零元素个数和总非零元素个数,再根据统计结果构建 CSR 或 CSC 格式的偏移数组(通过前缀和),最后执行 dense-to-sparse 转换。

核心运算为:

dirA = ROW:    nnzPerRow[i] = sum_{j=0}^{n-1} [A[i][j] != 0],  i in [0, m)
dirA = COLUMN: nnzPerCol[j] = sum_{i=0}^{m-1} [A[i][j] != 0],  j in [0, n)
nnzTotal = sum(nnzPerRow) = sum(nnzPerCol)

其中 [A[i][j] != 0] 为指示函数,当 A[i][j] != 0 时取 1,否则取 0。

包含以下接口:

接口名 功能简述
aclsparseSnnz 统计 FP32 稠密矩阵每行或每列的非零元素个数及总非零元素个数

算子执行接口

aclsparseSnnz

产品支持情况

  • Ascend 950PR / Ascend 950DT:支持
  • Atlas A3 训练系列产品 / Atlas A3 推理系列产品:不支持
  • Atlas A2 训练系列产品 / Atlas A2 推理系列产品:不支持

函数原型

aclsparseStatus_t aclsparseSnnz(aclsparseHandle_t handle, aclsparseDirection_t dirA, int m, int n, const aclsparseMatDescr_t descrA, const float *A, int lda, int *nnzPerRowColumn, int *nnzTotalDevHostPtr)

参数说明

参数名 输入/输出 参数类型 说明
handle 输入 aclsparseHandle_t ops-sparse 库上下文句柄,携带 stream,Host 内存
dirA 输入 aclsparseDirection_t 统计方向:ACL_SPARSE_DIRECTION_ROW(按行)或 ACL_SPARSE_DIRECTION_COLUMN(按列),Host 内存
m 输入 int 矩阵 A 的行数,Host 内存
n 输入 int 矩阵 A 的列数,Host 内存
descrA 输入 const aclsparseMatDescr_t 矩阵描述符,仅支持 ACL_SPARSE_MATRIX_TYPE_GENERAL 类型,Host 内存
A 输入 const float*(FP32) 稠密矩阵数据,列主序存储,维度为 lda × n,元素 A[i][j] = A[j * lda + i],Device 内存
lda 输入 int 矩阵 A 的 leading dimension,Host 内存
nnzPerRowColumn 输出 int* 每行(dirA=ROW 时大小为 m)或每列(dirA=COLUMN 时大小为 n)的非零元素个数数组,Device 内存
nnzTotalDevHostPtr 输出 int* 总非零元素个数,内存位置由 aclsparseSetPointerMode 控制:HOST 模式为 Host 内存,DEVICE 模式为 Device 内存

约束说明

  • m >= 0
  • n >= 0
  • lda >= max(1, m)
  • descrA 不可为 nullptr,且 descrA.type 仅支持 ACL_SPARSE_MATRIX_TYPE_GENERAL
  • 当 m > 0 且 n > 0 时,A 不可为 nullptr
  • 当 totalUnits > 0 时(totalUnits = m 或 n,取决于 dirA),nnzPerRowColumn 不可为 nullptr
  • nnzTotalDevHostPtr 不可为 nullptr
  • 当 m = 0 或 n = 0 时,函数直接返回成功,nnzTotal 置 0,nnzPerRowColumn 不写入

调用示例

示例代码如下,仅供参考,具体编译和执行过程请参考编译与运行样例

#include <cstdio>
#include <memory>
#include <vector>

#include "acl/acl.h"
#include "cann_ops_sparse.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)

class AclContext {
public:
    explicit AclContext(int32_t deviceId) : deviceId_(deviceId) {}

    ~AclContext()
    {
        if (stream_ != nullptr) {
            aclrtDestroyStream(stream_);
            stream_ = nullptr;
        }
        if (deviceSet_) {
            aclrtResetDevice(deviceId_);
            deviceSet_ = false;
        }
        if (aclInited_) {
            aclFinalize();
            aclInited_ = false;
        }
    }

    int Init()
    {
        auto ret = aclInit(nullptr);
        CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclInit failed. ERROR: %d\n", ret); return ret);
        aclInited_ = true;

        ret = aclrtSetDevice(deviceId_);
        CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtSetDevice failed. ERROR: %d\n", ret); return ret);
        deviceSet_ = true;

        ret = aclrtCreateStream(&stream_);
        CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclrtCreateStream failed. ERROR: %d\n", ret); return ret);
        return ACL_SUCCESS;
    }

    aclrtStream Stream() const { return stream_; }

private:
    int32_t deviceId_;
    aclrtStream stream_ = nullptr;
    bool aclInited_ = false;
    bool deviceSet_ = false;
};

int aclsparseSnnzTest(AclContext& ctx)
{
    aclrtStream stream = ctx.Stream();

    // 1. 创建 ops-sparse 句柄
    aclsparseHandle_t rawHandle = nullptr;
    auto sparseRet = aclsparseCreate(&rawHandle);
    CHECK_RET(sparseRet == ACL_SPARSE_STATUS_SUCCESS, LOG_PRINT("aclsparseCreate failed. ERROR: %d\n", sparseRet);
              return sparseRet);
    std::unique_ptr<aclsparseContext, aclsparseStatus_t (*)(aclsparseHandle_t)> handlePtr(rawHandle, aclsparseDestroy);

    sparseRet = aclsparseSetStream(static_cast<aclsparseHandle_t>(handlePtr.get()), stream);
    CHECK_RET(sparseRet == ACL_SPARSE_STATUS_SUCCESS, LOG_PRINT("aclsparseSetStream failed. ERROR: %d\n", sparseRet);
              return sparseRet);

    // 使用 DEVICE 模式:nnzTotalDevHostPtr 指向 Device 内存
    sparseRet = aclsparseSetPointerMode(static_cast<aclsparseHandle_t>(handlePtr.get()), ACL_SPARSE_POINTER_MODE_DEVICE);
    CHECK_RET(sparseRet == ACL_SPARSE_STATUS_SUCCESS, LOG_PRINT("aclsparseSetPointerMode failed. ERROR: %d\n", sparseRet);
              return sparseRet);

    // 2. 准备 Host 数据(列主序 3x3 矩阵)
    //    矩阵内容:
    //    [1.0  0.0  3.0]
    //    [0.0  0.0  4.0]
    //    [2.0  0.0  0.0]
    int m = 3, n = 3, lda = 3;
    std::vector<float> hA(static_cast<size_t>(lda) * n, 0.0f);
    hA[0 * lda + 0] = 1.0f;  // A[0][0]
    hA[0 * lda + 2] = 2.0f;  // A[2][0]
    hA[2 * lda + 0] = 3.0f;  // A[0][2]
    hA[2 * lda + 1] = 4.0f;  // A[1][2]

    // 3. 申请 Device 内存并拷贝数据
    void* rawMemA = nullptr;
    auto aclRet = aclrtMalloc(&rawMemA, static_cast<size_t>(lda) * n * sizeof(float), ACL_MEM_MALLOC_HUGE_FIRST);
    CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMalloc for A failed. ERROR: %d\n", aclRet); return aclRet);
    std::unique_ptr<void, aclError (*)(void*)> dA(rawMemA, aclrtFree);

    aclRet = aclrtMemcpy(dA.get(), static_cast<size_t>(lda) * n * sizeof(float),
                          hA.data(), static_cast<size_t>(lda) * n * sizeof(float),
                          ACL_MEMCPY_HOST_TO_DEVICE);
    CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMemcpy for A failed. ERROR: %d\n", aclRet); return aclRet);

    void* rawMemNnzPerRow = nullptr;
    aclRet = aclrtMalloc(&rawMemNnzPerRow, m * sizeof(int), ACL_MEM_MALLOC_HUGE_FIRST);
    CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMalloc for nnzPerRow failed. ERROR: %d\n", aclRet); return aclRet);
    std::unique_ptr<void, aclError (*)(void*)> dNnzPerRow(rawMemNnzPerRow, aclrtFree);

    void* rawMemNnzTotal = nullptr;
    aclRet = aclrtMalloc(&rawMemNnzTotal, sizeof(int), ACL_MEM_MALLOC_HUGE_FIRST);
    CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("aclrtMalloc for nnzTotal failed. ERROR: %d\n", aclRet); return aclRet);
    std::unique_ptr<void, aclError (*)(void*)> dNnzTotal(rawMemNnzTotal, aclrtFree);

    // 4. 创建 MatDescr(Legacy API)
    aclsparseMatDescr_t matDescr = nullptr;
    sparseRet = aclsparseCreateMatDescr(&matDescr);
    CHECK_RET(sparseRet == ACL_SPARSE_STATUS_SUCCESS, LOG_PRINT("aclsparseCreateMatDescr failed. ERROR: %d\n", sparseRet);
              return sparseRet);
    aclsparseSetMatType(matDescr, ACL_SPARSE_MATRIX_TYPE_GENERAL);
    aclsparseSetMatIndexBase(matDescr, ACL_SPARSE_INDEX_BASE_ZERO);

    // 5. 调用 aclsparseSnnz(按行统计)
    aclsparseDirection_t dirA = ACL_SPARSE_DIRECTION_ROW;
    sparseRet = aclsparseSnnz(
        static_cast<aclsparseHandle_t>(handlePtr.get()), dirA, m, n, matDescr,
        static_cast<const float*>(dA.get()), lda,
        static_cast<int*>(dNnzPerRow.get()), static_cast<int*>(dNnzTotal.get()));
    CHECK_RET(sparseRet == ACL_SPARSE_STATUS_SUCCESS, LOG_PRINT("aclsparseSnnz failed. ERROR: %d\n", sparseRet);
              aclsparseDestroyMatDescr(matDescr); return sparseRet);

    // 6. 同步等待任务执行结束
    aclRet = aclrtSynchronizeStream(stream);
    CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("aclrtSynchronizeStream failed. ERROR: %d\n", aclRet); return aclRet);

    // 7. 将结果从 Device 拷贝回 Host 并打印
    std::vector<int> hNnzPerRow(m, 0);
    aclRet = aclrtMemcpy(hNnzPerRow.data(), m * sizeof(int),
                          dNnzPerRow.get(), m * sizeof(int),
                          ACL_MEMCPY_DEVICE_TO_HOST);
    CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("copy nnzPerRow from device to host failed. ERROR: %d\n", aclRet);
              return aclRet);

    int hNnzTotal = 0;
    aclRet = aclrtMemcpy(&hNnzTotal, sizeof(int),
                          dNnzTotal.get(), sizeof(int),
                          ACL_MEMCPY_DEVICE_TO_HOST);
    CHECK_RET(aclRet == ACL_SUCCESS, LOG_PRINT("copy nnzTotal from device to host failed. ERROR: %d\n", aclRet);
              return aclRet);

    for (int i = 0; i < m; i++) {
        LOG_PRINT("nnzPerRow[%d] is: %d\n", i, hNnzPerRow[i]);
    }
    LOG_PRINT("nnzTotal is: %d\n", hNnzTotal);

    // 8. 清理 MatDescr
    aclsparseDestroyMatDescr(matDescr);

    return ACL_SPARSE_STATUS_SUCCESS;
}

int main()
{
    AclContext ctx(0);
    auto ret = ctx.Init();
    CHECK_RET(ret == ACL_SUCCESS, return ret);

    ret = aclsparseSnnzTest(ctx);
    CHECK_RET(ret == ACL_SPARSE_STATUS_SUCCESS, LOG_PRINT("aclsparseSnnzTest failed. ERROR: %d\n", ret); return ret);
    return 0;
}

预期输出如下:

nnzPerRow[0] is: 2
nnzPerRow[1] is: 1
nnzPerRow[2] is: 1
nnzTotal is: 4