* Copyright (c) 2025 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.
*/
* \file sparse4to2quant_matmul.h
* \brief
*/
#ifndef SPARSE4TO2_QUANT_MATMUL_H
#define SPARSE4TO2_QUANT_MATMUL_H
#include "sparse4to2quant_matmul_block.h"
#include "sparse4to2quant_matmul_update.h"
namespace AscendC {
template <typename x1Type, typename x2Type, typename yType, CubeFormat x1Format, CubeFormat x2Format,
bool aTrans = false, bool bTrans = true, class UPDATE_TYPE = Sparse4to2QuantMatmulUpdate>
class Sparse4to2QuantMatmul {
public:
__aicore__ inline Sparse4to2QuantMatmul() {}
__aicore__ inline void Init(GM_ADDR x, GM_ADDR sparseWeight, GM_ADDR index, GM_ADDR bias, GM_ADDR xScale,
GM_ADDR sparseWeightScale, GM_ADDR y, GM_ADDR workSpace,
const SparseQmm::Sparse4to2QuantMatmulTilingData* __restrict tilingData, TPipe* tPipe)
{
blockIdx_ = GetBlockIdx();
if ASCEND_IS_AIV {
blockIdx_ /= GetTaskRation();
}
subBlockIdx_ = GetSubBlockIdx();
usedCoreNum_ = tilingData->matmulTiling.usedCoreNum;
if (blockIdx_ >= usedCoreNum_) {
return;
}
pipe_ = tPipe;
mm_.Init(&(tilingData->matmulTiling), pipe_);
InitTilingData(tilingData);
InitGlobalBuffers(x, sparseWeight, index, bias, xScale, sparseWeightScale, y, workSpace);
if ASCEND_IS_AIV {
InitLocalBuffers();
}
offsetWorkspaceC_ = BUFFER_NUM * blockIdx_ * baseM_ * baseN_;
block_.Init(tilingData);
update_.template Init<x1Format, x2Format, aTrans, bTrans>(&tilingData->matmulTiling, block_.params_);
loop_ = 0;
}
__aicore__ inline void Process()
{
if (blockIdx_ >= usedCoreNum_) {
return;
}
bool reverse = true;
bool pongSwitch = false;
offsetWorkspaceC_ = BUFFER_NUM * blockIdx_ * baseM_ * baseN_;
loop_ = 0;
uint64_t pingOffsetC = offsetWorkspaceC_;
block_.InitFirstTileBlockIndex();
OneTileCompute(0, 0, pingOffsetC, pongSwitch);
for (uint64_t mTileIndex = 0; mTileIndex < block_.params_.mTileCntL2; mTileIndex++) {
reverse = !reverse;
for (uint64_t nTileIndexTemp = 0; nTileIndexTemp < block_.params_.nTileCntL2; nTileIndexTemp++) {
uint64_t nTileIndex = reverse ? (block_.params_.nTileCntL2 - nTileIndexTemp - 1) : nTileIndexTemp;
if (mTileIndex > 0 || nTileIndex > 0) {
block_.UpdateBlockCnt(mTileIndex, nTileIndex);
block_.InitBlockIndex();
OneTileCompute(mTileIndex, nTileIndex, pingOffsetC, pongSwitch);
}
}
}
End();
}
private:
__aicore__ inline void InitTilingData(const SparseQmm::Sparse4to2QuantMatmulTilingData* tilingData)
{
isPerTensor_ = tilingData->params.weightScaleDim == 1;
m_ = tilingData->matmulTiling.M;
n_ = tilingData->matmulTiling.N;
ka_ = tilingData->matmulTiling.Ka;
baseM_ = tilingData->matmulTiling.baseM;
baseN_ = tilingData->matmulTiling.baseN;
hasBias_ = tilingData->matmulTiling.isBias;
biasDtypeSize_ = sizeof(half);
ubCalcM_ = tilingData->params.ubCalcM;
ubCalcN_ = tilingData->params.ubCalcN;
ubTmpBuffer_ = tilingData->params.needUbBuffer;
}
__aicore__ inline void InitGlobalBuffers(GM_ADDR x, GM_ADDR sparseWeight, GM_ADDR index, GM_ADDR bias,
GM_ADDR xScale, GM_ADDR sparseWeightScale, GM_ADDR y, GM_ADDR workSpace)
{
if (isPerTensor_) {
scaleScalar_ = *((__gm__ float*)sparseWeightScale);
}
xGm_.SetGlobalBuffer((__gm__ x1Type*)x);
weightGm_.SetGlobalBuffer((__gm__ x2Type*)sparseWeight);
indexGm_.SetGlobalBuffer((__gm__ uint8_t*)index);
if (m_ <= baseM_) {
weightGm_.SetL2CacheHint(CacheMode::CACHE_MODE_DISABLE);
}
if (hasBias_ != 0U) {
biasGmBf16_.SetGlobalBuffer((__gm__ bfloat16_t*)bias);
}
yGm_.SetGlobalBuffer((__gm__ yType*)y);
scaleGm_.SetGlobalBuffer((__gm__ float*)sparseWeightScale);
xScaleGm_.SetGlobalBuffer((__gm__ float*)xScale);
mmOutGm_.SetGlobalBuffer((__gm__ int32_t*)workSpace, BUFFER_NUM * usedCoreNum_ * baseM_ * baseN_);
}
__aicore__ inline void InitLocalBuffers()
{
pipe_->InitBuffer(vecQueSrc_, BUFFER_NUM, ubCalcM_ * ubCalcN_ * sizeof(int32_t));
pipe_->InitBuffer(vecQueTmp_, ubTmpBuffer_);
pipe_->InitBuffer(vecQueOut_, BUFFER_NUM, ubCalcM_ * ubCalcN_ * sizeof(yType));
if (hasBias_ != 0U) {
pipe_->InitBuffer(biasFp32Tmp_, ubCalcN_ * sizeof(float));
pipe_->InitBuffer(vecQueBias_, BUFFER_NUM, ubCalcN_ * biasDtypeSize_);
}
if (!isPerTensor_) {
pipe_->InitBuffer(vecQueWightScale_, BUFFER_NUM, ubCalcN_ * sizeof(float));
}
pipe_->InitBuffer(outFp32Tmp_, ubCalcM_ * ubCalcN_ * sizeof(float));
pipe_->InitBuffer(vecQueXScale_, BUFFER_NUM, SparseQmm::Align(ubCalcM_, 8U) * sizeof(float));
pipe_->InitBuffer(broadcastFp32Tmp_, ubCalcM_ * ubCalcN_ * sizeof(float));
}
__aicore__ inline void OneTileCompute(uint64_t mTileIndex, uint64_t nTileIndex, uint64_t pingOffsetC,
bool& pongSwitch)
{
for (uint64_t j = 0; j < block_.realRound_; j++) {
update_.template UpdateBlockParamsAndCalcGmOffset<x1Format, x2Format, aTrans, bTrans>(
block_.params_, offset_, mTileIndex, nTileIndex);
offsetWorkspaceC_ = pingOffsetC + pongSwitch * baseM_ * baseN_;
BasicMMDequantCompute(block_.params_.singleCoreM, block_.params_.singleCoreN, C2V_PING_FLAG | pongSwitch,
V2C_PING_FLAG | pongSwitch);
pongSwitch = !pongSwitch;
block_.UpdateBlockIndex();
}
}
__aicore__ inline void BasicMMDequantCompute(uint32_t CurAicM, uint32_t CurAicN, uint16_t v2cSyncFlag,
uint16_t c2vSyncFlag)
{
if ASCEND_IS_AIC {
if (++loop_ > 2) {
WaitEvent(v2cSyncFlag);
}
BasicMMCompute(CurAicM, CurAicN);
NotifyEvent<PIPE_FIX>(c2vSyncFlag);
}
if ASCEND_IS_AIV {
WaitEvent(c2vSyncFlag);
BasicDequantCompute(mmOutGm_, CurAicM, CurAicN);
NotifyEvent<PIPE_MTE2>(v2cSyncFlag);
}
}
__aicore__ inline void BasicMMCompute(uint32_t baseM, uint32_t baseN)
{
mm_.SetSingleShape(baseM, baseN, ka_);
mm_.SetTensorA(xGm_[offset_.offsetA], aTrans);
mm_.SetTensorB(weightGm_[offset_.offsetB], bTrans);
mm_.SetSparseIndex(indexGm_[offset_.offsetSparseIndex]);
mm_.Iterate();
mm_.GetTensorC(mmOutGm_[offsetWorkspaceC_], 0, true);
}
__aicore__ inline void PertokenCalculate(uint32_t basicBlockComputeInfo[], uint32_t mUbLoopIdx,
DataCopyPadParams& padParams, LocalTensor<float>& dstLocalFp32,
LocalTensor<float>& tmpdstLocal)
{
uint32_t curAivN = basicBlockComputeInfo[0];
uint32_t curAivM = basicBlockComputeInfo[1];
uint32_t ubResAlignedN = basicBlockComputeInfo[2];
uint32_t subBlockoffset = basicBlockComputeInfo[3];
DataCopyParams scale2UbParams{1, 0, 0, 0};
scale2UbParams.blockLen = curAivM * sizeof(float);
uint64_t offsetPertoken = offset_.offsetPertoken + mUbLoopIdx * ubCalcM_ + subBlockoffset;
uint32_t computedAivN = SparseQmm::Align(curAivN, 8U);
const uint32_t broadCastDst[M_N_TWO_DIMS] = {curAivM, computedAivN};
const uint32_t broadCastSrc[M_N_TWO_DIMS] = {curAivM, 1};
LocalTensor<float> broadcastFp32 = broadcastFp32Tmp_.Get<float>();
LocalTensor<float> xScaleLocal = vecQueXScale_.AllocTensor<float>();
DataCopyPad(xScaleLocal, xScaleGm_[offsetPertoken], scale2UbParams, padParams);
vecQueXScale_.EnQue<float>(xScaleLocal);
xScaleLocal = vecQueXScale_.DeQue<float>();
BroadCast<float, M_N_TWO_DIMS, 1>(broadcastFp32, xScaleLocal, broadCastDst, broadCastSrc);
AscendC::PipeBarrier<PIPE_V>();
if (computedAivN == ubResAlignedN) {
Mul(tmpdstLocal, broadcastFp32, dstLocalFp32, computedAivN * curAivM);
} else {
for (auto i = 0; i < curAivM; i++) {
Mul(tmpdstLocal[ubResAlignedN * i], broadcastFp32[computedAivN * i], dstLocalFp32[computedAivN * i],
computedAivN);
}
}
vecQueXScale_.FreeTensor(xScaleLocal);
}
__aicore__ inline void BasicDequantCompute(GlobalTensor<int32_t>& curMmOutGm, uint32_t curAicM, uint32_t curAicN)
{
LocalTensor<float> dstLocalFp32 = outFp32Tmp_.Get<float>();
LocalTensor<float> biasFp32;
LocalTensor<bfloat16_t> oriBiasBf16;
LocalTensor<half> oriBiasFp16;
LocalTensor<float> oriBiasFp32;
uint32_t subBlockoffset = 0;
int64_t vecNum = GetTaskRation();
vecNum = (vecNum == 0) ? 1 : vecNum;
subBlockoffset = subBlockIdx_ * curAicM / vecNum;
curAicM = curAicM / vecNum + subBlockIdx_ * (curAicM % vecNum);
uint32_t curAivM = ubCalcM_;
uint32_t curAivN = curAicN;
uint32_t mUbLoops = SparseQmm::CeilDiv(curAicM, ubCalcM_);
DataCopyParams gm2UbParams{1, 0, 0, 0};
DataCopyExtParams ub2GmParams{1, 0, 0, 0, 0};
DataCopyPadParams padParams;
DequantParams dequantParams;
SparseQmm::CalcDequantParams(mUbLoops == 1 ? curAicM : ubCalcM_, curAicN, dequantParams);
for (uint32_t mUbLoopIdx = 0; mUbLoopIdx < mUbLoops; ++mUbLoopIdx) {
if (mUbLoopIdx == mUbLoops - 1) {
curAivM = curAicM - ubCalcM_ * (mUbLoops - 1);
SparseQmm::CalcDequantParams(curAivM, curAicN, dequantParams, mUbLoops != 1 && curAivM != ubCalcM_);
}
LocalTensor<int32_t> srcLocal = vecQueSrc_.AllocTensor<int32_t>();
LocalTensor<yType> dstLocal = vecQueOut_.AllocTensor<yType>();
LocalTensor<uint8_t> tmpLocal = vecQueTmp_.Get<uint8_t>();
SparseQmm::SetGm2UbParams(gm2UbParams, curAivM, curAivN);
SparseQmm::CopyMmOutToLocal(srcLocal, curMmOutGm, gm2UbParams, padParams,
offsetWorkspaceC_ + mUbLoopIdx * ubCalcM_ * curAicN + subBlockoffset * curAicN);
if (hasBias_ != 0) {
BiasTensorInit(dstLocalFp32, biasFp32, oriBiasBf16, oriBiasFp16, oriBiasFp32);
BiasGm2Ub(oriBiasBf16, oriBiasFp16, oriBiasFp32, padParams, curAicN);
}
if (isPerTensor_) {
AscendDequant(dstLocalFp32, srcLocal, scaleScalar_, tmpLocal, dequantParams);
} else {
LocalTensor<float> scaleLocal = vecQueWightScale_.AllocTensor<float>();
SparseQmm::Bf16ScaleGm2Ub<float>(scaleLocal, scaleGm_, padParams, curAicN, offset_.offsetScale);
AscendDequant(dstLocalFp32, srcLocal, scaleLocal, tmpLocal, dequantParams);
vecQueWightScale_.FreeTensor(scaleLocal);
}
uint32_t ubResAlignedN = SparseQmm::Align(curAivN);
LocalTensor<float> tmpdstLocal = vecQueTmp_.Get<float>();
uint32_t basicBlockComputeInfo[4] = {curAivN, curAivM, ubResAlignedN, subBlockoffset};
PertokenCalculate(basicBlockComputeInfo, mUbLoopIdx, padParams, dstLocalFp32, tmpdstLocal);
if (hasBias_ != 0) {
CalBiasAdd(tmpdstLocal, biasFp32, oriBiasBf16, oriBiasFp16, oriBiasFp32, curAivN, curAivM);
}
AscendC::PipeBarrier<PIPE_V>();
Cast(dstLocal, tmpdstLocal, RoundMode::CAST_RINT, curAivM * ubResAlignedN);
SetFlag<HardEvent::V_MTE3>(EVENT_ID2);
vecQueSrc_.FreeTensor(srcLocal);
SparseQmm::SetUb2GmParams<yType>(ub2GmParams, curAivM, curAivN, n_);
WaitFlag<HardEvent::V_MTE3>(EVENT_ID2);
SparseQmm::CopyUbToGm<yType>(offset_.offsetC + mUbLoopIdx * ubCalcM_ * n_ + subBlockoffset * n_,
ub2GmParams, dstLocal, yGm_, vecQueOut_);
}
}
__aicore__ inline void BiasTensorInit(LocalTensor<float>& , LocalTensor<float>& biasFp32,
LocalTensor<bfloat16_t>& oriBiasBf16, LocalTensor<half>& oriBiasFp16,
LocalTensor<float>& oriBiasFp32)
{
biasFp32 = biasFp32Tmp_.Get<float>();
oriBiasBf16 = vecQueBias_.AllocTensor<bfloat16_t>();
}
__aicore__ inline void BiasGm2Ub(LocalTensor<bfloat16_t>& oriBiasBf16, LocalTensor<half>& oriBiasFp16,
LocalTensor<float>& oriBiasFp32, DataCopyPadParams padParams, uint32_t curAivN)
{
DataCopyParams bias2UbParams{1, 0, 0, 0};
bias2UbParams.blockLen = curAivN * biasDtypeSize_;
DataCopyPad(oriBiasBf16, biasGmBf16_[offset_.offsetBias], bias2UbParams, padParams);
}
__aicore__ inline void CalBiasAdd(LocalTensor<float>& dstLocalFp32, LocalTensor<float>& biasFp32,
LocalTensor<bfloat16_t>& oriBiasBf16, LocalTensor<half>& oriBiasFp16,
LocalTensor<float>& oriBiasFp32, uint32_t curAivN, uint32_t curAivM)
{
uint32_t computedAivN = SparseQmm::Align(curAivN, 8U);
uint32_t ubResAlignedN = SparseQmm::Align(curAivN);
AscendC::PipeBarrier<PIPE_V>();
Cast(biasFp32, oriBiasBf16, RoundMode::CAST_NONE, ubResAlignedN);
AscendC::PipeBarrier<PIPE_V>();
vecQueBias_.FreeTensor(oriBiasBf16);
for (int32_t mIdx = 0; mIdx < curAivM; ++mIdx) {
Add(dstLocalFp32[mIdx * ubResAlignedN], dstLocalFp32[mIdx * ubResAlignedN], biasFp32, ubResAlignedN);
}
AscendC::PipeBarrier<PIPE_V>();
}
__aicore__ inline void End()
{
if ASCEND_IS_AIC {
if (loop_ > 0) {
WaitEvent(C2V_PING_FLAG);
}
if (loop_ > 1) {
WaitEvent(C2V_PONG_FLAG);
}
mm_.End();
}
}
private:
GlobalTensor<x1Type> xGm_;
GlobalTensor<x2Type> weightGm_;
GlobalTensor<uint8_t> indexGm_;
GlobalTensor<bfloat16_t> biasGmBf16_;
GlobalTensor<yType> yGm_;
GlobalTensor<float> scaleGm_;
GlobalTensor<float> xScaleGm_;
GlobalTensor<int32_t> mmOutGm_;
TPipe* pipe_;
TQue<QuePosition::VECIN, 1> vecQueSrc_;
TQue<QuePosition::VECIN, 1> vecQueWightScale_;
TQue<QuePosition::VECIN, 1> vecQueBias_;
TBuf<TPosition::VECCALC> vecQueTmp_;
TQue<QuePosition::VECOUT, 1> vecQueOut_;
TBuf<TPosition::VECCALC> biasFp32Tmp_;
TBuf<TPosition::VECCALC> outFp32Tmp_;
TQue<QuePosition::VECIN, 1> vecQueXScale_;
TBuf<TPosition::VECCALC> broadcastFp32Tmp_;
float scaleScalar_;
bool isPerTensor_;
uint32_t usedCoreNum_;
uint32_t m_;
uint32_t n_;
uint32_t ka_;
uint32_t baseM_;
uint32_t baseN_;
uint32_t hasBias_;
uint32_t biasDtypeSize_ = 0;
uint32_t ubCalcM_;
uint32_t ubCalcN_;
uint32_t ubTmpBuffer_;
uint32_t blockIdx_;
uint32_t subBlockIdx_ = 0;
uint64_t offsetWorkspaceC_ = 0;
uint64_t loop_ = 0;
Sparse4to2QuantMatmulBlock block_;
UPDATE_TYPE update_;
SparseBlockOffset offset_;
using AMatmulType = matmul::MatmulType<TPosition::GM, CubeFormat::ND, x1Type, aTrans>;
using BMatmulType = matmul::SparseMatmulType<TPosition::GM, TPosition::GM, x2Format, x2Type, bTrans>;
using BiasMatmulType = matmul::MatmulType<TPosition::GM, CubeFormat::ND, int32_t>;
using CMatmulType = matmul::MatmulType<TPosition::VECIN, CubeFormat::ND, int32_t>;
matmul::MatmulImpl<AMatmulType, BMatmulType, CMatmulType, BiasMatmulType, MM_DEFAULT_MDL_CFG> mm_;
};
}
#endif