* ----------------------------------------------------------------------------------------------------------
* Copyright (c) 2026 Huawei Technologies Co., Ltd.
* This program is free software, you can redistribute it and/or modify it under the terms and conditions of
* CANN Open Software License Agreement Version 2.0 (the "License").
* Please refer to the License for details. You may not use this file except in compliance with the License.
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
* See LICENSE in the root of the software repository for the full text of the License.
* ----------------------------------------------------------------------------------------------------------
*/
#include <stdint.h>
#include <cstdio>
#include "kernel_operator.h"
#include "simt_api/asc_simt.h"
#include "simt_api/common_functions.h"
#include "spmm.h"
#if defined(__NPU_ARCH__) && (__NPU_ARCH__ != 3510)
#error "SPMM SIMT: this TU is only for dav-3510 / Ascend 950PR (__NPU_ARCH__==3510)."
#endif
using namespace AscendC;
namespace {
constexpr uint32_t kMaxSimtThreadsPerBlock = 512u;
constexpr int32_t kChunk = 8;
template<typename T, typename U>
struct SpmmIsSame { static constexpr bool value = false; };
template<typename T>
struct SpmmIsSame<T, T> { static constexpr bool value = true; };
__aicore__ inline SpmmTilingData LoadSpmmTilingData(GM_ADDR tilingGM)
{
__gm__ SpmmTilingData *gmTiling = reinterpret_cast<__gm__ SpmmTilingData *>(tilingGM);
SpmmTilingData td;
td.m = gmTiling->m;
td.n = gmTiling->n;
td.ldb = gmTiling->ldb;
td.ldc = gmTiling->ldc;
td.reorder_offset = gmTiling->reorder_offset;
td.bin_edge_offset = gmTiling->bin_edge_offset;
td.high_precision = gmTiling->high_precision;
td.opB = gmTiling->opB;
td.order_pair = gmTiling->order_pair;
td.alpha_host = gmTiling->alpha_host;
td.beta_host = gmTiling->beta_host;
return td;
}
}
template<typename ValT, typename CT, typename ScalarT, bool UseKahan>
__simt_vf__ __aicore__ __launch_bounds__(kMaxSimtThreadsPerBlock) inline void SpmmCsrSimtCompute(
__gm__ int32_t *rowOff,
__gm__ int32_t *colInd,
__gm__ ValT *values,
__gm__ ValT *matB,
__gm__ CT *matC,
__gm__ int32_t *reorder,
int32_t n,
int32_t ldb,
int32_t ldc,
int32_t colTiles,
int32_t logicalRowStart,
int32_t logicalRowEnd,
ScalarT alpha,
ScalarT beta,
int32_t opB,
int32_t orderPair)
{
const int32_t numRows = logicalRowEnd - logicalRowStart;
if (numRows <= 0 || colTiles <= 0) {
return;
}
const uint64_t totalWork =
static_cast<uint64_t>(numRows) * static_cast<uint64_t>(colTiles);
const bool betaZero = (beta == static_cast<ScalarT>(0));
const uint32_t threadNum = blockDim.x;
const uint64_t threadIdxX = static_cast<uint64_t>(threadIdx.x);
const bool bRowMajor = (orderPair == SPMM_ORDER_RR || orderPair == SPMM_ORDER_RC);
const bool cRowMajor = (orderPair == SPMM_ORDER_RR || orderPair == SPMM_ORDER_CR);
for (uint64_t work = threadIdxX; work < totalWork; work += static_cast<uint64_t>(threadNum)) {
const int32_t logicalRow =
logicalRowStart + static_cast<int32_t>(work / static_cast<uint64_t>(colTiles));
const int32_t colTile = static_cast<int32_t>(work % static_cast<uint64_t>(colTiles));
const int32_t colFirst = colTile * kChunk;
if (colFirst >= n) {
continue;
}
const int32_t row = reorder[logicalRow];
const int32_t s = rowOff[row];
const int32_t e = rowOff[row + 1];
ScalarT acc[kChunk];
ScalarT comp[kChunk];
#pragma unroll
for (int j = 0; j < kChunk; ++j) {
if constexpr (SpmmIsSame<ScalarT, float>::value) {
acc[j] = 0.0f;
if constexpr (UseKahan) {
comp[j] = 0.0f;
}
} else {
acc[j] = 0;
}
}
for (int32_t p = s; p < e; ++p) {
const int32_t c = colInd[p];
if constexpr (SpmmIsSame<ScalarT, float>::value) {
const float v = static_cast<float>(values[p]);
#pragma unroll
for (int j = 0; j < kChunk; ++j) {
if (colFirst + j < n) {
int32_t colIdx = colFirst + j;
uint64_t bIdx;
if (opB == 0) {
bIdx = bRowMajor
? static_cast<uint64_t>(c) * static_cast<uint64_t>(ldb) +
static_cast<uint64_t>(colIdx)
: static_cast<uint64_t>(colIdx) * static_cast<uint64_t>(ldb) +
static_cast<uint64_t>(c);
} else {
bIdx = bRowMajor
? static_cast<uint64_t>(colIdx) * static_cast<uint64_t>(ldb) +
static_cast<uint64_t>(c)
: static_cast<uint64_t>(c) * static_cast<uint64_t>(ldb) +
static_cast<uint64_t>(colIdx);
}
const float prod = v * static_cast<float>(matB[bIdx]);
if constexpr (UseKahan) {
const float y = prod - comp[j];
const float t = acc[j] + y;
comp[j] = (t - acc[j]) - y;
acc[j] = t;
} else {
acc[j] += prod;
}
}
}
} else {
const int32_t v = static_cast<int32_t>(values[p]);
#pragma unroll
for (int j = 0; j < kChunk; ++j) {
if (colFirst + j < n) {
int32_t colIdx = colFirst + j;
uint64_t bIdx;
if (opB == 0) {
bIdx = bRowMajor
? static_cast<uint64_t>(c) * static_cast<uint64_t>(ldb) +
static_cast<uint64_t>(colIdx)
: static_cast<uint64_t>(colIdx) * static_cast<uint64_t>(ldb) +
static_cast<uint64_t>(c);
} else {
bIdx = bRowMajor
? static_cast<uint64_t>(colIdx) * static_cast<uint64_t>(ldb) +
static_cast<uint64_t>(c)
: static_cast<uint64_t>(c) * static_cast<uint64_t>(ldb) +
static_cast<uint64_t>(colIdx);
}
acc[j] += v * static_cast<int32_t>(matB[bIdx]);
}
}
}
}
if constexpr (SpmmIsSame<ScalarT, float>::value) {
#pragma unroll
for (int j = 0; j < kChunk; ++j) {
if (colFirst + j >= n) {
break;
}
int32_t colIdx = colFirst + j;
uint64_t cIdx = cRowMajor
? static_cast<uint64_t>(row) * static_cast<uint64_t>(ldc) +
static_cast<uint64_t>(colIdx)
: static_cast<uint64_t>(colIdx) * static_cast<uint64_t>(ldc) +
static_cast<uint64_t>(row);
float cNew;
if constexpr (UseKahan) {
cNew = alpha * (acc[j] + comp[j]);
} else {
cNew = alpha * acc[j];
}
if (!betaZero) {
cNew += beta * static_cast<float>(matC[cIdx]);
}
if constexpr (SpmmIsSame<CT, float>::value) {
matC[cIdx] = cNew;
} else {
constexpr float kFp16Max = 65504.0f;
float clamped = cNew;
if (clamped > kFp16Max) clamped = kFp16Max;
if (clamped < -kFp16Max) clamped = -kFp16Max;
matC[cIdx] = static_cast<ValT>(clamped);
}
}
} else {
#pragma unroll
for (int j = 0; j < kChunk; ++j) {
if (colFirst + j >= n) {
break;
}
int32_t colIdx = colFirst + j;
uint64_t cIdx = cRowMajor
? static_cast<uint64_t>(row) * static_cast<uint64_t>(ldc) +
static_cast<uint64_t>(colIdx)
: static_cast<uint64_t>(colIdx) * static_cast<uint64_t>(ldc) +
static_cast<uint64_t>(row);
int64_t cWide;
if (alpha == 1) {
cWide = static_cast<int64_t>(acc[j]);
} else {
cWide = static_cast<int64_t>(alpha) * static_cast<int64_t>(acc[j]);
}
if (!betaZero) {
cWide += static_cast<int64_t>(beta) * static_cast<int64_t>(matC[cIdx]);
}
constexpr int64_t kInt32Max = 2147483647LL;
constexpr int64_t kInt32Min = -2147483648LL;
if (cWide > kInt32Max) cWide = kInt32Max;
if (cWide < kInt32Min) cWide = kInt32Min;
matC[cIdx] = static_cast<int32_t>(cWide);
}
}
}
}
template<typename ValT, typename CT, typename ScalarT, bool UseKahan>
class KernelSpmmSimt {
public:
__aicore__ inline KernelSpmmSimt() {}
__aicore__ inline void Init(GM_ADDR csrRowOffsets,
GM_ADDR csrColInd,
GM_ADDR csrValues,
GM_ADDR matB,
GM_ADDR matC,
GM_ADDR workspaceGM,
GM_ADDR tilingGM)
{
tilingData_ = LoadSpmmTilingData(tilingGM);
wsBase_ = reinterpret_cast<__gm__ uint8_t *>(workspaceGM);
rowOff_ = reinterpret_cast<__gm__ int32_t *>(csrRowOffsets);
colInd_ = reinterpret_cast<__gm__ int32_t *>(csrColInd);
values_ = reinterpret_cast<__gm__ ValT *>(csrValues);
matB_ = reinterpret_cast<__gm__ ValT *>(matB);
matC_ = reinterpret_cast<__gm__ CT *>(matC);
reorder_ = reinterpret_cast<__gm__ int32_t *>(
wsBase_ + static_cast<uint64_t>(tilingData_.reorder_offset));
binEdge_ = reinterpret_cast<__gm__ int32_t *>(
wsBase_ + static_cast<uint64_t>(tilingData_.bin_edge_offset));
n_ = tilingData_.n;
ldb_ = tilingData_.ldb;
ldc_ = tilingData_.ldc;
opB_ = tilingData_.opB;
orderPair_ = tilingData_.order_pair;
colTiles_ = static_cast<int32_t>(
(static_cast<uint64_t>(tilingData_.n) + static_cast<uint64_t>(kChunk) - 1u) /
static_cast<uint64_t>(kChunk));
}
__aicore__ inline void Process()
{
const int32_t m = tilingData_.m;
ScalarT alpha;
ScalarT beta;
if constexpr (SpmmIsSame<ScalarT, int32_t>::value) {
alpha = static_cast<ScalarT>(static_cast<int32_t>(tilingData_.alpha_host));
beta = static_cast<ScalarT>(static_cast<int32_t>(tilingData_.beta_host));
} else {
alpha = static_cast<ScalarT>(tilingData_.alpha_host);
beta = static_cast<ScalarT>(tilingData_.beta_host);
}
const int32_t rowBinNum = static_cast<int32_t>(GetBlockNum());
const int32_t outerId = static_cast<int32_t>(GetBlockIdx());
int32_t rowStart = 0;
int32_t rowEnd = 0;
if (outerId >= 0 && outerId < rowBinNum) {
rowStart = binEdge_[outerId];
rowEnd = binEdge_[outerId + 1];
}
const uint64_t totalWork =
static_cast<uint64_t>(m) * static_cast<uint64_t>(colTiles_);
uint32_t simtThreadNum = 1u;
if (totalWork > 0u && rowBinNum > 0) {
simtThreadNum = static_cast<uint32_t>((totalWork + static_cast<uint64_t>(rowBinNum) - 1u) /
static_cast<uint64_t>(rowBinNum));
if (simtThreadNum > kMaxSimtThreadsPerBlock) {
simtThreadNum = kMaxSimtThreadsPerBlock;
}
}
asc_vf_call<SpmmCsrSimtCompute<ValT, CT, ScalarT, UseKahan>>(dim3{simtThreadNum},
rowOff_, colInd_, values_, matB_, matC_,
reorder_, n_, ldb_, ldc_, colTiles_, rowStart, rowEnd, alpha, beta,
opB_, orderPair_);
}
private:
__gm__ uint8_t *wsBase_{nullptr};
__gm__ int32_t *rowOff_{nullptr};
__gm__ int32_t *colInd_{nullptr};
__gm__ ValT *values_{nullptr};
__gm__ ValT *matB_{nullptr};
__gm__ CT *matC_{nullptr};
__gm__ int32_t *reorder_{nullptr};
__gm__ int32_t *binEdge_{nullptr};
int32_t n_{0};
int32_t ldb_{0};
int32_t ldc_{0};
int32_t opB_{0};
int32_t orderPair_{0};
int32_t colTiles_{0};
SpmmTilingData tilingData_{};
};
extern "C" __global__ __aicore__ void spmm_custom_fp32(
GM_ADDR csrRowOffsets,
GM_ADDR csrColInd,
GM_ADDR csrValues,
GM_ADDR matB,
GM_ADDR matC,
GM_ADDR workspaceGM,
GM_ADDR tilingGM)
{
KERNEL_TASK_TYPE_DEFAULT(KERNEL_TYPE_AIV_ONLY);
const SpmmTilingData tiling = LoadSpmmTilingData(tilingGM);
if (tiling.high_precision != 0) {
KernelSpmmSimt<float, float, float, true> op;
op.Init(csrRowOffsets, csrColInd, csrValues, matB, matC, workspaceGM, tilingGM);
op.Process();
} else {
KernelSpmmSimt<float, float, float, false> op;
op.Init(csrRowOffsets, csrColInd, csrValues, matB, matC, workspaceGM, tilingGM);
op.Process();
}
}
extern "C" __global__ __aicore__ void spmm_custom_fp16(
GM_ADDR csrRowOffsets,
GM_ADDR csrColInd,
GM_ADDR csrValues,
GM_ADDR matB,
GM_ADDR matC,
GM_ADDR workspaceGM,
GM_ADDR tilingGM)
{
KERNEL_TASK_TYPE_DEFAULT(KERNEL_TYPE_AIV_ONLY);
KernelSpmmSimt<__fp16, __fp16, float, false> op;
op.Init(csrRowOffsets, csrColInd, csrValues, matB, matC, workspaceGM, tilingGM);
op.Process();
}
extern "C" __global__ __aicore__ void spmm_custom_int8(
GM_ADDR csrRowOffsets,
GM_ADDR csrColInd,
GM_ADDR csrValues,
GM_ADDR matB,
GM_ADDR matC,
GM_ADDR workspaceGM,
GM_ADDR tilingGM)
{
KERNEL_TASK_TYPE_DEFAULT(KERNEL_TYPE_AIV_ONLY);
KernelSpmmSimt<int8_t, int32_t, int32_t, false> op;
op.Init(csrRowOffsets, csrColInd, csrValues, matB, matC, workspaceGM, tilingGM);
op.Process();
}
extern "C" void spmm_kernel_launch(
const void *csrRowOffsets,
const void *csrColInd,
const void *csrValues,
const void *matB,
void *matC,
void *workspaceGM,
void *tilingGM,
int32_t dataType,
uint32_t blockDim,
void *stream)
{
if (dataType == SPMM_DTYPE_FP32) {
spmm_custom_fp32<<<blockDim, nullptr, stream>>>(
(GM_ADDR)csrRowOffsets, (GM_ADDR)csrColInd, (GM_ADDR)csrValues,
(GM_ADDR)matB, (GM_ADDR)matC, (GM_ADDR)workspaceGM, (GM_ADDR)tilingGM);
} else if (dataType == SPMM_DTYPE_FP16) {
spmm_custom_fp16<<<blockDim, nullptr, stream>>>(
(GM_ADDR)csrRowOffsets, (GM_ADDR)csrColInd, (GM_ADDR)csrValues,
(GM_ADDR)matB, (GM_ADDR)matC, (GM_ADDR)workspaceGM, (GM_ADDR)tilingGM);
} else if (dataType == SPMM_DTYPE_INT8) {
spmm_custom_int8<<<blockDim, nullptr, stream>>>(
(GM_ADDR)csrRowOffsets, (GM_ADDR)csrColInd, (GM_ADDR)csrValues,
(GM_ADDR)matB, (GM_ADDR)matC, (GM_ADDR)workspaceGM, (GM_ADDR)tilingGM);
} else {
fprintf(stderr, "[ERROR] spmm_kernel_launch: unsupported dataType %d "
"(expected FP32=%d/FP16=%d/INT8=%d), kernel not launched\n",
dataType, SPMM_DTYPE_FP32, SPMM_DTYPE_FP16, SPMM_DTYPE_INT8);
}
}