Merged
fix(index): validate empty self and handle empty indices #12074
fix(index): validate empty self and handle empty indices #12074
Merged
fazhenyao123Add on 27 days ago
Total: 3 files changed+115-7
@@ -162,13 +162,19 @@ aclnnStatus aclnnIndex(
162 <td>传入的self是空指针。</td>162 <td>传入的self是空指针。</td>
163 </tr>163 </tr>
164 <tr>164 <tr>
165- <td rowspan="2">ACLNN_ERR_PARAM_INVALID</td>165+ <td rowspan="4">ACLNN_ERR_PARAM_INVALID</td>
166- <td rowspan="2">161002</td>166+ <td rowspan="4">161002</td>
167 <td>self和out的数据类型不在支持的范围之内。</td>167 <td>self和out的数据类型不在支持的范围之内。</td>
168 </tr>168 </tr>
169 <tr>169 <tr>
170 <td>self和out的数据类型不一致。</td>170 <td>self和out的数据类型不一致。</td>
171 </tr>171 </tr>
172+ <tr>
173+ <td>self为空且out非空。</td>
C
Cchenqi31725 days ago

本次只在返回值错误码表新增“self为空且out非空”,而同一文档的“约束说明”(aclnnIndex.md:221-233)仍只有 indices 个数、indices 取值不越界、bool 索引与 broadcast 三类约束,没有写“self 为空时 out 必须为空,否则返回 ACLNN_ERR_PARAM_INVALID”。后果:使用者按约束章节自检时仍会认为 self 空 + out 非空是可用组合,直到调用时才拿到 161002,接口文档两处口径不一致。修复建议:在约束说明补一条同义约束,并与错误码表措辞保持一致。


如已修复请回复说明,我会在确认后把该意见标记为已解决。

likedislike
fazhenyao123
fazhenyao123
23 days ago Comments:
174+ </tr>
175+ <tr>
176+ <td>indices中无tensor或所有tensor均为空时,out与self的shape不一致。</td>
177+ </tr>
172 </tbody>178 </tbody>
173 </table>179 </table>
174 180 
@@ -220,6 +226,8 @@ aclnnStatus aclnnIndex(
220- 确定性计算:226- 确定性计算:
221 - aclnnIndex默认确定性实现。227 - aclnnIndex默认确定性实现。
222- self非0维Tensor时,indices中tensor的个数需要小于等于self的维度数,self为0维Tensor时,indices中只能有1个tensor。228- self非0维Tensor时,indices中tensor的个数需要小于等于self的维度数,self为0维Tensor时,indices中只能有1个tensor。
229+- self为空时,out必须为空,否则返回ACLNN_ERR_PARAM_INVALID。
230+- indices中无tensor或所有tensor均为空时,输出为self的副本,out的shape必须与self一致,否则返回ACLNN_ERR_PARAM_INVALID。
223- indices中的各个tensor中的值不能超过self中对应维度的大小,否则会产生不可预知行为,如地址越界。231- indices中的各个tensor中的值不能超过self中对应维度的大小,否则会产生不可预知行为,如地址越界。
224<!-- npu="950" id9 -->232<!-- npu="950" id9 -->
225- <term>Ascend 950PR&950DT系列产品</term>:indices各tensor之间shape需相同或满足broadcast关系。233- <term>Ascend 950PR&950DT系列产品</term>:indices各tensor之间shape需相同或满足broadcast关系。
@@ -200,6 +200,10 @@ static aclnnStatus CheckParams(const aclTensor* self, const aclTensorList* indic
200 // 4. 检查shape是否支持200 // 4. 检查shape是否支持
201 CheckShape(self, indices);201 CheckShape(self, indices);
202 CHECK_RET(CheckShapeDim(self, indices), ACLNN_ERR_PARAM_INVALID);202 CHECK_RET(CheckShapeDim(self, indices), ACLNN_ERR_PARAM_INVALID);
203+ if (self->IsEmpty() && !out->IsEmpty()) {
C
Cchenqi31725 days ago

触发条件:self 非空(如 {30,10})、indices 仅含空 tensor(如 shape {0},按 ProcessIndicesAndMasks:408-421 的语义该维 mask=0)、out 非空(如 {30,10},即“该维不被索引、结果等于 self”的调用;IndexByTensor fallback 在 mask 全 0 时也会把被 mask 的维替换成空 tensor 后传入)。此时 indicesNum=0、allDefinedIndices 为空,CalculateOutputShapeAndTransposeFlag 仍返回 isAicore=true,函数在 aclnn_index.cpp:605 无条件调用 GetTransposedOutputShape(),其中 :339 直接取 allDefinedIndices[0]->GetViewShape(),对空 FVector 取下标属越界访问(UB,实际表现为进程崩溃),而不是返回 ACLNN_ERR_PARAM_INVALID。属存量缺陷(base 版本同样存在),本次新增的空 input 判据只按 self 判定,没有覆盖这一类空输入。修复建议:判据改为基于“推导出的输出是否为空 / out 是否与推导形状一致”,而不是只看 self->IsEmpty();或在 ProcessIndicesAndMasks 之后对 indicesNum==0 单独分支(全 mask 时直接 Contiguous+ViewCopy self→out,或明确返回 ACLNN_ERR_PARAM_INVALID),并禁止对空 allDefinedIndices 取 [0];同时补一条 indices 全空 + out 非空的 UT 作为回归保护。


如已修复请回复说明,我会在确认后把该意见标记为已解决。

likedislike
fazhenyao123
fazhenyao123
23 days ago Comments:
204+ OP_LOGE(ACLNN_ERR_PARAM_INVALID, "self is empty but out is non-empty.");
205+ return ACLNN_ERR_PARAM_INVALID;
206+ }
203 return ACLNN_SUCCESS;207 return ACLNN_SUCCESS;
204}208}
205 209 
@@ -540,14 +544,35 @@ aclnnStatus aclnnIndexGetWorkspaceSize(const aclTensor* self, const aclTensorLis
540 auto ret = CheckParams(self, indices, out);544 auto ret = CheckParams(self, indices, out);
541 CHECK_RET(ret == ACLNN_SUCCESS, ret);545 CHECK_RET(ret == ACLNN_SUCCESS, ret);
542 546 
543- // 算子的空tensor在kernel中支持,对标竞品根据算子实际情况补充547+ bool allIndicesEmpty = true;
544- bool selfOrOutIsEmpty = self->IsEmpty() || out->IsEmpty();548+ for (size_t i = 0; i < indices->Size(); i++) {
545- if (selfOrOutIsEmpty) {549+ if (!(*indices)[i]->IsEmpty()) {
550+ allIndicesEmpty = false;
551+ break;
552+ }
553+ }
554+ if (allIndicesEmpty) {
555+ OP_CHECK_SHAPE_NOT_EQUAL_WITH_EXPECTED_SIZE(out, self->GetViewShape(), return ACLNN_ERR_PARAM_INVALID);
556+ }
557+ 
558+ // 空输出不需要生成计算图;空self与非空out已在参数检查阶段拒绝。
559+ if (out->IsEmpty()) {
546 *workspaceSize = 0;560 *workspaceSize = 0;
547 uniqueExecutor.ReleaseTo(executor);561 uniqueExecutor.ReleaseTo(executor);
548 return ACLNN_SUCCESS;562 return ACLNN_SUCCESS;
549 }563 }
550 564 
565+ // 空索引仅作为占位,不改变self;在进入索引检查和形状推导前处理该场景。
566+ if (allIndicesEmpty) {
567+ auto selfContiguous = l0op::Contiguous(self, uniqueExecutor.get());
568+ CHECK_RET(selfContiguous != nullptr, ACLNN_ERR_INNER_NULLPTR);
569+ auto viewCopyResult = l0op::ViewCopy(selfContiguous, out, uniqueExecutor.get());
570+ CHECK_RET(viewCopyResult != nullptr, ACLNN_ERR_INNER_NULLPTR);
571+ *workspaceSize = uniqueExecutor->GetWorkspaceSize();
572+ uniqueExecutor.ReleaseTo(executor);
573+ return ACLNN_SUCCESS;
574+ }
575+ 
551 auto b4curArch = GetCurrentPlatformInfo().GetCurNpuArch();576 auto b4curArch = GetCurrentPlatformInfo().GetCurNpuArch();
552 if (b4curArch == NpuArch::DAV_2201) {577 if (b4curArch == NpuArch::DAV_2201) {
553 int64_t indicesSize = static_cast<int64_t>(indices->Size());578 int64_t indicesSize = static_cast<int64_t>(indices->Size());
@@ -59,7 +59,7 @@ TEST_F(l2_index_test, l2_test_self_empty_fp32)
59 59 
60 uint64_t workspaceSize = 0;60 uint64_t workspaceSize = 0;
61 aclnnStatus getWorkspaceResult = ut.TestGetWorkspaceSize(&workspaceSize);61 aclnnStatus getWorkspaceResult = ut.TestGetWorkspaceSize(&workspaceSize);
62- EXPECT_EQ(getWorkspaceResult, ACLNN_SUCCESS);62+ EXPECT_EQ(getWorkspaceResult, ACLNN_ERR_PARAM_INVALID);
63}63}
64 64 
65TEST_F(l2_index_test, l2_test_out_empty_fp32)65TEST_F(l2_index_test, l2_test_out_empty_fp32)
@@ -83,6 +83,81 @@ TEST_F(l2_index_test, l2_test_out_empty_fp32)
83 EXPECT_EQ(getWorkspaceResult, ACLNN_SUCCESS);83 EXPECT_EQ(getWorkspaceResult, ACLNN_SUCCESS);
84}84}
85 85 
86+TEST_F(l2_index_test, l2_test_self_and_out_empty_fp32)
87+{
88+ vector<int64_t> self_shape = {0, 10};
89+ vector<int64_t> indices_shape = {0};
90+ vector<int64_t> out_shape = {{0, 10}};
91+ 
92+ auto self_desc = TensorDesc(self_shape, ACL_FLOAT, ACL_FORMAT_ND).ValueRange(1, 4);
93+ auto indices_desc = TensorDesc(indices_shape, ACL_INT64, ACL_FORMAT_ND).ValueRange(1, 25);
94+ auto tensor_list_desc = TensorListDesc({
95+ indices_desc,
96+ });
97+ auto out_desc = TensorDesc(out_shape, ACL_FLOAT, ACL_FORMAT_ND);
98+ 
99+ auto ut = OP_API_UT(aclnnIndex, INPUT(self_desc, tensor_list_desc), // host api输入
100+ OUTPUT(out_desc));
101+ 
102+ uint64_t workspaceSize = 0;
103+ aclnnStatus getWorkspaceResult = ut.TestGetWorkspaceSize(&workspaceSize);
104+ EXPECT_EQ(getWorkspaceResult, ACLNN_SUCCESS);
105+ EXPECT_EQ(workspaceSize, 0);
106+}
107+ 
108+TEST_F(l2_index_test, l2_test_all_indices_empty_fp32)
109+{
110+ auto self_desc = TensorDesc({30, 10}, ACL_FLOAT, ACL_FORMAT_ND).ValueRange(1, 4);
111+ auto indices_desc = TensorDesc({0}, ACL_INT64, ACL_FORMAT_ND);
112+ auto out_desc = TensorDesc({30, 10}, ACL_FLOAT, ACL_FORMAT_ND);
113+ 
114+ for (int indices_count = 1; indices_count <= 2; ++indices_count) {
115+ SCOPED_TRACE(indices_count);
116+ auto tensor_list_desc = TensorListDesc(indices_count, indices_desc);
117+ auto ut = OP_API_UT(aclnnIndex, INPUT(self_desc, tensor_list_desc), OUTPUT(out_desc));
118+ uint64_t workspaceSize = 0;
119+ EXPECT_EQ(ut.TestGetWorkspaceSize(&workspaceSize), ACLNN_SUCCESS);
120+ }
121+}
122+ 
123+TEST_F(l2_index_test, l2_test_all_indices_empty_non_contiguous_fp32)
124+{
125+ auto self_desc = TensorDesc({30, 10}, ACL_FLOAT, ACL_FORMAT_ND, {1, 30}, 0, {10, 30}).ValueRange(1, 4);
126+ auto indices_desc = TensorDesc({0}, ACL_INT64, ACL_FORMAT_ND);
127+ auto tensor_list_desc = TensorListDesc({indices_desc, indices_desc});
128+ auto out_desc = TensorDesc({30, 10}, ACL_FLOAT, ACL_FORMAT_ND, {1, 30}, 0, {10, 30});
129+ 
130+ auto ut = OP_API_UT(aclnnIndex, INPUT(self_desc, tensor_list_desc), OUTPUT(out_desc));
131+ uint64_t workspaceSize = 0;
132+ EXPECT_EQ(ut.TestGetWorkspaceSize(&workspaceSize), ACLNN_SUCCESS);
133+}
134+ 
135+TEST_F(l2_index_test, l2_test_all_indices_empty_scalar_fp32)
136+{
137+ auto self_desc = TensorDesc({}, ACL_FLOAT, ACL_FORMAT_ND).ValueRange(1, 4);
138+ auto indices_desc = TensorDesc({0}, ACL_INT64, ACL_FORMAT_ND);
139+ auto tensor_list_desc = TensorListDesc({indices_desc});
140+ auto out_desc = TensorDesc({}, ACL_FLOAT, ACL_FORMAT_ND);
141+ 
142+ auto ut = OP_API_UT(aclnnIndex, INPUT(self_desc, tensor_list_desc), OUTPUT(out_desc));
143+ uint64_t workspaceSize = 0;
144+ EXPECT_EQ(ut.TestGetWorkspaceSize(&workspaceSize), ACLNN_SUCCESS);
145+}
146+ 
147+TEST_F(l2_index_test, l2_test_all_indices_empty_invalid_out_shape_fp32)
148+{
149+ auto self_desc = TensorDesc({30, 10}, ACL_FLOAT, ACL_FORMAT_ND).ValueRange(1, 4);
150+ auto indices_desc = TensorDesc({0}, ACL_INT64, ACL_FORMAT_ND);
151+ auto tensor_list_desc = TensorListDesc({indices_desc});
152+ 
153+ for (const auto& out_shape : vector<vector<int64_t>>{{10, 30}, {30}, {0, 10}}) {
154+ auto out_desc = TensorDesc(out_shape, ACL_FLOAT, ACL_FORMAT_ND);
155+ auto ut = OP_API_UT(aclnnIndex, INPUT(self_desc, tensor_list_desc), OUTPUT(out_desc));
156+ uint64_t workspaceSize = 0;
157+ EXPECT_EQ(ut.TestGetWorkspaceSize(&workspaceSize), ACLNN_ERR_PARAM_INVALID);
158+ }
159+}
160+ 
86TEST_F(l2_index_test, l2_test_success_multi_indices_aicore)161TEST_F(l2_index_test, l2_test_success_multi_indices_aicore)
87{162{
88 vector<int64_t> self_shape = {30, 30};163 vector<int64_t> self_shape = {30, 30};
@@ -432,4 +507,4 @@ TEST_F(l2_index_test, ascend950_l2_test_success_fp32)
432 uint64_t workspaceSize = 0;507 uint64_t workspaceSize = 0;
433 aclnnStatus getWorkspaceResult = ut.TestGetWorkspaceSize(&workspaceSize);508 aclnnStatus getWorkspaceResult = ut.TestGetWorkspaceSize(&workspaceSize);
434 EXPECT_EQ(getWorkspaceResult, ACLNN_SUCCESS);509 EXPECT_EQ(getWorkspaceResult, ACLNN_SUCCESS);
435-}510+}