已合并
feat: support IndexExpr and Arange autofusion #1961
feat: support IndexExpr and Arange autofusion #1961
已合并
Jett_Woo创建于 13 天前
36 个文件变更+3156-121
@@ -97,7 +97,7 @@ REG_ASC_IR(IndexExpr)
97 .Inputs({})97 .Inputs({})
98 .Output("y", "T")98 .Output("y", "T")
99 .StartNode()99 .StartNode()
100- .Attr<int64_t>("expr")100+ .Attr<Expression>("expr")
101 .ComputeType(ComputeType::kComputeInvalid)101 .ComputeType(ComputeType::kComputeInvalid)
102 .Impl(v1_soc_versions, {af::ascir::AscIrImplCreator<af::ascir::IndexExprAscIrAttImpl>(),102 .Impl(v1_soc_versions, {af::ascir::AscIrImplCreator<af::ascir::IndexExprAscIrAttImpl>(),
103 af::ascir::AscIrImplCreator<af::ascir::IndexExprAscIrCodegenImpl>(),103 af::ascir::AscIrImplCreator<af::ascir::IndexExprAscIrCodegenImpl>(),
@@ -126,6 +126,9 @@ bool CalculateDmaParams(const TPipe &tpipe, const Tensor &gm_tensor, const Tenso
126 const auto pos = std::find(gm_tensor.axis.begin(), gm_tensor.axis.end(), ub_tensor.vectorized_axis[vec_axis_pos]);126 const auto pos = std::find(gm_tensor.axis.begin(), gm_tensor.axis.end(), ub_tensor.vectorized_axis[vec_axis_pos]);
127 GE_ASSERT_TRUE((pos != gm_tensor.axis.end()), "Codegen vectorized axis[%zu] not found", vec_axis_pos);127 GE_ASSERT_TRUE((pos != gm_tensor.axis.end()), "Codegen vectorized axis[%zu] not found", vec_axis_pos);
128 const auto axis_pos = std::distance(gm_tensor.axis.begin(), pos);128 const auto axis_pos = std::distance(gm_tensor.axis.begin(), pos);
129+ if (af::SymbolicUtils::StaticCheckEq(gm_tensor.axis_size[axis_pos], af::ops::One) == af::TriBool::kTrue) {
130+ continue;
131+ }
129 // 如果当前轴gm和ub上对应的stride均为0,如果前序轴的stride不为1,则保留当前轴132 // 如果当前轴gm和ub上对应的stride均为0,如果前序轴的stride不为1,则保留当前轴
130 const bool ignore_zero_axis =133 const bool ignore_zero_axis =
131 ascgen_utils::ShouldIgnoreDataCopyZeroAxis(has_non_zero_axis, vec_axis_pos, ub_tensor.vectorized_strides);134 ascgen_utils::ShouldIgnoreDataCopyZeroAxis(has_non_zero_axis, vec_axis_pos, ub_tensor.vectorized_strides);
@@ -2062,8 +2062,8 @@ Status Kernel::AppendConstTensorInit(std::stringstream &ss) const {
2062 }2062 }
2063 GELOGI("const_value_expr: %s", tensor->second.const_value_expr.Str().get());2063 GELOGI("const_value_expr: %s", tensor->second.const_value_expr.Str().get());
2064 2064 
2065- string const_value = tensor->second.const_value_expr == 0 ? tensor->second.const_value2065+ string const_value = tensor->second.const_value.empty() ? tiler.Size(tensor->second.const_value_expr, true)
2066- : tiler.Size(tensor->second.const_value_expr, true);2066+ : tensor->second.const_value;
2067 ss << tensor->second.DefineConst(const_value.c_str()) << std::endl;2067 ss << tensor->second.DefineConst(const_value.c_str()) << std::endl;
2068 GELOGI("Define ss value: %s", ss.str().c_str());2068 GELOGI("Define ss value: %s", ss.str().c_str());
2069 }2069 }
@@ -2285,17 +2285,13 @@ Status Kernel::ParseGraph(const ascir::ImplGraph &graph, const ascir::FusedSched
2285 GE_CHK_STATUS_RET(kernel.tpipe.AddTensor(const_value, *output, tensor_name), "Codegen add tensor failed");2285 GE_CHK_STATUS_RET(kernel.tpipe.AddTensor(const_value, *output, tensor_name), "Codegen add tensor failed");
2286 GE_CHK_STATUS_RET(kernel.ParseOptimizeInfo(node, *output));2286 GE_CHK_STATUS_RET(kernel.ParseOptimizeInfo(node, *output));
2287 } else if (IsOps<IndexExpr>(node)) {2287 } else if (IsOps<IndexExpr>(node)) {
2288- int64_t size_id = 0;2288+ af::Expression expr;
2289 auto ir_attr = node->attr.ir_attr.get();2289 auto ir_attr = node->attr.ir_attr.get();
2290- if (ir_attr->GetAttrValue("expr", size_id) != af::GRAPH_SUCCESS) {2290+ if (ir_attr->GetAttrValue("expr", expr) != af::GRAPH_SUCCESS) {
2291- GELOGE(af::FAILED, "GetAttrValue index expr failed, size_id = %lld", size_id);2291+ GELOGE(af::FAILED, "GetAttrValue index expr failed");
2292 return af::FAILED;2292 return af::FAILED;
2293 }2293 }
2294- GELOGI("size_id = %lld", size_id);2294+ GE_CHK_STATUS_RET(kernel.tpipe.AddTensor(*output, expr, tensor_name), "Codegen add tensor failed");
2295- // todo index在Expression上是指什么? 暂时按照AddSizeVar顺序
2296- auto all_sizevar = graph.GetAllSizeVar();
2297- GE_CHK_STATUS_RET(kernel.tpipe.AddTensor(*output, all_sizevar.at(size_id)->expr, tensor_name),
2298- "Codegen add tensor failed");
2299 } else if (IsOps<Workspace>(node)) {2295 } else if (IsOps<Workspace>(node)) {
2300 GE_CHK_STATUS_RET(kernel.ParseWorkspaceTensor(output, fused_schedule_result, output_indices,2296 GE_CHK_STATUS_RET(kernel.ParseWorkspaceTensor(output, fused_schedule_result, output_indices,
2301 output_tensorid_to_index, output_index_to_name),2297 output_tensorid_to_index, output_index_to_name),
@@ -3773,8 +3769,8 @@ Status Kernel::GlobalTensorDefine(std::string &result) const {
3773 this->constant_tensors[i]);3769 this->constant_tensors[i]);
3774 GELOGI("const_value_expr: %s", tensor->second.const_value_expr.Str().get());3770 GELOGI("const_value_expr: %s", tensor->second.const_value_expr.Str().get());
3775 3771 
3776- string const_value = tensor->second.const_value_expr == 0 ? tensor->second.const_value3772+ string const_value = tensor->second.const_value.empty() ? tiler.Size(tensor->second.const_value_expr, true)
3777- : tiler.Size(tensor->second.const_value_expr, true);3773+ : tensor->second.const_value;
3778 ss << " " << tensor->second.DefineConst(const_value.c_str()) << std::endl;3774 ss << " " << tensor->second.DefineConst(const_value.c_str()) << std::endl;
3779 GELOGI("Define ss value: %s", ss.str().c_str());3775 GELOGI("Define ss value: %s", ss.str().c_str());
3780 }3776 }
@@ -162,7 +162,7 @@ class Tensor : public Variable {
162 return IsUbScalar() || IsConstScalar();162 return IsUbScalar() || IsConstScalar();
163 }163 }
164 std::string GetScalarValue() const {164 std::string GetScalarValue() const {
165- return IsUbScalar() ? ub_scalar_name : const_value;165+ return IsUbScalar() ? ub_scalar_name : (const_value.empty() ? Str() : const_value);
166 }166 }
167 inline ascir::SizeExpr GetTensorSize() const {167 inline ascir::SizeExpr GetTensorSize() const {
168 ascir::SizeExpr size_expr = af::Symbol(1);168 ascir::SizeExpr size_expr = af::Symbol(1);
@@ -37,6 +37,8 @@ inline constexpr char kAscBackendType[] = "AscBackend";
37inline constexpr char kIndexAttr[] = "index";37inline constexpr char kIndexAttr[] = "index";
38inline constexpr char kValueAttr[] = "value";38inline constexpr char kValueAttr[] = "value";
39inline constexpr char kExprAttr[] = "expr";39inline constexpr char kExprAttr[] = "expr";
40+inline constexpr char kBaseAttr[] = "base";
41+inline constexpr char kStepAttr[] = "step";
40inline constexpr char kOffsetAttr[] = "offset";42inline constexpr char kOffsetAttr[] = "offset";
41inline constexpr char kAxisAttr[] = "axis";43inline constexpr char kAxisAttr[] = "axis";
42inline constexpr char kHasRelu[] = "has_relu";44inline constexpr char kHasRelu[] = "has_relu";
@@ -1177,14 +1179,46 @@ auto GetValidatedIrAttr(PyObject *self, const char *attr_type_name) -> AttrDefTy
1177 return attr ? (attr->SetMethod(ReverseConvFunc(value)), 0) : -1; \1179 return attr ? (attr->SetMethod(ReverseConvFunc(value)), 0) : -1; \
1178 }1180 }
1179 1181 
1182+#define DEFINE_IR_EXPR_ATTR_ACCESSORS(OpType, AttrType, AttrName, SetMethod, GetMethod) \
1183+ template <> \
1184+ PyObject *OpsOperatorIrAttr<af::ascir_op::OpType, AttrName>::_getter(PyObject *self, void *closure) { \
1185+ (void)closure; \
1186+ auto *attr = GetValidatedIrAttr<af::ascir_op::OpType, af::ascir_op::OpType::AttrType>(self, #AttrType); \
1187+ af::Expression value; \
1188+ if (attr == nullptr) { \
1189+ return nullptr; \
1190+ } \
1191+ PY_ASSERT_GRAPH_SUCCESS(attr->GetMethod(value), "%s attr %s getter failed", #OpType, AttrName); \
1192+ return pyascir::SizeExpr::FromSizeExpr(value); \
1193+ } \
1194+ template <> \
1195+ int OpsOperatorIrAttr<af::ascir_op::OpType, AttrName>::_setter(PyObject *self, PyObject *value, void *closure) { \
1196+ (void)closure; \
1197+ if (PyObject_IsInstance(value, reinterpret_cast<PyObject *>(&pyascir::SizeExpr::type)) != kPythonSuccess) { \
1198+ PyErr_Format(PyExc_TypeError, "%s attr %s expected Expression", #OpType, AttrName); \
1199+ return -1; \
1200+ } \
1201+ auto expression = pyascir::SizeExpr::AsExpression(value); \
1202+ if (PyErr_Occurred() != nullptr) { \
1203+ return -1; \
1204+ } \
1205+ if (!expression.IsValid()) { \
1206+ PyErr_Format(PyExc_ValueError, "%s attr %s expected a valid Expression", #OpType, AttrName); \
1207+ return -1; \
1208+ } \
1209+ auto *attr = GetValidatedIrAttr<af::ascir_op::OpType, af::ascir_op::OpType::AttrType>(self, #AttrType); \
1210+ return attr ? (attr->SetMethod(expression), 0) : -1; \
1211+ }
1212+ 
1180DEFINE_IR_ATTR_ACCESSORS(Data, AscDataIrAttrDef, kIndexAttr, int64_t, PyLong_Check, PyLong_FromLong, PyLong_AsLong,1213DEFINE_IR_ATTR_ACCESSORS(Data, AscDataIrAttrDef, kIndexAttr, int64_t, PyLong_Check, PyLong_FromLong, PyLong_AsLong,
1181 SetIndex, GetIndex)1214 SetIndex, GetIndex)
1182DEFINE_IR_ATTR_ACCESSORS(Output, AscOutputIrAttrDef, kIndexAttr, int64_t, PyLong_Check, PyLong_FromLong, PyLong_AsLong,1215DEFINE_IR_ATTR_ACCESSORS(Output, AscOutputIrAttrDef, kIndexAttr, int64_t, PyLong_Check, PyLong_FromLong, PyLong_AsLong,
1183 SetIndex, GetIndex)1216 SetIndex, GetIndex)
1184DEFINE_IR_ATTR_ACCESSORS(ScalarData, AscScalarDataIrAttrDef, kIndexAttr, int64_t, PyLong_Check, PyLong_FromLong,1217DEFINE_IR_ATTR_ACCESSORS(ScalarData, AscScalarDataIrAttrDef, kIndexAttr, int64_t, PyLong_Check, PyLong_FromLong,
1185 PyLong_AsLong, SetIndex, GetIndex)1218 PyLong_AsLong, SetIndex, GetIndex)
1186-DEFINE_IR_ATTR_ACCESSORS(IndexExpr, AscIndexExprIrAttrDef, kExprAttr, int64_t, PyLong_Check, PyLong_FromLong,1219+DEFINE_IR_EXPR_ATTR_ACCESSORS(IndexExpr, AscIndexExprIrAttrDef, kExprAttr, SetExpr, GetExpr)
1187- PyLong_AsLong, SetExpr, GetExpr)1220+DEFINE_IR_EXPR_ATTR_ACCESSORS(Arange, AscArangeIrAttrDef, kBaseAttr, SetBase, GetBase)
1221+DEFINE_IR_EXPR_ATTR_ACCESSORS(Arange, AscArangeIrAttrDef, kStepAttr, SetStep, GetStep)
1188DEFINE_IR_ATTR_ACCESSORS(Gather, AscGatherIrAttrDef, kAxisAttr, int64_t, PyLong_Check, PyLong_FromLong, PyLong_AsLong,1222DEFINE_IR_ATTR_ACCESSORS(Gather, AscGatherIrAttrDef, kAxisAttr, int64_t, PyLong_Check, PyLong_FromLong, PyLong_AsLong,
1189 SetAxis, GetAxis)1223 SetAxis, GetAxis)
1190DEFINE_IR_ATTR_ACCESSORS(IndirectLoad, AscIndirectLoadIrAttrDef, kAxisAttr, int64_t, PyLong_Check, PyLong_FromLong,1224DEFINE_IR_ATTR_ACCESSORS(IndirectLoad, AscIndirectLoadIrAttrDef, kAxisAttr, int64_t, PyLong_Check, PyLong_FromLong,
@@ -1359,6 +1393,7 @@ const std::map<std::string, typename IrAttr<OpType>::handler> IrAttr<OpType>::at
1359 {"Scalar", AutoRegAttrHandle<af::ascir_op::Scalar, kValueAttr>::RegHandle},1393 {"Scalar", AutoRegAttrHandle<af::ascir_op::Scalar, kValueAttr>::RegHandle},
1360 {"ScalarData", AutoRegAttrHandle<af::ascir_op::ScalarData, kIndexAttr>::RegHandle},1394 {"ScalarData", AutoRegAttrHandle<af::ascir_op::ScalarData, kIndexAttr>::RegHandle},
1361 {"IndexExpr", AutoRegAttrHandle<af::ascir_op::IndexExpr, kExprAttr>::RegHandle},1395 {"IndexExpr", AutoRegAttrHandle<af::ascir_op::IndexExpr, kExprAttr>::RegHandle},
1396+ {"Arange", AutoRegAttrHandle<af::ascir_op::Arange, kBaseAttr, kStepAttr>::RegHandle},
1362 {"Output", AutoRegAttrHandle<af::ascir_op::Output, kIndexAttr>::RegHandle},1397 {"Output", AutoRegAttrHandle<af::ascir_op::Output, kIndexAttr>::RegHandle},
1363 {"Load", AutoRegAttrHandle<af::ascir_op::Load, kOffsetAttr>::RegHandle},1398 {"Load", AutoRegAttrHandle<af::ascir_op::Load, kOffsetAttr>::RegHandle},
1364 {"Store", AutoRegAttrHandle<af::ascir_op::Store, kOffsetAttr>::RegHandle},1399 {"Store", AutoRegAttrHandle<af::ascir_op::Store, kOffsetAttr>::RegHandle},
@@ -35,6 +35,7 @@ PyMODINIT_FUNC PyInit_pyautofuse(void);
35 OP(Workspace) \35 OP(Workspace) \
36 OP(Output) \36 OP(Output) \
37 OP(IndexExpr) \37 OP(IndexExpr) \
38+ OP(Arange) \
38 OP(Load) \39 OP(Load) \
39 OP(Broadcast) \40 OP(Broadcast) \
40 OP(Store) \41 OP(Store) \
@@ -340,6 +340,19 @@ const af::Expression SizeExpr::AsSizeExpr(PyObject *obj) {
340 return af::Expression();340 return af::Expression();
341}341}
342 342 
343+const af::Expression SizeExpr::AsExpression(PyObject *obj) {
344+ if (PyObject_IsInstance(obj, reinterpret_cast<PyObject *>(&SizeExpr::type)) == kPythonSuccess) {
345+ auto size = reinterpret_cast<SizeExpr::Object *>(obj);
346+ if (size->expression == nullptr) {
347+ PyErr_SetString(PyExc_ValueError, "Expression is not initialized");
348+ return af::Expression();
349+ }
350+ return af::Expression(*size->expression);
351+ }
352+ PyErr_Format(PyExc_TypeError, "Only support type of Expression");
353+ return af::Expression();
354+}
355+ 
343PyObject *SizeExpr::Add(PyObject *self, PyObject *args) {356PyObject *SizeExpr::Add(PyObject *self, PyObject *args) {
344 af::Expression left = SizeExpr::AsSizeExpr(self);357 af::Expression left = SizeExpr::AsSizeExpr(self);
345 PY_ASSERT_TRUE(left.IsValid(), "left operand of add is not a valid SizeExpr");358 PY_ASSERT_TRUE(left.IsValid(), "left operand of add is not a valid SizeExpr");
@@ -39,6 +39,7 @@ class SizeExpr {
39 static int Init(PyObject *self_pyobject, const af::Expression &expr);39 static int Init(PyObject *self_pyobject, const af::Expression &expr);
40 static PyObject *FromSizeExpr(const af::Expression &expr);40 static PyObject *FromSizeExpr(const af::Expression &expr);
41 static const af::Expression AsSizeExpr(PyObject *obj);41 static const af::Expression AsSizeExpr(PyObject *obj);
42+ static const af::Expression AsExpression(PyObject *obj);
42 static PyObject *Add(PyObject *self, PyObject *args);43 static PyObject *Add(PyObject *self, PyObject *args);
43 static PyObject *Mul(PyObject *self, PyObject *args);44 static PyObject *Mul(PyObject *self, PyObject *args);
44 static PyObject *Div(PyObject *self, PyObject *args);45 static PyObject *Div(PyObject *self, PyObject *args);
@@ -263,8 +263,13 @@ def Output(
263 263 
264 264 
265def IndexExpr(265def IndexExpr(
266- owner_graph: ascir.HintGraph, *, dtype: ascir.dtypes, expr: Optional[int] = None266+ owner_graph: ascir.HintGraph,
Z

接口变更是否要考虑兼容性?

likedislike
Jett_Woo
12 天前 评论:
267+ *,
268+ dtype: ascir.dtypes,
269+ expr: ascir.SizeExpr,
267) -> ascir.OpsOperatorOutput:270) -> ascir.OpsOperatorOutput:
271+ if not isinstance(expr, ascir.SizeExpr):
272+ raise TypeError("IndexExpr expr must be SizeExpr")
268 meta = _get_metadata(owner_graph)273 meta = _get_metadata(owner_graph)
269 name = _generate_op_name(owner_graph, "indexexpr")274 name = _generate_op_name(owner_graph, "indexexpr")
270 op = ascir.ops.IndexExpr(name, owner_graph)275 op = ascir.ops.IndexExpr(name, owner_graph)
@@ -275,6 +280,55 @@ def IndexExpr(
275 return op.y280 return op.y
276 281 
277 282 
283+def arange(
284+ owner_graph: ascir.HintGraph,
285+ *,
286+ dtype: ascir.dtypes,
287+ base: ascir.SizeExpr,
288+ step: ascir.SizeExpr,
289+ axis: List[ascir.Axis],
290+ size: Optional[List[ascir.SizeExpr]] = None,
291+ stride: Optional[List[ascir.SizeExpr]] = None,
292+) -> ascir.OpsOperatorOutput:
293+ if not isinstance(base, ascir.SizeExpr) or not isinstance(step, ascir.SizeExpr):
294+ raise TypeError("Arange base and step must be SizeExpr")
295+ if not axis:
296+ raise ValueError("Arange axis must not be empty")
297+ if stride is not None and len(stride) != len(axis):
298+ raise ValueError("stride should be same with axis len")
299+ if stride is not None:
300+ last_nonzero = next(
301+ (
302+ (index, value)
303+ for index, value in reversed(list(enumerate(stride)))
304+ if not (value == ascir.SizeExpr(0))
305+ ),
306+ None,
307+ )
308+ if last_nonzero is not None and not (last_nonzero[1] == ascir.SizeExpr(1)):
309+ raise ValueError(
310+ "Arange requires unit physical stride on the vectorized axis"
311+ )
312+ effective_size = size if size is not None else [item.size for item in axis]
313+ singleton_start = 0 if last_nonzero is None else last_nonzero[0] + 1
314+ if any(
315+ not (value == ascir.SizeExpr(1))
316+ for value in effective_size[singleton_start:]
317+ ):
318+ raise ValueError("Arange zero strides require singleton dimensions")
319+ meta = _get_metadata(owner_graph)
320+ name = _generate_op_name(owner_graph, "arange")
321+ op = ascir.ops.Arange(name, owner_graph)
322+ meta.ops.append(op)
323+ op.attr.ir_attr.base = base
324+ op.attr.ir_attr.step = step
325+ op.y.dtype = dtype
326+ _infer_or_set_view(op.y, axis, size, stride)
327+ op.attr.sched.axis = axis
328+ op.infer_dtype()
329+ return op.y
330+ 
331+ 
278def Load(332def Load(
279 owner_graph: ascir.HintGraph,333 owner_graph: ascir.HintGraph,
280 x: ascir.OpsOperatorOutput,334 x: ascir.OpsOperatorOutput,
@@ -380,6 +380,22 @@ Status BufQueAllocator::SetOutputTensorAttr(const af::AscGraph &impl_graph) cons
380 }380 }
381 }381 }
382 }382 }
383+ 
384+ // VF子图中的常量Scalar副本在分区时拷贝了未分配的tensor_id, 此处按命名映射回填根图分配结果。
385+ constexpr std::string_view kSubgraphScalarPrefix = "Scalar_";
386+ std::vector<af::AscGraph> sub_graphs;
387+ GE_ASSERT_GRAPH_SUCCESS(impl_graph.GetAllSubGraphs(sub_graphs));
388+ for (auto &sub_graph : sub_graphs) {
389+ for (const auto &node : sub_graph.GetAllNodes()) {
390+ if (!IsOps<Scalar>(node) || node->GetName().size() <= kSubgraphScalarPrefix.size()) {
391+ continue;
392+ }
393+ const auto root_node = impl_graph.FindNode(node->GetName().substr(kSubgraphScalarPrefix.size()).c_str());
394+ if ((root_node != nullptr) && (IsOps<Scalar>(root_node) || IsOps<IndexExpr>(root_node))) {
395+ node->outputs[0].attr.mem.tensor_id = root_node->outputs[0].attr.mem.tensor_id;
396+ }
397+ }
398+ }
383 return ge::GRAPH_SUCCESS;399 return ge::GRAPH_SUCCESS;
384}400}
385 401 
@@ -14,6 +14,7 @@
14#include "graph/symbolizer/symbolic_utils.h"14#include "graph/symbolizer/symbolic_utils.h"
15#include "indirect_load_utils.h"15#include "indirect_load_utils.h"
16#include "platform/platform_factory.h"16#include "platform/platform_factory.h"
17+#include "graph/ascendc_ir/ascir_registry.h"
17 18 
18namespace optimize {19namespace optimize {
19namespace {20namespace {
@@ -21,6 +22,15 @@ bool ShouldSkipRegularAlignment(const af::AscNodePtr &node) {
21 return ScheduleUtils::IsBuffer(node) ||22 return ScheduleUtils::IsBuffer(node) ||
22 ascgen_utils::indirect_load::GetTemplateBehavior(node).uses_direct_gm_pipeline;23 ascgen_utils::indirect_load::GetTemplateBehavior(node).uses_direct_gm_pipeline;
23}24}
25+ 
26+bool IsLegalZeroInputNode(const af::AscNodePtr &node) {
27+ if (!node->inputs().empty()) {
28+ return true;
29+ }
30+ const auto &definitions = af::ascir::AscirRegistry::GetInstance().GetAll();
31+ const auto definition = definitions.find(node->GetType());
32+ return definition != definitions.end() && definition->second.IsStartNode();
33+}
24} // namespace34} // namespace
25 35 
26const char *BaseAlignmentStrategy::AlignmentTypeToString(AlignmentType type) {36const char *BaseAlignmentStrategy::AlignmentTypeToString(AlignmentType type) {
@@ -391,10 +401,8 @@ af::Status BaseAlignmentStrategy::AlignVectorizedStrides(ascir::ImplGraph &impl_
391}401}
392 402 
393af::Status BaseAlignmentStrategy::InferAlignmentForOneNode(ascir::ImplGraph &, const af::AscNodePtr &node, bool &) {403af::Status BaseAlignmentStrategy::InferAlignmentForOneNode(ascir::ImplGraph &, const af::AscNodePtr &node, bool &) {
394- GE_ASSERT_TRUE(
395- !node->inputs().empty() || af::ops::IsOps<af::ascir_op::Rand>(node) || af::ops::IsOps<af::ascir_op::Randn>(node),
396- "The inputs of %s(%s) is empty.", node->GetTypePtr(), node->GetNamePtr());
397 GE_ASSERT_TRUE(!node->outputs().empty(), "The output of %s(%s) is empty.", node->GetTypePtr(), node->GetNamePtr());404 GE_ASSERT_TRUE(!node->outputs().empty(), "The output of %s(%s) is empty.", node->GetTypePtr(), node->GetNamePtr());
405+ GE_ASSERT_TRUE(IsLegalZeroInputNode(node), "The inputs of %s(%s) are empty.", node->GetTypePtr(), node->GetNamePtr());
398 af::ComputeType compute_type = node->attr.api.compute_type;406 af::ComputeType compute_type = node->attr.api.compute_type;
399 auto it = compute_type_to_infer_func_.find(compute_type);407 auto it = compute_type_to_infer_func_.find(compute_type);
400 if (it != compute_type_to_infer_func_.end()) {408 if (it != compute_type_to_infer_func_.end()) {
@@ -88,8 +88,10 @@ class ScheduleUtils {
88 static bool HasComputeType(const ascir::ImplGraph &impl_graph, const af::ComputeType compute_type);88 static bool HasComputeType(const ascir::ImplGraph &impl_graph, const af::ComputeType compute_type);
89 89 
90 static bool IsIOBuffer(const af::NodePtr &node) {90 static bool IsIOBuffer(const af::NodePtr &node) {
91- return af::ops::IsOps<af::ascir_op::Scalar>(node) || IsDataInput(node) ||91+ // IndexExpr 是无输入标量表达式节点(sched.axis 为空), Scalar 同属不参与
92- af::ops::IsOps<af::ascir_op::Output>(node);92+ // 全 reduce 展维的标量路径, 需一并跳过, 否则空 axis 触发展维断言。
93+ return af::ops::IsOps<af::ascir_op::Scalar>(node) || af::ops::IsOps<af::ascir_op::IndexExpr>(node) ||
94+ IsDataInput(node) || af::ops::IsOps<af::ascir_op::Output>(node);
93 }95 }
94 96 
95 static bool IsDataInput(const af::NodePtr &node) {97 static bool IsDataInput(const af::NodePtr &node) {
@@ -100,12 +102,17 @@ class ScheduleUtils {
100 return af::ops::IsOps<af::ascir_op::Data>(node) || af::ops::IsOps<af::ascir_op::ScalarData>(node);102 return af::ops::IsOps<af::ascir_op::Data>(node) || af::ops::IsOps<af::ascir_op::ScalarData>(node);
101 }103 }
102 104 
105+ // IndexExpr 是标量表达式节点(输出无 axis/repeats), 与 Scalar/ScalarData 同属
106+ // 标量语义; 标量路径的调度/缓存判定需将其一并识别, 否则以标量为输入的节点
107+ // 会因空 axis 与输出轴数不一致而触发断言。
103 static bool IsScalarLikeNode(const af::NodePtr &node) {108 static bool IsScalarLikeNode(const af::NodePtr &node) {
104- return af::ops::IsOps<af::ascir_op::Scalar>(node) || af::ops::IsOps<af::ascir_op::ScalarData>(node);109+ return af::ops::IsOps<af::ascir_op::Scalar>(node) || af::ops::IsOps<af::ascir_op::ScalarData>(node) ||
110+ af::ops::IsOps<af::ascir_op::IndexExpr>(node);
105 }111 }
106 112 
107 static bool IsScalarLikeNode(const af::Node *const node) {113 static bool IsScalarLikeNode(const af::Node *const node) {
108- return af::ops::IsOps<af::ascir_op::Scalar>(node) || af::ops::IsOps<af::ascir_op::ScalarData>(node);114+ return af::ops::IsOps<af::ascir_op::Scalar>(node) || af::ops::IsOps<af::ascir_op::ScalarData>(node) ||
115+ af::ops::IsOps<af::ascir_op::IndexExpr>(node);
109 }116 }
110 117 
111 static bool IsConstantScalar(const af::Node *const node) {118 static bool IsConstantScalar(const af::Node *const node) {
@@ -46,3 +46,14 @@ target_link_libraries(test_ascir_ut
46 ascir_builtin_ops46 ascir_builtin_ops
47 #ge_compiler47 #ge_compiler
48 GTest::gtest)48 GTest::gtest)
49+ 
50+add_executable(test_index_expr_arange_schema test_index_expr_arange_schema.cpp)
51+target_link_libraries(test_index_expr_arange_schema PRIVATE
52+ ascir
53+ ascir_builtin_ops
54+ ascir_ops_headers
55+ aihac_symbolizer_af
56+ metadef
57+ GTest::gtest
58+ GTest::gtest_main)
59+gtest_discover_tests(test_index_expr_arange_schema)
@@ -0,0 +1,105 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include "gtest/gtest.h"
12+ 
13+#include "ascir.h"
14+#include "ascir_ops.h"
15+ 
16+TEST(IndexExprSchema, StoresExpressionAttribute) {
17+ af::AscGraph graph("index_expr_schema");
18+ const auto expr = af::Expression::Parse("s0 + 2");
19+ 
20+ (void)af::ascir::cg::IndexExpr("index", graph, af::DT_INT64, {}, {}, {}, expr);
21+ 
22+ auto index_node = graph.FindNode("index");
23+ ASSERT_NE(index_node, nullptr);
24+ const auto &index_attr =
25+ dynamic_cast<const af::ascir_op::IndexExpr::AscIndexExprIrAttrDef &>(*index_node->attr.ir_attr);
26+ af::Expression stored_expr;
27+ ASSERT_EQ(index_attr.GetExpr(stored_expr), af::GRAPH_SUCCESS);
28+ EXPECT_EQ(stored_expr, expr);
29+ EXPECT_EQ(index_node->outputs[0].attr.dtype, af::DT_INT64);
30+ EXPECT_TRUE(index_node->outputs[0].attr.axis.empty());
31+}
32+ 
33+TEST(IndexExprSchema, SupportsOnlyV2Int32AndInt64) {
34+ for (const auto dtype : {af::DT_INT32, af::DT_INT64}) {
35+ for (const auto *npu_arch : {"3510", "5102"}) {
36+ std::vector<af::DataType> output_dtypes{dtype};
37+ EXPECT_EQ(af::ascir_op::IndexExpr::InferDataType({}, output_dtypes, npu_arch), af::SUCCESS);
38+ }
39+ }
40+ 
41+ std::vector<af::DataType> float_output{af::DT_FLOAT};
42+ EXPECT_NE(af::ascir_op::IndexExpr::InferDataType({}, float_output, "3510"), af::SUCCESS);
43+}
44+ 
45+TEST(IndexExprSchema, MissingExpressionAttributeFails) {
46+ af::AscGraph graph("missing_index_expr");
47+ af::ascir_op::IndexExpr index("index", graph);
48+ af::Expression expr;
49+ 
50+ EXPECT_NE(index.ir_attr.GetExpr(expr), af::GRAPH_SUCCESS);
51+}
52+ 
53+TEST(ArangeSchema, StoresAttributesAndOneDimensionalLaneView) {
54+ af::AscGraph graph("arange_schema");
55+ const auto lane_count = graph.CreateSizeVar("lane_count");
56+ const auto lane = graph.CreateAxis("lane", lane_count);
57+ const auto base = af::Expression::Parse("s0 + 1");
58+ const auto step = af::Expression::Parse("2");
59+ 
60+ (void)af::ascir::cg::Arange("arange", graph, af::DT_INT32, {lane.id}, std::vector<af::Expression>{lane_count},
61+ std::vector<af::Expression>{af::Expression::Parse("1")}, base, step);
62+ 
63+ auto arange_node = graph.FindNode("arange");
64+ ASSERT_NE(arange_node, nullptr);
65+ const auto &arange_attr = dynamic_cast<const af::ascir_op::Arange::AscArangeIrAttrDef &>(*arange_node->attr.ir_attr);
66+ af::Expression stored_base;
67+ af::Expression stored_step;
68+ ASSERT_EQ(arange_attr.GetBase(stored_base), af::GRAPH_SUCCESS);
69+ ASSERT_EQ(arange_attr.GetStep(stored_step), af::GRAPH_SUCCESS);
70+ EXPECT_EQ(stored_base, base);
71+ EXPECT_EQ(stored_step, step);
72+ EXPECT_EQ(arange_node->outputs[0].attr.dtype, af::DT_INT32);
73+ EXPECT_EQ(arange_node->outputs[0].attr.axis, std::vector<af::AxisId>{lane.id});
74+ EXPECT_EQ(arange_node->outputs[0].attr.repeats, std::vector<af::Expression>{lane_count});
75+ EXPECT_EQ(arange_node->outputs[0].attr.strides, std::vector<af::Expression>{af::Expression::Parse("1")});
76+}
77+ 
78+TEST(ArangeSchema, SupportsOnlyV2Int32AndInt64) {
79+ for (const auto dtype : {af::DT_INT32, af::DT_INT64}) {
80+ for (const auto *npu_arch : {"3510", "5102"}) {
81+ std::vector<af::DataType> output_dtypes{dtype};
82+ EXPECT_EQ(af::ascir_op::Arange::InferDataType({}, output_dtypes, npu_arch), af::SUCCESS);
83+ }
84+ }
85+ 
86+ std::vector<af::DataType> float_output{af::DT_FLOAT};
87+ const auto invalid_dtype_status = af::ascir_op::Arange::InferDataType({}, float_output, "3510");
88+ EXPECT_NE(invalid_dtype_status, af::SUCCESS);
89+ 
90+ std::vector<af::DataType> v1_output{af::DT_INT32};
91+ EXPECT_NE(af::ascir_op::Arange::InferDataType({}, v1_output, "2201"), af::SUCCESS);
92+ 
93+ std::vector<af::DataType> unknown_platform_output{af::DT_INT64};
94+ EXPECT_NE(af::ascir_op::Arange::InferDataType({}, unknown_platform_output, "unknown"), af::SUCCESS);
95+}
96+ 
97+TEST(ArangeSchema, MissingBaseOrStepAttributeFails) {
98+ af::AscGraph graph("missing_arange_attrs");
99+ af::ascir_op::Arange arange("arange", graph);
100+ af::Expression base;
101+ af::Expression step;
102+ 
103+ EXPECT_NE(arange.ir_attr.GetBase(base), af::GRAPH_SUCCESS);
104+ EXPECT_NE(arange.ir_attr.GetStep(step), af::GRAPH_SUCCESS);
105+}
@@ -127,7 +127,7 @@ TEST(CodegenKernel, StoreApiCall_TwoStoreOneOutput) {
127 127 
128 std::string result;128 std::string result;
129 call_0.Generate(tpipe, vector<af::AxisId>{}, result);129 call_0.Generate(tpipe, vector<af::AxisId>{}, result);
130- EXPECT_EQ(result, std::string{"DataCopyPadExtend(local_1[0 + 0], local_0, 1, 1, 16 - 1, 0);\n"});130+ EXPECT_EQ(result, std::string{"DataCopyPadExtend(local_1[0 + 0], local_0, 1, 1, 0, 0);\n"});
131 131 
132 codegen::StoreApiCall call_1("Store");132 codegen::StoreApiCall call_1("Store");
133 EXPECT_EQ(call_1.Init(store_1), 0);133 EXPECT_EQ(call_1.Init(store_1), 0);
@@ -238,7 +238,7 @@ TEST(CodegenKernel, StoreApiCall_NeetMte3SyncMte2) {
238 call_1.inputs.push_back(&x1);238 call_1.inputs.push_back(&x1);
239 x1.reads.push_back(&call_1);239 x1.reads.push_back(&call_1);
240 call_1.Generate(kernel.tpipe, vector<af::AxisId>{}, result);240 call_1.Generate(kernel.tpipe, vector<af::AxisId>{}, result);
241- EXPECT_EQ(result, std::string{"DataCopyPadExtend(local_1[0 + 0], local_0, 1, 1, 16 - 1, 0);\n"241+ EXPECT_EQ(result, std::string{"DataCopyPadExtend(local_1[0 + 0], local_0, 1, 1, 0, 0);\n"
242 "auto local_0_e_mte3_2_mte2_t_0 = tpipe.AllocEventID<HardEvent::MTE3_MTE2>();\n"242 "auto local_0_e_mte3_2_mte2_t_0 = tpipe.AllocEventID<HardEvent::MTE3_MTE2>();\n"
243 "TQueSync<PIPE_MTE3, PIPE_MTE2> local_0_s_mte3_2_mte2_t_0;\n"243 "TQueSync<PIPE_MTE3, PIPE_MTE2> local_0_s_mte3_2_mte2_t_0;\n"
244 "local_0_s_mte3_2_mte2_t_0.SetFlag(local_0_e_mte3_2_mte2_t_0);\n"244 "local_0_s_mte3_2_mte2_t_0.SetFlag(local_0_e_mte3_2_mte2_t_0);\n"
@@ -4192,11 +4192,12 @@ TEST(CodegenKernel, Kernel_ConstantTensorInit) {
4192TEST(CodegenKernel, Kernel_IndexExprTensorInit) {4192TEST(CodegenKernel, Kernel_IndexExprTensorInit) {
4193 af::AscGraph graph("test_graph");4193 af::AscGraph graph("test_graph");
4194 auto s0 = graph.CreateSizeVar("s0");4194 auto s0 = graph.CreateSizeVar("s0");
4195+ auto s1 = graph.CreateSizeVar("s1");
4195 4196 
4196 af::ascir_op::IndexExpr index("index");4197 af::ascir_op::IndexExpr index("index");
4197 graph.AddNode(index);4198 graph.AddNode(index);
4198- index.ir_attr.SetExpr(0);4199+ index.ir_attr.SetExpr(s0 + s1 * af::Symbol(2));
4199- index.y.dtype = ge::DT_FLOAT16;4200+ index.y.dtype = ge::DT_INT64;
4200 4201 
4201 // graph.SetInputs({index});4202 // graph.SetInputs({index});
4202 4203 
@@ -4210,7 +4211,96 @@ TEST(CodegenKernel, Kernel_IndexExprTensorInit) {
4210 codegen::Kernel::ParseGraph(graph, fused_schedule_result, kernel);4211 codegen::Kernel::ParseGraph(graph, fused_schedule_result, kernel);
4211 std::string result;4212 std::string result;
4212 kernel.GlobalTensorInit(result);4213 kernel.GlobalTensorInit(result);
4213- EXPECT_EQ(result, std::string{"const half scalar_0 = (t->s0)/(1);\n"});4214+ EXPECT_EQ(result, std::string{"const int64_t scalar_0 = (((2 * t->s1) + t->s0))/(1);\n"});
4215+}
4216+ 
4217+TEST(CodegenKernel, Kernel_ZeroIndexExprTensorInit) {
4218+ af::AscGraph graph("test_graph");
4219+ af::ascir_op::IndexExpr index("index", graph);
4220+ index.ir_attr.SetExpr(af::Symbol(0));
4221+ index.y.dtype = ge::DT_INT32;
4222+ 
4223+ auto index_node = graph.FindNode("index");
4224+ index_node->outputs[0].attr.mem.alloc_type = af::AllocType::kAllocTypeInvalid;
4225+ index_node->outputs[0].attr.mem.tensor_id = 0;
4226+ index_node->outputs[0].attr.mem.position = af::Position::kPositionInvalid;
4227+ 
4228+ ::ascir::FusedScheduledResult fused_schedule_result;
4229+ codegen::Kernel kernel(graph.GetName());
4230+ ASSERT_EQ(codegen::Kernel::ParseGraph(graph, fused_schedule_result, kernel), af::SUCCESS);
4231+ std::string result;
4232+ ASSERT_EQ(kernel.GlobalTensorInit(result), af::SUCCESS);
4233+ EXPECT_EQ(result, std::string{"const int32_t scalar_0 = 0;\n"});
4234+ ASSERT_NE(kernel.tpipe.GetTensor(0), nullptr);
4235+ EXPECT_EQ(kernel.tpipe.GetTensor(0)->GetScalarValue(), "scalar_0");
4236+}
4237+ 
4238+TEST(CodegenKernel, Kernel_IndexExprScalarConsumerDoesNotAllocateVectorTensor) {
4239+ af::AscGraph graph("index_expr_scalar_consumer");
4240+ const auto s1 = graph.CreateSizeVar("s1");
4241+ const auto z0 = graph.CreateAxis("z0", s1);
4242+ af::ascir_op::Data data("data", graph);
4243+ data.ir_attr.SetIndex(0);
4244+ data.y.dtype = ge::DT_INT32;
4245+ af::ascir_op::Load load("load");
4246+ graph.AddNode(load);
4247+ load.x = data.y;
4248+ load.y.dtype = ge::DT_INT32;
4249+ af::ascir_op::IndexExpr index("index", graph);
4250+ index.ir_attr.SetExpr(s1 + af::Symbol(2));
4251+ index.y.dtype = ge::DT_INT32;
4252+ af::ascir_op::Add add("add");
4253+ graph.AddNode(add);
4254+ add.x1 = load.y;
4255+ add.x2 = index.y;
4256+ add.y.dtype = ge::DT_INT32;
4257+ 
4258+ auto data_node = graph.FindNode("data");
4259+ auto load_node = graph.FindNode("load");
4260+ auto index_node = graph.FindNode("index");
4261+ auto add_node = graph.FindNode("add");
4262+ data_node->outputs[0].attr.mem.tensor_id = 0;
4263+ data_node->outputs[0].attr.mem.alloc_type = af::AllocType::kAllocTypeGlobal;
4264+ load_node->outputs[0].attr.axis = {z0.id};
4265+ load_node->outputs[0].attr.vectorized_axis = {z0.id};
4266+ load_node->outputs[0].attr.vectorized_strides = {One};
4267+ load_node->outputs[0].attr.repeats = {s1};
4268+ load_node->outputs[0].attr.strides = {One};
4269+ load_node->outputs[0].attr.mem.tensor_id = 1;
4270+ load_node->outputs[0].attr.mem.alloc_type = af::AllocType::kAllocTypeBuffer;
4271+ load_node->outputs[0].attr.mem.position = af::Position::kPositionVecIn;
4272+ load_node->outputs[0].attr.buf.id = 0;
4273+ index_node->outputs[0].attr.mem.tensor_id = 2;
4274+ index_node->outputs[0].attr.mem.alloc_type = af::AllocType::kAllocTypeInvalid;
4275+ index_node->outputs[0].attr.mem.position = af::Position::kPositionInvalid;
4276+ add_node->outputs[0].attr.axis = {z0.id};
4277+ add_node->outputs[0].attr.vectorized_axis = {z0.id};
4278+ add_node->outputs[0].attr.vectorized_strides = {One};
4279+ add_node->outputs[0].attr.repeats = {s1};
4280+ add_node->outputs[0].attr.strides = {One};
4281+ add_node->outputs[0].attr.mem.tensor_id = 3;
4282+ add_node->outputs[0].attr.mem.alloc_type = af::AllocType::kAllocTypeBuffer;
4283+ add_node->outputs[0].attr.mem.position = af::Position::kPositionVecCalc;
4284+ add_node->outputs[0].attr.buf.id = 1;
4285+ 
4286+ ::ascir::FusedScheduledResult fused_schedule_result;
4287+ fused_schedule_result.input_nodes.push_back(data_node);
4288+ codegen::Kernel kernel(graph.GetName());
4289+ ASSERT_EQ(codegen::Kernel::ParseGraph(graph, fused_schedule_result, kernel), af::SUCCESS);
4290+ ASSERT_NE(kernel.tpipe.GetTensor(2), nullptr);
4291+ EXPECT_TRUE(kernel.tpipe.GetTensor(2)->is_constant);
4292+ 
4293+ std::string scalar_init;
4294+ ASSERT_EQ(kernel.GlobalTensorInit(scalar_init), af::SUCCESS);
4295+ EXPECT_NE(scalar_init.find("const int32_t scalar_2 = ((2 + t->s1))/(1);"), std::string::npos);
4296+ 
4297+ std::string local_tensors;
4298+ ASSERT_EQ(kernel.tpipe.LocalTensorDefine(local_tensors), af::SUCCESS);
4299+ EXPECT_EQ(local_tensors.find("LocalTensor<int32_t> local_2"), std::string::npos);
4300+ 
4301+ std::string consumer_code;
4302+ ASSERT_EQ(kernel.root_loop.Generate(kernel.tiler, kernel.tpipe, consumer_code), af::SUCCESS);
4303+ EXPECT_NE(consumer_code.find("Adds(local_3[0], local_1[0], (int32_t)scalar_2"), std::string::npos) << consumer_code;
4214}4304}
4215 4305 
4216TEST(CodegenKernel, Kernel_KernelFunctionDeclare) {4306TEST(CodegenKernel, Kernel_KernelFunctionDeclare) {
@@ -487,3 +487,63 @@ TEST(CodegenLoadStore, CalculateDmaParams_LastAxisDisContinuous) {
487 EXPECT_EQ(param.gm_strides.size(), 2);487 EXPECT_EQ(param.gm_strides.size(), 2);
488 EXPECT_EQ(param.ub_strides.size(), 2);488 EXPECT_EQ(param.ub_strides.size(), 2);
489}489}
490+ 
491+TEST(CodegenLoadStore, CalculateDmaParams_IgnoresStaticSingletonAxes) {
492+ af::Axis z0{.id = 0, .name = "z0", .type = af::Axis::Type::kAxisTypeOriginal, .size = af::Symbol(8)};
493+ af::Axis z1{.id = 1, .name = "z1", .type = af::Axis::Type::kAxisTypeOriginal, .size = One};
494+ af::Axis z2{.id = 2, .name = "z2", .type = af::Axis::Type::kAxisTypeOriginal, .size = One};
495+ codegen::Tiler tiler;
496+ tiler.AddAxis(z0);
497+ tiler.AddAxis(z1);
498+ tiler.AddAxis(z2);
499+ codegen::TPipe tpipe("tpipe", tiler);
500+ 
501+ af::AscGraph graph("static_singleton_dma");
502+ af::ascir_op::Data x("x", graph);
503+ auto tensor = graph.FindNode("x")->outputs[0];
504+ tensor.attr.axis = {z0.id, z1.id, z2.id};
505+ tensor.attr.vectorized_axis = tensor.attr.axis;
506+ tensor.attr.repeats = {z0.size, One, One};
507+ tensor.attr.strides = {af::Symbol(6), af::Symbol(3), One};
508+ tensor.attr.vectorized_strides = {One, Zero, Zero};
509+ 
510+ std::string dtype_name;
511+ Tensor::DtypeName(tensor.attr.dtype, dtype_name);
512+ Tensor gm_tensor(tensor, dtype_name);
513+ Tensor ub_tensor(tensor, dtype_name);
514+ gm_tensor.vectorized_axis_pos = {0, 1, 2};
515+ ub_tensor.vectorized_axis_pos = {0, 1, 2};
516+ DataCopyParams param;
517+ ASSERT_TRUE(CalculateDmaParams(tpipe, gm_tensor, ub_tensor, param, true));
518+ ASSERT_EQ(param.repeats.size(), 1UL);
519+ EXPECT_EQ(param.repeats[0], af::Symbol(8));
520+}
521+ 
522+TEST(CodegenLoadStore, CalculateDmaParams_KeepsDynamicAxes) {
523+ af::SizeVar dynamic_size(af::Symbol("dynamic_size"));
524+ af::Axis z0{.id = 0, .name = "z0", .type = af::Axis::Type::kAxisTypeOriginal, .size = af::Symbol(8)};
525+ af::Axis z1{.id = 1, .name = "z1", .type = af::Axis::Type::kAxisTypeOriginal, .size = dynamic_size.expr};
526+ codegen::Tiler tiler;
527+ tiler.AddAxis(z0);
528+ tiler.AddAxis(z1);
529+ tiler.AddSizeVar(dynamic_size);
530+ codegen::TPipe tpipe("tpipe", tiler);
531+ 
532+ af::AscGraph graph("dynamic_dma");
533+ af::ascir_op::Data x("x", graph);
534+ auto tensor = graph.FindNode("x")->outputs[0];
535+ tensor.attr.axis = {z0.id, z1.id};
536+ tensor.attr.vectorized_axis = tensor.attr.axis;
537+ tensor.attr.repeats = {z0.size, z1.size};
538+ tensor.attr.strides = {z1.size, One};
539+ tensor.attr.vectorized_strides = {z1.size, One};
540+ 
541+ std::string dtype_name;
542+ Tensor::DtypeName(tensor.attr.dtype, dtype_name);
543+ Tensor gm_tensor(tensor, dtype_name);
544+ Tensor ub_tensor(tensor, dtype_name);
545+ DataCopyParams param;
546+ ASSERT_TRUE(CalculateDmaParams(tpipe, gm_tensor, ub_tensor, param, true));
547+ ASSERT_EQ(param.repeats.size(), 1UL);
548+ EXPECT_EQ(param.repeats[0], af::Symbol(8) * dynamic_size.expr);
549+}
@@ -99,6 +99,44 @@ static ascir::FusedScheduledResult MakeFusedScheduledResultWithGraphs(std::vecto
99 return fused_result;99 return fused_result;
100}100}
101 101 
102+TEST_F(BufQueAllocatorUT, ArangeOutputUsesNormalUbBufferAllocation) {
103+ ge::PlatformContext::GetInstance().SetPlatform("3510");
104+ af::AscGraph graph("arange_ub_allocation");
105+ const auto size = graph.CreateSizeVar("size");
106+ const auto axis = graph.CreateAxis("axis", size);
107+ 
108+ af::ascir_op::Arange arange("arange");
109+ arange.ir_attr.SetBase(af::Symbol(0));
110+ arange.ir_attr.SetStep(af::Symbol(1));
111+ arange.attr.api.unit = af::ComputeUnit::kUnitVector;
112+ arange.attr.sched.axis = {axis.id};
113+ arange.y.dtype = af::DT_INT32;
114+ *arange.y.axis = {axis.id};
115+ *arange.y.repeats = {size};
116+ *arange.y.strides = {af::ops::One};
117+ 
118+ ASSERT_NE(graph.AddNode(arange), nullptr);
119+ ASSERT_EQ(BufQueAllocator().SetOutputTensorAttr(graph), af::SUCCESS);
120+ 
121+ const auto arange_node = graph.FindNode("arange");
122+ ASSERT_NE(arange_node, nullptr);
123+ EXPECT_EQ(arange_node->outputs[0].attr.mem.hardware, af::MemHardware::kMemHardwareUB);
124+ EXPECT_EQ(arange_node->outputs[0].attr.mem.alloc_type, af::AllocType::kAllocTypeBuffer);
125+ EXPECT_EQ(arange_node->outputs[0].attr.mem.position, af::Position::kPositionVecCalc);
126+}
127+ 
128+TEST_F(BufQueAllocatorUT, IndexExprKeepsScalarNoUbAllocation) {
129+ ge::PlatformContext::GetInstance().SetPlatform("3510");
130+ af::AscGraph graph("index_expr_no_ub");
131+ (void)af::ascir::cg::IndexExpr("index", graph, af::DT_INT32, {}, {}, {}, af::Symbol(3));
132+ ASSERT_EQ(BufQueAllocator().SetOutputTensorAttr(graph), af::SUCCESS);
133+ 
134+ const auto index_node = graph.FindNode("index");
135+ ASSERT_NE(index_node, nullptr);
136+ EXPECT_NE(index_node->outputs[0].attr.mem.hardware, af::MemHardware::kMemHardwareUB);
137+ EXPECT_NE(index_node->outputs[0].attr.mem.alloc_type, af::AllocType::kAllocTypeBuffer);
138+}
139+ 
102TEST_F(BufQueAllocatorUT, ShortenVecoutLifetimeInsertsUb2ubBeforeStore) {140TEST_F(BufQueAllocatorUT, ShortenVecoutLifetimeInsertsUb2ubBeforeStore) {
103 auto graph = MakeStaticLoadStoreGraph("shorten_vecout", 32);141 auto graph = MakeStaticLoadStoreGraph("shorten_vecout", 32);
104 ASSERT_EQ(ScheduleUtils::TopologicalSorting(graph), af::GRAPH_SUCCESS);142 ASSERT_EQ(ScheduleUtils::TopologicalSorting(graph), af::GRAPH_SUCCESS);
@@ -11,6 +11,7 @@
11#include "gtest/gtest.h"11#include "gtest/gtest.h"
12 12 
13#include <ascendc_ir.h>13#include <ascendc_ir.h>
14+#include <array>
14#include <map>15#include <map>
15#include "ascir.h"16#include "ascir.h"
16#include <ascir_ops.h>17#include <ascir_ops.h>
@@ -44,6 +45,8 @@
44#include "codegen.h"45#include "codegen.h"
45#include "optimize/graph_pass/pass_utils.h"46#include "optimize/graph_pass/pass_utils.h"
46#include "common/autofuse_backend_spec_api.h"47#include "common/autofuse_backend_spec_api.h"
48+#include "autoschedule/alignment_handler.h"
49+#include "../../v35/ut/optimize/runtime_stub.h"
47 50 
48using namespace af;51using namespace af;
49using namespace af::ops;52using namespace af::ops;
@@ -53,6 +56,243 @@ using ge::InputValueSumSourceStub;
53using ge::RuntimeStub;56using ge::RuntimeStub;
54 57 
55namespace {58namespace {
59+enum class ArangeConsumer {
60+ kAdd,
61+ kSub,
62+ kMul,
63+ kCompare,
64+ kFloorDiv,
65+ kMinimum,
66+ kMaximum,
67+};
68+ 
69+std::vector<std::string> GetNodeNames(const af::AscGraph &graph) {
70+ std::vector<std::string> names;
71+ for (const auto &node : graph.GetAllNodes()) {
72+ names.emplace_back(node->GetNamePtr());
73+ }
74+ return names;
75+}
76+ 
77+std::string GetLineContaining(const std::string &text, const std::string &needle) {
78+ const auto offset = text.find(needle);
79+ if (offset == std::string::npos) {
80+ return {};
81+ }
82+ const auto line_begin = text.rfind('\n', offset);
83+ const auto line_end = text.find('\n', offset);
84+ return text.substr(line_begin == std::string::npos ? 0UL : line_begin + 1UL,
85+ line_end == std::string::npos ? std::string::npos : line_end - line_begin - 1UL);
86+}
87+ 
88+af::AscGraph CreateArangeConsumerGraph(const std::string &name, ArangeConsumer consumer,
89+ const af::Expression &vector_stride = af::ops::One,
90+ af::DataType dtype = af::DT_INT32,
91+ const std::array<af::Expression, 4> &arange_params = {
92+ af::Symbol(0), af::Symbol(1), af::Symbol(1), af::Symbol(1)}) {
93+ af::AscGraph graph(name.c_str());
94+ const auto size = graph.CreateSizeVar("size");
95+ const auto axis = graph.CreateAxis("axis", size);
96+ 
97+ Arange arange("arange");
98+ arange.ir_attr.SetBase(arange_params[0]);
99+ arange.ir_attr.SetStep(arange_params[1]);
100+ arange.attr.sched.axis = {axis.id};
101+ arange.attr.api.unit = af::ComputeUnit::kUnitVector;
102+ arange.y.dtype = dtype;
103+ *arange.y.axis = {axis.id};
104+ *arange.y.repeats = {size};
105+ *arange.y.strides = {af::ops::One};
106+ *arange.y.vectorized_axis = {axis.id};
107+ *arange.y.vectorized_strides = {af::ops::One};
108+ (void)graph.AddNode(arange);
109+ 
110+ Arange other_arange("other_arange");
111+ other_arange.ir_attr.SetBase(arange_params[2]);
112+ other_arange.ir_attr.SetStep(arange_params[3]);
113+ other_arange.attr.sched.axis = {axis.id};
114+ other_arange.attr.api.unit = af::ComputeUnit::kUnitVector;
115+ other_arange.y.dtype = dtype;
116+ *other_arange.y.axis = {axis.id};
117+ *other_arange.y.repeats = {size};
118+ *other_arange.y.strides = {af::ops::One};
119+ *other_arange.y.vectorized_axis = {axis.id};
120+ *other_arange.y.vectorized_strides = {af::ops::One};
121+ (void)graph.AddNode(other_arange);
122+ 
123+ Store store("store");
124+ if (consumer == ArangeConsumer::kCompare) {
125+ Lt compare("compare");
126+ compare.x1 = arange.y;
127+ compare.x2 = other_arange.y;
128+ compare.attr.sched.axis = {axis.id};
129+ compare.attr.api.unit = af::ComputeUnit::kUnitVector;
130+ compare.y.dtype = af::DT_UINT8;
131+ *compare.y.axis = {axis.id};
132+ *compare.y.repeats = {size};
133+ *compare.y.strides = {vector_stride};
134+ *compare.y.vectorized_axis = {axis.id};
135+ *compare.y.vectorized_strides = {vector_stride};
136+ store.x = compare.y;
137+ } else if (consumer == ArangeConsumer::kFloorDiv) {
138+ FloorDiv floor_div("floor_div");
139+ floor_div.x1 = arange.y;
140+ floor_div.x2 = other_arange.y;
141+ floor_div.attr.sched.axis = {axis.id};
142+ floor_div.attr.api.unit = af::ComputeUnit::kUnitVector;
143+ floor_div.y.dtype = dtype;
144+ *floor_div.y.axis = {axis.id};
145+ *floor_div.y.repeats = {size};
146+ *floor_div.y.strides = {af::ops::One};
147+ *floor_div.y.vectorized_axis = {axis.id};
148+ *floor_div.y.vectorized_strides = {af::ops::One};
149+ store.x = floor_div.y;
150+ } else {
151+ auto configure = [&](auto &op) {
152+ op.attr.sched.axis = {axis.id};
153+ op.attr.api.unit = af::ComputeUnit::kUnitVector;
154+ op.y.dtype = dtype;
155+ *op.y.axis = {axis.id};
156+ *op.y.repeats = {size};
157+ *op.y.strides = {af::ops::One};
158+ *op.y.vectorized_axis = {axis.id};
159+ *op.y.vectorized_strides = {af::ops::One};
160+ store.x = op.y;
161+ };
162+ if (consumer == ArangeConsumer::kAdd) {
163+ Add op("add");
164+ op.x1 = arange.y;
165+ op.x2 = other_arange.y;
166+ configure(op);
167+ } else if (consumer == ArangeConsumer::kSub) {
168+ Sub op("sub");
169+ op.x1 = arange.y;
170+ op.x2 = other_arange.y;
171+ configure(op);
172+ } else if (consumer == ArangeConsumer::kMul) {
173+ Mul op("mul");
174+ op.x1 = arange.y;
175+ op.x2 = other_arange.y;
176+ configure(op);
177+ } else if (consumer == ArangeConsumer::kMinimum) {
178+ Minimum op("minimum");
179+ op.x1 = arange.y;
180+ op.x2 = other_arange.y;
181+ configure(op);
182+ } else if (consumer == ArangeConsumer::kMaximum) {
183+ Maximum op("maximum");
184+ op.x1 = arange.y;
185+ op.x2 = other_arange.y;
186+ configure(op);
187+ }
188+ }
189+ 
190+ store.attr.sched.axis = {axis.id};
191+ store.attr.api.unit = af::ComputeUnit::kUnitMTE3;
192+ store.y.dtype = consumer == ArangeConsumer::kCompare ? af::DT_UINT8 : dtype;
193+ *store.y.axis = {axis.id};
194+ *store.y.repeats = {size};
195+ *store.y.strides = {af::ops::One};
196+ *store.y.vectorized_axis = {axis.id};
197+ *store.y.vectorized_strides = {af::ops::One};
198+ 
199+ Output output("output");
200+ output.x = store.y;
201+ output.ir_attr.SetIndex(0);
202+ return graph;
203+}
204+ 
205+af::AscGraph CreateWindowMathGraph(const std::string &name) {
206+ af::AscGraph graph(name.c_str());
207+ const auto size = graph.CreateSizeVar("size");
208+ const auto axis = graph.CreateAxis("axis", size);
209+ auto set_tensor = [&](auto &tensor) {
210+ tensor.dtype = af::DT_INT64;
211+ *tensor.axis = {axis.id};
212+ *tensor.repeats = {size};
213+ *tensor.strides = {af::ops::One};
214+ *tensor.vectorized_axis = {axis.id};
215+ *tensor.vectorized_strides = {af::ops::One};
216+ };
217+ 
218+ Arange arange("arange");
219+ arange.ir_attr.SetBase(af::Symbol(-17));
220+ arange.ir_attr.SetStep(af::Symbol(1));
221+ arange.attr.sched.axis = {axis.id};
222+ arange.attr.api.unit = af::ComputeUnit::kUnitVector;
223+ set_tensor(arange.y);
224+ (void)graph.AddNode(arange);
225+ Arange divisor("divisor");
226+ divisor.ir_attr.SetBase(af::Symbol(3));
227+ divisor.ir_attr.SetStep(af::Symbol(0));
228+ divisor.attr.sched.axis = {axis.id};
229+ divisor.attr.api.unit = af::ComputeUnit::kUnitVector;
230+ set_tensor(divisor.y);
231+ (void)graph.AddNode(divisor);
232+ FloorDiv floor_div("floor_div");
233+ floor_div.x1 = arange.y;
234+ floor_div.x2 = divisor.y;
235+ floor_div.attr.sched.axis = {axis.id};
236+ floor_div.attr.api.unit = af::ComputeUnit::kUnitVector;
237+ set_tensor(floor_div.y);
238+ Minimum minimum("minimum");
239+ minimum.x1 = floor_div.y;
240+ minimum.x2 = divisor.y;
241+ minimum.attr.sched.axis = {axis.id};
242+ minimum.attr.api.unit = af::ComputeUnit::kUnitVector;
243+ set_tensor(minimum.y);
244+ Maximum maximum("maximum");
245+ maximum.x1 = minimum.y;
246+ maximum.x2 = arange.y;
247+ maximum.attr.sched.axis = {axis.id};
248+ maximum.attr.api.unit = af::ComputeUnit::kUnitVector;
249+ set_tensor(maximum.y);
250+ Store store("store");
251+ store.x = maximum.y;
252+ store.attr.sched.axis = {axis.id};
253+ store.attr.api.unit = af::ComputeUnit::kUnitMTE3;
254+ set_tensor(store.y);
255+ Output output("output");
256+ output.x = store.y;
257+ output.ir_attr.SetIndex(0);
258+ return graph;
259+}
260+ 
261+af::AscGraph CreateArangeStoreGraph(const std::string &name) {
262+ af::AscGraph graph(name.c_str());
263+ const auto size = graph.CreateSizeVar("size");
264+ const auto axis = graph.CreateAxis("axis", size);
265+ 
266+ Arange arange("arange");
267+ arange.ir_attr.SetBase(af::Symbol(0));
268+ arange.ir_attr.SetStep(af::Symbol(1));
269+ arange.attr.sched.axis = {axis.id};
270+ arange.attr.api.unit = af::ComputeUnit::kUnitVector;
271+ arange.y.dtype = af::DT_INT32;
272+ *arange.y.axis = {axis.id};
273+ *arange.y.repeats = {size};
274+ *arange.y.strides = {af::ops::One};
275+ *arange.y.vectorized_axis = {axis.id};
276+ *arange.y.vectorized_strides = {af::ops::One};
277+ (void)graph.AddNode(arange);
278+ 
279+ Store store("store");
280+ store.x = arange.y;
281+ store.attr.sched.axis = {axis.id};
282+ store.attr.api.unit = af::ComputeUnit::kUnitMTE3;
283+ store.y.dtype = af::DT_INT32;
284+ *store.y.axis = {axis.id};
285+ *store.y.repeats = {size};
286+ *store.y.strides = {af::ops::One};
287+ *store.y.vectorized_axis = {axis.id};
288+ *store.y.vectorized_strides = {af::ops::One};
289+ 
290+ Output output("output");
291+ output.x = store.y;
292+ output.ir_attr.SetIndex(0);
293+ return graph;
294+}
295+ 
56class GraphBuilder {296class GraphBuilder {
57 public:297 public:
58 explicit GraphBuilder(const std::string &name) {298 explicit GraphBuilder(const std::string &name) {
@@ -114,6 +354,10 @@ class TestOptimizer : public ::testing::Test {
114 354 
115 TestOptimizer() : optimizer(optimize::OptimizerOptions{}) {}355 TestOptimizer() : optimizer(optimize::OptimizerOptions{}) {}
116 356 
357+ static void SetUpV2RuntimeStub() {
358+ RuntimeStub::SetInstance(std::make_shared<af::RuntimeStubV2>());
359+ }
360+ 
117 static std::stringstream &SizeExprListStr(std::stringstream &ss, const af::AscGraph &graph,361 static std::stringstream &SizeExprListStr(std::stringstream &ss, const af::AscGraph &graph,
118 const std::vector<af::Expression> &size_expr_list) {362 const std::vector<af::Expression> &size_expr_list) {
119 for (auto &size_expr : size_expr_list) {363 for (auto &size_expr : size_expr_list) {
@@ -202,6 +446,447 @@ TEST_F(TestOptimizer, TwoWorkspace) {
202 EXPECT_EQ(workspace2->outputs[0].attr.mem.tensor_id, store2->outputs[0].attr.mem.tensor_id);446 EXPECT_EQ(workspace2->outputs[0].attr.mem.tensor_id, store2->outputs[0].attr.mem.tensor_id);
203}447}
204 448 
449+TEST_F(TestOptimizer, V2ArangeCompareOptimizeProducesDeterministicVectorFunc) {
450+ SetUpV2RuntimeStub();
451+ ge::PlatformContext::GetInstance().SetPlatform("3510");
452+ std::vector<std::string> expected_root_order;
453+ std::vector<std::string> expected_vf_order;
454+ for (size_t run = 0UL; run < 3UL; ++run) {
455+ auto graph = CreateArangeConsumerGraph("arange_compare_vf", ArangeConsumer::kCompare);
456+ ::ascir::FusedScheduledResult result;
457+ ASSERT_EQ(optimizer.Optimize(graph, result), af::SUCCESS);
458+ ASSERT_EQ(result.node_idx_to_scheduled_results.size(), 1UL);
459+ ASSERT_EQ(result.node_idx_to_scheduled_results[0].size(), 1UL);
460+ ASSERT_EQ(result.node_idx_to_scheduled_results[0][0].schedule_groups.size(), 1UL);
461+ ASSERT_EQ(result.node_idx_to_scheduled_results[0][0].schedule_groups[0].impl_graphs.size(), 1UL);
462+ const auto &impl_graph = result.node_idx_to_scheduled_results[0][0].schedule_groups[0].impl_graphs[0];
463+ std::vector<AscGraph> subgraphs;
464+ ASSERT_EQ(impl_graph.GetAllSubGraphs(subgraphs), af::SUCCESS);
465+ ASSERT_EQ(subgraphs.size(), 1UL);
466+ const auto root_order = GetNodeNames(impl_graph);
467+ const auto vf_order = GetNodeNames(subgraphs[0]);
468+ EXPECT_EQ(root_order, (std::vector<std::string>{"arange_compare_vf_0_B0Y0_S0G0C0_VfNode_0", "store", "output"}));
469+ EXPECT_EQ(vf_order,
470+ (std::vector<std::string>{"arange", "other_arange", "compare", "Store_compare0", "Output_compare0"}));
471+ if (run == 0UL) {
472+ expected_root_order = root_order;
473+ expected_vf_order = vf_order;
474+ } else {
475+ EXPECT_EQ(root_order, expected_root_order);
476+ EXPECT_EQ(vf_order, expected_vf_order);
477+ }
478+ }
479+}
480+ 
481+TEST_F(TestOptimizer, V2ArangeStoreOptimizeProducesSingletonVectorFunc) {
482+ SetUpV2RuntimeStub();
483+ ge::PlatformContext::GetInstance().SetPlatform("3510");
484+ auto graph = CreateArangeStoreGraph("arange_store_vf");
485+ ::ascir::FusedScheduledResult result;
486+ 
487+ ASSERT_EQ(optimizer.Optimize(graph, result), af::SUCCESS);
488+ ASSERT_EQ(result.node_idx_to_scheduled_results.size(), 1UL);
489+ ASSERT_EQ(result.node_idx_to_scheduled_results[0].size(), 1UL);
490+ ASSERT_EQ(result.node_idx_to_scheduled_results[0][0].schedule_groups.size(), 1UL);
491+ ASSERT_EQ(result.node_idx_to_scheduled_results[0][0].schedule_groups[0].impl_graphs.size(), 1UL);
492+ 
493+ const auto &impl_graph = result.node_idx_to_scheduled_results[0][0].schedule_groups[0].impl_graphs[0];
494+ std::vector<AscGraph> subgraphs;
495+ ASSERT_EQ(impl_graph.GetAllSubGraphs(subgraphs), af::SUCCESS);
496+ ASSERT_EQ(subgraphs.size(), 1UL);
497+ EXPECT_EQ(GetNodeNames(impl_graph),
498+ (std::vector<std::string>{"arange_store_vf_0_B0Y0_S0G0C0_VfNode_0", "store", "output"}));
499+ EXPECT_EQ(GetNodeNames(subgraphs[0]), (std::vector<std::string>{"arange", "Store_arange0", "Output_arange0"}));
500+}
501+ 
502+TEST_F(TestOptimizer, V2HighRankArangeOptimizeAndCodegenOuterFor) {
503+ SetUpV2RuntimeStub();
504+ ge::PlatformContext::GetInstance().SetPlatform("3510");
505+ af::AscGraph graph("high_rank_arange_pipeline");
506+ std::vector<af::Expression> sizes;
507+ std::vector<af::AxisId> axes;
508+ for (size_t i = 0UL; i < 5UL; ++i) {
509+ sizes.push_back(graph.CreateSizeVar("size" + std::to_string(i)));
510+ axes.push_back(graph.CreateAxis("axis" + std::to_string(i), sizes.back()).id);
511+ }
512+ const std::vector<af::Expression> strides = {sizes[1] * sizes[2] * sizes[3] * sizes[4] * af::Symbol(5),
513+ sizes[2] * sizes[3] * sizes[4] * af::Symbol(4),
514+ sizes[3] * sizes[4] * af::Symbol(3), sizes[4] * af::Symbol(2), One};
515+ 
516+ Arange arange("arange");
517+ arange.ir_attr.SetBase(af::Symbol(3));
518+ arange.ir_attr.SetStep(af::Symbol(2));
519+ arange.attr.sched.axis = axes;
520+ arange.attr.api.unit = af::ComputeUnit::kUnitVector;
521+ arange.y.dtype = af::DT_INT64;
522+ *arange.y.axis = axes;
523+ *arange.y.repeats = sizes;
524+ *arange.y.strides = strides;
525+ ASSERT_NE(graph.AddNode(arange), nullptr);
526+ 
527+ Abs abs("abs");
528+ abs.x = arange.y;
529+ abs.attr.sched.axis = axes;
530+ abs.attr.api.unit = af::ComputeUnit::kUnitVector;
531+ abs.y.dtype = af::DT_INT64;
532+ *abs.y.axis = axes;
533+ *abs.y.repeats = sizes;
534+ *abs.y.strides = strides;
535+ 
536+ Store store("store");
537+ store.x = abs.y;
538+ store.attr.sched.axis = axes;
539+ store.attr.api.unit = af::ComputeUnit::kUnitMTE3;
540+ store.y.dtype = af::DT_INT64;
541+ *store.y.axis = axes;
542+ *store.y.repeats = sizes;
543+ *store.y.strides = strides;
544+ 
545+ Output output("output");
546+ output.x = store.y;
547+ output.ir_attr.SetIndex(0);
548+ 
549+ ::ascir::FusedScheduledResult scheduled;
550+ ASSERT_EQ(optimizer.Optimize(graph, scheduled), af::SUCCESS);
551+ ASSERT_EQ(scheduled.node_idx_to_scheduled_results.size(), 1UL);
552+ ASSERT_EQ(scheduled.node_idx_to_scheduled_results[0].size(), 1UL);
553+ ASSERT_EQ(scheduled.node_idx_to_scheduled_results[0][0].schedule_groups.size(), 1UL);
554+ ASSERT_FALSE(scheduled.node_idx_to_scheduled_results[0][0].schedule_groups[0].impl_graphs.empty());
555+ 
556+ codegen::CodegenResult generated;
557+ const std::map<std::string, std::string> shape_info = {
558+ {"size0", "2"}, {"size1", "2"}, {"size2", "2"}, {"size3", "2"}, {"size4", "65"}};
559+ ASSERT_EQ(codegen::Codegen(codegen::CodegenOptions{}).Generate(shape_info, scheduled, generated), af::SUCCESS);
560+ EXPECT_NE(generated.kernel.find("outer_for_0"), std::string::npos) << generated.kernel;
561+ EXPECT_NE(generated.kernel.find("arange_offset_"), std::string::npos) << generated.kernel;
562+}
563+ 
564+TEST_F(TestOptimizer, V2ArangeFloorDivOptimizeProducesDeterministicKernel) {
565+ SetUpV2RuntimeStub();
566+ ge::PlatformContext::GetInstance().SetPlatform("3510");
567+ std::vector<std::string> expected_root_order;
568+ for (size_t run = 0UL; run < 3UL; ++run) {
569+ auto graph = CreateArangeConsumerGraph("arange_floor_div_vf", ArangeConsumer::kFloorDiv);
570+ ::ascir::FusedScheduledResult result;
571+ ASSERT_EQ(optimizer.Optimize(graph, result), af::SUCCESS);
572+ ASSERT_EQ(result.node_idx_to_scheduled_results.size(), 1UL);
573+ ASSERT_EQ(result.node_idx_to_scheduled_results[0].size(), 1UL);
574+ ASSERT_EQ(result.node_idx_to_scheduled_results[0][0].schedule_groups.size(), 1UL);
575+ ASSERT_EQ(result.node_idx_to_scheduled_results[0][0].schedule_groups[0].impl_graphs.size(), 1UL);
576+ const auto &impl_graph = result.node_idx_to_scheduled_results[0][0].schedule_groups[0].impl_graphs[0];
577+ std::vector<AscGraph> subgraphs;
578+ ASSERT_EQ(impl_graph.GetAllSubGraphs(subgraphs), af::SUCCESS);
579+ ASSERT_EQ(subgraphs.size(), 2UL);
580+ const auto root_order = GetNodeNames(impl_graph);
581+ EXPECT_EQ(root_order,
582+ (std::vector<std::string>{"arange_floor_div_vf_0_B0Y0_S0G0C0_VfNode_0",
583+ "arange_floor_div_vf_0_B0Y0_S0G0C0_VfNode_1", "floor_div", "store", "output"}));
584+ if (run == 0UL) {
585+ expected_root_order = root_order;
586+ } else {
587+ EXPECT_EQ(root_order, expected_root_order);
588+ }
589+ }
590+}
591+ 
592+TEST_F(TestOptimizer, V2Int64ArangeFloorDivKeepsInt64Composition) {
593+ SetUpV2RuntimeStub();
594+ ge::PlatformContext::GetInstance().SetPlatform("3510");
595+ auto graph =
596+ CreateArangeConsumerGraph("arange_int64_floor_div", ArangeConsumer::kFloorDiv, af::ops::One, af::DT_INT64);
597+ ::ascir::FusedScheduledResult result;
598+ ASSERT_EQ(optimizer.Optimize(graph, result), af::SUCCESS);
599+ ASSERT_EQ(result.node_idx_to_scheduled_results.size(), 1UL);
600+ ASSERT_EQ(result.node_idx_to_scheduled_results[0].size(), 1UL);
601+ ASSERT_EQ(result.node_idx_to_scheduled_results[0][0].schedule_groups.size(), 1UL);
602+ ASSERT_EQ(result.node_idx_to_scheduled_results[0][0].schedule_groups[0].impl_graphs.size(), 1UL);
603+ const auto &impl_graph = result.node_idx_to_scheduled_results[0][0].schedule_groups[0].impl_graphs[0];
604+ std::vector<AscGraph> subgraphs;
605+ ASSERT_EQ(impl_graph.GetAllSubGraphs(subgraphs), af::SUCCESS);
606+ ASSERT_EQ(subgraphs.size(), 2UL);
607+ EXPECT_EQ(GetNodeNames(impl_graph), (std::vector<std::string>{"arange_int64_floor_div_0_B0Y0_S0G0C0_VfNode_0",
608+ "arange_int64_floor_div_0_B0Y0_S0G0C0_VfNode_1",
609+ "floor_div", "store", "output"}));
610+ EXPECT_EQ(subgraphs[0].FindNode("arange")->outputs[0].attr.dtype, af::DT_INT64);
611+ EXPECT_EQ(subgraphs[1].FindNode("other_arange")->outputs[0].attr.dtype, af::DT_INT64);
612+ EXPECT_EQ(impl_graph.FindNode("floor_div")->outputs[0].attr.dtype, af::DT_INT64);
613+}
614+ 
615+TEST_F(TestOptimizer, V2WindowMathOptimizeProducesOneDeterministicKernel) {
616+ SetUpV2RuntimeStub();
617+ ge::PlatformContext::GetInstance().SetPlatform("3510");
618+ std::vector<std::string> expected_root_order;
619+ std::vector<std::vector<std::string>> expected_vf_orders;
620+ for (size_t run = 0UL; run < 3UL; ++run) {
621+ auto graph = CreateWindowMathGraph("window_math");
622+ ::ascir::FusedScheduledResult result;
623+ ASSERT_EQ(optimizer.Optimize(graph, result), af::SUCCESS);
624+ ASSERT_EQ(result.node_idx_to_scheduled_results.size(), 1UL);
625+ ASSERT_EQ(result.node_idx_to_scheduled_results[0].size(), 1UL);
626+ ASSERT_EQ(result.node_idx_to_scheduled_results[0][0].schedule_groups.size(), 1UL);
627+ ASSERT_EQ(result.node_idx_to_scheduled_results[0][0].schedule_groups[0].impl_graphs.size(), 1UL);
628+ const auto &impl_graph = result.node_idx_to_scheduled_results[0][0].schedule_groups[0].impl_graphs[0];
629+ std::vector<AscGraph> subgraphs;
630+ ASSERT_EQ(impl_graph.GetAllSubGraphs(subgraphs), af::SUCCESS);
631+ ASSERT_EQ(subgraphs.size(), 3UL);
632+ const auto root_order = GetNodeNames(impl_graph);
633+ std::vector<std::vector<std::string>> vf_orders;
634+ for (const auto &subgraph : subgraphs) {
635+ vf_orders.push_back(GetNodeNames(subgraph));
636+ }
637+ if (run == 0UL) {
638+ expected_root_order = root_order;
639+ expected_vf_orders = vf_orders;
640+ } else {
641+ EXPECT_EQ(root_order, expected_root_order);
642+ EXPECT_EQ(vf_orders, expected_vf_orders);
643+ }
644+ EXPECT_EQ(root_order,
645+ (std::vector<std::string>{"window_math_0_B0Y0_S0G0C0_VfNode_0", "window_math_0_B0Y0_S0G0C0_VfNode_1",
646+ "floor_div", "window_math_0_B0Y0_S0G0C0_VfNode_2", "store", "output"}));
647+ EXPECT_EQ(vf_orders[0], (std::vector<std::string>{"arange", "Store_arange0", "Output_arange0"}));
648+ EXPECT_EQ(vf_orders[1], (std::vector<std::string>{"divisor", "Store_divisor0", "Output_divisor0"}));
649+ EXPECT_EQ(vf_orders[2], (std::vector<std::string>{"Data_floor_div0", "Data_window_math_0_B0Y0_S0G0C0_VfNode_01",
650+ "Data_window_math_0_B0Y0_S0G0C0_VfNode_12", "Load_floor_div0",
651+ "Load_window_math_0_B0Y0_S0G0C0_VfNode_01",
652+ "Load_window_math_0_B0Y0_S0G0C0_VfNode_12", "minimum", "maximum",
653+ "Store_maximum0", "Output_maximum0"}));
654+ }
655+}
656+ 
657+TEST_F(TestOptimizer, V2MultiArangeOptimizeKeepsOrderEdgesAndParamsIsolated) {
658+ SetUpV2RuntimeStub();
659+ ge::PlatformContext::GetInstance().SetPlatform("3510");
660+ const std::vector<std::string> root_golden = {"multi_arange_0_B0Y0_S0G0C0_VfNode_0", "store", "output"};
661+ const std::vector<std::string> edge_golden = {"arange:0->add:0", "other_arange:0->add:1"};
662+ std::vector<std::string> expected_root_order;
663+ std::vector<std::string> expected_vf_order;
664+ std::vector<std::string> expected_add_edges;
665+ for (size_t run = 0UL; run < 3UL; ++run) {
666+ auto graph = CreateArangeConsumerGraph("multi_arange", ArangeConsumer::kAdd, af::ops::One, af::DT_INT64,
667+ {af::Symbol(1), af::Symbol(2), af::Symbol(7), af::Symbol(4)});
668+ 
669+ ::ascir::FusedScheduledResult result;
670+ ASSERT_EQ(optimizer.Optimize(graph, result), af::SUCCESS);
671+ const auto &impl_graph = result.node_idx_to_scheduled_results.at(0).at(0).schedule_groups.at(0).impl_graphs.at(0);
672+ std::vector<AscGraph> subgraphs;
673+ ASSERT_EQ(impl_graph.GetAllSubGraphs(subgraphs), af::SUCCESS);
674+ ASSERT_EQ(subgraphs.size(), 1UL);
675+ const auto root_order = GetNodeNames(impl_graph);
676+ const auto vf_order = GetNodeNames(subgraphs[0]);
677+ EXPECT_EQ(root_order, root_golden);
678+ EXPECT_EQ(vf_order, (std::vector<std::string>{"arange", "other_arange", "add", "Store_add0", "Output_add0"}));
679+ auto vf_arange = subgraphs[0].FindNode("arange");
680+ auto vf_other_arange = subgraphs[0].FindNode("other_arange");
681+ auto vf_add = subgraphs[0].FindNode("add");
682+ ASSERT_NE(vf_arange, nullptr);
683+ ASSERT_NE(vf_other_arange, nullptr);
684+ ASSERT_NE(vf_add, nullptr);
685+ af::Expression base;
686+ af::Expression step;
687+ ASSERT_EQ(vf_arange->attr.ir_attr->GetAttrValue("base", base), af::GRAPH_SUCCESS);
688+ ASSERT_EQ(vf_arange->attr.ir_attr->GetAttrValue("step", step), af::GRAPH_SUCCESS);
689+ EXPECT_EQ(base, af::Symbol(1));
690+ EXPECT_EQ(step, af::Symbol(2));
691+ ASSERT_EQ(vf_other_arange->attr.ir_attr->GetAttrValue("base", base), af::GRAPH_SUCCESS);
692+ ASSERT_EQ(vf_other_arange->attr.ir_attr->GetAttrValue("step", step), af::GRAPH_SUCCESS);
693+ EXPECT_EQ(base, af::Symbol(7));
694+ EXPECT_EQ(step, af::Symbol(4));
695+ ASSERT_EQ(vf_add->GetAllInDataAnchorsSize(), 2UL);
696+ ASSERT_NE(vf_add->GetInDataAnchor(0)->GetPeerOutAnchor(), nullptr);
697+ ASSERT_NE(vf_add->GetInDataAnchor(1)->GetPeerOutAnchor(), nullptr);
698+ EXPECT_EQ(vf_add->GetInDataAnchor(0)->GetPeerOutAnchor()->GetOwnerNode()->GetName(), "arange");
699+ EXPECT_EQ(vf_add->GetInDataAnchor(1)->GetPeerOutAnchor()->GetOwnerNode()->GetName(), "other_arange");
700+ const std::vector<std::string> add_edges = {
701+ vf_add->GetInDataAnchor(0)->GetPeerOutAnchor()->GetOwnerNode()->GetName() + ":" +
702+ std::to_string(vf_add->GetInDataAnchor(0)->GetPeerOutAnchor()->GetIdx()) +
703+ "->add:" + std::to_string(vf_add->GetInDataAnchor(0)->GetIdx()),
704+ vf_add->GetInDataAnchor(1)->GetPeerOutAnchor()->GetOwnerNode()->GetName() + ":" +
705+ std::to_string(vf_add->GetInDataAnchor(1)->GetPeerOutAnchor()->GetIdx()) +
706+ "->add:" + std::to_string(vf_add->GetInDataAnchor(1)->GetIdx())};
707+ EXPECT_EQ(add_edges, edge_golden);
708+ if (run == 0UL) {
709+ expected_root_order = root_order;
710+ expected_vf_order = vf_order;
711+ expected_add_edges = add_edges;
712+ } else {
713+ EXPECT_EQ(root_order, expected_root_order);
714+ EXPECT_EQ(vf_order, expected_vf_order);
715+ EXPECT_EQ(add_edges, expected_add_edges);
716+ }
717+ }
718+}
719+ 
720+struct Int64ConsumerCodegenCase {
721+ ArangeConsumer consumer;
722+ const char *consumer_name;
723+ const char *expected_api;
724+ const char *expected_call;
725+};
726+ 
727+class Int64ArangeConsumerCodegenTest : public TestOptimizer,
728+ public ::testing::WithParamInterface<Int64ConsumerCodegenCase> {};
729+ 
730+TEST_P(Int64ArangeConsumerCodegenTest, GeneratesInt64ConsumerWithoutNarrowing) {
731+ SetUpV2RuntimeStub();
732+ ge::PlatformContext::GetInstance().SetPlatform("3510");
733+ const auto &test_case = GetParam();
734+ auto graph = CreateArangeConsumerGraph("int64_consumer", test_case.consumer, af::ops::One, af::DT_INT64);
735+ ::ascir::FusedScheduledResult scheduled;
736+ ASSERT_EQ(optimizer.Optimize(graph, scheduled), af::SUCCESS);
737+ const auto &impl_graph = scheduled.node_idx_to_scheduled_results.at(0).at(0).schedule_groups.at(0).impl_graphs.at(0);
738+ std::vector<AscGraph> subgraphs;
739+ ASSERT_EQ(impl_graph.GetAllSubGraphs(subgraphs), af::SUCCESS);
740+ ASSERT_EQ(subgraphs.size(), 1UL);
741+ const auto consumer = subgraphs[0].FindNode(test_case.consumer_name);
742+ ASSERT_NE(consumer, nullptr);
743+ ASSERT_EQ(consumer->inputs.Size(), 2UL);
744+ ASSERT_EQ(consumer->outputs().size(), 1UL);
745+ EXPECT_EQ(consumer->inputs[0].attr.dtype, af::DT_INT64);
746+ EXPECT_EQ(consumer->inputs[1].attr.dtype, af::DT_INT64);
747+ EXPECT_EQ(consumer->outputs[0].attr.dtype, af::DT_INT64);
748+ codegen::CodegenResult generated;
749+ ASSERT_EQ(codegen::Codegen(codegen::CodegenOptions{}).Generate({{"size", "stub_size"}}, scheduled, generated),
750+ af::SUCCESS);
751+ const auto consumer_call = GetLineContaining(generated.kernel, test_case.expected_api);
752+ ASSERT_FALSE(consumer_call.empty());
753+ EXPECT_EQ(consumer_call, test_case.expected_call);
754+ if (test_case.consumer == ArangeConsumer::kAdd) {
755+ const auto vf_signature = GetLineContaining(generated.kernel, "int64_t arange_offset_0");
756+ ASSERT_FALSE(vf_signature.empty());
757+ EXPECT_EQ(vf_signature,
758+ "inline __simd_vf__ void VFCallint64_consumer_0_B0Y0_S0G0C0_VfNode_0(__local_mem__ int64_t "
759+ "*local_1_addr, uint32_t output_dims_0, int64_t arange_offset_0, int64_t arange_base_0, int64_t "
760+ "arange_step_0, int64_t arange_offset_1, int64_t arange_base_1, int64_t arange_step_1)");
761+ }
762+}
763+ 
764+INSTANTIATE_TEST_SUITE_P(
765+ Int64Consumers, Int64ArangeConsumerCodegenTest,
766+ ::testing::Values(Int64ConsumerCodegenCase{ArangeConsumer::kAdd, "add", "AscendC::MicroAPI::Add",
767+ " AscendC::MicroAPI::Add(vreg_2, vreg_0, vreg_1, preg_0);"},
768+ Int64ConsumerCodegenCase{ArangeConsumer::kSub, "sub", "AscendC::MicroAPI::Sub",
769+ " AscendC::MicroAPI::Sub(vreg_2, vreg_0, vreg_1, preg_0);"},
770+ Int64ConsumerCodegenCase{ArangeConsumer::kMul, "mul", "AscendC::MicroAPI::Mul",
771+ " AscendC::MicroAPI::Mul(vreg_2, vreg_0, vreg_1, preg_0);"},
772+ Int64ConsumerCodegenCase{ArangeConsumer::kMinimum, "minimum", "AscendC::MicroAPI::Min",
773+ " AscendC::MicroAPI::Min(vreg_2, vreg_0, vreg_1, preg_0);"},
774+ Int64ConsumerCodegenCase{ArangeConsumer::kMaximum, "maximum", "AscendC::MicroAPI::Max",
775+ " AscendC::MicroAPI::Max(vreg_2, vreg_0, vreg_1, preg_0);"}));
776+ 
777+TEST_F(TestOptimizer, V2ScalarVectorFuncPreservesRootScalarThroughCodegen) {
778+ SetUpV2RuntimeStub();
779+ ge::PlatformContext::GetInstance().SetPlatform("3510");
780+ af::AscGraph graph("scalar_vf_regression");
781+ const auto size = graph.CreateSizeVar("size");
782+ const auto axis = graph.CreateAxis("axis", size);
783+ 
784+ Data data("data", graph);
785+ data.ir_attr.SetIndex(0);
786+ data.y.dtype = af::DT_FLOAT;
787+ 
788+ Load load("load");
789+ load.x = data.y;
790+ load.attr.sched.axis = {axis.id};
791+ load.attr.api.unit = af::ComputeUnit::kUnitMTE2;
792+ load.y.dtype = af::DT_FLOAT;
793+ *load.y.axis = {axis.id};
794+ *load.y.repeats = {size};
795+ *load.y.strides = {One};
796+ *load.y.vectorized_axis = {axis.id};
797+ *load.y.vectorized_strides = {One};
798+ 
799+ Scalar scalar("scalar", graph);
800+ scalar.ir_attr.SetValue("2.0");
801+ scalar.y.dtype = af::DT_FLOAT;
802+ 
803+ Add add("add");
804+ add.x1 = load.y;
805+ add.x2 = scalar.y;
806+ add.attr.sched.axis = {axis.id};
807+ add.attr.api.unit = af::ComputeUnit::kUnitVector;
808+ add.y.dtype = af::DT_FLOAT;
809+ *add.y.axis = {axis.id};
810+ *add.y.repeats = {size};
811+ *add.y.strides = {One};
812+ *add.y.vectorized_axis = {axis.id};
813+ *add.y.vectorized_strides = {One};
814+ 
815+ Abs abs("abs");
816+ abs.x = add.y;
817+ abs.attr.sched.axis = {axis.id};
818+ abs.attr.api.unit = af::ComputeUnit::kUnitVector;
819+ abs.y.dtype = af::DT_FLOAT;
820+ *abs.y.axis = {axis.id};
821+ *abs.y.repeats = {size};
822+ *abs.y.strides = {One};
823+ *abs.y.vectorized_axis = {axis.id};
824+ *abs.y.vectorized_strides = {One};
825+ 
826+ Store store("store");
827+ store.x = abs.y;
828+ store.attr.sched.axis = {axis.id};
829+ store.attr.api.unit = af::ComputeUnit::kUnitMTE3;
830+ store.y.dtype = af::DT_FLOAT;
831+ *store.y.axis = {axis.id};
832+ *store.y.repeats = {size};
833+ *store.y.strides = {One};
834+ *store.y.vectorized_axis = {axis.id};
835+ *store.y.vectorized_strides = {One};
836+ 
837+ Output output("output");
838+ output.x = store.y;
839+ output.ir_attr.SetIndex(0);
840+ 
841+ ::ascir::FusedScheduledResult scheduled;
842+ ASSERT_EQ(optimizer.Optimize(graph, scheduled), af::SUCCESS);
843+ ASSERT_EQ(scheduled.node_idx_to_scheduled_results.size(), 1UL);
844+ ASSERT_EQ(scheduled.node_idx_to_scheduled_results[0].size(), 1UL);
845+ ASSERT_EQ(scheduled.node_idx_to_scheduled_results[0][0].schedule_groups.size(), 1UL);
846+ ASSERT_EQ(scheduled.node_idx_to_scheduled_results[0][0].schedule_groups[0].impl_graphs.size(), 1UL);
847+ const auto &impl_graph = scheduled.node_idx_to_scheduled_results[0][0].schedule_groups[0].impl_graphs[0];
848+ 
849+ const auto root_scalar = impl_graph.FindNode("scalar");
850+ ASSERT_NE(root_scalar, nullptr);
851+ ASSERT_NE(root_scalar->outputs[0].attr.mem.tensor_id, af::kIdNone);
852+ EXPECT_NE(root_scalar->outputs[0].attr.mem.alloc_type, af::AllocType::kAllocTypeBuffer);
853+ 
854+ af::AscNodePtr vf_node;
855+ for (const auto &node : impl_graph.GetAllNodes()) {
856+ if (node->GetType() == VectorFunc::Type) {
857+ vf_node = node;
858+ break;
859+ }
860+ }
861+ ASSERT_NE(vf_node, nullptr);
862+ std::vector<AscGraph> subgraphs;
863+ ASSERT_EQ(impl_graph.GetAllSubGraphs(subgraphs), af::SUCCESS);
864+ ASSERT_EQ(subgraphs.size(), 1UL);
865+ const auto sub_scalar = subgraphs[0].FindNode("Scalar_scalar");
866+ ASSERT_NE(sub_scalar, nullptr);
867+ EXPECT_EQ(sub_scalar->outputs[0].attr.mem.tensor_id, root_scalar->outputs[0].attr.mem.tensor_id);
868+ 
869+ codegen::CodegenResult generated;
870+ ASSERT_EQ(codegen::Codegen(codegen::CodegenOptions{}).Generate({{"size", "stub_size"}}, scheduled, generated),
871+ af::SUCCESS);
872+ const std::string scalar_name = "scalar_" + std::to_string(root_scalar->outputs[0].attr.mem.tensor_id);
873+ const std::string function_name = "VFCall" + vf_node->GetName();
874+ const auto signature_offset = generated.kernel.find("inline __simd_vf__ void " + function_name + "(");
875+ ASSERT_NE(signature_offset, std::string::npos);
876+ const auto signature_end = generated.kernel.find('\n', signature_offset);
877+ const auto signature = generated.kernel.substr(signature_offset, signature_end - signature_offset);
878+ const std::string scalar_param = "float " + scalar_name;
879+ const auto scalar_param_offset = signature.find(scalar_param);
880+ ASSERT_NE(scalar_param_offset, std::string::npos) << signature;
881+ EXPECT_EQ(signature.find(scalar_param, scalar_param_offset + scalar_param.size()), std::string::npos) << signature;
882+ 
883+ const auto call_offset = generated.kernel.find(function_name + "(", signature_end);
884+ ASSERT_NE(call_offset, std::string::npos);
885+ const auto call_end = generated.kernel.find('\n', call_offset);
886+ const auto call = generated.kernel.substr(call_offset, call_end - call_offset);
887+ EXPECT_NE(call.find(scalar_name), std::string::npos) << call;
888+}
889+ 
205TEST_F(TestOptimizer, ReOrderMergeAxisGraph_scheduler) {890TEST_F(TestOptimizer, ReOrderMergeAxisGraph_scheduler) {
206 // z0, z1, z2 mergeaxis891 // z0, z1, z2 mergeaxis
207 af::AscGraph graph("test_graph");892 af::AscGraph graph("test_graph");
@@ -173,6 +173,103 @@ class TestAscir:
173 == "Check dtype failed for cast_0 Cast; input_dtypes: [DT_INT8], output_dtypes: [DT_INT4]"173 == "Check dtype failed for cast_0 Cast; input_dtypes: [DT_INT8], output_dtypes: [DT_INT4]"
174 )174 )
175 175 
176+ @staticmethod
177+ def test_index_expr_and_arange_api():
178+ ascir.utils.set_platform("3510", 1, 245760)
179+ try:
180+ graph = ascir.HintGraph("index_expr_arange_api")
181+ size = graph.create_size("size")
182+ axis = graph.create_axis("axis", size)
183+ 
184+ index = ascir_api.IndexExpr(graph, dtype=ascir.dtypes.int64, expr=size + 1)
185+ values = ascir_api.arange(
186+ graph,
187+ dtype=ascir.dtypes.int64,
188+ base=ascir.SizeExpr(3),
189+ step=ascir.SizeExpr(2),
190+ axis=[axis],
191+ size=[size],
192+ stride=[ascir.SizeExpr(1)],
193+ )
194+ 
195+ assert index.dtype == ascir.dtypes.int64
196+ assert values.dtype == ascir.dtypes.int64
197+ assert values.axis == [axis.id]
198+ assert values.size == [size]
199+ assert values.strides == [ascir.SizeExpr(1)]
200+ assert not hasattr(values, "vectorized_axis")
201+ assert not hasattr(values, "vectorized_strides")
202+ 
203+ singleton = graph.create_axis("singleton", 1)
204+ trailing_singleton = ascir_api.arange(
205+ graph,
206+ dtype=ascir.dtypes.int64,
207+ base=ascir.SizeExpr(0),
208+ step=ascir.SizeExpr(1),
209+ axis=[axis, singleton],
210+ size=[size, ascir.SizeExpr(1)],
211+ stride=[ascir.SizeExpr(1), ascir.SizeExpr(0)],
212+ )
213+ assert trailing_singleton.strides == [ascir.SizeExpr(1), ascir.SizeExpr(0)]
214+ 
215+ stored = ascir_api.Store(
216+ graph,
217+ values,
218+ axis=[axis],
219+ size=[size],
220+ stride=[ascir.SizeExpr(1)],
221+ )
222+ ascir_api.Output(graph, stored, dtype=ascir.dtypes.int64)
223+ Autofuser(AutofuserOptions()).schedule(graph)
224+ 
225+ with pytest.raises(ValueError, match="axis must not be empty"):
226+ ascir_api.arange(
227+ graph,
228+ dtype=ascir.dtypes.int32,
229+ base=ascir.SizeExpr(0),
230+ step=ascir.SizeExpr(1),
231+ axis=[],
232+ )
233+ with pytest.raises(ValueError, match="unit physical stride"):
234+ ascir_api.arange(
235+ graph,
236+ dtype=ascir.dtypes.int32,
237+ base=ascir.SizeExpr(0),
238+ step=ascir.SizeExpr(1),
239+ axis=[axis],
240+ size=[size],
241+ stride=[ascir.SizeExpr(2)],
242+ )
243+ with pytest.raises(ValueError, match="zero strides require singleton"):
244+ ascir_api.arange(
245+ graph,
246+ dtype=ascir.dtypes.int32,
247+ base=ascir.SizeExpr(0),
248+ step=ascir.SizeExpr(1),
249+ axis=[axis, singleton],
250+ size=[size, ascir.SizeExpr(2)],
251+ stride=[ascir.SizeExpr(1), ascir.SizeExpr(0)],
252+ )
253+ with pytest.raises(ValueError, match="same with axis len"):
254+ ascir_api.arange(
255+ graph,
256+ dtype=ascir.dtypes.int32,
257+ base=ascir.SizeExpr(0),
258+ step=ascir.SizeExpr(1),
259+ axis=[axis],
260+ stride=[],
261+ )
262+ with pytest.raises(Exception, match="Check dtype failed"):
263+ ascir_api.arange(
264+ graph,
265+ dtype=ascir.dtypes.float32,
266+ base=ascir.SizeExpr(0),
267+ step=ascir.SizeExpr(1),
268+ axis=[axis],
269+ )
270+ finally:
271+ ascir.utils.set_platform("2201", 1, 245760)
272+ 
176 @staticmethod273 @staticmethod
177 def test_indirect_load_max_expression_attr():274 def test_indirect_load_max_expression_attr():
178 graph = ascir.HintGraph("test_indirect_load_max")275 graph = ascir.HintGraph("test_indirect_load_max")
@@ -0,0 +1,151 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include <memory>
12+#include <string>
13+ 
14+#include "gtest/gtest.h"
15+ 
16+#include "ascendc_ir.h"
17+#include "ascir_ops.h"
18+#include "codegen_kernel.h"
19+#include "micro_api_call/micro_api_call_factory.h"
20+#include "micro_api_call/micro_arange_api_call.h"
21+#include "platform_context.h"
22+#include "runtime_stub.h"
23+ 
24+namespace codegen {
25+namespace {
26+struct ArangeMicroFixture {
27+ af::AscGraph graph{"micro_arange"};
28+ af::AscNodePtr arange;
29+ TensorManager tensor_manager;
30+ Tiler tiler;
31+ TPipe tpipe{"tpipe", tiler};
32+ 
33+ ArangeMicroFixture(const af::Expression &base, const af::Expression &step, ge::DataType dtype = ge::DT_INT32) {
34+ const auto size = graph.CreateSizeVar("size");
35+ const auto axis = graph.CreateAxis("axis", size);
36+ af::ascir_op::Arange arange_op("arange");
37+ graph.AddNode(arange_op);
38+ arange_op.ir_attr.SetBase(base);
39+ arange_op.ir_attr.SetStep(step);
40+ arange_op.y.dtype = dtype;
41+ *arange_op.y.axis = {axis.id};
42+ *arange_op.y.repeats = {size};
43+ *arange_op.y.strides = {af::sym::kSymbolOne};
44+ *arange_op.y.vectorized_axis = {axis.id};
45+ *arange_op.y.vectorized_strides = {af::sym::kSymbolOne};
46+ 
47+ arange = graph.FindNode("arange");
48+ arange->outputs[0].attr.mem.tensor_id = 0;
49+ std::string dtype_name;
50+ EXPECT_EQ(Tensor::DtypeName(dtype, dtype_name), af::SUCCESS);
51+ EXPECT_EQ(tensor_manager.AddTensor(MicroApiTensor(arange->outputs[0], dtype_name)), af::SUCCESS);
52+ tiler.AddSizeVar(af::SizeVar(size));
53+ }
54+};
55+ 
56+class MicroArangeApiCallTest : public testing::Test {
57+ protected:
58+ void SetUp() override {
59+ ge::PlatformContext::GetInstance().Reset();
60+ ge::RuntimeStub::SetInstance(std::make_shared<ge::RuntimeStubV2Common>());
61+ }
62+ 
63+ void TearDown() override {
64+ ge::RuntimeStub::Reset();
65+ ge::PlatformContext::GetInstance().Reset();
66+ }
67+};
68+} // namespace
69+ 
70+TEST_F(MicroArangeApiCallTest, GeneratesDirectArangeForUnitStep) {
71+ ArangeMicroFixture fixture(af::Symbol(3), af::Symbol(1));
72+ MicroArangeApiCall call("Arange");
73+ ASSERT_EQ(call.Init(fixture.arange), af::SUCCESS);
74+ call.AddOutput(0);
75+ 
76+ CallParam param{"p_reg", "", "int32_t"};
77+ std::string result;
78+ ASSERT_EQ(call.Generate(fixture.tensor_manager, fixture.tpipe, param, result), af::SUCCESS);
79+ EXPECT_EQ(result, "AscendC::Reg::Arange(vreg_0, static_cast<int32_t>(3));\n");
80+}
81+ 
82+TEST_F(MicroArangeApiCallTest, GeneratesInt64WithVectorBlockOffset) {
83+ ArangeMicroFixture fixture(af::Symbol(3), af::Symbol(2), ge::DT_INT64);
84+ MicroArangeApiCall call("Arange");
85+ ASSERT_EQ(call.Init(fixture.arange), af::SUCCESS);
86+ call.AddOutput(0);
87+ 
88+ CallParam param{"p_reg", "axis * ELEMENT_PER_VECTOR_LENGTH * 3", "int64_t"};
89+ std::string result;
90+ ASSERT_EQ(call.Generate(fixture.tensor_manager, fixture.tpipe, param, result), af::SUCCESS);
91+ EXPECT_NE(result.find("axis * ELEMENT_PER_VECTOR_LENGTH * 3"), std::string::npos);
92+ EXPECT_NE(result.find("3 + (axis * ELEMENT_PER_VECTOR_LENGTH * 3) * (2)"), std::string::npos);
93+ EXPECT_NE(result.find("static_cast<int64_t>(2)"), std::string::npos);
94+}
95+ 
96+TEST_F(MicroArangeApiCallTest, GeneratesInt32WithVectorBlockOffset) {
97+ ArangeMicroFixture fixture(af::Symbol(3), af::Symbol(2));
98+ MicroArangeApiCall call("Arange");
99+ ASSERT_EQ(call.Init(fixture.arange), af::SUCCESS);
100+ call.AddOutput(0);
101+ 
102+ CallParam param{"p_reg", "axis * ELEMENT_PER_VECTOR_LENGTH * 3", "int32_t"};
103+ std::string result;
104+ ASSERT_EQ(call.Generate(fixture.tensor_manager, fixture.tpipe, param, result), af::SUCCESS);
105+ EXPECT_NE(result.find("axis * ELEMENT_PER_VECTOR_LENGTH * 3"), std::string::npos);
106+ EXPECT_NE(result.find("3 + (axis * ELEMENT_PER_VECTOR_LENGTH * 3) * (2)"), std::string::npos);
107+ EXPECT_NE(result.find("static_cast<int32_t>(2)"), std::string::npos);
108+}
109+ 
110+TEST_F(MicroArangeApiCallTest, GeneratesScaledArangeWithDynamicBaseAndStep) {
111+ ArangeMicroFixture fixture(af::Expression::Parse("size + 1"), af::Expression::Parse("size * 2"));
112+ MicroArangeApiCall call("Arange");
113+ ASSERT_EQ(call.Init(fixture.arange), af::SUCCESS);
114+ call.AddOutput(0);
115+ 
116+ CallParam param{"p_reg", "", "int32_t"};
117+ std::string result;
118+ ASSERT_EQ(call.Generate(fixture.tensor_manager, fixture.tpipe, param, result), af::SUCCESS);
119+ EXPECT_EQ(result,
120+ "AscendC::Reg::Arange(vreg_0, static_cast<int32_t>(0));\n"
121+ "AscendC::Reg::Muls(vreg_0, vreg_0, static_cast<int32_t>((2 * t->size)), p_reg);\n"
122+ "AscendC::Reg::Adds(vreg_0, vreg_0, static_cast<int32_t>((1 + t->size)), p_reg);\n");
123+}
124+ 
125+TEST_F(MicroArangeApiCallTest, UsesVfArangeParamsWhenProvided) {
126+ ArangeMicroFixture fixture(af::Symbol(3), af::Symbol(2));
127+ MicroArangeApiCall call("Arange");
128+ ASSERT_EQ(call.Init(fixture.arange), af::SUCCESS);
129+ call.AddOutput(0);
130+ 
131+ CallParam param{"p_reg", "", "int32_t", {true, "arange_base_0", "arange_step_0"}};
132+ std::string result;
133+ ASSERT_EQ(call.Generate(fixture.tensor_manager, fixture.tpipe, param, result), af::SUCCESS);
134+ EXPECT_NE(result.find("arange_base_0"), std::string::npos);
135+ EXPECT_NE(result.find("arange_step_0"), std::string::npos);
136+}
137+ 
138+TEST_F(MicroArangeApiCallTest, FactoryCreatesArangeCall) {
139+ ArangeMicroFixture fixture(af::Symbol(0), af::Symbol(1));
140+ std::unique_ptr<MicroApiCall> call(CreateMicroApiCallObject(fixture.arange));
141+ ASSERT_NE(call, nullptr);
142+ EXPECT_NE(dynamic_cast<MicroArangeApiCall *>(call.get()), nullptr);
143+ EXPECT_EQ(call->Init(fixture.arange), af::SUCCESS);
144+}
145+ 
146+TEST_F(MicroArangeApiCallTest, RejectsUnsupportedDtype) {
147+ ArangeMicroFixture fixture(af::Symbol(0), af::Symbol(1), ge::DT_FLOAT);
148+ MicroArangeApiCall call("Arange");
149+ EXPECT_NE(call.Init(fixture.arange), af::SUCCESS);
150+}
151+} // namespace codegen
@@ -129,8 +129,7 @@ TEST(CodegenKernel, StoreRegApiCall_TwoStoreOneOutput) {
129 call_0.Generate(tpipe, vector<af::AxisId>{}, result);129 call_0.Generate(tpipe, vector<af::AxisId>{}, result);
130 EXPECT_EQ(130 EXPECT_EQ(
131 result,131 result,
132- std::string{132+ std::string{"DataCopyPadExtend<float, AscendC::PaddingMode::Normal>(local_1[0 + 0], local_0[0], 1, 1, 0, 0);\n"});
133- "DataCopyPadExtend<float, AscendC::PaddingMode::Normal>(local_1[0 + 0], local_0[0], 1, 1, (16 - 1), 0);\n"});
134 133 
135 codegen::StoreRegApiCall call_1("DataCopyPadExtend");134 codegen::StoreRegApiCall call_1("DataCopyPadExtend");
136 EXPECT_EQ(call_1.Init(store_1), 0);135 EXPECT_EQ(call_1.Init(store_1), 0);
@@ -351,13 +350,12 @@ TEST(CodegenKernel, StoreRegApiCall_NeetMte3SyncMte2) {
351 call_1.Generate(kernel.tpipe, vector<af::AxisId>{}, result);350 call_1.Generate(kernel.tpipe, vector<af::AxisId>{}, result);
352 EXPECT_EQ(351 EXPECT_EQ(
353 result,352 result,
354- std::string{353+ std::string{"DataCopyPadExtend<float, AscendC::PaddingMode::Normal>(local_1[0 + 0], local_0[0], 1, 1, 0, 0);\n"
355- "DataCopyPadExtend<float, AscendC::PaddingMode::Normal>(local_1[0 + 0], local_0[0], 1, 1, (16 - 1), 0);\n"354+ "auto local_0_e_mte3_2_mte2_t_0 = tpipe.AllocEventID<HardEvent::MTE3_MTE2>();\n"
356- "auto local_0_e_mte3_2_mte2_t_0 = tpipe.AllocEventID<HardEvent::MTE3_MTE2>();\n"355+ "TQueSync<PIPE_MTE3, PIPE_MTE2> local_0_s_mte3_2_mte2_t_0;\n"
357- "TQueSync<PIPE_MTE3, PIPE_MTE2> local_0_s_mte3_2_mte2_t_0;\n"356+ "local_0_s_mte3_2_mte2_t_0.SetFlag(local_0_e_mte3_2_mte2_t_0);\n"
358- "local_0_s_mte3_2_mte2_t_0.SetFlag(local_0_e_mte3_2_mte2_t_0);\n"357+ "local_0_s_mte3_2_mte2_t_0.WaitFlag(local_0_e_mte3_2_mte2_t_0);\n"
359- "local_0_s_mte3_2_mte2_t_0.WaitFlag(local_0_e_mte3_2_mte2_t_0);\n"358+ "tpipe.ReleaseEventID<HardEvent::MTE3_MTE2>(local_0_e_mte3_2_mte2_t_0);\n"});
360- "tpipe.ReleaseEventID<HardEvent::MTE3_MTE2>(local_0_e_mte3_2_mte2_t_0);\n"});
361}359}
362 360 
363TEST(CodegenKernel, StoreRegApiCall_ThreeDimStore) {361TEST(CodegenKernel, StoreRegApiCall_ThreeDimStore) {
@@ -20,6 +20,8 @@
20#include "codegen_kernel.h"20#include "codegen_kernel.h"
21#include "utils/api_call_factory.h"21#include "utils/api_call_factory.h"
22#include "vec_func_call.h"22#include "vec_func_call.h"
23+#include "micro_api_call/micro_api_call_factory.h"
24+#include "micro_api_call/micro_arange_api_call.h"
23#include "../common.h"25#include "../common.h"
24#include "codegen_graph_check.h"26#include "codegen_graph_check.h"
25 27 
@@ -30,6 +32,47 @@ using namespace af::ascir_op;
30using namespace codegen;32using namespace codegen;
31 33 
32namespace {34namespace {
35+std::string GetLineContaining(const std::string &text, const std::string &needle) {
36+ const auto offset = text.find(needle);
37+ if (offset == std::string::npos) {
38+ return {};
39+ }
40+ const auto line_begin = text.rfind('\n', offset);
41+ const auto line_end = text.find('\n', offset);
42+ return text.substr(line_begin == std::string::npos ? 0UL : line_begin + 1UL,
43+ line_end == std::string::npos ? std::string::npos : line_end - line_begin - 1UL);
44+}
45+ 
46+class FailingMicroApiCall final : public MicroApiCall {
47+ public:
48+ FailingMicroApiCall() : MicroApiCall("FailingMicroApiCall") {}
49+ 
50+ Status Generate(const TensorManager &, const TPipe &, CallParam &, std::string &) override {
51+ return af::FAILED;
52+ }
53+};
54+ 
55+class FixedArangeMicroApiCall final : public MicroApiCall {
56+ public:
57+ FixedArangeMicroApiCall(int64_t tensor_id, std::string base, std::string step)
58+ : MicroApiCall("FixedArangeMicroApiCall"), base_(std::move(base)), step_(std::move(step)) {
59+ AddOutput(tensor_id);
60+ }
61+ 
62+ bool HasArangeParam() const override {
63+ return true;
64+ }
65+ 
66+ void GetArangeParams(const TPipe &, std::string &base, std::string &step) const override {
67+ base = base_;
68+ step = step_;
69+ }
70+ 
71+ private:
72+ std::string base_;
73+ std::string step_;
74+};
75+ 
33template <typename TensorLike>76template <typename TensorLike>
34void SetTwoDimSchedule(TensorLike &tensor, const af::Axis &z0, const af::Axis &z1, const af::Expression &s0,77void SetTwoDimSchedule(TensorLike &tensor, const af::Axis &z0, const af::Axis &z1, const af::Expression &s0,
35 const af::Expression &s1) {78 const af::Expression &s1) {
@@ -63,7 +106,7 @@ codegen::Tensor MakeCvUbFuseTensor(af::AscGraph &graph, ge::DataType dtype, int6
63}106}
64 107 
65void InitScalarDataVfGraph(VectorFunc &vf_op, Store &store_op, Broadcast &sub_brc_op, Abs &abs_op, Store &sub_store_op,108void InitScalarDataVfGraph(VectorFunc &vf_op, Store &store_op, Broadcast &sub_brc_op, Abs &abs_op, Store &sub_store_op,
66- Output &sub_output_op, const ScalarData &scalar_data_op, const Scalar &sub_scalar_op,109+ Output &sub_output_op, const ScalarData &scalar_data_op, Scalar &sub_scalar_op,
67 const af::Axis &z0, const af::Axis &z1, const af::Expression &s0, const af::Expression &s1) {110 const af::Axis &z0, const af::Axis &z1, const af::Expression &s0, const af::Expression &s1) {
68 vf_op.InstanceOutputy(1);111 vf_op.InstanceOutputy(1);
69 vf_op.x = {scalar_data_op.y};112 vf_op.x = {scalar_data_op.y};
@@ -75,6 +118,7 @@ void InitScalarDataVfGraph(VectorFunc &vf_op, Store &store_op, Broadcast &sub_br
75 SetTwoDimSchedule(store_op.y, z0, z1, s0, s1);118 SetTwoDimSchedule(store_op.y, z0, z1, s0, s1);
76 119 
77 sub_brc_op.x = sub_scalar_op.y;120 sub_brc_op.x = sub_scalar_op.y;
121+ sub_scalar_op.attr.api.unit = af::ComputeUnit::kUnitNone;
78 sub_brc_op.attr.sched.axis = {z0.id, z1.id};122 sub_brc_op.attr.sched.axis = {z0.id, z1.id};
79 SetTwoDimSchedule(sub_brc_op.y, z0, z1, s0, s1);123 SetTwoDimSchedule(sub_brc_op.y, z0, z1, s0, s1);
80 124 
@@ -86,6 +130,7 @@ void InitScalarDataVfGraph(VectorFunc &vf_op, Store &store_op, Broadcast &sub_br
86 sub_store_op.attr.sched.axis = {z0.id, z1.id};130 sub_store_op.attr.sched.axis = {z0.id, z1.id};
87 SetTwoDimSchedule(sub_store_op.y, z0, z1, s0, s1);131 SetTwoDimSchedule(sub_store_op.y, z0, z1, s0, s1);
88 sub_output_op.x = sub_store_op.y;132 sub_output_op.x = sub_store_op.y;
133+ sub_output_op.attr.api.unit = af::ComputeUnit::kUnitNone;
89}134}
90 135 
91void InitScalarDataVfTensorAttrs(AscGraph &graph, AscGraph &vf_sub_graph, const af::Axis &z0, const af::Axis &z1,136void InitScalarDataVfTensorAttrs(AscGraph &graph, AscGraph &vf_sub_graph, const af::Axis &z0, const af::Axis &z1,
@@ -130,8 +175,9 @@ std::string GenerateScalarDataVfCall(AscGraph &graph, const af::Axis &z0, const
130 auto vf = graph.FindNode("vf");175 auto vf = graph.FindNode("vf");
131 codegen::Tiler tiler;176 codegen::Tiler tiler;
132 codegen::TPipe tpipe("tpipe", tiler);177 codegen::TPipe tpipe("tpipe", tiler);
178+ EXPECT_EQ(tpipe.CollectQues(graph), af::SUCCESS);
133 EXPECT_EQ(tpipe.AddTensor(scalar_data->outputs[0], "scalar_data_y"), 0);179 EXPECT_EQ(tpipe.AddTensor(scalar_data->outputs[0], "scalar_data_y"), 0);
134- tpipe.AddTensor(vf->outputs[0]);180+ EXPECT_EQ(tpipe.AddTensor(vf->outputs[0]), af::SUCCESS);
135 181 
136 tiler.AddAxis(z0);182 tiler.AddAxis(z0);
137 tiler.AddAxis(z1);183 tiler.AddAxis(z1);
@@ -237,6 +283,9 @@ void PrepareDoubleLoopCodegen(AscGraph &graph, const std::vector<af::Axis> &axes
237 codegen::VfCall &call, std::stringstream &func_def) {283 codegen::VfCall &call, std::stringstream &func_def) {
238 auto load = graph.FindNode("load");284 auto load = graph.FindNode("load");
239 auto vf = graph.FindNode("vf");285 auto vf = graph.FindNode("vf");
286+ // 模拟生产 Kernel::ParseGraph 流程: queue 类型 tensor 入 tpipe 前需先从图收集 que 定义,
287+ // 否则 AddTensor 报 "Cannot find que"。
288+ ASSERT_EQ(tpipe.CollectQues(graph), af::SUCCESS);
240 tpipe.AddTensor(load->outputs[0]);289 tpipe.AddTensor(load->outputs[0]);
241 tpipe.AddTensor(vf->outputs[0]);290 tpipe.AddTensor(vf->outputs[0]);
242 for (const auto &axis : axes) {291 for (const auto &axis : axes) {
@@ -259,6 +308,10 @@ void RegisterDoubleLoopNodes(AscGraph &graph, AscGraph &vf_sub_graph, VectorFunc
259 vf_op.SetAttr("sub_graph_name", sub_graph_name);308 vf_op.SetAttr("sub_graph_name", sub_graph_name);
260 sub_x_op.ir_attr.SetIndex(0);309 sub_x_op.ir_attr.SetIndex(0);
261 sub_output_op.ir_attr.SetIndex(0);310 sub_output_op.ir_attr.SetIndex(0);
311+ // 生产图 CompleteApiInfo 会将 Data/Output 的 api.unit 置为 kUnitNone(不发射代码);
312+ // 手工 fixture 需显式设置, 否则 base MicroApiCall 会参与 Generate 并失败。
313+ sub_x_op.attr.api.unit = af::ComputeUnit::kUnitNone;
314+ sub_output_op.attr.api.unit = af::ComputeUnit::kUnitNone;
262 graph.AddNode(load_op);315 graph.AddNode(load_op);
263 graph.AddSubGraph(vf_sub_graph);316 graph.AddSubGraph(vf_sub_graph);
264 graph.AddNode(store_op);317 graph.AddNode(store_op);
@@ -279,6 +332,588 @@ void CheckDoubleLoopVectorFuncParams(const af::AscNodePtr &vf) {
279}332}
280} // namespace333} // namespace
281 334 
335+TEST(VFLoopTest, PropagatesFailureWhenMicroApiCallFails) {
336+ // 生成失败必须上抛: 吞错会在真机产生未初始化寄存器参与计算的静默错误数据。
337+ VFLoop loop(af::kIdNone);
338+ loop.AddCall(new FailingMicroApiCall());
339+ 
340+ Tiler tiler;
341+ TPipe tpipe("tpipe", tiler);
342+ TensorManager tensor_manager;
343+ std::string result;
344+ std::string loop_size;
345+ int32_t max_depth = -1;
346+ std::vector<std::string> loop_sizes;
347+ EXPECT_NE(loop.Generate(tpipe, tensor_manager, 0, result, loop_size, max_depth, loop_sizes), af::SUCCESS);
348+ EXPECT_NE(loop.GenerateCvUbFuse(tpipe, tensor_manager, result, loop_size), af::SUCCESS);
349+ loop.Destruct();
350+}
351+ 
352+TEST(VFLoopTest, GeneratesInt64ArangeAcrossVectorBlocks) {
353+ ge::SetupRuntimeStub();
354+ af::AscGraph graph("arange_vf_loop");
355+ const auto size = graph.CreateSizeVar("size");
356+ const auto axis = graph.CreateAxis("axis", size);
357+ af::AscGraph vf_sub_graph("arange_vf_sub_graph");
358+ VectorFunc vf_op("vf");
359+ vf_op.InstanceOutputy(1);
360+ vf_op.SetAttr("sub_graph_name", "arange_vf_sub_graph");
361+ graph.AddSubGraph(vf_sub_graph);
362+ graph.AddNode(vf_op);
363+ 
364+ Arange arange_op("arange");
365+ Abs abs_op("abs");
366+ Output output_op("output");
367+ arange_op.ir_attr.SetBase(af::Symbol(3));
368+ arange_op.ir_attr.SetStep(af::Symbol(2));
369+ arange_op.attr.api.unit = af::ComputeUnit::kUnitVector;
370+ arange_op.attr.sched.axis = {axis.id};
371+ arange_op.y.dtype = af::DT_INT64;
372+ *arange_op.y.axis = {axis.id};
373+ *arange_op.y.repeats = {size};
374+ *arange_op.y.strides = {One};
375+ *arange_op.y.vectorized_axis = {axis.id};
376+ *arange_op.y.vectorized_strides = {One};
377+ abs_op.attr.api.unit = af::ComputeUnit::kUnitVector;
378+ abs_op.attr.sched.axis = {axis.id};
379+ abs_op.y.dtype = af::DT_INT64;
380+ *abs_op.y.axis = {axis.id};
381+ *abs_op.y.repeats = {size};
382+ *abs_op.y.strides = {One};
383+ *abs_op.y.vectorized_axis = {axis.id};
384+ *abs_op.y.vectorized_strides = {One};
385+ output_op.ir_attr.SetIndex(0);
386+ vf_sub_graph.AddNode(arange_op);
387+ vf_sub_graph.AddNode(abs_op);
388+ vf_sub_graph.AddNode(output_op);
389+ abs_op.x = arange_op.y;
390+ output_op.x = abs_op.y;
391+ const auto node = vf_sub_graph.FindNode("arange");
392+ node->outputs[0].attr.mem.tensor_id = 0;
393+ auto abs_node = vf_sub_graph.FindNode("abs");
394+ abs_node->outputs[0].attr.mem.tensor_id = 2;
395+ auto vf_node = graph.FindNode("vf");
396+ vf_node->outputs[0].attr.dtype = af::DT_INT64;
397+ vf_node->outputs[0].attr.mem.tensor_id = 1;
398+ vf_node->outputs[0].attr.mem.position = af::Position::kPositionVecOut;
399+ 
400+ std::string dtype_name;
401+ ASSERT_EQ(codegen::Tensor::DtypeName(af::DT_INT64, dtype_name), af::SUCCESS);
402+ TensorManager tensor_manager;
403+ ASSERT_EQ(tensor_manager.AddTensor(MicroApiTensor(node->outputs[0], dtype_name)), af::SUCCESS);
404+ ASSERT_EQ(tensor_manager.AddTensor(MicroApiTensor(vf_node->outputs[0], dtype_name)), af::SUCCESS);
405+ ASSERT_EQ(tensor_manager.AddTensor(MicroApiTensor(abs_node->outputs[0], dtype_name)), af::SUCCESS);
406+ Tiler tiler;
407+ tiler.AddAxis(axis);
408+ tiler.AddSizeVar(af::SizeVar(size));
409+ TPipe tpipe("tpipe", tiler);
410+ ASSERT_EQ(tpipe.AddTensor(vf_node->outputs[0]), af::SUCCESS);
411+ 
412+ VfCall vf_call;
413+ ASSERT_EQ(vf_call.Init(vf_node), af::SUCCESS);
414+ tpipe.cv_fusion_type = ::ascir::CubeTemplateType::kUBFuse;
415+ std::stringstream ub_fuse_definition;
416+ EXPECT_NE(vf_call.GenerateFuncDefinition(tpipe, tiler, ub_fuse_definition), af::SUCCESS);
417+ tpipe.cv_fusion_type = ::ascir::CubeTemplateType::kDefault;
418+ 
419+ VFLoop loop(axis.id);
420+ loop.SetMaxDtypeSize("int64_t");
421+ ASSERT_EQ(loop.ConstructFromNodes(vf_sub_graph.GetAllNodes(), vf_node), af::SUCCESS);
422+ 
423+ std::string result;
424+ std::string loop_size;
425+ int32_t max_depth = -1;
426+ std::vector<std::string> loop_sizes;
427+ ASSERT_EQ(
428+ loop.Generate(tpipe, tensor_manager, 0, result, loop_size, max_depth, loop_sizes, {{0, "device_block_offset"}}),
429+ af::SUCCESS);
430+ EXPECT_NE(result.find("for (uint16_t axis"), std::string::npos);
431+ const auto arange_adds = GetLineContaining(result, "AscendC::Reg::Adds");
432+ ASSERT_FALSE(arange_adds.empty());
433+ EXPECT_EQ(arange_adds,
434+ "AscendC::Reg::Adds(vreg_0, vreg_0, static_cast<int64_t>((arange_base_0 + (device_block_offset + "
435+ "(axis * ELEMENT_PER_VECTOR_LENGTH)) * (arange_step_0))), preg_0);");
436+ loop.Destruct();
437+}
438+ 
439+TEST(CodegenKernel, HighRankArangeOuterForUsesLogicalOffset) {
440+ for (const size_t rank : {5UL, 6UL}) {
441+ ge::SetupRuntimeStub();
442+ af::AscGraph graph(("high_rank_arange_" + std::to_string(rank)).c_str());
443+ std::vector<af::Axis> axes;
444+ std::vector<af::AxisId> axis_ids;
445+ std::vector<af::Expression> logical_strides;
446+ for (size_t i = 0UL; i < rank; ++i) {
447+ axes.push_back(graph.CreateAxis("axis" + std::to_string(i), af::Symbol(2)));
448+ axis_ids.push_back(axes.back().id);
449+ logical_strides.push_back(af::Symbol(static_cast<int64_t>(1UL << (rank - i - 1UL))));
450+ }
451+ const std::vector<af::Expression> repeats(rank, af::Symbol(2));
452+ const std::vector<af::Expression> physical_strides =
453+ rank == 5UL ? std::vector<af::Expression>{af::Symbol(64), af::Symbol(24), af::Symbol(10), af::Symbol(3), One}
454+ : std::vector<af::Expression>{af::Symbol(180), af::Symbol(70), af::Symbol(26),
455+ af::Symbol(10), af::Symbol(3), One};
456+ 
457+ af::AscGraph subgraph(("high_rank_arange_subgraph_" + std::to_string(rank)).c_str());
458+ VectorFunc vf_op("vf");
459+ vf_op.InstanceOutputy(1);
460+ vf_op.SetAttr("sub_graph_name", "high_rank_arange_subgraph_" + std::to_string(rank));
461+ graph.AddSubGraph(subgraph);
462+ graph.AddNode(vf_op);
463+ 
464+ Arange arange_op("arange");
465+ arange_op.ir_attr.SetBase(af::Symbol(3));
466+ arange_op.ir_attr.SetStep(af::Symbol(2));
467+ arange_op.attr.api.unit = af::ComputeUnit::kUnitVector;
468+ arange_op.attr.sched.axis = axis_ids;
469+ arange_op.attr.sched.loop_axis = axes.back().id;
470+ arange_op.y.dtype = af::DT_INT64;
471+ *arange_op.y.axis = axis_ids;
472+ *arange_op.y.repeats = repeats;
473+ *arange_op.y.strides = logical_strides;
474+ *arange_op.y.vectorized_axis = axis_ids;
475+ *arange_op.y.vectorized_strides = physical_strides;
476+ 
477+ Abs abs_op("abs");
478+ abs_op.attr.api.unit = af::ComputeUnit::kUnitVector;
479+ abs_op.attr.sched.axis = axis_ids;
480+ abs_op.attr.sched.loop_axis = axes.back().id;
481+ abs_op.y.dtype = af::DT_INT64;
482+ *abs_op.y.axis = axis_ids;
483+ *abs_op.y.repeats = repeats;
484+ *abs_op.y.strides = logical_strides;
485+ *abs_op.y.vectorized_axis = axis_ids;
486+ *abs_op.y.vectorized_strides = physical_strides;
487+ 
488+ Store store_op("store");
489+ store_op.attr.api.unit = af::ComputeUnit::kUnitMTE3;
490+ store_op.attr.sched.axis = axis_ids;
491+ store_op.y.dtype = af::DT_INT64;
492+ *store_op.y.axis = axis_ids;
493+ *store_op.y.repeats = repeats;
494+ *store_op.y.strides = logical_strides;
495+ *store_op.y.vectorized_axis = axis_ids;
496+ *store_op.y.vectorized_strides = physical_strides;
497+ 
498+ Output output_op("output");
499+ output_op.ir_attr.SetIndex(0);
500+ subgraph.AddNode(arange_op);
501+ subgraph.AddNode(abs_op);
502+ subgraph.AddNode(store_op);
503+ subgraph.AddNode(output_op);
504+ abs_op.x = arange_op.y;
505+ store_op.x = abs_op.y;
506+ output_op.x = store_op.y;
507+ 
508+ const auto arange_node = subgraph.FindNode("arange");
509+ const auto abs_node = subgraph.FindNode("abs");
510+ ASSERT_NE(arange_node, nullptr);
511+ ASSERT_NE(abs_node, nullptr);
512+ arange_node->outputs[0].attr.mem.tensor_id = 0;
513+ abs_node->outputs[0].attr.mem.tensor_id = 1;
514+ const auto store_node = subgraph.FindNode("store");
515+ ASSERT_NE(store_node, nullptr);
516+ store_node->outputs[0].attr.mem.tensor_id = 2;
517+ const auto vf_node = graph.FindNode("vf");
518+ ASSERT_NE(vf_node, nullptr);
519+ vf_node->outputs[0].attr.dtype = af::DT_INT64;
520+ vf_node->outputs[0].attr.axis = axis_ids;
521+ vf_node->outputs[0].attr.repeats = repeats;
522+ vf_node->outputs[0].attr.strides = physical_strides;
523+ vf_node->outputs[0].attr.vectorized_axis = axis_ids;
524+ vf_node->outputs[0].attr.vectorized_strides = physical_strides;
525+ vf_node->outputs[0].attr.mem.tensor_id = 3;
526+ vf_node->outputs[0].attr.mem.position = af::Position::kPositionVecOut;
527+ 
528+ Tiler tiler;
529+ for (const auto &axis : axes) {
530+ ASSERT_EQ(tiler.AddAxis(axis), af::SUCCESS);
531+ }
532+ TPipe tpipe("tpipe", tiler);
533+ ASSERT_EQ(tpipe.AddTensor(vf_node->outputs[0]), af::SUCCESS);
534+ VfCall call;
535+ ASSERT_EQ(call.Init(vf_node), af::SUCCESS);
536+ 
537+ std::stringstream definition;
538+ ASSERT_EQ(call.GenerateFuncDefinition(tpipe, tiler, definition), af::SUCCESS);
539+ std::string invocation;
540+ ASSERT_EQ(call.Generate(tpipe, {}, invocation), af::SUCCESS);
541+ EXPECT_NE(invocation.find("for(int outer_for_0 = 0;"), std::string::npos) << invocation;
542+ const auto vf_call = GetLineContaining(invocation, "VFCallvf(");
543+ ASSERT_FALSE(vf_call.empty()) << invocation;
544+ EXPECT_NE(vf_call.find("outer_for_0 * " + std::to_string(1UL << (rank - 1UL))), std::string::npos) << vf_call;
545+ EXPECT_EQ(vf_call.find("0 + outer_for_0 * " + std::to_string(rank == 5UL ? 64UL : 180UL)), std::string::npos)
546+ << vf_call;
547+ if (rank == 6UL) {
548+ EXPECT_NE(invocation.find("for(int outer_for_1 = 0;"), std::string::npos) << invocation;
549+ EXPECT_NE(vf_call.find("outer_for_1 * 16"), std::string::npos) << vf_call;
550+ }
551+ }
552+}
553+ 
554+TEST(VFLoopTest, UsesLogicalOuterStrideForAlignedArangeLayout) {
555+ ge::SetupRuntimeStub();
556+ af::AscGraph graph("aligned_arange_vf_loop");
557+ const auto rows = graph.CreateSizeVar("rows");
558+ const auto cols = graph.CreateSizeVar("cols");
559+ const auto row_axis = graph.CreateAxis("row", rows);
560+ const auto col_axis = graph.CreateAxis("col", cols);
561+ af::AscGraph vf_sub_graph("aligned_arange_vf_sub_graph");
562+ VectorFunc vf_op("vf");
563+ vf_op.InstanceOutputy(1);
564+ vf_op.SetAttr("sub_graph_name", "aligned_arange_vf_sub_graph");
565+ graph.AddSubGraph(vf_sub_graph);
566+ graph.AddNode(vf_op);
567+ 
568+ Arange arange_op("arange");
569+ Abs abs_op("abs");
570+ Output output_op("output");
571+ arange_op.ir_attr.SetBase(af::Symbol(0));
572+ arange_op.ir_attr.SetStep(af::Symbol(1));
573+ arange_op.attr.api.unit = af::ComputeUnit::kUnitVector;
574+ arange_op.attr.sched.axis = {row_axis.id, col_axis.id};
575+ arange_op.attr.sched.loop_axis = col_axis.id;
576+ arange_op.y.dtype = af::DT_INT64;
577+ *arange_op.y.axis = {row_axis.id, col_axis.id};
578+ *arange_op.y.repeats = {rows, cols};
579+ *arange_op.y.strides = {cols, One};
580+ *arange_op.y.vectorized_axis = {row_axis.id, col_axis.id};
581+ *arange_op.y.vectorized_strides = {af::Symbol(16), One};
582+ abs_op.attr.api.unit = af::ComputeUnit::kUnitVector;
583+ abs_op.attr.sched.axis = {row_axis.id, col_axis.id};
584+ abs_op.attr.sched.loop_axis = col_axis.id;
585+ abs_op.y.dtype = af::DT_INT64;
586+ *abs_op.y.axis = {row_axis.id, col_axis.id};
587+ *abs_op.y.repeats = {rows, cols};
588+ *abs_op.y.strides = {cols, One};
589+ *abs_op.y.vectorized_axis = {row_axis.id, col_axis.id};
590+ *abs_op.y.vectorized_strides = {af::Symbol(16), One};
591+ output_op.ir_attr.SetIndex(0);
592+ vf_sub_graph.AddNode(arange_op);
593+ vf_sub_graph.AddNode(abs_op);
594+ vf_sub_graph.AddNode(output_op);
595+ abs_op.x = arange_op.y;
596+ output_op.x = abs_op.y;
597+ const auto arange_node = vf_sub_graph.FindNode("arange");
598+ arange_node->outputs[0].attr.mem.tensor_id = 0;
599+ const auto abs_node = vf_sub_graph.FindNode("abs");
600+ abs_node->outputs[0].attr.mem.tensor_id = 2;
601+ auto vf_node = graph.FindNode("vf");
602+ vf_node->outputs[0].attr.dtype = af::DT_INT64;
603+ vf_node->outputs[0].attr.mem.tensor_id = 1;
604+ vf_node->outputs[0].attr.mem.position = af::Position::kPositionVecOut;
605+ 
606+ std::string dtype_name;
607+ ASSERT_EQ(codegen::Tensor::DtypeName(af::DT_INT64, dtype_name), af::SUCCESS);
608+ TensorManager tensor_manager;
609+ ASSERT_EQ(tensor_manager.AddTensor(MicroApiTensor(arange_node->outputs[0], dtype_name)), af::SUCCESS);
610+ ASSERT_EQ(tensor_manager.AddTensor(MicroApiTensor(vf_node->outputs[0], dtype_name)), af::SUCCESS);
611+ ASSERT_EQ(tensor_manager.AddTensor(MicroApiTensor(abs_node->outputs[0], dtype_name)), af::SUCCESS);
612+ Tiler tiler;
613+ tiler.AddAxis(row_axis);
614+ tiler.AddAxis(col_axis);
615+ tiler.AddSizeVar(af::SizeVar(rows));
616+ tiler.AddSizeVar(af::SizeVar(cols));
617+ TPipe tpipe("tpipe", tiler);
618+ ASSERT_EQ(tpipe.AddTensor(vf_node->outputs[0]), af::SUCCESS);
619+ 
620+ VFLoop loop(af::kIdNone);
621+ loop.SetMaxDtypeSize("int64_t");
622+ ASSERT_EQ(loop.ConstructFromNodes(vf_sub_graph.GetAllNodes(), vf_node), af::SUCCESS);
623+ 
624+ std::string result;
625+ std::string loop_size;
626+ int32_t max_depth = -1;
627+ std::vector<std::string> loop_sizes;
628+ ASSERT_EQ(
629+ loop.Generate(tpipe, tensor_manager, 0, result, loop_size, max_depth, loop_sizes, {{0, "device_block_offset"}}),
630+ af::SUCCESS);
631+ const auto arange_call = GetLineContaining(result, "AscendC::Reg::Arange");
632+ ASSERT_FALSE(arange_call.empty()) << result;
633+ EXPECT_NE(arange_call.find("row * t->cols"), std::string::npos);
634+ EXPECT_NE(arange_call.find("col * ELEMENT_PER_VECTOR_LENGTH"), std::string::npos);
635+ EXPECT_EQ(arange_call.find("row * 16"), std::string::npos);
636+ loop.Destruct();
637+ 
638+ arange_node->attr.sched.axis = {row_axis.id};
639+ arange_node->attr.sched.loop_axis = row_axis.id;
640+ arange_node->outputs[0].attr.repeats = {rows, One};
641+ arange_node->outputs[0].attr.strides = {One, Zero};
642+ arange_node->outputs[0].attr.vectorized_strides = {One, Zero};
643+ abs_node->attr.sched.axis = {row_axis.id};
644+ abs_node->attr.sched.loop_axis = row_axis.id;
645+ abs_node->outputs[0].attr.repeats = {rows, One};
646+ abs_node->outputs[0].attr.strides = {One, Zero};
647+ abs_node->outputs[0].attr.vectorized_strides = {One, Zero};
648+ TensorManager trailing_singleton_tensor_manager;
649+ ASSERT_EQ(trailing_singleton_tensor_manager.AddTensor(MicroApiTensor(arange_node->outputs[0], dtype_name)),
650+ af::SUCCESS);
651+ ASSERT_EQ(trailing_singleton_tensor_manager.AddTensor(MicroApiTensor(abs_node->outputs[0], dtype_name)), af::SUCCESS);
652+ ASSERT_EQ(trailing_singleton_tensor_manager.AddTensor(MicroApiTensor(vf_node->outputs[0], dtype_name)), af::SUCCESS);
653+ VFLoop trailing_singleton_loop(af::kIdNone);
654+ trailing_singleton_loop.SetMaxDtypeSize("int64_t");
655+ ASSERT_EQ(trailing_singleton_loop.ConstructFromNodes(vf_sub_graph.GetAllNodes(), vf_node), af::SUCCESS);
656+ result.clear();
657+ loop_size.clear();
658+ max_depth = -1;
659+ loop_sizes.clear();
660+ ASSERT_EQ(trailing_singleton_loop.Generate(tpipe, trailing_singleton_tensor_manager, 0, result, loop_size, max_depth,
661+ loop_sizes),
662+ af::SUCCESS);
663+ const auto trailing_singleton_call = GetLineContaining(result, "AscendC::Reg::Arange");
664+ ASSERT_FALSE(trailing_singleton_call.empty()) << result;
665+ EXPECT_NE(trailing_singleton_call.find("row * ELEMENT_PER_VECTOR_LENGTH"), std::string::npos)
666+ << trailing_singleton_call;
667+ trailing_singleton_loop.Destruct();
668+ 
669+ arange_node->attr.sched.axis.clear();
670+ arange_node->attr.sched.loop_axis = af::kIdNone;
671+ abs_node->attr.sched.axis.clear();
672+ abs_node->attr.sched.loop_axis = af::kIdNone;
673+ VFLoop singleton_loop(af::kIdNone);
674+ singleton_loop.SetMaxDtypeSize("int64_t");
675+ ASSERT_EQ(singleton_loop.ConstructFromNodes(vf_sub_graph.GetAllNodes(), vf_node), af::SUCCESS);
676+ result.clear();
677+ loop_size.clear();
678+ max_depth = -1;
679+ loop_sizes.clear();
680+ ASSERT_EQ(singleton_loop.Generate(tpipe, tensor_manager, 0, result, loop_size, max_depth, loop_sizes,
681+ {{0, "device_block_offset"}}),
682+ af::SUCCESS);
683+ const auto singleton_arange_call = GetLineContaining(result, "AscendC::Reg::Arange");
684+ ASSERT_FALSE(singleton_arange_call.empty()) << result;
685+ EXPECT_NE(singleton_arange_call.find("device_block_offset + (0)"), std::string::npos);
686+ EXPECT_EQ(singleton_arange_call.find("row *"), std::string::npos);
687+ EXPECT_EQ(singleton_arange_call.find("col *"), std::string::npos);
688+ singleton_loop.Destruct();
689+}
690+ 
691+TEST(VFLoopTest, UsesInnermostLogicalStrideForPartiallyMergedArangeAxis) {
692+ ge::SetupRuntimeStub();
693+ af::AscGraph graph("partially_merged_arange");
694+ const auto outer = graph.CreateAxis("outer", af::Symbol(2));
695+ const auto middle = graph.CreateAxis("middle", af::Symbol(3));
696+ const auto inner = graph.CreateAxis("inner", af::Symbol(3));
697+ const auto merged = graph.MergeAxis({middle.id, outer.id});
698+ ASSERT_NE(merged, nullptr);
699+ af::AscGraph vf_sub_graph("partially_merged_arange_subgraph");
700+ VectorFunc vf_op("vf");
701+ vf_op.InstanceOutputy(1);
702+ vf_op.SetAttr("sub_graph_name", "partially_merged_arange_subgraph");
703+ graph.AddSubGraph(vf_sub_graph);
704+ graph.AddNode(vf_op);
705+ Arange arange_op("arange");
706+ Abs abs_op("abs");
707+ Output output_op("output");
708+ arange_op.ir_attr.SetBase(af::Symbol(0));
709+ arange_op.ir_attr.SetStep(af::Symbol(1));
710+ arange_op.attr.api.unit = af::ComputeUnit::kUnitVector;
711+ arange_op.attr.sched.axis = {merged->id, inner.id};
712+ arange_op.attr.sched.loop_axis = inner.id;
713+ arange_op.y.dtype = af::DT_INT64;
714+ *arange_op.y.axis = {outer.id, middle.id, inner.id};
715+ *arange_op.y.repeats = {af::Symbol(2), af::Symbol(3), af::Symbol(3)};
716+ *arange_op.y.strides = {af::Symbol(9), af::Symbol(3), One};
717+ *arange_op.y.vectorized_axis = {merged->id, inner.id};
718+ *arange_op.y.vectorized_strides = {af::Symbol(4), One};
719+ abs_op.attr.api.unit = af::ComputeUnit::kUnitVector;
720+ abs_op.attr.sched.axis = {merged->id, inner.id};
721+ abs_op.attr.sched.loop_axis = inner.id;
722+ abs_op.y.dtype = af::DT_INT64;
723+ *abs_op.y.axis = *arange_op.y.axis;
724+ *abs_op.y.repeats = *arange_op.y.repeats;
725+ *abs_op.y.strides = *arange_op.y.strides;
726+ *abs_op.y.vectorized_axis = *arange_op.y.vectorized_axis;
727+ *abs_op.y.vectorized_strides = *arange_op.y.vectorized_strides;
728+ output_op.ir_attr.SetIndex(0);
729+ vf_sub_graph.AddNode(arange_op);
730+ vf_sub_graph.AddNode(abs_op);
731+ vf_sub_graph.AddNode(output_op);
732+ abs_op.x = arange_op.y;
733+ output_op.x = abs_op.y;
734+ const auto arange_node = vf_sub_graph.FindNode("arange");
735+ arange_node->outputs[0].attr.mem.tensor_id = 0;
736+ const auto abs_node = vf_sub_graph.FindNode("abs");
737+ abs_node->outputs[0].attr.mem.tensor_id = 2;
738+ const auto vf_node = graph.FindNode("vf");
739+ vf_node->outputs[0].attr.dtype = af::DT_INT64;
740+ vf_node->outputs[0].attr.axis = abs_node->outputs[0].attr.axis;
741+ vf_node->outputs[0].attr.repeats = abs_node->outputs[0].attr.repeats;
742+ vf_node->outputs[0].attr.strides = abs_node->outputs[0].attr.strides;
743+ vf_node->outputs[0].attr.vectorized_axis = abs_node->outputs[0].attr.vectorized_axis;
744+ vf_node->outputs[0].attr.vectorized_strides = abs_node->outputs[0].attr.vectorized_strides;
745+ vf_node->outputs[0].attr.mem.tensor_id = 1;
746+ vf_node->outputs[0].attr.mem.position = af::Position::kPositionVecOut;
747+ 
748+ std::string dtype_name;
749+ ASSERT_EQ(codegen::Tensor::DtypeName(af::DT_INT64, dtype_name), af::SUCCESS);
750+ TensorManager tensor_manager;
751+ ASSERT_EQ(tensor_manager.AddTensor(MicroApiTensor(arange_node->outputs[0], dtype_name)), af::SUCCESS);
752+ ASSERT_EQ(tensor_manager.AddTensor(MicroApiTensor(vf_node->outputs[0], dtype_name)), af::SUCCESS);
753+ ASSERT_EQ(tensor_manager.AddTensor(MicroApiTensor(abs_node->outputs[0], dtype_name)), af::SUCCESS);
754+ Tiler tiler;
755+ for (const auto &axis : graph.GetAllAxis()) {
756+ ASSERT_EQ(tiler.AddAxis(*axis), af::SUCCESS);
757+ }
758+ TPipe tpipe("tpipe", tiler);
759+ VFLoop loop(af::kIdNone);
760+ loop.SetMaxDtypeSize("int64_t");
761+ ASSERT_EQ(loop.ConstructFromNodes(vf_sub_graph.GetAllNodes(), vf_node), af::SUCCESS);
762+ 
763+ std::string result;
764+ std::string loop_size;
765+ int32_t max_depth = -1;
766+ std::vector<std::string> loop_sizes;
767+ EXPECT_NE(loop.Generate(tpipe, tensor_manager, 0, result, loop_size, max_depth, loop_sizes), af::SUCCESS);
768+ loop.Destruct();
769+}
770+ 
771+TEST(VFLoopTest, RejectsInterleavedNestedMergedArangeAsOnlyVectorizedAxis) {
772+ ge::SetupRuntimeStub();
773+ af::AscGraph graph("interleaved_nested_merged_arange");
774+ const auto axis0 = graph.CreateAxis("axis0", af::Symbol(2));
775+ const auto axis1 = graph.CreateAxis("axis1", af::Symbol(2));
776+ const auto axis2 = graph.CreateAxis("axis2", af::Symbol(2));
777+ const auto axis3 = graph.CreateAxis("axis3", af::Symbol(2));
778+ const auto left = graph.MergeAxis({axis0.id, axis2.id});
779+ const auto right = graph.MergeAxis({axis1.id, axis3.id});
780+ ASSERT_NE(left, nullptr);
781+ ASSERT_NE(right, nullptr);
782+ const auto merged = graph.MergeAxis({left->id, right->id});
783+ ASSERT_NE(merged, nullptr);
784+ af::AscGraph vf_sub_graph("interleaved_nested_merged_arange_subgraph");
785+ VectorFunc vf_op("vf");
786+ vf_op.InstanceOutputy(1);
787+ vf_op.SetAttr("sub_graph_name", "interleaved_nested_merged_arange_subgraph");
788+ graph.AddSubGraph(vf_sub_graph);
789+ graph.AddNode(vf_op);
790+ Arange arange_op("arange");
791+ Abs abs_op("abs");
792+ Output output_op("output");
793+ arange_op.ir_attr.SetBase(af::Symbol(0));
794+ arange_op.ir_attr.SetStep(af::Symbol(1));
795+ arange_op.attr.api.unit = af::ComputeUnit::kUnitVector;
796+ arange_op.attr.sched.axis = {merged->id};
797+ arange_op.attr.sched.loop_axis = merged->id;
798+ arange_op.y.dtype = af::DT_INT64;
799+ *arange_op.y.axis = {axis0.id, axis1.id, axis2.id, axis3.id};
800+ *arange_op.y.repeats = {af::Symbol(2), af::Symbol(2), af::Symbol(2), af::Symbol(2)};
801+ *arange_op.y.strides = {af::Symbol(8), af::Symbol(4), af::Symbol(2), One};
802+ *arange_op.y.vectorized_axis = {merged->id};
803+ *arange_op.y.vectorized_strides = {One};
804+ abs_op.attr.api.unit = af::ComputeUnit::kUnitVector;
805+ abs_op.attr.sched.axis = {merged->id};
806+ abs_op.attr.sched.loop_axis = merged->id;
807+ abs_op.y.dtype = af::DT_INT64;
808+ *abs_op.y.axis = *arange_op.y.axis;
809+ *abs_op.y.repeats = *arange_op.y.repeats;
810+ *abs_op.y.strides = *arange_op.y.strides;
811+ *abs_op.y.vectorized_axis = *arange_op.y.vectorized_axis;
812+ *abs_op.y.vectorized_strides = *arange_op.y.vectorized_strides;
813+ output_op.ir_attr.SetIndex(0);
814+ vf_sub_graph.AddNode(arange_op);
815+ vf_sub_graph.AddNode(abs_op);
816+ vf_sub_graph.AddNode(output_op);
817+ abs_op.x = arange_op.y;
818+ output_op.x = abs_op.y;
819+ const auto arange_node = vf_sub_graph.FindNode("arange");
820+ arange_node->outputs[0].attr.mem.tensor_id = 0;
821+ const auto abs_node = vf_sub_graph.FindNode("abs");
822+ abs_node->outputs[0].attr.mem.tensor_id = 2;
823+ const auto vf_node = graph.FindNode("vf");
824+ vf_node->outputs[0].attr.dtype = af::DT_INT64;
825+ vf_node->outputs[0].attr.mem.tensor_id = 1;
826+ vf_node->outputs[0].attr.mem.position = af::Position::kPositionVecOut;
827+ 
828+ std::string dtype_name;
829+ ASSERT_EQ(codegen::Tensor::DtypeName(af::DT_INT64, dtype_name), af::SUCCESS);
830+ TensorManager tensor_manager;
831+ ASSERT_EQ(tensor_manager.AddTensor(MicroApiTensor(arange_node->outputs[0], dtype_name)), af::SUCCESS);
832+ ASSERT_EQ(tensor_manager.AddTensor(MicroApiTensor(vf_node->outputs[0], dtype_name)), af::SUCCESS);
833+ ASSERT_EQ(tensor_manager.AddTensor(MicroApiTensor(abs_node->outputs[0], dtype_name)), af::SUCCESS);
834+ Tiler tiler;
835+ for (const auto &axis : graph.GetAllAxis()) {
836+ ASSERT_EQ(tiler.AddAxis(*axis), af::SUCCESS);
837+ }
838+ TPipe tpipe("tpipe", tiler);
839+ ASSERT_EQ(tpipe.AddTensor(vf_node->outputs[0]), af::SUCCESS);
840+ VFLoop loop(af::kIdNone);
841+ loop.SetMaxDtypeSize("int64_t");
842+ ASSERT_EQ(loop.ConstructFromNodes(vf_sub_graph.GetAllNodes(), vf_node), af::SUCCESS);
843+ 
844+ std::string result;
845+ std::string loop_size;
846+ int32_t max_depth = -1;
847+ std::vector<std::string> loop_sizes;
848+ EXPECT_NE(loop.Generate(tpipe, tensor_manager, 0, result, loop_size, max_depth, loop_sizes), af::SUCCESS);
849+ loop.Destruct();
850+}
851+ 
852+TEST(VFLoopTest, KeepsDistinctParamsForMultipleArangeCalls) {
853+ Tiler tiler;
854+ TPipe tpipe("tpipe", tiler);
855+ VFLoop loop(af::kIdNone);
856+ loop.AddCall(new FixedArangeMicroApiCall(10, "1", "2"));
857+ loop.AddCall(new FixedArangeMicroApiCall(11, "7", "4"));
858+ 
859+ std::vector<ArangeParam> params;
860+ loop.CollectArangeParams(tpipe, params);
861+ ASSERT_EQ(params.size(), 2U);
862+ EXPECT_EQ(params[0].tensor_id, 10);
863+ EXPECT_EQ(params[0].base, "1");
864+ EXPECT_EQ(params[0].step, "2");
865+ EXPECT_EQ(params[1].tensor_id, 11);
866+ EXPECT_EQ(params[1].base, "7");
867+ EXPECT_EQ(params[1].step, "4");
868+ loop.Destruct();
869+}
870+ 
871+TEST(VFLoopTest, MapsBoundaryOrdinalsToConnectedRootInputs) {
872+ ge::SetupRuntimeStub();
873+ af::AscGraph graph("vf_input_mapping");
874+ af::AscGraph subgraph("vf_subgraph");
875+ const auto axis = graph.CreateAxis("axis", af::Symbol(8));
876+ af::ascir_op::Data root_data("root_data", graph);
877+ root_data.ir_attr.SetIndex(0);
878+ root_data.y.dtype = af::DT_FLOAT;
879+ af::ascir_op::VectorFunc vf("vf");
880+ vf.InstanceOutputy(1);
881+ // 与 VfCall_TwoDimLoad 相同的隐式入图模式: vf 由输入连接隐式创建并绑定 operator,
882+ // 显式 AddNode 会使 operator 与节点绑定分裂(inputs 为空)。
883+ vf.x = {root_data.y};
884+ auto root = graph.FindNode("vf");
885+ ASSERT_NE(root, nullptr);
886+ 
887+ af::ascir_op::Data data("data", subgraph);
888+ data.ir_attr.SetIndex(1);
889+ data.y.dtype = af::DT_FLOAT;
890+ af::ascir_op::Load load("load");
891+ load.x = data.y;
892+ load.attr.api.unit = af::ComputeUnit::kUnitMTE2;
893+ load.attr.sched.axis = {axis.id};
894+ load.y.dtype = af::DT_FLOAT;
895+ *load.y.axis = {axis.id};
896+ *load.y.repeats = {af::Symbol(8)};
897+ *load.y.strides = {One};
898+ *load.y.vectorized_axis = {axis.id};
899+ *load.y.vectorized_strides = {One};
900+ af::ascir_op::Output output("output");
901+ subgraph.AddNode(output);
902+ output.ir_attr.SetIndex(0);
903+ output.x = load.y;
904+ ASSERT_NE(subgraph.FindNode("data"), nullptr);
905+ ASSERT_NE(subgraph.FindNode("load"), nullptr);
906+ ASSERT_NE(subgraph.FindNode("output"), nullptr);
907+ ASSERT_TRUE(af::AttrUtils::SetInt(subgraph.FindNode("data")->GetOpDesc(), "vf_root_input_index", 0));
908+ root->outputs[0].attr.mem.tensor_id = 10;
909+ 
910+ VFLoop loop(af::kIdNone);
911+ loop.SetMaxDtypeSize("float");
912+ const VFInputMapping mapping = {{1, 0}};
913+ EXPECT_EQ(loop.ConstructFromNodes(subgraph.GetAllNodes(), root, mapping), af::SUCCESS);
914+ loop.Destruct();
915+}
916+ 
282TEST(CodegenKernel, VfCall_TwoDimLoad) {917TEST(CodegenKernel, VfCall_TwoDimLoad) {
283 ge::SetupRuntimeStub();918 ge::SetupRuntimeStub();
284 af::AscGraph graph("test_graph");919 af::AscGraph graph("test_graph");
@@ -303,12 +938,14 @@ TEST(CodegenKernel, VfCall_TwoDimLoad) {
303 938 
304 Data sub_x_op("sub_x", vf_sub_graph);939 Data sub_x_op("sub_x", vf_sub_graph);
305 sub_x_op.ir_attr.SetIndex(0);940 sub_x_op.ir_attr.SetIndex(0);
941+ sub_x_op.attr.api.unit = af::ComputeUnit::kUnitNone;
306 942 
307 Load sub_load_op("sub_load");943 Load sub_load_op("sub_load");
308 Abs abs_op("abs");944 Abs abs_op("abs");
309 Store sub_store_op("sub_store");945 Store sub_store_op("sub_store");
310 Output sub_output_op("sub_output");946 Output sub_output_op("sub_output");
311 sub_output_op.ir_attr.SetIndex(0);947 sub_output_op.ir_attr.SetIndex(0);
948+ sub_output_op.attr.api.unit = af::ComputeUnit::kUnitNone;
312 949 
313 Store store_op("store");950 Store store_op("store");
314 graph.AddNode(load_op);951 graph.AddNode(load_op);
@@ -421,8 +1058,9 @@ TEST(CodegenKernel, VfCall_TwoDimLoad) {
421 1058 
422 codegen::Tiler tiler;1059 codegen::Tiler tiler;
423 codegen::TPipe tpipe("tpipe", tiler);1060 codegen::TPipe tpipe("tpipe", tiler);
424- tpipe.AddTensor(load->outputs[0]);1061+ ASSERT_EQ(tpipe.CollectQues(graph), af::SUCCESS);
425- tpipe.AddTensor(vf->outputs[0]);1062+ ASSERT_EQ(tpipe.AddTensor(load->outputs[0]), af::SUCCESS);
1063+ ASSERT_EQ(tpipe.AddTensor(vf->outputs[0]), af::SUCCESS);
426 1064 
427 tiler.AddAxis(z0);1065 tiler.AddAxis(z0);
428 tiler.AddAxis(z1);1066 tiler.AddAxis(z1);
@@ -534,12 +1172,14 @@ TEST(CodegenKernel, VfCall_TwoDimLoad_VFLoop) {
534 1172 
535 Data sub_x_op("sub_x", vf_sub_graph);1173 Data sub_x_op("sub_x", vf_sub_graph);
536 sub_x_op.ir_attr.SetIndex(0);1174 sub_x_op.ir_attr.SetIndex(0);
1175+ sub_x_op.attr.api.unit = af::ComputeUnit::kUnitNone;
537 1176 
538 Load sub_load_op("sub_load");1177 Load sub_load_op("sub_load");
539 Abs abs_op("abs");1178 Abs abs_op("abs");
540 Store sub_store_op("sub_store");1179 Store sub_store_op("sub_store");
541 Output sub_output_op("sub_output");1180 Output sub_output_op("sub_output");
542 sub_output_op.ir_attr.SetIndex(0);1181 sub_output_op.ir_attr.SetIndex(0);
1182+ sub_output_op.attr.api.unit = af::ComputeUnit::kUnitNone;
543 1183 
544 Store store_op("store");1184 Store store_op("store");
545 graph.AddNode(load_op);1185 graph.AddNode(load_op);
@@ -647,8 +1287,9 @@ TEST(CodegenKernel, VfCall_TwoDimLoad_VFLoop) {
647 1287 
648 codegen::Tiler tiler;1288 codegen::Tiler tiler;
649 codegen::TPipe tpipe("tpipe", tiler);1289 codegen::TPipe tpipe("tpipe", tiler);
650- tpipe.AddTensor(load->outputs[0]);1290+ ASSERT_EQ(tpipe.CollectQues(graph), af::SUCCESS);
651- tpipe.AddTensor(vf->outputs[0]);1291+ ASSERT_EQ(tpipe.AddTensor(load->outputs[0]), af::SUCCESS);
1292+ ASSERT_EQ(tpipe.AddTensor(vf->outputs[0]), af::SUCCESS);
652 1293 
653 tiler.AddAxis(z0);1294 tiler.AddAxis(z0);
654 tiler.AddAxis(z1);1295 tiler.AddAxis(z1);
@@ -698,12 +1339,14 @@ TEST(CodegenKernel, VfCall_TwoDim_Scalar) {
698 Scalar sub_x_op("sub_x", vf_sub_graph);1339 Scalar sub_x_op("sub_x", vf_sub_graph);
699 sub_x_op.ir_attr.SetValue("2.0");1340 sub_x_op.ir_attr.SetValue("2.0");
700 sub_x_op.ir_attr.SetIndex(0);1341 sub_x_op.ir_attr.SetIndex(0);
1342+ sub_x_op.attr.api.unit = af::ComputeUnit::kUnitNone;
701 1343 
702 Broadcast sub_brc_op("sub_brc");1344 Broadcast sub_brc_op("sub_brc");
703 Abs abs_op("abs");1345 Abs abs_op("abs");
704 Store sub_store_op("sub_store");1346 Store sub_store_op("sub_store");
705 Output sub_output_op("sub_output");1347 Output sub_output_op("sub_output");
706 sub_output_op.ir_attr.SetIndex(0);1348 sub_output_op.ir_attr.SetIndex(0);
1349+ sub_output_op.attr.api.unit = af::ComputeUnit::kUnitNone;
707 1350 
708 Store store_op("store");1351 Store store_op("store");
709 graph.AddNode(x_op);1352 graph.AddNode(x_op);
@@ -804,8 +1447,9 @@ TEST(CodegenKernel, VfCall_TwoDim_Scalar) {
804 1447 
805 codegen::Tiler tiler;1448 codegen::Tiler tiler;
806 codegen::TPipe tpipe("tpipe", tiler);1449 codegen::TPipe tpipe("tpipe", tiler);
807- tpipe.AddTensor("2.0", x->outputs[0], "scalar_x");1450+ ASSERT_EQ(tpipe.CollectQues(graph), af::SUCCESS);
808- tpipe.AddTensor(vf->outputs[0]);1451+ ASSERT_EQ(tpipe.AddTensor("2.0", x->outputs[0], "scalar_x"), af::SUCCESS);
1452+ ASSERT_EQ(tpipe.AddTensor(vf->outputs[0]), af::SUCCESS);
809 1453 
810 tiler.AddAxis(z0);1454 tiler.AddAxis(z0);
811 tiler.AddAxis(z1);1455 tiler.AddAxis(z1);
@@ -904,12 +1548,14 @@ TEST(CodegenKernel, VfCall_ThreeDimLoad) {
904 1548 
905 Data sub_x_op("sub_x", vf_sub_graph);1549 Data sub_x_op("sub_x", vf_sub_graph);
906 sub_x_op.ir_attr.SetIndex(0);1550 sub_x_op.ir_attr.SetIndex(0);
1551+ sub_x_op.attr.api.unit = af::ComputeUnit::kUnitNone;
907 1552 
908 Load sub_load_op("sub_load");1553 Load sub_load_op("sub_load");
909 Abs abs_op("abs");1554 Abs abs_op("abs");
910 Store sub_store_op("sub_store");1555 Store sub_store_op("sub_store");
911 Output sub_output_op("sub_output");1556 Output sub_output_op("sub_output");
912 sub_output_op.ir_attr.SetIndex(0);1557 sub_output_op.ir_attr.SetIndex(0);
1558+ sub_output_op.attr.api.unit = af::ComputeUnit::kUnitNone;
913 1559 
914 Store store_op("store");1560 Store store_op("store");
915 graph.AddNode(load_op);1561 graph.AddNode(load_op);
@@ -1019,8 +1665,9 @@ TEST(CodegenKernel, VfCall_ThreeDimLoad) {
1019 1665 
1020 codegen::Tiler tiler;1666 codegen::Tiler tiler;
1021 codegen::TPipe tpipe("tpipe", tiler);1667 codegen::TPipe tpipe("tpipe", tiler);
1022- tpipe.AddTensor(load->outputs[0]);1668+ ASSERT_EQ(tpipe.CollectQues(graph), af::SUCCESS);
1023- tpipe.AddTensor(vf->outputs[0]);1669+ ASSERT_EQ(tpipe.AddTensor(load->outputs[0]), af::SUCCESS);
1670+ ASSERT_EQ(tpipe.AddTensor(vf->outputs[0]), af::SUCCESS);
1024 1671 
1025 tiler.AddAxis(z0);1672 tiler.AddAxis(z0);
1026 tiler.AddAxis(z1);1673 tiler.AddAxis(z1);
@@ -1086,12 +1733,14 @@ TEST(CodegenKernel, VfCall_FiveDimLoad) {
1086 1733 
1087 Data sub_x_op("sub_x", vf_sub_graph);1734 Data sub_x_op("sub_x", vf_sub_graph);
1088 sub_x_op.ir_attr.SetIndex(0);1735 sub_x_op.ir_attr.SetIndex(0);
1736+ sub_x_op.attr.api.unit = af::ComputeUnit::kUnitNone;
1089 1737 
1090 Load sub_load_op("sub_load");1738 Load sub_load_op("sub_load");
1091 Abs abs_op("abs");1739 Abs abs_op("abs");
1092 Store sub_store_op("sub_store");1740 Store sub_store_op("sub_store");
1093 Output sub_output_op("sub_output");1741 Output sub_output_op("sub_output");
1094 sub_output_op.ir_attr.SetIndex(0);1742 sub_output_op.ir_attr.SetIndex(0);
1743+ sub_output_op.attr.api.unit = af::ComputeUnit::kUnitNone;
1095 1744 
1096 Store store_op("store");1745 Store store_op("store");
1097 graph.AddNode(load_op);1746 graph.AddNode(load_op);
@@ -1196,8 +1845,9 @@ TEST(CodegenKernel, VfCall_FiveDimLoad) {
1196 1845 
1197 codegen::Tiler tiler;1846 codegen::Tiler tiler;
1198 codegen::TPipe tpipe("tpipe", tiler);1847 codegen::TPipe tpipe("tpipe", tiler);
1199- tpipe.AddTensor(load->outputs[0]);1848+ ASSERT_EQ(tpipe.CollectQues(graph), af::SUCCESS);
1200- tpipe.AddTensor(vf->outputs[0]);1849+ ASSERT_EQ(tpipe.AddTensor(load->outputs[0]), af::SUCCESS);
1850+ ASSERT_EQ(tpipe.AddTensor(vf->outputs[0]), af::SUCCESS);
1201 1851 
1202 tiler.AddAxis(z0);1852 tiler.AddAxis(z0);
1203 tiler.AddAxis(z1);1853 tiler.AddAxis(z1);
@@ -1313,12 +1963,14 @@ TEST(CodegenKernel, VfCall_OneDim_NoOptimization) {
1313 1963 
1314 Data sub_x_op("sub_x", vf_sub_graph);1964 Data sub_x_op("sub_x", vf_sub_graph);
1315 sub_x_op.ir_attr.SetIndex(0);1965 sub_x_op.ir_attr.SetIndex(0);
1966+ sub_x_op.attr.api.unit = af::ComputeUnit::kUnitNone;
1316 1967 
1317 Load sub_load_op("sub_load");1968 Load sub_load_op("sub_load");
1318 Abs abs_op("abs");1969 Abs abs_op("abs");
1319 Store sub_store_op("sub_store");1970 Store sub_store_op("sub_store");
1320 Output sub_output_op("sub_output");1971 Output sub_output_op("sub_output");
1321 sub_output_op.ir_attr.SetIndex(0);1972 sub_output_op.ir_attr.SetIndex(0);
1973+ sub_output_op.attr.api.unit = af::ComputeUnit::kUnitNone;
1322 1974 
1323 Store store_op("store");1975 Store store_op("store");
1324 graph.AddNode(load_op);1976 graph.AddNode(load_op);
@@ -1429,8 +2081,9 @@ TEST(CodegenKernel, VfCall_OneDim_NoOptimization) {
1429 2081 
1430 codegen::Tiler tiler;2082 codegen::Tiler tiler;
1431 codegen::TPipe tpipe("tpipe", tiler);2083 codegen::TPipe tpipe("tpipe", tiler);
1432- tpipe.AddTensor(load->outputs[0]);2084+ ASSERT_EQ(tpipe.CollectQues(graph), af::SUCCESS);
1433- tpipe.AddTensor(vf->outputs[0]);2085+ ASSERT_EQ(tpipe.AddTensor(load->outputs[0]), af::SUCCESS);
2086+ ASSERT_EQ(tpipe.AddTensor(vf->outputs[0]), af::SUCCESS);
1434 2087 
1435 tiler.AddAxis(z0);2088 tiler.AddAxis(z0);
1436 tiler.AddSizeVar(af::SizeVar(s0));2089 tiler.AddSizeVar(af::SizeVar(s0));
@@ -35,7 +35,7 @@ namespace optimize {
35using namespace ge;35using namespace ge;
36 36 
37namespace {37namespace {
38-void SetupGraphAxes(af::AscGraph &graph, const std::vector<af::Symbol> &loops) {38+void SetupGraphAxes(af::AscGraph &graph, const std::vector<af::Expression> &loops) {
39 for (size_t i = 0UL; i < loops.size(); ++i) {39 for (size_t i = 0UL; i < loops.size(); ++i) {
40 graph.CreateAxis("z" + std::to_string(i), loops[i]);40 graph.CreateAxis("z" + std::to_string(i), loops[i]);
41 }41 }
@@ -65,8 +65,9 @@ void SetupGraphAxes(af::AscGraph &graph, const std::vector<af::Symbol> &loops) {
65 for (const auto &node : graph.GetAllNodes()) {65 for (const auto &node : graph.GetAllNodes()) {
66 node->attr.sched.axis = axis_ids;66 node->attr.sched.axis = axis_ids;
67 if (ScheduleUtils::IsBuffer(node)) continue;67 if (ScheduleUtils::IsBuffer(node)) continue;
68- bool is_follow_input = (node->attr.api.compute_type == af::ComputeType::kComputeElewise ||68+ bool is_follow_input =
69- node->attr.api.compute_type == af::ComputeType::kComputeStore);69+ !node->inputs().empty() && (node->attr.api.compute_type == af::ComputeType::kComputeElewise ||
70+ node->attr.api.compute_type == af::ComputeType::kComputeStore);
70 for (auto &output : node->outputs()) {71 for (auto &output : node->outputs()) {
71 output->attr.axis = axis_ids;72 output->attr.axis = axis_ids;
72 output->attr.vectorized_axis = axis_ids;73 output->attr.vectorized_axis = axis_ids;
@@ -111,6 +112,75 @@ af::AscGraph BuildParallelVfGraph(bool reverse_branch_order) {
111 return graph;112 return graph;
112}113}
113 114 
115+af::AscGraph BuildIndexExprArangeCompositionGraph() {
116+ af::AscGraph graph("index_expr_arange_composition_python");
117+ const auto row = af::Symbol("row");
118+ const auto col = af::Symbol("col");
119+ const auto row_axis = graph.CreateAxis("row", row);
120+ const auto col_axis = graph.CreateAxis("col", col);
121+ const std::vector<af::AxisId> axes = {row_axis.id, col_axis.id};
122+ const std::vector<af::Expression> shape = {row, col};
123+ const std::vector<af::Expression> strides = {col, One};
124+ 
125+ af::ascir_op::Data data("data_0", graph);
126+ data.ir_attr.SetIndex(0);
127+ data.y.dtype = af::DT_INT32;
128+ auto data_node = graph.FindNode("data_0");
129+ af::ascir_op::Load load("load_0");
130+ load.y.dtype = af::DT_INT32;
131+ auto load_node = graph.AddNode(load);
132+ af::ascir_op::Arange arange("arange_0", graph);
133+ arange.ir_attr.SetBase(af::Symbol(0));
134+ arange.ir_attr.SetStep(af::Symbol(1));
135+ arange.y.dtype = af::DT_INT32;
136+ auto arange_node = graph.FindNode("arange_0");
137+ af::ascir_op::IndexExpr index_expr("indexexpr_0", graph);
138+ index_expr.ir_attr.SetExpr(row + col);
139+ index_expr.y.dtype = af::DT_INT32;
140+ auto index_expr_node = graph.FindNode("indexexpr_0");
141+ af::ascir_op::Broadcast broadcast("broadcast_0");
142+ broadcast.y.dtype = af::DT_INT32;
143+ auto broadcast_node = graph.AddNode(broadcast);
144+ af::ascir_op::Add offset("add_0");
145+ offset.y.dtype = af::DT_INT32;
146+ auto offset_node = graph.AddNode(offset);
147+ af::ascir_op::Add add("add_1");
148+ add.y.dtype = af::DT_INT32;
149+ auto add_node = graph.AddNode(add);
150+ af::ascir_op::Store store("store_0");
151+ store.y.dtype = af::DT_INT32;
152+ auto store_node = graph.AddNode(store);
153+ af::ascir_op::Output output("output_0");
154+ output.ir_attr.SetIndex(0);
155+ output.y.dtype = af::DT_INT32;
156+ auto output_node = graph.AddNode(output);
157+ if (data_node == nullptr || load_node == nullptr || arange_node == nullptr || index_expr_node == nullptr ||
158+ broadcast_node == nullptr || offset_node == nullptr || add_node == nullptr || store_node == nullptr ||
159+ output_node == nullptr) {
160+ return af::AscGraph("invalid_index_expr_arange_composition");
161+ }
162+ load.x = data.y;
163+ broadcast.x = index_expr.y;
164+ offset.x1 = arange.y;
165+ offset.x2 = broadcast.y;
166+ add.x1 = load.y;
167+ add.x2 = offset.y;
168+ store.x = add.y;
169+ output.x = store.y;
170+ SetupGraphAxes(graph, {row, col});
171+ index_expr_node->outputs[0].attr.axis.clear();
172+ index_expr_node->outputs[0].attr.repeats.clear();
173+ index_expr_node->outputs[0].attr.strides.clear();
174+ index_expr_node->outputs[0].attr.vectorized_axis.clear();
175+ index_expr_node->outputs[0].attr.vectorized_strides.clear();
176+ broadcast_node->outputs[0].attr.axis = axes;
177+ broadcast_node->outputs[0].attr.repeats = shape;
178+ broadcast_node->outputs[0].attr.vectorized_axis = axes;
179+ broadcast_node->outputs[0].attr.strides = {Zero, Zero};
180+ broadcast_node->outputs[0].attr.vectorized_strides = {Zero, Zero};
181+ return graph;
182+}
183+ 
114std::vector<std::string> GetVfOutputConsumers(af::AscGraph &graph) {184std::vector<std::string> GetVfOutputConsumers(af::AscGraph &graph) {
115 EXPECT_EQ(AlignmentHandler::AlignVectorizedStrides(graph), af::SUCCESS);185 EXPECT_EQ(AlignmentHandler::AlignVectorizedStrides(graph), af::SUCCESS);
116 VectorFuncPartitioner partitioner(graph);186 VectorFuncPartitioner partitioner(graph);
@@ -162,6 +232,7 @@ std::vector<std::string> GetGraphNodeNames(const af::AscGraph &graph) {
162 }232 }
163 return node_names;233 return node_names;
164}234}
235+ 
165} // namespace236} // namespace
166 237 
167class VfPartition : public testing::Test {238class VfPartition : public testing::Test {
@@ -340,6 +411,419 @@ TEST_F(VfPartition, brc_only_revert) {
340 EXPECT_TRUE(sub_graphs.empty());411 EXPECT_TRUE(sub_graphs.empty());
341}412}
342 413 
414+TEST_F(VfPartition, ArangeStoreControlEdgeRejectsSingletonPartition) {
415+ af::AscGraph graph("arange_store_control");
416+ const auto size = af::Symbol(32);
417+ const auto axis = graph.CreateAxis("z0", size);
418+ af::ascir_op::Arange arange("arange");
419+ arange.ir_attr.SetBase(af::Symbol(0));
420+ arange.ir_attr.SetStep(af::Symbol(1));
421+ arange.y.dtype = af::DT_INT32;
422+ af::ascir_op::Store store("store");
423+ store.y.dtype = af::DT_INT32;
424+ af::ascir_op::Output output("output");
425+ ASSERT_NE(graph.AddNode(arange), nullptr);
426+ ASSERT_NE(graph.AddNode(store), nullptr);
427+ ASSERT_NE(graph.AddNode(output), nullptr);
428+ store.x = arange.y;
429+ output.x = store.y;
430+ output.ir_attr.SetIndex(0);
431+ for (const auto &node : graph.GetAllNodes()) {
432+ node->attr.sched.axis = {axis.id};
433+ for (auto &node_output : node->outputs()) {
434+ node_output->attr.axis = {axis.id};
435+ node_output->attr.repeats = {size};
436+ node_output->attr.strides = {One};
437+ node_output->attr.vectorized_axis = {axis.id};
438+ node_output->attr.vectorized_strides = {One};
439+ }
440+ }
441+ optimize::AscGraphInfoComplete::CompleteApiInfo(graph);
442+ ASSERT_EQ(af::GraphUtils::AddEdge(graph.FindNode("arange")->GetOutControlAnchor(),
443+ graph.FindNode("store")->GetInControlAnchor()),
444+ af::GRAPH_SUCCESS);
445+ 
446+ auto cluster = std::make_shared<Cluster>(graph.FindNode("arange"), 0UL);
447+ cluster->meta_data_.enable_vf = true;
448+ cluster->out_nodes_.insert(graph.FindNode("arange"));
449+ VectorFuncPartitioner partitioner(graph);
450+ partitioner.cluster_dict_.AddCluster(cluster);
451+ ASSERT_NE(partitioner.BuildSubgraphs(), af::SUCCESS);
452+ std::vector<af::AscGraph> subgraphs;
453+ ASSERT_EQ(graph.GetAllSubGraphs(subgraphs), af::SUCCESS);
454+ EXPECT_TRUE(subgraphs.empty());
455+ EXPECT_EQ(graph.FindNode("arange")->GetOutControlNodesSize(), 1UL);
456+ EXPECT_EQ(graph.FindNode("store")->GetInControlNodesSize(), 1UL);
457+}
458+ 
459+TEST_F(VfPartition, ArangeWithMismatchedVectorizedLayoutIsNotPartitioned) {
460+ af::AscGraph graph("arange_invalid_vectorized_layout");
461+ const auto size = af::Symbol(32);
462+ const auto axis = graph.CreateAxis("z0", size);
463+ af::ascir_op::Arange arange("arange", graph);
464+ arange.ir_attr.SetBase(af::Symbol(0));
465+ arange.ir_attr.SetStep(af::Symbol(1));
466+ arange.y.dtype = af::DT_INT32;
467+ af::ascir_op::Store store("store");
468+ store.x = arange.y;
469+ store.y.dtype = af::DT_INT32;
470+ af::ascir_op::Output output("output");
471+ output.x = store.y;
472+ output.ir_attr.SetIndex(0);
473+ for (const auto &node : graph.GetAllNodes()) {
474+ node->attr.sched.axis = {axis.id};
475+ for (auto &node_output : node->outputs()) {
476+ node_output->attr.axis = {axis.id};
477+ node_output->attr.repeats = {size};
478+ node_output->attr.strides = {One};
479+ node_output->attr.vectorized_axis = {axis.id};
480+ node_output->attr.vectorized_strides = {One};
481+ }
482+ }
483+ graph.FindNode("arange")->outputs[0].attr.vectorized_strides.clear();
484+ optimize::AscGraphInfoComplete::CompleteApiInfo(graph);
485+ 
486+ VectorFuncPartitioner partitioner(graph);
487+ ASSERT_EQ(partitioner.Partition(), af::SUCCESS);
488+ 
489+ std::vector<af::AscGraph> subgraphs;
490+ ASSERT_EQ(graph.GetAllSubGraphs(subgraphs), af::SUCCESS);
491+ EXPECT_TRUE(subgraphs.empty());
492+ EXPECT_NE(graph.FindNode("arange"), nullptr);
493+}
494+ 
495+TEST_F(VfPartition, ArangeVectorFunctionRequiresUnitStride) {
496+ af::AscGraph graph("arange_vf_capability");
497+ const auto size = af::Symbol(32);
498+ const auto axis = graph.CreateAxis("z0", size);
499+ af::ascir_op::Arange arange("arange", graph);
500+ arange.ir_attr.SetBase(af::Symbol(0));
501+ arange.ir_attr.SetStep(af::Symbol(1));
502+ arange.y.dtype = af::DT_INT32;
503+ *arange.y.vectorized_axis = {axis.id};
504+ *arange.y.vectorized_strides = {One};
505+ auto arange_node = graph.FindNode("arange");
506+ ASSERT_NE(arange_node, nullptr);
507+ auto codegen_impl = ascgen_utils::GetAscIrCodegenImpl(arange_node->GetType());
508+ ASSERT_NE(codegen_impl, nullptr);
509+ EXPECT_TRUE(codegen_impl->IsVectorFunctionSupported(*arange_node));
510+ 
511+ arange_node->outputs[0].attr.vectorized_strides = {Zero};
512+ EXPECT_FALSE(codegen_impl->IsVectorFunctionSupported(*arange_node));
513+ arange_node->outputs[0].attr.vectorized_strides = {af::Symbol(2)};
514+ EXPECT_FALSE(codegen_impl->IsVectorFunctionSupported(*arange_node));
515+ arange_node->outputs[0].attr.vectorized_strides.clear();
516+ EXPECT_FALSE(codegen_impl->IsVectorFunctionSupported(*arange_node));
517+}
518+ 
519+TEST_F(VfPartition, ArangeVectorFunctionSupportsContiguousMultiAxisLayout) {
520+ af::AscGraph graph("arange_contiguous_multi_axis");
521+ const auto rows = af::Symbol(4);
522+ const auto cols = af::Symbol(8);
523+ const auto row_axis = graph.CreateAxis("row", rows);
524+ const auto col_axis = graph.CreateAxis("col", cols);
525+ af::ascir_op::Arange arange("arange", graph);
526+ arange.ir_attr.SetBase(af::Symbol(0));
527+ arange.ir_attr.SetStep(af::Symbol(1));
528+ arange.y.dtype = af::DT_INT64;
529+ *arange.y.axis = {row_axis.id, col_axis.id};
530+ *arange.y.repeats = {rows, cols};
531+ *arange.y.strides = {cols, One};
532+ *arange.y.vectorized_axis = {row_axis.id, col_axis.id};
533+ *arange.y.vectorized_strides = {cols, One};
534+ 
535+ auto arange_node = graph.FindNode("arange");
536+ ASSERT_NE(arange_node, nullptr);
537+ auto codegen_impl = ascgen_utils::GetAscIrCodegenImpl(arange_node->GetType());
538+ ASSERT_NE(codegen_impl, nullptr);
539+ EXPECT_TRUE(codegen_impl->IsVectorFunctionSupported(*arange_node));
540+}
541+ 
542+TEST_F(VfPartition, ArangeVectorFunctionSupportsAlignedOuterVectorizedStride) {
543+ af::AscGraph graph("arange_aligned_outer_stride");
544+ const auto rows = af::Symbol(4);
545+ const auto cols = af::Symbol(8);
546+ const auto row_axis = graph.CreateAxis("row", rows);
547+ const auto col_axis = graph.CreateAxis("col", cols);
548+ af::ascir_op::Arange arange("arange", graph);
549+ arange.ir_attr.SetBase(af::Symbol(0));
550+ arange.ir_attr.SetStep(af::Symbol(1));
551+ arange.y.dtype = af::DT_INT64;
552+ *arange.y.axis = {row_axis.id, col_axis.id};
553+ *arange.y.repeats = {rows, cols};
554+ *arange.y.strides = {cols, One};
555+ *arange.y.vectorized_axis = {row_axis.id, col_axis.id};
556+ *arange.y.vectorized_strides = {af::Symbol(16), One};
557+ 
558+ auto arange_node = graph.FindNode("arange");
559+ ASSERT_NE(arange_node, nullptr);
560+ auto codegen_impl = ascgen_utils::GetAscIrCodegenImpl(arange_node->GetType());
561+ ASSERT_NE(codegen_impl, nullptr);
562+ EXPECT_TRUE(codegen_impl->IsVectorFunctionSupported(*arange_node));
563+}
564+ 
565+TEST_F(VfPartition, ArangeVectorFunctionUsesLastNonZeroStride) {
566+ af::AscGraph graph("arange_trailing_singleton");
567+ const auto rows = af::Symbol(8);
568+ const auto row_axis = graph.CreateAxis("row", rows);
569+ const auto singleton_axis = graph.CreateAxis("singleton", One);
570+ af::ascir_op::Arange arange("arange", graph);
571+ arange.ir_attr.SetBase(af::Symbol(0));
572+ arange.ir_attr.SetStep(af::Symbol(1));
573+ arange.y.dtype = af::DT_INT64;
574+ *arange.y.axis = {row_axis.id, singleton_axis.id};
575+ *arange.y.repeats = {rows, One};
576+ *arange.y.strides = {One, Zero};
577+ *arange.y.vectorized_axis = {row_axis.id, singleton_axis.id};
578+ *arange.y.vectorized_strides = {One, Zero};
579+ 
580+ auto arange_node = graph.FindNode("arange");
581+ ASSERT_NE(arange_node, nullptr);
582+ auto codegen_impl = ascgen_utils::GetAscIrCodegenImpl(arange_node->GetType());
583+ ASSERT_NE(codegen_impl, nullptr);
584+ EXPECT_TRUE(codegen_impl->IsVectorFunctionSupported(*arange_node));
585+ 
586+ arange_node->outputs[0].attr.vectorized_strides = {af::Symbol(2), Zero};
587+ EXPECT_FALSE(codegen_impl->IsVectorFunctionSupported(*arange_node));
588+ arange_node->outputs[0].attr.vectorized_strides = {Zero, Zero};
589+ EXPECT_FALSE(codegen_impl->IsVectorFunctionSupported(*arange_node));
590+}
591+ 
592+TEST_F(VfPartition, ArangeWithMoreThanFourUnmergedAxesSupportsPartition) {
593+ af::AscGraph graph("high_rank_arange");
594+ std::vector<af::AxisId> axes;
595+ for (size_t i = 0UL; i < 5UL; ++i) {
596+ axes.push_back(graph.CreateAxis("z" + std::to_string(i), af::Symbol(2)).id);
597+ }
598+ af::ascir_op::Arange arange("arange", graph);
599+ arange.ir_attr.SetBase(af::Symbol(0));
600+ arange.ir_attr.SetStep(af::Symbol(1));
601+ arange.y.dtype = af::DT_INT32;
602+ af::ascir_op::Store store("store");
603+ store.y.dtype = af::DT_INT32;
604+ af::ascir_op::Output output("output");
605+ output.ir_attr.SetIndex(0);
606+ output.y.dtype = af::DT_INT32;
607+ ASSERT_NE(graph.AddNode(store), nullptr);
608+ ASSERT_NE(graph.AddNode(output), nullptr);
609+ store.x = arange.y;
610+ output.x = store.y;
611+ const std::vector<af::Expression> repeats(5UL, af::Symbol(2));
612+ const std::vector<af::Expression> strides = {af::Symbol(16), af::Symbol(8), af::Symbol(4), af::Symbol(2), One};
613+ for (const auto &node : graph.GetAllNodes()) {
614+ node->attr.sched.axis = axes;
615+ for (auto &node_output : node->outputs()) {
616+ node_output->attr.axis = axes;
617+ node_output->attr.repeats = repeats;
618+ node_output->attr.strides = strides;
619+ node_output->attr.vectorized_axis = axes;
620+ node_output->attr.vectorized_strides = strides;
621+ }
622+ }
623+ optimize::AscGraphInfoComplete::CompleteApiInfo(graph);
624+ 
625+ VectorFuncPartitioner partitioner(graph);
626+ ASSERT_EQ(partitioner.Partition(), af::SUCCESS);
627+ EXPECT_EQ(graph.FindNode("arange"), nullptr);
628+ EXPECT_NE(graph.FindNode("store"), nullptr);
629+ EXPECT_NE(graph.FindNode("output"), nullptr);
630+ const auto vf_node = graph.FindNode("high_rank_arange_VfNode_0");
631+ ASSERT_NE(vf_node, nullptr);
632+ EXPECT_EQ(vf_node->GetAllInDataAnchorsSize(), 0UL);
633+ EXPECT_EQ(vf_node->GetAllOutDataAnchorsSize(), 1UL);
634+ std::vector<af::AscGraph> subgraphs;
635+ ASSERT_EQ(graph.GetAllSubGraphs(subgraphs), af::SUCCESS);
636+ ASSERT_EQ(subgraphs.size(), 1UL);
637+ EXPECT_NE(subgraphs[0].FindNode("arange"), nullptr);
638+ const auto store_inputs = graph.FindNode("store")->GetInDataNodes();
639+ ASSERT_EQ(store_inputs.size(), 1UL);
640+ EXPECT_EQ((*store_inputs.begin())->GetName(), "high_rank_arange_VfNode_0");
641+ const auto output_inputs = graph.FindNode("output")->GetInDataNodes();
642+ ASSERT_EQ(output_inputs.size(), 1UL);
643+ EXPECT_EQ((*output_inputs.begin())->GetName(), "store");
644+}
645+ 
646+TEST_F(VfPartition, SingletonArangeIsPartitionedByFullPipeline) {
647+ af::AscGraph graph("singleton_arange");
648+ af::ascir_op::Arange arange("arange", graph);
649+ arange.ir_attr.SetBase(af::Symbol(0));
650+ arange.ir_attr.SetStep(af::Symbol(1));
651+ arange.y.dtype = af::DT_INT32;
652+ af::ascir_op::Store store("store");
653+ store.y.dtype = af::DT_INT32;
654+ af::ascir_op::Output output("output");
655+ output.ir_attr.SetIndex(0);
656+ output.y.dtype = af::DT_INT32;
657+ ASSERT_NE(graph.AddNode(store), nullptr);
658+ ASSERT_NE(graph.AddNode(output), nullptr);
659+ store.x = arange.y;
660+ output.x = store.y;
661+ SetupGraphAxes(graph, {One, One});
662+ ASSERT_EQ(AlignmentHandler::AlignVectorizedStrides(graph), af::SUCCESS);
663+ 
664+ VectorFuncPartitioner partitioner(graph);
665+ ASSERT_EQ(partitioner.Partition(), af::SUCCESS);
666+ 
667+ EXPECT_EQ(graph.FindNode("arange"), nullptr);
668+ EXPECT_NE(graph.FindNode("singleton_arange_VfNode_0"), nullptr);
669+ std::vector<af::AscGraph> subgraphs;
670+ ASSERT_EQ(graph.GetAllSubGraphs(subgraphs), af::SUCCESS);
671+ ASSERT_EQ(subgraphs.size(), 1UL);
672+ EXPECT_NE(subgraphs[0].FindNode("arange"), nullptr);
673+}
674+ 
675+TEST_F(VfPartition, CompareRejectsIndexExprOnlyOnFirstInput) {
676+ const auto is_supported = [](bool index_expr_is_first) {
677+ af::AscGraph graph(index_expr_is_first ? "index_expr_first" : "index_expr_second");
678+ const auto size = af::Symbol(32);
679+ const auto axis = graph.CreateAxis("z0", size);
680+ af::ascir_op::Arange arange("arange", graph);
681+ arange.ir_attr.SetBase(af::Symbol(0));
682+ arange.ir_attr.SetStep(af::Symbol(1));
683+ arange.y.dtype = af::DT_INT32;
684+ af::ascir_op::IndexExpr index_expr("index_expr", graph);
685+ index_expr.ir_attr.SetExpr(af::Symbol(7));
686+ index_expr.y.dtype = af::DT_INT32;
687+ af::ascir_op::Lt compare("compare");
688+ compare.y.dtype = af::DT_UINT8;
689+ EXPECT_NE(graph.AddNode(compare), nullptr);
690+ if (index_expr_is_first) {
691+ compare.x1 = index_expr.y;
692+ compare.x2 = arange.y;
693+ } else {
694+ compare.x1 = arange.y;
695+ compare.x2 = index_expr.y;
696+ }
697+ for (const auto &node : graph.GetAllNodes()) {
698+ if (node->outputs().empty()) {
699+ continue;
700+ }
701+ node->outputs[0].attr.vectorized_axis = {axis.id};
702+ node->outputs[0].attr.vectorized_strides = {One};
703+ }
704+ auto compare_node = graph.FindNode("compare");
705+ EXPECT_NE(compare_node, nullptr);
706+ auto codegen_impl = ascgen_utils::GetAscIrCodegenImpl(compare_node->GetType());
707+ EXPECT_NE(codegen_impl, nullptr);
708+ return codegen_impl->IsVectorFunctionSupported(*compare_node);
709+ };
710+ 
711+ EXPECT_FALSE(is_supported(true));
712+ EXPECT_TRUE(is_supported(false));
713+}
714+ 
715+TEST_F(VfPartition, ArangeZeroInputClusterBuildsSubgraphWithoutDynamicInputs) {
716+ af::AscGraph graph("arange_store_zero_input");
717+ const auto size = af::Symbol(65);
718+ const auto axis = graph.CreateAxis("z0", size);
719+ af::ascir_op::Arange arange("arange");
720+ arange.ir_attr.SetBase(af::Symbol(0));
721+ arange.ir_attr.SetStep(af::Symbol(1));
722+ arange.y.dtype = af::DT_INT32;
723+ af::ascir_op::Store store("store");
724+ store.y.dtype = af::DT_INT32;
725+ af::ascir_op::Output output("output");
726+ ASSERT_NE(graph.AddNode(arange), nullptr);
727+ ASSERT_NE(graph.AddNode(store), nullptr);
728+ ASSERT_NE(graph.AddNode(output), nullptr);
729+ store.x = arange.y;
730+ output.x = store.y;
731+ output.ir_attr.SetIndex(0);
732+ for (const auto &node : graph.GetAllNodes()) {
733+ node->attr.sched.axis = {axis.id};
734+ for (auto &node_output : node->outputs()) {
735+ node_output->attr.axis = {axis.id};
736+ node_output->attr.repeats = {size};
737+ node_output->attr.strides = {One};
738+ node_output->attr.vectorized_axis = {axis.id};
739+ node_output->attr.vectorized_strides = {One};
740+ }
741+ }
742+ optimize::AscGraphInfoComplete::CompleteApiInfo(graph);
743+ 
744+ auto cluster = std::make_shared<Cluster>(graph.FindNode("arange"), 0UL);
745+ cluster->meta_data_.enable_vf = true;
746+ cluster->out_nodes_.insert(graph.FindNode("arange"));
747+ VectorFuncPartitioner partitioner(graph);
748+ partitioner.root_graph_ = af::AscGraphUtils::GetComputeGraph(graph);
749+ partitioner.cluster_dict_.AddCluster(cluster);
750+ ASSERT_EQ(partitioner.BuildSubgraphs(), af::SUCCESS);
751+ 
752+ std::vector<af::AscGraph> subgraphs;
753+ ASSERT_EQ(graph.GetAllSubGraphs(subgraphs), af::SUCCESS);
754+ ASSERT_EQ(subgraphs.size(), 1UL);
755+ const auto vf_node = graph.FindNode("arange_store_zero_input_VfNode_0");
756+ ASSERT_NE(vf_node, nullptr);
757+ EXPECT_EQ(vf_node->GetAllInDataAnchorsSize(), 0UL);
758+ EXPECT_EQ(vf_node->GetAllOutDataAnchorsSize(), 1UL);
759+ EXPECT_NE(subgraphs[0].FindNode("arange"), nullptr);
760+ EXPECT_EQ(subgraphs[0].FindNode("store"), nullptr);
761+}
762+ 
763+TEST_F(VfPartition, IndexExprBoundaryBuildsScalarPlaceholder) {
764+ af::AscGraph graph("index_expr_boundary");
765+ af::ascir_op::IndexExpr index_expr("index_expr", graph);
766+ index_expr.ir_attr.SetExpr(af::Symbol(7));
767+ index_expr.y.dtype = af::DT_INT32;
768+ af::ascir_op::Add add("add");
769+ add.y.dtype = af::DT_INT32;
770+ ASSERT_NE(graph.AddNode(add), nullptr);
771+ add.x1 = index_expr.y;
772+ 
773+ auto index_node = graph.FindNode("index_expr");
774+ auto add_node = graph.FindNode("add");
775+ ASSERT_NE(index_node, nullptr);
776+ ASSERT_NE(add_node, nullptr);
777+ af::AscGraph vf_graph("index_expr_boundary_vf");
778+ ASSERT_EQ(VectorFuncPartitioner::InsertScalarNode(vf_graph, index_node->GetOutDataAnchor(0),
779+ {add_node->GetInDataAnchor(0)}, 0),
780+ af::SUCCESS);
781+ 
782+ auto scalar_node = vf_graph.FindNode("Scalar_index_expr");
783+ ASSERT_NE(scalar_node, nullptr);
784+ int64_t index = -1;
785+ ASSERT_EQ(scalar_node->attr.ir_attr->GetAttrValue("index", index), af::GRAPH_SUCCESS);
786+ EXPECT_EQ(index, 0);
787+}
788+ 
789+TEST_F(VfPartition, IndexExprArangeCompositionPreservesStoreOutputBoundary) {
790+ auto graph = BuildIndexExprArangeCompositionGraph();
791+ ASSERT_EQ(AlignmentHandler::AlignVectorizedStrides(graph), af::SUCCESS);
792+ 
793+ VectorFuncPartitioner partitioner(graph);
794+ ASSERT_EQ(partitioner.Partition(), af::SUCCESS);
795+ 
796+ // Partition 阶段不做 FinalizeIndexedGraphs 的图名重排(_B0Y0_S0G0C0 后缀由 Optimize
797+ // 主流程追加), 此处使用 Partition 直接产出的原始 VF 节点名。
798+ const auto vf_node = graph.FindNode("index_expr_arange_composition_python_VfNode_0");
799+ ASSERT_NE(vf_node, nullptr);
800+ ASSERT_EQ(vf_node->GetAllOutDataAnchorsSize(), 1UL);
801+ const auto peers = vf_node->GetOutDataAnchor(0)->GetPeerInDataAnchors();
802+ ASSERT_EQ(peers.size(), 1UL);
803+ EXPECT_EQ((*peers.begin())->GetOwnerNodeBarePtr()->GetName(), "store_0");
804+ std::vector<af::AscGraph> subgraphs;
805+ ASSERT_EQ(graph.GetAllSubGraphs(subgraphs), af::SUCCESS);
806+ ASSERT_EQ(subgraphs.size(), 1UL);
807+ EXPECT_EQ(subgraphs[0].FindNode("index_expr_arange_composition_python_VfNode_0"), nullptr);
808+ // 子图出口副本命名: kNamePrefix + 源节点名 + parent_out_idx(无分隔符拼接)。
809+ EXPECT_NE(subgraphs[0].FindNode("Store_add_10"), nullptr);
810+ EXPECT_NE(subgraphs[0].FindNode("Output_add_10"), nullptr);
811+ 
812+ std::vector<int64_t> tensor_ids;
813+ for (const auto &node : subgraphs[0].GetAllNodes()) {
814+ if (ScheduleUtils::IsBuffer(node)) {
815+ continue;
816+ }
817+ for (const auto &node_output : node->outputs()) {
818+ tensor_ids.push_back(node_output->attr.mem.tensor_id);
819+ }
820+ }
821+ std::sort(tensor_ids.begin(), tensor_ids.end());
822+ for (size_t i = 0UL; i < tensor_ids.size(); ++i) {
823+ EXPECT_EQ(tensor_ids[i], static_cast<int64_t>(i));
824+ }
825+}
826+ 
343TEST_F(VfPartition, brc_with_cycle) {827TEST_F(VfPartition, brc_with_cycle) {
344 af::AscGraph graph("brc_abs");828 af::AscGraph graph("brc_abs");
345 auto s0 = af::Symbol(10);829 auto s0 = af::Symbol(10);
@@ -1854,6 +2338,67 @@ TEST_F(VfPartition, tail_axis_stride_not_one_disable_vf) {
1854 EXPECT_EQ(sub_graphs.size(), 0UL);2338 EXPECT_EQ(sub_graphs.size(), 0UL);
1855}2339}
1856 2340 
2341+TEST_F(VfPartition, OriginalTailStrideDoesNotDisableContinuousVectorizedVf) {
2342+ af::AscGraph graph("original_tail_stride_test");
2343+ const auto s0 = af::Symbol(10);
2344+ const auto s1 = af::Symbol(10);
2345+ const auto axis0 = graph.CreateAxis("z0", s0);
2346+ const auto axis1 = graph.CreateAxis("z1", s1);
2347+ af::ascir_op::Data data("data", graph);
2348+ data.ir_attr.SetIndex(0);
2349+ af::ascir_op::Load load("load");
2350+ load.x = data.y;
2351+ load.y.dtype = ge::DT_FLOAT;
2352+ af::ascir_op::Abs abs("abs");
2353+ abs.x = load.y;
2354+ abs.y.dtype = ge::DT_FLOAT;
2355+ optimize::AscGraphInfoComplete::CompleteApiInfo(graph);
2356+ 
2357+ auto abs_node = graph.FindNode("abs");
2358+ ASSERT_NE(abs_node, nullptr);
2359+ abs_node->outputs[0].attr.axis = {axis0.id, axis1.id};
2360+ abs_node->outputs[0].attr.repeats = {s0, s1};
2361+ abs_node->outputs[0].attr.strides = {s1 * af::Symbol(2), af::Symbol(2)};
2362+ abs_node->outputs[0].attr.vectorized_axis = {axis0.id, axis1.id};
2363+ abs_node->outputs[0].attr.vectorized_strides = {s1, One};
2364+ 
2365+ auto cluster = std::make_shared<Cluster>(abs_node, 0UL);
2366+ cluster->meta_data_.enable_vf = true;
2367+ auto codegen_impl = ascgen_utils::GetAscIrCodegenImpl(abs_node->GetType());
2368+ ASSERT_NE(codegen_impl, nullptr);
2369+ ASSERT_EQ(VectorFuncPartitioner::InitClusterAttr(codegen_impl, abs_node, cluster), af::SUCCESS);
2370+ EXPECT_TRUE(cluster->meta_data_.enable_vf);
2371+}
2372+ 
2373+TEST_F(VfPartition, EmptyVectorizedAxisDoesNotSkipTensorId) {
2374+ af::AscGraph graph("empty_vectorized_axis_tensor_id");
2375+ const auto size = af::Symbol(16);
2376+ const auto axis = graph.CreateAxis("z0", size);
2377+ af::ascir_op::Data data("data", graph);
2378+ data.ir_attr.SetIndex(0);
2379+ af::ascir_op::Load load("load");
2380+ load.x = data.y;
2381+ load.y.dtype = ge::DT_FLOAT;
2382+ af::ascir_op::Abs abs("abs");
2383+ abs.x = load.y;
2384+ abs.y.dtype = ge::DT_FLOAT;
2385+ optimize::AscGraphInfoComplete::CompleteApiInfo(graph);
2386+ 
2387+ auto load_node = graph.FindNode("load");
2388+ auto abs_node = graph.FindNode("abs");
2389+ ASSERT_NE(load_node, nullptr);
2390+ ASSERT_NE(abs_node, nullptr);
2391+ load_node->outputs[0].attr.vectorized_axis.clear();
2392+ load_node->outputs[0].attr.vectorized_strides.clear();
2393+ abs_node->outputs[0].attr.vectorized_axis = {axis.id};
2394+ abs_node->outputs[0].attr.vectorized_strides = {One};
2395+ 
2396+ VectorFuncPartitioner partitioner(graph);
2397+ ASSERT_EQ(partitioner.SetSubGraphAttrs(graph), af::SUCCESS);
2398+ EXPECT_EQ(load_node->outputs[0].attr.mem.tensor_id, 0);
2399+ EXPECT_EQ(abs_node->outputs[0].attr.mem.tensor_id, 1);
2400+}
2401+ 
1857TEST_F(VfPartition, topological_sort_for_vf_graph_keeps_load_before_consumer) {2402TEST_F(VfPartition, topological_sort_for_vf_graph_keeps_load_before_consumer) {
1858 af::AscGraph graph("vf_sort_keep_dependency");2403 af::AscGraph graph("vf_sort_keep_dependency");
1859 auto z0 = graph.CreateAxis("z0", af::Symbol(2));2404 auto z0 = graph.CreateAxis("z0", af::Symbol(2));
@@ -489,11 +489,20 @@ REG_ASC_IR(ScalarData)
489 {{"T", TensorType{DT_INT8, DT_UINT8, DT_INT16, DT_UINT16, DT_INT32, DT_UINT32, DT_INT64,489 {{"T", TensorType{DT_INT8, DT_UINT8, DT_INT16, DT_UINT16, DT_INT32, DT_UINT32, DT_INT64,
490 DT_UINT64, DT_FLOAT16, DT_FLOAT, DT_BF16, DT_BOOL}}}});490 DT_UINT64, DT_FLOAT16, DT_FLOAT, DT_BF16, DT_BOOL}}}});
491 491 
492-REG_ASC_IR(IndexExpr).Impl(v2_soc_versions,492+REG_ASC_IR(IndexExpr).Impl(v2_soc_versions, {af::ascir::AscIrImplCreator<af::ascir::IndexExprAscIrAttImplV2>(),
493- {af::ascir::AscIrImplCreator<af::ascir::IndexExprAscIrAttImplV2>(),493+ af::ascir::AscIrImplCreator<af::ascir::IndexExprAscIrCodegenImplV2>(),
494- af::ascir::AscIrImplCreator<af::ascir::IndexExprAscIrCodegenImplV2>(),494+ {{"T", TensorType{DT_INT32, DT_INT64}}}});
495- {{"T", TensorType{DT_INT8, DT_UINT8, DT_INT16, DT_UINT16, DT_INT32, DT_UINT32, DT_INT64,495+ 
496- DT_UINT64, DT_FLOAT16, DT_FLOAT}}}});496+REG_ASC_IR(Arange)
Z

新增ASCIR需要提供一下典型Shape的性能测试,是否涉及外抛for循环,是否会导致Vector bound

likedislike
Jett_Woo
12 天前 评论:
Jett_Woo
12 天前 评论:
497+ .Inputs({})
498+ .Output("y", "T")
499+ .StartNode()
500+ .Attr<Expression>("base")
501+ .Attr<Expression>("step")
502+ .ComputeType(ComputeType::kComputeElewise)
503+ .Impl(v2_soc_versions, {af::ascir::AscIrImplCreator<af::ascir::AscIrAttStubV2>(),
504+ af::ascir::AscIrImplCreator<af::ascir::ArangeAscIrCodegenImplV2>(),
505+ {{"T", TensorType{DT_INT32, DT_INT64}}}});
497 506 
498REG_ASC_IR(Output).Impl(v2_soc_versions,507REG_ASC_IR(Output).Impl(v2_soc_versions,
499 {af::ascir::AscIrImplCreator<af::ascir::OutputAscIrAttImplV2>(),508 {af::ascir::AscIrImplCreator<af::ascir::OutputAscIrAttImplV2>(),
@@ -206,6 +206,60 @@ class IndexExprAscIrCodegenImplV2 : public AscIrCodegenV2 {
206 }206 }
207};207};
208 208 
209+class ArangeAscIrCodegenImplV2 : public AscIrCodegenV2 {
210+ public:
211+ [[nodiscard]] bool IsVectorFunctionSupported(const AscNode &node) const override {
212+ auto &mutable_node = const_cast<AscNode &>(node);
213+ if (mutable_node.outputs().size() != 1UL) {
214+ return false;
215+ }
216+ const auto &output = mutable_node.outputs[0].attr;
217+ if (output.dtype != DT_INT32 && output.dtype != DT_INT64) {
218+ return false;
219+ }
220+ if (output.vectorized_axis.empty() || output.vectorized_axis.size() != output.vectorized_strides.size()) {
221+ return false;
222+ }
223+ for (auto iter = output.vectorized_strides.rbegin(); iter != output.vectorized_strides.rend(); ++iter) {
224+ const auto stride = iter->Simplify();
225+ if (af::SymbolicUtils::StaticCheckEq(stride, af::sym::kSymbolZero) == af::TriBool::kTrue) {
226+ continue;
227+ }
228+ return af::SymbolicUtils::StaticCheckEq(stride, af::sym::kSymbolOne) == af::TriBool::kTrue;
229+ }
230+ for (size_t i = 0UL; i < output.vectorized_axis.size(); ++i) {
231+ const auto axis_iter = std::find(output.axis.begin(), output.axis.end(), output.vectorized_axis[i]);
232+ if (axis_iter == output.axis.end()) {
233+ return false;
234+ }
235+ const auto axis_index = static_cast<size_t>(std::distance(output.axis.begin(), axis_iter));
236+ if (axis_index >= output.repeats.size() ||
237+ af::SymbolicUtils::StaticCheckEq(output.repeats[axis_index].Simplify(), af::sym::kSymbolOne) !=
238+ af::TriBool::kTrue) {
239+ return false;
240+ }
241+ }
242+ return true;
243+ }
244+ [[nodiscard]] std::string GetApiCallName() const override {
245+ return "";
246+ }
247+ [[nodiscard]] std::string GetApiName() const override {
248+ return "Arange";
249+ }
250+ [[nodiscard]] std::string GetMicroApiCallName() const override {
251+ return "MicroArangeApiCall";
252+ }
253+ [[nodiscard]] std::string GetMicroApiName() const override {
254+ return "Arange";
255+ }
256+ [[nodiscard]] std::vector<std::string> IncludeApiHeaderFiles() const override {
257+ return {
258+ "basic_api/reg_compute/kernel_reg_compute_intf.h",
259+ };
260+ }
261+};
262+ 
209class OutputAscIrCodegenImplV2 : public AscIrCodegenV2 {263class OutputAscIrCodegenImplV2 : public AscIrCodegenV2 {
210 public:264 public:
211 [[nodiscard]] std::string GetApiCallName() const override {265 [[nodiscard]] std::string GetApiCallName() const override {
@@ -2645,7 +2699,8 @@ class CompareAscIrCodegenImplV2 : public AscIrCodegenV2 {
2645 if (!IsAllVecAxisContinuous(node)) {2699 if (!IsAllVecAxisContinuous(node)) {
2646 return false;2700 return false;
2647 }2701 }
2648- if (node.GetInDataNodes().at(0)->GetType() == "Scalar") {2702+ const auto first_input_type = node.GetInDataNodes().at(0)->GetType();
2703+ if (first_input_type == "Scalar" || first_input_type == "IndexExpr") {
2649 return false;2704 return false;
2650 }2705 }
2651 return true;2706 return true;
@@ -17,10 +17,17 @@
17#include "codegen_kernel.h"17#include "codegen_kernel.h"
18namespace codegen {18namespace codegen {
19 19 
20+struct ArangeParams {
21+ bool valid = false;
22+ std::string base;
23+ std::string step;
24+};
25+ 
20struct CallParam {26struct CallParam {
21 std::string p_reg;27 std::string p_reg;
22 std::string offset;28 std::string offset;
23 std::string max_dtype_size;29 std::string max_dtype_size;
30+ ArangeParams arange;
24};31};
25 32 
26enum class TensorType : int8_t {33enum class TensorType : int8_t {
@@ -68,6 +75,10 @@ class MicroApiCall {
68 75 
69 // 生成micro api的调用76 // 生成micro api的调用
70 virtual Status Generate(const TensorManager &tensor_mng, const TPipe &tpipe, CallParam &param, std::string &result);77 virtual Status Generate(const TensorManager &tensor_mng, const TPipe &tpipe, CallParam &param, std::string &result);
78+ virtual bool HasArangeParam() const {
79+ return false;
80+ }
81+ virtual void GetArangeParams(const TPipe &, std::string &, std::string &) const {}
71 82 
72 // 生成outputs;83 // 生成outputs;
73 virtual Status Init([[maybe_unused]] const ascir::NodeView &node) {84 virtual Status Init([[maybe_unused]] const ascir::NodeView &node) {
@@ -0,0 +1,67 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+#include "micro_arange_api_call.h"
11+ 
12+#include <sstream>
13+ 
14+#include "common/checker.h"
15+#include "micro_api_call_factory.h"
16+ 
17+namespace codegen {
18+Status MicroArangeApiCall::Init(const ascir::NodeView &node) {
19+ GE_CHK_STATUS_RET(MicroApiCall::Init(node));
20+ GE_ASSERT_TRUE(node->outputs[0].attr.dtype == ge::DT_INT32 || node->outputs[0].attr.dtype == ge::DT_INT64,
21+ "Arange supports only int32 and int64, dtype:%d", static_cast<int32_t>(node->outputs[0].attr.dtype));
22+ GE_CHK_GRAPH_STATUS_RET(node->attr.ir_attr->GetAttrValue("base", base_), "Failed to get Arange base attr");
23+ GE_CHK_GRAPH_STATUS_RET(node->attr.ir_attr->GetAttrValue("step", step_), "Failed to get Arange step attr");
24+ return af::SUCCESS;
25+}
26+ 
27+void MicroArangeApiCall::GetArangeParams(const TPipe &tpipe, std::string &base, std::string &step) const {
28+ base = tpipe.tiler.ActualSize(base_);
29+ step = tpipe.tiler.ActualSize(step_);
30+}
31+ 
32+Status MicroArangeApiCall::Generate(const TensorManager &tensor_mng, const TPipe &tpipe, CallParam &param,
33+ std::string &result) {
34+ GE_ASSERT_TRUE(outputs_.size() == 1U, "Arange micro api call must have one output");
35+ const auto *output = tensor_mng.GetTensor(outputs_[0].second);
36+ GE_ASSERT_NOTNULL(output);
37+ 
38+ std::string dtype_name;
39+ GE_CHK_STATUS_RET(Tensor::DtypeName(output->dtype_, dtype_name), "Get Arange dtype:%d failed",
40+ static_cast<int32_t>(output->dtype_));
41+ const auto base = tpipe.tiler.ActualSize(base_);
42+ const auto step = tpipe.tiler.ActualSize(step_);
43+ GE_ASSERT_TRUE(!base.empty() && !step.empty(), "Failed to generate Arange base or step expression");
44+ const std::string block_offset = param.offset.empty() ? "0" : param.offset;
45+ const auto vf_base = param.arange.valid ? param.arange.base : base;
46+ const auto vf_step = param.arange.valid ? param.arange.step : step;
47+ GE_ASSERT_TRUE(!vf_base.empty() && !vf_step.empty(), "Failed to generate Arange base or step expression");
48+ const std::string block_base =
49+ param.offset.empty() ? vf_base : "(" + vf_base + " + (" + block_offset + ") * (" + vf_step + "))";
50+ 
51+ std::stringstream ss;
52+ if (af::SymbolicUtils::StaticCheckEq(step_, af::sym::kSymbolOne) == af::TriBool::kTrue) {
53+ ss << "AscendC::Reg::Arange(" << output->name << ", static_cast<" << dtype_name << ">(" << block_base << "));"
54+ << std::endl;
55+ } else {
56+ ss << "AscendC::Reg::Arange(" << output->name << ", static_cast<" << dtype_name << ">(0));" << std::endl;
57+ ss << "AscendC::Reg::Muls(" << output->name << ", " << output->name << ", static_cast<" << dtype_name << ">("
58+ << vf_step << "), " << param.p_reg << ");" << std::endl;
59+ ss << "AscendC::Reg::Adds(" << output->name << ", " << output->name << ", static_cast<" << dtype_name << ">("
60+ << block_base << "), " << param.p_reg << ");" << std::endl;
61+ }
62+ result = ss.str();
63+ return af::SUCCESS;
64+}
65+ 
66+static MicroApiCallRegister<MicroArangeApiCall> register_micro_arange_api_call("MicroArangeApiCall");
67+} // namespace codegen
@@ -0,0 +1,34 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+#ifndef AUTOFUSE_MICRO_ARANGE_API_CALL_H
11+#define AUTOFUSE_MICRO_ARANGE_API_CALL_H
12+ 
13+#include "micro_api_call.h"
14+ 
15+namespace codegen {
16+class MicroArangeApiCall final : public MicroApiCall {
17+ public:
18+ explicit MicroArangeApiCall(const std::string &api_name) : MicroApiCall(api_name) {}
19+ ~MicroArangeApiCall() final = default;
20+ 
21+ Status Init(const ascir::NodeView &node) override;
22+ Status Generate(const TensorManager &tensor_mng, const TPipe &tpipe, CallParam &param, std::string &result) override;
23+ bool HasArangeParam() const override {
24+ return true;
25+ }
26+ void GetArangeParams(const TPipe &tpipe, std::string &base, std::string &step) const override;
27+ 
28+ private:
29+ af::Expression base_;
30+ af::Expression step_;
31+};
32+} // namespace codegen
33+ 
34+#endif // AUTOFUSE_MICRO_ARANGE_API_CALL_H
@@ -15,7 +15,8 @@
15namespace codegen {15namespace codegen {
16namespace {16namespace {
17bool IsScalarNodeType(const std::string &node_type) {17bool IsScalarNodeType(const std::string &node_type) {
18- return node_type == af::ascir_op::Scalar::Type || node_type == af::ascir_op::ScalarData::Type;18+ return node_type == af::ascir_op::Scalar::Type || node_type == af::ascir_op::ScalarData::Type ||
19+ node_type == af::ascir_op::IndexExpr::Type;
19}20}
20 21 
21Status GenerateInput(const std::pair<TensorType, ascir::TensorId> &input, const codegen::TensorManager &tensor_mng,22Status GenerateInput(const std::pair<TensorType, ascir::TensorId> &input, const codegen::TensorManager &tensor_mng,
@@ -26,15 +26,16 @@ Status MicroCompareApiCall::Generate(const codegen::TensorManager &tensor_mng, [
26 // Compare 输出到 MaskReg(实际输出或临时)26 // Compare 输出到 MaskReg(实际输出或临时)
27 std::string output_name = is_mask_reg ? output_tensor->name : (output_tensor->name + "_temp_mask");27 std::string output_name = is_mask_reg ? output_tensor->name : (output_tensor->name + "_temp_mask");
28 28 
29- auto dtype = tensor_mng.GetTensor(this->inputs_[0].second)->dtype_;29+ const auto *first_input = tensor_mng.GetTensor(this->inputs_[0].second);
30+ GE_ASSERT_NOTNULL(first_input);
31+ auto dtype = first_input->dtype_;
30 string dtype_name;32 string dtype_name;
31 Tensor::DtypeName(dtype, dtype_name);33 Tensor::DtypeName(dtype, dtype_name);
32 ss << "AscendC::MicroAPI::" << "Compare" << (this->second_input_scalar_ ? "s" : "");34 ss << "AscendC::MicroAPI::" << "Compare" << (this->second_input_scalar_ ? "s" : "");
33 ss << "<" << dtype_name << ", CMPMODE::" << this->api_name_ << ">(";35 ss << "<" << dtype_name << ", CMPMODE::" << this->api_name_ << ">(";
34 ss << output_name << ", ";36 ss << output_name << ", ";
35 37 
36- GE_ASSERT_NOTNULL(tensor_mng.GetTensor(this->inputs_[0].second));38+ ss << *first_input << ", ";
37- ss << *(tensor_mng.GetTensor(this->inputs_[0].second)) << ", ";
38 if (inputs_[1].first != TensorType::REG_TENSOR) {39 if (inputs_[1].first != TensorType::REG_TENSOR) {
39 GE_ASSERT_NOTNULL(tpipe.GetTensor(this->inputs_[1].second));40 GE_ASSERT_NOTNULL(tpipe.GetTensor(this->inputs_[1].second));
40 ss << *(tpipe.GetTensor(inputs_[1].second)) << ", ";41 ss << *(tpipe.GetTensor(inputs_[1].second)) << ", ";
@@ -57,7 +58,8 @@ Status MicroCompareApiCall::Generate(const codegen::TensorManager &tensor_mng, [
57 58 
58Status MicroCompareApiCall::Init(const ascir::NodeView &node) {59Status MicroCompareApiCall::Init(const ascir::NodeView &node) {
59 // 判断第二个输入是否是scalar60 // 判断第二个输入是否是scalar
60- if (node->GetInDataNodes().at(1)->GetType() == "Scalar") {61+ if (node->GetInDataNodes().at(1)->GetType() == "Scalar" ||
62+ node->GetInDataNodes().at(1)->GetType() == af::ascir_op::IndexExpr::Type) {
61 this->second_input_scalar_ = true;63 this->second_input_scalar_ = true;
62 }64 }
63 GELOGI("name:%s, second input scalar:%d", node->GetNamePtr(), this->second_input_scalar_);65 GELOGI("name:%s, second input scalar:%d", node->GetNamePtr(), this->second_input_scalar_);
@@ -184,17 +184,22 @@ void CreateDimAndStrideParmas(const TPipe &tpipe, const VectorizedAxisLoopMergeS
184void CreateVFCall(const TPipe &tpipe, const std::string &vf_call_name, const std::vector<Tensor> &inputs,184void CreateVFCall(const TPipe &tpipe, const std::string &vf_call_name, const std::vector<Tensor> &inputs,
185 const std::vector<Tensor> &outputs, const std::vector<std::string> &input_ub_offsets,185 const std::vector<Tensor> &outputs, const std::vector<std::string> &input_ub_offsets,
186 const std::vector<std::string> &output_ub_offsets, const std::vector<Tensor> &tensors_scalar,186 const std::vector<std::string> &output_ub_offsets, const std::vector<Tensor> &tensors_scalar,
187- const VectorizedAxisLoopMergeStatus &merge_info, std::stringstream &ss) {187+ const VectorizedAxisLoopMergeStatus &merge_info, const std::vector<ArangeParam> &arange_params,
188+ std::stringstream &ss) {
188 ss << vf_call_name << "(";189 ss << vf_call_name << "(";
189 CreateTensorAddr(outputs, output_ub_offsets, {}, ss);190 CreateTensorAddr(outputs, output_ub_offsets, {}, ss);
190 CreateTensorAddr(inputs, input_ub_offsets, tensors_scalar, ss);191 CreateTensorAddr(inputs, input_ub_offsets, tensors_scalar, ss);
191 CreateDimAndStrideParmas(tpipe, merge_info, ss);192 CreateDimAndStrideParmas(tpipe, merge_info, ss);
193+ for (const auto &param : arange_params) {
194+ ss << ", " << param.offset << ", " << param.base << ", " << param.step;
195+ }
192 ss << ");" << std::endl;196 ss << ");" << std::endl;
193}197}
194 198 
195void CreateOuterForVFCall(const TPipe &tpipe, const std::string &vf_call_name, const std::vector<Tensor> &inputs,199void CreateOuterForVFCall(const TPipe &tpipe, const std::string &vf_call_name, const std::vector<Tensor> &inputs,
196 const std::vector<Tensor> &outputs, const std::vector<Tensor> &tensors_scalar,200 const std::vector<Tensor> &outputs, const std::vector<Tensor> &tensors_scalar,
197- const VectorizedAxisLoopMergeStatus &merge_info, std::stringstream &ss) {201+ const VectorizedAxisLoopMergeStatus &merge_info,
202+ const std::vector<ArangeParam> &arange_params, std::stringstream &ss) {
198 std::vector<std::string> repeats(merge_info.merge_repeats_str.begin(),203 std::vector<std::string> repeats(merge_info.merge_repeats_str.begin(),
199 merge_info.merge_repeats_str.end() - kVFMaxLoop);204 merge_info.merge_repeats_str.end() - kVFMaxLoop);
200 std::vector<std::vector<ascir::SizeExpr>> inputs_strides;205 std::vector<std::vector<ascir::SizeExpr>> inputs_strides;
@@ -207,7 +212,7 @@ void CreateOuterForVFCall(const TPipe &tpipe, const std::string &vf_call_name, c
207 GetOuterForOffset(tpipe, outputs_strides, outputs_ub_offsets);212 GetOuterForOffset(tpipe, outputs_strides, outputs_ub_offsets);
208 std::stringstream ss1;213 std::stringstream ss1;
209 CreateVFCall(tpipe, vf_call_name, inputs, outputs, inputs_ub_offsets, outputs_ub_offsets, tensors_scalar, merge_info,214 CreateVFCall(tpipe, vf_call_name, inputs, outputs, inputs_ub_offsets, outputs_ub_offsets, tensors_scalar, merge_info,
210- ss1);215+ arange_params, ss1);
211 CreateComputeNodeOuterFor(repeats, ss1, ss, 0);216 CreateComputeNodeOuterFor(repeats, ss1, ss, 0);
212}217}
213 218 
@@ -300,9 +305,35 @@ Status VfCall::ParseSubGraph(const ascir::NodeView &vf_node, const ascir::ImplGr
300 GELOGI("VF node:%s, sub_graph_name:%s", vf_node->GetNamePtr(), graph_name->c_str());305 GELOGI("VF node:%s, sub_graph_name:%s", vf_node->GetNamePtr(), graph_name->c_str());
301 306 
302 uint32_t max_dtype_size = 0;307 uint32_t max_dtype_size = 0;
308+ VFInputMapping input_mapping;
303 for (auto node : sub_graph.GetAllNodes()) {309 for (auto node : sub_graph.GetAllNodes()) {
304 // subgraph上的Load api直接使用Tpipe上保存的UB tensor, 因此vf子图上Data节点的输出Tensor不必保存在tensor manager中.310 // subgraph上的Load api直接使用Tpipe上保存的UB tensor, 因此vf子图上Data节点的输出Tensor不必保存在tensor manager中.
305- if (IsOps<Output>(node) || IsOps<Data>(node) || IsOps<Scalar>(node)) {311+ if (IsOps<Output>(node) || IsOps<Data>(node) || IsOps<Scalar>(node) || IsOps<ScalarData>(node) ||
312+ IsOps<IndexExpr>(node)) {
313+ if (IsOps<Scalar>(node)) {
314+ const auto scalar_tensor_id = node->outputs[0].attr.mem.tensor_id;
315+ if (scalar_tensor_id != af::kIdNone) {
316+ subgraph_scalar_ids_.emplace_back(scalar_tensor_id);
317+ }
318+ }
319+ if (IsOps<Data>(node) || IsOps<ScalarData>(node)) {
320+ int64_t boundary_index = 0;
321+ int64_t root_input_index = 0;
322+ GE_CHK_GRAPH_STATUS_RET(node->attr.ir_attr->GetAttrValue("index", boundary_index),
323+ "Get boundary index failed, node:%s", node->GetNamePtr());
324+ if (!af::AttrUtils::GetInt(node->GetOpDesc(), "vf_root_input_index", root_input_index)) {
325+ // vf_root_input_index 由 VectorFuncPartitioner 在建边后写入; 缺失说明该子图未经过分区
326+ // (如手工构造的UT fixture)。回退用 boundary index, 对"按序连接边界"的子图语义等价。
327+ GELOGW("Node [%s] lacks vf_root_input_index; fall back to boundary index [%ld].", node->GetNamePtr(),
328+ boundary_index);
329+ root_input_index = boundary_index;
330+ }
331+ GE_CHK_BOOL_RET_STATUS(
332+ root_input_index >= 0 && static_cast<uint64_t>(root_input_index) < vf_node->inputs.Size(), af::FAILED,
333+ "Invalid root input index:%ld for node:%s", root_input_index, node->GetNamePtr());
334+ GE_CHK_BOOL_RET_STATUS(input_mapping.emplace(boundary_index, static_cast<uint32_t>(root_input_index)).second,
335+ af::FAILED, "Duplicate root input mapping for boundary index:%ld", boundary_index);
336+ }
306 continue;337 continue;
307 }338 }
308 // broadcast inline场景,子图内会对轴进行重排序,生成vf代码时,需要找到原始最内层轴所在的位置,用于生成UpdateMask动作339 // broadcast inline场景,子图内会对轴进行重排序,生成vf代码时,需要找到原始最内层轴所在的位置,用于生成UpdateMask动作
@@ -328,7 +359,7 @@ Status VfCall::ParseSubGraph(const ascir::NodeView &vf_node, const ascir::ImplGr
328 }359 }
329 root_loop_.SetMaxDtypeSize(this->max_dtype_size_);360 root_loop_.SetMaxDtypeSize(this->max_dtype_size_);
330 // Parse for loop361 // Parse for loop
331- return root_loop_.ConstructFromNodes(sub_graph.GetAllNodes(), vf_node);362+ return root_loop_.ConstructFromNodes(sub_graph.GetAllNodes(), vf_node, input_mapping);
332}363}
333 364 
334Status VfCall::ParseInputOutputInfo(const TPipe &tpipe) const {365Status VfCall::ParseInputOutputInfo(const TPipe &tpipe) const {
@@ -347,6 +378,18 @@ Status VfCall::ParseInputOutputInfo(const TPipe &tpipe) const {
347 GE_CHK_BOOL_RET_STATUS(tensor_ptr != nullptr, af::FAILED, "Check[Param] tensor_ptr is nullptr");378 GE_CHK_BOOL_RET_STATUS(tensor_ptr != nullptr, af::FAILED, "Check[Param] tensor_ptr is nullptr");
348 ub_outputs_.emplace_back(*tensor_ptr);379 ub_outputs_.emplace_back(*tensor_ptr);
349 }380 }
381+ 
382+ // VF子图内消费的常量Scalar副本不是根图边界输入, 其const tensor需单独补入scalar参数。
383+ for (const auto scalar_id : subgraph_scalar_ids_) {
384+ bool already_collected = false;
385+ for (const auto &scalar : scalar_inputs_) {
386+ already_collected = already_collected || (scalar.id == scalar_id);
387+ }
388+ const auto tensor_ptr = already_collected ? nullptr : tpipe.GetTensor(scalar_id);
389+ if ((tensor_ptr != nullptr) && tensor_ptr->IsConstScalar()) {
390+ scalar_inputs_.emplace_back(*tensor_ptr);
391+ }
392+ }
350 // 处理合轴信息393 // 处理合轴信息
351 return af::SUCCESS;394 return af::SUCCESS;
352}395}
@@ -448,12 +491,19 @@ void GenerateTensorDefs(const TPipe &tpipe, const TensorManager &tensor_mgr, con
448 491 
449void GenerateVfCallFuncHeader(const TPipe &tpipe, const std::string &vf_call_name, const std::vector<Tensor> &inputs,492void GenerateVfCallFuncHeader(const TPipe &tpipe, const std::string &vf_call_name, const std::vector<Tensor> &inputs,
450 const std::vector<Tensor> &scalar_inputs, const std::vector<Tensor> &outputs,493 const std::vector<Tensor> &scalar_inputs, const std::vector<Tensor> &outputs,
451- const VectorizedAxisLoopMergeStatus &merge_info, std::stringstream &ss) {494+ const VectorizedAxisLoopMergeStatus &merge_info,
495+ const std::vector<ArangeParam> &arange_params, std::stringstream &ss) {
452 ss << "#if defined(__DAV_C310__) || "496 ss << "#if defined(__DAV_C310__) || "
453 "(defined(__NPU_ARCH__) && (__NPU_ARCH__ == 5102 || __NPU_ARCH__ == 3510 || __NPU_ARCH__ == 9202))"497 "(defined(__NPU_ARCH__) && (__NPU_ARCH__ == 5102 || __NPU_ARCH__ == 3510 || __NPU_ARCH__ == 9202))"
454 << std::endl;498 << std::endl;
455 ss << "\ninline __simd_vf__ void " << vf_call_name << "(";499 ss << "\ninline __simd_vf__ void " << vf_call_name << "(";
456 CreateVFCallDimAndStrideParmas(tpipe, inputs, scalar_inputs, outputs, merge_info, ss);500 CreateVFCallDimAndStrideParmas(tpipe, inputs, scalar_inputs, outputs, merge_info, ss);
501+ if (!arange_params.empty()) {
502+ for (const auto &arange_param : arange_params) {
503+ ss << ", int64_t arange_offset_" << arange_param.tensor_id << ", int64_t arange_base_" << arange_param.tensor_id
504+ << ", int64_t arange_step_" << arange_param.tensor_id;
505+ }
506+ }
457 ss << ")" << std::endl;507 ss << ")" << std::endl;
458}508}
459 509 
@@ -510,9 +560,18 @@ Status GenerateVfCallLoopBody(const TPipe &tpipe, const TensorManager &tensor_mg
510 int32_t only_loop_max_depth = -1;560 int32_t only_loop_max_depth = -1;
511 std::vector<std::string> loop_size_vec;561 std::vector<std::string> loop_size_vec;
512 if (tpipe.cv_fusion_type == ascir::CubeTemplateType::kUBFuse) {562 if (tpipe.cv_fusion_type == ascir::CubeTemplateType::kUBFuse) {
513- root_loop.GenerateCvUbFuse(tpipe, tensor_mgr, loop_body, loop_size);563+ GE_CHK_STATUS_RET(root_loop.GenerateCvUbFuse(tpipe, tensor_mgr, loop_body, loop_size),
564+ "Generate CV UBFuse VectorFunc body failed");
514 } else {565 } else {
515- root_loop.Generate(tpipe, tensor_mgr, stride_depth, loop_body, loop_size, only_loop_max_depth, loop_size_vec);566+ ArangeOffsetMap arange_offsets;
567+ std::vector<ArangeParam> arange_params;
568+ root_loop.CollectArangeParams(tpipe, arange_params);
569+ for (const auto &param : arange_params) {
570+ arange_offsets.emplace(param.tensor_id, "arange_offset_" + std::to_string(param.tensor_id));
571+ }
572+ GE_CHK_STATUS_RET(root_loop.Generate(tpipe, tensor_mgr, stride_depth, loop_body, loop_size, only_loop_max_depth,
573+ loop_size_vec, arange_offsets),
574+ "Generate VectorFunc body failed");
516 }575 }
517 const bool is_double_loop = tpipe.cv_fusion_type != ascir::CubeTemplateType::kUBFuse &&576 const bool is_double_loop = tpipe.cv_fusion_type != ascir::CubeTemplateType::kUBFuse &&
518 stride_depth == MAX_VF_AXIS_MERGE_SIZE - 1 &&577 stride_depth == MAX_VF_AXIS_MERGE_SIZE - 1 &&
@@ -544,6 +603,10 @@ af::Status UpdateVectorFuncNodeParams(const af::AscNodePtr &node, const Vectoriz
544}603}
545 604 
546Status VfCall::GenerateFuncDefinition(const TPipe &tpipe, const Tiler &tiler, std::stringstream &ss) const {605Status VfCall::GenerateFuncDefinition(const TPipe &tpipe, const Tiler &tiler, std::stringstream &ss) const {
606+ std::vector<ArangeParam> arange_params;
607+ root_loop_.CollectArangeParams(tpipe, arange_params);
608+ GE_ASSERT_TRUE(tpipe.cv_fusion_type != ascir::CubeTemplateType::kUBFuse || arange_params.empty(),
609+ "Arange is not supported in CV UBFuse VectorFunc.");
547 // 收集输入输出信息,由于GenInnerLoopSizeAndActualSize函数中会刷新tiler对象中的actual_sizes字段,610 // 收集输入输出信息,由于GenInnerLoopSizeAndActualSize函数中会刷新tiler对象中的actual_sizes字段,
548 // 导致生成函数签名和函数调用时,获取到的size信息不一致,因此生成函数签名和函数调用时均需要调用合轴函数611 // 导致生成函数签名和函数调用时,获取到的size信息不一致,因此生成函数签名和函数调用时均需要调用合轴函数
549 GE_ASSERT_SUCCESS(ParseInputOutputInfo(tpipe));612 GE_ASSERT_SUCCESS(ParseInputOutputInfo(tpipe));
@@ -554,7 +617,7 @@ Status VfCall::GenerateFuncDefinition(const TPipe &tpipe, const Tiler &tiler, st
554 GE_ASSERT_TRUE(status, "GenerateVectorizedAxisMergeStatus failed");617 GE_ASSERT_TRUE(status, "GenerateVectorizedAxisMergeStatus failed");
555 618 
556 GenerateVfCallFuncHeader(tpipe, this->vf_call_name_, this->ub_inputs_, this->scalar_inputs_, this->ub_outputs_,619 GenerateVfCallFuncHeader(tpipe, this->vf_call_name_, this->ub_inputs_, this->scalar_inputs_, this->ub_outputs_,
557- merge_info, ss);620+ merge_info, arange_params, ss);
558 621 
559 // func body622 // func body
560 std::stringstream params;623 std::stringstream params;
@@ -589,7 +652,25 @@ Status VfCall::Generate(const TPipe &tpipe, [[maybe_unused]] const std::vector<a
589 GE_ASSERT_TRUE(status, "GenerateVectorizedAxisMergeStatus failed");652 GE_ASSERT_TRUE(status, "GenerateVectorizedAxisMergeStatus failed");
590 653 
591 std::stringstream ss;654 std::stringstream ss;
655+ std::vector<ArangeParam> arange_params;
656+ root_loop_.CollectArangeParams(tpipe, arange_params);
592 size_t loop_num = merge_info.merge_repeats_str.size();657 size_t loop_num = merge_info.merge_repeats_str.size();
658+ const size_t outer_loop_num = loop_num > kVFMaxLoop ? loop_num - kVFMaxLoop : 0UL;
659+ for (auto &param : arange_params) {
660+ const auto *arange_tensor = tensor_mgr_.GetTensor(param.tensor_id);
661+ GE_ASSERT_NOTNULL(arange_tensor);
662+ std::stringstream offset;
663+ offset << (current_axis.empty()
664+ ? "0"
665+ : tpipe.tiler.Offset(current_axis, arange_tensor->axis_, arange_tensor->axis_strides_));
666+ for (size_t i = 0UL; i < outer_loop_num; ++i) {
667+ std::string stride;
668+ GE_CHK_STATUS_RET(GetArangeLogicalStride(tpipe, arange_tensor, merge_info.merge_axis_ids[i], stride),
669+ "Generate Arange outer-for stride failed");
670+ offset << " + outer_for_" << i << " * " << stride;
671+ }
672+ param.offset = offset.str();
673+ }
593 ss << "#if defined(__DAV_C310__) || "674 ss << "#if defined(__DAV_C310__) || "
594 "(defined(__NPU_ARCH__) && (__NPU_ARCH__ == 5102 || __NPU_ARCH__ == 3510 || __NPU_ARCH__ == 9202))"675 "(defined(__NPU_ARCH__) && (__NPU_ARCH__ == 5102 || __NPU_ARCH__ == 3510 || __NPU_ARCH__ == 9202))"
595 << std::endl;676 << std::endl;
@@ -598,10 +679,10 @@ Status VfCall::Generate(const TPipe &tpipe, [[maybe_unused]] const std::vector<a
598 std::vector<std::string> inputs_ub_offsets = {};679 std::vector<std::string> inputs_ub_offsets = {};
599 std::vector<std::string> outputs_ub_offsets = {};680 std::vector<std::string> outputs_ub_offsets = {};
600 CreateVFCall(tpipe, this->vf_call_name_, this->ub_inputs_, this->ub_outputs_, inputs_ub_offsets, outputs_ub_offsets,681 CreateVFCall(tpipe, this->vf_call_name_, this->ub_inputs_, this->ub_outputs_, inputs_ub_offsets, outputs_ub_offsets,
601- this->scalar_inputs_, merge_info, ss);682+ this->scalar_inputs_, merge_info, arange_params, ss);
602 } else {683 } else {
603 CreateOuterForVFCall(tpipe, this->vf_call_name_, this->ub_inputs_, this->ub_outputs_, this->scalar_inputs_,684 CreateOuterForVFCall(tpipe, this->vf_call_name_, this->ub_inputs_, this->ub_outputs_, this->scalar_inputs_,
604- merge_info, ss);685+ merge_info, arange_params, ss);
605 }686 }
606 ss << "#endif" << std::endl;687 ss << "#endif" << std::endl;
607 result = ss.str();688 result = ss.str();
@@ -50,6 +50,7 @@ class VfCall final : public ApiCall {
50 mutable std::vector<Tensor> ub_inputs_;50 mutable std::vector<Tensor> ub_inputs_;
51 mutable std::vector<Tensor> ub_outputs_;51 mutable std::vector<Tensor> ub_outputs_;
52 mutable std::vector<Tensor> scalar_inputs_;52 mutable std::vector<Tensor> scalar_inputs_;
53+ mutable std::vector<ascir::TensorId> subgraph_scalar_ids_;
53};54};
54} // namespace codegen55} // namespace codegen
55#endif // __AUTOFUSE_VEC_FUNC_CALL_H__56#endif // __AUTOFUSE_VEC_FUNC_CALL_H__