* 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 "aclnn_quant_max.h"
#include "quant_max.h"
#include "aclnn_kernels/contiguous.h"
#include "aclnn_kernels/common/op_error_check.h"
#include "opdev/op_log.h"
#include "opdev/op_dfx.h"
#include "opdev/common_types.h"
#include "opdev/data_type_utils.h"
#include "opdev/make_op_executor.h"
#include "opdev/platform.h"
#include "opdev/shape_utils.h"
#include <string>
#include <cstring>
using namespace op;
constexpr int64_t ACLNN_MIN_SHAPE_RANK = 1;
constexpr int64_t ACLNN_MAX_SHAPE_RANK = 8;
constexpr int64_t DST_TYPE_HIFLOAT8 = 34;
constexpr int64_t DST_TYPE_FLOAT8_E5M2 = 35;
constexpr int64_t DST_TYPE_FLOAT8_E4M3FN = 36;
static const std::initializer_list<op::DataType> AICORE_DTYPE_SUPPORT_LIST = {
DataType::DT_FLOAT, DataType::DT_FLOAT16, DataType::DT_BF16};
static op::DataType GetOutputDtype(int64_t dstType)
{
switch (dstType) {
case DST_TYPE_HIFLOAT8:
return DataType::DT_HIFLOAT8;
case DST_TYPE_FLOAT8_E5M2:
return DataType::DT_FLOAT8_E5M2;
case DST_TYPE_FLOAT8_E4M3FN:
return DataType::DT_FLOAT8_E4M3FN;
default:
return DataType::DT_UNDEFINED;
}
}
static bool IsShapeEqualsOne(const aclTensor* tensor)
{
auto shape = tensor->GetViewShape();
return shape.GetDimNum() == 1 && shape.GetDim(0) == 1;
}
static bool IsShapeEquals(const aclTensor* tensor1, const aclTensor* tensor2)
{
auto shape1 = tensor1->GetViewShape();
auto shape2 = tensor2->GetViewShape();
OP_LOGE(ACLNN_ERR_PARAM_INVALID, "x shape %ld, y shape %ld", shape1.GetShapeSize(), shape2.GetShapeSize());
if (shape1.GetDimNum() != shape2.GetDimNum()) {
return false;
}
for (size_t i = 0; i < shape1.GetDimNum(); i++) {
if (shape1.GetDim(i) != shape2.GetDim(i)) {
return false;
}
}
return true;
}
static aclnnStatus CheckNullParams(const aclTensor* x, const aclTensor* scale, const aclTensor* y, const aclTensor* amax)
{
OP_CHECK_NULL(x, return ACLNN_ERR_PARAM_NULLPTR);
OP_CHECK_NULL(scale, return ACLNN_ERR_PARAM_NULLPTR);
OP_CHECK_NULL(y, return ACLNN_ERR_PARAM_NULLPTR);
OP_CHECK_NULL(amax, return ACLNN_ERR_PARAM_NULLPTR);
return ACLNN_SUCCESS;
}
static aclnnStatus CheckInputParams(const aclTensor* x, const aclTensor* scale)
{
if (!CheckType(x->GetDataType(), AICORE_DTYPE_SUPPORT_LIST)) {
OP_LOGE(ACLNN_ERR_PARAM_INVALID, "x dtype must be FLOAT/FLOAT16/BFLOAT16, got %d",
static_cast<int>(x->GetDataType()));
return ACLNN_ERR_PARAM_INVALID;
}
auto xShape = x->GetViewShape();
if (xShape.GetDimNum() < ACLNN_MIN_SHAPE_RANK || xShape.GetDimNum() > ACLNN_MAX_SHAPE_RANK) {
OP_LOGE(ACLNN_ERR_PARAM_INVALID, "x dim must be in range [1, 8], got %zu", xShape.GetDimNum());
return ACLNN_ERR_PARAM_INVALID;
}
if (scale->GetDataType() != DataType::DT_FLOAT) {
OP_LOGE(ACLNN_ERR_PARAM_INVALID, "scale dtype must be FLOAT, got %d",
static_cast<int>(scale->GetDataType()));
return ACLNN_ERR_PARAM_INVALID;
}
if (scale->IsEmpty()) {
OP_LOGE(ACLNN_ERR_PARAM_INVALID, "scale does not support empty tensor");
return ACLNN_ERR_PARAM_INVALID;
}
if (!IsShapeEqualsOne(scale)) {
OP_LOGE(ACLNN_ERR_PARAM_INVALID, "scale shape must be [1]");
return ACLNN_ERR_PARAM_INVALID;
}
return ACLNN_SUCCESS;
}
static aclnnStatus CheckDstType(int64_t dstType)
{
if (dstType != DST_TYPE_HIFLOAT8 && dstType != DST_TYPE_FLOAT8_E5M2 && dstType != DST_TYPE_FLOAT8_E4M3FN) {
OP_LOGE(ACLNN_ERR_PARAM_INVALID, "dstType must be 34(HIFLOAT8)/35(FLOAT8_E5M2)/36(FLOAT8_E4M3FN), got %ld",
dstType);
return ACLNN_ERR_PARAM_INVALID;
}
return ACLNN_SUCCESS;
}
static aclnnStatus CheckRoundMode(int64_t dstType, const char* roundMode)
{
if (roundMode == nullptr) {
OP_LOGE(ACLNN_ERR_PARAM_INVALID, "roundMode cannot be nullptr");
return ACLNN_ERR_PARAM_INVALID;
}
if (strlen(roundMode) == 0) {
OP_LOGE(ACLNN_ERR_PARAM_INVALID, "roundMode cannot be empty string");
return ACLNN_ERR_PARAM_INVALID;
}
if (dstType == DST_TYPE_FLOAT8_E5M2 || dstType == DST_TYPE_FLOAT8_E4M3FN) {
if (strcmp(roundMode, "rint") != 0) {
OP_LOGE(ACLNN_ERR_PARAM_INVALID,
"roundMode must be 'rint' for dstType FLOAT8_E5M2(35) or FLOAT8_E4M3FN(36), got %s",
roundMode);
return ACLNN_ERR_PARAM_INVALID;
}
} else if (dstType == DST_TYPE_HIFLOAT8) {
if (strcmp(roundMode, "round") != 0 && strcmp(roundMode, "hybrid") != 0) {
OP_LOGE(ACLNN_ERR_PARAM_INVALID,
"roundMode must be 'round' or 'hybrid' for dstType HIFLOAT8(34), got %s",
roundMode);
return ACLNN_ERR_PARAM_INVALID;
}
}
return ACLNN_SUCCESS;
}
static aclnnStatus CheckOutputParams(const aclTensor* x, const aclTensor* y, const aclTensor* amax, int64_t dstType)
{
auto expectedYDtype = GetOutputDtype(dstType);
if (y->GetDataType() != expectedYDtype) {
OP_LOGE(ACLNN_ERR_PARAM_INVALID, "y dtype must be %d according to dstType, got %d",
static_cast<int>(expectedYDtype), static_cast<int>(y->GetDataType()));
return ACLNN_ERR_PARAM_INVALID;
}
if (!IsShapeEquals(y, x)) {
OP_LOGE(ACLNN_ERR_PARAM_INVALID, "y shape must be same as x shape");
return ACLNN_ERR_PARAM_INVALID;
}
if (amax->GetDataType() != x->GetDataType()) {
OP_LOGE(ACLNN_ERR_PARAM_INVALID, "amax dtype must be same as x dtype, got amax=%d, x=%d",
static_cast<int>(amax->GetDataType()), static_cast<int>(x->GetDataType()));
return ACLNN_ERR_PARAM_INVALID;
}
if (!IsShapeEqualsOne(amax)) {
OP_LOGE(ACLNN_ERR_PARAM_INVALID, "amax shape must be [1]");
return ACLNN_ERR_PARAM_INVALID;
}
return ACLNN_SUCCESS;
}
static aclnnStatus CheckParams(
const aclTensor* x, const aclTensor* scale, int64_t dstType, const char* roundMode, const aclTensor* y,
const aclTensor* amax)
{
auto ret = CheckNullParams(x, scale, y, amax);
CHECK_RET(ret == ACLNN_SUCCESS, ret);
ret = CheckInputParams(x, scale);
CHECK_RET(ret == ACLNN_SUCCESS, ret);
ret = CheckDstType(dstType);
CHECK_RET(ret == ACLNN_SUCCESS, ret);
ret = CheckRoundMode(dstType, roundMode);
CHECK_RET(ret == ACLNN_SUCCESS, ret);
ret = CheckOutputParams(x, y, amax, dstType);
CHECK_RET(ret == ACLNN_SUCCESS, ret);
return ACLNN_SUCCESS;
}
aclnnStatus aclnnQuantMaxGetWorkspaceSize(
const aclTensor* x, const aclTensor* scale, char* roundMode, int64_t dstType, const aclTensor* y,
const aclTensor* amax, uint64_t* workspaceSize, aclOpExecutor** executor)
{
L2_DFX_PHASE_1(aclnnQuantMax, DFX_IN(x, scale), DFX_OUT(y, amax));
auto uniqueExecutor = CREATE_EXECUTOR();
CHECK_RET(uniqueExecutor.get() != nullptr, ACLNN_ERR_INNER_CREATE_EXECUTOR);
auto ret = CheckParams(x, scale, dstType, roundMode, y, amax);
CHECK_RET(ret == ACLNN_SUCCESS, ret);
if (x->IsEmpty()) {
*workspaceSize = 0;
uniqueExecutor.ReleaseTo(executor);
return ACLNN_SUCCESS;
}
auto xContiguous = l0op::Contiguous(x, uniqueExecutor.get());
CHECK_RET(xContiguous != nullptr, ACLNN_ERR_INNER_NULLPTR);
auto scaleContiguous = l0op::Contiguous(scale, uniqueExecutor.get());
CHECK_RET(scaleContiguous != nullptr, ACLNN_ERR_INNER_NULLPTR);
auto yContiguous = l0op::Contiguous(y, uniqueExecutor.get());
CHECK_RET(yContiguous != nullptr, ACLNN_ERR_INNER_NULLPTR);
auto amaxContiguous = l0op::Contiguous(amax, uniqueExecutor.get());
CHECK_RET(amaxContiguous != nullptr, ACLNN_ERR_INNER_NULLPTR);
const aclTensor* opResult = l0op::QuantMax(
xContiguous, scaleContiguous, roundMode, dstType, yContiguous, amaxContiguous, uniqueExecutor.get());
CHECK_RET(opResult != nullptr, ACLNN_ERR_INNER_NULLPTR);
auto viewCopyResult = l0op::ViewCopy(opResult, y, uniqueExecutor.get());
CHECK_RET(viewCopyResult != nullptr, ACLNN_ERR_INNER_NULLPTR);
*workspaceSize = uniqueExecutor->GetWorkspaceSize();
uniqueExecutor.ReleaseTo(executor);
return ACLNN_SUCCESS;
}
aclnnStatus aclnnQuantMax(void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream)
{
L2_DFX_PHASE_2(aclnnQuantMax);
return CommonOpExecutorRun(workspace, workspaceSize, executor, stream);
}