* 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 flat_quant_tiling.cpp
* \brief
*/
#include "register/tilingdata_base.h"
#include "register/op_impl_registry.h"
#include "log/log.h"
#include "error_util.h"
#include "op_host/tiling_base.h"
#include "op_host/tiling_util.h"
#include "op_host/tiling_templates_registry.h"
#include "flat_quant_tiling.h"
#include "platform/soc_spec.h"
namespace optiling {
using namespace Ops::NN::OpTiling;
constexpr uint8_t INPUT_TENSOR_NUM = 3;
constexpr uint8_t INDEX_TWO = 2;
constexpr uint8_t INDEX_ONE = 1;
constexpr uint8_t INDEX_ZERO = 0;
constexpr uint8_t INDEX_THREE = 3;
constexpr uint8_t DIM_THREE = 3;
constexpr uint8_t DIM_TWO = 2;
constexpr uint8_t WORKSPACE_NUM = 1;
constexpr uint8_t BYTE_LEN_2 = 2;
constexpr uint8_t BYTE_LEN_4 = 4;
constexpr uint8_t CEIL_SIZE = 16;
constexpr int32_t MAX_K_SIZE = 262144;
constexpr int32_t NUM_EIGHT = 8;
constexpr int32_t MAX_MN_SIZE = 256;
constexpr int32_t FACTOR_TWO = 2;
constexpr int32_t DATA_TYPE_SIZE = 2;
constexpr int32_t K_PER_VEC = 4;
constexpr int32_t BASE_K = 64;
constexpr int32_t BASE_SIZE = 128;
constexpr int32_t L1_SIZE = 512 * 1024;
constexpr int32_t L0_SIZE = 64 * 1024;
constexpr int32_t L0C_SIZE = 256 * 1024;
constexpr float ZERO_FLOAT = 0.0f;
constexpr float ONE_FLOAT = 1.0f;
constexpr float SIX_FLOAT = 6.0f;
constexpr float TWELVE_FLOAT = 12.0f;
constexpr uint8_t MM_BASE_MODE = 1;
constexpr uint8_t MM_DOUBLE_MODE = 2;
constexpr uint8_t MM_SPLIT_MODE = 3;
constexpr uint8_t MM_HIGH_MODE = 4;
constexpr uint8_t MM_ONE_MODE = 5;
constexpr uint8_t MM_HIGH_MODE_ALIGN = 6;
constexpr uint64_t WORK_SPACE_SIZE = 16 * 1024 * 1024;
constexpr uint64_t WORK_SPACE_SIZE_APT = 48 * 1024 * 1024;
class FlatQuantTiling {
public:
explicit FlatQuantTiling(gert::TilingContext* context) : tilingContext_(context){};
ge::graphStatus RunBigKernelTiling();
private:
bool ValidateAll() const;
bool CheckShapes() const;
bool CheckClipRatio() const;
bool CheckDstDtype(ge::DataType outDtype) const;
bool CheckDstTypeMax(ge::DataType outDtype) const;
void GetKernelMode(int64_t aivNum);
uint8_t DetermineMmMode(ge::DataType outDtype) const;
void CalculateWorkspace(int64_t aivNum, ge::DataType outDtype);
ge::graphStatus GetTCubeTiling();
ge::graphStatus InitializeInputsAndAttributes();
ge::graphStatus SetBasicTilingData();
ge::graphStatus CalculateIterBatch();
ge::graphStatus SetTilingContextAndSaveData();
template <typename T1, typename T2>
inline auto CeilA2B(T1 a, T2 b) const -> T1;
private:
ge::DataType dataType_ = ge::DT_UNDEFINED;
gert::TilingContext* tilingContext_ = nullptr;
gert::Shape xShape_;
gert::Shape p1Shape_;
gert::Shape p2Shape_;
uint64_t mAlign_ = 0;
uint64_t nAlign_ = 0;
uint64_t iterBatch_ = 0;
uint8_t mmMode_ = MM_BASE_MODE;
bool hasP2_ = true;
const float* clipRatio_ = nullptr;
const int64_t* dstDtype_ = nullptr;
const float* dstTypeMax_ = nullptr;
FlatQuantTilingData tilingData_;
};
ge::graphStatus FlatQuantTiling::InitializeInputsAndAttributes()
{
auto xTensor = tilingContext_->GetInputTensor(INDEX_ZERO);
auto p1Tensor = tilingContext_->GetInputTensor(INDEX_ONE);
auto p2Tensor = tilingContext_->GetInputTensor(INDEX_TWO);
const gert::RuntimeAttrs* attrs = tilingContext_->GetAttrs();
if (xTensor == nullptr || p1Tensor == nullptr || p2Tensor == nullptr || attrs == nullptr) {
return ge::GRAPH_FAILED;
}
for (uint64_t i = 0; i < INPUT_TENSOR_NUM; i++) {
auto temp = tilingContext_->GetInputDesc(i);
OP_CHECK_NULL_WITH_CONTEXT(tilingContext_, temp);
if (dataType_ == ge::DT_UNDEFINED) {
dataType_ = temp->GetDataType();
} else if (dataType_ != temp->GetDataType()) {
return ge::GRAPH_FAILED;
}
}
auto xShape = tilingContext_->GetInputShape(INDEX_ZERO);
auto p1Shape = tilingContext_->GetInputShape(INDEX_ONE);
auto p2Shape = tilingContext_->GetInputShape(INDEX_TWO);
OP_CHECK_NULL_WITH_CONTEXT(tilingContext_, xShape);
OP_CHECK_NULL_WITH_CONTEXT(tilingContext_, p1Shape);
OP_CHECK_NULL_WITH_CONTEXT(tilingContext_, p2Shape);
xShape_ = xShape->GetOriginShape();
p1Shape_ = p1Shape->GetOriginShape();
p2Shape_ = p2Shape->GetOriginShape();
hasP2_ = (p2Shape_.GetDim(INDEX_ZERO) > 0 && p2Shape_.GetDim(INDEX_ONE) > 0);
clipRatio_ = attrs->GetAttrPointer<float>(INDEX_ZERO);
dstDtype_ = attrs->GetAttrPointer<int64_t>(INDEX_ONE);
dstTypeMax_ = attrs->GetAttrPointer<float>(INDEX_TWO);
return ge::GRAPH_SUCCESS;
}
ge::graphStatus FlatQuantTiling::SetBasicTilingData()
{
int64_t M = xShape_.GetDim(INDEX_ONE);
int64_t N = xShape_.GetDim(INDEX_TWO);
nAlign_ = CeilA2B(N, CEIL_SIZE) * CEIL_SIZE;
mAlign_ = CeilA2B(M, CEIL_SIZE) * CEIL_SIZE;
if (!ValidateAll()) {
return ge::GRAPH_FAILED;
}
tilingData_.set_hasP2(hasP2_ ? 1 : 0);
tilingData_.set_K(xShape_.GetDim(INDEX_ZERO));
tilingData_.set_M(xShape_.GetDim(INDEX_ONE));
tilingData_.set_N(xShape_.GetDim(INDEX_TWO));
tilingData_.set_clipRatio(*clipRatio_);
if (dstTypeMax_ != nullptr) {
tilingData_.set_dstTypeMax(*dstTypeMax_);
tilingData_.set_invDstTypeMax(*dstTypeMax_ != ZERO_FLOAT ? ONE_FLOAT / *dstTypeMax_ : ZERO_FLOAT);
} else {
tilingData_.set_dstTypeMax(ZERO_FLOAT);
tilingData_.set_invDstTypeMax(ZERO_FLOAT);
}
return ge::GRAPH_SUCCESS;
}
ge::graphStatus FlatQuantTiling::CalculateIterBatch()
{
auto compileInfo = tilingContext_->GetCompileInfo<FlatQuantCompileInfo>();
int64_t aicNum = compileInfo->coreNum;
int64_t aivNum = compileInfo->aivNum;
GetKernelMode(aivNum);
if (mmMode_ == MM_HIGH_MODE && GetTCubeTiling() != ge::GRAPH_SUCCESS) {
return ge::GRAPH_FAILED;
}
uint64_t p1Size = nAlign_ * nAlign_ * DATA_TYPE_SIZE;
uint64_t p2Size = mAlign_ * mAlign_ * DATA_TYPE_SIZE;
uint64_t xInputSize = mAlign_ * nAlign_ * DATA_TYPE_SIZE;
uint64_t xL0CSize = mAlign_ * nAlign_ * sizeof(float);
uint64_t iterBatchL1 = (L1_SIZE - p1Size - p2Size) / xInputSize / FACTOR_TWO;
uint64_t iterBatchL0 = L0_SIZE / std::max(mAlign_, nAlign_) / BASE_K / DATA_TYPE_SIZE / FACTOR_TWO;
uint64_t iterBatchL0C = L0C_SIZE / xL0CSize / FACTOR_TWO;
uint64_t iterBatchK = CeilA2B(xShape_.GetDim(INDEX_ZERO), aicNum);
iterBatch_ = std::max(std::min({iterBatchL1, iterBatchL0, iterBatchL0C, iterBatchK}), 1UL);
tilingData_.set_iterBatch(iterBatch_);
return ge::GRAPH_SUCCESS;
}
ge::graphStatus FlatQuantTiling::SetTilingContextAndSaveData()
{
auto compileInfo = tilingContext_->GetCompileInfo<FlatQuantCompileInfo>();
tilingContext_->SetBlockDim(compileInfo->coreNum);
tilingContext_->SetTilingKey(mmMode_);
tilingData_.SaveToBuffer(tilingContext_->GetRawTilingData()->GetData(),
tilingContext_->GetRawTilingData()->GetCapacity());
tilingContext_->GetRawTilingData()->SetDataSize(tilingData_.GetDataSize());
return ge::GRAPH_SUCCESS;
}
ge::graphStatus FlatQuantTiling::RunBigKernelTiling()
{
if (InitializeInputsAndAttributes() != ge::GRAPH_SUCCESS) {
return ge::GRAPH_FAILED;
}
if (SetBasicTilingData() != ge::GRAPH_SUCCESS) {
return ge::GRAPH_FAILED;
}
if (CalculateIterBatch() != ge::GRAPH_SUCCESS) {
return ge::GRAPH_FAILED;
}
if (SetTilingContextAndSaveData() != ge::GRAPH_SUCCESS) {
return ge::GRAPH_FAILED;
}
return ge::GRAPH_SUCCESS;
}
uint8_t FlatQuantTiling::DetermineMmMode(ge::DataType outDtype) const
{
int64_t N = xShape_.GetDim(INDEX_TWO);
int64_t M = xShape_.GetDim(INDEX_ONE);
int64_t K = xShape_.GetDim(INDEX_ZERO);
if (outDtype == ge::DT_FLOAT4_E2M1) {
if ((N % CEIL_SIZE == 0) && (M % CEIL_SIZE == 0)) {
return MM_HIGH_MODE_ALIGN;
}
return MM_HIGH_MODE;
}
auto compileInfo = tilingContext_->GetCompileInfo<FlatQuantCompileInfo>();
if (compileInfo != nullptr && compileInfo->npuArch == NpuArch::DAV_3510) {
return MM_HIGH_MODE;
}
if (M == 1 && N % NUM_EIGHT == 0) {
return MM_ONE_MODE;
}
if (mAlign_ <= BASE_SIZE / FACTOR_TWO && nAlign_ <= BASE_SIZE && K > 1) {
return MM_DOUBLE_MODE;
}
if (mAlign_ * mAlign_ + nAlign_ * nAlign_ + FACTOR_TWO * FACTOR_TWO * mAlign_ * nAlign_ > L1_SIZE / BYTE_LEN_2) {
return MM_HIGH_MODE;
}
if (mAlign_ > BASE_SIZE || nAlign_ > BASE_SIZE) {
return MM_SPLIT_MODE;
}
return MM_BASE_MODE;
}
void FlatQuantTiling::CalculateWorkspace(int64_t aivNum, ge::DataType outDtype)
{
int64_t N = xShape_.GetDim(INDEX_TWO);
int64_t M = xShape_.GetDim(INDEX_ONE);
int64_t K = xShape_.GetDim(INDEX_ZERO);
size_t* workspaces = tilingContext_->GetWorkspaceSizes(WORKSPACE_NUM);
if (outDtype == ge::DT_FLOAT4_E2M1) {
if (IsRegbaseSocVersion(tilingContext_)) {
int64_t useAivNum = CeilA2B(K, K_PER_VEC) <= aivNum ? CeilA2B(K, K_PER_VEC) : aivNum;
workspaces[0] = useAivNum *
(K_PER_VEC * M * N * BYTE_LEN_2 + FACTOR_TWO * K_PER_VEC * mAlign_ * N * BYTE_LEN_4) +
WORK_SPACE_SIZE_APT;
}
return;
}
int64_t alignedK = K;
if (mmMode_ == MM_HIGH_MODE) {
int64_t useAivNum = CeilA2B(K, K_PER_VEC) <= aivNum ? CeilA2B(K, K_PER_VEC) : aivNum;
workspaces[0] = useAivNum *
(K_PER_VEC * M * N * BYTE_LEN_2 + FACTOR_TWO * K_PER_VEC * mAlign_ * N * BYTE_LEN_4) +
WORK_SPACE_SIZE;
} else if (mmMode_ == MM_DOUBLE_MODE) {
alignedK += (alignedK % FACTOR_TWO);
workspaces[0] = (alignedK * mAlign_ * N + mAlign_ * mAlign_) * BYTE_LEN_2 + WORK_SPACE_SIZE;
} else if (mmMode_ == MM_ONE_MODE) {
workspaces[0] = alignedK * mAlign_ * nAlign_ * BYTE_LEN_2 + WORK_SPACE_SIZE;
} else {
workspaces[0] = (alignedK * mAlign_ * N) * BYTE_LEN_2 + WORK_SPACE_SIZE;
}
}
void FlatQuantTiling::GetKernelMode(int64_t aivNum)
{
auto outDesc = tilingContext_->GetOutputDesc(INDEX_ZERO);
auto outDtype = outDesc->GetDataType();
mmMode_ = DetermineMmMode(outDtype);
CalculateWorkspace(aivNum, outDtype);
}
ge::graphStatus FlatQuantTiling::GetTCubeTiling()
{
int64_t N = xShape_.GetDim(INDEX_TWO);
int64_t M = xShape_.GetDim(INDEX_ONE);
int64_t K = xShape_.GetDim(INDEX_ZERO);
auto mmDataType = static_cast<matmul_tiling::DataType>(dataType_);
matmul_tiling::MatmulApiTiling mmTilingR;
mmTilingR.SetAType(matmul_tiling::TPosition::GM, matmul_tiling::CubeFormat::ND, mmDataType);
mmTilingR.SetBType(matmul_tiling::TPosition::GM, matmul_tiling::CubeFormat::ND, mmDataType);
mmTilingR.SetCType(matmul_tiling::TPosition::GM, matmul_tiling::CubeFormat::ND, mmDataType);
mmTilingR.SetOrgShape(K * M, N, N);
mmTilingR.SetShape(K_PER_VEC * M, N, N);
if (hasP2_ && mmTilingR.GetTiling(tilingData_.matmulTilingR) == -1) {
return ge::GRAPH_FAILED;
}
matmul_tiling::MatmulApiTiling mmTilingL;
mmTilingL.SetAType(matmul_tiling::TPosition::GM, matmul_tiling::CubeFormat::ND, mmDataType);
mmTilingL.SetBType(matmul_tiling::TPosition::GM, matmul_tiling::CubeFormat::ND, mmDataType);
mmTilingL.SetCType(matmul_tiling::TPosition::GM, matmul_tiling::CubeFormat::ND, matmul_tiling::DataType::DT_FLOAT);
mmTilingL.SetOrgShape(M, N, M);
mmTilingL.SetShape(M, N, M);
if (mmTilingL.GetTiling(tilingData_.matmulTilingL) == -1) {
return ge::GRAPH_FAILED;
}
return ge::GRAPH_SUCCESS;
}
bool FlatQuantTiling::CheckShapes() const
{
OP_CHECK_IF(xShape_.GetDimNum() != DIM_THREE,
OP_LOGE(tilingContext_->GetNodeName(), "x shape dims must be 3, got %zu.", xShape_.GetDimNum()),
return false);
OP_CHECK_IF(p1Shape_.GetDimNum() != DIM_TWO,
OP_LOGE(tilingContext_->GetNodeName(), "p1 shape dims must be 2, got %zu.", p1Shape_.GetDimNum()),
return false);
OP_CHECK_IF(hasP2_ && p2Shape_.GetDimNum() != DIM_TWO,
OP_LOGE(tilingContext_->GetNodeName(), "p2 shape dims must be 2, got %zu.", p2Shape_.GetDimNum()),
return false);
int64_t K = xShape_.GetDim(INDEX_ZERO);
int64_t M = xShape_.GetDim(INDEX_ONE);
int64_t N = xShape_.GetDim(INDEX_TWO);
OP_CHECK_IF(K > MAX_K_SIZE || M > MAX_MN_SIZE || N > MAX_MN_SIZE,
OP_LOGE(tilingContext_->GetNodeName(), "K[%ld]/M[%ld]/N[%ld] exceeds limit %d/%d/%d.", K, M, N,
MAX_K_SIZE, MAX_MN_SIZE, MAX_MN_SIZE),
return false);
OP_CHECK_IF(p1Shape_.GetDim(INDEX_ZERO) != M || p1Shape_.GetDim(INDEX_ONE) != M,
OP_LOGE(tilingContext_->GetNodeName(), "p1 shape must be [%ld,%ld], got [%ld,%ld].", M, M,
p1Shape_.GetDim(INDEX_ZERO), p1Shape_.GetDim(INDEX_ONE)),
return false);
bool isP2ZeroShape = (p2Shape_.GetDim(INDEX_ZERO) == 0 && p2Shape_.GetDim(INDEX_ONE) == 0);
bool isP2NormalShape = (p2Shape_.GetDim(INDEX_ZERO) == N && p2Shape_.GetDim(INDEX_ONE) == N);
OP_CHECK_IF(!isP2ZeroShape && !isP2NormalShape,
OP_LOGE(tilingContext_->GetNodeName(), "p2 shape must be [0,0] or [%ld,%ld], got [%ld,%ld].", N, N,
p2Shape_.GetDim(INDEX_ZERO), p2Shape_.GetDim(INDEX_ONE)),
return false);
return true;
}
bool FlatQuantTiling::CheckClipRatio() const
{
OP_CHECK_IF(clipRatio_ == nullptr, OP_LOGE(tilingContext_->GetNodeName(), "clip_ratio attribute is null."),
return false);
OP_CHECK_IF(*clipRatio_ <= ZERO_FLOAT || *clipRatio_ > ONE_FLOAT,
OP_LOGE(tilingContext_->GetNodeName(), "clip_ratio must be in (0, 1], got %f.", *clipRatio_),
return false);
return true;
}
bool FlatQuantTiling::CheckDstDtype(ge::DataType outDtype) const
{
if (dstDtype_ != nullptr && outDtype == ge::DT_FLOAT4_E2M1) {
OP_CHECK_IF(*dstDtype_ != ge::DT_FLOAT4_E2M1,
OP_LOGE(tilingContext_->GetNodeName(), "dst_dtype mismatch for FLOAT4 output."), return false);
}
return true;
}
bool FlatQuantTiling::CheckDstTypeMax(ge::DataType outDtype) const
{
if (dstTypeMax_ != nullptr && outDtype == ge::DT_FLOAT4_E2M1) {
float localDstTypeMax = *dstTypeMax_;
OP_CHECK_IF(
localDstTypeMax != ZERO_FLOAT && (localDstTypeMax < SIX_FLOAT || localDstTypeMax > TWELVE_FLOAT),
OP_LOGE(tilingContext_->GetNodeName(), "dst_type_max[%f] must be 0 or in range [6, 12].", localDstTypeMax),
return false);
}
return true;
}
bool FlatQuantTiling::ValidateAll() const
{
if (!CheckShapes()) {
return false;
}
if (!CheckClipRatio()) {
return false;
}
auto outDesc = tilingContext_->GetOutputDesc(INDEX_ZERO);
OP_CHECK_NULL_WITH_CONTEXT(tilingContext_, outDesc);
auto outDtype = outDesc->GetDataType();
if (!CheckDstDtype(outDtype)) {
return false;
}
if (!CheckDstTypeMax(outDtype)) {
return false;
}
return true;
}
template <typename T1, typename T2>
inline auto FlatQuantTiling::CeilA2B(T1 a, T2 b) const -> T1
{
if (b != 0) {
return (a + b - 1) / b;
} else {
return a;
}
}
static ge::graphStatus Tiling4FlatQuantTiling(gert::TilingContext* context)
{
OP_TILING_CHECK(context == nullptr, "FlatQuant context is null", return ge::GRAPH_FAILED);
FlatQuantTiling tilingObject(context);
return tilingObject.RunBigKernelTiling();
}
static ge::graphStatus TilingPrepareTiling(gert::TilingParseContext* context)
{
OP_TILING_CHECK(context == nullptr, "FlatQuant context is null", return ge::GRAPH_FAILED);
fe::PlatFormInfos* platformInfo = context->GetPlatformInfo();
OP_TILING_CHECK(platformInfo == nullptr, "FlatQuant platformInfoPtr is null", return ge::GRAPH_FAILED);
auto ascendcPlatform = platform_ascendc::PlatformAscendC(platformInfo);
auto compileInfo = context->GetCompiledInfo<FlatQuantCompileInfo>();
OP_CHECK_NULL_WITH_CONTEXT(context, compileInfo);
compileInfo->coreNum = ascendcPlatform.GetCoreNumAic();
compileInfo->aivNum = ascendcPlatform.GetCoreNumAiv();
compileInfo->socVersion = ascendcPlatform.GetSocVersion();
compileInfo->npuArch = ascendcPlatform.GetCurNpuArch();
OP_LOGI("FlatQuant", "parse compile info success soc:%d, aicNum:%ld, aivNum:%ld",
static_cast<int>(compileInfo->socVersion), compileInfo->coreNum, compileInfo->aivNum);
OP_CHECK_IF(compileInfo->coreNum <= 0,
OP_LOGE(context->GetNodeName(), "FlatQuant is not supported for aicNum <=0 "), return ge::GRAPH_FAILED);
if (compileInfo->npuArch == NpuArch::DAV_3510) {
OP_CHECK_IF(compileInfo->aivNum != (compileInfo->coreNum * 2),
OP_LOGE(context->GetNodeName(), "FlatQuantTiling is only supported for aivNum == aicNum*2 "),
return ge::GRAPH_FAILED);
}
return ge::GRAPH_SUCCESS;
}
IMPL_OP_OPTILING(FlatQuant).Tiling(Tiling4FlatQuantTiling).TilingParse<FlatQuantCompileInfo>(TilingPrepareTiling);
}