* 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 dyn_attr_to_static.cpp
* \brief
*/
#include "passes/block_graph_pass/dyn_attr_to_static.h"
namespace npu {
namespace tile_fwk {
struct CoaInfo {
CoaType macroType = CoaType::INVALID;
int dim = -1;
int base = -1;
int idx = -1;
static bool ParseParamOffset(const std::string &coaExpr, std::smatch &match) {
static std::regex pattern("RUNTIME_COA_GET_PARAM_OFFSET\\((\\d+), (\\d+), (\\d+)\\)");
return std::regex_search(coaExpr, match, pattern);
}
static bool ParseParamValidShape(const std::string &coaExpr, std::smatch &match) {
static std::regex pattern("RUNTIME_COA_GET_PARAM_VALID_SHAPE\\((\\d+), (\\d+), (\\d+)\\)");
return std::regex_search(coaExpr, match, pattern);
}
static bool ParseParam(const std::string &coaExpr, std::smatch &match) {
static std::regex pattern("RUNTIME_COA_GET_PARAM\\((\\d+)\\)");
return std::regex_search(coaExpr, match, pattern);
}
Status SToIParamShapeAndOffset(const std::smatch &match) {
if (SToIWrapper(match[INPUT_PARAM_POS_ONE].str(), dim) != SUCCESS) {
APASS_LOG_ERROR_F(Elements::Operation, "Failed to convert dim.");
return FAILED;
}
if (SToIWrapper(match[INPUT_PARAM_POS_TWO].str(), base) != SUCCESS) {
APASS_LOG_ERROR_F(Elements::Operation, "Failed to convert base.");
return FAILED;
}
if (SToIWrapper(match[INPUT_PARAM_POS_THREE].str(), idx) != SUCCESS) {
APASS_LOG_ERROR_F(Elements::Operation, "Failed to convert idx.");
return FAILED;
}
return SUCCESS;
}
Status ParseCoaString(const std::string &coaExpr) {
std::smatch match;
if (ParseParamOffset(coaExpr, match)) {
macroType = CoaType::PARAM_OFFSET;
if (SToIParamShapeAndOffset(match) != SUCCESS) {
APASS_LOG_ERROR_F(Elements::Operation, "ParseCoaString failed to convert indices,"
"CoaType::PARAM_OFFSET, input coaExpr %s.", coaExpr.c_str());
return FAILED;
}
} else if (ParseParamValidShape(coaExpr, match)) {
macroType = CoaType::PARAM_VALID_SHAPE;
if (SToIParamShapeAndOffset(match) != SUCCESS) {
APASS_LOG_ERROR_F(Elements::Operation, "ParseCoaString failed to convert indices,"
"CoaType::PARAM_VALID_SHAPE, input coaExpr %s.", coaExpr.c_str());
return FAILED;
}
} else if (ParseParam(coaExpr, match)) {
macroType = CoaType::PARAM;
if (SToIWrapper(match[INPUT_PARAM_POS_ONE].str(), idx) != SUCCESS) {
APASS_LOG_ERROR_F(Elements::Operation, "ParseCoaString failed to convert indices,"
"CoaType::PARAM, input coaExpr %s.", coaExpr.c_str());
return FAILED;
}
} else if (coaExpr.find(MAYBE_CONST_POSTFIX) != std::string::npos) {
APASS_LOG_ERROR_F(Elements::Operation, "This coaExpr %s has been processed", coaExpr.c_str());
return FAILED;
} else {
APASS_LOG_ERROR_F(Elements::Operation, "ParseCoaString input coaExpr %s is not recognized.", coaExpr.c_str());
return FAILED;
}
return SUCCESS;
}
int CalculateCoaIndex() {
if (macroType == CoaType::PARAM_OFFSET) {
return ((base) + 1) + OFFSET_INDEX_ORDER * (dim) + idx;
} else if (macroType == CoaType::PARAM_VALID_SHAPE) {
return ((base) + 1) + VALID_SHAPE_INDEX_ORDER * (dim) + idx;
} else if (macroType == CoaType::PARAM) {
return idx;
}
APASS_LOG_ERROR_F(Elements::Operation, "GetCoaFinalIdx Coa type is invalid.");
return 0;
}
SymbolicScalar BuildMaybeConstCoa(int isConst, int attrValue) {
if (macroType == CoaType::PARAM_OFFSET) {
return MAYBE_CONST_COA_GetOffset(isConst, attrValue, dim, base, idx);
} else if (macroType == CoaType::PARAM_VALID_SHAPE) {
return MAYBE_CONST_COA_GetValidShape(isConst, attrValue, dim, base, idx);
} else if (macroType == CoaType::PARAM) {
return MAYBE_CONST_COA_GetParam(isConst, attrValue, idx);
}
APASS_LOG_ERROR_F(Elements::Operation, "BuildMaybeConstCoa Coa type is invalid.");
return 0;
}
};
struct IsConstMetric {
int isConst = 1;
int attrValue = -1;
void MarkNotConst() {isConst = 0;}
int GetIsConst() {return isConst;}
int GetAttrValue() {return attrValue;}
bool TryInitAndCheckEqual(int newValue) {
if (attrValue == -1) {
attrValue = newValue;
return true;
}
if (newValue < 0 || newValue != attrValue) {
isConst = 0;
return false;
}
return true;
}
};
Status SToIWrapper(const std::string str, int& result) {
try {
result = std::stoi(str);
return SUCCESS;
} catch (const std::exception &e) {
APASS_LOG_ERROR_F(Elements::Operation, "Failed to convert %s to int, error is %s.", str.c_str(), e.what());
}
return FAILED;
}
void DynAttrToStatic::RefSpecifiedValue(std::vector<SymbolicScalar> &oriList,
std::vector<std::reference_wrapper<SymbolicScalar>> &newList) const
{
for (auto &value : oriList) {
newList.push_back(std::reference_wrapper<SymbolicScalar>(value));
}
}
void DynAttrToStatic::FilterSpecifiedValue(std::vector<OpImmediate> &oriList,
std::vector<std::reference_wrapper<SymbolicScalar>> &newList) const
{
for (auto &value : oriList) {
if (value.IsSpecified()) {
newList.push_back(std::reference_wrapper<SymbolicScalar>(value.GetSpecifiedValue()));
}
}
}
std::vector<std::reference_wrapper<SymbolicScalar>> DynAttrToStatic::GetOpDynamicAttributeList(Operation &op) {
std::vector<std::reference_wrapper<SymbolicScalar>> dynamicAttributeList;
auto opcode = op.GetOpcode();
if(opcode == Opcode::OP_VIEW) {
auto viewAttr = std::static_pointer_cast<ViewOpAttribute>(op.GetOpAttribute());
if (viewAttr != nullptr) {
RefSpecifiedValue(viewAttr->GetFromDynOffset(), dynamicAttributeList);
RefSpecifiedValue(viewAttr->GetToDynValidShape(), dynamicAttributeList);
}
return dynamicAttributeList;
}
if(opcode == Opcode::OP_ASSEMBLE) {
auto assembleAttr = std::static_pointer_cast<AssembleOpAttribute>(op.GetOpAttribute());
if (assembleAttr != nullptr) {
RefSpecifiedValue(assembleAttr->GetToDynOffset(), dynamicAttributeList);
RefSpecifiedValue(assembleAttr->GetFromDynValidShape(), dynamicAttributeList);
}
return dynamicAttributeList;
}
const std::set<Opcode> specifiedOps = {Opcode::OP_VEC_DUP, Opcode::OP_EXPAND, Opcode::OP_RESHAPE};
if (specifiedOps.count(opcode)) {
auto &attrDict = op.GetAllAttr();
auto it = attrDict.find(OpAttributeKey::dynScalar);
if (it != attrDict.end()) {
auto &value = *npu::tile_fwk::AnyCast<SymbolicScalar>(&it->second);
dynamicAttributeList.push_back(std::reference_wrapper<SymbolicScalar>(value));
}
return dynamicAttributeList;
}
if (OpcodeManager::Inst().IsCopyInOrOut(opcode)) {
auto copyAttr = std::static_pointer_cast<CopyOpAttribute>(op.GetOpAttribute());
if (copyAttr != nullptr) {
FilterSpecifiedValue(copyAttr->GetToOffset(), dynamicAttributeList);
FilterSpecifiedValue(copyAttr->GetFromOffset(), dynamicAttributeList);
FilterSpecifiedValue(copyAttr->GetToDynValidShape(), dynamicAttributeList);
FilterSpecifiedValue(copyAttr->GetFromDynValidShape(), dynamicAttributeList);
}
}
return dynamicAttributeList;
}
Status DynAttrToStatic::GetCallee(const Operation &callop, Function *&callFunc) {
auto callopAttr = std::static_pointer_cast<CallOpAttribute>(callop.GetOpAttribute());
callFunc = Program::GetInstance().GetFunctionByMagicName(callopAttr->GetCalleeMagicName());
if (callFunc == nullptr) {
APASS_LOG_ERROR_F(Elements::Operation, "Get callee function %s failed.", callopAttr->GetCalleeMagicName().c_str());
return FAILED;
}
return SUCCESS;
}
Status DynAttrToStatic::BuildLeafToCaller(Function *func) {
if (func->IsFunctionTypeAndGraphType(
{FunctionType::DYNAMIC, FunctionType::DYNAMIC_LOOP, FunctionType::DYNAMIC_LOOP_PATH}, GraphType::TENSOR_GRAPH)) {
for (auto callop : func->GetCallopList()) {
Function *nextFunc = nullptr;
if (GetCallee(*callop, nextFunc) != SUCCESS) {
APASS_LOG_ERROR_F(Elements::Operation, "BuildLeafToCaller at %s, %s[%d] GetCallee failed.%s",
func->GetRawName().c_str(), callop->GetOpcodeStr().c_str(), callop->GetOpMagic(), GetFormatBacktrace(callop).c_str());
return FAILED;
}
if (BuildLeafToCaller(nextFunc) != SUCCESS) {
APASS_LOG_ERROR_F(Elements::Operation, "BuildLeafToCaller at %s, nextFunc at %s failed",
func->GetRawName().c_str(), nextFunc->GetRawName().c_str());
return FAILED;
}
}
return SUCCESS;
} else if (func->GetGraphType() == GraphType::TILE_GRAPH) {
Function *rootFunc = func->GetRootFunction();
return BuildLeafToCaller(rootFunc);
} else if (func->GetGraphType() == GraphType::EXECUTE_GRAPH) {
if (func->programModule_ != nullptr) {
return SUCCESS;
}
for (auto callop : func->GetCallopList()) {
Function *leafFunc = nullptr;
if (GetCallee(*callop, leafFunc) != SUCCESS) {
APASS_LOG_ERROR_F(Elements::Operation, "BuildLeafToCaller at %s, %s[%d] GetCallee failed.%s",
func->GetRawName().c_str(), callop->GetOpcodeStr().c_str(), callop->GetOpMagic(), GetFormatBacktrace(callop).c_str());
return FAILED;
}
leaf2Caller[leafFunc].push_back(callop);
}
return SUCCESS;
}
APASS_LOG_ERROR_F(Elements::Operation, "BuildLeafToCaller at %s entered unexpected function type %d.",
func->GetRawName().c_str(), static_cast<int>(func->GetFunctionType()));
return FAILED;
}
Status DynAttrToStatic::BuildNewCoa(
std::reference_wrapper<SymbolicScalar>& dynScalar,
std::vector<std::vector<SymbolicScalar>>& callopArglistOneDim)
{
std::string dynParamExpr = SymbolicExpressionTable::BuildExpression(dynScalar);
if (dynParamExpr.find(COA_PREFIX) != 1) {
APASS_LOG_INFO_F(Elements::Operation, "BuildNewCoa skips non-COA dynamic expression.");
return SUCCESS;
}
CoaInfo coaExpr;
if (coaExpr.ParseCoaString(dynParamExpr) != SUCCESS) {
APASS_LOG_ERROR_F(Elements::Operation, "BuildNewCoa found unexpected COA expression at dynParamExpr.");
return FAILED;
}
int coaIndex = coaExpr.CalculateCoaIndex();
IsConstMetric scalarValue;
for (auto& argList : callopArglistOneDim) {
auto& callopAttr = argList[coaIndex];
if (!callopAttr.IsImmediate()) {
scalarValue.MarkNotConst();
break;
} else {
if (!scalarValue.TryInitAndCheckEqual(callopAttr.Concrete())) {
break;
}
}
}
APASS_LOG_INFO_F(Elements::Operation, "BuildNewCoa update dynScalar[%s] with isConst=%d, value=%d.",
dynParamExpr.c_str(), scalarValue.isConst, scalarValue.attrValue);
dynScalar.get() = coaExpr.BuildMaybeConstCoa(scalarValue.isConst, scalarValue.attrValue);
return SUCCESS;
}
inline int GetCoaIndex(const DynParamInfo ¶mInfo) {
if (paramInfo.type == DynParamInfoType::VALID_SHAPE) {
return ((paramInfo.tensorBaseAddrCoaIndex + 1) + VALID_SHAPE_INDEX_ORDER * paramInfo.dimSize + paramInfo.dimIndex);
}
if (paramInfo.type == DynParamInfoType::OFFSET) {
return ((paramInfo.tensorBaseAddrCoaIndex + 1) + OFFSET_INDEX_ORDER * paramInfo.dimSize + paramInfo.dimIndex);
}
return paramInfo.dimIndex;
}
void ReplaceCommonSymbol(Function *leafFunc, std::vector<std::vector<SymbolicScalar>> &callopArglistOneDim) {
VectorParamConsistencyChecker checker;
for (auto& argList : callopArglistOneDim) {
checker.RegisterCall(argList);
}
auto allRes1 = checker.GetAllConsistentIndexGroups();
APASS_LOG_DEBUG_F(Elements::Operation, "Get all condicate params: %s.",checker.PrintIndexGroups(allRes1).c_str());
std::map<size_t, size_t> index2GroupId;
std::map<size_t, std::vector<size_t>> groupId2Index;
for (size_t i = 0; i < allRes1.size(); i++) {
for (size_t j = 0; j < allRes1[i].size(); j++) {
index2GroupId[allRes1[i][j]] = i;
}
}
std::map<std::string, int> symbol2CoaIdx;
for (const auto &dynParam : leafFunc->GetDynParamTable()) {
if (dynParam.second.dim.IsValid()) {
std::string dynParamExpr = SymbolicExpressionTable::BuildExpression(dynParam.second.dim);
if (dynParamExpr.find(COA_PREFIX) != 1) {
continue;
}
}
int coaIndex = GetCoaIndex(dynParam.second);
symbol2CoaIdx.emplace(dynParam.first, coaIndex);
APASS_LOG_DEBUG_F(Elements::Operation, "Need Replace symbols %s idx %d", dynParam.first.c_str(), coaIndex);
}
std::map<size_t, std::string> index2BaseSymbol;
for (auto [symbolStr, coaIdx] : symbol2CoaIdx) {
if (index2GroupId.find(coaIdx) != index2GroupId.end()) {
if (index2BaseSymbol.find(index2GroupId[coaIdx]) == index2BaseSymbol.end()) {
leafFunc->GetMutableDynParam(symbolStr).isBaseParam = true;
index2BaseSymbol[index2GroupId[coaIdx]] = symbolStr;
APASS_LOG_INFO_F(Elements::Operation, "Mark coaIndex[%d] groupId[%zu] symbolStr[%s] as baseParam",
coaIdx, index2GroupId[coaIdx], symbolStr.c_str());
} else {
leafFunc->GetMutableDynParam(symbolStr).replacedSymbol = index2BaseSymbol[index2GroupId[coaIdx]];
APASS_LOG_INFO_F(Elements::Operation, "Replace coaIndex[%d] groupId[%zu] symbolStr[%s] with baseParam[%s]",
coaIdx, index2GroupId[coaIdx], symbolStr.c_str(), index2BaseSymbol[index2GroupId[coaIdx]].c_str());
}
}
}
}
inline SymbolicScalar BuildMaybeConstCoa(int attrValue, const DynParamInfo ¶mInfo) {
if (paramInfo.type == DynParamInfoType::OFFSET) {
return MAYBE_CONST_COA_GetOffset(1, attrValue, paramInfo.dimSize, paramInfo.tensorBaseAddrCoaIndex, paramInfo.dimIndex);
}
if (paramInfo.type == DynParamInfoType::VALID_SHAPE) {
return MAYBE_CONST_COA_GetValidShape(1, attrValue,paramInfo.dimSize, paramInfo.tensorBaseAddrCoaIndex, paramInfo.dimIndex);
}
return MAYBE_CONST_COA_GetParam(1, attrValue, paramInfo.dimIndex);
}
void ReBuildConcreteParam(Function *leafFunc, std::vector<std::vector<SymbolicScalar>> &callopArglistOneDim) {
std::map<std::string, int> concreteParamCoaIdx;
for (auto &dynParam : leafFunc->GetDynParamTable()) {
if (dynParam.second.dim.IsValid()) {
std::string dynParamExpr = SymbolicExpressionTable::BuildExpression(dynParam.second.dim);
if (dynParamExpr.find(COA_PREFIX) != 1) {
continue;
}
}
if (!(dynParam.second.isBaseParam) && !(dynParam.second.replacedSymbol.empty())) {
continue;
}
auto coaIdx = GetCoaIndex(dynParam.second);
APASS_LOG_DEBUG_F(Elements::Operation, "Get concrete symbols %s idx %d", dynParam.first.c_str(), coaIdx);
IsConstMetric scalarValue;
auto isConstParam = [&callopArglistOneDim, &scalarValue](int argIdx) {
for (auto& calleeArgs : callopArglistOneDim) {
auto callopAttr = calleeArgs[argIdx];
if (!callopAttr.IsImmediate()) {
return false;
}
if (!scalarValue.TryInitAndCheckEqual(callopAttr.Concrete())) {
return false;
}
}
return true;
};
if (!isConstParam(coaIdx)) {
continue;
}
auto constParam = BuildMaybeConstCoa(scalarValue.attrValue, dynParam.second);
leafFunc->GetMutableDynParam(dynParam.first).dim = constParam;
}
}
Status DynAttrToStatic::TryRemoveDynAttr(Function* leafFunc, std::vector<Operation*> callList) {
std::vector<std::vector<SymbolicScalar>> callopArglistOneDim;
for (size_t i = 0; i < callList.size(); i++) {
auto callop = std::static_pointer_cast<CallOpAttribute>(callList[i]->GetOpAttribute());
callopArglistOneDim.push_back(callop->GetLinearArgList());
}
auto operationViewer = leafFunc->Operations(false);
for (size_t j = 0; j < operationViewer.size(); j++) {
auto &op = operationViewer[j];
std::vector<std::reference_wrapper<SymbolicScalar>> dynScalarList = GetOpDynamicAttributeList(op);
for (auto dynScalar : dynScalarList) {
if (BuildNewCoa(dynScalar, callopArglistOneDim) != SUCCESS) {
APASS_LOG_ERROR_F(Elements::Operation, "TryRemoveDynAttr failed to execute BuildNewCoa for op [%d][%s].",
op.GetOpMagic(), op.GetOpcodeStr().c_str());
return FAILED;
}
}
}
ReplaceCommonSymbol(leafFunc, callopArglistOneDim);
ReBuildConcreteParam(leafFunc, callopArglistOneDim);
return SUCCESS;
}
Status DynAttrToStatic::RunOnFunction(Function &function) {
APASS_LOG_INFO_F(Elements::Operation, "==============> Start DynAttrToStatic.");
if (BuildLeafToCaller(&function) != SUCCESS) {
APASS_LOG_ERROR_F(Elements::Operation, "Failed to call BuildLeafToCaller.");
return FAILED;
}
for (const auto& pair : leaf2Caller) {
if (TryRemoveDynAttr(pair.first, pair.second) != SUCCESS) {
APASS_LOG_ERROR_F(Elements::Operation, "Failed to call TryRemoveDynAttr for leafFunc %s.", pair.first->GetRawName().c_str());
return FAILED;
}
}
APASS_LOG_INFO_F(Elements::Operation, "==============> End DynAttrToStatic.");
return SUCCESS;
}
}
}