已合并
新增 SortedNMS 算子 Ascend 950 实现 #1236
黄晓彬创建于 8月5日
新增 SortedNMS 算子 Ascend 950 实现 #1236
已合并
共 16 个文件变更+1900-39
| @@ -1155,45 +1155,6 @@ REG_OP(RpnProposals) | |||
| 1155 | .OUTPUT(sorted_box, TensorType({DT_FLOAT16})) | 1155 | .OUTPUT(sorted_box, TensorType({DT_FLOAT16})) |
| 1156 | .OP_END_FACTORY_REG(RpnProposals) | 1156 | .OP_END_FACTORY_REG(RpnProposals) |
| 1157 | 1157 | ||
| 1158 | -/** | ||
| 1159 | -* @brief Greedily selects a subset of bounding boxes in descending order of | ||
| 1160 | -* score . \n | ||
| 1161 | - | ||
| 1162 | -* @par Inputs: | ||
| 1163 | -* @li boxes: A 2-D float tensor of shape [num_boxes, 4]. They are expected to be in (x1, y1, x2, y2) format | ||
| 1164 | -* with 0 <= x1 < x2 and 0 <= y1 < y2. Supported type: float16, float32. Supported format: ND. | ||
| 1165 | -* @li sorted_scores: A 1-D float tensor of shape [num_boxes] representing boxes' scores, which is sorted | ||
| 1166 | -* by descending order. Supported type: float16, float32. Supported format: ND. | ||
| 1167 | -* @li input_indices: A 1-D integer tensor of shape [num_boxes] representing the indices for each row of | ||
| 1168 | -* boxes that would sort row of boxes by scores in descending order. Supported type: int32. Supported format: ND. | ||
| 1169 | -* @li max_output_size: A scalar integer tensor representing the maximum number | ||
| 1170 | -* of boxes to be selected by non max suppression. Supported type: int32. Supported format: ND. | ||
| 1171 | -* @li iou_threshold: A 0-D float tensor representing the threshold for deciding | ||
| 1172 | -* whether boxes overlap too much with respect to IOU. Supported type: float16, float32. Supported format: ND. | ||
| 1173 | -* @li score_threshold: A 0-D float tensor representing the threshold for | ||
| 1174 | -* deciding when to remove boxes based on score. Supported type: float16, float32. Supported format: ND . \n | ||
| 1175 | - | ||
| 1176 | -* @par Attributes: | ||
| 1177 | -* offset: An optional int. Defaults to "0". \n | ||
| 1178 | - | ||
| 1179 | -* @par Outputs: | ||
| 1180 | -* @li selected_indices: A 1-D integer tensor of shape [M] representing the selected | ||
| 1181 | -* indices from the boxes tensor, where M <= max_output_size. Supported type: int32. Supported format: ND . \n | ||
| 1182 | - | ||
| 1183 | -* @attention Constraints: | ||
| 1184 | -* Input boxes and scores must be float type . \n | ||
| 1185 | -*/ | ||
| 1186 | -REG_OP(SortedNMS) | ||
| 1187 | - .INPUT(boxes, TensorType({DT_FLOAT16, DT_FLOAT})) | ||
| 1188 | - .INPUT(sorted_scores, TensorType({DT_FLOAT16, DT_FLOAT})) | ||
| 1189 | - .INPUT(input_indices, TensorType({DT_INT32})) | ||
| 1190 | - .INPUT(max_output_size, TensorType({DT_INT32})) | ||
| 1191 | - .INPUT(iou_threshold, TensorType({DT_FLOAT16, DT_FLOAT})) | ||
| 1192 | - .INPUT(score_threshold, TensorType({DT_FLOAT16, DT_FLOAT})) | ||
| 1193 | - .OUTPUT(selected_indices, TensorType({DT_INT32})) | ||
| 1194 | - .ATTR(offset, Int, 0) | ||
| 1195 | - .OP_END_FACTORY_REG(SortedNMS) | ||
| 1196 | - | ||
| 1197 | /** | 1158 | /** |
| 1198 | *@brief HDRNet and ISP direct data conversion | 1159 | *@brief HDRNet and ISP direct data conversion |
| 1199 | returned tensor's dimension will correspond to input dimension [0, 3, 4, 2, 1], | 1160 | returned tensor's dimension will correspond to input dimension [0, 3, 4, 2, 1], |
| @@ -0,0 +1,17 @@ | |||
| 1 | +file(GLOB CURRENT_DIRS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/*) | ||
| 2 | + | ||
| 3 | +if(NOT ENABLE_TEST AND NOT BENCHMARK) | ||
| 4 | + list(REMOVE_ITEM CURRENT_DIRS tests) | ||
| 5 | +endif() | ||
| 6 | + | ||
| 7 | +foreach(SUB_DIR ${CURRENT_DIRS}) | ||
| 8 | + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${SUB_DIR}/CMakeLists.txt") | ||
| 9 | + add_subdirectory(${SUB_DIR}) | ||
| 10 | + endif() | ||
| 11 | +endforeach() | ||
| 12 | + | ||
| 13 | +set(SUPPORT_COMPUTE_UNIT "ascend950") | ||
| 14 | +set(SUPPORT_TILING_DIR "arch35") | ||
| 15 | +add_all_modules_sources(OPTYPE sorted_nms ACLNNTYPE aclnn_exclude | ||
| 16 | + COMPUTE_UNIT ${SUPPORT_COMPUTE_UNIT} | ||
| 17 | + TILING_DIR ${SUPPORT_TILING_DIR}) | ||
| @@ -0,0 +1,123 @@ | |||
| 1 | +# SortedNMS | ||
吴 | |||
| 2 | + | ||
| 3 | +## 产品支持情况 | ||
| 4 | + | ||
| 5 | +|产品 | 是否支持 | | ||
| 6 | +|:-------------------------|:----------:| | ||
| 7 | +| Ascend 950PR/Ascend 950DT | √ | | ||
| 8 | +| Atlas A3 训练系列产品/Atlas A3 推理系列产品 | √ | | ||
| 9 | +| Atlas A2 训练系列产品/Atlas A2 推理系列产品 | √ | | ||
| 10 | +| Atlas 200I/500 A2 推理产品 | × | | ||
| 11 | +| Atlas 推理系列产品 | × | | ||
| 12 | +| Atlas 训练系列产品 | × | | ||
| 13 | +| Kirin X90 处理器系列产品 | × | | ||
| 14 | +| Kirin 9030 处理器系列产品 | × | | ||
| 15 | + | ||
| 16 | +## 功能说明 | ||
| 17 | + | ||
| 18 | +- 算子功能:在已按分数降序排列的候选框序列上,按照交并比(IoU)阈值贪心选择非抑制框,输出被选中框在原始boxes中的索引。 | ||
| 19 | + | ||
| 20 | +- 计算公式: | ||
| 21 | + | ||
| 22 | + $$ | ||
| 23 | + IoU = \frac {Area_{inter}} {Area_{current} + Area_{next} - Area_{inter}} | ||
| 24 | + $$ | ||
| 25 | + | ||
| 26 | + 其中,Area_current为当前选中框的面积,Area_next为候选框的面积,Area_inter为两个框的重叠面积,offset为坐标计算偏移量。 | ||
| 27 | + | ||
| 28 | + $$ | ||
| 29 | + Area_i = max(X_{2i} - X_{1i} + offset, 0) * max(Y_{2i} - Y_{1i} + offset, 0) \\ | ||
| 30 | + Area_{inter} = max(min(X_{2c}, X_{2n}) - max(X_{1c}, X_{1n}) + offset, 0) * max(min(Y_{2c}, Y_{2n}) - max(Y_{1c}, Y_{1n}) + offset, 0) | ||
| 31 | + $$ | ||
| 32 | + | ||
| 33 | + 算子按照sorted_scores的非递增顺序遍历候选框。当候选框的分数大于score_threshold且未被抑制时,将input_indices中对应的索引加入输出;当候选框与当前选中框的IoU大于iou_threshold时,抑制该候选框。 | ||
| 34 | + | ||
| 35 | +## 参数说明 | ||
| 36 | + | ||
| 37 | +<table style="undefined;table-layout: fixed; width: 1005px"><colgroup> | ||
| 38 | + <col style="width: 170px"> | ||
| 39 | + <col style="width: 170px"> | ||
| 40 | + <col style="width: 352px"> | ||
| 41 | + <col style="width: 213px"> | ||
| 42 | + <col style="width: 100px"> | ||
| 43 | + </colgroup> | ||
| 44 | + <thead> | ||
| 45 | + <tr> | ||
| 46 | + <th>参数名</th> | ||
| 47 | + <th>输入/输出/属性</th> | ||
| 48 | + <th>描述</th> | ||
| 49 | + <th>数据类型</th> | ||
| 50 | + <th>数据格式</th> | ||
| 51 | + </tr></thead> | ||
| 52 | + <tbody> | ||
| 53 | + <tr> | ||
| 54 | + <td>boxes</td> | ||
| 55 | + <td>输入</td> | ||
| 56 | + <td>候选矩形框,shape为(N, 4),坐标格式为(X1, Y1, X2, Y2)。</td> | ||
| 57 | + <td>FLOAT32、FLOAT16</td> | ||
| 58 | + <td>ND</td> | ||
| 59 | + </tr> | ||
| 60 | + <tr> | ||
| 61 | + <td>sorted_scores</td> | ||
| 62 | + <td>输入</td> | ||
| 63 | + <td>候选矩形框的分数,shape为(N,),需要按非递增顺序排列。</td> | ||
| 64 | + <td>FLOAT32、FLOAT16</td> | ||
| 65 | + <td>ND</td> | ||
| 66 | + </tr> | ||
| 67 | + <tr> | ||
| 68 | + <td>input_indices</td> | ||
| 69 | + <td>输入</td> | ||
| 70 | + <td>sorted_scores对应的候选框索引,shape为(N),取值范围为[0, N)。</td> | ||
| 71 | + <td>INT32</td> | ||
| 72 | + <td>ND</td> | ||
| 73 | + </tr> | ||
| 74 | + <tr> | ||
| 75 | + <td>max_output_size</td> | ||
| 76 | + <td>输入</td> | ||
| 77 | + <td>最多输出的候选框数量,输入为标量或shape为(1,)的张量。</td> | ||
| 78 | + <td>INT32</td> | ||
| 79 | + <td>ND</td> | ||
| 80 | + </tr> | ||
| 81 | + <tr> | ||
| 82 | + <td>iou_threshold</td> | ||
| 83 | + <td>输入</td> | ||
| 84 | + <td>判断候选框是否需要抑制的IoU阈值,输入为标量或shape为(1,)的张量。</td> | ||
| 85 | + <td>FLOAT32、FLOAT16</td> | ||
| 86 | + <td>ND</td> | ||
| 87 | + </tr> | ||
| 88 | + <tr> | ||
| 89 | + <td>score_threshold</td> | ||
| 90 | + <td>输入</td> | ||
| 91 | + <td>过滤候选框的分数阈值,输入为标量或shape为(1,)的张量。</td> | ||
| 92 | + <td>FLOAT32、FLOAT16</td> | ||
| 93 | + <td>ND</td> | ||
| 94 | + </tr> | ||
| 95 | + <tr> | ||
| 96 | + <td>offset</td> | ||
| 97 | + <td>属性</td> | ||
| 98 | + <td>计算坐标差值时使用的偏移量,取值为0或1,默认值为0。</td> | ||
| 99 | + <td>INT</td> | ||
| 100 | + <td>-</td> | ||
| 101 | + </tr> | ||
| 102 | + <tr> | ||
| 103 | + <td>selected_indices</td> | ||
| 104 | + <td>输出</td> | ||
| 105 | + <td>被选中候选框在原始boxes中的索引,shape为(M),M为运行时计算结果且M不大于min(max_output_size, N)。</td> | ||
| 106 | + <td>INT32</td> | ||
| 107 | + <td>ND</td> | ||
| 108 | + </tr> | ||
| 109 | + </tbody></table> | ||
| 110 | + | ||
| 111 | +- boxes与iou_threshold的数据类型需要保持一致;sorted_scores与score_threshold的数据类型需要保持一致,两组数据类型可以不同。 | ||
| 112 | + | ||
| 113 | +## 约束说明 | ||
| 114 | + | ||
| 115 | +- 输入shape限制:boxes为(N, 4)的二维张量,sorted_scores和input_indices为(N,)的一维张量,max_output_size、iou_threshold和score_threshold为标量或shape为(1,)的张量。 | ||
| 116 | + | ||
| 117 | +- sorted_scores需要按照非递增顺序排列,input_indices需要为合法的候选框索引。 | ||
| 118 | + | ||
| 119 | +## 调用说明 | ||
| 120 | + | ||
| 121 | +| 调用方式 | 调用样例 | 说明 | | ||
| 122 | +|--------------|------------------------------------------------------------------------|--------------------------------------------------------------| | ||
| 123 | +| 图模式调用 | [test_geir_sorted_nms](./examples/test_geir_sorted_nms.cpp) | 通过[算子IR](./op_graph/sorted_nms_proto.h)构图方式调用SortedNMS算子。 | | ||
| @@ -0,0 +1,195 @@ | |||
| 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 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + | ||
| 28 | + | ||
| 29 | +namespace { | ||
| 30 | +constexpr int32_t kSuccess = 0; | ||
| 31 | +constexpr int32_t kFailed = -1; | ||
| 32 | +constexpr int64_t kBoxCount = 5; | ||
| 33 | +constexpr size_t kExpectedCount = 3; | ||
| 34 | + | ||
| 35 | +ge::TensorDesc MakeTensorDesc(const std::vector<int64_t>& shape, ge::DataType dtype) | ||
| 36 | +{ | ||
| 37 | + ge::TensorDesc desc(ge::Shape(shape), ge::FORMAT_ND, dtype); | ||
| 38 | + desc.SetPlacement(ge::kPlacementHost); | ||
| 39 | + return desc; | ||
| 40 | +} | ||
| 41 | + | ||
| 42 | +template <typename T> | ||
| 43 | +ge::Tensor MakeTensor(const ge::TensorDesc& desc, std::vector<T>& data) | ||
| 44 | +{ | ||
| 45 | + return ge::Tensor(desc, reinterpret_cast<uint8_t*>(data.data()), data.size() * sizeof(T)); | ||
| 46 | +} | ||
| 47 | + | ||
| 48 | +bool CheckOutput(const std::vector<ge::Tensor>& outputs) | ||
| 49 | +{ | ||
| 50 | + const std::array<int32_t, kExpectedCount> expected = {0, 2, 3}; | ||
| 51 | + const std::vector<int64_t> expectedShape = {static_cast<int64_t>(kExpectedCount)}; | ||
| 52 | + if (outputs.size() != 1 || outputs[0].GetTensorDesc().GetDataType() != ge::DT_INT32 || | ||
| 53 | + outputs[0].GetTensorDesc().GetShape().GetDims() != expectedShape) { | ||
| 54 | + std::cerr << "Unexpected SortedNMS output descriptor: output_count=" << outputs.size(); | ||
| 55 | + if (!outputs.empty()) { | ||
| 56 | + const auto& outputDesc = outputs[0].GetTensorDesc(); | ||
| 57 | + std::cerr << ", dtype=" << static_cast<int32_t>(outputDesc.GetDataType()) << ", shape=["; | ||
| 58 | + const auto& dims = outputDesc.GetShape().GetDims(); | ||
| 59 | + for (size_t i = 0; i < dims.size(); ++i) { | ||
| 60 | + std::cerr << (i == 0 ? "" : ", ") << dims[i]; | ||
| 61 | + } | ||
| 62 | + std::cerr << "], bytes=" << outputs[0].GetSize(); | ||
| 63 | + const uint8_t* outputData = outputs[0].GetData(); | ||
| 64 | + const size_t valueCount = outputs[0].GetSize() / sizeof(int32_t); | ||
| 65 | + if (outputData != nullptr && valueCount > 0) { | ||
| 66 | + const size_t printCount = valueCount < kExpectedCount ? valueCount : kExpectedCount; | ||
| 67 | + std::cerr << ", raw_values=["; | ||
| 68 | + for (size_t i = 0; i < printCount; ++i) { | ||
| 69 | + int32_t value = 0; | ||
| 70 | + std::memcpy(&value, outputData + i * sizeof(value), sizeof(value)); | ||
| 71 | + std::cerr << (i == 0 ? "" : ", ") << value; | ||
| 72 | + } | ||
| 73 | + std::cerr << "]"; | ||
| 74 | + } | ||
| 75 | + } | ||
| 76 | + std::cerr << std::endl; | ||
| 77 | + return false; | ||
| 78 | + } | ||
| 79 | + | ||
| 80 | + const uint8_t* outputData = outputs[0].GetData(); | ||
| 81 | + if (outputData == nullptr) { | ||
| 82 | + std::cerr << "SortedNMS output data is null" << std::endl; | ||
| 83 | + return false; | ||
| 84 | + } | ||
| 85 | + | ||
| 86 | + std::array<int32_t, kExpectedCount> actual{}; | ||
| 87 | + std::memcpy(actual.data(), outputData, sizeof(actual)); | ||
| 88 | + for (size_t i = 0; i < actual.size(); ++i) { | ||
| 89 | + std::cout << "selected_indices[" << i << "] = " << actual[i] << std::endl; | ||
| 90 | + if (actual[i] != expected[i]) { | ||
| 91 | + std::cerr << "Unexpected SortedNMS result at " << i << ": expected " << expected[i] << ", got " << actual[i] | ||
| 92 | + << std::endl; | ||
| 93 | + return false; | ||
| 94 | + } | ||
| 95 | + } | ||
| 96 | + return true; | ||
| 97 | +} | ||
| 98 | +} // namespace | ||
| 99 | + | ||
| 100 | +int main() | ||
| 101 | +{ | ||
| 102 | + const std::map<ge::AscendString, ge::AscendString> globalOptions = {{"ge.exec.deviceId", "0"}, | ||
| 103 | + {"ge.graphRunMode", "1"}}; | ||
| 104 | + ge::Status ret = ge::GEInitialize(globalOptions); | ||
| 105 | + if (ret != ge::GRAPH_SUCCESS) { | ||
| 106 | + std::cerr << "GEInitialize failed: " << ret << std::endl; | ||
| 107 | + return kFailed; | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + int32_t result = kFailed; | ||
| 111 | + { | ||
| 112 | + ge::Graph graph("sorted_nms_geir_example"); | ||
| 113 | + auto boxes = ge::op::Data("boxes").set_attr_index(0); | ||
| 114 | + auto sortedScores = ge::op::Data("sorted_scores").set_attr_index(1); | ||
| 115 | + auto inputIndices = ge::op::Data("input_indices").set_attr_index(2); | ||
| 116 | + auto maxOutputSize = ge::op::Data("max_output_size").set_attr_index(3); | ||
| 117 | + auto iouThreshold = ge::op::Data("iou_threshold").set_attr_index(4); | ||
| 118 | + auto scoreThreshold = ge::op::Data("score_threshold").set_attr_index(5); | ||
| 119 | + auto sortedNms = ge::op::SortedNMS("sorted_nms"); | ||
| 120 | + | ||
| 121 | + const ge::TensorDesc boxesDesc = MakeTensorDesc({kBoxCount, 4}, ge::DT_FLOAT); | ||
| 122 | + const ge::TensorDesc scoresDesc = MakeTensorDesc({kBoxCount}, ge::DT_FLOAT); | ||
| 123 | + const ge::TensorDesc indicesDesc = MakeTensorDesc({kBoxCount}, ge::DT_INT32); | ||
| 124 | + // GEIR graph inputs use the supported single-element scalar representation. | ||
| 125 | + const ge::TensorDesc scalarFloatDesc = MakeTensorDesc({1}, ge::DT_FLOAT); | ||
| 126 | + const ge::TensorDesc scalarIntDesc = MakeTensorDesc({1}, ge::DT_INT32); | ||
| 127 | + const ge::TensorDesc outputDesc = MakeTensorDesc({ge::UNKNOWN_DIM}, ge::DT_INT32); | ||
| 128 | + | ||
| 129 | + boxes.update_input_desc_x(boxesDesc); | ||
| 130 | + sortedScores.update_input_desc_x(scoresDesc); | ||
| 131 | + inputIndices.update_input_desc_x(indicesDesc); | ||
| 132 | + maxOutputSize.update_input_desc_x(scalarIntDesc); | ||
| 133 | + iouThreshold.update_input_desc_x(scalarFloatDesc); | ||
| 134 | + scoreThreshold.update_input_desc_x(scalarFloatDesc); | ||
| 135 | + sortedNms.set_input_boxes(boxes) | ||
| 136 | + .set_input_sorted_scores(sortedScores) | ||
| 137 | + .set_input_input_indices(inputIndices) | ||
| 138 | + .set_input_max_output_size(maxOutputSize) | ||
| 139 | + .set_input_iou_threshold(iouThreshold) | ||
| 140 | + .set_input_score_threshold(scoreThreshold) | ||
| 141 | + .set_attr_offset(0); | ||
| 142 | + sortedNms.update_output_desc_selected_indices(outputDesc); | ||
| 143 | + | ||
| 144 | + graph.AddOp(boxes); | ||
| 145 | + graph.AddOp(sortedScores); | ||
| 146 | + graph.AddOp(inputIndices); | ||
| 147 | + graph.AddOp(maxOutputSize); | ||
| 148 | + graph.AddOp(iouThreshold); | ||
| 149 | + graph.AddOp(scoreThreshold); | ||
| 150 | + const std::vector<ge::Operator> graphInputs = {boxes, sortedScores, inputIndices, | ||
| 151 | + maxOutputSize, iouThreshold, scoreThreshold}; | ||
| 152 | + const std::vector<ge::Operator> graphOutputs = {sortedNms}; | ||
| 153 | + graph.SetInputs(graphInputs).SetOutputs(graphOutputs); | ||
| 154 | + | ||
| 155 | + // Scores are descending. Box 1 overlaps box 0 and must be suppressed. | ||
| 156 | + std::vector<float> boxesData = {0.0F, 0.0F, 10.0F, 10.0F, 1.0F, 1.0F, 9.0F, 9.0F, 20.0F, 20.0F, | ||
| 157 | + 30.0F, 30.0F, 40.0F, 40.0F, 50.0F, 50.0F, 60.0F, 60.0F, 70.0F, 70.0F}; | ||
| 158 | + std::vector<float> scoresData = {0.95F, 0.90F, 0.75F, 0.60F, 0.40F}; | ||
| 159 | + std::vector<int32_t> indicesData = {0, 1, 2, 3, 4}; | ||
| 160 | + std::vector<int32_t> maxOutputSizeData = {3}; | ||
| 161 | + std::vector<float> iouThresholdData = {0.5F}; | ||
| 162 | + std::vector<float> scoreThresholdData = {0.5F}; | ||
| 163 | + std::vector<ge::Tensor> inputs = {MakeTensor(boxesDesc, boxesData), | ||
| 164 | + MakeTensor(scoresDesc, scoresData), | ||
| 165 | + MakeTensor(indicesDesc, indicesData), | ||
| 166 | + MakeTensor(scalarIntDesc, maxOutputSizeData), | ||
| 167 | + MakeTensor(scalarFloatDesc, iouThresholdData), | ||
| 168 | + MakeTensor(scalarFloatDesc, scoreThresholdData)}; | ||
| 169 | + | ||
| 170 | + const std::map<ge::AscendString, ge::AscendString> sessionOptions; | ||
| 171 | + const std::map<ge::AscendString, ge::AscendString> graphOptions; | ||
| 172 | + std::unique_ptr<ge::Session> session(new (std::nothrow) ge::Session(sessionOptions)); | ||
| 173 | + if (session == nullptr) { | ||
| 174 | + std::cerr << "Failed to create GE session" << std::endl; | ||
| 175 | + } else if ((ret = session->AddGraph(0, graph, graphOptions)) != ge::GRAPH_SUCCESS) { | ||
| 176 | + std::cerr << "AddGraph failed: " << ret << std::endl; | ||
| 177 | + } else { | ||
| 178 | + std::vector<ge::Tensor> outputs; | ||
| 179 | + ret = session->RunGraph(0, inputs, outputs); | ||
| 180 | + if (ret != ge::GRAPH_SUCCESS) { | ||
| 181 | + std::cerr << "RunGraph failed: " << ret << std::endl; | ||
| 182 | + } else if (CheckOutput(outputs)) { | ||
| 183 | + std::cout << "SortedNMS GEIR example passed" << std::endl; | ||
| 184 | + result = kSuccess; | ||
| 185 | + } | ||
| 186 | + } | ||
| 187 | + } | ||
| 188 | + | ||
| 189 | + ret = ge::GEFinalize(); | ||
| 190 | + if (ret != ge::GRAPH_SUCCESS) { | ||
| 191 | + std::cerr << "GEFinalize failed: " << ret << std::endl; | ||
| 192 | + return kFailed; | ||
| 193 | + } | ||
| 194 | + return result; | ||
| 195 | +} | ||
| @@ -0,0 +1,47 @@ | |||
| 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 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | +namespace ge { | ||
| 17 | +/** | ||
| 18 | + * @brief Greedily selects non-suppressed boxes in the supplied descending score order. | ||
| 19 | + * | ||
| 20 | + * @par Inputs: | ||
| 21 | + * @li boxes: A 2-D tensor of shape [num_boxes, 4]. | ||
| 22 | + * @li sorted_scores: A 1-D tensor of shape [num_boxes], sorted in descending order. | ||
| 23 | + * @li input_indices: A 1-D int32 tensor of shape [num_boxes], with values in [0, num_boxes). | ||
| 24 | + * @li max_output_size: A scalar int32 tensor giving the maximum number of selected boxes. | ||
| 25 | + * @li iou_threshold: A scalar float tensor in [0, 1]. | ||
| 26 | + * @li score_threshold: A scalar float tensor. | ||
| 27 | + * | ||
| 28 | + * boxes and iou_threshold must have the same type (float16 or float32). sorted_scores and | ||
| 29 | + * score_threshold must have the same type (float16 or float32), independently of boxes. | ||
| 30 | + * | ||
| 31 | + * @par Outputs: | ||
| 32 | + * selected_indices: A 1-D int32 tensor of shape [M], where M is computed at runtime and | ||
| 33 | + * M <= min(max_output_size, num_boxes). | ||
| 34 | + */ | ||
| 35 | +REG_OP(SortedNMS) | ||
| 36 | + .INPUT(boxes, TensorType({DT_FLOAT16, DT_FLOAT})) | ||
| 37 | + .INPUT(sorted_scores, TensorType({DT_FLOAT16, DT_FLOAT})) | ||
| 38 | + .INPUT(input_indices, TensorType({DT_INT32})) | ||
| 39 | + .INPUT(max_output_size, TensorType({DT_INT32})) | ||
| 40 | + .INPUT(iou_threshold, TensorType({DT_FLOAT16, DT_FLOAT})) | ||
| 41 | + .INPUT(score_threshold, TensorType({DT_FLOAT16, DT_FLOAT})) | ||
| 42 | + .OUTPUT(selected_indices, TensorType({DT_INT32})) | ||
| 43 | + .ATTR(offset, Int, 0) | ||
| 44 | + .OP_END_FACTORY_REG(SortedNMS) | ||
| 45 | +} // namespace ge | ||
| 46 | + | ||
| 47 | + | ||
| @@ -0,0 +1,232 @@ | |||
| 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 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | +namespace optiling { | ||
| 20 | +namespace { | ||
| 21 | +constexpr size_t WORKSPACE_NUM = 1; | ||
| 22 | +constexpr int64_t INPUT_BOXES = 0; | ||
| 23 | +constexpr int64_t INPUT_SORTED_SCORES = 1; | ||
| 24 | +constexpr int64_t INPUT_INDICES = 2; | ||
| 25 | +constexpr int64_t INPUT_MAX_OUTPUT_SIZE = 3; | ||
| 26 | +constexpr int64_t INPUT_IOU_THRESHOLD = 4; | ||
| 27 | +constexpr int64_t INPUT_SCORE_THRESHOLD = 5; | ||
| 28 | +constexpr int64_t ATTR_OFFSET = 0; | ||
| 29 | +constexpr int64_t BOX_RANK = 2; | ||
| 30 | +constexpr int64_t BOX_COORDS = 4; | ||
| 31 | +constexpr int64_t MASK_BITS = 32; | ||
| 32 | +constexpr int64_t PAIR_MASK_WORDS_PER_CORE = 1024; | ||
| 33 | +constexpr int64_t MULTI_CORE_MIN_BOXES = 1025; | ||
| 34 | +constexpr int64_t MULTI_CORE_MAX_BOXES = 8192; | ||
| 35 | +constexpr int32_t STRATEGY_SINGLE_CORE = 0; | ||
| 36 | +constexpr int32_t STRATEGY_PAIRWISE_MASK = 1; | ||
| 37 | +constexpr size_t WORK_CONTROL_NUM = 2U; | ||
| 38 | +constexpr uint64_t UB_BLOCK_SIZE = 32U; | ||
| 39 | +constexpr uint64_t SIMT_DATA_CACHE_RESERVE = 128U * 1024U; | ||
| 40 | +constexpr int64_t MAX_BOXES_NUM = static_cast<int64_t>(std::numeric_limits<int32_t>::max()); | ||
| 41 | +} // namespace | ||
| 42 | + | ||
| 43 | +static uint64_t AlignUbBytes(uint64_t bytes) { return (bytes + UB_BLOCK_SIZE - 1U) / UB_BLOCK_SIZE * UB_BLOCK_SIZE; } | ||
| 44 | + | ||
| 45 | +static bool IsScalarOrSingleElement(const gert::Shape& shape) | ||
| 46 | +{ | ||
| 47 | + return shape.IsScalar() || (shape.GetDimNum() == 1U && shape.GetDim(0) == 1); | ||
| 48 | +} | ||
| 49 | + | ||
| 50 | +static ge::graphStatus SetWorkspace(gert::TilingContext* context, int64_t boxesNum, int32_t strategy, | ||
| 51 | + int64_t maskWordNum) | ||
| 52 | +{ | ||
| 53 | + size_t* workspace = context->GetWorkspaceSizes(WORKSPACE_NUM); | ||
| 54 | + OP_CHECK_NULL_WITH_CONTEXT(context, workspace); | ||
| 55 | + OP_CHECK_IF(boxesNum < 0 || boxesNum > MAX_BOXES_NUM, | ||
| 56 | + OP_LOGE(context, "boxes_num must be in [0, %ld], got %ld", MAX_BOXES_NUM, boxesNum), | ||
| 57 | + return ge::GRAPH_FAILED); | ||
| 58 | + constexpr size_t MAX_WORKSPACE_INT32S = std::numeric_limits<size_t>::max() / sizeof(int32_t); | ||
| 59 | + const size_t boxesNumSize = static_cast<size_t>(boxesNum); | ||
| 60 | + size_t userWorkspaceInt32s = 0; | ||
| 61 | + if (strategy == STRATEGY_PAIRWISE_MASK) { | ||
| 62 | + const size_t maskWordNumSize = static_cast<size_t>(maskWordNum); | ||
| 63 | + OP_CHECK_IF(maskWordNumSize != 0 && boxesNumSize > MAX_WORKSPACE_INT32S / maskWordNumSize, | ||
| 64 | + OP_LOGE(context, "pairwise mask workspace overflows for boxes_num %ld", boxesNum), | ||
| 65 | + return ge::GRAPH_FAILED); | ||
| 66 | + const size_t pairMaskWords = boxesNumSize * maskWordNumSize; | ||
| 67 | + OP_CHECK_IF(maskWordNumSize > MAX_WORKSPACE_INT32S - WORK_CONTROL_NUM || | ||
| 68 | + pairMaskWords > MAX_WORKSPACE_INT32S - WORK_CONTROL_NUM - maskWordNumSize, | ||
| 69 | + OP_LOGE(context, "pairwise mask workspace overflows for boxes_num %ld", boxesNum), | ||
| 70 | + return ge::GRAPH_FAILED); | ||
| 71 | + userWorkspaceInt32s = WORK_CONTROL_NUM + maskWordNumSize + pairMaskWords; | ||
| 72 | + } else { | ||
| 73 | + OP_CHECK_IF(boxesNumSize > MAX_WORKSPACE_INT32S - WORK_CONTROL_NUM, | ||
| 74 | + OP_LOGE(context, "workspace size overflows for boxes_num %ld", boxesNum), return ge::GRAPH_FAILED); | ||
| 75 | + userWorkspaceInt32s = WORK_CONTROL_NUM + boxesNumSize; | ||
| 76 | + } | ||
| 77 | + const size_t userWorkspaceSize = userWorkspaceInt32s * sizeof(int32_t); | ||
| 78 | + auto* platformInfo = context->GetPlatformInfo(); | ||
| 79 | + OP_CHECK_NULL_WITH_CONTEXT(context, platformInfo); | ||
| 80 | + const auto ascendcPlatform = platform_ascendc::PlatformAscendC(platformInfo); | ||
| 81 | + const size_t systemWorkspaceSize = static_cast<size_t>(ascendcPlatform.GetLibApiWorkSpaceSize()); | ||
| 82 | + OP_CHECK_IF(userWorkspaceSize > std::numeric_limits<size_t>::max() - systemWorkspaceSize, | ||
| 83 | + OP_LOGE(context, "workspace size overflows for boxes_num %ld", boxesNum), return ge::GRAPH_FAILED); | ||
| 84 | + workspace[0] = systemWorkspaceSize + userWorkspaceSize; | ||
| 85 | + return ge::GRAPH_SUCCESS; | ||
| 86 | +} | ||
| 87 | + | ||
| 88 | +static ge::graphStatus CheckInputDesc(gert::TilingContext* context) | ||
| 89 | +{ | ||
| 90 | + const std::set<ge::DataType> dataDtypes = {ge::DT_FLOAT16, ge::DT_FLOAT}; | ||
| 91 | + const auto* boxesDesc = context->GetInputDesc(INPUT_BOXES); | ||
| 92 | + const auto* scoresDesc = context->GetInputDesc(INPUT_SORTED_SCORES); | ||
| 93 | + const auto* indicesDesc = context->GetInputDesc(INPUT_INDICES); | ||
| 94 | + const auto* maxOutputDesc = context->GetInputDesc(INPUT_MAX_OUTPUT_SIZE); | ||
| 95 | + const auto* iouDesc = context->GetInputDesc(INPUT_IOU_THRESHOLD); | ||
| 96 | + const auto* scoreThrDesc = context->GetInputDesc(INPUT_SCORE_THRESHOLD); | ||
| 97 | + OP_CHECK_IF(boxesDesc == nullptr || scoresDesc == nullptr || indicesDesc == nullptr || maxOutputDesc == nullptr || | ||
| 98 | + iouDesc == nullptr || scoreThrDesc == nullptr, | ||
| 99 | + OP_LOGE(context, "input desc is nullptr"), return ge::GRAPH_FAILED); | ||
| 100 | + | ||
| 101 | + OP_CHECK_IF(dataDtypes.count(boxesDesc->GetDataType()) == 0, OP_LOGE(context, "unsupported boxes dtype"), | ||
| 102 | + return ge::GRAPH_FAILED); | ||
| 103 | + OP_CHECK_IF(dataDtypes.count(scoresDesc->GetDataType()) == 0, OP_LOGE(context, "unsupported sorted_scores dtype"), | ||
| 104 | + return ge::GRAPH_FAILED); | ||
| 105 | + OP_CHECK_IF(dataDtypes.count(iouDesc->GetDataType()) == 0, OP_LOGE(context, "unsupported iou_threshold dtype"), | ||
| 106 | + return ge::GRAPH_FAILED); | ||
| 107 | + OP_CHECK_IF(dataDtypes.count(scoreThrDesc->GetDataType()) == 0, | ||
| 108 | + OP_LOGE(context, "unsupported score_threshold dtype"), return ge::GRAPH_FAILED); | ||
| 109 | + OP_CHECK_IF(indicesDesc->GetDataType() != ge::DT_INT32, OP_LOGE(context, "input_indices dtype must be int32"), | ||
| 110 | + return ge::GRAPH_FAILED); | ||
| 111 | + OP_CHECK_IF(maxOutputDesc->GetDataType() != ge::DT_INT32, OP_LOGE(context, "max_output_size dtype must be int32"), | ||
| 112 | + return ge::GRAPH_FAILED); | ||
| 113 | + OP_CHECK_IF(boxesDesc->GetDataType() != iouDesc->GetDataType(), | ||
| 114 | + OP_LOGE(context, "boxes and iou_threshold must share one dtype"), return ge::GRAPH_FAILED); | ||
| 115 | + OP_CHECK_IF(scoresDesc->GetDataType() != scoreThrDesc->GetDataType(), | ||
| 116 | + OP_LOGE(context, "sorted_scores and score_threshold must share one dtype"), return ge::GRAPH_FAILED); | ||
| 117 | + return ge::GRAPH_SUCCESS; | ||
| 118 | +} | ||
| 119 | + | ||
| 120 | +static ge::graphStatus GetOffsetAttr(gert::TilingContext* context, int32_t* offset) | ||
| 121 | +{ | ||
| 122 | + auto attrs = context->GetAttrs(); | ||
| 123 | + OP_CHECK_NULL_WITH_CONTEXT(context, attrs); | ||
| 124 | + const auto* offsetPtr = attrs->GetAttrPointer<int64_t>(ATTR_OFFSET); | ||
| 125 | + OP_CHECK_NULL_WITH_CONTEXT(context, offsetPtr); | ||
| 126 | + OP_CHECK_IF(*offsetPtr != 0 && *offsetPtr != 1, OP_LOGE(context, "offset must be 0 or 1"), return ge::GRAPH_FAILED); | ||
| 127 | + *offset = static_cast<int32_t>(*offsetPtr); | ||
| 128 | + return ge::GRAPH_SUCCESS; | ||
| 129 | +} | ||
| 130 | + | ||
| 131 | +static ge::graphStatus SortedNMSTilingFunc(gert::TilingContext* context) | ||
| 132 | +{ | ||
| 133 | + OP_LOGI(context->GetNodeName(), "Enter SortedNMSTilingFunc"); | ||
| 134 | + OP_CHECK_IF(CheckInputDesc(context) != ge::GRAPH_SUCCESS, OP_LOGE(context, "check input desc failed"), | ||
| 135 | + return ge::GRAPH_FAILED); | ||
| 136 | + | ||
| 137 | + auto boxesShapePtr = context->GetInputShape(INPUT_BOXES); | ||
| 138 | + auto scoresShapePtr = context->GetInputShape(INPUT_SORTED_SCORES); | ||
| 139 | + auto indicesShapePtr = context->GetInputShape(INPUT_INDICES); | ||
| 140 | + auto maxOutputShapePtr = context->GetInputShape(INPUT_MAX_OUTPUT_SIZE); | ||
| 141 | + auto iouShapePtr = context->GetInputShape(INPUT_IOU_THRESHOLD); | ||
| 142 | + auto scoreThrShapePtr = context->GetInputShape(INPUT_SCORE_THRESHOLD); | ||
| 143 | + OP_CHECK_IF(boxesShapePtr == nullptr || scoresShapePtr == nullptr || indicesShapePtr == nullptr || | ||
| 144 | + maxOutputShapePtr == nullptr || iouShapePtr == nullptr || scoreThrShapePtr == nullptr, | ||
| 145 | + OP_LOGE(context, "input shape is nullptr"), return ge::GRAPH_FAILED); | ||
| 146 | + | ||
| 147 | + auto boxesShape = boxesShapePtr->GetStorageShape(); | ||
| 148 | + auto scoresShape = scoresShapePtr->GetStorageShape(); | ||
| 149 | + auto indicesShape = indicesShapePtr->GetStorageShape(); | ||
| 150 | + auto maxOutputShape = maxOutputShapePtr->GetStorageShape(); | ||
| 151 | + auto iouShape = iouShapePtr->GetStorageShape(); | ||
| 152 | + auto scoreThrShape = scoreThrShapePtr->GetStorageShape(); | ||
| 153 | + OP_CHECK_IF(boxesShape.GetDimNum() != BOX_RANK, OP_LOGE(context, "boxes rank must be 2"), return ge::GRAPH_FAILED); | ||
| 154 | + OP_CHECK_IF(boxesShape.GetDim(1) != ge::UNKNOWN_DIM && boxesShape.GetDim(1) != BOX_COORDS, | ||
| 155 | + OP_LOGE(context, "boxes second dim must be 4"), return ge::GRAPH_FAILED); | ||
| 156 | + int64_t boxesNum = boxesShape.GetDim(0); | ||
| 157 | + OP_CHECK_IF(boxesNum < 0 || boxesNum > MAX_BOXES_NUM, | ||
| 158 | + OP_LOGE(context, "boxes first dim must be in [0, %ld] for tiling, got %ld", MAX_BOXES_NUM, boxesNum), | ||
| 159 | + return ge::GRAPH_FAILED); | ||
| 160 | + OP_CHECK_IF(scoresShape.GetDimNum() != 1 || scoresShape.GetDim(0) != boxesNum, | ||
| 161 | + OP_LOGE(context, "sorted_scores shape must be [boxes_num]"), return ge::GRAPH_FAILED); | ||
| 162 | + OP_CHECK_IF(indicesShape.GetDimNum() != 1 || indicesShape.GetDim(0) != boxesNum, | ||
| 163 | + OP_LOGE(context, "input_indices shape must be [boxes_num]"), return ge::GRAPH_FAILED); | ||
| 164 | + OP_CHECK_IF(!IsScalarOrSingleElement(maxOutputShape), | ||
| 165 | + OP_LOGE(context, "max_output_size shape must be scalar or [1]"), return ge::GRAPH_FAILED); | ||
| 166 | + OP_CHECK_IF(!IsScalarOrSingleElement(iouShape), OP_LOGE(context, "iou_threshold shape must be scalar or [1]"), | ||
| 167 | + return ge::GRAPH_FAILED); | ||
| 168 | + OP_CHECK_IF(!IsScalarOrSingleElement(scoreThrShape), | ||
| 169 | + OP_LOGE(context, "score_threshold shape must be scalar or [1]"), return ge::GRAPH_FAILED); | ||
| 170 | + const int32_t strategy = boxesNum >= MULTI_CORE_MIN_BOXES && boxesNum <= MULTI_CORE_MAX_BOXES ? | ||
| 171 | + STRATEGY_PAIRWISE_MASK : | ||
| 172 | + STRATEGY_SINGLE_CORE; | ||
| 173 | + const int64_t maskWordNum = strategy == STRATEGY_PAIRWISE_MASK ? (boxesNum + MASK_BITS - 1) / MASK_BITS : 0; | ||
| 174 | + OP_CHECK_IF(SetWorkspace(context, boxesNum, strategy, maskWordNum) != ge::GRAPH_SUCCESS, | ||
| 175 | + OP_LOGE(context, "set workspace failed"), return ge::GRAPH_FAILED); | ||
| 176 | + | ||
| 177 | + SortedNMSTilingData* tiling = context->GetTilingData<SortedNMSTilingData>(); | ||
| 178 | + OP_CHECK_NULL_WITH_CONTEXT(context, tiling); | ||
| 179 | + OP_CHECK_IF(memset_s(tiling, sizeof(SortedNMSTilingData), 0, sizeof(SortedNMSTilingData)) != EOK, | ||
| 180 | + OP_LOGE(context, "set tiling data failed"), return ge::GRAPH_FAILED); | ||
| 181 | + | ||
| 182 | + tiling->boxesNum = boxesNum; | ||
| 183 | + OP_CHECK_IF(GetOffsetAttr(context, &tiling->offset) != ge::GRAPH_SUCCESS, OP_LOGE(context, "get offset failed"), | ||
| 184 | + return ge::GRAPH_FAILED); | ||
| 185 | + auto* platformInfo = context->GetPlatformInfo(); | ||
| 186 | + OP_CHECK_NULL_WITH_CONTEXT(context, platformInfo); | ||
| 187 | + const auto ascendcPlatform = platform_ascendc::PlatformAscendC(platformInfo); | ||
| 188 | + const int64_t physicalCoreNum = static_cast<int64_t>(ascendcPlatform.GetCoreNumAiv()); | ||
| 189 | + OP_CHECK_IF(physicalCoreNum <= 0, OP_LOGE(context, "AIV core num must be positive"), return ge::GRAPH_FAILED); | ||
| 190 | + if (strategy == STRATEGY_PAIRWISE_MASK) { | ||
| 191 | + const auto* boxesDesc = context->GetInputDesc(INPUT_BOXES); | ||
| 192 | + OP_CHECK_NULL_WITH_CONTEXT(context, boxesDesc); | ||
| 193 | + if (boxesDesc->GetDataType() == ge::DT_FLOAT16) { | ||
| 194 | + const uint64_t boxesBytes = AlignUbBytes(static_cast<uint64_t>(boxesNum) * BOX_COORDS * sizeof(uint16_t)); | ||
| 195 | + const uint64_t areasBytes = AlignUbBytes(static_cast<uint64_t>(boxesNum) * sizeof(float)); | ||
| 196 | + uint64_t ubSize = 0; | ||
| 197 | + ascendcPlatform.GetCoreMemSize(platform_ascendc::CoreMemType::UB, ubSize); | ||
| 198 | + OP_CHECK_IF(ubSize <= SIMT_DATA_CACHE_RESERVE, | ||
| 199 | + OP_LOGE(context, "%lu-byte UB cannot reserve 128KB SIMT Data Cache", ubSize), | ||
| 200 | + return ge::GRAPH_FAILED); | ||
| 201 | + const uint64_t localMemorySize = ubSize - SIMT_DATA_CACHE_RESERVE; | ||
| 202 | + OP_CHECK_IF(localMemorySize > std::numeric_limits<uint32_t>::max(), | ||
| 203 | + OP_LOGE(context, "local memory size %lu exceeds uint32 range", localMemorySize), | ||
| 204 | + return ge::GRAPH_FAILED); | ||
| 205 | + tiling->useLocalBoxes = boxesBytes <= localMemorySize && areasBytes <= localMemorySize - boxesBytes ? 1 : 0; | ||
| 206 | + OP_CHECK_IF(context->SetLocalMemorySize(static_cast<uint32_t>(localMemorySize)) != ge::GRAPH_SUCCESS, | ||
| 207 | + OP_LOGE(context, "SetLocalMemorySize failed for %lu bytes", localMemorySize), | ||
| 208 | + return ge::GRAPH_FAILED); | ||
| 209 | + } | ||
| 210 | + } | ||
| 211 | + int64_t requiredCoreNum = 1; | ||
| 212 | + if (strategy == STRATEGY_PAIRWISE_MASK) { | ||
| 213 | + const int64_t pairMaskWords = boxesNum * maskWordNum; | ||
| 214 | + requiredCoreNum = (pairMaskWords + PAIR_MASK_WORDS_PER_CORE - 1) / PAIR_MASK_WORDS_PER_CORE; | ||
| 215 | + } | ||
| 216 | + tiling->coreNum = static_cast<int32_t>(std::min(requiredCoreNum, physicalCoreNum)); | ||
| 217 | + context->SetBlockDim(static_cast<uint32_t>(tiling->coreNum)); | ||
| 218 | + if (strategy == STRATEGY_PAIRWISE_MASK) { | ||
| 219 | + context->SetScheduleMode(1); | ||
| 220 | + } | ||
| 221 | + return ge::GRAPH_SUCCESS; | ||
| 222 | +} | ||
| 223 | + | ||
| 224 | +static ge::graphStatus TilingParseForSortedNMS([[maybe_unused]] gert::TilingParseContext* context) | ||
| 225 | +{ | ||
| 226 | + return ge::GRAPH_SUCCESS; | ||
| 227 | +} | ||
| 228 | + | ||
| 229 | +struct SortedNMSCompileInfo {}; | ||
| 230 | + | ||
| 231 | +IMPL_OP_OPTILING(SortedNMS).Tiling(SortedNMSTilingFunc).TilingParse<SortedNMSCompileInfo>(TilingParseForSortedNMS); | ||
| 232 | +} // namespace optiling | ||
| @@ -0,0 +1,313 @@ | |||
| 1 | +{ | ||
| 2 | + "op_type": "SortedNMS", | ||
| 3 | + "op_list": [ | ||
| 4 | + { | ||
| 5 | + "bin_filename": "SortedNMS_fbc40177cc5f4223a624050998ede2f1", | ||
| 6 | + "inputs": [ | ||
| 7 | + { | ||
| 8 | + "name": "boxes", | ||
| 9 | + "index": 0, | ||
| 10 | + "dtype": "float16", | ||
| 11 | + "format": "ND", | ||
| 12 | + "paramType": "required", | ||
| 13 | + "shape": [-2], | ||
| 14 | + "format_match_mode": "FormatAgnostic" | ||
| 15 | + }, | ||
| 16 | + { | ||
| 17 | + "name": "sorted_scores", | ||
| 18 | + "index": 1, | ||
| 19 | + "dtype": "float16", | ||
| 20 | + "format": "ND", | ||
| 21 | + "paramType": "required", | ||
| 22 | + "shape": [-2], | ||
| 23 | + "format_match_mode": "FormatAgnostic" | ||
| 24 | + }, | ||
| 25 | + { | ||
| 26 | + "name": "input_indices", | ||
| 27 | + "index": 2, | ||
| 28 | + "dtype": "int32", | ||
| 29 | + "format": "ND", | ||
| 30 | + "paramType": "required", | ||
| 31 | + "shape": [-2], | ||
| 32 | + "format_match_mode": "FormatAgnostic" | ||
| 33 | + }, | ||
| 34 | + { | ||
| 35 | + "name": "max_output_size", | ||
| 36 | + "index": 3, | ||
| 37 | + "dtype": "int32", | ||
| 38 | + "format": "ND", | ||
| 39 | + "paramType": "required", | ||
| 40 | + "shape": [-2], | ||
| 41 | + "format_match_mode": "FormatAgnostic" | ||
| 42 | + }, | ||
| 43 | + { | ||
| 44 | + "name": "iou_threshold", | ||
| 45 | + "index": 4, | ||
| 46 | + "dtype": "float16", | ||
| 47 | + "format": "ND", | ||
| 48 | + "paramType": "required", | ||
| 49 | + "shape": [-2], | ||
| 50 | + "format_match_mode": "FormatAgnostic" | ||
| 51 | + }, | ||
| 52 | + { | ||
| 53 | + "name": "score_threshold", | ||
| 54 | + "index": 5, | ||
| 55 | + "dtype": "float16", | ||
| 56 | + "format": "ND", | ||
| 57 | + "paramType": "required", | ||
| 58 | + "shape": [-2], | ||
| 59 | + "format_match_mode": "FormatAgnostic" | ||
| 60 | + } | ||
| 61 | + ], | ||
| 62 | + "outputs": [ | ||
| 63 | + { | ||
| 64 | + "name": "selected_indices", | ||
| 65 | + "index": 0, | ||
| 66 | + "dtype": "int32", | ||
| 67 | + "format": "ND", | ||
| 68 | + "paramType": "required", | ||
| 69 | + "shape": [-2], | ||
| 70 | + "format_match_mode": "FormatAgnostic" | ||
| 71 | + } | ||
| 72 | + ], | ||
| 73 | + "attrs": [ | ||
| 74 | + { | ||
| 75 | + "name": "offset", | ||
| 76 | + "dtype": "int", | ||
| 77 | + "value": 0 | ||
| 78 | + } | ||
| 79 | + ] | ||
| 80 | + }, | ||
| 81 | + { | ||
| 82 | + "bin_filename": "SortedNMS_d1e8cd8e75b0c427e4f4efcf546ca39b", | ||
| 83 | + "inputs": [ | ||
| 84 | + { | ||
| 85 | + "name": "boxes", | ||
| 86 | + "index": 0, | ||
| 87 | + "dtype": "float16", | ||
| 88 | + "format": "ND", | ||
| 89 | + "paramType": "required", | ||
| 90 | + "shape": [-2], | ||
| 91 | + "format_match_mode": "FormatAgnostic" | ||
| 92 | + }, | ||
| 93 | + { | ||
| 94 | + "name": "sorted_scores", | ||
| 95 | + "index": 1, | ||
| 96 | + "dtype": "float32", | ||
| 97 | + "format": "ND", | ||
| 98 | + "paramType": "required", | ||
| 99 | + "shape": [-2], | ||
| 100 | + "format_match_mode": "FormatAgnostic" | ||
| 101 | + }, | ||
| 102 | + { | ||
| 103 | + "name": "input_indices", | ||
| 104 | + "index": 2, | ||
| 105 | + "dtype": "int32", | ||
| 106 | + "format": "ND", | ||
| 107 | + "paramType": "required", | ||
| 108 | + "shape": [-2], | ||
| 109 | + "format_match_mode": "FormatAgnostic" | ||
| 110 | + }, | ||
| 111 | + { | ||
| 112 | + "name": "max_output_size", | ||
| 113 | + "index": 3, | ||
| 114 | + "dtype": "int32", | ||
| 115 | + "format": "ND", | ||
| 116 | + "paramType": "required", | ||
| 117 | + "shape": [-2], | ||
| 118 | + "format_match_mode": "FormatAgnostic" | ||
| 119 | + }, | ||
| 120 | + { | ||
| 121 | + "name": "iou_threshold", | ||
| 122 | + "index": 4, | ||
| 123 | + "dtype": "float16", | ||
| 124 | + "format": "ND", | ||
| 125 | + "paramType": "required", | ||
| 126 | + "shape": [-2], | ||
| 127 | + "format_match_mode": "FormatAgnostic" | ||
| 128 | + }, | ||
| 129 | + { | ||
| 130 | + "name": "score_threshold", | ||
| 131 | + "index": 5, | ||
| 132 | + "dtype": "float32", | ||
| 133 | + "format": "ND", | ||
| 134 | + "paramType": "required", | ||
| 135 | + "shape": [-2], | ||
| 136 | + "format_match_mode": "FormatAgnostic" | ||
| 137 | + } | ||
| 138 | + ], | ||
| 139 | + "outputs": [ | ||
| 140 | + { | ||
| 141 | + "name": "selected_indices", | ||
| 142 | + "index": 0, | ||
| 143 | + "dtype": "int32", | ||
| 144 | + "format": "ND", | ||
| 145 | + "paramType": "required", | ||
| 146 | + "shape": [-2], | ||
| 147 | + "format_match_mode": "FormatAgnostic" | ||
| 148 | + } | ||
| 149 | + ], | ||
| 150 | + "attrs": [ | ||
| 151 | + { | ||
| 152 | + "name": "offset", | ||
| 153 | + "dtype": "int", | ||
| 154 | + "value": 0 | ||
| 155 | + } | ||
| 156 | + ] | ||
| 157 | + }, | ||
| 158 | + { | ||
| 159 | + "bin_filename": "SortedNMS_b2480a92abd1aca211709bf6275fa350", | ||
| 160 | + "inputs": [ | ||
| 161 | + { | ||
| 162 | + "name": "boxes", | ||
| 163 | + "index": 0, | ||
| 164 | + "dtype": "float32", | ||
| 165 | + "format": "ND", | ||
| 166 | + "paramType": "required", | ||
| 167 | + "shape": [-2], | ||
| 168 | + "format_match_mode": "FormatAgnostic" | ||
| 169 | + }, | ||
| 170 | + { | ||
| 171 | + "name": "sorted_scores", | ||
| 172 | + "index": 1, | ||
| 173 | + "dtype": "float16", | ||
| 174 | + "format": "ND", | ||
| 175 | + "paramType": "required", | ||
| 176 | + "shape": [-2], | ||
| 177 | + "format_match_mode": "FormatAgnostic" | ||
| 178 | + }, | ||
| 179 | + { | ||
| 180 | + "name": "input_indices", | ||
| 181 | + "index": 2, | ||
| 182 | + "dtype": "int32", | ||
| 183 | + "format": "ND", | ||
| 184 | + "paramType": "required", | ||
| 185 | + "shape": [-2], | ||
| 186 | + "format_match_mode": "FormatAgnostic" | ||
| 187 | + }, | ||
| 188 | + { | ||
| 189 | + "name": "max_output_size", | ||
| 190 | + "index": 3, | ||
| 191 | + "dtype": "int32", | ||
| 192 | + "format": "ND", | ||
| 193 | + "paramType": "required", | ||
| 194 | + "shape": [-2], | ||
| 195 | + "format_match_mode": "FormatAgnostic" | ||
| 196 | + }, | ||
| 197 | + { | ||
| 198 | + "name": "iou_threshold", | ||
| 199 | + "index": 4, | ||
| 200 | + "dtype": "float32", | ||
| 201 | + "format": "ND", | ||
| 202 | + "paramType": "required", | ||
| 203 | + "shape": [-2], | ||
| 204 | + "format_match_mode": "FormatAgnostic" | ||
| 205 | + }, | ||
| 206 | + { | ||
| 207 | + "name": "score_threshold", | ||
| 208 | + "index": 5, | ||
| 209 | + "dtype": "float16", | ||
| 210 | + "format": "ND", | ||
| 211 | + "paramType": "required", | ||
| 212 | + "shape": [-2], | ||
| 213 | + "format_match_mode": "FormatAgnostic" | ||
| 214 | + } | ||
| 215 | + ], | ||
| 216 | + "outputs": [ | ||
| 217 | + { | ||
| 218 | + "name": "selected_indices", | ||
| 219 | + "index": 0, | ||
| 220 | + "dtype": "int32", | ||
| 221 | + "format": "ND", | ||
| 222 | + "paramType": "required", | ||
| 223 | + "shape": [-2], | ||
| 224 | + "format_match_mode": "FormatAgnostic" | ||
| 225 | + } | ||
| 226 | + ], | ||
| 227 | + "attrs": [ | ||
| 228 | + { | ||
| 229 | + "name": "offset", | ||
| 230 | + "dtype": "int", | ||
| 231 | + "value": 0 | ||
| 232 | + } | ||
| 233 | + ] | ||
| 234 | + }, | ||
| 235 | + { | ||
| 236 | + "bin_filename": "SortedNMS_55c1d7cf8ff616b8d9d92d67eed23d89", | ||
| 237 | + "inputs": [ | ||
| 238 | + { | ||
| 239 | + "name": "boxes", | ||
| 240 | + "index": 0, | ||
| 241 | + "dtype": "float32", | ||
| 242 | + "format": "ND", | ||
| 243 | + "paramType": "required", | ||
| 244 | + "shape": [-2], | ||
| 245 | + "format_match_mode": "FormatAgnostic" | ||
| 246 | + }, | ||
| 247 | + { | ||
| 248 | + "name": "sorted_scores", | ||
| 249 | + "index": 1, | ||
| 250 | + "dtype": "float32", | ||
| 251 | + "format": "ND", | ||
| 252 | + "paramType": "required", | ||
| 253 | + "shape": [-2], | ||
| 254 | + "format_match_mode": "FormatAgnostic" | ||
| 255 | + }, | ||
| 256 | + { | ||
| 257 | + "name": "input_indices", | ||
| 258 | + "index": 2, | ||
| 259 | + "dtype": "int32", | ||
| 260 | + "format": "ND", | ||
| 261 | + "paramType": "required", | ||
| 262 | + "shape": [-2], | ||
| 263 | + "format_match_mode": "FormatAgnostic" | ||
| 264 | + }, | ||
| 265 | + { | ||
| 266 | + "name": "max_output_size", | ||
| 267 | + "index": 3, | ||
| 268 | + "dtype": "int32", | ||
| 269 | + "format": "ND", | ||
| 270 | + "paramType": "required", | ||
| 271 | + "shape": [-2], | ||
| 272 | + "format_match_mode": "FormatAgnostic" | ||
| 273 | + }, | ||
| 274 | + { | ||
| 275 | + "name": "iou_threshold", | ||
| 276 | + "index": 4, | ||
| 277 | + "dtype": "float32", | ||
| 278 | + "format": "ND", | ||
| 279 | + "paramType": "required", | ||
| 280 | + "shape": [-2], | ||
| 281 | + "format_match_mode": "FormatAgnostic" | ||
| 282 | + }, | ||
| 283 | + { | ||
| 284 | + "name": "score_threshold", | ||
| 285 | + "index": 5, | ||
| 286 | + "dtype": "float32", | ||
| 287 | + "format": "ND", | ||
| 288 | + "paramType": "required", | ||
| 289 | + "shape": [-2], | ||
| 290 | + "format_match_mode": "FormatAgnostic" | ||
| 291 | + } | ||
| 292 | + ], | ||
| 293 | + "outputs": [ | ||
| 294 | + { | ||
| 295 | + "name": "selected_indices", | ||
| 296 | + "index": 0, | ||
| 297 | + "dtype": "int32", | ||
| 298 | + "format": "ND", | ||
| 299 | + "paramType": "required", | ||
| 300 | + "shape": [-2], | ||
| 301 | + "format_match_mode": "FormatAgnostic" | ||
| 302 | + } | ||
| 303 | + ], | ||
| 304 | + "attrs": [ | ||
| 305 | + { | ||
| 306 | + "name": "offset", | ||
| 307 | + "dtype": "int", | ||
| 308 | + "value": 0 | ||
| 309 | + } | ||
| 310 | + ] | ||
| 311 | + } | ||
| 312 | + ] | ||
| 313 | +} | ||
| @@ -0,0 +1,13 @@ | |||
| 1 | +; 该文件主要影响 opc 工具 编译二进制kernel时, --simplified_key_mode 选项中填写的值,格式如下所示: | ||
| 2 | +; [某算子] | ||
| 3 | +; default=xx | ||
| 4 | +; ascendxx=xx | ||
| 5 | +; 其中,default为默认mode,ascnedxx为可选mode,如果不同芯片有差异化要求时,需要配置; | ||
| 6 | +; 1)如果没有配置:非ascendC算子继续按空处理,即opc编译命令中不添加 --simplified_key_mode 选项,AscendC算子按照 simplified_key_mode=0 处理 | ||
| 7 | +; 2)如果仅有default配置:各个版本按default配置 | ||
| 8 | +; 3)如果仅有某些平台的配置,没有default配置:对应平台的按照配置的值传递,非对应平台的:非AscendC算子继续按空处理,AscendC算子按照 simplified_key_mode=0 处理 | ||
| 9 | +; 4)如果default配置和平台配置都有:对应平台的使用平台的配置,非对应的平台的以default值配置。 | ||
| 10 | +; 5)对于自定义simplified key的情况,需要在binary_simplified_key_mode.ini 文件中显式配置为None,不传入 --simplified_key_mode 选项,由opc工具和FE框架自行判断使用何种模式 | ||
| 11 | +; 6)是否是AscendC算子,由 ops/built-in/tbe/op_info_cfg/parser/ascendc_config.json 中配置的算子名字和对于的平台决定 | ||
| 12 | +[SortedNMS] | ||
| 13 | +default=0 | ||
| @@ -0,0 +1,112 @@ | |||
| 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 | + | ||
| 12 | + | ||
| 13 | +namespace ops { | ||
| 14 | +class SortedNMS : public OpDef { | ||
| 15 | +public: | ||
| 16 | + explicit SortedNMS(const char* name) : OpDef(name) | ||
| 17 | + { | ||
| 18 | + this->Input("boxes") | ||
| 19 | + .ParamType(REQUIRED) | ||
| 20 | + .DataType({ge::DT_FLOAT16, ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_FLOAT}) | ||
| 21 | + .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) | ||
| 22 | + .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) | ||
| 23 | + .AutoContiguous(); | ||
| 24 | + this->Input("sorted_scores") | ||
| 25 | + .ParamType(REQUIRED) | ||
| 26 | + .DataType({ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_FLOAT16, ge::DT_FLOAT}) | ||
| 27 | + .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) | ||
| 28 | + .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) | ||
| 29 | + .AutoContiguous(); | ||
| 30 | + this->Input("input_indices") | ||
| 31 | + .ParamType(REQUIRED) | ||
| 32 | + .DataType({ge::DT_INT32, ge::DT_INT32, ge::DT_INT32, ge::DT_INT32}) | ||
| 33 | + .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) | ||
| 34 | + .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) | ||
| 35 | + .AutoContiguous(); | ||
| 36 | + this->Input("max_output_size") | ||
| 37 | + .ParamType(REQUIRED) | ||
| 38 | + .DataType({ge::DT_INT32, ge::DT_INT32, ge::DT_INT32, ge::DT_INT32}) | ||
| 39 | + .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) | ||
| 40 | + .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) | ||
| 41 | + .AutoContiguous(); | ||
| 42 | + this->Input("iou_threshold") | ||
| 43 | + .ParamType(REQUIRED) | ||
| 44 | + .DataType({ge::DT_FLOAT16, ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_FLOAT}) | ||
| 45 | + .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) | ||
| 46 | + .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) | ||
| 47 | + .AutoContiguous(); | ||
| 48 | + this->Input("score_threshold") | ||
| 49 | + .ParamType(REQUIRED) | ||
| 50 | + .DataType({ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_FLOAT16, ge::DT_FLOAT}) | ||
| 51 | + .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) | ||
| 52 | + .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) | ||
| 53 | + .AutoContiguous(); | ||
| 54 | + this->Output("selected_indices") | ||
| 55 | + .ParamType(REQUIRED) | ||
| 56 | + .DataType({ge::DT_INT32, ge::DT_INT32, ge::DT_INT32, ge::DT_INT32}) | ||
| 57 | + .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) | ||
| 58 | + .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) | ||
| 59 | + .AutoContiguous() | ||
| 60 | + .OutputShapeDependOnCompute(); | ||
| 61 | + | ||
| 62 | + this->Attr("offset").AttrType(OPTIONAL).Int(0); | ||
| 63 | + | ||
| 64 | + OpAICoreConfig aiCoreConfig; | ||
| 65 | + aiCoreConfig.Input("boxes") | ||
| 66 | + .ParamType(REQUIRED) | ||
| 67 | + .DataType({ge::DT_FLOAT16, ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_FLOAT}) | ||
| 68 | + .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) | ||
| 69 | + .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}); | ||
| 70 | + aiCoreConfig.Input("sorted_scores") | ||
| 71 | + .ParamType(REQUIRED) | ||
| 72 | + .DataType({ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_FLOAT16, ge::DT_FLOAT}) | ||
| 73 | + .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) | ||
| 74 | + .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}); | ||
| 75 | + aiCoreConfig.Input("input_indices") | ||
| 76 | + .ParamType(REQUIRED) | ||
| 77 | + .DataType({ge::DT_INT32, ge::DT_INT32, ge::DT_INT32, ge::DT_INT32}) | ||
| 78 | + .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) | ||
| 79 | + .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}); | ||
| 80 | + aiCoreConfig.Input("max_output_size") | ||
| 81 | + .ParamType(REQUIRED) | ||
| 82 | + .DataType({ge::DT_INT32, ge::DT_INT32, ge::DT_INT32, ge::DT_INT32}) | ||
| 83 | + .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) | ||
| 84 | + .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}); | ||
| 85 | + aiCoreConfig.Input("iou_threshold") | ||
| 86 | + .ParamType(REQUIRED) | ||
| 87 | + .DataType({ge::DT_FLOAT16, ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_FLOAT}) | ||
| 88 | + .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) | ||
| 89 | + .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}); | ||
| 90 | + aiCoreConfig.Input("score_threshold") | ||
| 91 | + .ParamType(REQUIRED) | ||
| 92 | + .DataType({ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_FLOAT16, ge::DT_FLOAT}) | ||
| 93 | + .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) | ||
| 94 | + .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}); | ||
| 95 | + aiCoreConfig.Output("selected_indices") | ||
| 96 | + .ParamType(REQUIRED) | ||
| 97 | + .DataType({ge::DT_INT32, ge::DT_INT32, ge::DT_INT32, ge::DT_INT32}) | ||
| 98 | + .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) | ||
| 99 | + .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) | ||
| 100 | + .OutputShapeDependOnCompute(); | ||
| 101 | + aiCoreConfig.DynamicCompileStaticFlag(true) | ||
| 102 | + .DynamicFormatFlag(false) | ||
| 103 | + .DynamicRankSupportFlag(true) | ||
| 104 | + .DynamicShapeSupportFlag(true) | ||
| 105 | + .NeedCheckSupportFlag(false) | ||
| 106 | + .PrecisionReduceFlag(false) | ||
| 107 | + .ExtendCfgInfo("opFile.value", "sorted_nms_apt"); | ||
| 108 | + this->AICore().AddConfig("ascend950", aiCoreConfig); | ||
| 109 | + } | ||
| 110 | +}; | ||
| 111 | +OP_ADD(SortedNMS); | ||
| 112 | +} // namespace ops | ||
| @@ -0,0 +1,82 @@ | |||
| 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 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | +namespace { | ||
| 15 | +constexpr int64_t INPUT_BOXES = 0; | ||
| 16 | +constexpr int64_t OUTPUT_SELECTED_INDICES = 0; | ||
| 17 | +constexpr int64_t BOX_RANK = 2; | ||
| 18 | +constexpr int64_t BOX_COORDS = 4; | ||
| 19 | +constexpr int64_t OUTPUT_RANK = 1; | ||
| 20 | +} // namespace | ||
| 21 | + | ||
| 22 | +namespace ops { | ||
| 23 | +static ge::graphStatus InferShapeForSortedNMS(gert::InferShapeContext* context) | ||
| 24 | +{ | ||
| 25 | + auto boxesShape = context->GetInputShape(INPUT_BOXES); | ||
| 26 | + auto selectedShape = context->GetOutputShape(OUTPUT_SELECTED_INDICES); | ||
| 27 | + if (boxesShape == nullptr || selectedShape == nullptr) { | ||
| 28 | + OP_LOGE(context, "boxes shape or selected_indices shape is nullptr"); | ||
| 29 | + return ge::GRAPH_FAILED; | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + if (boxesShape->GetDimNum() != BOX_RANK) { | ||
| 33 | + OP_LOGE(context, "boxes rank must be 2"); | ||
| 34 | + return ge::GRAPH_FAILED; | ||
| 35 | + } | ||
| 36 | + if (boxesShape->GetDim(1) != ge::UNKNOWN_DIM && boxesShape->GetDim(1) != BOX_COORDS) { | ||
| 37 | + OP_LOGE(context, "boxes second dim must be 4"); | ||
| 38 | + return ge::GRAPH_FAILED; | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + selectedShape->SetDimNum(OUTPUT_RANK); | ||
| 42 | + selectedShape->SetDim(0, ge::UNKNOWN_DIM); | ||
| 43 | + return ge::GRAPH_SUCCESS; | ||
| 44 | +} | ||
| 45 | + | ||
| 46 | +static ge::graphStatus InferShapeRangeForSortedNMS(gert::InferShapeRangeContext* context) | ||
| 47 | +{ | ||
| 48 | + OP_CHECK_IF(context == nullptr, OP_LOGE("SortedNMS", "InferShapeRangeContext is nullptr"), return ge::GRAPH_FAILED); | ||
| 49 | + | ||
| 50 | + auto boxesRange = context->GetInputShapeRange(INPUT_BOXES); | ||
| 51 | + auto selectedRange = context->GetOutputShapeRange(OUTPUT_SELECTED_INDICES); | ||
| 52 | + OP_CHECK_NULL_WITH_CONTEXT(context, boxesRange); | ||
| 53 | + OP_CHECK_NULL_WITH_CONTEXT(context, boxesRange->GetMin()); | ||
| 54 | + OP_CHECK_NULL_WITH_CONTEXT(context, boxesRange->GetMax()); | ||
| 55 | + OP_CHECK_NULL_WITH_CONTEXT(context, selectedRange); | ||
| 56 | + OP_CHECK_NULL_WITH_CONTEXT(context, selectedRange->GetMin()); | ||
| 57 | + OP_CHECK_NULL_WITH_CONTEXT(context, selectedRange->GetMax()); | ||
| 58 | + | ||
| 59 | + OP_CHECK_IF(boxesRange->GetMin()->GetDimNum() != BOX_RANK || boxesRange->GetMax()->GetDimNum() != BOX_RANK, | ||
| 60 | + OP_LOGE(context, "boxes shape range rank must be 2"), return ge::GRAPH_FAILED); | ||
| 61 | + const int64_t maxBoxesNum = boxesRange->GetMax()->GetDim(0); | ||
| 62 | + OP_CHECK_IF(maxBoxesNum < 0, OP_LOGE(context, "boxes shape range first dim must be known"), | ||
| 63 | + return ge::GRAPH_FAILED); | ||
| 64 | + | ||
| 65 | + selectedRange->GetMin()->SetDimNum(OUTPUT_RANK); | ||
| 66 | + selectedRange->GetMin()->SetDim(0, 0); | ||
| 67 | + selectedRange->GetMax()->SetDimNum(OUTPUT_RANK); | ||
| 68 | + selectedRange->GetMax()->SetDim(0, maxBoxesNum); | ||
| 69 | + return ge::GRAPH_SUCCESS; | ||
| 70 | +} | ||
| 71 | + | ||
| 72 | +static ge::graphStatus InferDataTypeForSortedNMS(gert::InferDataTypeContext* context) | ||
| 73 | +{ | ||
| 74 | + context->SetOutputDataType(OUTPUT_SELECTED_INDICES, ge::DT_INT32); | ||
| 75 | + return ge::GRAPH_SUCCESS; | ||
| 76 | +} | ||
| 77 | + | ||
| 78 | +IMPL_OP_INFERSHAPE(SortedNMS) | ||
| 79 | + .InferShape(InferShapeForSortedNMS) | ||
| 80 | + .InferShapeRange(InferShapeRangeForSortedNMS) | ||
| 81 | + .InferDataType(InferDataTypeForSortedNMS); | ||
| 82 | +} // namespace ops | ||
| @@ -0,0 +1,503 @@ | |||
| 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 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | +namespace NsSortedNMS { | ||
| 22 | +using namespace AscendC; | ||
| 23 | + | ||
| 24 | +constexpr uint32_t THREAD_NUM = 1024; | ||
| 25 | +constexpr uint32_t LOCAL_PAIRWISE_THREAD_NUM = 1024; | ||
| 26 | +constexpr uint32_t MIN_THREAD_NUM = 32; | ||
| 27 | +constexpr int32_t BOX_COORDS = 4; | ||
| 28 | +constexpr int32_t INIT_INDEX = -1; | ||
| 29 | +constexpr int32_t NOT_SUPPRESSED = 0; | ||
| 30 | +constexpr int32_t SUPPRESSED = 1; | ||
| 31 | +constexpr int32_t WORK_SELECTED_COUNT = 0; | ||
| 32 | +constexpr int32_t WORK_CURRENT_INDEX = 1; | ||
| 33 | +constexpr int32_t OUTPUT_SHAPE_INFO_SIZE = 9; | ||
| 34 | +constexpr int32_t MASK_BITS = 32; | ||
| 35 | +constexpr int64_t MULTI_CORE_MIN_BOXES = 1025; | ||
| 36 | +constexpr int64_t MULTI_CORE_MAX_BOXES = 8192; | ||
| 37 | +constexpr uint32_t UB_BLOCK_SIZE = 32; | ||
| 38 | +// shape_out uses uint64_t entries. Bit 31 marks the rank field as uint64_t encoded. | ||
| 39 | +constexpr uint64_t OUTPUT_SHAPE_RANK_ONE = 0x80000001ULL; | ||
| 40 | + | ||
| 41 | +__aicore__ inline uint32_t AlignUbBytes(uint32_t bytes) | ||
| 42 | +{ | ||
| 43 | + return (bytes + UB_BLOCK_SIZE - 1U) / UB_BLOCK_SIZE * UB_BLOCK_SIZE; | ||
| 44 | +} | ||
| 45 | + | ||
| 46 | +__aicore__ inline uint32_t GetSimtThreadNum(int64_t workItems) | ||
| 47 | +{ | ||
| 48 | + uint32_t threadNum = MIN_THREAD_NUM; | ||
| 49 | + while (static_cast<int64_t>(threadNum) < workItems && threadNum < THREAD_NUM) { | ||
| 50 | + threadNum <<= 1; | ||
| 51 | + } | ||
| 52 | + return threadNum; | ||
| 53 | +} | ||
| 54 | + | ||
| 55 | +__simt_callee__ __aicore__ inline void WriteOutputShape(__gm__ uint64_t* outputShape, int32_t selectedCount) | ||
| 56 | +{ | ||
| 57 | + for (int32_t index = 0; index < OUTPUT_SHAPE_INFO_SIZE; ++index) { | ||
| 58 | + outputShape[index] = 0; | ||
| 59 | + } | ||
| 60 | + outputShape[0] = OUTPUT_SHAPE_RANK_ONE; | ||
| 61 | + outputShape[1] = static_cast<uint64_t>(selectedCount); | ||
| 62 | +} | ||
| 63 | + | ||
| 64 | +__simt_callee__ __aicore__ inline float ReadAsFloat(float val) { return val; } | ||
| 65 | + | ||
| 66 | +__simt_callee__ __aicore__ inline float ReadAsFloat(half val) { return __half2float(val); } | ||
| 67 | + | ||
| 68 | +template <typename TScore, typename TScoreThreshold> | ||
| 69 | +__simt_vf__ __aicore__ inline void FindActiveBoxesNum(int64_t boxesNum, __gm__ TScore* sortedScores, | ||
| 70 | + __gm__ TScoreThreshold* scoreThreshold, | ||
| 71 | + __gm__ int32_t* activeBoxesNum) | ||
| 72 | +{ | ||
| 73 | + if (threadIdx.x != 0) { | ||
| 74 | + return; | ||
| 75 | + } | ||
| 76 | + const float scoreThr = ReadAsFloat(scoreThreshold[0]); | ||
| 77 | + int64_t left = 0; | ||
| 78 | + int64_t right = boxesNum; | ||
| 79 | + while (left < right) { | ||
| 80 | + const int64_t middle = left + (right - left) / 2; | ||
| 81 | + if (ReadAsFloat(sortedScores[middle]) > scoreThr) { | ||
| 82 | + left = middle + 1; | ||
| 83 | + } else { | ||
| 84 | + right = middle; | ||
| 85 | + } | ||
| 86 | + } | ||
| 87 | + activeBoxesNum[0] = static_cast<int32_t>(left); | ||
| 88 | +} | ||
| 89 | + | ||
| 90 | +__simt_callee__ __aicore__ inline float ClampNonNegative(float val) { return val > 0.0f ? val : 0.0f; } | ||
| 91 | + | ||
| 92 | +template <typename T> | ||
| 93 | +__simt_callee__ __aicore__ inline float BoxArea(__gm__ T* boxes, int32_t boxIdx, float offset) | ||
| 94 | +{ | ||
| 95 | + int64_t base = static_cast<int64_t>(boxIdx) * BOX_COORDS; | ||
| 96 | + float x1 = ReadAsFloat(boxes[base]); | ||
| 97 | + float y1 = ReadAsFloat(boxes[base + 1]); | ||
| 98 | + float x2 = ReadAsFloat(boxes[base + 2]); | ||
| 99 | + float y2 = ReadAsFloat(boxes[base + 3]); | ||
| 100 | + float width = ClampNonNegative(x2 - x1 + offset); | ||
| 101 | + float height = ClampNonNegative(y2 - y1 + offset); | ||
| 102 | + return width * height; | ||
| 103 | +} | ||
| 104 | + | ||
| 105 | +template <typename T> | ||
| 106 | +__simt_callee__ __aicore__ inline float Intersection(__gm__ T* boxes, int32_t lhs, int32_t rhs, float offset) | ||
| 107 | +{ | ||
| 108 | + int64_t lhsBase = static_cast<int64_t>(lhs) * BOX_COORDS; | ||
| 109 | + int64_t rhsBase = static_cast<int64_t>(rhs) * BOX_COORDS; | ||
| 110 | + float x1 = fmaxf(ReadAsFloat(boxes[lhsBase]), ReadAsFloat(boxes[rhsBase])); | ||
| 111 | + float y1 = fmaxf(ReadAsFloat(boxes[lhsBase + 1]), ReadAsFloat(boxes[rhsBase + 1])); | ||
| 112 | + float x2 = fminf(ReadAsFloat(boxes[lhsBase + 2]), ReadAsFloat(boxes[rhsBase + 2])); | ||
| 113 | + float y2 = fminf(ReadAsFloat(boxes[lhsBase + 3]), ReadAsFloat(boxes[rhsBase + 3])); | ||
| 114 | + float width = ClampNonNegative(x2 - x1 + offset); | ||
| 115 | + float height = ClampNonNegative(y2 - y1 + offset); | ||
| 116 | + return width * height; | ||
| 117 | +} | ||
| 118 | + | ||
| 119 | +template <typename T> | ||
| 120 | +__simt_callee__ __aicore__ inline float Iou(__gm__ T* boxes, int32_t lhs, int32_t rhs, float offset) | ||
| 121 | +{ | ||
| 122 | + float inter = Intersection(boxes, lhs, rhs, offset); | ||
| 123 | + float lhsArea = BoxArea(boxes, lhs, offset); | ||
| 124 | + float rhsArea = BoxArea(boxes, rhs, offset); | ||
| 125 | + float denom = lhsArea + rhsArea - inter; | ||
| 126 | + if (denom <= 0.0f) { | ||
| 127 | + return 0.0f; | ||
| 128 | + } | ||
| 129 | + return inter / denom; | ||
| 130 | +} | ||
| 131 | + | ||
| 132 | +template <typename T> | ||
| 133 | +__simt_callee__ __aicore__ inline float IntersectionLocal(__ubuf__ T* boxes, int32_t lhs, int32_t rhs, float offset) | ||
| 134 | +{ | ||
| 135 | + int64_t lhsBase = static_cast<int64_t>(lhs) * BOX_COORDS; | ||
| 136 | + int64_t rhsBase = static_cast<int64_t>(rhs) * BOX_COORDS; | ||
| 137 | + float x1 = fmaxf(ReadAsFloat(boxes[lhsBase]), ReadAsFloat(boxes[rhsBase])); | ||
| 138 | + float y1 = fmaxf(ReadAsFloat(boxes[lhsBase + 1]), ReadAsFloat(boxes[rhsBase + 1])); | ||
| 139 | + float x2 = fminf(ReadAsFloat(boxes[lhsBase + 2]), ReadAsFloat(boxes[rhsBase + 2])); | ||
| 140 | + float y2 = fminf(ReadAsFloat(boxes[lhsBase + 3]), ReadAsFloat(boxes[rhsBase + 3])); | ||
| 141 | + float width = ClampNonNegative(x2 - x1 + offset); | ||
| 142 | + float height = ClampNonNegative(y2 - y1 + offset); | ||
| 143 | + return width * height; | ||
| 144 | +} | ||
| 145 | + | ||
| 146 | +template <typename TBox> | ||
| 147 | +__simt_vf__ __aicore__ __launch_bounds__(THREAD_NUM) inline void BuildBoxAreas(int64_t boxesNum, int32_t offset, | ||
| 148 | + __ubuf__ TBox* boxes, | ||
| 149 | + __ubuf__ float* boxAreas) | ||
| 150 | +{ | ||
| 151 | + const float offsetVal = static_cast<float>(offset); | ||
| 152 | + for (int64_t boxIndex = threadIdx.x; boxIndex < boxesNum; boxIndex += blockDim.x) { | ||
| 153 | + const int64_t base = boxIndex * BOX_COORDS; | ||
| 154 | + const float width = ClampNonNegative(ReadAsFloat(boxes[base + 2]) - ReadAsFloat(boxes[base]) + offsetVal); | ||
| 155 | + const float height = ClampNonNegative(ReadAsFloat(boxes[base + 3]) - ReadAsFloat(boxes[base + 1]) + offsetVal); | ||
| 156 | + boxAreas[boxIndex] = width * height; | ||
| 157 | + } | ||
| 158 | +} | ||
| 159 | + | ||
| 160 | +template <typename T> | ||
| 161 | +__simt_callee__ __aicore__ inline float IouLocal(__ubuf__ T* boxes, __ubuf__ float* boxAreas, int32_t lhs, int32_t rhs, | ||
| 162 | + float offset) | ||
| 163 | +{ | ||
| 164 | + const float inter = IntersectionLocal(boxes, lhs, rhs, offset); | ||
| 165 | + const float denom = boxAreas[lhs] + boxAreas[rhs] - inter; | ||
| 166 | + if (denom <= 0.0f) { | ||
| 167 | + return 0.0f; | ||
| 168 | + } | ||
| 169 | + return inter / denom; | ||
| 170 | +} | ||
| 171 | + | ||
| 172 | +template <typename TBox, typename TScore, typename TIouThreshold, typename TScoreThreshold> | ||
| 173 | +__simt_vf__ __aicore__ __launch_bounds__(THREAD_NUM) inline void SortedNMSSingleCore( | ||
| 174 | + int64_t boxesNum, int32_t offset, __gm__ TBox* boxes, __gm__ TScore* sortedScores, __gm__ int32_t* inputIndices, | ||
| 175 | + __gm__ int32_t* maxOutputSize, __gm__ TIouThreshold* iouThreshold, __gm__ TScoreThreshold* scoreThreshold, | ||
| 176 | + __gm__ int32_t* selectedIndices, __gm__ uint64_t* outputShape, __gm__ int32_t* work) | ||
| 177 | +{ | ||
| 178 | + if (boxesNum <= 0) { | ||
| 179 | + if (threadIdx.x == 0) { | ||
| 180 | + WriteOutputShape(outputShape, 0); | ||
| 181 | + } | ||
| 182 | + return; | ||
| 183 | + } | ||
| 184 | + | ||
| 185 | + __gm__ int32_t* control = work; | ||
| 186 | + __gm__ int32_t* suppressed = work + 2; | ||
| 187 | + | ||
| 188 | + for (int64_t idx = threadIdx.x; idx < boxesNum; idx += blockDim.x) { | ||
| 189 | + suppressed[idx] = NOT_SUPPRESSED; | ||
| 190 | + } | ||
| 191 | + if (threadIdx.x == 0) { | ||
| 192 | + control[WORK_SELECTED_COUNT] = 0; | ||
| 193 | + control[WORK_CURRENT_INDEX] = INIT_INDEX; | ||
| 194 | + } | ||
| 195 | + asc_threadfence_block(); | ||
| 196 | + asc_syncthreads(); | ||
| 197 | + | ||
| 198 | + int32_t maxOut = maxOutputSize[0]; | ||
| 199 | + if (maxOut < 0) { | ||
| 200 | + maxOut = 0; | ||
| 201 | + } | ||
| 202 | + if (static_cast<int64_t>(maxOut) > boxesNum) { | ||
| 203 | + maxOut = static_cast<int32_t>(boxesNum); | ||
| 204 | + } | ||
| 205 | + float iouThr = ReadAsFloat(iouThreshold[0]); | ||
| 206 | + float scoreThr = ReadAsFloat(scoreThreshold[0]); | ||
| 207 | + float offsetVal = static_cast<float>(offset); | ||
| 208 | + | ||
| 209 | + for (int64_t sortedPos = 0; sortedPos < boxesNum; ++sortedPos) { | ||
| 210 | + if (threadIdx.x == 0) { | ||
| 211 | + control[WORK_CURRENT_INDEX] = INIT_INDEX; | ||
| 212 | + int32_t selectedCount = control[WORK_SELECTED_COUNT]; | ||
| 213 | + if (selectedCount < maxOut && suppressed[sortedPos] == NOT_SUPPRESSED) { | ||
| 214 | + float score = ReadAsFloat(sortedScores[sortedPos]); | ||
| 215 | + int32_t current = inputIndices[sortedPos]; | ||
| 216 | + if (score > scoreThr && current >= 0 && static_cast<int64_t>(current) < boxesNum) { | ||
| 217 | + selectedIndices[selectedCount] = current; | ||
| 218 | + control[WORK_SELECTED_COUNT] = selectedCount + 1; | ||
| 219 | + control[WORK_CURRENT_INDEX] = current; | ||
| 220 | + } | ||
| 221 | + } | ||
| 222 | + } | ||
| 223 | + asc_threadfence_block(); | ||
| 224 | + asc_syncthreads(); | ||
| 225 | + | ||
| 226 | + int32_t currentIndex = control[WORK_CURRENT_INDEX]; | ||
| 227 | + if (currentIndex >= 0) { | ||
| 228 | + for (int64_t nextPos = sortedPos + 1 + threadIdx.x; nextPos < boxesNum; nextPos += blockDim.x) { | ||
| 229 | + if (suppressed[nextPos] == NOT_SUPPRESSED) { | ||
| 230 | + float score = ReadAsFloat(sortedScores[nextPos]); | ||
| 231 | + int32_t nextIndex = inputIndices[nextPos]; | ||
| 232 | + if (score > scoreThr && nextIndex >= 0 && static_cast<int64_t>(nextIndex) < boxesNum) { | ||
| 233 | + float overlap = Iou(boxes, currentIndex, nextIndex, offsetVal); | ||
| 234 | + if (overlap > iouThr) { | ||
| 235 | + suppressed[nextPos] = SUPPRESSED; | ||
| 236 | + } | ||
| 237 | + } | ||
| 238 | + } | ||
| 239 | + } | ||
| 240 | + } | ||
| 241 | + asc_threadfence_block(); | ||
| 242 | + asc_syncthreads(); | ||
| 243 | + | ||
| 244 | + if (control[WORK_SELECTED_COUNT] >= maxOut) { | ||
| 245 | + break; | ||
| 246 | + } | ||
| 247 | + } | ||
| 248 | + if (threadIdx.x == 0) { | ||
| 249 | + WriteOutputShape(outputShape, control[WORK_SELECTED_COUNT]); | ||
| 250 | + } | ||
| 251 | +} | ||
| 252 | + | ||
| 253 | +template <typename TBox, typename TScore, typename TIouThreshold, typename TScoreThreshold> | ||
| 254 | +__simt_vf__ __aicore__ __launch_bounds__(THREAD_NUM) inline void BuildPairwiseMasksLocal( | ||
| 255 | + int64_t boxesNum, int64_t activeBoxesNum, int64_t activeMaskWordNum, int32_t offset, int32_t coreIdx, | ||
| 256 | + int32_t coreNum, __ubuf__ TBox* boxes, __gm__ TScore* sortedScores, __gm__ int32_t* inputIndices, | ||
| 257 | + __ubuf__ float* boxAreas, __gm__ TIouThreshold* iouThreshold, __gm__ TScoreThreshold* scoreThreshold, | ||
| 258 | + __gm__ uint32_t* pairwiseMasks) | ||
| 259 | +{ | ||
| 260 | + float iouThr = ReadAsFloat(iouThreshold[0]); | ||
| 261 | + float offsetVal = static_cast<float>(offset); | ||
| 262 | + const int64_t globalThreadIdx = static_cast<int64_t>(coreIdx) * blockDim.x + threadIdx.x; | ||
| 263 | + const int64_t globalThreadNum = static_cast<int64_t>(coreNum) * blockDim.x; | ||
| 264 | + const int64_t pairwiseMaskWords = activeBoxesNum * activeMaskWordNum; | ||
| 265 | + | ||
| 266 | + for (int64_t task = globalThreadIdx; task < pairwiseMaskWords; task += globalThreadNum) { | ||
| 267 | + const int64_t sortedPos = task / activeMaskWordNum; | ||
| 268 | + const int64_t wordIndex = task - sortedPos * activeMaskWordNum; | ||
| 269 | + const int64_t firstRelevantWord = (sortedPos + 1) / MASK_BITS; | ||
| 270 | + if (wordIndex < firstRelevantWord) { | ||
| 271 | + continue; | ||
| 272 | + } | ||
| 273 | + const int64_t firstNextPos = wordIndex * MASK_BITS; | ||
| 274 | + uint32_t mask = 0; | ||
| 275 | + const int32_t currentIndex = inputIndices[sortedPos]; | ||
| 276 | + if (currentIndex >= 0 && static_cast<int64_t>(currentIndex) < boxesNum) { | ||
| 277 | + for (int32_t bit = 0; bit < MASK_BITS; ++bit) { | ||
| 278 | + const int64_t nextPos = firstNextPos + bit; | ||
| 279 | + if (nextPos <= sortedPos || nextPos >= activeBoxesNum) { | ||
| 280 | + continue; | ||
| 281 | + } | ||
| 282 | + const int32_t nextIndex = inputIndices[nextPos]; | ||
| 283 | + if (nextIndex >= 0 && static_cast<int64_t>(nextIndex) < boxesNum && | ||
| 284 | + IouLocal(boxes, boxAreas, currentIndex, nextIndex, offsetVal) > iouThr) { | ||
| 285 | + mask |= 1U << bit; | ||
| 286 | + } | ||
| 287 | + } | ||
| 288 | + } | ||
| 289 | + pairwiseMasks[task] = mask; | ||
| 290 | + } | ||
| 291 | +} | ||
| 292 | + | ||
| 293 | +template <typename TBox, typename TScore, typename TIouThreshold, typename TScoreThreshold> | ||
| 294 | +__simt_vf__ __aicore__ __launch_bounds__(THREAD_NUM) inline void BuildPairwiseMasks( | ||
| 295 | + int64_t boxesNum, int64_t activeBoxesNum, int64_t activeMaskWordNum, int32_t offset, int32_t coreIdx, | ||
| 296 | + int32_t coreNum, __gm__ TBox* boxes, __gm__ TScore* sortedScores, __gm__ int32_t* inputIndices, | ||
| 297 | + __gm__ TIouThreshold* iouThreshold, __gm__ TScoreThreshold* scoreThreshold, __gm__ uint32_t* pairwiseMasks) | ||
| 298 | +{ | ||
| 299 | + float iouThr = ReadAsFloat(iouThreshold[0]); | ||
| 300 | + float offsetVal = static_cast<float>(offset); | ||
| 301 | + const int64_t globalThreadIdx = static_cast<int64_t>(coreIdx) * blockDim.x + threadIdx.x; | ||
| 302 | + const int64_t globalThreadNum = static_cast<int64_t>(coreNum) * blockDim.x; | ||
| 303 | + const int64_t pairwiseMaskWords = activeBoxesNum * activeMaskWordNum; | ||
| 304 | + | ||
| 305 | + for (int64_t task = globalThreadIdx; task < pairwiseMaskWords; task += globalThreadNum) { | ||
| 306 | + const int64_t sortedPos = task / activeMaskWordNum; | ||
| 307 | + const int64_t wordIndex = task - sortedPos * activeMaskWordNum; | ||
| 308 | + const int64_t firstRelevantWord = (sortedPos + 1) / MASK_BITS; | ||
| 309 | + if (wordIndex < firstRelevantWord) { | ||
| 310 | + continue; | ||
| 311 | + } | ||
| 312 | + const int64_t firstNextPos = wordIndex * MASK_BITS; | ||
| 313 | + uint32_t mask = 0; | ||
| 314 | + const int32_t currentIndex = inputIndices[sortedPos]; | ||
| 315 | + if (currentIndex >= 0 && static_cast<int64_t>(currentIndex) < boxesNum) { | ||
| 316 | + for (int32_t bit = 0; bit < MASK_BITS; ++bit) { | ||
| 317 | + const int64_t nextPos = firstNextPos + bit; | ||
| 318 | + if (nextPos <= sortedPos || nextPos >= activeBoxesNum) { | ||
| 319 | + continue; | ||
| 320 | + } | ||
| 321 | + const int32_t nextIndex = inputIndices[nextPos]; | ||
| 322 | + if (nextIndex >= 0 && static_cast<int64_t>(nextIndex) < boxesNum && | ||
| 323 | + Iou(boxes, currentIndex, nextIndex, offsetVal) > iouThr) { | ||
| 324 | + mask |= 1U << bit; | ||
| 325 | + } | ||
| 326 | + } | ||
| 327 | + } | ||
| 328 | + pairwiseMasks[task] = mask; | ||
| 329 | + } | ||
| 330 | +} | ||
| 331 | + | ||
| 332 | +template <typename TScore, typename TScoreThreshold> | ||
| 333 | +__simt_vf__ __aicore__ __launch_bounds__(THREAD_NUM) inline void SelectFromPairwiseMasks( | ||
| 334 | + int64_t boxesNum, int64_t activeBoxesNum, int64_t activeMaskWordNum, __gm__ TScore* sortedScores, | ||
| 335 | + __gm__ int32_t* inputIndices, __gm__ int32_t* maxOutputSize, __gm__ TScoreThreshold* scoreThreshold, | ||
| 336 | + __gm__ int32_t* selectedIndices, __gm__ uint64_t* outputShape, __gm__ int32_t* work) | ||
| 337 | +{ | ||
| 338 | + __gm__ int32_t* control = work; | ||
| 339 | + __gm__ uint32_t* suppressedMasks = (__gm__ uint32_t*)(work + 2); | ||
| 340 | + __gm__ uint32_t* pairwiseMasks = suppressedMasks + activeMaskWordNum; | ||
| 341 | + | ||
| 342 | + for (int64_t wordIndex = threadIdx.x; wordIndex < activeMaskWordNum; wordIndex += blockDim.x) { | ||
| 343 | + suppressedMasks[wordIndex] = 0; | ||
| 344 | + } | ||
| 345 | + if (threadIdx.x == 0) { | ||
| 346 | + control[WORK_SELECTED_COUNT] = 0; | ||
| 347 | + control[WORK_CURRENT_INDEX] = INIT_INDEX; | ||
| 348 | + } | ||
| 349 | + asc_threadfence_block(); | ||
| 350 | + asc_syncthreads(); | ||
| 351 | + | ||
| 352 | + int32_t maxOut = maxOutputSize[0]; | ||
| 353 | + maxOut = maxOut < 0 ? 0 : maxOut; | ||
| 354 | + if (static_cast<int64_t>(maxOut) > boxesNum) { | ||
| 355 | + maxOut = static_cast<int32_t>(boxesNum); | ||
| 356 | + } | ||
| 357 | + if (maxOut == 0 || activeBoxesNum == 0) { | ||
| 358 | + if (threadIdx.x == 0) { | ||
| 359 | + WriteOutputShape(outputShape, 0); | ||
| 360 | + } | ||
| 361 | + return; | ||
| 362 | + } | ||
| 363 | + | ||
| 364 | + for (int64_t sortedPos = 0; sortedPos < activeBoxesNum; ++sortedPos) { | ||
| 365 | + if (threadIdx.x == 0) { | ||
| 366 | + control[WORK_CURRENT_INDEX] = INIT_INDEX; | ||
| 367 | + const int64_t wordIndex = sortedPos / MASK_BITS; | ||
| 368 | + const int32_t bitIndex = static_cast<int32_t>(sortedPos - wordIndex * MASK_BITS); | ||
| 369 | + const bool isSuppressed = (suppressedMasks[wordIndex] & (1U << bitIndex)) != 0; | ||
| 370 | + const int32_t currentIndex = inputIndices[sortedPos]; | ||
| 371 | + if (!isSuppressed && currentIndex >= 0 && static_cast<int64_t>(currentIndex) < boxesNum) { | ||
| 372 | + const int32_t selectedCount = control[WORK_SELECTED_COUNT]; | ||
| 373 | + selectedIndices[selectedCount] = currentIndex; | ||
| 374 | + control[WORK_SELECTED_COUNT] = selectedCount + 1; | ||
| 375 | + control[WORK_CURRENT_INDEX] = static_cast<int32_t>(sortedPos); | ||
| 376 | + } | ||
| 377 | + } | ||
| 378 | + asc_threadfence_block(); | ||
| 379 | + asc_syncthreads(); | ||
| 380 | + | ||
| 381 | + const int32_t selectedRow = control[WORK_CURRENT_INDEX]; | ||
| 382 | + if (selectedRow >= 0) { | ||
| 383 | + const int64_t rowOffset = static_cast<int64_t>(selectedRow) * activeMaskWordNum; | ||
| 384 | + const int64_t firstRelevantWord = (static_cast<int64_t>(selectedRow) + 1) / MASK_BITS; | ||
| 385 | + for (int64_t wordIndex = firstRelevantWord + threadIdx.x; wordIndex < activeMaskWordNum; | ||
| 386 | + wordIndex += blockDim.x) { | ||
| 387 | + suppressedMasks[wordIndex] |= pairwiseMasks[rowOffset + wordIndex]; | ||
| 388 | + } | ||
| 389 | + } | ||
| 390 | + asc_threadfence_block(); | ||
| 391 | + asc_syncthreads(); | ||
| 392 | + | ||
| 393 | + if (control[WORK_SELECTED_COUNT] >= maxOut) { | ||
| 394 | + break; | ||
| 395 | + } | ||
| 396 | + } | ||
| 397 | + if (threadIdx.x == 0) { | ||
| 398 | + WriteOutputShape(outputShape, control[WORK_SELECTED_COUNT]); | ||
| 399 | + } | ||
| 400 | +} | ||
| 401 | + | ||
| 402 | +template <typename TBox, typename TScore, typename TIouThreshold, typename TScoreThreshold> | ||
| 403 | +__aicore__ inline void Process(GM_ADDR boxes, GM_ADDR sortedScores, GM_ADDR inputIndices, GM_ADDR maxOutputSize, | ||
| 404 | + GM_ADDR iouThreshold, GM_ADDR scoreThreshold, GM_ADDR selectedIndices, | ||
| 405 | + GM_ADDR outputShape, GM_ADDR workspace, const SortedNMSTilingData* tilingData, | ||
| 406 | + TPipe* pipe) | ||
| 407 | +{ | ||
| 408 | + __gm__ TBox* boxesGm = (__gm__ TBox*)boxes; | ||
| 409 | + __gm__ TScore* sortedScoresGm = (__gm__ TScore*)sortedScores; | ||
| 410 | + __gm__ int32_t* inputIndicesGm = (__gm__ int32_t*)inputIndices; | ||
| 411 | + __gm__ int32_t* maxOutputSizeGm = (__gm__ int32_t*)maxOutputSize; | ||
| 412 | + __gm__ TIouThreshold* iouThresholdGm = (__gm__ TIouThreshold*)iouThreshold; | ||
| 413 | + __gm__ TScoreThreshold* scoreThresholdGm = (__gm__ TScoreThreshold*)scoreThreshold; | ||
| 414 | + __gm__ int32_t* selectedIndicesGm = (__gm__ int32_t*)selectedIndices; | ||
| 415 | + __gm__ uint64_t* outputShapeGm = (__gm__ uint64_t*)outputShape; | ||
| 416 | + __gm__ int32_t* workGm = (__gm__ int32_t*)workspace; | ||
| 417 | + const uint32_t singleCoreThreadNum = GetSimtThreadNum(tilingData->boxesNum); | ||
| 418 | + | ||
| 419 | + if (tilingData->boxesNum >= MULTI_CORE_MIN_BOXES && tilingData->boxesNum <= MULTI_CORE_MAX_BOXES) { | ||
| 420 | + const int32_t coreIdx = static_cast<int32_t>(GetBlockIdx()); | ||
| 421 | + const int64_t maskWordNum = (tilingData->boxesNum + MASK_BITS - 1) / MASK_BITS; | ||
| 422 | + int32_t maxOut = maxOutputSizeGm[0]; | ||
| 423 | + maxOut = maxOut < 0 ? 0 : maxOut; | ||
| 424 | + if (static_cast<int64_t>(maxOut) > tilingData->boxesNum) { | ||
| 425 | + maxOut = static_cast<int32_t>(tilingData->boxesNum); | ||
| 426 | + } | ||
| 427 | + const int64_t pairwiseBreakEven = (tilingData->boxesNum + tilingData->coreNum - 1) / tilingData->coreNum; | ||
| 428 | + if (static_cast<int64_t>(maxOut) <= pairwiseBreakEven) { | ||
| 429 | + if (coreIdx == 0) { | ||
| 430 | + asc_vf_call<SortedNMSSingleCore<TBox, TScore, TIouThreshold, TScoreThreshold>>( | ||
| 431 | + dim3(singleCoreThreadNum), tilingData->boxesNum, tilingData->offset, boxesGm, sortedScoresGm, | ||
| 432 | + inputIndicesGm, maxOutputSizeGm, iouThresholdGm, scoreThresholdGm, selectedIndicesGm, outputShapeGm, | ||
| 433 | + workGm); | ||
| 434 | + } | ||
| 435 | + return; | ||
| 436 | + } | ||
| 437 | + | ||
| 438 | + __gm__ uint32_t* suppressedMasks = (__gm__ uint32_t*)(workGm + 2); | ||
| 439 | + if (coreIdx == 0) { | ||
| 440 | + asc_vf_call<FindActiveBoxesNum<TScore, TScoreThreshold>>(dim3(1), tilingData->boxesNum, sortedScoresGm, | ||
| 441 | + scoreThresholdGm, workGm); | ||
| 442 | + } | ||
| 443 | + SyncAll(); | ||
| 444 | + const int64_t activeBoxesNum = static_cast<int64_t>(workGm[0]); | ||
| 445 | + const int64_t activeMaskWordNum = (activeBoxesNum + MASK_BITS - 1) / MASK_BITS; | ||
| 446 | + __gm__ uint32_t* pairwiseMasks = suppressedMasks + activeMaskWordNum; | ||
| 447 | + if (tilingData->useLocalBoxes != 0) { | ||
| 448 | + const uint32_t boxesBytes = AlignUbBytes( | ||
| 449 | + static_cast<uint32_t>(tilingData->boxesNum * BOX_COORDS * sizeof(TBox))); | ||
| 450 | + const uint32_t areasBytes = AlignUbBytes(static_cast<uint32_t>(tilingData->boxesNum * sizeof(float))); | ||
| 451 | + TQue<QuePosition::VECIN, 1> boxesQueue; | ||
| 452 | + TBuf<TPosition::VECCALC> areasBuffer; | ||
| 453 | + pipe->InitBuffer(boxesQueue, 1, boxesBytes); | ||
| 454 | + pipe->InitBuffer(areasBuffer, areasBytes); | ||
| 455 | + | ||
| 456 | + GlobalTensor<TBox> boxesTensor; | ||
| 457 | + boxesTensor.SetGlobalBuffer(boxesGm, tilingData->boxesNum * BOX_COORDS); | ||
| 458 | + LocalTensor<TBox> boxesLocal = boxesQueue.AllocTensor<TBox>(); | ||
| 459 | + const DataCopyExtParams boxesCopyParams{ | ||
| 460 | + 1, static_cast<uint32_t>(tilingData->boxesNum * BOX_COORDS * sizeof(TBox)), 0, 0, 0}; | ||
| 461 | + DataCopyPad(boxesLocal, boxesTensor, boxesCopyParams, DataCopyPadExtParams<TBox>{false, 0, 0, 0}); | ||
| 462 | + boxesQueue.EnQue(boxesLocal); | ||
| 463 | + boxesLocal = boxesQueue.DeQue<TBox>(); | ||
| 464 | + LocalTensor<float> areasLocal = areasBuffer.Get<float>(); | ||
| 465 | + __ubuf__ TBox* boxesUb = reinterpret_cast<__ubuf__ TBox*>(boxesLocal.GetPhyAddr()); | ||
| 466 | + __ubuf__ float* areasUb = reinterpret_cast<__ubuf__ float*>(areasLocal.GetPhyAddr()); | ||
| 467 | + | ||
| 468 | + asc_vf_call<BuildBoxAreas<TBox>>(dim3(GetSimtThreadNum(tilingData->boxesNum)), tilingData->boxesNum, | ||
| 469 | + tilingData->offset, boxesUb, areasUb); | ||
| 470 | + event_t areasReady = static_cast<event_t>(pipe->FetchEventID(HardEvent::V_S)); | ||
| 471 | + SetFlag<HardEvent::V_S>(areasReady); | ||
| 472 | + WaitFlag<HardEvent::V_S>(areasReady); | ||
| 473 | + asc_vf_call<BuildPairwiseMasksLocal<TBox, TScore, TIouThreshold, TScoreThreshold>>( | ||
| 474 | + dim3(LOCAL_PAIRWISE_THREAD_NUM), tilingData->boxesNum, activeBoxesNum, activeMaskWordNum, | ||
| 475 | + tilingData->offset, coreIdx, tilingData->coreNum, boxesUb, sortedScoresGm, inputIndicesGm, areasUb, | ||
| 476 | + iouThresholdGm, scoreThresholdGm, pairwiseMasks); | ||
| 477 | + event_t pairwiseDone = static_cast<event_t>(pipe->FetchEventID(HardEvent::V_S)); | ||
| 478 | + SetFlag<HardEvent::V_S>(pairwiseDone); | ||
| 479 | + WaitFlag<HardEvent::V_S>(pairwiseDone); | ||
| 480 | + boxesQueue.FreeTensor(boxesLocal); | ||
| 481 | + } else { | ||
| 482 | + asc_vf_call<BuildPairwiseMasks<TBox, TScore, TIouThreshold, TScoreThreshold>>( | ||
| 483 | + dim3(THREAD_NUM), tilingData->boxesNum, activeBoxesNum, activeMaskWordNum, tilingData->offset, coreIdx, | ||
| 484 | + tilingData->coreNum, boxesGm, sortedScoresGm, inputIndicesGm, iouThresholdGm, scoreThresholdGm, | ||
| 485 | + pairwiseMasks); | ||
| 486 | + } | ||
| 487 | + SyncAll(); | ||
| 488 | + if (coreIdx == 0) { | ||
| 489 | + const uint32_t selectThreadNum = GetSimtThreadNum(activeMaskWordNum); | ||
| 490 | + asc_vf_call<SelectFromPairwiseMasks<TScore, TScoreThreshold>>( | ||
| 491 | + dim3(selectThreadNum), tilingData->boxesNum, activeBoxesNum, activeMaskWordNum, sortedScoresGm, | ||
| 492 | + inputIndicesGm, maxOutputSizeGm, scoreThresholdGm, selectedIndicesGm, outputShapeGm, workGm); | ||
| 493 | + } | ||
| 494 | + return; | ||
| 495 | + } | ||
| 496 | + | ||
| 497 | + asc_vf_call<SortedNMSSingleCore<TBox, TScore, TIouThreshold, TScoreThreshold>>( | ||
| 498 | + dim3(singleCoreThreadNum), tilingData->boxesNum, tilingData->offset, boxesGm, sortedScoresGm, inputIndicesGm, | ||
| 499 | + maxOutputSizeGm, iouThresholdGm, scoreThresholdGm, selectedIndicesGm, outputShapeGm, workGm); | ||
| 500 | +} | ||
| 501 | +} // namespace NsSortedNMS | ||
| 502 | + | ||
| 503 | + | ||
| @@ -0,0 +1,23 @@ | |||
| 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 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | +struct SortedNMSTilingData { | ||
| 17 | + int64_t boxesNum = 0; | ||
| 18 | + int32_t offset = 0; | ||
| 19 | + int32_t coreNum = 1; | ||
| 20 | + int32_t useLocalBoxes = 0; | ||
| 21 | +}; | ||
| 22 | + | ||
| 23 | + | ||
| @@ -0,0 +1,35 @@ | |||
| 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 | + | ||
| 12 | + | ||
| 13 | +extern "C" __global__ __aicore__ void sorted_nms(GM_ADDR boxes, GM_ADDR sorted_scores, GM_ADDR input_indices, | ||
| 14 | + GM_ADDR max_output_size, GM_ADDR iou_threshold, | ||
| 15 | + GM_ADDR score_threshold, GM_ADDR selected_indices, GM_ADDR shape_out, | ||
| 16 | + GM_ADDR workspace, GM_ADDR tiling) | ||
| 17 | +{ | ||
| 18 | + KERNEL_TASK_TYPE_DEFAULT(KERNEL_TYPE_AIV_ONLY); | ||
| 19 | + if (workspace == nullptr) { | ||
| 20 | + return; | ||
| 21 | + } | ||
| 22 | + AscendC::SetSysWorkspace(workspace); | ||
| 23 | + GM_ADDR userWorkspace = AscendC::GetUserWorkspace(workspace); | ||
| 24 | + if (userWorkspace == nullptr) { | ||
| 25 | + return; | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + REGISTER_TILING_DEFAULT(SortedNMSTilingData); | ||
| 29 | + GET_TILING_DATA_WITH_STRUCT(SortedNMSTilingData, tilingData, tiling); | ||
| 30 | + | ||
| 31 | + AscendC::TPipe pipe; | ||
| 32 | + NsSortedNMS::Process<DTYPE_BOXES, DTYPE_SORTED_SCORES, DTYPE_IOU_THRESHOLD, DTYPE_SCORE_THRESHOLD>( | ||
| 33 | + boxes, sorted_scores, input_indices, max_output_size, iou_threshold, score_threshold, selected_indices, | ||
| 34 | + shape_out, userWorkspace, &tilingData, &pipe); | ||
| 35 | +} | ||
| @@ -0,0 +1,153 @@ | |||
| 1 | +#!/usr/bin/env python3 | ||
| 2 | +# -*- coding: utf-8 -*- | ||
| 3 | +# Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 4 | +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 5 | +# CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 6 | +# Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 7 | +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 8 | +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 9 | +# See LICENSE in the root of the software repository for the full text of the License. | ||
| 10 | + | ||
| 11 | +import importlib.util | ||
| 12 | +from pathlib import Path | ||
| 13 | + | ||
| 14 | +import numpy as np | ||
| 15 | + | ||
| 16 | + | ||
| 17 | +def _load_customize_inputs(): | ||
| 18 | + input_path = Path(__file__).with_name("input.py") | ||
| 19 | + spec = importlib.util.spec_from_file_location("sorted_nms_test_input", input_path) | ||
| 20 | + module = importlib.util.module_from_spec(spec) | ||
| 21 | + spec.loader.exec_module(module) | ||
| 22 | + return module.customize_inputs | ||
| 23 | + | ||
| 24 | + | ||
| 25 | +__spec__ = {"sorted_nms": "SortedNmsTestSpec"} | ||
| 26 | + | ||
| 27 | + | ||
| 28 | +class SortedNmsTestSpec: | ||
| 29 | + """Deterministic input generation and a sequential SortedNMS reference.""" | ||
| 30 | + | ||
| 31 | + tolerance = {"int32": {"standard": "binary_equal"}} | ||
| 32 | + customize_inputs = staticmethod(_load_customize_inputs()) | ||
| 33 | + | ||
| 34 | + | ||
| 35 | + def compare(*values, **kwargs): | ||
| 36 | + """Compare the logical output and the meaningful dynamic-shape metadata.""" | ||
| 37 | + if len(values) != 4: | ||
| 38 | + return { | ||
| 39 | + "pass": False, | ||
| 40 | + "precision": "INVALID_OUTPUT_COUNT", | ||
| 41 | + "error_info": f"expected 2 outputs and 2 goldens, got {len(values)} values", | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + selected_output, shape_output, selected_golden, shape_golden = values | ||
| 45 | + output_shape = np.asarray(shape_output, dtype=np.uint64).reshape(-1) | ||
| 46 | + golden_shape = np.asarray(shape_golden, dtype=np.uint64).reshape(-1) | ||
| 47 | + if output_shape.size < 2 or golden_shape.size < 2: | ||
| 48 | + return { | ||
| 49 | + "pass": False, | ||
| 50 | + "precision": "INVALID_SHAPE_METADATA", | ||
| 51 | + "error_info": "dynamic-shape metadata must contain rank and at least one dimension", | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + # TTK marks the uint64 encoding in bit 31 of the first metadata word. | ||
| 55 | + rank_mask = np.uint64(0x7FFFFFFF) | ||
| 56 | + output_rank = int(output_shape[0] & rank_mask) | ||
| 57 | + golden_rank = int(golden_shape[0] & rank_mask) | ||
| 58 | + output_count = int(output_shape[1]) | ||
| 59 | + golden_count = int(golden_shape[1]) | ||
| 60 | + output_values = np.asarray(selected_output).reshape(-1) | ||
| 61 | + golden_values = np.asarray(selected_golden).reshape(-1) | ||
| 62 | + | ||
| 63 | + errors = [] | ||
| 64 | + if output_rank != 1 or golden_rank != 1: | ||
| 65 | + errors.append(f"rank mismatch: output={output_rank}, golden={golden_rank}") | ||
| 66 | + if output_count != golden_count or golden_count != golden_values.size: | ||
| 67 | + errors.append( | ||
| 68 | + f"selected count mismatch: output={output_count}, " | ||
| 69 | + f"metadata_golden={golden_count}, golden={golden_values.size}" | ||
| 70 | + ) | ||
| 71 | + if output_count > output_values.size: | ||
| 72 | + errors.append( | ||
| 73 | + f"selected count {output_count} exceeds output buffer {output_values.size}" | ||
| 74 | + ) | ||
| 75 | + elif not np.array_equal(output_values[:output_count], golden_values): | ||
| 76 | + errors.append("selected_indices differ from golden") | ||
| 77 | + | ||
| 78 | + passed = not errors | ||
| 79 | + return { | ||
| 80 | + "pass": passed, | ||
| 81 | + "precision": "BINARY_EQUAL" if passed else "MISMATCH", | ||
| 82 | + "error_info": "; ".join(errors), | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + | ||
| 86 | + def golden( | ||
| 87 | + boxes, | ||
| 88 | + sorted_scores, | ||
| 89 | + input_indices, | ||
| 90 | + max_output_size, | ||
| 91 | + iou_threshold, | ||
| 92 | + score_threshold, | ||
| 93 | + *, | ||
| 94 | + offset=0, | ||
| 95 | + **kwargs, | ||
| 96 | + ): | ||
| 97 | + boxes_num = boxes.shape[0] | ||
| 98 | + if sorted_scores.size > 1 and np.any(sorted_scores[:-1] < sorted_scores[1:]): | ||
| 99 | + raise ValueError("sorted_scores must be sorted in non-increasing order") | ||
| 100 | + max_out = min(max(int(max_output_size[0]), 0), boxes_num) | ||
| 101 | + iou_thr = float(iou_threshold[0]) | ||
| 102 | + score_thr = float(score_threshold[0]) | ||
| 103 | + boxes_f32 = boxes.astype(np.float32) | ||
| 104 | + suppressed = np.zeros(boxes_num, dtype=np.bool_) | ||
| 105 | + selected = [] | ||
| 106 | + | ||
| 107 | + for sorted_pos in range(boxes_num): | ||
| 108 | + if len(selected) >= max_out: | ||
| 109 | + break | ||
| 110 | + score = float(sorted_scores[sorted_pos]) | ||
| 111 | + current = int(input_indices[sorted_pos]) | ||
| 112 | + if ( | ||
| 113 | + suppressed[sorted_pos] | ||
| 114 | + or score <= score_thr | ||
| 115 | + or current < 0 | ||
| 116 | + or current >= boxes_num | ||
| 117 | + ): | ||
| 118 | + continue | ||
| 119 | + | ||
| 120 | + selected.append(current) | ||
| 121 | + current_box = boxes_f32[current] | ||
| 122 | + for next_pos in range(sorted_pos + 1, boxes_num): | ||
| 123 | + if suppressed[next_pos] or float(sorted_scores[next_pos]) <= score_thr: | ||
| 124 | + continue | ||
| 125 | + next_index = int(input_indices[next_pos]) | ||
| 126 | + if next_index < 0 or next_index >= boxes_num: | ||
| 127 | + continue | ||
| 128 | + next_box = boxes_f32[next_index] | ||
| 129 | + width = max( | ||
| 130 | + 0.0, | ||
| 131 | + min(current_box[2], next_box[2]) | ||
| 132 | + - max(current_box[0], next_box[0]) | ||
| 133 | + + offset, | ||
| 134 | + ) | ||
| 135 | + height = max( | ||
| 136 | + 0.0, | ||
| 137 | + min(current_box[3], next_box[3]) | ||
| 138 | + - max(current_box[1], next_box[1]) | ||
| 139 | + + offset, | ||
| 140 | + ) | ||
| 141 | + intersection = width * height | ||
| 142 | + current_area = max(0.0, current_box[2] - current_box[0] + offset) * max( | ||
| 143 | + 0.0, current_box[3] - current_box[1] + offset | ||
| 144 | + ) | ||
| 145 | + next_area = max(0.0, next_box[2] - next_box[0] + offset) * max( | ||
| 146 | + 0.0, next_box[3] - next_box[1] + offset | ||
| 147 | + ) | ||
| 148 | + union = current_area + next_area - intersection | ||
| 149 | + overlap = intersection / union if union > 0.0 else 0.0 | ||
| 150 | + if overlap > iou_thr: | ||
| 151 | + suppressed[next_pos] = True | ||
| 152 | + | ||
| 153 | + return [np.asarray(selected, dtype=np.int32)] | ||
| @@ -0,0 +1,51 @@ | |||
| 1 | +#!/usr/bin/env python3 | ||
| 2 | +# -*- coding: utf-8 -*- | ||
| 3 | +# Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 4 | +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 5 | +# CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 6 | +# Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 7 | +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 8 | +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 9 | +# See LICENSE in the root of the software repository for the full text of the License. | ||
| 10 | + | ||
| 11 | +import numpy as np | ||
| 12 | + | ||
| 13 | + | ||
| 14 | +def customize_inputs( | ||
| 15 | + boxes, | ||
| 16 | + sorted_scores, | ||
| 17 | + input_indices, | ||
| 18 | + max_output_size, | ||
| 19 | + iou_threshold, | ||
| 20 | + score_threshold, | ||
| 21 | + **kwargs, | ||
| 22 | +): | ||
| 23 | + """Generate deterministic SortedNMS inputs with retain and suppress paths.""" | ||
| 24 | + boxes_num = boxes.shape[0] | ||
| 25 | + box_ids = np.arange(boxes_num, dtype=np.int64) | ||
| 26 | + group_ids = box_ids // 4 | ||
| 27 | + | ||
| 28 | + # Four boxes in each group are identical, while different groups are | ||
| 29 | + # spatially separated. This guarantees both suppress and retain paths. | ||
| 30 | + x1 = (group_ids % 64) * 8 | ||
| 31 | + y1 = (group_ids // 64) * 8 | ||
| 32 | + generated_boxes = np.stack((x1, y1, x1 + 4, y1 + 4), axis=1) | ||
| 33 | + boxes[...] = generated_boxes.astype(boxes.dtype) | ||
| 34 | + | ||
| 35 | + # Scores are already sorted by position as required by SortedNMS. | ||
| 36 | + if boxes_num > 0: | ||
| 37 | + scores = np.linspace(0.99, 0.01, boxes_num, dtype=np.float32) | ||
| 38 | + sorted_scores[...] = scores.astype(sorted_scores.dtype) | ||
| 39 | + # A fixed coprime affine permutation exercises the input-index map. | ||
| 40 | + multiplier = boxes_num - 1 if boxes_num % 2 == 0 else boxes_num - 2 | ||
| 41 | + multiplier = max(multiplier, 1) | ||
| 42 | + input_indices[...] = ((box_ids * multiplier + 1) % boxes_num).astype(np.int32) | ||
| 43 | + | ||
| 44 | + return ( | ||
| 45 | + boxes, | ||
| 46 | + sorted_scores, | ||
| 47 | + input_indices, | ||
| 48 | + max_output_size, | ||
| 49 | + iou_threshold, | ||
| 50 | + score_threshold, | ||
| 51 | + ) | ||
| @@ -586,6 +586,7 @@ | |||
| 586 | {"name":"Bincount", "compute_units": ["ascend950"], "auto_sync" : false, "impl_mode" : ""}, | 586 | {"name":"Bincount", "compute_units": ["ascend950"], "auto_sync" : false, "impl_mode" : ""}, |
| 587 | {"name":"Addr", "compute_units": ["ascend950"], "auto_sync" : false, "impl_mode" : ""}, | 587 | {"name":"Addr", "compute_units": ["ascend950"], "auto_sync" : false, "impl_mode" : ""}, |
| 588 | {"name":"NMSWithMask", "compute_units": ["ascend910b", "ascend950"], "auto_sync" : false, "impl_mode" : ""}, | 588 | {"name":"NMSWithMask", "compute_units": ["ascend910b", "ascend950"], "auto_sync" : false, "impl_mode" : ""}, |
| 589 | + {"name": "SortedNMS", "compute_units": ["ascend950"], "auto_sync": false, "impl_mode": ""}, | ||
| 589 | {"name":"Rasterizer", "compute_units": ["ascend910b", "ascend910_93"], "auto_sync" : false, "impl_mode" : ""}, | 590 | {"name":"Rasterizer", "compute_units": ["ascend910b", "ascend910_93"], "auto_sync" : false, "impl_mode" : ""}, |
| 590 | {"name":"ExtractGlimpseV2", "compute_units": ["ascend950"], "auto_sync": false}, | 591 | {"name":"ExtractGlimpseV2", "compute_units": ["ascend950"], "auto_sync": false}, |
| 591 | {"name":"RoiAlignV2", "compute_units": ["ascend910b"], "auto_sync":false, "impl_mode" : ""}, | 592 | {"name":"RoiAlignV2", "compute_units": ["ascend910b"], "auto_sync":false, "impl_mode" : ""}, |


新增算子需补充op_list.md