已合并
feat: 支持Eager自定义算子辅流资源编译期统计 #5158
feat: 支持Eager自定义算子辅流资源编译期统计 #5158
已合并
Chang-an-HW创建于 9月24日
共 35 个文件变更+1484-4
@@ -284,6 +284,7 @@ if("ge-executor" IN_LIST BUILD_COMPONENT)
284 ${CMAKE_CURRENT_SOURCE_DIR}/inc/graph_metadef/external/exe_graph/runtime/eager_op_execution_context.h284 ${CMAKE_CURRENT_SOURCE_DIR}/inc/graph_metadef/external/exe_graph/runtime/eager_op_execution_context.h
285 ${CMAKE_CURRENT_SOURCE_DIR}/inc/graph_metadef/external/exe_graph/runtime/host_cpu_op_execution_context.h285 ${CMAKE_CURRENT_SOURCE_DIR}/inc/graph_metadef/external/exe_graph/runtime/host_cpu_op_execution_context.h
286 ${CMAKE_CURRENT_SOURCE_DIR}/inc/graph_metadef/external/exe_graph/runtime/op_compile_context.h286 ${CMAKE_CURRENT_SOURCE_DIR}/inc/graph_metadef/external/exe_graph/runtime/op_compile_context.h
287+ ${CMAKE_CURRENT_SOURCE_DIR}/inc/graph_metadef/external/exe_graph/runtime/resource_usage_context.h
287 ${CMAKE_CURRENT_SOURCE_DIR}/inc/graph_metadef/external/exe_graph/runtime/update_args_context.h288 ${CMAKE_CURRENT_SOURCE_DIR}/inc/graph_metadef/external/exe_graph/runtime/update_args_context.h
288 ${CMAKE_CURRENT_SOURCE_DIR}/inc/graph_metadef/external/exe_graph/runtime/kernel_args.h289 ${CMAKE_CURRENT_SOURCE_DIR}/inc/graph_metadef/external/exe_graph/runtime/kernel_args.h
289 DESTINATION ${ARCH_LINUX_PATH}/include/exe_graph/runtime COMPONENT ge-executor290 DESTINATION ${ARCH_LINUX_PATH}/include/exe_graph/runtime COMPONENT ge-executor
@@ -746,10 +746,13 @@ Status GraphBuilder::RefreshInfoOfDynamicShapeGraph(ComputeGraphPtr &comp_graph,
746 uint32_t stream_num = 0U;746 uint32_t stream_num = 0U;
747 uint32_t event_num = 0U;747 uint32_t event_num = 0U;
748 uint32_t notify_num = 0U;748 uint32_t notify_num = 0U;
749+ uint32_t eager_stream_num = 0U;
749 std::vector<uint32_t> notify_types;750 std::vector<uint32_t> notify_types;
750 (void)AttrUtils::GetInt(root_model, ATTR_MODEL_STREAM_NUM, stream_num);751 (void)AttrUtils::GetInt(root_model, ATTR_MODEL_STREAM_NUM, stream_num);
751 (void)AttrUtils::GetInt(root_model, ATTR_MODEL_EVENT_NUM, event_num);752 (void)AttrUtils::GetInt(root_model, ATTR_MODEL_EVENT_NUM, event_num);
752 (void)AttrUtils::GetInt(root_model, ATTR_MODEL_NOTIFY_NUM, notify_num);753 (void)AttrUtils::GetInt(root_model, ATTR_MODEL_NOTIFY_NUM, notify_num);
754+ // eager 自定义算子辅流统计为可选属性:任一子模型带该属性时才在根模型聚合写回,避免无该特性的模型产物变化
755+ bool has_eager_attr = AttrUtils::GetInt(root_model, ATTR_MODEL_EAGER_CUSTOM_OP_STREAM_NUM, eager_stream_num);
753 GELOGI("Root model: %s, stream num: %u, event num: %u, notify num: %u.", iter_root->first.c_str(), stream_num,756 GELOGI("Root model: %s, stream num: %u, event num: %u, notify num: %u.", iter_root->first.c_str(), stream_num,
754 event_num, notify_num);757 event_num, notify_num);
755 758 
@@ -760,23 +763,33 @@ Status GraphBuilder::RefreshInfoOfDynamicShapeGraph(ComputeGraphPtr &comp_graph,
760 uint32_t tmp_stream = 0U;763 uint32_t tmp_stream = 0U;
761 uint32_t tmp_event = 0U;764 uint32_t tmp_event = 0U;
762 uint32_t tmp_notify = 0U;765 uint32_t tmp_notify = 0U;
766+ uint32_t tmp_eager_stream_num = 0U;
763 (void)AttrUtils::GetInt(ge_model.second, ATTR_MODEL_STREAM_NUM, tmp_stream);767 (void)AttrUtils::GetInt(ge_model.second, ATTR_MODEL_STREAM_NUM, tmp_stream);
764 (void)AttrUtils::GetInt(ge_model.second, ATTR_MODEL_EVENT_NUM, tmp_event);768 (void)AttrUtils::GetInt(ge_model.second, ATTR_MODEL_EVENT_NUM, tmp_event);
765 (void)AttrUtils::GetInt(ge_model.second, ATTR_MODEL_NOTIFY_NUM, tmp_notify);769 (void)AttrUtils::GetInt(ge_model.second, ATTR_MODEL_NOTIFY_NUM, tmp_notify);
766- GELOGI("Sub model: %s, stream num: %u, event num: %u, notify num: %u.", ge_model.first.c_str(), tmp_stream,770+ // |= 不短路,保证每个子模型的属性读取都执行;不能写作 ||,否则置位后会跳过 GetInt 漏读 tmp 值
767- tmp_event, tmp_notify);771+ has_eager_attr |= AttrUtils::GetInt(ge_model.second, ATTR_MODEL_EAGER_CUSTOM_OP_STREAM_NUM, tmp_eager_stream_num);
772+ GELOGI("Sub model: %s, stream num: %u, event num: %u, notify num: %u, eager custom op stream num: %u.",
773+ ge_model.first.c_str(), tmp_stream, tmp_event, tmp_notify, tmp_eager_stream_num);
768 774 
769 GE_ASSERT_SUCCESS(CheckUint32AddOverflow(stream_num, tmp_stream));775 GE_ASSERT_SUCCESS(CheckUint32AddOverflow(stream_num, tmp_stream));
770 GE_ASSERT_SUCCESS(CheckUint32AddOverflow(event_num, tmp_event));776 GE_ASSERT_SUCCESS(CheckUint32AddOverflow(event_num, tmp_event));
777+ // eager 辅流按模型执行实例各自建流,跨子模型同 key 不共享物理流,因此求和不去重
778+ GE_ASSERT_SUCCESS(CheckUint32AddOverflow(eager_stream_num, tmp_eager_stream_num));
771 stream_num += tmp_stream;779 stream_num += tmp_stream;
772 event_num += tmp_event;780 event_num += tmp_event;
773 notify_num += tmp_notify;781 notify_num += tmp_notify;
782+ eager_stream_num += tmp_eager_stream_num;
774 }783 }
775 784 
776- GELOGI("Total stream num: %u, event num: %u, notify num: %u.", stream_num, event_num, notify_num);785+ GELOGI("Total stream num: %u, event num: %u, notify num: %u, eager custom op stream num: %u.", stream_num, event_num,
786+ notify_num, eager_stream_num);
777 GE_ASSERT_TRUE(AttrUtils::SetInt(root_model, ATTR_MODEL_STREAM_NUM, stream_num));787 GE_ASSERT_TRUE(AttrUtils::SetInt(root_model, ATTR_MODEL_STREAM_NUM, stream_num));
778 GE_ASSERT_TRUE(AttrUtils::SetInt(root_model, ATTR_MODEL_EVENT_NUM, event_num));788 GE_ASSERT_TRUE(AttrUtils::SetInt(root_model, ATTR_MODEL_EVENT_NUM, event_num));
779 GE_ASSERT_TRUE(AttrUtils::SetInt(root_model, ATTR_MODEL_NOTIFY_NUM, notify_num));789 GE_ASSERT_TRUE(AttrUtils::SetInt(root_model, ATTR_MODEL_NOTIFY_NUM, notify_num));
790+ if (has_eager_attr) {
791+ GE_ASSERT_TRUE(AttrUtils::SetInt(root_model, ATTR_MODEL_EAGER_CUSTOM_OP_STREAM_NUM, eager_stream_num));
792+ }
780 793 
781 return SUCCESS;794 return SUCCESS;
782}795}
@@ -159,6 +159,8 @@ CompiledGraphSummaryPtr CompiledGraphSummary::Builder::Build(const GeModelPtr &g
159 // 支持动态图中静态子图获取对应的fix地址大小数据159 // 支持动态图中静态子图获取对应的fix地址大小数据
160 GE_ASSERT_SUCCESS(summary->data_->SetFixedFeatureMemorySize(ge_model, ge_root_model));160 GE_ASSERT_SUCCESS(summary->data_->SetFixedFeatureMemorySize(ge_model, ge_root_model));
161 GE_ASSERT_SUCCESS(summary->data_->SetStreamNum(ge_model));161 GE_ASSERT_SUCCESS(summary->data_->SetStreamNum(ge_model));
162+ // eager 自定义算子辅流统计对动态图同样有效,必须在动态图提前返回之前读取
163+ GE_ASSERT_SUCCESS(summary->data_->SetEagerCustomOpStreamNum(ge_model));
162 if (!summary->data_->is_static_) {164 if (!summary->data_->is_static_) {
163 GELOGI("Dynamic graph summary build success, graph_name:%s, model_name:%s", root_graph->GetName().c_str(),165 GELOGI("Dynamic graph summary build success, graph_name:%s, model_name:%s", root_graph->GetName().c_str(),
164 ge_model->GetName().c_str());166 ge_model->GetName().c_str());
@@ -261,6 +263,12 @@ Status CompiledGraphSummary::SummaryData::SetStreamNum(const GeModelPtr &ge_mode
261 return SUCCESS;263 return SUCCESS;
262}264}
263 265 
266+Status CompiledGraphSummary::SummaryData::SetEagerCustomOpStreamNum(const GeModelPtr &ge_model) {
267+ // 可选属性:旧 OM 或无上报算子的模型不带该属性,缺省按 0 处理
268+ (void)AttrUtils::GetInt(ge_model, ATTR_MODEL_EAGER_CUSTOM_OP_STREAM_NUM, eager_custom_op_stream_num_);
269+ return SUCCESS;
270+}
271+ 
264Status CompiledGraphSummary::SummaryData::SetEventNum(const GeModelPtr &ge_model) {272Status CompiledGraphSummary::SummaryData::SetEventNum(const GeModelPtr &ge_model) {
265 GE_ASSERT(AttrUtils::GetInt(ge_model, ATTR_MODEL_EVENT_NUM, event_num_));273 GE_ASSERT(AttrUtils::GetInt(ge_model, ATTR_MODEL_EVENT_NUM, event_num_));
266 return SUCCESS;274 return SUCCESS;
@@ -53,7 +53,8 @@ class CompiledGraphSummary::SummaryData {
53 return refreshable_feature_mem_size_;53 return refreshable_feature_mem_size_;
54 }54 }
55 size_t GetStreamNum() const {55 size_t GetStreamNum() const {
56- return stream_num_;56+ // eager 自定义算子上报的辅流统计只在 summary 观测出口合并,不进入 ATTR_MODEL_STREAM_NUM 及运行时资源链路
57+ return stream_num_ + eager_custom_op_stream_num_;
57 }58 }
58 size_t GetEventNum() const {59 size_t GetEventNum() const {
59 return event_num_;60 return event_num_;
@@ -83,6 +84,7 @@ class CompiledGraphSummary::SummaryData {
83 Status SetRefreshablFeatureMemorySize(const GeModelPtr &ge_model);84 Status SetRefreshablFeatureMemorySize(const GeModelPtr &ge_model);
84 Status SetFeatureMemoryBaseRefreshable(const GeModelPtr &ge_model);85 Status SetFeatureMemoryBaseRefreshable(const GeModelPtr &ge_model);
85 Status SetStreamNum(const GeModelPtr &ge_model);86 Status SetStreamNum(const GeModelPtr &ge_model);
87+ Status SetEagerCustomOpStreamNum(const GeModelPtr &ge_model);
86 Status SetEventNum(const GeModelPtr &ge_model);88 Status SetEventNum(const GeModelPtr &ge_model);
87 Status SetOutputTensorInfo(const GeModelPtr &ge_model);89 Status SetOutputTensorInfo(const GeModelPtr &ge_model);
88 Status ConstructIoOffsetToRoleToIndex(90 Status ConstructIoOffsetToRoleToIndex(
@@ -103,6 +105,7 @@ class CompiledGraphSummary::SummaryData {
103 */105 */
104 std::vector<FeatureMemoryPtr> feature_memory_;106 std::vector<FeatureMemoryPtr> feature_memory_;
105 size_t stream_num_{0UL};107 size_t stream_num_{0UL};
108+ size_t eager_custom_op_stream_num_{0UL};
106 size_t event_num_{0UL};109 size_t event_num_{0UL};
107 std::vector<ge::Shape> netoutput_shapes_;110 std::vector<ge::Shape> netoutput_shapes_;
108 std::vector<ge::DataType> netoutput_dtypes_;111 std::vector<ge::DataType> netoutput_dtypes_;
@@ -14,16 +14,20 @@
14#include <cstring>14#include <cstring>
15#include <iostream>15#include <iostream>
16#include <memory>16#include <memory>
17+#include <limits>
17#include <set>18#include <set>
18#include <unordered_map>19#include <unordered_map>
19#include "mmpa/mmpa_api.h"20#include "mmpa/mmpa_api.h"
20#include "common/dump/dump_manager.h"21#include "common/dump/dump_manager.h"
21#include "graph/build/input_h2d_overlap_planner.h"22#include "graph/build/input_h2d_overlap_planner.h"
23+#include "common/ge_common/ge_types.h"
22#include "graph/build/stream/dynamic_stream_allocator.h"24#include "graph/build/stream/dynamic_stream_allocator.h"
23#include "graph/build/stream_graph_optimizer.h"25#include "graph/build/stream_graph_optimizer.h"
24#include "common/omg_util/omg_util.h"26#include "common/omg_util/omg_util.h"
25#include "common/compile_profiling/ge_trace_wrapper.h"27#include "common/compile_profiling/ge_trace_wrapper.h"
26#include "graph/ge_context.h"28#include "graph/ge_context.h"
29+#include "graph/custom_op_factory.h"
30+#include "graph/debug/ge_util.h"
27#include "graph/optimize/params.h"31#include "graph/optimize/params.h"
28#include "graph/unfold/graph_unfolder.h"32#include "graph/unfold/graph_unfolder.h"
29#include "graph/utils/graph_utils.h"33#include "graph/utils/graph_utils.h"
@@ -33,6 +37,9 @@
33#include "graph/utils/tensor_utils_ex.h"37#include "graph/utils/tensor_utils_ex.h"
34#include "graph/utils/type_utils.h"38#include "graph/utils/type_utils.h"
35#include "graph/utils/op_desc_utils_ex.h"39#include "graph/utils/op_desc_utils_ex.h"
40+#include "exe_graph/lowering/kernel_run_context_builder.h"
41+#include "exe_graph/runtime/resource_usage_context.h"
42+#include "graph_metadef/exe_graph/runtime/resource_usage_collector.h"
36#include "api/gelib/gelib.h"43#include "api/gelib/gelib.h"
37#include "framework/memory/memory_assigner.h"44#include "framework/memory/memory_assigner.h"
38#include "framework/omg/version.h"45#include "framework/omg/version.h"
@@ -134,6 +141,109 @@ ge::Status SaveSoftSyncOpWeightByDependNames(const ge::NodePtr &node, const std:
134 }141 }
135 return ge::SUCCESS;142 return ge::SUCCESS;
136}143}
144+ 
145+void GetResourceUsageStorageShape(const ge::GeTensorDesc &tensor_desc, gert::StorageShape &storage_shape) {
146+ for (const auto &dim : tensor_desc.GetShape().GetDims()) {
147+ (void)storage_shape.MutableStorageShape().AppendDim(dim);
148+ }
149+ for (const auto &dim : tensor_desc.GetOriginShape().GetDims()) {
150+ (void)storage_shape.MutableOriginShape().AppendDim(dim);
151+ }
152+}
153+ 
154+ge::Status ConstructResourceUsageTensors(const ge::OpDescPtr &op_desc, const bool is_input,
155+ std::vector<std::unique_ptr<gert::Tensor>> &tensors) {
156+ const size_t desc_size = is_input ? op_desc->GetAllInputsSize() : op_desc->GetAllOutputsDescSize();
157+ for (size_t i = 0UL; i < desc_size; ++i) {
158+ const auto &tensor_desc =
159+ is_input ? op_desc->GetInputDesc(static_cast<uint32_t>(i)) : op_desc->GetOutputDesc(static_cast<uint32_t>(i));
160+ if (is_input && (tensor_desc.IsValid() != ge::GRAPH_SUCCESS)) {
T
Ttang-haojie9月24日

此条代码评论区间+156至+160

这里构造输入 tensor 用的是 GetAllInputsSize()(声明的 desc 数)减去 IsValid 失败的,而 ReportAttachedStreamKeys 定位 collector 槽位用的 GetAdditionalInputStartIndex() 来自 ComputeNodeInfo::GetInputsNum()——查了下实现链,那个值是 node->GetInDataNodesAndAnchors().size()(bg_kernel_context_extend.cc:158),实际连接数口径。

两边对齐完全依赖"IsValid 失败 ⟺ 未连接的可选输入"这个隐含约定(既有 ConstructInputTensors 同款口径)。一旦出现未连接但 IsValid 通过的可选输入(取决于算子注册原型给可选输入的默认 dtype/format),构造的 tensor 数就多于连接数,collector_index 会落到某个 tensor 槽上——Tensor* 被当成 ResourceUsageCollector* 用,类型混淆;反向错位则是越界读 null,报一句误导性的 "collector is null"。UT 的算子都是全连接的,测不出这个场景。

建议构造时直接按 node->GetInDataNodesAndAnchors() 对齐,或者 collector 槽位不走 additional_input_start、直接用 inputs.size() - 1,至少把两个数相等的断言加上。

likedislike
Chang-an-HW
9月24日 评论:
161+ // 无效的可选输入不构造 tensor:与 KernelRunContextBuilder::MakeNode 建边口径一致
162+ // (invalid desc 跳过建边),保证 tensor 数 == ComputeNodeInfo::GetInputsNum(),
163+ // 收集器槽位(additional input 起点)不会错位;调用侧另有数量与指针同一性校验兜底
164+ continue;
165+ }
166+ gert::StorageShape storage_shape;
167+ GetResourceUsageStorageShape(tensor_desc, storage_shape);
168+ const gert::StorageFormat storage_format(tensor_desc.GetOriginFormat(), tensor_desc.GetFormat(),
169+ gert::ExpandDimsType());
170+ auto tensor_holder = ge::ComGraphMakeUnique<gert::Tensor>(storage_shape, storage_format, tensor_desc.GetDataType());
171+ GE_ASSERT_NOTNULL(tensor_holder, "Create resource usage context tensor holder failed, op:%s.",
172+ op_desc->GetNamePtr());
173+ (void)tensors.emplace_back(std::move(tensor_holder));
174+ }
175+ return ge::SUCCESS;
176+}
177+ 
178+std::vector<void *> GetResourceUsageTensorPtrs(const std::vector<std::unique_ptr<gert::Tensor>> &tensors) {
179+ std::vector<void *> tensor_ptrs;
180+ tensor_ptrs.reserve(tensors.size());
181+ for (const auto &tensor : tensors) {
182+ (void)tensor_ptrs.emplace_back(tensor.get());
183+ }
184+ return tensor_ptrs;
185+}
186+ 
187+// 声明式自定义算子的辅流已按唯一 key 数精确计入 stream_num,跳过以避免双计;
188+// 未注册为 device 自定义算子或未实现 ResourceUsageReporter 的节点返回空指针,由调用方跳过。
189+ge::Status GetResourceUsageReporter(const ge::OpDescPtr &op_desc, ge::ResourceUsageReporter *&reporter) {
190+ reporter = nullptr;
191+ const ge::AscendString op_type(op_desc->GetTypePtr());
192+ if (ge::CustomOpFactory::GetArgsRefreshStrategy(op_type) == ge::ArgsRefreshStrategy::kAnnotatedArgs) {
193+ return ge::SUCCESS;
194+ }
195+ auto *const base_custom_op = ge::CustomOpFactory::CreateOrGetCustomOp(op_type, ge::OpBackend::kDevice);
196+ if (base_custom_op == nullptr) {
197+ return ge::SUCCESS;
198+ }
199+ reporter = ge::CustomOpCast<ge::ResourceUsageReporter>(base_custom_op);
200+ return ge::SUCCESS;
201+}
202+ 
203+ge::Status DeclareNodeResourceUsage(const ge::NodePtr &node, ge::ResourceUsageReporter &reporter,
204+ gert::ResourceUsageCollector &collector) {
205+ const auto op_desc = node->GetOpDesc();
206+ GE_CHECK_NOTNULL(op_desc);
207+ std::vector<std::unique_ptr<gert::Tensor>> input_tensors;
208+ std::vector<std::unique_ptr<gert::Tensor>> output_tensors;
209+ GE_ASSERT_SUCCESS(ConstructResourceUsageTensors(op_desc, true, input_tensors));
210+ GE_ASSERT_SUCCESS(ConstructResourceUsageTensors(op_desc, false, output_tensors));
211+ auto inputs = GetResourceUsageTensorPtrs(input_tensors);
212+ // 附加输入槽位布局由 gert::ResourceUsageInput 单一定义,按枚举下标写入,避免与消费侧的读取下标分叉
213+ const size_t additional_input_start = inputs.size();
214+ inputs.resize(additional_input_start + static_cast<size_t>(gert::ResourceUsageInput::kNum), nullptr);
215+ inputs[additional_input_start + static_cast<size_t>(gert::ResourceUsageInput::kCollector)] = &collector;
216+ auto outputs = GetResourceUsageTensorPtrs(output_tensors);
217+ auto context_holder =
218+ gert::KernelRunContextBuilder().Inputs(std::move(inputs)).Outputs(std::move(outputs)).Build(op_desc);
219+ auto *const kernel_context = context_holder.GetKernelContext();
220+ GE_ASSERT_NOTNULL(kernel_context, "Create resource usage context failed, op_name:%s, op_type:%s.",
221+ op_desc->GetNamePtr(), op_desc->GetTypePtr());
222+ // ResourceUsageContext 为零成员 standard-layout 视图类(头文件 static_assert 保证),
223+ // 基类为 protected 继承,与 getcdim.cc 同款使用 PtrToPtr 完成视图转换
224+ auto *const resource_usage_context = ge::PtrToPtr<gert::KernelContext, gert::ResourceUsageContext>(kernel_context);
225+ // 槽位校验与 DeclareLaunchArgsTaskPlan 同款:数量 + 指针同一性双重检查。
226+ // tensor 构造与 MakeNode 建边共用"跳过 invalid desc"口径,正常恒对齐;此校验兜底
227+ // 任一侧口径未来变化导致的错位,避免 tensor 槽被误当收集器使用(类型混淆)
228+ const size_t compute_input_num = resource_usage_context->GetComputeNodeInputNum();
229+ const size_t additional_input_num = static_cast<size_t>(gert::ResourceUsageInput::kNum);
230+ GE_ASSERT_TRUE(kernel_context->GetInputNum() == (compute_input_num + additional_input_num),
231+ "Resource usage context input size %zu does not equal compute input size %zu plus additional "
232+ "input size %zu, op_name:%s, op_type:%s.",
233+ kernel_context->GetInputNum(), compute_input_num, additional_input_num, op_desc->GetNamePtr(),
234+ op_desc->GetTypePtr());
235+ auto *const slot_collector = kernel_context->GetInputValue<gert::ResourceUsageCollector *>(
236+ compute_input_num + static_cast<size_t>(gert::ResourceUsageInput::kCollector));
237+ GE_ASSERT_TRUE(slot_collector == &collector,
238+ "Resource usage context collector slot mismatch, op_name:%s, op_type:%s.", op_desc->GetNamePtr(),
239+ op_desc->GetTypePtr());
240+ const auto declare_ret = reporter.DeclareResourceUsage(*resource_usage_context);
241+ // 收集器错误状态粘滞,即使算子吞掉 ReportAttachedStream 的错误码也在此拦截
242+ GE_ASSERT_TRUE((declare_ret == ge::GRAPH_SUCCESS) && (!collector.HasError()),
243+ "Declare resource usage failed, op_name:%s, op_type:%s, status:%d.", op_desc->GetNamePtr(),
244+ op_desc->GetTypePtr(), declare_ret);
245+ return ge::SUCCESS;
246+}
137} // namespace247} // namespace
138 248 
139namespace ge {249namespace ge {
@@ -1243,6 +1353,8 @@ Status ModelBuilder::BuildModelForGetTask(ge::Model &model) {
1243}1353}
1244 1354 
1245Status ModelBuilder::BuildModelDefForStream(ge::Model &model) {1355Status ModelBuilder::BuildModelDefForStream(ge::Model &model) {
1356+ GE_ASSERT_SUCCESS(CollectEagerCustomOpStreamNum(model), "[Collect][EagerCustomOpStreamNum] failed! graph:%s",
1357+ compute_graph_->GetName().c_str());
1246 GE_ASSERT_TRUE(ge::AttrUtils::SetInt(&model, ATTR_MODEL_STREAM_NUM, stream_num_), "[Set][Attr] %s in model failed",1358 GE_ASSERT_TRUE(ge::AttrUtils::SetInt(&model, ATTR_MODEL_STREAM_NUM, stream_num_), "[Set][Attr] %s in model failed",
1247 ATTR_MODEL_STREAM_NUM.c_str());1359 ATTR_MODEL_STREAM_NUM.c_str());
1248 GE_ASSERT_TRUE(ge::AttrUtils::SetInt(&model, ATTR_MODEL_NOTIFY_NUM, notify_num_), "[Set][Attr] %s in model failed",1360 GE_ASSERT_TRUE(ge::AttrUtils::SetInt(&model, ATTR_MODEL_NOTIFY_NUM, notify_num_), "[Set][Attr] %s in model failed",
@@ -1269,6 +1381,41 @@ Status ModelBuilder::BuildModelDefForStream(ge::Model &model) {
1269 return SUCCESS;1381 return SUCCESS;
1270}1382}
1271 1383 
1384+Status ModelBuilder::CollectEagerCustomOpStreamNum(ge::Model &model) {
1385+ GE_CHECK_NOTNULL(compute_graph_);
1386+ gert::ResourceUsageCollector collector;
1387+ bool has_reporter = false;
1388+ for (const auto &node : compute_graph_->GetAllNodes()) {
1389+ GE_CHECK_NOTNULL(node);
1390+ const auto op_desc = node->GetOpDesc();
1391+ GE_CHECK_NOTNULL(op_desc);
1392+ if (op_desc->GetOpKernelLibName() != kCustomOpKernelLibName) {
1393+ continue;
1394+ }
1395+ ResourceUsageReporter *reporter = nullptr;
1396+ GE_ASSERT_SUCCESS(GetResourceUsageReporter(op_desc, reporter));
1397+ if (reporter == nullptr) {
1398+ continue;
1399+ }
1400+ has_reporter = true;
1401+ GE_ASSERT_SUCCESS(DeclareNodeResourceUsage(node, *reporter, collector));
1402+ }
1403+ if (!has_reporter) {
1404+ // 无上报算子时不写属性,属性缺省语义为"未统计",与"统计为 0"区分,仅供内部与 DFX 使用
1405+ return SUCCESS;
1406+ }
1407+ const size_t attached_stream_key_num = collector.GetAttachedStreamKeys().size();
1408+ GE_ASSERT_TRUE(attached_stream_key_num <= static_cast<size_t>(std::numeric_limits<uint32_t>::max()),
1409+ "Eager custom op attached stream key num %zu overflow, graph:%s.", attached_stream_key_num,
1410+ compute_graph_->GetName().c_str());
1411+ GE_ASSERT_TRUE(ge::AttrUtils::SetInt(&model, ATTR_MODEL_EAGER_CUSTOM_OP_STREAM_NUM,
1412+ static_cast<int64_t>(attached_stream_key_num)),
1413+ "[Set][Attr] %s in model failed", ATTR_MODEL_EAGER_CUSTOM_OP_STREAM_NUM.c_str());
1414+ GELOGI("Graph:%s eager custom op attached stream num:%zu.", compute_graph_->GetName().c_str(),
1415+ attached_stream_key_num);
1416+ return SUCCESS;
1417+}
1418+ 
1272Status ModelBuilder::SaveSoftSyncOpWeight() const {1419Status ModelBuilder::SaveSoftSyncOpWeight() const {
1273 GELOGD("Start to recover soft sync op's weight of graph: %s.", compute_graph_->GetName().c_str());1420 GELOGD("Start to recover soft sync op's weight of graph: %s.", compute_graph_->GetName().c_str());
1274 for (const auto &node : compute_graph_->GetAllNodes()) {1421 for (const auto &node : compute_graph_->GetAllNodes()) {
@@ -135,6 +135,8 @@ class ModelBuilder {
135 void DelNodeRepeatSaveAttr();135 void DelNodeRepeatSaveAttr();
136 void ReuseWeightMem(const size_t output_size, GeTensorPtr &weight, bool &find_same_const, size_t &current_mem_offset);136 void ReuseWeightMem(const size_t output_size, GeTensorPtr &weight, bool &find_same_const, size_t &current_mem_offset);
137 137 
138+ Status CollectEagerCustomOpStreamNum(ge::Model &model);
139+ 
138 uint64_t session_id_;140 uint64_t session_id_;
139 141 
140 std::map<uint64_t, size_t> mem_type_to_mem_offset_;142 std::map<uint64_t, size_t> mem_type_to_mem_offset_;
@@ -402,6 +402,7 @@
402 - [MallocWorkSpace](cpp/gert/EagerOpExecutionContext/MallocWorkSpace.md)402 - [MallocWorkSpace](cpp/gert/EagerOpExecutionContext/MallocWorkSpace.md)
403 - [GetOutputTensor](cpp/gert/EagerOpExecutionContext/GetOutputTensor.md)403 - [GetOutputTensor](cpp/gert/EagerOpExecutionContext/GetOutputTensor.md)
404 - [MallocReadOnlyDevArgs](cpp/gert/EagerOpExecutionContext/MallocReadOnlyDevArgs.md)404 - [MallocReadOnlyDevArgs](cpp/gert/EagerOpExecutionContext/MallocReadOnlyDevArgs.md)
405+ - [RequestAttachedStream](cpp/gert/EagerOpExecutionContext/RequestAttachedStream.md)
405 406 
406 - [HostCpuExecuteOp](cpp/ge/HostCpuExecuteOp/HostCpuExecuteOp.md)407 - [HostCpuExecuteOp](cpp/ge/HostCpuExecuteOp/HostCpuExecuteOp.md)
407 - [简介](cpp/ge/HostCpuExecuteOp/overview.md)408 - [简介](cpp/ge/HostCpuExecuteOp/overview.md)
@@ -751,6 +752,21 @@
751 - [Serialize](cpp/ge/PortableOp/Serialize.md)752 - [Serialize](cpp/ge/PortableOp/Serialize.md)
752 - [Deserialize](cpp/ge/PortableOp/Deserialize.md)753 - [Deserialize](cpp/ge/PortableOp/Deserialize.md)
753 754 
755+ - [ResourceUsageReporter](cpp/ge/ResourceUsageReporter/ResourceUsageReporter.md)
756+ - [简介](cpp/ge/ResourceUsageReporter/overview.md)
757+ - [DeclareResourceUsage](cpp/ge/ResourceUsageReporter/DeclareResourceUsage.md)
758+ 
759+ - [ResourceUsageContext](cpp/gert/ResourceUsageContext/ResourceUsageContext.md)
760+ - [简介](cpp/gert/ResourceUsageContext/overview.md)
761+ - [ReportAttachedStream](cpp/gert/ResourceUsageContext/ReportAttachedStream.md)
762+ - [GetInputTensor](cpp/gert/ResourceUsageContext/GetInputTensor.md)
763+ - [GetOutputTensor](cpp/gert/ResourceUsageContext/GetOutputTensor.md)
764+ - [GetRequiredInputTensor](cpp/gert/ResourceUsageContext/GetRequiredInputTensor.md)
765+ - [GetOptionalInputTensor](cpp/gert/ResourceUsageContext/GetOptionalInputTensor.md)
766+ - [GetDynamicInputTensor](cpp/gert/ResourceUsageContext/GetDynamicInputTensor.md)
767+ - [GetRequiredOutputTensor](cpp/gert/ResourceUsageContext/GetRequiredOutputTensor.md)
768+ - [GetDynamicOutputTensor](cpp/gert/ResourceUsageContext/GetDynamicOutputTensor.md)
769+ 
754 - [ShapeAndType](cpp/ge/ShapeAndType/ShapeAndType.md)770 - [ShapeAndType](cpp/ge/ShapeAndType/ShapeAndType.md)
755 - [简介](cpp/ge/ShapeAndType/overview.md)771 - [简介](cpp/ge/ShapeAndType/overview.md)
756 - [ShapeAndType构造函数和析构函数](cpp/ge/ShapeAndType/ShapeAndType_constructor_and_destructor.md)772 - [ShapeAndType构造函数和析构函数](cpp/ge/ShapeAndType/ShapeAndType_constructor_and_destructor.md)
@@ -0,0 +1,79 @@
1+# DeclareResourceUsage
2+ 
3+## 产品支持情况
4+ 
5+全量芯片支持。
6+ 
7+## 头文件/库文件
8+ 
9+- 头文件:\#include <graph/custom\_op.h\>
10+- 库文件:libgraph.so
11+ 
12+## 功能说明
13+ 
14+在图编译阶段申报自定义算子节点的辅流资源使用。GE对图中每个实现该接口的自定义算子节点回调一次,算子上报本节点运行期将请求的资源。
15+ 
16+## 函数原型
17+ 
18+```c++
19+virtual graphStatus DeclareResourceUsage(gert::ResourceUsageContext &ctx) = 0
20+```
21+ 
22+## 参数说明
23+ 
24+| 参数名 | 输入/输出 | 说明 |
25+| --- | --- | --- |
26+| ctx | 输入 | 资源申报上下文。可获取节点信息并上报资源使用量,仅在回调期间有效。详见[`gert::ResourceUsageContext`](../../gert/ResourceUsageContext/overview.md)。 |
27+ 
28+## 返回值说明
29+ 
30+| 参数名 | 类型 | 说明 |
31+| --- | --- | --- |
32+| - | graphStatus | `GRAPH_SUCCESS(0)`:申报成功;其他值:申报失败。 |
33+ 
34+## 约束说明
35+ 
36+无
37+ 
38+## 调用示例
39+ 
40+```c++
41+#include <string>
42+ 
43+#include "graph/custom_op.h"
44+ 
45+class MyEagerOp : public ge::EagerExecuteOp, public ge::ResourceUsageReporter {
46+ public:
47+ ge::graphStatus Execute(gert::EagerOpExecutionContext *ctx) override {
48+ const auto *input = ctx->GetInputTensor(0U);
49+ if (input == nullptr) {
50+ return ge::GRAPH_FAILED;
51+ }
52+ // 运行期按 key 申请辅流(key 与 DeclareResourceUsage 申报一致)
53+ gert::rtStream aux_stream =
54+ ctx->RequestAttachedStream(ge::AscendString(MakeKey(input->GetShape().GetStorageShape().GetDim(0U)).c_str()));
55+ if (aux_stream == nullptr) {
56+ return ge::GRAPH_FAILED;
57+ }
58+ // ... 在辅流上下发 kernel,并通过 event 与主流同步 ...
59+ return ge::GRAPH_SUCCESS;
60+ }
61+ 
62+ ge::graphStatus DeclareResourceUsage(gert::ResourceUsageContext &ctx) override {
63+ // 编译期按节点静态 shape 分桶申报运行期将使用的全部 key
64+ const auto *input = ctx.GetInputTensor(0U);
65+ if (input == nullptr) {
66+ return ge::GRAPH_FAILED;
67+ }
68+ const std::vector<ge::AscendString> keys{
69+ ge::AscendString(MakeKey(input->GetShape().GetStorageShape().GetDim(0U)).c_str())};
70+ return ctx.ReportAttachedStream(keys);
71+ }
72+ 
73+ private:
74+ static std::string MakeKey(int64_t dim0) {
75+ return "my_op_aux_" + std::to_string(dim0);
76+ }
77+};
78+```
79+上述示例中,运行期`RequestAttachedStream`与编译期`ReportAttachedStream`共用同一key生成逻辑,保证申报与运行期请求一致;两个输入shape分别为1024和2048的节点,模型统计值为2。
@@ -0,0 +1,28 @@
1+# 简介
2+ 
3+`ge::ResourceUsageReporter`是自定义算子的资源申报接口,算子实现`DeclareResourceUsage`后,GE会在图编译阶段按节点回调该接口,由算子申报本节点运行期将使用资源。
4+ 
5+该接口面向Eager类自定义算子(实现[`EagerExecuteOp`](../EagerExecuteOp/overview.md)并在`Execute`中申请辅流)的辅流资源观测场景:
6+ 
7+- 统计是纯编译期旁路观测能力,不改变流分配、内存规划与运行期执行行为;统计值不是资源配额或预留承诺。
8+- 未实现该接口的算子不参与统计,编译与执行不受影响。
9+- 声明式算子(`ArgsRefreshStrategy`为`kAnnotatedArgs`的节点)的资源使用由框架精确计数,GE不会对此类节点回调`DeclareResourceUsage`;即使算子同时实现`AnnotatedArgsOp`与`ResourceUsageReporter`,也按声明式路径处理,避免重复计入。
10+ 
11+## 需要包含的头文件
12+ 
13+```c++
14+#include <graph/custom_op.h>
15+```
16+ 
17+## Public成员函数
18+ 
19+```c++
20+virtual graphStatus DeclareResourceUsage(gert::ResourceUsageContext &ctx) = 0
21+```
22+ 
23+## 相关接口
24+ 
25+- [`DeclareResourceUsage`](DeclareResourceUsage.md)
26+- [`gert::ResourceUsageContext`](../../gert/ResourceUsageContext/overview.md)
27+- [`gert::EagerOpExecutionContext::RequestAttachedStream`](../../gert/EagerOpExecutionContext/RequestAttachedStream.md)
28+- [`CompiledGraphSummary::GetStreamNum`](../CompiledGraphSummary/GetStreamNum.md)
@@ -0,0 +1,35 @@
1+# GetDynamicInputTensor
2+ 
3+## 产品支持情况
4+ 
5+全量芯片支持。
6+ 
7+## 头文件/库文件
8+ 
9+- 头文件:\#include <exe\_graph/runtime/resource\_usage\_context.h\>
10+- 库文件:liblowering.so
11+ 
12+## 功能说明
13+ 
14+基于算子IR原型定义,获取DYNAMIC\_INPUT(动态输入)类型的输入Tensor指针,需要IR原型索引和相对索引两个参数来定位动态输入端口实例化后的具体输入项,可读取编译期静态shape、format与dtype。
15+ 
16+## 函数原型
17+ 
18+```c++
19+const Tensor *GetDynamicInputTensor(size_t ir_index, size_t relative_index) const
20+```
21+ 
22+## 参数说明
23+ 
24+| 参数名 | 输入/输出 | 描述 |
25+| --- | --- | --- |
26+| ir_index | 输入 | IR原型定义中的索引,标识是哪个动态输入端口。 |
27+| relative_index | 输入 | 相对索引,标识该动态输入实例化后的具体第几个输入。例如某个DYNAMIC\_INPUT实例化了3个输入,那么relative\_index的有效范围是[0,2]。 |
28+ 
29+## 返回值说明
30+ 
31+Tensor指针,异常或越界时返回空指针。
32+ 
33+## 约束说明
34+ 
35+无
@@ -0,0 +1,35 @@
1+# GetDynamicOutputTensor
2+ 
3+## 产品支持情况
4+ 
5+全量芯片支持。
6+ 
7+## 头文件/库文件
8+ 
9+- 头文件:\#include <exe\_graph/runtime/resource\_usage\_context.h\>
10+- 库文件:liblowering.so
11+ 
12+## 功能说明
13+ 
14+基于算子IR原型定义,获取DYNAMIC\_OUTPUT(动态输出)类型的输出Tensor指针,需要IR原型索引和相对索引两个参数来定位动态输出端口实例化后的具体输出项,可读取编译期静态shape、format与dtype。
15+ 
16+## 函数原型
17+ 
18+```c++
19+const Tensor *GetDynamicOutputTensor(size_t ir_index, size_t relative_index) const
20+```
21+ 
22+## 参数说明
23+ 
24+| 参数名 | 输入/输出 | 描述 |
25+| --- | --- | --- |
26+| ir_index | 输入 | IR原型定义中的索引,标识是哪个动态输出端口。 |
27+| relative_index | 输入 | 相对索引,标识该动态输出实例化后的具体第几个输出。例如某个DYNAMIC\_OUTPUT实例化了3个输出,那么relative\_index的有效范围是[0,2]。 |
28+ 
29+## 返回值说明
30+ 
31+Tensor指针,异常或越界时返回空指针。
32+ 
33+## 约束说明
34+ 
35+无
@@ -0,0 +1,34 @@
1+# GetInputTensor
2+ 
3+## 产品支持情况
4+ 
5+全量芯片支持。
6+ 
7+## 头文件/库文件
8+ 
9+- 头文件:\#include <exe\_graph/runtime/resource\_usage\_context.h\>
10+- 库文件:liblowering.so
11+ 
12+## 功能说明
13+ 
14+根据输入index,获取输入tensor指针,可读取编译期静态shape、format与dtype。
15+ 
16+## 函数原型
17+ 
18+```c++
19+const Tensor *GetInputTensor(size_t index) const
20+```
21+ 
22+## 参数说明
23+ 
24+| 参数名 | 输入/输出 | 描述 |
25+| --- | --- | --- |
26+| index | 输入 | 输入index。 |
27+ 
28+## 返回值说明
29+ 
30+Tensor指针,异常或越界时返回空指针。
31+ 
32+## 约束说明
33+ 
34+无
@@ -0,0 +1,34 @@
1+# GetOptionalInputTensor
2+ 
3+## 产品支持情况
4+ 
5+全量芯片支持。
6+ 
7+## 头文件/库文件
8+ 
9+- 头文件:\#include <exe\_graph/runtime/resource\_usage\_context.h\>
10+- 库文件:liblowering.so
11+ 
12+## 功能说明
13+ 
14+基于算子IR原型定义,获取OPTIONAL\_INPUT(可选输入)类型的输入Tensor指针,使用IR原型定义中的索引定位,可读取编译期静态shape、format与dtype。
15+ 
16+## 函数原型
17+ 
18+```c++
19+const Tensor *GetOptionalInputTensor(size_t ir_index) const
20+```
21+ 
22+## 参数说明
23+ 
24+| 参数名 | 输入/输出 | 描述 |
25+| --- | --- | --- |
26+| ir_index | 输入 | IR原型定义中的索引。 |
27+ 
28+## 返回值说明
29+ 
30+Tensor指针,异常或该输入未实例化时返回空指针。
31+ 
32+## 约束说明
33+ 
34+无
@@ -0,0 +1,34 @@
1+# GetOutputTensor
2+ 
3+## 产品支持情况
4+ 
5+全量芯片支持。
6+ 
7+## 头文件/库文件
8+ 
9+- 头文件:\#include <exe\_graph/runtime/resource\_usage\_context.h\>
10+- 库文件:liblowering.so
11+ 
12+## 功能说明
13+ 
14+根据输出index,获取输出tensor指针,可读取编译期静态shape、format与dtype。
15+ 
16+## 函数原型
17+ 
18+```c++
19+const Tensor *GetOutputTensor(size_t index) const
20+```
21+ 
22+## 参数说明
23+ 
24+| 参数名 | 输入/输出 | 描述 |
25+| --- | --- | --- |
26+| index | 输入 | 输出index。 |
27+ 
28+## 返回值说明
29+ 
30+Tensor指针,异常或越界时返回空指针。
31+ 
32+## 约束说明
33+ 
34+无
@@ -0,0 +1,34 @@
1+# GetRequiredInputTensor
2+ 
3+## 产品支持情况
4+ 
5+全量芯片支持。
6+ 
7+## 头文件/库文件
8+ 
9+- 头文件:\#include <exe\_graph/runtime/resource\_usage\_context.h\>
10+- 库文件:liblowering.so
11+ 
12+## 功能说明
13+ 
14+基于算子IR原型定义,获取REQUIRED\_INPUT(必需输入)类型的输入Tensor指针,使用IR原型定义中的索引定位,可读取编译期静态shape、format与dtype。
15+ 
16+## 函数原型
17+ 
18+```c++
19+const Tensor *GetRequiredInputTensor(size_t ir_index) const
20+```
21+ 
22+## 参数说明
23+ 
24+| 参数名 | 输入/输出 | 描述 |
25+| --- | --- | --- |
26+| ir_index | 输入 | IR原型定义中的索引。 |
27+ 
28+## 返回值说明
29+ 
30+Tensor指针,异常或该输入未实例化时返回空指针。
31+ 
32+## 约束说明
33+ 
34+- 仅在`DeclareResourceUsage`回调期间有效,不得缓存到回调之外使用。
@@ -0,0 +1,34 @@
1+# GetRequiredOutputTensor
2+ 
3+## 产品支持情况
4+ 
5+全量芯片支持。
6+ 
7+## 头文件/库文件
8+ 
9+- 头文件:\#include <exe\_graph/runtime/resource\_usage\_context.h\>
10+- 库文件:liblowering.so
11+ 
12+## 功能说明
13+ 
14+基于算子IR原型定义,获取REQUIRED\_OUTPUT(必选输出)类型的输出Tensor指针。该接口将IR原型索引映射到对应输出实例,与[`GetOutputTensor`](GetOutputTensor.md)使用的扁平实例索引不同,可读取编译期静态shape、format与dtype。
15+ 
16+## 函数原型
17+ 
18+```c++
19+const Tensor *GetRequiredOutputTensor(size_t ir_index) const
20+```
21+ 
22+## 参数说明
23+ 
24+| 参数名 | 输入/输出 | 描述 |
25+| --- | --- | --- |
26+| ir_index | 输入 | IR原型定义中的索引。 |
27+ 
28+## 返回值说明
29+ 
30+Tensor指针,异常或该输出未实例化时返回空指针。
31+ 
32+## 约束说明
33+ 
34+无
@@ -0,0 +1,62 @@
1+# ReportAttachedStream
2+ 
3+## 产品支持情况
4+ 
5+全量芯片支持。
6+ 
7+## 头文件/库文件
8+ 
9+- 头文件:\#include <exe\_graph/runtime/resource\_usage\_context.h\>
10+- 库文件:liblowering.so
11+ 
12+## 功能说明
13+ 
14+上报本节点运行期将通过[`gert::EagerOpExecutionContext::RequestAttachedStream`](../EagerOpExecutionContext/RequestAttachedStream.md)请求的辅流key列表。框架按key在整个模型内去重统计,统计值在[`CompiledGraphSummary::GetStreamNum`](../../ge/CompiledGraphSummary/GetStreamNum.md)的返回值中合并展示。
15+ 
16+## 函数原型
17+ 
18+```c++
19+ge::graphStatus ReportAttachedStream(const std::vector<ge::AscendString> &keys)
20+```
21+ 
22+## 参数说明
23+ 
24+| 参数名 | 输入/输出 | 描述 |
25+| --- | --- | --- |
26+| keys | 输入 | 辅流key列表。每个key不能为空字符串。在动态图场景中如果无法确认本节点运行期使用的具体key列表,请上报所有可能情况的key列表。 |
27+ 
28+## 返回值说明
29+ 
30+| 参数名 | 类型 | 说明 |
31+| --- | --- | --- |
32+| - | graphStatus | `GRAPH_SUCCESS(0)`:上报成功;`GRAPH_FAILED`:存在空key或内部错误。|
33+ 
34+## 约束说明
35+ 
36+无
37+ 
38+## 调用示例
39+ 
40+```c++
41+ge::graphStatus DeclareResourceUsage(gert::ResourceUsageContext &ctx) override {
42+ const auto *input = ctx.GetInputTensor(0U);
43+ if (input == nullptr) {
44+ return ge::GRAPH_FAILED;
45+ }
46+ const auto dim = input->GetShape().GetStorageShape().GetDim(0U);
47+ if (dim <= 0) { /* -1 未知 / -2 维数未知 / 0 空 tensor */
48+ // 动态图场景,无法确认运行期信息,这里以input的第0维dim为例,上报所有可能的key
49+ const std::vector<ge::AscendString> keys{
50+ ge::AscendString("my_op_aux_fixed"),
51+ ge::AscendString("my_op_aux_1"),
52+ ge::AscendString("my_op_aux_2"),
53+ ge::AscendString("my_op_aux_3")};
54+ } else {
55+ // 申报运行期将请求的全部 key:固定辅流 + 按 shape 分桶的辅流
56+ const std::vector<ge::AscendString> keys{
57+ ge::AscendString("my_op_aux_fixed"),
58+ ge::AscendString(("my_op_aux_" + std::to_string(dim)).c_str())};
59+ }
60+ return ctx.ReportAttachedStream(keys);
61+}
62+```
@@ -0,0 +1,30 @@
1+# 简介
2+ 
3+`gert::ResourceUsageContext`继承自`gert::ExtendedKernelContext`,是[`ge::ResourceUsageReporter::DeclareResourceUsage`](../../ge/ResourceUsageReporter/DeclareResourceUsage.md)的资源申报上下文。
4+ 
5+## 需要包含的头文件
6+ 
7+```c++
8+#include <exe_graph/runtime/resource_usage_context.h>
9+```
10+ 
11+库文件:liblowering.so
12+ 
13+## Public成员函数
14+ 
15+| 函数 | 功能 |
16+| --- | --- |
17+| [`ge::graphStatus ReportAttachedStream(const std::vector<ge::AscendString> &keys)`](ReportAttachedStream.md) | 上报本节点运行期将请求的辅流key列表。 |
18+| [`const Tensor *GetInputTensor(size_t index) const`](GetInputTensor.md) | 按扁平实例索引获取输入Tensor。 |
19+| [`const Tensor *GetOutputTensor(size_t index) const`](GetOutputTensor.md) | 按扁平实例索引获取输出Tensor。 |
20+| [`const Tensor *GetRequiredInputTensor(size_t ir_index) const`](GetRequiredInputTensor.md) | 按IR原型索引获取必选输入Tensor。 |
21+| [`const Tensor *GetOptionalInputTensor(size_t ir_index) const`](GetOptionalInputTensor.md) | 按IR原型索引获取可选输入Tensor。 |
22+| [`const Tensor *GetDynamicInputTensor(size_t ir_index, size_t relative_index) const`](GetDynamicInputTensor.md) | 按IR原型索引和相对索引获取动态输入Tensor。 |
23+| [`const Tensor *GetRequiredOutputTensor(size_t ir_index) const`](GetRequiredOutputTensor.md) | 按IR原型索引获取必选输出Tensor。 |
24+| [`const Tensor *GetDynamicOutputTensor(size_t ir_index, size_t relative_index) const`](GetDynamicOutputTensor.md) | 按IR原型索引和相对索引获取动态输出Tensor。 |
25+ 
26+## 相关接口
27+ 
28+- [`ge::ResourceUsageReporter`](../../ge/ResourceUsageReporter/overview.md)
29+- [`gert::EagerOpExecutionContext::RequestAttachedStream`](../EagerOpExecutionContext/RequestAttachedStream.md)
30+- [`CompiledGraphSummary::GetStreamNum`](../../ge/CompiledGraphSummary/GetStreamNum.md)
@@ -30,6 +30,7 @@ set(LOWERING_SRCS
30 runtime/host_cpu_op_execution_context.cc30 runtime/host_cpu_op_execution_context.cc
31 runtime/annotated_args_context.cc31 runtime/annotated_args_context.cc
32 runtime/annotated_args_handler.cc32 runtime/annotated_args_handler.cc
33+ runtime/resource_usage_context.cc
33 runtime/update_args_context.cc34 runtime/update_args_context.cc
34 runtime/op_compile_context.cc35 runtime/op_compile_context.cc
35)36)
@@ -0,0 +1,66 @@
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+#ifndef METADEF_CXX_EXE_GRAPH_RUNTIME_RESOURCE_USAGE_COLLECTOR_H_
12+#define METADEF_CXX_EXE_GRAPH_RUNTIME_RESOURCE_USAGE_COLLECTOR_H_
13+ 
14+#include <cstdint>
15+#include <set>
16+#include <string>
17+#include <vector>
18+ 
19+#include "graph/ascend_string.h"
20+#include "graph/error_codes.h"
21+ 
22+namespace gert {
23+/**
24+ * 资源申报上下文的附加输入槽位布局,框架内部约定,不在对外头文件中暴露。
25+ * 生产侧(compiler/graph/build/model_builder.cc)按本枚举下标写入附加输入,
26+ * 消费侧(graph_metadef/exe_graph/runtime/resource_usage_context.cc)按同一下标读取。
27+ * 变更规则:只允许在 kNum 前追加;禁止插入、重排或修改已有值;生产侧与消费侧必须同步修改。
28+ */
29+enum class ResourceUsageInput : uint32_t { kCollector = 0U, kNum };
30+ 
31+/**
32+ * 编译期资源申报收集器,按 key 去重收集单个模型内全部节点上报的辅流 key。
33+ * 由编译流程构造并经 ResourceUsageContext 附加输入槽位传递给算子回调,
34+ * key 原文仅在编译窗口内驻留,不持久化。
35+ */
36+class ResourceUsageCollector {
37+ public:
38+ ResourceUsageCollector() = default;
39+ ~ResourceUsageCollector() = default;
40+ 
41+ /**
42+ * 收集一批辅流 key,重复 key 幂等去重。
43+ * @param keys 辅流 key 列表
44+ * @return GRAPH_SUCCESS 表示收集成功;存在空 key 时返回错误码并记录错误状态
45+ */
46+ ge::graphStatus CollectAttachedStreamKeys(const std::vector<ge::AscendString> &keys);
47+ 
48+ /**
49+ * 是否发生过收集错误。错误状态粘滞,供编译窗口在算子回调返回后复核,
50+ * 防止算子吞掉 ReportAttachedStream 的错误码。
51+ */
52+ bool HasError() const {
53+ return has_error_;
54+ }
55+ 
56+ const std::set<std::string> &GetAttachedStreamKeys() const {
57+ return attached_stream_keys_;
58+ }
59+ 
60+ private:
61+ std::set<std::string> attached_stream_keys_;
62+ bool has_error_{false};
63+};
64+} // namespace gert
65+ 
66+#endif // METADEF_CXX_EXE_GRAPH_RUNTIME_RESOURCE_USAGE_COLLECTOR_H_
@@ -0,0 +1,49 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include "exe_graph/runtime/resource_usage_context.h"
12+ 
13+#include "resource_usage_collector.h"
14+#include "common/checker.h"
15+ 
16+namespace gert {
17+ge::graphStatus ResourceUsageCollector::CollectAttachedStreamKeys(const std::vector<ge::AscendString> &keys) {
18+ for (size_t i = 0U; i < keys.size(); ++i) {
19+ // 判空必须使用 GetString:stub 库中 AscendString::GetLength 恒返 0,不可依赖
20+ const char *const key = keys[i].GetString();
21+ if ((key == nullptr) || (key[0] == '\0')) {
22+ GELOGE(ge::GRAPH_FAILED, "Attached stream key at index %zu is empty.", i);
23+ has_error_ = true;
24+ return ge::GRAPH_FAILED;
25+ }
26+ (void)attached_stream_keys_.emplace(key);
27+ }
28+ return ge::GRAPH_SUCCESS;
29+}
30+ 
31+ge::graphStatus ResourceUsageContext::ReportAttachedStream(const std::vector<ge::AscendString> &keys) {
32+ const auto additional_input_start = GetAdditionalInputStartIndex();
33+ if (additional_input_start < 0) {
34+ GELOGE(ge::GRAPH_FAILED, "Resource usage context has no compute node info.");
35+ return ge::GRAPH_FAILED;
36+ }
37+ const int64_t collector_index = additional_input_start + static_cast<int64_t>(ResourceUsageInput::kCollector);
38+ if (collector_index < 0) {
39+ GELOGE(ge::GRAPH_FAILED, "Resource usage context collector index %ld overflow.", collector_index);
40+ return ge::GRAPH_FAILED;
41+ }
42+ auto *collector = GetInputValue<ResourceUsageCollector *>(static_cast<size_t>(collector_index));
43+ if (collector == nullptr) {
44+ GELOGE(ge::GRAPH_FAILED, "Resource usage context collector is null.");
45+ return ge::GRAPH_FAILED;
46+ }
47+ return collector->CollectAttachedStreamKeys(keys);
48+}
49+} // namespace gert
@@ -11,6 +11,7 @@
11#include "exe_graph/runtime/eager_op_execution_context.h"11#include "exe_graph/runtime/eager_op_execution_context.h"
12#include "exe_graph/runtime/host_cpu_op_execution_context.h"12#include "exe_graph/runtime/host_cpu_op_execution_context.h"
13#include "exe_graph/runtime/annotated_args_context.h"13#include "exe_graph/runtime/annotated_args_context.h"
14+#include "exe_graph/runtime/resource_usage_context.h"
14#include "runtime/annotated_args_handler.h"15#include "runtime/annotated_args_handler.h"
15 16 
16#include <limits>17#include <limits>
@@ -221,6 +222,11 @@ ge::graphStatus AnnotatedArgsContext::AddLaunch(const AnnotatedKernelLaunchInfo
221 return ge::GRAPH_SUCCESS;222 return ge::GRAPH_SUCCESS;
222}223}
223 224 
225+ge::graphStatus ResourceUsageContext::ReportAttachedStream(const std::vector<ge::AscendString> &keys) {
226+ (void)keys;
227+ return ge::GRAPH_FAILED;
228+}
229+ 
224Tensor *HostCpuOpExecutionContext::MallocOutputTensor(size_t index, const StorageShape &shape,230Tensor *HostCpuOpExecutionContext::MallocOutputTensor(size_t index, const StorageShape &shape,
225 const StorageFormat &format, ge::DataType dtype) {231 const StorageFormat &format, ge::DataType dtype) {
226 (void)index;232 (void)index;
@@ -743,6 +743,8 @@ const std::string ATTR_MODEL_TARGET_TYPE = "target_type";
743 743 
744const std::string ATTR_MODEL_STREAM_NUM = "stream_num";744const std::string ATTR_MODEL_STREAM_NUM = "stream_num";
745 745 
746+const std::string ATTR_MODEL_EAGER_CUSTOM_OP_STREAM_NUM = "eager_custom_op_stream_num";
747+ 
746const std::string ATTR_MODEL_EVENT_NUM = "event_num";748const std::string ATTR_MODEL_EVENT_NUM = "event_num";
747 749 
748const std::string ATTR_MODEL_NOTIFY_NUM = "notify_num";750const std::string ATTR_MODEL_NOTIFY_NUM = "notify_num";
@@ -0,0 +1,157 @@
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+#ifndef METADEF_CXX_INC_EXE_GRAPH_RUNTIME_RESOURCE_USAGE_CONTEXT_H_
12+#define METADEF_CXX_INC_EXE_GRAPH_RUNTIME_RESOURCE_USAGE_CONTEXT_H_
13+ 
14+#include <cstddef>
15+#include <cstdint>
16+#include <type_traits>
17+#include <vector>
18+ 
19+#include "exe_graph/runtime/extended_kernel_context.h"
20+#include "graph/ascend_string.h"
21+#include "graph/error_codes.h"
22+#include "graph/tensor.h"
23+ 
24+namespace gert {
25+/**
26+ * 资源申报上下文,供 ResourceUsageReporter::DeclareResourceUsage 在编译期使用。
27+ * 继承自 ExtendedKernelContext,可通过基类接口获取节点名、节点类型、算子属性
28+ * 以及输入/输出 tensor 描述等编译期信息。
29+ * @since 9.3.0(2026-09)
30+ */
31+class ResourceUsageContext : public ExtendedKernelContext {
32+ public:
33+ /**
34+ * 上报本节点运行期将请求的辅流列表。
35+ * 框架按 key 去重统计。
36+ * 空 key 将导致上报失败并使编译失败。
37+ * @param keys 辅流 key 列表
38+ * @return GRAPH_SUCCESS 表示上报成功,否则返回错误码
39+ * @since 9.3.0(2026-09)
40+ */
41+ ge::graphStatus ReportAttachedStream(const std::vector<ge::AscendString> &keys);
42+ 
43+ /**
44+ * 根据输入 index 获取输入 Tensor 指针,可读取编译期静态 shape、format、dtype。
45+ * @param index 输入索引
46+ * @return Tensor 指针,异常或越界时返回 nullptr
47+ * @since 9.3.0(2026-09)
48+ */
49+ const Tensor *GetInputTensor(size_t index) const {
H
Hhugo1119月24日

跟eager的context接口对不上

likedislike
50+ const auto additional_input_start = GetAdditionalInputStartIndex();
51+ if ((additional_input_start < 0) || (index >= static_cast<size_t>(additional_input_start))) {
52+ return nullptr;
53+ }
54+ return GetInputPointer<Tensor>(index);
55+ }
56+ 
57+ /**
58+ * 根据输出 index 获取输出 Tensor 指针,可读取编译期静态 shape、format、dtype。
59+ * @param index 输出索引
60+ * @return Tensor 指针,异常或越界时返回 nullptr
61+ * @since 9.3.0(2026-09)
62+ */
63+ const Tensor *GetOutputTensor(size_t index) const {
64+ const auto compute_node_info = GetComputeNodeInfo();
65+ if (compute_node_info == nullptr) {
66+ return nullptr;
67+ }
68+ if (index >= compute_node_info->GetOutputsNum()) {
69+ return nullptr;
70+ }
71+ return GetOutputPointer<Tensor>(index);
72+ }
73+ 
74+ /**
75+ * 基于算子 IR 原型定义,获取 REQUIRED_INPUT 类型的输入 Tensor 指针。
76+ * @param ir_index IR 原型定义中的 index
77+ * @return Tensor 指针,异常或该输入未实例化时返回 nullptr
78+ * @since 9.3.0(2026-09)
79+ */
80+ const Tensor *GetRequiredInputTensor(size_t ir_index) const {
81+ return GetDynamicInputPointer<Tensor>(ir_index, 0);
82+ }
83+ 
84+ /**
85+ * 基于算子 IR 原型定义,获取 OPTIONAL_INPUT 类型的输入 Tensor 指针。
86+ * @param ir_index IR 原型定义中的 index
87+ * @return Tensor 指针,异常或该输入未实例化时返回 nullptr
88+ * @since 9.3.0(2026-09)
89+ */
90+ const Tensor *GetOptionalInputTensor(size_t ir_index) const {
91+ return GetDynamicInputPointer<Tensor>(ir_index, 0);
92+ }
93+ 
94+ /**
95+ * 基于算子 IR 原型定义,获取 DYNAMIC_INPUT 类型的输入 Tensor 指针。
96+ * @param ir_index IR 原型定义中的 index
97+ * @param relative_index 该输入实例化后的相对 index,例如某个 DYNAMIC_INPUT 实例化了 3 个输入,
98+ * 那么 relative_index 的有效范围是 [0, 2]
99+ * @return Tensor 指针,异常或越界时返回 nullptr
100+ * @since 9.3.0(2026-09)
101+ */
102+ const Tensor *GetDynamicInputTensor(size_t ir_index, size_t relative_index) const {
103+ return GetDynamicInputPointer<Tensor>(ir_index, relative_index);
104+ }
105+ 
106+ /**
107+ * 基于算子 IR 原型定义,获取 REQUIRED_OUTPUT 类型的输出 Tensor 指针。
108+ * @param ir_index IR 原型定义中的 index
109+ * @return Tensor 指针,异常或该输出未实例化时返回 nullptr
110+ * @since 9.3.0(2026-09)
111+ */
112+ const Tensor *GetRequiredOutputTensor(size_t ir_index) const {
113+ const auto ins_info = GetIrOutputInstanceInfo(ir_index);
114+ if (ins_info == nullptr) {
115+ return nullptr;
116+ }
117+ if (ins_info->GetInstanceNum() == 0U) {
118+ return nullptr;
119+ }
120+ return GetOutputPointer<Tensor>(ins_info->GetInstanceStart());
121+ }
122+ 
123+ /**
124+ * 基于算子 IR 原型定义,获取 DYNAMIC_OUTPUT 类型的输出 Tensor 指针。
125+ * @param ir_index IR 原型定义中的 index
126+ * @param relative_index 该输出实例化后的相对 index,例如某个 DYNAMIC_OUTPUT 实例化了 3 个输出,
127+ * 那么 relative_index 的有效范围是 [0, 2]
128+ * @return Tensor 指针,异常或越界时返回 nullptr
129+ * @since 9.3.0(2026-09)
130+ */
131+ const Tensor *GetDynamicOutputTensor(size_t ir_index, size_t relative_index) const {
132+ const auto ins_info = GetIrOutputInstanceInfo(ir_index);
133+ if (ins_info == nullptr) {
134+ return nullptr;
135+ }
136+ if (ins_info->GetInstanceNum() <= relative_index) {
137+ return nullptr;
138+ }
139+ return GetOutputPointer<Tensor>(ins_info->GetInstanceStart() + relative_index);
140+ }
141+ 
142+ protected:
143+ int64_t GetAdditionalInputStartIndex() const {
144+ const auto compute_node_info = GetComputeNodeInfo();
145+ if (compute_node_info == nullptr) {
146+ return -1;
147+ }
148+ return compute_node_info->GetInputsNum();
149+ }
150+};
151+ 
152+static_assert(std::is_standard_layout<ResourceUsageContext>::value, "ResourceUsageContext must be standard layout");
153+static_assert(sizeof(ResourceUsageContext) == sizeof(ExtendedKernelContext),
154+ "ResourceUsageContext must not add member variables");
155+} // namespace gert
156+ 
157+#endif // METADEF_CXX_INC_EXE_GRAPH_RUNTIME_RESOURCE_USAGE_CONTEXT_H_
@@ -21,6 +21,7 @@
21#include "exe_graph/runtime/infer_datatype_context.h"21#include "exe_graph/runtime/infer_datatype_context.h"
22#include "exe_graph/runtime/infer_shape_context.h"22#include "exe_graph/runtime/infer_shape_context.h"
23#include "exe_graph/runtime/op_compile_context.h"23#include "exe_graph/runtime/op_compile_context.h"
24+#include "exe_graph/runtime/resource_usage_context.h"
24#include "exe_graph/runtime/update_args_context.h"25#include "exe_graph/runtime/update_args_context.h"
25 26 
26namespace ge {27namespace ge {
@@ -127,6 +128,25 @@ class HostCpuExecuteOp : virtual public BaseCustomOp {
127 virtual graphStatus Execute(gert::HostCpuOpExecutionContext *ctx) = 0;128 virtual graphStatus Execute(gert::HostCpuOpExecutionContext *ctx) = 0;
128};129};
129 130 
131+/**
132+ * 自定义算子的资源申报接口。
133+ * 实现该接口的 Eager 自定义算子在编译期按节点被回调,申报运行期将请求的资源,
134+ * 未实现该接口的算子不参与统计,其运行期资源不计入统计值。
135+ * @since 9.3.0(2026-09)
136+ */
137+class ResourceUsageReporter : virtual public BaseCustomOp {
138+ public:
139+ ~ResourceUsageReporter() override = default;
140+ /**
141+ * 申报本节点的资源使用,编译期按节点调用。
142+ * 同一算子实例会被逐节点串行回调,实现必须可重入、无跨节点副作用。
143+ * @param ctx 资源申报上下文,可获取节点信息并上报资源使用量
144+ * @return GRAPH_SUCCESS 表示申报成功,否则编译失败
145+ * @since 9.3.0(2026-09)
146+ */
147+ virtual graphStatus DeclareResourceUsage(gert::ResourceUsageContext &ctx) = 0;
148+};
149+ 
130/**150/**
131 * 自定义算子的 kernel launch 参数声明接口。151 * 自定义算子的 kernel launch 参数声明接口。
132 * 用于自定义算子在编译期通过 DeclareLaunchArgs 声明 kernel launch 所需的参数。152 * 用于自定义算子在编译期通过 DeclareLaunchArgs 声明 kernel launch 所需的参数。
@@ -23,6 +23,7 @@ enum class CustomOpCapability : uint32_t {
23 kAnnotatedArgs = 1U << 5U,23 kAnnotatedArgs = 1U << 5U,
24 kInferMeta = 1U << 6U,24 kInferMeta = 1U << 6U,
25 kHostCpuExecute = 1U << 7U,25 kHostCpuExecute = 1U << 7U,
26+ kResourceReporting = 1U << 8U,
26};27};
27 28 
28using CustomOpCapabilityMask = uint32_t;29using CustomOpCapabilityMask = uint32_t;
@@ -65,6 +65,11 @@ struct CustomOpCapabilityTrait<AnnotatedArgsOp> {
65 static constexpr CustomOpCapability kCapability = CustomOpCapability::kAnnotatedArgs;65 static constexpr CustomOpCapability kCapability = CustomOpCapability::kAnnotatedArgs;
66};66};
67 67 
68+template <>
69+struct CustomOpCapabilityTrait<ResourceUsageReporter> {
70+ static constexpr CustomOpCapability kCapability = CustomOpCapability::kResourceReporting;
71+};
72+ 
68template <typename T>73template <typename T>
69T *CustomOpCast(BaseCustomOp *op) {74T *CustomOpCast(BaseCustomOp *op) {
70 if (op == nullptr) {75 if (op == nullptr) {
@@ -724,6 +724,8 @@ GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_MOD
724 724 
725GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_MODEL_STREAM_NUM;725GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_MODEL_STREAM_NUM;
726 726 
727+GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_MODEL_EAGER_CUSTOM_OP_STREAM_NUM;
728+ 
727GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_MODEL_EVENT_NUM;729GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_MODEL_EVENT_NUM;
728 730 
729GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_MODEL_NOTIFY_NUM;731GE_FUNC_DEV_VISIBILITY GE_FUNC_HOST_VISIBILITY extern const std::string ATTR_MODEL_NOTIFY_NUM;
@@ -21,6 +21,7 @@
21 <file value="eager_op_execution_context.h"/>21 <file value="eager_op_execution_context.h"/>
22 <file value="host_cpu_op_execution_context.h"/>22 <file value="host_cpu_op_execution_context.h"/>
23 <file value="op_compile_context.h"/>23 <file value="op_compile_context.h"/>
24+ <file value="resource_usage_context.h"/>
24 <file value="update_args_context.h"/>25 <file value="update_args_context.h"/>
25 <file value="kernel_args.h"/>26 <file value="kernel_args.h"/>
26 </file_info>27 </file_info>
@@ -10,6 +10,10 @@
10 10 
11#include <gtest/gtest.h>11#include <gtest/gtest.h>
12#include "common/memory/external_weight_desc_impl.h"12#include "common/memory/external_weight_desc_impl.h"
13+#include "common/model/ge_model.h"
14+#include "graph/build/graph_compile_summary_impl.h"
15+#include "graph/debug/ge_attr_define.h"
16+#include "graph/utils/attr_utils.h"
13 17 
14namespace ge {18namespace ge {
15class UtestGraphCompileSummaryImpl : public testing::Test {19class UtestGraphCompileSummaryImpl : public testing::Test {
@@ -62,4 +66,31 @@ TEST(UtestGraphCompileSummaryImpl, DataIntegrity) {
62 ASSERT_EQ(data.GetOffset(), 0);66 ASSERT_EQ(data.GetOffset(), 0);
63 ASSERT_EQ(data.GetId(), id);67 ASSERT_EQ(data.GetId(), id);
64}68}
69+ 
70+TEST(UtestGraphCompileSummaryImpl, EagerCustomOpStreamNumMergedIntoGetStreamNum) {
71+ CompiledGraphSummary::SummaryData data;
72+ data.stream_num_ = 5UL;
73+ // 无上报算子模型:eager 统计缺省 0,GetStreamNum 与既有行为逐一相等
74+ EXPECT_EQ(data.GetStreamNum(), 5UL);
75+ data.eager_custom_op_stream_num_ = 3UL;
76+ // 唯一合并点:观测出口返回 框架流数 + eager 辅流统计
77+ EXPECT_EQ(data.GetStreamNum(), 8UL);
78+}
79+ 
80+TEST(UtestGraphCompileSummaryImpl, SetEagerCustomOpStreamNumDefaultsToZeroWhenAttrAbsent) {
81+ auto ge_model = std::make_shared<GeModel>();
82+ CompiledGraphSummary::SummaryData data;
83+ data.stream_num_ = 2UL;
84+ // 旧 OM / 未统计模型无该属性:缺省按 0,不影响 GetStreamNum
85+ ASSERT_EQ(data.SetEagerCustomOpStreamNum(ge_model), SUCCESS);
86+ EXPECT_EQ(data.eager_custom_op_stream_num_, 0UL);
87+ EXPECT_EQ(data.GetStreamNum(), 2UL);
88+ // 属性存在时读取(根模型聚合值)
89+ ASSERT_TRUE(AttrUtils::SetInt(ge_model, ATTR_MODEL_EAGER_CUSTOM_OP_STREAM_NUM, static_cast<int64_t>(7)));
90+ ASSERT_EQ(data.SetEagerCustomOpStreamNum(ge_model), SUCCESS);
91+ EXPECT_EQ(data.eager_custom_op_stream_num_, 7UL);
92+ EXPECT_EQ(data.GetStreamNum(), 9UL);
93+ // 隔离不变量:eager 统计读取不触碰 stream_num_
94+ EXPECT_EQ(data.stream_num_, 2UL);
95+}
65} // namespace ge96} // namespace ge
@@ -11,6 +11,9 @@
11#include <gtest/gtest.h>11#include <gtest/gtest.h>
12#include <gmock/gmock.h>12#include <gmock/gmock.h>
13#include <set>13#include <set>
14+#include <limits>
15+#include "common/model/ge_model.h"
16+#include "common/model/ge_root_model.h"
14#include "ge_graph_dsl/graph_dsl.h"17#include "ge_graph_dsl/graph_dsl.h"
15 18 
16#include "macro_utils/dt_public_scope.h"19#include "macro_utils/dt_public_scope.h"
@@ -1647,4 +1650,70 @@ TEST_F(GraphBuilderTest, Build_GraphWithWorkspaceUpdate) {
1647 VarManager::Instance(0UL)->Init(0U, 0UL, 0UL, 0UL);1650 VarManager::Instance(0UL)->Init(0U, 0UL, 0UL, 0UL);
1648 auto ret = graph_builder.Build(root_graph, root_model);1651 auto ret = graph_builder.Build(root_graph, root_model);
1649}1652}
1653+ 
1654+TEST_F(GraphBuilderTest, RefreshInfoAggregatesEagerCustomOpStreamNum) {
1655+ auto root_graph = std::make_shared<ComputeGraph>("eager_agg_root");
1656+ auto ge_root_model = std::make_shared<GeRootModel>();
1657+ auto root_model = std::make_shared<GeModel>();
1658+ auto sub_model_with_keys = std::make_shared<GeModel>();
1659+ auto sub_model_without_keys = std::make_shared<GeModel>();
1660+ ASSERT_TRUE(AttrUtils::SetInt(root_model, ATTR_MODEL_STREAM_NUM, 2));
1661+ ASSERT_TRUE(AttrUtils::SetInt(root_model, ATTR_MODEL_EAGER_CUSTOM_OP_STREAM_NUM, 3));
1662+ ASSERT_TRUE(AttrUtils::SetInt(sub_model_with_keys, ATTR_MODEL_STREAM_NUM, 5));
1663+ ASSERT_TRUE(AttrUtils::SetInt(sub_model_with_keys, ATTR_MODEL_EAGER_CUSTOM_OP_STREAM_NUM, 4));
1664+ ASSERT_TRUE(AttrUtils::SetInt(sub_model_without_keys, ATTR_MODEL_STREAM_NUM, 1));
1665+ ge_root_model->SetSubgraphInstanceNameToModel("eager_agg_root", root_model);
1666+ ge_root_model->SetSubgraphInstanceNameToModel("eager_agg_sub_1", sub_model_with_keys);
1667+ ge_root_model->SetSubgraphInstanceNameToModel("eager_agg_sub_2", sub_model_without_keys);
1668+ 
1669+ GraphBuilder graph_builder;
1670+ ASSERT_EQ(graph_builder.RefreshInfoOfDynamicShapeGraph(root_graph, ge_root_model), SUCCESS);
1671+ 
1672+ // stream_num 聚合行为不变:2 + 5 + 1 = 8
1673+ int64_t stream_num = 0;
1674+ ASSERT_TRUE(AttrUtils::GetInt(root_model, ATTR_MODEL_STREAM_NUM, stream_num));
1675+ EXPECT_EQ(stream_num, 8);
1676+ // eager 辅流统计求和不去重(各子模型执行实例各自建流):3 + 4 + 缺省 0 = 7
1677+ int64_t eager_custom_op_stream_num = 0;
1678+ ASSERT_TRUE(AttrUtils::GetInt(root_model, ATTR_MODEL_EAGER_CUSTOM_OP_STREAM_NUM, eager_custom_op_stream_num));
1679+ EXPECT_EQ(eager_custom_op_stream_num, 7);
1680+}
1681+ 
1682+TEST_F(GraphBuilderTest, RefreshInfoOmitsEagerCustomOpStreamNumWhenAbsent) {
1683+ auto root_graph = std::make_shared<ComputeGraph>("eager_agg_absent_root");
1684+ auto ge_root_model = std::make_shared<GeRootModel>();
1685+ auto root_model = std::make_shared<GeModel>();
1686+ auto sub_model = std::make_shared<GeModel>();
1687+ ASSERT_TRUE(AttrUtils::SetInt(root_model, ATTR_MODEL_STREAM_NUM, 2));
1688+ ASSERT_TRUE(AttrUtils::SetInt(sub_model, ATTR_MODEL_STREAM_NUM, 5));
1689+ ge_root_model->SetSubgraphInstanceNameToModel("eager_agg_absent_root", root_model);
1690+ ge_root_model->SetSubgraphInstanceNameToModel("eager_agg_absent_sub", sub_model);
1691+ 
1692+ GraphBuilder graph_builder;
1693+ ASSERT_EQ(graph_builder.RefreshInfoOfDynamicShapeGraph(root_graph, ge_root_model), SUCCESS);
1694+ 
1695+ int64_t stream_num = 0;
1696+ ASSERT_TRUE(AttrUtils::GetInt(root_model, ATTR_MODEL_STREAM_NUM, stream_num));
1697+ EXPECT_EQ(stream_num, 7);
1698+ // 全部模型均未上报时不写根模型属性,产物零变化
1699+ int64_t eager_custom_op_stream_num = 0;
1700+ EXPECT_FALSE(AttrUtils::GetInt(root_model, ATTR_MODEL_EAGER_CUSTOM_OP_STREAM_NUM, eager_custom_op_stream_num));
1701+}
1702+ 
1703+TEST_F(GraphBuilderTest, RefreshInfoEagerCustomOpStreamNumOverflowFails) {
1704+ auto root_graph = std::make_shared<ComputeGraph>("eager_agg_overflow_root");
1705+ auto ge_root_model = std::make_shared<GeRootModel>();
1706+ auto root_model = std::make_shared<GeModel>();
1707+ auto sub_model = std::make_shared<GeModel>();
1708+ ASSERT_TRUE(AttrUtils::SetInt(root_model, ATTR_MODEL_STREAM_NUM, 1));
1709+ ASSERT_TRUE(AttrUtils::SetInt(root_model, ATTR_MODEL_EAGER_CUSTOM_OP_STREAM_NUM,
1710+ static_cast<int64_t>(std::numeric_limits<uint32_t>::max())));
1711+ ASSERT_TRUE(AttrUtils::SetInt(sub_model, ATTR_MODEL_STREAM_NUM, 1));
1712+ ASSERT_TRUE(AttrUtils::SetInt(sub_model, ATTR_MODEL_EAGER_CUSTOM_OP_STREAM_NUM, 1));
1713+ ge_root_model->SetSubgraphInstanceNameToModel("eager_agg_overflow_root", root_model);
1714+ ge_root_model->SetSubgraphInstanceNameToModel("eager_agg_overflow_sub", sub_model);
1715+ 
1716+ GraphBuilder graph_builder;
1717+ EXPECT_NE(graph_builder.RefreshInfoOfDynamicShapeGraph(root_graph, ge_root_model), SUCCESS);
1718+}
1650} // namespace ge1719} // namespace ge
@@ -63,6 +63,7 @@
63#include "api/gelib/gelib.h"63#include "api/gelib/gelib.h"
64#include "register/core_num_utils.h"64#include "register/core_num_utils.h"
65#include "ge/ut/ge/test_tools_task_info.h"65#include "ge/ut/ge/test_tools_task_info.h"
66+#include "graph/custom_op_factory.h"
66#include "macro_utils/dt_public_unscope.h"67#include "macro_utils/dt_public_unscope.h"
67 68 
68using namespace std;69using namespace std;
@@ -2516,4 +2517,221 @@ TEST_F(UtestModelBuilderTest, ClearOriginalFormatKeepIrAttr) {
2516 EXPECT_TRUE(bool_value);2517 EXPECT_TRUE(bool_value);
2517 EXPECT_FALSE(normal_format_desc->HasAttr(ATTR_NAME_FORMAT));2518 EXPECT_FALSE(normal_format_desc->HasAttr(ATTR_NAME_FORMAT));
2518}2519}
2520+ 
2521+namespace {
2522+// 上报算子:按静态 shape 分桶申报 key(use_shape_key),或申报固定 key 列表(fixed_keys)
2523+class ResourceUsageTestOp : public ResourceUsageReporter {
2524+ public:
2525+ static void Reset() {
2526+ call_count = 0U;
2527+ use_shape_key = false;
2528+ return_failure = false;
2529+ swallow_empty_key = false;
2530+ fixed_keys = {};
2531+ }
2532+ graphStatus DeclareResourceUsage(gert::ResourceUsageContext &ctx) override {
2533+ ++call_count;
2534+ if (return_failure) {
2535+ return GRAPH_FAILED;
2536+ }
2537+ std::vector<AscendString> keys;
2538+ if (use_shape_key) {
2539+ (void)keys.emplace_back(AscendString("key_s"));
2540+ const auto *tensor = ctx.GetInputTensor(0U);
2541+ if (tensor == nullptr) {
2542+ return GRAPH_FAILED;
2543+ }
2544+ const auto dim = tensor->GetShape().GetStorageShape().GetDim(0U);
2545+ (void)keys.emplace_back(AscendString(("key_v_" + std::to_string(dim)).c_str()));
2546+ } else {
2547+ for (const auto &key : fixed_keys) {
2548+ (void)keys.emplace_back(AscendString(key.c_str()));
2549+ }
2550+ }
2551+ if (swallow_empty_key) {
2552+ (void)keys.emplace_back(AscendString(""));
2553+ // 模拟算子吞掉 ReportAttachedStream 的错误码并返回成功,验证窗口经粘滞错误复核拦截
2554+ (void)ctx.ReportAttachedStream(keys);
2555+ return GRAPH_SUCCESS;
2556+ }
2557+ return ctx.ReportAttachedStream(keys);
2558+ }
2559+ static uint32_t call_count;
2560+ static bool use_shape_key;
2561+ static bool return_failure;
2562+ static bool swallow_empty_key;
2563+ static std::vector<std::string> fixed_keys;
2564+};
2565+uint32_t ResourceUsageTestOp::call_count = 0U;
2566+bool ResourceUsageTestOp::use_shape_key = false;
2567+bool ResourceUsageTestOp::return_failure = false;
2568+bool ResourceUsageTestOp::swallow_empty_key = false;
2569+std::vector<std::string> ResourceUsageTestOp::fixed_keys;
2570+ 
2571+// 声明式策略算子(实现 AnnotatedArgsOp → GetArgsRefreshStrategy 为 kAnnotatedArgs),同时实现上报接口验证防双计跳过
2572+class DeclarativeResourceUsageTestOp : public AnnotatedArgsOp, public ResourceUsageReporter {
2573+ public:
2574+ static void Reset() {
2575+ declare_called = false;
2576+ }
2577+ graphStatus DeclareLaunchArgs(gert::AnnotatedArgsContext &ctx) override {
2578+ (void)ctx;
2579+ return GRAPH_SUCCESS;
2580+ }
2581+ graphStatus DeclareResourceUsage(gert::ResourceUsageContext &ctx) override {
2582+ (void)ctx;
2583+ declare_called = true;
2584+ return GRAPH_SUCCESS;
2585+ }
2586+ static bool declare_called;
2587+};
2588+bool DeclarativeResourceUsageTestOp::declare_called = false;
2589+ 
2590+class PlainResourceUsageTestOp : public BaseCustomOp {};
2591+ 
2592+void RegisterResourceUsageTestOps() {
2593+ static bool registered = false;
2594+ if (registered) {
2595+ return;
2596+ }
2597+ registered = true;
2598+ (void)CustomOpFactory::RegisterCustomOpCreator(
2599+ AscendString("ResourceUsageTestOp"), OpBackend::kDevice,
2600+ []() -> std::unique_ptr<BaseCustomOp> { return std::make_unique<ResourceUsageTestOp>(); });
2601+ (void)CustomOpFactory::RegisterCustomOpCreator(
2602+ AscendString("DeclarativeResourceUsageTestOp"), OpBackend::kDevice,
2603+ []() -> std::unique_ptr<BaseCustomOp> { return std::make_unique<DeclarativeResourceUsageTestOp>(); });
2604+ (void)CustomOpFactory::RegisterCustomOpCreator(
2605+ AscendString("PlainResourceUsageTestOp"), OpBackend::kDevice,
2606+ []() -> std::unique_ptr<BaseCustomOp> { return std::make_unique<PlainResourceUsageTestOp>(); });
2607+}
2608+ 
2609+OpDescPtr MakeResourceUsageNodeDesc(const std::string &name, const std::string &type, const int64_t dim,
2610+ const std::string &kernel_lib_name) {
2611+ auto op_desc = std::make_shared<OpDesc>(name, type);
2612+ op_desc->SetOpKernelLibName(kernel_lib_name);
2613+ const GeTensorDesc tensor_desc(GeShape({dim}), FORMAT_ND, DT_FLOAT);
2614+ (void)op_desc->AddInputDesc("x", tensor_desc);
2615+ // invalid 可选输入:MakeNode 建边与 tensor 构造均按 IsValid 跳过,覆盖收集器槽位对齐口径
2616+ const GeTensorDesc invalid_optional_desc(GeShape(), FORMAT_RESERVED, DT_UNDEFINED);
2617+ (void)op_desc->AddInputDesc("optional_x", invalid_optional_desc);
2618+ (void)op_desc->AddOutputDesc("y", tensor_desc);
2619+ return op_desc;
2620+}
2621+} // namespace
2622+ 
2623+TEST_F(UtestModelBuilderTest, CollectEagerCustomOpStreamNumPerNodeShapeBucketing) {
2624+ RegisterResourceUsageTestOps();
2625+ ResourceUsageTestOp::Reset();
2626+ ResourceUsageTestOp::use_shape_key = true;
2627+ auto graph = std::make_shared<ComputeGraph>("resource_usage_bucketing_graph");
2628+ ASSERT_NE(graph->AddNode(MakeResourceUsageNodeDesc("node_1024", "ResourceUsageTestOp", 1024, kCustomOpKernelLibName)),
2629+ nullptr);
2630+ ASSERT_NE(graph->AddNode(MakeResourceUsageNodeDesc("node_2048", "ResourceUsageTestOp", 2048, kCustomOpKernelLibName)),
2631+ nullptr);
2632+ 
2633+ Graph2SubGraphInfoList subgraphs;
2634+ std::map<std::string, int> stream_max_parallel_num;
2635+ ModelBuilder builder(0, graph, subgraphs, stream_max_parallel_num, false);
2636+ Model model;
2637+ ASSERT_EQ(builder.CollectEagerCustomOpStreamNum(model), SUCCESS);
2638+ 
2639+ // per-node 粒度:同 op_type 两节点各回调一次
2640+ EXPECT_EQ(ResourceUsageTestOp::call_count, 2U);
2641+ int64_t eager_custom_op_stream_num = 0;
2642+ ASSERT_TRUE(AttrUtils::GetInt(&model, ATTR_MODEL_EAGER_CUSTOM_OP_STREAM_NUM, eager_custom_op_stream_num));
2643+ // union = {key_s, key_v_1024, key_v_2048}
2644+ EXPECT_EQ(eager_custom_op_stream_num, 3);
2645+ // 隔离不变量:收集窗口不修改 stream_num(保持 ge::Model 构造缺省值 0)
2646+ int64_t stream_num = -1;
2647+ ASSERT_TRUE(AttrUtils::GetInt(&model, ATTR_MODEL_STREAM_NUM, stream_num));
2648+ EXPECT_EQ(stream_num, 0);
2649+}
2650+ 
2651+TEST_F(UtestModelBuilderTest, CollectEagerCustomOpStreamNumDedupAndSkip) {
2652+ RegisterResourceUsageTestOps();
2653+ ResourceUsageTestOp::Reset();
2654+ DeclarativeResourceUsageTestOp::Reset();
2655+ ResourceUsageTestOp::fixed_keys = {"key_a", "key_b"};
2656+ auto graph = std::make_shared<ComputeGraph>("resource_usage_dedup_graph");
2657+ ASSERT_NE(graph->AddNode(MakeResourceUsageNodeDesc("node_a1", "ResourceUsageTestOp", 8, kCustomOpKernelLibName)),
2658+ nullptr);
2659+ ASSERT_NE(graph->AddNode(MakeResourceUsageNodeDesc("node_a2", "ResourceUsageTestOp", 8, kCustomOpKernelLibName)),
2660+ nullptr);
2661+ ASSERT_NE(graph->AddNode(
2662+ MakeResourceUsageNodeDesc("node_decl", "DeclarativeResourceUsageTestOp", 8, kCustomOpKernelLibName)),
2663+ nullptr);
2664+ ASSERT_NE(
2665+ graph->AddNode(MakeResourceUsageNodeDesc("node_plain", "PlainResourceUsageTestOp", 8, kCustomOpKernelLibName)),
2666+ nullptr);
2667+ ASSERT_NE(graph->AddNode(MakeResourceUsageNodeDesc("node_relu", RELU, 8, "AIcoreEngine")), nullptr);
2668+ 
2669+ Graph2SubGraphInfoList subgraphs;
2670+ std::map<std::string, int> stream_max_parallel_num;
2671+ ModelBuilder builder(0, graph, subgraphs, stream_max_parallel_num, false);
2672+ Model model;
2673+ ASSERT_EQ(builder.CollectEagerCustomOpStreamNum(model), SUCCESS);
2674+ 
2675+ EXPECT_EQ(ResourceUsageTestOp::call_count, 2U);
2676+ // 声明式策略节点即使实现上报接口也跳过,防止与 stream_num 双计
2677+ EXPECT_FALSE(DeclarativeResourceUsageTestOp::declare_called);
2678+ int64_t eager_custom_op_stream_num = 0;
2679+ ASSERT_TRUE(AttrUtils::GetInt(&model, ATTR_MODEL_EAGER_CUSTOM_OP_STREAM_NUM, eager_custom_op_stream_num));
2680+ // 同 key 跨节点去重:{key_a, key_b}
2681+ EXPECT_EQ(eager_custom_op_stream_num, 2);
2682+}
2683+ 
2684+TEST_F(UtestModelBuilderTest, CollectEagerCustomOpStreamNumNoReportingOpSkipsAttr) {
2685+ RegisterResourceUsageTestOps();
2686+ ResourceUsageTestOp::Reset();
2687+ DeclarativeResourceUsageTestOp::Reset();
2688+ auto graph = std::make_shared<ComputeGraph>("resource_usage_no_reporting_graph");
2689+ ASSERT_NE(graph->AddNode(
2690+ MakeResourceUsageNodeDesc("node_decl", "DeclarativeResourceUsageTestOp", 8, kCustomOpKernelLibName)),
2691+ nullptr);
2692+ ASSERT_NE(
2693+ graph->AddNode(MakeResourceUsageNodeDesc("node_plain", "PlainResourceUsageTestOp", 8, kCustomOpKernelLibName)),
2694+ nullptr);
2695+ ASSERT_NE(graph->AddNode(MakeResourceUsageNodeDesc("node_relu", RELU, 8, "AIcoreEngine")), nullptr);
2696+ 
2697+ Graph2SubGraphInfoList subgraphs;
2698+ std::map<std::string, int> stream_max_parallel_num;
2699+ ModelBuilder builder(0, graph, subgraphs, stream_max_parallel_num, false);
2700+ Model model;
2701+ ASSERT_EQ(builder.CollectEagerCustomOpStreamNum(model), SUCCESS);
2702+ 
2703+ EXPECT_FALSE(DeclarativeResourceUsageTestOp::declare_called);
2704+ // 无命中上报算子时不写属性(缺省 = 未统计)
2705+ int64_t eager_custom_op_stream_num = 0;
2706+ EXPECT_FALSE(AttrUtils::GetInt(&model, ATTR_MODEL_EAGER_CUSTOM_OP_STREAM_NUM, eager_custom_op_stream_num));
2707+}
2708+ 
2709+TEST_F(UtestModelBuilderTest, CollectEagerCustomOpStreamNumFailurePropagation) {
2710+ RegisterResourceUsageTestOps();
2711+ Graph2SubGraphInfoList subgraphs;
2712+ std::map<std::string, int> stream_max_parallel_num;
2713+ 
2714+ // 回调返回失败 → 编译失败
2715+ ResourceUsageTestOp::Reset();
2716+ ResourceUsageTestOp::return_failure = true;
2717+ auto failure_graph = std::make_shared<ComputeGraph>("resource_usage_failure_graph");
2718+ ASSERT_NE(
2719+ failure_graph->AddNode(MakeResourceUsageNodeDesc("node_f", "ResourceUsageTestOp", 8, kCustomOpKernelLibName)),
2720+ nullptr);
2721+ ModelBuilder failure_builder(0, failure_graph, subgraphs, stream_max_parallel_num, false);
2722+ Model failure_model;
2723+ EXPECT_NE(failure_builder.CollectEagerCustomOpStreamNum(failure_model), SUCCESS);
2724+ 
2725+ // 算子吞掉空 key 错误码并返回成功 → 粘滞错误经窗口复核仍编译失败
2726+ ResourceUsageTestOp::Reset();
2727+ ResourceUsageTestOp::swallow_empty_key = true;
2728+ auto swallow_graph = std::make_shared<ComputeGraph>("resource_usage_swallow_graph");
2729+ ASSERT_NE(
2730+ swallow_graph->AddNode(MakeResourceUsageNodeDesc("node_s", "ResourceUsageTestOp", 8, kCustomOpKernelLibName)),
2731+ nullptr);
2732+ ModelBuilder swallow_builder(0, swallow_graph, subgraphs, stream_max_parallel_num, false);
2733+ Model swallow_model;
2734+ EXPECT_NE(swallow_builder.CollectEagerCustomOpStreamNum(swallow_model), SUCCESS);
2735+ ResourceUsageTestOp::Reset();
2736+}
2519} // namespace ge2737} // namespace ge
@@ -0,0 +1,221 @@
1+/**
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include "exe_graph/runtime/resource_usage_context.h"
12+ 
13+#include <cstddef>
14+#include <limits>
15+#include <set>
16+#include <string>
17+#include <vector>
18+ 
19+#include <gtest/gtest.h>
20+#include "exe_graph/lowering/kernel_run_context_builder.h"
21+#include "runtime/resource_usage_collector.h"
22+#include "graph/op_desc.h"
23+ 
24+namespace gert {
25+namespace {
26+constexpr const char *kOpName = "resource_usage_op";
27+constexpr const char *kOpType = "ResourceUsageOp";
28+ 
29+ge::OpDescPtr MakeOpDesc() {
30+ auto op_desc = std::make_shared<ge::OpDesc>(kOpName, kOpType);
31+ const ge::GeTensorDesc tensor_desc(ge::GeShape({2, 8}), ge::FORMAT_ND, ge::DT_FLOAT);
32+ (void)op_desc->AddInputDesc("x", tensor_desc);
33+ (void)op_desc->AddOutputDesc("y", tensor_desc);
34+ return op_desc;
35+}
36+ 
37+Tensor MakeTensor(void *addr) {
38+ return Tensor({{2, 8}, {2, 8}}, {ge::FORMAT_ND, ge::FORMAT_ND, {}}, kOnDeviceHbm, ge::DT_FLOAT, addr);
39+}
40+ 
41+// 带 IR 原型信息的 OpDesc:输入 x(必需,1 实例)、dyn(动态,2 实例)、opt(可选,未实例化);
42+// 输出 y(必需,1 实例)、outs(动态,2 实例)。扁平实例序为 [x, dyn0, dyn1] 与 [y, outs0, outs1]
43+ge::OpDescPtr MakeIrOpDesc() {
44+ auto op_desc = std::make_shared<ge::OpDesc>("resource_usage_ir_op", "ResourceUsageIrOp");
45+ const ge::GeTensorDesc tensor_desc(ge::GeShape({2, 8}), ge::FORMAT_ND, ge::DT_FLOAT);
46+ (void)op_desc->AddInputDesc("x", tensor_desc);
47+ (void)op_desc->AddInputDesc("dyn0", tensor_desc);
48+ (void)op_desc->AddInputDesc("dyn1", tensor_desc);
49+ op_desc->AppendIrInput("x", ge::kIrInputRequired);
50+ op_desc->AppendIrInput("dyn", ge::kIrInputDynamic);
51+ op_desc->AppendIrInput("opt", ge::kIrInputOptional);
52+ (void)op_desc->AddOutputDesc("y", tensor_desc);
53+ (void)op_desc->AddOutputDesc("outs0", tensor_desc);
54+ (void)op_desc->AddOutputDesc("outs1", tensor_desc);
55+ op_desc->AppendIrOutput("y", ge::kIrOutputRequired);
56+ op_desc->AppendIrOutput("outs", ge::kIrOutputDynamic);
57+ return op_desc;
58+}
59+} // namespace
60+ 
61+TEST(ResourceUsageContextUT, ReportsKeysAndDedupsAcrossCalls) {
62+ ResourceUsageCollector collector;
63+ Tensor input_tensor = MakeTensor(reinterpret_cast<void *>(0x1000U));
64+ Tensor output_tensor = MakeTensor(reinterpret_cast<void *>(0x2000U));
65+ // 收集器挂在附加输入槽 ResourceUsageInput::kCollector,与生产侧 model_builder.cc 同款布局
66+ auto holder =
67+ KernelRunContextBuilder().Inputs({&input_tensor, &collector}).Outputs({&output_tensor}).Build(MakeOpDesc());
68+ auto *context = reinterpret_cast<ResourceUsageContext *>(holder.GetKernelContext());
69+ ASSERT_NE(context, nullptr);
70+ 
71+ // 模拟同 op_type 两个节点按静态 shape 申报不同 key 集:{key_s, key_v_1024} 与 {key_s, key_v_2048}
72+ EXPECT_EQ(context->ReportAttachedStream({ge::AscendString("key_s"), ge::AscendString("key_v_1024")}),
73+ ge::GRAPH_SUCCESS);
74+ EXPECT_EQ(context->ReportAttachedStream({ge::AscendString("key_s"), ge::AscendString("key_v_2048")}),
75+ ge::GRAPH_SUCCESS);
76+ EXPECT_FALSE(collector.HasError());
77+ const std::set<std::string> expected_keys{"key_s", "key_v_1024", "key_v_2048"};
78+ EXPECT_EQ(collector.GetAttachedStreamKeys(), expected_keys);
79+}
80+ 
81+TEST(ResourceUsageContextUT, RejectsEmptyKeyWithStickyError) {
82+ ResourceUsageCollector collector;
83+ Tensor input_tensor = MakeTensor(reinterpret_cast<void *>(0x1000U));
84+ Tensor output_tensor = MakeTensor(reinterpret_cast<void *>(0x2000U));
85+ auto holder =
86+ KernelRunContextBuilder().Inputs({&input_tensor, &collector}).Outputs({&output_tensor}).Build(MakeOpDesc());
87+ auto *context = reinterpret_cast<ResourceUsageContext *>(holder.GetKernelContext());
88+ ASSERT_NE(context, nullptr);
89+ 
90+ // 空 key 之前的合法 key 已入 union,空 key 处失败返回
91+ EXPECT_NE(context->ReportAttachedStream({ge::AscendString("key_a"), ge::AscendString("")}), ge::GRAPH_SUCCESS);
92+ EXPECT_TRUE(collector.HasError());
93+ const std::set<std::string> expected_keys{"key_a"};
94+ EXPECT_EQ(collector.GetAttachedStreamKeys(), expected_keys);
95+ 
96+ // 错误状态粘滞:即使算子吞掉错误码后继续成功上报,HasError 仍为 true,供编译窗口复核拦截
97+ EXPECT_EQ(context->ReportAttachedStream({ge::AscendString("key_b")}), ge::GRAPH_SUCCESS);
98+ EXPECT_TRUE(collector.HasError());
99+}
100+ 
101+TEST(ResourceUsageContextUT, EmptyKeyListSucceeds) {
102+ ResourceUsageCollector collector;
103+ Tensor input_tensor = MakeTensor(reinterpret_cast<void *>(0x1000U));
104+ Tensor output_tensor = MakeTensor(reinterpret_cast<void *>(0x2000U));
105+ auto holder =
106+ KernelRunContextBuilder().Inputs({&input_tensor, &collector}).Outputs({&output_tensor}).Build(MakeOpDesc());
107+ auto *context = reinterpret_cast<ResourceUsageContext *>(holder.GetKernelContext());
108+ ASSERT_NE(context, nullptr);
109+ 
110+ EXPECT_EQ(context->ReportAttachedStream({}), ge::GRAPH_SUCCESS);
111+ EXPECT_FALSE(collector.HasError());
112+ EXPECT_TRUE(collector.GetAttachedStreamKeys().empty());
113+}
114+ 
115+TEST(ResourceUsageContextUT, FailsWhenCollectorSlotIsNull) {
116+ Tensor input_tensor = MakeTensor(reinterpret_cast<void *>(0x1000U));
117+ Tensor output_tensor = MakeTensor(reinterpret_cast<void *>(0x2000U));
118+ auto holder =
119+ KernelRunContextBuilder().Inputs({&input_tensor, nullptr}).Outputs({&output_tensor}).Build(MakeOpDesc());
120+ auto *context = reinterpret_cast<ResourceUsageContext *>(holder.GetKernelContext());
121+ ASSERT_NE(context, nullptr);
122+ EXPECT_NE(context->ReportAttachedStream({ge::AscendString("key_s")}), ge::GRAPH_SUCCESS);
123+}
124+ 
125+TEST(ResourceUsageContextUT, ExposesNodeInfoAndTensors) {
126+ ResourceUsageCollector collector;
127+ Tensor input_tensor = MakeTensor(reinterpret_cast<void *>(0x1000U));
128+ Tensor output_tensor = MakeTensor(reinterpret_cast<void *>(0x2000U));
129+ auto holder =
130+ KernelRunContextBuilder().Inputs({&input_tensor, &collector}).Outputs({&output_tensor}).Build(MakeOpDesc());
131+ auto *context = reinterpret_cast<ResourceUsageContext *>(holder.GetKernelContext());
132+ ASSERT_NE(context, nullptr);
133+ 
134+ // 节点信息全部继承自 ExtendedKernelContext
135+ ASSERT_NE(context->GetNodeName(), nullptr);
136+ EXPECT_STREQ(context->GetNodeName(), kOpName);
137+ ASSERT_NE(context->GetNodeType(), nullptr);
138+ EXPECT_STREQ(context->GetNodeType(), kOpType);
139+ 
140+ // tensor 薄封装:输入含附加槽位越界保护,输出按 ComputeNodeInfo 数量保护
141+ EXPECT_EQ(context->GetInputTensor(0U), &input_tensor);
142+ EXPECT_EQ(context->GetInputTensor(1U), nullptr);
143+ EXPECT_EQ(context->GetInputTensor(std::numeric_limits<size_t>::max()), nullptr);
144+ EXPECT_EQ(context->GetOutputTensor(0U), &output_tensor);
145+ EXPECT_EQ(context->GetOutputTensor(1U), nullptr);
146+ 
147+ // 编译期静态 shape / dtype 可读取(算子按 shape 分桶申报 key 的依据)
148+ const auto *tensor = context->GetInputTensor(0U);
149+ ASSERT_NE(tensor, nullptr);
150+ const auto &shape = tensor->GetShape().GetStorageShape();
151+ ASSERT_EQ(shape.GetDimNum(), 2U);
152+ EXPECT_EQ(shape.GetDim(0U), 2);
153+ EXPECT_EQ(shape.GetDim(1U), 8);
154+ EXPECT_EQ(tensor->GetDataType(), ge::DT_FLOAT);
155+}
156+ 
157+// IR 原型索引系列:按 ir_index(+ relative_index)定位实例,未实例化/越界均返回 nullptr
158+TEST(ResourceUsageContextUT, ExposesIrIndexedTensors) {
159+ ResourceUsageCollector collector;
160+ Tensor input_x = MakeTensor(reinterpret_cast<void *>(0x1000U));
161+ Tensor input_dyn0 = MakeTensor(reinterpret_cast<void *>(0x1100U));
162+ Tensor input_dyn1 = MakeTensor(reinterpret_cast<void *>(0x1200U));
163+ Tensor output_y = MakeTensor(reinterpret_cast<void *>(0x2000U));
164+ Tensor output_s0 = MakeTensor(reinterpret_cast<void *>(0x2100U));
165+ Tensor output_s1 = MakeTensor(reinterpret_cast<void *>(0x2200U));
166+ auto holder = KernelRunContextBuilder()
167+ .Inputs({&input_x, &input_dyn0, &input_dyn1, &collector})
168+ .Outputs({&output_y, &output_s0, &output_s1})
169+ .Build(MakeIrOpDesc());
170+ auto *context = reinterpret_cast<ResourceUsageContext *>(holder.GetKernelContext());
171+ ASSERT_NE(context, nullptr);
172+ 
173+ // 输入侧:必需输入 1 实例,动态输入 2 实例
174+ EXPECT_EQ(context->GetRequiredInputTensor(0U), &input_x);
175+ EXPECT_EQ(context->GetDynamicInputTensor(1U, 0U), &input_dyn0);
176+ EXPECT_EQ(context->GetDynamicInputTensor(1U, 1U), &input_dyn1);
177+ // relative_index 越界、可选输入未实例化、ir_index 越界均为 nullptr
178+ EXPECT_EQ(context->GetDynamicInputTensor(1U, 2U), nullptr);
179+ EXPECT_EQ(context->GetOptionalInputTensor(2U), nullptr);
180+ EXPECT_EQ(context->GetRequiredInputTensor(3U), nullptr);
181+ 
182+ // 输出侧:必需输出 1 实例,动态输出 2 实例
183+ EXPECT_EQ(context->GetRequiredOutputTensor(0U), &output_y);
184+ EXPECT_EQ(context->GetDynamicOutputTensor(1U, 0U), &output_s0);
185+ EXPECT_EQ(context->GetDynamicOutputTensor(1U, 1U), &output_s1);
186+ EXPECT_EQ(context->GetDynamicOutputTensor(1U, 2U), nullptr);
187+ EXPECT_EQ(context->GetRequiredOutputTensor(2U), nullptr);
188+ 
189+ // 扁平索引口径不受影响,附加槽仍被隔离;申报链路正常
190+ EXPECT_EQ(context->GetInputTensor(0U), &input_x);
191+ EXPECT_EQ(context->GetInputTensor(3U), nullptr);
192+ EXPECT_EQ(context->GetOutputTensor(0U), &output_y);
193+ EXPECT_EQ(context->ReportAttachedStream({ge::AscendString("key_ir")}), ge::GRAPH_SUCCESS);
194+ EXPECT_FALSE(collector.HasError());
195+}
196+ 
197+// OpDesc 不带 IR 原型信息时,IR 索引系列安全退化为 nullptr(不影响扁平索引与申报)
198+TEST(ResourceUsageContextUT, IrIndexedTensorsReturnNullWithoutIrPrototype) {
199+ ResourceUsageCollector collector;
200+ Tensor input_tensor = MakeTensor(reinterpret_cast<void *>(0x1000U));
201+ Tensor output_tensor = MakeTensor(reinterpret_cast<void *>(0x2000U));
202+ auto holder =
203+ KernelRunContextBuilder().Inputs({&input_tensor, &collector}).Outputs({&output_tensor}).Build(MakeOpDesc());
204+ auto *context = reinterpret_cast<ResourceUsageContext *>(holder.GetKernelContext());
205+ ASSERT_NE(context, nullptr);
206+ 
207+ EXPECT_EQ(context->GetRequiredInputTensor(0U), nullptr);
208+ EXPECT_EQ(context->GetDynamicInputTensor(0U, 0U), nullptr);
209+ EXPECT_EQ(context->GetRequiredOutputTensor(0U), nullptr);
210+ EXPECT_EQ(context->GetDynamicOutputTensor(0U, 0U), nullptr);
211+ EXPECT_EQ(context->GetInputTensor(0U), &input_tensor);
212+ EXPECT_EQ(context->ReportAttachedStream({ge::AscendString("key_no_ir")}), ge::GRAPH_SUCCESS);
213+}
214+ 
215+// 附加输入槽位布局守护:只允许在 kNum 前追加,禁止插入、重排或修改已有值。
216+// 生产侧 model_builder.cc 与消费侧 resource_usage_context.cc 共用该内部枚举定位收集器槽位
217+TEST(ResourceUsageContextUT, AdditionalInputLayoutIsAppendOnly) {
218+ EXPECT_EQ(static_cast<uint32_t>(ResourceUsageInput::kCollector), 0U);
219+ EXPECT_EQ(static_cast<uint32_t>(ResourceUsageInput::kNum), 1U);
220+}
221+} // namespace gert